Skip to content

Commit 62d75c2

Browse files
feat(language-core): proper support for class component (#4354)
1 parent 6943743 commit 62d75c2

File tree

9 files changed

+83
-85
lines changed

9 files changed

+83
-85
lines changed

packages/language-core/lib/codegen/script/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export function* generateScript(options: ScriptCodegenOptions): Generator<Code>
6464
yield* generateSrc(options.sfc.script, options.sfc.script.src);
6565
}
6666
if (options.sfc.script && options.scriptRanges) {
67-
const { exportDefault } = options.scriptRanges;
67+
const { exportDefault, classBlockEnd } = options.scriptRanges;
6868
const isExportRawObject = exportDefault
6969
&& options.sfc.script.content[exportDefault.expression.start] === '{';
7070
if (options.sfc.scriptSetup && options.scriptSetupRanges) {
@@ -117,6 +117,11 @@ export function* generateScript(options: ScriptCodegenOptions): Generator<Code>
117117
yield options.vueCompilerOptions.optionsWrapper[1];
118118
yield generateSfcBlockSection(options.sfc.script, exportDefault.expression.end, options.sfc.script.content.length, codeFeatures.all);
119119
}
120+
else if (classBlockEnd !== undefined) {
121+
yield generateSfcBlockSection(options.sfc.script, 0, classBlockEnd, codeFeatures.all);
122+
yield* generateTemplate(options, ctx, true);
123+
yield generateSfcBlockSection(options.sfc.script, classBlockEnd, options.sfc.script.content.length, codeFeatures.all);
124+
}
120125
else {
121126
yield generateSfcBlockSection(options.sfc.script, 0, options.sfc.script.content.length, codeFeatures.all);
122127
}
@@ -132,7 +137,7 @@ export function* generateScript(options: ScriptCodegenOptions): Generator<Code>
132137
yield `\ntype __VLS_IntrinsicElementsCompletion = __VLS_IntrinsicElements${endOfLine}`;
133138

134139
if (!ctx.generatedTemplate) {
135-
yield* generateTemplate(options, ctx);
140+
yield* generateTemplate(options, ctx, false);
136141
}
137142

138143
if (options.sfc.scriptSetup) {

packages/language-core/lib/codegen/script/scriptSetup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ function* generateSetupFunction(
238238

239239
yield* generateComponentProps(options, ctx, scriptSetup, scriptSetupRanges, definePropMirrors);
240240
yield* generateModelEmits(options, scriptSetup, scriptSetupRanges);
241-
yield* generateTemplate(options, ctx);
241+
yield* generateTemplate(options, ctx, false);
242242

243243
if (syntax) {
244244
if (!options.vueCompilerOptions.skipTemplateCodegen && (options.templateCodegen?.hasSlot || scriptSetupRanges?.slots.define)) {

packages/language-core/lib/codegen/script/template.ts

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,24 @@ import { combineLastMapping } from '../common';
1212
export function* generateTemplate(
1313
options: ScriptCodegenOptions,
1414
ctx: ScriptCodegenContext,
15+
isClassComponent: boolean,
1516
): Generator<Code> {
16-
1717
ctx.generatedTemplate = true;
1818

1919
if (!options.vueCompilerOptions.skipTemplateCodegen) {
20+
const templateCodegenCtx = createTemplateCodegenContext();
21+
if (isClassComponent) {
22+
yield `__VLS_template() {${newLine}`;
23+
}
24+
else {
25+
yield `function __VLS_template() {${newLine}`;
26+
}
27+
yield* generateCtx(options, ctx, isClassComponent);
28+
yield* generateTemplateContext(options, templateCodegenCtx);
2029
yield* generateExportOptions(options);
2130
yield* generateConstNameOption(options);
22-
yield `function __VLS_template() {${newLine}`;
23-
const templateCodegenCtx = createTemplateCodegenContext();
24-
yield* generateTemplateContext(options, ctx, templateCodegenCtx);
25-
yield `}${newLine}`;
2631
yield* generateInternalComponent(options, ctx, templateCodegenCtx);
32+
yield `}${newLine}`;
2733
}
2834
else {
2935
yield `function __VLS_template() {${newLine}`;
@@ -54,7 +60,6 @@ function* generateExportOptions(options: ScriptCodegenOptions): Generator<Code>
5460
}
5561

5662
function* generateConstNameOption(options: ScriptCodegenOptions): Generator<Code> {
57-
yield newLine;
5863
if (options.sfc.script && options.scriptRanges?.exportDefault?.nameOption) {
5964
const nameOption = options.scriptRanges.exportDefault.nameOption;
6065
yield `const __VLS_name = `;
@@ -69,37 +74,50 @@ function* generateConstNameOption(options: ScriptCodegenOptions): Generator<Code
6974
}
7075
}
7176

72-
function* generateTemplateContext(
77+
function* generateCtx(
7378
options: ScriptCodegenOptions,
7479
ctx: ScriptCodegenContext,
75-
templateCodegenCtx: TemplateCodegenContext,
80+
isClassComponent: boolean,
7681
): Generator<Code> {
77-
78-
const useGlobalThisTypeInCtx = options.fileBaseName.endsWith('.html');
79-
80-
yield `let __VLS_ctx!: ${useGlobalThisTypeInCtx ? 'typeof globalThis &' : ''}`;
81-
yield `InstanceType<__VLS_PickNotAny<typeof __VLS_internalComponent, new () => {}>> & {${newLine}`;
82-
82+
yield `let __VLS_ctx!: `;
83+
if (options.vueCompilerOptions.petiteVueExtensions.some(ext => options.fileBaseName.endsWith(ext))) {
84+
yield `typeof globalThis & `;
85+
}
86+
if (!isClassComponent) {
87+
yield `InstanceType<__VLS_PickNotAny<typeof __VLS_internalComponent, new () => {}>>`;
88+
}
89+
else {
90+
yield `typeof this`;
91+
}
8392
/* CSS Module */
84-
for (let i = 0; i < options.sfc.styles.length; i++) {
85-
const style = options.sfc.styles[i];
86-
if (style.module) {
87-
yield `${style.module}: Record<string, string> & ${ctx.helperTypes.Prettify.name}<{}`;
88-
for (const className of style.classNames) {
89-
yield* generateCssClassProperty(
90-
i,
91-
className.text,
92-
className.offset,
93-
'string',
94-
false,
95-
true,
96-
);
93+
if (options.sfc.styles.some(style => style.module)) {
94+
yield `& {${newLine}`;
95+
for (let i = 0; i < options.sfc.styles.length; i++) {
96+
const style = options.sfc.styles[i];
97+
if (style.module) {
98+
yield `${style.module}: Record<string, string> & ${ctx.helperTypes.Prettify.name}<{}`;
99+
for (const className of style.classNames) {
100+
yield* generateCssClassProperty(
101+
i,
102+
className.text,
103+
className.offset,
104+
'string',
105+
false,
106+
true,
107+
);
108+
}
109+
yield `>${endOfLine}`;
97110
}
98-
yield `>${endOfLine}`;
99111
}
112+
yield `}`;
100113
}
101-
yield `}${endOfLine}`;
114+
yield endOfLine;
115+
}
102116

117+
function* generateTemplateContext(
118+
options: ScriptCodegenOptions,
119+
templateCodegenCtx: TemplateCodegenContext,
120+
): Generator<Code> {
103121
/* Components */
104122
yield `/* Components */${newLine}`;
105123
yield `let __VLS_otherComponents!: NonNullable<typeof __VLS_internalComponent extends { components: infer C } ? C : {}> & typeof __VLS_componentsOption${endOfLine}`;
@@ -195,7 +213,6 @@ function* generateCssVars(options: ScriptCodegenOptions, ctx: TemplateCodegenCon
195213
for (const cssBind of style.cssVars) {
196214
for (const [segment, offset, onlyError] of forEachInterpolationSegment(
197215
options.ts,
198-
options.vueCompilerOptions,
199216
ctx,
200217
cssBind.text,
201218
cssBind.offset,

packages/language-core/lib/codegen/template/interpolation.ts

Lines changed: 9 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { isGloballyWhitelisted } from '@vue/shared';
22
import type * as ts from 'typescript';
33
import { getNodeText, getStartEnd } from '../../parsers/scriptSetupRanges';
4-
import type { Code, VueCodeInformation, VueCompilerOptions } from '../../types';
4+
import type { Code, VueCodeInformation } from '../../types';
55
import { collectVars, createTsAst } from '../common';
66
import type { TemplateCodegenContext } from './context';
77
import type { TemplateCodegenOptions } from './index';
@@ -25,7 +25,6 @@ export function* generateInterpolation(
2525
}[] = [];
2626
for (let [section, offset, onlyError] of forEachInterpolationSegment(
2727
options.ts,
28-
options.vueCompilerOptions,
2928
ctx,
3029
code,
3130
start !== undefined ? start - prefix.length : undefined,
@@ -72,7 +71,6 @@ export function* generateInterpolation(
7271

7372
export function* forEachInterpolationSegment(
7473
ts: typeof import('typescript'),
75-
vueOptions: VueCompilerOptions,
7674
ctx: TemplateCodegenContext,
7775
code: string,
7876
offset: number | undefined,
@@ -128,53 +126,19 @@ export function* forEachInterpolationSegment(
128126
// fix https://github.com/vuejs/language-tools/issues/1205
129127
// fix https://github.com/vuejs/language-tools/issues/1264
130128
yield ['', ctxVars[i + 1].offset, true];
131-
if (vueOptions.experimentalUseElementAccessInTemplate) {
132-
const varStart = ctxVars[i].offset;
133-
const varEnd = ctxVars[i].offset + ctxVars[i].text.length;
134-
yield ['__VLS_ctx[', undefined];
135-
yield ['', varStart, true];
136-
yield ["'", undefined];
137-
yield [code.substring(varStart, varEnd), varStart];
138-
yield ["'", undefined];
139-
yield ['', varEnd, true];
140-
yield [']', undefined];
141-
if (ctxVars[i + 1].isShorthand) {
142-
yield [code.substring(varEnd, ctxVars[i + 1].offset + ctxVars[i + 1].text.length), varEnd];
143-
yield [': ', undefined];
144-
}
145-
else {
146-
yield [code.substring(varEnd, ctxVars[i + 1].offset), varEnd];
147-
}
129+
yield ['__VLS_ctx.', undefined];
130+
if (ctxVars[i + 1].isShorthand) {
131+
yield [code.substring(ctxVars[i].offset, ctxVars[i + 1].offset + ctxVars[i + 1].text.length), ctxVars[i].offset];
132+
yield [': ', undefined];
148133
}
149134
else {
150-
yield ['__VLS_ctx.', undefined];
151-
if (ctxVars[i + 1].isShorthand) {
152-
yield [code.substring(ctxVars[i].offset, ctxVars[i + 1].offset + ctxVars[i + 1].text.length), ctxVars[i].offset];
153-
yield [': ', undefined];
154-
}
155-
else {
156-
yield [code.substring(ctxVars[i].offset, ctxVars[i + 1].offset), ctxVars[i].offset];
157-
}
135+
yield [code.substring(ctxVars[i].offset, ctxVars[i + 1].offset), ctxVars[i].offset];
158136
}
159137
}
160138

161-
if (vueOptions.experimentalUseElementAccessInTemplate) {
162-
const varStart = ctxVars[ctxVars.length - 1].offset;
163-
const varEnd = ctxVars[ctxVars.length - 1].offset + ctxVars[ctxVars.length - 1].text.length;
164-
yield ['__VLS_ctx[', undefined];
165-
yield ['', varStart, true];
166-
yield ["'", undefined];
167-
yield [code.substring(varStart, varEnd), varStart];
168-
yield ["'", undefined];
169-
yield ['', varEnd, true];
170-
yield [']', undefined];
171-
yield [code.substring(varEnd), varEnd];
172-
}
173-
else {
174-
yield ['', ctxVars[ctxVars.length - 1].offset, true];
175-
yield ['__VLS_ctx.', undefined];
176-
yield [code.substring(ctxVars[ctxVars.length - 1].offset), ctxVars[ctxVars.length - 1].offset];
177-
}
139+
yield ['', ctxVars[ctxVars.length - 1].offset, true];
140+
yield ['__VLS_ctx.', undefined];
141+
yield [code.substring(ctxVars[ctxVars.length - 1].offset), ctxVars[ctxVars.length - 1].offset];
178142
}
179143
else {
180144
yield [code, 0];

packages/language-core/lib/parsers/scriptRanges.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
1414
componentsOptionNode: ts.ObjectLiteralExpression | undefined,
1515
nameOption: TextRange | undefined,
1616
}) | undefined;
17+
let classBlockEnd: number | undefined;
1718

1819
const bindings = hasScriptSetup ? parseBindingRanges(ts, ast) : [];
1920

@@ -61,10 +62,19 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
6162
};
6263
}
6364
}
65+
66+
if (
67+
ts.isClassDeclaration(raw)
68+
&& raw.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword)
69+
&& raw.modifiers?.some(mod => mod.kind === ts.SyntaxKind.DefaultKeyword)
70+
) {
71+
classBlockEnd = raw.end - 1;
72+
}
6473
});
6574

6675
return {
6776
exportDefault,
77+
classBlockEnd,
6878
bindings,
6979
};
7080

packages/language-core/lib/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ export interface VueCompilerOptions {
5656
experimentalDefinePropProposal: 'kevinEdition' | 'johnsonEdition' | false;
5757
experimentalResolveStyleCssClasses: 'scoped' | 'always' | 'never';
5858
experimentalModelPropName: Record<string, Record<string, boolean | Record<string, string> | Record<string, string>[]>>;
59-
experimentalUseElementAccessInTemplate: boolean;
6059
}
6160

6261
export const pluginVersion = 2;

packages/language-core/lib/utils/ts.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,5 @@ export function resolveVueCompilerOptions(vueOptions: Partial<VueCompilerOptions
262262
select: true
263263
}
264264
},
265-
experimentalUseElementAccessInTemplate: vueOptions.experimentalUseElementAccessInTemplate ?? false,
266265
};
267266
}

packages/language-core/schemas/vue-tsconfig.schema.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,6 @@
9090
],
9191
"markdownDescription": "https://github.com/vuejs/language-tools/issues/1038, https://github.com/vuejs/language-tools/issues/1121"
9292
},
93-
"experimentalUseElementAccessInTemplate": {
94-
"type": "boolean",
95-
"default": false,
96-
"markdownDescription": "https://github.com/vuejs/language-tools/issues/997"
97-
},
9893
"experimentalModelPropName": {
9994
"type": "object",
10095
"default": {

test-workspace/tsc/vue3/#997/main.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<template>
2+
{{ count }}
3+
</template>
4+
5+
<script lang="ts">
6+
export default class Counter {
7+
private count = 0
8+
}
9+
</script>

0 commit comments

Comments
 (0)