Skip to content

Commit

Permalink
feat: detect missing component import
Browse files Browse the repository at this point in the history
close #1203
  • Loading branch information
johnsoncodehk committed Jun 5, 2022
1 parent ea304fe commit cc57bff
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 50 deletions.
49 changes: 21 additions & 28 deletions packages/vue-code-gen/src/generators/template.ts
@@ -1,6 +1,6 @@
import * as SourceMaps from '@volar/source-map';
import { CodeGen } from '@volar/code-gen';
import { camelize, hyphenate, isHTMLTag, isSVGTag } from '@vue/shared';
import { camelize, hyphenate, capitalize, isHTMLTag, isSVGTag } from '@vue/shared';
import * as CompilerDOM from '@vue/compiler-dom';
import * as CompilerCore from '@vue/compiler-core';
import { EmbeddedFileMappingData } from '../types';
Expand Down Expand Up @@ -47,7 +47,9 @@ export const transformContext: CompilerDOM.TransformContext = {
function _isHTMLTag(tag: string) {
return isHTMLTag(tag)
// fix https://github.com/johnsoncodehk/volar/issues/1340
|| tag === 'hgroup';
|| tag === 'hgroup'
|| tag === 'slot'
|| tag === 'component';
}

export function isIntrinsicElement(runtimeMode: 'runtime-dom' | 'runtime-uni-app' = 'runtime-dom', tag: string) {
Expand Down Expand Up @@ -94,10 +96,8 @@ export function generate(
}>,
}> = {};
const tagResolves: Record<string, {
rawComponent: string,
slotsComponent: string,
component: string,
emit: string,
slots: string,
offsets: number[],
}> = {};
const localVars: Record<string, number> = {};
Expand All @@ -119,10 +119,8 @@ export function generate(
const isNamespacedTag = tagName.indexOf('.') >= 0;

const var_correctTagName = `__VLS_${elementIndex++}`;
const var_rawComponent = `__VLS_${elementIndex++}`;
const var_slotsComponent = `__VLS_${elementIndex++}`;
const var_rawComponent = capitalize(camelize(tagName));
const var_emit = `__VLS_${elementIndex++}`;
const var_slots = `__VLS_${elementIndex++}`;

if (isNamespacedTag) {
for (let i = 0; i < tagRanges.length; i++) {
Expand All @@ -146,16 +144,14 @@ export function generate(
}
}
else {
tsCodeGen.addText(`declare const ${var_correctTagName}: __VLS_types.GetComponentName<typeof __VLS_rawComponents, '${tagName}'>;\n`);
tsCodeGen.addText(`declare const ${var_rawComponent}: __VLS_types.GetProperty<typeof __VLS_rawComponents, typeof ${var_correctTagName}, any>;\n`);
tsCodeGen.addText(`declare const ${var_correctTagName}: __VLS_types.GetComponentName<typeof __VLS_components, '${tagName}'>;\n`);
tsCodeGen.addText(`declare const ${var_rawComponent}: __VLS_types.GetProperty<typeof __VLS_components, typeof ${var_correctTagName}, unknown>;\n`);
}
tsCodeGen.addText(`declare const ${var_slotsComponent}: __VLS_types.WithSlots<typeof ${var_rawComponent}>;\n`);
tsCodeGen.addText(`declare const ${var_emit}: __VLS_types.ExtractEmit2<typeof ${var_rawComponent}>;\n`);
tsCodeGen.addText(`declare const ${var_slots}: __VLS_types.DefaultSlots<typeof ${var_rawComponent}>;\n`);

const name1 = tagName; // hello-world
const name2 = camelize(tagName); // helloWorld
const name3 = name2[0].toUpperCase() + name2.slice(1); // HelloWorld
const name2 = isIntrinsicElement(experimentalRuntimeMode, tagName) ? tagName : camelize(tagName); // helloWorld
const name3 = isIntrinsicElement(experimentalRuntimeMode, tagName) ? tagName : capitalize(name2); // HelloWorld
const componentNames = new Set([name1, name2, name3]);

if (!isNamespacedTag) {
Expand Down Expand Up @@ -196,7 +192,7 @@ export function generate(
if (i > 0) {
tsCodeGen.addText(', ');
}
tsCodeGen.addText(`typeof __VLS_rawComponents`);
tsCodeGen.addText(`typeof __VLS_components`);
writePropertyAccess2(
names[i],
[tagRange],
Expand Down Expand Up @@ -225,14 +221,12 @@ export function generate(
tsCodeGen.addText('/* Completion: Props */\n');
for (const name of componentNames) {
tsCodeGen.addText('// @ts-ignore\n');
tsCodeGen.addText(`(<${var_rawComponent} ${searchTexts.getPropsCompletion(name)}/>);\n`);
tsCodeGen.addText(`(<${isIntrinsicElement(experimentalRuntimeMode, tagName) ? tagName : var_rawComponent} ${searchTexts.getPropsCompletion(name)}/>);\n`);
}

tagResolves[tagName] = {
rawComponent: var_rawComponent,
slotsComponent: var_slotsComponent,
component: var_rawComponent,
emit: var_emit,
slots: var_slots,
offsets: tag.offsets.map(offset => htmlToTemplate(offset, offset)?.start).filter(notEmpty),
};
}
Expand Down Expand Up @@ -565,7 +559,7 @@ export function generate(
tsCodeGen.addText(`{\n`);
{

const tagText = isIntrinsicElement(experimentalRuntimeMode, node.tag) ? node.tag : tagResolves[node.tag].rawComponent;
const tagText = isIntrinsicElement(experimentalRuntimeMode, node.tag) ? node.tag : tagResolves[node.tag].component;
const fullTagStart = tsCodeGen.getText().length;

tsCodeGen.addText(`<`);
Expand Down Expand Up @@ -667,18 +661,18 @@ export function generate(

const varInstanceProps = `__VLS_${elementIndex++}`;

tsCodeGen.addText(`type ${varInstanceProps} = typeof ${varComponentInstance} extends { $props: infer Props } ? Props & Record<string, unknown> : typeof ${tagResolves[node.tag].rawComponent} & Record<string, unknown>;\n`);
tsCodeGen.addText(`type ${varInstanceProps} = typeof ${varComponentInstance} extends { $props: infer Props } ? Props & Record<string, unknown> : typeof ${tagResolves[node.tag].component} & Record<string, unknown>;\n`);
tsCodeGen.addText(`const __VLS_${elementIndex++}: {\n`);
tsCodeGen.addText(`'${prop.arg.loc.source}': __VLS_types.FillingEventArg<\n`);
{
tsCodeGen.addText(`__VLS_types.FirstFunction<\n`);
{

const key_2 = camelize('on-' + prop.arg.loc.source); // onClickOutside
const key_3 = 'on' + prop.arg.loc.source[0].toUpperCase() + prop.arg.loc.source.substring(1); // onClick-outside
const key_3 = 'on' + capitalize(prop.arg.loc.source); // onClick-outside

{
tsCodeGen.addText(`__VLS_types.EmitEvent<typeof ${tagResolves[node.tag].rawComponent}, '${prop.arg.loc.source}'>,\n`);
tsCodeGen.addText(`__VLS_types.EmitEvent<typeof ${tagResolves[node.tag].component}, '${prop.arg.loc.source}'>,\n`);
}

{
Expand Down Expand Up @@ -714,7 +708,7 @@ export function generate(
vueTag: 'template',
capabilities: capabilitiesSet.attrReference,
normalizeNewName(newName) {
return 'on' + newName[0].toUpperCase() + newName.substring(1);
return 'on' + capitalize(newName);
},
applyNewName(oldName, newName) {
const hName = hyphenate(newName);
Expand Down Expand Up @@ -869,7 +863,7 @@ export function generate(
if (writedInstance)
return;

tsCodeGen.addText(`const ${varComponentInstance} = new ${tagResolves[node.tag].rawComponent}({ `);
tsCodeGen.addText(`const ${varComponentInstance} = new ${tagResolves[node.tag].component}({ `);
writeProps(node, false, 'slots');
tsCodeGen.addText(`});\n`);

Expand Down Expand Up @@ -1324,11 +1318,10 @@ export function generate(
const varComponentInstance = `__VLS_${elementIndex++}`;
const varSlots = `__VLS_${elementIndex++}`;

tsCodeGen.addText(`const ${varComponentInstance} = new ${tagResolves[parentEl.tag].slotsComponent}({ `);
tsCodeGen.addText(`const ${varComponentInstance} = new ${tagResolves[parentEl.tag].component}({ `);
writeProps(parentEl, false, 'slots');
tsCodeGen.addText(`});\n`);

tsCodeGen.addText(`declare const ${varSlots}: typeof ${tagResolves[parentEl.tag].slots} & __VLS_types.ScriptSlots<typeof ${varComponentInstance}>;\n`);
tsCodeGen.addText(`declare const ${varSlots}: __VLS_types.ExtractComponentSlots<typeof ${varComponentInstance}>;\n`);

if (prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) {
tsCodeGen.addText(`const `);
Expand Down
5 changes: 3 additions & 2 deletions packages/vue-test-workspace/typeChecks/events.vue
Expand Up @@ -26,7 +26,8 @@
<C10 @foo-bar="exactType($event, {} as number)"></C10>

<!-- invalid component type don't fallback to native event type -->
<C9 @click="exactType($event, {} as any)" />
<!-- TODO -->
<!-- <C9 @click="exactType($event, {} as any)" /> -->
</template>

<script lang="ts" setup>
Expand All @@ -47,7 +48,7 @@ declare const C4: new <T>(props: { value: T; }) => {
$props: typeof props;
$emit: { (event: 'fooBar', e: T): void; };
};
declare const C9: new () => {};
// declare const C9: new () => {};
declare const C10: new () => {
$props: { 'onFoo-bar'?: (num: number) => void; };
};
Expand Down
9 changes: 4 additions & 5 deletions packages/vue-typescript/src/use/useSfcTemplateScript.ts
Expand Up @@ -141,12 +141,11 @@ export function useSfcTemplateScript(

/* Components */
codeGen.addText('/* Components */\n');
codeGen.addText('declare var __VLS_otherComponents: NonNullable<typeof __VLS_component extends { components: infer C } ? C : {}> & __VLS_types.GlobalComponents & typeof __VLS_vmUnwrap.components & __VLS_types.PickComponents<typeof __VLS_ctx>;\n');
codeGen.addText(`declare var __VLS_ownComponent: __VLS_types.SelfComponent<typeof __VLS_name, typeof __VLS_component & (new () => { ${getSlotsPropertyName(compilerOptions.experimentalCompatMode ?? 3)}: typeof __VLS_slots })>;\n`);
codeGen.addText('declare var __VLS_allComponents: typeof __VLS_otherComponents & Omit<typeof __VLS_ownComponent, keyof typeof __VLS_otherComponents>;\n');
codeGen.addText('declare var __VLS_rawComponents: __VLS_types.ConvertInvalidComponents<typeof __VLS_allComponents> & JSX.IntrinsicElements;\n'); // sort by priority
codeGen.addText('declare var __VLS_otherComponents: NonNullable<typeof __VLS_component extends { components: infer C } ? C : {}> & __VLS_types.GlobalComponents & typeof __VLS_vmUnwrap.components & typeof __VLS_ctx;\n');
codeGen.addText(`declare var __VLS_selfComponent: __VLS_types.SelfComponent<typeof __VLS_name, typeof __VLS_component & (new () => { ${getSlotsPropertyName(compilerOptions.experimentalCompatMode ?? 3)}: typeof __VLS_slots })>;\n`);
codeGen.addText('declare var __VLS_components: typeof __VLS_otherComponents & Omit<typeof __VLS_selfComponent, keyof typeof __VLS_otherComponents>;\n');

codeGen.addText(`__VLS_allComponents.${SearchTexts.Components};\n`);
codeGen.addText(`__VLS_components.${SearchTexts.Components};\n`);
codeGen.addText(`({} as __VLS_types.GlobalAttrs).${SearchTexts.GlobalAttrs};\n`);

/* Style Scoped */
Expand Down
21 changes: 6 additions & 15 deletions packages/vue-typescript/src/utils/localTypes.ts
Expand Up @@ -25,11 +25,6 @@ import type {
} from '${libName}';
type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
type IsFunctionalComponent<T> = T extends (...args: any) => JSX.Element ? true : false;
type IsConstructorComponent<T> = T extends new (...args: any) => JSX.ElementClass ? true : false;
type IsComponent_Loose<T> = IsConstructorComponent<T> extends false ? IsFunctionalComponent<T> extends false ? false : true : true; // allow any type
type IsComponent_Strict<T> = IsConstructorComponent<T> extends true ? true : IsFunctionalComponent<T> extends true ? true : false; // don't allow any type
type ComponentKeys<T> = keyof { [K in keyof T as IsComponent_Loose<T[K]> extends true ? K : never]: any };
export type PickNotAny<A, B> = IsAny<A> extends true ? B : A;
type AnyArray<T = any> = T[] | readonly T[];
type ForableSource<T> = [
Expand Down Expand Up @@ -57,16 +52,14 @@ export declare function directiveFunction<T>(dir: T):
: T extends FunctionDirective<infer E, infer V> ? undefined extends V ? (value?: V) => void : (value: V) => void
: T;
export type HasScriptSlotsType<T> = T extends new (...args: any) => { ${slots}?: infer _ } ? true : false;
export type DefaultSlots<C> = HasScriptSlotsType<C> extends true ? {} : Record<string, any>;
export type WithSlots<T> = T extends new (...args: any) => { ${slots}?: infer S } ? T : new (...args: any) => { ${slots}: {} };
export type ScriptSlots<T> = T extends { ${slots}?: infer S }
? { [K in keyof S]-?: S[K] extends ((obj: infer O) => any) | undefined ? O : S[K] }
: {};
export type ExtractComponentSlots<T> =
IsAny<T> extends true ? Record<string, any>
: T extends { $slots?: infer S } ? { [K in keyof S]-?: S[K] extends ((obj: infer O) => any) | undefined ? O : S[K] }
: Record<string, any>;
export type GetComponentName<T, K extends string> = K extends keyof T ? IsAny<T[K]> extends false ? K : GetComponentName_CamelCase<T, CamelCase<K>> : GetComponentName_CamelCase<T, CamelCase<K>>;
export type GetComponentName_CamelCase<T, K extends string> = K extends keyof T ? IsAny<T[K]> extends false ? K : GetComponentName_CapitalCase<T, Capitalize<K>> : GetComponentName_CapitalCase<T, Capitalize<K>>;
export type GetComponentName_CapitalCase<T, K> = K extends keyof T ? K : never;
type GetComponentName_CamelCase<T, K extends string> = K extends keyof T ? IsAny<T[K]> extends false ? K : GetComponentName_CapitalCase<T, Capitalize<K>, K> : GetComponentName_CapitalCase<T, Capitalize<K>, K>;
type GetComponentName_CapitalCase<T, K, O> = K extends keyof T ? K : O;
export type FillingEventArg_ParametersLength<E extends (...args: any) => any> = IsAny<Parameters<E>> extends true ? -1 : Parameters<E>['length'];
export type FillingEventArg<E> = E extends (...args: any) => any ? FillingEventArg_ParametersLength<E> extends 0 ? ($event?: undefined) => ReturnType<E> : E : E;
Expand Down Expand Up @@ -118,8 +111,6 @@ export type FirstFunction<F0 = void, F1 = void, F2 = void, F3 = void, F4 = void>
NonNullable<F4> extends (Function | AnyArray<Function>) ? F4 :
unknown;
export type GlobalAttrs = JSX.IntrinsicElements['div'];
export type PickComponents<T> = ComponentKeys<T> extends keyof T ? Pick<T, ComponentKeys<T>> : T;
export type ConvertInvalidComponents<T> = { [K in keyof T]: IsComponent_Strict<T[K]> extends true ? T[K] : any };
export type SelfComponent<N, C> = string extends N ? {} : N extends string ? { [P in N]: C } : {};
`;
}
Expand Down

0 comments on commit cc57bff

Please sign in to comment.