diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts index 83befdd7fc..2081541203 100644 --- a/packages/language-core/lib/codegen/globalTypes.ts +++ b/packages/language-core/lib/codegen/globalTypes.ts @@ -134,7 +134,7 @@ export function generateGlobalTypes(options: VueCompilerOptions) { type __VLS_ResolveDirectives = { [K in keyof T & string as \`v\${Capitalize}\`]: T[K]; }; - type __VLS_PrettifyGlobal = { [K in keyof T as K]: T[K]; } & {}; + type __VLS_PrettifyGlobal = (T extends any ? { [K in keyof T]: T[K]; } : { [K in keyof T as K]: T[K]; }) & {}; type __VLS_WithDefaultsGlobal = { [K in keyof P as K extends keyof D ? K : never]-?: P[K]; } & { diff --git a/packages/language-core/lib/codegen/localTypes.ts b/packages/language-core/lib/codegen/localTypes.ts index 84e65d4aec..8e212cd2c7 100644 --- a/packages/language-core/lib/codegen/localTypes.ts +++ b/packages/language-core/lib/codegen/localTypes.ts @@ -17,7 +17,8 @@ type __VLS_WithDefaultsLocal = { ); const PrettifyLocal = defineHelper( `__VLS_PrettifyLocal`, - () => `type __VLS_PrettifyLocal = { [K in keyof T as K]: T[K]; } & {}${endOfLine}`, + () => + `type __VLS_PrettifyLocal = (T extends any ? { [K in keyof T]: T[K]; } : { [K in keyof T as K]: T[K]; }) & {}${endOfLine}`, ); const WithSlots = defineHelper( `__VLS_WithSlots`, diff --git a/packages/language-core/lib/codegen/script/scriptSetup.ts b/packages/language-core/lib/codegen/script/scriptSetup.ts index d0d2826031..4f3fbd87cc 100644 --- a/packages/language-core/lib/codegen/script/scriptSetup.ts +++ b/packages/language-core/lib/codegen/script/scriptSetup.ts @@ -2,7 +2,14 @@ import { camelize } from '@vue/shared'; import type { ScriptSetupRanges } from '../../parsers/scriptSetupRanges'; import type { Code, Sfc, TextRange } from '../../types'; import { codeFeatures } from '../codeFeatures'; -import { endOfLine, generatePartiallyEnding, generateSfcBlockSection, identifierRegex, newLine } from '../utils'; +import { + createSfcBlockGenerator, + endOfLine, + generatePartiallyEnding, + generateSfcBlockSection, + identifierRegex, + newLine, +} from '../utils'; import { generateCamelized } from '../utils/camelized'; import { wrapWith } from '../utils/wrapWith'; import { generateComponent } from './component'; @@ -129,103 +136,99 @@ function* generateSetupFunction( scriptSetupRanges: ScriptSetupRanges, syntax: 'return' | 'export default' | undefined, ): Generator { - let setupCodeModifies: [Code[], number, number][] = []; + const { replace, generate } = createSfcBlockGenerator( + scriptSetup, + Math.max(scriptSetupRanges.importSectionEndOffset, scriptSetupRanges.leadingCommentEndOffset), + scriptSetup.content.length, + codeFeatures.all, + ); + if (scriptSetupRanges.defineProps) { const { name, statement, callExp, typeArg } = scriptSetupRanges.defineProps; - setupCodeModifies.push(...generateDefineWithType( - scriptSetup, - statement, - scriptSetupRanges.withDefaults?.callExp ?? callExp, - typeArg, - name, - `__VLS_props`, - `__VLS_Props`, - )); + for ( + const replacement of generateDefineWithType( + scriptSetup, + statement, + scriptSetupRanges.withDefaults?.callExp ?? callExp, + typeArg, + name, + `__VLS_props`, + `__VLS_Props`, + ) + ) { + replace(...replacement); + } } if (scriptSetupRanges.defineEmits) { const { name, statement, callExp, typeArg } = scriptSetupRanges.defineEmits; - setupCodeModifies.push(...generateDefineWithType( - scriptSetup, - statement, - callExp, - typeArg, - name, - `__VLS_emit`, - `__VLS_Emit`, - )); + for ( + const replacement of generateDefineWithType( + scriptSetup, + statement, + callExp, + typeArg, + name, + `__VLS_emit`, + `__VLS_Emit`, + ) + ) { + replace(...replacement); + } } if (scriptSetupRanges.defineSlots) { const { name, statement, callExp, typeArg } = scriptSetupRanges.defineSlots; - setupCodeModifies.push(...generateDefineWithType( - scriptSetup, - statement, - callExp, - typeArg, - name, - `__VLS_slots`, - `__VLS_Slots`, - )); + for ( + const replacement of generateDefineWithType( + scriptSetup, + statement, + callExp, + typeArg, + name, + `__VLS_slots`, + `__VLS_Slots`, + ) + ) { + replace(...replacement); + } } if (scriptSetupRanges.defineExpose) { const { callExp, arg, typeArg } = scriptSetupRanges.defineExpose; if (typeArg) { - setupCodeModifies.push([ - [ - `let __VLS_exposed!: `, - generateSfcBlockSection(scriptSetup, typeArg.start, typeArg.end, codeFeatures.all), - endOfLine, - ], + replace( callExp.start, callExp.start, - ], [ - [`typeof __VLS_exposed`], - typeArg.start, - typeArg.end, - ]); + `let __VLS_exposed!: `, + generateSfcBlockSection(scriptSetup, typeArg.start, typeArg.end, codeFeatures.all), + endOfLine, + ); + replace(typeArg.start, typeArg.end, `typeof __VLS_exposed`); } else if (arg) { - setupCodeModifies.push([ - [ - `const __VLS_exposed = `, - generateSfcBlockSection(scriptSetup, arg.start, arg.end, codeFeatures.all), - endOfLine, - ], + replace( callExp.start, callExp.start, - ], [ - [`__VLS_exposed`], - arg.start, - arg.end, - ]); + `const __VLS_exposed = `, + generateSfcBlockSection(scriptSetup, arg.start, arg.end, codeFeatures.all), + endOfLine, + ); + replace(arg.start, arg.end, `__VLS_exposed`); } else { - setupCodeModifies.push([ - [`const __VLS_exposed = {}${endOfLine}`], - callExp.start, - callExp.start, - ]); + replace(callExp.start, callExp.start, `const __VLS_exposed = {}${endOfLine}`); } } if (options.vueCompilerOptions.inferTemplateDollarAttrs) { for (const { callExp } of scriptSetupRanges.useAttrs) { - setupCodeModifies.push([ - [`(`], - callExp.start, - callExp.start, - ], [ - [` as typeof __VLS_dollars.$attrs)`], - callExp.end, - callExp.end, - ]); + replace(callExp.start, callExp.start, `(`); + replace(callExp.end, callExp.end, ` as typeof __VLS_dollars.$attrs)`); } } for (const { callExp, exp, arg } of scriptSetupRanges.useCssModule) { - setupCodeModifies.push([ - [`(`], - callExp.start, - callExp.start, - ], [ - arg + replace(callExp.start, callExp.start, `(`); + replace( + callExp.end, + callExp.end, + ...arg ? [ ` as Omit<__VLS_StyleModules, '$style'>[`, generateSfcBlockSection(scriptSetup, arg.start, arg.end, codeFeatures.withoutSemantic), @@ -242,28 +245,15 @@ function* generateSetupFunction( ), `])`, ], - callExp.end, - callExp.end, - ]); + ); if (arg) { - setupCodeModifies.push([ - [`__VLS_placeholder`], - arg.start, - arg.end, - ]); + replace(arg.start, arg.end, `__VLS_placeholder`); } } if (options.vueCompilerOptions.inferTemplateDollarSlots) { for (const { callExp } of scriptSetupRanges.useSlots) { - setupCodeModifies.push([ - [`(`], - callExp.start, - callExp.start, - ], [ - [` as typeof __VLS_dollars.$slots)`], - callExp.end, - callExp.end, - ]); + replace(callExp.start, callExp.start, `(`); + replace(callExp.end, callExp.end, ` as typeof __VLS_dollars.$slots)`); } } const isTs = options.lang !== 'js' && options.lang !== 'jsx'; @@ -276,49 +266,18 @@ function* generateSetupFunction( ] : [`unknown`]; if (isTs) { - setupCodeModifies.push([ - [ - `<`, - ...templateRefType, - `>`, - ], - exp.end, - exp.end, - ]); + replace(exp.end, exp.end, `<`, ...templateRefType, `>`); } else { - setupCodeModifies.push([ - [`(`], - callExp.start, - callExp.start, - ], [ - [ - ` as __VLS_UseTemplateRef<`, - ...templateRefType, - `>)`, - ], - callExp.end, - callExp.end, - ]); + replace(callExp.start, callExp.start, `(`); + replace(callExp.end, callExp.end, ` as __VLS_UseTemplateRef<`, ...templateRefType, `>)`); } if (arg) { - setupCodeModifies.push([ - [`__VLS_placeholder`], - arg.start, - arg.end, - ]); + replace(arg.start, arg.end, `__VLS_placeholder`); } } - setupCodeModifies = setupCodeModifies.sort((a, b) => a[1] - b[1]); - - let nextStart = Math.max(scriptSetupRanges.importSectionEndOffset, scriptSetupRanges.leadingCommentEndOffset); - for (const [codes, start, end] of setupCodeModifies) { - yield generateSfcBlockSection(scriptSetup, nextStart, start, codeFeatures.all); - yield* codes; - nextStart = end; - } - yield generateSfcBlockSection(scriptSetup, nextStart, scriptSetup.content.length, codeFeatures.all); + yield* generate(); yield* generatePartiallyEnding(scriptSetup.name, scriptSetup.content.length, '#3632/scriptSetup.vue'); yield* generateMacros(options, ctx); @@ -375,67 +334,57 @@ function* generateDefineWithType( name: string | undefined, defaultName: string, typeName: string, -): Generator<[Code[], number, number]> { +): Generator<[number, number, ...Code[]]> { if (typeArg) { yield [ - [ - `type ${typeName} = `, - generateSfcBlockSection(scriptSetup, typeArg.start, typeArg.end, codeFeatures.all), - endOfLine, - ], statement.start, statement.start, + `type ${typeName} = `, + generateSfcBlockSection(scriptSetup, typeArg.start, typeArg.end, codeFeatures.all), + endOfLine, ]; - yield [[typeName], typeArg.start, typeArg.end]; + yield [typeArg.start, typeArg.end, typeName]; } if (!name) { if (statement.start === callExp.start && statement.end === callExp.end) { - yield [[`const ${defaultName} = `], callExp.start, callExp.start]; + yield [callExp.start, callExp.start, `const ${defaultName} = `]; } else if (typeArg) { yield [ - [ - `const ${defaultName} = `, - generateSfcBlockSection(scriptSetup, callExp.start, typeArg.start, codeFeatures.all), - ], statement.start, typeArg.start, + `const ${defaultName} = `, + generateSfcBlockSection(scriptSetup, callExp.start, typeArg.start, codeFeatures.all), ]; yield [ - [ - generateSfcBlockSection(scriptSetup, typeArg.end, callExp.end, codeFeatures.all), - endOfLine, - generateSfcBlockSection(scriptSetup, statement.start, callExp.start, codeFeatures.all), - defaultName, - ], typeArg.end, callExp.end, + generateSfcBlockSection(scriptSetup, typeArg.end, callExp.end, codeFeatures.all), + endOfLine, + generateSfcBlockSection(scriptSetup, statement.start, callExp.start, codeFeatures.all), + defaultName, ]; } else { yield [ - [ - `const ${defaultName} = `, - generateSfcBlockSection(scriptSetup, callExp.start, callExp.end, codeFeatures.all), - endOfLine, - generateSfcBlockSection(scriptSetup, statement.start, callExp.start, codeFeatures.all), - defaultName, - ], statement.start, callExp.end, + `const ${defaultName} = `, + generateSfcBlockSection(scriptSetup, callExp.start, callExp.end, codeFeatures.all), + endOfLine, + generateSfcBlockSection(scriptSetup, statement.start, callExp.start, codeFeatures.all), + defaultName, ]; } } else if (!identifierRegex.test(name)) { - yield [[`const ${defaultName} = `], statement.start, callExp.start]; + yield [statement.start, callExp.start, `const ${defaultName} = `]; yield [ - [ - endOfLine, - generateSfcBlockSection(scriptSetup, statement.start, callExp.start, codeFeatures.all), - defaultName, - ], statement.end, statement.end, + endOfLine, + generateSfcBlockSection(scriptSetup, statement.start, callExp.start, codeFeatures.all), + defaultName, ]; } } diff --git a/packages/language-core/lib/codegen/script/template.ts b/packages/language-core/lib/codegen/script/template.ts index f0e2f937c7..d6eafef9e0 100644 --- a/packages/language-core/lib/codegen/script/template.ts +++ b/packages/language-core/lib/codegen/script/template.ts @@ -7,7 +7,7 @@ import { generateStyleScopedClasses } from '../style/scopedClasses'; import { createTemplateCodegenContext, type TemplateCodegenContext } from '../template/context'; import { generateInterpolation } from '../template/interpolation'; import { generateStyleScopedClassReferences } from '../template/styleScopedClasses'; -import { endOfLine, generateSfcBlockSection, newLine } from '../utils'; +import { createSfcBlockGenerator, endOfLine, generateSfcBlockSection, newLine } from '../utils'; import { generateSpreadMerge } from '../utils/merge'; import type { ScriptCodegenContext } from './context'; import type { ScriptCodegenOptions } from './index'; @@ -27,9 +27,20 @@ export function* generateTemplate( function* generateSelf(options: ScriptCodegenOptions): Generator { if (options.sfc.script && options.scriptRanges?.componentOptions) { + const { args, expose } = options.scriptRanges.componentOptions; + const { replace, generate } = createSfcBlockGenerator( + options.sfc.script, + args.start, + args.end, + codeFeatures.navigation, + ); + + if (expose) { + replace(expose.start, expose.end, `undefined`); + } + yield `const __VLS_self = (await import('${options.vueCompilerOptions.lib}')).defineComponent(`; - const { args } = options.scriptRanges.componentOptions; - yield generateSfcBlockSection(options.sfc.script, args.start, args.end, codeFeatures.all); + yield* generate(); yield `)${endOfLine}`; } else if (options.sfc.script && options.scriptRanges?.exportDefault) { diff --git a/packages/language-core/lib/codegen/utils/index.ts b/packages/language-core/lib/codegen/utils/index.ts index 7b09218b5c..7c83580495 100644 --- a/packages/language-core/lib/codegen/utils/index.ts +++ b/packages/language-core/lib/codegen/utils/index.ts @@ -35,6 +35,30 @@ export function createTsAst( return ast; } +export function createSfcBlockGenerator( + block: SfcBlock, + start: number, + end: number, + features: VueCodeInformation, +) { + const replacement: [number, number, ...Code[]][] = []; + + return { + replace(...args: typeof replacement[number]) { + replacement.push(args); + }, + *generate() { + let offset = start; + for (const [start, end, ...codes] of replacement.sort((a, b) => a[0] - b[0])) { + yield generateSfcBlockSection(block, offset, start, features); + yield* codes; + offset = end; + } + yield generateSfcBlockSection(block, offset, end, features); + }, + }; +} + export function generateSfcBlockSection( block: SfcBlock, start: number, diff --git a/packages/language-core/lib/parsers/scriptRanges.ts b/packages/language-core/lib/parsers/scriptRanges.ts index 72788c2551..cdd767c198 100644 --- a/packages/language-core/lib/parsers/scriptRanges.ts +++ b/packages/language-core/lib/parsers/scriptRanges.ts @@ -21,6 +21,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc directives: TextRange | undefined; name: TextRange | undefined; inheritAttrs: string | undefined; + expose: TextRange | undefined; } | undefined; @@ -57,6 +58,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc let directivesOptionNode: ts.ObjectLiteralExpression | undefined; let nameOptionNode: ts.Expression | undefined; let inheritAttrsOption: string | undefined; + let exposeOptionNode: ts.Expression | undefined; ts.forEachChild(obj, node => { if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name)) { const name = _getNodeText(node.name); @@ -72,6 +74,9 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc else if (name === 'inheritAttrs') { inheritAttrsOption = _getNodeText(node.initializer); } + else if (name === 'expose') { + exposeOptionNode = node.initializer; + } } }); componentOptions = { @@ -83,6 +88,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc directives: directivesOptionNode ? _getStartEnd(directivesOptionNode) : undefined, name: nameOptionNode ? _getStartEnd(nameOptionNode) : undefined, inheritAttrs: inheritAttrsOption, + expose: exposeOptionNode ? _getStartEnd(exposeOptionNode) : undefined, }; } } diff --git a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap index f925cc47e0..ca8fe89438 100644 --- a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap +++ b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap @@ -81,9 +81,11 @@ exports[`vue-tsc-dts > Input: generic/component.vue, Output: generic/component.v }; declare const _default: typeof __VLS_export; export default _default; -type __VLS_PrettifyLocal = { +type __VLS_PrettifyLocal = (T extends any ? { + [K in keyof T]: T[K]; +} : { [K in keyof T as K]: T[K]; -} & {}; +}) & {}; " `; @@ -114,9 +116,11 @@ exports[`vue-tsc-dts > Input: generic/custom-extension-component.cext, Output: g }; declare const _default: typeof __VLS_export; export default _default; -type __VLS_PrettifyLocal = { +type __VLS_PrettifyLocal = (T extends any ? { + [K in keyof T]: T[K]; +} : { [K in keyof T as K]: T[K]; -} & {}; +}) & {}; " `; diff --git a/test-workspace/tsc/passedFixtures/vue3/#5069/main.vue b/test-workspace/tsc/passedFixtures/vue3/#5069/main.vue new file mode 100644 index 0000000000..5877f3d8f3 --- /dev/null +++ b/test-workspace/tsc/passedFixtures/vue3/#5069/main.vue @@ -0,0 +1,42 @@ + + + diff --git a/test-workspace/tsc/passedFixtures/vue3/#5592/child.vue b/test-workspace/tsc/passedFixtures/vue3/#5592/child.vue new file mode 100644 index 0000000000..748369e89e --- /dev/null +++ b/test-workspace/tsc/passedFixtures/vue3/#5592/child.vue @@ -0,0 +1,8 @@ + + + diff --git a/test-workspace/tsc/passedFixtures/vue3/#5592/main.vue b/test-workspace/tsc/passedFixtures/vue3/#5592/main.vue new file mode 100644 index 0000000000..4e7c74e34c --- /dev/null +++ b/test-workspace/tsc/passedFixtures/vue3/#5592/main.vue @@ -0,0 +1,9 @@ + + +