diff --git a/packages/language-core/src/generators/script.ts b/packages/language-core/src/generators/script.ts index 117ac4c878..0af79f5e5a 100644 --- a/packages/language-core/src/generators/script.ts +++ b/packages/language-core/src/generators/script.ts @@ -1,16 +1,16 @@ -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, SfcBlock, VueCompilerOptions } from '../types'; import { Sfc } from '../types'; import { getSlotsPropertyName, hyphenateTag } from '../utils/shared'; -import { walkInterpolationFragment } from '../utils/transform'; +import { eachInterpolationSegment } from '../utils/transform'; import { disableAllFeatures, enableAllFeatures } from './utils'; -export function generate( +export function* generate( ts: typeof import('typescript/lib/tsserverlibrary'), fileName: string, script: Sfc['script'], @@ -22,11 +22,9 @@ export function generate( htmlGen: ReturnType | undefined, compilerOptions: ts.CompilerOptions, vueCompilerOptions: VueCompilerOptions, - codegenStack: boolean, -) { - - const [codes, codeStacks] = codegenStack ? track([] as Code[]) : [[], []]; - const mirrorBehaviorMappings: Mapping[] = []; + getGeneratedLength: () => number, + linkedCodeMappings: Mapping[] = [], +): Generator { //#region monkey fix: https://github.com/vuejs/language-tools/pull/2113 if (!script && !scriptSetup) { @@ -68,85 +66,95 @@ export function generate( PropsChildren: false, }; - codes.push(`/* __placeholder__ */\n`); - 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, @@ -170,212 +178,196 @@ 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(`,`); - } - 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); + ]; + if (!scriptSetup.generic.endsWith(`,`)) { + yield `,`; + } + 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 +378,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 +388,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, _, 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 +530,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)) { 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 +710,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,66 +726,66 @@ 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, disableAllFeatures({ navigation: true, }), - ]); + ]; } 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 +794,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,109 +825,105 @@ 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); + yield s; } } if (!htmlGen) { - codes.push(`// no template\n`); + 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`); + yield `return ${scriptSetupRanges?.slots.name ?? '__VLS_slots'};\n`; - return { cssIds }; - - 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() { @@ -960,25 +949,21 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: 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..28e99d075e 100644 --- a/packages/language-core/src/generators/template.ts +++ b/packages/language-core/src/generators/template.ts @@ -5,7 +5,7 @@ import { minimatch } from 'minimatch'; import type * as ts from 'typescript/lib/tsserverlibrary'; import { Code, Sfc, VueCodeInformation, VueCompilerOptions } from '../types'; import { hyphenateAttr, hyphenateTag } from '../utils/shared'; -import { collectVars, walkInterpolationFragment } from '../utils/transform'; +import { collectVars, eachInterpolationSegment } from '../utils/transform'; import { mergeFeatureSettings, disableAllFeatures, enableAllFeatures } from './utils'; const presetInfos = { @@ -1950,47 +1950,52 @@ export function generate( 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) { + 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); + } + } if (start !== undefined) { for (const v of vars) { v.offset = start + v.offset - prefix.length; diff --git a/packages/language-core/src/plugins/vue-tsx.ts b/packages/language-core/src/plugins/vue-tsx.ts index 92ef46e700..f0d9649091 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, 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)) { @@ -185,20 +185,36 @@ function createTsx(fileName: string, _sfc: Sfc, { vueCompilerOptions, compilerOp 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, codeStacks] = codegenStack ? track([] as Code[]) : [[], []]; + const linkedCodeMappings: Mapping[] = []; + let generatedLength = 0; + for (const code of generateScript( + ts, + fileName, + _sfc.script, + _sfc.scriptSetup, + _sfc.styles, + lang(), + scriptRanges(), + scriptSetupRanges(), + generatedTemplate(), + compilerOptions, + vueCompilerOptions, + () => generatedLength, + linkedCodeMappings, + )) { + codes.push(code); + generatedLength += typeof code === 'string' + ? code.length + : code[0].length; + }; + return { + codes, + codeStacks, + linkedCodeMappings, + }; + }); return { scriptRanges, 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(