Skip to content

Commit

Permalink
Enable search of local Typescript Language Service Plugins (fix #45856)…
Browse files Browse the repository at this point in the history
… (#45858)

* Added pluginPaths configuration option to provide additional plugins discovery locations

* fixed review comments: added array comparison for tsServerPluginPaths, setting made executable
  • Loading branch information
killerDJO authored and mjbvz committed Mar 23, 2018
1 parent a9761fb commit d3cd393
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 12 deletions.
11 changes: 11 additions & 0 deletions extensions/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,17 @@
"description": "%typescript.tsserver.log%",
"scope": "window"
},
"typescript.tsserver.pluginPaths": {
"type": "array",
"items": {
"type": "string",
"description": "%typescript.tsserver.pluginPaths.item%"
},
"default": [],
"description": "%typescript.tsserver.pluginPaths%",
"scope": "window",
"isExecutable": true
},
"typescript.tsserver.trace": {
"type": "string",
"enum": [
Expand Down
2 changes: 2 additions & 0 deletions extensions/typescript/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"typescript.tsdk.desc": "Specifies the folder path containing the tsserver and lib*.d.ts files to use.",
"typescript.disableAutomaticTypeAcquisition": "Disables automatic type acquisition. Requires TypeScript >= 2.0.6.",
"typescript.tsserver.log": "Enables logging of the TS server to a file. This log can be used to diagnose TS Server issues. The log may contain file paths, source code, and other potentially sensitive information from your project.",
"typescript.tsserver.pluginPaths": "Additional paths to discover Typescript Language Service plugins. Requires TypeScript >= 2.3.0.",
"typescript.tsserver.pluginPaths.item": "Either an absolute or relative path. Relative path will be resolved against workspace folder(s).",
"typescript.tsserver.trace": "Enables tracing of messages sent to the TS server. This trace can be used to diagnose TS Server issues. The trace may contain file paths, source code, and other potentially sensitive information from your project.",
"typescript.validate.enable": "Enable/disable TypeScript validation.",
"typescript.format.enable": "Enable/disable default TypeScript formatter.",
Expand Down
16 changes: 13 additions & 3 deletions extensions/typescript/src/typescriptServiceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { inferredProjectConfig } from './utils/tsconfig';
import LogDirectoryProvider from './utils/logDirectoryProvider';
import { disposeAll } from './utils/dipose';
import { DiagnosticKind } from './features/diagnostics';
import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider';

const localize = nls.loadMessageBundle();

Expand Down Expand Up @@ -154,6 +155,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
private _onReady?: { promise: Promise<void>; resolve: () => void; reject: () => void; };
private _configuration: TypeScriptServiceConfiguration;
private versionProvider: TypeScriptVersionProvider;
private pluginPathsProvider: TypeScriptPluginPathsProvider;
private versionPicker: TypeScriptVersionPicker;

private tracer: Tracer;
Expand Down Expand Up @@ -210,6 +212,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
this.callbacks = new CallbackMap();
this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace();
this.versionProvider = new TypeScriptVersionProvider(this._configuration);
this.pluginPathsProvider = new TypeScriptPluginPathsProvider(this._configuration);
this.versionPicker = new TypeScriptVersionPicker(this.versionProvider, this.workspaceState);

this._apiVersion = API.defaultVersion;
Expand All @@ -221,6 +224,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace();

this.versionProvider.updateConfiguration(this._configuration);
this.pluginPathsProvider.updateConfiguration(this._configuration);
this.tracer.updateConfiguration();

if (this.servicePromise) {
Expand Down Expand Up @@ -791,7 +795,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
this.tracer.traceEvent(event);
this.dispatchEvent(event);
} else {
throw new Error('Unknown message type ' + message.type + ' recevied');
throw new Error('Unknown message type ' + message.type + ' received');
}
} finally {
this.sendNextRequests();
Expand Down Expand Up @@ -938,12 +942,19 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
}

if (this.apiVersion.has230Features()) {
const pluginPaths = this.pluginPathsProvider.getPluginPaths();

if (this.plugins.length) {
args.push('--globalPlugins', this.plugins.map(x => x.name).join(','));

if (currentVersion.path === this.versionProvider.defaultVersion.path) {
args.push('--pluginProbeLocations', this.plugins.map(x => x.path).join(','));
pluginPaths.push(...this.plugins.map(x => x.path));
}
}

if (pluginPaths.length !== 0) {
args.push('--pluginProbeLocations', pluginPaths.join(','));
}
}

if (this.apiVersion.has234Features()) {
Expand All @@ -961,7 +972,6 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
return args;
}


private getDebugPort(): number | undefined {
const value = process.env['TSS_DEBUG'];
if (value) {
Expand Down
17 changes: 17 additions & 0 deletions extensions/typescript/src/utils/arrays.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function equals<T>(one: T[], other: T[], itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean {
if (one.length !== other.length) {
return false;
}

for (let i = 0, len = one.length; i < len; i++) {
if (!itemEquals(one[i], other[i])) {
return false;
}
}

return true;
}
10 changes: 9 additions & 1 deletion extensions/typescript/src/utils/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { WorkspaceConfiguration, workspace } from 'vscode';
import * as arrays from './arrays';

export enum TsServerLogLevel {
Off,
Expand Down Expand Up @@ -47,6 +48,7 @@ export class TypeScriptServiceConfiguration {
public readonly localTsdk: string | null;
public readonly npmLocation: string | null;
public readonly tsServerLogLevel: TsServerLogLevel = TsServerLogLevel.Off;
public readonly tsServerPluginPaths: string[];
public readonly checkJs: boolean;
public readonly experimentalDecorators: boolean;
public readonly disableAutomaticTypeAcquisition: boolean;
Expand All @@ -63,6 +65,7 @@ export class TypeScriptServiceConfiguration {
this.localTsdk = TypeScriptServiceConfiguration.extractLocalTsdk(configuration);
this.npmLocation = TypeScriptServiceConfiguration.readNpmLocation(configuration);
this.tsServerLogLevel = TypeScriptServiceConfiguration.readTsServerLogLevel(configuration);
this.tsServerPluginPaths = TypeScriptServiceConfiguration.readTsServerPluginPaths(configuration);
this.checkJs = TypeScriptServiceConfiguration.readCheckJs(configuration);
this.experimentalDecorators = TypeScriptServiceConfiguration.readExperimentalDecorators(configuration);
this.disableAutomaticTypeAcquisition = TypeScriptServiceConfiguration.readDisableAutomaticTypeAcquisition(configuration);
Expand All @@ -76,7 +79,8 @@ export class TypeScriptServiceConfiguration {
&& this.tsServerLogLevel === other.tsServerLogLevel
&& this.checkJs === other.checkJs
&& this.experimentalDecorators === other.experimentalDecorators
&& this.disableAutomaticTypeAcquisition === other.disableAutomaticTypeAcquisition;
&& this.disableAutomaticTypeAcquisition === other.disableAutomaticTypeAcquisition
&& arrays.equals(this.tsServerPluginPaths, other.tsServerPluginPaths);
}

private static extractGlobalTsdk(configuration: WorkspaceConfiguration): string | null {
Expand All @@ -100,6 +104,10 @@ export class TypeScriptServiceConfiguration {
return TsServerLogLevel.fromString(setting);
}

private static readTsServerPluginPaths(configuration: WorkspaceConfiguration): string[] {
return configuration.get<string[]>('typescript.tsserver.pluginPaths', []);
}

private static readCheckJs(configuration: WorkspaceConfiguration): boolean {
return configuration.get<boolean>('javascript.implicitProjectConfig.checkJs', false);
}
Expand Down
43 changes: 43 additions & 0 deletions extensions/typescript/src/utils/pluginPathsProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { workspace } from 'vscode';

import { TypeScriptServiceConfiguration } from './configuration';
import { RelativeWorkspacePathResolver } from './relativePathResolver';

export class TypeScriptPluginPathsProvider {
public readonly relativePathResolver: RelativeWorkspacePathResolver = new RelativeWorkspacePathResolver();

public constructor(
private configuration: TypeScriptServiceConfiguration
) { }

public updateConfiguration(configuration: TypeScriptServiceConfiguration): void {
this.configuration = configuration;
}

public getPluginPaths(): string[] {
const pluginPaths = [];
for (const pluginPath of this.configuration.tsServerPluginPaths) {
pluginPaths.push(...this.resolvePluginPath(pluginPath));
}
return pluginPaths;
}

private resolvePluginPath(pluginPath: string): string[] {
if (path.isAbsolute(pluginPath)) {
return [pluginPath];
}

const workspacePath = this.relativePathResolver.asAbsoluteWorkspacePath(pluginPath);
if (workspacePath !== undefined) {
return [workspacePath];
}

return (workspace.workspaceFolders || [])
.map(workspaceFolder => path.join(workspaceFolder.uri.fsPath, pluginPath));
}
}
21 changes: 21 additions & 0 deletions extensions/typescript/src/utils/relativePathResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { workspace } from 'vscode';

export class RelativeWorkspacePathResolver {
public asAbsoluteWorkspacePath(relativePath: string): string | undefined {
for (const root of workspace.workspaceFolders || []) {
const rootPrefixes = [`./${root.name}/`, `${root.name}/`, `.\\${root.name}\\`, `${root.name}\\`];
for (const rootPrefix of rootPrefixes) {
if (relativePath.startsWith(rootPrefix)) {
return path.join(root.uri.fsPath, relativePath.replace(rootPrefix, ''));
}
}
}

return undefined;
}
}
14 changes: 6 additions & 8 deletions extensions/typescript/src/utils/versionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as fs from 'fs';
import { workspace, window } from 'vscode';

import { TypeScriptServiceConfiguration } from './configuration';
import { RelativeWorkspacePathResolver } from './relativePathResolver';
import API from './api';


Expand Down Expand Up @@ -91,6 +92,8 @@ export class TypeScriptVersion {


export class TypeScriptVersionProvider {
private readonly relativePathResolver: RelativeWorkspacePathResolver = new RelativeWorkspacePathResolver();

public constructor(
private configuration: TypeScriptServiceConfiguration
) { }
Expand Down Expand Up @@ -165,14 +168,9 @@ export class TypeScriptVersionProvider {
return [new TypeScriptVersion(tsdkPathSetting)];
}

for (const root of workspace.workspaceFolders || []) {
const rootPrefixes = [`./${root.name}/`, `${root.name}/`, `.\\${root.name}\\`, `${root.name}\\`];
for (const rootPrefix of rootPrefixes) {
if (tsdkPathSetting.startsWith(rootPrefix)) {
const workspacePath = path.join(root.uri.fsPath, tsdkPathSetting.replace(rootPrefix, ''));
return [new TypeScriptVersion(workspacePath, tsdkPathSetting)];
}
}
const workspacePath = this.relativePathResolver.asAbsoluteWorkspacePath(tsdkPathSetting);
if (workspacePath !== undefined) {
return [new TypeScriptVersion(workspacePath, tsdkPathSetting)];
}

return this.loadTypeScriptVersionsFromPath(tsdkPathSetting);
Expand Down

0 comments on commit d3cd393

Please sign in to comment.