Skip to content
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

feat(language-core): auto infer $el type #4805

Merged
merged 10 commits into from
Sep 6, 2024
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