Skip to content

Commit

Permalink
feat(language-core): auto infer $el type (#4805)
Browse files Browse the repository at this point in the history
  • Loading branch information
KazariEX authored Sep 6, 2024
1 parent 27e5383 commit ba63fab
Show file tree
Hide file tree
Showing 13 changed files with 147 additions and 87 deletions.
8 changes: 4 additions & 4 deletions packages/language-core/lib/codegen/globalTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates
>
>;
type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
type __VLS_PickFunctionalComponentCtx<T, K> = NonNullable<__VLS_PickNotAny<
'__ctx' extends keyof __VLS_PickNotAny<K, {}> ? K extends { __ctx?: infer Ctx } ? Ctx : never : any
, T extends (props: any, ctx: infer Ctx) => any ? Ctx : any
>>;
function __VLS_getVForSourceType(source: number): [number, number, number][];
function __VLS_getVForSourceType(source: string): [string, number, number][];
Expand Down Expand Up @@ -129,10 +133,6 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates
: (_: {}${strictTemplates ? '' : ' & Record<string, unknown>'}, ctx?: any) => { __ctx?: { attrs?: any, expose?: any, slots?: any, emit?: any, props?: {}${strictTemplates ? '' : ' & Record<string, unknown>'} } };
function __VLS_elementAsFunction<T>(tag: T, endTag?: T): (_: T${strictTemplates ? '' : ' & Record<string, unknown>'}) => void;
function __VLS_functionalComponentArgsRest<T extends (...args: any) => any>(t: T): 2 extends Parameters<T>['length'] ? [any] : [];
function __VLS_pickFunctionalComponentCtx<T, K>(comp: T, compInstance: K): NonNullable<__VLS_PickNotAny<
'__ctx' extends keyof __VLS_PickNotAny<K, {}> ? K extends { __ctx?: infer Ctx } ? Ctx : never : any
, T extends (props: any, ctx: infer Ctx) => any ? Ctx : any
>>;
function __VLS_normalizeSlot<S>(s: S): S extends () => infer R ? (props: {}) => R : S;
function __VLS_tryAsConstant<const T>(t: T): T;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/language-core/lib/codegen/script/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export function* generateComponent(
if (options.vueCompilerOptions.target >= 3.5 && scriptSetupRanges.templateRefs.length) {
yield `__typeRefs: {} as __VLS_TemplateResult['refs'],${newLine}`;
}
if (options.vueCompilerOptions.target >= 3.5 && options.templateCodegen?.singleRootElType) {
yield `__typeEl: {} as __VLS_TemplateResult['rootEl'],${newLine}`;
}
yield `})`;
}

Expand Down
6 changes: 4 additions & 2 deletions packages/language-core/lib/codegen/script/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,16 @@ function* generateTemplateBody(
if (!options.scriptSetupRanges?.slots.define) {
yield `const __VLS_slots = {}${endOfLine}`;
}
yield `const $refs = {}${endOfLine}`;
yield `const __VLS_inheritedAttrs = {}${endOfLine}`;
yield `const $refs = {}${endOfLine}`;
yield `const $el = {} as any${endOfLine}`;
}

yield `return {${newLine}`;
yield ` attrs: {} as Partial<typeof __VLS_inheritedAttrs>,${newLine}`;
yield ` slots: ${options.scriptSetupRanges?.slots.name ?? '__VLS_slots'},${newLine}`;
yield ` refs: $refs,${newLine}`;
yield ` attrs: {} as Partial<typeof __VLS_inheritedAttrs>,${newLine}`;
yield ` rootEl: $el,${newLine}`;
yield `}${endOfLine}`;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/language-core/lib/codegen/template/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
}[] = [];
const emptyClassOffsets: number[] = [];
const inlayHints: InlayHintInfo[] = [];
const templateRefs = new Map<string, [string, number]>();
const templateRefs = new Map<string, [varName: string, offset: number]>();

