Skip to content

Commit 3f138d6

Browse files
committed
refactor(language-core): rewrite vueCompilerOptions resolution logic
1 parent 8b8bfbb commit 3f138d6

File tree

10 files changed

+152
-162
lines changed

10 files changed

+152
-162
lines changed

Diff for: packages/language-core/lib/parsers/vueCompilerOptions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { VueCompilerOptions } from '../types';
1+
import type { RawVueCompilerOptions } from '../types';
22

33
const syntaxReg = /^\s*@(?<key>.+?)\s+(?<value>.+?)\s*$/m;
44

5-
export function parseVueCompilerOptions(comments: string[]): Partial<VueCompilerOptions> | undefined {
5+
export function parseVueCompilerOptions(comments: string[]): RawVueCompilerOptions | undefined {
66
const entries = comments
77
.map(text => {
88
try {

Diff for: packages/language-core/lib/plugins/vue-tsx.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { parseScriptRanges } from '../parsers/scriptRanges';
88
import { parseScriptSetupRanges } from '../parsers/scriptSetupRanges';
99
import { parseVueCompilerOptions } from '../parsers/vueCompilerOptions';
1010
import type { Code, Sfc, VueLanguagePlugin } from '../types';
11-
import { resolveVueCompilerOptions } from '../utils/ts';
11+
import { CompilerOptionsResolver } from '../utils/ts';
1212

1313
export const tsCodegen = new WeakMap<Sfc, ReturnType<typeof createTsx>>();
1414

@@ -83,9 +83,12 @@ function createTsx(
8383
});
8484
const vueCompilerOptions = computed(() => {
8585
const options = parseVueCompilerOptions(_sfc.comments);
86-
return options
87-
? resolveVueCompilerOptions(options, ctx.vueCompilerOptions)
88-
: ctx.vueCompilerOptions;
86+
if (options) {
87+
const resolver = new CompilerOptionsResolver();
88+
resolver.addConfig(options, path.dirname(fileName));
89+
return resolver.build(ctx.vueCompilerOptions);
90+
}
91+
return ctx.vueCompilerOptions;
8992
});
9093
const scriptRanges = computed(() =>
9194
_sfc.script

Diff for: packages/language-core/lib/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export type { SFCParseResult } from '@vue/compiler-sfc';
1010
export { VueEmbeddedCode };
1111

1212
export type RawVueCompilerOptions = Partial<Omit<VueCompilerOptions, 'target' | 'plugins'>> & {
13-
target?: 'auto' | 2 | 2.7 | 3 | 3.3;
13+
target?: 'auto' | 2 | 2.7 | 3 | 3.3 | 3.5 | 99 | number;
1414
plugins?: string[];
1515
};
1616

Diff for: packages/language-core/lib/utils/ts.ts

+116-98
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,19 @@ export function createParsedCommandLineByJson(
2323
const proxyHost = proxyParseConfigHostForExtendConfigPaths(parseConfigHost);
2424
ts.parseJsonConfigFileContent(json, proxyHost.host, rootDir, {}, configFileName);
2525

26-
let vueOptions: Partial<VueCompilerOptions> = {};
26+
const resolver = new CompilerOptionsResolver();
2727

2828
for (const extendPath of proxyHost.extendConfigPaths.reverse()) {
2929
try {
30-
vueOptions = {
31-
...vueOptions,
32-
...getPartialVueCompilerOptions(ts, ts.readJsonConfigFile(extendPath, proxyHost.host.readFile)),
33-
};
30+
const configFile = ts.readJsonConfigFile(extendPath, proxyHost.host.readFile);
31+
const obj = ts.convertToObject(configFile, []);
32+
const rawOptions: RawVueCompilerOptions = obj?.vueCompilerOptions ?? {};
33+
resolver.addConfig(rawOptions, path.dirname(configFile.fileName));
3434
} catch (err) { }
3535
}
3636

37-
const resolvedVueOptions = resolveVueCompilerOptions(vueOptions);
37+
const resolvedVueOptions = resolver.build();
38+
3839
if (skipGlobalTypesSetup) {
3940
resolvedVueOptions.__setupedGlobalTypes = true;
4041
}
@@ -78,18 +79,19 @@ export function createParsedCommandLine(
7879
const config = ts.readJsonConfigFile(tsConfigPath, proxyHost.host.readFile);
7980
ts.parseJsonSourceFileConfigFileContent(config, proxyHost.host, path.dirname(tsConfigPath), {}, tsConfigPath);
8081

81-
let vueOptions: Partial<VueCompilerOptions> = {};
82+
const resolver = new CompilerOptionsResolver();
8283

8384
for (const extendPath of proxyHost.extendConfigPaths.reverse()) {
8485
try {
85-
vueOptions = {
86-
...vueOptions,
87-
...getPartialVueCompilerOptions(ts, ts.readJsonConfigFile(extendPath, proxyHost.host.readFile)),
88-
};
86+
const configFile = ts.readJsonConfigFile(extendPath, proxyHost.host.readFile);
87+
const obj = ts.convertToObject(configFile, []);
88+
const rawOptions: RawVueCompilerOptions = obj?.vueCompilerOptions ?? {};
89+
resolver.addConfig(rawOptions, path.dirname(configFile.fileName));
8990
} catch (err) { }
9091
}
9192

92-
const resolvedVueOptions = resolveVueCompilerOptions(vueOptions);
93+
const resolvedVueOptions = resolver.build();
94+
9395
if (skipGlobalTypesSetup) {
9496
resolvedVueOptions.__setupedGlobalTypes = true;
9597
}
@@ -126,7 +128,7 @@ export function createParsedCommandLine(
126128
return {
127129
fileNames: [],
128130
options: {},
129-
vueOptions: resolveVueCompilerOptions({}),
131+
vueOptions: getDefaultCompilerOptions(),
130132
errors: [],
131133
};
132134
}
@@ -153,76 +155,115 @@ function proxyParseConfigHostForExtendConfigPaths(parseConfigHost: ts.ParseConfi
153155
};
154156
}
155157

156-
function getPartialVueCompilerOptions(
157-
ts: typeof import('typescript'),
158-
tsConfigSourceFile: ts.TsConfigSourceFile
159-
) {
160-
161-
const folder = path.dirname(tsConfigSourceFile.fileName);
162-
const obj = ts.convertToObject(tsConfigSourceFile, []);
163-
const rawOptions: RawVueCompilerOptions = obj?.vueCompilerOptions ?? {};
164-
const result: Partial<VueCompilerOptions> = {
165-
...rawOptions as any,
166-
};
167-
const target = rawOptions.target ?? 'auto';
158+
export class CompilerOptionsResolver {
159+
options: Omit<RawVueCompilerOptions, 'target' | 'plugin'> = {};
160+
fallbackTarget: number | undefined;
161+
target: number | undefined;
162+
plugins: VueLanguagePlugin[] = [];
168163

169-
if (target === 'auto') {
170-
const resolvedPath = resolvePath('vue/package.json');
171-
if (resolvedPath) {
172-
const vuePackageJson = require(resolvedPath);
173-
const versionNumbers = vuePackageJson.version.split('.');
174-
result.target = Number(versionNumbers[0] + '.' + versionNumbers[1]);
164+
addConfig(options: RawVueCompilerOptions, rootDir: string) {
165+
for (const key in options) {
166+
switch (key) {
167+
case 'target':
168+
const target = options.target!;
169+
if (typeof target === 'string') {
170+
this.target = findVueVersion(rootDir);
171+
}
172+
else {
173+
this.target = target;
174+
}
175+
break;
176+
case 'plugins':
177+
this.plugins = (options.plugins ?? [])
178+
.map<VueLanguagePlugin>((pluginPath: string) => {
179+
try {
180+
const resolvedPath = resolvePath(pluginPath, rootDir);
181+
if (resolvedPath) {
182+
const plugin = require(resolvedPath);
183+
plugin.__moduleName = pluginPath;
184+
return plugin;
185+
}
186+
else {
187+
console.warn('[Vue] Load plugin failed:', pluginPath);
188+
}
189+
}
190+
catch (error) {
191+
console.warn('[Vue] Resolve plugin path failed:', pluginPath, error);
192+
}
193+
return [];
194+
});
195+
break;
196+
default:
197+
// @ts-expect-error
198+
this.options[key] = options[key];
199+
break;
200+
}
175201
}
176-
else {
177-
// console.warn('Load vue/package.json failed from', folder);
202+
if (this.target === undefined) {
203+
this.fallbackTarget = findVueVersion(rootDir);
178204
}
179205
}
180-
else {
181-
result.target = target;
182-
}
183-
if (rawOptions.plugins) {
184-
const plugins = rawOptions.plugins
185-
.map<VueLanguagePlugin>((pluginPath: string) => {
186-
try {
187-
const resolvedPath = resolvePath(pluginPath);
188-
if (resolvedPath) {
189-
const plugin = require(resolvedPath);
190-
plugin.__moduleName = pluginPath;
191-
return plugin;
192-
}
193-
else {
194-
console.warn('[Vue] Load plugin failed:', pluginPath);
206+
207+
build(defaults?: VueCompilerOptions): VueCompilerOptions {
208+
const target = this.target ?? this.fallbackTarget;
209+
defaults ??= getDefaultCompilerOptions(target, this.options.lib);
210+
return {
211+
...defaults,
212+
...this.options,
213+
plugins: this.plugins,
214+
macros: {
215+
...defaults.macros,
216+
...this.options.macros,
217+
},
218+
composables: {
219+
...defaults.composables,
220+
...this.options.composables,
221+
},
222+
// https://github.com/vuejs/vue-next/blob/master/packages/compiler-dom/src/transforms/vModel.ts#L49-L51
223+
// https://vuejs.org/guide/essentials/forms.html#form-input-bindings
224+
experimentalModelPropName: Object.fromEntries(Object.entries(
225+
this.options.experimentalModelPropName ?? defaults.experimentalModelPropName ?? {
226+
'': {
227+
input: true
228+
},
229+
value: {
230+
input: { type: 'text' },
231+
textarea: true,
232+
select: true
195233
}
196234
}
197-
catch (error) {
198-
console.warn('[Vue] Resolve plugin path failed:', pluginPath, error);
199-
}
200-
return [];
201-
});
202-
203-
result.plugins = plugins;
235+
).map(([k, v]) => [camelize(k), v])),
236+
};
204237
}
238+
}
205239

206-
return result;
240+
function findVueVersion(rootDir: string) {
241+
const resolvedPath = resolvePath('vue/package.json', rootDir);
242+
if (resolvedPath) {
243+
const vuePackageJson = require(resolvedPath);
244+
const versionNumbers = vuePackageJson.version.split('.');
245+
return Number(versionNumbers[0] + '.' + versionNumbers[1]);
246+
}
247+
else {
248+
// console.warn('Load vue/package.json failed from', folder);
249+
}
250+
}
207251

208-
function resolvePath(scriptPath: string) {
209-
try {
210-
if (require?.resolve) {
211-
return require.resolve(scriptPath, { paths: [folder] });
212-
}
213-
else {
214-
// console.warn('failed to resolve path:', scriptPath, 'require.resolve is not supported in web');
215-
}
252+
function resolvePath(scriptPath: string, root: string) {
253+
try {
254+
if (require?.resolve) {
255+
return require.resolve(scriptPath, { paths: [root] });
216256
}
217-
catch (error) {
218-
// console.warn(error);
257+
else {
258+
// console.warn('failed to resolve path:', scriptPath, 'require.resolve is not supported in web');
219259
}
220260
}
261+
catch (error) {
262+
// console.warn(error);
263+
}
221264
}
222265

223-
function getDefaultOptions(options: Partial<VueCompilerOptions>): VueCompilerOptions {
224-
const target = options.target ?? 3.3;
225-
const lib = options.lib ?? 'vue';
266+
export function getDefaultCompilerOptions(target = 99, lib = 'vue'): VueCompilerOptions {
226267
return {
227268
target,
228269
lib,
@@ -258,38 +299,15 @@ function getDefaultOptions(options: Partial<VueCompilerOptions>): VueCompilerOpt
258299
experimentalResolveStyleCssClasses: 'scoped',
259300
experimentalModelPropName: null!
260301
};
261-
};
302+
}
262303

263-
export function resolveVueCompilerOptions(
264-
options: Partial<VueCompilerOptions>,
265-
defaults: VueCompilerOptions = getDefaultOptions(options)
266-
): VueCompilerOptions {
304+
/**
305+
* @deprecated use `getDefaultCompilerOptions` instead
306+
*/
307+
export function resolveVueCompilerOptions(options: Partial<VueCompilerOptions>): VueCompilerOptions {
267308
return {
268-
...defaults,
309+
...getDefaultCompilerOptions(options.target, options.lib),
269310
...options,
270-
macros: {
271-
...defaults.macros,
272-
...options.macros,
273-
},
274-
composables: {
275-
...defaults.composables,
276-
...options.composables,
277-
},
278-
279-
// https://github.com/vuejs/vue-next/blob/master/packages/compiler-dom/src/transforms/vModel.ts#L49-L51
280-
// https://vuejs.org/guide/essentials/forms.html#form-input-bindings
281-
experimentalModelPropName: Object.fromEntries(Object.entries(
282-
options.experimentalModelPropName ?? defaults.experimentalModelPropName ?? {
283-
'': {
284-
input: true
285-
},
286-
value: {
287-
input: { type: 'text' },
288-
textarea: true,
289-
select: true
290-
}
291-
}
292-
).map(([k, v]) => [camelize(k), v])),
293311
};
294312
}
295313

Diff for: packages/language-server/lib/initialize.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { LanguageServer } from '@volar/language-server';
22
import { createTypeScriptProject } from '@volar/language-server/node';
3-
import { createParsedCommandLine, createVueLanguagePlugin, generateGlobalTypes, getAllExtensions, resolveVueCompilerOptions, VueCompilerOptions } from '@vue/language-core';
3+
import { createParsedCommandLine, createVueLanguagePlugin, generateGlobalTypes, getAllExtensions, getDefaultCompilerOptions, VueCompilerOptions } from '@vue/language-core';
44
import { Disposable, getFullLanguageServicePlugins, InitializeParams } from '@vue/language-service';
55
import type * as ts from 'typescript';
66

@@ -35,7 +35,7 @@ export function initialize(
3535
}
3636
else {
3737
compilerOptions = ts.getDefaultCompilerOptions();
38-
vueCompilerOptions = resolveVueCompilerOptions({});
38+
vueCompilerOptions = getDefaultCompilerOptions();
3939
}
4040
vueCompilerOptions.__test = params.initializationOptions.typescript.disableAutoImportCache;
4141
updateFileWatcher(vueCompilerOptions);

Diff for: packages/language-server/node.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createConnection, createServer, loadTsdkByPath } from '@volar/language-server/node';
2-
import { createParsedCommandLine, createVueLanguagePlugin, resolveVueCompilerOptions } from '@vue/language-core';
2+
import { createParsedCommandLine, createVueLanguagePlugin, getDefaultCompilerOptions } from '@vue/language-core';
33
import { getHybridModeLanguageServicePlugins } from '@vue/language-service';
44
import * as namedPipeClient from '@vue/typescript-plugin/lib/client';
55
import { createHybridModeProject } from './lib/hybridModeProject';
@@ -23,7 +23,7 @@ connection.onInitialize(params => {
2323
const commandLine = configFileName
2424
? createParsedCommandLine(ts, ts.sys, configFileName)
2525
: {
26-
vueOptions: resolveVueCompilerOptions({}),
26+
vueOptions: getDefaultCompilerOptions(),
2727
options: ts.getDefaultCompilerOptions(),
2828
};
2929
commandLine.vueOptions.__test = params.initializationOptions.typescript.disableAutoImportCache;

Diff for: packages/language-service/tests/utils/format.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import * as kit from '@volar/kit';
22
import * as ts from 'typescript';
33
import { describe, expect, it } from 'vitest';
44
import type { URI } from 'vscode-uri';
5-
import { createVueLanguagePlugin, getFullLanguageServicePlugins, resolveVueCompilerOptions } from '../..';
5+
import { createVueLanguagePlugin, getDefaultCompilerOptions, getFullLanguageServicePlugins } from '../..';
66

7-
const resolvedVueOptions = resolveVueCompilerOptions({});
7+
const resolvedVueOptions = getDefaultCompilerOptions();
88
const vueLanguagePlugin = createVueLanguagePlugin<URI>(
99
ts,
1010
{},

Diff for: packages/tsc/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function run(tscPath = require.resolve('typescript/lib/tsc')) {
1515
const { configFilePath } = options.options;
1616
const vueOptions = typeof configFilePath === 'string'
1717
? vue.createParsedCommandLine(ts, ts.sys, configFilePath.replace(windowsPathReg, '/')).vueOptions
18-
: vue.resolveVueCompilerOptions({});
18+
: vue.getDefaultCompilerOptions();
1919
const allExtensions = vue.getAllExtensions(vueOptions);
2020
if (
2121
runExtensions.length === allExtensions.length

0 commit comments

Comments
 (0)