import type { CallExpression, Node, ObjectPattern, Program } from '@babel/types' import type { SFCDescriptor } from '../parse' import { generateCodeFrame, isArray } from '@vue/shared' import { type ParserPlugin, parse as babelParse } from '@babel/parser' import type { ImportBinding, SFCScriptCompileOptions } from '../compileScript' import type { PropsDestructureBindings } from './defineProps' import type { ModelDecl } from './defineModel' import type { BindingMetadata } from '../../../compiler-core/src' import MagicString from 'magic-string' import type { TypeScope } from './resolveType' import { warn } from '../warn' import { isJS, isTS } from './utils' export class ScriptCompileContext { isJS: boolean isTS: boolean isCE = false scriptAst: Program | null scriptSetupAst: Program | null source: string = this.descriptor.source filename: string = this.descriptor.filename s: MagicString = new MagicString(this.source) startOffset: number | undefined = this.descriptor.scriptSetup?.loc.start.offset endOffset: number | undefined = this.descriptor.scriptSetup?.loc.end.offset // import / type analysis scope?: TypeScope globalScopes?: TypeScope[] userImports: Record = Object.create(null) // macros presence check hasDefinePropsCall = false hasDefineEmitCall = false hasDefineExposeCall = false hasDefaultExportName = false hasDefaultExportRender = false hasDefineOptionsCall = false hasDefineSlotsCall = false hasDefineModelCall = false // defineProps propsCall: CallExpression | undefined propsDecl: Node | undefined propsRuntimeDecl: Node | undefined propsTypeDecl: Node | undefined propsDestructureDecl: ObjectPattern | undefined propsDestructuredBindings: PropsDestructureBindings = Object.create(null) propsDestructureRestId: string | undefined propsRuntimeDefaults: Node | undefined // defineEmits emitsRuntimeDecl: Node | undefined emitsTypeDecl: Node | undefined emitDecl: Node | undefined // defineModel modelDecls: Record = Object.create(null) // defineOptions optionsRuntimeDecl: Node | undefined // codegen bindingMetadata: BindingMetadata = {} helperImports: Set = new Set() helper(key: string): string { this.helperImports.add(key) return `_${key}` } /** * to be exposed on compiled script block for HMR cache busting */ deps?: Set /** * cache for resolved fs */ fs?: NonNullable constructor( public descriptor: SFCDescriptor, public options: Partial, ) { const { script, scriptSetup } = descriptor const scriptLang = script && script.lang const scriptSetupLang = scriptSetup && scriptSetup.lang this.isJS = isJS(scriptLang, scriptSetupLang) this.isTS = isTS(scriptLang, scriptSetupLang) const customElement = options.customElement const filename = this.descriptor.filename if (customElement) { this.isCE = typeof customElement === 'boolean' ? customElement : customElement(filename) } // resolve parser plugins const plugins: ParserPlugin[] = resolveParserPlugins( (scriptLang || scriptSetupLang)!, options.babelParserPlugins, ) function parse(input: string, offset: number): Program { try { return babelParse(input, { plugins, sourceType: 'module', }).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 = descriptor.script && parse(descriptor.script.content, descriptor.script.loc.start.offset) this.scriptSetupAst = descriptor.scriptSetup && parse(descriptor.scriptSetup!.content, 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!) } warn(msg: string, node: Node, scope?: TypeScope): void { warn(generateError(msg, node, this, scope)) } error(msg: string, node: Node, scope?: TypeScope): never { throw new Error( `[@vue/compiler-sfc] ${generateError(msg, node, this, scope)}`, ) } } function generateError( msg: string, node: Node, ctx: ScriptCompileContext, scope?: TypeScope, ) { const offset = scope ? scope.offset : ctx.startOffset! return `${msg}\n\n${(scope || ctx.descriptor).filename}\n${generateCodeFrame( (scope || ctx.descriptor).source, node.start! + offset, node.end! + offset, )}` } export function resolveParserPlugins( lang: string, userPlugins?: ParserPlugin[], dts = false, ): ParserPlugin[] { const plugins: ParserPlugin[] = [] if ( !userPlugins || !userPlugins.some( p => p === 'importAssertions' || p === 'importAttributes' || (isArray(p) && p[0] === 'importAttributes'), ) ) { plugins.push('importAttributes') } if (lang === 'jsx' || lang === 'tsx' || lang === 'mtsx') { plugins.push('jsx') } else if (userPlugins) { // If don't match the case of adding jsx // should remove the jsx from user options userPlugins = userPlugins.filter(p => p !== 'jsx') } if ( lang === 'ts' || lang === 'mts' || lang === 'tsx' || lang === 'cts' || lang === 'mtsx' ) { plugins.push(['typescript', { dts }], 'explicitResourceManagement') if (!userPlugins || !userPlugins.includes('decorators')) { plugins.push('decorators-legacy') } } if (userPlugins) { plugins.push(...userPlugins) } return plugins }