Skip to content

Commit

Permalink
fix(language-core, typescript-plugin): handle self-reference componen…
Browse files Browse the repository at this point in the history
…t correctly (vuejs#5102)
  • Loading branch information
KazariEX authored Dec 31, 2024
1 parent 7f93a97 commit f76c5eb
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 73 deletions.
3 changes: 2 additions & 1 deletion packages/language-core/lib/codegen/globalTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates
type __VLS_IsAny<T> = 0 extends 1 & T ? true : false;
type __VLS_PickNotAny<A, B> = __VLS_IsAny<A> extends true ? B : A;
type __VLS_unknownDirective = (arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown) => void;
type __VLS_WithComponent<N0 extends string, LocalComponents, N1 extends string, N2 extends string, N3 extends string> =
type __VLS_WithComponent<N0 extends string, LocalComponents, Self, N1 extends string, N2 extends string, N3 extends string> =
N1 extends keyof LocalComponents ? N1 extends N0 ? Pick<LocalComponents, N0 extends keyof LocalComponents ? N0 : never> : { [K in N0]: LocalComponents[N1] } :
N2 extends keyof LocalComponents ? N2 extends N0 ? Pick<LocalComponents, N0 extends keyof LocalComponents ? N0 : never> : { [K in N0]: LocalComponents[N2] } :
N3 extends keyof LocalComponents ? N3 extends N0 ? Pick<LocalComponents, N0 extends keyof LocalComponents ? N0 : never> : { [K in N0]: LocalComponents[N3] } :
Self extends object ? { [K in N0]: Self } :
N1 extends keyof __VLS_GlobalComponents ? N1 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N1] } :
N2 extends keyof __VLS_GlobalComponents ? N2 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N2] } :
N3 extends keyof __VLS_GlobalComponents ? N3 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N3] } :
Expand Down
20 changes: 1 addition & 19 deletions packages/language-core/lib/codegen/script/template.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as path from 'path-browserify';
import type { Code } from '../../types';
import { getSlotsPropertyName, hyphenateTag } from '../../utils/shared';
import { hyphenateTag } from '../../utils/shared';
import { TemplateCodegenContext, createTemplateCodegenContext } from '../template/context';
import { generateInterpolation } from '../template/interpolation';
import { generateStyleScopedClassReferences } from '../template/styleScopedClasses';
Expand Down Expand Up @@ -69,23 +68,6 @@ function* generateTemplateComponents(options: ScriptCodegenOptions): Generator<C
types.push(`typeof __VLS_componentsOption`);
}

let nameType: Code | undefined;
if (options.sfc.script && options.scriptRanges?.exportDefault?.nameOption) {
const { nameOption } = options.scriptRanges.exportDefault;
nameType = options.sfc.script.content.slice(nameOption.start, nameOption.end);
}
else if (options.sfc.scriptSetup) {
const baseName = path.basename(options.fileName);
nameType = `'${options.scriptSetupRanges?.defineOptions?.name ?? baseName.slice(0, baseName.lastIndexOf('.'))}'`;
}
if (nameType) {
types.push(
`{ [K in ${nameType}]: typeof __VLS_self & (new () => { `
+ getSlotsPropertyName(options.vueCompilerOptions.target)
+ `: typeof ${options.scriptSetupRanges?.defineSlots?.name ?? `__VLS_slots`} }) }`
);
}

types.push(`typeof __VLS_ctx`);

