Skip to content
This repository was archived by the owner on Oct 16, 2020. It is now read-only.

Commit 0d79881

Browse files
committed
feat: plugin configuration
Exposes initializationOptions containing globalPlugins, allowLocalPluginLoads, pluginProbeLocations Also removes default config and outdated comments
1 parent 64d7378 commit 0d79881

File tree

6 files changed

+59
-24
lines changed

6 files changed

+59
-24
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@
7979
"source-map-support": "^0.4.11",
8080
"temp": "^0.8.3",
8181
"tslint": "^5.0.0",
82-
"tslint-language-service": "^0.9.6",
8382
"validate-commit-msg": "^2.12.2"
8483
},
8584
"bin": {

src/match-files.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function matchFiles(path: string, extensions: string[], excludes: string[
5555

5656
const directorySeparator = '/';
5757

58-
function combinePaths(path1: string, path2: string) {
58+
export function combinePaths(path1: string, path2: string) {
5959
if (!(path1 && path1.length)) return path2;
6060
if (!(path2 && path2.length)) return path1;
6161
if (getRootLength(path2) !== 0) return path2;

src/project-manager.ts

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import * as ts from 'typescript';
88
import { Disposable } from './disposable';
99
import { FileSystemUpdater } from './fs';
1010
import { Logger, NoopLogger } from './logging';
11+
import { combinePaths } from './match-files';
1112
import { InMemoryFileSystem } from './memfs';
13+
import { InitializationOptions } from './request-type';
1214
import { traceObservable, traceSync } from './tracing';
1315
import {
1416
isConfigFile,
@@ -146,6 +148,11 @@ export class ProjectManager implements Disposable {
146148
*/
147149
private subscriptions = new Subscription();
148150

151+
/**
152+
* Options passed to the language server at startup
153+
*/
154+
private initializationOptions?: InitializationOptions;
155+
149156
/**
150157
* @param rootPath root path as passed to `initialize`
151158
* @param inMemoryFileSystem File system that keeps structure and contents in memory
@@ -157,12 +164,14 @@ export class ProjectManager implements Disposable {
157164
inMemoryFileSystem: InMemoryFileSystem,
158165
updater: FileSystemUpdater,
159166
traceModuleResolution?: boolean,
167+
initializationOptions?: InitializationOptions,
160168
protected logger: Logger = new NoopLogger()
161169
) {
162170
this.rootPath = rootPath;
163171
this.updater = updater;
164172
this.inMemoryFs = inMemoryFileSystem;
165173
this.versions = new Map<string, number>();
174+
this.initializationOptions = initializationOptions;
166175
this.traceModuleResolution = traceModuleResolution || false;
167176

168177
// Share DocumentRegistry between all ProjectConfigurations
@@ -190,6 +199,7 @@ export class ProjectManager implements Disposable {
190199
'',
191200
tsConfig,
192201
this.traceModuleResolution,
202+
this.initializationOptions,
193203
this.logger
194204
);
195205
configs.set(trimmedRootPath, config);
@@ -219,6 +229,7 @@ export class ProjectManager implements Disposable {
219229
filePath,
220230
undefined,
221231
this.traceModuleResolution,
232+
this.initializationOptions,
222233
this.logger
223234
));
224235
// Remove catch-all config (if exists)
@@ -817,6 +828,10 @@ export class ProjectConfiguration {
817828
*/
818829
private traceModuleResolution: boolean;
819830

831+
private allowLocalPluginLoads: boolean = false;
832+
private globalPlugins: string[] = [];
833+
private pluginProbeLocations: string[] = [];
834+
820835
/**
821836
* Root file path, relative to workspace hierarchy root
822837
*/
@@ -848,12 +863,18 @@ export class ProjectConfiguration {
848863
configFilePath: string,
849864
configContent?: any,
850865
traceModuleResolution?: boolean,
866+
initializationOptions?: InitializationOptions,
851867
private logger: Logger = new NoopLogger()
852868
) {
853869
this.fs = fs;
854870
this.configFilePath = configFilePath;
855871
this.configContent = configContent;
856872
this.versions = versions;
873+
if (initializationOptions) {
874+
this.allowLocalPluginLoads = initializationOptions.allowLocalPluginLoads || false;
875+
this.globalPlugins = initializationOptions.globalPlugins || [];
876+
this.pluginProbeLocations = initializationOptions.pluginProbeLocations || [];
877+
}
857878
this.traceModuleResolution = traceModuleResolution || false;
858879
this.rootFilePath = rootFilePath;
859880
}
@@ -961,27 +982,41 @@ export class ProjectConfiguration {
961982
}
962983

963984
private enablePlugins(options: ts.CompilerOptions) {
964-
const searchPaths = [];
965-
// TODO: support pluginProbeLocations?
966-
// TODO: add peer node_modules to source path.
967-
968-
// TODO: determine how to expose this setting.
969-
// VS Code starts tsserver with --allowLocalPluginLoads by default: https://github.com/Microsoft/TypeScript/pull/15924
970-
const allowLocalPluginLoads = true;
971-
if (allowLocalPluginLoads) {
985+
// Search our peer node_modules, then any globally-specified probe paths
986+
// ../../.. to walk from X/node_modules/javascript-typescript-langserver/lib/project-manager.js to X/node_modules/
987+
const searchPaths = [combinePaths(__filename, '../../..'), ...this.pluginProbeLocations];
988+
989+
// Corresponds to --allowLocalPluginLoads, opt-in to avoid remote code execution.
990+
if (this.allowLocalPluginLoads) {
972991
const local = this.rootFilePath;
973992
this.logger.info(`Local plugin loading enabled; adding ${local} to search paths`);
974993
searchPaths.unshift(local);
975994
}
976995

996+
let pluginImports: ts.PluginImport[] = [];
997+
if (options.plugins) {
998+
pluginImports = options.plugins as ts.PluginImport[];
999+
}
1000+
9771001
// Enable tsconfig-specified plugins
9781002
if (options.plugins) {
979-
for (const pluginConfigEntry of options.plugins as ts.PluginImport[]) {
1003+
for (const pluginConfigEntry of pluginImports) {
9801004
this.enablePlugin(pluginConfigEntry, searchPaths);
9811005
}
9821006
}
9831007

984-
// TODO: support globalPlugins flag if desired
1008+
if (this.globalPlugins) {
1009+
// Enable global plugins with synthetic configuration entries
1010+
for (const globalPluginName of this.globalPlugins) {
1011+
// Skip already-locally-loaded plugins
1012+
if (!pluginImports || pluginImports.some(p => p.name === globalPluginName)) {
1013+
continue;
1014+
}
1015+
1016+
// Provide global: true so plugins can detect why they can't find their config
1017+
this.enablePlugin({ name: globalPluginName, global: true } as ts.PluginImport, searchPaths);
1018+
}
1019+
}
9851020
}
9861021

9871022
/**
@@ -1006,9 +1041,7 @@ export class ProjectConfiguration {
10061041
* @param initialDir
10071042
*/
10081043
private resolveModule(moduleName: string, initialDir: string): {} | undefined {
1009-
// const resolvedPath = path.resolve(initialDir, 'node_modules');
1010-
const resolvedPath = '';
1011-
this.logger.info(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`);
1044+
this.logger.info(`Loading ${moduleName} from ${initialDir}`);
10121045
const result = this.requirePlugin(initialDir, moduleName);
10131046
if (result.error) {
10141047
this.logger.info(`Failed to load module: ${JSON.stringify(result.error)}`);
@@ -1031,20 +1064,18 @@ export class ProjectConfiguration {
10311064
}
10321065
}
10331066

1034-
// TODO: stolen from moduleNameResolver.ts because marked as internal
10351067
/**
10361068
* Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations.
10371069
* No way to do this with `require()`: https://github.com/nodejs/node/issues/5963
10381070
* Throws an error if the module can't be resolved.
1071+
* stolen from moduleNameResolver.ts because marked as internal
10391072
*/
10401073
private resolveJavaScriptModule(moduleName: string, initialDir: string, host: ts.ModuleResolutionHost): string {
1041-
// const { resolvedModule /* , failedLookupLocations */ } =
1074+
// TODO: this should set jsOnly=true to the internal resolver, but this parameter is not exposed on a public api.
10421075
const result =
10431076
ts.nodeModuleNameResolver(moduleName, /* containingFile */ initialDir.replace('\\', '/') + '/package.json', { moduleResolution: ts.ModuleResolutionKind.NodeJs, allowJs: true }, this.fs, undefined);
1044-
// TODO: jsOnly flag missing :(
10451077
if (!result.resolvedModule) {
1046-
// TODO: add Looked in: ${failedLookupLocations.join(', ')} back into error.
1047-
// this.logger.error(result.failedLookupLocations!);
1078+
// this.logger.error(result.failedLookupLocations);
10481079
throw new Error(`Could not resolve JS module ${moduleName} starting at ${initialDir}.`);
10491080
}
10501081
return result.resolvedModule.resolvedFileName;

src/request-type.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import * as vscode from 'vscode-languageserver';
33

44
export interface InitializeParams extends vscode.InitializeParams {
55
capabilities: ClientCapabilities;
6+
initializationOptions?: InitializationOptions
7+
}
8+
9+
export interface InitializationOptions {
10+
allowLocalPluginLoads: boolean;
11+
globalPlugins: string[];
12+
pluginProbeLocations: string[];
613
}
714

815
export interface ClientCapabilities extends vscode.ClientCapabilities {

src/typescript-service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ export class TypeScriptService {
223223
this.inMemoryFileSystem,
224224
this.updater,
225225
this.traceModuleResolution,
226+
params.initializationOptions,
226227
this.logger
227228
);
228229
this.packageManager = new PackageManager(this.updater, this.inMemoryFileSystem, this.logger);

tsconfig.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@
1616
"noImplicitThis": true,
1717
"noUnusedLocals": true,
1818
"noImplicitAny": true,
19-
"strictNullChecks": true,
20-
"plugins": [
21-
{ "name": "tslint-language-service"}
22-
]
19+
"strictNullChecks": true
2320
},
2421
"include": [
2522
"src/**/*.ts"

0 commit comments

Comments
 (0)