mirror of https://github.com/vuejs/core.git
refactor(compiler-sfc): rework macro type resolution
This commit is contained in:
parent
ae5a9323b7
commit
b2cdb2645f
|
@ -16,8 +16,7 @@ import {
|
||||||
Identifier,
|
Identifier,
|
||||||
ExportSpecifier,
|
ExportSpecifier,
|
||||||
Statement,
|
Statement,
|
||||||
CallExpression,
|
CallExpression
|
||||||
TSEnumDeclaration
|
|
||||||
} from '@babel/types'
|
} from '@babel/types'
|
||||||
import { walk } from 'estree-walker'
|
import { walk } from 'estree-walker'
|
||||||
import { RawSourceMap } from 'source-map-js'
|
import { RawSourceMap } from 'source-map-js'
|
||||||
|
@ -47,7 +46,6 @@ import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
|
||||||
import { processDefineSlots } from './script/defineSlots'
|
import { processDefineSlots } from './script/defineSlots'
|
||||||
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
|
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
|
||||||
import { isLiteralNode, unwrapTSNode, isCallOf } from './script/utils'
|
import { isLiteralNode, unwrapTSNode, isCallOf } from './script/utils'
|
||||||
import { inferRuntimeType } from './script/resolveType'
|
|
||||||
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
|
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
|
||||||
import { isImportUsed } from './script/importUsageCheck'
|
import { isImportUsed } from './script/importUsageCheck'
|
||||||
import { processAwait } from './script/topLevelAwait'
|
import { processAwait } from './script/topLevelAwait'
|
||||||
|
@ -169,7 +167,6 @@ export function compileScript(
|
||||||
|
|
||||||
// metadata that needs to be returned
|
// metadata that needs to be returned
|
||||||
// const ctx.bindingMetadata: BindingMetadata = {}
|
// const ctx.bindingMetadata: BindingMetadata = {}
|
||||||
const userImports: Record<string, ImportBinding> = Object.create(null)
|
|
||||||
const scriptBindings: Record<string, BindingTypes> = Object.create(null)
|
const scriptBindings: Record<string, BindingTypes> = Object.create(null)
|
||||||
const setupBindings: Record<string, BindingTypes> = Object.create(null)
|
const setupBindings: Record<string, BindingTypes> = Object.create(null)
|
||||||
|
|
||||||
|
@ -223,7 +220,7 @@ export function compileScript(
|
||||||
isUsedInTemplate = isImportUsed(local, sfc)
|
isUsedInTemplate = isImportUsed(local, sfc)
|
||||||
}
|
}
|
||||||
|
|
||||||
userImports[local] = {
|
ctx.userImports[local] = {
|
||||||
isType,
|
isType,
|
||||||
imported,
|
imported,
|
||||||
local,
|
local,
|
||||||
|
@ -303,7 +300,7 @@ export function compileScript(
|
||||||
const local = specifier.local.name
|
const local = specifier.local.name
|
||||||
const imported = getImportedName(specifier)
|
const imported = getImportedName(specifier)
|
||||||
const source = node.source.value
|
const source = node.source.value
|
||||||
const existing = userImports[local]
|
const existing = ctx.userImports[local]
|
||||||
if (
|
if (
|
||||||
source === 'vue' &&
|
source === 'vue' &&
|
||||||
(imported === DEFINE_PROPS ||
|
(imported === DEFINE_PROPS ||
|
||||||
|
@ -345,8 +342,8 @@ export function compileScript(
|
||||||
|
|
||||||
// 1.3 resolve possible user import alias of `ref` and `reactive`
|
// 1.3 resolve possible user import alias of `ref` and `reactive`
|
||||||
const vueImportAliases: Record<string, string> = {}
|
const vueImportAliases: Record<string, string> = {}
|
||||||
for (const key in userImports) {
|
for (const key in ctx.userImports) {
|
||||||
const { source, imported, local } = userImports[key]
|
const { source, imported, local } = ctx.userImports[key]
|
||||||
if (source === 'vue') vueImportAliases[imported] = local
|
if (source === 'vue') vueImportAliases[imported] = local
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -658,7 +655,6 @@ export function compileScript(
|
||||||
node.exportKind === 'type') ||
|
node.exportKind === 'type') ||
|
||||||
(node.type === 'VariableDeclaration' && node.declare)
|
(node.type === 'VariableDeclaration' && node.declare)
|
||||||
) {
|
) {
|
||||||
recordType(node, ctx.declaredTypes)
|
|
||||||
if (node.type !== 'TSEnumDeclaration') {
|
if (node.type !== 'TSEnumDeclaration') {
|
||||||
hoistNode(node)
|
hoistNode(node)
|
||||||
}
|
}
|
||||||
|
@ -723,7 +719,7 @@ export function compileScript(
|
||||||
Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
|
Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
|
||||||
}
|
}
|
||||||
for (const [key, { isType, imported, source }] of Object.entries(
|
for (const [key, { isType, imported, source }] of Object.entries(
|
||||||
userImports
|
ctx.userImports
|
||||||
)) {
|
)) {
|
||||||
if (isType) continue
|
if (isType) continue
|
||||||
ctx.bindingMetadata[key] =
|
ctx.bindingMetadata[key] =
|
||||||
|
@ -823,8 +819,11 @@ export function compileScript(
|
||||||
...scriptBindings,
|
...scriptBindings,
|
||||||
...setupBindings
|
...setupBindings
|
||||||
}
|
}
|
||||||
for (const key in userImports) {
|
for (const key in ctx.userImports) {
|
||||||
if (!userImports[key].isType && userImports[key].isUsedInTemplate) {
|
if (
|
||||||
|
!ctx.userImports[key].isType &&
|
||||||
|
ctx.userImports[key].isUsedInTemplate
|
||||||
|
) {
|
||||||
allBindings[key] = true
|
allBindings[key] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -832,8 +831,8 @@ export function compileScript(
|
||||||
for (const key in allBindings) {
|
for (const key in allBindings) {
|
||||||
if (
|
if (
|
||||||
allBindings[key] === true &&
|
allBindings[key] === true &&
|
||||||
userImports[key].source !== 'vue' &&
|
ctx.userImports[key].source !== 'vue' &&
|
||||||
!userImports[key].source.endsWith('.vue')
|
!ctx.userImports[key].source.endsWith('.vue')
|
||||||
) {
|
) {
|
||||||
// generate getter for import bindings
|
// generate getter for import bindings
|
||||||
// skip vue imports since we know they will never change
|
// skip vue imports since we know they will never change
|
||||||
|
@ -1012,7 +1011,7 @@ export function compileScript(
|
||||||
return {
|
return {
|
||||||
...scriptSetup,
|
...scriptSetup,
|
||||||
bindings: ctx.bindingMetadata,
|
bindings: ctx.bindingMetadata,
|
||||||
imports: userImports,
|
imports: ctx.userImports,
|
||||||
content: ctx.s.toString(),
|
content: ctx.s.toString(),
|
||||||
map:
|
map:
|
||||||
options.sourceMap !== false
|
options.sourceMap !== false
|
||||||
|
@ -1201,38 +1200,6 @@ function walkPattern(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
|
||||||
if (isCallOf(node, userReactiveImport)) {
|
if (isCallOf(node, userReactiveImport)) {
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -2,12 +2,12 @@ import { Node, ObjectPattern, Program } from '@babel/types'
|
||||||
import { SFCDescriptor } from '../parse'
|
import { SFCDescriptor } from '../parse'
|
||||||
import { generateCodeFrame } from '@vue/shared'
|
import { generateCodeFrame } from '@vue/shared'
|
||||||
import { parse as babelParse, ParserOptions, ParserPlugin } from '@babel/parser'
|
import { parse as babelParse, ParserOptions, ParserPlugin } from '@babel/parser'
|
||||||
import { SFCScriptCompileOptions } from '../compileScript'
|
import { ImportBinding, SFCScriptCompileOptions } from '../compileScript'
|
||||||
import { PropsDeclType, PropsDestructureBindings } from './defineProps'
|
import { PropsDestructureBindings } from './defineProps'
|
||||||
import { ModelDecl } from './defineModel'
|
import { ModelDecl } from './defineModel'
|
||||||
import { BindingMetadata } from '../../../compiler-core/src'
|
import { BindingMetadata } from '../../../compiler-core/src'
|
||||||
import MagicString from 'magic-string'
|
import MagicString from 'magic-string'
|
||||||
import { EmitsDeclType } from './defineEmits'
|
import { TypeScope } from './resolveType'
|
||||||
|
|
||||||
export class ScriptCompileContext {
|
export class ScriptCompileContext {
|
||||||
isJS: boolean
|
isJS: boolean
|
||||||
|
@ -20,7 +20,9 @@ export class ScriptCompileContext {
|
||||||
startOffset = this.descriptor.scriptSetup?.loc.start.offset
|
startOffset = this.descriptor.scriptSetup?.loc.start.offset
|
||||||
endOffset = this.descriptor.scriptSetup?.loc.end.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
|
// macros presence check
|
||||||
hasDefinePropsCall = false
|
hasDefinePropsCall = false
|
||||||
|
@ -35,7 +37,7 @@ export class ScriptCompileContext {
|
||||||
// defineProps
|
// defineProps
|
||||||
propsIdentifier: string | undefined
|
propsIdentifier: string | undefined
|
||||||
propsRuntimeDecl: Node | undefined
|
propsRuntimeDecl: Node | undefined
|
||||||
propsTypeDecl: PropsDeclType | undefined
|
propsTypeDecl: Node | undefined
|
||||||
propsDestructureDecl: ObjectPattern | undefined
|
propsDestructureDecl: ObjectPattern | undefined
|
||||||
propsDestructuredBindings: PropsDestructureBindings = Object.create(null)
|
propsDestructuredBindings: PropsDestructureBindings = Object.create(null)
|
||||||
propsDestructureRestId: string | undefined
|
propsDestructureRestId: string | undefined
|
||||||
|
@ -43,7 +45,7 @@ export class ScriptCompileContext {
|
||||||
|
|
||||||
// defineEmits
|
// defineEmits
|
||||||
emitsRuntimeDecl: Node | undefined
|
emitsRuntimeDecl: Node | undefined
|
||||||
emitsTypeDecl: EmitsDeclType | undefined
|
emitsTypeDecl: Node | undefined
|
||||||
emitIdentifier: string | undefined
|
emitIdentifier: string | undefined
|
||||||
|
|
||||||
// defineModel
|
// defineModel
|
||||||
|
|
|
@ -1,22 +1,10 @@
|
||||||
import {
|
import { Identifier, LVal, Node, RestElement } from '@babel/types'
|
||||||
Identifier,
|
import { isCallOf } from './utils'
|
||||||
LVal,
|
|
||||||
Node,
|
|
||||||
RestElement,
|
|
||||||
TSFunctionType,
|
|
||||||
TSInterfaceBody,
|
|
||||||
TSTypeLiteral
|
|
||||||
} from '@babel/types'
|
|
||||||
import { FromNormalScript, isCallOf } from './utils'
|
|
||||||
import { ScriptCompileContext } from './context'
|
import { ScriptCompileContext } from './context'
|
||||||
import { resolveQualifiedType } from './resolveType'
|
import { resolveTypeElements } from './resolveType'
|
||||||
|
|
||||||
export const DEFINE_EMITS = 'defineEmits'
|
export const DEFINE_EMITS = 'defineEmits'
|
||||||
|
|
||||||
export type EmitsDeclType = FromNormalScript<
|
|
||||||
TSFunctionType | TSTypeLiteral | TSInterfaceBody
|
|
||||||
>
|
|
||||||
|
|
||||||
export function processDefineEmits(
|
export function processDefineEmits(
|
||||||
ctx: ScriptCompileContext,
|
ctx: ScriptCompileContext,
|
||||||
node: Node,
|
node: Node,
|
||||||
|
@ -38,21 +26,7 @@ export function processDefineEmits(
|
||||||
node
|
node
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
ctx.emitsTypeDecl = node.typeParameters.params[0]
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (declId) {
|
if (declId) {
|
||||||
|
@ -89,36 +63,32 @@ export function genRuntimeEmits(ctx: ScriptCompileContext): string | undefined {
|
||||||
function extractRuntimeEmits(ctx: ScriptCompileContext): Set<string> {
|
function extractRuntimeEmits(ctx: ScriptCompileContext): Set<string> {
|
||||||
const emits = new Set<string>()
|
const emits = new Set<string>()
|
||||||
const node = ctx.emitsTypeDecl!
|
const node = ctx.emitsTypeDecl!
|
||||||
if (node.type === 'TSTypeLiteral' || node.type === 'TSInterfaceBody') {
|
|
||||||
const members = node.type === 'TSTypeLiteral' ? node.members : node.body
|
if (node.type === 'TSFunctionType') {
|
||||||
let hasCallSignature = false
|
extractEventNames(node.parameters[0], emits)
|
||||||
|
return emits
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements = resolveTypeElements(ctx, node)
|
||||||
|
|
||||||
let hasProperty = false
|
let hasProperty = false
|
||||||
for (let t of members) {
|
for (const key in elements) {
|
||||||
if (t.type === 'TSCallSignatureDeclaration') {
|
emits.add(key)
|
||||||
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
|
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 (elements.__callSignatures) {
|
||||||
if (hasCallSignature && hasProperty) {
|
if (hasProperty) {
|
||||||
ctx.error(
|
ctx.error(
|
||||||
`defineEmits() type cannot mixed call signature and property syntax.`,
|
`defineEmits() type cannot mixed call signature and property syntax.`,
|
||||||
node
|
node
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
for (const call of elements.__callSignatures) {
|
||||||
extractEventNames(node.parameters[0], emits)
|
extractEventNames(call.parameters[0], emits)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return emits
|
return emits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ export function genModelProps(ctx: ScriptCompileContext) {
|
||||||
for (const [name, { type, options }] of Object.entries(ctx.modelDecls)) {
|
for (const [name, { type, options }] of Object.entries(ctx.modelDecls)) {
|
||||||
let skipCheck = false
|
let skipCheck = false
|
||||||
|
|
||||||
let runtimeTypes = type && inferRuntimeType(type, ctx.declaredTypes)
|
let runtimeTypes = type && inferRuntimeType(ctx, type)
|
||||||
if (runtimeTypes) {
|
if (runtimeTypes) {
|
||||||
const hasUnknownType = runtimeTypes.includes(UNKNOWN_TYPE)
|
const hasUnknownType = runtimeTypes.includes(UNKNOWN_TYPE)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import {
|
import {
|
||||||
Node,
|
Node,
|
||||||
LVal,
|
LVal,
|
||||||
TSTypeLiteral,
|
|
||||||
TSInterfaceBody,
|
|
||||||
ObjectProperty,
|
ObjectProperty,
|
||||||
ObjectMethod,
|
ObjectMethod,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
|
@ -10,9 +8,8 @@ import {
|
||||||
} from '@babel/types'
|
} from '@babel/types'
|
||||||
import { BindingTypes, isFunctionType } from '@vue/compiler-dom'
|
import { BindingTypes, isFunctionType } from '@vue/compiler-dom'
|
||||||
import { ScriptCompileContext } from './context'
|
import { ScriptCompileContext } from './context'
|
||||||
import { inferRuntimeType, resolveQualifiedType } from './resolveType'
|
import { inferRuntimeType, resolveTypeElements } from './resolveType'
|
||||||
import {
|
import {
|
||||||
FromNormalScript,
|
|
||||||
resolveObjectKey,
|
resolveObjectKey,
|
||||||
UNKNOWN_TYPE,
|
UNKNOWN_TYPE,
|
||||||
concatStrings,
|
concatStrings,
|
||||||
|
@ -28,8 +25,6 @@ import { processPropsDestructure } from './definePropsDestructure'
|
||||||
export const DEFINE_PROPS = 'defineProps'
|
export const DEFINE_PROPS = 'defineProps'
|
||||||
export const WITH_DEFAULTS = 'withDefaults'
|
export const WITH_DEFAULTS = 'withDefaults'
|
||||||
|
|
||||||
export type PropsDeclType = FromNormalScript<TSTypeLiteral | TSInterfaceBody>
|
|
||||||
|
|
||||||
export interface PropTypeData {
|
export interface PropTypeData {
|
||||||
key: string
|
key: string
|
||||||
type: string[]
|
type: string[]
|
||||||
|
@ -76,20 +71,7 @@ export function processDefineProps(
|
||||||
node
|
node
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
ctx.propsTypeDecl = node.typeParameters.params[0]
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (declId) {
|
if (declId) {
|
||||||
|
@ -176,56 +158,19 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
|
||||||
}
|
}
|
||||||
|
|
||||||
function genRuntimePropsFromTypes(ctx: ScriptCompileContext) {
|
function genRuntimePropsFromTypes(ctx: ScriptCompileContext) {
|
||||||
|
// this is only called if propsTypeDecl exists
|
||||||
|
const props = resolveRuntimePropsFromType(ctx, ctx.propsTypeDecl!)
|
||||||
|
if (!props.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const propStrings: string[] = []
|
const propStrings: string[] = []
|
||||||
const hasStaticDefaults = hasStaticWithDefaults(ctx)
|
const hasStaticDefaults = hasStaticWithDefaults(ctx)
|
||||||
|
|
||||||
// this is only called if propsTypeDecl exists
|
for (const prop of props) {
|
||||||
const node = ctx.propsTypeDecl!
|
propStrings.push(genRuntimePropFromType(ctx, prop, hasStaticDefaults))
|
||||||
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
|
// register bindings
|
||||||
ctx.bindingMetadata[key] = BindingTypes.PROPS
|
ctx.bindingMetadata[prop.key] = BindingTypes.PROPS
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!propStrings.length) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let propsDecls = `{
|
let propsDecls = `{
|
||||||
|
@ -240,12 +185,43 @@ function genRuntimePropsFromTypes(ctx: ScriptCompileContext) {
|
||||||
return 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(
|
function genRuntimePropFromType(
|
||||||
ctx: ScriptCompileContext,
|
ctx: ScriptCompileContext,
|
||||||
key: string,
|
{ key, required, type, skipCheck }: PropTypeData,
|
||||||
required: boolean,
|
|
||||||
type: string[],
|
|
||||||
skipCheck: boolean,
|
|
||||||
hasStaticDefaults: boolean
|
hasStaticDefaults: boolean
|
||||||
): string {
|
): string {
|
||||||
let defaultString: string | undefined
|
let defaultString: string | undefined
|
||||||
|
|
|
@ -1,122 +1,227 @@
|
||||||
import {
|
import {
|
||||||
Node,
|
Node,
|
||||||
Statement,
|
Statement,
|
||||||
TSInterfaceBody,
|
TSCallSignatureDeclaration,
|
||||||
|
TSEnumDeclaration,
|
||||||
|
TSExpressionWithTypeArguments,
|
||||||
|
TSFunctionType,
|
||||||
|
TSMethodSignature,
|
||||||
|
TSPropertySignature,
|
||||||
TSType,
|
TSType,
|
||||||
TSTypeElement
|
TSTypeAnnotation,
|
||||||
|
TSTypeElement,
|
||||||
|
TSTypeReference
|
||||||
} from '@babel/types'
|
} from '@babel/types'
|
||||||
import { FromNormalScript, UNKNOWN_TYPE } from './utils'
|
import { UNKNOWN_TYPE } from './utils'
|
||||||
import { ScriptCompileContext } from './context'
|
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,
|
ctx: ScriptCompileContext,
|
||||||
node: Node,
|
node: Node & { _resolvedElements?: ResolvedElements }
|
||||||
qualifier: (node: Node) => boolean
|
): ResolvedElements {
|
||||||
): Node | undefined {
|
if (node._resolvedElements) {
|
||||||
if (qualifier(node)) {
|
return node._resolvedElements
|
||||||
return node
|
|
||||||
}
|
}
|
||||||
if (node.type === 'TSTypeReference' && node.typeName.type === 'Identifier') {
|
return (node._resolvedElements = innerResolveTypeElements(ctx, node))
|
||||||
const refName = node.typeName.name
|
}
|
||||||
const { scriptAst, scriptSetupAst } = ctx
|
|
||||||
const body = scriptAst
|
function innerResolveTypeElements(
|
||||||
? [...scriptSetupAst!.body, ...scriptAst.body]
|
ctx: ScriptCompileContext,
|
||||||
: scriptSetupAst!.body
|
node: Node
|
||||||
for (let i = 0; i < body.length; i++) {
|
): ResolvedElements {
|
||||||
const node = body[i]
|
switch (node.type) {
|
||||||
let qualified = isQualifiedType(
|
case 'TSTypeLiteral':
|
||||||
node,
|
return typeElementsToMap(ctx, node.members)
|
||||||
qualifier,
|
case 'TSInterfaceDeclaration':
|
||||||
refName
|
return resolveInterfaceMembers(ctx, node)
|
||||||
) as TSInterfaceBody
|
case 'TSTypeAliasDeclaration':
|
||||||
if (qualified) {
|
case 'TSParenthesizedType':
|
||||||
const extendsTypes = resolveExtendsType(body, node, qualifier)
|
return resolveTypeElements(ctx, node.typeAnnotation)
|
||||||
if (extendsTypes.length) {
|
case 'TSFunctionType': {
|
||||||
const bodies: TSTypeElement[] = [...qualified.body]
|
const ret: ResolvedElements = {}
|
||||||
filterExtendsType(extendsTypes, bodies)
|
addCallSignature(ret, node)
|
||||||
qualified.body = bodies
|
return ret
|
||||||
}
|
|
||||||
;(qualified as FromNormalScript<Node>).__fromNormalScript =
|
|
||||||
scriptAst && i >= scriptSetupAst!.body.length
|
|
||||||
return qualified
|
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isQualifiedType(
|
function typeElementsToMap(
|
||||||
node: Node,
|
ctx: ScriptCompileContext,
|
||||||
qualifier: (node: Node) => boolean,
|
elements: TSTypeElement[]
|
||||||
refName: String
|
): ResolvedElements {
|
||||||
): Node | undefined {
|
const ret: ResolvedElements = {}
|
||||||
if (node.type === 'TSInterfaceDeclaration' && node.id.name === refName) {
|
for (const e of elements) {
|
||||||
return node.body
|
if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') {
|
||||||
} else if (
|
const name =
|
||||||
node.type === 'TSTypeAliasDeclaration' &&
|
e.key.type === 'Identifier'
|
||||||
node.id.name === refName &&
|
? e.key.name
|
||||||
qualifier(node.typeAnnotation)
|
: e.key.type === 'StringLiteral'
|
||||||
) {
|
? e.key.value
|
||||||
return node.typeAnnotation
|
: null
|
||||||
} else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
if (name && !e.computed) {
|
||||||
return isQualifiedType(node.declaration, qualifier, refName)
|
ret[name] = e
|
||||||
}
|
} else {
|
||||||
}
|
ctx.error(
|
||||||
|
`computed keys are not supported in types referenced by SFC macros.`,
|
||||||
function resolveExtendsType(
|
e
|
||||||
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)
|
} else if (e.type === 'TSCallSignatureDeclaration') {
|
||||||
resolveExtendsType(body, node, qualifier, cache)
|
addCallSignature(ret, e)
|
||||||
return cache
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return ret
|
||||||
})
|
|
||||||
}
|
|
||||||
return cache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter all extends types to keep the override declaration
|
function resolveInterfaceMembers(
|
||||||
function filterExtendsType(extendsTypes: Node[], bodies: TSTypeElement[]) {
|
ctx: ScriptCompileContext,
|
||||||
extendsTypes.forEach(extend => {
|
node: TSInterfaceDeclaration
|
||||||
const body = (extend as TSInterfaceBody).body
|
): ResolvedElements {
|
||||||
body.forEach(newBody => {
|
const base = typeElementsToMap(ctx, node.body.body)
|
||||||
if (
|
if (node.extends) {
|
||||||
newBody.type === 'TSPropertySignature' &&
|
for (const ext of node.extends) {
|
||||||
newBody.key.type === 'Identifier'
|
const resolvedExt = resolveTypeElements(ctx, ext)
|
||||||
) {
|
for (const key in resolvedExt) {
|
||||||
const name = newBody.key.name
|
if (!hasOwn(base, key)) {
|
||||||
const hasOverride = bodies.some(
|
base[key] = resolvedExt[key]
|
||||||
seenBody =>
|
|
||||||
seenBody.type === 'TSPropertySignature' &&
|
|
||||||
seenBody.key.type === 'Identifier' &&
|
|
||||||
seenBody.key.name === name
|
|
||||||
)
|
|
||||||
if (!hasOverride) bodies.push(newBody)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 getRootScope(ctx: ScriptCompileContext): TypeScope {
|
||||||
|
if (ctx.scope) {
|
||||||
|
return ctx.scope
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
|
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(
|
export function inferRuntimeType(
|
||||||
node: TSType,
|
ctx: ScriptCompileContext,
|
||||||
declaredTypes: Record<string, string[]>
|
node: Node,
|
||||||
|
scope = getRootScope(ctx)
|
||||||
): string[] {
|
): string[] {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'TSStringKeyword':
|
case 'TSStringKeyword':
|
||||||
|
@ -129,10 +234,13 @@ export function inferRuntimeType(
|
||||||
return ['Object']
|
return ['Object']
|
||||||
case 'TSNullKeyword':
|
case 'TSNullKeyword':
|
||||||
return ['null']
|
return ['null']
|
||||||
case 'TSTypeLiteral': {
|
case 'TSTypeLiteral':
|
||||||
|
case 'TSInterfaceDeclaration': {
|
||||||
// TODO (nice to have) generate runtime property validation
|
// TODO (nice to have) generate runtime property validation
|
||||||
const types = new Set<string>()
|
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 (
|
if (
|
||||||
m.type === 'TSCallSignatureDeclaration' ||
|
m.type === 'TSCallSignatureDeclaration' ||
|
||||||
m.type === 'TSConstructSignatureDeclaration'
|
m.type === 'TSConstructSignatureDeclaration'
|
||||||
|
@ -166,8 +274,9 @@ export function inferRuntimeType(
|
||||||
|
|
||||||
case 'TSTypeReference':
|
case 'TSTypeReference':
|
||||||
if (node.typeName.type === 'Identifier') {
|
if (node.typeName.type === 'Identifier') {
|
||||||
if (declaredTypes[node.typeName.name]) {
|
const resolved = resolveTypeReference(ctx, node, scope, false)
|
||||||
return declaredTypes[node.typeName.name]
|
if (resolved) {
|
||||||
|
return inferRuntimeType(ctx, resolved, scope)
|
||||||
}
|
}
|
||||||
switch (node.typeName.name) {
|
switch (node.typeName.name) {
|
||||||
case 'Array':
|
case 'Array':
|
||||||
|
@ -205,26 +314,21 @@ export function inferRuntimeType(
|
||||||
case 'NonNullable':
|
case 'NonNullable':
|
||||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||||
return inferRuntimeType(
|
return inferRuntimeType(
|
||||||
|
ctx,
|
||||||
node.typeParameters.params[0],
|
node.typeParameters.params[0],
|
||||||
declaredTypes
|
scope
|
||||||
).filter(t => t !== 'null')
|
).filter(t => t !== 'null')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'Extract':
|
case 'Extract':
|
||||||
if (node.typeParameters && node.typeParameters.params[1]) {
|
if (node.typeParameters && node.typeParameters.params[1]) {
|
||||||
return inferRuntimeType(
|
return inferRuntimeType(ctx, node.typeParameters.params[1], scope)
|
||||||
node.typeParameters.params[1],
|
|
||||||
declaredTypes
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'Exclude':
|
case 'Exclude':
|
||||||
case 'OmitThisParameter':
|
case 'OmitThisParameter':
|
||||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||||
return inferRuntimeType(
|
return inferRuntimeType(ctx, node.typeParameters.params[0], scope)
|
||||||
node.typeParameters.params[0],
|
|
||||||
declaredTypes
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -233,16 +337,19 @@ export function inferRuntimeType(
|
||||||
return [UNKNOWN_TYPE]
|
return [UNKNOWN_TYPE]
|
||||||
|
|
||||||
case 'TSParenthesizedType':
|
case 'TSParenthesizedType':
|
||||||
return inferRuntimeType(node.typeAnnotation, declaredTypes)
|
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
||||||
|
|
||||||
case 'TSUnionType':
|
case 'TSUnionType':
|
||||||
return flattenTypes(node.types, declaredTypes)
|
return flattenTypes(ctx, node.types, scope)
|
||||||
case 'TSIntersectionType': {
|
case 'TSIntersectionType': {
|
||||||
return flattenTypes(node.types, declaredTypes).filter(
|
return flattenTypes(ctx, node.types, scope).filter(
|
||||||
t => t !== UNKNOWN_TYPE
|
t => t !== UNKNOWN_TYPE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'TSEnumDeclaration':
|
||||||
|
return inferEnumType(node)
|
||||||
|
|
||||||
case 'TSSymbolKeyword':
|
case 'TSSymbolKeyword':
|
||||||
return ['Symbol']
|
return ['Symbol']
|
||||||
|
|
||||||
|
@ -252,14 +359,32 @@ export function inferRuntimeType(
|
||||||
}
|
}
|
||||||
|
|
||||||
function flattenTypes(
|
function flattenTypes(
|
||||||
|
ctx: ScriptCompileContext,
|
||||||
types: TSType[],
|
types: TSType[],
|
||||||
declaredTypes: Record<string, string[]>
|
scope: TypeScope
|
||||||
): string[] {
|
): string[] {
|
||||||
return [
|
return [
|
||||||
...new Set(
|
...new Set(
|
||||||
([] as string[]).concat(
|
([] 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']
|
||||||
|
}
|
||||||
|
|
|
@ -3,8 +3,6 @@ import { TS_NODE_TYPES } from '@vue/compiler-dom'
|
||||||
|
|
||||||
export const UNKNOWN_TYPE = 'Unknown'
|
export const UNKNOWN_TYPE = 'Unknown'
|
||||||
|
|
||||||
export type FromNormalScript<T> = T & { __fromNormalScript?: boolean | null }
|
|
||||||
|
|
||||||
export function resolveObjectKey(node: Node, computed: boolean) {
|
export function resolveObjectKey(node: Node, computed: boolean) {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'StringLiteral':
|
case 'StringLiteral':
|
||||||
|
|
Loading…
Reference in New Issue