Skip to content

Commit

Permalink
refactor(langauge-core): codegen based on Generator (#3778)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Dec 21, 2023
1 parent c7ec93f commit 7dc8c9d
Show file tree
Hide file tree
Showing 8 changed files with 1,541 additions and 1,587 deletions.
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

0 comments on commit 7dc8c9d

Please sign in to comment.