mirror of https://github.com/vuejs/core.git
1302 lines
40 KiB
TypeScript
1302 lines
40 KiB
TypeScript
import {
|
|
BindingTypes,
|
|
UNREF,
|
|
isFunctionType,
|
|
unwrapTSNode,
|
|
walkIdentifiers,
|
|
} from '@vue/compiler-dom'
|
|
import {
|
|
DEFAULT_FILENAME,
|
|
type SFCDescriptor,
|
|
type SFCScriptBlock,
|
|
} from './parse'
|
|
import type { ParserPlugin } from '@babel/parser'
|
|
import { generateCodeFrame } from '@vue/shared'
|
|
import type {
|
|
ArrayPattern,
|
|
CallExpression,
|
|
Declaration,
|
|
ExportSpecifier,
|
|
Identifier,
|
|
Node,
|
|
ObjectPattern,
|
|
Statement,
|
|
} from '@babel/types'
|
|
import { walk } from 'estree-walker'
|
|
import type { RawSourceMap } from 'source-map-js'
|
|
import {
|
|
normalScriptDefaultVar,
|
|
processNormalScript,
|
|
} from './script/normalScript'
|
|
import { CSS_VARS_HELPER, genCssVarsCode } from './style/cssVars'
|
|
import {
|
|
type SFCTemplateCompileOptions,
|
|
compileTemplate,
|
|
} from './compileTemplate'
|
|
import { warnOnce } from './warn'
|
|
import { transformDestructuredProps } from './script/definePropsDestructure'
|
|
import { ScriptCompileContext } from './script/context'
|
|
import {
|
|
DEFINE_PROPS,
|
|
WITH_DEFAULTS,
|
|
genRuntimeProps,
|
|
processDefineProps,
|
|
} from './script/defineProps'
|
|
import {
|
|
DEFINE_EMITS,
|
|
genRuntimeEmits,
|
|
processDefineEmits,
|
|
} from './script/defineEmits'
|
|
import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
|
|
import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
|
|
import { DEFINE_SLOTS, processDefineSlots } from './script/defineSlots'
|
|
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
|
|
import { getImportedName, isCallOf, isLiteralNode } from './script/utils'
|
|
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
|
|
import { isImportUsed } from './script/importUsageCheck'
|
|
import { processAwait } from './script/topLevelAwait'
|
|
|
|
export interface SFCScriptCompileOptions {
|
|
/**
|
|
* Scope ID for prefixing injected CSS variables.
|
|
* This must be consistent with the `id` passed to `compileStyle`.
|
|
*/
|
|
id: string
|
|
/**
|
|
* Production mode. Used to determine whether to generate hashed CSS variables
|
|
*/
|
|
isProd?: boolean
|
|
/**
|
|
* Enable/disable source map. Defaults to true.
|
|
*/
|
|
sourceMap?: boolean
|
|
/**
|
|
* https://babeljs.io/docs/en/babel-parser#plugins
|
|
*/
|
|
babelParserPlugins?: ParserPlugin[]
|
|
/**
|
|
* A list of files to parse for global types to be made available for type
|
|
* resolving in SFC macros. The list must be fully resolved file system paths.
|
|
*/
|
|
globalTypeFiles?: string[]
|
|
/**
|
|
* Compile the template and inline the resulting render function
|
|
* directly inside setup().
|
|
* - Only affects `<script setup>`
|
|
* - This should only be used in production because it prevents the template
|
|
* from being hot-reloaded separately from component state.
|
|
*/
|
|
inlineTemplate?: boolean
|
|
/**
|
|
* Generate the final component as a variable instead of default export.
|
|
* This is useful in e.g. @vitejs/plugin-vue where the script needs to be
|
|
* placed inside the main module.
|
|
*/
|
|
genDefaultAs?: string
|
|
/**
|
|
* Options for template compilation when inlining. Note these are options that
|
|
* would normally be passed to `compiler-sfc`'s own `compileTemplate()`, not
|
|
* options passed to `compiler-dom`.
|
|
*/
|
|
templateOptions?: Partial<SFCTemplateCompileOptions>
|
|
/**
|
|
* Hoist <script setup> static constants.
|
|
* - Only enables when one `<script setup>` exists.
|
|
* @default true
|
|
*/
|
|
hoistStatic?: boolean
|
|
/**
|
|
* Set to `false` to disable reactive destructure for `defineProps` (pre-3.5
|
|
* behavior), or set to `'error'` to throw hard error on props destructures.
|
|
* @default true
|
|
*/
|
|
propsDestructure?: boolean | 'error'
|
|
/**
|
|
* File system access methods to be used when resolving types
|
|
* imported in SFC macros. Defaults to ts.sys in Node.js, can be overwritten
|
|
* to use a virtual file system for use in browsers (e.g. in REPLs)
|
|
*/
|
|
fs?: {
|
|
fileExists(file: string): boolean
|
|
readFile(file: string): string | undefined
|
|
realpath?(file: string): string
|
|
}
|
|
/**
|
|
* Transform Vue SFCs into custom elements.
|
|
*/
|
|
customElement?: boolean | ((filename: string) => boolean)
|
|
/**
|
|
* Force to use of Vapor mode.
|
|
*/
|
|
vapor?: boolean
|
|
}
|
|
|
|
export interface ImportBinding {
|
|
isType: boolean
|
|
imported: string
|
|
local: string
|
|
source: string
|
|
isFromSetup: boolean
|
|
isUsedInTemplate: boolean
|
|
}
|
|
|
|
const MACROS = [
|
|
DEFINE_PROPS,
|
|
DEFINE_EMITS,
|
|
DEFINE_EXPOSE,
|
|
DEFINE_OPTIONS,
|
|
DEFINE_SLOTS,
|
|
DEFINE_MODEL,
|
|
WITH_DEFAULTS,
|
|
]
|
|
|
|
/**
|
|
* Compile `<script setup>`
|
|
* It requires the whole SFC descriptor because we need to handle and merge
|
|
* normal `<script>` + `<script setup>` if both are present.
|
|
*/
|
|
export function compileScript(
|
|
sfc: SFCDescriptor,
|
|
options: SFCScriptCompileOptions,
|
|
): SFCScriptBlock {
|
|
if (!options.id) {
|
|
warnOnce(
|
|
`compileScript now requires passing the \`id\` option.\n` +
|
|
`Upgrade your vite or vue-loader version for compatibility with ` +
|
|
`the latest experimental proposals.`,
|
|
)
|
|
}
|
|
|
|
const ctx = new ScriptCompileContext(sfc, options)
|
|
const { script, scriptSetup, source, filename } = sfc
|
|
const hoistStatic = options.hoistStatic !== false && !script
|
|
const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
|
|
const scriptLang = script && script.lang
|
|
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
|
const vapor = sfc.vapor || options.vapor
|
|
|
|
let refBindings: string[] | undefined
|
|
|
|
if (!scriptSetup) {
|
|
if (!script) {
|
|
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
|
}
|
|
// normal <script> only
|
|
return processNormalScript(ctx, scopeId)
|
|
}
|
|
|
|
if (script && scriptLang !== scriptSetupLang) {
|
|
throw new Error(
|
|
`[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
|
|
`language type.`,
|
|
)
|
|
}
|
|
|
|
if (scriptSetupLang && !ctx.isJS && !ctx.isTS) {
|
|
// do not process non js/ts script blocks
|
|
return scriptSetup
|
|
}
|
|
|
|
// metadata that needs to be returned
|
|
// const ctx.bindingMetadata: BindingMetadata = {}
|
|
const scriptBindings: Record<string, BindingTypes> = Object.create(null)
|
|
const setupBindings: Record<string, BindingTypes> = Object.create(null)
|
|
|
|
let defaultExport: Node | undefined
|
|
let hasAwait = false
|
|
let hasInlinedSsrRenderFn = false
|
|
|
|
// string offsets
|
|
const startOffset = ctx.startOffset!
|
|
const endOffset = ctx.endOffset!
|
|
const scriptStartOffset = script && script.loc.start.offset
|
|
const scriptEndOffset = script && script.loc.end.offset
|
|
|
|
function hoistNode(node: Statement) {
|
|
const start = node.start! + startOffset
|
|
let end = node.end! + startOffset
|
|
// locate comment
|
|
if (node.trailingComments && node.trailingComments.length > 0) {
|
|
const lastCommentNode =
|
|
node.trailingComments[node.trailingComments.length - 1]
|
|
end = lastCommentNode.end! + startOffset
|
|
}
|
|
// locate the end of whitespace between this statement and the next
|
|
while (end <= source.length) {
|
|
if (!/\s/.test(source.charAt(end))) {
|
|
break
|
|
}
|
|
end++
|
|
}
|
|
ctx.s.move(start, end, 0)
|
|
}
|
|
|
|
function registerUserImport(
|
|
source: string,
|
|
local: string,
|
|
imported: string,
|
|
isType: boolean,
|
|
isFromSetup: boolean,
|
|
needTemplateUsageCheck: boolean,
|
|
) {
|
|
// template usage check is only needed in non-inline mode, so we can skip
|
|
// the work if inlineTemplate is true.
|
|
let isUsedInTemplate = needTemplateUsageCheck
|
|
if (
|
|
needTemplateUsageCheck &&
|
|
ctx.isTS &&
|
|
sfc.template &&
|
|
!sfc.template.src &&
|
|
!sfc.template.lang
|
|
) {
|
|
isUsedInTemplate = isImportUsed(local, sfc)
|
|
}
|
|
|
|
ctx.userImports[local] = {
|
|
isType,
|
|
imported,
|
|
local,
|
|
source,
|
|
isFromSetup,
|
|
isUsedInTemplate,
|
|
}
|
|
}
|
|
|
|
function checkInvalidScopeReference(node: Node | undefined, method: string) {
|
|
if (!node) return
|
|
walkIdentifiers(node, id => {
|
|
const binding = setupBindings[id.name]
|
|
if (binding && binding !== BindingTypes.LITERAL_CONST) {
|
|
ctx.error(
|
|
`\`${method}()\` in <script setup> cannot reference locally ` +
|
|
`declared variables because it will be hoisted outside of the ` +
|
|
`setup() function. If your component options require initialization ` +
|
|
`in the module scope, use a separate normal <script> to export ` +
|
|
`the options instead.`,
|
|
id,
|
|
)
|
|
}
|
|
})
|
|
}
|
|
|
|
const scriptAst = ctx.scriptAst
|
|
const scriptSetupAst = ctx.scriptSetupAst!
|
|
|
|
// 1.1 walk import declarations of <script>
|
|
if (scriptAst) {
|
|
for (const node of scriptAst.body) {
|
|
if (node.type === 'ImportDeclaration') {
|
|
// record imports for dedupe
|
|
for (const specifier of node.specifiers) {
|
|
const imported = getImportedName(specifier)
|
|
registerUserImport(
|
|
node.source.value,
|
|
specifier.local.name,
|
|
imported,
|
|
node.importKind === 'type' ||
|
|
(specifier.type === 'ImportSpecifier' &&
|
|
specifier.importKind === 'type'),
|
|
false,
|
|
!options.inlineTemplate,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1.2 walk import declarations of <script setup>
|
|
for (const node of scriptSetupAst.body) {
|
|
if (node.type === 'ImportDeclaration') {
|
|
// import declarations are moved to top
|
|
hoistNode(node)
|
|
|
|
// dedupe imports
|
|
let removed = 0
|
|
const removeSpecifier = (i: number) => {
|
|
const removeLeft = i > removed
|
|
removed++
|
|
const current = node.specifiers[i]
|
|
const next = node.specifiers[i + 1]
|
|
ctx.s.remove(
|
|
removeLeft
|
|
? node.specifiers[i - 1].end! + startOffset
|
|
: current.start! + startOffset,
|
|
next && !removeLeft
|
|
? next.start! + startOffset
|
|
: current.end! + startOffset,
|
|
)
|
|
}
|
|
|
|
for (let i = 0; i < node.specifiers.length; i++) {
|
|
const specifier = node.specifiers[i]
|
|
const local = specifier.local.name
|
|
const imported = getImportedName(specifier)
|
|
const source = node.source.value
|
|
const existing = ctx.userImports[local]
|
|
if (source === 'vue' && MACROS.includes(imported)) {
|
|
if (local === imported) {
|
|
warnOnce(
|
|
`\`${imported}\` is a compiler macro and no longer needs to be imported.`,
|
|
)
|
|
} else {
|
|
ctx.error(
|
|
`\`${imported}\` is a compiler macro and cannot be aliased to ` +
|
|
`a different name.`,
|
|
specifier,
|
|
)
|
|
}
|
|
removeSpecifier(i)
|
|
} else if (existing) {
|
|
if (existing.source === source && existing.imported === imported) {
|
|
// already imported in <script setup>, dedupe
|
|
removeSpecifier(i)
|
|
} else {
|
|
ctx.error(
|
|
`different imports aliased to same local name.`,
|
|
specifier,
|
|
)
|
|
}
|
|
} else {
|
|
registerUserImport(
|
|
source,
|
|
local,
|
|
imported,
|
|
node.importKind === 'type' ||
|
|
(specifier.type === 'ImportSpecifier' &&
|
|
specifier.importKind === 'type'),
|
|
true,
|
|
!options.inlineTemplate,
|
|
)
|
|
}
|
|
}
|
|
if (node.specifiers.length && removed === node.specifiers.length) {
|
|
ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1.3 resolve possible user import alias of `ref` and `reactive`
|
|
const vueImportAliases: Record<string, string> = {}
|
|
for (const key in ctx.userImports) {
|
|
const { source, imported, local } = ctx.userImports[key]
|
|
if (source === 'vue') vueImportAliases[imported] = local
|
|
}
|
|
|
|
// 2.1 process normal <script> body
|
|
if (script && scriptAst) {
|
|
for (const node of scriptAst.body) {
|
|
if (node.type === 'ExportDefaultDeclaration') {
|
|
// export default
|
|
defaultExport = node
|
|
|
|
// check if user has manually specified `name` or 'render` option in
|
|
// export default
|
|
// if has name, skip name inference
|
|
// if has render and no template, generate return object instead of
|
|
// empty render function (#4980)
|
|
let optionProperties
|
|
if (defaultExport.declaration.type === 'ObjectExpression') {
|
|
optionProperties = defaultExport.declaration.properties
|
|
} else if (
|
|
defaultExport.declaration.type === 'CallExpression' &&
|
|
defaultExport.declaration.arguments[0] &&
|
|
defaultExport.declaration.arguments[0].type === 'ObjectExpression'
|
|
) {
|
|
optionProperties = defaultExport.declaration.arguments[0].properties
|
|
}
|
|
if (optionProperties) {
|
|
for (const p of optionProperties) {
|
|
if (
|
|
p.type === 'ObjectProperty' &&
|
|
p.key.type === 'Identifier' &&
|
|
p.key.name === 'name'
|
|
) {
|
|
ctx.hasDefaultExportName = true
|
|
}
|
|
if (
|
|
(p.type === 'ObjectMethod' || p.type === 'ObjectProperty') &&
|
|
p.key.type === 'Identifier' &&
|
|
p.key.name === 'render'
|
|
) {
|
|
// TODO warn when we provide a better way to do it?
|
|
ctx.hasDefaultExportRender = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// export default { ... } --> const __default__ = { ... }
|
|
const start = node.start! + scriptStartOffset!
|
|
const end = node.declaration.start! + scriptStartOffset!
|
|
ctx.s.overwrite(start, end, `const ${normalScriptDefaultVar} = `)
|
|
} else if (node.type === 'ExportNamedDeclaration') {
|
|
const defaultSpecifier = node.specifiers.find(
|
|
s =>
|
|
s.exported.type === 'Identifier' && s.exported.name === 'default',
|
|
) as ExportSpecifier
|
|
if (defaultSpecifier) {
|
|
defaultExport = node
|
|
// 1. remove specifier
|
|
if (node.specifiers.length > 1) {
|
|
ctx.s.remove(
|
|
defaultSpecifier.start! + scriptStartOffset!,
|
|
defaultSpecifier.end! + scriptStartOffset!,
|
|
)
|
|
} else {
|
|
ctx.s.remove(
|
|
node.start! + scriptStartOffset!,
|
|
node.end! + scriptStartOffset!,
|
|
)
|
|
}
|
|
if (node.source) {
|
|
// export { x as default } from './x'
|
|
// rewrite to `import { x as __default__ } from './x'` and
|
|
// add to top
|
|
ctx.s.prepend(
|
|
`import { ${defaultSpecifier.local.name} as ${normalScriptDefaultVar} } from '${node.source.value}'\n`,
|
|
)
|
|
} else {
|
|
// export { x as default }
|
|
// rewrite to `const __default__ = x` and move to end
|
|
ctx.s.appendLeft(
|
|
scriptEndOffset!,
|
|
`\nconst ${normalScriptDefaultVar} = ${defaultSpecifier.local.name}\n`,
|
|
)
|
|
}
|
|
}
|
|
if (node.declaration) {
|
|
walkDeclaration(
|
|
'script',
|
|
node.declaration,
|
|
scriptBindings,
|
|
vueImportAliases,
|
|
hoistStatic,
|
|
)
|
|
}
|
|
} else if (
|
|
(node.type === 'VariableDeclaration' ||
|
|
node.type === 'FunctionDeclaration' ||
|
|
node.type === 'ClassDeclaration' ||
|
|
node.type === 'TSEnumDeclaration') &&
|
|
!node.declare
|
|
) {
|
|
walkDeclaration(
|
|
'script',
|
|
node,
|
|
scriptBindings,
|
|
vueImportAliases,
|
|
hoistStatic,
|
|
)
|
|
}
|
|
}
|
|
|
|
// <script> after <script setup>
|
|
// we need to move the block up so that `const __default__` is
|
|
// declared before being used in the actual component definition
|
|
if (scriptStartOffset! > startOffset) {
|
|
// if content doesn't end with newline, add one
|
|
if (!/\n$/.test(script.content.trim())) {
|
|
ctx.s.appendLeft(scriptEndOffset!, `\n`)
|
|
}
|
|
ctx.s.move(scriptStartOffset!, scriptEndOffset!, 0)
|
|
}
|
|
}
|
|
|
|
// 2.2 process <script setup> body
|
|
for (const node of scriptSetupAst.body) {
|
|
if (node.type === 'ExpressionStatement') {
|
|
const expr = unwrapTSNode(node.expression)
|
|
// process `defineProps` and `defineEmit(s)` calls
|
|
if (
|
|
processDefineProps(ctx, expr) ||
|
|
processDefineEmits(ctx, expr) ||
|
|
processDefineOptions(ctx, expr) ||
|
|
processDefineSlots(ctx, expr)
|
|
) {
|
|
ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
|
|
} else if (processDefineExpose(ctx, expr)) {
|
|
// defineExpose({}) -> expose({})
|
|
const callee = (expr as CallExpression).callee
|
|
ctx.s.overwrite(
|
|
callee.start! + startOffset,
|
|
callee.end! + startOffset,
|
|
'__expose',
|
|
)
|
|
} else {
|
|
processDefineModel(ctx, expr)
|
|
}
|
|
}
|
|
|
|
if (node.type === 'VariableDeclaration' && !node.declare) {
|
|
const total = node.declarations.length
|
|
let left = total
|
|
let lastNonRemoved: number | undefined
|
|
|
|
for (let i = 0; i < total; i++) {
|
|
const decl = node.declarations[i]
|
|
const init = decl.init && unwrapTSNode(decl.init)
|
|
if (init) {
|
|
if (processDefineOptions(ctx, init)) {
|
|
ctx.error(
|
|
`${DEFINE_OPTIONS}() has no returning value, it cannot be assigned.`,
|
|
node,
|
|
)
|
|
}
|
|
|
|
// defineProps
|
|
const isDefineProps = processDefineProps(ctx, init, decl.id)
|
|
if (ctx.propsDestructureRestId) {
|
|
setupBindings[ctx.propsDestructureRestId] =
|
|
BindingTypes.SETUP_REACTIVE_CONST
|
|
}
|
|
|
|
// defineEmits
|
|
const isDefineEmits =
|
|
!isDefineProps && processDefineEmits(ctx, init, decl.id)
|
|
!isDefineEmits &&
|
|
(processDefineSlots(ctx, init, decl.id) ||
|
|
processDefineModel(ctx, init, decl.id))
|
|
|
|
if (
|
|
isDefineProps &&
|
|
!ctx.propsDestructureRestId &&
|
|
ctx.propsDestructureDecl
|
|
) {
|
|
if (left === 1) {
|
|
ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
|
|
} else {
|
|
let start = decl.start! + startOffset
|
|
let end = decl.end! + startOffset
|
|
if (i === total - 1) {
|
|
// last one, locate the end of the last one that is not removed
|
|
// if we arrive at this branch, there must have been a
|
|
// non-removed decl before us, so lastNonRemoved is non-null.
|
|
start = node.declarations[lastNonRemoved!].end! + startOffset
|
|
} else {
|
|
// not the last one, locate the start of the next
|
|
end = node.declarations[i + 1].start! + startOffset
|
|
}
|
|
ctx.s.remove(start, end)
|
|
left--
|
|
}
|
|
} else if (isDefineEmits) {
|
|
ctx.s.overwrite(
|
|
startOffset + init.start!,
|
|
startOffset + init.end!,
|
|
'__emit',
|
|
)
|
|
} else {
|
|
lastNonRemoved = i
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let isAllLiteral = false
|
|
// walk declarations to record declared bindings
|
|
if (
|
|
(node.type === 'VariableDeclaration' ||
|
|
node.type === 'FunctionDeclaration' ||
|
|
node.type === 'ClassDeclaration' ||
|
|
node.type === 'TSEnumDeclaration') &&
|
|
!node.declare
|
|
) {
|
|
isAllLiteral = walkDeclaration(
|
|
'scriptSetup',
|
|
node,
|
|
setupBindings,
|
|
vueImportAliases,
|
|
hoistStatic,
|
|
!!ctx.propsDestructureDecl,
|
|
)
|
|
}
|
|
|
|
// hoist literal constants
|
|
if (hoistStatic && isAllLiteral) {
|
|
hoistNode(node)
|
|
}
|
|
|
|
// walk statements & named exports / variable declarations for top level
|
|
// await
|
|
if (
|
|
(node.type === 'VariableDeclaration' && !node.declare) ||
|
|
node.type.endsWith('Statement')
|
|
) {
|
|
const scope: Statement[][] = [scriptSetupAst.body]
|
|
walk(node, {
|
|
enter(child: Node, parent: Node | null) {
|
|
if (isFunctionType(child)) {
|
|
this.skip()
|
|
}
|
|
if (child.type === 'BlockStatement') {
|
|
scope.push(child.body)
|
|
}
|
|
if (child.type === 'AwaitExpression') {
|
|
hasAwait = true
|
|
// if the await expression is an expression statement and
|
|
// - is in the root scope
|
|
// - or is not the first statement in a nested block scope
|
|
// then it needs a semicolon before the generated code.
|
|
const currentScope = scope[scope.length - 1]
|
|
const needsSemi = currentScope.some((n, i) => {
|
|
return (
|
|
(scope.length === 1 || i > 0) &&
|
|
n.type === 'ExpressionStatement' &&
|
|
n.start === child.start
|
|
)
|
|
})
|
|
processAwait(
|
|
ctx,
|
|
child,
|
|
needsSemi,
|
|
parent!.type === 'ExpressionStatement',
|
|
)
|
|
}
|
|
},
|
|
exit(node: Node) {
|
|
if (node.type === 'BlockStatement') scope.pop()
|
|
},
|
|
})
|
|
}
|
|
|
|
if (
|
|
(node.type === 'ExportNamedDeclaration' && node.exportKind !== 'type') ||
|
|
node.type === 'ExportAllDeclaration' ||
|
|
node.type === 'ExportDefaultDeclaration'
|
|
) {
|
|
ctx.error(
|
|
`<script setup> cannot contain ES module exports. ` +
|
|
`If you are using a previous version of <script setup>, please ` +
|
|
`consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`,
|
|
node,
|
|
)
|
|
}
|
|
|
|
if (ctx.isTS) {
|
|
// move all Type declarations to outer scope
|
|
if (
|
|
node.type.startsWith('TS') ||
|
|
(node.type === 'ExportNamedDeclaration' &&
|
|
node.exportKind === 'type') ||
|
|
(node.type === 'VariableDeclaration' && node.declare)
|
|
) {
|
|
if (node.type !== 'TSEnumDeclaration') {
|
|
hoistNode(node)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3 props destructure transform
|
|
if (ctx.propsDestructureDecl) {
|
|
transformDestructuredProps(ctx, vueImportAliases)
|
|
}
|
|
|
|
// 4. check macro args to make sure it doesn't reference setup scope
|
|
// variables
|
|
checkInvalidScopeReference(ctx.propsRuntimeDecl, DEFINE_PROPS)
|
|
checkInvalidScopeReference(ctx.propsRuntimeDefaults, DEFINE_PROPS)
|
|
checkInvalidScopeReference(ctx.propsDestructureDecl, DEFINE_PROPS)
|
|
checkInvalidScopeReference(ctx.emitsRuntimeDecl, DEFINE_EMITS)
|
|
checkInvalidScopeReference(ctx.optionsRuntimeDecl, DEFINE_OPTIONS)
|
|
for (const { runtimeOptionNodes } of Object.values(ctx.modelDecls)) {
|
|
for (const node of runtimeOptionNodes) {
|
|
checkInvalidScopeReference(node, DEFINE_MODEL)
|
|
}
|
|
}
|
|
|
|
// 5. remove non-script content
|
|
if (script) {
|
|
if (startOffset < scriptStartOffset!) {
|
|
// <script setup> before <script>
|
|
ctx.s.remove(0, startOffset)
|
|
ctx.s.remove(endOffset, scriptStartOffset!)
|
|
ctx.s.remove(scriptEndOffset!, source.length)
|
|
} else {
|
|
// <script> before <script setup>
|
|
ctx.s.remove(0, scriptStartOffset!)
|
|
ctx.s.remove(scriptEndOffset!, startOffset)
|
|
ctx.s.remove(endOffset, source.length)
|
|
}
|
|
} else {
|
|
// only <script setup>
|
|
ctx.s.remove(0, startOffset)
|
|
ctx.s.remove(endOffset, source.length)
|
|
}
|
|
|
|
// 6. analyze binding metadata
|
|
// `defineProps` & `defineModel` also register props bindings
|
|
if (scriptAst) {
|
|
Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
|
|
}
|
|
for (const [key, { isType, imported, source }] of Object.entries(
|
|
ctx.userImports,
|
|
)) {
|
|
if (isType) continue
|
|
ctx.bindingMetadata[key] =
|
|
imported === '*' ||
|
|
(imported === 'default' && source.endsWith('.vue')) ||
|
|
source === 'vue'
|
|
? BindingTypes.SETUP_CONST
|
|
: BindingTypes.SETUP_MAYBE_REF
|
|
}
|
|
for (const key in scriptBindings) {
|
|
ctx.bindingMetadata[key] = scriptBindings[key]
|
|
}
|
|
for (const key in setupBindings) {
|
|
ctx.bindingMetadata[key] = setupBindings[key]
|
|
}
|
|
// known ref bindings
|
|
if (refBindings) {
|
|
for (const key of refBindings) {
|
|
ctx.bindingMetadata[key] = BindingTypes.SETUP_REF
|
|
}
|
|
}
|
|
|
|
// 7. inject `useCssVars` calls
|
|
if (
|
|
sfc.cssVars.length &&
|
|
// no need to do this when targeting SSR
|
|
!options.templateOptions?.ssr
|
|
) {
|
|
ctx.helperImports.add(CSS_VARS_HELPER)
|
|
ctx.helperImports.add('unref')
|
|
ctx.s.prependLeft(
|
|
startOffset,
|
|
`\n${genCssVarsCode(
|
|
sfc.cssVars,
|
|
ctx.bindingMetadata,
|
|
scopeId,
|
|
!!options.isProd,
|
|
)}\n`,
|
|
)
|
|
}
|
|
|
|
// 8. finalize setup() argument signature
|
|
let args = `__props`
|
|
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
|
|
args += `: any`
|
|
}
|
|
// inject user assignment of props
|
|
// we use a default __props so that template expressions referencing props
|
|
// can use it directly
|
|
if (ctx.propsDecl) {
|
|
if (ctx.propsDestructureRestId) {
|
|
ctx.s.overwrite(
|
|
startOffset + ctx.propsCall!.start!,
|
|
startOffset + ctx.propsCall!.end!,
|
|
`${ctx.helper(`createPropsRestProxy`)}(__props, ${JSON.stringify(
|
|
Object.keys(ctx.propsDestructuredBindings),
|
|
)})`,
|
|
)
|
|
ctx.s.overwrite(
|
|
startOffset + ctx.propsDestructureDecl!.start!,
|
|
startOffset + ctx.propsDestructureDecl!.end!,
|
|
ctx.propsDestructureRestId,
|
|
)
|
|
} else if (!ctx.propsDestructureDecl) {
|
|
ctx.s.overwrite(
|
|
startOffset + ctx.propsCall!.start!,
|
|
startOffset + ctx.propsCall!.end!,
|
|
'__props',
|
|
)
|
|
}
|
|
}
|
|
|
|
// inject temp variables for async context preservation
|
|
if (hasAwait) {
|
|
const any = ctx.isTS ? `: any` : ``
|
|
ctx.s.prependLeft(startOffset, `\nlet __temp${any}, __restore${any}\n`)
|
|
}
|
|
|
|
const destructureElements =
|
|
ctx.hasDefineExposeCall || !options.inlineTemplate
|
|
? [`expose: __expose`]
|
|
: []
|
|
if (ctx.emitDecl) {
|
|
destructureElements.push(`emit: __emit`)
|
|
}
|
|
if (destructureElements.length) {
|
|
args += `, { ${destructureElements.join(', ')} }`
|
|
}
|
|
|
|
// 9. generate return statement
|
|
let returned
|
|
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> = {
|
|
...scriptBindings,
|
|
...setupBindings,
|
|
}
|
|
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 &&
|
|
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
|
|
returned += `get ${key}() { return ${key} }, `
|
|
} else if (ctx.bindingMetadata[key] === BindingTypes.SETUP_LET) {
|
|
// local let binding, also add setter
|
|
const setArg = key === 'v' ? `_v` : `v`
|
|
returned +=
|
|
`get ${key}() { return ${key} }, ` +
|
|
`set ${key}(${setArg}) { ${key} = ${setArg} }, `
|
|
} else {
|
|
returned += `${key}, `
|
|
}
|
|
}
|
|
returned = returned.replace(/, $/, '') + ` }`
|
|
} else {
|
|
// inline mode
|
|
if (sfc.template && !sfc.template.src) {
|
|
if (options.templateOptions && options.templateOptions.ssr) {
|
|
hasInlinedSsrRenderFn = true
|
|
}
|
|
// inline render function mode - we are going to compile the template and
|
|
// inline it right here
|
|
const { code, preamble, tips, errors, helpers } = compileTemplate({
|
|
filename,
|
|
ast: sfc.template.ast,
|
|
source: sfc.template.content,
|
|
inMap: sfc.template.map,
|
|
...options.templateOptions,
|
|
id: scopeId,
|
|
scoped: sfc.styles.some(s => s.scoped),
|
|
isProd: options.isProd,
|
|
ssrCssVars: sfc.cssVars,
|
|
compilerOptions: {
|
|
...(options.templateOptions &&
|
|
options.templateOptions.compilerOptions),
|
|
inline: true,
|
|
isTS: ctx.isTS,
|
|
bindingMetadata: ctx.bindingMetadata,
|
|
},
|
|
})
|
|
if (tips.length) {
|
|
tips.forEach(warnOnce)
|
|
}
|
|
const err = errors[0]
|
|
if (typeof err === 'string') {
|
|
throw new Error(err)
|
|
} else if (err) {
|
|
if (err.loc) {
|
|
err.message +=
|
|
`\n\n` +
|
|
sfc.filename +
|
|
'\n' +
|
|
generateCodeFrame(
|
|
source,
|
|
err.loc.start.offset,
|
|
err.loc.end.offset,
|
|
) +
|
|
`\n`
|
|
}
|
|
throw err
|
|
}
|
|
if (preamble) {
|
|
ctx.s.prepend(preamble)
|
|
}
|
|
// avoid duplicated unref import
|
|
// as this may get injected by the render function preamble OR the
|
|
// css vars codegen
|
|
if (helpers && helpers.has(UNREF)) {
|
|
ctx.helperImports.delete('unref')
|
|
}
|
|
returned = code
|
|
} else {
|
|
returned = `() => {}`
|
|
}
|
|
}
|
|
|
|
if (!options.inlineTemplate && !__TEST__) {
|
|
// in non-inline mode, the `__isScriptSetup: true` flag is used by
|
|
// componentPublicInstance proxy to allow properties that start with $ or _
|
|
ctx.s.appendRight(
|
|
endOffset,
|
|
`\nconst __returned__ = ${returned}\n` +
|
|
`Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })\n` +
|
|
`return __returned__` +
|
|
`\n}\n\n`,
|
|
)
|
|
} else {
|
|
ctx.s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
|
|
}
|
|
|
|
// 10. finalize default export
|
|
const genDefaultAs = options.genDefaultAs
|
|
? `const ${options.genDefaultAs} =`
|
|
: `export default`
|
|
|
|
let runtimeOptions = ``
|
|
if (vapor) {
|
|
runtimeOptions += `\n vapor: true,`
|
|
}
|
|
if (!ctx.hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
|
|
const match = filename.match(/([^/\\]+)\.\w+$/)
|
|
if (match) {
|
|
runtimeOptions += `\n __name: '${match[1]}',`
|
|
}
|
|
}
|
|
if (hasInlinedSsrRenderFn) {
|
|
runtimeOptions += `\n __ssrInlineRender: true,`
|
|
}
|
|
|
|
const propsDecl = genRuntimeProps(ctx)
|
|
if (propsDecl) runtimeOptions += `\n props: ${propsDecl},`
|
|
|
|
const emitsDecl = genRuntimeEmits(ctx)
|
|
if (emitsDecl) runtimeOptions += `\n emits: ${emitsDecl},`
|
|
|
|
let definedOptions = ''
|
|
if (ctx.optionsRuntimeDecl) {
|
|
definedOptions = scriptSetup.content
|
|
.slice(ctx.optionsRuntimeDecl.start!, ctx.optionsRuntimeDecl.end!)
|
|
.trim()
|
|
}
|
|
|
|
// <script setup> components are closed by default. If the user did not
|
|
// explicitly call `defineExpose`, call expose() with no args.
|
|
const exposeCall =
|
|
ctx.hasDefineExposeCall || options.inlineTemplate ? `` : ` __expose();\n`
|
|
// wrap setup code with function.
|
|
if (ctx.isTS) {
|
|
// for TS, make sure the exported type is still valid type with
|
|
// correct props information
|
|
// we have to use object spread for types to be merged properly
|
|
// user's TS setting should compile it down to proper targets
|
|
// export default defineComponent({ ...__default__, ... })
|
|
const def =
|
|
(defaultExport ? `\n ...${normalScriptDefaultVar},` : ``) +
|
|
(definedOptions ? `\n ...${definedOptions},` : '')
|
|
ctx.s.prependLeft(
|
|
startOffset,
|
|
`\n${genDefaultAs} /*@__PURE__*/${ctx.helper(
|
|
vapor ? `defineVaporComponent` : `defineComponent`,
|
|
)}({${def}${runtimeOptions}\n ${
|
|
hasAwait ? `async ` : ``
|
|
}setup(${args}) {\n${exposeCall}`,
|
|
)
|
|
ctx.s.appendRight(endOffset, `})`)
|
|
} else {
|
|
if (defaultExport || definedOptions) {
|
|
// without TS, can't rely on rest spread, so we use Object.assign
|
|
// export default Object.assign(__default__, { ... })
|
|
ctx.s.prependLeft(
|
|
startOffset,
|
|
`\n${genDefaultAs} /*@__PURE__*/Object.assign(${
|
|
defaultExport ? `${normalScriptDefaultVar}, ` : ''
|
|
}${definedOptions ? `${definedOptions}, ` : ''}{${runtimeOptions}\n ` +
|
|
`${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`,
|
|
)
|
|
ctx.s.appendRight(endOffset, `})`)
|
|
} else {
|
|
ctx.s.prependLeft(
|
|
startOffset,
|
|
`\n${genDefaultAs} {${runtimeOptions}\n ` +
|
|
`${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`,
|
|
)
|
|
ctx.s.appendRight(endOffset, `}`)
|
|
}
|
|
}
|
|
|
|
// 11. finalize Vue helper imports
|
|
if (ctx.helperImports.size > 0) {
|
|
const runtimeModuleName =
|
|
options.templateOptions?.compilerOptions?.runtimeModuleName
|
|
const importSrc = runtimeModuleName
|
|
? JSON.stringify(runtimeModuleName)
|
|
: `'vue'`
|
|
ctx.s.prepend(
|
|
`import { ${[...ctx.helperImports]
|
|
.map(h => `${h} as _${h}`)
|
|
.join(', ')} } from ${importSrc}\n`,
|
|
)
|
|
}
|
|
|
|
return {
|
|
...scriptSetup,
|
|
bindings: ctx.bindingMetadata,
|
|
imports: ctx.userImports,
|
|
content: ctx.s.toString(),
|
|
map:
|
|
options.sourceMap !== false
|
|
? (ctx.s.generateMap({
|
|
source: filename,
|
|
hires: true,
|
|
includeContent: true,
|
|
}) as unknown as RawSourceMap)
|
|
: undefined,
|
|
scriptAst: scriptAst?.body,
|
|
scriptSetupAst: scriptSetupAst?.body,
|
|
deps: ctx.deps ? [...ctx.deps] : undefined,
|
|
}
|
|
}
|
|
|
|
function registerBinding(
|
|
bindings: Record<string, BindingTypes>,
|
|
node: Identifier,
|
|
type: BindingTypes,
|
|
) {
|
|
bindings[node.name] = type
|
|
}
|
|
|
|
function walkDeclaration(
|
|
from: 'script' | 'scriptSetup',
|
|
node: Declaration,
|
|
bindings: Record<string, BindingTypes>,
|
|
userImportAliases: Record<string, string>,
|
|
hoistStatic: boolean,
|
|
isPropsDestructureEnabled = false,
|
|
): boolean {
|
|
let isAllLiteral = false
|
|
|
|
if (node.type === 'VariableDeclaration') {
|
|
const isConst = node.kind === 'const'
|
|
isAllLiteral =
|
|
isConst &&
|
|
node.declarations.every(
|
|
decl => decl.id.type === 'Identifier' && isStaticNode(decl.init!),
|
|
)
|
|
|
|
// export const foo = ...
|
|
for (const { id, init: _init } of node.declarations) {
|
|
const init = _init && unwrapTSNode(_init)
|
|
const isConstMacroCall =
|
|
isConst &&
|
|
isCallOf(
|
|
init,
|
|
c =>
|
|
c === DEFINE_PROPS ||
|
|
c === DEFINE_EMITS ||
|
|
c === WITH_DEFAULTS ||
|
|
c === DEFINE_SLOTS,
|
|
)
|
|
if (id.type === 'Identifier') {
|
|
let bindingType
|
|
const userReactiveBinding = userImportAliases['reactive']
|
|
if (
|
|
(hoistStatic || from === 'script') &&
|
|
(isAllLiteral || (isConst && isStaticNode(init!)))
|
|
) {
|
|
bindingType = BindingTypes.LITERAL_CONST
|
|
} else if (isCallOf(init, userReactiveBinding)) {
|
|
// treat reactive() calls as let since it's meant to be mutable
|
|
bindingType = isConst
|
|
? BindingTypes.SETUP_REACTIVE_CONST
|
|
: BindingTypes.SETUP_LET
|
|
} else if (
|
|
// if a declaration is a const literal, we can mark it so that
|
|
// the generated render fn code doesn't need to unref() it
|
|
isConstMacroCall ||
|
|
(isConst && canNeverBeRef(init!, userReactiveBinding))
|
|
) {
|
|
bindingType = isCallOf(init, DEFINE_PROPS)
|
|
? BindingTypes.SETUP_REACTIVE_CONST
|
|
: BindingTypes.SETUP_CONST
|
|
} else if (isConst) {
|
|
if (
|
|
isCallOf(
|
|
init,
|
|
m =>
|
|
m === userImportAliases['ref'] ||
|
|
m === userImportAliases['computed'] ||
|
|
m === userImportAliases['shallowRef'] ||
|
|
m === userImportAliases['customRef'] ||
|
|
m === userImportAliases['toRef'] ||
|
|
m === DEFINE_MODEL,
|
|
)
|
|
) {
|
|
bindingType = BindingTypes.SETUP_REF
|
|
} else {
|
|
bindingType = BindingTypes.SETUP_MAYBE_REF
|
|
}
|
|
} else {
|
|
bindingType = BindingTypes.SETUP_LET
|
|
}
|
|
registerBinding(bindings, id, bindingType)
|
|
} else {
|
|
if (isCallOf(init, DEFINE_PROPS) && isPropsDestructureEnabled) {
|
|
continue
|
|
}
|
|
if (id.type === 'ObjectPattern') {
|
|
walkObjectPattern(id, bindings, isConst, isConstMacroCall)
|
|
} else if (id.type === 'ArrayPattern') {
|
|
walkArrayPattern(id, bindings, isConst, isConstMacroCall)
|
|
}
|
|
}
|
|
}
|
|
} else if (node.type === 'TSEnumDeclaration') {
|
|
isAllLiteral = node.members.every(
|
|
member => !member.initializer || isStaticNode(member.initializer),
|
|
)
|
|
bindings[node.id!.name] = isAllLiteral
|
|
? BindingTypes.LITERAL_CONST
|
|
: BindingTypes.SETUP_CONST
|
|
} else if (
|
|
node.type === 'FunctionDeclaration' ||
|
|
node.type === 'ClassDeclaration'
|
|
) {
|
|
// export function foo() {} / export class Foo {}
|
|
// export declarations must be named.
|
|
bindings[node.id!.name] = BindingTypes.SETUP_CONST
|
|
}
|
|
|
|
return isAllLiteral
|
|
}
|
|
|
|
function walkObjectPattern(
|
|
node: ObjectPattern,
|
|
bindings: Record<string, BindingTypes>,
|
|
isConst: boolean,
|
|
isDefineCall = false,
|
|
) {
|
|
for (const p of node.properties) {
|
|
if (p.type === 'ObjectProperty') {
|
|
if (p.key.type === 'Identifier' && p.key === p.value) {
|
|
// shorthand: const { x } = ...
|
|
const type = isDefineCall
|
|
? BindingTypes.SETUP_CONST
|
|
: isConst
|
|
? BindingTypes.SETUP_MAYBE_REF
|
|
: BindingTypes.SETUP_LET
|
|
registerBinding(bindings, p.key, type)
|
|
} else {
|
|
walkPattern(p.value, bindings, isConst, isDefineCall)
|
|
}
|
|
} else {
|
|
// ...rest
|
|
// argument can only be identifier when destructuring
|
|
const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
|
|
registerBinding(bindings, p.argument as Identifier, type)
|
|
}
|
|
}
|
|
}
|
|
|
|
function walkArrayPattern(
|
|
node: ArrayPattern,
|
|
bindings: Record<string, BindingTypes>,
|
|
isConst: boolean,
|
|
isDefineCall = false,
|
|
) {
|
|
for (const e of node.elements) {
|
|
e && walkPattern(e, bindings, isConst, isDefineCall)
|
|
}
|
|
}
|
|
|
|
function walkPattern(
|
|
node: Node,
|
|
bindings: Record<string, BindingTypes>,
|
|
isConst: boolean,
|
|
isDefineCall = false,
|
|
) {
|
|
if (node.type === 'Identifier') {
|
|
const type = isDefineCall
|
|
? BindingTypes.SETUP_CONST
|
|
: isConst
|
|
? BindingTypes.SETUP_MAYBE_REF
|
|
: BindingTypes.SETUP_LET
|
|
registerBinding(bindings, node, type)
|
|
} else if (node.type === 'RestElement') {
|
|
// argument can only be identifier when destructuring
|
|
const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
|
|
registerBinding(bindings, node.argument as Identifier, type)
|
|
} else if (node.type === 'ObjectPattern') {
|
|
walkObjectPattern(node, bindings, isConst)
|
|
} else if (node.type === 'ArrayPattern') {
|
|
walkArrayPattern(node, bindings, isConst)
|
|
} else if (node.type === 'AssignmentPattern') {
|
|
if (node.left.type === 'Identifier') {
|
|
const type = isDefineCall
|
|
? BindingTypes.SETUP_CONST
|
|
: isConst
|
|
? BindingTypes.SETUP_MAYBE_REF
|
|
: BindingTypes.SETUP_LET
|
|
registerBinding(bindings, node.left, type)
|
|
} else {
|
|
walkPattern(node.left, bindings, isConst)
|
|
}
|
|
}
|
|
}
|
|
|
|
function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
|
|
if (isCallOf(node, userReactiveImport)) {
|
|
return true
|
|
}
|
|
switch (node.type) {
|
|
case 'UnaryExpression':
|
|
case 'BinaryExpression':
|
|
case 'ArrayExpression':
|
|
case 'ObjectExpression':
|
|
case 'FunctionExpression':
|
|
case 'ArrowFunctionExpression':
|
|
case 'UpdateExpression':
|
|
case 'ClassExpression':
|
|
case 'TaggedTemplateExpression':
|
|
return true
|
|
case 'SequenceExpression':
|
|
return canNeverBeRef(
|
|
node.expressions[node.expressions.length - 1],
|
|
userReactiveImport,
|
|
)
|
|
default:
|
|
if (isLiteralNode(node)) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
function isStaticNode(node: Node): boolean {
|
|
node = unwrapTSNode(node)
|
|
|
|
switch (node.type) {
|
|
case 'UnaryExpression': // void 0, !true
|
|
return isStaticNode(node.argument)
|
|
|
|
case 'LogicalExpression': // 1 > 2
|
|
case 'BinaryExpression': // 1 + 2
|
|
return isStaticNode(node.left) && isStaticNode(node.right)
|
|
|
|
case 'ConditionalExpression': {
|
|
// 1 ? 2 : 3
|
|
return (
|
|
isStaticNode(node.test) &&
|
|
isStaticNode(node.consequent) &&
|
|
isStaticNode(node.alternate)
|
|
)
|
|
}
|
|
|
|
case 'SequenceExpression': // (1, 2)
|
|
case 'TemplateLiteral': // `foo${1}`
|
|
return node.expressions.every(expr => isStaticNode(expr))
|
|
|
|
case 'ParenthesizedExpression': // (1)
|
|
return isStaticNode(node.expression)
|
|
|
|
case 'StringLiteral':
|
|
case 'NumericLiteral':
|
|
case 'BooleanLiteral':
|
|
case 'NullLiteral':
|
|
case 'BigIntLiteral':
|
|
return true
|
|
}
|
|
return false
|
|
}
|