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

feat: Support for Additional Language Modules (Experimental) #2267

Merged
merged 1 commit into from Dec 30, 2022
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
4 changes: 2 additions & 2 deletions examples/vue-and-svelte-language-server/src/index.ts
Expand Up @@ -22,14 +22,14 @@ const plugin: LanguageServerPlugin<LanguageServerInitializationOptions, vue.Lang
};
},
getLanguageModules(host) {
const vueLanguageModule = vue.createLanguageModule(
const vueLanguageModules = vue.createLanguageModules(
host.getTypeScriptModule(),
host.getCurrentDirectory(),
host.getCompilationSettings(),
host.getVueCompilationSettings(),
);
return [
vueLanguageModule,
...vueLanguageModules,
svelteLanguageModule,
];
},
Expand Down
6 changes: 3 additions & 3 deletions packages/language-server/src/common/project.ts
Expand Up @@ -9,14 +9,14 @@ import { URI } from 'vscode-uri';
import { FileSystem, LanguageServerPlugin } from '../types';
import { createUriMap } from './utils/uriMap';
import { WorkspaceContext } from './workspace';
import { LanguageServicePlugin } from '@volar/language-service';
import { ServerConfig } from './utils/serverConfig';

export interface ProjectContext {
workspace: WorkspaceContext;
rootUri: URI;
tsConfig: path.PosixPath | ts.CompilerOptions,
documentRegistry: ts.DocumentRegistry,
workspacePlugins: LanguageServicePlugin[],
serverConfig: ServerConfig | undefined,
}

export type Project = ReturnType<typeof createProject>;
Expand Down Expand Up @@ -73,7 +73,7 @@ export async function createProject(context: ProjectContext) {
context: languageContext,
getPlugins() {
return [
...context.workspacePlugins,
...context.serverConfig?.plugins ?? [],
...context.workspace.workspaces.plugins.map(plugin => plugin.semanticService?.getServicePlugins?.(languageServiceHost, languageService!) ?? []).flat(),
];
},
Expand Down
5 changes: 3 additions & 2 deletions packages/language-server/src/common/syntaxServicesHost.ts
@@ -1,7 +1,7 @@
import * as embedded from '@volar/language-service';
import { URI } from 'vscode-uri';
import { LanguageServerInitializationOptions, LanguageServerPlugin, RuntimeEnvironment } from '../types';
import { loadCustomPlugins } from './utils/serverConfig';
import { loadServerConfig } from './utils/serverConfig';

// fix build
import type * as _ from 'vscode-languageserver-textdocument';
Expand Down Expand Up @@ -46,6 +46,7 @@ export function createSyntaxServicesHost(
configurationHost: configHost,
fileSystemProvider: runtimeEnv.fileSystemProvide,
};
const serverConfig = loadServerConfig(rootUri.fsPath, initOptions.configFilePath);
const serviceContext = embedded.createDocumentServiceContext({
ts,
env,
Expand All @@ -54,7 +55,7 @@ export function createSyntaxServicesHost(
},
getPlugins() {
return [
...loadCustomPlugins(rootUri.fsPath, initOptions.configFilePath),
...serverConfig?.plugins ?? [],
...plugins.map(plugin => plugin.syntacticService?.getServicePlugins?.(serviceContext) ?? []).flat(),
];
},
Expand Down
12 changes: 7 additions & 5 deletions packages/language-server/src/common/utils/serverConfig.ts
@@ -1,21 +1,23 @@
import { LanguageServicePlugin } from '@volar/language-service';

export function loadCustomPlugins(dir: string, configFile: string | undefined) {
export interface ServerConfig {
plugins?: LanguageServicePlugin[];
}

export function loadServerConfig(dir: string, configFile: string | undefined): ServerConfig | undefined {
let configPath: string | undefined;
try {
configPath = require.resolve(configFile ?? './volar.config.js', { paths: [dir] });
} catch { }

try {
if (configPath) {
const config: { plugins?: LanguageServicePlugin[]; } = require(configPath);
const config: ServerConfig = require(configPath);
delete require.cache[configPath];
return config.plugins ?? [];
return config;
}
}
catch (err) {
console.log(err);
}

return [];
}
8 changes: 4 additions & 4 deletions packages/language-server/src/common/workspace.ts
Expand Up @@ -5,7 +5,7 @@ import * as vscode from 'vscode-languageserver';
import { URI } from 'vscode-uri';
import { createProject, Project } from './project';
import { getInferredCompilerOptions } from './utils/inferredCompilerOptions';
import { loadCustomPlugins } from './utils/serverConfig';
import { loadServerConfig } from './utils/serverConfig';
import { createUriMap } from './utils/uriMap';
import { WorkspacesContext } from './workspaces';

Expand All @@ -20,7 +20,7 @@ export async function createWorkspace(context: WorkspaceContext) {

let inferredProject: Project | undefined;

const workspacePlugins = loadCustomPlugins(shared.getPathOfUri(context.rootUri.toString()), context.workspaces.initOptions.configFilePath);
const serverConfig = loadServerConfig(shared.getPathOfUri(context.rootUri.toString()), context.workspaces.initOptions.configFilePath);
const sys = context.workspaces.fileSystemHost.getWorkspaceFileSystem(context.rootUri);
const documentRegistry = context.workspaces.ts.createDocumentRegistry(sys.useCaseSensitiveFileNames, shared.getPathOfUri(context.rootUri.toString()));
const projects = createUriMap<Project>();
Expand Down Expand Up @@ -86,10 +86,10 @@ export async function createWorkspace(context: WorkspaceContext) {
const inferOptions = await getInferredCompilerOptions(context.workspaces.ts, context.workspaces.configurationHost);
return createProject({
workspace: context,
workspacePlugins,
rootUri: context.rootUri,
tsConfig: inferOptions,
documentRegistry,
serverConfig,
});
})();
}
Expand Down Expand Up @@ -218,7 +218,7 @@ export async function createWorkspace(context: WorkspaceContext) {
if (!project) {
project = createProject({
workspace: context,
workspacePlugins,
serverConfig,
rootUri: URI.parse(shared.getUriByPath(path.dirname(tsConfig))),
tsConfig,
documentRegistry,
Expand Down
4 changes: 2 additions & 2 deletions vue-language-tools/vue-component-meta/src/index.ts
Expand Up @@ -168,13 +168,13 @@ export function baseCreate(
return _host[prop as keyof typeof _host];
},
}) as vue.LanguageServiceHost;
const vueLanguageModule = vue.createLanguageModule(
const vueLanguageModules = vue.createLanguageModules(
host.getTypeScriptModule(),
host.getCurrentDirectory(),
host.getCompilationSettings(),
host.getVueCompilationSettings(),
);
const core = embedded.createLanguageContext(host, [vueLanguageModule]);
const core = embedded.createLanguageContext(host, vueLanguageModules);
const proxyApis: Partial<ts.LanguageServiceHost> = checkerOptions.forceUseTs ? {
getScriptKind: (fileName) => {
if (fileName.endsWith('.vue.js')) {
Expand Down
Expand Up @@ -60,6 +60,10 @@
"default": [ ],
"markdownDescription": "Plugins to be used in the SFC compiler."
},
"hooks": {
"type": "array",
"markdownDescription": "https://github.com/johnsoncodehk/volar/pull/2217"
},
"optionsWrapper": {
"type": "array",
"default": [
Expand Down Expand Up @@ -107,9 +111,9 @@
},
"markdownDescription": "https://github.com/johnsoncodehk/volar/issues/1969"
},
"hooks": {
"experimentalAdditionalLanguageModules": {
"type": "array",
"markdownDescription": "https://github.com/johnsoncodehk/volar/pull/2217"
"markdownDescription": "https://github.com/johnsoncodehk/volar/pull/2267"
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions vue-language-tools/vue-language-core/src/languageModule.ts
Expand Up @@ -7,13 +7,13 @@ import * as localTypes from './utils/localTypes';
import { resolveVueCompilerOptions } from './utils/ts';
import type * as ts from 'typescript/lib/tsserverlibrary';

export function createLanguageModule(
export function createLanguageModules(
ts: typeof import('typescript/lib/tsserverlibrary'),
rootDir: string,
compilerOptions: ts.CompilerOptions,
_vueCompilerOptions: VueCompilerOptions,
extraPlugins: VueLanguagePlugin[] = [],
): embedded.LanguageModule {
): embedded.LanguageModule[] {

const vueCompilerOptions = resolveVueCompilerOptions(_vueCompilerOptions);
const vueLanguagePlugin = getDefaultVueLanguagePlugins(
Expand Down Expand Up @@ -97,7 +97,10 @@ export function createLanguageModule(
},
};

return languageModule;
return [
languageModule,
...vueCompilerOptions.experimentalAdditionalLanguageModules?.map(module => require(module)) ?? [],
];

function getSharedTypesFiles(fileNames: string[]) {
const moduleFiles = fileNames.filter(fileName => vueCompilerOptions.extensions.some(ext => fileName.endsWith(ext)));
Expand Down
1 change: 1 addition & 0 deletions vue-language-tools/vue-language-core/src/types.ts
Expand Up @@ -32,6 +32,7 @@ export interface ResolvedVueCompilerOptions {
experimentalRfc436: boolean;
experimentalModelPropName: Record<string, Record<string, boolean | Record<string, string> | Record<string, string>[]>>;
experimentalUseElementAccessInTemplate: boolean;
experimentalAdditionalLanguageModules: string[];
}

export type VueLanguagePlugin = (ctx: {
Expand Down
19 changes: 19 additions & 0 deletions vue-language-tools/vue-language-core/src/utils/ts.ts
Expand Up @@ -79,6 +79,15 @@ function createParsedCommandLineBase(
...content.raw.vueCompilerOptions,
};

vueOptions.plugins = vueOptions.plugins?.map(plugin => {
try {
plugin = require.resolve(plugin, { paths: [folder] });
}
catch (error) {
console.error(error);
}
return plugin;
});
vueOptions.hooks = vueOptions.hooks?.map(hook => {
try {
hook = require.resolve(hook, { paths: [folder] });
Expand All @@ -88,6 +97,15 @@ function createParsedCommandLineBase(
}
return hook;
});
vueOptions.experimentalAdditionalLanguageModules = vueOptions.experimentalAdditionalLanguageModules?.map(module => {
try {
module = require.resolve(module, { paths: [folder] });
}
catch (error) {
console.error(error);
}
return module;
});

return {
...content,
Expand Down Expand Up @@ -148,6 +166,7 @@ export function resolveVueCompilerOptions(vueOptions: VueCompilerOptions): Resol
narrowingTypesInInlineHandlers: vueOptions.narrowingTypesInInlineHandlers ?? false,
plugins: vueOptions.plugins ?? [],
hooks: vueOptions.hooks ?? [],
experimentalAdditionalLanguageModules: vueOptions.experimentalAdditionalLanguageModules ?? [],

// experimental
experimentalResolveStyleCssClasses: vueOptions.experimentalResolveStyleCssClasses ?? 'scoped',
Expand Down
Expand Up @@ -42,13 +42,13 @@ const plugin: LanguageServerPlugin<VueServerInitializationOptions, vue.LanguageS
};
},
getLanguageModules(host) {
const vueLanguageModule = vue2.createLanguageModule(
const vueLanguageModules = vue2.createLanguageModules(
host.getTypeScriptModule(),
host.getCurrentDirectory(),
host.getCompilationSettings(),
host.getVueCompilationSettings(),
);
return [vueLanguageModule];
return vueLanguageModules;
},
getServicePlugins(host, service) {
const settings: vue.Settings = {};
Expand Down
Expand Up @@ -55,7 +55,7 @@ export function createDocumentService(
env: embeddedLS.LanguageServicePluginContext['env'],
) {

const vueLanguageModule = vue.createLanguageModule(
const vueLanguageModules = vue.createLanguageModules(
ts,
shared.getPathOfUri(env.rootUri.toString()),
{},
Expand All @@ -65,7 +65,7 @@ export function createDocumentService(
ts,
env,
getLanguageModules() {
return [vueLanguageModule];
return vueLanguageModules;
},
getPlugins() {
return plugins;
Expand Down
Expand Up @@ -237,13 +237,13 @@ export function createLanguageService(
settings?: Settings,
) {

const vueLanguageModule = vue.createLanguageModule(
const vueLanguageModules = vue.createLanguageModules(
host.getTypeScriptModule(),
host.getCurrentDirectory(),
host.getCompilationSettings(),
host.getVueCompilationSettings(),
);
const core = embedded.createLanguageContext(host, [vueLanguageModule]);
const core = embedded.createLanguageContext(host, vueLanguageModules);
const languageServiceContext = embeddedLS.createLanguageServiceContext({
env,
host,
Expand Down
6 changes: 3 additions & 3 deletions vue-language-tools/vue-tsc/src/index.ts
@@ -1,4 +1,4 @@
import * as ts from 'typescript/lib/tsserverlibrary';
import * as ts from 'typescript';
import * as vue from '@volar/vue-language-core';
import * as vueTs from '@volar/vue-typescript';
import { state } from './shared';
Expand Down Expand Up @@ -86,12 +86,12 @@ export function createProgram(
const vueTsLs = vueTs.createLanguageService(vueLsHost);

program = vueTsLs.getProgram() as (ts.Program & { __vue: ProgramContext; });
program!.__vue = ctx;
program.__vue = ctx;

function getVueCompilerOptions(): vue.VueCompilerOptions {
const tsConfig = ctx.options.options.configFilePath;
if (typeof tsConfig === 'string') {
return vue.createParsedCommandLine(ts, ts.sys, tsConfig, []).vueOptions;
return vue.createParsedCommandLine(ts as any, ts.sys, tsConfig, []).vueOptions;
}
return {};
}
Expand Down
5 changes: 2 additions & 3 deletions vue-language-tools/vue-typescript/src/index.ts
Expand Up @@ -2,11 +2,10 @@ import * as base from '@volar/typescript';
import * as vue from '@volar/vue-language-core';

export function createLanguageService(host: vue.LanguageServiceHost) {
const mods = [vue.createLanguageModule(
return base.createLanguageService(host, vue.createLanguageModules(
host.getTypeScriptModule(),
host.getCurrentDirectory(),
host.getCompilationSettings(),
host.getVueCompilationSettings(),
)];
return base.createLanguageService(host, mods);
));
}