diff --git a/extensions/typescript/src/features/taskProvider.ts b/extensions/typescript/src/features/taskProvider.ts index 76528021983ef..ba53903e447ce 100644 --- a/extensions/typescript/src/features/taskProvider.ts +++ b/extensions/typescript/src/features/taskProvider.ts @@ -11,6 +11,7 @@ import * as vscode from 'vscode'; import * as Proto from '../protocol'; import TypeScriptServiceClient from '../typescriptServiceClient'; +import TsConfigProvider from "../utils/tsconfigProvider"; const exists = (file: string): Promise => @@ -21,32 +22,47 @@ const exists = (file: string): Promise => }); export default class TypeScriptTaskProvider implements vscode.TaskProvider { + private readonly tsconfigProvider: TsConfigProvider; public constructor( private readonly lazyClient: () => TypeScriptServiceClient - ) { } + ) { + this.tsconfigProvider = new TsConfigProvider(); + } + + dispose() { + this.tsconfigProvider.dispose(); + } - async provideTasks(token: vscode.CancellationToken): Promise { + public async provideTasks(token: vscode.CancellationToken): Promise { const rootPath = vscode.workspace.rootPath; if (!rootPath) { return []; } - const projects = (await this.getConfigForActiveFile(token)).concat(await this.getConfigsForWorkspace()); const command = await this.getCommand(); + const projects = await this.getAllTsConfigs(token); - return projects - .filter((x, i) => projects.indexOf(x) === i) - .map(configFile => { - const configFileName = path.relative(rootPath, configFile); - const buildTask = new vscode.ShellTask(`tsc: build ${configFileName}`, `${command} -p ${configFile}`, '$tsc'); - buildTask.group = vscode.TaskGroup.Build; - return buildTask; - }); + return projects.map(configFile => { + const configFileName = path.relative(rootPath, configFile); + const buildTask = new vscode.ShellTask(`tsc: build ${configFileName}`, `${command} -p ${configFile}`, '$tsc'); + buildTask.group = vscode.TaskGroup.Build; + return buildTask; + }); } + private async getAllTsConfigs(token: vscode.CancellationToken): Promise { + const out: string[] = []; + const configs = (await this.getTsConfigForActiveFile(token)).concat(await this.getTsConfigsInWorkspace()); + for (const config of configs) { + if (await exists(config)) { + out.push(config); + } + } + return out; + } - private async getConfigForActiveFile(token: vscode.CancellationToken): Promise { + private async getTsConfigForActiveFile(token: vscode.CancellationToken): Promise { const editor = vscode.window.activeTextEditor; if (editor) { if (path.basename(editor.document.fileName).match(/^tsconfig\.(.\.)?json$/)) { @@ -75,15 +91,8 @@ export default class TypeScriptTaskProvider implements vscode.TaskProvider { return []; } - private async getConfigsForWorkspace(): Promise { - if (!vscode.workspace.rootPath) { - return []; - } - const rootTsConfig = path.join(vscode.workspace.rootPath, 'tsconfig.json'); - if (!await exists(rootTsConfig)) { - return []; - } - return [rootTsConfig]; + private async getTsConfigsInWorkspace(): Promise { + return Array.from(await this.tsconfigProvider.getConfigsForWorkspace()); } private async getCommand(): Promise { diff --git a/extensions/typescript/src/utils/tsconfigProvider.ts b/extensions/typescript/src/utils/tsconfigProvider.ts new file mode 100644 index 0000000000000..e85ca24bbff77 --- /dev/null +++ b/extensions/typescript/src/utils/tsconfigProvider.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; + +export default class TsConfigProvider extends vscode.Disposable { + private readonly tsconfigs = new Set(); + + private activated: boolean = false; + private disposables: vscode.Disposable[] = []; + + constructor() { + super(() => this.dispose()); + } + + dispose(): void { + this.disposables.forEach(d => d.dispose()); + } + + public async getConfigsForWorkspace(): Promise> { + if (!vscode.workspace.rootPath) { + return []; + } + await this.ensureActivated(); + return this.tsconfigs; + } + + private async ensureActivated() { + if (this.activated) { + return this; + } + this.activated = true; + + for (const config of await TsConfigProvider.loadWorkspaceTsconfigs()) { + this.tsconfigs.add(config.fsPath); + } + + const configFileWatcher = vscode.workspace.createFileSystemWatcher('**/tsconfig*.json'); + this.disposables.push(configFileWatcher); + configFileWatcher.onDidCreate(this.handleProjectCreate, this, this.disposables); + configFileWatcher.onDidDelete(this.handleProjectDelete, this, this.disposables); + + return this; + } + + private static loadWorkspaceTsconfigs() { + return vscode.workspace.findFiles('**/tsconfig*.json', '**/node_modules/**'); + } + + private handleProjectCreate(e: vscode.Uri) { + this.tsconfigs.add(e.fsPath); + } + + private handleProjectDelete(e: vscode.Uri) { + this.tsconfigs.delete(e.fsPath); + } +}