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

Commit 4e0a636

Browse files
committed
feat: plugin configuration
Exposes initializationOptions containing globalPlugins, allowLocalPluginLoads, pluginProbeLocations Also removes default config and outdated comments
1 parent a0229ea commit 4e0a636

File tree

6 files changed

+60
-25
lines changed

6 files changed

+60
-25
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@
6464
"@types/mocha": "2.2.41",
6565
"@types/mz": "0.0.31",
6666
"@types/node": "7.0.32",
67-
"@types/rimraf": "2.0.2",
6867
"@types/object-hash": "0.5.29",
68+
"@types/rimraf": "2.0.2",
6969
"@types/sinon": "2.3.3",
7070
"@types/temp": "0.8.29",
7171
"commitizen": "^2.9.6",
@@ -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
@@ -7,7 +7,9 @@ import * as ts from 'typescript';
77
import { Disposable } from './disposable';
88
import { FileSystemUpdater } from './fs';
99
import { Logger, NoopLogger } from './logging';
10+
import { combinePaths } from './match-files';
1011
import { InMemoryFileSystem } from './memfs';
12+
import { InitializationOptions } from './request-type';
1113
import { traceObservable, traceSync } from './tracing';
1214
import {
1315
isConfigFile,
@@ -140,6 +142,11 @@ export class ProjectManager implements Disposable {
140142
*/
141143
private subscriptions = new Subscription();
142144

145+
/**
146+
* Options passed to the language server at startup
147+
*/
148+
private initializationOptions?: InitializationOptions;
149+
143150
/**
144151
* @param rootPath root path as passed to `initialize`
145152
* @param inMemoryFileSystem File system that keeps structure and contents in memory
@@ -151,12 +158,14 @@ export class ProjectManager implements Disposable {
151158
inMemoryFileSystem: InMemoryFileSystem,
152159
updater: FileSystemUpdater,
153160
traceModuleResolution?: boolean,
161+
initializationOptions?: InitializationOptions,
154162
protected logger: Logger = new NoopLogger()
155163
) {
156164
this.rootPath = rootPath;
157165
this.updater = updater;
158166
this.inMemoryFs = inMemoryFileSystem;
159167
this.versions = new Map<string, number>();
168+
this.initializationOptions = initializationOptions;
160169
this.traceModuleResolution = traceModuleResolution || false;
161170

162171
// Share DocumentRegistry between all ProjectConfigurations
@@ -184,6 +193,7 @@ export class ProjectManager implements Disposable {
184193
'',
185194
tsConfig,
186195
this.traceModuleResolution,
196+
this.initializationOptions,
187197
this.logger
188198
);
189199
configs.set(trimmedRootPath, config);
@@ -213,6 +223,7 @@ export class ProjectManager implements Disposable {
213223
filePath,
214224
undefined,
215225
this.traceModuleResolution,
226+
this.initializationOptions,
216227
this.logger
217228
));
218229
// Remove catch-all config (if exists)
@@ -769,6 +780,10 @@ export class ProjectConfiguration {
769780
*/
770781
private traceModuleResolution: boolean;
771782

783+
private allowLocalPluginLoads: boolean = false;
784+
private globalPlugins: string[] = [];
785+
private pluginProbeLocations: string[] = [];
786+
772787
/**
773788
* Root file path, relative to workspace hierarchy root
774789
*/
@@ -795,12 +810,18 @@ export class ProjectConfiguration {
795810
configFilePath: string,
796811
configContent?: any,
797812
traceModuleResolution?: boolean,
813+
initializationOptions?: InitializationOptions,
798814
private logger: Logger = new NoopLogger()
799815
) {
800816
this.fs = fs;
801817
this.configFilePath = configFilePath;
802818
this.configContent = configContent;
803819
this.versions = versions;
820+
if (initializationOptions) {
821+
this.allowLocalPluginLoads = initializationOptions.allowLocalPluginLoads || false;
822+
this.globalPlugins = initializationOptions.globalPlugins || [];
823+
this.pluginProbeLocations = initializationOptions.pluginProbeLocations || [];
824+
}
804825
this.traceModuleResolution = traceModuleResolution || false;
805826
this.rootFilePath = rootFilePath;
806827
}
@@ -903,27 +924,41 @@ export class ProjectConfiguration {
903924
}
904925

905926
private enablePlugins(options: ts.CompilerOptions) {
906-
const searchPaths = [];
907-
// TODO: support pluginProbeLocations?
908-
// TODO: add peer node_modules to source path.
909-
910-
// TODO: determine how to expose this setting.
911-
// VS Code starts tsserver with --allowLocalPluginLoads by default: https://github.com/Microsoft/TypeScript/pull/15924
912-
const allowLocalPluginLoads = true;
913-
if (allowLocalPluginLoads) {
927+
// Search our peer node_modules, then any globally-specified probe paths
928+
// ../../.. to walk from X/node_modules/javascript-typescript-langserver/lib/project-manager.js to X/node_modules/
929+
const searchPaths = [combinePaths(__filename, '../../..'), ...this.pluginProbeLocations];
930+
931+
// Corresponds to --allowLocalPluginLoads, opt-in to avoid remote code execution.
932+
if (this.allowLocalPluginLoads) {
914933
const local = this.rootFilePath;
915934
this.logger.info(`Local plugin loading enabled; adding ${local} to search paths`);
916935
searchPaths.unshift(local);
917936
}
918937

938+
let pluginImports: ts.PluginImport[] = [];
939+
if (options.plugins) {
940+
pluginImports = options.plugins as ts.PluginImport[];
941+
}
942+
919943
// Enable tsconfig-specified plugins
920944
if (options.plugins) {
921-
for (const pluginConfigEntry of options.plugins as ts.PluginImport[]) {
945+
for (const pluginConfigEntry of pluginImports) {
922946
this.enablePlugin(pluginConfigEntry, searchPaths);
923947
}
924948
}
925949

926-
// TODO: support globalPlugins flag if desired
950+
if (this.globalPlugins) {
951+
// Enable global plugins with synthetic configuration entries
952+
for (const globalPluginName of this.globalPlugins) {
953+
// Skip already-locally-loaded plugins
954+
if (!pluginImports || pluginImports.some(p => p.name === globalPluginName)) {
955+
continue;
956+
}
957+
958+
// Provide global: true so plugins can detect why they can't find their config
959+
this.enablePlugin({ name: globalPluginName, global: true } as ts.PluginImport, searchPaths);
960+
}
961+
}
927962
}
928963

929964
/**
@@ -948,9 +983,7 @@ export class ProjectConfiguration {
948983
* @param initialDir
949984
*/
950985
private resolveModule(moduleName: string, initialDir: string): {} | undefined {
951-
// const resolvedPath = path.resolve(initialDir, 'node_modules');
952-
const resolvedPath = '';
953-
this.logger.info(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`);
986+
this.logger.info(`Loading ${moduleName} from ${initialDir}`);
954987
const result = this.requirePlugin(initialDir, moduleName);
955988
if (result.error) {
956989
this.logger.info(`Failed to load module: ${JSON.stringify(result.error)}`);
@@ -973,20 +1006,18 @@ export class ProjectConfiguration {
9731006
}
9741007
}
9751008

976-
// TODO: stolen from moduleNameResolver.ts because marked as internal
9771009
/**
9781010
* Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations.
9791011
* No way to do this with `require()`: https://github.com/nodejs/node/issues/5963
9801012
* Throws an error if the module can't be resolved.
1013+
* stolen from moduleNameResolver.ts because marked as internal
9811014
*/
9821015
private resolveJavaScriptModule(moduleName: string, initialDir: string, host: ts.ModuleResolutionHost): string {
983-
// const { resolvedModule /* , failedLookupLocations */ } =
1016+
// TODO: this should set jsOnly=true to the internal resolver, but this parameter is not exposed on a public api.
9841017
const result =
9851018
ts.nodeModuleNameResolver(moduleName, /* containingFile */ initialDir.replace('\\', '/') + '/package.json', { moduleResolution: ts.ModuleResolutionKind.NodeJs, allowJs: true }, this.fs, undefined);
986-
// TODO: jsOnly flag missing :(
9871019
if (!result.resolvedModule) {
988-
// TODO: add Looked in: ${failedLookupLocations.join(', ')} back into error.
989-
// this.logger.error(result.failedLookupLocations!);
1020+
// this.logger.error(result.failedLookupLocations);
9901021
throw new Error(`Could not resolve JS module ${moduleName} starting at ${initialDir}.`);
9911022
}
9921023
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)