Skip to content
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

fix(language-core): avoid globalTypesHolder being specified from a node_modules file #3990

Merged
merged 5 commits into from Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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