diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index 7b84706108..e516c44d89 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -24,25 +24,26 @@ export function* generateComponent( componentCtxVar: string | undefined, ): Generator { const startTagOffset = node.loc.start.offset + options.template.content.substring(node.loc.start.offset).indexOf(node.tag); + const endTagOffset = !node.isSelfClosing && options.template.lang === 'html' ? node.loc.start.offset + node.loc.source.lastIndexOf(node.tag) : undefined; + const tagOffsets = endTagOffset !== undefined + ? [startTagOffset, endTagOffset] + : [startTagOffset]; const propsFailedExps: CompilerDOM.SimpleExpressionNode[] = []; - const var_originalComponent = ctx.getInternalVariable(); + const possibleOriginalNames = getPossibleOriginalComponentNames(node.tag, true); + const matchImportName = possibleOriginalNames.find(name => options.scriptSetupImportComponentNames.has(name)); + const var_originalComponent = matchImportName ?? ctx.getInternalVariable(); const var_functionalComponent = ctx.getInternalVariable(); const var_componentInstance = ctx.getInternalVariable(); const var_componentEvents = ctx.getInternalVariable(); + const var_defineComponentCtx = ctx.getInternalVariable(); const isComponentTag = node.tag.toLowerCase() === 'component'; - let endTagOffset = !node.isSelfClosing && options.template.lang === 'html' ? node.loc.start.offset + node.loc.source.lastIndexOf(node.tag) : undefined; - let tag = node.tag; - let tagOffsets = endTagOffset !== undefined - ? [startTagOffset, endTagOffset] - : [startTagOffset]; let props = node.props; let dynamicTagInfo: { exp: string; offset: number; astHolder: any; } | undefined; - let defineComponentCtxVar: string | undefined; let usedComponentEventsVar = false; if (isComponentTag) { @@ -58,16 +59,46 @@ export function* generateComponent( } } } - else if (tag.includes('.')) { + else if (node.tag.includes('.')) { // namespace tag dynamicTagInfo = { - exp: tag, + exp: node.tag, astHolder: node.loc, offset: startTagOffset, }; } - if (dynamicTagInfo) { + if (matchImportName) { + // hover, renaming / find references support + yield `// @ts-ignore${newLine}`; // #2304 + yield `[`; + for (const tagOffset of tagOffsets) { + if (var_originalComponent === node.tag) { + yield [ + var_originalComponent, + 'template', + tagOffset, + ctx.codeFeatures.withoutHighlightAndCompletion, + ]; + } + else { + yield* generateCamelized( + capitalize(node.tag), + tagOffset, + { + ...ctx.codeFeatures.withoutHighlightAndCompletion, + navigation: { + resolveRenameNewName: camelizeComponentName, + resolveRenameEditText: getTagRenameApply(node.tag), + }, + }, + ); + } + yield `,`; + } + yield `]${endOfLine}`; + } + else if (dynamicTagInfo) { yield `const ${var_originalComponent} = `; yield* generateInterpolation( options, @@ -83,8 +114,8 @@ export function* generateComponent( } else if (!isComponentTag) { yield `const ${var_originalComponent} = ({} as `; - for (const componentName of getPossibleOriginalComponentNames(tag, true)) { - yield `'${componentName}' extends keyof typeof __VLS_ctx ? { '${getCanonicalComponentName(tag)}': typeof __VLS_ctx`; + for (const componentName of possibleOriginalNames) { + yield `'${componentName}' extends keyof typeof __VLS_ctx ? { '${getCanonicalComponentName(node.tag)}': typeof __VLS_ctx`; yield* generatePropertyAccess(options, ctx, componentName); yield ` }: `; } @@ -92,29 +123,17 @@ export function* generateComponent( yield* generatePropertyAccess( options, ctx, - getCanonicalComponentName(tag), + getCanonicalComponentName(node.tag), startTagOffset, ctx.codeFeatures.verification, ); yield endOfLine; - } - else { - yield `const ${var_originalComponent} = {} as any${endOfLine}`; - } - - yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({`; - yield* generateElementProps(options, ctx, node, props, false); - yield `}))${endOfLine}`; - if ( - !dynamicTagInfo - && !isComponentTag - ) { // hover support for (const offset of tagOffsets) { - yield `({} as { ${getCanonicalComponentName(tag)}: typeof ${var_originalComponent} }).`; + yield `({} as { ${getCanonicalComponentName(node.tag)}: typeof ${var_originalComponent} }).`; yield* generateCanonicalComponentName( - tag, + node.tag, offset, ctx.codeFeatures.withoutHighlightAndCompletionAndNavigation, ); @@ -160,13 +179,20 @@ export function* generateComponent( yield `]${endOfLine}`; } } + else { + yield `const ${var_originalComponent} = {} as any${endOfLine}`; + } + + yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({`; + yield* generateElementProps(options, ctx, node, props, false); + yield `}))${endOfLine}`; if (options.vueCompilerOptions.strictTemplates) { // with strictTemplates, generate once for props type-checking + instance type yield `const ${var_componentInstance} = ${var_functionalComponent}(`; yield* wrapWith( startTagOffset, - startTagOffset + tag.length, + startTagOffset + node.tag.length, ctx.codeFeatures.verification, `{`, ...generateElementProps(options, ctx, node, props, true, propsFailedExps), @@ -183,7 +209,7 @@ export function* generateComponent( yield `({} as (props: __VLS_FunctionalComponentProps & Record) => void)(`; yield* wrapWith( startTagOffset, - startTagOffset + tag.length, + startTagOffset + node.tag.length, ctx.codeFeatures.verification, `{`, ...generateElementProps(options, ctx, node, props, true, propsFailedExps), @@ -192,8 +218,7 @@ export function* generateComponent( yield `)${endOfLine}`; } - defineComponentCtxVar = ctx.getInternalVariable(); - componentCtxVar = defineComponentCtxVar; + componentCtxVar = var_defineComponentCtx; currentComponent = node; for (const failedExp of propsFailedExps) { @@ -212,20 +237,18 @@ export function* generateComponent( yield* generateVScope(options, ctx, node, props); - if (componentCtxVar) { - ctx.usedComponentCtxVars.add(componentCtxVar); - yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEvents, () => usedComponentEventsVar = true); - } + ctx.usedComponentCtxVars.add(componentCtxVar); + yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEvents, () => usedComponentEventsVar = true); const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; - if (slotDir && componentCtxVar) { + if (slotDir) { yield* generateComponentSlot(options, ctx, node, slotDir, currentComponent, componentCtxVar); } else { yield* generateElementChildren(options, ctx, node, currentComponent, componentCtxVar); } - if (defineComponentCtxVar && ctx.usedComponentCtxVars.has(defineComponentCtxVar)) { + if (var_defineComponentCtx && ctx.usedComponentCtxVars.has(var_defineComponentCtx)) { yield `const ${componentCtxVar} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})!${endOfLine}`; } if (usedComponentEventsVar) { diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index 413122413b..0e655f6c28 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -15,6 +15,7 @@ export interface TemplateCodegenOptions { template: NonNullable; shouldGenerateScopedClasses?: boolean; stylesScopedClasses: Set; + scriptSetupImportComponentNames: Set; hasDefineSlots?: boolean; slotsAssignName?: string; propsAssignName?: string; diff --git a/packages/language-core/lib/parsers/scriptSetupRanges.ts b/packages/language-core/lib/parsers/scriptSetupRanges.ts index 9727e3eba3..fc3696efd8 100644 --- a/packages/language-core/lib/parsers/scriptSetupRanges.ts +++ b/packages/language-core/lib/parsers/scriptSetupRanges.ts @@ -49,6 +49,7 @@ export function parseScriptSetupRanges( const bindings = parseBindingRanges(ts, ast); const text = ast.text; const leadingCommentEndOffset = ts.getLeadingCommentRanges(text, 0)?.reverse()[0].end ?? 0; + const importComponentNames = new Set(); ts.forEachChild(ast, node => { const isTypeExport = (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); @@ -70,6 +71,17 @@ export function parseScriptSetupRanges( } foundNonImportExportNode = true; } + + if ( + ts.isImportDeclaration(node) + && node.importClause?.name + && !node.importClause.isTypeOnly + ) { + const moduleName = getNodeText(ts, node.moduleSpecifier, ast).slice(1, -1); + if (vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext))) { + importComponentNames.add(getNodeText(ts, node.importClause.name, ast)); + } + } }); ts.forEachChild(ast, child => visitNode(child, [ast])); @@ -77,6 +89,7 @@ export function parseScriptSetupRanges( leadingCommentEndOffset, importSectionEndOffset, bindings, + importComponentNames, props, slots, emits, diff --git a/packages/language-core/lib/plugins/vue-tsx.ts b/packages/language-core/lib/plugins/vue-tsx.ts index db9870a87d..cf367a73d9 100644 --- a/packages/language-core/lib/plugins/vue-tsx.ts +++ b/packages/language-core/lib/plugins/vue-tsx.ts @@ -119,6 +119,7 @@ function createTsx( template: _sfc.template, shouldGenerateScopedClasses: shouldGenerateScopedClasses(), stylesScopedClasses: stylesScopedClasses(), + scriptSetupImportComponentNames: scriptSetupImportComponentNames(), hasDefineSlots: hasDefineSlots(), slotsAssignName: slotsAssignName(), propsAssignName: propsAssignName(), @@ -138,6 +139,13 @@ function createTsx( }; }); const hasDefineSlots = computed(() => !!scriptSetupRanges()?.slots.define); + const scriptSetupImportComponentNames = computed>(oldNames => { + const newNames = scriptSetupRanges()?.importComponentNames ?? new Set(); + if (newNames && oldNames && twoSetsEqual(newNames, oldNames)) { + return oldNames; + } + return newNames; + }); const slotsAssignName = computed(() => scriptSetupRanges()?.slots.name); const propsAssignName = computed(() => scriptSetupRanges()?.props.name); const generatedScript = computed(() => { @@ -182,3 +190,15 @@ function createTsx( generatedTemplate, }; } + +function twoSetsEqual(a: Set, b: Set) { + if (a.size !== b.size) { + return false; + } + for (const file of a) { + if (!b.has(file)) { + return false; + } + } + return true; +}