Identifier,
ExportSpecifier,
Statement,
- CallExpression,
- TSEnumDeclaration
+ CallExpression
} from '@babel/types'
import { walk } from 'estree-walker'
import { RawSourceMap } from 'source-map-js'
import { processDefineSlots } from './script/defineSlots'
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
import { isLiteralNode, unwrapTSNode, isCallOf } from './script/utils'
-import { inferRuntimeType } from './script/resolveType'
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
import { isImportUsed } from './script/importUsageCheck'
import { processAwait } from './script/topLevelAwait'
// metadata that needs to be returned
// const ctx.bindingMetadata: BindingMetadata = {}
- const userImports: Record<string, ImportBinding> = Object.create(null)
const scriptBindings: Record<string, BindingTypes> = Object.create(null)
const setupBindings: Record<string, BindingTypes> = Object.create(null)
isUsedInTemplate = isImportUsed(local, sfc)
}
- userImports[local] = {
+ ctx.userImports[local] = {
isType,
imported,
local,
const local = specifier.local.name
const imported = getImportedName(specifier)
const source = node.source.value
- const existing = userImports[local]
+ const existing = ctx.userImports[local]
if (
source === 'vue' &&
(imported === DEFINE_PROPS ||
// 1.3 resolve possible user import alias of `ref` and `reactive`
const vueImportAliases: Record<string, string> = {}
- for (const key in userImports) {
- const { source, imported, local } = userImports[key]
+ for (const key in ctx.userImports) {
+ const { source, imported, local } = ctx.userImports[key]
if (source === 'vue') vueImportAliases[imported] = local
}
node.exportKind === 'type') ||
(node.type === 'VariableDeclaration' && node.declare)
) {
- recordType(node, ctx.declaredTypes)
if (node.type !== 'TSEnumDeclaration') {
hoistNode(node)
}
Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
}
for (const [key, { isType, imported, source }] of Object.entries(
- userImports
+ ctx.userImports
)) {
if (isType) continue
ctx.bindingMetadata[key] =
...scriptBindings,
...setupBindings
}
- for (const key in userImports) {
- if (!userImports[key].isType && userImports[key].isUsedInTemplate) {
+ for (const key in ctx.userImports) {
+ if (
+ !ctx.userImports[key].isType &&
+ ctx.userImports[key].isUsedInTemplate
+ ) {
allBindings[key] = true
}
}
for (const key in allBindings) {
if (
allBindings[key] === true &&
- userImports[key].source !== 'vue' &&
- !userImports[key].source.endsWith('.vue')
+ ctx.userImports[key].source !== 'vue' &&
+ !ctx.userImports[key].source.endsWith('.vue')
) {
// generate getter for import bindings
// skip vue imports since we know they will never change
return {
...scriptSetup,
bindings: ctx.bindingMetadata,
- imports: userImports,
+ imports: ctx.userImports,
content: ctx.s.toString(),
map:
options.sourceMap !== false
}
}
-function recordType(node: Node, declaredTypes: Record<string, string[]>) {
- if (node.type === 'TSInterfaceDeclaration') {
- declaredTypes[node.id.name] = [`Object`]
- } else if (node.type === 'TSTypeAliasDeclaration') {
- declaredTypes[node.id.name] = inferRuntimeType(
- node.typeAnnotation,
- declaredTypes
- )
- } else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
- recordType(node.declaration, declaredTypes)
- } else if (node.type === 'TSEnumDeclaration') {
- declaredTypes[node.id.name] = inferEnumType(node)
- }
-}
-
-function inferEnumType(node: TSEnumDeclaration): string[] {
- const types = new Set<string>()
- for (const m of node.members) {
- if (m.initializer) {
- switch (m.initializer.type) {
- case 'StringLiteral':
- types.add('String')
- break
- case 'NumericLiteral':
- types.add('Number')
- break
- }
- }
- }
- return types.size ? [...types] : ['Number']
-}
-
function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
if (isCallOf(node, userReactiveImport)) {
return true
import { SFCDescriptor } from '../parse'
import { generateCodeFrame } from '@vue/shared'
import { parse as babelParse, ParserOptions, ParserPlugin } from '@babel/parser'
-import { SFCScriptCompileOptions } from '../compileScript'
-import { PropsDeclType, PropsDestructureBindings } from './defineProps'
+import { ImportBinding, SFCScriptCompileOptions } from '../compileScript'
+import { PropsDestructureBindings } from './defineProps'
import { ModelDecl } from './defineModel'
import { BindingMetadata } from '../../../compiler-core/src'
import MagicString from 'magic-string'
-import { EmitsDeclType } from './defineEmits'
+import { TypeScope } from './resolveType'
export class ScriptCompileContext {
isJS: boolean
startOffset = this.descriptor.scriptSetup?.loc.start.offset
endOffset = this.descriptor.scriptSetup?.loc.end.offset
- declaredTypes: Record<string, string[]> = Object.create(null)
+ // import / type analysis
+ scope: TypeScope | undefined
+ userImports: Record<string, ImportBinding> = Object.create(null)
// macros presence check
hasDefinePropsCall = false
// defineProps
propsIdentifier: string | undefined
propsRuntimeDecl: Node | undefined
- propsTypeDecl: PropsDeclType | undefined
+ propsTypeDecl: Node | undefined
propsDestructureDecl: ObjectPattern | undefined
propsDestructuredBindings: PropsDestructureBindings = Object.create(null)
propsDestructureRestId: string | undefined
// defineEmits
emitsRuntimeDecl: Node | undefined
- emitsTypeDecl: EmitsDeclType | undefined
+ emitsTypeDecl: Node | undefined
emitIdentifier: string | undefined
// defineModel
-import {
- Identifier,
- LVal,
- Node,
- RestElement,
- TSFunctionType,
- TSInterfaceBody,
- TSTypeLiteral
-} from '@babel/types'
-import { FromNormalScript, isCallOf } from './utils'
+import { Identifier, LVal, Node, RestElement } from '@babel/types'
+import { isCallOf } from './utils'
import { ScriptCompileContext } from './context'
-import { resolveQualifiedType } from './resolveType'
+import { resolveTypeElements } from './resolveType'
export const DEFINE_EMITS = 'defineEmits'
-export type EmitsDeclType = FromNormalScript<
- TSFunctionType | TSTypeLiteral | TSInterfaceBody
->
-
export function processDefineEmits(
ctx: ScriptCompileContext,
node: Node,
node
)
}
-
- const emitsTypeDeclRaw = node.typeParameters.params[0]
- ctx.emitsTypeDecl = resolveQualifiedType(
- ctx,
- emitsTypeDeclRaw,
- node => node.type === 'TSFunctionType' || node.type === 'TSTypeLiteral'
- ) as EmitsDeclType | undefined
-
- if (!ctx.emitsTypeDecl) {
- ctx.error(
- `type argument passed to ${DEFINE_EMITS}() must be a function type, ` +
- `a literal type with call signatures, or a reference to the above types.`,
- emitsTypeDeclRaw
- )
- }
+ ctx.emitsTypeDecl = node.typeParameters.params[0]
}
if (declId) {
function extractRuntimeEmits(ctx: ScriptCompileContext): Set<string> {
const emits = new Set<string>()
const node = ctx.emitsTypeDecl!
- if (node.type === 'TSTypeLiteral' || node.type === 'TSInterfaceBody') {
- const members = node.type === 'TSTypeLiteral' ? node.members : node.body
- let hasCallSignature = false
- let hasProperty = false
- for (let t of members) {
- if (t.type === 'TSCallSignatureDeclaration') {
- extractEventNames(t.parameters[0], emits)
- hasCallSignature = true
- }
- if (t.type === 'TSPropertySignature') {
- if (t.key.type === 'Identifier' && !t.computed) {
- emits.add(t.key.name)
- hasProperty = true
- } else if (t.key.type === 'StringLiteral' && !t.computed) {
- emits.add(t.key.value)
- hasProperty = true
- } else {
- ctx.error(`defineEmits() type cannot use computed keys.`, t.key)
- }
- }
- }
- if (hasCallSignature && hasProperty) {
+
+ if (node.type === 'TSFunctionType') {
+ extractEventNames(node.parameters[0], emits)
+ return emits
+ }
+
+ const elements = resolveTypeElements(ctx, node)
+
+ let hasProperty = false
+ for (const key in elements) {
+ emits.add(key)
+ hasProperty = true
+ }
+
+ if (elements.__callSignatures) {
+ if (hasProperty) {
ctx.error(
`defineEmits() type cannot mixed call signature and property syntax.`,
node
)
}
- } else {
- extractEventNames(node.parameters[0], emits)
+ for (const call of elements.__callSignatures) {
+ extractEventNames(call.parameters[0], emits)
+ }
}
+
return emits
}
for (const [name, { type, options }] of Object.entries(ctx.modelDecls)) {
let skipCheck = false
- let runtimeTypes = type && inferRuntimeType(type, ctx.declaredTypes)
+ let runtimeTypes = type && inferRuntimeType(ctx, type)
if (runtimeTypes) {
const hasUnknownType = runtimeTypes.includes(UNKNOWN_TYPE)
import {
Node,
LVal,
- TSTypeLiteral,
- TSInterfaceBody,
ObjectProperty,
ObjectMethod,
ObjectExpression,
} from '@babel/types'
import { BindingTypes, isFunctionType } from '@vue/compiler-dom'
import { ScriptCompileContext } from './context'
-import { inferRuntimeType, resolveQualifiedType } from './resolveType'
+import { inferRuntimeType, resolveTypeElements } from './resolveType'
import {
- FromNormalScript,
resolveObjectKey,
UNKNOWN_TYPE,
concatStrings,
export const DEFINE_PROPS = 'defineProps'
export const WITH_DEFAULTS = 'withDefaults'
-export type PropsDeclType = FromNormalScript<TSTypeLiteral | TSInterfaceBody>
-
export interface PropTypeData {
key: string
type: string[]
node
)
}
-
- const rawDecl = node.typeParameters.params[0]
- ctx.propsTypeDecl = resolveQualifiedType(
- ctx,
- rawDecl,
- node => node.type === 'TSTypeLiteral'
- ) as PropsDeclType | undefined
- if (!ctx.propsTypeDecl) {
- ctx.error(
- `type argument passed to ${DEFINE_PROPS}() must be a literal type, ` +
- `or a reference to an interface or literal type.`,
- rawDecl
- )
- }
+ ctx.propsTypeDecl = node.typeParameters.params[0]
}
if (declId) {
}
function genRuntimePropsFromTypes(ctx: ScriptCompileContext) {
- const propStrings: string[] = []
- const hasStaticDefaults = hasStaticWithDefaults(ctx)
-
// this is only called if propsTypeDecl exists
- const node = ctx.propsTypeDecl!
- const members = node.type === 'TSTypeLiteral' ? node.members : node.body
- for (const m of members) {
- if (
- (m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&
- m.key.type === 'Identifier'
- ) {
- const key = m.key.name
- let type: string[] | undefined
- let skipCheck = false
- if (m.type === 'TSMethodSignature') {
- type = ['Function']
- } else if (m.typeAnnotation) {
- type = inferRuntimeType(
- m.typeAnnotation.typeAnnotation,
- ctx.declaredTypes
- )
- // skip check for result containing unknown types
- if (type.includes(UNKNOWN_TYPE)) {
- if (type.includes('Boolean') || type.includes('Function')) {
- type = type.filter(t => t !== UNKNOWN_TYPE)
- skipCheck = true
- } else {
- type = ['null']
- }
- }
- }
-
- propStrings.push(
- genRuntimePropFromType(
- ctx,
- key,
- !m.optional,
- type || [`null`],
- skipCheck,
- hasStaticDefaults
- )
- )
-
- // register bindings
- ctx.bindingMetadata[key] = BindingTypes.PROPS
- }
+ const props = resolveRuntimePropsFromType(ctx, ctx.propsTypeDecl!)
+ if (!props.length) {
+ return
}
- if (!propStrings.length) {
- return
+ const propStrings: string[] = []
+ const hasStaticDefaults = hasStaticWithDefaults(ctx)
+
+ for (const prop of props) {
+ propStrings.push(genRuntimePropFromType(ctx, prop, hasStaticDefaults))
+ // register bindings
+ ctx.bindingMetadata[prop.key] = BindingTypes.PROPS
}
let propsDecls = `{
return propsDecls
}
+function resolveRuntimePropsFromType(
+ ctx: ScriptCompileContext,
+ node: Node
+): PropTypeData[] {
+ const props: PropTypeData[] = []
+ const elements = resolveTypeElements(ctx, node)
+ for (const key in elements) {
+ const e = elements[key]
+ let type: string[] | undefined
+ let skipCheck = false
+ if (e.type === 'TSMethodSignature') {
+ type = ['Function']
+ } else if (e.typeAnnotation) {
+ type = inferRuntimeType(ctx, e.typeAnnotation.typeAnnotation)
+ // skip check for result containing unknown types
+ if (type.includes(UNKNOWN_TYPE)) {
+ if (type.includes('Boolean') || type.includes('Function')) {
+ type = type.filter(t => t !== UNKNOWN_TYPE)
+ skipCheck = true
+ } else {
+ type = ['null']
+ }
+ }
+ }
+ props.push({
+ key,
+ required: !e.optional,
+ type: type || [`null`],
+ skipCheck
+ })
+ }
+ return props
+}
+
function genRuntimePropFromType(
ctx: ScriptCompileContext,
- key: string,
- required: boolean,
- type: string[],
- skipCheck: boolean,
+ { key, required, type, skipCheck }: PropTypeData,
hasStaticDefaults: boolean
): string {
let defaultString: string | undefined
import {
Node,
Statement,
- TSInterfaceBody,
+ TSCallSignatureDeclaration,
+ TSEnumDeclaration,
+ TSExpressionWithTypeArguments,
+ TSFunctionType,
+ TSMethodSignature,
+ TSPropertySignature,
TSType,
- TSTypeElement
+ TSTypeAnnotation,
+ TSTypeElement,
+ TSTypeReference
} from '@babel/types'
-import { FromNormalScript, UNKNOWN_TYPE } from './utils'
+import { UNKNOWN_TYPE } from './utils'
import { ScriptCompileContext } from './context'
+import { ImportBinding } from '../compileScript'
+import { TSInterfaceDeclaration } from '@babel/types'
+import { hasOwn } from '@vue/shared'
-export function resolveQualifiedType(
+export interface TypeScope {
+ filename: string
+ body: Statement[]
+ imports: Record<string, ImportBinding>
+ types: Record<string, Node>
+}
+
+type ResolvedElements = Record<
+ string,
+ TSPropertySignature | TSMethodSignature
+> & {
+ __callSignatures?: (TSCallSignatureDeclaration | TSFunctionType)[]
+}
+
+/**
+ * Resolve arbitrary type node to a list of type elements that can be then
+ * mapped to runtime props or emits.
+ */
+export function resolveTypeElements(
ctx: ScriptCompileContext,
- node: Node,
- qualifier: (node: Node) => boolean
-): Node | undefined {
- if (qualifier(node)) {
- return node
+ node: Node & { _resolvedElements?: ResolvedElements }
+): ResolvedElements {
+ if (node._resolvedElements) {
+ return node._resolvedElements
+ }
+ return (node._resolvedElements = innerResolveTypeElements(ctx, node))
+}
+
+function innerResolveTypeElements(
+ ctx: ScriptCompileContext,
+ node: Node
+): ResolvedElements {
+ switch (node.type) {
+ case 'TSTypeLiteral':
+ return typeElementsToMap(ctx, node.members)
+ case 'TSInterfaceDeclaration':
+ return resolveInterfaceMembers(ctx, node)
+ case 'TSTypeAliasDeclaration':
+ case 'TSParenthesizedType':
+ return resolveTypeElements(ctx, node.typeAnnotation)
+ case 'TSFunctionType': {
+ const ret: ResolvedElements = {}
+ addCallSignature(ret, node)
+ return ret
+ }
+ case 'TSExpressionWithTypeArguments':
+ case 'TSTypeReference':
+ return resolveTypeElements(ctx, resolveTypeReference(ctx, node))
+ }
+ ctx.error(`Unsupported type in SFC macro: ${node.type}`, node)
+}
+
+function addCallSignature(
+ elements: ResolvedElements,
+ node: TSCallSignatureDeclaration | TSFunctionType
+) {
+ if (!elements.__callSignatures) {
+ Object.defineProperty(elements, '__callSignatures', {
+ enumerable: false,
+ value: [node]
+ })
+ } else {
+ elements.__callSignatures.push(node)
}
- if (node.type === 'TSTypeReference' && node.typeName.type === 'Identifier') {
- const refName = node.typeName.name
- const { scriptAst, scriptSetupAst } = ctx
- const body = scriptAst
- ? [...scriptSetupAst!.body, ...scriptAst.body]
- : scriptSetupAst!.body
- for (let i = 0; i < body.length; i++) {
- const node = body[i]
- let qualified = isQualifiedType(
- node,
- qualifier,
- refName
- ) as TSInterfaceBody
- if (qualified) {
- const extendsTypes = resolveExtendsType(body, node, qualifier)
- if (extendsTypes.length) {
- const bodies: TSTypeElement[] = [...qualified.body]
- filterExtendsType(extendsTypes, bodies)
- qualified.body = bodies
+}
+
+function typeElementsToMap(
+ ctx: ScriptCompileContext,
+ elements: TSTypeElement[]
+): ResolvedElements {
+ const ret: ResolvedElements = {}
+ for (const e of elements) {
+ if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') {
+ const name =
+ e.key.type === 'Identifier'
+ ? e.key.name
+ : e.key.type === 'StringLiteral'
+ ? e.key.value
+ : null
+ if (name && !e.computed) {
+ ret[name] = e
+ } else {
+ ctx.error(
+ `computed keys are not supported in types referenced by SFC macros.`,
+ e
+ )
+ }
+ } else if (e.type === 'TSCallSignatureDeclaration') {
+ addCallSignature(ret, e)
+ }
+ }
+ return ret
+}
+
+function resolveInterfaceMembers(
+ ctx: ScriptCompileContext,
+ node: TSInterfaceDeclaration
+): ResolvedElements {
+ const base = typeElementsToMap(ctx, node.body.body)
+ if (node.extends) {
+ for (const ext of node.extends) {
+ const resolvedExt = resolveTypeElements(ctx, ext)
+ for (const key in resolvedExt) {
+ if (!hasOwn(base, key)) {
+ base[key] = resolvedExt[key]
}
- ;(qualified as FromNormalScript<Node>).__fromNormalScript =
- scriptAst && i >= scriptSetupAst!.body.length
- return qualified
}
}
}
+ return base
}
-function isQualifiedType(
- node: Node,
- qualifier: (node: Node) => boolean,
- refName: String
+function resolveTypeReference(
+ ctx: ScriptCompileContext,
+ node: TSTypeReference | TSExpressionWithTypeArguments,
+ scope?: TypeScope
+): Node
+function resolveTypeReference(
+ ctx: ScriptCompileContext,
+ node: TSTypeReference | TSExpressionWithTypeArguments,
+ scope: TypeScope,
+ bail: false
+): Node | undefined
+function resolveTypeReference(
+ ctx: ScriptCompileContext,
+ node: TSTypeReference | TSExpressionWithTypeArguments,
+ scope = getRootScope(ctx),
+ bail = true
): Node | undefined {
- if (node.type === 'TSInterfaceDeclaration' && node.id.name === refName) {
- return node.body
- } else if (
- node.type === 'TSTypeAliasDeclaration' &&
- node.id.name === refName &&
- qualifier(node.typeAnnotation)
- ) {
- return node.typeAnnotation
- } else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
- return isQualifiedType(node.declaration, qualifier, refName)
+ const ref = node.type === 'TSTypeReference' ? node.typeName : node.expression
+ if (ref.type === 'Identifier') {
+ if (scope.imports[ref.name]) {
+ // TODO external import
+ } else if (scope.types[ref.name]) {
+ return scope.types[ref.name]
+ }
+ } else {
+ // TODO qualified name, e.g. Foo.Bar
+ // return resolveTypeReference()
+ }
+ if (bail) {
+ ctx.error('Failed to resolve type reference.', node)
}
}
-function resolveExtendsType(
- body: Statement[],
- node: Node,
- qualifier: (node: Node) => boolean,
- cache: Array<Node> = []
-): Array<Node> {
- if (node.type === 'TSInterfaceDeclaration' && node.extends) {
- node.extends.forEach(extend => {
- if (
- extend.type === 'TSExpressionWithTypeArguments' &&
- extend.expression.type === 'Identifier'
- ) {
- for (const node of body) {
- const qualified = isQualifiedType(
- node,
- qualifier,
- extend.expression.name
- )
- if (qualified) {
- cache.push(qualified)
- resolveExtendsType(body, node, qualifier, cache)
- return cache
- }
- }
- }
- })
+function getRootScope(ctx: ScriptCompileContext): TypeScope {
+ if (ctx.scope) {
+ return ctx.scope
}
- return cache
+
+ const body = ctx.scriptAst
+ ? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body]
+ : ctx.scriptSetupAst!.body
+
+ return (ctx.scope = {
+ filename: ctx.descriptor.filename,
+ imports: ctx.userImports,
+ types: recordTypes(body),
+ body
+ })
}
-// filter all extends types to keep the override declaration
-function filterExtendsType(extendsTypes: Node[], bodies: TSTypeElement[]) {
- extendsTypes.forEach(extend => {
- const body = (extend as TSInterfaceBody).body
- body.forEach(newBody => {
- if (
- newBody.type === 'TSPropertySignature' &&
- newBody.key.type === 'Identifier'
- ) {
- const name = newBody.key.name
- const hasOverride = bodies.some(
- seenBody =>
- seenBody.type === 'TSPropertySignature' &&
- seenBody.key.type === 'Identifier' &&
- seenBody.key.name === name
- )
- if (!hasOverride) bodies.push(newBody)
+function recordTypes(body: Statement[]) {
+ const types: Record<string, Node> = Object.create(null)
+ for (const s of body) {
+ recordType(s, types)
+ }
+ return types
+}
+
+function recordType(node: Node, types: Record<string, Node>) {
+ switch (node.type) {
+ case 'TSInterfaceDeclaration':
+ case 'TSEnumDeclaration':
+ types[node.id.name] = node
+ break
+ case 'TSTypeAliasDeclaration':
+ types[node.id.name] = node.typeAnnotation
+ break
+ case 'ExportNamedDeclaration': {
+ if (node.exportKind === 'type') {
+ recordType(node.declaration!, types)
}
- })
- })
+ break
+ }
+ case 'VariableDeclaration': {
+ if (node.declare) {
+ for (const decl of node.declarations) {
+ if (decl.id.type === 'Identifier' && decl.id.typeAnnotation) {
+ types[decl.id.name] = (
+ decl.id.typeAnnotation as TSTypeAnnotation
+ ).typeAnnotation
+ }
+ }
+ }
+ break
+ }
+ }
}
export function inferRuntimeType(
- node: TSType,
- declaredTypes: Record<string, string[]>
+ ctx: ScriptCompileContext,
+ node: Node,
+ scope = getRootScope(ctx)
): string[] {
switch (node.type) {
case 'TSStringKeyword':
return ['Object']
case 'TSNullKeyword':
return ['null']
- case 'TSTypeLiteral': {
+ case 'TSTypeLiteral':
+ case 'TSInterfaceDeclaration': {
// TODO (nice to have) generate runtime property validation
const types = new Set<string>()
- for (const m of node.members) {
+ const members =
+ node.type === 'TSTypeLiteral' ? node.members : node.body.body
+ for (const m of members) {
if (
m.type === 'TSCallSignatureDeclaration' ||
m.type === 'TSConstructSignatureDeclaration'
case 'TSTypeReference':
if (node.typeName.type === 'Identifier') {
- if (declaredTypes[node.typeName.name]) {
- return declaredTypes[node.typeName.name]
+ const resolved = resolveTypeReference(ctx, node, scope, false)
+ if (resolved) {
+ return inferRuntimeType(ctx, resolved, scope)
}
switch (node.typeName.name) {
case 'Array':
case 'NonNullable':
if (node.typeParameters && node.typeParameters.params[0]) {
return inferRuntimeType(
+ ctx,
node.typeParameters.params[0],
- declaredTypes
+ scope
).filter(t => t !== 'null')
}
break
case 'Extract':
if (node.typeParameters && node.typeParameters.params[1]) {
- return inferRuntimeType(
- node.typeParameters.params[1],
- declaredTypes
- )
+ return inferRuntimeType(ctx, node.typeParameters.params[1], scope)
}
break
case 'Exclude':
case 'OmitThisParameter':
if (node.typeParameters && node.typeParameters.params[0]) {
- return inferRuntimeType(
- node.typeParameters.params[0],
- declaredTypes
- )
+ return inferRuntimeType(ctx, node.typeParameters.params[0], scope)
}
break
}
return [UNKNOWN_TYPE]
case 'TSParenthesizedType':
- return inferRuntimeType(node.typeAnnotation, declaredTypes)
+ return inferRuntimeType(ctx, node.typeAnnotation, scope)
case 'TSUnionType':
- return flattenTypes(node.types, declaredTypes)
+ return flattenTypes(ctx, node.types, scope)
case 'TSIntersectionType': {
- return flattenTypes(node.types, declaredTypes).filter(
+ return flattenTypes(ctx, node.types, scope).filter(
t => t !== UNKNOWN_TYPE
)
}
+ case 'TSEnumDeclaration':
+ return inferEnumType(node)
+
case 'TSSymbolKeyword':
return ['Symbol']
}
function flattenTypes(
+ ctx: ScriptCompileContext,
types: TSType[],
- declaredTypes: Record<string, string[]>
+ scope: TypeScope
): string[] {
return [
...new Set(
([] as string[]).concat(
- ...types.map(t => inferRuntimeType(t, declaredTypes))
+ ...types.map(t => inferRuntimeType(ctx, t, scope))
)
)
]
}
+
+function inferEnumType(node: TSEnumDeclaration): string[] {
+ const types = new Set<string>()
+ for (const m of node.members) {
+ if (m.initializer) {
+ switch (m.initializer.type) {
+ case 'StringLiteral':
+ types.add('String')
+ break
+ case 'NumericLiteral':
+ types.add('Number')
+ break
+ }
+ }
+ }
+ return types.size ? [...types] : ['Number']
+}
export const UNKNOWN_TYPE = 'Unknown'
-export type FromNormalScript<T> = T & { __fromNormalScript?: boolean | null }
-
export function resolveObjectKey(node: Node, computed: boolean) {
switch (node.type) {
case 'StringLiteral':