Skip to content

Commit

Permalink
feat(language-core): navigation support for $attrs, $slots, `$ref…
Browse files Browse the repository at this point in the history
…s` and `$el` in the template (#5056)
  • Loading branch information
KazariEX authored Dec 31, 2024
1 parent f76c5eb commit 74bd7de
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 70 deletions.
8 changes: 4 additions & 4 deletions packages/language-core/lib/codegen/script/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ function* generateTemplateBody(
yield `const __VLS_slots = {}${endOfLine}`;
}
yield `const __VLS_inheritedAttrs = {}${endOfLine}`;
yield `const $refs = {}${endOfLine}`;
yield `const $el = {} as any${endOfLine}`;
yield `const __VLS_refs = {}${endOfLine}`;
yield `const __VLS_rootEl = {} as any${endOfLine}`;
}

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

Expand Down
2 changes: 2 additions & 0 deletions packages/language-core/lib/codegen/template/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
},
});
const localVars = new Map<string, number>();
const specialVars = new Set<string>();
const accessExternalVariables = new Map<string, Set<number>>();
const slots: {
name: string;
Expand Down Expand Up @@ -132,6 +133,7 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
slots,
dynamicSlots,
codeFeatures,
specialVars,
accessExternalVariables,
lastGenericComment,
hasSlotElements,
Expand Down
37 changes: 20 additions & 17 deletions packages/language-core/lib/codegen/template/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,16 +277,10 @@ export function* generateComponent(
yield `let ${var_componentEvents}!: __VLS_NormalizeEmits<typeof ${var_componentEmit}>${endOfLine}`;
}

if (
options.vueCompilerOptions.fallthroughAttributes
&& (
node.props.some(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && prop.name === 'bind' && prop.exp?.loc.source === '$attrs')
|| node === ctx.singleRootNode
)
) {
const varAttrs = ctx.getInternalVariable();
ctx.inheritedAttrVars.add(varAttrs);
yield `var ${varAttrs}!: Parameters<typeof ${var_functionalComponent}>[0];\n`;
if (hasVBindAttrs(options, ctx, node)) {
const attrsVar = ctx.getInternalVariable();
ctx.inheritedAttrVars.add(attrsVar);
yield `let ${attrsVar}!: Parameters<typeof ${var_functionalComponent}>[0];\n`;
}

const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode;
Expand Down Expand Up @@ -365,13 +359,7 @@ export function* generateElement(
yield* generateElementChildren(options, ctx, node);
}

if (
options.vueCompilerOptions.fallthroughAttributes
&& (
node.props.some(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && prop.name === 'bind' && prop.exp?.loc.source === '$attrs')
|| node === ctx.singleRootNode
)
) {
if (hasVBindAttrs(options, ctx, node)) {
ctx.inheritedAttrVars.add(`__VLS_intrinsicElements.${node.tag}`);
}
}
Expand Down Expand Up @@ -625,6 +613,21 @@ function* generateReferencesForElements(
return [];
}

function hasVBindAttrs(
options: TemplateCodegenOptions,
ctx: TemplateCodegenContext,
node: CompilerDOM.ElementNode
) {
return options.vueCompilerOptions.fallthroughAttributes && (
node === ctx.singleRootNode ||
node.props.some(prop =>
prop.type === CompilerDOM.NodeTypes.DIRECTIVE
&& prop.name === 'bind'
&& prop.exp?.loc.source === '$attrs'
)
);
}

function camelizeComponentName(newName: string) {
return camelize('-' + newName);
}
Expand Down
5 changes: 2 additions & 3 deletions packages/language-core/lib/codegen/template/elementProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ export function* generateElementProps(
prop,
prop.exp,
ctx.codeFeatures.all,
prop.arg?.loc.start.offset === prop.exp?.loc.start.offset,
enableCodeFeatures
),
`)`
Expand Down Expand Up @@ -257,7 +256,6 @@ export function* generateElementProps(
prop,
prop.exp,
ctx.codeFeatures.all,
false,
enableCodeFeatures
)
);
Expand All @@ -279,9 +277,10 @@ function* generatePropExp(
prop: CompilerDOM.DirectiveNode,
exp: CompilerDOM.SimpleExpressionNode | undefined,
features: VueCodeInformation,
isShorthand: boolean,
enableCodeFeatures: boolean
): Generator<Code> {
const isShorthand = prop.arg?.loc.start.offset === prop.exp?.loc.start.offset;

if (isShorthand && features.completion) {
features = {
...features,
Expand Down
60 changes: 38 additions & 22 deletions packages/language-core/lib/codegen/template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,38 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator<Co
if (options.propsAssignName) {
ctx.addLocalVariable(options.propsAssignName);
}
ctx.addLocalVariable(getSlotsPropertyName(options.vueCompilerOptions.target));
ctx.addLocalVariable('$attrs');
ctx.addLocalVariable('$refs');
ctx.addLocalVariable('$el');
const slotsPropertyName = getSlotsPropertyName(options.vueCompilerOptions.target);
ctx.specialVars.add(slotsPropertyName);
ctx.specialVars.add('$attrs');
ctx.specialVars.add('$refs');
ctx.specialVars.add('$el');

if (options.template.ast) {
yield* generateTemplateChild(options, ctx, options.template.ast, undefined);
}

yield* generateStyleScopedClassReferences(ctx);
yield* generateSlots(options, ctx);
yield* generateInheritedAttrs(ctx);
yield* generateRefs(ctx);
yield* generateRootEl(ctx);
const speicalTypes = [
[slotsPropertyName, yield* generateSlots(options, ctx)],
['$attrs', yield* generateInheritedAttrs(ctx)],
['$refs', yield* generateRefs(ctx)],
['$el', yield* generateRootEl(ctx)]
];

yield `var __VLS_special!: {${newLine}`;
for (const [name, type] of speicalTypes) {
yield `${name}: ${type}${endOfLine}`;
}
yield `} & { [K in keyof typeof __VLS_ctx]: unknown }${endOfLine}`;

yield* ctx.generateAutoImportCompletion();
return ctx;
}

function* generateSlots(options: TemplateCodegenOptions, ctx: TemplateCodegenContext): Generator<Code> {
function* generateSlots(
options: TemplateCodegenOptions,
ctx: TemplateCodegenContext
): Generator<Code> {
if (!options.hasDefineSlots) {
yield `var __VLS_slots!: `;
for (const { expVar, varName } of ctx.dynamicSlots) {
Expand Down Expand Up @@ -86,21 +98,22 @@ function* generateSlots(options: TemplateCodegenOptions, ctx: TemplateCodegenCon
}
yield `}${endOfLine}`;
}
const name = getSlotsPropertyName(options.vueCompilerOptions.target);
yield `var ${name}!: typeof ${options.slotsAssignName ?? '__VLS_slots'}${endOfLine}`;
return `typeof ${options.slotsAssignName ?? `__VLS_slots`}`;
}

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

if (ctx.bindingAttrLocs.length) {
yield `[`;
for (const loc of ctx.bindingAttrLocs) {
yield `__VLS_special.`;
yield [
loc.source,
'template',
Expand All @@ -111,9 +124,12 @@ function* generateInheritedAttrs(ctx: TemplateCodegenContext): Generator<Code> {
}
yield `]${endOfLine}`;
}
return `Partial<typeof __VLS_inheritedAttrs> & Record<string, unknown>`;
}

function* generateRefs(ctx: TemplateCodegenContext): Generator<Code> {
function* generateRefs(
ctx: TemplateCodegenContext
): Generator<Code> {
yield `const __VLS_refs = {${newLine}`;
for (const [name, [varName, offset]] of ctx.templateRefs) {
yield* generateStringLiteralKey(
Expand All @@ -124,16 +140,16 @@ function* generateRefs(ctx: TemplateCodegenContext): Generator<Code> {
yield `: ${varName},${newLine}`;
}
yield `}${endOfLine}`;
yield `var $refs!: typeof __VLS_refs${endOfLine}`;
return `typeof __VLS_refs`;
}

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

export function* forEachElementNode(node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode): Generator<CompilerDOM.ElementNode> {
Expand Down
42 changes: 20 additions & 22 deletions packages/language-core/lib/codegen/template/interpolation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ export function* generateInterpolation(
}
}

interface CtxVar {
text: string;
isShorthand: boolean;
offset: number;
};

function* forEachInterpolationSegment(
ts: typeof import('typescript'),
destructuredPropNames: Set<string> | undefined,
Expand All @@ -80,20 +86,16 @@ function* forEachInterpolationSegment(
offset: number | undefined,
ast: ts.SourceFile
): Generator<[fragment: string, offset: number | undefined, type?: 'errorMappingOnly' | 'startText' | 'endText']> {
let ctxVars: {
text: string,
isShorthand: boolean,
offset: number,
}[] = [];
let ctxVars: CtxVar[] = [];

const varCb = (id: ts.Identifier, isShorthand: boolean) => {
const text = getNodeText(ts, id, ast);
if (
ctx.hasLocalVariable(text) ||
ctx.hasLocalVariable(text)
// https://github.com/vuejs/core/blob/245230e135152900189f13a4281302de45fdcfaa/packages/compiler-core/src/transforms/transformExpression.ts#L342-L352
isGloballyAllowed(text) ||
text === 'require' ||
text.startsWith('__VLS_')
|| isGloballyAllowed(text)
|| text === 'require'
|| text.startsWith('__VLS_')
) {
// localVarOffsets.push(localVar.getStart(ast));
}
Expand Down Expand Up @@ -132,7 +134,7 @@ function* forEachInterpolationSegment(
const curVar = ctxVars[i];
const nextVar = ctxVars[i + 1];

yield* generateVar(code, destructuredPropNames, templateRefNames, curVar, nextVar);
yield* generateVar(code, ctx.specialVars, destructuredPropNames, templateRefNames, curVar, nextVar);

if (nextVar.isShorthand) {
yield [code.slice(curVar.offset + curVar.text.length, nextVar.offset + nextVar.text.length), curVar.offset + curVar.text.length];
Expand All @@ -144,7 +146,7 @@ function* forEachInterpolationSegment(
}

const lastVar = ctxVars.at(-1)!;
yield* generateVar(code, destructuredPropNames, templateRefNames, lastVar);
yield* generateVar(code, ctx.specialVars, destructuredPropNames, templateRefNames, lastVar);
if (lastVar.offset + lastVar.text.length < code.length) {
yield [code.slice(lastVar.offset + lastVar.text.length), lastVar.offset + lastVar.text.length, 'endText'];
}
Expand All @@ -156,18 +158,11 @@ function* forEachInterpolationSegment(

function* generateVar(
code: string,
specialVars: Set<string>,
destructuredPropNames: Set<string> | undefined,
templateRefNames: Set<string> | undefined,
curVar: {
text: string,
isShorthand: boolean,
offset: number,
},
nextVar: {
text: string,
isShorthand: boolean,
offset: number,
} = curVar
curVar: CtxVar,
nextVar: CtxVar = curVar
): Generator<[fragment: string, offset: number | undefined, type?: 'errorMappingOnly']> {
// fix https://github.com/vuejs/language-tools/issues/1205
// fix https://github.com/vuejs/language-tools/issues/1264
Expand All @@ -181,7 +176,10 @@ function* generateVar(
yield [`)`, undefined];
}
else {
if (!isDestructuredProp) {
if (specialVars.has(curVar.text)) {
yield [`__VLS_special.`, undefined];
}
else if (!isDestructuredProp) {
yield [`__VLS_ctx.`, undefined];
}
yield [code.slice(curVar.offset, curVar.offset + curVar.text.length), curVar.offset];
Expand Down
4 changes: 2 additions & 2 deletions packages/language-core/lib/plugins/vue-tsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ function createTsx(
slotsAssignName: slotsAssignName.get(),
propsAssignName: propsAssignName.get(),
inheritAttrs: inheritAttrs.get(),
selfComponentName: selfComponentName.get()
selfComponentName: selfComponentName.get(),
});

let current = codegen.next();
Expand All @@ -202,7 +202,7 @@ function createTsx(

return {
...current.value,
codes: codes,
codes,
};
});
const generatedScript = computed(() => {
Expand Down

0 comments on commit 74bd7de

Please sign in to comment.