Fixes #25518: Cannot associate a problem matcher with an auto detected tasks

This commit is contained in:
Dirk Baeumer 2017-05-19 17:35:08 +02:00
parent a331c647e7
commit 87cfa19524
16 changed files with 500 additions and 88 deletions

View file

@ -33,7 +33,8 @@ const extensions = [
'html',
'git',
'gulp',
'grunt'
'grunt',
'jake'
];
extensions.forEach(extension => npmInstall(`extensions/${extension}`));

View file

@ -12,8 +12,8 @@
"Other"
],
"scripts": {
"compile": "gulp compile-extension:gulp",
"watch": "gulp watch-extension:gulp"
"compile": "gulp compile-extension:grunt",
"watch": "gulp watch-extension:grunt"
},
"dependencies": {
"vscode-nls": "^2.0.2"

View file

@ -103,6 +103,7 @@ async function getGulpTasks(): Promise<vscode.Task[]> {
let { stdout, stderr } = await exec(commandLine, { cwd: workspaceRoot });
if (stderr) {
channel.appendLine(stderr);
channel.show(true);
}
let result: vscode.Task[] = [];
if (stdout) {

View file

@ -0,0 +1,48 @@
{
"name": "jake",
"publisher": "vscode",
"description": "Extension to add Jake capabilities to VSCode.",
"displayName": "Jake support for VSCode",
"version": "0.0.1",
"engines": {
"vscode": "*"
},
"enableProposedApi": true,
"categories": [
"Other"
],
"scripts": {
"compile": "gulp compile-extension:jake",
"watch": "gulp watch-extension:jake"
},
"dependencies": {
"vscode-nls": "^2.0.2"
},
"devDependencies": {
"@types/node": "^7.0.18"
},
"main": "./out/main",
"activationEvents": [
"onCommand:workbench.action.tasks.runTask",
"onCommand:workbench.action.tasks.build",
"onCommand:workbench.action.tasks.test"
],
"contributes": {
"configuration": {
"id": "jake",
"type": "object",
"title": "Jake",
"properties": {
"jake.autoDetect": {
"type": "string",
"enum": [
"off",
"on"
],
"default": "on",
"description": "%config.jake.autoDetect%"
}
}
}
}
}

View file

@ -0,0 +1,3 @@
{
"config.jake.autoDetect": "Controls whether auto detection of Jake tasks is on or off. Default is on."
}

151
extensions/jake/src/main.ts Normal file
View file

@ -0,0 +1,151 @@
/*---------------------------------------------------------------------------------------------
* 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 path from 'path';
import * as fs from 'fs';
import * as cp from 'child_process';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.config(process.env.VSCODE_NLS_CONFIG)();
type AutoDetect = 'on' | 'off';
let taskProvider: vscode.Disposable | undefined;
export function activate(_context: vscode.ExtensionContext): void {
let workspaceRoot = vscode.workspace.rootPath;
if (!workspaceRoot) {
return;
}
let pattern = path.join(workspaceRoot, '{Jakefile,Jakefile.js}');
let jakePromise: Thenable<vscode.Task[]> | undefined = undefined;
let fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
fileWatcher.onDidChange(() => jakePromise = undefined);
fileWatcher.onDidCreate(() => jakePromise = undefined);
fileWatcher.onDidDelete(() => jakePromise = undefined);
function onConfigurationChanged() {
let autoDetect = vscode.workspace.getConfiguration('jake').get<AutoDetect>('autoDetect');
if (taskProvider && autoDetect === 'off') {
jakePromise = undefined;
taskProvider.dispose();
taskProvider = undefined;
} else if (!taskProvider && autoDetect === 'on') {
taskProvider = vscode.workspace.registerTaskProvider({
provideTasks: () => {
if (!jakePromise) {
jakePromise = getJakeTasks();
}
return jakePromise;
}
});
}
}
vscode.workspace.onDidChangeConfiguration(onConfigurationChanged);
onConfigurationChanged();
}
export function deactivate(): void {
if (taskProvider) {
taskProvider.dispose();
}
}
function exists(file: string): Promise<boolean> {
return new Promise<boolean>((resolve, _reject) => {
fs.exists(file, (value) => {
resolve(value);
});
});
}
function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: string; stderr: string }> {
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
cp.exec(command, options, (error, stdout, stderr) => {
if (error) {
reject({ error, stdout, stderr });
}
resolve({ stdout, stderr });
});
});
}
async function getJakeTasks(): Promise<vscode.Task[]> {
let workspaceRoot = vscode.workspace.rootPath;
let emptyTasks: vscode.Task[] = [];
if (!workspaceRoot) {
return emptyTasks;
}
let jakefile = path.join(workspaceRoot, 'Jakefile');
if (!await exists(jakefile)) {
jakefile = path.join(workspaceRoot, 'Jakefile.js');
if (! await exists(jakefile)) {
return emptyTasks;
}
}
let jakeCommand: string;
let platform = process.platform;
if (platform === 'win32' && await exists(path.join(workspaceRoot!, 'node_modules', '.bin', 'jake.cmd'))) {
jakeCommand = path.join('.', 'node_modules', '.bin', 'jake.cmd');
} else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(workspaceRoot!, 'node_modules', '.bin', 'jake'))) {
jakeCommand = path.join('.', 'node_modules', '.bin', 'jake');
} else {
jakeCommand = 'jake';
}
let commandLine = `${jakeCommand} --tasks`;
let channel = vscode.window.createOutputChannel('tasks');
try {
let { stdout, stderr } = await exec(commandLine, { cwd: workspaceRoot });
if (stderr) {
channel.appendLine(stderr);
channel.show(true);
}
let result: vscode.Task[] = [];
if (stdout) {
let buildTask: { task: vscode.Task | undefined, rank: number } = { task: undefined, rank: 0 };
let testTask: { task: vscode.Task | undefined, rank: number } = { task: undefined, rank: 0 };
let lines = stdout.split(/\r{0,1}\n/);
for (let line of lines) {
if (line.length === 0) {
continue;
}
let regExp = /^jake\s+([^\s]+)\s/g;
let matches = regExp.exec(line);
if (matches && matches.length === 2) {
let taskName = matches[1];
let task = new vscode.ShellTask(`jake: ${taskName}`, `${jakeCommand} ${taskName}`);
task.identifier = `jake.${taskName}`;
result.push(task);
let lowerCaseLine = line.toLowerCase();
if (lowerCaseLine === 'build') {
buildTask = { task, rank: 2 };
} else if (lowerCaseLine.indexOf('build') !== -1 && buildTask.rank < 1) {
buildTask = { task, rank: 1 };
} else if (lowerCaseLine === 'test') {
testTask = { task, rank: 2 };
} else if (lowerCaseLine.indexOf('test') !== -1 && testTask.rank < 1) {
testTask = { task, rank: 1 };
}
}
}
if (buildTask.task) {
buildTask.task.group = vscode.TaskGroup.Build;
}
if (testTask.task) {
testTask.task.group = vscode.TaskGroup.Test;
}
}
return result;
} catch (err) {
if (err.stderr) {
channel.appendLine(err.stderr);
}
channel.appendLine(localize('execFailed', 'Auto detecting Jake failed with error: {0}', err.error ? err.error.toString() : 'unknown'));
return emptyTasks;
}
}

8
extensions/jake/src/typings/refs.d.ts vendored Normal file
View file

@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>
/// <reference types='@types/node'/>

View file

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"lib": [
"es2016"
],
"outDir": "./out",
"strictNullChecks": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": [
"src/**/*"
]
}

View file

@ -268,7 +268,7 @@ namespace ShellConfiguration {
namespace Tasks {
export function from(tasks: vscode.Task[], uuidMap: UUIDMap): TaskSystem.Task[] {
export function from(tasks: vscode.Task[], extension: IExtensionDescription, uuidMap: UUIDMap): TaskSystem.Task[] {
if (tasks === void 0 || tasks === null) {
return [];
}
@ -276,7 +276,7 @@ namespace Tasks {
try {
uuidMap.start();
for (let task of tasks) {
let converted = fromSingle(task, uuidMap);
let converted = fromSingle(task, extension, uuidMap);
if (converted) {
result.push(converted);
}
@ -287,7 +287,7 @@ namespace Tasks {
return result;
}
function fromSingle(task: vscode.Task, uuidMap: UUIDMap): TaskSystem.Task {
function fromSingle(task: vscode.Task, extension: IExtensionDescription, uuidMap: UUIDMap): TaskSystem.Task {
if (typeof task.name !== 'string' || typeof task.identifier !== 'string') {
return undefined;
}
@ -306,6 +306,7 @@ namespace Tasks {
command.echo = behaviour.echo;
let result: TaskSystem.Task = {
_id: uuidMap.getUUID(task.identifier),
_source: { kind: TaskSystem.TaskSourceKind.Extension, detail: extension.id },
name: task.name,
identifier: task.identifier,
group: types.TaskGroup.is(task.group) ? task.group : undefined,
@ -421,7 +422,7 @@ export class ExtHostTask extends ExtHostTaskShape {
}
return asWinJsPromise(token => handler.provider.provideTasks(token)).then(value => {
return {
tasks: Tasks.from(value, this.getUUIDMap(handler.extension.id)),
tasks: Tasks.from(value, handler.extension, this.getUUIDMap(handler.extension.id)),
extension: handler.extension
};
});

View file

@ -12,7 +12,7 @@ import QuickOpen = require('vs/base/parts/quickopen/common/quickOpen');
import Model = require('vs/base/parts/quickopen/browser/quickOpenModel');
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { Task } from 'vs/workbench/parts/tasks/common/tasks';
import { Task, TaskSourceKind } from 'vs/workbench/parts/tasks/common/tasks';
import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService';
export class TaskEntry extends Model.QuickOpenEntry {
@ -31,11 +31,20 @@ export class TaskEntry extends Model.QuickOpenEntry {
}
}
export class TaskGroupEntry extends Model.QuickOpenEntryGroup {
constructor(entry: TaskEntry, groupLabel: string, withBorder: boolean) {
super(entry, groupLabel, withBorder);
}
}
export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler {
private tasks: TPromise<Task[]>;
constructor(
@IQuickOpenService protected quickOpenService: IQuickOpenService,
@ITaskService protected taskService: ITaskService
protected quickOpenService: IQuickOpenService,
protected taskService: ITaskService,
) {
super();
@ -43,38 +52,64 @@ export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler {
this.taskService = taskService;
}
public onOpen(): void {
this.tasks = this.getTasks();
}
public onClose(canceled: boolean): void {
this.tasks = undefined;
}
public getResults(input: string): TPromise<Model.QuickOpenModel> {
return this.getTasks().then(tasks => tasks
.sort((a, b) => a.name.localeCompare(b.name))
.map(task => ({ task: task, highlights: Filters.matchesContiguousSubString(input, task.name) }))
.filter(({ highlights }) => !!highlights)
.map(({ task, highlights }) => this.createEntry(this.taskService, task, highlights))
, _ => []).then(e => new Model.QuickOpenModel(e));
return this.tasks.then((tasks) => {
let entries: Model.QuickOpenEntry[] = [];
if (tasks.length === 0) {
return new Model.QuickOpenModel(entries);
}
tasks = tasks.sort((a, b) => {
let aKind = a._source.kind;
let bKind = b._source.kind;
if (aKind === bKind) {
return a.name.localeCompare(b.name);
}
if (aKind === TaskSourceKind.Workspace) {
return -1;
} else {
return +1;
}
});
let hasWorkspace: boolean = tasks[0]._source.kind === TaskSourceKind.Workspace;
let hasExtension: boolean = tasks[tasks.length - 1]._source.kind === TaskSourceKind.Extension;
let groupWorkspace = hasWorkspace && hasExtension;
let groupExtension = groupWorkspace;
let hadWorkspace = false;
for (let task of tasks) {
let highlights = Filters.matchesContiguousSubString(input, task.name);
if (!highlights) {
continue;
}
if (task._source.kind === TaskSourceKind.Workspace && groupWorkspace) {
groupWorkspace = false;
hadWorkspace = true;
entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('workspace', 'From Workspace'), false));
} else if (task._source.kind === TaskSourceKind.Extension && groupExtension) {
groupExtension = false;
entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('extension', 'From Extensions'), hadWorkspace));
} else {
entries.push(this.createEntry(this.taskService, task, highlights));
}
}
return new Model.QuickOpenModel(entries);
});
}
protected abstract getTasks(): TPromise<Task[]>;
protected abstract createEntry(taskService: ITaskService, task: Task, highlights: Model.IHighlight[]): TaskEntry;
public getClass(): string {
return null;
}
public canRun(): boolean {
return true;
}
public getAutoFocus(input: string): QuickOpen.IAutoFocus {
return {
autoFocusFirstEntry: !!input
};
}
public onClose(canceled: boolean): void {
return;
}
public getGroupLabel(): string {
return null;
}
}

View file

@ -20,10 +20,6 @@ class TaskEntry extends base.TaskEntry {
super(taskService, task, highlights);
}
public getAriaLabel(): string {
return nls.localize('entryAriaLabel', "{0}, tasks", this.getLabel());
}
public run(mode: QuickOpen.Mode, context: Model.IContext): boolean {
if (mode === QuickOpen.Mode.PREVIEW) {
return false;

View file

@ -22,10 +22,6 @@ class TaskEntry extends base.TaskEntry {
super(taskService, task, highlights);
}
public getAriaLabel(): string {
return nls.localize('entryAriaLabel', "{0}, tasks", this.getLabel());
}
public run(mode: QuickOpen.Mode, context: Model.IContext): boolean {
if (mode === QuickOpen.Mode.PREVIEW) {
return false;

View file

@ -631,13 +631,24 @@ namespace ProblemMatcherConverter {
}
}
interface TaskParseResult {
tasks: Tasks.Task[] | undefined;
annotatingTasks: Tasks.Task[] | undefined;
}
namespace TaskDescription {
export function from(this: void, tasks: TaskDescription[], globals: Globals, context: ParseContext): Tasks.Task[] {
export let source: Tasks.TaskSource = {
kind: Tasks.TaskSourceKind.Workspace,
detail: '.settins\tasks.json'
};
export function from(this: void, tasks: TaskDescription[], globals: Globals, context: ParseContext): TaskParseResult {
if (!tasks) {
return undefined;
}
let parsedTasks: Tasks.Task[] = [];
let annotatingTasks: Tasks.Task[] = [];
let defaultBuildTask: { task: Tasks.Task; rank: number; } = { task: null, rank: -1 };
let defaultTestTask: { task: Tasks.Task; rank: number; } = { task: null, rank: -1 };
tasks.forEach((externalTask) => {
@ -653,6 +664,7 @@ namespace TaskDescription {
let identifer = Types.isString(externalTask.identifier) ? externalTask.identifier : taskName;
let task: Tasks.Task = {
_id: UUID.generateUuid(),
_source: source,
name: taskName,
identifier: identifer,
command,
@ -691,6 +703,10 @@ namespace TaskDescription {
task.problemMatchers = problemMatchers;
}
mergeGlobals(task, globals);
if (context.isTermnial && isAnnotating(task)) {
annotatingTasks.push(task);
return;
}
fillDefaults(task);
let addTask: boolean = true;
if (context.isTermnial && task.command && task.command.name && task.command.isShellCommand && task.command.args && task.command.args.length > 0) {
@ -739,10 +755,10 @@ namespace TaskDescription {
if (defaultTestTask.task) {
defaultTestTask.task.group = Tasks.TaskGroup.Test;
}
return parsedTasks.length === 0 ? undefined : parsedTasks;
return parsedTasks.length === 0 && annotatingTasks.length === 0 ? undefined : { tasks: parsedTasks, annotatingTasks: annotatingTasks };
}
export function merge(target: Tasks.Task[], source: Tasks.Task[]): Tasks.Task[] {
export function mergeTasks(target: Tasks.Task[], source: Tasks.Task[]): Tasks.Task[] {
if (source === void 0 || source.length === 0) {
return target;
}
@ -841,6 +857,30 @@ namespace TaskDescription {
return false;
}
}
function isAnnotating(task: Tasks.Task): boolean {
return (task.command === void 0 || task.command.name === void 0) && (task.dependsOn === void 0 || task.dependsOn.length === 0);
}
export function merge(target: Tasks.Task, source: Tasks.Task): Tasks.Task {
if (!target) {
return source;
}
if (!source) {
return target;
}
mergeProperty(target, source, 'group');
target.command = CommandConfiguration.merge(target.command, source.command);
mergeProperty(target, source, 'suppressTaskName');
mergeProperty(target, source, 'args');
mergeProperty(target, source, 'isBackground');
mergeProperty(target, source, 'promptOnClose');
mergeProperty(target, source, 'showOutput');
mergeProperty(target, source, 'dependsOn');
mergeProperty(target, source, 'problemMatchers');
return target;
}
}
interface Globals {
@ -950,6 +990,7 @@ export namespace ExecutionEngine {
export interface ParseResult {
validationStatus: ValidationStatus;
tasks: Tasks.Task[];
annotatingTasks: Tasks.Task[];
engine: Tasks.ExecutionEngine;
}
@ -960,7 +1001,6 @@ export interface IProblemReporter extends IProblemReporterBase {
class ConfigurationParser {
private problemReporter: IProblemReporter;
constructor(problemReporter: IProblemReporter) {
this.problemReporter = problemReporter;
}
@ -970,21 +1010,27 @@ class ConfigurationParser {
if (engine === Tasks.ExecutionEngine.Terminal) {
this.problemReporter.clearOutput();
}
let context: ParseContext = { problemReporter: this.problemReporter, namedProblemMatchers: undefined, isTermnial: engine === Tasks.ExecutionEngine.Terminal };
let context: ParseContext = {
problemReporter: this.problemReporter,
namedProblemMatchers: undefined,
isTermnial: engine === Tasks.ExecutionEngine.Terminal
};
let taskParseResult = this.createTaskRunnerConfiguration(fileConfig, context);
return {
validationStatus: this.problemReporter.status,
tasks: this.createTaskRunnerConfiguration(fileConfig, context),
tasks: taskParseResult.tasks,
annotatingTasks: taskParseResult.annotatingTasks,
engine
};
}
private createTaskRunnerConfiguration(fileConfig: ExternalTaskRunnerConfiguration, context: ParseContext): Tasks.Task[] {
private createTaskRunnerConfiguration(fileConfig: ExternalTaskRunnerConfiguration, context: ParseContext): TaskParseResult {
let globals = Globals.from(fileConfig, context);
if (this.problemReporter.status.isFatal()) {
return undefined;
}
context.namedProblemMatchers = ProblemMatcherConverter.namedFrom(fileConfig.declares, context);
let globalTasks: Tasks.Task[];
let globalTasks: TaskParseResult;
if (fileConfig.windows && Platform.platform === Platform.Platform.Windows) {
globalTasks = TaskDescription.from(fileConfig.windows.tasks, globals, context);
} else if (fileConfig.osx && Platform.platform === Platform.Platform.Mac) {
@ -993,36 +1039,44 @@ class ConfigurationParser {
globalTasks = TaskDescription.from(fileConfig.linux.tasks, globals, context);
}
let tasks: Tasks.Task[];
let result: TaskParseResult = { tasks: undefined, annotatingTasks: undefined };
if (fileConfig.tasks) {
tasks = TaskDescription.from(fileConfig.tasks, globals, context);
result = TaskDescription.from(fileConfig.tasks, globals, context);
}
if (globalTasks) {
result.tasks = TaskDescription.mergeTasks(result.tasks, globalTasks.tasks);
result.annotatingTasks = TaskDescription.mergeTasks(result.annotatingTasks, globalTasks.annotatingTasks);
}
tasks = TaskDescription.merge(tasks, globalTasks);
if (!tasks || tasks.length === 0) {
if (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 = {
_id: UUID.generateUuid(),
name: globals.command.name,
identifier: globals.command.name,
group: Tasks.TaskGroup.Build,
command: undefined,
isBackground: isBackground,
showOutput: undefined,
suppressTaskName: true, // this must be true since we infer the task from the global data.
problemMatchers: matchers
};
TaskDescription.mergeGlobals(task, globals);
TaskDescription.fillDefaults(task);
tasks = [task];
}
if ((!result.tasks || result.tasks.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 = {
_id: UUID.generateUuid(),
_source: TaskDescription.source,
name: globals.command.name,
identifier: globals.command.name,
group: Tasks.TaskGroup.Build,
command: undefined,
isBackground: isBackground,
showOutput: undefined,
suppressTaskName: true, // this must be true since we infer the task from the global data.
problemMatchers: matchers
};
TaskDescription.mergeGlobals(task, globals);
TaskDescription.fillDefaults(task);
result.tasks = [task];
}
return tasks || [];
result.tasks = result.tasks || [];
result.annotatingTasks = result.annotatingTasks || [];
return result;
}
}
export function parse(configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter): ParseResult {
return (new ConfigurationParser(logger)).run(configuration);
}
export function mergeTasks(target: Tasks.Task, source: Tasks.Task): Tasks.Task {
return TaskDescription.merge(target, source);
}

View file

@ -110,6 +110,17 @@ export namespace TaskGroup {
export type TaskGroup = 'clean' | 'build' | 'rebuildAll' | 'test';
export enum TaskSourceKind {
Workspace = 1,
Extension = 2,
Generic = 3
}
export interface TaskSource {
kind: TaskSourceKind;
detail?: string;
}
/**
* A task description
*/
@ -120,6 +131,11 @@ export interface Task {
*/
_id: string;
/**
* Indicated the source of the task (e.g tasks.json or extension)
*/
_source: TaskSource;
/**
* The task's name
*/

View file

@ -69,7 +69,7 @@ import { IOutputService, IOutputChannelRegistry, Extensions as OutputExt, IOutpu
import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal';
import { ITaskSystem, ITaskResolver, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskSystemEvents } from 'vs/workbench/parts/tasks/common/taskSystem';
import { Task, TaskSet, TaskGroup, ExecutionEngine, ShowOutput } from 'vs/workbench/parts/tasks/common/tasks';
import { Task, TaskSet, TaskGroup, ExecutionEngine, ShowOutput, TaskSourceKind } from 'vs/workbench/parts/tasks/common/tasks';
import { ITaskService, TaskServiceEvents, ITaskProvider } from 'vs/workbench/parts/tasks/common/taskService';
import { templates as taskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates';
@ -489,6 +489,10 @@ class ProblemReporter implements TaskConfig.IProblemReporter {
interface WorkspaceTaskResult {
set: TaskSet;
annotatingTasks: {
byIdentifier: IStringDictionary<Task>;
byName: IStringDictionary<Task>;
};
hasErrors: boolean;
}
@ -522,7 +526,8 @@ class TaskService extends EventEmitter implements ITaskService {
private _configHasErrors: boolean;
private _providers: Map<number, ITaskProvider>;
private _workspaceTasksPromise: TPromise<TaskSet>;
private _workspaceTasksPromise: TPromise<WorkspaceTaskResult>;
private _taskSystem: ITaskSystem;
private _taskSystemListeners: IDisposable[];
@ -759,6 +764,7 @@ class TaskService extends EventEmitter implements ITaskService {
let id: string = UUID.generateUuid();
let task: Task = {
_id: id,
_source: { kind: TaskSourceKind.Generic },
name: id,
identifier: id,
dependsOn: primaryTasks.map(task => task._id),
@ -894,30 +900,94 @@ class TaskService extends EventEmitter implements ITaskService {
provider.provideTasks().done(done, error);
});
}
// Do this last since the then of a resolved promise returns immediatelly.
counter++;
this.getWorkspaceTasks().done(done, error);
}).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) {
for (let set of result) {
for (let task of set.tasks) {
if (annotatingTasks) {
let annotatingTask = annotatingTasks.byIdentifier[task.identifier] || annotatingTasks.byName[task.name];
if (annotatingTask) {
TaskConfig.mergeTasks(task, annotatingTask);
task._source.kind = TaskSourceKind.Workspace;
continue;
}
}
if (legacyAnnotatingTasks) {
let legacyAnnotatingTask = legacyAnnotatingTasks[task.identifier];
if (legacyAnnotatingTask) {
TaskConfig.mergeTasks(task, legacyAnnotatingTask);
task._source.kind = TaskSourceKind.Workspace;
workspaceTasksToDelete.push(legacyAnnotatingTask);
continue;
}
}
}
}
}
if (workspaceTaskResult.set) {
if (workspaceTasksToDelete.length > 0) {
let tasks = workspaceTaskResult.set.tasks;
let newSet: TaskSet = {
extension: workspaceTaskResult.set.extension,
tasks: []
};
let toDelete = workspaceTasksToDelete.reduce<IStringDictionary<boolean>>((map, task) => {
map[task._id] = true;
return map;
}, Object.create(null));
newSet.tasks = tasks.filter(task => !toDelete[task._id]);
result.push(newSet);
} else {
result.push(workspaceTaskResult.set);
}
}
return result;
}, () => {
// If we can't read the tasks.json file provide at least the contributed tasks
return result;
});
});
}
private getWorkspaceTasks(): TPromise<TaskSet> {
private getLegacyAnnotatingTasks(workspaceTasks: TaskSet): IStringDictionary<Task> {
let result: IStringDictionary<Task>;
function getResult() {
if (result) {
return result;
}
result = Object.create(null);
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;
}
}
return result;
}
private getWorkspaceTasks(): TPromise<WorkspaceTaskResult> {
if (this._workspaceTasksPromise) {
return this._workspaceTasksPromise;
}
this._workspaceTasksPromise = this.computeWorkspaceTasks().then(value => {
this._configHasErrors = value.hasErrors;
if (this._taskSystem instanceof ProcessTaskSystem) {
this._taskSystem.hasErrors(this._configHasErrors);
}
return value.set;
});
this.updateWorkspaceTasks();
return this._workspaceTasksPromise;
}
private updateWorkspaceTasks(): void {
this._workspaceTasksPromise = this.computeWorkspaceTasks().then(value => {
this._configHasErrors = value.hasErrors;
return value.set;
if (this._taskSystem instanceof ProcessTaskSystem) {
this._taskSystem.hasErrors(this._configHasErrors);
}
return value;
});
}
@ -966,7 +1036,7 @@ class TaskService extends EventEmitter implements ITaskService {
return configPromise.then((resolved) => {
return ProblemMatcherRegistry.onReady().then((): WorkspaceTaskResult => {
if (!resolved || !resolved.config) {
return { set: undefined, hasErrors: resolved !== void 0 ? resolved.hasErrors : false };
return { set: undefined, annotatingTasks: undefined, hasErrors: resolved !== void 0 ? resolved.hasErrors : false };
}
let problemReporter = new ProblemReporter(this._outputChannel);
let parseResult = TaskConfig.parse(resolved.config, problemReporter);
@ -977,9 +1047,22 @@ 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, hasErrors };
return { set: undefined, annotatingTasks: undefined, hasErrors };
}
return { set: { tasks: parseResult.tasks }, hasErrors };
let annotatingTasks: { byIdentifier: IStringDictionary<Task>; byName: IStringDictionary<Task>; };
if (parseResult.annotatingTasks && parseResult.annotatingTasks.length > 0) {
annotatingTasks = {
byIdentifier: Object.create(null),
byName: Object.create(null)
};
for (let task of parseResult.annotatingTasks) {
annotatingTasks.byIdentifier[task.identifier] = task;
if (task.name) {
annotatingTasks.byName[task.name] = task;
}
}
}
return { set: { tasks: parseResult.tasks }, annotatingTasks: annotatingTasks, hasErrors };
});
});
}

View file

@ -123,6 +123,7 @@ class TaskBuilder {
this.commandBuilder = new CommandConfigurationBuilder(this, command);
this.result = {
_id: name,
_source: { kind: Tasks.TaskSourceKind.Workspace },
identifier: name,
name: name,
command: this.commandBuilder.result,