From ad097485479e8db8c4df270f31fc54d18ca03af8 Mon Sep 17 00:00:00 2001 From: Yevhenii Andrushchak Date: Mon, 19 Mar 2018 12:54:48 +0200 Subject: [PATCH 1/2] Added pluginPaths configuration option to provide additional plugins discovery locations --- extensions/typescript/package.json | 10 +++++ extensions/typescript/package.nls.json | 2 + .../typescript/src/typescriptServiceClient.ts | 16 +++++-- .../typescript/src/utils/configuration.ts | 7 +++ .../src/utils/pluginPathsProvider.ts | 43 +++++++++++++++++++ .../src/utils/relativePathResolver.ts | 21 +++++++++ .../typescript/src/utils/versionProvider.ts | 14 +++--- 7 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 extensions/typescript/src/utils/pluginPathsProvider.ts create mode 100644 extensions/typescript/src/utils/relativePathResolver.ts diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index 2dc0cdb439912..5872d90ccab70 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -107,6 +107,16 @@ "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" + }, "typescript.tsserver.trace": { "type": "string", "enum": [ diff --git a/extensions/typescript/package.nls.json b/extensions/typescript/package.nls.json index 9e2c885647d3c..a63addb44831d 100644 --- a/extensions/typescript/package.nls.json +++ b/extensions/typescript/package.nls.json @@ -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.", diff --git a/extensions/typescript/src/typescriptServiceClient.ts b/extensions/typescript/src/typescriptServiceClient.ts index b1798d93dd422..5c4eb3c4ed1d2 100644 --- a/extensions/typescript/src/typescriptServiceClient.ts +++ b/extensions/typescript/src/typescriptServiceClient.ts @@ -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(); @@ -154,6 +155,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient private _onReady?: { promise: Promise; resolve: () => void; reject: () => void; }; private _configuration: TypeScriptServiceConfiguration; private versionProvider: TypeScriptVersionProvider; + private pluginPathsProvider: TypeScriptPluginPathsProvider; private versionPicker: TypeScriptVersionPicker; private tracer: Tracer; @@ -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; @@ -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) { @@ -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(); @@ -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()) { @@ -961,7 +972,6 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient return args; } - private getDebugPort(): number | undefined { const value = process.env['TSS_DEBUG']; if (value) { diff --git a/extensions/typescript/src/utils/configuration.ts b/extensions/typescript/src/utils/configuration.ts index 4976a3ec26a59..8b01771239537 100644 --- a/extensions/typescript/src/utils/configuration.ts +++ b/extensions/typescript/src/utils/configuration.ts @@ -47,6 +47,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; @@ -63,6 +64,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); @@ -74,6 +76,7 @@ export class TypeScriptServiceConfiguration { && this.localTsdk === other.localTsdk && this.npmLocation === other.npmLocation && this.tsServerLogLevel === other.tsServerLogLevel + && this.tsServerPluginPaths === other.tsServerPluginPaths && this.checkJs === other.checkJs && this.experimentalDecorators === other.experimentalDecorators && this.disableAutomaticTypeAcquisition === other.disableAutomaticTypeAcquisition; @@ -100,6 +103,10 @@ export class TypeScriptServiceConfiguration { return TsServerLogLevel.fromString(setting); } + private static readTsServerPluginPaths(configuration: WorkspaceConfiguration): string[] { + return configuration.get('typescript.tsserver.pluginPaths', []); + } + private static readCheckJs(configuration: WorkspaceConfiguration): boolean { return configuration.get('javascript.implicitProjectConfig.checkJs', false); } diff --git a/extensions/typescript/src/utils/pluginPathsProvider.ts b/extensions/typescript/src/utils/pluginPathsProvider.ts new file mode 100644 index 0000000000000..e2e39d6880117 --- /dev/null +++ b/extensions/typescript/src/utils/pluginPathsProvider.ts @@ -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)); + } +} \ No newline at end of file diff --git a/extensions/typescript/src/utils/relativePathResolver.ts b/extensions/typescript/src/utils/relativePathResolver.ts new file mode 100644 index 0000000000000..e424fca126a87 --- /dev/null +++ b/extensions/typescript/src/utils/relativePathResolver.ts @@ -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; + } +} \ No newline at end of file diff --git a/extensions/typescript/src/utils/versionProvider.ts b/extensions/typescript/src/utils/versionProvider.ts index 4f3d4ed85f414..25ecb3355d1f7 100644 --- a/extensions/typescript/src/utils/versionProvider.ts +++ b/extensions/typescript/src/utils/versionProvider.ts @@ -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'; @@ -91,6 +92,8 @@ export class TypeScriptVersion { export class TypeScriptVersionProvider { + private readonly relativePathResolver: RelativeWorkspacePathResolver = new RelativeWorkspacePathResolver(); + public constructor( private configuration: TypeScriptServiceConfiguration ) { } @@ -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); From 27f0ac141ff62fd15ff5de5df7bfc3f1e2de2763 Mon Sep 17 00:00:00 2001 From: Yevhenii Andrushchak Date: Wed, 21 Mar 2018 11:49:18 +0200 Subject: [PATCH 2/2] fixed review comments: added array comparison for tsServerPluginPaths, setting made executable --- extensions/typescript/package.json | 3 ++- extensions/typescript/src/utils/arrays.ts | 17 +++++++++++++++++ .../typescript/src/utils/configuration.ts | 5 +++-- 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 extensions/typescript/src/utils/arrays.ts diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index 5872d90ccab70..4e48b3ec25a48 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -115,7 +115,8 @@ }, "default": [], "description": "%typescript.tsserver.pluginPaths%", - "scope": "window" + "scope": "window", + "isExecutable": true }, "typescript.tsserver.trace": { "type": "string", diff --git a/extensions/typescript/src/utils/arrays.ts b/extensions/typescript/src/utils/arrays.ts new file mode 100644 index 0000000000000..57dbd54a29c28 --- /dev/null +++ b/extensions/typescript/src/utils/arrays.ts @@ -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(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; +} \ No newline at end of file diff --git a/extensions/typescript/src/utils/configuration.ts b/extensions/typescript/src/utils/configuration.ts index 8b01771239537..9359a2bbed022 100644 --- a/extensions/typescript/src/utils/configuration.ts +++ b/extensions/typescript/src/utils/configuration.ts @@ -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, @@ -76,10 +77,10 @@ export class TypeScriptServiceConfiguration { && this.localTsdk === other.localTsdk && this.npmLocation === other.npmLocation && this.tsServerLogLevel === other.tsServerLogLevel - && this.tsServerPluginPaths === other.tsServerPluginPaths && 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 {