yield `type __VLS_LocalComponents =`;
Expand Down
10 changes: 9 additions & 1 deletion packages/language-core/lib/codegen/template/element.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as CompilerDOM from '@vue/compiler-dom';
import { camelize, capitalize } from '@vue/shared';
import type { Code, VueCodeInformation } from '../../types';
import { hyphenateTag } from '../../utils/shared';
import { getSlotsPropertyName, hyphenateTag } from '../../utils/shared';
import { createVBindShorthandInlayHintInfo } from '../inlayHints';
import { collectVars, createTsAst, endOfLine, newLine, normalizeAttributeValue, variableNameRegex, wrapWith } from '../utils';
import { generateCamelized } from '../utils/camelized';
Expand Down Expand Up @@ -151,6 +151,14 @@ export function* generateComponent(
}
else if (!isComponentTag) {
yield `const ${var_originalComponent} = ({} as __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', __VLS_LocalComponents, `;
if (options.selfComponentName && possibleOriginalNames.includes(options.selfComponentName)) {
yield `typeof __VLS_self & (new () => { `
+ getSlotsPropertyName(options.vueCompilerOptions.target)
+ `: typeof ${options.slotsAssignName ?? `__VLS_slots`} }), `;
}
else {
yield `void, `;
}
yield getPossibleOriginalComponentNames(node.tag, false)
.map(name => `'${name}'`)
.join(`, `);
Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/codegen/template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface TemplateCodegenOptions {
slotsAssignName?: string;
propsAssignName?: string;
inheritAttrs: boolean;
selfComponentName?: string;
}

export function* generateTemplate(options: TemplateCodegenOptions): Generator<Code, TemplateCodegenContext> {
Expand Down
16 changes: 16 additions & 0 deletions packages/language-core/lib/plugins/vue-tsx.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Mapping } from '@volar/language-core';
import { camelize, capitalize } from '@vue/shared';
import { computed, unstable } from 'alien-signals';
import * as path from 'path-browserify';
import { generateScript } from '../codegen/script';
import { generateTemplate } from '../codegen/template';
import { parseScriptRanges } from '../parsers/scriptRanges';
Expand Down Expand Up @@ -153,6 +155,19 @@ function createTsx(
const value = scriptSetupRanges.get()?.defineOptions?.inheritAttrs ?? scriptRanges.get()?.exportDefault?.inheritAttrsOption;
return value !== 'false';
});
const selfComponentName = computed(() => {
const { exportDefault } = scriptRanges.get() ?? {};
if (_sfc.script && exportDefault?.nameOption) {
const { nameOption } = exportDefault;
return _sfc.script.content.slice(nameOption.start + 1, nameOption.end - 1);
}
const { defineOptions } = scriptSetupRanges.get() ?? {};
if (_sfc.scriptSetup && defineOptions?.name) {
return defineOptions.name;
}
const baseName = path.basename(fileName);
return capitalize(camelize(baseName.slice(0, baseName.lastIndexOf('.'))));
});
const generatedTemplate = computed(() => {

if (vueCompilerOptions.get().skipTemplateCodegen || !_sfc.template) {
Expand All @@ -174,6 +189,7 @@ function createTsx(
slotsAssignName: slotsAssignName.get(),
propsAssignName: propsAssignName.get(),
inheritAttrs: inheritAttrs.get(),
selfComponentName: selfComponentName.get()
});

let current = codegen.next();
Expand Down
2 changes: 1 addition & 1 deletion packages/language-server/tests/completions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ describe('Completions', async () => {
"component",
"slot",
"template",
"fixture",
"BaseTransition",
"Fixture",
]
`);
});
Expand Down
102 changes: 51 additions & 51 deletions packages/typescript-plugin/lib/requests/componentInfos.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vue from '@vue/language-core';
import { camelize, capitalize } from '@vue/shared';
import * as path from 'node:path';
import type * as ts from 'typescript';
import type { RequestContext } from './types';

Expand All @@ -21,28 +22,11 @@ export function getComponentProps(
return [];
}

const name = tag.split('.');

let componentSymbol = components.type.getProperty(name[0])
?? components.type.getProperty(camelize(name[0]))
?? components.type.getProperty(capitalize(camelize(name[0])));

if (!componentSymbol) {
const componentType = getComponentType(ts, languageService, vueCode, components, fileName, tag);
if (!componentType) {
return [];
}

let componentType = checker.getTypeOfSymbolAtLocation(componentSymbol, components.node);

for (let i = 1; i < name.length; i++) {
componentSymbol = componentType.getProperty(name[i]);
if (componentSymbol) {
componentType = checker.getTypeOfSymbolAtLocation(componentSymbol, components.node);
}
else {
return [];
}
}

const result = new Map<string, {
name: string;
required?: true;
Expand Down Expand Up @@ -104,31 +88,11 @@ export function getComponentEvents(
return [];
}

const name = tag.split('.');

let componentSymbol = components.type.getProperty(name[0]);

if (!componentSymbol) {
componentSymbol = components.type.getProperty(camelize(name[0]))
?? components.type.getProperty(capitalize(camelize(name[0])));
}

if (!componentSymbol) {
const componentType = getComponentType(ts, languageService, vueCode, components, fileName, tag);
if (!componentType) {
return [];
}

let componentType = checker.getTypeOfSymbolAtLocation(componentSymbol, components.node);

for (let i = 1; i < name.length; i++) {
componentSymbol = componentType.getProperty(name[i]);
if (componentSymbol) {
componentType = checker.getTypeOfSymbolAtLocation(componentSymbol, components.node);
}
else {
return [];
}
}

const result = new Set<string>();

// for (const sig of componentType.getCallSignatures()) {
Expand Down Expand Up @@ -185,26 +149,23 @@ export function getComponentNames(
return;
}
const vueCode = volarFile.generated.root;

return getVariableType(ts, languageService, vueCode, '__VLS_components')
?.type
?.getProperties()
.map(c => c.name)
.filter(entry => !entry.includes('$') && !entry.startsWith('_'))
?? [];
return _getComponentNames(ts, languageService, vueCode);
}

export function _getComponentNames(
ts: typeof import('typescript'),
tsLs: ts.LanguageService,
vueCode: vue.VueVirtualCode
) {
return getVariableType(ts, tsLs, vueCode, '__VLS_components')
const names = getVariableType(ts, tsLs, vueCode, '__VLS_components')
?.type
?.getProperties()
.map(c => c.name)
.filter(entry => !entry.includes('$') && !entry.startsWith('_'))
?? [];

names.push(getSelfComponentName(vueCode.fileName));
return names;
}

export function getElementAttrs(
Expand Down Expand Up @@ -242,6 +203,42 @@ export function getElementAttrs(
return [];
}

function getComponentType(
ts: typeof import('typescript'),
languageService: ts.LanguageService,
vueCode: vue.VueVirtualCode,
components: NonNullable<ReturnType<typeof getVariableType>>,
fileName: string,
tag: string
) {
const program = languageService.getProgram()!;
const checker = program.getTypeChecker();
const name = tag.split('.');

let componentSymbol = components.type.getProperty(name[0])
?? components.type.getProperty(camelize(name[0]))
?? components.type.getProperty(capitalize(camelize(name[0])));
let componentType: ts.Type | undefined;

if (!componentSymbol) {
const name = getSelfComponentName(fileName);
if (name === capitalize(camelize(tag))) {
componentType = getVariableType(ts, languageService, vueCode, '__VLS_self')?.type;
}
}
else {
componentType = checker.getTypeOfSymbolAtLocation(componentSymbol, components.node);
for (let i = 1; i < name.length; i++) {
componentSymbol = componentType.getProperty(name[i]);
if (componentSymbol) {
componentType = checker.getTypeOfSymbolAtLocation(componentSymbol, components.node);
}
}
}

return componentType;
}

function getVariableType(
ts: typeof import('typescript'),
languageService: ts.LanguageService,
Expand All @@ -266,6 +263,11 @@ function getVariableType(
}
}

function getSelfComponentName(fileName: string) {
const baseName = path.basename(fileName);
return capitalize(camelize(baseName.slice(0, baseName.lastIndexOf('.'))));
}

function searchVariableDeclarationNode(
ts: typeof import('typescript'),
sourceFile: ts.SourceFile,
Expand Down Expand Up @@ -298,7 +300,6 @@ function generateCommentMarkdown(parts: ts.SymbolDisplayPart[], jsDocTags: ts.JS
return result;
}


function _symbolDisplayPartsToMarkdown(parts: ts.SymbolDisplayPart[]) {
return parts.map(part => {
switch (part.kind) {
Expand All @@ -312,7 +313,6 @@ function _symbolDisplayPartsToMarkdown(parts: ts.SymbolDisplayPart[]) {
}).join('');
}


function _jsDocTagInfoToMarkdown(jsDocTags: ts.JSDocTagInfo[]) {
return jsDocTags.map(tag => {
const tagName = `*@${tag.name}*`;
Expand Down
1 change: 1 addition & 0 deletions test-workspace/tsc/passedFixtures/vue3/#5097/child.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Main } from './child.vue';
5 changes: 5 additions & 0 deletions test-workspace/tsc/passedFixtures/vue3/#5097/child.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script setup lang="ts">
defineProps<{
bar: string;
}>();
</script>
11 changes: 11 additions & 0 deletions test-workspace/tsc/passedFixtures/vue3/#5097/main.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup lang="ts">
import { Main } from './child';
defineProps<{
foo: string;
}>();
</script>

<template>
<Main bar=""></Main>
</template>

0 comments on commit f76c5eb

Please sign in to comment.