vue3-core/packages/compiler-sfc/src/script/defineEmits.ts

125 lines
3.2 KiB
TypeScript

import { Identifier, LVal, Node, RestElement } from '@babel/types'
import { isCallOf } from './utils'
import { ScriptCompileContext } from './context'
import { resolveTypeElements } from './resolveType'
export const DEFINE_EMITS = 'defineEmits'
export function processDefineEmits(
ctx: ScriptCompileContext,
node: Node,
declId?: LVal
): boolean {
if (!isCallOf(node, DEFINE_EMITS)) {
return false
}
if (ctx.hasDefineEmitCall) {
ctx.error(`duplicate ${DEFINE_EMITS}() call`, node)
}
ctx.hasDefineEmitCall = true
ctx.emitsRuntimeDecl = node.arguments[0]
if (node.typeParameters) {
if (ctx.emitsRuntimeDecl) {
ctx.error(
`${DEFINE_EMITS}() cannot accept both type and non-type arguments ` +
`at the same time. Use one or the other.`,
node
)
}
ctx.emitsTypeDecl = node.typeParameters.params[0]
}
if (declId) {
ctx.emitIdentifier =
declId.type === 'Identifier' ? declId.name : ctx.getString(declId)
}
return true
}
export function genRuntimeEmits(ctx: ScriptCompileContext): string | undefined {
let emitsDecl = ''
if (ctx.emitsRuntimeDecl) {
emitsDecl = ctx.getString(ctx.emitsRuntimeDecl).trim()
} else if (ctx.emitsTypeDecl) {
const typeDeclaredEmits = extractRuntimeEmits(ctx)
emitsDecl = typeDeclaredEmits.size
? `[${Array.from(typeDeclaredEmits)
.map(k => JSON.stringify(k))
.join(', ')}]`
: ``
}
if (ctx.hasDefineModelCall) {
let modelEmitsDecl = `[${Object.keys(ctx.modelDecls)
.map(n => JSON.stringify(`update:${n}`))
.join(', ')}]`
emitsDecl = emitsDecl
? `${ctx.helper('mergeModels')}(${emitsDecl}, ${modelEmitsDecl})`
: modelEmitsDecl
}
return emitsDecl
}
function extractRuntimeEmits(ctx: ScriptCompileContext): Set<string> {
const emits = new Set<string>()
const node = ctx.emitsTypeDecl!
if (node.type === 'TSFunctionType') {
extractEventNames(node.parameters[0], emits)
return emits
}
const { props, calls } = resolveTypeElements(ctx, node)
let hasProperty = false
for (const key in props) {
emits.add(key)
hasProperty = true
}
if (calls) {
if (hasProperty) {
ctx.error(
`defineEmits() type cannot mixed call signature and property syntax.`,
node
)
}
for (const call of calls) {
extractEventNames(call.parameters[0], emits)
}
}
return emits
}
function extractEventNames(
eventName: Identifier | RestElement,
emits: Set<string>
) {
if (
eventName.type === 'Identifier' &&
eventName.typeAnnotation &&
eventName.typeAnnotation.type === 'TSTypeAnnotation'
) {
const typeNode = eventName.typeAnnotation.typeAnnotation
if (typeNode.type === 'TSLiteralType') {
if (
typeNode.literal.type !== 'UnaryExpression' &&
typeNode.literal.type !== 'TemplateLiteral'
) {
emits.add(String(typeNode.literal.value))
}
} else if (typeNode.type === 'TSUnionType') {
for (const t of typeNode.types) {
if (
t.type === 'TSLiteralType' &&
t.literal.type !== 'UnaryExpression' &&
t.literal.type !== 'TemplateLiteral'
) {
emits.add(String(t.literal.value))
}
}
}
}
}