mirror of https://github.com/vuejs/core.git
refactor(compiler-sfc): (wip) split compileScript logic, use context, move defineProps
This commit is contained in:
parent
b16866d56b
commit
acd7eb22cf
|
@ -15,12 +15,7 @@ import {
|
|||
isCallOf
|
||||
} from '@vue/compiler-dom'
|
||||
import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse'
|
||||
import {
|
||||
parse as _parse,
|
||||
parseExpression,
|
||||
ParserOptions,
|
||||
ParserPlugin
|
||||
} from '@babel/parser'
|
||||
import { parse as _parse, parseExpression, ParserPlugin } from '@babel/parser'
|
||||
import { camelize, capitalize, generateCodeFrame, makeMap } from '@vue/shared'
|
||||
import {
|
||||
Node,
|
||||
|
@ -41,7 +36,6 @@ import {
|
|||
TSInterfaceBody,
|
||||
TSTypeElement,
|
||||
AwaitExpression,
|
||||
Program,
|
||||
ObjectMethod,
|
||||
LVal,
|
||||
Expression,
|
||||
|
@ -59,13 +53,19 @@ import { warnOnce } from './warn'
|
|||
import { rewriteDefaultAST } from './rewriteDefault'
|
||||
import { createCache } from './cache'
|
||||
import { shouldTransform, transformAST } from '@vue/reactivity-transform'
|
||||
import { transformDestructuredProps } from './script/propsDestructure'
|
||||
import { transformDestructuredProps } from './script/definePropsDestructure'
|
||||
import { resolveObjectKey, FromNormalScript } from './script/utils'
|
||||
import { ScriptCompileContext } from './script/context'
|
||||
import {
|
||||
processDefineProps,
|
||||
DEFINE_PROPS,
|
||||
WITH_DEFAULTS,
|
||||
PropsDeclType
|
||||
} from './script/defineProps'
|
||||
|
||||
// Special compiler macros
|
||||
const DEFINE_PROPS = 'defineProps'
|
||||
const DEFINE_EMITS = 'defineEmits'
|
||||
const DEFINE_EXPOSE = 'defineExpose'
|
||||
const WITH_DEFAULTS = 'withDefaults'
|
||||
const DEFINE_OPTIONS = 'defineOptions'
|
||||
const DEFINE_SLOTS = 'defineSlots'
|
||||
const DEFINE_MODEL = 'defineModel'
|
||||
|
@ -149,8 +149,6 @@ export type PropsDestructureBindings = Record<
|
|||
}
|
||||
>
|
||||
|
||||
type FromNormalScript<T> = T & { __fromNormalScript?: boolean | null }
|
||||
type PropsDeclType = FromNormalScript<TSTypeLiteral | TSInterfaceBody>
|
||||
type EmitsDeclType = FromNormalScript<
|
||||
TSFunctionType | TSTypeLiteral | TSInterfaceBody
|
||||
>
|
||||
|
@ -195,41 +193,15 @@ export function compileScript(
|
|||
? `const ${options.genDefaultAs} =`
|
||||
: `export default`
|
||||
const normalScriptDefaultVar = `__default__`
|
||||
const isJS =
|
||||
scriptLang === 'js' ||
|
||||
scriptLang === 'jsx' ||
|
||||
scriptSetupLang === 'js' ||
|
||||
scriptSetupLang === 'jsx'
|
||||
const isTS =
|
||||
scriptLang === 'ts' ||
|
||||
scriptLang === 'tsx' ||
|
||||
scriptSetupLang === 'ts' ||
|
||||
scriptSetupLang === 'tsx'
|
||||
|
||||
// resolve parser plugins
|
||||
const plugins: ParserPlugin[] = []
|
||||
if (!isTS || scriptLang === 'tsx' || scriptSetupLang === 'tsx') {
|
||||
plugins.push('jsx')
|
||||
} else {
|
||||
// If don't match the case of adding jsx, should remove the jsx from the babelParserPlugins
|
||||
if (options.babelParserPlugins)
|
||||
options.babelParserPlugins = options.babelParserPlugins.filter(
|
||||
n => n !== 'jsx'
|
||||
)
|
||||
}
|
||||
if (options.babelParserPlugins) plugins.push(...options.babelParserPlugins)
|
||||
if (isTS) {
|
||||
plugins.push('typescript')
|
||||
if (!plugins.includes('decorators')) {
|
||||
plugins.push('decorators-legacy')
|
||||
}
|
||||
}
|
||||
const ctx = new ScriptCompileContext(sfc, options)
|
||||
const { isTS } = ctx
|
||||
|
||||
if (!scriptSetup) {
|
||||
if (!script) {
|
||||
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
||||
}
|
||||
if (scriptLang && !isJS && !isTS) {
|
||||
if (scriptLang && !ctx.isJS && !ctx.isTS) {
|
||||
// do not process non js/ts script blocks
|
||||
return script
|
||||
}
|
||||
|
@ -237,10 +209,7 @@ export function compileScript(
|
|||
try {
|
||||
let content = script.content
|
||||
let map = script.map
|
||||
const scriptAst = _parse(content, {
|
||||
plugins,
|
||||
sourceType: 'module'
|
||||
}).program
|
||||
const scriptAst = ctx.scriptAst!
|
||||
const bindings = analyzeScriptBindings(scriptAst.body)
|
||||
if (enableReactivityTransform && shouldTransform(content)) {
|
||||
const s = new MagicString(source)
|
||||
|
@ -268,7 +237,7 @@ export function compileScript(
|
|||
if (cssVars.length || options.genDefaultAs) {
|
||||
const defaultVar = options.genDefaultAs || normalScriptDefaultVar
|
||||
const s = new MagicString(content)
|
||||
rewriteDefaultAST(scriptAst.body, s, defaultVar)
|
||||
rewriteDefaultAST(ctx.scriptAst!.body, s, defaultVar)
|
||||
content = s.toString()
|
||||
if (cssVars.length) {
|
||||
content += genNormalScriptCssVarsCode(
|
||||
|
@ -304,7 +273,7 @@ export function compileScript(
|
|||
)
|
||||
}
|
||||
|
||||
if (scriptSetupLang && !isJS && !isTS) {
|
||||
if (scriptSetupLang && !ctx.isJS && !ctx.isTS) {
|
||||
// do not process non js/ts script blocks
|
||||
return scriptSetup
|
||||
}
|
||||
|
@ -317,21 +286,7 @@ export function compileScript(
|
|||
const setupBindings: Record<string, BindingTypes> = Object.create(null)
|
||||
|
||||
let defaultExport: Node | undefined
|
||||
let hasDefinePropsCall = false
|
||||
let hasDefineEmitCall = false
|
||||
let hasDefineExposeCall = false
|
||||
let hasDefaultExportName = false
|
||||
let hasDefaultExportRender = false
|
||||
let hasDefineOptionsCall = false
|
||||
let hasDefineSlotsCall = false
|
||||
let hasDefineModelCall = false
|
||||
let propsRuntimeDecl: Node | undefined
|
||||
let propsRuntimeDefaults: Node | undefined
|
||||
let propsDestructureDecl: Node | undefined
|
||||
let propsDestructureRestId: string | undefined
|
||||
let propsTypeDecl: PropsDeclType | undefined
|
||||
let propsTypeDeclRaw: Node | undefined
|
||||
let propsIdentifier: string | undefined
|
||||
// let propsRuntimeDefaults: Node | undefined
|
||||
let emitsRuntimeDecl: Node | undefined
|
||||
let emitsTypeDecl: EmitsDeclType | undefined
|
||||
let emitIdentifier: string | undefined
|
||||
|
@ -344,9 +299,6 @@ export function compileScript(
|
|||
const typeDeclaredEmits: Set<string> = new Set()
|
||||
// record declared types for runtime props type generation
|
||||
const declaredTypes: Record<string, string[]> = {}
|
||||
// props destructure data
|
||||
const propsDestructuredBindings: PropsDestructureBindings =
|
||||
Object.create(null)
|
||||
|
||||
// magic-string state
|
||||
const s = new MagicString(source)
|
||||
|
@ -360,21 +312,6 @@ export function compileScript(
|
|||
return `_${key}`
|
||||
}
|
||||
|
||||
function parse(
|
||||
input: string,
|
||||
options: ParserOptions,
|
||||
offset: number
|
||||
): Program {
|
||||
try {
|
||||
return _parse(input, options).program
|
||||
} catch (e: any) {
|
||||
e.message = `[@vue/compiler-sfc] ${e.message}\n\n${
|
||||
sfc.filename
|
||||
}\n${generateCodeFrame(source, e.pos + offset, e.pos + offset + 1)}`
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
function error(
|
||||
msg: string,
|
||||
node: Node,
|
||||
|
@ -439,136 +376,14 @@ export function compileScript(
|
|||
}
|
||||
}
|
||||
|
||||
function processDefineProps(node: Node, declId?: LVal): boolean {
|
||||
if (!isCallOf(node, DEFINE_PROPS)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (hasDefinePropsCall) {
|
||||
error(`duplicate ${DEFINE_PROPS}() call`, node)
|
||||
}
|
||||
hasDefinePropsCall = true
|
||||
|
||||
propsRuntimeDecl = node.arguments[0]
|
||||
|
||||
// call has type parameters - infer runtime types from it
|
||||
if (node.typeParameters) {
|
||||
if (propsRuntimeDecl) {
|
||||
error(
|
||||
`${DEFINE_PROPS}() cannot accept both type and non-type arguments ` +
|
||||
`at the same time. Use one or the other.`,
|
||||
node
|
||||
)
|
||||
}
|
||||
|
||||
propsTypeDeclRaw = node.typeParameters.params[0]
|
||||
propsTypeDecl = resolveQualifiedType(
|
||||
propsTypeDeclRaw,
|
||||
node => node.type === 'TSTypeLiteral'
|
||||
) as PropsDeclType | undefined
|
||||
|
||||
if (!propsTypeDecl) {
|
||||
error(
|
||||
`type argument passed to ${DEFINE_PROPS}() must be a literal type, ` +
|
||||
`or a reference to an interface or literal type.`,
|
||||
propsTypeDeclRaw
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (declId) {
|
||||
// handle props destructure
|
||||
if (declId.type === 'ObjectPattern') {
|
||||
propsDestructureDecl = declId
|
||||
for (const prop of declId.properties) {
|
||||
if (prop.type === 'ObjectProperty') {
|
||||
const propKey = resolveObjectKey(prop.key, prop.computed)
|
||||
|
||||
if (!propKey) {
|
||||
error(
|
||||
`${DEFINE_PROPS}() destructure cannot use computed key.`,
|
||||
prop.key
|
||||
)
|
||||
}
|
||||
|
||||
if (prop.value.type === 'AssignmentPattern') {
|
||||
// default value { foo = 123 }
|
||||
const { left, right } = prop.value
|
||||
if (left.type !== 'Identifier') {
|
||||
error(
|
||||
`${DEFINE_PROPS}() destructure does not support nested patterns.`,
|
||||
left
|
||||
)
|
||||
}
|
||||
// store default value
|
||||
propsDestructuredBindings[propKey] = {
|
||||
local: left.name,
|
||||
default: right
|
||||
}
|
||||
} else if (prop.value.type === 'Identifier') {
|
||||
// simple destructure
|
||||
propsDestructuredBindings[propKey] = {
|
||||
local: prop.value.name
|
||||
}
|
||||
} else {
|
||||
error(
|
||||
`${DEFINE_PROPS}() destructure does not support nested patterns.`,
|
||||
prop.value
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// rest spread
|
||||
propsDestructureRestId = (prop.argument as Identifier).name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function processWithDefaults(node: Node, declId?: LVal): boolean {
|
||||
if (!isCallOf(node, WITH_DEFAULTS)) {
|
||||
return false
|
||||
}
|
||||
if (processDefineProps(node.arguments[0], declId)) {
|
||||
if (propsRuntimeDecl) {
|
||||
error(
|
||||
`${WITH_DEFAULTS} can only be used with type-based ` +
|
||||
`${DEFINE_PROPS} declaration.`,
|
||||
node
|
||||
)
|
||||
}
|
||||
if (propsDestructureDecl) {
|
||||
error(
|
||||
`${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` +
|
||||
`Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`,
|
||||
node.callee
|
||||
)
|
||||
}
|
||||
propsRuntimeDefaults = node.arguments[1]
|
||||
if (!propsRuntimeDefaults) {
|
||||
error(`The 2nd argument of ${WITH_DEFAULTS} is required.`, node)
|
||||
}
|
||||
} else {
|
||||
error(
|
||||
`${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,
|
||||
node.arguments[0] || node
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function processDefineEmits(node: Node, declId?: LVal): boolean {
|
||||
if (!isCallOf(node, DEFINE_EMITS)) {
|
||||
return false
|
||||
}
|
||||
if (hasDefineEmitCall) {
|
||||
if (ctx.hasDefineEmitCall) {
|
||||
error(`duplicate ${DEFINE_EMITS}() call`, node)
|
||||
}
|
||||
hasDefineEmitCall = true
|
||||
ctx.hasDefineEmitCall = true
|
||||
emitsRuntimeDecl = node.arguments[0]
|
||||
if (node.typeParameters) {
|
||||
if (emitsRuntimeDecl) {
|
||||
|
@ -608,10 +423,10 @@ export function compileScript(
|
|||
if (!isCallOf(node, DEFINE_SLOTS)) {
|
||||
return false
|
||||
}
|
||||
if (hasDefineSlotsCall) {
|
||||
if (ctx.hasDefineSlotsCall) {
|
||||
error(`duplicate ${DEFINE_SLOTS}() call`, node)
|
||||
}
|
||||
hasDefineSlotsCall = true
|
||||
ctx.hasDefineSlotsCall = true
|
||||
|
||||
if (node.arguments.length > 0) {
|
||||
error(`${DEFINE_SLOTS}() cannot accept arguments`, node)
|
||||
|
@ -632,7 +447,7 @@ export function compileScript(
|
|||
if (!enableDefineModel || !isCallOf(node, DEFINE_MODEL)) {
|
||||
return false
|
||||
}
|
||||
hasDefineModelCall = true
|
||||
ctx.hasDefineModelCall = true
|
||||
|
||||
const type =
|
||||
(node.typeParameters && node.typeParameters.params[0]) || undefined
|
||||
|
@ -781,7 +596,7 @@ export function compileScript(
|
|||
if (!isCallOf(node, DEFINE_OPTIONS)) {
|
||||
return false
|
||||
}
|
||||
if (hasDefineOptionsCall) {
|
||||
if (ctx.hasDefineOptionsCall) {
|
||||
error(`duplicate ${DEFINE_OPTIONS}() call`, node)
|
||||
}
|
||||
if (node.typeParameters) {
|
||||
|
@ -789,7 +604,7 @@ export function compileScript(
|
|||
}
|
||||
if (!node.arguments[0]) return true
|
||||
|
||||
hasDefineOptionsCall = true
|
||||
ctx.hasDefineOptionsCall = true
|
||||
optionsRuntimeDecl = unwrapTSNode(node.arguments[0])
|
||||
|
||||
let propsOption = undefined
|
||||
|
@ -875,10 +690,10 @@ export function compileScript(
|
|||
|
||||
function processDefineExpose(node: Node): boolean {
|
||||
if (isCallOf(node, DEFINE_EXPOSE)) {
|
||||
if (hasDefineExposeCall) {
|
||||
if (ctx.hasDefineExposeCall) {
|
||||
error(`duplicate ${DEFINE_EXPOSE}() call`, node)
|
||||
}
|
||||
hasDefineExposeCall = true
|
||||
ctx.hasDefineExposeCall = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -958,9 +773,9 @@ export function compileScript(
|
|||
*/
|
||||
function hasStaticWithDefaults() {
|
||||
return (
|
||||
propsRuntimeDefaults &&
|
||||
propsRuntimeDefaults.type === 'ObjectExpression' &&
|
||||
propsRuntimeDefaults.properties.every(
|
||||
ctx.propsRuntimeDefaults &&
|
||||
ctx.propsRuntimeDefaults.type === 'ObjectExpression' &&
|
||||
ctx.propsRuntimeDefaults.properties.every(
|
||||
node =>
|
||||
node.type !== 'SpreadElement' &&
|
||||
(!node.computed || node.key.type.endsWith('Literal'))
|
||||
|
@ -993,7 +808,7 @@ export function compileScript(
|
|||
}`
|
||||
} else if (hasStaticDefaults) {
|
||||
const prop = (
|
||||
propsRuntimeDefaults as ObjectExpression
|
||||
ctx.propsRuntimeDefaults as ObjectExpression
|
||||
).properties.find(node => {
|
||||
if (node.type === 'SpreadElement') return false
|
||||
return resolveObjectKey(node.key, node.computed) === key
|
||||
|
@ -1045,10 +860,10 @@ export function compileScript(
|
|||
})
|
||||
.join(',\n ')}\n }`
|
||||
|
||||
if (propsRuntimeDefaults && !hasStaticDefaults) {
|
||||
if (ctx.propsRuntimeDefaults && !hasStaticDefaults) {
|
||||
propsDecls = `${helper('mergeDefaults')}(${propsDecls}, ${source.slice(
|
||||
propsRuntimeDefaults.start! + startOffset,
|
||||
propsRuntimeDefaults.end! + startOffset
|
||||
ctx.propsRuntimeDefaults.start! + startOffset,
|
||||
ctx.propsRuntimeDefaults.end! + startOffset
|
||||
)})`
|
||||
}
|
||||
|
||||
|
@ -1056,7 +871,7 @@ export function compileScript(
|
|||
}
|
||||
|
||||
function genModels() {
|
||||
if (!hasDefineModelCall) return
|
||||
if (!ctx.hasDefineModelCall) return
|
||||
|
||||
let modelPropsDecl = ''
|
||||
for (const [name, { type, options }] of Object.entries(modelDecls)) {
|
||||
|
@ -1088,7 +903,7 @@ export function compileScript(
|
|||
|
||||
let decl: string
|
||||
if (runtimeType && options) {
|
||||
decl = isTS
|
||||
decl = ctx.isTS
|
||||
? `{ ${codegenOptions}, ...${options} }`
|
||||
: `Object.assign({ ${codegenOptions} }, ${options})`
|
||||
} else {
|
||||
|
@ -1100,13 +915,13 @@ export function compileScript(
|
|||
}
|
||||
|
||||
let propsDecls: undefined | string
|
||||
if (propsRuntimeDecl) {
|
||||
if (ctx.propsRuntimeDecl) {
|
||||
propsDecls = scriptSetup!.content
|
||||
.slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!)
|
||||
.slice(ctx.propsRuntimeDecl.start!, ctx.propsRuntimeDecl.end!)
|
||||
.trim()
|
||||
if (propsDestructureDecl) {
|
||||
if (ctx.propsDestructureDecl) {
|
||||
const defaults: string[] = []
|
||||
for (const key in propsDestructuredBindings) {
|
||||
for (const key in ctx.propsDestructuredBindings) {
|
||||
const d = genDestructuredDefaultValue(key)
|
||||
if (d)
|
||||
defaults.push(
|
||||
|
@ -1121,7 +936,7 @@ export function compileScript(
|
|||
)}(${propsDecls}, {\n ${defaults.join(',\n ')}\n})`
|
||||
}
|
||||
}
|
||||
} else if (propsTypeDecl) {
|
||||
} else if (ctx.propsTypeDecl) {
|
||||
propsDecls = genPropsFromTS()
|
||||
}
|
||||
|
||||
|
@ -1143,7 +958,7 @@ export function compileScript(
|
|||
needSkipFactory: boolean
|
||||
}
|
||||
| undefined {
|
||||
const destructured = propsDestructuredBindings[key]
|
||||
const destructured = ctx.propsDestructuredBindings[key]
|
||||
const defaultVal = destructured && destructured.default
|
||||
if (defaultVal) {
|
||||
const value = scriptSetup!.content.slice(
|
||||
|
@ -1204,13 +1019,15 @@ export function compileScript(
|
|||
m.key.type === 'Identifier'
|
||||
) {
|
||||
if (
|
||||
(propsRuntimeDefaults as ObjectExpression).properties.some(p => {
|
||||
if (p.type === 'SpreadElement') return false
|
||||
return (
|
||||
resolveObjectKey(p.key, p.computed) ===
|
||||
(m.key as Identifier).name
|
||||
)
|
||||
})
|
||||
(ctx.propsRuntimeDefaults as ObjectExpression).properties.some(
|
||||
p => {
|
||||
if (p.type === 'SpreadElement') return false
|
||||
return (
|
||||
resolveObjectKey(p.key, p.computed) ===
|
||||
(m.key as Identifier).name
|
||||
)
|
||||
}
|
||||
)
|
||||
) {
|
||||
res +=
|
||||
m.key.name +
|
||||
|
@ -1248,7 +1065,7 @@ export function compileScript(
|
|||
} else if (emitsTypeDecl) {
|
||||
emitsDecl = genEmitsFromTS()
|
||||
}
|
||||
if (hasDefineModelCall) {
|
||||
if (ctx.hasDefineModelCall) {
|
||||
let modelEmitsDecl = `[${Object.keys(modelDecls)
|
||||
.map(n => JSON.stringify(`update:${n}`))
|
||||
.join(', ')}]`
|
||||
|
@ -1260,29 +1077,32 @@ export function compileScript(
|
|||
}
|
||||
|
||||
// 0. parse both <script> and <script setup> blocks
|
||||
const scriptAst =
|
||||
script &&
|
||||
parse(
|
||||
script.content,
|
||||
{
|
||||
plugins,
|
||||
sourceType: 'module'
|
||||
},
|
||||
scriptStartOffset!
|
||||
)
|
||||
// const scriptAst =
|
||||
// script &&
|
||||
// parse(
|
||||
// script.content,
|
||||
// {
|
||||
// plugins,
|
||||
// sourceType: 'module'
|
||||
// },
|
||||
// scriptStartOffset!
|
||||
// )
|
||||
|
||||
const scriptSetupAst = parse(
|
||||
scriptSetup.content,
|
||||
{
|
||||
plugins: [
|
||||
...plugins,
|
||||
// allow top level await but only inside <script setup>
|
||||
'topLevelAwait'
|
||||
],
|
||||
sourceType: 'module'
|
||||
},
|
||||
startOffset
|
||||
)
|
||||
// const scriptSetupAst = parse(
|
||||
// scriptSetup.content,
|
||||
// {
|
||||
// plugins: [
|
||||
// ...plugins,
|
||||
// // allow top level await but only inside <script setup>
|
||||
// 'topLevelAwait'
|
||||
// ],
|
||||
// sourceType: 'module'
|
||||
// },
|
||||
// startOffset
|
||||
// )
|
||||
|
||||
const scriptAst = ctx.scriptAst
|
||||
const scriptSetupAst = ctx.scriptSetupAst!
|
||||
|
||||
// 1.1 walk import delcarations of <script>
|
||||
if (scriptAst) {
|
||||
|
@ -1407,7 +1227,7 @@ export function compileScript(
|
|||
s.key.type === 'Identifier' &&
|
||||
s.key.name === 'name'
|
||||
) {
|
||||
hasDefaultExportName = true
|
||||
ctx.hasDefaultExportName = true
|
||||
}
|
||||
if (
|
||||
(s.type === 'ObjectMethod' || s.type === 'ObjectProperty') &&
|
||||
|
@ -1415,7 +1235,7 @@ export function compileScript(
|
|||
s.key.name === 'render'
|
||||
) {
|
||||
// TODO warn when we provide a better way to do it?
|
||||
hasDefaultExportRender = true
|
||||
ctx.hasDefaultExportRender = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1531,10 +1351,9 @@ export function compileScript(
|
|||
const expr = unwrapTSNode(node.expression)
|
||||
// process `defineProps` and `defineEmit(s)` calls
|
||||
if (
|
||||
processDefineProps(expr) ||
|
||||
processDefineProps(ctx, expr) ||
|
||||
processDefineEmits(expr) ||
|
||||
processDefineOptions(expr) ||
|
||||
processWithDefaults(expr) ||
|
||||
processDefineSlots(expr)
|
||||
) {
|
||||
s.remove(node.start! + startOffset, node.end! + startOffset)
|
||||
|
@ -1568,9 +1387,7 @@ export function compileScript(
|
|||
}
|
||||
|
||||
// defineProps / defineEmits
|
||||
const isDefineProps =
|
||||
processDefineProps(init, decl.id) ||
|
||||
processWithDefaults(init, decl.id)
|
||||
const isDefineProps = processDefineProps(ctx, init, decl.id)
|
||||
const isDefineEmits =
|
||||
!isDefineProps && processDefineEmits(init, decl.id)
|
||||
!isDefineEmits &&
|
||||
|
@ -1697,12 +1514,12 @@ export function compileScript(
|
|||
}
|
||||
|
||||
// 3.1 props destructure transform
|
||||
if (propsDestructureDecl) {
|
||||
if (ctx.propsDestructureDecl) {
|
||||
transformDestructuredProps(
|
||||
scriptSetupAst,
|
||||
s,
|
||||
startOffset,
|
||||
propsDestructuredBindings,
|
||||
ctx.propsDestructuredBindings,
|
||||
error,
|
||||
vueImportAliases
|
||||
)
|
||||
|
@ -1728,8 +1545,8 @@ export function compileScript(
|
|||
}
|
||||
|
||||
// 4. extract runtime props/emits code from setup context type
|
||||
if (propsTypeDecl) {
|
||||
extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes)
|
||||
if (ctx.propsTypeDecl) {
|
||||
extractRuntimeProps(ctx.propsTypeDecl, typeDeclaredProps, declaredTypes)
|
||||
}
|
||||
if (emitsTypeDecl) {
|
||||
extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits, error)
|
||||
|
@ -1737,9 +1554,9 @@ export function compileScript(
|
|||
|
||||
// 5. check macro args to make sure it doesn't reference setup scope
|
||||
// variables
|
||||
checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS)
|
||||
checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS)
|
||||
checkInvalidScopeReference(propsDestructureDecl, DEFINE_PROPS)
|
||||
checkInvalidScopeReference(ctx.propsRuntimeDecl, DEFINE_PROPS)
|
||||
checkInvalidScopeReference(ctx.propsRuntimeDefaults, DEFINE_PROPS)
|
||||
checkInvalidScopeReference(ctx.propsDestructureDecl, DEFINE_PROPS)
|
||||
checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_EMITS)
|
||||
checkInvalidScopeReference(optionsRuntimeDecl, DEFINE_OPTIONS)
|
||||
|
||||
|
@ -1766,8 +1583,8 @@ export function compileScript(
|
|||
if (scriptAst) {
|
||||
Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body))
|
||||
}
|
||||
if (propsRuntimeDecl) {
|
||||
for (const key of getObjectOrArrayExpressionKeys(propsRuntimeDecl)) {
|
||||
if (ctx.propsRuntimeDecl) {
|
||||
for (const key of getObjectOrArrayExpressionKeys(ctx.propsRuntimeDecl)) {
|
||||
bindingMetadata[key] = BindingTypes.PROPS
|
||||
}
|
||||
}
|
||||
|
@ -1778,13 +1595,13 @@ export function compileScript(
|
|||
bindingMetadata[key] = BindingTypes.PROPS
|
||||
}
|
||||
// props aliases
|
||||
if (propsDestructureDecl) {
|
||||
if (propsDestructureRestId) {
|
||||
bindingMetadata[propsDestructureRestId] =
|
||||
if (ctx.propsDestructureDecl) {
|
||||
if (ctx.propsDestructureRestId) {
|
||||
bindingMetadata[ctx.propsDestructureRestId] =
|
||||
BindingTypes.SETUP_REACTIVE_CONST
|
||||
}
|
||||
for (const key in propsDestructuredBindings) {
|
||||
const { local } = propsDestructuredBindings[key]
|
||||
for (const key in ctx.propsDestructuredBindings) {
|
||||
const { local } = ctx.propsDestructuredBindings[key]
|
||||
if (local !== key) {
|
||||
bindingMetadata[local] = BindingTypes.PROPS_ALIASED
|
||||
;(bindingMetadata.__propsAliases ||
|
||||
|
@ -1832,7 +1649,7 @@ export function compileScript(
|
|||
|
||||
// 9. finalize setup() argument signature
|
||||
let args = `__props`
|
||||
if (propsTypeDecl) {
|
||||
if (ctx.propsTypeDecl) {
|
||||
// mark as any and only cast on assignment
|
||||
// since the user defined complex types may be incompatible with the
|
||||
// inferred type from generated runtime declarations
|
||||
|
@ -1841,20 +1658,22 @@ export function compileScript(
|
|||
// inject user assignment of props
|
||||
// we use a default __props so that template expressions referencing props
|
||||
// can use it directly
|
||||
if (propsIdentifier) {
|
||||
if (ctx.propsIdentifier) {
|
||||
s.prependLeft(
|
||||
startOffset,
|
||||
`\nconst ${propsIdentifier} = __props${
|
||||
propsTypeDecl ? ` as ${genSetupPropsType(propsTypeDecl)}` : ``
|
||||
`\nconst ${ctx.propsIdentifier} = __props${
|
||||
ctx.propsTypeDecl ? ` as ${genSetupPropsType(ctx.propsTypeDecl)}` : ``
|
||||
};\n`
|
||||
)
|
||||
}
|
||||
if (propsDestructureRestId) {
|
||||
if (ctx.propsDestructureRestId) {
|
||||
s.prependLeft(
|
||||
startOffset,
|
||||
`\nconst ${propsDestructureRestId} = ${helper(
|
||||
`\nconst ${ctx.propsDestructureRestId} = ${helper(
|
||||
`createPropsRestProxy`
|
||||
)}(__props, ${JSON.stringify(Object.keys(propsDestructuredBindings))});\n`
|
||||
)}(__props, ${JSON.stringify(
|
||||
Object.keys(ctx.propsDestructuredBindings)
|
||||
)});\n`
|
||||
)
|
||||
}
|
||||
// inject temp variables for async context preservation
|
||||
|
@ -1864,7 +1683,9 @@ export function compileScript(
|
|||
}
|
||||
|
||||
const destructureElements =
|
||||
hasDefineExposeCall || !options.inlineTemplate ? [`expose: __expose`] : []
|
||||
ctx.hasDefineExposeCall || !options.inlineTemplate
|
||||
? [`expose: __expose`]
|
||||
: []
|
||||
if (emitIdentifier) {
|
||||
destructureElements.push(
|
||||
emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`
|
||||
|
@ -1876,7 +1697,10 @@ export function compileScript(
|
|||
|
||||
// 10. generate return statement
|
||||
let returned
|
||||
if (!options.inlineTemplate || (!sfc.template && hasDefaultExportRender)) {
|
||||
if (
|
||||
!options.inlineTemplate ||
|
||||
(!sfc.template && ctx.hasDefaultExportRender)
|
||||
) {
|
||||
// non-inline mode, or has manual render in normal <script>
|
||||
// return bindings from script and script setup
|
||||
const allBindings: Record<string, any> = {
|
||||
|
@ -1986,7 +1810,7 @@ export function compileScript(
|
|||
|
||||
// 11. finalize default export
|
||||
let runtimeOptions = ``
|
||||
if (!hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
|
||||
if (!ctx.hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
|
||||
const match = filename.match(/([^/\\]+)\.\w+$/)
|
||||
if (match) {
|
||||
runtimeOptions += `\n __name: '${match[1]}',`
|
||||
|
@ -2012,7 +1836,7 @@ export function compileScript(
|
|||
// <script setup> components are closed by default. If the user did not
|
||||
// explicitly call `defineExpose`, call expose() with no args.
|
||||
const exposeCall =
|
||||
hasDefineExposeCall || options.inlineTemplate ? `` : ` __expose();\n`
|
||||
ctx.hasDefineExposeCall || options.inlineTemplate ? `` : ` __expose();\n`
|
||||
// wrap setup code with function.
|
||||
if (isTS) {
|
||||
// for TS, make sure the exported type is still valid type with
|
||||
|
@ -2893,14 +2717,3 @@ export function hmrShouldReload(
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
export function resolveObjectKey(node: Node, computed: boolean) {
|
||||
switch (node.type) {
|
||||
case 'StringLiteral':
|
||||
case 'NumericLiteral':
|
||||
return node.value
|
||||
case 'Identifier':
|
||||
if (!computed) return node.name
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
import { Expression, Node, ObjectPattern, Program } from '@babel/types'
|
||||
import { SFCDescriptor } from '../parse'
|
||||
import { generateCodeFrame } from '@vue/shared'
|
||||
import { PropsDeclType } from './defineProps'
|
||||
import { parse as babelParse, ParserOptions, ParserPlugin } from '@babel/parser'
|
||||
import { SFCScriptCompileOptions } from '../compileScript'
|
||||
import MagicString from 'magic-string'
|
||||
|
||||
export type PropsDestructureBindings = Record<
|
||||
string, // public prop key
|
||||
{
|
||||
local: string // local identifier, may be different
|
||||
default?: Expression
|
||||
}
|
||||
>
|
||||
|
||||
export class ScriptCompileContext {
|
||||
isJS: boolean
|
||||
isTS: boolean
|
||||
|
||||
scriptAst: Program | null
|
||||
scriptSetupAst: Program | null
|
||||
|
||||
s = new MagicString(this.descriptor.source)
|
||||
|
||||
startOffset = this.descriptor.scriptSetup?.loc.start.offset
|
||||
endOffset = this.descriptor.scriptSetup?.loc.end.offset
|
||||
|
||||
scriptStartOffset = this.descriptor.script?.loc.start.offset
|
||||
scriptEndOffset = this.descriptor.script?.loc.end.offset
|
||||
|
||||
// macros presence check
|
||||
hasDefinePropsCall = false
|
||||
hasDefineEmitCall = false
|
||||
hasDefineExposeCall = false
|
||||
hasDefaultExportName = false
|
||||
hasDefaultExportRender = false
|
||||
hasDefineOptionsCall = false
|
||||
hasDefineSlotsCall = false
|
||||
hasDefineModelCall = false
|
||||
|
||||
// defineProps
|
||||
propsIdentifier: string | undefined
|
||||
propsRuntimeDecl: Node | undefined
|
||||
propsTypeDecl: PropsDeclType | undefined
|
||||
propsDestructureDecl: ObjectPattern | undefined
|
||||
propsDestructuredBindings: PropsDestructureBindings = Object.create(null)
|
||||
propsDestructureRestId: string | undefined
|
||||
propsRuntimeDefaults: Node | undefined
|
||||
|
||||
constructor(
|
||||
public descriptor: SFCDescriptor,
|
||||
public options: SFCScriptCompileOptions
|
||||
) {
|
||||
const { script, scriptSetup } = descriptor
|
||||
const scriptLang = script && script.lang
|
||||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||
|
||||
this.isJS =
|
||||
scriptLang === 'js' ||
|
||||
scriptLang === 'jsx' ||
|
||||
scriptSetupLang === 'js' ||
|
||||
scriptSetupLang === 'jsx'
|
||||
this.isTS =
|
||||
scriptLang === 'ts' ||
|
||||
scriptLang === 'tsx' ||
|
||||
scriptSetupLang === 'ts' ||
|
||||
scriptSetupLang === 'tsx'
|
||||
|
||||
// resolve parser plugins
|
||||
const plugins: ParserPlugin[] = []
|
||||
if (!this.isTS || scriptLang === 'tsx' || scriptSetupLang === 'tsx') {
|
||||
plugins.push('jsx')
|
||||
} else {
|
||||
// If don't match the case of adding jsx, should remove the jsx from the babelParserPlugins
|
||||
if (options.babelParserPlugins)
|
||||
options.babelParserPlugins = options.babelParserPlugins.filter(
|
||||
n => n !== 'jsx'
|
||||
)
|
||||
}
|
||||
if (options.babelParserPlugins) plugins.push(...options.babelParserPlugins)
|
||||
if (this.isTS) {
|
||||
plugins.push('typescript')
|
||||
if (!plugins.includes('decorators')) {
|
||||
plugins.push('decorators-legacy')
|
||||
}
|
||||
}
|
||||
|
||||
function parse(
|
||||
input: string,
|
||||
options: ParserOptions,
|
||||
offset: number
|
||||
): Program {
|
||||
try {
|
||||
return babelParse(input, options).program
|
||||
} catch (e: any) {
|
||||
e.message = `[@vue/compiler-sfc] ${e.message}\n\n${
|
||||
descriptor.filename
|
||||
}\n${generateCodeFrame(
|
||||
descriptor.source,
|
||||
e.pos + offset,
|
||||
e.pos + offset + 1
|
||||
)}`
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
this.scriptAst =
|
||||
this.descriptor.script &&
|
||||
parse(
|
||||
this.descriptor.script.content,
|
||||
{
|
||||
plugins,
|
||||
sourceType: 'module'
|
||||
},
|
||||
this.scriptStartOffset!
|
||||
)
|
||||
|
||||
this.scriptSetupAst =
|
||||
this.descriptor.scriptSetup &&
|
||||
parse(
|
||||
this.descriptor.scriptSetup!.content,
|
||||
{
|
||||
plugins: [...plugins, 'topLevelAwait'],
|
||||
sourceType: 'module'
|
||||
},
|
||||
this.startOffset!
|
||||
)
|
||||
}
|
||||
|
||||
getString(node: Node, scriptSetup = true): string {
|
||||
const block = scriptSetup
|
||||
? this.descriptor.scriptSetup!
|
||||
: this.descriptor.script!
|
||||
return block.content.slice(node.start!, node.end!)
|
||||
}
|
||||
|
||||
error(
|
||||
msg: string,
|
||||
node: Node,
|
||||
end: number = node.end! + this.startOffset!
|
||||
): never {
|
||||
throw new Error(
|
||||
`[@vue/compiler-sfc] ${msg}\n\n${
|
||||
this.descriptor.filename
|
||||
}\n${generateCodeFrame(
|
||||
this.descriptor.source,
|
||||
node.start! + this.startOffset!,
|
||||
end
|
||||
)}`
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
import {
|
||||
Node,
|
||||
LVal,
|
||||
Identifier,
|
||||
TSTypeLiteral,
|
||||
TSInterfaceBody
|
||||
} from '@babel/types'
|
||||
import { isCallOf } from '@vue/compiler-dom'
|
||||
import { ScriptCompileContext } from './context'
|
||||
import { resolveObjectKey } from './utils'
|
||||
import { resolveQualifiedType } from './resolveType'
|
||||
|
||||
export const DEFINE_PROPS = 'defineProps'
|
||||
export const WITH_DEFAULTS = 'withDefaults'
|
||||
|
||||
export type PropsDeclType = (TSTypeLiteral | TSInterfaceBody) & {
|
||||
__fromNormalScript?: boolean | null
|
||||
}
|
||||
|
||||
export function processDefineProps(
|
||||
ctx: ScriptCompileContext,
|
||||
node: Node,
|
||||
declId?: LVal
|
||||
) {
|
||||
if (!isCallOf(node, DEFINE_PROPS)) {
|
||||
return processWithDefaults(ctx, node, declId)
|
||||
}
|
||||
|
||||
if (ctx.hasDefinePropsCall) {
|
||||
ctx.error(`duplicate ${DEFINE_PROPS}() call`, node)
|
||||
}
|
||||
ctx.hasDefinePropsCall = true
|
||||
|
||||
ctx.propsRuntimeDecl = node.arguments[0]
|
||||
|
||||
// call has type parameters - infer runtime types from it
|
||||
if (node.typeParameters) {
|
||||
if (ctx.propsRuntimeDecl) {
|
||||
ctx.error(
|
||||
`${DEFINE_PROPS}() cannot accept both type and non-type arguments ` +
|
||||
`at the same time. Use one or the other.`,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (declId) {
|
||||
// handle props destructure
|
||||
if (declId.type === 'ObjectPattern') {
|
||||
ctx.propsDestructureDecl = declId
|
||||
for (const prop of declId.properties) {
|
||||
if (prop.type === 'ObjectProperty') {
|
||||
const propKey = resolveObjectKey(prop.key, prop.computed)
|
||||
|
||||
if (!propKey) {
|
||||
ctx.error(
|
||||
`${DEFINE_PROPS}() destructure cannot use computed key.`,
|
||||
prop.key
|
||||
)
|
||||
}
|
||||
|
||||
if (prop.value.type === 'AssignmentPattern') {
|
||||
// default value { foo = 123 }
|
||||
const { left, right } = prop.value
|
||||
if (left.type !== 'Identifier') {
|
||||
ctx.error(
|
||||
`${DEFINE_PROPS}() destructure does not support nested patterns.`,
|
||||
left
|
||||
)
|
||||
}
|
||||
// store default value
|
||||
ctx.propsDestructuredBindings[propKey] = {
|
||||
local: left.name,
|
||||
default: right
|
||||
}
|
||||
} else if (prop.value.type === 'Identifier') {
|
||||
// simple destructure
|
||||
ctx.propsDestructuredBindings[propKey] = {
|
||||
local: prop.value.name
|
||||
}
|
||||
} else {
|
||||
ctx.error(
|
||||
`${DEFINE_PROPS}() destructure does not support nested patterns.`,
|
||||
prop.value
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// rest spread
|
||||
ctx.propsDestructureRestId = (prop.argument as Identifier).name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.propsIdentifier = ctx.getString(declId)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function processWithDefaults(
|
||||
ctx: ScriptCompileContext,
|
||||
node: Node,
|
||||
declId?: LVal
|
||||
): boolean {
|
||||
if (!isCallOf(node, WITH_DEFAULTS)) {
|
||||
return false
|
||||
}
|
||||
if (processDefineProps(ctx, node.arguments[0], declId)) {
|
||||
if (ctx.propsRuntimeDecl) {
|
||||
ctx.error(
|
||||
`${WITH_DEFAULTS} can only be used with type-based ` +
|
||||
`${DEFINE_PROPS} declaration.`,
|
||||
node
|
||||
)
|
||||
}
|
||||
if (ctx.propsDestructureDecl) {
|
||||
ctx.error(
|
||||
`${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` +
|
||||
`Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`,
|
||||
node.callee
|
||||
)
|
||||
}
|
||||
ctx.propsRuntimeDefaults = node.arguments[1]
|
||||
if (!ctx.propsRuntimeDefaults) {
|
||||
ctx.error(`The 2nd argument of ${WITH_DEFAULTS} is required.`, node)
|
||||
}
|
||||
} else {
|
||||
ctx.error(
|
||||
`${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,
|
||||
node.arguments[0] || node
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
import { Node, Statement, TSInterfaceBody, TSTypeElement } from '@babel/types'
|
||||
import { FromNormalScript } from './utils'
|
||||
import { ScriptCompileContext } from './context'
|
||||
|
||||
/**
|
||||
* Resolve a type Node into
|
||||
*/
|
||||
export function resolveType() {}
|
||||
|
||||
export function resolveQualifiedType(
|
||||
ctx: ScriptCompileContext,
|
||||
node: Node,
|
||||
qualifier: (node: Node) => boolean
|
||||
): Node | undefined {
|
||||
if (qualifier(node)) {
|
||||
return 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
|
||||
}
|
||||
;(qualified as FromNormalScript<Node>).__fromNormalScript =
|
||||
scriptAst && i >= scriptSetupAst!.body.length
|
||||
return qualified
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isQualifiedType(
|
||||
node: Node,
|
||||
qualifier: (node: Node) => boolean,
|
||||
refName: String
|
||||
): 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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return cache
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import { Node } from '@babel/types'
|
||||
|
||||
export type FromNormalScript<T> = T & { __fromNormalScript?: boolean | null }
|
||||
|
||||
export function resolveObjectKey(node: Node, computed: boolean) {
|
||||
switch (node.type) {
|
||||
case 'StringLiteral':
|
||||
case 'NumericLiteral':
|
||||
return node.value
|
||||
case 'Identifier':
|
||||
if (!computed) return node.name
|
||||
}
|
||||
return undefined
|
||||
}
|
Loading…
Reference in New Issue