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): navigation support for $attrs, $slots, $refs and $el in the template #5056

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions packages/language-core/lib/codegen/script/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function createScriptCodegenContext(options: ScriptCodegenOptions) {
return {
generatedTemplate: false,
generatedPropsType: false,
templateGeneratedOffset: undefined as number | undefined,
scriptSetupGeneratedOffset: undefined as number | undefined,
bypassDefineComponent: options.lang === 'js' || options.lang === 'jsx',
bindingNames: new Set([
Expand Down
12 changes: 7 additions & 5 deletions packages/language-core/lib/codegen/script/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function* generateTemplate(
yield* generateTemplateCtx(options);
yield* generateTemplateComponents(options);
yield* generateTemplateDirectives(options);
yield* generateTemplateBody(options, templateCodegenCtx);
yield* generateTemplateBody(options, ctx, templateCodegenCtx);
return templateCodegenCtx;
}

Expand Down Expand Up @@ -128,13 +128,15 @@ export function* generateTemplateDirectives(options: ScriptCodegenOptions): Gene

function* generateTemplateBody(
options: ScriptCodegenOptions,
ctx: ScriptCodegenContext,
templateCodegenCtx: TemplateCodegenContext
): Generator<Code> {
yield* generateStyleScopedClasses(options, templateCodegenCtx);
yield* generateStyleScopedClassReferences(templateCodegenCtx, true);
yield* generateCssVars(options, templateCodegenCtx);

if (options.templateCodegen) {
ctx.templateGeneratedOffset = options.getGeneratedLength();
for (const code of options.templateCodegen.codes) {
yield code;
}
Expand All @@ -145,15 +147,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
39 changes: 21 additions & 18 deletions packages/language-core/lib/codegen/template/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,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 @@ -343,13 +337,7 @@ export function* generateElement(
yield* generateElementChildren(options, ctx, node, currentComponent, componentCtxVar);
}

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 @@ -749,6 +737,21 @@ function* generateReferencesForScopedCssClasses(
}
}

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 Expand Up @@ -802,4 +805,4 @@ function collectClasses(content: string, startOffset = 0) {
// isTemplateExpression is missing in tsc
function isTemplateExpression(node: ts.Node): node is ts.TemplateExpression {
return node.kind === 228 satisfies ts.SyntaxKind.TemplateExpression;
}
}
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
65 changes: 49 additions & 16 deletions packages/language-core/lib/codegen/template/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Mapping } from '@volar/language-core';
import * as CompilerDOM from '@vue/compiler-dom';
import type * as ts from 'typescript';
import type { Code, Sfc, VueCompilerOptions } from '../../types';
Expand All @@ -23,6 +24,8 @@ export interface TemplateCodegenOptions {
slotsAssignName?: string;
propsAssignName?: string;
inheritAttrs: boolean;
getGeneratedLength: () => number;
linkedCodeMappings: Mapping[];
}

export function* generateTemplate(options: TemplateCodegenOptions): Generator<Code, TemplateCodegenContext> {
Expand All @@ -45,15 +48,18 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator<Co

yield* generateStyleScopedClassReferences(ctx);
yield* generateSlots(options, ctx);
yield* generateInheritedAttrs(ctx);
yield* generateRefs(ctx);
yield* generateRootEl(ctx);
yield* generateInheritedAttrs(options, ctx);
yield* generateRefs(options, ctx);
yield* generateRootEl(options, ctx);

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,16 +92,19 @@ function* generateSlots(options: TemplateCodegenOptions, ctx: TemplateCodegenCon
yield `}${endOfLine}`;
}
const name = getSlotsPropertyName(options.vueCompilerOptions.target);
yield `var ${name}!: typeof ${options.slotsAssignName ?? '__VLS_slots'}${endOfLine}`;
yield* generateContextVariable(options, name, `typeof ${options.slotsAssignName ?? `__VLS_slots`}`);
}

function* generateInheritedAttrs(ctx: TemplateCodegenContext): Generator<Code> {
function* generateInheritedAttrs(
options: TemplateCodegenOptions,
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}`;
yield* generateContextVariable(options, `$attrs`, `Partial<typeof __VLS_inheritedAttrs> & Record<string, unknown>`);

if (ctx.bindingAttrLocs.length) {
yield `[`;
Expand All @@ -112,7 +121,10 @@ function* generateInheritedAttrs(ctx: TemplateCodegenContext): Generator<Code> {
}
}

function* generateRefs(ctx: TemplateCodegenContext): Generator<Code> {
function* generateRefs(
options: TemplateCodegenOptions,
ctx: TemplateCodegenContext
): Generator<Code> {
yield `const __VLS_refs = {${newLine}`;
for (const [name, [varName, offset]] of ctx.templateRefs) {
yield* generateStringLiteralKey(
Expand All @@ -123,16 +135,37 @@ function* generateRefs(ctx: TemplateCodegenContext): Generator<Code> {
yield `: ${varName},${newLine}`;
}
yield `}${endOfLine}`;
yield `var $refs!: typeof __VLS_refs${endOfLine}`;
yield* generateContextVariable(options, `$refs`, `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(
options: TemplateCodegenOptions,
ctx: TemplateCodegenContext
): Generator<Code> {
yield `let __VLS_rootEl!: `;
yield ctx.singleRootElType ?? `any`;
yield endOfLine;
yield* generateContextVariable(options, `$el`, `typeof __VLS_rootEl`);
}

function* generateContextVariable(
options: TemplateCodegenOptions,
varName: string,
typeExp: string
): Generator<Code> {
yield `/** @type { typeof __VLS_ctx.`;
const sourceOffset = options.getGeneratedLength();
yield `${varName} } */${endOfLine}`;
yield `var `;
const generatedOffset = options.getGeneratedLength();
yield `${varName}!: ${typeExp}${endOfLine}`;

options.linkedCodeMappings.push({
sourceOffsets: [sourceOffset],
generatedOffsets: [generatedOffset],
lengths: [varName.length],
data: undefined,
});
}

export function* forEachElementNode(node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode): Generator<CompilerDOM.ElementNode> {
Expand Down
32 changes: 13 additions & 19 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 @@ -158,16 +160,8 @@ function* generateVar(
code: 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 Down
29 changes: 23 additions & 6 deletions packages/language-core/lib/plugins/vue-tsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,22 @@ const plugin: VueLanguagePlugin = ctx => {

resolveEmbeddedCode(fileName, sfc, embeddedFile) {

const _tsx = useTsx(fileName, sfc);
const tsx = useTsx(fileName, sfc);

if (/script_(js|jsx|ts|tsx)/.test(embeddedFile.id)) {
const tsx = _tsx.generatedScript.get();
if (tsx) {
embeddedFile.content = [...tsx.codes];
embeddedFile.linkedCodeMappings = [...tsx.linkedCodeMappings];
const script = tsx.generatedScript.get();
const template = tsx.generatedTemplate.get();
if (script) {
const linkedCodeMappings = [
...script.linkedCodeMappings,
...template?.linkedCodeMappings.map(mapping => ({
...mapping,
sourceOffsets: mapping.sourceOffsets.map(offset => offset + script.templateGeneratedOffset!),
generatedOffsets: mapping.generatedOffsets.map(offset => offset + script.templateGeneratedOffset!),
})) ?? []
];
embeddedFile.content = [...script.codes];
embeddedFile.linkedCodeMappings = linkedCodeMappings;
}
}
},
Expand Down Expand Up @@ -160,6 +169,8 @@ function createTsx(
}

const codes: Code[] = [];
const linkedCodeMappings: Mapping[] = [];
let generatedLength = 0;
const codegen = generateTemplate({
ts,
compilerOptions: ctx.compilerOptions,
Expand All @@ -174,19 +185,25 @@ function createTsx(
slotsAssignName: slotsAssignName.get(),
propsAssignName: propsAssignName.get(),
inheritAttrs: inheritAttrs.get(),
getGeneratedLength: () => generatedLength,
linkedCodeMappings,
});

let current = codegen.next();

while (!current.done) {
const code = current.value;
codes.push(code);
generatedLength += typeof code === 'string'
? code.length
: code[0].length;
current = codegen.next();
}

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