8000 fix: support type narrowing for components by so1ve · Pull Request #3350 · vuejs/language-tools · GitHub
[go: up one dir, main page]

Skip to content

fix: support type narrowing for components #3350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/vue-language-core/src/generators/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,10 +827,10 @@ declare function defineProp<T>(value?: T | (() => T), required?: boolean, rest?:

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

/* Style Scoped */
codes.push('/* Style Scoped */\n');
Expand Down
115 changes: 64 additions & 51 deletions packages/vue-language-core/src/generators/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export function generate(
let expectedErrorStart: undefined | number;
let expectedErrorNode: CompilerDOM.CommentNode | undefined;

const componentVars = generateComponentVars();
generatePreResolveComponents();

if (sfc.templateAst) {
visitNode(sfc.templateAst, undefined, undefined, undefined);
Expand Down Expand Up @@ -176,11 +176,22 @@ export function generate(
codes.push('}\n');
}

function generateComponentVars() {
function toCanonicalComponentName(tagText: string) {
return validTsVar.test(tagText) ? tagText : capitalize(camelize(tagText.replace(/:/g, '-')));
}

function getPossibleOriginalComponentName(tagText: string) {
return [...new Set([
// order is important: https://github.com/vuejs/language-tools/issues/2010
capitalize(camelize(tagText)),
camelize(tagText),
tagText,
])];
}

const data: Record<string, string> = {};
function generatePreResolveComponents() {

codes.push(`let __VLS_templateComponents!: {}\n`);
codes.push(`let __VLS_resolvedLocalAndGlobalComponents!: {}\n`);

for (const tagName in tagNames) {

Expand All @@ -191,18 +202,14 @@ export function generate(
if (isNamespacedTag)
continue;

const validName = validTsVar.test(tagName) ? tagName : capitalize(camelize(tagName.replace(/:/g, '-')));

codes.push(
`& __VLS_WithComponent<'${validName}', typeof __VLS_localComponents, `,
`& __VLS_WithComponent<'${toCanonicalComponentName(tagName)}', typeof __VLS_localComponents, `,
// order is important: https://github.com/vuejs/language-tools/issues/2010
`"${capitalize(camelize(tagName))}", `,
`"${camelize(tagName)}", `,
`"${tagName}"`,
'>\n',
);

data[tagName] = validName;
}

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

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

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

const validName = data[tagName];
if (nativeTags.has(tagName))
continue;

if (validName) {
codes.push(
'// @ts-ignore\n', // #2304
'[',
);
for (const tagRange of tagRanges) {
codes.push([
validName,
'template',
tagRange,
{
completion: {
additional: true,
autoImportOnly: true,
},
const isNamespacedTag = tagName.indexOf('.') >= 0;
if (isNamespacedTag)
continue;

codes.push(
'// @ts-ignore\n', // #2304
'[',
);
const validName = toCanonicalComponentName(tagName);
for (const tagRange of tagRanges) {
codes.push([
validName,
'template',
tagRange,
{
completion: {
additional: true,
autoImportOnly: true,
},
]);
codes.push(',');
}
codes.push(`];\n`);
},
]);
codes.push(',');
}
codes.push(`];\n`);
}

return data;
}

function collectTagOffsets() {
Expand Down Expand Up @@ -669,11 +673,12 @@ export function generate(
';\n',
);
}
else if (componentVars[tag]) {
codes.push(`const ${var_originalComponent} = __VLS_templateComponents['${componentVars[tag]}'];\n`);
}
else {
codes.push(`const ${var_originalComponent} = {} as any;\n`);
codes.push(`let ${var_originalComponent}!: `);
for (const componentName of getPossibleOriginalComponentName(tag)) {
codes.push(`'${componentName}' extends keyof typeof __VLS_ctx ? typeof __VLS_ctx${validTsVar.test(componentName) ? `.${componentName}` : `['${componentName}']`} : `);
}
codes.push(`typeof __VLS_resolvedLocalAndGlobalComponents['${toCanonicalComponentName(tag)}'];\n`);
}

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

for (const offset of tagOffsets) {
if (isNamespacedTag) {
if (isNamespacedTag || dynamicTagExp) {
continue;
}
else if (dynamicTagExp) {
continue;
else if (isIntrinsicElement) {
codes.push(`({} as __VLS_IntrinsicElements).`);
codes.push(
[
tag,
'template',
[offset, offset + tag.length],
{
...capabilitiesPresets.tagHover,
...capabilitiesPresets.diagnosticOnly,
},
],
';\n',
);
}
else {
if (isIntrinsicElement) {
codes.push(`({} as __VLS_IntrinsicElements).`);
}
else {
codes.push(`__VLS_templateComponents.`);
}
const key = toCanonicalComponentName(tag);
codes.push(`({} as { ${key}: typeof ${var_originalComponent} }).`);
codes.push(
[
componentVars[tag] ?? tag,
key,
'template',
[offset, offset + tag.length],
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup lang="ts">
import { defineAsyncComponent } from 'vue';

const ReloadPrompt =
typeof window !== 'undefined'
? defineAsyncComponent(() => Promise.resolve({}))
: undefined;
</script>

<template>
<template v-if="ReloadPrompt">
<ReloadPrompt />
</template>
</template>
0