Skip to content

refactor(langauge-core): codegen based on Generator #3778

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

Merged
merged 24 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b3a0cb2
updates
johnsoncodehk Nov 16, 2023
d10b0e5
updates [skip ci]
johnsoncodehk Nov 17, 2023
1c3a7b5
sync https://github.com/volarjs/volar.js/pull/86 changes [skip ci]
johnsoncodehk Nov 17, 2023
22cc868
updates
johnsoncodehk Nov 17, 2023
0e54fed
first fix for https://github.com/volarjs/volar.js/pull/91 [skip ci]
johnsoncodehk Nov 24, 2023
e294393
second fix [skip ci]
johnsoncodehk Nov 24, 2023
516eccd
updates for https://github.com/volarjs/volar.js/pull/95
johnsoncodehk Nov 26, 2023
dabf16a
fix empty slot name completion [skip ci]
johnsoncodehk Nov 27, 2023
9cf5dd4
fix prop completion [ckip ci]
johnsoncodehk Nov 27, 2023
f275f94
fix script codegen feature settings [skip ci]
johnsoncodehk Nov 27, 2023
91d18ef
Update script.ts [skip ci]
johnsoncodehk Nov 27, 2023
100ca5b
sync https://github.com/volarjs/volar.js/pull/96 changes [skip ci]
johnsoncodehk Nov 28, 2023
9c5d245
sync https://github.com/volarjs/volar.js/pull/97 changes [skip ci]
johnsoncodehk Nov 28, 2023
12d3e22
Merge branch 'master' into volar-1.12
johnsoncodehk Nov 29, 2023
4a10d31
fix merge [skip ci]
johnsoncodehk Nov 29, 2023
b003402
updates
johnsoncodehk Dec 5, 2023
d61d074
Merge branch 'master' into volar-1.12
johnsoncodehk Dec 5, 2023
b53d75d
use volar 2.0 alpha
johnsoncodehk Dec 5, 2023
db6d4d7
Update template.ts [skip ci]
johnsoncodehk Dec 5, 2023
3e3b90c
use Generator for script codegen [skip ci]
johnsoncodehk Dec 5, 2023
a9bb290
make codegen stack working
johnsoncodehk Dec 6, 2023
e90af86
also refactor for template codegen
johnsoncodehk Dec 6, 2023
1a9470d
Merge branch 'master' into refactor-codegen
johnsoncodehk Dec 6, 2023
39f3e83
Update createTester.ts
johnsoncodehk Dec 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
964 changes: 479 additions & 485 deletions packages/language-core/src/generators/script.ts

Large diffs are not rendered by default.

1,946 changes: 910 additions & 1,036 deletions packages/language-core/src/generators/template.ts

Large diffs are not rendered by default.

19 changes: 18 additions & 1 deletion packages/language-core/src/generators/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
import { VueCodeInformation } from '../types';
import { Code, CodeAndStack, VueCodeInformation } from '../types';

export function withStack(code: Code): CodeAndStack {
return [code, getStack()];
}

// TODO: import from muggle-string
export function getStack() {
const stack = new Error().stack!;
let source = stack.split('\n')[3].trim();
if (source.endsWith(')')) {
source = source.slice(source.lastIndexOf('(') + 1, -1);
}
else {
source = source.slice(source.lastIndexOf(' ') + 1);
}
return source;
}