return {
slots,
Expand All @@ -132,6 +132,7 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
hasSlot: false,
inheritedAttrVars: new Set(),
templateRefs,
singleRootElType: undefined as string | undefined,
singleRootNode: undefined as CompilerDOM.ElementNode | undefined,
accessExternalVariable(name: string, offset?: number) {
let arr = accessExternalVariables.get(name);
Expand Down
17 changes: 14 additions & 3 deletions packages/language-core/lib/codegen/template/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,10 @@ export function* generateComponent(
}

const [refName, offset] = yield* generateVScope(options, ctx, node, props);
if (refName) {
const isRootNode = node === ctx.singleRootNode;

if (refName || isRootNode) {
const varName = ctx.getInternalVariable();
ctx.templateRefs.set(refName, [varName, offset!]);
ctx.usedComponentCtxVars.add(var_defineComponentCtx);

yield `var ${varName} = {} as (Parameters<NonNullable<typeof ${var_defineComponentCtx}['expose']>>[0] | null)`;
Expand All @@ -237,6 +238,13 @@ export function* generateComponent(
yield `[]`;
}
yield `${endOfLine}`;

if (refName) {
ctx.templateRefs.set(refName, [varName, offset!]);
}
if (isRootNode) {
ctx.singleRootElType = `NonNullable<typeof ${varName}>['$el']`;
}
}

const usedComponentEventsVar = yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEmit, var_componentEvents);
Expand Down Expand Up @@ -267,7 +275,7 @@ export function* generateComponent(
}

if (ctx.usedComponentCtxVars.has(var_defineComponentCtx)) {
yield `const ${var_defineComponentCtx} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})${endOfLine}`;
yield `var ${var_defineComponentCtx}!: __VLS_PickFunctionalComponentCtx<typeof ${var_originalComponent}, typeof ${var_componentInstance}>${endOfLine}`;
}
}

Expand Down Expand Up @@ -335,6 +343,9 @@ export function* generateElement(
if (refName) {
ctx.templateRefs.set(refName, [`__VLS_nativeElements['${node.tag}']`, offset!]);
}
if (ctx.singleRootNode === node) {
ctx.singleRootElType = `typeof __VLS_nativeElements['${node.tag}']`;
}

const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode;
if (slotDir && componentCtxVar) {
Expand Down
155 changes: 82 additions & 73 deletions packages/language-core/lib/codegen/template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator<Co
if (options.propsAssignName) {
ctx.addLocalVariable(options.propsAssignName);
}
ctx.addLocalVariable('$el');
ctx.addLocalVariable('$refs');

yield* generatePreResolveComponents();
yield* generatePreResolveComponents(options);

if (options.template.ast) {
yield* generateTemplateChild(options, ctx, options.template.ast, undefined, undefined, undefined);
Expand All @@ -45,96 +46,104 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator<Co

if (!options.hasDefineSlots) {
yield `var __VLS_slots!:`;
yield* generateSlotsType();
yield* generateSlotsType(options, ctx);
yield endOfLine;
}

yield* generateInheritedAttrs();

yield* ctx.generateAutoImportCompletion();

yield* generateRefs();
yield* generateInheritedAttrs(ctx);
yield* generateRefs(ctx);
yield* generateRootEl(ctx);

return ctx;
}

function* generateRefs(): Generator<Code> {
yield `const __VLS_refs = {${newLine}`;
for (const [name, [varName, offset]] of ctx.templateRefs) {
yield* generateStringLiteralKey(
name,
offset,
ctx.codeFeatures.navigationAndCompletion
)
yield `: ${varName},${newLine}`;
}
yield `}${endOfLine}`;
yield `var $refs!: typeof __VLS_refs${endOfLine}`;
function* generateSlotsType(options: TemplateCodegenOptions, ctx: TemplateCodegenContext): Generator<Code> {
for (const { expVar, varName } of ctx.dynamicSlots) {
ctx.hasSlot = true;
yield `Partial<Record<NonNullable<typeof ${expVar}>, (_: typeof ${varName}) => any>> &${newLine}`;
}

function* generateSlotsType(): Generator<Code> {
for (const { expVar, varName } of ctx.dynamicSlots) {
ctx.hasSlot = true;
yield `Partial<Record<NonNullable<typeof ${expVar}>, (_: typeof ${varName}) => any>> &${newLine}`;
yield `{${newLine}`;
for (const slot of ctx.slots) {
ctx.hasSlot = true;
if (slot.name && slot.loc !== undefined) {
yield* generateObjectProperty(
options,
ctx,
slot.name,
slot.loc,
ctx.codeFeatures.withoutHighlightAndCompletion,
slot.nodeLoc
);
}
yield `{${newLine}`;
for (const slot of ctx.slots) {
ctx.hasSlot = true;
if (slot.name && slot.loc !== undefined) {
yield* generateObjectProperty(
options,
ctx,
slot.name,
slot.loc,
ctx.codeFeatures.withoutHighlightAndCompletion,
slot.nodeLoc
);
}
else {
yield* wrapWith(
slot.tagRange[0],
slot.tagRange[1],
ctx.codeFeatures.withoutHighlightAndCompletion,
`default`
);
}
yield `?(_: typeof ${slot.varName}): any,${newLine}`;
else {
yield* wrapWith(
slot.tagRange[0],
slot.tagRange[1],
ctx.codeFeatures.withoutHighlightAndCompletion,
`default`
);
}
yield `}`;
yield `?(_: typeof ${slot.varName}): any,${newLine}`;
}
yield `}`;
}

function* generateInheritedAttrs(): Generator<Code> {
yield 'var __VLS_inheritedAttrs!: {}';
for (const varName of ctx.inheritedAttrVars) {
yield ` & typeof ${varName}`;
}
yield endOfLine;
function* generateInheritedAttrs(ctx: TemplateCodegenContext): Generator<Code> {
yield 'var __VLS_inheritedAttrs!: {}';
for (const varName of ctx.inheritedAttrVars) {
yield ` & typeof ${varName}`;
}
yield endOfLine;
}

function* generateRefs(ctx: TemplateCodegenContext): Generator<Code> {
yield `const __VLS_refs = {${newLine}`;
for (const [name, [varName, offset]] of ctx.templateRefs) {
yield* generateStringLiteralKey(
name,
offset,
ctx.codeFeatures.navigationAndCompletion
);
yield `: ${varName},${newLine}`;
}
yield `}${endOfLine}`;
yield `var $refs!: typeof __VLS_refs${endOfLine}`;
}

function* generateRootEl(ctx: TemplateCodegenContext): Generator<Code> {
if (ctx.singleRootElType) {
yield `var $el!: ${ctx.singleRootElType}${endOfLine}`;
}
else {
yield `var $el!: any${endOfLine}`;
}
}

function* generatePreResolveComponents(): Generator<Code> {
yield `let __VLS_resolvedLocalAndGlobalComponents!: Required<{}`;
if (options.template.ast) {
const components = new Set<string>();
for (const node of forEachElementNode(options.template.ast)) {
if (
node.tagType === CompilerDOM.ElementTypes.COMPONENT
&& node.tag.toLowerCase() !== 'component'
&& !node.tag.includes('.') // namespace tag
) {
if (components.has(node.tag)) {
continue;
}
components.add(node.tag);
yield newLine;
yield ` & __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', typeof __VLS_localComponents, `;
yield getPossibleOriginalComponentNames(node.tag, false)
.map(name => `"${name}"`)
.join(', ');
yield `>`;
function* generatePreResolveComponents(options: TemplateCodegenOptions): Generator<Code> {
yield `let __VLS_resolvedLocalAndGlobalComponents!: Required<{}`;
if (options.template.ast) {
const components = new Set<string>();
for (const node of forEachElementNode(options.template.ast)) {
if (
node.tagType === CompilerDOM.ElementTypes.COMPONENT
&& node.tag.toLowerCase() !== 'component'
&& !node.tag.includes('.') // namespace tag
) {
if (components.has(node.tag)) {
continue;
}
components.add(node.tag);
yield newLine;
yield ` & __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', typeof __VLS_localComponents, `;
yield getPossibleOriginalComponentNames(node.tag, false)
.map(name => `"${name}"`)
.join(', ');
yield `>`;
}
}
yield `>${endOfLine}`;
}
yield `>${endOfLine}`;
}

export function* forEachElementNode(node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode): Generator<CompilerDOM.ElementNode> {
Expand Down
12 changes: 8 additions & 4 deletions packages/tsc/tests/__snapshots__/dts.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@ export {};
exports[`vue-tsc-dts > Input: template-slots/component.vue, Output: template-slots/component.vue.d.ts 1`] = `
"declare function __VLS_template(): {
attrs: Partial<{}>;
slots: {
"no-bind"?(_: {}): any;
default?(_: {
Expand All @@ -648,7 +649,7 @@ exports[`vue-tsc-dts > Input: template-slots/component.vue, Output: template-slo
}): any;
};
refs: {};
attrs: Partial<{}>;
rootEl: any;
};
type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
Expand All @@ -665,6 +666,7 @@ type __VLS_WithTemplateSlots<T, S> = T & {
exports[`vue-tsc-dts > Input: template-slots/component-define-slots.vue, Output: template-slots/component-define-slots.vue.d.ts 1`] = `
"import { VNode } from 'vue';
declare function __VLS_template(): {
attrs: Partial<{}>;
slots: Readonly<{
default: (props: {
num: number;
Expand All @@ -691,7 +693,7 @@ declare function __VLS_template(): {
'no-bind': () => VNode[];
};
refs: {};
attrs: Partial<{}>;
rootEl: any;
};
type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
Expand All @@ -707,6 +709,7 @@ type __VLS_WithTemplateSlots<T, S> = T & {
exports[`vue-tsc-dts > Input: template-slots/component-destructuring.vue, Output: template-slots/component-destructuring.vue.d.ts 1`] = `
"declare function __VLS_template(): {
attrs: Partial<{}>;
slots: Readonly<{
bottom: (props: {
num: number;
Expand All @@ -717,7 +720,7 @@ exports[`vue-tsc-dts > Input: template-slots/component-destructuring.vue, Output
}) => any[];
};
refs: {};
attrs: Partial<{}>;
rootEl: any;
};
type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
Expand All @@ -733,6 +736,7 @@ type __VLS_WithTemplateSlots<T, S> = T & {
exports[`vue-tsc-dts > Input: template-slots/component-no-script.vue, Output: template-slots/component-no-script.vue.d.ts 1`] = `
"declare function __VLS_template(): {
attrs: Partial<{}>;
slots: {
"no-bind"?(_: {}): any;
default?(_: {
Expand All @@ -747,7 +751,7 @@ exports[`vue-tsc-dts > Input: template-slots/component-no-script.vue, Output: te
}): any;
};
refs: {};
attrs: Partial<{}>;
rootEl: any;
};
type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
Expand Down
1 change: 1 addition & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test-workspace/tsc/passedFixtures/vue2/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"../vue3/directives/option.vue",
"../vue3/events",
"../vue3/no-script-block",
"../vue3/rootEl",
"../vue3/slots",
"../vue3/templateRef",
"../vue3/templateRef_native",
Expand Down
1 change: 1 addition & 0 deletions test-workspace/tsc/passedFixtures/vue3.4/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"exclude": [
"../vue3/#3820",
"../vue3/#4777",
"../vue3/rootEl",
"../vue3/templateRef",
"../vue3/templateRef_native",
"../vue3/components",
Expand Down
Loading

0 comments on commit ba63fab

Please sign in to comment.