From 7dc8c9dcdcd218826b281f06492134657dec4332 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 6 Dec 2023 22:03:24 +0800 Subject: [PATCH] refactor(langauge-core): codegen based on Generator (#3778) --- .../language-core/src/generators/script.ts | 964 ++++---- .../language-core/src/generators/template.ts | 1946 ++++++++--------- .../language-core/src/generators/utils.ts | 19 +- packages/language-core/src/plugins/vue-tsx.ts | 115 +- packages/language-core/src/types.ts | 2 + packages/language-core/src/utils/transform.ts | 72 +- packages/language-service/src/helpers.ts | 4 +- .../plugins/vue-toggle-v-bind-codeaction.ts | 6 +- 8 files changed, 1541 insertions(+), 1587 deletions(-) diff --git a/packages/language-core/src/generators/script.ts b/packages/language-core/src/generators/script.ts index 117ac4c878..111b4c12bd 100644 --- a/packages/language-core/src/generators/script.ts +++ b/packages/language-core/src/generators/script.ts @@ -1,16 +1,15 @@ -import { Mapping, getLength, offsetStack, resetOffsetStack, setTracking, track } from '@volar/language-core'; +import { Mapping } from '@volar/language-core'; import * as path from 'path-browserify'; import type * as ts from 'typescript/lib/tsserverlibrary'; -import type * as templateGen from '../generators/template'; import type { ScriptRanges } from '../parsers/scriptRanges'; import type { ScriptSetupRanges } from '../parsers/scriptSetupRanges'; -import type { Code, VueCompilerOptions } from '../types'; +import type { Code, CodeAndStack, SfcBlock, VueCompilerOptions } from '../types'; import { Sfc } from '../types'; import { getSlotsPropertyName, hyphenateTag } from '../utils/shared'; -import { walkInterpolationFragment } from '../utils/transform'; -import { disableAllFeatures, enableAllFeatures } from './utils'; +import { eachInterpolationSegment } from '../utils/transform'; +import { disableAllFeatures, enableAllFeatures, withStack } from './utils'; -export function generate( +export function* generate( ts: typeof import('typescript/lib/tsserverlibrary'), fileName: string, script: Sfc['script'], @@ -19,14 +18,19 @@ export function generate( lang: string, scriptRanges: ScriptRanges | undefined, scriptSetupRanges: ScriptSetupRanges | undefined, - htmlGen: ReturnType | undefined, + templateCodegen: { + tsCodes: Code[]; + tsCodegenStacks: string[]; + tagNames: Set; + accessedGlobalVariables: Set; + hasSlot: boolean; + } | undefined, compilerOptions: ts.CompilerOptions, vueCompilerOptions: VueCompilerOptions, + getGeneratedLength: () => number, + linkedCodeMappings: Mapping[] = [], codegenStack: boolean, -) { - - const [codes, codeStacks] = codegenStack ? track([] as Code[]) : [[], []]; - const mirrorBehaviorMappings: Mapping[] = []; +): Generator { //#region monkey fix: https://github.com/vuejs/language-tools/pull/2113 if (!script && !scriptSetup) { @@ -67,86 +71,97 @@ export function generate( WithTemplateSlots: false, PropsChildren: false, }; - - codes.push(`/* __placeholder__ */\n`); + const _ = codegenStack ? withStack : (code: Code): CodeAndStack => [code, '']; let generatedTemplate = false; + let scriptSetupGeneratedOffset: number | undefined; - generateSrc(); - generateScriptSetupImports(); - generateScriptContentBeforeExportDefault(); - generateScriptSetupAndTemplate(); - generateHelperTypes(); - generateScriptContentAfterExportDefault(); + yield _(`/* __placeholder__ */\n`); + yield* generateSrc(); + yield* generateScriptSetupImports(); + yield* generateScriptContentBeforeExportDefault(); + yield* generateScriptSetupAndTemplate(); + yield* generateHelperTypes(); + yield* generateScriptContentAfterExportDefault(); if (!generatedTemplate) { - generateTemplate(false); + yield* generateTemplate(false); } if (scriptSetup) { - // for code action edits - codes.push([ - '', - 'scriptSetup', - scriptSetup.content.length, - disableAllFeatures({}), - ]); + yield _(['', 'scriptSetup', scriptSetup.content.length, disableAllFeatures({ verification: true })]); } - return { - codes, - codeStacks, - mirrorBehaviorMappings, - }; - - function generateHelperTypes() { + function* generateHelperTypes(): Generator { if (usedHelperTypes.DefinePropsToOptions) { if (compilerOptions.exactOptionalPropertyTypes) { - codes.push(`type __VLS_TypePropsToRuntimeProps = { [K in keyof T]-?: {} extends Pick ? { type: import('${vueCompilerOptions.lib}').PropType } : { type: import('${vueCompilerOptions.lib}').PropType, required: true } };\n`); + yield _(`type __VLS_TypePropsToRuntimeProps = {\n` + + ` [K in keyof T]-?: {} extends Pick\n` + + ` ? { type: import('${vueCompilerOptions.lib}').PropType }\n` + + ` : { type: import('${vueCompilerOptions.lib}').PropType, required: true }\n` + + `};\n`); } else { - codes.push(`type __VLS_NonUndefinedable = T extends undefined ? never : T;\n`); - codes.push(`type __VLS_TypePropsToRuntimeProps = { [K in keyof T]-?: {} extends Pick ? { type: import('${vueCompilerOptions.lib}').PropType<__VLS_NonUndefinedable> } : { type: import('${vueCompilerOptions.lib}').PropType, required: true } };\n`); + yield _(`type __VLS_NonUndefinedable = T extends undefined ? never : T;\n`); + yield _(`type __VLS_TypePropsToRuntimeProps = {\n` + + ` [K in keyof T]-?: {} extends Pick\n` + + ` ? { type: import('${vueCompilerOptions.lib}').PropType<__VLS_NonUndefinedable> }\n` + + ` : { type: import('${vueCompilerOptions.lib}').PropType, required: true }\n` + + `};\n`); } } if (usedHelperTypes.MergePropDefaults) { - codes.push(`type __VLS_WithDefaults = { - // use 'keyof Pick' instead of 'keyof P' to keep props jsdoc - [K in keyof Pick]: K extends keyof D ? __VLS_Prettify : P[K] - };\n`); - codes.push(`type __VLS_Prettify = { [K in keyof T]: T[K]; } & {};\n`); + yield _(`type __VLS_WithDefaults = {\n` + // use 'keyof Pick' instead of 'keyof P' to keep props jsdoc + + ` [K in keyof Pick]: K extends keyof D\n` + + ` ? __VLS_Prettify\n` + + ` : P[K]\n` + + `};\n`); + yield _(`type __VLS_Prettify = { [K in keyof T]: T[K]; } & {};\n`); } if (usedHelperTypes.WithTemplateSlots) { - codes.push( - `type __VLS_WithTemplateSlots = T & { new(): {\n`, - `${getSlotsPropertyName(vueCompilerOptions.target)}: S;\n`, - ); + yield _(`type __VLS_WithTemplateSlots = T & {\n` + + ` new(): {\n` + + ` ${getSlotsPropertyName(vueCompilerOptions.target)}: S;\n`); if (vueCompilerOptions.jsxSlots) { usedHelperTypes.PropsChildren = true; - codes.push(`$props: __VLS_PropsChildren;\n`); + yield _(` $props: __VLS_PropsChildren;\n`); } - codes.push(`} };\n`); + yield _(` }\n` + + `};\n`); } if (usedHelperTypes.PropsChildren) { - codes.push(`type __VLS_PropsChildren = { [K in keyof (boolean extends (JSX.ElementChildrenAttribute extends never ? true : false) ? never : JSX.ElementChildrenAttribute)]?: S; };\n`); + yield _(`type __VLS_PropsChildren = {\n` + + ` [K in keyof (\n` + + ` boolean extends (\n` + + ` JSX.ElementChildrenAttribute extends never\n` + + ` ? true\n` + + ` : false\n` + + ` )\n` + + ` ? never\n` + + ` : JSX.ElementChildrenAttribute\n` + + ` )]?: S;\n` + + `};\n`); } } - function generateSrc() { + function* generateSrc(): Generator { if (!script?.src) return; let src = script.src; - if (src.endsWith('.d.ts')) src = src.substring(0, src.length - '.d.ts'.length); - else if (src.endsWith('.ts')) src = src.substring(0, src.length - '.ts'.length); - else if (src.endsWith('.tsx')) src = src.substring(0, src.length - '.tsx'.length) + '.jsx'; + if (src.endsWith('.d.ts')) + src = src.substring(0, src.length - '.d.ts'.length); + else if (src.endsWith('.ts')) + src = src.substring(0, src.length - '.ts'.length); + else if (src.endsWith('.tsx')) + src = src.substring(0, src.length - '.tsx'.length) + '.jsx'; - if (!src.endsWith('.js') && !src.endsWith('.jsx')) src = src + '.js'; + if (!src.endsWith('.js') && !src.endsWith('.jsx')) + src = src + '.js'; - codes.push(`export * from `); - codes.push([ + yield _(`export * from `); + yield _([ `'${src}'`, 'script', script.srcOffset - 1, @@ -171,211 +186,195 @@ export function generate( }, }), ]); - codes.push(`;\n`); - codes.push(`export { default } from '${src}';\n`); + yield _(`;\n`); + yield _(`export { default } from '${src}';\n`); } - function generateScriptContentBeforeExportDefault() { + function* generateScriptContentBeforeExportDefault(): Generator { if (!script) return; - if (!!scriptSetup && scriptRanges?.exportDefault) { - addVirtualCode('script', 0, scriptRanges.exportDefault.expression.start); - } - else { - let isExportRawObject = false; - if (scriptRanges?.exportDefault) { - isExportRawObject = script.content.substring(scriptRanges.exportDefault.expression.start, scriptRanges.exportDefault.expression.end).startsWith('{'); - } - if (isExportRawObject && vueCompilerOptions.optionsWrapper.length === 2 && scriptRanges?.exportDefault) { - addVirtualCode('script', 0, scriptRanges.exportDefault.expression.start); - codes.push(vueCompilerOptions.optionsWrapper[0]); - { - codes.push(['', 'script', scriptRanges.exportDefault.expression.start, disableAllFeatures({ - __hint: { - setting: 'vue.inlayHints.optionsWrapper', - label: vueCompilerOptions.optionsWrapper[0], - tooltip: [ - 'This is virtual code that is automatically wrapped for type support, it does not affect your runtime behavior, you can customize it via `vueCompilerOptions.optionsWrapper` option in tsconfig / jsconfig.', - 'To hide it, you can set `"vue.inlayHints.optionsWrapper": false` in IDE settings.', - ].join('\n\n'), - } - })]); - addVirtualCode('script', scriptRanges.exportDefault.expression.start, scriptRanges.exportDefault.expression.end); - codes.push(['', 'script', scriptRanges.exportDefault.expression.end, disableAllFeatures({ - __hint: { - setting: 'vue.inlayHints.optionsWrapper', - label: vueCompilerOptions.optionsWrapper[1], - tooltip: '', - } - })]); - } - codes.push(vueCompilerOptions.optionsWrapper[1]); - addVirtualCode('script', scriptRanges.exportDefault.expression.end, script.content.length); - } - else { - addVirtualCode('script', 0, script.content.length); - } - } + if (!!scriptSetup && scriptRanges?.exportDefault) + return yield _(generateSourceCode(script, 0, scriptRanges.exportDefault.expression.start)); + + let isExportRawObject = false; + if (scriptRanges?.exportDefault) + isExportRawObject = script.content.substring(scriptRanges.exportDefault.expression.start, scriptRanges.exportDefault.expression.end).startsWith('{'); + + if (!isExportRawObject || !vueCompilerOptions.optionsWrapper.length || !scriptRanges?.exportDefault) + return yield _(generateSourceCode(script, 0, script.content.length)); + + yield _(generateSourceCode(script, 0, scriptRanges.exportDefault.expression.start)); + yield _(vueCompilerOptions.optionsWrapper[0]); + yield _(['', 'script', scriptRanges.exportDefault.expression.start, disableAllFeatures({ + __hint: { + setting: 'vue.inlayHints.optionsWrapper', + label: vueCompilerOptions.optionsWrapper[0], + tooltip: [ + 'This is virtual code that is automatically wrapped for type support, it does not affect your runtime behavior, you can customize it via `vueCompilerOptions.optionsWrapper` option in tsconfig / jsconfig.', + 'To hide it, you can set `"vue.inlayHints.optionsWrapper": false` in IDE settings.', + ].join('\n\n'), + } + })]); + yield _(generateSourceCode(script, scriptRanges.exportDefault.expression.start, scriptRanges.exportDefault.expression.end)); + yield _(['', 'script', scriptRanges.exportDefault.expression.end, disableAllFeatures({ + __hint: { + setting: 'vue.inlayHints.optionsWrapper', + label: vueCompilerOptions.optionsWrapper[1], + tooltip: '', + } + })]); + yield _(vueCompilerOptions.optionsWrapper[1]); + yield _(generateSourceCode(script, scriptRanges.exportDefault.expression.end, script.content.length)); } - function generateScriptContentAfterExportDefault() { + function* generateScriptContentAfterExportDefault(): Generator { if (!script) return; - if (!!scriptSetup && scriptRanges?.exportDefault) { - addVirtualCode('script', scriptRanges.exportDefault.expression.end, script.content.length); - } + if (!!scriptSetup && scriptRanges?.exportDefault) + yield _(generateSourceCode(script, scriptRanges.exportDefault.expression.end, script.content.length)); } - function generateScriptSetupImports() { - - if (!scriptSetup) + function* generateScriptSetupImports(): Generator { + if (!scriptSetup || !scriptSetupRanges) return; - if (!scriptSetupRanges) - return; - - codes.push([ + yield _([ scriptSetup.content.substring(0, Math.max(scriptSetupRanges.importSectionEndOffset, scriptSetupRanges.leadingCommentEndOffset)) + '\n', 'scriptSetup', 0, enableAllFeatures({}), ]); } - function generateScriptSetupAndTemplate() { - - if (!scriptSetup || !scriptSetupRanges) { + function* generateScriptSetupAndTemplate(): Generator { + if (!scriptSetup || !scriptSetupRanges) return; - } const definePropMirrors = new Map(); - let scriptSetupGeneratedOffset: number | undefined; if (scriptSetup.generic) { if (!scriptRanges?.exportDefault) { - codes.push('export default '); + yield _(`export default `); } - codes.push(`(<`); - codes.push([ + yield _(`(<`); + yield _([ scriptSetup.generic, scriptSetup.name, scriptSetup.genericOffset, enableAllFeatures({}), ]); - if (!scriptSetup.generic.endsWith(',')) { - codes.push(`,`); + if (!scriptSetup.generic.endsWith(`,`)) { + yield _(`,`); } - codes.push(`>`); - codes.push('(\n'); - codes.push(`__VLS_props: Awaited['props'],\n`); - codes.push(`__VLS_ctx?: __VLS_Prettify, 'attrs' | 'emit' | 'slots'>>,\n`); // use __VLS_Prettify for less dts code - codes.push(`__VLS_expose?: NonNullable>['expose'],\n`); - codes.push('__VLS_setup = (async () => {\n'); - scriptSetupGeneratedOffset = generateSetupFunction(true, 'none', definePropMirrors); + yield _(`>`); + yield _(`(\n` + + ` __VLS_props: Awaited['props'],\n` + + ` __VLS_ctx?: __VLS_Prettify, 'attrs' | 'emit' | 'slots'>>,\n` // use __VLS_Prettify for less dts code + + ` __VLS_expose?: NonNullable>['expose'],\n` + + ` __VLS_setup = (async () => {\n`); + + yield* generateSetupFunction(true, 'none', definePropMirrors); //#region props - codes.push(`const __VLS_fnComponent = `); - codes.push(`(await import('${vueCompilerOptions.lib}')).defineComponent({\n`); + yield _(`const __VLS_fnComponent = ` + + `(await import('${vueCompilerOptions.lib}')).defineComponent({\n`); if (scriptSetupRanges.props.define?.arg) { - codes.push(`props: `); - addExtraReferenceVirtualCode('scriptSetup', scriptSetupRanges.props.define.arg.start, scriptSetupRanges.props.define.arg.end); - codes.push(`,\n`); - + yield _(` props: `); + yield _(generateSourceCodeForExtraReference(scriptSetup, scriptSetupRanges.props.define.arg.start, scriptSetupRanges.props.define.arg.end)); + yield _(`,\n`); } if (scriptSetupRanges.emits.define) { - codes.push( - `emits: ({} as __VLS_NormalizeEmits),\n`, - ); + yield _(` emits: ({} as __VLS_NormalizeEmits),\n`); } - codes.push(`});\n`); + yield _(`});\n`); + if (scriptSetupRanges.defineProp.length) { - codes.push(`const __VLS_defaults = {\n`); + yield _(`const __VLS_defaults = {\n`); for (const defineProp of scriptSetupRanges.defineProp) { if (defineProp.defaultValue) { if (defineProp.name) { - codes.push(scriptSetup.content.substring(defineProp.name.start, defineProp.name.end)); + yield _(scriptSetup.content.substring(defineProp.name.start, defineProp.name.end)); } else { - codes.push('modelValue'); + yield _('modelValue'); } - codes.push(`: `); - codes.push(scriptSetup.content.substring(defineProp.defaultValue.start, defineProp.defaultValue.end)); - codes.push(`,\n`); + yield _(`: `); + yield _(scriptSetup.content.substring(defineProp.defaultValue.start, defineProp.defaultValue.end)); + yield _(`,\n`); } } - codes.push(`};\n`); + yield _(`};\n`); } - codes.push(`let __VLS_fnPropsTypeOnly!: {}`); // TODO: reuse __VLS_fnPropsTypeOnly even without generic, and remove __VLS_propsOption_defineProp + + yield _(`let __VLS_fnPropsTypeOnly!: {}`); // TODO: reuse __VLS_fnPropsTypeOnly even without generic, and remove __VLS_propsOption_defineProp if (scriptSetupRanges.props.define?.typeArg) { - codes.push(` & `); - addVirtualCode('scriptSetup', scriptSetupRanges.props.define.typeArg.start, scriptSetupRanges.props.define.typeArg.end); + yield _(` & `); + yield _(generateSourceCode(scriptSetup, scriptSetupRanges.props.define.typeArg.start, scriptSetupRanges.props.define.typeArg.end)); } if (scriptSetupRanges.defineProp.length) { - codes.push(` & {\n`); + yield _(` & {\n`); for (const defineProp of scriptSetupRanges.defineProp) { let propName = 'modelValue'; if (defineProp.name) { propName = scriptSetup.content.substring(defineProp.name.start, defineProp.name.end); - definePropMirrors.set(propName, getLength(codes)); + definePropMirrors.set(propName, getGeneratedLength()); } - codes.push(`${propName}${defineProp.required ? '' : '?'}: `); + yield _(`${propName}${defineProp.required ? '' : '?'}: `); if (defineProp.type) { - codes.push(scriptSetup.content.substring(defineProp.type.start, defineProp.type.end)); + yield _(scriptSetup.content.substring(defineProp.type.start, defineProp.type.end)); } else if (defineProp.defaultValue) { - codes.push(`typeof __VLS_defaults['`); - codes.push(propName); - codes.push(`']`); + yield _(`typeof __VLS_defaults['`); + yield _(propName); + yield _(`']`); } else { - codes.push(`any`); + yield _(`any`); } - codes.push(',\n'); + yield _(',\n'); } - codes.push(`}`); + yield _(`}`); } - codes.push(`;\n`); - codes.push(`let __VLS_fnPropsDefineComponent!: InstanceType['$props']`); - codes.push(`;\n`); - codes.push(`let __VLS_fnPropsSlots!: `); + yield _(`;\n`); + + yield _(`let __VLS_fnPropsDefineComponent!: InstanceType['$props'];\n`); + yield _(`let __VLS_fnPropsSlots!: `); if (scriptSetupRanges.slots.define && vueCompilerOptions.jsxSlots) { usedHelperTypes.PropsChildren = true; - codes.push(`__VLS_PropsChildren`); + yield _(`__VLS_PropsChildren`); } else { - codes.push(`{}`); - } - codes.push(`;\n`); - codes.push( - `let __VLS_defaultProps!: `, - `import('${vueCompilerOptions.lib}').VNodeProps`, - `& import('${vueCompilerOptions.lib}').AllowedComponentProps`, - `& import('${vueCompilerOptions.lib}').ComponentCustomProps`, - `;\n`, - ); + yield _(`{}`); + } + yield _(`;\n`); + + yield _(`let __VLS_defaultProps!:\n` + + ` import('${vueCompilerOptions.lib}').VNodeProps\n` + + ` & import('${vueCompilerOptions.lib}').AllowedComponentProps\n` + + ` & import('${vueCompilerOptions.lib}').ComponentCustomProps;\n`); //#endregion - codes.push('return {} as {\n'); - codes.push(`props: __VLS_Prettify<__VLS_OmitKeepDiscriminatedUnion> & typeof __VLS_fnPropsSlots & typeof __VLS_defaultProps,\n`); - codes.push(`expose(exposed: import('${vueCompilerOptions.lib}').ShallowUnwrapRef<${scriptSetupRanges.expose.define ? 'typeof __VLS_exposed' : '{}'}>): void,\n`); - codes.push('attrs: any,\n'); - codes.push('slots: ReturnType,\n'); - codes.push(`emit: typeof ${scriptSetupRanges.emits.name ?? '__VLS_emit'},\n`); - codes.push('};\n'); - codes.push('})(),\n'); - codes.push(`) => ({} as import('${vueCompilerOptions.lib}').VNode & { __ctx?: Awaited }))`); + yield _(` return {} as {\n` + + ` props: __VLS_Prettify<__VLS_OmitKeepDiscriminatedUnion> & typeof __VLS_fnPropsSlots & typeof __VLS_defaultProps,\n` + + ` expose(exposed: import('${vueCompilerOptions.lib}').ShallowUnwrapRef<${scriptSetupRanges.expose.define ? 'typeof __VLS_exposed' : '{}'}>): void,\n` + + ` attrs: any,\n` + + ` slots: ReturnType,\n` + + ` emit: typeof ${scriptSetupRanges.emits.name ?? '__VLS_emit'},\n` + + ` };\n`); + yield _(` })(),\n`); // __VLS_setup = (async () => { + yield _(`) => ({} as import('${vueCompilerOptions.lib}').VNode & { __ctx?: Awaited }))`); } else if (!script) { // no script block, generate script setup code at root - scriptSetupGeneratedOffset = generateSetupFunction(false, 'export', definePropMirrors); + yield* generateSetupFunction(false, 'export', definePropMirrors); } else { if (!scriptRanges?.exportDefault) { - codes.push('export default '); + yield _(`export default `); } - codes.push('await (async () => {\n'); - scriptSetupGeneratedOffset = generateSetupFunction(false, 'return', definePropMirrors); - codes.push(`})()`); + yield _(`await (async () => {\n`); + yield* generateSetupFunction(false, 'return', definePropMirrors); + yield _(`})()`); } if (scriptSetupGeneratedOffset !== undefined) { @@ -386,7 +385,7 @@ export function generate( const propName = scriptSetup.content.substring(defineProp.name.start, defineProp.name.end); const propMirror = definePropMirrors.get(propName); if (propMirror !== undefined) { - mirrorBehaviorMappings.push({ + linkedCodeMappings.push({ sourceOffsets: [defineProp.name.start + scriptSetupGeneratedOffset], generatedOffsets: [propMirror], lengths: [defineProp.name.end - defineProp.name.start], @@ -396,128 +395,138 @@ export function generate( } } } - function generateSetupFunction(functional: boolean, mode: 'return' | 'export' | 'none', definePropMirrors: Map) { - - if (!scriptSetupRanges || !scriptSetup) { + function* generateSetupFunction(functional: boolean, mode: 'return' | 'export' | 'none', definePropMirrors: Map): Generator { + if (!scriptSetupRanges || !scriptSetup) return; - } const definePropProposalA = scriptSetup.content.trimStart().startsWith('// @experimentalDefinePropProposal=kevinEdition') || vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition'; const definePropProposalB = scriptSetup.content.trimStart().startsWith('// @experimentalDefinePropProposal=johnsonEdition') || vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition'; if (vueCompilerOptions.target >= 3.3) { - codes.push('const { '); + yield _(`const { `); for (const macro of Object.keys(vueCompilerOptions.macros)) { if (!bindingNames.has(macro)) { - codes.push(macro, ', '); + yield _(macro + `, `); } } - codes.push(`} = await import('${vueCompilerOptions.lib}');\n`); + yield _(`} = await import('${vueCompilerOptions.lib}');\n`); } if (definePropProposalA) { - codes.push(` -declare function defineProp(name: string, options: { required: true } & Record): import('${vueCompilerOptions.lib}').ComputedRef; -declare function defineProp(name: string, options: { default: any } & Record): import('${vueCompilerOptions.lib}').ComputedRef; -declare function defineProp(name?: string, options?: any): import('${vueCompilerOptions.lib}').ComputedRef; -`.trim() + '\n'); + yield _(`declare function defineProp(name: string, options: { required: true } & Record): import('${vueCompilerOptions.lib}').ComputedRef;\n`); + yield _(`declare function defineProp(name: string, options: { default: any } & Record): import('${vueCompilerOptions.lib}').ComputedRef;\n`); + yield _(`declare function defineProp(name?: string, options?: any): import('${vueCompilerOptions.lib}').ComputedRef;\n`); } if (definePropProposalB) { - codes.push(` -declare function defineProp(value: T | (() => T), required?: boolean, rest?: any): import('${vueCompilerOptions.lib}').ComputedRef; -declare function defineProp(value: T | (() => T) | undefined, required: true, rest?: any): import('${vueCompilerOptions.lib}').ComputedRef; -declare function defineProp(value?: T | (() => T), required?: boolean, rest?: any): import('${vueCompilerOptions.lib}').ComputedRef; -`.trim() + '\n'); + yield _(`declare function defineProp(value: T | (() => T), required?: boolean, rest?: any): import('${vueCompilerOptions.lib}').ComputedRef;\n`); + yield _(`declare function defineProp(value: T | (() => T) | undefined, required: true, rest?: any): import('${vueCompilerOptions.lib}').ComputedRef;\n`); + yield _(`declare function defineProp(value?: T | (() => T), required?: boolean, rest?: any): import('${vueCompilerOptions.lib}').ComputedRef;\n`); } - const scriptSetupGeneratedOffset = getLength(codes) - scriptSetupRanges.importSectionEndOffset; + scriptSetupGeneratedOffset = getGeneratedLength() - scriptSetupRanges.importSectionEndOffset; - let setupCodeModifies: [() => void, number, number][] = []; + let setupCodeModifies: [Code[], number, number][] = []; if (scriptSetupRanges.props.define && !scriptSetupRanges.props.name) { const range = scriptSetupRanges.props.withDefaults ?? scriptSetupRanges.props.define; const statement = scriptSetupRanges.props.define.statement; if (statement.start === range.start && statement.end === range.end) { - setupCodeModifies.push([() => codes.push(`const __VLS_props = `), range.start, range.start]); + setupCodeModifies.push([[`const __VLS_props = `], range.start, range.start]); } else { - setupCodeModifies.push([() => { - codes.push(`const __VLS_props = `); - addVirtualCode('scriptSetup', range.start, range.end); - codes.push(`;\n`); - addVirtualCode('scriptSetup', statement.start, range.start); - codes.push(`__VLS_props`); - }, statement.start, range.end]); + setupCodeModifies.push([[ + `const __VLS_props = `, + generateSourceCode(scriptSetup, range.start, range.end), + `;\n`, + generateSourceCode(scriptSetup, statement.start, range.start), + `__VLS_props`, + ], statement.start, range.end]); } } if (scriptSetupRanges.slots.define && !scriptSetupRanges.slots.name) { - setupCodeModifies.push([() => codes.push(`const __VLS_slots = `), scriptSetupRanges.slots.define.start, scriptSetupRanges.slots.define.start]); + setupCodeModifies.push([[`const __VLS_slots = `], scriptSetupRanges.slots.define.start, scriptSetupRanges.slots.define.start]); } if (scriptSetupRanges.emits.define && !scriptSetupRanges.emits.name) { - setupCodeModifies.push([() => codes.push(`const __VLS_emit = `), scriptSetupRanges.emits.define.start, scriptSetupRanges.emits.define.start]); + setupCodeModifies.push([[`const __VLS_emit = `], scriptSetupRanges.emits.define.start, scriptSetupRanges.emits.define.start]); } if (scriptSetupRanges.expose.define) { - setupCodeModifies.push([() => { - if (scriptSetupRanges?.expose.define?.typeArg) { - codes.push(`let __VLS_exposed!: `); - addExtraReferenceVirtualCode('scriptSetup', scriptSetupRanges.expose.define.typeArg.start, scriptSetupRanges.expose.define.typeArg.end); - codes.push(`;\n`); - } - else if (scriptSetupRanges?.expose.define?.arg) { - codes.push(`const __VLS_exposed = `); - addExtraReferenceVirtualCode('scriptSetup', scriptSetupRanges.expose.define.arg.start, scriptSetupRanges.expose.define.arg.end); - codes.push(`;\n`); - } - else { - codes.push(`const __VLS_exposed = {};\n`); - } - }, scriptSetupRanges.expose.define.start, scriptSetupRanges.expose.define.start]); + if (scriptSetupRanges.expose.define?.typeArg) { + setupCodeModifies.push([ + [ + `let __VLS_exposed!: `, + generateSourceCodeForExtraReference(scriptSetup, scriptSetupRanges.expose.define.typeArg.start, scriptSetupRanges.expose.define.typeArg.end), + `;\n`, + ], + scriptSetupRanges.expose.define.start, + scriptSetupRanges.expose.define.start, + ]); + } + else if (scriptSetupRanges.expose.define?.arg) { + setupCodeModifies.push([ + [ + `const __VLS_exposed = `, + generateSourceCodeForExtraReference(scriptSetup, scriptSetupRanges.expose.define.arg.start, scriptSetupRanges.expose.define.arg.end), + `;\n`, + ], + scriptSetupRanges.expose.define.start, + scriptSetupRanges.expose.define.start, + ]); + } + else { + setupCodeModifies.push([ + [`const __VLS_exposed = {};\n`], + scriptSetupRanges.expose.define.start, + scriptSetupRanges.expose.define.start, + ]); + } } setupCodeModifies = setupCodeModifies.sort((a, b) => a[1] - b[1]); if (setupCodeModifies.length) { - addVirtualCode('scriptSetup', scriptSetupRanges.importSectionEndOffset, setupCodeModifies[0][1]); + yield _(generateSourceCode(scriptSetup, scriptSetupRanges.importSectionEndOffset, setupCodeModifies[0][1])); while (setupCodeModifies.length) { - const [generate, _, end] = setupCodeModifies.shift()!; - generate(); + const [codes, _start, end] = setupCodeModifies.shift()!; + for (const code of codes) { + yield _(code); + } if (setupCodeModifies.length) { const nextStart = setupCodeModifies[0][1]; - addVirtualCode('scriptSetup', end, nextStart); + yield _(generateSourceCode(scriptSetup, end, nextStart)); } else { - addVirtualCode('scriptSetup', end); + yield _(generateSourceCode(scriptSetup, end)); } } } else { - addVirtualCode('scriptSetup', scriptSetupRanges.importSectionEndOffset); + yield _(generateSourceCode(scriptSetup, scriptSetupRanges.importSectionEndOffset)); } if (scriptSetupRanges.props.define?.typeArg && scriptSetupRanges.props.withDefaults?.arg) { // fix https://github.com/vuejs/language-tools/issues/1187 - codes.push(`const __VLS_withDefaultsArg = (function (t: T) { return t })(`); - addExtraReferenceVirtualCode('scriptSetup', scriptSetupRanges.props.withDefaults.arg.start, scriptSetupRanges.props.withDefaults.arg.end); - codes.push(`);\n`); + yield _(`const __VLS_withDefaultsArg = (function (t: T) { return t })(`); + yield _(generateSourceCodeForExtraReference(scriptSetup, scriptSetupRanges.props.withDefaults.arg.start, scriptSetupRanges.props.withDefaults.arg.end)); + yield _(`);\n`); } if (!functional && scriptSetupRanges.defineProp.length) { - codes.push(`let __VLS_propsOption_defineProp!: {\n`); + yield _(`let __VLS_propsOption_defineProp!: {\n`); for (const defineProp of scriptSetupRanges.defineProp) { let propName = 'modelValue'; if (defineProp.name && defineProp.nameIsString) { // renaming support - addExtraReferenceVirtualCode('scriptSetup', defineProp.name.start, defineProp.name.end); + yield _(generateSourceCodeForExtraReference(scriptSetup, defineProp.name.start, defineProp.name.end)); } else if (defineProp.name) { propName = scriptSetup.content.substring(defineProp.name.start, defineProp.name.end); - const start = getLength(codes); + const start = getGeneratedLength(); definePropMirrors.set(propName, start); - codes.push(propName); + yield _(propName); } else { - codes.push(propName); + yield _(propName); } - codes.push(`: `); + yield _(`: `); let type = 'any'; if (!defineProp.nameIsString) { @@ -528,182 +537,173 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: } if (defineProp.required) { - codes.push(`{ required: true, type: import('${vueCompilerOptions.lib}').PropType<${type}> },\n`); + yield _(`{ required: true, type: import('${vueCompilerOptions.lib}').PropType<${type}> },\n`); } else { - codes.push(`import('${vueCompilerOptions.lib}').PropType<${type}>,\n`); + yield _(`import('${vueCompilerOptions.lib}').PropType<${type}>,\n`); } } - codes.push(`};\n`); + yield _(`};\n`); } - generateTemplate(functional); + yield* generateTemplate(functional); if (mode === 'return' || mode === 'export') { - if (!vueCompilerOptions.skipTemplateCodegen && (htmlGen?.hasSlot || scriptSetupRanges?.slots.define)) { + if (!vueCompilerOptions.skipTemplateCodegen && (templateCodegen?.hasSlot || scriptSetupRanges?.slots.define)) { usedHelperTypes.WithTemplateSlots = true; - codes.push(`const __VLS_component = `); - generateComponent(functional); - codes.push(`;\n`); - codes.push(mode === 'return' ? 'return ' : 'export default '); - codes.push(`{} as __VLS_WithTemplateSlots>;\n`); + yield _(`const __VLS_component = `); + yield* generateComponent(functional); + yield _(`;\n`); + yield _(mode === 'return' ? 'return ' : 'export default '); + yield _(`{} as __VLS_WithTemplateSlots>;\n`); } else { - codes.push(mode === 'return' ? 'return ' : 'export default '); - generateComponent(functional); - codes.push(`;\n`); + yield _(mode === 'return' ? 'return ' : 'export default '); + yield* generateComponent(functional); + yield _(`;\n`); } } - - return scriptSetupGeneratedOffset; } - function generateComponent(functional: boolean) { - + function* generateComponent(functional: boolean): Generator { if (!scriptSetupRanges) return; - if (scriptRanges?.exportDefault && scriptRanges.exportDefault.expression.start !== scriptRanges.exportDefault.args.start) { + if (script && scriptRanges?.exportDefault && scriptRanges.exportDefault.expression.start !== scriptRanges.exportDefault.args.start) { // use defineComponent() from user space code if it exist - addVirtualCode('script', scriptRanges.exportDefault.expression.start, scriptRanges.exportDefault.args.start); - codes.push(`{\n`); + yield _(generateSourceCode(script, scriptRanges.exportDefault.expression.start, scriptRanges.exportDefault.args.start)); + yield _(`{\n`); } else { - codes.push(`(await import('${vueCompilerOptions.lib}')).defineComponent({\n`); + yield _(`(await import('${vueCompilerOptions.lib}')).defineComponent({\n`); } - codes.push(`setup() {\n`); - codes.push(`return {\n`); - generateSetupReturns(); + yield _(`setup() {\n`); + yield _(`return {\n`); + yield* generateSetupReturns(); if (scriptSetupRanges.expose.define) { - codes.push(`...__VLS_exposed,\n`); + yield _(`...__VLS_exposed,\n`); } - codes.push(`};\n`); - codes.push(`},\n`); - generateComponentOptions(functional); - codes.push(`})`); + yield _(`};\n`); + yield _(`},\n`); + yield* generateComponentOptions(functional); + yield _(`})`); } - function generateComponentOptions(functional: boolean) { - if (scriptSetupRanges && !bypassDefineComponent) { + function* generateComponentOptions(functional: boolean): Generator { + if (scriptSetup && scriptSetupRanges && !bypassDefineComponent) { const ranges = scriptSetupRanges; - const propsCodegens: (() => void)[] = []; + const propsCodegens: (() => Generator)[] = []; if (ranges.props.define?.arg) { const arg = ranges.props.define.arg; - propsCodegens.push(() => { - addExtraReferenceVirtualCode('scriptSetup', arg.start, arg.end); + propsCodegens.push(function* () { + yield _(generateSourceCodeForExtraReference(scriptSetup!, arg.start, arg.end)); }); } if (ranges.props.define?.typeArg) { const typeArg = ranges.props.define.typeArg; - propsCodegens.push(() => { + propsCodegens.push(function* () { usedHelperTypes.DefinePropsToOptions = true; - codes.push(`{} as `); + yield _(`{} as `); if (ranges.props.withDefaults?.arg) { usedHelperTypes.MergePropDefaults = true; - codes.push(`__VLS_WithDefaults<`); + yield _(`__VLS_WithDefaults<`); } - codes.push(`__VLS_TypePropsToRuntimeProps<`); + yield _(`__VLS_TypePropsToRuntimeProps<`); if (functional) { - codes.push(`typeof __VLS_fnPropsTypeOnly`); + yield _(`typeof __VLS_fnPropsTypeOnly`); } else { - addExtraReferenceVirtualCode('scriptSetup', typeArg.start, typeArg.end); + yield _(generateSourceCodeForExtraReference(scriptSetup!, typeArg.start, typeArg.end)); } - codes.push(`>`); + yield _(`>`); if (ranges.props.withDefaults?.arg) { - codes.push(`, typeof __VLS_withDefaultsArg`); - codes.push(`>`); + yield _(`, typeof __VLS_withDefaultsArg`); + yield _(`>`); } }); } if (!functional && ranges.defineProp.length) { - propsCodegens.push(() => { - codes.push(`__VLS_propsOption_defineProp`); + propsCodegens.push(function* () { + yield _(`__VLS_propsOption_defineProp`); }); } if (propsCodegens.length === 1) { - codes.push(`props: `); + yield _(`props: `); for (const generate of propsCodegens) { - generate(); + yield* generate(); } - codes.push(`,\n`); + yield _(`,\n`); } else if (propsCodegens.length >= 2) { - codes.push(`props: {\n`); + yield _(`props: {\n`); for (const generate of propsCodegens) { - codes.push('...'); - generate(); - codes.push(',\n'); + yield _(`...`); + yield* generate(); + yield _(`,\n`); } - codes.push(`},\n`); + yield _(`},\n`); } if (ranges.emits.define) { - codes.push( - `emits: ({} as __VLS_NormalizeEmits),\n`, - ); + yield _(`emits: ({} as __VLS_NormalizeEmits),\n`); } } - if (scriptRanges?.exportDefault?.args) { - addVirtualCode('script', scriptRanges.exportDefault.args.start + 1, scriptRanges.exportDefault.args.end - 1); + if (script && scriptRanges?.exportDefault?.args) { + yield _(generateSourceCode(script, scriptRanges.exportDefault.args.start + 1, scriptRanges.exportDefault.args.end - 1)); } } - function generateSetupReturns() { + function* generateSetupReturns(): Generator { if (scriptSetupRanges && bypassDefineComponent) { // fill $props if (scriptSetupRanges.props.define) { // NOTE: defineProps is inaccurate for $props - codes.push(`$props: __VLS_makeOptional(${scriptSetupRanges.props.name ?? `__VLS_props`}),\n`); - codes.push(`...${scriptSetupRanges.props.name ?? `__VLS_props`},\n`); + yield _(`$props: __VLS_makeOptional(${scriptSetupRanges.props.name ?? `__VLS_props`}),\n`); + yield _(`...${scriptSetupRanges.props.name ?? `__VLS_props`},\n`); } // fill $emit if (scriptSetupRanges.emits.define) { - codes.push(`$emit: ${scriptSetupRanges.emits.name ?? '__VLS_emit'},\n`); + yield _(`$emit: ${scriptSetupRanges.emits.name ?? '__VLS_emit'},\n`); } } } - function generateTemplate(functional: boolean) { + function* generateTemplate(functional: boolean): Generator { generatedTemplate = true; if (!vueCompilerOptions.skipTemplateCodegen) { - - generateExportOptions(); - generateConstNameOption(); - - codes.push(`function __VLS_template() {\n`); - - const templateGened = generateTemplateContext(); - - codes.push(`}\n`); - - generateComponentForTemplateUsage(functional, templateGened.cssIds); + yield* generateExportOptions(); + yield* generateConstNameOption(); + yield _(`function __VLS_template() {\n`); + const cssIds = new Set(); + yield* generateTemplateContext(cssIds); + yield _(`}\n`); + yield* generateComponentForTemplateUsage(functional, cssIds); } else { - codes.push(`function __VLS_template() {\n`); + yield _(`function __VLS_template() {\n`); const templateUsageVars = [...getTemplateUsageVars()]; - codes.push(`// @ts-ignore\n`); - codes.push(`[${templateUsageVars.join(', ')}]\n`); - codes.push(`return {};\n`); - codes.push(`}\n`); + yield _(`// @ts-ignore\n`); + yield _(`[${templateUsageVars.join(', ')}]\n`); + yield _(`return {};\n`); + yield _(`}\n`); } } - function generateComponentForTemplateUsage(functional: boolean, cssIds: Set) { + function* generateComponentForTemplateUsage(functional: boolean, cssIds: Set): Generator { if (scriptSetup && scriptSetupRanges) { - codes.push(`const __VLS_internalComponent = (await import('${vueCompilerOptions.lib}')).defineComponent({\n`); - codes.push(`setup() {\n`); - codes.push(`return {\n`); - generateSetupReturns(); + yield _(`const __VLS_internalComponent = (await import('${vueCompilerOptions.lib}')).defineComponent({\n`); + yield _(`setup() {\n`); + yield _(`return {\n`); + yield* generateSetupReturns(); // bindings const templateUsageVars = getTemplateUsageVars(); for (const [content, bindings] of [ @@ -717,15 +717,15 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: if (!templateUsageVars.has(varName) && !cssIds.has(varName)) { continue; } - const templateOffset = getLength(codes); - codes.push(varName); - codes.push(`: ${varName} as typeof `); + const templateOffset = getGeneratedLength(); + yield _(varName); + yield _(`: ${varName} as typeof `); - const scriptOffset = getLength(codes); - codes.push(varName); - codes.push(',\n'); + const scriptOffset = getGeneratedLength(); + yield _(varName); + yield _(`,\n`); - mirrorBehaviorMappings.push({ + linkedCodeMappings.push({ sourceOffsets: [scriptOffset], generatedOffsets: [templateOffset], lengths: [varName.length], @@ -733,24 +733,24 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: }); } } - codes.push(`};\n`); // return { - codes.push(`},\n`); // setup() { - generateComponentOptions(functional); - codes.push(`});\n`); // defineComponent({ + yield _(`};\n`); // return { + yield _(`},\n`); // setup() { + yield* generateComponentOptions(functional); + yield _(`});\n`); // defineComponent { } else if (script) { - codes.push(`let __VLS_internalComponent!: typeof import('./${path.basename(fileName)}')['default'];\n`); + yield _(`let __VLS_internalComponent!: typeof import('./${path.basename(fileName)}')['default'];\n`); } else { - codes.push(`const __VLS_internalComponent = (await import('${vueCompilerOptions.lib}')).defineComponent({});\n`); + yield _(`const __VLS_internalComponent = (await import('${vueCompilerOptions.lib}')).defineComponent({});\n`); } } - function generateExportOptions() { - codes.push(`\n`); - codes.push(`const __VLS_componentsOption = `); + function* generateExportOptions(): Generator { + yield _(`\n`); + yield _(`const __VLS_componentsOption = `); if (script && scriptRanges?.exportDefault?.componentsOption) { const componentsOption = scriptRanges.exportDefault.componentsOption; - codes.push([ + yield _([ script.content.substring(componentsOption.start, componentsOption.end), 'script', componentsOption.start, @@ -760,39 +760,39 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: ]); } else { - codes.push('{}'); + yield _(`{}`); } - codes.push(`;\n`); + yield _(`;\n`); } - function generateConstNameOption() { - codes.push(`\n`); + function* generateConstNameOption(): Generator { + yield _(`\n`); if (script && scriptRanges?.exportDefault?.nameOption) { const nameOption = scriptRanges.exportDefault.nameOption; - codes.push(`const __VLS_name = `); - codes.push(`${script.content.substring(nameOption.start, nameOption.end)} as const`); - codes.push(`;\n`); + yield _(`const __VLS_name = `); + yield _(`${script.content.substring(nameOption.start, nameOption.end)} as const`); + yield _(`;\n`); } else if (scriptSetup) { - codes.push(`let __VLS_name!: '${path.basename(fileName.substring(0, fileName.lastIndexOf('.')))}';\n`); + yield _(`let __VLS_name!: '${path.basename(fileName.substring(0, fileName.lastIndexOf('.')))}';\n`); } else { - codes.push(`const __VLS_name = undefined;\n`); + yield _(`const __VLS_name = undefined;\n`); } } - function generateTemplateContext() { + function* generateTemplateContext(cssIds = new Set()): Generator { const useGlobalThisTypeInCtx = fileName.endsWith('.html'); - codes.push(`let __VLS_ctx!: ${useGlobalThisTypeInCtx ? 'typeof globalThis &' : ''}`); - codes.push(`InstanceType<__VLS_PickNotAny {}>> & {\n`); + yield _(`let __VLS_ctx!: ${useGlobalThisTypeInCtx ? 'typeof globalThis &' : ''}`); + yield _(`InstanceType<__VLS_PickNotAny {}>> & {\n`); /* CSS Module */ for (let i = 0; i < styles.length; i++) { const style = styles[i]; if (style.module) { - codes.push(`${style.module}: Record & __VLS_Prettify<{}`); + yield _(`${style.module}: Record & __VLS_Prettify<{}`); for (const className of style.classNames) { - generateCssClassProperty( + yield* generateCssClassProperty( i, className.text, className.offset, @@ -801,27 +801,27 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: true, ); } - codes.push('>;\n'); + yield _(`>;\n`); } } - codes.push(`};\n`); + yield _(`};\n`); /* Components */ - codes.push('/* Components */\n'); - codes.push(`let __VLS_otherComponents!: NonNullable & typeof __VLS_componentsOption;\n`); - codes.push(`let __VLS_own!: __VLS_SelfComponent { ${getSlotsPropertyName(vueCompilerOptions.target)}: typeof ${scriptSetupRanges?.slots?.name ?? '__VLS_slots'} })>;\n`); - codes.push(`let __VLS_localComponents!: typeof __VLS_otherComponents & Omit;\n`); - codes.push(`let __VLS_components!: typeof __VLS_localComponents & __VLS_GlobalComponents & typeof __VLS_ctx;\n`); // for html completion, TS references... + yield _(`/* Components */\n`); + yield _(`let __VLS_otherComponents!: NonNullable & typeof __VLS_componentsOption;\n`); + yield _(`let __VLS_own!: __VLS_SelfComponent { ${getSlotsPropertyName(vueCompilerOptions.target)}: typeof ${scriptSetupRanges?.slots?.name ?? '__VLS_slots'} })>;\n`); + yield _(`let __VLS_localComponents!: typeof __VLS_otherComponents & Omit;\n`); + yield _(`let __VLS_components!: typeof __VLS_localComponents & __VLS_GlobalComponents & typeof __VLS_ctx;\n`); // for html completion, TS references... /* Style Scoped */ - codes.push('/* Style Scoped */\n'); - codes.push('type __VLS_StyleScopedClasses = {}'); + yield _(`/* Style Scoped */\n`); + yield _(`type __VLS_StyleScopedClasses = {}`); for (let i = 0; i < styles.length; i++) { const style = styles[i]; const option = vueCompilerOptions.experimentalResolveStyleCssClasses; if (option === 'always' || (option === 'scoped' && style.scoped)) { for (const className of style.classNames) { - generateCssClassProperty( + yield* generateCssClassProperty( i, className.text, className.offset, @@ -832,153 +832,147 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: } } } - codes.push(';\n'); - codes.push('let __VLS_styleScopedClasses!: __VLS_StyleScopedClasses | keyof __VLS_StyleScopedClasses | (keyof __VLS_StyleScopedClasses)[];\n'); + yield _(`;\n`); + yield _('let __VLS_styleScopedClasses!: __VLS_StyleScopedClasses | keyof __VLS_StyleScopedClasses | (keyof __VLS_StyleScopedClasses)[];\n'); - codes.push(`/* CSS variable injection */\n`); - const cssIds = generateCssVars(); - codes.push(`/* CSS variable injection end */\n`); + yield _(`/* CSS variable injection */\n`); + yield* generateCssVars(cssIds); + yield _(`/* CSS variable injection end */\n`); - if (htmlGen) { - setTracking(false); - for (const s of htmlGen.codes) { - codes.push(s); - } - setTracking(true); - for (const s of htmlGen.codeStacks) { - codeStacks.push(s); + if (templateCodegen) { + for (let i = 0; i < templateCodegen.tsCodes.length; i++) { + yield [ + templateCodegen.tsCodes[i], + templateCodegen.tsCodegenStacks[i], + ]; } } - - if (!htmlGen) { - codes.push(`// no template\n`); + else { + yield _(`// no template\n`); if (!scriptSetupRanges?.slots.define) { - codes.push(`const __VLS_slots = {};\n`); + yield _(`const __VLS_slots = {};\n`); } } - codes.push(`return ${scriptSetupRanges?.slots.name ?? '__VLS_slots'};\n`); - - return { cssIds }; + yield _(`return ${scriptSetupRanges?.slots.name ?? '__VLS_slots'};\n`); - function generateCssClassProperty(styleIndex: number, classNameWithDot: string, offset: number, propertyType: string, optional: boolean, referencesCodeLens: boolean) { - codes.push(`\n & { `); - codes.push([ - '', - 'style_' + styleIndex, - offset, - disableAllFeatures({ - navigation: true, - __referencesCodeLens: referencesCodeLens, - }), - ]); - codes.push(`'`); - codes.push([ - '', - 'style_' + styleIndex, - offset, - disableAllFeatures({ - navigation: { - resolveRenameNewName: normalizeCssRename, - resolveRenameEditText: applyCssRename, - }, - }), - ]); - codes.push([ - classNameWithDot.substring(1), - 'style_' + styleIndex, - offset + 1, - disableAllFeatures({ __combineLastMappping: true }), - ]); - codes.push(`'`); - codes.push([ - '', - 'style_' + styleIndex, - offset + classNameWithDot.length, - disableAllFeatures({}), - ]); - codes.push(`${optional ? '?' : ''}: ${propertyType}`); - codes.push(` }`); - } - function generateCssVars() { - - const emptyLocalVars = new Map(); - const identifiers = new Set(); - - for (const style of styles) { - for (const cssBind of style.cssVars) { - walkInterpolationFragment( - ts, - cssBind.text, - ts.createSourceFile('/a.txt', cssBind.text, ts.ScriptTarget.ESNext), - (frag, fragOffset, onlyForErrorMapping) => { - if (fragOffset === undefined) { - codes.push(frag); - } - else { - codes.push([ - frag, - style.name, - cssBind.offset + fragOffset, - onlyForErrorMapping - ? disableAllFeatures({ verification: true }) - : enableAllFeatures({}), - ]); - } - }, - emptyLocalVars, - identifiers, - vueCompilerOptions, - ); - codes.push(';\n'); + } + function* generateCssClassProperty( + styleIndex: number, + classNameWithDot: string, + offset: number, + propertyType: string, + optional: boolean, + referencesCodeLens: boolean + ): Generator { + yield _(`\n & { `); + yield _([ + '', + 'style_' + styleIndex, + offset, + disableAllFeatures({ + navigation: true, + __referencesCodeLens: referencesCodeLens, + }), + ]); + yield _(`'`); + yield _([ + '', + 'style_' + styleIndex, + offset, + disableAllFeatures({ + navigation: { + resolveRenameNewName: normalizeCssRename, + resolveRenameEditText: applyCssRename, + }, + }), + ]); + yield _([ + classNameWithDot.substring(1), + 'style_' + styleIndex, + offset + 1, + disableAllFeatures({ __combineLastMappping: true }), + ]); + yield _(`'`); + yield _([ + '', + 'style_' + styleIndex, + offset + classNameWithDot.length, + disableAllFeatures({}), + ]); + yield _(`${optional ? '?' : ''}: ${propertyType}`); + yield _(` }`); + } + function* generateCssVars(cssIds: Set): Generator { + + const emptyLocalVars = new Map(); + + for (const style of styles) { + for (const cssBind of style.cssVars) { + for (const [segment, offset, onlyError] of eachInterpolationSegment( + ts, + cssBind.text, + ts.createSourceFile('/a.txt', cssBind.text, ts.ScriptTarget.ESNext), + emptyLocalVars, + cssIds, + vueCompilerOptions, + )) { + if (offset === undefined) { + yield _(segment); + } + else { + yield _([ + segment, + style.name, + cssBind.offset + offset, + onlyError + ? disableAllFeatures({ verification: true }) + : enableAllFeatures({}), + ]); + } } + yield _(`;\n`); } - - return identifiers; } } function getTemplateUsageVars() { const usageVars = new Set(); - if (htmlGen) { + if (templateCodegen) { // fix import components unused report for (const varName of bindingNames) { - if (!!htmlGen.tagNames[varName] || !!htmlGen.tagNames[hyphenateTag(varName)]) { + if (templateCodegen.tagNames.has(varName) || templateCodegen.tagNames.has(hyphenateTag(varName))) { usageVars.add(varName); } } - for (const tag of Object.keys(htmlGen.tagNames)) { + for (const tag of Object.keys(templateCodegen.tagNames)) { if (tag.indexOf('.') >= 0) { usageVars.add(tag.split('.')[0]); } } - for (const _id of htmlGen.accessedGlobalVariables) { + for (const _id of templateCodegen.accessedGlobalVariables) { usageVars.add(_id); } } return usageVars; } - function addVirtualCode(vueTag: 'script' | 'scriptSetup', start: number, end?: number) { - offsetStack(); - codes.push([ - (vueTag === 'script' ? script : scriptSetup)!.content.substring(start, end), - vueTag, + function generateSourceCode(block: SfcBlock, start: number, end?: number): Code { + return [ + block.content.substring(start, end), + block.name, start, enableAllFeatures({}), // diagnostic also working for setup() returns unused in template checking - ]); - resetOffsetStack(); + ]; } - function addExtraReferenceVirtualCode(vueTag: 'script' | 'scriptSetup', start: number, end: number) { - offsetStack(); - codes.push([ - (vueTag === 'script' ? script : scriptSetup)!.content.substring(start, end), - vueTag, + function generateSourceCodeForExtraReference(block: SfcBlock, start: number, end: number): Code { + return [ + block.content.substring(start, end), + block.name, start, disableAllFeatures({ navigation: true }), - ]); - resetOffsetStack(); + ]; } } diff --git a/packages/language-core/src/generators/template.ts b/packages/language-core/src/generators/template.ts index 8b4e6af233..973cb321b6 100644 --- a/packages/language-core/src/generators/template.ts +++ b/packages/language-core/src/generators/template.ts @@ -1,12 +1,12 @@ -import { toString, track } from '@volar/language-core'; +import { toString } from '@volar/language-core'; import * as CompilerDOM from '@vue/compiler-dom'; import { camelize, capitalize } from '@vue/shared'; import { minimatch } from 'minimatch'; import type * as ts from 'typescript/lib/tsserverlibrary'; -import { Code, Sfc, VueCodeInformation, VueCompilerOptions } from '../types'; +import { Code, CodeAndStack, Sfc, VueCodeInformation, VueCompilerOptions } from '../types'; import { hyphenateAttr, hyphenateTag } from '../utils/shared'; -import { collectVars, walkInterpolationFragment } from '../utils/transform'; -import { mergeFeatureSettings, disableAllFeatures, enableAllFeatures } from './utils'; +import { collectVars, eachInterpolationSegment } from '../utils/transform'; +import { disableAllFeatures, enableAllFeatures, getStack, mergeFeatureSettings } from './utils'; const presetInfos = { disabledAll: disableAllFeatures({}), @@ -63,7 +63,9 @@ const transformContext: CompilerDOM.TransformContext = { expressionPlugins: ['typescript'], }; -export function generate( +type _CodeAndStack = [codeType: 'ts' | 'tsFormat' | 'inlineCss', ...codeAndStack: CodeAndStack]; + +export function* generate( ts: typeof import('typescript/lib/tsserverlibrary'), compilerOptions: ts.CompilerOptions, vueCompilerOptions: VueCompilerOptions, @@ -76,10 +78,43 @@ export function generate( codegenStack: boolean, ) { + const processDirectiveComment = (code: Code) => { + if (ignoreError && typeof code !== 'string') { + const data = code[3]; + if (data.verification) { + code[3] = { + ...data, + verification: false, + }; + } + } + if (expectErrorToken && typeof code !== 'string') { + const token = expectErrorToken; + const data = code[3]; + if (data.verification) { + code[3] = { + ...data, + verification: { + shouldReport: () => { + token.errors++; + return false; + }, + }, + }; + } + } + return code; + }; + const _ts = codegenStack + ? (code: Code): _CodeAndStack => ['ts', processDirectiveComment(code), getStack()] + : (code: Code): _CodeAndStack => ['ts', processDirectiveComment(code), '']; + const _tsFormat = codegenStack + ? (code: Code): _CodeAndStack => ['tsFormat', code, getStack()] + : (code: Code): _CodeAndStack => ['tsFormat', code, '']; + const _inlineCss = codegenStack + ? (code: Code): _CodeAndStack => ['inlineCss', code, getStack()] + : (code: Code): _CodeAndStack => ['inlineCss', code, '']; const nativeTags = new Set(vueCompilerOptions.nativeTags); - const [codes, codeStacks] = codegenStack ? track([] as Code[]) : [[], []]; - const [formatCodes, formatCodeStacks] = codegenStack ? track([] as Code[]) : [[], []]; - const [cssCodes, cssCodeStacks] = codegenStack ? track([] as Code[]) : [[], []]; const slots = new Map(); const slotExps = new Map(); - const tagNames = collectTagOffsets(); + const tagOffsetsMap = collectTagOffsets(); const localVars = new Map(); const tempVars: { text: string, @@ -102,10 +137,10 @@ export function generate( const componentCtxVar2EmitEventsVar = new Map(); let hasSlot = false; - let elementIndex = 0; - let ignoreStart: undefined | number; - let expectedErrorStart: undefined | number; + let ignoreError = false; + let expectErrorToken: { errors: number; } | undefined; let expectedErrorNode: CompilerDOM.CommentNode | undefined; + let elementIndex = 0; if (slotsAssignName) { localVars.set(slotsAssignName, 1); @@ -115,93 +150,109 @@ export function generate( localVars.set(propsAssignName, 1); } - generatePreResolveComponents(); + yield* generatePreResolveComponents(); if (template.ast) { - visitNode(template.ast, undefined, undefined, undefined); + yield* generateAstNode(template.ast, undefined, undefined, undefined); } - generateStyleScopedClasses(); + yield* generateStyleScopedClasses(); if (!hasScriptSetupSlots) { - codes.push( - 'var __VLS_slots!:', - ...createSlotsTypeCode(), - ';\n', - ); + yield _ts('var __VLS_slots!:'); + yield* generateSlotsType(); + yield _ts(';\n'); } - generateAutoImportCompletionCode(); + yield* generateExtraAutoImport(); return { - codes, - codeStacks, - formatCodes, - formatCodeStacks, - cssCodes, - cssCodeStacks, - tagNames, + tagOffsetsMap, accessedGlobalVariables, hasSlot, }; - function* createSlotsTypeCode(): Generator { - for (const [exp, slot] of slotExps) { - hasSlot = true; - yield `Partial, (_: typeof ${slot.varName}) => any>> &\n`; + function collectTagOffsets() { + + const tagOffsetsMap = new Map(); + + if (!template.ast) { + return tagOffsetsMap; } - yield `{\n`; - for (const [_, slot] of slots) { - hasSlot = true; - if (slot.name && slot.loc !== undefined) { - yield* createObjectPropertyCode( - slot.name, - slot.loc, - mergeFeatureSettings(presetInfos.slotNameExport, disableAllFeatures({ __referencesCodeLens: true })), - slot.nodeLoc - ); + + for (const node of eachElementNode(template.ast)) { + if (node.tag === 'slot') { + // ignore + } + else if (node.tag === 'component' || node.tag === 'Component') { + for (const prop of node.props) { + if (prop.type === CompilerDOM.NodeTypes.ATTRIBUTE && prop.name === 'is' && prop.value) { + const tag = prop.value.content; + let offsets = tagOffsetsMap.get(tag); + if (!offsets) { + tagOffsetsMap.set(tag, offsets = []); + } + offsets.push(prop.value.loc.start.offset + prop.value.loc.source.lastIndexOf(tag)); + break; + } + } } else { - yield ['', 'template', slot.tagRange[0], mergeFeatureSettings(presetInfos.slotNameExport, disableAllFeatures({ __referencesCodeLens: true }))]; - yield 'default'; - yield ['', 'template', slot.tagRange[1], disableAllFeatures({ __combineLastMappping: true })]; + let offsets = tagOffsetsMap.get(node.tag); + if (!offsets) { + tagOffsetsMap.set(node.tag, offsets = []); + } + const source = template.content.substring(node.loc.start.offset); + const startTagOffset = node.loc.start.offset + source.indexOf(node.tag); + + offsets.push(startTagOffset); // start tag + if (!node.isSelfClosing && template.lang === 'html') { + const endTagOffset = node.loc.start.offset + node.loc.source.lastIndexOf(node.tag); + if (endTagOffset !== startTagOffset) { + offsets.push(endTagOffset); // end tag + } + } } - yield `?(_: typeof ${slot.varName}): any,\n`; } - yield `}`; + + return tagOffsetsMap; } - function generateStyleScopedClasses() { - codes.push(`if (typeof __VLS_styleScopedClasses === 'object' && !Array.isArray(__VLS_styleScopedClasses)) {\n`); - for (const { className, offset } of scopedClasses) { - codes.push( - `__VLS_styleScopedClasses[`, - ...createStringLiteralKeyCode( - className, - offset, - mergeFeatureSettings( - presetInfos.scopedClassName, - disableAllFeatures({ __displayWithLink: stylesScopedClasses.has(className) }), - ), - ), - `];\n`, - ); + function* generateExpectErrorComment(): Generator<_CodeAndStack> { + + if (expectErrorToken && expectedErrorNode) { + const token = expectErrorToken; + yield _ts([ + '', + 'template', + expectedErrorNode.loc.start.offset, + disableAllFeatures({ + verification: { + shouldReport: () => token.errors === 0, + }, + }), + ]); + yield _ts('// @ts-expect-error __VLS_TS_EXPECT_ERROR'); + yield _ts([ + '', + 'template', + expectedErrorNode.loc.end.offset, + disableAllFeatures({ __combineLastMappping: true }), + ]); + yield _ts('\n;\n'); } - codes.push('}\n'); - } - function toCanonicalComponentName(tagText: string) { - return validTsVarReg.test(tagText) - ? tagText - : capitalize(camelize(tagText.replace(colonReg, '-'))); + ignoreError = false; + expectErrorToken = undefined; + expectedErrorNode = undefined; } - function* createCanonicalComponentNameCode(tagText: string, offset: number, info: VueCodeInformation): Generator { + function* generateCanonicalComponentName(tagText: string, offset: number, info: VueCodeInformation): Generator<_CodeAndStack> { if (validTsVarReg.test(tagText)) { - yield [tagText, 'template', offset, info]; + yield _ts([tagText, 'template', offset, info]); } else { - yield* createCamelizeCode( + yield* generateCamelized( capitalize(tagText.replace(colonReg, '-')), offset, info @@ -209,20 +260,54 @@ export function generate( } } - function getPossibleOriginalComponentNames(tagText: string) { - return [...new Set([ - // order is important: https://github.com/vuejs/language-tools/issues/2010 - capitalize(camelize(tagText)), - camelize(tagText), - tagText, - ])]; + function* generateSlotsType(): Generator<_CodeAndStack> { + for (const [exp, slot] of slotExps) { + hasSlot = true; + yield _ts(`Partial, (_: typeof ${slot.varName}) => any>> &\n`); + } + yield _ts(`{\n`); + for (const [_, slot] of slots) { + hasSlot = true; + if (slot.name && slot.loc !== undefined) { + yield* generateObjectProperty( + slot.name, + slot.loc, + mergeFeatureSettings(presetInfos.slotNameExport, disableAllFeatures({ __referencesCodeLens: true })), + slot.nodeLoc + ); + } + else { + yield _ts(['', 'template', slot.tagRange[0], mergeFeatureSettings(presetInfos.slotNameExport, disableAllFeatures({ __referencesCodeLens: true }))]); + yield _ts('default'); + yield _ts(['', 'template', slot.tagRange[1], disableAllFeatures({ __combineLastMappping: true })]); + } + yield _ts(`?(_: typeof ${slot.varName}): any,\n`); + } + yield _ts(`}`); } - function generatePreResolveComponents() { + function* generateStyleScopedClasses(): Generator<_CodeAndStack> { + yield _ts(`if (typeof __VLS_styleScopedClasses === 'object' && !Array.isArray(__VLS_styleScopedClasses)) {\n`); + for (const { className, offset } of scopedClasses) { + yield _ts(`__VLS_styleScopedClasses[`); + yield* generateStringLiteralKey( + className, + offset, + mergeFeatureSettings( + presetInfos.scopedClassName, + disableAllFeatures({ __displayWithLink: stylesScopedClasses.has(className) }), + ), + ); + yield _ts(`];\n`); + } + yield _ts('}\n'); + } + + function* generatePreResolveComponents(): Generator<_CodeAndStack> { - codes.push(`let __VLS_resolvedLocalAndGlobalComponents!: {}\n`); + yield _ts(`let __VLS_resolvedLocalAndGlobalComponents!: {}\n`); - for (const tagName in tagNames) { + for (const [tagName] of tagOffsetsMap) { if (nativeTags.has(tagName)) continue; @@ -231,213 +316,96 @@ export function generate( if (isNamespacedTag) continue; - codes.push( - `& __VLS_WithComponent<'${toCanonicalComponentName(tagName)}', typeof __VLS_localComponents, `, - // order is important: https://github.com/vuejs/language-tools/issues/2010 - `"${capitalize(camelize(tagName))}", `, - `"${camelize(tagName)}", `, - `"${tagName}"`, - '>\n', - ); + yield _ts(`& __VLS_WithComponent<'${getCanonicalComponentName(tagName)}', typeof __VLS_localComponents, `); + // order is important: https://github.com/vuejs/language-tools/issues/2010 + yield _ts(`"${capitalize(camelize(tagName))}", `); + yield _ts(`"${camelize(tagName)}", `); + yield _ts(`"${tagName}"`); + yield _ts('>\n'); } - codes.push(`;\n`); + yield _ts(`;\n`); - for (const tagName in tagNames) { - - const tagOffsets = tagNames[tagName]; + for (const [tagName, tagOffsets] of tagOffsetsMap) { for (const tagOffset of tagOffsets) { if (nativeTags.has(tagName)) { - codes.push( - '__VLS_intrinsicElements', - ...createPropertyAccessCode( - tagName, + yield _ts('__VLS_intrinsicElements'); + yield* generatePropertyAccess( + tagName, + tagOffset, + mergeFeatureSettings( + presetInfos.tagReference, + { + navigation: true + }, + ...(nativeTags.has(tagName) ? [ + presetInfos.tagHover, + presetInfos.diagnosticOnly, + ] : []), + ), + ); + yield _ts(';'); + } + else if (validTsVarReg.test(camelize(tagName))) { + for (const shouldCapitalize of tagName[0] === tagName.toUpperCase() ? [false] : [true, false]) { + const expectName = shouldCapitalize ? capitalize(camelize(tagName)) : camelize(tagName); + yield _ts('__VLS_components.'); + yield* generateCamelized( + shouldCapitalize ? capitalize(tagName) : tagName, tagOffset, mergeFeatureSettings( presetInfos.tagReference, { - navigation: true + navigation: { + resolveRenameNewName: tagName !== expectName ? camelizeComponentName : undefined, + resolveRenameEditText: getTagRenameApply(tagName), + } }, ...(nativeTags.has(tagName) ? [ presetInfos.tagHover, presetInfos.diagnosticOnly, ] : []), ), - ), - ';', - ); - } - else if (validTsVarReg.test(camelize(tagName))) { - for (const shouldCapitalize of tagName[0] === tagName.toUpperCase() ? [false] : [true, false]) { - const expectName = shouldCapitalize ? capitalize(camelize(tagName)) : camelize(tagName); - codes.push( - '__VLS_components.', - ...createCamelizeCode( - shouldCapitalize ? capitalize(tagName) : tagName, - tagOffset, - mergeFeatureSettings( - presetInfos.tagReference, - { - navigation: { - resolveRenameNewName: tagName !== expectName ? camelizeComponentName : undefined, - resolveRenameEditText: getTagRenameApply(tagName), - } - }, - ...(nativeTags.has(tagName) ? [ - presetInfos.tagHover, - presetInfos.diagnosticOnly, - ] : []), - ), - ), - ';', ); + yield _ts(';'); } } } - codes.push('\n'); + yield _ts('\n'); if ( !nativeTags.has(tagName) && validTsVarReg.test(camelize(tagName)) ) { - codes.push( - '// @ts-ignore\n', // #2304 - '[', - ); + yield _ts('// @ts-ignore\n'); // #2304 + yield _ts('['); for (const tagOffset of tagOffsets) { - codes.push( - ...createCamelizeCode( - capitalize(tagName), - tagOffset, - disableAllFeatures({ - completion: { - isAdditional: true, - onlyImport: true, - }, - }), - ), - ',', + yield* generateCamelized( + capitalize(tagName), + tagOffset, + disableAllFeatures({ + completion: { + isAdditional: true, + onlyImport: true, + }, + }), ); + yield _ts(','); } - codes.push(`];\n`); - } - } - } - - function collectTagOffsets() { - - const tagOffsetsMap: Record = {}; - - if (!template.ast) { - return tagOffsetsMap; - } - - walkElementNodes(template.ast, node => { - if (node.tag === 'slot') { - // ignore - } - else if (node.tag === 'component' || node.tag === 'Component') { - for (const prop of node.props) { - if (prop.type === CompilerDOM.NodeTypes.ATTRIBUTE && prop.name === 'is' && prop.value) { - const tag = prop.value.content; - tagOffsetsMap[tag] ??= []; - tagOffsetsMap[tag].push(prop.value.loc.start.offset + prop.value.loc.source.lastIndexOf(tag)); - break; - } - } - } - else { - tagOffsetsMap[node.tag] ??= []; - - const offsets = tagOffsetsMap[node.tag]; - const source = template.content.substring(node.loc.start.offset); - const startTagOffset = node.loc.start.offset + source.indexOf(node.tag); - - offsets.push(startTagOffset); // start tag - if (!node.isSelfClosing && template.lang === 'html') { - const endTagOffset = node.loc.start.offset + node.loc.source.lastIndexOf(node.tag); - if (endTagOffset !== startTagOffset) { - offsets.push(endTagOffset); // end tag - } - } + yield _ts(`];\n`); } - }); - - return tagOffsetsMap; - } - - function resolveComment() { - if (ignoreStart !== undefined) { - for (let i = ignoreStart; i < codes.length; i++) { - const code = codes[i]; - if (typeof code === 'string') { - continue; - } - const data = code[3]; - if (data.verification) { - code[3] = { - ...data, - verification: false, - }; - } - } - ignoreStart = undefined; - } - if (expectedErrorStart !== undefined && expectedErrorStart !== codes.length && expectedErrorNode) { - let errors = 0; - const suppressError = () => { - errors++; - return false; - }; - for (let i = expectedErrorStart; i < codes.length; i++) { - const code = codes[i]; - if (typeof code === 'string') { - continue; - } - const data = code[3]; - if (data.verification) { - code[3] = { - ...data, - verification: { - shouldReport: suppressError, - }, - }; - } - } - codes.push( - [ - '', - 'template', - expectedErrorNode.loc.start.offset, - disableAllFeatures({ - verification: { - shouldReport: () => errors === 0, - }, - }), - ], - '// @ts-expect-error __VLS_TS_EXPECT_ERROR', - [ - '', - 'template', - expectedErrorNode.loc.end.offset, - disableAllFeatures({ __combineLastMappping: true }), - ], - '\n;\n', - ); - expectedErrorStart = undefined; - expectedErrorNode = undefined; } } - function visitNode( + function* generateAstNode( node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode | CompilerDOM.InterpolationNode | CompilerDOM.CompoundExpressionNode | CompilerDOM.TextNode | CompilerDOM.SimpleExpressionNode, parentEl: CompilerDOM.ElementNode | undefined, prevNode: CompilerDOM.TemplateChildNode | undefined, componentCtxVar: string | undefined, - ): void { + ): Generator<_CodeAndStack> { - resolveComment(); + yield* generateExpectErrorComment(); if (prevNode?.type === CompilerDOM.NodeTypes.COMMENT) { const commentText = prevNode.content.trim().split(' ')[0]; @@ -445,10 +413,10 @@ export function generate( return; } else if (commentText.match(/^@vue-ignore\b[\s\S]*/)) { - ignoreStart = codes.length; + ignoreError = true; } else if (commentText.match(/^@vue-expect-error\b[\s\S]*/)) { - expectedErrorStart = codes.length; + expectErrorToken = { errors: 0 }; expectedErrorNode = prevNode; } } @@ -456,33 +424,33 @@ export function generate( if (node.type === CompilerDOM.NodeTypes.ROOT) { let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - visitNode(childNode, parentEl, prev, componentCtxVar); + yield* generateAstNode(childNode, parentEl, prev, componentCtxVar); prev = childNode; } - resolveComment(); + yield* generateExpectErrorComment(); } else if (node.type === CompilerDOM.NodeTypes.ELEMENT) { const vForNode = getVForNode(node); const vIfNode = getVIfNode(node); if (vForNode) { - visitVForNode(vForNode, parentEl, componentCtxVar); + yield* generateVFor(vForNode, parentEl, componentCtxVar); } else if (vIfNode) { - visitVIfNode(vIfNode, parentEl, componentCtxVar); + yield* generateVIf(vIfNode, parentEl, componentCtxVar); } else { - visitElementNode(node, parentEl, componentCtxVar); + yield* generateElement(node, parentEl, componentCtxVar); } } else if (node.type === CompilerDOM.NodeTypes.TEXT_CALL) { // {{ var }} - visitNode(node.content, parentEl, undefined, componentCtxVar); + yield* generateAstNode(node.content, parentEl, undefined, componentCtxVar); } else if (node.type === CompilerDOM.NodeTypes.COMPOUND_EXPRESSION) { // {{ ... }} {{ ... }} for (const childNode of node.children) { if (typeof childNode === 'object') { - visitNode(childNode, parentEl, undefined, componentCtxVar); + yield* generateAstNode(childNode, parentEl, undefined, componentCtxVar); } } } @@ -503,42 +471,38 @@ export function generate( content = content + rightCharacter; } - codes.push( - ...createInterpolationCode( - content, - node.content.loc, - start, - presetInfos.all, - '(', - ');\n', - ), + yield* generateInterpolation( + content, + node.content.loc, + start, + presetInfos.all, + '(', + ');\n', ); const lines = content.split('\n'); - formatCodes.push( - ...createFormatCode( - content, - start, - lines.length <= 1 ? formatBrackets.curly : [ - formatBrackets.curly[0], - lines[lines.length - 1].trim() === '' ? '' : formatBrackets.curly[1], - ], - ), + yield* generateTsFormat( + content, + start, + lines.length <= 1 ? formatBrackets.curly : [ + formatBrackets.curly[0], + lines[lines.length - 1].trim() === '' ? '' : formatBrackets.curly[1], + ], ); } else if (node.type === CompilerDOM.NodeTypes.IF) { // v-if / v-else-if / v-else - visitVIfNode(node, parentEl, componentCtxVar); + yield* generateVIf(node, parentEl, componentCtxVar); } else if (node.type === CompilerDOM.NodeTypes.FOR) { // v-for - visitVForNode(node, parentEl, componentCtxVar); + yield* generateVFor(node, parentEl, componentCtxVar); } else if (node.type === CompilerDOM.NodeTypes.TEXT) { // not needed progress } } - function visitVIfNode(node: CompilerDOM.IfNode, parentEl: CompilerDOM.ElementNode | undefined, componentCtxVar: string | undefined) { + function* generateVIf(node: CompilerDOM.IfNode, parentEl: CompilerDOM.ElementNode | undefined, componentCtxVar: string | undefined): Generator<_CodeAndStack> { let originalBlockConditionsLength = blockConditions.length; @@ -547,52 +511,50 @@ export function generate( const branch = node.branches[i]; if (i === 0) - codes.push('if'); + yield _ts('if'); else if (branch.condition) - codes.push('else if'); + yield _ts('else if'); else - codes.push('else'); + yield _ts('else'); let addedBlockCondition = false; if (branch.condition?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { - codes.push(` `); - const beforeCodeLength = codes.length; - codes.push( - ...createInterpolationCode( - branch.condition.content, - branch.condition.loc, - branch.condition.loc.start.offset, - presetInfos.all, - '(', - ')', - ), + yield _ts(` `); + yield* generateInterpolation( + branch.condition.content, + branch.condition.loc, + branch.condition.loc.start.offset, + presetInfos.all, + '(', + ')', ); - const afterCodeLength = codes.length; - - formatCodes.push( - ...createFormatCode( - branch.condition.content, - branch.condition.loc.start.offset, - formatBrackets.normal, - ), + blockConditions.push( + toString( + [...generateInterpolation(branch.condition.content, branch.condition.loc, undefined, undefined, '(', ')')] + .map(code => code[1]) + ) ); - - blockConditions.push(toString(codes.slice(beforeCodeLength, afterCodeLength))); addedBlockCondition = true; + + yield* generateTsFormat( + branch.condition.content, + branch.condition.loc.start.offset, + formatBrackets.normal, + ); } - codes.push(` {\n`); + yield _ts(` {\n`); let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of branch.children) { - visitNode(childNode, parentEl, prev, componentCtxVar); + yield* generateAstNode(childNode, parentEl, prev, componentCtxVar); prev = childNode; } - resolveComment(); + yield* generateExpectErrorComment(); - generateAutoImportCompletionCode(); - codes.push('}\n'); + yield* generateExtraAutoImport(); + yield _ts('}\n'); if (addedBlockCondition) { blockConditions[blockConditions.length - 1] = `!(${blockConditions[blockConditions.length - 1]})`; @@ -602,14 +564,14 @@ export function generate( blockConditions.length = originalBlockConditionsLength; } - function visitVForNode(node: CompilerDOM.ForNode, parentEl: CompilerDOM.ElementNode | undefined, componentCtxVar: string | undefined) { + function* generateVFor(node: CompilerDOM.ForNode, parentEl: CompilerDOM.ElementNode | undefined, componentCtxVar: string | undefined): Generator<_CodeAndStack> { const { source, value, key, index } = node.parseResult; const leftExpressionRange = value ? { start: (value ?? key ?? index).loc.start.offset, end: (index ?? key ?? value).loc.end.offset } : undefined; const leftExpressionText = leftExpressionRange ? node.loc.source.substring(leftExpressionRange.start - node.loc.start.offset, leftExpressionRange.end - node.loc.start.offset) : undefined; const forBlockVars: string[] = []; - codes.push(`for (const [`); + yield _ts(`for (const [`); if (leftExpressionRange && leftExpressionText) { const collectAst = createTsAst(node.parseResult, `const [${leftExpressionText}]`); @@ -618,41 +580,37 @@ export function generate( for (const varName of forBlockVars) localVars.set(varName, (localVars.get(varName) ?? 0) + 1); - codes.push([leftExpressionText, 'template', leftExpressionRange.start, presetInfos.all]); - formatCodes.push(...createFormatCode(leftExpressionText, leftExpressionRange.start, formatBrackets.normal)); + yield _ts([leftExpressionText, 'template', leftExpressionRange.start, presetInfos.all]); + yield* generateTsFormat(leftExpressionText, leftExpressionRange.start, formatBrackets.normal); } - codes.push(`] of __VLS_getVForSourceType`); + yield _ts(`] of __VLS_getVForSourceType`); if (source.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { - codes.push( + yield _ts('('); + yield* generateInterpolation( + source.content, + source.loc, + source.loc.start.offset, + presetInfos.all, '(', - ...createInterpolationCode( - source.content, - source.loc, - source.loc.start.offset, - presetInfos.all, - '(', - ')', - ), - '!)', // #3102 - ') {\n', + ')', ); + yield _ts('!)'); // #3102 + yield _ts(') {\n'); let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - visitNode(childNode, parentEl, prev, componentCtxVar); + yield* generateAstNode(childNode, parentEl, prev, componentCtxVar); prev = childNode; } - resolveComment(); + yield* generateExpectErrorComment(); - generateAutoImportCompletionCode(); - codes.push('}\n'); + yield* generateExtraAutoImport(); + yield _ts('}\n'); - formatCodes.push( - ...createFormatCode( - source.content, - source.loc.start.offset, - formatBrackets.normal, - ), + yield* generateTsFormat( + source.content, + source.loc.start.offset, + formatBrackets.normal, ); } @@ -660,9 +618,9 @@ export function generate( localVars.set(varName, localVars.get(varName)! - 1); } - function visitElementNode(node: CompilerDOM.ElementNode, parentEl: CompilerDOM.ElementNode | undefined, componentCtxVar: string | undefined) { + function* generateElement(node: CompilerDOM.ElementNode, parentEl: CompilerDOM.ElementNode | undefined, componentCtxVar: string | undefined): Generator<_CodeAndStack> { - codes.push(`{\n`); + yield _ts(`{\n`); const startTagOffset = node.loc.start.offset + template.content.substring(node.loc.start.offset).indexOf(node.tag); let endTagOffset = !node.isSelfClosing && template.lang === 'html' ? node.loc.start.offset + node.loc.source.lastIndexOf(node.tag) : undefined; @@ -706,140 +664,128 @@ export function generate( const isIntrinsicElement = nativeTags.has(tag) && tagOffsets.length; if (isIntrinsicElement) { - codes.push( - 'const ', - var_originalComponent, - ` = __VLS_intrinsicElements[`, - ...createStringLiteralKeyCode( - tag, - tagOffsets[0], - presetInfos.diagnosticOnly, - ), - '];\n', + yield _ts('const '); + yield _ts(var_originalComponent); + yield _ts(` = __VLS_intrinsicElements[`); + yield* generateStringLiteralKey( + tag, + tagOffsets[0], + presetInfos.diagnosticOnly, ); + yield _ts('];\n'); } else if (isNamespacedTag) { - codes.push( - `const ${var_originalComponent} = `, - ...createInterpolationCode(tag, node.loc, startTagOffset, presetInfos.all, '', ''), - ';\n', - ); + yield _ts(`const ${var_originalComponent} = `); + yield* generateInterpolation(tag, node.loc, startTagOffset, presetInfos.all, '', ''); + yield _ts(';\n'); } else if (dynamicTagExp) { - codes.push( - `const ${var_originalComponent} = `, - ...createInterpolationCode(dynamicTagExp.loc.source, dynamicTagExp.loc, dynamicTagExp.loc.start.offset, presetInfos.all, '(', ')'), - ';\n', - ); + yield _ts(`const ${var_originalComponent} = `); + yield* generateInterpolation(dynamicTagExp.loc.source, dynamicTagExp.loc, dynamicTagExp.loc.start.offset, presetInfos.all, '(', ')'); + yield _ts(';\n'); } else { - codes.push( - `const ${var_originalComponent} = ({} as `, - ); + yield _ts(`const ${var_originalComponent} = ({} as `); for (const componentName of getPossibleOriginalComponentNames(tag)) { - codes.push( - `'${componentName}' extends keyof typeof __VLS_ctx ? `, - `{ '${toCanonicalComponentName(tag)}': typeof __VLS_ctx`, - ...createPropertyAccessCode(componentName), - ` }: `, + yield _ts(`'${componentName}' extends keyof typeof __VLS_ctx ? `); + yield _ts(`{ '${getCanonicalComponentName(tag)}': typeof __VLS_ctx`); + yield* generatePropertyAccess(componentName); + yield _ts(` }: `); + } + yield _ts(`typeof __VLS_resolvedLocalAndGlobalComponents)`); + if (tagOffsets.length) { + yield* generatePropertyAccess( + getCanonicalComponentName(tag), + tagOffsets[0], + presetInfos.diagnosticOnly, ); } - codes.push( - `typeof __VLS_resolvedLocalAndGlobalComponents)`, - ...(tagOffsets.length - ? createPropertyAccessCode( - toCanonicalComponentName(tag), - tagOffsets[0], - presetInfos.diagnosticOnly, - ) - : createPropertyAccessCode(toCanonicalComponentName(tag)) - ), - ';\n', - ); + else { + yield* generatePropertyAccess(getCanonicalComponentName(tag)); + } + yield _ts(';\n'); } if (isIntrinsicElement) { - codes.push(`const ${var_functionalComponent} = __VLS_elementAsFunctionalComponent(${var_originalComponent});\n`,); + yield _ts(`const ${var_functionalComponent} = __VLS_elementAsFunctionalComponent(${var_originalComponent});\n`); } else { - codes.push( - `const ${var_functionalComponent} = __VLS_asFunctionalComponent(`, - `${var_originalComponent}, `, - `new ${var_originalComponent}({`, - ...createPropsCode(node, props, 'extraReferences'), - '})', - ');\n', - ); + yield _ts(`const ${var_functionalComponent} = __VLS_asFunctionalComponent(`); + yield _ts(`${var_originalComponent}, `); + yield _ts(`new ${var_originalComponent}({`); + yield* generateProps(node, props, 'extraReferences'); + yield _ts('})'); + yield _ts(');\n'); } for (const offset of tagOffsets) { if (isNamespacedTag || dynamicTagExp || isIntrinsicElement) { continue; } - codes.push( - `({} as { ${toCanonicalComponentName(tag)}: typeof ${var_originalComponent} }).`, - ...createCanonicalComponentNameCode( - tag, - offset, - mergeFeatureSettings( - presetInfos.tagHover, - presetInfos.diagnosticOnly, - ), + yield _ts(`({} as { ${getCanonicalComponentName(tag)}: typeof ${var_originalComponent} }).`); + yield* generateCanonicalComponentName( + tag, + offset, + mergeFeatureSettings( + presetInfos.tagHover, + presetInfos.diagnosticOnly, ), - ';\n', ); + yield _ts(';\n'); } if (vueCompilerOptions.strictTemplates) { // with strictTemplates, generate once for props type-checking + instance type - codes.push( - `const ${var_componentInstance} = ${var_functionalComponent}(`, - // diagnostic start + yield _ts(`const ${var_componentInstance} = ${var_functionalComponent}(`); + // diagnostic start + yield _ts( tagOffsets.length ? ['', 'template', tagOffsets[0], presetInfos.diagnosticOnly] : dynamicTagExp ? ['', 'template', startTagOffset, presetInfos.diagnosticOnly] - : '', - '{ ', - ...createPropsCode(node, props, 'normal', propsFailedExps), - '}', - // diagnostic end + : '' + ); + yield _ts('{ '); + yield* generateProps(node, props, 'normal', propsFailedExps); + yield _ts('}'); + // diagnostic end + yield _ts( tagOffsets.length ? ['', 'template', tagOffsets[0] + tag.length, presetInfos.diagnosticOnly] : dynamicTagExp ? ['', 'template', startTagOffset + tag.length, presetInfos.diagnosticOnly] - : '', - `, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}));\n`, + : '' ); + yield _ts(`, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}));\n`); } else { // without strictTemplates, this only for instacne type - codes.push( - `const ${var_componentInstance} = ${var_functionalComponent}(`, - '{ ', - ...createPropsCode(node, props, 'extraReferences'), - '}', - `, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}));\n`, - ); + yield _ts(`const ${var_componentInstance} = ${var_functionalComponent}(`); + yield _ts('{ '); + yield* generateProps(node, props, 'extraReferences'); + yield _ts('}'); + yield _ts(`, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}));\n`); // and this for props type-checking - codes.push( - `({} as (props: __VLS_FunctionalComponentProps & Record) => void)(`, - // diagnostic start + yield _ts(`({} as (props: __VLS_FunctionalComponentProps & Record) => void)(`); + // diagnostic start + yield _ts( tagOffsets.length ? ['', 'template', tagOffsets[0], presetInfos.diagnosticOnly] : dynamicTagExp ? ['', 'template', startTagOffset, presetInfos.diagnosticOnly] - : '', - '{ ', - ...createPropsCode(node, props, 'normal', propsFailedExps), - '}', - // diagnostic end + : '' + ); + yield _ts('{ '); + yield* generateProps(node, props, 'normal', propsFailedExps); + yield _ts('}'); + // diagnostic end + yield _ts( tagOffsets.length ? ['', 'template', tagOffsets[0] + tag.length, presetInfos.diagnosticOnly] : dynamicTagExp ? ['', 'template', startTagOffset + tag.length, presetInfos.diagnosticOnly] - : '', - `);\n`, + : '' ); + yield _ts(`);\n`); } if (tag !== 'template' && tag !== 'slot') { componentCtxVar = `__VLS_${elementIndex++}`; const componentEventsVar = `__VLS_${elementIndex++}`; - codes.push(`const ${componentCtxVar} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})!;\n`); - codes.push(`let ${componentEventsVar}!: __VLS_NormalizeEmits;\n`); + yield _ts(`const ${componentCtxVar} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})!;\n`); + yield _ts(`let ${componentEventsVar}!: __VLS_NormalizeEmits;\n`); componentCtxVar2EmitEventsVar.set(componentCtxVar, componentEventsVar); parentEl = node; } @@ -847,30 +793,26 @@ export function generate( //#region // fix https://github.com/vuejs/language-tools/issues/1775 for (const failedExp of propsFailedExps) { - codes.push( - ...createInterpolationCode( - failedExp.loc.source, - failedExp.loc, - failedExp.loc.start.offset, - presetInfos.all, - '(', - ')', - ), - ';\n', + yield* generateInterpolation( + failedExp.loc.source, + failedExp.loc, + failedExp.loc.start.offset, + presetInfos.all, + '(', + ')', ); + yield _ts(';\n'); const fb = formatBrackets.normal; if (fb) { - formatCodes.push( - ...createFormatCode( - failedExp.loc.source, - failedExp.loc.start.offset, - fb, - ), + yield* generateTsFormat( + failedExp.loc.source, + failedExp.loc.start.offset, + fb, ); } } - generateInlineCss(props); + yield* generateInlineCss(props); const vScope = props.find(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && (prop.name === 'scope' || prop.name === 'data')); let inScope = false; @@ -881,33 +823,33 @@ export function generate( const scopeVar = `__VLS_${elementIndex++}`; const condition = `__VLS_withScope(__VLS_ctx, ${scopeVar})`; - codes.push(`const ${scopeVar} = `); - codes.push([ + yield _ts(`const ${scopeVar} = `); + yield _ts([ vScope.exp.loc.source, 'template', vScope.exp.loc.start.offset, presetInfos.all, ]); - codes.push(';\n'); - codes.push(`if (${condition}) {\n`); + yield _ts(';\n'); + yield _ts(`if (${condition}) {\n`); blockConditions.push(condition); inScope = true; } - generateDirectives(node); - generateElReferences(node); // + yield* generateDirectives(node); + yield* generateReferencesForElements(node); // if (shouldGenerateScopedClasses) { - generateClassScoped(node); + yield* generateReferencesForScopedCssClasses(node); } if (componentCtxVar) { - generateEvents(node, var_functionalComponent, var_componentInstance, componentCtxVar); + yield* generateEvents(node, var_functionalComponent, var_componentInstance, componentCtxVar); } if (node.tag === 'slot') { - generateSlot(node, startTagOffset); + yield* generateSlot(node, startTagOffset); } if (inScope) { - codes.push('}\n'); + yield _ts('}\n'); blockConditions.length = originalConditionsNum; } //#endregion @@ -918,70 +860,61 @@ export function generate( hasSlotElements.add(parentEl); } const slotBlockVars: string[] = []; - codes.push(`{\n`); + yield _ts(`{\n`); let hasProps = false; if (slotDir?.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { - formatCodes.push( - ...createFormatCode( - slotDir.exp.content, - slotDir.exp.loc.start.offset, - formatBrackets.params, - ), + yield* generateTsFormat( + slotDir.exp.content, + slotDir.exp.loc.start.offset, + formatBrackets.params, ); const slotAst = createTsAst(slotDir, `(${slotDir.exp.content}) => {}`); collectVars(ts, slotAst, slotBlockVars); hasProps = true; if (slotDir.exp.content.indexOf(':') === -1) { - codes.push( - 'const [', - [ - slotDir.exp.content, - 'template', - slotDir.exp.loc.start.offset, - presetInfos.all, - ], - `] = __VLS_getSlotParams(`, - ); + yield _ts('const ['); + yield _ts([ + slotDir.exp.content, + 'template', + slotDir.exp.loc.start.offset, + presetInfos.all, + ]); + yield _ts(`] = __VLS_getSlotParams(`); } else { - codes.push( - 'const ', - [ - slotDir.exp.content, - 'template', - slotDir.exp.loc.start.offset, - presetInfos.all, - ], - ` = __VLS_getSlotParam(`, - ); + yield _ts('const '); + yield _ts([ + slotDir.exp.content, + 'template', + slotDir.exp.loc.start.offset, + presetInfos.all, + ]); + yield _ts(` = __VLS_getSlotParam(`); } } - codes.push( - ['', 'template', (slotDir.arg ?? slotDir).loc.start.offset, presetInfos.diagnosticOnly], - `(${componentCtxVar}.slots!)`, - ...( - (slotDir?.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && slotDir.arg.content) - ? createPropertyAccessCode( - slotDir.arg.loc.source, - slotDir.arg.loc.start.offset, - slotDir.arg.isStatic ? presetInfos.slotName : presetInfos.all, - slotDir.arg.loc - ) - : [ - '.', - ['', 'template', slotDir.loc.start.offset, { ...presetInfos.slotName, completion: false }] satisfies Code, - 'default', - ['', 'template', slotDir.loc.start.offset + (slotDir.loc.source.startsWith('#') ? '#'.length : slotDir.loc.source.startsWith('v-slot:') ? 'v-slot:'.length : 0), disableAllFeatures({ __combineLastMappping: true })] satisfies Code, - ] - ), - ['', 'template', (slotDir.arg ?? slotDir).loc.end.offset, presetInfos.diagnosticOnly], - ); + yield _ts(['', 'template', (slotDir.arg ?? slotDir).loc.start.offset, presetInfos.diagnosticOnly]); + yield _ts(`(${componentCtxVar}.slots!)`); + if (slotDir?.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && slotDir.arg.content) { + yield* generatePropertyAccess( + slotDir.arg.loc.source, + slotDir.arg.loc.start.offset, + slotDir.arg.isStatic ? presetInfos.slotName : presetInfos.all, + slotDir.arg.loc + ); + } + else { + yield _ts('.'); + yield _ts(['', 'template', slotDir.loc.start.offset, { ...presetInfos.slotName, completion: false }] satisfies Code); + yield _ts('default'); + yield _ts(['', 'template', slotDir.loc.start.offset + (slotDir.loc.source.startsWith('#') ? '#'.length : slotDir.loc.source.startsWith('v-slot:') ? 'v-slot:'.length : 0), disableAllFeatures({ __combineLastMappping: true })] satisfies Code); + } + yield _ts(['', 'template', (slotDir.arg ?? slotDir).loc.end.offset, presetInfos.diagnosticOnly]); if (hasProps) { - codes.push(')'); + yield _ts(')'); } - codes.push(';\n'); + yield _ts(';\n'); slotBlockVars.forEach(varName => { localVars.set(varName, (localVars.get(varName) ?? 0) + 1); @@ -989,11 +922,11 @@ export function generate( let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - visitNode(childNode, parentEl, prev, componentCtxVar); + yield* generateAstNode(childNode, parentEl, prev, componentCtxVar); prev = childNode; } - resolveComment(); - generateAutoImportCompletionCode(); + yield* generateExpectErrorComment(); + yield* generateExtraAutoImport(); slotBlockVars.forEach(varName => { localVars.set(varName, localVars.get(varName)! - 1); @@ -1003,48 +936,44 @@ export function generate( isStatic = slotDir.arg.isStatic; } if (isStatic && slotDir && !slotDir.arg) { - codes.push( - `${componentCtxVar}.slots!['`, - [ - '', - 'template', - slotDir.loc.start.offset + ( - slotDir.loc.source.startsWith('#') - ? '#'.length : slotDir.loc.source.startsWith('v-slot:') - ? 'v-slot:'.length - : 0 - ), - disableAllFeatures({ completion: true }), - ], - `'/* empty slot name completion */]\n`, - ); + yield _ts(`${componentCtxVar}.slots!['`); + yield _ts([ + '', + 'template', + slotDir.loc.start.offset + ( + slotDir.loc.source.startsWith('#') + ? '#'.length : slotDir.loc.source.startsWith('v-slot:') + ? 'v-slot:'.length + : 0 + ), + disableAllFeatures({ completion: true }), + ]); + yield _ts(`'/* empty slot name completion */]\n`); } - codes.push(`}\n`); + yield _ts(`}\n`); } else { let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - visitNode(childNode, parentEl, prev, componentCtxVar); + yield* generateAstNode(childNode, parentEl, prev, componentCtxVar); prev = childNode; } - resolveComment(); + yield* generateExpectErrorComment(); // fix https://github.com/vuejs/language-tools/issues/932 if (!hasSlotElements.has(node) && node.children.length) { - codes.push( - `(${componentCtxVar}.slots!).`, - ['', 'template', node.children[0].loc.start.offset, disableAllFeatures({ navigation: true })], - 'default', - ['', 'template', node.children[node.children.length - 1].loc.end.offset, disableAllFeatures({ __combineLastMappping: true })], - ';\n', - ); + yield _ts(`(${componentCtxVar}.slots!).`); + yield _ts(['', 'template', node.children[0].loc.start.offset, disableAllFeatures({ navigation: true })]); + yield _ts('default'); + yield _ts(['', 'template', node.children[node.children.length - 1].loc.end.offset, disableAllFeatures({ __combineLastMappping: true })]); + yield _ts(';\n'); } } - codes.push(`}\n`); + yield _ts(`}\n`); } - function generateEvents(node: CompilerDOM.ElementNode, componentVar: string, componentInstanceVar: string, componentCtxVar: string) { + function* generateEvents(node: CompilerDOM.ElementNode, componentVar: string, componentInstanceVar: string, componentCtxVar: string): Generator<_CodeAndStack> { for (const prop of node.props) { if ( @@ -1054,12 +983,10 @@ export function generate( ) { const eventsVar = componentCtxVar2EmitEventsVar.get(componentCtxVar); const eventVar = `__VLS_${elementIndex++}`; - codes.push( - `let ${eventVar} = { '${prop.arg.loc.source}': `, - `__VLS_pickEvent(`, - `${eventsVar}['${prop.arg.loc.source}'], `, - `({} as __VLS_FunctionalComponentProps)`, - ); + yield _ts(`let ${eventVar} = { '${prop.arg.loc.source}': `); + yield _ts(`__VLS_pickEvent(`); + yield _ts(`${eventsVar}['${prop.arg.loc.source}'], `); + yield _ts(`({} as __VLS_FunctionalComponentProps)`); const startCode: Code = [ '', 'template', @@ -1085,65 +1012,55 @@ export function generate( ), ]; if (validTsVarReg.test(camelize(prop.arg.loc.source))) { - codes.push( - `.`, - startCode, - `on`, - ...createCamelizeCode( - capitalize(prop.arg.loc.source), - prop.arg.loc.start.offset, - disableAllFeatures({ __combineLastMappping: true }), - ), + yield _ts(`.`); + yield _ts(startCode); + yield _ts(`on`); + yield* generateCamelized( + capitalize(prop.arg.loc.source), + prop.arg.loc.start.offset, + disableAllFeatures({ __combineLastMappping: true }), ); } else { - codes.push( - `[`, - startCode, - `'`, - ['', 'template', prop.arg.loc.start.offset, disableAllFeatures({ __combineLastMappping: true })], - 'on', - ...createCamelizeCode( - capitalize(prop.arg.loc.source), - prop.arg.loc.start.offset, - disableAllFeatures({ __combineLastMappping: true }), - ), - `'`, - ['', 'template', prop.arg.loc.end.offset, disableAllFeatures({ __combineLastMappping: true })], - `]`, + yield _ts(`[`); + yield _ts(startCode); + yield _ts(`'`); + yield _ts(['', 'template', prop.arg.loc.start.offset, disableAllFeatures({ __combineLastMappping: true })]); + yield _ts('on'); + yield* generateCamelized( + capitalize(prop.arg.loc.source), + prop.arg.loc.start.offset, + disableAllFeatures({ __combineLastMappping: true }), ); + yield _ts(`'`); + yield _ts(['', 'template', prop.arg.loc.end.offset, disableAllFeatures({ __combineLastMappping: true })]); + yield _ts(`]`); } - codes.push( - `) };\n`, - `${eventVar} = { `, - ); + yield _ts(`) };\n`); + yield _ts(`${eventVar} = { `); if (prop.arg.loc.source.startsWith('[') && prop.arg.loc.source.endsWith(']')) { - codes.push( - '[(', - ...createInterpolationCode( - prop.arg.loc.source.slice(1, -1), - prop.arg.loc, - prop.arg.loc.start.offset + 1, - presetInfos.all, - '', - '', - ), - ')!]', + yield _ts('[('); + yield* generateInterpolation( + prop.arg.loc.source.slice(1, -1), + prop.arg.loc, + prop.arg.loc.start.offset + 1, + presetInfos.all, + '', + '', ); + yield _ts(')!]'); } else { - codes.push( - ...createObjectPropertyCode( - prop.arg.loc.source, - prop.arg.loc.start.offset, - presetInfos.event, - prop.arg.loc - ) + yield* generateObjectProperty( + prop.arg.loc.source, + prop.arg.loc.start.offset, + presetInfos.event, + prop.arg.loc ); } - codes.push(`: `); - appendExpressionNode(prop); - codes.push(` };\n`); + yield _ts(`: `); + yield* appendExpressionNode(prop); + yield _ts(` };\n`); } else if ( prop.type === CompilerDOM.NodeTypes.DIRECTIVE @@ -1152,107 +1069,99 @@ export function generate( ) { // for vue 2 nameless event // https://github.com/johnsoncodehk/vue-tsc/issues/67 - codes.push( - ...createInterpolationCode( - prop.exp.content, - prop.exp.loc, - prop.exp.loc.start.offset, - presetInfos.all, - '$event => {(', - ')}', - ), - ';\n', + yield* generateInterpolation( + prop.exp.content, + prop.exp.loc, + prop.exp.loc.start.offset, + presetInfos.all, + '$event => {(', + ')}', ); - formatCodes.push( - ...createFormatCode( - prop.exp.content, - prop.exp.loc.start.offset, - formatBrackets.normal, - ), + yield _ts(';\n'); + yield* generateTsFormat( + prop.exp.content, + prop.exp.loc.start.offset, + formatBrackets.normal, ); } + } + } - function appendExpressionNode(prop: CompilerDOM.DirectiveNode) { - if (prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { + function* appendExpressionNode(prop: CompilerDOM.DirectiveNode): Generator<_CodeAndStack> { + if (prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { - const ast = createTsAst(prop.exp, prop.exp.content); - let isCompoundExpression = true; + const ast = createTsAst(prop.exp, prop.exp.content); + let isCompoundExpression = true; - if (ast.getChildCount() === 2) { // with EOF - ast.forEachChild(child_1 => { - if (ts.isExpressionStatement(child_1)) { - child_1.forEachChild(child_2 => { - if (ts.isArrowFunction(child_2)) { - isCompoundExpression = false; - } - else if (ts.isIdentifier(child_2)) { - isCompoundExpression = false; - } - }); + if (ast.getChildCount() === 2) { // with EOF + ast.forEachChild(child_1 => { + if (ts.isExpressionStatement(child_1)) { + child_1.forEachChild(child_2 => { + if (ts.isArrowFunction(child_2)) { + isCompoundExpression = false; } - else if (ts.isFunctionDeclaration(child_1)) { + else if (ts.isIdentifier(child_2)) { isCompoundExpression = false; } }); } + else if (ts.isFunctionDeclaration(child_1)) { + isCompoundExpression = false; + } + }); + } - let prefix = '('; - let suffix = ')'; - let isFirstMapping = true; - - if (isCompoundExpression) { - - codes.push('$event => {\n'); - localVars.set('$event', (localVars.get('$event') ?? 0) + 1); + let prefix = '('; + let suffix = ')'; + let isFirstMapping = true; - prefix = ''; - suffix = ''; - for (const blockCondition of blockConditions) { - prefix += `if (!(${blockCondition})) return;\n`; - } - } + if (isCompoundExpression) { - codes.push( - ...createInterpolationCode( - prop.exp.content, - prop.exp.loc, - prop.exp.loc.start.offset, - () => { - if (isCompoundExpression && isFirstMapping) { - isFirstMapping = false; - return presetInfos.allWithHiddenParam; - } - return presetInfos.all; - }, - prefix, - suffix, - ) - ); + yield _ts('$event => {\n'); + localVars.set('$event', (localVars.get('$event') ?? 0) + 1); - if (isCompoundExpression) { - localVars.set('$event', localVars.get('$event')! - 1); + prefix = ''; + suffix = ''; + for (const blockCondition of blockConditions) { + prefix += `if (!(${blockCondition})) return;\n`; + } + } - codes.push(';\n'); - generateAutoImportCompletionCode(); - codes.push('}\n'); + yield* generateInterpolation( + prop.exp.content, + prop.exp.loc, + prop.exp.loc.start.offset, + () => { + if (isCompoundExpression && isFirstMapping) { + isFirstMapping = false; + return presetInfos.allWithHiddenParam; } + return presetInfos.all; + }, + prefix, + suffix, + ); - formatCodes.push( - ...createFormatCode( - prop.exp.content, - prop.exp.loc.start.offset, - isCompoundExpression ? formatBrackets.event : formatBrackets.normal, - ), - ); - } - else { - codes.push(`() => {}`); - } + if (isCompoundExpression) { + localVars.set('$event', localVars.get('$event')! - 1); + + yield _ts(';\n'); + yield* generateExtraAutoImport(); + yield _ts('}\n'); } + + yield* generateTsFormat( + prop.exp.content, + prop.exp.loc.start.offset, + isCompoundExpression ? formatBrackets.event : formatBrackets.normal, + ); + } + else { + yield _ts(`() => {}`); } } - function createPropsCode(node: CompilerDOM.ElementNode, props: CompilerDOM.ElementNode['props'], mode: 'normal' | 'extraReferences', propsFailedExps?: CompilerDOM.SimpleExpressionNode[]): Code[] { + function* generateProps(node: CompilerDOM.ElementNode, props: CompilerDOM.ElementNode['props'], mode: 'normal' | 'extraReferences', propsFailedExps?: CompilerDOM.SimpleExpressionNode[]): Generator<_CodeAndStack> { let styleAttrNum = 0; let classAttrNum = 0; @@ -1268,8 +1177,6 @@ export function generate( classAttrNum++; } - const codes: Code[] = []; - let caps_all: VueCodeInformation = presetInfos.all; let caps_diagnosticOnly: VueCodeInformation = presetInfos.diagnosticOnly; let caps_attr: VueCodeInformation = presetInfos.attr; @@ -1280,17 +1187,17 @@ export function generate( caps_attr = disableAllFeatures({ navigation: caps_attr.navigation }); } - codes.push(`...{ `); + yield _ts(`...{ `); for (const prop of props) { if ( prop.type === CompilerDOM.NodeTypes.DIRECTIVE && prop.name === 'on' && prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION ) { - codes.push(`'${camelize('on-' + prop.arg.loc.source)}': {} as any, `); + yield _ts(`'${camelize('on-' + prop.arg.loc.source)}': {} as any, `); } } - codes.push(`}, `); + yield _ts(`}, `); const canCamelize = !nativeTags.has(node.tag) || node.tagType === CompilerDOM.ElementTypes.COMPONENT; @@ -1331,61 +1238,55 @@ export function generate( && hyphenateAttr(propName) === propName && !vueCompilerOptions.htmlAttributes.some(pattern => minimatch(propName!, pattern)); - codes.push( - ['', 'template', prop.loc.start.offset, caps_diagnosticOnly], - ...createObjectPropertyCode( - propName, - prop.arg - ? prop.arg.loc.start.offset - : prop.loc.start.offset, - prop.arg - ? mergeFeatureSettings( - caps_attr, - { - navigation: caps_attr.navigation ? { - resolveRenameNewName: camelize, - resolveRenameEditText: shouldCamelize ? hyphenateAttr : undefined, - } : undefined, - }, - ) - : caps_attr, - (prop.loc as any).name_2 ?? ((prop.loc as any).name_2 = {}), - shouldCamelize, - ), - ': (', + yield _ts(['', 'template', prop.loc.start.offset, caps_diagnosticOnly]); + yield* generateObjectProperty( + propName, + prop.arg + ? prop.arg.loc.start.offset + : prop.loc.start.offset, + prop.arg + ? mergeFeatureSettings( + caps_attr, + { + navigation: caps_attr.navigation ? { + resolveRenameNewName: camelize, + resolveRenameEditText: shouldCamelize ? hyphenateAttr : undefined, + } : undefined, + }, + ) + : caps_attr, + (prop.loc as any).name_2 ?? ((prop.loc as any).name_2 = {}), + shouldCamelize, ); + yield _ts(': ('); if (prop.exp && !(prop.exp.constType === CompilerDOM.ConstantTypes.CAN_STRINGIFY)) { // style='z-index: 2' will compile to {'z-index':'2'} - codes.push( - ...createInterpolationCode( - prop.exp.loc.source, - prop.exp.loc, - prop.exp.loc.start.offset, - caps_all, - '(', - ')', - ), + yield* generateInterpolation( + prop.exp.loc.source, + prop.exp.loc, + prop.exp.loc.start.offset, + caps_all, + '(', + ')', ); if (mode === 'normal') { - formatCodes.push( - ...createFormatCode( - prop.exp.loc.source, - prop.exp.loc.start.offset, - formatBrackets.normal, - ), + yield* generateTsFormat( + prop.exp.loc.source, + prop.exp.loc.start.offset, + formatBrackets.normal, ); } } else { - codes.push('{}'); + yield _ts('{}'); } - codes.push(')'); - codes.push([ + yield _ts(')'); + yield _ts([ '', 'template', prop.loc.end.offset, caps_diagnosticOnly, ]); - codes.push(', '); + yield _ts(', '); } else if (prop.type === CompilerDOM.NodeTypes.ATTRIBUTE) { @@ -1400,35 +1301,31 @@ export function generate( && hyphenateAttr(prop.name) === prop.name && !vueCompilerOptions.htmlAttributes.some(pattern => minimatch(prop.name, pattern)); - codes.push( - ['', 'template', prop.loc.start.offset, caps_diagnosticOnly], - ...createObjectPropertyCode( - prop.name, - prop.loc.start.offset, - shouldCamelize - ? mergeFeatureSettings(caps_attr, { - navigation: caps_attr.navigation ? { - resolveRenameNewName: camelize, - resolveRenameEditText: hyphenateAttr, - } : undefined, - }) - : caps_attr, - (prop.loc as any).name_1 ?? ((prop.loc as any).name_1 = {}), - shouldCamelize, - ), - ': (', + yield _ts(['', 'template', prop.loc.start.offset, caps_diagnosticOnly]); + yield* generateObjectProperty( + prop.name, + prop.loc.start.offset, + shouldCamelize + ? mergeFeatureSettings(caps_attr, { + navigation: caps_attr.navigation ? { + resolveRenameNewName: camelize, + resolveRenameEditText: hyphenateAttr, + } : undefined, + }) + : caps_attr, + (prop.loc as any).name_1 ?? ((prop.loc as any).name_1 = {}), + shouldCamelize, ); + yield _ts(': ('); if (prop.value) { - codes.push( - ...createAttrValueCode(prop.value, caps_all), - ); + yield* generateAttrValue(prop.value, caps_all); } else { - codes.push('true'); + yield _ts('true'); } - codes.push(')'); - codes.push(['', 'template', prop.loc.end.offset, caps_diagnosticOnly]); - codes.push(', '); + yield _ts(')'); + yield _ts(['', 'template', prop.loc.end.offset, caps_diagnosticOnly]); + yield _ts(', '); } else if ( prop.type === CompilerDOM.NodeTypes.DIRECTIVE @@ -1436,27 +1333,23 @@ export function generate( && !prop.arg && prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION ) { - codes.push( - ['', 'template', prop.exp.loc.start.offset, presetInfos.diagnosticOnly], - '...', - ...createInterpolationCode( - prop.exp.content, - prop.exp.loc, - prop.exp.loc.start.offset, - caps_all, - '(', - ')', - ), - ['', 'template', prop.exp.loc.end.offset, presetInfos.diagnosticOnly], - ', ', + yield _ts(['', 'template', prop.exp.loc.start.offset, presetInfos.diagnosticOnly]); + yield _ts('...'); + yield* generateInterpolation( + prop.exp.content, + prop.exp.loc, + prop.exp.loc.start.offset, + caps_all, + '(', + ')', ); + yield _ts(['', 'template', prop.exp.loc.end.offset, presetInfos.diagnosticOnly]); + yield _ts(', '); if (mode === 'normal') { - formatCodes.push( - ...createFormatCode( - prop.exp.content, - prop.exp.loc.start.offset, - formatBrackets.normal, - ), + yield* generateTsFormat( + prop.exp.content, + prop.exp.loc.start.offset, + formatBrackets.normal, ); } } @@ -1465,11 +1358,9 @@ export function generate( // tsCodeGen.addText("/* " + [prop.type, prop.name, prop.arg?.loc.source, prop.exp?.loc.source, prop.loc.source].join(", ") + " */ "); } } - - return codes; } - function generateInlineCss(props: CompilerDOM.ElementNode['props']) { + function* generateInlineCss(props: CompilerDOM.ElementNode['props']): Generator<_CodeAndStack> { for (const prop of props) { if ( prop.type === CompilerDOM.NodeTypes.DIRECTIVE @@ -1484,8 +1375,8 @@ export function generate( const end = prop.arg.loc.source.lastIndexOf(endCrt); const content = prop.arg.loc.source.substring(start, end); - cssCodes.push(`x { `); - cssCodes.push([ + yield _inlineCss(`x { `); + yield _inlineCss([ content, 'template', prop.arg.loc.start.offset + start, @@ -1494,12 +1385,12 @@ export function generate( structure: false, }), ]); - cssCodes.push(` }\n`); + yield _inlineCss(` }\n`); } } } - function generateDirectives(node: CompilerDOM.ElementNode) { + function* generateDirectives(node: CompilerDOM.ElementNode): Generator<_CodeAndStack> { for (const prop of node.props) { if ( prop.type === CompilerDOM.NodeTypes.DIRECTIVE @@ -1513,116 +1404,101 @@ export function generate( accessedGlobalVariables.add(camelize('v-' + prop.name)); if (prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && !prop.arg.isStatic) { - codes.push( - ...createInterpolationCode( - prop.arg.content, - prop.arg.loc, - prop.arg.loc.start.offset + prop.arg.loc.source.indexOf(prop.arg.content), - presetInfos.all, - '(', - ')', - ), - ';\n', + yield* generateInterpolation( + prop.arg.content, + prop.arg.loc, + prop.arg.loc.start.offset + prop.arg.loc.source.indexOf(prop.arg.content), + presetInfos.all, + '(', + ')', ); - formatCodes.push( - ...createFormatCode( - prop.arg.content, - prop.arg.loc.start.offset, - formatBrackets.normal, - ), + yield _ts(';\n'); + yield* generateTsFormat( + prop.arg.content, + prop.arg.loc.start.offset, + formatBrackets.normal, ); } - codes.push( - ['', 'template', prop.loc.start.offset, presetInfos.diagnosticOnly], - `__VLS_directiveFunction(__VLS_ctx.`, - ...createCamelizeCode( - 'v-' + prop.name, - prop.loc.start.offset, - mergeFeatureSettings( - presetInfos.noDiagnostics, - { - completion: { - // fix https://github.com/vuejs/language-tools/issues/1905 - isAdditional: true, - }, - navigation: { - resolveRenameNewName: camelize, - resolveRenameEditText: getPropRenameApply(prop.name), - }, + yield _ts(['', 'template', prop.loc.start.offset, presetInfos.diagnosticOnly]); + yield _ts(`__VLS_directiveFunction(__VLS_ctx.`); + yield* generateCamelized( + 'v-' + prop.name, + prop.loc.start.offset, + mergeFeatureSettings( + presetInfos.noDiagnostics, + { + completion: { + // fix https://github.com/vuejs/language-tools/issues/1905 + isAdditional: true, }, - ), + navigation: { + resolveRenameNewName: camelize, + resolveRenameEditText: getPropRenameApply(prop.name), + }, + }, ), - ')', - '(', ); + yield _ts(')'); + yield _ts('('); + if (prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { - codes.push( - ['', 'template', prop.exp.loc.start.offset, presetInfos.diagnosticOnly], - ...createInterpolationCode( - prop.exp.content, - prop.exp.loc, - prop.exp.loc.start.offset, - presetInfos.all, - '(', - ')', - ), - ['', 'template', prop.exp.loc.end.offset, presetInfos.diagnosticOnly], + yield _ts(['', 'template', prop.exp.loc.start.offset, presetInfos.diagnosticOnly]); + yield* generateInterpolation( + prop.exp.content, + prop.exp.loc, + prop.exp.loc.start.offset, + presetInfos.all, + '(', + ')', ); - formatCodes.push( - ...createFormatCode( - prop.exp.content, - prop.exp.loc.start.offset, - formatBrackets.normal, - ), + yield _ts(['', 'template', prop.exp.loc.end.offset, presetInfos.diagnosticOnly]); + yield* generateTsFormat( + prop.exp.content, + prop.exp.loc.start.offset, + formatBrackets.normal, ); } else { - codes.push('undefined'); + yield _ts('undefined'); } - codes.push( - ')', - ['', 'template', prop.loc.end.offset, presetInfos.diagnosticOnly], - ';\n', - ); + yield _ts(')'); + yield _ts(['', 'template', prop.loc.end.offset, presetInfos.diagnosticOnly]); + yield _ts(';\n'); } } } - function generateElReferences(node: CompilerDOM.ElementNode) { + function* generateReferencesForElements(node: CompilerDOM.ElementNode): Generator<_CodeAndStack> { for (const prop of node.props) { if ( prop.type === CompilerDOM.NodeTypes.ATTRIBUTE && prop.name === 'ref' && prop.value ) { - codes.push( - '// @ts-ignore\n', - ...createInterpolationCode( - prop.value.content, - prop.value.loc, - prop.value.loc.start.offset + 1, - presetInfos.refAttr, - '(', - ')', - ), - ';\n', + yield _ts('// @ts-ignore\n'); + yield* generateInterpolation( + prop.value.content, + prop.value.loc, + prop.value.loc.start.offset + 1, + presetInfos.refAttr, + '(', + ')', ); + yield _ts(';\n'); } } } - function generateClassScoped(node: CompilerDOM.ElementNode) { + function* generateReferencesForScopedCssClasses(node: CompilerDOM.ElementNode): Generator<_CodeAndStack> { for (const prop of node.props) { if ( prop.type === CompilerDOM.NodeTypes.ATTRIBUTE && prop.name === 'class' && prop.value ) { - let startOffset = prop.value.loc.start.offset; let tempClassName = ''; - for (const char of (prop.value.loc.source + ' ')) { if (char.trim() === '' || char === '"' || char === "'") { if (tempClassName !== '') { @@ -1643,40 +1519,38 @@ export function generate( && prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && prop.arg.content === 'class' ) { - codes.push(`__VLS_styleScopedClasses = (`); - codes.push([ + yield _ts(`__VLS_styleScopedClasses = (`); + yield _ts([ prop.exp.content, 'template', prop.exp.loc.start.offset, presetInfos.scopedClassName, ]); - codes.push(`);\n`); + yield _ts(`);\n`); } } } - function generateSlot(node: CompilerDOM.ElementNode, startTagOffset: number) { + function* generateSlot(node: CompilerDOM.ElementNode, startTagOffset: number): Generator<_CodeAndStack> { const varSlot = `__VLS_${elementIndex++}`; const slotNameExpNode = getSlotNameExpNode(); if (hasScriptSetupSlots) { - codes.push( - '__VLS_normalizeSlot(', - ['', 'template', node.loc.start.offset, presetInfos.diagnosticOnly], - `${slotsAssignName ?? '__VLS_slots'}[`, - ['', 'template', node.loc.start.offset, disableAllFeatures({ __combineLastMappping: true })], - slotNameExpNode?.content ?? `('${getSlotName()?.[0] ?? 'default'}' as const)`, - ['', 'template', node.loc.end.offset, disableAllFeatures({ __combineLastMappping: true })], - ']', - ['', 'template', node.loc.end.offset, disableAllFeatures({ __combineLastMappping: true })], - ')?.(', - ['', 'template', startTagOffset, disableAllFeatures({ __combineLastMappping: true })], - '{\n', - ); + yield _ts('__VLS_normalizeSlot('); + yield _ts(['', 'template', node.loc.start.offset, presetInfos.diagnosticOnly]); + yield _ts(`${slotsAssignName ?? '__VLS_slots'}[`); + yield _ts(['', 'template', node.loc.start.offset, disableAllFeatures({ __combineLastMappping: true })]); + yield _ts(slotNameExpNode?.content ?? `('${getSlotName()?.[0] ?? 'default'}' as const)`); + yield _ts(['', 'template', node.loc.end.offset, disableAllFeatures({ __combineLastMappping: true })]); + yield _ts(']'); + yield _ts(['', 'template', node.loc.end.offset, disableAllFeatures({ __combineLastMappping: true })]); + yield _ts(')?.('); + yield _ts(['', 'template', startTagOffset, disableAllFeatures({ __combineLastMappping: true })]); + yield _ts('{\n'); } else { - codes.push(`var ${varSlot} = {\n`); + yield _ts(`var ${varSlot} = {\n`); } for (const prop of node.props) { if ( @@ -1684,18 +1558,16 @@ export function generate( && !prop.arg && prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION ) { - codes.push( - '...', - ...createInterpolationCode( - prop.exp.content, - prop.exp.loc, - prop.exp.loc.start.offset, - presetInfos.attrReference, - '(', - ')', - ), - ',\n', + yield _ts('...'); + yield* generateInterpolation( + prop.exp.content, + prop.exp.loc, + prop.exp.loc.start.offset, + presetInfos.attrReference, + '(', + ')', ); + yield _ts(',\n'); } else if ( prop.type === CompilerDOM.NodeTypes.DIRECTIVE @@ -1703,65 +1575,64 @@ export function generate( && prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && prop.arg.content !== 'name' ) { - codes.push( - ...createObjectPropertyCode( - prop.arg.content, - prop.arg.loc.start.offset, - mergeFeatureSettings( - presetInfos.slotProp, - { - navigation: { - resolveRenameNewName: camelize, - resolveRenameEditText: getPropRenameApply(prop.arg.content), - }, + yield* generateObjectProperty( + prop.arg.content, + prop.arg.loc.start.offset, + mergeFeatureSettings( + presetInfos.slotProp, + { + navigation: { + resolveRenameNewName: camelize, + resolveRenameEditText: getPropRenameApply(prop.arg.content), }, - ), - prop.arg.loc - ), - ': ', - ...createInterpolationCode( - prop.exp.content, - prop.exp.loc, - prop.exp.loc.start.offset, - presetInfos.attrReference, - '(', - ')', + }, ), - ',\n', + prop.arg.loc + ); + yield _ts(': '); + yield* generateInterpolation( + prop.exp.content, + prop.exp.loc, + prop.exp.loc.start.offset, + presetInfos.attrReference, + '(', + ')', ); + yield _ts(',\n'); } else if ( prop.type === CompilerDOM.NodeTypes.ATTRIBUTE && prop.name !== 'name' // slot name ) { - codes.push( - ...createObjectPropertyCode( - prop.name, - prop.loc.start.offset, - mergeFeatureSettings( - presetInfos.attr, - { - navigation: { - resolveRenameNewName: camelize, - resolveRenameEditText: getPropRenameApply(prop.name), - }, + yield* generateObjectProperty( + prop.name, + prop.loc.start.offset, + mergeFeatureSettings( + presetInfos.attr, + { + navigation: { + resolveRenameNewName: camelize, + resolveRenameEditText: getPropRenameApply(prop.name), }, - ), - prop.loc + }, ), - ': (', + prop.loc + ); + yield _ts(': ('); + yield _ts( prop.value !== undefined ? `"${needToUnicode(prop.value.content) ? toUnicode(prop.value.content) : prop.value.content}"` - : 'true', - '),\n', + : 'true' ); + yield _ts('),\n'); } } - codes.push( - '}', - hasScriptSetupSlots ? ['', 'template', startTagOffset + node.tag.length, presetInfos.diagnosticOnly] : '', - hasScriptSetupSlots ? `);\n` : `;\n` - ); + yield _ts('}'); + if (hasScriptSetupSlots) { + yield _ts(['', 'template', startTagOffset + node.tag.length, presetInfos.diagnosticOnly]); + yield _ts(`)`); + } + yield _ts(`;\n`); if (hasScriptSetupSlots) { return; @@ -1769,22 +1640,20 @@ export function generate( if (slotNameExpNode) { const varSlotExp = `__VLS_${elementIndex++}`; - codes.push(`var ${varSlotExp} = `); + yield _ts(`var ${varSlotExp} = `); if (typeof slotNameExpNode === 'string') { - codes.push(slotNameExpNode); + yield _ts(slotNameExpNode); } else { - codes.push( - ...createInterpolationCode( - slotNameExpNode.content, - slotNameExpNode, - undefined, undefined, - '(', - ')', - ), + yield* generateInterpolation( + slotNameExpNode.content, + slotNameExpNode, + undefined, undefined, + '(', + ')', ); } - codes.push(` as const;\n`); + yield _ts(` as const;\n`); slotExps.set(varSlotExp, { varName: varSlot, }); @@ -1823,33 +1692,31 @@ export function generate( } } - function generateAutoImportCompletionCode() { + function* generateExtraAutoImport(): Generator<_CodeAndStack> { if (!tempVars.length) return; - codes.push('// @ts-ignore\n'); // #2304 - codes.push('['); + yield _ts('// @ts-ignore\n'); // #2304 + yield _ts('['); for (const _vars of tempVars) { for (const v of _vars) { - codes.push([ + yield _ts([ v.text, 'template', v.offset, disableAllFeatures({ completion: { isAdditional: true }, }), ]); - codes.push(','); + yield _ts(','); } } - codes.push('];\n'); + yield _ts('];\n'); tempVars.length = 0; } - // functional like - - function* createAttrValueCode(attrNode: CompilerDOM.TextNode, info: VueCodeInformation): Generator { + function* generateAttrValue(attrNode: CompilerDOM.TextNode, info: VueCodeInformation): Generator<_CodeAndStack> { const char = attrNode.loc.source.startsWith("'") ? "'" : '"'; - yield char; + yield _ts(char); let start = attrNode.loc.start.offset; let end = attrNode.loc.end.offset; let content = attrNode.loc.source; @@ -1862,22 +1729,22 @@ export function generate( content = content.slice(1, -1); } if (needToUnicode(content)) { - yield ['', 'template', start, info]; - yield toUnicode(content); - yield ['', 'template', end, disableAllFeatures({ __combineLastMappping: true })]; + yield _ts(['', 'template', start, info]); + yield _ts(toUnicode(content)); + yield _ts(['', 'template', end, disableAllFeatures({ __combineLastMappping: true })]); } else { - yield [content, 'template', start, info]; + yield _ts([content, 'template', start, info]); } - yield char; + yield _ts(char); } - function* createCamelizeCode(code: string, offset: number, info: VueCodeInformation): Generator { + function* generateCamelized(code: string, offset: number, info: VueCodeInformation): Generator<_CodeAndStack> { const parts = code.split('-'); for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (part !== '') { - yield [ + yield _ts([ i === 0 ? part : capitalize(part), @@ -1886,15 +1753,15 @@ export function generate( i === 0 ? info : disableAllFeatures({ __combineLastMappping: true }), - ]; + ]); } offset += part.length + 1; } } - function* createFormatCode(code: string, offset: number, formatWrapper: [string, string]): Generator { - yield formatWrapper[0]; - yield [ + function* generateTsFormat(code: string, offset: number, formatWrapper: [string, string]): Generator<_CodeAndStack> { + yield _tsFormat(formatWrapper[0]); + yield _tsFormat([ code, 'template', offset, @@ -1905,92 +1772,93 @@ export function generate( // autoInserts: true, // TODO: support vue-autoinsert-parentheses }, ), - ]; - yield formatWrapper[1]; - yield '\n'; + ]); + yield _tsFormat(formatWrapper[1]); + yield _tsFormat('\n'); } - function* createObjectPropertyCode(code: string, offset: number, info: VueCodeInformation, astHolder?: any, shouldCamelize = false): Generator { - + function* generateObjectProperty(code: string, offset: number, info: VueCodeInformation, astHolder?: any, shouldCamelize = false): Generator<_CodeAndStack> { if (code.startsWith('[') && code.endsWith(']') && astHolder) { - yield* createInterpolationCode(code, astHolder, offset, info, '', ''); - return; + yield* generateInterpolation(code, astHolder, offset, info, '', ''); } - - if (shouldCamelize) { + else if (shouldCamelize) { if (validTsVarReg.test(camelize(code))) { - yield* createCamelizeCode(code, offset, info); + yield* generateCamelized(code, offset, info); } else { - yield ['', 'template', offset, info]; - yield '"'; - yield* createCamelizeCode(code, offset, disableAllFeatures({ __combineLastMappping: true })); - yield '"'; - yield ['', 'template', offset + code.length, disableAllFeatures({ __combineLastMappping: true })]; + yield _ts(['', 'template', offset, info]); + yield _ts('"'); + yield* generateCamelized(code, offset, disableAllFeatures({ __combineLastMappping: true })); + yield _ts('"'); + yield _ts(['', 'template', offset + code.length, disableAllFeatures({ __combineLastMappping: true })]); } } else { if (validTsVarReg.test(code)) { - yield [code, 'template', offset, info]; + yield _ts([code, 'template', offset, info]); } else { - yield* createStringLiteralKeyCode(code, offset, info); + yield* generateStringLiteralKey(code, offset, info); } } } - function* createInterpolationCode( + function* generateInterpolation( _code: string, astHolder: any, start: number | undefined, data: VueCodeInformation | (() => VueCodeInformation) | undefined, prefix: string, suffix: string, - ): Generator { + ): Generator<_CodeAndStack> { const code = prefix + _code + suffix; const ast = createTsAst(astHolder, code); - const codes: Code[] = []; - const vars = walkInterpolationFragment( + const vars: { + text: string, + isShorthand: boolean, + offset: number, + }[] = []; + for (let [section, offset, onlyError] of eachInterpolationSegment( ts, code, ast, - (section, offset, onlyError) => { - if (offset === undefined) { - codes.push(section); - } - else { - offset -= prefix.length; - let addSuffix = ''; - const overLength = offset + section.length - _code.length; - if (overLength > 0) { - addSuffix = section.substring(section.length - overLength); - section = section.substring(0, section.length - overLength); - } - if (offset < 0) { - codes.push(section.substring(0, -offset)); - section = section.substring(-offset); - offset = 0; - } - if (start !== undefined && data !== undefined) { - codes.push([ - section, - 'template', - start + offset, - onlyError - ? presetInfos.diagnosticOnly - : typeof data === 'function' ? data() : data, - ]); - } - else { - codes.push(section); - } - codes.push(addSuffix); - } - }, localVars, accessedGlobalVariables, vueCompilerOptions, - ); + vars, + )) { + if (offset === undefined) { + yield _ts(section); + } + else { + offset -= prefix.length; + let addSuffix = ''; + const overLength = offset + section.length - _code.length; + if (overLength > 0) { + addSuffix = section.substring(section.length - overLength); + section = section.substring(0, section.length - overLength); + } + if (offset < 0) { + yield _ts(section.substring(0, -offset)); + section = section.substring(-offset); + offset = 0; + } + if (start !== undefined && data !== undefined) { + yield _ts([ + section, + 'template', + start + offset, + onlyError + ? presetInfos.diagnosticOnly + : typeof data === 'function' ? data() : data, + ]); + } + else { + yield _ts(section); + } + yield _ts(addSuffix); + } + } if (start !== undefined) { for (const v of vars) { v.offset = start + v.offset - prefix.length; @@ -1999,71 +1867,77 @@ export function generate( tempVars.push(vars); } } - for (const code of codes) { - yield code; // TODO: rewrite to yield* - } - } - - function createTsAst(astHolder: any, text: string) { - if (astHolder.__volar_ast_text !== text) { - astHolder.__volar_ast_text = text; - astHolder.__volar_ast = ts.createSourceFile('/a.ts', text, ts.ScriptTarget.ESNext); - } - return astHolder.__volar_ast as ts.SourceFile; } - function* createPropertyAccessCode(code: string, offset?: number, info?: VueCodeInformation, astHolder?: any): Generator { + function* generatePropertyAccess(code: string, offset?: number, info?: VueCodeInformation, astHolder?: any): Generator<_CodeAndStack> { if (!compilerOptions.noPropertyAccessFromIndexSignature && validTsVarReg.test(code)) { - yield '.'; - yield offset !== undefined && info + yield _ts('.'); + yield _ts(offset !== undefined && info ? [code, 'template', offset, info] - : code; + : code); } else if (code.startsWith('[') && code.endsWith(']')) { - yield* createInterpolationCode( - code, - astHolder, - offset, - info, - '', - '', - ); + yield* generateInterpolation(code, astHolder, offset, info, '', ''); } else { - yield '['; - yield* createStringLiteralKeyCode(code, offset, info); - yield ']'; + yield _ts('['); + yield* generateStringLiteralKey(code, offset, info); + yield _ts(']'); } } - function* createStringLiteralKeyCode(code: string, offset?: number, info?: VueCodeInformation): Generator { + function* generateStringLiteralKey(code: string, offset?: number, info?: VueCodeInformation): Generator<_CodeAndStack> { if (offset === undefined || !info) { - yield `"${code}"`; - return; + yield _ts(`"${code}"`); + } + else { + yield _ts(['', 'template', offset, info]); + yield _ts('"'); + yield _ts([code, 'template', offset, disableAllFeatures({ __combineLastMappping: true })]); + yield _ts('"'); + yield _ts(['', 'template', offset + code.length, disableAllFeatures({ __combineLastMappping: true })]); + } + } + + function createTsAst(astHolder: any, text: string) { + if (astHolder.__volar_ast_text !== text) { + astHolder.__volar_ast_text = text; + astHolder.__volar_ast = ts.createSourceFile('/a.ts', text, ts.ScriptTarget.ESNext); } - yield ['', 'template', offset, info]; - yield '"'; - yield [code, 'template', offset, disableAllFeatures({ __combineLastMappping: true })]; - yield '"'; - yield ['', 'template', offset + code.length, disableAllFeatures({ __combineLastMappping: true })]; + return astHolder.__volar_ast as ts.SourceFile; } } -export function walkElementNodes(node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode, cb: (node: CompilerDOM.ElementNode) => void) { +function getCanonicalComponentName(tagText: string) { + return validTsVarReg.test(tagText) + ? tagText + : capitalize(camelize(tagText.replace(colonReg, '-'))); +} + +function getPossibleOriginalComponentNames(tagText: string) { + return [...new Set([ + // order is important: https://github.com/vuejs/language-tools/issues/2010 + capitalize(camelize(tagText)), + camelize(tagText), + tagText, + ])]; +} + +export function* eachElementNode(node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode): Generator { if (node.type === CompilerDOM.NodeTypes.ROOT) { for (const child of node.children) { - walkElementNodes(child, cb); + yield* eachElementNode(child); } } else if (node.type === CompilerDOM.NodeTypes.ELEMENT) { const patchForNode = getVForNode(node); if (patchForNode) { - walkElementNodes(patchForNode, cb); + yield* eachElementNode(patchForNode); } else { - cb(node); + yield node; for (const child of node.children) { - walkElementNodes(child, cb); + yield* eachElementNode(child); } } } @@ -2072,14 +1946,14 @@ export function walkElementNodes(node: CompilerDOM.RootNode | CompilerDOM.Templa for (let i = 0; i < node.branches.length; i++) { const branch = node.branches[i]; for (const childNode of branch.children) { - walkElementNodes(childNode, cb); + yield* eachElementNode(childNode); } } } else if (node.type === CompilerDOM.NodeTypes.FOR) { // v-for for (const child of node.children) { - walkElementNodes(child, cb); + yield* eachElementNode(child); } } } diff --git a/packages/language-core/src/generators/utils.ts b/packages/language-core/src/generators/utils.ts index 041cb87f9e..3556496cbc 100644 --- a/packages/language-core/src/generators/utils.ts +++ b/packages/language-core/src/generators/utils.ts @@ -1,4 +1,21 @@ -import { VueCodeInformation } from '../types'; +import { Code, CodeAndStack, VueCodeInformation } from '../types'; + +export function withStack(code: Code): CodeAndStack { + return [code, getStack()]; +} + +// TODO: import from muggle-string +export function getStack() { + const stack = new Error().stack!; + let source = stack.split('\n')[3].trim(); + if (source.endsWith(')')) { + source = source.slice(source.lastIndexOf('(') + 1, -1); + } + else { + source = source.slice(source.lastIndexOf(' ') + 1); + } + return source; +} export function disableAllFeatures(override: Partial): VueCodeInformation { return { diff --git a/packages/language-core/src/plugins/vue-tsx.ts b/packages/language-core/src/plugins/vue-tsx.ts index 92ef46e700..0bc3a4d4e1 100644 --- a/packages/language-core/src/plugins/vue-tsx.ts +++ b/packages/language-core/src/plugins/vue-tsx.ts @@ -1,10 +1,10 @@ -import { CodeInformation, Segment, track } from '@volar/language-core'; +import { CodeInformation, Mapping, Segment, StackNode, track } from '@volar/language-core'; import { computed, computedSet } from 'computeds'; import { generate as generateScript } from '../generators/script'; import { generate as generateTemplate } from '../generators/template'; import { parseScriptRanges } from '../parsers/scriptRanges'; import { parseScriptSetupRanges } from '../parsers/scriptSetupRanges'; -import { Sfc, VueLanguagePlugin } from '../types'; +import { Code, Sfc, VueLanguagePlugin } from '../types'; import { enableAllFeatures } from '../generators/utils'; const templateFormatReg = /^\.template_format\.ts$/; @@ -64,7 +64,7 @@ const plugin: VueLanguagePlugin = (ctx) => { }); embeddedFile.content = content; embeddedFile.contentStacks = contentStacks; - embeddedFile.linkedNavigationMappings = [...tsx.mirrorBehaviorMappings]; + embeddedFile.linkedNavigationMappings = [...tsx.linkedCodeMappings]; } } else if (suffix.match(templateFormatReg)) { @@ -74,8 +74,8 @@ const plugin: VueLanguagePlugin = (ctx) => { const template = _tsx.generatedTemplate(); if (template) { const [content, contentStacks] = ctx.codegenStack - ? track([...template.formatCodes], [...template.formatCodeStacks]) - : [[...template.formatCodes], [...template.formatCodeStacks]]; + ? track([...template.formatCodes], template.formatCodeStacks.map(stack => ({ stack, length: 1 }))) + : [[...template.formatCodes], template.formatCodeStacks.map(stack => ({ stack, length: 1 }))]; embeddedFile.content = content; embeddedFile.contentStacks = contentStacks; } @@ -101,8 +101,8 @@ const plugin: VueLanguagePlugin = (ctx) => { const template = _tsx.generatedTemplate(); if (template) { const [content, contentStacks] = ctx.codegenStack - ? track([...template.cssCodes], [...template.cssCodeStacks]) - : [[...template.cssCodes], [...template.cssCodeStacks]]; + ? track([...template.cssCodes], template.cssCodeStacks.map(stack => ({ stack, length: 1 }))) + : [[...template.cssCodes], template.cssCodeStacks.map(stack => ({ stack, length: 1 }))]; embeddedFile.content = content as Segment[]; embeddedFile.contentStacks = contentStacks; } @@ -169,7 +169,13 @@ function createTsx(fileName: string, _sfc: Sfc, { vueCompilerOptions, compilerOp if (!_sfc.template) return; - return generateTemplate( + const tsCodes: Code[] = []; + const tsFormatCodes: Code[] = []; + const inlineCssCodes: Code[] = []; + const tsCodegenStacks: string[] = []; + const tsFormatCodegenStacks: string[] = []; + const inlineCssCodegenStacks: string[] = []; + const codegen = generateTemplate( ts, compilerOptions, vueCompilerOptions, @@ -181,24 +187,89 @@ function createTsx(fileName: string, _sfc: Sfc, { vueCompilerOptions, compilerOp propsAssignName(), codegenStack, ); + + let current = codegen.next(); + + while (!current.done) { + const [type, code, stack] = current.value; + if (type === 'ts') { + tsCodes.push(code); + } + else if (type === 'tsFormat') { + tsFormatCodes.push(code); + } + else if (type === 'inlineCss') { + inlineCssCodes.push(code); + } + if (codegenStack) { + if (type === 'ts') { + tsCodegenStacks.push(stack); + } + else if (type === 'tsFormat') { + tsFormatCodegenStacks.push(stack); + } + else if (type === 'inlineCss') { + inlineCssCodegenStacks.push(stack); + } + } + current = codegen.next(); + } + + return { + ...current.value, + codes: tsCodes, + codeStacks: tsCodegenStacks, + formatCodes: tsFormatCodes, + formatCodeStacks: tsFormatCodegenStacks, + cssCodes: inlineCssCodes, + cssCodeStacks: inlineCssCodegenStacks, + }; }); const hasScriptSetupSlots = computed(() => !!scriptSetupRanges()?.slots.define); const slotsAssignName = computed(() => scriptSetupRanges()?.slots.name); const propsAssignName = computed(() => scriptSetupRanges()?.props.name); - const generatedScript = computed(() => generateScript( - ts, - fileName, - _sfc.script, - _sfc.scriptSetup, - _sfc.styles, - lang(), - scriptRanges(), - scriptSetupRanges(), - generatedTemplate(), - compilerOptions, - vueCompilerOptions, - codegenStack, - )); + const generatedScript = computed(() => { + const codes: Code[] = []; + const codeStacks: StackNode[] = []; + const linkedCodeMappings: Mapping[] = []; + const _template = generatedTemplate(); + let generatedLength = 0; + for (const [code, stack] of generateScript( + ts, + fileName, + _sfc.script, + _sfc.scriptSetup, + _sfc.styles, + lang(), + scriptRanges(), + scriptSetupRanges(), + _template ? { + tsCodes: _template.codes, + tsCodegenStacks: _template.codeStacks, + accessedGlobalVariables: _template.accessedGlobalVariables, + hasSlot: _template.hasSlot, + tagNames: new Set(_template.tagOffsetsMap.keys()), + } : undefined, + compilerOptions, + vueCompilerOptions, + () => generatedLength, + linkedCodeMappings, + codegenStack, + )) { + codes.push(code); + if (codegenStack) { + codeStacks.push({ stack, length: 1 }); + } + generatedLength += typeof code === 'string' + ? code.length + : code[0].length; + }; + return { + codes, + codeStacks, + linkedCodeMappings, + }; + }); return { scriptRanges, diff --git a/packages/language-core/src/types.ts b/packages/language-core/src/types.ts index 9e5ee90e2d..2db86be6eb 100644 --- a/packages/language-core/src/types.ts +++ b/packages/language-core/src/types.ts @@ -24,6 +24,8 @@ export interface VueCodeInformation extends CodeInformation { __combineLastMappping?: boolean; } +export type CodeAndStack = [code: Code, stack: string]; + export type Code = Segment; export interface VueCompilerOptions { diff --git a/packages/language-core/src/utils/transform.ts b/packages/language-core/src/utils/transform.ts index 9465908195..5bed52f50f 100644 --- a/packages/language-core/src/utils/transform.ts +++ b/packages/language-core/src/utils/transform.ts @@ -2,21 +2,19 @@ import { isGloballyWhitelisted } from '@vue/shared'; import type * as ts from 'typescript/lib/tsserverlibrary'; import { VueCompilerOptions } from '../types'; -export function walkInterpolationFragment( +export function* eachInterpolationSegment( ts: typeof import('typescript/lib/tsserverlibrary'), code: string, ast: ts.SourceFile, - cb: (fragment: string, offset: number | undefined, isJustForErrorMapping?: boolean) => void, localVars: Map, identifiers: Set, vueOptions: VueCompilerOptions, -) { - - let ctxVars: { + ctxVars: { text: string, isShorthand: boolean, offset: number, - }[] = []; + }[] = [] +): Generator<[fragment: string, offset: number | undefined, isJustForErrorMapping?: boolean]> { const varCb = (id: ts.Identifier, isShorthand: boolean) => { if ( @@ -44,44 +42,44 @@ export function walkInterpolationFragment( if (ctxVars.length) { if (ctxVars[0].isShorthand) { - cb(code.substring(0, ctxVars[0].offset + ctxVars[0].text.length), 0); - cb(': ', undefined); + yield [code.substring(0, ctxVars[0].offset + ctxVars[0].text.length), 0]; + yield [': ', undefined]; } else { - cb(code.substring(0, ctxVars[0].offset), 0); + yield [code.substring(0, ctxVars[0].offset), 0]; } for (let i = 0; i < ctxVars.length - 1; i++) { // fix https://github.com/vuejs/language-tools/issues/1205 // fix https://github.com/vuejs/language-tools/issues/1264 - cb('', ctxVars[i + 1].offset, true); + yield ['', ctxVars[i + 1].offset, true]; if (vueOptions.experimentalUseElementAccessInTemplate) { const varStart = ctxVars[i].offset; const varEnd = ctxVars[i].offset + ctxVars[i].text.length; - cb('__VLS_ctx[', undefined); - cb('', varStart, true); - cb("'", undefined); - cb(code.substring(varStart, varEnd), varStart); - cb("'", undefined); - cb('', varEnd, true); - cb(']', undefined); + yield ['__VLS_ctx[', undefined]; + yield ['', varStart, true]; + yield ["'", undefined]; + yield [code.substring(varStart, varEnd), varStart]; + yield ["'", undefined]; + yield ['', varEnd, true]; + yield [']', undefined]; if (ctxVars[i + 1].isShorthand) { - cb(code.substring(varEnd, ctxVars[i + 1].offset + ctxVars[i + 1].text.length), varEnd); - cb(': ', undefined); + yield [code.substring(varEnd, ctxVars[i + 1].offset + ctxVars[i + 1].text.length), varEnd]; + yield [': ', undefined]; } else { - cb(code.substring(varEnd, ctxVars[i + 1].offset), varEnd); + yield [code.substring(varEnd, ctxVars[i + 1].offset), varEnd]; } } else { - cb('__VLS_ctx.', undefined); + yield ['__VLS_ctx.', undefined]; if (ctxVars[i + 1].isShorthand) { - cb(code.substring(ctxVars[i].offset, ctxVars[i + 1].offset + ctxVars[i + 1].text.length), ctxVars[i].offset); - cb(': ', undefined); + yield [code.substring(ctxVars[i].offset, ctxVars[i + 1].offset + ctxVars[i + 1].text.length), ctxVars[i].offset]; + yield [': ', undefined]; } else { - cb(code.substring(ctxVars[i].offset, ctxVars[i + 1].offset), ctxVars[i].offset); + yield [code.substring(ctxVars[i].offset, ctxVars[i + 1].offset), ctxVars[i].offset]; } } } @@ -89,26 +87,24 @@ export function walkInterpolationFragment( if (vueOptions.experimentalUseElementAccessInTemplate) { const varStart = ctxVars[ctxVars.length - 1].offset; const varEnd = ctxVars[ctxVars.length - 1].offset + ctxVars[ctxVars.length - 1].text.length; - cb('__VLS_ctx[', undefined); - cb('', varStart, true); - cb("'", undefined); - cb(code.substring(varStart, varEnd), varStart); - cb("'", undefined); - cb('', varEnd, true); - cb(']', undefined); - cb(code.substring(varEnd), varEnd); + yield ['__VLS_ctx[', undefined]; + yield ['', varStart, true]; + yield ["'", undefined]; + yield [code.substring(varStart, varEnd), varStart]; + yield ["'", undefined]; + yield ['', varEnd, true]; + yield [']', undefined]; + yield [code.substring(varEnd), varEnd]; } else { - cb('', ctxVars[ctxVars.length - 1].offset, true); - cb('__VLS_ctx.', undefined); - cb(code.substring(ctxVars[ctxVars.length - 1].offset), ctxVars[ctxVars.length - 1].offset); + yield ['', ctxVars[ctxVars.length - 1].offset, true]; + yield ['__VLS_ctx.', undefined]; + yield [code.substring(ctxVars[ctxVars.length - 1].offset), ctxVars[ctxVars.length - 1].offset]; } } else { - cb(code, 0); + yield [code, 0]; } - - return ctxVars; } function walkIdentifiers( diff --git a/packages/language-service/src/helpers.ts b/packages/language-service/src/helpers.ts index 8d3dc7c852..8f845dd533 100644 --- a/packages/language-service/src/helpers.ts +++ b/packages/language-service/src/helpers.ts @@ -281,7 +281,7 @@ export function getTemplateTagsAndAttrs(sourceFile: embedded.VirtualFile): Tags const ast = sourceFile.sfc.template?.ast; const tags: Tags = new Map(); if (ast) { - vue.walkElementNodes(ast, node => { + for (const node of vue.eachElementNode(ast)) { if (!tags.has(node.tag)) { tags.set(node.tag, { offsets: [], attrs: new Map() }); @@ -323,7 +323,7 @@ export function getTemplateTagsAndAttrs(sourceFile: embedded.VirtualFile): Tags tag.attrs.get(name)!.offsets.push(offset); } } - }); + } } return tags; }); diff --git a/packages/language-service/src/plugins/vue-toggle-v-bind-codeaction.ts b/packages/language-service/src/plugins/vue-toggle-v-bind-codeaction.ts index 6947a4397c..06ce613917 100644 --- a/packages/language-service/src/plugins/vue-toggle-v-bind-codeaction.ts +++ b/packages/language-service/src/plugins/vue-toggle-v-bind-codeaction.ts @@ -1,5 +1,5 @@ import { ServicePlugin, ServicePluginInstance } from '@volar/language-service'; -import { VueFile, walkElementNodes, type CompilerDOM } from '@vue/language-core'; +import { VueFile, eachElementNode, type CompilerDOM } from '@vue/language-core'; import type * as vscode from 'vscode-languageserver-protocol'; export function create(ts: typeof import('typescript/lib/tsserverlibrary')): ServicePlugin { @@ -23,7 +23,7 @@ export function create(ts: typeof import('typescript/lib/tsserverlibrary')): Ser const templateStartOffset = template!.startTagEnd; const result: vscode.CodeAction[] = []; - walkElementNodes(template.ast, node => { + for (const node of eachElementNode(template.ast)) { if (startOffset > templateStartOffset + node.loc.end.offset || endOffset < templateStartOffset + node.loc.start.offset) { return; } @@ -128,7 +128,7 @@ export function create(ts: typeof import('typescript/lib/tsserverlibrary')): Ser } } } - }); + } return result; },