From 2ef8e48de48e51176cff255484c3b278a807a18f Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Wed, 21 Jun 2017 23:44:32 +0200 Subject: [PATCH] Implemented latest feedback from #28235 --- .vscode/tasks.json | 73 +- extensions/grunt/package.json | 18 +- extensions/grunt/src/main.ts | 14 +- extensions/gulp/src/main.ts | 12 +- extensions/jake/package.json | 18 +- extensions/jake/src/main.ts | 12 +- extensions/npm/src/main.ts | 14 +- .../typescript/src/features/taskProvider.ts | 8 +- src/vs/vscode.proposed.d.ts | 60 +- src/vs/workbench/api/node/extHostTask.ts | 21 +- src/vs/workbench/api/node/extHostTypes.ts | 69 +- .../parts/tasks/browser/quickOpen.ts | 6 +- .../parts/tasks/common/taskTypeRegistry.ts | 123 +++ src/vs/workbench/parts/tasks/common/tasks.ts | 142 ++- .../tasks/electron-browser/jsonSchema_v2.ts | 44 +- .../electron-browser/task.contribution.ts | 117 +-- .../electron-browser/terminalTaskSystem.ts | 4 +- .../parts/tasks/node/processRunnerDetector.ts | 11 +- .../parts/tasks/node/processTaskSystem.ts | 4 +- .../{common => node}/taskConfiguration.ts | 876 +++++++++++------- .../configuration.test.ts | 67 +- 21 files changed, 1121 insertions(+), 592 deletions(-) create mode 100644 src/vs/workbench/parts/tasks/common/taskTypeRegistry.ts rename src/vs/workbench/parts/tasks/{common => node}/taskConfiguration.ts (69%) rename src/vs/workbench/parts/tasks/test/{node => electron-browser}/configuration.test.ts (96%) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 4ad8767d06c..77d26a05b04 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,23 +1,14 @@ { - "version": "0.1.0", - "windows": { - "command": ".\\node_modules\\.bin\\gulp" - }, - "osx": { - "command": "./node_modules/.bin/gulp" - }, - "linux": { - "command": "./node_modules/.bin/gulp" - }, - "isShellCommand": true, + "version": "2.0.0", "tasks": [ { - "taskName": "watch", - "args": [ - "--no-color" - ], - "isBuildCommand": true, + "customize": "vscode.npm.run watch", + "taskName": "Build VS Code", + "group": "build", "isBackground": true, + "terminal": { + "reveal": "never" + }, "problemMatcher": { "owner": "typescript", "applyTo": "closedDocuments", @@ -37,38 +28,34 @@ } }, { - "taskName": "tslint", - "args": [], - "problemMatcher": { - "owner": "tslint", - "fileLocation": [ - "relative", - "${workspaceRoot}" - ], - "severity": "warning", - "pattern": { - "regexp": "(.*)\\[(\\d+),\\s(\\d+)\\]:\\s(.*)$", // (.*)\[(\d+), (\d+)\]: (.*) - "file": 1, - "line": 2, - "column": 3, - "message": 4 - } + "customize": "gulp.tslint", + "taskName": "Run tslint", + "problemMatcher": ["$tslint4"] + }, + { + "taskName": "Run tests", + "type": "shell", + "command": "./scripts/test.sh", + "windows": { + "command": ".\\scripts\\test.bat" + }, + "group": "test", + "terminal": { + "echo": true, + "reveal": "always" } }, { - "taskName": "test", - "args": [ - "--no-color" - ], - "showOutput": "always", - "isTestCommand": true + "taskName": "Run Dev", + "type": "shell", + "command": "./scripts/code.sh", + "windows": { + "command": ".\\scripts\\code.bat" + } }, { - "taskName": "electron", - "args": [ - "--no-color" - ], - "showOutput": "never" + "customize": "gulp.electron", + "taskName": "Download electron" } ] } \ No newline at end of file diff --git a/extensions/grunt/package.json b/extensions/grunt/package.json index 274d158ab28..94ea88bbc30 100644 --- a/extensions/grunt/package.json +++ b/extensions/grunt/package.json @@ -41,6 +41,22 @@ "description": "%config.grunt.autoDetect%" } } - } + }, + "taskTypes": [ + { + "taskType": "grunt", + "required": ["task"], + "properties": { + "task": { + "type": "string", + "description": "The Grunt task to customize" + }, + "file": { + "type": "string", + "description": "The Grunt file that provides the task. Can be omitted." + } + } + } + ] } } \ No newline at end of file diff --git a/extensions/grunt/src/main.ts b/extensions/grunt/src/main.ts index 6af05a0d312..a3a933e0043 100644 --- a/extensions/grunt/src/main.ts +++ b/extensions/grunt/src/main.ts @@ -81,6 +81,11 @@ function getOutputChannel(): vscode.OutputChannel { return _channel; } +interface GruntTaskIdentifier extends vscode.TaskIdentifier { + task: string; + file?: string; +} + async function getGruntTasks(): Promise { let workspaceRoot = vscode.workspace.rootPath; let emptyTasks: vscode.Task[] = []; @@ -145,10 +150,13 @@ async function getGruntTasks(): Promise { let matches = regExp.exec(line); if (matches && matches.length === 2) { let taskName = matches[1]; + let identifier: GruntTaskIdentifier = { + type: 'grunt', + task: taskName + }; let task = taskName.indexOf(' ') === -1 - ? new vscode.ShellTask(taskName, `${command} ${taskName}`) - : new vscode.ShellTask(taskName, `${command} "${taskName}"`); - task.identifier = `grunt.${taskName}`; + ? new vscode.ShellTask(identifier, taskName, `${command} ${taskName}`) + : new vscode.ShellTask(identifier, taskName, `${command} "${taskName}"`); result.push(task); let lowerCaseTaskName = taskName.toLowerCase(); if (lowerCaseTaskName === 'build') { diff --git a/extensions/gulp/src/main.ts b/extensions/gulp/src/main.ts index 1de05aa70d8..49b13ca1d85 100644 --- a/extensions/gulp/src/main.ts +++ b/extensions/gulp/src/main.ts @@ -81,6 +81,11 @@ function getOutputChannel(): vscode.OutputChannel { return _channel; } +interface GulpTaskIdentifier extends vscode.TaskIdentifier { + task: string; + file?: string; +} + async function getGulpTasks(): Promise { let workspaceRoot = vscode.workspace.rootPath; let emptyTasks: vscode.Task[] = []; @@ -121,8 +126,11 @@ async function getGulpTasks(): Promise { if (line.length === 0) { continue; } - let task = new vscode.ShellTask(line, `${gulpCommand} ${line}`); - task.identifier = `gulp.${line}`; + let identifier: GulpTaskIdentifier = { + type: 'gulp', + task: line + }; + let task = new vscode.ShellTask(identifier, line, `${gulpCommand} ${line}`); result.push(task); let lowerCaseLine = line.toLowerCase(); if (lowerCaseLine === 'build') { diff --git a/extensions/jake/package.json b/extensions/jake/package.json index 16892a14655..cb3eca33145 100644 --- a/extensions/jake/package.json +++ b/extensions/jake/package.json @@ -41,6 +41,22 @@ "description": "%config.jake.autoDetect%" } } - } + }, + "taskTypes": [ + { + "taskType": "jake", + "required": ["task"], + "properties": { + "task": { + "type": "string", + "description": "The Jake task to customize" + }, + "file": { + "type": "string", + "description": "The Jake file that provides the task. Can be omitted." + } + } + } + ] } } \ No newline at end of file diff --git a/extensions/jake/src/main.ts b/extensions/jake/src/main.ts index eb87180ca78..1e9ef387aba 100644 --- a/extensions/jake/src/main.ts +++ b/extensions/jake/src/main.ts @@ -81,6 +81,11 @@ function getOutputChannel(): vscode.OutputChannel { return _channel; } +interface JakeTaskIdentifier extends vscode.TaskIdentifier { + task: string; + file?: string; +} + async function getJakeTasks(): Promise { let workspaceRoot = vscode.workspace.rootPath; let emptyTasks: vscode.Task[] = []; @@ -125,8 +130,11 @@ async function getJakeTasks(): Promise { let matches = regExp.exec(line); if (matches && matches.length === 2) { let taskName = matches[1]; - let task = new vscode.ShellTask(taskName, `${jakeCommand} ${taskName}`); - task.identifier = `jake.${taskName}`; + let identifier: JakeTaskIdentifier = { + type: 'jake', + task: taskName + }; + let task = new vscode.ShellTask(identifier, taskName, `${jakeCommand} ${taskName}`); result.push(task); let lowerCaseLine = line.toLowerCase(); if (lowerCaseLine === 'build') { diff --git a/extensions/npm/src/main.ts b/extensions/npm/src/main.ts index beeddfa5a86..e115aa95986 100644 --- a/extensions/npm/src/main.ts +++ b/extensions/npm/src/main.ts @@ -59,6 +59,12 @@ async function readFile(file: string): Promise { }); } +interface NpmTaskIdentifier extends vscode.TaskIdentifier { + script: string; + file?: string; +} + + async function getNpmScriptsAsTasks(): Promise { let workspaceRoot = vscode.workspace.rootPath; let emptyTasks: vscode.Task[] = []; @@ -81,7 +87,11 @@ async function getNpmScriptsAsTasks(): Promise { const result: vscode.Task[] = []; Object.keys(json.scripts).forEach(each => { - const task = new vscode.ShellTask(`run ${each}`, `npm run ${each}`); + const identifier: NpmTaskIdentifier = { + type: 'npm', + script: each + }; + const task = new vscode.ShellTask(identifier, `run ${each}`, `npm run ${each}`); const lowerCaseTaskName = each.toLowerCase(); if (lowerCaseTaskName === 'build') { task.group = vscode.TaskGroup.Build; @@ -91,7 +101,7 @@ async function getNpmScriptsAsTasks(): Promise { result.push(task); }); // add some 'well known' npm tasks - result.push(new vscode.ShellTask(`install`, `npm install`)); + result.push(new vscode.ShellTask({ type: 'npm', script: 'install' } as NpmTaskIdentifier, `install`, `npm install`)); return Promise.resolve(result); } catch (e) { return Promise.resolve(emptyTasks); diff --git a/extensions/typescript/src/features/taskProvider.ts b/extensions/typescript/src/features/taskProvider.ts index fc15145b65e..0cb9034224a 100644 --- a/extensions/typescript/src/features/taskProvider.ts +++ b/extensions/typescript/src/features/taskProvider.ts @@ -21,6 +21,11 @@ const exists = (file: string): Promise => }); }); + +interface TypeScriptTaskIdentifier extends vscode.TaskIdentifier { + configFile: string; +} + /** * Provides tasks for building `tsconfig.json` files in a project. */ @@ -48,7 +53,8 @@ class TscTaskProvider implements vscode.TaskProvider { return projects.map(configFile => { const configFileName = path.relative(rootPath, configFile); - const buildTask = new vscode.ShellTask(`build ${configFileName}`, `${command} -p "${configFile}"`, '$tsc'); + const identifier: TypeScriptTaskIdentifier = { type: 'typescript', configFile: configFileName }; + const buildTask = new vscode.ShellTask(identifier, `build ${configFileName}`, `${command} -p "${configFile}"`, '$tsc'); buildTask.source = 'tsc'; buildTask.group = vscode.TaskGroup.Build; return buildTask; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 2faeac7e3ee..666734b3540 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -122,6 +122,21 @@ declare module 'vscode' { export const Test: 'test'; } + /** + * An identifer to uniquely identify a task in the system. + * The value must be JSON-stringifyable. + */ + export interface TaskIdentifier { + /** + * The task type as defined by the extension implementing a + * task provider. Examples are 'grunt', 'npm' or 'tsc'. + * Usually a task provider defines more properties to identify + * a task. They need to be defined in the package.json of the + * extension under the 'taskTypes' extension point. + */ + readonly type: string; + } + /** * A task that starts an external process. */ @@ -130,17 +145,19 @@ declare module 'vscode' { /** * Creates a process task. * + * @param identifier: the task's identifier as defined in the 'taskTypes' extension point. * @param name the task's name. Is presented in the user interface. * @param process the process to start. * @param problemMatchers the names of problem matchers to use, like '$tsc' * or '$eslint'. Problem matchers can be contributed by an extension using * the `problemMatchers` extension point. */ - constructor(name: string, process: string, problemMatchers?: string | string[]); + constructor(identifier: TaskIdentifier, name: string, process: string, problemMatchers?: string | string[]); /** * Creates a process task. * + * @param identifier: the task's identifier as defined in the 'taskTypes' extension point. * @param name the task's name. Is presented in the user interface. * @param process the process to start. * @param args arguments to be passed to the process. @@ -148,11 +165,12 @@ declare module 'vscode' { * or '$eslint'. Problem matchers can be contributed by an extension using * the `problemMatchers` extension point. */ - constructor(name: string, process: string, args: string[], problemMatchers?: string | string[]); + constructor(identifier: TaskIdentifier, name: string, process: string, args: string[], problemMatchers?: string | string[]); /** * Creates a process task. * + * @param identifier: the task's identifier as defined in the 'taskTypes' extension point. * @param name the task's name. Is presented in the user interface. * @param process the process to start. * @param args arguments to be passed to the process. @@ -161,18 +179,17 @@ declare module 'vscode' { * or '$eslint'. Problem matchers can be contributed by an extension using * the `problemMatchers` extension point. */ - constructor(name: string, process: string, args: string[], options: ProcessTaskOptions, problemMatchers?: string | string[]); + constructor(identifier: TaskIdentifier, name: string, process: string, args: string[], options: ProcessTaskOptions, problemMatchers?: string | string[]); + + /** + * The task's identifier. + */ + identifier: TaskIdentifier; /** * The task's name */ - readonly name: string; - - /** - * The task's identifier. If omitted the internal identifier will - * be `${extensionName}:${name}` - */ - identifier: string | undefined; + name: string; /** * Whether the task is a background task or not. @@ -182,7 +199,7 @@ declare module 'vscode' { /** * The process to be executed. */ - readonly process: string; + process: string; /** * The arguments passed to the process. Defaults to an empty array. @@ -280,17 +297,19 @@ declare module 'vscode' { /** * Creates a shell task. * + * @param identifier: the task's identifier as defined in the 'taskTypes' extension point. * @param name the task's name. Is presented in the user interface. * @param commandLine the command line to execute. * @param problemMatchers the names of problem matchers to use, like '$tsc' * or '$eslint'. Problem matchers can be contributed by an extension using * the `problemMatchers` extension point. */ - constructor(name: string, commandLine: string, problemMatchers?: string | string[]); + constructor(identifier: TaskIdentifier, name: string, commandLine: string, problemMatchers?: string | string[]); /** * Creates a shell task. * + * @param identifier: the task's identifier as defined in the 'taskTypes' extension point. * @param name the task's name. Is presented in the user interface. * @param commandLine the command line to execute. * @param options additional options used when creating the shell. @@ -298,18 +317,17 @@ declare module 'vscode' { * or '$eslint'. Problem matchers can be contributed by an extension using * the `problemMatchers` extension point. */ - constructor(name: string, commandLine: string, options: ShellTaskOptions, problemMatchers?: string | string[]); + constructor(identifier: TaskIdentifier, name: string, commandLine: string, options: ShellTaskOptions, problemMatchers?: string | string[]); + + /** + * The task's identifier. + */ + identifier: TaskIdentifier; /** * The task's name */ - readonly name: string; - - /** - * The task's identifier. If omitted the internal identifier will - * be `${extensionName}:${name}` - */ - identifier: string | undefined; + name: string; /** * Whether the task is a background task or not. @@ -319,7 +337,7 @@ declare module 'vscode' { /** * The command line to execute. */ - readonly commandLine: string; + commandLine: string; /** * A human-readable string describing the source of this diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index e66f2582316..bfbcfb16fa9 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -7,6 +7,7 @@ import * as nls from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import * as UUID from 'vs/base/common/uuid'; +import * as Objects from 'vs/base/common/objects'; import { asWinJsPromise } from 'vs/base/common/async'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -315,7 +316,7 @@ namespace Tasks { return result; } - function fromSingle(task: vscode.Task, extension: IExtensionDescription, uuidMap: UUIDMap): TaskSystem.Task { + function fromSingle(task: vscode.Task, extension: IExtensionDescription, uuidMap: UUIDMap): TaskSystem.ContributedTask { if (typeof task.name !== 'string') { return undefined; } @@ -336,12 +337,20 @@ namespace Tasks { detail: extension.id }; let label = nls.localize('task.label', '{0}: {1}', source.label, task.name); - let result: TaskSystem.Task = { - _id: uuidMap.getUUID(task.identifier), + let id = `${extension.id}.${task.identifierKey}`; + let identifier: TaskSystem.TaskIdentifier = { + _key: task.identifierKey, + type: task.identifier.type + }; + Objects.assign(identifier, task.identifier); + let result: TaskSystem.ContributedTask = { + _id: id, // uuidMap.getUUID(identifier), _source: source, _label: label, + type: task.identifier.type, + defines: identifier, name: task.name, - identifier: task.identifier ? task.identifier : `${extension.id}.${task.name}`, + identifier: label, group: types.TaskGroup.is(task.group) ? task.group : undefined, command: command, isBackground: !!task.isBackground, @@ -357,7 +366,7 @@ namespace Tasks { let result: TaskSystem.CommandConfiguration = { name: value.process, args: Strings.from(value.args), - type: TaskSystem.CommandType.Process, + runtime: TaskSystem.RuntimeType.Process, suppressTaskName: true, presentation: PresentationOptions.from(value.presentationOptions) }; @@ -373,7 +382,7 @@ namespace Tasks { } let result: TaskSystem.CommandConfiguration = { name: value.commandLine, - type: TaskSystem.CommandType.Shell, + runtime: TaskSystem.RuntimeType.Shell, presentation: PresentationOptions.from(value.presentationOptions) }; if (value.options) { diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index ed530ee907a..b6a8d7604d3 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import * as crypto from 'crypto'; + import URI from 'vs/base/common/uri'; import { illegalArgument } from 'vs/base/common/errors'; import * as vscode from 'vscode'; @@ -1049,40 +1051,53 @@ export class BaseTask { private _name: string; private _problemMatchers: string[]; - private _identifier: string; + private _identifier: vscode.TaskIdentifier; + private _identifierKey: string; private _isBackground: boolean; private _source: string; private _group: string; private _presentationOptions: vscode.TaskPresentationOptions; - constructor(name: string, problemMatchers: string[]) { - if (typeof name !== 'string') { - throw illegalArgument('name'); - } - this._name = name; + constructor(identifier: vscode.TaskIdentifier, name: string, problemMatchers: string[]) { + this.identifier = identifier; + this.name = name; this._problemMatchers = problemMatchers || []; this._isBackground = false; this._presentationOptions = Object.create(null); } - get identifier(): string { + get identifier(): vscode.TaskIdentifier { return this._identifier; } - set identifier(value: string) { + set identifier(value: vscode.TaskIdentifier) { if (value === void 0 || value === null) { - this._identifier = undefined; - } - if (typeof value !== 'string' || value.length === 0) { - throw illegalArgument('identifier must be a string of length > 0'); + throw illegalArgument('Identifier can\'t be undefined or null'); } + this._identifierKey = undefined; this._identifier = value; } + get identifierKey(): string { + if (!this._identifierKey) { + const hash = crypto.createHash('md5'); + hash.update(JSON.stringify(this._identifier)); + this._identifierKey = hash.digest('hex'); + } + return this._identifierKey; + } + get name(): string { return this._name; } + set name(value: string) { + if (typeof value !== 'string') { + throw illegalArgument('name'); + } + this._name = value; + } + get isBackground(): boolean { return this._isBackground; } @@ -1194,9 +1209,9 @@ export class ProcessTask extends BaseTask { private _args: string[]; private _options: vscode.ProcessTaskOptions; - constructor(name: string, process: string, args?: string[], problemMatchers?: string | string[]); - constructor(name: string, process: string, args: string[] | undefined, options: vscode.ProcessTaskOptions, problemMatchers?: string | string[]); - constructor(name: string, process: string, arg3?: string[], arg4?: vscode.ProcessTaskOptions | string | string[], arg5?: string | string[]) { + constructor(identifier: vscode.TaskIdentifier, name: string, process: string, args?: string[], problemMatchers?: string | string[]); + constructor(identifier: vscode.TaskIdentifier, name: string, process: string, args: string[] | undefined, options: vscode.ProcessTaskOptions, problemMatchers?: string | string[]); + constructor(identifier: vscode.TaskIdentifier, name: string, process: string, varg1?: string[], varg2?: vscode.ProcessTaskOptions | string | string[], varg3?: string | string[]) { if (typeof process !== 'string') { throw illegalArgument('process'); } @@ -1204,16 +1219,16 @@ export class ProcessTask extends BaseTask { let options: vscode.ProcessTaskOptions; let problemMatchers: string | string[]; - args = arg3 || []; - if (arg4) { - if (Array.isArray(arg4) || typeof arg4 === 'string') { - problemMatchers = arg4; + args = varg1 || []; + if (varg2) { + if (Array.isArray(varg2) || typeof varg2 === 'string') { + problemMatchers = varg2; } else { - options = arg4; + options = varg2; } } - if (arg5 && !problemMatchers) { - problemMatchers = arg5; + if (varg3 && !problemMatchers) { + problemMatchers = varg3; } let pm: string[]; if (problemMatchers && (typeof problemMatchers === 'string')) { @@ -1222,7 +1237,7 @@ export class ProcessTask extends BaseTask { pm = problemMatchers; } pm = pm || []; - super(name, pm); + super(identifier, name, pm); this._process = process; this._args = args; this._options = options || Object.create(null); @@ -1260,9 +1275,9 @@ export class ShellTask extends BaseTask implements vscode.ShellTask { private _commandLine: string; private _options: vscode.ShellTaskOptions; - constructor(name: string, commandLine: string, problemMatchers?: string | string[]); - constructor(name: string, commandLine: string, options: vscode.ShellTaskOptions, problemMatchers?: string | string[]); - constructor(name: string, commandLine: string, optionsOrProblemMatchers?: vscode.ShellTaskOptions | string | string[], problemMatchers?: string | string[]) { + constructor(identifier: vscode.TaskIdentifier, name: string, commandLine: string, problemMatchers?: string | string[]); + constructor(identifier: vscode.TaskIdentifier, name: string, commandLine: string, options: vscode.ShellTaskOptions, problemMatchers?: string | string[]); + constructor(identifier: vscode.TaskIdentifier, name: string, commandLine: string, optionsOrProblemMatchers?: vscode.ShellTaskOptions | string | string[], problemMatchers?: string | string[]) { if (typeof commandLine !== 'string') { throw illegalArgument('commandLine'); } @@ -1279,7 +1294,7 @@ export class ShellTask extends BaseTask implements vscode.ShellTask { pm = problemMatchers; } pm = pm || []; - super(name, pm); + super(identifier, name, pm); this._commandLine = commandLine; this._options = options || Object.create(null); } diff --git a/src/vs/workbench/parts/tasks/browser/quickOpen.ts b/src/vs/workbench/parts/tasks/browser/quickOpen.ts index 35b7a680080..7438f990001 100644 --- a/src/vs/workbench/parts/tasks/browser/quickOpen.ts +++ b/src/vs/workbench/parts/tasks/browser/quickOpen.ts @@ -78,7 +78,7 @@ export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler { let configured: Task[] = []; let detected: Task[] = []; let taskMap: IStringDictionary = Object.create(null); - tasks.forEach(task => taskMap[task.identifier] = task); + tasks.forEach(task => taskMap[Task.getKey(task)] = task); recentlyUsedTasks.keys().forEach(key => { let task = taskMap[key]; if (task) { @@ -86,7 +86,7 @@ export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler { } }); for (let task of tasks) { - if (!recentlyUsedTasks.has(task.identifier)) { + if (!recentlyUsedTasks.has(Task.getKey(task))) { if (task._source.kind === TaskSourceKind.Workspace) { configured.push(task); } else { @@ -98,7 +98,7 @@ export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler { this.fillEntries(entries, input, recent, nls.localize('recentlyUsed', 'recently used tasks')); configured = configured.sort((a, b) => a._label.localeCompare(b._label)); let hasConfigured = configured.length > 0; - this.fillEntries(entries, input, configured, nls.localize('configured', 'configured tasks'), hasRecentlyUsed); + this.fillEntries(entries, input, configured, nls.localize('configured', 'custom tasks'), hasRecentlyUsed); detected = detected.sort((a, b) => a._label.localeCompare(b._label)); this.fillEntries(entries, input, detected, nls.localize('detected', 'detected tasks'), hasRecentlyUsed || hasConfigured); return new Model.QuickOpenModel(entries, new ContributableActionProvider()); diff --git a/src/vs/workbench/parts/tasks/common/taskTypeRegistry.ts b/src/vs/workbench/parts/tasks/common/taskTypeRegistry.ts new file mode 100644 index 00000000000..dcd613f4062 --- /dev/null +++ b/src/vs/workbench/parts/tasks/common/taskTypeRegistry.ts @@ -0,0 +1,123 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as nls from 'vs/nls'; +import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { TPromise } from 'vs/base/common/winjs.base'; +import * as Types from 'vs/base/common/types'; +import * as Objects from 'vs/base/common/objects'; + +import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry'; + +import * as Tasks from 'vs/workbench/parts/tasks/common/tasks'; + + +const taskTypeSchema: IJSONSchema = { + type: 'object', + additionalProperties: false, + properties: { + taskType: { + type: 'string', + description: nls.localize('TaskType.description', 'The actual task type') + }, + properties: { + type: 'object', + description: nls.localize('TaskType.properties', 'Additional properties of the task type'), + additionalProperties: { + $ref: 'http://json-schema.org/draft-04/schema#' + } + } + } +}; + +namespace Configuration { + export interface TaskTypeDescription { + taskType?: string; + required?: string[]; + properties?: IJSONSchemaMap; + } + + export function from(value: TaskTypeDescription, messageCollector: ExtensionMessageCollector): Tasks.TaskTypeDescription { + if (!value) { + return undefined; + } + let taskType = Types.isString(value.taskType) ? value.taskType : undefined; + if (!taskType || taskType.length === 0) { + messageCollector.error(nls.localize('TaskTypeConfiguration.noType', 'The task type configuration is missing the required \'taskType\' property')); + return undefined; + } + let required: string[] = []; + if (Array.isArray(value.required)) { + for (let element of value.required) { + if (Types.isString(element)) { + required.push(element); + } + } + } + return { taskType, required: required.length >= 0 ? required : undefined, properties: value.properties ? Objects.deepClone(value.properties) : undefined }; + } +} + + +const taskTypesExtPoint = ExtensionsRegistry.registerExtensionPoint('taskTypes', [], { + description: nls.localize('TaskTypeExtPoint', 'Contributes task types'), + type: 'array', + items: taskTypeSchema +}); + +export interface ITaskTypeRegistry { + onReady(): TPromise; + + exists(key: string): boolean; + get(key: string): Tasks.TaskTypeDescription; + all(): Tasks.TaskTypeDescription[]; +} + +class TaskTypeRegistryImpl implements ITaskTypeRegistry { + + private taskTypes: IStringDictionary; + private readyPromise: TPromise; + + constructor() { + this.taskTypes = Object.create(null); + this.readyPromise = new TPromise((resolve, reject) => { + taskTypesExtPoint.setHandler((extensions) => { + try { + extensions.forEach(extension => { + let taskTypes = extension.value; + for (let taskType of taskTypes) { + let type = Configuration.from(taskType, extension.collector); + if (type) { + this.taskTypes[type.taskType] = type; + } + } + }); + } catch (error) { + } + resolve(undefined); + }); + }); + } + + public onReady(): TPromise { + return this.readyPromise; + } + + public get(key: string): Tasks.TaskTypeDescription { + return this.taskTypes[key]; + } + + public exists(key: string): boolean { + return !!this.taskTypes[key]; + } + + public all(): Tasks.TaskTypeDescription[] { + return Object.keys(this.taskTypes).map(key => this.taskTypes[key]); + } +} + +export const TaskTypeRegistry: ITaskTypeRegistry = new TaskTypeRegistryImpl(); \ No newline at end of file diff --git a/src/vs/workbench/parts/tasks/common/tasks.ts b/src/vs/workbench/parts/tasks/common/tasks.ts index d84a4ce1f14..deaee2789eb 100644 --- a/src/vs/workbench/parts/tasks/common/tasks.ts +++ b/src/vs/workbench/parts/tasks/common/tasks.ts @@ -5,6 +5,7 @@ 'use strict'; import * as Types from 'vs/base/common/types'; +import { IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ProblemMatcher } from 'vs/platform/markers/common/problemMatcher'; @@ -140,20 +141,20 @@ export interface PresentationOptions { panel: PanelKind; } -export enum CommandType { +export enum RuntimeType { Shell = 1, Process = 2 } -export namespace CommandType { - export function fromString(value: string): CommandType { +export namespace RuntimeType { + export function fromString(value: string): RuntimeType { switch (value.toLowerCase()) { case 'shell': - return CommandType.Shell; + return RuntimeType.Shell; case 'process': - return CommandType.Process; + return RuntimeType.Process; default: - return CommandType.Process; + return RuntimeType.Process; } } } @@ -163,7 +164,7 @@ export interface CommandConfiguration { /** * The task type */ - type: CommandType; + runtime: RuntimeType; /** * The command to execute @@ -225,40 +226,22 @@ export interface TaskSource { detail?: string; } -/** - * A task description - */ -export interface Task { +export interface TaskIdentifier { + _key: string; + type: string; +} - /** - * The task's internal id - */ - _id: string; - - /** - * The cached label. - */ - _label: string; - - /** - * Indicated the source of the task (e.g tasks.json or extension) - */ - _source: TaskSource; +export interface ConfigurationProperties { /** * The task's name */ - name: string; + name?: string; /** - * The task's identifier. + * The task's name */ - identifier: string; - - /** - * The id of the customized task - */ - customize?: string; + identifier?: string; /** * the task's group; @@ -266,9 +249,9 @@ export interface Task { group?: string; /** - * The command configuration + * The presentation options */ - command: CommandConfiguration; + presentation?: PresentationOptions; /** * Whether the task is a background task or not. @@ -291,6 +274,89 @@ export interface Task { problemMatchers?: (string | ProblemMatcher)[]; } +export interface CommonTask { + + /** + * The task's internal id + */ + _id: string; + + /** + * The cached label. + */ + _label: string; + + /** + * Indicated the source of the task (e.g tasks.json or extension) + */ + _source: TaskSource; + + type: string; +} + +export interface CustomTask extends CommonTask, ConfigurationProperties { + + type: 'custom'; + + name: string; + + identifier: string; + + /** + * The command configuration + */ + command: CommandConfiguration; +} + +export namespace CustomTask { + export function is(value: any): value is CustomTask { + let candidate: CustomTask = value; + return candidate && candidate.type === 'custom'; + } +} + +export interface ConfiguringTask extends CommonTask, ConfigurationProperties { + + configures: TaskIdentifier; +} + +export namespace ConfiguringTask { + export function is(value: any): value is ConfiguringTask { + let candidate: ConfiguringTask = value; + return candidate && candidate.configures && Types.isString(candidate.configures.type) && value.command === void 0; + } +} + +export interface ContributedTask extends CommonTask, ConfigurationProperties { + + defines: TaskIdentifier; + + /** + * The command configuration + */ + command: CommandConfiguration; +} + +export namespace ContributedTask { + export function is(value: any): value is ContributedTask { + let candidate: ContributedTask = value; + return candidate && candidate.defines && Types.isString(candidate.defines.type) && candidate.command !== void 0; + } +} + +export type Task = CustomTask | ContributedTask; + +export namespace Task { + export function getKey(task: Task): string { + if (CustomTask.is(task)) { + return task.identifier; + } else { + return task.defines._key; + } + } +} + + export enum ExecutionEngine { Process = 1, Terminal = 2 @@ -304,4 +370,10 @@ export enum JsonSchemaVersion { export interface TaskSet { tasks: Task[]; extension?: IExtensionDescription; +} + +export interface TaskTypeDescription { + taskType: string; + required: string[]; + properties: IJSONSchemaMap; } \ No newline at end of file diff --git a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts b/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts index f9c2f251a89..127ebb2d002 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts @@ -11,6 +11,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import commonSchema from './jsonSchemaCommon'; import { ProblemMatcherRegistry } from 'vs/platform/markers/common/problemMatcher'; +import { TaskTypeRegistry } from '../common/taskTypeRegistry'; const shellCommand: IJSONSchema = { anyOf: [ @@ -96,9 +97,42 @@ const version: IJSONSchema = { description: nls.localize('JsonSchema.version', 'The config\'s version number.') }; -const customize: IJSONSchema = { - type: 'string', - description: nls.localize('JsonSchema.tasks.customize', 'The contributed task to be customized.') +const customizeTaskDescription: IJSONSchema = Objects.deepClone(commonSchema.definitions.taskDescription); +customizeTaskDescription.required = undefined; +customizeTaskDescription.properties.presentation = Objects.deepClone(presentation); + +TaskTypeRegistry.onReady().then(() => { + let oneOf: IJSONSchema[] = []; + for (let taskType of TaskTypeRegistry.all()) { + let schema: IJSONSchema = { + type: 'object', + properties: { + } + }; + schema.properties.type = { + type: 'string', + description: nls.localize('JsonSchema.customizations.customizes.type', 'The task system to customize'), + enum: [taskType.taskType] + }; + if (taskType.required) { + schema.required = taskType.required.slice(); + } + for (let key of Object.keys(taskType.properties)) { + let property = taskType.properties[key]; + schema.properties[key] = Objects.deepClone(property); + } + oneOf.push(schema); + } + customizeTaskDescription.properties.customizes = { + description: nls.localize('JsonSchema.customizations.customizes', 'The task to be customized'), + oneOf + }; +}); + +const customizations: IJSONSchema = { + type: 'array', + description: nls.localize('JsonSchema.customizations', 'The tasks to be customized'), + items: customizeTaskDescription }; const schema: IJSONSchema = { @@ -121,7 +155,8 @@ const schema: IJSONSchema = { linux: { '$ref': '#/definitions/taskRunnerConfiguration', 'description': nls.localize('JsonSchema.linux', 'Linux specific command configuration') - } + }, + customizations: customizations } }, { @@ -141,7 +176,6 @@ definitions.taskDescription.properties.dependsOn = dependsOn; // definitions.taskDescription.properties.echoCommand.deprecationMessage = nls.localize('JsonSchema.tasks.echoCommand.deprecated', 'The property echoCommand is deprecated. Use the terminal property instead.'); // definitions.taskDescription.properties.isBuildCommand.deprecationMessage = nls.localize('JsonSchema.tasks.isBuildCommand.deprecated', 'The property isBuildCommand is deprecated. Use the group property instead.'); // definitions.taskDescription.properties.isTestCommand.deprecationMessage = nls.localize('JsonSchema.tasks.isTestCommand.deprecated', 'The property isTestCommand is deprecated. Use the group property instead.'); -definitions.taskDescription.properties.customize = customize; definitions.taskDescription.properties.type = Objects.deepClone(taskType); definitions.taskDescription.properties.presentation = presentation; definitions.taskDescription.properties.group = group; diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 13697f20a92..13c474bcb08 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - 'use strict'; import 'vs/css!./media/task.contribution'; @@ -52,6 +51,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; + import jsonContributionRegistry = require('vs/platform/jsonschemas/common/jsonContributionRegistry'); import { IJSONSchema } from 'vs/base/common/jsonSchema'; @@ -75,11 +75,11 @@ import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal'; import { ITaskSystem, ITaskResolver, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskSystemEvents, TaskTerminateResponse } from 'vs/workbench/parts/tasks/common/taskSystem'; -import { Task, TaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind } from 'vs/workbench/parts/tasks/common/tasks'; +import { Task, CustomTask, ConfiguringTask, ContributedTask, TaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, TaskIdentifier } from 'vs/workbench/parts/tasks/common/tasks'; import { ITaskService, TaskServiceEvents, ITaskProvider, TaskEvent } from 'vs/workbench/parts/tasks/common/taskService'; import { templates as taskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates'; -import * as TaskConfig from 'vs/workbench/parts/tasks/common/taskConfiguration'; +import * as TaskConfig from '../node/taskConfiguration'; import { ProcessTaskSystem } from 'vs/workbench/parts/tasks/node/processTaskSystem'; import { TerminalTaskSystem } from './terminalTaskSystem'; import { ProcessRunnerDetector } from 'vs/workbench/parts/tasks/node/processRunnerDetector'; @@ -513,8 +513,8 @@ class ProblemReporter implements TaskConfig.IProblemReporter { interface WorkspaceTaskResult { set: TaskSet; - annotatingTasks: { - byIdentifier: IStringDictionary; + configurations: { + byIdentifier: IStringDictionary; }; hasErrors: boolean; } @@ -821,7 +821,7 @@ class TaskService extends EventEmitter implements ITaskService { } public customize(task: Task, openConfig: boolean = false): TPromise { - if (task._source.kind !== TaskSourceKind.Extension) { + if (!ContributedTask.is(task)) { return TPromise.as(undefined); } let configuration = this.getConfiguration(); @@ -830,20 +830,25 @@ class TaskService extends EventEmitter implements ITaskService { return TPromise.as(undefined); } let fileConfig = configuration.config; - let customize: TaskConfig.TaskDescription = { customize: task.identifier, taskName: task._label }; - if (task.problemMatchers === void 0) { - customize.problemMatcher = []; + let customizes: TaskConfig.ConfiguringTask = { + }; + let identifier: TaskConfig.TaskIdentifier = Objects.assign(Object.create(null), task.defines); + delete identifier['_key']; + Object.keys(identifier).forEach(key => customizes[key] = identifier[key]); + + if (task.problemMatchers === void 0 || task.problemMatchers.length === 0) { + customizes.problemMatcher = []; } if (!fileConfig) { fileConfig = { version: '2.0.0', - tasks: [customize] + tasks: [customizes] }; } else { if (Array.isArray(fileConfig.tasks)) { - fileConfig.tasks.push(customize); + fileConfig.tasks.push(customizes); } else { - fileConfig.tasks = [customize]; + fileConfig.tasks = [customizes]; } }; return this.configurationEditingService.writeConfiguration(ConfigurationTarget.WORKSPACE, { key: 'tasks', value: fileConfig }).then(() => { @@ -861,7 +866,7 @@ class TaskService extends EventEmitter implements ITaskService { } private createRunnableTask(sets: TaskSet[], group: TaskGroup): { task: Task; resolver: ITaskResolver } { - let uuidMap: IStringDictionary = Object.create(null); + let idMap: IStringDictionary = Object.create(null); let labelMap: IStringDictionary = Object.create(null); let identifierMap: IStringDictionary = Object.create(null); @@ -869,7 +874,7 @@ class TaskService extends EventEmitter implements ITaskService { let extensionTasks: Task[] = []; sets.forEach((set) => { set.tasks.forEach((task) => { - uuidMap[task._id] = task; + idMap[task._id] = task; labelMap[task._label] = task; identifierMap[task.identifier] = task; if (group && task.group === group) { @@ -883,7 +888,7 @@ class TaskService extends EventEmitter implements ITaskService { }); let resolver: ITaskResolver = { resolve: (id: string) => { - return uuidMap[id] || labelMap[id] || identifierMap[id]; + return idMap[id] || labelMap[id] || identifierMap[id]; } }; if (workspaceTasks.length > 0) { @@ -900,10 +905,11 @@ class TaskService extends EventEmitter implements ITaskService { return { task: extensionTasks[0], resolver }; } else { let id: string = UUID.generateUuid(); - let task: Task = { + let task: CustomTask = { _id: id, _source: { kind: TaskSourceKind.Generic, label: 'generic' }, _label: id, + type: 'custom', name: id, identifier: id, dependsOn: extensionTasks.map(task => task._id), @@ -937,7 +943,7 @@ class TaskService extends EventEmitter implements ITaskService { return ProblemMatcherRegistry.onReady().then(() => { return this.textFileService.saveAll().then((value) => { // make sure all dirty files are saved let executeResult = this.getTaskSystem().run(task, resolver); - this.getRecentlyUsedTasks().set(task.identifier, task.identifier, Touch.First); + this.getRecentlyUsedTasks().set(Task.getKey(task), Task.getKey(task), Touch.First); if (executeResult.kind === TaskExecuteKind.Active) { let active = executeResult.active; if (active.same) { @@ -1042,29 +1048,28 @@ class TaskService extends EventEmitter implements ITaskService { }).then((result) => { return this.getWorkspaceTasks().then((workspaceTaskResult) => { let workspaceTasksToDelete: Task[] = []; - let annotatingTasks = workspaceTaskResult.annotatingTasks; - let legacyAnnotatingTasks = workspaceTaskResult.set ? this.getLegacyAnnotatingTasks(workspaceTaskResult.set) : undefined; - if (annotatingTasks || legacyAnnotatingTasks) { + let configurations = workspaceTaskResult.configurations; + let legacyTaskConfigurations = workspaceTaskResult.set ? this.getLegacyTaskConfigurations(workspaceTaskResult.set) : undefined; + if (configurations || legacyTaskConfigurations) { for (let set of result) { - for (let task of set.tasks) { - if (annotatingTasks) { - let annotatingTask = annotatingTasks.byIdentifier[task.identifier]; - if (annotatingTask) { - TaskConfig.mergeTasks(task, annotatingTask); - task.name = annotatingTask.name; - task._label = annotatingTask._label; - task._source.kind = TaskSourceKind.Workspace; + for (let i = 0; i < set.tasks.length; i++) { + let task = set.tasks[i]; + if (!ContributedTask.is(task)) { + continue; + } + if (configurations) { + let configuredTask = configurations.byIdentifier[task.defines._key]; + if (configuredTask) { + set.tasks[i] = TaskConfig.createCustomTask(task, configuredTask); continue; } } - if (legacyAnnotatingTasks) { - let legacyAnnotatingTask = legacyAnnotatingTasks[task.identifier]; - if (legacyAnnotatingTask) { - TaskConfig.mergeTasks(task, legacyAnnotatingTask); - task._source.kind = TaskSourceKind.Workspace; - task.name = legacyAnnotatingTask.name; - task._label = legacyAnnotatingTask._label; - workspaceTasksToDelete.push(legacyAnnotatingTask); + if (legacyTaskConfigurations) { + let configuredTask = legacyTaskConfigurations[task.defines._key]; + if (configuredTask) { + set.tasks[i] = TaskConfig.createCustomTask(task, configuredTask); + workspaceTasksToDelete.push(configuredTask); + set.tasks[i] = configuredTask; continue; } } @@ -1096,7 +1101,7 @@ class TaskService extends EventEmitter implements ITaskService { }); } - private getLegacyAnnotatingTasks(workspaceTasks: TaskSet): IStringDictionary { + private getLegacyTaskConfigurations(workspaceTasks: TaskSet): IStringDictionary { let result: IStringDictionary; function getResult() { if (result) { @@ -1106,11 +1111,17 @@ class TaskService extends EventEmitter implements ITaskService { return result; } for (let task of workspaceTasks.tasks) { - let commandName = task.command && task.command.name; - // This is for backwards compatibility with the 0.1.0 task annotation code - // if we had a gulp, jake or grunt command a task specification was a annotation - if (commandName === 'gulp' || commandName === 'grunt' || commandName === 'jake') { - getResult()[`${commandName}.${task.name}`] = task; + if (CustomTask.is(task)) { + let commandName = task.command && task.command.name; + // This is for backwards compatibility with the 0.1.0 task annotation code + // if we had a gulp, jake or grunt command a task specification was a annotation + if (commandName === 'gulp' || commandName === 'grunt' || commandName === 'jake') { + let identifier: TaskIdentifier = TaskConfig.getTaskIdentifier({ + type: commandName, + task: task.name + } as TaskConfig.TaskIdentifier); + getResult()[identifier._key] = task; + } } } return result; @@ -1139,7 +1150,7 @@ class TaskService extends EventEmitter implements ITaskService { { let { config, hasParseErrors } = this.getConfiguration(); if (hasParseErrors) { - return TPromise.as({ set: undefined, hasErrors: true, annotatingTasks: undefined }); + return TPromise.as({ set: undefined, hasErrors: true, configurations: undefined }); } let engine = TaskConfig.ExecutionEngine._default; if (config) { @@ -1153,7 +1164,7 @@ class TaskService extends EventEmitter implements ITaskService { return { config, hasErrors }; } let result: TaskConfig.ExternalTaskRunnerConfiguration = Objects.clone(config); - let configuredTasks: IStringDictionary = Object.create(null); + let configuredTasks: IStringDictionary = Object.create(null); if (!result.tasks) { if (detectedConfig.tasks) { result.tasks = detectedConfig.tasks; @@ -1188,7 +1199,7 @@ class TaskService extends EventEmitter implements ITaskService { return configPromise.then((resolved) => { return ProblemMatcherRegistry.onReady().then((): WorkspaceTaskResult => { if (!resolved || !resolved.config) { - return { set: undefined, annotatingTasks: undefined, hasErrors: resolved !== void 0 ? resolved.hasErrors : false }; + return { set: undefined, configurations: undefined, hasErrors: resolved !== void 0 ? resolved.hasErrors : false }; } let problemReporter = new ProblemReporter(this._outputChannel); let parseResult = TaskConfig.parse(resolved.config, problemReporter); @@ -1199,18 +1210,18 @@ class TaskService extends EventEmitter implements ITaskService { } if (problemReporter.status.isFatal()) { problemReporter.fatal(nls.localize('TaskSystem.configurationErrors', 'Error: the provided task configuration has validation errors and can\'t not be used. Please correct the errors first.')); - return { set: undefined, annotatingTasks: undefined, hasErrors }; + return { set: undefined, configurations: undefined, hasErrors }; } - let annotatingTasks: { byIdentifier: IStringDictionary; }; - if (parseResult.annotatingTasks && parseResult.annotatingTasks.length > 0) { - annotatingTasks = { + let customizedTasks: { byIdentifier: IStringDictionary; }; + if (parseResult.configured && parseResult.configured.length > 0) { + customizedTasks = { byIdentifier: Object.create(null) }; - for (let task of parseResult.annotatingTasks) { - annotatingTasks.byIdentifier[task.customize] = task; + for (let task of parseResult.configured) { + customizedTasks.byIdentifier[task.configures._key] = task; } } - return { set: { tasks: parseResult.tasks }, annotatingTasks: annotatingTasks, hasErrors }; + return { set: { tasks: parseResult.custom }, configurations: customizedTasks, hasErrors }; }); }); } @@ -1623,4 +1634,4 @@ schema.oneOf = [...schemaVersion1.oneOf, ...schemaVersion2.oneOf]; let jsonRegistry = Registry.as(jsonContributionRegistry.Extensions.JSONContribution); -jsonRegistry.registerSchema(schemaId, schema); +jsonRegistry.registerSchema(schemaId, schema); \ No newline at end of file diff --git a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts index 43b7fde932d..20e15048e1a 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts @@ -33,7 +33,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; import { IOutputService, IOutputChannel } from 'vs/workbench/parts/output/common/output'; import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEvents } from 'vs/workbench/parts/tasks/common/problemCollectors'; -import { Task, RevealKind, CommandOptions, ShellConfiguration, CommandType, PanelKind } from 'vs/workbench/parts/tasks/common/tasks'; +import { Task, RevealKind, CommandOptions, ShellConfiguration, RuntimeType, PanelKind } from 'vs/workbench/parts/tasks/common/tasks'; import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver, TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType, TaskTerminateResponse @@ -406,7 +406,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { waitOnExit = nls.localize('reuseTerminal', 'Terminal will be reused by tasks, press any key to close it.'); }; let shellLaunchConfig: IShellLaunchConfig = undefined; - let isShellCommand = task.command.type === CommandType.Shell; + let isShellCommand = task.command.runtime === RuntimeType.Shell; if (isShellCommand) { if (Platform.isWindows && ((options.cwd && TPath.isUNC(options.cwd)) || (!options.cwd && TPath.isUNC(process.cwd())))) { throw new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive.'), TaskErrors.UnknownError); diff --git a/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts b/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts index a280054865e..ddab1546e7b 100644 --- a/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts +++ b/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts @@ -20,7 +20,8 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import * as TaskConfig from '../common/taskConfiguration'; +import * as Tasks from '../common/tasks'; +import * as TaskConfig from './taskConfiguration'; let build: string = 'build'; let test: string = 'test'; @@ -313,8 +314,8 @@ export class ProcessRunnerDetector { }); } - private createTaskDescriptions(tasks: string[], problemMatchers: string[], list: boolean): TaskConfig.TaskDescription[] { - let taskConfigs: TaskConfig.TaskDescription[] = []; + private createTaskDescriptions(tasks: string[], problemMatchers: string[], list: boolean): TaskConfig.CustomTask[] { + let taskConfigs: TaskConfig.CustomTask[] = []; if (list) { tasks.forEach((task) => { taskConfigs.push({ @@ -337,7 +338,7 @@ export class ProcessRunnerDetector { taskConfigs.push({ taskName: name, args: [], - isBuildCommand: true, + group: Tasks.TaskGroup.Build, problemMatcher: problemMatchers }); } @@ -347,7 +348,7 @@ export class ProcessRunnerDetector { taskConfigs.push({ taskName: name, args: [], - isTestCommand: true + group: Tasks.TaskGroup.Test, }); } } diff --git a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts b/src/vs/workbench/parts/tasks/node/processTaskSystem.ts index 7d47d95d92a..f45236b181b 100644 --- a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/node/processTaskSystem.ts @@ -30,7 +30,7 @@ import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType, TaskTerminateResponse } from 'vs/workbench/parts/tasks/common/taskSystem'; -import { Task, CommandOptions, RevealKind, CommandConfiguration, CommandType } from 'vs/workbench/parts/tasks/common/tasks'; +import { Task, CommandOptions, RevealKind, CommandConfiguration, RuntimeType } from 'vs/workbench/parts/tasks/common/tasks'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -171,7 +171,7 @@ export class ProcessTaskSystem extends EventEmitter implements ITaskSystem { let args: string[] = commandConfig.args ? commandConfig.args.slice() : []; args = this.resolveVariables(args); let command: string = this.resolveVariable(commandConfig.name); - this.childProcess = new LineProcess(command, args, commandConfig.type === CommandType.Shell, this.resolveOptions(commandConfig.options)); + this.childProcess = new LineProcess(command, args, commandConfig.runtime === RuntimeType.Shell, this.resolveOptions(commandConfig.options)); telemetryEvent.command = this.childProcess.getSanitizedCommand(); // we have no problem matchers defined. So show the output log let reveal = task.command.presentation.reveal; diff --git a/src/vs/workbench/parts/tasks/common/taskConfiguration.ts b/src/vs/workbench/parts/tasks/node/taskConfiguration.ts similarity index 69% rename from src/vs/workbench/parts/tasks/common/taskConfiguration.ts rename to src/vs/workbench/parts/tasks/node/taskConfiguration.ts index 889816804a6..b5bd3d81ab6 100644 --- a/src/vs/workbench/parts/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/parts/tasks/node/taskConfiguration.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import * as crypto from 'crypto'; + import nls = require('vs/nls'); import * as Objects from 'vs/base/common/objects'; @@ -18,7 +20,8 @@ import { isNamedProblemMatcher, ProblemMatcherRegistry } from 'vs/platform/markers/common/problemMatcher'; -import * as Tasks from './tasks'; +import * as Tasks from '../common/tasks'; +import { TaskTypeRegistry } from '../common/taskTypeRegistry'; /** * Defines the problem handling strategy @@ -55,18 +58,85 @@ export interface CommandOptions { shell?: ShellConfiguration; } -export interface PlatformTaskDescription { +export interface PresentationOptions { + /** + * Controls whether the terminal executing a task is brought to front or not. + * Defaults to `RevealKind.Always`. + */ + reveal?: string; /** - * Whether the task is a shell task or a process task. + * Controls whether the executed command is printed to the output window or terminal as well. + */ + echo?: boolean; + + /** + * Controls whether the terminal is focus when this task is executed + */ + focus?: boolean; + + /** + * Controls whether the task runs in a new terminal + */ + panel?: string; +} + +export interface TaskIdentifier { + type?: string; +} + +export interface LegacyTaskProperties { + /** + * @deprecated Use `isBackground` instead. + * Whether the executed command is kept alive and is watching the file system. + */ + isWatching?: boolean; + + /** + * @deprecated Use `group` instead. + * Whether this task maps to the default build command. + */ + isBuildCommand?: boolean; + + /** + * @deprecated Use `group` instead. + * Whether this task maps to the default test command. + */ + isTestCommand?: boolean; +} + +export interface LegacyCommandProperties { + + /** + * Whether this is a shell or process */ type?: string; /** - * The command to be executed. Can be an external program or a shell - * command. + * @deprecated Use presentation options + * Controls whether the output view of the running tasks is brought to front or not. + * See BaseTaskRunnerConfiguration#showOutput for details. */ - command?: string; + showOutput?: string; + + /** + * @deprecated Use presentation options + * Controls whether the executed command is printed to the output windows as well. + */ + echoCommand?: boolean; + + /** + * @deprecated Use inline commands. + * See BaseTaskRunnerConfiguration#suppressTaskName for details. + */ + suppressTaskName?: boolean; + + /** + * Some commands require that the task argument is highlighted with a special + * prefix (e.g. /t: for msbuild). This property can be used to control such + * a prefix. + */ + taskSelector?: string; /** * @deprecated use the task type instead. @@ -76,6 +146,20 @@ export interface PlatformTaskDescription { * Defaults to false if omitted. */ isShellCommand?: boolean | ShellConfiguration; +} + +export interface BaseCommandProperties { + + /** + * Whether the task is a shell task or a process task. + */ + runtime?: string; + + /** + * The command to be executed. Can be an external program or a shell + * command. + */ + command?: string; /** * The command options used when the command is executed. Can be omitted. @@ -89,48 +173,37 @@ export interface PlatformTaskDescription { args?: string[]; } -/** - * The description of a task. - */ -export interface TaskDescription extends PlatformTaskDescription { +export interface CommandProperties extends BaseCommandProperties { + + /** + * Windows specific command properties + */ + windows?: BaseCommandProperties; + + /** + * OSX specific command properties + */ + osx?: BaseCommandProperties; + + /** + * linux specific command properties + */ + linux?: BaseCommandProperties; +} + +export interface ConfigurationProperties { /** * The task's name */ - taskName: string; + taskName?: string; /** - * A unique optional identifier in case the name - * can't be used as such. + * An optional indentifier which can be used to reference a task + * in a dependsOn or other attributes. */ identifier?: string; - /** - * The id of the customized task - */ - customize?: string; - - /** - * Windows specific task configuration - */ - windows?: PlatformTaskDescription; - - /** - * Mac specific task configuration - */ - osx?: PlatformTaskDescription; - - /** - * Linux speciif task configuration - */ - linux?: PlatformTaskDescription; - - /** - * @deprecated Use `isBackground` instead. - * Whether the executed command is kept alive and is watching the file system. - */ - isWatching?: boolean; - /** * Whether the executed command is kept alive and runs in the background. */ @@ -146,39 +219,16 @@ export interface TaskDescription extends PlatformTaskDescription { */ group?: string; - /** - * @deprecated Use `group` instead. - * Whether this task maps to the default build command. - */ - isBuildCommand?: boolean; - - /** - * @deprecated Use `group` instead. - * Whether this task maps to the default test command. - */ - isTestCommand?: boolean; - - /** - * Controls whether the output view of the running tasks is brought to front or not. - * See BaseTaskRunnerConfiguration#showOutput for details. - */ - showOutput?: string; - - /** - * Controls whether the executed command is printed to the output windows as well. - */ - echoCommand?: boolean; - - /** - * See BaseTaskRunnerConfiguration#suppressTaskName for details. - */ - suppressTaskName?: boolean; - /** * The other tasks the task depend on */ dependsOn?: string | string[]; + /** + * Controls the behavior of the used terminal + */ + presentation?: PresentationOptions; + /** * The problem matcher(s) to use to capture problems in the tasks * output. @@ -186,6 +236,21 @@ export interface TaskDescription extends PlatformTaskDescription { problemMatcher?: ProblemMatcherConfig.ProblemMatcherType; } +export interface CustomTask extends CommandProperties, ConfigurationProperties { + /** + * Custom tasks have the type 'custom' + */ + type?: string; + +} + +export interface ConfiguringTask extends ConfigurationProperties { + /** + * The contributed type of the task + */ + type?: string; +} + /** * The base task runner configuration */ @@ -234,28 +299,7 @@ export interface BaseTaskRunnerConfiguration { /** * Controls the behavior of the used terminal */ - presentation?: { - /** - * Controls whether the terminal executing a task is brought to front or not. - * Defaults to `RevealKind.Always`. - */ - reveal?: string; - - /** - * Controls whether the executed command is printed to the output window or terminal as well. - */ - echo?: boolean; - - /** - * Controls whether the terminal is focus when this task is executed - */ - focus?: boolean; - - /** - * Controls whether the task runs in a new terminal - */ - panel?: string; - }; + presentation?: PresentationOptions; /** * If set to false the task name is added as an additional argument to the @@ -301,7 +345,7 @@ export interface BaseTaskRunnerConfiguration { * The configuration of the available tasks. A tasks.json file can either * contain a global problemMatcher property or a tasks property but not both. */ - tasks?: TaskDescription[]; + tasks?: (CustomTask | ConfiguringTask)[]; /** * Problem matcher declarations @@ -353,13 +397,13 @@ enum ProblemMatcherKind { const EMPTY_ARRAY: any[] = []; Object.freeze(EMPTY_ARRAY); -function assignProperty(target: T, source: T, key: K) { +function assignProperty(target: T, source: Partial, key: K) { if (source[key] !== void 0) { target[key] = source[key]; } } -function fillProperty(target: T, source: T, key: K) { +function fillProperty(target: T, source: Partial, key: K) { if (target[key] === void 0 && source[key] !== void 0) { target[key] = source[key]; } @@ -428,16 +472,12 @@ function _fillProperties(this: void, target: T, source: T, properties: MetaDa } for (let meta of properties) { let property = meta.property; - if (target[property] !== void 0) { - continue; - } let value: any; if (meta.type) { value = meta.type.fillProperties(target[property], source[property]); - } else { + } else if (target[property] === void 0) { value = source[property]; } - if (value !== void 0 && value !== null) { target[property] = value; } @@ -500,6 +540,7 @@ interface ParseContext { uuidMap: UUIDMap; engine: Tasks.ExecutionEngine; schemaVersion: Tasks.JsonSchemaVersion; + taskConfigurations: boolean; } @@ -607,40 +648,15 @@ namespace CommandOptions { namespace CommandConfiguration { - interface PresentationOptions { - echo?: boolean; - reveal?: string; - focus?: boolean; - panel?: string; - } - - interface BaseCommandConfiguationShape { - command?: string; - type?: string; - isShellCommand?: boolean | ShellConfiguration; - args?: string[]; - options?: CommandOptions; - echoCommand?: boolean; - showOutput?: string; - /** - * @deprecated Use panel instead. - */ - terminal?: PresentationOptions; - presentation?: PresentationOptions; - taskSelector?: string; - suppressTaskName?: boolean; - } - - interface CommandConfiguationShape extends BaseCommandConfiguationShape { - windows?: BaseCommandConfiguationShape; - osx?: BaseCommandConfiguationShape; - linux?: BaseCommandConfiguationShape; - } - export namespace PresentationOptions { const properties: MetaData[] = [{ property: 'echo' }, { property: 'reveal' }, { property: 'focus' }, { property: 'panel' }]; - export function from(this: void, config: BaseCommandConfiguationShape, context: ParseContext): Tasks.PresentationOptions { + interface PresentationOptionsShape extends LegacyCommandProperties { + terminal?: PresentationOptions; + presentation?: PresentationOptions; + } + + export function from(this: void, config: PresentationOptionsShape, context: ParseContext): Tasks.PresentationOptions { let echo: boolean; let reveal: Tasks.RevealKind; let focus: boolean; @@ -694,8 +710,17 @@ namespace CommandConfiguration { } } - const properties: MetaData[] = [ - { property: 'type' }, { property: 'name' }, { property: 'options', type: CommandOptions }, + interface BaseCommandConfiguationShape extends BaseCommandProperties, LegacyCommandProperties { + } + + interface CommandConfiguationShape extends BaseCommandConfiguationShape { + windows?: BaseCommandConfiguationShape; + osx?: BaseCommandConfiguationShape; + linux?: BaseCommandConfiguationShape; + } + + const properties: MetaData[] = [ + { property: 'runtime' }, { property: 'name' }, { property: 'options', type: CommandOptions }, { property: 'args' }, { property: 'taskSelector' }, { property: 'suppressTaskName' }, { property: 'presentation', type: PresentationOptions } ]; @@ -720,20 +745,25 @@ namespace CommandConfiguration { function fromBase(this: void, config: BaseCommandConfiguationShape, context: ParseContext): Tasks.CommandConfiguration { let result: Tasks.CommandConfiguration = { name: undefined, - type: undefined, + runtime: undefined, presentation: undefined }; if (Types.isString(config.command)) { result.name = config.command; } if (Types.isString(config.type)) { - result.type = Tasks.CommandType.fromString(config.type); + if (config.type === 'shell' || config.type === 'process') { + result.runtime = Tasks.RuntimeType.fromString(config.type); + } } let isShellConfiguration = ShellConfiguration.is(config.isShellCommand); if (Types.isBoolean(config.isShellCommand) || isShellConfiguration) { - result.type = Tasks.CommandType.Shell; + result.runtime = Tasks.RuntimeType.Shell; } else if (config.isShellCommand !== void 0) { - result.type = !!config.isShellCommand ? Tasks.CommandType.Shell : Tasks.CommandType.Process; + result.runtime = !!config.isShellCommand ? Tasks.RuntimeType.Shell : Tasks.RuntimeType.Process; + } + if (Types.isString(config.runtime)) { + result.runtime = Tasks.RuntimeType.fromString(config.runtime); } if (config.args !== void 0) { if (Types.isStringArray(config.args)) { @@ -771,7 +801,7 @@ namespace CommandConfiguration { export function onlyTerminalBehaviour(value: Tasks.CommandConfiguration): boolean { return value && value.presentation && (value.presentation.echo !== void 0 || value.presentation.reveal !== void 0) && - value.name === void 0 && value.type === void 0 && value.args === void 0 && CommandOptions.isEmpty(value.options); + value.name === void 0 && value.runtime === void 0 && value.args === void 0 && CommandOptions.isEmpty(value.options); } export function assignProperties(target: Tasks.CommandConfiguration, source: Tasks.CommandConfiguration): Tasks.CommandConfiguration { @@ -782,7 +812,7 @@ namespace CommandConfiguration { return source; } assignProperty(target, source, 'name'); - assignProperty(target, source, 'type'); + assignProperty(target, source, 'runtime'); assignProperty(target, source, 'taskSelector'); assignProperty(target, source, 'suppressTaskName'); if (source.args !== void 0) { @@ -797,17 +827,21 @@ namespace CommandConfiguration { return target; } + export function fillProperties(target: Tasks.CommandConfiguration, source: Tasks.CommandConfiguration): Tasks.CommandConfiguration { + return _fillProperties(target, source, properties); + } + export function fillGlobals(target: Tasks.CommandConfiguration, source: Tasks.CommandConfiguration, taskName: string): Tasks.CommandConfiguration { if (isEmpty(source)) { return target; } target = target || { name: undefined, - type: undefined, + runtime: undefined, presentation: undefined }; fillProperty(target, source, 'name'); - fillProperty(target, source, 'type'); + fillProperty(target, source, 'runtime'); fillProperty(target, source, 'taskSelector'); fillProperty(target, source, 'suppressTaskName'); @@ -833,8 +867,8 @@ namespace CommandConfiguration { if (!value || Object.isFrozen(value)) { return; } - if (value.name !== void 0 && value.type === void 0) { - value.type = Tasks.CommandType.Process; + if (value.name !== void 0 && value.runtime === void 0) { + value.runtime = Tasks.RuntimeType.Process; } value.presentation = PresentationOptions.fillDefaults(value.presentation, context); if (!isEmpty(value)) { @@ -939,170 +973,193 @@ namespace ProblemMatcherConverter { } } -interface TaskParseResult { - tasks: Tasks.Task[] | undefined; - annotatingTasks: Tasks.Task[] | undefined; -} - -namespace TaskDescription { - - export let source: Tasks.TaskSource = { - kind: Tasks.TaskSourceKind.Workspace, - label: 'Workspace', - detail: '.settins\\tasks.json' - }; - - export function from(this: void, tasks: TaskDescription[], globals: Globals, context: ParseContext): TaskParseResult { - if (!tasks) { +namespace TaskIdentifier { + export function from(this: void, value: TaskIdentifier): Tasks.TaskIdentifier { + if (!value || !Types.isString(value.type)) { return undefined; } - let parsedTasks: Tasks.Task[] = []; - let annotatingTasks: Tasks.Task[] = []; - let defaultBuildTask: { task: Tasks.Task; rank: number; } = { task: undefined, rank: -1 }; - let defaultTestTask: { task: Tasks.Task; rank: number; } = { task: undefined, rank: -1 }; - let schema2_0_0: boolean = context.schemaVersion === Tasks.JsonSchemaVersion.V2_0_0; - tasks.forEach((externalTask) => { - let taskName = externalTask.taskName; - if (!taskName) { - context.problemReporter.fatal(nls.localize('ConfigurationParser.noTaskName', 'Error: tasks must provide a taskName property. The task will be ignored.\n{0}\n', JSON.stringify(externalTask, null, 4))); - return; + const hash = crypto.createHash('md5'); + hash.update(JSON.stringify(value)); + let key = hash.digest('hex'); + let result: Tasks.TaskIdentifier = { + _key: key, + type: value.type + }; + result = Objects.assign(result, value); + return result; + } +} + +const source: Tasks.TaskSource = { + kind: Tasks.TaskSourceKind.Workspace, + label: 'Workspace', + detail: '.settins\\tasks.json' +}; + +namespace ConfigurationProperties { + + const properties: MetaData[] = [ + { property: 'name' }, { property: 'identifier' }, { property: 'group' }, { property: 'isBackground' }, + { property: 'promptOnClose' }, { property: 'dependsOn' }, + { property: 'presentation', type: CommandConfiguration.PresentationOptions }, { property: 'problemMatchers' } + ]; + + export function from(this: void, external: ConfigurationProperties, context: ParseContext, includePresentation): Tasks.ConfigurationProperties { + if (!external) { + return undefined; + } + let result: Tasks.ConfigurationProperties = {}; + if (Types.isString(external.taskName)) { + result.name = external.taskName; + } + if (Types.isString(external.identifier)) { + result.identifier = external.identifier; + } + if (external.isBackground !== void 0) { + result.isBackground = !!external.isBackground; + } + if (external.promptOnClose !== void 0) { + result.promptOnClose = !!external.promptOnClose; + } + if (Tasks.TaskGroup.is(external.group)) { + result.group = external.group; + } + if (external.dependsOn !== void 0) { + if (Types.isString(external.dependsOn)) { + result.dependsOn = [external.dependsOn]; + } else if (Types.isStringArray(external.dependsOn)) { + result.dependsOn = external.dependsOn.slice(); } - let problemMatchers = ProblemMatcherConverter.from(externalTask.problemMatcher, context); - let command: Tasks.CommandConfiguration = CommandConfiguration.from(externalTask, context); - let identifer = Types.isString(externalTask.identifier) ? externalTask.identifier : taskName; - let task: Tasks.Task = { - _id: context.uuidMap.getUUID(taskName), - _source: source, - _label: taskName, - name: taskName, - identifier: identifer, - command - }; - if (externalTask.isWatching !== void 0) { - task.isBackground = !!externalTask.isWatching; - } - if (externalTask.isBackground !== void 0) { - task.isBackground = !!externalTask.isBackground; - } - if (externalTask.promptOnClose !== void 0) { - task.promptOnClose = !!externalTask.promptOnClose; - } - if (Tasks.TaskGroup.is(externalTask.group)) { - task.group = externalTask.group; - } - if (task.group === void 0) { - if (Types.isBoolean(externalTask.isBuildCommand) && externalTask.isBuildCommand) { - task.group = Tasks.TaskGroup.Build; - } else if (Types.isBoolean(externalTask.isTestCommand && externalTask.isTestCommand)) { - task.group = Tasks.TaskGroup.Test; - } - } - if (Types.isString(externalTask.customize)) { - task.customize = externalTask.customize; - } - if (externalTask.command !== void 0) { - // if the task has its own command then we suppress the - // task name by default. - command.suppressTaskName = true; - } - if (externalTask.dependsOn !== void 0) { - if (Types.isString(externalTask.dependsOn)) { - task.dependsOn = [externalTask.dependsOn]; - } else if (Types.isStringArray(externalTask.dependsOn)) { - task.dependsOn = externalTask.dependsOn.slice(); - } - } - if (problemMatchers) { - task.problemMatchers = problemMatchers; - } - if (schema2_0_0 && isAnnotating(task)) { - mergeGlobalsIntoAnnnotation(task, globals); - annotatingTasks.push(task); - return; - } - fillGlobals(task, globals); - fillDefaults(task, context); - let addTask: boolean = true; - if (context.engine === Tasks.ExecutionEngine.Terminal && task.command && task.command.name && task.command.type === Tasks.CommandType.Shell && task.command.args && task.command.args.length > 0) { - if (hasUnescapedSpaces(task.command.name) || task.command.args.some(hasUnescapedSpaces)) { - context.problemReporter.warn(nls.localize('taskConfiguration.shellArgs', 'Warning: the task \'{0}\' is a shell command and either the command name or one of its arguments has unescaped spaces. To ensure correct command line quoting please merge args into the command.', task.name)); - } - } - if (schema2_0_0) { - if ((task.command === void 0 || task.command.name === void 0) && (task.dependsOn === void 0 || task.dependsOn.length === 0)) { - context.problemReporter.error(nls.localize( - 'taskConfiguration.noCommandOrDependsOn', 'Error: the task \'{0}\' neither specifies a command nor a dependsOn property. The task will be ignored. Its definition is:\n{1}', - task.name, JSON.stringify(externalTask, undefined, 4) - )); - addTask = false; - } - } else { - if (task.command === void 0 || task.command.name === void 0) { - context.problemReporter.warn(nls.localize( - 'taskConfiguration.noCommand', 'Error: the task \'{0}\' doesn\'t define a command. The task will be ignored. Its definition is:\n{1}', - task.name, JSON.stringify(externalTask, undefined, 4) - )); - addTask = false; - } - } - if (addTask) { - parsedTasks.push(task); - if (task.group === Tasks.TaskGroup.Build && defaultBuildTask.rank < 2) { - defaultBuildTask.task = task; - defaultBuildTask.rank = 2; - } else if (task.group === Tasks.TaskGroup.Test && defaultTestTask.rank < 2) { - defaultTestTask.task = task; - defaultTestTask.rank = 2; - } else if (task.name === 'build' && defaultBuildTask.rank < 1) { - defaultBuildTask.task = task; - defaultBuildTask.rank = 1; - } else if (task.name === 'test' && defaultTestTask.rank < 1) { - defaultTestTask.task = task; - defaultTestTask.rank = 1; - } + } + if (includePresentation && external.presentation !== void 0) { + result.presentation = CommandConfiguration.PresentationOptions.from(external, context); + } + if (external.problemMatcher) { + result.problemMatchers = ProblemMatcherConverter.from(external.problemMatcher, context); + } + return isEmpty(result) ? undefined : result; + } + + export function isEmpty(this: void, value: Tasks.ConfigurationProperties): boolean { + return _isEmpty(value, properties); + } +} + +namespace ConfiguringTask { + + export function from(this: void, external: ConfiguringTask, context: ParseContext): Tasks.ConfiguringTask { + if (!external) { + return undefined; + } + let type = external.type; + if (!type) { + context.problemReporter.fatal(nls.localize('ConfigurationParser.noTaskType', 'Error: tasks configuration must have a type property. The configuration will be ignored.\n{0}\n', JSON.stringify(external, null, 4))); + return undefined; + } + let typeDeclaration = TaskTypeRegistry.get(type); + let identifier: TaskIdentifier = { + type + }; + Object.keys(typeDeclaration.properties).forEach((property) => { + let value = external[property]; + if (value !== void 0 && value !== null) { + identifier[property] = value; } }); - if (defaultBuildTask.rank > -1 && defaultBuildTask.rank < 2) { - defaultBuildTask.task.group = Tasks.TaskGroup.Build; - } else if (defaultTestTask.rank > -1 && defaultTestTask.rank < 2) { - defaultTestTask.task.group = Tasks.TaskGroup.Test; - } - return { - tasks: parsedTasks.length > 0 ? parsedTasks : undefined, - annotatingTasks: annotatingTasks.length > 0 ? annotatingTasks : undefined + let taskIdentifier = TaskIdentifier.from(identifier); + let result: Tasks.ConfiguringTask = { + type: type, + configures: taskIdentifier, + _id: taskIdentifier._key, + _source: source, + _label: undefined }; + let configuration = ConfigurationProperties.from(external, context, true); + if (configuration) { + result = Objects.assign(result, configuration); + if (result.name) { + result._label = result.name; + } else { + let label = result.configures.type; + if (typeDeclaration.required && typeDeclaration.required.length > 0) { + for (let required of typeDeclaration.required) { + let value = result.configures[required]; + if (value) { + label = label + ' ' + value; + break; + } + } + } + result._label = label; + } + if (!result.identifier) { + result.identifier = taskIdentifier._key; + } + } + return result; + } +} + +namespace CustomTask { + + export function from(this: void, external: CustomTask, context: ParseContext): Tasks.CustomTask { + if (!external) { + return undefined; + } + let type = external.type; + if (type === void 0 || type === null) { + type = 'custom'; + } + if (type !== 'custom' && type !== 'shell' && type !== 'process') { + context.problemReporter.fatal(nls.localize('ConfigurationParser.notCustom', 'Error: tasks is not declared as a custom task. The configuration will be ignored.\n{0}\n', JSON.stringify(external, null, 4))); + return undefined; + } + let taskName = external.taskName; + if (!taskName) { + context.problemReporter.fatal(nls.localize('ConfigurationParser.noTaskName', 'Error: tasks must provide a taskName property. The task will be ignored.\n{0}\n', JSON.stringify(external, null, 4))); + return undefined; + } + + let result: Tasks.CustomTask = { + type: 'custom', + _id: context.uuidMap.getUUID(taskName), + _source: source, + _label: taskName, + name: taskName, + identifier: taskName, + command: undefined + }; + let configuration = ConfigurationProperties.from(external, context, false); + if (configuration) { + result = Objects.assign(result, configuration); + } + let supportLegacy: boolean = true; //context.schemaVersion === Tasks.JsonSchemaVersion.V2_0_0; + if (supportLegacy) { + let legacy: LegacyTaskProperties = external as LegacyTaskProperties; + if (result.isBackground === void 0 && legacy.isWatching !== void 0) { + result.isBackground = !!legacy.isWatching; + } + if (result.group === void 0) { + if (legacy.isBuildCommand === true) { + result.group = Tasks.TaskGroup.Build; + } else if (legacy.isTestCommand === true) { + result.group = Tasks.TaskGroup.Test; + } + } + } + let command: Tasks.CommandConfiguration = CommandConfiguration.from(external, context); + if (command) { + result.command = command; + } + if (external.command !== void 0) { + // if the task has its own command then we suppress the + // task name by default. + command.suppressTaskName = true; + } + return result; } - export function assignTasks(target: Tasks.Task[], source: Tasks.Task[]): Tasks.Task[] { - if (source === void 0 || source.length === 0) { - return target; - } - if (target === void 0 || target.length === 0) { - return source; - } - - if (source) { - // Tasks are keyed by ID but we need to merge by name - let map: IStringDictionary = Object.create(null); - target.forEach((task) => { - map[task.name] = task; - }); - - source.forEach((task) => { - map[task.name] = task; - }); - let newTarget: Tasks.Task[] = []; - target.forEach(task => { - newTarget.push(map[task.name]); - delete map[task.name]; - }); - Object.keys(map).forEach(key => newTarget.push(map[key])); - target = newTarget; - } - return target; - } - - export function fillGlobals(task: Tasks.Task, globals: Globals): void { + export function fillGlobals(task: Tasks.CustomTask, globals: Globals): void { // We only merge a command from a global definition if there is no dependsOn if (task.dependsOn === void 0) { task.command = CommandConfiguration.fillGlobals(task.command, globals.command, task.name); @@ -1113,10 +1170,7 @@ namespace TaskDescription { } } - export function mergeGlobalsIntoAnnnotation(task: Tasks.Task, globals: Globals): void { - } - - export function fillDefaults(task: Tasks.Task, context: ParseContext): void { + export function fillDefaults(task: Tasks.CustomTask, context: ParseContext): void { CommandConfiguration.fillDefaults(task.command, context); if (task.promptOnClose === void 0) { task.promptOnClose = task.isBackground !== void 0 ? !task.isBackground : true; @@ -1129,6 +1183,154 @@ namespace TaskDescription { } } + export function createCustomTask(contributedTask: Tasks.ContributedTask, configuredProps: Tasks.ConfigurationProperties): Tasks.CustomTask { + let result: Tasks.CustomTask = { + _id: contributedTask._id, + _source: source, + _label: configuredProps.name || contributedTask._label, + type: 'custom', + command: contributedTask.command, + name: configuredProps.name || contributedTask.name, + identifier: configuredProps.identifier || contributedTask.identifier + }; + let resultConfigProps: Tasks.ConfigurationProperties = result; + + assignProperty(resultConfigProps, configuredProps, 'group'); + assignProperty(resultConfigProps, configuredProps, 'isBackground'); + assignProperty(resultConfigProps, configuredProps, 'dependsOn'); + assignProperty(resultConfigProps, configuredProps, 'problemMatchers'); + assignProperty(resultConfigProps, configuredProps, 'promptOnClose'); + result.command.presentation = CommandConfiguration.PresentationOptions.assignProperties( + result.command.presentation, configuredProps.presentation); + + let contributedConfigProps: Tasks.ConfigurationProperties = contributedTask; + fillProperty(resultConfigProps, contributedConfigProps, 'group'); + fillProperty(resultConfigProps, contributedConfigProps, 'isBackground'); + fillProperty(resultConfigProps, contributedConfigProps, 'dependsOn'); + fillProperty(resultConfigProps, contributedConfigProps, 'problemMatchers'); + fillProperty(resultConfigProps, contributedConfigProps, 'promptOnClose'); + result.command.presentation = CommandConfiguration.PresentationOptions.fillProperties( + result.command.presentation, contributedConfigProps.presentation); + + return result; + } +} + +interface TaskParseResult { + custom: Tasks.CustomTask[]; + configured: Tasks.ConfiguringTask[]; +} + +namespace TaskParser { + + function isCustomTask(value: CustomTask | ConfiguringTask): value is CustomTask { + let type = value.type; + return type === void 0 || type === null || type === 'custom' || type === 'shell' || type === 'process'; + } + + export function from(this: void, externals: (CustomTask | ConfiguringTask)[], globals: Globals, context: ParseContext): TaskParseResult { + let result: TaskParseResult = { custom: [], configured: [] }; + if (!externals) { + return result; + } + let defaultBuildTask: { task: Tasks.Task; rank: number; } = { task: undefined, rank: -1 }; + let defaultTestTask: { task: Tasks.Task; rank: number; } = { task: undefined, rank: -1 }; + let schema2_0_0: boolean = context.schemaVersion === Tasks.JsonSchemaVersion.V2_0_0; + + for (let external of externals) { + if (isCustomTask(external)) { + let customTask = CustomTask.from(external, context); + if (customTask) { + CustomTask.fillGlobals(customTask, globals); + CustomTask.fillDefaults(customTask, context); + if (context.engine === Tasks.ExecutionEngine.Terminal && customTask.command && customTask.command.name && customTask.command.runtime === Tasks.RuntimeType.Shell && customTask.command.args && customTask.command.args.length > 0) { + if (hasUnescapedSpaces(customTask.command.name) || customTask.command.args.some(hasUnescapedSpaces)) { + context.problemReporter.warn( + nls.localize( + 'taskConfiguration.shellArgs', + 'Warning: the task \'{0}\' is a shell command and either the command name or one of its arguments has unescaped spaces. To ensure correct command line quoting please merge args into the command.', + customTask.name + ) + ); + } + } + if (schema2_0_0) { + if ((customTask.command === void 0 || customTask.command.name === void 0) && (customTask.dependsOn === void 0 || customTask.dependsOn.length === 0)) { + context.problemReporter.error(nls.localize( + 'taskConfiguration.noCommandOrDependsOn', 'Error: the task \'{0}\' neither specifies a command nor a dependsOn property. The task will be ignored. Its definition is:\n{1}', + customTask.name, JSON.stringify(external, undefined, 4) + )); + continue; + } + } else { + if (customTask.command === void 0 || customTask.command.name === void 0) { + context.problemReporter.warn(nls.localize( + 'taskConfiguration.noCommand', 'Error: the task \'{0}\' doesn\'t define a command. The task will be ignored. Its definition is:\n{1}', + customTask.name, JSON.stringify(external, undefined, 4) + )); + continue; + } + } + if (customTask.group === Tasks.TaskGroup.Build && defaultBuildTask.rank < 2) { + defaultBuildTask.task = customTask; + defaultBuildTask.rank = 2; + } else if (customTask.group === Tasks.TaskGroup.Test && defaultTestTask.rank < 2) { + defaultTestTask.task = customTask; + defaultTestTask.rank = 2; + } else if (customTask.name === 'build' && defaultBuildTask.rank < 1) { + defaultBuildTask.task = customTask; + defaultBuildTask.rank = 1; + } else if (customTask.name === 'test' && defaultTestTask.rank < 1) { + defaultTestTask.task = customTask; + defaultTestTask.rank = 1; + } + result.custom.push(customTask); + } + } else { + let configuredTask = ConfiguringTask.from(external, context); + if (configuredTask) { + result.configured.push(configuredTask); + } + } + } + if (defaultBuildTask.rank > -1 && defaultBuildTask.rank < 2) { + defaultBuildTask.task.group = Tasks.TaskGroup.Build; + } else if (defaultTestTask.rank > -1 && defaultTestTask.rank < 2) { + defaultTestTask.task.group = Tasks.TaskGroup.Test; + } + + return result; + } + + export function assignTasks(target: Tasks.CustomTask[], source: Tasks.CustomTask[]): Tasks.CustomTask[] { + if (source === void 0 || source.length === 0) { + return target; + } + if (target === void 0 || target.length === 0) { + return source; + } + + if (source) { + // Tasks are keyed by ID but we need to merge by name + let map: IStringDictionary = Object.create(null); + target.forEach((task) => { + map[task.name] = task; + }); + + source.forEach((task) => { + map[task.name] = task; + }); + let newTarget: Tasks.CustomTask[] = []; + target.forEach(task => { + newTarget.push(map[task.name]); + delete map[task.name]; + }); + Object.keys(map).forEach(key => newTarget.push(map[key])); + target = newTarget; + } + return target; + } + function hasUnescapedSpaces(value: string): boolean { if (Platform.isWindows) { if (value.length >= 2 && value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') { @@ -1150,27 +1352,6 @@ namespace TaskDescription { return false; } } - - function isAnnotating(task: Tasks.Task): boolean { - return task.customize !== void 0 && (task.command === void 0 || task.command.name === void 0); - } - - export function assignProperties(target: Tasks.Task, source: Tasks.Task): Tasks.Task { - if (!target) { - return source; - } - if (!source) { - return target; - } - - assignProperty(target, source, 'group'); - target.command = CommandConfiguration.assignProperties(target.command, source.command); - assignProperty(target, source, 'isBackground'); - assignProperty(target, source, 'promptOnClose'); - assignProperty(target, source, 'dependsOn'); - assignProperty(target, source, 'problemMatchers'); - return target; - } } interface Globals { @@ -1302,8 +1483,8 @@ export namespace JsonSchemaVersion { export interface ParseResult { validationStatus: ValidationStatus; - tasks: Tasks.Task[]; - annotatingTasks: Tasks.Task[]; + custom: Tasks.CustomTask[]; + configured: Tasks.ConfiguringTask[]; engine: Tasks.ExecutionEngine; } @@ -1384,12 +1565,13 @@ class ConfigurationParser { namedProblemMatchers: undefined, engine, schemaVersion, + taskConfigurations: schemaVersion !== Tasks.JsonSchemaVersion.V0_1_0 }; let taskParseResult = this.createTaskRunnerConfiguration(fileConfig, context); return { validationStatus: this.problemReporter.status, - tasks: taskParseResult.tasks, - annotatingTasks: taskParseResult.annotatingTasks, + custom: taskParseResult.custom, + configured: taskParseResult.configured, engine }; } @@ -1397,52 +1579,52 @@ class ConfigurationParser { private createTaskRunnerConfiguration(fileConfig: ExternalTaskRunnerConfiguration, context: ParseContext): TaskParseResult { let globals = Globals.from(fileConfig, context); if (this.problemReporter.status.isFatal()) { - return { tasks: [], annotatingTasks: [] }; + return { custom: [], configured: [] }; } context.namedProblemMatchers = ProblemMatcherConverter.namedFrom(fileConfig.declares, context); - let globalTasks: TaskParseResult; + let globalTasks: Tasks.CustomTask[]; if (fileConfig.windows && Platform.platform === Platform.Platform.Windows) { - globalTasks = TaskDescription.from(fileConfig.windows.tasks, globals, context); + globalTasks = TaskParser.from(fileConfig.windows.tasks, globals, context).custom; } else if (fileConfig.osx && Platform.platform === Platform.Platform.Mac) { - globalTasks = TaskDescription.from(fileConfig.osx.tasks, globals, context); + globalTasks = TaskParser.from(fileConfig.osx.tasks, globals, context).custom; } else if (fileConfig.linux && Platform.platform === Platform.Platform.Linux) { - globalTasks = TaskDescription.from(fileConfig.linux.tasks, globals, context); + globalTasks = TaskParser.from(fileConfig.linux.tasks, globals, context).custom; } - let result: TaskParseResult = { tasks: undefined, annotatingTasks: undefined }; + let result: TaskParseResult = { custom: undefined, configured: undefined }; if (fileConfig.tasks) { - result = TaskDescription.from(fileConfig.tasks, globals, context); + result = TaskParser.from(fileConfig.tasks, globals, context); } if (globalTasks) { - result.tasks = TaskDescription.assignTasks(result.tasks, globalTasks.tasks); - result.annotatingTasks = TaskDescription.assignTasks(result.annotatingTasks, globalTasks.annotatingTasks); + result.custom = TaskParser.assignTasks(result.custom, globalTasks); } - if ((!result.tasks || result.tasks.length === 0) && (globals.command && globals.command.name)) { + if ((!result.custom || result.custom.length === 0) && (globals.command && globals.command.name)) { let matchers: ProblemMatcher[] = ProblemMatcherConverter.from(fileConfig.problemMatcher, context); let isBackground = fileConfig.isBackground ? !!fileConfig.isBackground : fileConfig.isWatching ? !!fileConfig.isWatching : undefined; - let task: Tasks.Task = { + let task: Tasks.CustomTask = { _id: context.uuidMap.getUUID(globals.command.name), - _source: TaskDescription.source, + _source: source, _label: globals.command.name, + type: 'custom', name: globals.command.name, identifier: globals.command.name, group: Tasks.TaskGroup.Build, command: { name: undefined, - type: undefined, + runtime: undefined, presentation: undefined, suppressTaskName: true }, isBackground: isBackground, problemMatchers: matchers }; - TaskDescription.fillGlobals(task, globals); - TaskDescription.fillDefaults(task, context); - result.tasks = [task]; + CustomTask.fillGlobals(task, globals); + CustomTask.fillDefaults(task, context); + result.custom = [task]; } - result.tasks = result.tasks || []; - result.annotatingTasks = result.annotatingTasks || []; + result.custom = result.custom || []; + result.configured = result.configured || []; return result; } } @@ -1457,8 +1639,12 @@ export function parse(configuration: ExternalTaskRunnerConfiguration, logger: IP } } -export function mergeTasks(target: Tasks.Task, source: Tasks.Task): Tasks.Task { - return TaskDescription.assignProperties(target, source); +export function createCustomTask(contributedTask: Tasks.ContributedTask, configuredProps: Tasks.ConfigurationProperties): Tasks.CustomTask { + return CustomTask.createCustomTask(contributedTask, configuredProps); +} + +export function getTaskIdentifier(value: TaskIdentifier): Tasks.TaskIdentifier { + return TaskIdentifier.from(value); } /* diff --git a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts b/src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts similarity index 96% rename from src/vs/workbench/parts/tasks/test/node/configuration.test.ts rename to src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts index 9c12f67048d..c2a4d6826e0 100644 --- a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts +++ b/src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts @@ -13,7 +13,7 @@ import { ValidationStatus } from 'vs/base/common/parsers'; import { ProblemMatcher, FileLocationKind, ProblemPattern, ApplyToKind } from 'vs/platform/markers/common/problemMatcher'; import * as Tasks from 'vs/workbench/parts/tasks/common/tasks'; -import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration } from 'vs/workbench/parts/tasks/common/taskConfiguration'; +import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration, CustomTask } from 'vs/workbench/parts/tasks/node/taskConfiguration'; class ProblemReporter implements IProblemReporter { @@ -56,15 +56,15 @@ class ProblemReporter implements IProblemReporter { class ConfiguationBuilder { public result: Tasks.Task[]; - private builders: TaskBuilder[]; + private builders: CustomTaskBuilder[]; constructor() { this.result = []; this.builders = []; } - public task(name: string, command: string): TaskBuilder { - let builder = new TaskBuilder(this, name, command); + public task(name: string, command: string): CustomTaskBuilder { + let builder = new CustomTaskBuilder(this, name, command); this.builders.push(builder); this.result.push(builder.result); return builder; @@ -114,11 +114,11 @@ class CommandConfigurationBuilder { private presentationBuilder: PresentationBuilder; - constructor(public parent: TaskBuilder, command: string) { + constructor(public parent: CustomTaskBuilder, command: string) { this.presentationBuilder = new PresentationBuilder(this); this.result = { name: command, - type: Tasks.CommandType.Process, + runtime: Tasks.RuntimeType.Process, args: [], options: { cwd: '${workspaceRoot}' @@ -133,8 +133,8 @@ class CommandConfigurationBuilder { return this; } - public type(value: Tasks.CommandType): CommandConfigurationBuilder { - this.result.type = value; + public runtime(value: Tasks.RuntimeType): CommandConfigurationBuilder { + this.result.runtime = value; return this; } @@ -168,9 +168,9 @@ class CommandConfigurationBuilder { } } -class TaskBuilder { +class CustomTaskBuilder { - public result: Tasks.Task; + public result: Tasks.CustomTask; private commandBuilder: CommandConfigurationBuilder; constructor(public parent: ConfiguationBuilder, name: string, command: string) { @@ -179,6 +179,7 @@ class TaskBuilder { _id: name, _source: { kind: Tasks.TaskSourceKind.Workspace, label: 'workspace' }, _label: name, + type: 'custom', identifier: name, name: name, command: this.commandBuilder.result, @@ -188,22 +189,22 @@ class TaskBuilder { }; } - public identifier(value: string): TaskBuilder { + public identifier(value: string): CustomTaskBuilder { this.result.identifier = value; return this; } - public group(value: Tasks.TaskGroup): TaskBuilder { + public group(value: Tasks.TaskGroup): CustomTaskBuilder { this.result.group = value; return this; } - public isBackground(value: boolean): TaskBuilder { + public isBackground(value: boolean): CustomTaskBuilder { this.result.isBackground = value; return this; } - public promptOnClose(value: boolean): TaskBuilder { + public promptOnClose(value: boolean): CustomTaskBuilder { this.result.promptOnClose = value; return this; } @@ -229,7 +230,7 @@ class ProblemMatcherBuilder { public result: ProblemMatcher; - constructor(public parent: TaskBuilder) { + constructor(public parent: CustomTaskBuilder) { this.result = { owner: ProblemMatcherBuilder.DEFAULT_UUID, applyTo: ApplyToKind.allDocuments, @@ -342,8 +343,8 @@ function testDefaultProblemMatcher(external: ExternalTaskRunnerConfiguration, re let reporter = new ProblemReporter(); let result = parse(external, reporter); assert.ok(!reporter.receivedMessage); - assert.strictEqual(result.tasks.length, 1); - let task = result.tasks[0]; + assert.strictEqual(result.custom.length, 1); + let task = result.custom[0]; assert.ok(task); assert.strictEqual(task.problemMatchers.length, resolved); } @@ -401,7 +402,7 @@ class TaskGroupMap { function assertConfiguration(result: ParseResult, expected: Tasks.Task[]): void { assert.ok(result.validationStatus.isOK()); - let actual = result.tasks; + let actual = result.custom; assert.strictEqual(typeof actual, typeof expected); if (!actual) { return; @@ -460,7 +461,7 @@ function assertCommandConfiguration(actual: Tasks.CommandConfiguration, expected if (actual && expected) { assertPresentation(actual.presentation, expected.presentation); assert.strictEqual(actual.name, expected.name, 'name'); - assert.strictEqual(actual.type, expected.type, 'task type'); + assert.strictEqual(actual.runtime, expected.runtime, 'runtime type'); assert.strictEqual(actual.suppressTaskName, expected.suppressTaskName, 'suppressTaskName'); assert.strictEqual(actual.taskSelector, expected.taskSelector, 'taskSelector'); assert.deepEqual(actual.args, expected.args, 'args'); @@ -558,7 +559,7 @@ suite('Tasks version 0.1.0', () => { builder.task('tsc', 'tsc'). group(Tasks.TaskGroup.Build). command().suppressTaskName(true). - type(Tasks.CommandType.Shell); + runtime(Tasks.RuntimeType.Shell); testConfiguration( { version: '0.1.0', @@ -753,7 +754,7 @@ suite('Tasks version 0.1.0', () => { task(name, name). group(Tasks.TaskGroup.Build). command().suppressTaskName(true). - type(Tasks.CommandType.Shell); + runtime(Tasks.RuntimeType.Shell); let external: ExternalTaskRunnerConfiguration = { version: '0.1.0', command: 'tsc', @@ -877,7 +878,7 @@ suite('Tasks version 0.1.0', () => { { taskName: 'taskName', isBuildCommand: true - } + } as CustomTask ] }; let builder = new ConfiguationBuilder(); @@ -908,7 +909,7 @@ suite('Tasks version 0.1.0', () => { { taskName: 'taskName', isTestCommand: true - } + } as CustomTask ] }; let builder = new ConfiguationBuilder(); @@ -942,7 +943,7 @@ suite('Tasks version 0.1.0', () => { echoCommand: true, args: ['--p'], isWatching: true - } + } as CustomTask ] }; let builder = new ConfiguationBuilder(); @@ -1166,7 +1167,7 @@ suite('Tasks version 0.1.0', () => { { taskName: 'taskName', isWatching: true - } + } as CustomTask ] }; let builder = new ConfiguationBuilder(); @@ -1222,7 +1223,7 @@ suite('Tasks version 0.1.0', () => { { taskName: 'taskName', suppressTaskName: true - } + } as CustomTask ] }; let builder = new ConfiguationBuilder(); @@ -1319,12 +1320,12 @@ suite('Tasks version 0.1.0', () => { env: 'env' } } - } + } as CustomTask ] }; let builder = new ConfiguationBuilder(); builder.task('taskNameOne', 'tsc').command().suppressTaskName(true). - type(Tasks.CommandType.Shell).args(['arg']).options({ cwd: 'cwd', env: { env: 'env' } }); + runtime(Tasks.RuntimeType.Shell).args(['arg']).options({ cwd: 'cwd', env: { env: 'env' } }); testConfiguration(external, builder); }); @@ -1395,11 +1396,11 @@ suite('Tasks version 0.1.0', () => { { taskName: 'taskNameOne', isShellCommand: true, - } + } as CustomTask ] }; let builder = new ConfiguationBuilder(); - builder.task('taskNameOne', 'tsc').command().type(Tasks.CommandType.Shell).args(['$name']); + builder.task('taskNameOne', 'tsc').command().runtime(Tasks.RuntimeType.Shell).args(['$name']); testConfiguration(external, builder); }); @@ -1456,7 +1457,7 @@ suite('Tasks version 2.0.0', () => { builder.task('dir', 'dir'). group(Tasks.TaskGroup.Build). command().suppressTaskName(true). - type(Tasks.CommandType.Shell). + runtime(Tasks.RuntimeType.Shell). presentation().echo(true); testConfiguration(external, builder); }); @@ -1490,7 +1491,7 @@ suite('Bugs / regression tests', () => { isBuildCommand: false, showOutput: 'always', echoCommand: true - } + } as CustomTask ] }, osx: { @@ -1508,7 +1509,7 @@ suite('Bugs / regression tests', () => { ], isBuildCommand: false, showOutput: 'always' - } + } as CustomTask ] } };