Skip to content

Commit

Permalink
refactor(compiler-sfc): rework macro type resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 authored and IAmSSH committed Apr 29, 2023
1 parent 0c658cc commit 356800d
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 292 deletions.
61 changes: 14 additions & 47 deletions packages/compiler-sfc/src/compileScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import {
Identifier,
ExportSpecifier,
Statement,
CallExpression,
TSEnumDeclaration
CallExpression
} from '@babel/types'
import { walk } from 'estree-walker'
import { RawSourceMap } from 'source-map-js'
Expand Down Expand Up @@ -47,7 +46,6 @@ import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
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'
Expand Down Expand Up @@ -169,7 +167,6 @@ export function compileScript(

// 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)

Expand Down Expand Up @@ -223,7 +220,7 @@ export function compileScript(
isUsedInTemplate = isImportUsed(local, sfc)
}

userImports[local] = {
ctx.userImports[local] = {
isType,
imported,
local,
Expand Down Expand Up @@ -303,7 +300,7 @@ export function compileScript(
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 ||
Expand Down Expand Up @@ -345,8 +342,8 @@ export function compileScript(

// 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
}

Expand Down Expand Up @@ -658,7 +655,6 @@ export function compileScript(
node.exportKind === 'type') ||
(node.type === 'VariableDeclaration' && node.declare)
) {
recordType(node, ctx.declaredTypes)
if (node.type !== 'TSEnumDeclaration') {
hoistNode(node)
}
Expand Down Expand Up @@ -723,7 +719,7 @@ export function compileScript(
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] =
Expand Down Expand Up @@ -823,17 +819,20 @@ export function compileScript(
...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
}
}
returned = `{ `
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
Expand Down Expand Up @@ -1012,7 +1011,7 @@ export function compileScript(
return {
...scriptSetup,
bindings: ctx.bindingMetadata,
imports: userImports,
imports: ctx.userImports,
content: ctx.s.toString(),
map:
options.sourceMap !== false
Expand Down Expand Up @@ -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 {
if (isCallOf(node, userReactiveImport)) {
return true
Expand Down
14 changes: 8 additions & 6 deletions packages/compiler-sfc/src/script/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { Node, ObjectPattern, Program } from '@babel/types'
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
Expand All @@ -20,7 +20,9 @@ export class ScriptCompileContext {
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
Expand All @@ -35,15 +37,15 @@ export class ScriptCompileContext {
// defineProps
propsIdentifier: string | undefined
propsRuntimeDecl: Node | undefined
propsTypeDecl: PropsDeclType | undefined
propsTypeDecl: Node | undefined
propsDestructureDecl: ObjectPattern | undefined
propsDestructuredBindings: PropsDestructureBindings = Object.create(null)
propsDestructureRestId: string | undefined
propsRuntimeDefaults: Node | undefined

// defineEmits
emitsRuntimeDecl: Node | undefined
emitsTypeDecl: EmitsDeclType | undefined
emitsTypeDecl: Node | undefined
emitIdentifier: string | undefined

// defineModel
Expand Down
78 changes: 24 additions & 54 deletions packages/compiler-sfc/src/script/defineEmits.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
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,
Expand All @@ -38,21 +26,7 @@ export function processDefineEmits(
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) {
Expand Down Expand Up @@ -89,36 +63,32 @@ export function genRuntimeEmits(ctx: ScriptCompileContext): string | undefined {
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
}

Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-sfc/src/script/defineModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function genModelProps(ctx: ScriptCompileContext) {
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)

Expand Down
Loading

0 comments on commit 356800d

Please sign in to comment.