Skip to content

Commit

Permalink
feat: Support for Additional Language Modules (Experimental) (#2267)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Dec 30, 2022
1 parent b989a39 commit 8990ddc
Show file tree
Hide file tree
Showing 15 changed files with 64 additions and 35 deletions.
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);
));
}

0 comments on commit 8990ddc

Please sign in to comment.