8000 fix: support type narrowing for components (#3350) · vuejs/language-tools@cefbaff · GitHub
[go: up one dir, main page]

Skip to content

Commit cefbaff

Browse files
so1vejohnsoncodehk
andauthored
fix: support type narrowing for components (#3350)
Co-authored-by: Johnson Chu <johnsoncodehk@gmail.com>
1 parent 896411f commit cefbaff

File tree

3 files changed

+80
-53
lines changed

3 files changed

+80
-53
lines changed

packages/vue-language-core/src/generators/script.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -827,10 +827,10 @@ declare function defineProp<T>(value?: T | (() => T), required?: boolean, rest?:
827827

828828
/* Components */
829829
codes.push('/* Components */\n');
830-
codes.push(`let __VLS_otherComponents!: NonNullable<typeof __VLS_internalComponent extends { components: infer C } ? C : {}> & typeof __VLS_componentsOption & typeof __VLS_ctx;\n`);
830+
codes.push(`let __VLS_otherComponents!: NonNullable<typeof __VLS_internalComponent extends { components: infer C } ? C : {}> & typeof __VLS_componentsOption;\n`);
831831
codes.push(`let __VLS_own!: __VLS_SelfComponent<typeof __VLS_name, typeof __VLS_internalComponent & typeof __VLS_publicComponent & (new () => { ${getSlotsPropertyName(vueCompilerOptions.target)}: typeof __VLS_slots })>;\n`);
832832
codes.push(`let __VLS_localComponents!: typeof __VLS_otherComponents & Omit<typeof __VLS_own, keyof typeof __VLS_otherComponents>;\n`);
833-
codes.push(`let __VLS_components!: typeof __VLS_localComponents & __VLS_GlobalComponents;\n`); // for html completion, TS references...
833+
codes.push(`let __VLS_components!: typeof __VLS_localComponents & __VLS_GlobalComponents & typeof __VLS_ctx;\n`); // for html completion, TS references...
834834

835835
/* Style Scoped */
836836
codes.push('/* Style Scoped */\n');

packages/vue-language-core/src/generators/template.ts

Lines changed: 64 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export function generate(
9191
let expectedErrorStart: undefined | number;
9292
let expectedErrorNode: CompilerDOM.CommentNode | undefined;
9393

94-
const componentVars = generateComponentVars();
94+
generatePreResolveComponents();
9595

9696
if (sfc.templateAst) {
9797
visitNode(sfc.templateAst, undefined, undefined, undefined);
@@ -176,11 +176,22 @@ export function generate(
176176
codes.push('}\n');
177177
}
178178

179-
function generateComponentVars() {
179+
function toCanonicalComponentName(tagText: string) {
180+
return validTsVar.test(tagText) ? tagText : capitalize(camelize(tagText.replace(/:/g, '-')));
181+
}
182+
183+
function getPossibleOriginalComponentName(tagText: string) {
184+
return [...new Set([
185+
// order is important: https://github.com/vuejs/language-tools/issues/2010
186+
capitalize(camelize(tagText)),
187+
camelize(tagText),
188+
tagText,
189+
])];
190+
}
180191

181-
const data: Record<string, string> = {};
192+
function generatePreResolveComponents() {
182193

183-
codes.push(`let __VLS_templateComponents!: {}\n`);
194+
codes.push(`let __VLS_resolvedLocalAndGlobalComponents!: {}\n`);
184195

185196
for (const tagName in tagNames) {
186197

@@ -191,18 +202,14 @@ export function generate(
191202
if (isNamespacedTag)
192203
continue;
193204

194-
const validName = validTsVar.test(tagName) ? tagName : capitalize(camelize(tagName.replace(/:/g, '-')));
195-
196205
codes.push(
197-
`& __VLS_WithComponent<'${validName}', typeof __VLS_localComponents, `,
206+
`& __VLS_WithComponent<'${toCanonicalComponentName(tagName)}', typeof __VLS_localComponents, `,
198207
// order is important: https://github.com/vuejs/language-tools/issues/2010
199208
`"${capitalize(camelize(tagName))}", `,
200209
`"${camelize(tagName)}", `,
201210
`"${tagName}"`,
202211
'>\n',
203212
);
204-
205-
data[tagName] = validName;
206213
}
207214

208215
codes.push(`;\n`);
@@ -211,12 +218,7 @@ export function generate(
211218

212219
const tagOffsets = tagNames[tagName];
213220
const tagRanges: [number, number][] = tagOffsets.map(offset => [offset, offset + tagName.length]);
214-
const names = new Set(nativeTags.has(tagName) ? [tagName] : [
215-
// order is important: https://github.com/vuejs/language-tools/issues/2010
216-
capitalize(camelize(tagName)),
217-
camelize(tagName),
218-
tagName,
219-
]);
221+
const names = nativeTags.has(tagName) ? [tagName] : getPossibleOriginalComponentName(tagName);
220222

221223
for (const name of names) {
222224
for (const tagRange of tagRanges) {
@@ -240,32 +242,34 @@ export function generate(
240242
}
241243
codes.push('\n');
242244

243-
const validName = data[tagName];
245+
if (nativeTags.has(tagName))
246+
continue;
244247

245-
if (validName) {
246-
codes.push(
247-
'// @ts-ignore\n', // #2304
248-
'[',
249-
);
250-
for (const tagRange of tagRanges) {
251-
codes.push([
252-
validName,
253-
'template',
254-
tagRange,
255-
{
256-
completion: {
257-
additional: true,
258-
autoImportOnly: true,
259-
},
248+
const isNamespacedTag = tagName.indexOf('.') >= 0;
249+
if (isNamespacedTag)
250+
continue;
251+
252+
codes.push(
253+
'// @ts-ignore\n', // #2304
254+
'[',
255+
);
256+
const validName = toCanonicalComponentName(tagName);
257+
for (const tagRange of tagRanges) {
258+
codes.push([
259+
validName,
260+
'template',
261+
tagRange,
262+
{
263+
completion: {
264+
additional: true,
265+
autoImportOnly: true,
260266
},
261-
]);
262-
codes.push(',');
263-
}
264-
codes.push(`];\n`);
267+
},
268+
]);
269+
codes.push(',');
265270
}
271+
codes.push(`];\n`);
266272
}
267-
268-
return data;
269273
}
270274

271275
function collectTagOffsets() {
@@ -669,11 +673,12 @@ export function generate(
669673
';\n',
670674
);
671675
}
672-
else if (componentVars[tag]) {
673-
codes.push(`const ${var_originalComponent} = __VLS_templateComponents['${componentVars[tag]}'];\n`);
674-
}
675676
else {
676-
codes.push(`const ${var_originalComponent} = {} as any;\n`);
677+
codes.push(`let ${var_originalComponent}!: `);
678+
for (const componentName of getPossibleOriginalComponentName(tag)) {
679+
codes.push(`'${componentName}' extends keyof typeof __VLS_ctx ? typeof __VLS_ctx${validTsVar.test(componentName) ? `.${componentName}` : `['${componentName}']`} : `);
680+
}
681+
codes.push(`typeof __VLS_resolvedLocalAndGlobalComponents['${toCanonicalComponentName(tag)}'];\n`);
677682
}
678683

679684
codes.push(
@@ -693,22 +698,30 @@ export function generate(
693698
codes.push(');\n');
694699

695700
for (const offset of tagOffsets) {
696-
if (isNamespacedTag) {
701+
if (isNamespacedTag || dynamicTagExp) {
697702
continue;
698703
}
699-
else if (dynamicTagExp) {
700-
continue;
704+
else if (isIntrinsicElement) {
705+
codes.push(`({} as __VLS_IntrinsicElements).`);
706+
codes.push(
707+
[
708+
tag,
709+
'template',
710+
[offset, offset + tag.length],
711+
{
712+
...capabilitiesPresets.tagHover,
713+
...capabilitiesPresets.diagnosticOnly,
714+
},
715+
],
716+
';\n',
717+
);
701718
}
702719
else {
703-
if (isIntrinsicElement) {
704-
codes.push(`({} as __VLS_IntrinsicElements).`);
705-
}
706-
else {
707-
codes.push(`__VLS_templateComponents.`);
708-
}
720+
const key = toCanonicalComponentName(tag);
721+
codes.push(`({} as { ${key}: typeof ${var_originalComponent} }).`);
709722
codes.push(
710723
[
711-
componentVars[tag] ?? tag,
724+
key,
712725
'template',
713726
[offset, offset + tag.length],
714727
{
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script setup lang="ts">
2+
import { defineAsyncComponent } from 'vue';
3+
4+
const ReloadPrompt =
5+
typeof window !== 'undefined'
6+
? defineAsyncComponent(() => Promise.resolve({}))
7+
: undefined;
8+
</script>
9+
10+
<template>
11+
<template v-if="ReloadPrompt">
12+
<ReloadPrompt />
13+
</template>
14+
</template>

0 commit comments

Comments
 (0)
0