From f076dd327658ff823c2203d7fe47093a3c598ebe Mon Sep 17 00:00:00 2001 From: so1ve Date: Fri, 26 Sep 2025 11:08:37 +0800 Subject: [PATCH 1/4] test: add tests --- test-workspace/tsc/passedFixtures/vue3/#5592/child.vue | 8 ++++++++ test-workspace/tsc/passedFixtures/vue3/#5592/main.vue | 9 +++++++++ 2 files changed, 17 insertions(+) create mode 100644 test-workspace/tsc/passedFixtures/vue3/#5592/child.vue create mode 100644 test-workspace/tsc/passedFixtures/vue3/#5592/main.vue 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 @@ + + + From f40eccfdfddf9f9ecf8e9d748064ddcaf8a5e63e Mon Sep 17 00:00:00 2001 From: so1ve Date: Fri, 26 Sep 2025 11:10:17 +0800 Subject: [PATCH 2/4] Revert "fix(language-core): `Prettify` breaks generics inferencing (#5424)" This reverts commit 7c5371548279b126a5d003cd0b6bc7bb296367c5. --- packages/language-core/lib/codegen/globalTypes.ts | 2 +- packages/language-core/lib/codegen/localTypes.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts index 93134095e0..df60a90882 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 = { [K in keyof T]: 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..dc1b2e2fc7 100644 --- a/packages/language-core/lib/codegen/localTypes.ts +++ b/packages/language-core/lib/codegen/localTypes.ts @@ -17,7 +17,7 @@ type __VLS_WithDefaultsLocal = { ); const PrettifyLocal = defineHelper( `__VLS_PrettifyLocal`, - () => `type __VLS_PrettifyLocal = { [K in keyof T as K]: T[K]; } & {}${endOfLine}`, + () => `type __VLS_PrettifyLocal = { [K in keyof T]: T[K]; } & {}${endOfLine}`, ); const WithSlots = defineHelper( `__VLS_WithSlots`, From 98364996dfaccfd1f863a08d0acf441812edc666 Mon Sep 17 00:00:00 2001 From: so1ve Date: Fri, 26 Sep 2025 11:19:39 +0800 Subject: [PATCH 3/4] fix --- packages/language-core/lib/codegen/globalTypes.ts | 2 +- packages/language-core/lib/codegen/localTypes.ts | 3 ++- packages/tsc/tests/__snapshots__/dts.spec.ts.snap | 12 ++++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts index df60a90882..7dd3b48c6b 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]: 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 dc1b2e2fc7..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]: 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/tsc/tests/__snapshots__/dts.spec.ts.snap b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap index 3061be7155..9e66bd021a 100644 --- a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap +++ b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap @@ -79,9 +79,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]; -} & {}; +}) & {}; " `; @@ -110,9 +112,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]; -} & {}; +}) & {}; " `; From 349856a107cdc70a0a10a50e2653ea007f1ef3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:50:03 +0800 Subject: [PATCH 4/4] fix(language-core): strip `expose` from internal component options (#5763) Co-authored-by: serkodev --- .../lib/codegen/script/scriptSetup.ts | 259 +++++++----------- .../lib/codegen/script/template.ts | 17 +- .../language-core/lib/codegen/utils/index.ts | 24 ++ .../language-core/lib/parsers/scriptRanges.ts | 6 + .../tsc/passedFixtures/vue3/#5069/main.vue | 42 +++ 5 files changed, 190 insertions(+), 158 deletions(-) create mode 100644 test-workspace/tsc/passedFixtures/vue3/#5069/main.vue 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/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 @@ + + +