Skip to content

Commit

Permalink
fix(language-core): avoid globalTypesHolder being specified from a …
Browse files Browse the repository at this point in the history
…`node_modules` file (#3990)
  • Loading branch information
johnsoncodehk committed Mar 5, 2024
1 parent c4cbb03 commit b8afeed
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 114 deletions.
14 changes: 14 additions & 0 deletions packages/component-meta/lib/base.ts
Expand Up @@ -152,6 +152,20 @@ export function baseCreate(
const vueLanguagePlugin = vue.createVueLanguagePlugin(
ts,
id => id,
fileName => {
if (ts.sys.useCaseSensitiveFileNames) {
return host.getScriptFileNames().includes(fileName) ?? false;
}
else {
const lowerFileName = fileName.toLowerCase();
for (const rootFile of host.getScriptFileNames()) {
if (rootFile.toLowerCase() === lowerFileName) {
return true;
}
}
return false;
}
},
host.getCompilationSettings(),
vueCompilerOptions,
);
Expand Down
10 changes: 5 additions & 5 deletions packages/language-core/lib/generators/script.ts
Expand Up @@ -437,7 +437,7 @@ type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
let propName = 'modelValue';
if (defineProp.name) {
propName = scriptSetup.content.substring(defineProp.name.start, defineProp.name.end);
propName = propName.replace(/['"]+/g, '')
propName = propName.replace(/['"]+/g, '');
}
yield _(`'update:${propName}': [${propName}:`);
if (defineProp.type) {
Expand Down Expand Up @@ -545,7 +545,7 @@ type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
}
yield _(`;\n`);

yield* generateModelEmits()
yield* generateModelEmits();

yield _(`let __VLS_fnPropsDefineComponent!: InstanceType<typeof __VLS_fnComponent>['$props'];\n`);
yield _(`let __VLS_fnPropsSlots!: `);
Expand Down Expand Up @@ -755,7 +755,7 @@ type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
yield _(`};\n`);
}

yield* generateModelEmits()
yield* generateModelEmits();
yield* generateTemplate(functional);

if (mode === 'return' || mode === 'export') {
Expand Down Expand Up @@ -856,9 +856,9 @@ type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
}
yield _(`},\n`);
}
yield _(`emits: ({} as __VLS_NormalizeEmits<typeof __VLS_modelEmitsType`)
yield _(`emits: ({} as __VLS_NormalizeEmits<typeof __VLS_modelEmitsType`);
if (ranges.emits.define) {
yield _(` & typeof `)
yield _(` & typeof `);
yield _(ranges.emits.name ?? '__VLS_emit');
}
yield _(`>),\n`);
Expand Down
128 changes: 68 additions & 60 deletions packages/language-core/lib/languageModule.ts
@@ -1,24 +1,25 @@
import { forEachEmbeddedCode, type LanguagePlugin } from '@volar/language-core';
import type * as ts from 'typescript';
import { createPluginContext, getDefaultVueLanguagePlugins } from './plugins';
import { getDefaultVueLanguagePlugins } from './plugins';
import type { VueCompilerOptions, VueLanguagePlugin } from './types';
import { resolveVueCompilerOptions } from './utils/ts';
import { VueGeneratedCode } from './virtualFile/vueFile';
import * as CompilerDOM from '@vue/compiler-dom';
import * as CompilerVue2 from './utils/vue2TemplateCompiler';

const fileRegistries: {
const normalFileRegistries: {
key: string;
plugins: VueLanguagePlugin[];
files: Map<string, VueGeneratedCode>;
}[] = [];
const holderFileRegistries: typeof normalFileRegistries = [];

function getVueFileRegistry(key: string, plugins: VueLanguagePlugin[]) {

function getVueFileRegistry(isGlobalTypesHolder: boolean, key: string, plugins: VueLanguagePlugin[]) {
const fileRegistries = isGlobalTypesHolder ? holderFileRegistries : normalFileRegistries;
let fileRegistry = fileRegistries.find(r =>
r.key === key
&& r.plugins.length === plugins.length
&& r.plugins.every(plugin => plugins.includes(plugin))
)?.files;

if (!fileRegistry) {
fileRegistry = new Map();
fileRegistries.push({
Expand All @@ -27,18 +28,15 @@ function getVueFileRegistry(key: string, plugins: VueLanguagePlugin[]) {
files: fileRegistry,
});
}

return fileRegistry;
}

function getFileRegistryKey(
compilerOptions: ts.CompilerOptions,
vueCompilerOptions: VueCompilerOptions,
plugins: ReturnType<VueLanguagePlugin>[],
globalTypesHolder: string | undefined,
) {
const values = [
globalTypesHolder,
...Object.keys(vueCompilerOptions)
.sort()
.filter(key => key !== 'plugins')
Expand All @@ -53,21 +51,27 @@ function getFileRegistryKey(
export function createVueLanguagePlugin(
ts: typeof import('typescript'),
getFileName: (fileId: string) => string,
compilerOptions: ts.CompilerOptions = {},
_vueCompilerOptions: Partial<VueCompilerOptions> = {},
isValidGlobalTypesHolder: (fileName: string) => boolean,
compilerOptions: ts.CompilerOptions,
vueCompilerOptions: VueCompilerOptions,
codegenStack: boolean = false,
globalTypesHolder?: string
): LanguagePlugin<VueGeneratedCode> {

const vueCompilerOptions = resolveVueCompilerOptions(_vueCompilerOptions);
const allowLanguageIds = new Set(['vue']);
const pluginContext = createPluginContext(
ts,
const pluginContext: Parameters<VueLanguagePlugin>[0] = {
modules: {
'@vue/compiler-dom': vueCompilerOptions.target < 3
? {
...CompilerDOM,
compile: CompilerVue2.compile,
}
: CompilerDOM,
typescript: ts,
},
compilerOptions,
vueCompilerOptions,
codegenStack,
globalTypesHolder,
);
globalTypesHolder: undefined,
};
const plugins = getDefaultVueLanguagePlugins(pluginContext);

if (vueCompilerOptions.extensions.includes('.md')) {
Expand All @@ -77,63 +81,59 @@ export function createVueLanguagePlugin(
allowLanguageIds.add('html');
}

let generatedCodeRegistry: Map<string, VueGeneratedCode> | undefined;

return {
createVirtualCode(fileId, languageId, snapshot) {
if (allowLanguageIds.has(languageId)) {

const fileName = getFileName(fileId);

if (!generatedCodeRegistry) {

pluginContext.globalTypesHolder ??= fileName;

generatedCodeRegistry = getVueFileRegistry(
getFileRegistryKey(compilerOptions, vueCompilerOptions, plugins, pluginContext.globalTypesHolder),
vueCompilerOptions.plugins,
);
if (!pluginContext.globalTypesHolder && isValidGlobalTypesHolder(fileName)) {
pluginContext.globalTypesHolder = fileName;
}

if (generatedCodeRegistry.has(fileId)) {
const reusedResult = generatedCodeRegistry.get(fileId)!;
reusedResult.update(snapshot);
return reusedResult;
const fileRegistry = getFileRegistry(pluginContext.globalTypesHolder === fileName);
const code = fileRegistry.get(fileId);
if (code) {
code.update(snapshot);
return code;
}
else {
const code = new VueGeneratedCode(
fileName,
languageId,
snapshot,
vueCompilerOptions,
plugins,
ts,
codegenStack,
);
fileRegistry.set(fileId, code);
return code;
}
const vueFile = new VueGeneratedCode(fileName, languageId, snapshot, vueCompilerOptions, plugins, ts, codegenStack);
generatedCodeRegistry.set(fileId, vueFile);
return vueFile;
}
},
updateVirtualCode(_fileId, vueFile, snapshot) {
vueFile.update(snapshot);
return vueFile;
updateVirtualCode(_fileId, code, snapshot) {
code.update(snapshot);
return code;
},
disposeVirtualCode(fileId, vueFile, files) {
generatedCodeRegistry?.delete(fileId);
if (vueFile.fileName === pluginContext.globalTypesHolder) {
if (generatedCodeRegistry?.size) {
for (const [fileName, file] of generatedCodeRegistry!) {
pluginContext.globalTypesHolder = fileName;

generatedCodeRegistry = getVueFileRegistry(
getFileRegistryKey(compilerOptions, vueCompilerOptions, plugins, pluginContext.globalTypesHolder),
vueCompilerOptions.plugins,
);

disposeVirtualCode(fileId, code, files) {
const isGlobalTypesHolder = code.fileName === pluginContext.globalTypesHolder;
const fileRegistry = getFileRegistry(isGlobalTypesHolder);
fileRegistry.delete(fileId);
if (isGlobalTypesHolder) {
pluginContext.globalTypesHolder = undefined;
const fileRegistry2 = getFileRegistry(false);
for (const [fileId, code] of fileRegistry2) {
if (isValidGlobalTypesHolder(code.fileName)) {
pluginContext.globalTypesHolder = code.fileName;
fileRegistry2.delete(fileId);
// force dirty
files?.delete(fileId);
files?.set(
fileId,
file.languageId,
// force dirty
{ ...file.snapshot },
code.languageId,
code.snapshot,
);
break;
}
}
else {
generatedCodeRegistry = undefined;
pluginContext.globalTypesHolder = undefined;
}
}
},
typescript: {
Expand All @@ -159,4 +159,12 @@ export function createVueLanguagePlugin(
},
},
};

function getFileRegistry(isGlobalTypesHolder: boolean) {
return getVueFileRegistry(
isGlobalTypesHolder,
getFileRegistryKey(compilerOptions, vueCompilerOptions, plugins),
vueCompilerOptions.plugins,
);
}
}
30 changes: 1 addition & 29 deletions packages/language-core/lib/plugins.ts
@@ -1,5 +1,3 @@
import * as CompilerDOM from '@vue/compiler-dom';
import type * as ts from 'typescript';
import useHtmlFilePlugin from './plugins/file-html';
import useMdFilePlugin from './plugins/file-md';
import useVueFilePlugin from './plugins/file-vue';
Expand All @@ -9,33 +7,7 @@ import useVueSfcStyles from './plugins/vue-sfc-styles';
import useVueSfcTemplate from './plugins/vue-sfc-template';
import useHtmlTemplatePlugin from './plugins/vue-template-html';
import useVueTsx from './plugins/vue-tsx';
import { pluginVersion, type VueCompilerOptions, type VueLanguagePlugin } from './types';
import * as CompilerVue2 from './utils/vue2TemplateCompiler';

export function createPluginContext(
ts: typeof import('typescript'),
compilerOptions: ts.CompilerOptions,
vueCompilerOptions: VueCompilerOptions,
codegenStack: boolean,
globalTypesHolder: string | undefined,
) {
const pluginCtx: Parameters<VueLanguagePlugin>[0] = {
modules: {
'@vue/compiler-dom': vueCompilerOptions.target < 3
? {
...CompilerDOM,
compile: CompilerVue2.compile,
}
: CompilerDOM,
typescript: ts,
},
compilerOptions,
vueCompilerOptions,
codegenStack,
globalTypesHolder,
};
return pluginCtx;
}
import { pluginVersion, type VueLanguagePlugin } from './types';

export function getDefaultVueLanguagePlugins(pluginContext: Parameters<VueLanguagePlugin>[0]) {

Expand Down
22 changes: 21 additions & 1 deletion packages/language-server/node.ts
Expand Up @@ -42,7 +42,27 @@ connection.onInitialize(async params => {
async getLanguagePlugins(serviceEnv, projectContext) {
const [commandLine, vueOptions] = await parseCommandLine();
const resolvedVueOptions = resolveVueCompilerOptions(vueOptions);
const vueLanguagePlugin = createVueLanguagePlugin(tsdk.typescript, serviceEnv.typescript!.uriToFileName, commandLine?.options ?? {}, resolvedVueOptions, options.codegenStack);
const vueLanguagePlugin = createVueLanguagePlugin(
tsdk.typescript,
serviceEnv.typescript!.uriToFileName,
fileName => {
if (projectContext.typescript?.sys.useCaseSensitiveFileNames ?? false) {
return projectContext.typescript?.host.getScriptFileNames().includes(fileName) ?? false;
}
else {
const lowerFileName = fileName.toLowerCase();
for (const rootFile of projectContext.typescript?.host.getScriptFileNames() ?? []) {
if (rootFile.toLowerCase() === lowerFileName) {
return true;
}
}
return false;
}
},
commandLine?.options ?? {},
resolvedVueOptions,
options.codegenStack,
);

envToVueOptions.set(serviceEnv, resolvedVueOptions);

Expand Down
21 changes: 20 additions & 1 deletion packages/language-service/tests/utils/createTester.ts
Expand Up @@ -27,7 +27,26 @@ function createTester(rootUri: string) {
getLanguageId: resolveCommonLanguageId,
};
const resolvedVueOptions = resolveVueCompilerOptions(parsedCommandLine.vueOptions);
const vueLanguagePlugin = createVueLanguagePlugin(ts, serviceEnv.typescript!.uriToFileName, parsedCommandLine.options, resolvedVueOptions);
const vueLanguagePlugin = createVueLanguagePlugin(
ts,
serviceEnv.typescript!.uriToFileName,
fileName => {
if (ts.sys.useCaseSensitiveFileNames) {
return projectHost.getScriptFileNames().includes(fileName);
}
else {
const lowerFileName = fileName.toLowerCase();
for (const rootFile of projectHost.getScriptFileNames()) {
if (rootFile.toLowerCase() === lowerFileName) {
return true;
}
}
return false;
}
},
parsedCommandLine.options,
resolvedVueOptions,
);
const vueServicePlugins = createVueServicePlugins(ts, () => resolvedVueOptions);
const defaultVSCodeSettings: any = {
'typescript.preferences.quoteStyle': 'single',
Expand Down
8 changes: 7 additions & 1 deletion packages/language-service/tests/utils/format.ts
Expand Up @@ -4,7 +4,13 @@ import { describe, expect, it } from 'vitest';
import { createVueLanguagePlugin, createVueServicePlugins, resolveVueCompilerOptions } from '../..';

const resolvedVueOptions = resolveVueCompilerOptions({});
const vueLanguagePlugin = createVueLanguagePlugin(ts, fileId => formatter.env.typescript!.uriToFileName(fileId), {}, resolvedVueOptions);
const vueLanguagePlugin = createVueLanguagePlugin(
ts,
fileId => formatter.env.typescript!.uriToFileName(fileId),
() => false,
{},
resolvedVueOptions,
);
const vueServicePLugins = createVueServicePlugins(ts, () => resolvedVueOptions);
const formatter = kit.createFormatter([vueLanguagePlugin], vueServicePLugins);

Expand Down

0 comments on commit b8afeed

Please sign in to comment.