Skip to content

Commit

Permalink
feat(language-core): auto infer type for $refs & useTemplateRef (#4644)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiyuanzmj authored Aug 25, 2024
1 parent bee8ef5 commit 4dd5ee9
Show file tree
Hide file tree
Showing 18 changed files with 297 additions and 139 deletions.
9 changes: 6 additions & 3 deletions packages/language-core/lib/codegen/script/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export function* generateComponent(
if (options.sfc.script && options.scriptRanges) {
yield* generateScriptOptions(options.sfc.script, options.scriptRanges);
}
if (options.vueCompilerOptions.target >= 3.5 && scriptSetupRanges.templateRefs.length) {
yield `__typeRefs: {} as __VLS_Refs,${newLine}`;
}
yield `})`;
}

Expand Down Expand Up @@ -83,10 +86,10 @@ export function* generatePropsOption(
if (options.vueCompilerOptions.target >= 3.5) {
const types = [];
if (inheritAttrs && options.templateCodegen?.inheritedAttrVars.size) {
types.push('typeof __VLS_template>[1]');
types.push(`ReturnType<typeof __VLS_template>['attrs']`);
}
if (ctx.generatedPropsType) {
types.push('{} as __VLS_PublicProps');
types.push(`{} as __VLS_PublicProps`);
}
if (types.length) {
yield `__typeProps: ${types.join(' & ')},${newLine}`;
Expand All @@ -97,7 +100,7 @@ export function* generatePropsOption(

if (inheritAttrs && options.templateCodegen?.inheritedAttrVars.size) {
codegens.push(function* () {
yield `{} as ${ctx.helperTypes.TypePropsToOption.name}<__VLS_PickNotAny<${ctx.helperTypes.OmitIndexSignature.name}<ReturnType<typeof __VLS_template>[1]>, {}>>`;
yield `{} as ${ctx.helperTypes.TypePropsToOption.name}<__VLS_PickNotAny<${ctx.helperTypes.OmitIndexSignature.name}<ReturnType<typeof __VLS_template>['attrs']>, {}>>`;
});
}

Expand Down
7 changes: 7 additions & 0 deletions packages/language-core/lib/codegen/script/globalTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ declare global {
>
>;
type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
type __VLS_PickRefsExpose<T> = T extends object
? { [K in keyof T]: (T[K] extends any[]
? Parameters<T[K][0]['expose']>[0][]
: T[K] extends { expose?: (exposed: infer E) => void }
? E
: T[K]) | null }
: never;
}
export const __VLS_globalTypesEnd = {};
`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export function* generateInternalComponent(
}
yield `}${endOfLine}`; // return {
yield `},${newLine}`; // setup() {
if (options.vueCompilerOptions.target >= 3.5) {
yield `__typeRefs: {} as __VLS_Refs,${newLine}`;
}
if (options.sfc.scriptSetup && options.scriptSetupRanges && !ctx.bypassDefineComponent) {
yield* generateScriptSetupOptions(options, ctx, options.sfc.scriptSetup, options.scriptSetupRanges, false);
}
Expand Down
13 changes: 10 additions & 3 deletions packages/language-core/lib/codegen/script/scriptSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function* generateScriptSetup(
+ ` props: ${ctx.helperTypes.Prettify.name}<typeof __VLS_functionalComponentProps & __VLS_PublicProps> & __VLS_BuiltInPublicProps,${newLine}`
+ ` expose(exposed: import('${options.vueCompilerOptions.lib}').ShallowUnwrapRef<${scriptSetupRanges.expose.define ? 'typeof __VLS_exposed' : '{}'}>): void,${newLine}`
+ ` attrs: any,${newLine}`
+ ` slots: ReturnType<typeof __VLS_template>[0],${newLine}`
+ ` slots: __VLS_Slots,${newLine}`
+ ` emit: ${emitTypes.join(' & ')},${newLine}`
+ ` }${endOfLine}`;
yield ` })(),${newLine}`; // __VLS_setup = (async () => {
Expand Down Expand Up @@ -116,7 +116,7 @@ function* generateSetupFunction(
if (options.vueCompilerOptions.target >= 3.3) {
yield `const { `;
for (const macro of Object.keys(options.vueCompilerOptions.macros)) {
if (!ctx.bindingNames.has(macro)) {
if (!ctx.bindingNames.has(macro) && macro !== 'templateRef') {
yield macro + `, `;
}
}
Expand Down Expand Up @@ -211,6 +211,11 @@ function* generateSetupFunction(
]);
}
}
for (const { define } of scriptSetupRanges.templateRefs) {
if (define?.arg) {
setupCodeModifies.push([[`<__VLS_Refs[${scriptSetup.content.slice(define.arg.start, define.arg.end)}], keyof __VLS_Refs>`], define.arg.start - 1, define.arg.start - 1]);
}
}
setupCodeModifies = setupCodeModifies.sort((a, b) => a[1] - b[1]);

if (setupCodeModifies.length) {
Expand Down Expand Up @@ -243,14 +248,16 @@ function* generateSetupFunction(
yield* generateComponentProps(options, ctx, scriptSetup, scriptSetupRanges, definePropMirrors);
yield* generateModelEmits(options, scriptSetup, scriptSetupRanges);
yield* generateTemplate(options, ctx, false);
yield `type __VLS_Refs = ReturnType<typeof __VLS_template>['refs']${endOfLine}`;
yield `type __VLS_Slots = ReturnType<typeof __VLS_template>['slots']${endOfLine}`;

if (syntax) {
if (!options.vueCompilerOptions.skipTemplateCodegen && (options.templateCodegen?.hasSlot || scriptSetupRanges?.slots.define)) {
yield `const __VLS_component = `;
yield* generateComponent(options, ctx, scriptSetup, scriptSetupRanges);
yield endOfLine;
yield `${syntax} `;
yield `{} as ${ctx.helperTypes.WithTemplateSlots.name}<typeof __VLS_component, ReturnType<typeof __VLS_template>[0]>${endOfLine}`;
yield `{} as ${ctx.helperTypes.WithTemplateSlots.name}<typeof __VLS_component, __VLS_Slots>${endOfLine}`;
}
else {
yield `${syntax} `;
Expand Down
7 changes: 6 additions & 1 deletion packages/language-core/lib/codegen/script/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,16 @@ function* generateTemplateContext(
yield `// no template${newLine}`;
if (!options.scriptSetupRanges?.slots.define) {
yield `const __VLS_slots = {}${endOfLine}`;
yield `const __VLS_refs = {}${endOfLine}`;
yield `const __VLS_inheritedAttrs = {}${endOfLine}`;
}
}