export function disableAllFeatures(override: Partial<VueCodeInformation>): VueCodeInformation {
return {
Expand Down
115 changes: 93 additions & 22 deletions packages/language-core/src/plugins/vue-tsx.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { CodeInformation, Segment, track } from '@volar/language-core';
import { CodeInformation, Mapping, Segment, StackNode, track } from '@volar/language-core';
import { computed, computedSet } from 'computeds';
import { generate as generateScript } from '../generators/script';
import { generate as generateTemplate } from '../generators/template';
import { parseScriptRanges } from '../parsers/scriptRanges';
import { parseScriptSetupRanges } from '../parsers/scriptSetupRanges';
import { Sfc, VueLanguagePlugin } from '../types';
import { Code, Sfc, VueLanguagePlugin } from '../types';
import { enableAllFeatures } from '../generators/utils';

const templateFormatReg = /^\.template_format\.ts$/;
Expand Down Expand Up @@ -64,7 +64,7 @@ const plugin: VueLanguagePlugin = (ctx) => {
});
embeddedFile.content = content;
embeddedFile.contentStacks = contentStacks;
embeddedFile.linkedNavigationMappings = [...tsx.mirrorBehaviorMappings];
embeddedFile.linkedNavigationMappings = [...tsx.linkedCodeMappings];
}
}
else if (suffix.match(templateFormatReg)) {
Expand All @@ -74,8 +74,8 @@ const plugin: VueLanguagePlugin = (ctx) => {
const template = _tsx.generatedTemplate();
if (template) {
const [content, contentStacks] = ctx.codegenStack
? track([...template.formatCodes], [...template.formatCodeStacks])
: [[...template.formatCodes], [...template.formatCodeStacks]];
? track([...template.formatCodes], template.formatCodeStacks.map(stack => ({ stack, length: 1 })))
: [[...template.formatCodes], template.formatCodeStacks.map(stack => ({ stack, length: 1 }))];
embeddedFile.content = content;
embeddedFile.contentStacks = contentStacks;
}
Expand All @@ -101,8 +101,8 @@ const plugin: VueLanguagePlugin = (ctx) => {
const template = _tsx.generatedTemplate();
if (template) {
const [content, contentStacks] = ctx.codegenStack
? track([...template.cssCodes], [...template.cssCodeStacks])
: [[...template.cssCodes], [...template.cssCodeStacks]];
? track([...template.cssCodes], template.cssCodeStacks.map(stack => ({ stack, length: 1 })))
: [[...template.cssCodes], template.cssCodeStacks.map(stack => ({ stack, length: 1 }))];
embeddedFile.content = content as Segment<CodeInformation>[];
embeddedFile.contentStacks = contentStacks;
}
Expand Down Expand Up @@ -169,7 +169,13 @@ function createTsx(fileName: string, _sfc: Sfc, { vueCompilerOptions, compilerOp
if (!_sfc.template)
return;

return generateTemplate(
const tsCodes: Code[] = [];
const tsFormatCodes: Code[] = [];
const inlineCssCodes: Code[] = [];
const tsCodegenStacks: string[] = [];
const tsFormatCodegenStacks: string[] = [];
const inlineCssCodegenStacks: string[] = [];
const codegen = generateTemplate(
ts,
compilerOptions,
vueCompilerOptions,
Expand All @@ -181,24 +187,89 @@ function createTsx(fileName: string, _sfc: Sfc, { vueCompilerOptions, compilerOp
propsAssignName(),
codegenStack,
);

let current = codegen.next();

while (!current.done) {
const [type, code, stack] = current.value;
if (type === 'ts') {
tsCodes.push(code);
}
else if (type === 'tsFormat') {
tsFormatCodes.push(code);
}
else if (type === 'inlineCss') {
inlineCssCodes.push(code);
}
if (codegenStack) {
if (type === 'ts') {
tsCodegenStacks.push(stack);
}
else if (type === 'tsFormat') {
tsFormatCodegenStacks.push(stack);
}
else if (type === 'inlineCss') {
inlineCssCodegenStacks.push(stack);
}
}
current = codegen.next();
}

return {
...current.value,
codes: tsCodes,
codeStacks: tsCodegenStacks,
formatCodes: tsFormatCodes,
formatCodeStacks: tsFormatCodegenStacks,
cssCodes: inlineCssCodes,
cssCodeStacks: inlineCssCodegenStacks,
};
});
const hasScriptSetupSlots = computed(() => !!scriptSetupRanges()?.slots.define);
const slotsAssignName = computed(() => scriptSetupRanges()?.slots.name);
const propsAssignName = computed(() => scriptSetupRanges()?.props.name);
const generatedScript = computed(() => generateScript(
ts,
fileName,
_sfc.script,
_sfc.scriptSetup,
_sfc.styles,
lang(),
scriptRanges(),
scriptSetupRanges(),
generatedTemplate(),
compilerOptions,
vueCompilerOptions,
codegenStack,
));
const generatedScript = computed(() => {
const codes: Code[] = [];
const codeStacks: StackNode[] = [];
const linkedCodeMappings: Mapping[] = [];
const _template = generatedTemplate();
let generatedLength = 0;
for (const [code, stack] of generateScript(
ts,
fileName,
_sfc.script,
_sfc.scriptSetup,
_sfc.styles,
lang(),
scriptRanges(),
scriptSetupRanges(),
_template ? {
tsCodes: _template.codes,
tsCodegenStacks: _template.codeStacks,
accessedGlobalVariables: _template.accessedGlobalVariables,
hasSlot: _template.hasSlot,
tagNames: new Set(_template.tagOffsetsMap.keys()),
} : undefined,
compilerOptions,
vueCompilerOptions,
() => generatedLength,
linkedCodeMappings,
codegenStack,
)) {
codes.push(code);
if (codegenStack) {
codeStacks.push({ stack, length: 1 });
}
generatedLength += typeof code === 'string'
? code.length
: code[0].length;
};
return {
codes,
codeStacks,
linkedCodeMappings,
};
});

return {
scriptRanges,
Expand Down
2 changes: 2 additions & 0 deletions packages/language-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export interface VueCodeInformation extends CodeInformation {
__combineLastMappping?: boolean;
}

export type CodeAndStack = [code: Code, stack: string];

export type Code = Segment<VueCodeInformation>;

export interface VueCompilerOptions {
Expand Down
72 changes: 34 additions & 38 deletions packages/language-core/src/utils/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,19 @@ import { isGloballyWhitelisted } from '@vue/shared';
import type * as ts from 'typescript/lib/tsserverlibrary';
import { VueCompilerOptions } from '../types';

export function walkInterpolationFragment(
export function* eachInterpolationSegment(
ts: typeof import('typescript/lib/tsserverlibrary'),
code: string,
ast: ts.SourceFile,
cb: (fragment: string, offset: number | undefined, isJustForErrorMapping?: boolean) => void,
localVars: Map<string, number>,
identifiers: Set<string>,
vueOptions: VueCompilerOptions,
) {

let ctxVars: {
ctxVars: {
text: string,
isShorthand: boolean,
offset: number,
}[] = [];
}[] = []
): Generator<[fragment: string, offset: number | undefined, isJustForErrorMapping?: boolean]> {

const varCb = (id: ts.Identifier, isShorthand: boolean) => {
if (
Expand Down Expand Up @@ -44,71 +42,69 @@ export function walkInterpolationFragment(
if (ctxVars.length) {

if (ctxVars[0].isShorthand) {
cb(code.substring(0, ctxVars[0].offset + ctxVars[0].text.length), 0);
cb(': ', undefined);
yield [code.substring(0, ctxVars[0].offset + ctxVars[0].text.length), 0];
yield [': ', undefined];
}
else {
cb(code.substring(0, ctxVars[0].offset), 0);
yield [code.substring(0, ctxVars[0].offset), 0];
}

for (let i = 0; i < ctxVars.length - 1; i++) {

// fix https://github.com/vuejs/language-tools/issues/1205
// fix https://github.com/vuejs/language-tools/issues/1264
cb('', ctxVars[i + 1].offset, true);
yield ['', ctxVars[i + 1].offset, true];
if (vueOptions.experimentalUseElementAccessInTemplate) {
const varStart = ctxVars[i].offset;
const varEnd = ctxVars[i].offset + ctxVars[i].text.length;
cb('__VLS_ctx[', undefined);
cb('', varStart, true);
cb("'", undefined);
cb(code.substring(varStart, varEnd), varStart);
cb("'", undefined);
cb('', varEnd, true);
cb(']', undefined);
yield ['__VLS_ctx[', undefined];
yield ['', varStart, true];
yield ["'", undefined];
yield [code.substring(varStart, varEnd), varStart];
yield ["'", undefined];
yield ['', varEnd, true];
yield [']', undefined];
if (ctxVars[i + 1].isShorthand) {
cb(code.substring(varEnd, ctxVars[i + 1].offset + ctxVars[i + 1].text.length), varEnd);
cb(': ', undefined);
yield [code.substring(varEnd, ctxVars[i + 1].offset + ctxVars[i + 1].text.length), varEnd];
yield [': ', undefined];
}
else {
cb(code.substring(varEnd, ctxVars[i + 1].offset), varEnd);
yield [code.substring(varEnd, ctxVars[i + 1].offset), varEnd];
}
}
else {
cb('__VLS_ctx.', undefined);
yield ['__VLS_ctx.', undefined];
if (ctxVars[i + 1].isShorthand) {
cb(code.substring(ctxVars[i].offset, ctxVars[i + 1].offset + ctxVars[i + 1].text.length), ctxVars[i].offset);
cb(': ', undefined);
yield [code.substring(ctxVars[i].offset, ctxVars[i + 1].offset + ctxVars[i + 1].text.length), ctxVars[i].offset];
yield [': ', undefined];
}
else {
cb(code.substring(ctxVars[i].offset, ctxVars[i + 1].offset), ctxVars[i].offset);
yield [code.substring(ctxVars[i].offset, ctxVars[i + 1].offset), ctxVars[i].offset];
}
}
}

if (vueOptions.experimentalUseElementAccessInTemplate) {
const varStart = ctxVars[ctxVars.length - 1].offset;
const varEnd = ctxVars[ctxVars.length - 1].offset + ctxVars[ctxVars.length - 1].text.length;
cb('__VLS_ctx[', undefined);
cb('', varStart, true);
cb("'", undefined);
cb(code.substring(varStart, varEnd), varStart);
cb("'", undefined);
cb('', varEnd, true);
cb(']', undefined);
cb(code.substring(varEnd), varEnd);
yield ['__VLS_ctx[', undefined];
yield ['', varStart, true];
yield ["'", undefined];
yield [code.substring(varStart, varEnd), varStart];
yield ["'", undefined];
yield ['', varEnd, true];
yield [']', undefined];
yield [code.substring(varEnd), varEnd];
}
else {
cb('', ctxVars[ctxVars.length - 1].offset, true);
cb('__VLS_ctx.', undefined);
cb(code.substring(ctxVars[ctxVars.length - 1].offset), ctxVars[ctxVars.length - 1].offset);
yield ['', ctxVars[ctxVars.length - 1].offset, true];
yield ['__VLS_ctx.', undefined];
yield [code.substring(ctxVars[ctxVars.length - 1].offset), ctxVars[ctxVars.length - 1].offset];
}
}
else {
cb(code, 0);
yield [code, 0];
}

return ctxVars;
}

function walkIdentifiers(
Expand Down
4 changes: 2 additions & 2 deletions packages/language-service/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ export function getTemplateTagsAndAttrs(sourceFile: embedded.VirtualFile): Tags
const ast = sourceFile.sfc.template?.ast;
const tags: Tags = new Map();
if (ast) {
vue.walkElementNodes(ast, node => {
for (const node of vue.eachElementNode(ast)) {

if (!tags.has(node.tag)) {
tags.set(node.tag, { offsets: [], attrs: new Map() });
Expand Down Expand Up @@ -323,7 +323,7 @@ export function getTemplateTagsAndAttrs(sourceFile: embedded.VirtualFile): Tags
tag.attrs.get(name)!.offsets.push(offset);
}
}
});
}
}
return tags;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ServicePlugin, ServicePluginInstance } from '@volar/language-service';
import { VueFile, walkElementNodes, type CompilerDOM } from '@vue/language-core';
import { VueFile, eachElementNode, type CompilerDOM } from '@vue/language-core';
import type * as vscode from 'vscode-languageserver-protocol';

export function create(ts: typeof import('typescript/lib/tsserverlibrary')): ServicePlugin {
Expand All @@ -23,7 +23,7 @@ export function create(ts: typeof import('typescript/lib/tsserverlibrary')): Ser
const templateStartOffset = template!.startTagEnd;
const result: vscode.CodeAction[] = [];

walkElementNodes(template.ast, node => {
for (const node of eachElementNode(template.ast)) {
if (startOffset > templateStartOffset + node.loc.end.offset || endOffset < templateStartOffset + node.loc.start.offset) {
return;
}
Expand Down Expand Up @@ -128,7 +128,7 @@ export function create(ts: typeof import('typescript/lib/tsserverlibrary')): Ser
}
}
}
});
}

return result;
},
Expand Down