yield `return [${options.scriptSetupRanges?.slots.name ?? '__VLS_slots'}, __VLS_inheritedAttrs] as const${endOfLine}`;
yield `return {${newLine}`;
yield `slots: ${options.scriptSetupRanges?.slots.name ?? '__VLS_slots'},${newLine}`;
yield `refs: __VLS_refs as __VLS_PickRefsExpose<typeof __VLS_refs>,${newLine}`;
yield `attrs: __VLS_inheritedAttrs,${newLine}`;
yield `}${endOfLine}`;
}

function* generateCssClassProperty(
Expand Down
34 changes: 31 additions & 3 deletions packages/language-core/lib/codegen/template/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,13 +259,26 @@ export function* generateComponent(
yield endOfLine;
}

yield* generateVScope(options, ctx, node, props);
const refName = yield* generateVScope(options, ctx, node, props);

ctx.usedComponentCtxVars.add(componentCtxVar);
const usedComponentEventsVar = yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEmit, var_componentEvents);

if (var_defineComponentCtx && ctx.usedComponentCtxVars.has(var_defineComponentCtx)) {
yield `const ${componentCtxVar} = __VLS_nonNullable(__VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance}))${endOfLine}`;
if (refName) {
yield `// @ts-ignore${newLine}`;
if (node.codegenNode?.type === CompilerDOM.NodeTypes.VNODE_CALL
&& node.codegenNode.props?.type === CompilerDOM.NodeTypes.JS_OBJECT_EXPRESSION
&& node.codegenNode.props.properties.find(({ key }) => key.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && key.content === 'ref_for')
) {
yield `(${refName} ??= []).push(${var_defineComponentCtx})`;
} else {
yield `${refName} = ${var_defineComponentCtx}`;
}

yield endOfLine;
}
}
if (usedComponentEventsVar) {
yield `let ${var_componentEmit}!: typeof ${componentCtxVar}.emit${endOfLine}`;
Expand Down Expand Up @@ -350,7 +363,17 @@ export function* generateElement(
yield endOfLine;
}

yield* generateVScope(options, ctx, node, node.props);
const refName = yield* generateVScope(options, ctx, node, node.props);
if (refName) {
yield `// @ts-ignore${newLine}`;
yield `${refName} = __VLS_intrinsicElements`;
yield* generatePropertyAccess(
options,
ctx,
node.tag
);
yield endOfLine;
}

const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode;
if (slotDir && componentCtxVar) {
Expand Down Expand Up @@ -397,13 +420,14 @@ function* generateVScope(
}

yield* generateElementDirectives(options, ctx, node);
yield* generateReferencesForElements(options, ctx, node); // <el ref="foo" />
const refName = yield* generateReferencesForElements(options, ctx, node); // <el ref="foo" />
yield* generateReferencesForScopedCssClasses(options, ctx, node);

if (inScope) {
yield `}${newLine}`;
ctx.blockConditions.length = originalConditionsNum;
}
return refName;
}

export function getCanonicalComponentName(tagText: string) {
Expand Down Expand Up @@ -571,6 +595,10 @@ function* generateReferencesForElements(
')'
);
yield endOfLine;

const refName = CompilerDOM.toValidAssetId(prop.value.content, '_VLS_refs' as any);
options.templateRefNames.set(prop.value.content, refName);
return refName;
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions packages/language-core/lib/codegen/template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface TemplateCodegenOptions {
template: NonNullable<Sfc['template']>;
scriptSetupBindingNames: Set<string>;
scriptSetupImportComponentNames: Set<string>;
templateRefNames: Map<string, string>;
hasDefineSlots?: boolean;
slotsAssignName?: string;
propsAssignName?: string;
Expand Down Expand Up @@ -49,8 +50,21 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator<Co

yield* ctx.generateAutoImportCompletion();

yield* generateRefs()

return ctx;

function* generateRefs(): Generator<Code> {
for (const [, validId] of options.templateRefNames) {
yield `let ${validId}${newLine}`;
}
yield `const __VLS_refs = {${newLine}`;
for (const [name, validId] of options.templateRefNames) {
yield `'${name}': ${validId}!,${newLine}`;
}
yield `}${endOfLine}`;
}

function* generateSlotsType(): Generator<Code> {
for (const { expVar, varName } of ctx.dynamicSlots) {
ctx.hasSlot = true;
Expand Down
16 changes: 16 additions & 0 deletions packages/language-core/lib/parsers/scriptSetupRanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export function parseScriptSetupRanges(
name?: string;
inheritAttrs?: string;
} = {};
const templateRefs: {
name?: string;
define?: ReturnType<typeof parseDefineFunction>;
}[] = [];

const definePropProposalA = vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=kevinEdition');
const definePropProposalB = vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=johnsonEdition');
Expand Down Expand Up @@ -104,6 +108,7 @@ export function parseScriptSetupRanges(
expose,
options,
defineProp,
templateRefs,
};

function _getStartEnd(node: ts.Node) {
Expand Down Expand Up @@ -298,6 +303,17 @@ export function parseScriptSetupRanges(
}
}
}
} else if (vueCompilerOptions.macros.templateRef.includes(callText) && node.arguments.length && !node.typeArguments?.length) {
const define = parseDefineFunction(node);
define.arg = _getStartEnd(node.arguments[0]);
let name;
if (ts.isVariableDeclaration(parent)) {
name = getNodeText(ts, parent.name, ast);
}
templateRefs.push({
name,
define
});
}
}
ts.forEachChild(node, child => {
Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/plugins/vue-tsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ function createTsx(
template: _sfc.template,
scriptSetupBindingNames: scriptSetupBindingNames(),
scriptSetupImportComponentNames: scriptSetupImportComponentNames(),
templateRefNames: new Map(),
hasDefineSlots: hasDefineSlots(),
slotsAssignName: slotsAssignName(),
propsAssignName: propsAssignName(),
Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface VueCompilerOptions {
defineModel: string[];
defineOptions: string[];
withDefaults: string[];
templateRef: string[];
};
plugins: VueLanguagePlugin[];

Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/utils/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ export function resolveVueCompilerOptions(vueOptions: Partial<VueCompilerOptions
defineModel: ['defineModel'],
defineOptions: ['defineOptions'],
withDefaults: ['withDefaults'],
templateRef: ['templateRef', 'useTemplateRef'],
...vueOptions.macros,
},
plugins: vueOptions.plugins ?? [],
Expand Down
3 changes: 2 additions & 1 deletion packages/language-core/schemas/vue-tsconfig.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@
"defineSlots": [ "defineSlots" ],
"defineEmits": [ "defineEmits" ],
"defineExpose": [ "defineExpose" ],
"withDefaults": [ "withDefaults" ]
"withDefaults": [ "withDefaults" ],
"templateRef": [ "templateRef", "useTemplateRef" ]
}
},
"experimentalResolveStyleCssClasses": {
Expand Down
Loading

0 comments on commit 4dd5ee9

Please sign in to comment.