problemMatcher.ts 57.6 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { localize } from 'vs/nls';
E
Erich Gamma 已提交
7 8 9 10

import * as Objects from 'vs/base/common/objects';
import * as Strings from 'vs/base/common/strings';
import * as Assert from 'vs/base/common/assert';
11
import { join } from 'vs/base/common/path';
E
Erich Gamma 已提交
12
import * as Types from 'vs/base/common/types';
13
import * as UUID from 'vs/base/common/uuid';
14
import * as Platform from 'vs/base/common/platform';
E
Erich Gamma 已提交
15
import Severity from 'vs/base/common/severity';
16
import { URI } from 'vs/base/common/uri';
17 18
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { ValidationStatus, ValidationState, IProblemReporter, Parser } from 'vs/base/common/parsers';
E
Erich Gamma 已提交
19 20
import { IStringDictionary } from 'vs/base/common/collections';

J
Johannes Rieken 已提交
21
import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers';
22
import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry';
23
import { Event, Emitter } from 'vs/base/common/event';
24
import { IFileService, IFileStat } from 'vs/platform/files/common/files';
E
Erich Gamma 已提交
25 26

export enum FileLocationKind {
27
	Default,
E
Erich Gamma 已提交
28
	Relative,
29 30
	Absolute,
	AutoDetect
E
Erich Gamma 已提交
31 32 33
}

export module FileLocationKind {
34
	export function fromString(value: string): FileLocationKind | undefined {
E
Erich Gamma 已提交
35 36 37 38 39
		value = value.toLowerCase();
		if (value === 'absolute') {
			return FileLocationKind.Absolute;
		} else if (value === 'relative') {
			return FileLocationKind.Relative;
40 41
		} else if (value === 'autodetect') {
			return FileLocationKind.AutoDetect;
E
Erich Gamma 已提交
42 43 44 45 46 47
		} else {
			return undefined;
		}
	}
}

48 49 50 51 52 53
export enum ProblemLocationKind {
	File,
	Location
}

export module ProblemLocationKind {
54
	export function fromString(value: string): ProblemLocationKind | undefined {
55 56 57 58 59 60 61 62 63 64 65
		value = value.toLowerCase();
		if (value === 'file') {
			return ProblemLocationKind.File;
		} else if (value === 'location') {
			return ProblemLocationKind.Location;
		} else {
			return undefined;
		}
	}
}

E
Erich Gamma 已提交
66 67 68
export interface ProblemPattern {
	regexp: RegExp;

69 70
	kind?: ProblemLocationKind;

E
Erich Gamma 已提交
71 72 73 74 75 76 77 78
	file?: number;

	message?: number;

	location?: number;

	line?: number;

79
	character?: number;
E
Erich Gamma 已提交
80 81 82

	endLine?: number;

83
	endCharacter?: number;
E
Erich Gamma 已提交
84 85 86 87 88 89 90 91

	code?: number;

	severity?: number;

	loop?: boolean;
}

92 93 94 95
export interface NamedProblemPattern extends ProblemPattern {
	name: string;
}

96
export type MultiLineProblemPattern = ProblemPattern[];
E
Erich Gamma 已提交
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115

export interface WatchingPattern {
	regexp: RegExp;
	file?: number;
}

export interface WatchingMatcher {
	activeOnStart: boolean;
	beginsPattern: WatchingPattern;
	endsPattern: WatchingPattern;
}

export enum ApplyToKind {
	allDocuments,
	openDocuments,
	closedDocuments
}

export module ApplyToKind {
116
	export function fromString(value: string): ApplyToKind | undefined {
E
Erich Gamma 已提交
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
		value = value.toLowerCase();
		if (value === 'alldocuments') {
			return ApplyToKind.allDocuments;
		} else if (value === 'opendocuments') {
			return ApplyToKind.openDocuments;
		} else if (value === 'closeddocuments') {
			return ApplyToKind.closedDocuments;
		} else {
			return undefined;
		}
	}
}

export interface ProblemMatcher {
	owner: string;
132
	source?: string;
E
Erich Gamma 已提交
133 134 135 136
	applyTo: ApplyToKind;
	fileLocation: FileLocationKind;
	filePrefix?: string;
	pattern: ProblemPattern | ProblemPattern[];
D
Dirk Baeumer 已提交
137
	severity?: Severity;
E
Erich Gamma 已提交
138
	watching?: WatchingMatcher;
139
	uriProvider?: (path: string) => URI;
E
Erich Gamma 已提交
140 141 142 143
}

export interface NamedProblemMatcher extends ProblemMatcher {
	name: string;
144
	label: string;
145
	deprecated?: boolean;
E
Erich Gamma 已提交
146 147
}

148 149
export interface NamedMultiLineProblemPattern {
	name: string;
150
	label: string;
151 152 153
	patterns: MultiLineProblemPattern;
}

A
Alex Ross 已提交
154
export function isNamedProblemMatcher(value: ProblemMatcher | undefined): value is NamedProblemMatcher {
155
	return value && Types.isString((<NamedProblemMatcher>value).name) ? true : false;
E
Erich Gamma 已提交
156 157 158 159
}

interface Location {
	startLineNumber: number;
160
	startCharacter: number;
E
Erich Gamma 已提交
161
	endLineNumber: number;
162
	endCharacter: number;
E
Erich Gamma 已提交
163 164 165
}

interface ProblemData {
166
	kind?: ProblemLocationKind;
E
Erich Gamma 已提交
167
	file?: string;
D
Dirk Baeumer 已提交
168
	location?: string;
E
Erich Gamma 已提交
169
	line?: string;
170
	character?: string;
E
Erich Gamma 已提交
171
	endLine?: string;
172
	endCharacter?: string;
E
Erich Gamma 已提交
173 174 175 176 177 178
	message?: string;
	severity?: string;
	code?: string;
}

export interface ProblemMatch {
179
	resource: Promise<URI>;
E
Erich Gamma 已提交
180 181 182 183 184
	marker: IMarkerData;
	description: ProblemMatcher;
}

export interface HandleResult {
185
	match: ProblemMatch | null;
E
Erich Gamma 已提交
186 187 188
	continue: boolean;
}

189 190

export async function getResource(filename: string, matcher: ProblemMatcher, fileService?: IFileService): Promise<URI> {
E
Erich Gamma 已提交
191
	let kind = matcher.fileLocation;
192
	let fullPath: string | undefined;
E
Erich Gamma 已提交
193 194
	if (kind === FileLocationKind.Absolute) {
		fullPath = filename;
195
	} else if ((kind === FileLocationKind.Relative) && matcher.filePrefix) {
196
		fullPath = join(matcher.filePrefix, filename);
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
	} else if (kind === FileLocationKind.AutoDetect) {
		const matcherClone = Objects.deepClone(matcher);
		matcherClone.fileLocation = FileLocationKind.Relative;
		if (fileService) {
			const relative = await getResource(filename, matcherClone);
			let stat: IFileStat | undefined = undefined;
			try {
				stat = await fileService.resolve(relative);
			} catch (ex) {
				// Do nothing, we just need to catch file resolution errors.
			}
			if (stat) {
				return relative;
			}
		}

		matcherClone.fileLocation = FileLocationKind.Absolute;
		return getResource(filename, matcherClone);
E
Erich Gamma 已提交
215
	}
R
Rob Lourens 已提交
216
	if (fullPath === undefined) {
217 218
		throw new Error('FileLocationKind is not actionable. Does the matcher have a filePrefix? This should never happen.');
	}
E
Erich Gamma 已提交
219 220 221 222
	fullPath = fullPath.replace(/\\/g, '/');
	if (fullPath[0] !== '/') {
		fullPath = '/' + fullPath;
	}
R
Rob Lourens 已提交
223
	if (matcher.uriProvider !== undefined) {
224
		return matcher.uriProvider(fullPath);
225 226 227
	} else {
		return URI.file(fullPath);
	}
E
Erich Gamma 已提交
228 229 230 231
}

export interface ILineMatcher {
	matchLength: number;
232
	next(line: string): ProblemMatch | null;
E
Erich Gamma 已提交
233 234 235
	handle(lines: string[], start?: number): HandleResult;
}

236
export function createLineMatcher(matcher: ProblemMatcher, fileService?: IFileService): ILineMatcher {
E
Erich Gamma 已提交
237 238
	let pattern = matcher.pattern;
	if (Types.isArray(pattern)) {
239
		return new MultiLineMatcher(matcher, fileService);
E
Erich Gamma 已提交
240
	} else {
241
		return new SingleLineMatcher(matcher, fileService);
E
Erich Gamma 已提交
242 243 244
	}
}

245 246
const endOfLine: string = Platform.OS === Platform.OperatingSystem.Windows ? '\r\n' : '\n';

247
abstract class AbstractLineMatcher implements ILineMatcher {
E
Erich Gamma 已提交
248
	private matcher: ProblemMatcher;
249
	private fileService?: IFileService;
E
Erich Gamma 已提交
250

251
	constructor(matcher: ProblemMatcher, fileService?: IFileService) {
E
Erich Gamma 已提交
252
		this.matcher = matcher;
253
		this.fileService = fileService;
E
Erich Gamma 已提交
254 255 256 257 258 259
	}

	public handle(lines: string[], start: number = 0): HandleResult {
		return { match: null, continue: false };
	}

260
	public next(line: string): ProblemMatch | null {
E
Erich Gamma 已提交
261 262 263
		return null;
	}

264
	public abstract get matchLength(): number;
E
Erich Gamma 已提交
265

266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
	protected fillProblemData(data: ProblemData | null, pattern: ProblemPattern, matches: RegExpExecArray): data is ProblemData {
		if (data) {
			this.fillProperty(data, 'file', pattern, matches, true);
			this.appendProperty(data, 'message', pattern, matches, true);
			this.fillProperty(data, 'code', pattern, matches, true);
			this.fillProperty(data, 'severity', pattern, matches, true);
			this.fillProperty(data, 'location', pattern, matches, true);
			this.fillProperty(data, 'line', pattern, matches);
			this.fillProperty(data, 'character', pattern, matches);
			this.fillProperty(data, 'endLine', pattern, matches);
			this.fillProperty(data, 'endCharacter', pattern, matches);
			return true;
		} else {
			return false;
		}
E
Erich Gamma 已提交
281 282
	}

283
	private appendProperty(data: ProblemData, property: keyof ProblemData, pattern: ProblemPattern, matches: RegExpExecArray, trim: boolean = false): void {
284
		const patternProperty = pattern[property];
285 286 287
		if (Types.isUndefined(data[property])) {
			this.fillProperty(data, property, pattern, matches, trim);
		}
288 289
		else if (!Types.isUndefined(patternProperty) && patternProperty < matches.length) {
			let value = matches[patternProperty];
290
			if (trim) {
291
				value = Strings.trim(value)!;
292
			}
M
Matt Bierner 已提交
293
			(data as any)[property] += endOfLine + value;
294 295 296
		}
	}

297
	private fillProperty(data: ProblemData, property: keyof ProblemData, pattern: ProblemPattern, matches: RegExpExecArray, trim: boolean = false): void {
298 299 300
		const patternAtProperty = pattern[property];
		if (Types.isUndefined(data[property]) && !Types.isUndefined(patternAtProperty) && patternAtProperty < matches.length) {
			let value = matches[patternAtProperty];
R
Rob Lourens 已提交
301
			if (value !== undefined) {
302
				if (trim) {
303
					value = Strings.trim(value)!;
304
				}
M
Matt Bierner 已提交
305
				(data as any)[property] = value;
E
Erich Gamma 已提交
306 307 308 309
			}
		}
	}

310
	protected getMarkerMatch(data: ProblemData): ProblemMatch | undefined {
311 312 313 314 315 316 317
		try {
			let location = this.getLocation(data);
			if (data.file && location && data.message) {
				let marker: IMarkerData = {
					severity: this.getSeverity(data),
					startLineNumber: location.startLineNumber,
					startColumn: location.startCharacter,
318
					endLineNumber: location.endLineNumber,
319 320 321
					endColumn: location.endCharacter,
					message: data.message
				};
R
Rob Lourens 已提交
322
				if (data.code !== undefined) {
323 324
					marker.code = data.code;
				}
R
Rob Lourens 已提交
325
				if (this.matcher.source !== undefined) {
326 327
					marker.source = this.matcher.source;
				}
328 329 330 331 332
				return {
					description: this.matcher,
					resource: this.getResource(data.file),
					marker: marker
				};
E
Erich Gamma 已提交
333
			}
334 335
		} catch (err) {
			console.error(`Failed to convert problem data into match: ${JSON.stringify(data)}`);
E
Erich Gamma 已提交
336
		}
M
Matt Bierner 已提交
337
		return undefined;
E
Erich Gamma 已提交
338 339
	}

340 341
	protected getResource(filename: string): Promise<URI> {
		return getResource(filename, this.matcher, this.fileService);
E
Erich Gamma 已提交
342 343
	}

344
	private getLocation(data: ProblemData): Location | null {
345 346 347
		if (data.kind === ProblemLocationKind.File) {
			return this.createLocation(0, 0, 0, 0);
		}
E
Erich Gamma 已提交
348 349 350 351 352 353 354
		if (data.location) {
			return this.parseLocationInfo(data.location);
		}
		if (!data.line) {
			return null;
		}
		let startLine = parseInt(data.line);
355
		let startColumn = data.character ? parseInt(data.character) : undefined;
E
Erich Gamma 已提交
356
		let endLine = data.endLine ? parseInt(data.endLine) : undefined;
357
		let endColumn = data.endCharacter ? parseInt(data.endCharacter) : undefined;
E
Erich Gamma 已提交
358 359 360
		return this.createLocation(startLine, startColumn, endLine, endColumn);
	}

361
	private parseLocationInfo(value: string): Location | null {
E
Erich Gamma 已提交
362 363 364 365 366 367 368 369 370 371 372 373 374
		if (!value || !value.match(/(\d+|\d+,\d+|\d+,\d+,\d+,\d+)/)) {
			return null;
		}
		let parts = value.split(',');
		let startLine = parseInt(parts[0]);
		let startColumn = parts.length > 1 ? parseInt(parts[1]) : undefined;
		if (parts.length > 3) {
			return this.createLocation(startLine, startColumn, parseInt(parts[2]), parseInt(parts[3]));
		} else {
			return this.createLocation(startLine, startColumn, undefined, undefined);
		}
	}

375
	private createLocation(startLine: number, startColumn: number | undefined, endLine: number | undefined, endColumn: number | undefined): Location {
D
Dirk Baeumer 已提交
376
		if (startColumn !== undefined && endColumn !== undefined) {
377
			return { startLineNumber: startLine, startCharacter: startColumn, endLineNumber: endLine || startLine, endCharacter: endColumn };
E
Erich Gamma 已提交
378
		}
D
Dirk Baeumer 已提交
379
		if (startColumn !== undefined) {
380
			return { startLineNumber: startLine, startCharacter: startColumn, endLineNumber: startLine, endCharacter: startColumn };
E
Erich Gamma 已提交
381
		}
382
		return { startLineNumber: startLine, startCharacter: 1, endLineNumber: startLine, endCharacter: Number.MAX_VALUE };
E
Erich Gamma 已提交
383 384
	}

J
Johannes Rieken 已提交
385
	private getSeverity(data: ProblemData): MarkerSeverity {
386
		let result: Severity | null = null;
E
Erich Gamma 已提交
387 388
		if (data.severity) {
			let value = data.severity;
389
			if (value) {
E
Erich Gamma 已提交
390
				result = Severity.fromValue(value);
391 392 393 394 395 396 397 398 399 400 401 402 403
				if (result === Severity.Ignore) {
					if (value === 'E') {
						result = Severity.Error;
					} else if (value === 'W') {
						result = Severity.Warning;
					} else if (value === 'I') {
						result = Severity.Info;
					} else if (Strings.equalsIgnoreCase(value, 'hint')) {
						result = Severity.Info;
					} else if (Strings.equalsIgnoreCase(value, 'note')) {
						result = Severity.Info;
					}
				}
E
Erich Gamma 已提交
404 405 406 407 408
			}
		}
		if (result === null || result === Severity.Ignore) {
			result = this.matcher.severity || Severity.Error;
		}
J
Johannes Rieken 已提交
409
		return MarkerSeverity.fromSeverity(result);
E
Erich Gamma 已提交
410 411 412 413 414 415 416
	}
}

class SingleLineMatcher extends AbstractLineMatcher {

	private pattern: ProblemPattern;

417 418
	constructor(matcher: ProblemMatcher, fileService?: IFileService) {
		super(matcher, fileService);
E
Erich Gamma 已提交
419 420 421 422 423 424 425 426 427 428
		this.pattern = <ProblemPattern>matcher.pattern;
	}

	public get matchLength(): number {
		return 1;
	}

	public handle(lines: string[], start: number = 0): HandleResult {
		Assert.ok(lines.length - start === 1);
		let data: ProblemData = Object.create(null);
R
Rob Lourens 已提交
429
		if (this.pattern.kind !== undefined) {
430 431
			data.kind = this.pattern.kind;
		}
E
Erich Gamma 已提交
432 433 434 435 436 437 438 439 440 441 442
		let matches = this.pattern.regexp.exec(lines[start]);
		if (matches) {
			this.fillProblemData(data, this.pattern, matches);
			let match = this.getMarkerMatch(data);
			if (match) {
				return { match: match, continue: false };
			}
		}
		return { match: null, continue: false };
	}

443
	public next(line: string): ProblemMatch | null {
E
Erich Gamma 已提交
444 445 446 447 448 449 450
		return null;
	}
}

class MultiLineMatcher extends AbstractLineMatcher {

	private patterns: ProblemPattern[];
451
	private data: ProblemData | null;
E
Erich Gamma 已提交
452

453 454
	constructor(matcher: ProblemMatcher, fileService?: IFileService) {
		super(matcher, fileService);
E
Erich Gamma 已提交
455 456 457 458 459 460 461 462 463 464
		this.patterns = <ProblemPattern[]>matcher.pattern;
	}

	public get matchLength(): number {
		return this.patterns.length;
	}

	public handle(lines: string[], start: number = 0): HandleResult {
		Assert.ok(lines.length - start === this.patterns.length);
		this.data = Object.create(null);
465
		let data = this.data!;
466
		data.kind = this.patterns[0].kind;
E
Erich Gamma 已提交
467 468 469 470
		for (let i = 0; i < this.patterns.length; i++) {
			let pattern = this.patterns[i];
			let matches = pattern.regexp.exec(lines[i + start]);
			if (!matches) {
D
Dirk Baeumer 已提交
471
				return { match: null, continue: false };
E
Erich Gamma 已提交
472 473 474
			} else {
				// Only the last pattern can loop
				if (pattern.loop && i === this.patterns.length - 1) {
J
Johannes Rieken 已提交
475
					data = Objects.deepClone(data);
E
Erich Gamma 已提交
476 477 478 479
				}
				this.fillProblemData(data, pattern, matches);
			}
		}
480
		let loop = !!this.patterns[this.patterns.length - 1].loop;
E
Erich Gamma 已提交
481 482 483
		if (!loop) {
			this.data = null;
		}
484 485
		const markerMatch = data ? this.getMarkerMatch(data) : null;
		return { match: markerMatch ? markerMatch : null, continue: loop };
E
Erich Gamma 已提交
486 487
	}

488
	public next(line: string): ProblemMatch | null {
E
Erich Gamma 已提交
489 490 491 492 493 494 495
		let pattern = this.patterns[this.patterns.length - 1];
		Assert.ok(pattern.loop === true && this.data !== null);
		let matches = pattern.regexp.exec(line);
		if (!matches) {
			this.data = null;
			return null;
		}
J
Johannes Rieken 已提交
496
		let data = Objects.deepClone(this.data);
497 498 499 500 501
		let problemMatch: ProblemMatch | undefined;
		if (this.fillProblemData(data, pattern, matches)) {
			problemMatch = this.getMarkerMatch(data);
		}
		return problemMatch ? problemMatch : null;
E
Erich Gamma 已提交
502 503 504 505 506 507 508 509 510 511 512 513 514
	}
}

export namespace Config {

	export interface ProblemPattern {

		/**
		* The regular expression to find a problem in the console output of an
		* executed task.
		*/
		regexp?: string;

515 516 517 518 519 520 521 522
		/**
		* Whether the pattern matches a whole file, or a location (file/line)
		*
		* The default is to match for a location. Only valid on the
		* first problem pattern in a multi line problem matcher.
		*/
		kind?: string;

E
Erich Gamma 已提交
523 524 525 526 527 528 529
		/**
		* The match group index of the filename.
		* If omitted 1 is used.
		*/
		file?: number;

		/**
530
		* The match group index of the problem's location. Valid location
E
Erich Gamma 已提交
531
		* patterns are: (line), (line,column) and (startLine,startColumn,endLine,endColumn).
532
		* If omitted the line and column properties are used.
E
Erich Gamma 已提交
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
		*/
		location?: number;

		/**
		* The match group index of the problem's line in the source file.
		*
		* Defaults to 2.
		*/
		line?: number;

		/**
		* The match group index of the problem's column in the source file.
		*
		* Defaults to 3.
		*/
		column?: number;

		/**
		* The match group index of the problem's end line in the source file.
		*
		* Defaults to undefined. No end line is captured.
		*/
		endLine?: number;

		/**
		* The match group index of the problem's end column in the source file.
		*
		* Defaults to undefined. No end column is captured.
		*/
		endColumn?: number;

		/**
		* The match group index of the problem's severity.
		*
		* Defaults to undefined. In this case the problem matcher's severity
		* is used.
		*/
		severity?: number;

		/**
573
		* The match group index of the problem's code.
E
Erich Gamma 已提交
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
		*
		* Defaults to undefined. No code is captured.
		*/
		code?: number;

		/**
		* The match group index of the message. If omitted it defaults
		* to 4 if location is specified. Otherwise it defaults to 5.
		*/
		message?: number;

		/**
		* Specifies if the last pattern in a multi line problem matcher should
		* loop as long as it does match a line consequently. Only valid on the
		* last problem pattern in a multi line problem matcher.
		*/
		loop?: boolean;
	}

593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
	export interface CheckedProblemPattern extends ProblemPattern {
		/**
		* The regular expression to find a problem in the console output of an
		* executed task.
		*/
		regexp: string;
	}

	export namespace CheckedProblemPattern {
		export function is(value: any): value is CheckedProblemPattern {
			let candidate: ProblemPattern = value as ProblemPattern;
			return candidate && Types.isString(candidate.regexp);
		}
	}

608 609 610 611 612
	export interface NamedProblemPattern extends ProblemPattern {
		/**
		 * The name of the problem pattern.
		 */
		name: string;
613 614 615 616 617

		/**
		 * A human readable label
		 */
		label?: string;
618 619 620
	}

	export namespace NamedProblemPattern {
621
		export function is(value: any): value is NamedProblemPattern {
622 623 624 625 626
			let candidate: NamedProblemPattern = value as NamedProblemPattern;
			return candidate && Types.isString(candidate.name);
		}
	}

627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
	export interface NamedCheckedProblemPattern extends NamedProblemPattern {
		/**
		* The regular expression to find a problem in the console output of an
		* executed task.
		*/
		regexp: string;
	}

	export namespace NamedCheckedProblemPattern {
		export function is(value: any): value is NamedCheckedProblemPattern {
			let candidate: NamedProblemPattern = value as NamedProblemPattern;
			return candidate && NamedProblemPattern.is(candidate) && Types.isString(candidate.regexp);
		}
	}

642 643 644 645 646 647 648 649
	export type MultiLineProblemPattern = ProblemPattern[];

	export namespace MultiLineProblemPattern {
		export function is(value: any): value is MultiLineProblemPattern {
			return value && Types.isArray(value);
		}
	}

650 651 652 653
	export type MultiLineCheckedProblemPattern = CheckedProblemPattern[];

	export namespace MultiLineCheckedProblemPattern {
		export function is(value: any): value is MultiLineCheckedProblemPattern {
654 655 656 657 658 659 660
			if (!MultiLineProblemPattern.is(value)) {
				return false;
			}
			for (const element of value) {
				if (!Config.CheckedProblemPattern.is(element)) {
					return false;
				}
661
			}
662
			return true;
663 664 665 666
		}
	}

	export interface NamedMultiLineCheckedProblemPattern {
667 668 669 670 671
		/**
		 * The name of the problem pattern.
		 */
		name: string;

672 673 674 675 676
		/**
		 * A human readable label
		 */
		label?: string;

677 678 679
		/**
		 * The actual patterns
		 */
680
		patterns: MultiLineCheckedProblemPattern;
681 682
	}

683 684 685 686
	export namespace NamedMultiLineCheckedProblemPattern {
		export function is(value: any): value is NamedMultiLineCheckedProblemPattern {
			let candidate = value as NamedMultiLineCheckedProblemPattern;
			return candidate && Types.isString(candidate.name) && Types.isArray(candidate.patterns) && MultiLineCheckedProblemPattern.is(candidate.patterns);
687 688 689
		}
	}

690
	export type NamedProblemPatterns = (Config.NamedProblemPattern | Config.NamedMultiLineCheckedProblemPattern)[];
691

E
Erich Gamma 已提交
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710
	/**
	* A watching pattern
	*/
	export interface WatchingPattern {
		/**
		* The actual regular expression
		*/
		regexp?: string;

		/**
		* The match group index of the filename. If provided the expression
		* is matched for that file only.
		*/
		file?: number;
	}

	/**
	* A description to track the start and end of a watching task.
	*/
711
	export interface BackgroundMonitor {
E
Erich Gamma 已提交
712 713 714 715

		/**
		* If set to true the watcher is in active mode when the task
		* starts. This is equals of issuing a line that matches the
716
		* beginsPattern.
E
Erich Gamma 已提交
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
		*/
		activeOnStart?: boolean;

		/**
		* If matched in the output the start of a watching task is signaled.
		*/
		beginsPattern?: string | WatchingPattern;

		/**
		* If matched in the output the end of a watching task is signaled.
		*/
		endsPattern?: string | WatchingPattern;
	}

	/**
	* A description of a problem matcher that detects problems
	* in build output.
	*/
	export interface ProblemMatcher {

		/**
738 739 740 741 742
		 * The name of a base problem matcher to use. If specified the
		 * base problem matcher will be used as a template and properties
		 * specified here will replace properties of the base problem
		 * matcher
		 */
E
Erich Gamma 已提交
743 744 745
		base?: string;

		/**
746 747 748 749 750
		 * The owner of the produced VSCode problem. This is typically
		 * the identifier of a VSCode language service if the problems are
		 * to be merged with the one produced by the language service
		 * or a generated internal id. Defaults to the generated internal id.
		 */
E
Erich Gamma 已提交
751 752
		owner?: string;

753 754 755 756 757 758
		/**
		 * A human-readable string describing the source of this problem.
		 * E.g. 'typescript' or 'super lint'.
		 */
		source?: string;

E
Erich Gamma 已提交
759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812
		/**
		* Specifies to which kind of documents the problems found by this
		* matcher are applied. Valid values are:
		*
		*   "allDocuments": problems found in all documents are applied.
		*   "openDocuments": problems found in documents that are open
		*   are applied.
		*   "closedDocuments": problems found in closed documents are
		*   applied.
		*/
		applyTo?: string;

		/**
		* The severity of the VSCode problem produced by this problem matcher.
		*
		* Valid values are:
		*   "error": to produce errors.
		*   "warning": to produce warnings.
		*   "info": to produce infos.
		*
		* The value is used if a pattern doesn't specify a severity match group.
		* Defaults to "error" if omitted.
		*/
		severity?: string;

		/**
		* Defines how filename reported in a problem pattern
		* should be read. Valid values are:
		*  - "absolute": the filename is always treated absolute.
		*  - "relative": the filename is always treated relative to
		*    the current working directory. This is the default.
		*  - ["relative", "path value"]: the filename is always
		*    treated relative to the given path value.
		*/
		fileLocation?: string | string[];

		/**
		* The name of a predefined problem pattern, the inline definintion
		* of a problem pattern or an array of problem patterns to match
		* problems spread over multiple lines.
		*/
		pattern?: string | ProblemPattern | ProblemPattern[];

		/**
		* A regular expression signaling that a watched tasks begins executing
		* triggered through file watching.
		*/
		watchedTaskBeginsRegExp?: string;

		/**
		* A regular expression signaling that a watched tasks ends executing.
		*/
		watchedTaskEndsRegExp?: string;

813 814 815 816 817
		/**
		 * @deprecated Use background instead.
		 */
		watching?: BackgroundMonitor;
		background?: BackgroundMonitor;
E
Erich Gamma 已提交
818 819
	}

820
	export type ProblemMatcherType = string | ProblemMatcher | Array<string | ProblemMatcher>;
E
Erich Gamma 已提交
821 822 823

	export interface NamedProblemMatcher extends ProblemMatcher {
		/**
824
		* This name can be used to refer to the
825
		* problem matcher from within a task.
E
Erich Gamma 已提交
826
		*/
827
		name: string;
828 829

		/**
830
		 * A human readable label.
831 832
		 */
		label?: string;
E
Erich Gamma 已提交
833 834 835 836 837 838 839
	}

	export function isNamedProblemMatcher(value: ProblemMatcher): value is NamedProblemMatcher {
		return Types.isString((<NamedProblemMatcher>value).name);
	}
}

840
export class ProblemPatternParser extends Parser {
E
Erich Gamma 已提交
841

842 843 844 845
	constructor(logger: IProblemReporter) {
		super(logger);
	}

846
	public parse(value: Config.ProblemPattern): ProblemPattern;
847
	public parse(value: Config.MultiLineProblemPattern): MultiLineProblemPattern;
848
	public parse(value: Config.NamedProblemPattern): NamedProblemPattern;
849 850 851
	public parse(value: Config.NamedMultiLineCheckedProblemPattern): NamedMultiLineProblemPattern;
	public parse(value: Config.ProblemPattern | Config.MultiLineProblemPattern | Config.NamedProblemPattern | Config.NamedMultiLineCheckedProblemPattern): any {
		if (Config.NamedMultiLineCheckedProblemPattern.is(value)) {
852
			return this.createNamedMultiLineProblemPattern(value);
853
		} else if (Config.MultiLineCheckedProblemPattern.is(value)) {
854
			return this.createMultiLineProblemPattern(value);
855
		} else if (Config.NamedCheckedProblemPattern.is(value)) {
856 857 858
			let result = this.createSingleProblemPattern(value) as NamedProblemPattern;
			result.name = value.name;
			return result;
859
		} else if (Config.CheckedProblemPattern.is(value)) {
860 861
			return this.createSingleProblemPattern(value);
		} else {
862
			this.error(localize('ProblemPatternParser.problemPattern.missingRegExp', 'The problem pattern is missing a regular expression.'));
863 864 865 866
			return null;
		}
	}

867
	private createSingleProblemPattern(value: Config.CheckedProblemPattern): ProblemPattern | null {
868
		let result = this.doCreateSingleProblemPattern(value, true);
869 870 871
		if (result === undefined) {
			return null;
		} else if (result.kind === undefined) {
872 873
			result.kind = ProblemLocationKind.Location;
		}
874 875 876
		return this.validateProblemPattern([result]) ? result : null;
	}

877 878 879 880 881
	private createNamedMultiLineProblemPattern(value: Config.NamedMultiLineCheckedProblemPattern): NamedMultiLineProblemPattern | null {
		const validPatterns = this.createMultiLineProblemPattern(value.patterns);
		if (!validPatterns) {
			return null;
		}
882 883
		let result = {
			name: value.name,
884
			label: value.label ? value.label : value.name,
885
			patterns: validPatterns
886
		};
887
		return result;
888 889
	}

890
	private createMultiLineProblemPattern(values: Config.MultiLineCheckedProblemPattern): MultiLineProblemPattern | null {
891 892 893
		let result: MultiLineProblemPattern = [];
		for (let i = 0; i < values.length; i++) {
			let pattern = this.doCreateSingleProblemPattern(values[i], false);
894 895 896
			if (pattern === undefined) {
				return null;
			}
897 898 899 900 901 902 903 904
			if (i < values.length - 1) {
				if (!Types.isUndefined(pattern.loop) && pattern.loop) {
					pattern.loop = false;
					this.error(localize('ProblemPatternParser.loopProperty.notLast', 'The loop property is only supported on the last line matcher.'));
				}
			}
			result.push(pattern);
		}
905 906 907
		if (result[0].kind === undefined) {
			result[0].kind = ProblemLocationKind.Location;
		}
908 909 910
		return this.validateProblemPattern(result) ? result : null;
	}

911
	private doCreateSingleProblemPattern(value: Config.CheckedProblemPattern, setDefaults: boolean): ProblemPattern | undefined {
912
		const regexp = this.createRegularExpression(value.regexp);
R
Rob Lourens 已提交
913
		if (regexp === undefined) {
914
			return undefined;
915 916
		}
		let result: ProblemPattern = { regexp };
917 918 919
		if (value.kind) {
			result.kind = ProblemLocationKind.fromString(value.kind);
		}
920 921

		function copyProperty(result: ProblemPattern, source: Config.ProblemPattern, resultKey: keyof ProblemPattern, sourceKey: keyof Config.ProblemPattern) {
M
Matt Bierner 已提交
922
			const value = source[sourceKey];
923
			if (typeof value === 'number') {
M
Matt Bierner 已提交
924
				(result as any)[resultKey] = value;
925
			}
926 927 928 929 930 931 932 933 934 935 936 937 938
		}
		copyProperty(result, value, 'file', 'file');
		copyProperty(result, value, 'location', 'location');
		copyProperty(result, value, 'line', 'line');
		copyProperty(result, value, 'character', 'column');
		copyProperty(result, value, 'endLine', 'endLine');
		copyProperty(result, value, 'endCharacter', 'endColumn');
		copyProperty(result, value, 'severity', 'severity');
		copyProperty(result, value, 'code', 'code');
		copyProperty(result, value, 'message', 'message');
		if (value.loop === true || value.loop === false) {
			result.loop = value.loop;
		}
939
		if (setDefaults) {
940
			if (result.location || result.kind === ProblemLocationKind.File) {
941
				let defaultValue: Partial<ProblemPattern> = {
942 943
					file: 1,
					message: 0
944 945
				};
				result = Objects.mixin(result, defaultValue, false);
946
			} else {
947
				let defaultValue: Partial<ProblemPattern> = {
948 949
					file: 1,
					line: 2,
950
					character: 3,
951
					message: 0
952 953
				};
				result = Objects.mixin(result, defaultValue, false);
954 955 956 957 958 959
			}
		}
		return result;
	}

	private validateProblemPattern(values: ProblemPattern[]): boolean {
960
		let file: boolean = false, message: boolean = false, location: boolean = false, line: boolean = false;
961 962 963 964 965 966
		let locationKind = (values[0].kind === undefined) ? ProblemLocationKind.Location : values[0].kind;

		values.forEach((pattern, i) => {
			if (i !== 0 && pattern.kind) {
				this.error(localize('ProblemPatternParser.problemPattern.kindProperty.notFirst', 'The problem pattern is invalid. The kind property must be provided only in the first element'));
			}
967 968 969 970 971
			file = file || !Types.isUndefined(pattern.file);
			message = message || !Types.isUndefined(pattern.message);
			location = location || !Types.isUndefined(pattern.location);
			line = line || !Types.isUndefined(pattern.line);
		});
972 973 974 975 976 977
		if (!(file && message)) {
			this.error(localize('ProblemPatternParser.problemPattern.missingProperty', 'The problem pattern is invalid. It must have at least have a file and a message.'));
			return false;
		}
		if (locationKind === ProblemLocationKind.Location && !(location || line)) {
			this.error(localize('ProblemPatternParser.problemPattern.missingLocation', 'The problem pattern is invalid. It must either have kind: "file" or have a line or location match group.'));
978 979 980 981 982
			return false;
		}
		return true;
	}

983 984
	private createRegularExpression(value: string): RegExp | undefined {
		let result: RegExp | undefined;
985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
		try {
			result = new RegExp(value);
		} catch (err) {
			this.error(localize('ProblemPatternParser.invalidRegexp', 'Error: The string {0} is not a valid regular expression.\n', value));
		}
		return result;
	}
}

export class ExtensionRegistryReporter implements IProblemReporter {
	constructor(private _collector: ExtensionMessageCollector, private _validationStatus: ValidationStatus = new ValidationStatus()) {
	}

	public info(message: string): void {
		this._validationStatus.state = ValidationState.Info;
		this._collector.info(message);
	}

	public warn(message: string): void {
		this._validationStatus.state = ValidationState.Warning;
		this._collector.warn(message);
	}

	public error(message: string): void {
		this._validationStatus.state = ValidationState.Error;
		this._collector.error(message);
	}

	public fatal(message: string): void {
		this._validationStatus.state = ValidationState.Fatal;
		this._collector.error(message);
	}

	public get status(): ValidationStatus {
		return this._validationStatus;
	}
}

export namespace Schemas {

	export const ProblemPattern: IJSONSchema = {
		default: {
			regexp: '^([^\\\\s].*)\\\\((\\\\d+,\\\\d+)\\\\):\\\\s*(.*)$',
			file: 1,
			location: 2,
			message: 3
		},
		type: 'object',
		additionalProperties: false,
		properties: {
			regexp: {
				type: 'string',
				description: localize('ProblemPatternSchema.regexp', 'The regular expression to find an error, warning or info in the output.')
			},
1039 1040 1041 1042
			kind: {
				type: 'string',
				description: localize('ProblemPatternSchema.kind', 'whether the pattern matches a location (file and line) or only a file.')
			},
1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085
			file: {
				type: 'integer',
				description: localize('ProblemPatternSchema.file', 'The match group index of the filename. If omitted 1 is used.')
			},
			location: {
				type: 'integer',
				description: localize('ProblemPatternSchema.location', 'The match group index of the problem\'s location. Valid location patterns are: (line), (line,column) and (startLine,startColumn,endLine,endColumn). If omitted (line,column) is assumed.')
			},
			line: {
				type: 'integer',
				description: localize('ProblemPatternSchema.line', 'The match group index of the problem\'s line. Defaults to 2')
			},
			column: {
				type: 'integer',
				description: localize('ProblemPatternSchema.column', 'The match group index of the problem\'s line character. Defaults to 3')
			},
			endLine: {
				type: 'integer',
				description: localize('ProblemPatternSchema.endLine', 'The match group index of the problem\'s end line. Defaults to undefined')
			},
			endColumn: {
				type: 'integer',
				description: localize('ProblemPatternSchema.endColumn', 'The match group index of the problem\'s end line character. Defaults to undefined')
			},
			severity: {
				type: 'integer',
				description: localize('ProblemPatternSchema.severity', 'The match group index of the problem\'s severity. Defaults to undefined')
			},
			code: {
				type: 'integer',
				description: localize('ProblemPatternSchema.code', 'The match group index of the problem\'s code. Defaults to undefined')
			},
			message: {
				type: 'integer',
				description: localize('ProblemPatternSchema.message', 'The match group index of the message. If omitted it defaults to 4 if location is specified. Otherwise it defaults to 5.')
			},
			loop: {
				type: 'boolean',
				description: localize('ProblemPatternSchema.loop', 'In a multi line matcher loop indicated whether this pattern is executed in a loop as long as it matches. Can only specified on a last pattern in a multi line pattern.')
			}
		}
	};

J
Johannes Rieken 已提交
1086
	export const NamedProblemPattern: IJSONSchema = Objects.deepClone(ProblemPattern);
1087
	NamedProblemPattern.properties = Objects.deepClone(NamedProblemPattern.properties) || {};
1088 1089 1090 1091 1092
	NamedProblemPattern.properties['name'] = {
		type: 'string',
		description: localize('NamedProblemPatternSchema.name', 'The name of the problem pattern.')
	};

S
Sylvain Joyeux 已提交
1093
	export const MultiLineProblemPattern: IJSONSchema = {
1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114
		type: 'array',
		items: ProblemPattern
	};

	export const NamedMultiLineProblemPattern: IJSONSchema = {
		type: 'object',
		additionalProperties: false,
		properties: {
			name: {
				type: 'string',
				description: localize('NamedMultiLineProblemPatternSchema.name', 'The name of the problem multi line problem pattern.')
			},
			patterns: {
				type: 'array',
				description: localize('NamedMultiLineProblemPatternSchema.patterns', 'The actual patterns.'),
				items: ProblemPattern
			}
		}
	};
}

1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125
const problemPatternExtPoint = ExtensionsRegistry.registerExtensionPoint<Config.NamedProblemPatterns>({
	extensionPoint: 'problemPatterns',
	jsonSchema: {
		description: localize('ProblemPatternExtPoint', 'Contributes problem patterns'),
		type: 'array',
		items: {
			anyOf: [
				Schemas.NamedProblemPattern,
				Schemas.NamedMultiLineProblemPattern
			]
		}
1126
	}
1127 1128 1129
});

export interface IProblemPatternRegistry {
D
Dirk Baeumer 已提交
1130
	onReady(): Promise<void>;
1131 1132 1133 1134 1135 1136 1137

	get(key: string): ProblemPattern | MultiLineProblemPattern;
}

class ProblemPatternRegistryImpl implements IProblemPatternRegistry {

	private patterns: IStringDictionary<ProblemPattern | ProblemPattern[]>;
D
Dirk Baeumer 已提交
1138
	private readyPromise: Promise<void>;
1139 1140 1141 1142

	constructor() {
		this.patterns = Object.create(null);
		this.fillDefaults();
D
Dirk Baeumer 已提交
1143
		this.readyPromise = new Promise<void>((resolve, reject) => {
1144
			problemPatternExtPoint.setHandler((extensions, delta) => {
1145 1146
				// We get all statically know extension during startup in one batch
				try {
1147 1148 1149 1150 1151 1152 1153 1154 1155
					delta.removed.forEach(extension => {
						let problemPatterns = extension.value as Config.NamedProblemPatterns;
						for (let pattern of problemPatterns) {
							if (this.patterns[pattern.name]) {
								delete this.patterns[pattern.name];
							}
						}
					});
					delta.added.forEach(extension => {
1156 1157 1158
						let problemPatterns = extension.value as Config.NamedProblemPatterns;
						let parser = new ProblemPatternParser(new ExtensionRegistryReporter(extension.collector));
						for (let pattern of problemPatterns) {
1159
							if (Config.NamedMultiLineCheckedProblemPattern.is(pattern)) {
1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184
								let result = parser.parse(pattern);
								if (parser.problemReporter.status.state < ValidationState.Error) {
									this.add(result.name, result.patterns);
								} else {
									extension.collector.error(localize('ProblemPatternRegistry.error', 'Invalid problem pattern. The pattern will be ignored.'));
									extension.collector.error(JSON.stringify(pattern, undefined, 4));
								}
							}
							else if (Config.NamedProblemPattern.is(pattern)) {
								let result = parser.parse(pattern);
								if (parser.problemReporter.status.state < ValidationState.Error) {
									this.add(pattern.name, result);
								} else {
									extension.collector.error(localize('ProblemPatternRegistry.error', 'Invalid problem pattern. The pattern will be ignored.'));
									extension.collector.error(JSON.stringify(pattern, undefined, 4));
								}
							}
							parser.reset();
						}
					});
				} catch (error) {
					// Do nothing
				}
				resolve(undefined);
			});
1185
		});
1186 1187
	}

D
Dirk Baeumer 已提交
1188
	public onReady(): Promise<void> {
1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201
		return this.readyPromise;
	}

	public add(key: string, value: ProblemPattern | ProblemPattern[]): void {
		this.patterns[key] = value;
	}

	public get(key: string): ProblemPattern | ProblemPattern[] {
		return this.patterns[key];
	}

	private fillDefaults(): void {
		this.add('msCompile', {
1202
			regexp: /^(?:\s+\d+\>)?([^\s].*)\((\d+|\d+,\d+|\d+,\d+,\d+,\d+)\)\s*:\s+(error|warning|info)\s+(\w{1,2}\d+)\s*:\s*(.*)$/,
1203
			kind: ProblemLocationKind.Location,
1204 1205 1206 1207 1208 1209 1210 1211
			file: 1,
			location: 2,
			severity: 3,
			code: 4,
			message: 5
		});
		this.add('gulp-tsc', {
			regexp: /^([^\s].*)\((\d+|\d+,\d+|\d+,\d+,\d+,\d+)\):\s+(\d+)\s+(.*)$/,
1212
			kind: ProblemLocationKind.Location,
1213 1214 1215 1216 1217 1218 1219
			file: 1,
			location: 2,
			code: 3,
			message: 4
		});
		this.add('cpp', {
			regexp: /^([^\s].*)\((\d+|\d+,\d+|\d+,\d+,\d+,\d+)\):\s+(error|warning|info)\s+(C\d+)\s*:\s*(.*)$/,
1220
			kind: ProblemLocationKind.Location,
1221 1222 1223 1224 1225 1226 1227 1228
			file: 1,
			location: 2,
			severity: 3,
			code: 4,
			message: 5
		});
		this.add('csc', {
			regexp: /^([^\s].*)\((\d+|\d+,\d+|\d+,\d+,\d+,\d+)\):\s+(error|warning|info)\s+(CS\d+)\s*:\s*(.*)$/,
1229
			kind: ProblemLocationKind.Location,
1230 1231 1232 1233 1234 1235 1236 1237
			file: 1,
			location: 2,
			severity: 3,
			code: 4,
			message: 5
		});
		this.add('vb', {
			regexp: /^([^\s].*)\((\d+|\d+,\d+|\d+,\d+,\d+,\d+)\):\s+(error|warning|info)\s+(BC\d+)\s*:\s*(.*)$/,
1238
			kind: ProblemLocationKind.Location,
1239 1240 1241 1242 1243 1244 1245 1246
			file: 1,
			location: 2,
			severity: 3,
			code: 4,
			message: 5
		});
		this.add('lessCompile', {
			regexp: /^\s*(.*) in file (.*) line no. (\d+)$/,
1247
			kind: ProblemLocationKind.Location,
1248 1249 1250 1251 1252 1253
			message: 1,
			file: 2,
			line: 3
		});
		this.add('jshint', {
			regexp: /^(.*):\s+line\s+(\d+),\s+col\s+(\d+),\s(.+?)(?:\s+\((\w)(\d+)\))?$/,
1254
			kind: ProblemLocationKind.Location,
1255 1256
			file: 1,
			line: 2,
1257
			character: 3,
1258 1259 1260 1261 1262 1263 1264
			message: 4,
			severity: 5,
			code: 6
		});
		this.add('jshint-stylish', [
			{
				regexp: /^(.+)$/,
1265
				kind: ProblemLocationKind.Location,
1266 1267 1268 1269 1270
				file: 1
			},
			{
				regexp: /^\s+line\s+(\d+)\s+col\s+(\d+)\s+(.+?)(?:\s+\((\w)(\d+)\))?$/,
				line: 1,
1271
				character: 2,
1272 1273 1274 1275 1276 1277 1278 1279 1280
				message: 3,
				severity: 4,
				code: 5,
				loop: true
			}
		]);
		this.add('eslint-compact', {
			regexp: /^(.+):\sline\s(\d+),\scol\s(\d+),\s(Error|Warning|Info)\s-\s(.+)\s\((.+)\)$/,
			file: 1,
1281
			kind: ProblemLocationKind.Location,
1282
			line: 2,
1283
			character: 3,
1284 1285 1286 1287 1288 1289 1290
			severity: 4,
			message: 5,
			code: 6
		});
		this.add('eslint-stylish', [
			{
				regexp: /^([^\s].*)$/,
1291
				kind: ProblemLocationKind.Location,
1292 1293 1294
				file: 1
			},
			{
1295
				regexp: /^\s+(\d+):(\d+)\s+(error|warning|info)\s+(.+?)(?:\s\s+(.*))?$/,
1296
				line: 1,
1297
				character: 2,
1298 1299 1300 1301 1302 1303 1304 1305
				severity: 3,
				message: 4,
				code: 5,
				loop: true
			}
		]);
		this.add('go', {
			regexp: /^([^:]*: )?((.:)?[^:]*):(\d+)(:(\d+))?: (.*)$/,
1306
			kind: ProblemLocationKind.Location,
1307 1308
			file: 2,
			line: 4,
1309
			character: 6,
1310 1311 1312 1313 1314 1315
			message: 7
		});
	}
}

export const ProblemPatternRegistry: IProblemPatternRegistry = new ProblemPatternRegistryImpl();
E
Erich Gamma 已提交
1316

1317
export class ProblemMatcherParser extends Parser {
E
Erich Gamma 已提交
1318

1319 1320
	constructor(logger: IProblemReporter) {
		super(logger);
E
Erich Gamma 已提交
1321 1322
	}

A
Alex Ross 已提交
1323
	public parse(json: Config.ProblemMatcher): ProblemMatcher | undefined {
E
Erich Gamma 已提交
1324 1325
		let result = this.createProblemMatcher(json);
		if (!this.checkProblemMatcherValid(json, result)) {
A
Alex Ross 已提交
1326
			return undefined;
E
Erich Gamma 已提交
1327 1328 1329 1330 1331 1332
		}
		this.addWatchingMatcher(json, result);

		return result;
	}

1333
	private checkProblemMatcherValid(externalProblemMatcher: Config.ProblemMatcher, problemMatcher: ProblemMatcher | null): problemMatcher is ProblemMatcher {
1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347
		if (!problemMatcher) {
			this.error(localize('ProblemMatcherParser.noProblemMatcher', 'Error: the description can\'t be converted into a problem matcher:\n{0}\n', JSON.stringify(externalProblemMatcher, null, 4)));
			return false;
		}
		if (!problemMatcher.pattern) {
			this.error(localize('ProblemMatcherParser.noProblemPattern', 'Error: the description doesn\'t define a valid problem pattern:\n{0}\n', JSON.stringify(externalProblemMatcher, null, 4)));
			return false;
		}
		if (!problemMatcher.owner) {
			this.error(localize('ProblemMatcherParser.noOwner', 'Error: the description doesn\'t define an owner:\n{0}\n', JSON.stringify(externalProblemMatcher, null, 4)));
			return false;
		}
		if (Types.isUndefined(problemMatcher.fileLocation)) {
			this.error(localize('ProblemMatcherParser.noFileLocation', 'Error: the description doesn\'t define a file location:\n{0}\n', JSON.stringify(externalProblemMatcher, null, 4)));
E
Erich Gamma 已提交
1348 1349 1350 1351 1352
			return false;
		}
		return true;
	}

1353
	private createProblemMatcher(description: Config.ProblemMatcher): ProblemMatcher | null {
1354
		let result: ProblemMatcher | null = null;
E
Erich Gamma 已提交
1355

1356
		let owner = Types.isString(description.owner) ? description.owner : UUID.generateUuid();
1357
		let source = Types.isString(description.source) ? description.source : undefined;
E
Erich Gamma 已提交
1358 1359 1360 1361
		let applyTo = Types.isString(description.applyTo) ? ApplyToKind.fromString(description.applyTo) : ApplyToKind.allDocuments;
		if (!applyTo) {
			applyTo = ApplyToKind.allDocuments;
		}
1362 1363
		let fileLocation: FileLocationKind | undefined = undefined;
		let filePrefix: string | undefined = undefined;
E
Erich Gamma 已提交
1364

1365
		let kind: FileLocationKind | undefined;
E
Erich Gamma 已提交
1366 1367
		if (Types.isUndefined(description.fileLocation)) {
			fileLocation = FileLocationKind.Relative;
1368
			filePrefix = '${workspaceFolder}';
E
Erich Gamma 已提交
1369 1370 1371 1372
		} else if (Types.isString(description.fileLocation)) {
			kind = FileLocationKind.fromString(<string>description.fileLocation);
			if (kind) {
				fileLocation = kind;
1373
				if ((kind === FileLocationKind.Relative) || (kind === FileLocationKind.AutoDetect)) {
1374
					filePrefix = '${workspaceFolder}';
E
Erich Gamma 已提交
1375 1376 1377 1378 1379 1380 1381 1382
				}
			}
		} else if (Types.isStringArray(description.fileLocation)) {
			let values = <string[]>description.fileLocation;
			if (values.length > 0) {
				kind = FileLocationKind.fromString(values[0]);
				if (values.length === 1 && kind === FileLocationKind.Absolute) {
					fileLocation = kind;
1383
				} else if (values.length === 2 && (kind === FileLocationKind.Relative || kind === FileLocationKind.AutoDetect) && values[1]) {
E
Erich Gamma 已提交
1384 1385 1386 1387 1388 1389 1390 1391 1392 1393
					fileLocation = kind;
					filePrefix = values[1];
				}
			}
		}

		let pattern = description.pattern ? this.createProblemPattern(description.pattern) : undefined;

		let severity = description.severity ? Severity.fromValue(description.severity) : undefined;
		if (severity === Severity.Ignore) {
1394
			this.info(localize('ProblemMatcherParser.unknownSeverity', 'Info: unknown severity {0}. Valid values are error, warning and info.\n', description.severity));
E
Erich Gamma 已提交
1395 1396 1397 1398 1399 1400
			severity = Severity.Error;
		}

		if (Types.isString(description.base)) {
			let variableName = <string>description.base;
			if (variableName.length > 1 && variableName[0] === '$') {
1401
				let base = ProblemMatcherRegistry.get(variableName.substring(1));
E
Erich Gamma 已提交
1402
				if (base) {
J
Johannes Rieken 已提交
1403
					result = Objects.deepClone(base);
R
Rob Lourens 已提交
1404
					if (description.owner !== undefined && owner !== undefined) {
E
Erich Gamma 已提交
1405 1406
						result.owner = owner;
					}
R
Rob Lourens 已提交
1407
					if (description.source !== undefined && source !== undefined) {
1408 1409
						result.source = source;
					}
R
Rob Lourens 已提交
1410
					if (description.fileLocation !== undefined && fileLocation !== undefined) {
E
Erich Gamma 已提交
1411 1412 1413
						result.fileLocation = fileLocation;
						result.filePrefix = filePrefix;
					}
R
Rob Lourens 已提交
1414
					if (description.pattern !== undefined && pattern !== undefined && pattern !== null) {
E
Erich Gamma 已提交
1415 1416
						result.pattern = pattern;
					}
R
Rob Lourens 已提交
1417
					if (description.severity !== undefined && severity !== undefined) {
E
Erich Gamma 已提交
1418 1419
						result.severity = severity;
					}
R
Rob Lourens 已提交
1420
					if (description.applyTo !== undefined && applyTo !== undefined) {
1421 1422
						result.applyTo = applyTo;
					}
E
Erich Gamma 已提交
1423 1424
				}
			}
1425
		} else if (fileLocation && pattern) {
E
Erich Gamma 已提交
1426 1427 1428 1429 1430 1431
			result = {
				owner: owner,
				applyTo: applyTo,
				fileLocation: fileLocation,
				pattern: pattern,
			};
1432 1433 1434
			if (source) {
				result.source = source;
			}
E
Erich Gamma 已提交
1435 1436 1437 1438 1439 1440 1441 1442
			if (filePrefix) {
				result.filePrefix = filePrefix;
			}
			if (severity) {
				result.severity = severity;
			}
		}
		if (Config.isNamedProblemMatcher(description)) {
1443 1444
			(result as NamedProblemMatcher).name = description.name;
			(result as NamedProblemMatcher).label = Types.isString(description.label) ? description.label : description.name;
E
Erich Gamma 已提交
1445 1446 1447 1448
		}
		return result;
	}

1449
	private createProblemPattern(value: string | Config.ProblemPattern | Config.MultiLineProblemPattern): ProblemPattern | ProblemPattern[] | null {
E
Erich Gamma 已提交
1450
		if (Types.isString(value)) {
D
Dirk Baeumer 已提交
1451
			let variableName: string = <string>value;
E
Erich Gamma 已提交
1452
			if (variableName.length > 1 && variableName[0] === '$') {
1453 1454
				let result = ProblemPatternRegistry.get(variableName.substring(1));
				if (!result) {
D
Dirk Baeumer 已提交
1455
					this.error(localize('ProblemMatcherParser.noDefinedPatter', 'Error: the pattern with the identifier {0} doesn\'t exist.', variableName));
1456 1457 1458 1459 1460 1461
				}
				return result;
			} else {
				if (variableName.length === 0) {
					this.error(localize('ProblemMatcherParser.noIdentifier', 'Error: the pattern property refers to an empty identifier.'));
				} else {
D
Dirk Baeumer 已提交
1462
					this.error(localize('ProblemMatcherParser.noValidIdentifier', 'Error: the pattern property {0} is not a valid pattern variable name.', variableName));
1463
				}
E
Erich Gamma 已提交
1464
			}
1465 1466
		} else if (value) {
			let problemPatternParser = new ProblemPatternParser(this.problemReporter);
1467 1468 1469 1470 1471
			if (Array.isArray(value)) {
				return problemPatternParser.parse(value);
			} else {
				return problemPatternParser.parse(value);
			}
E
Erich Gamma 已提交
1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483
		}
		return null;
	}

	private addWatchingMatcher(external: Config.ProblemMatcher, internal: ProblemMatcher): void {
		let oldBegins = this.createRegularExpression(external.watchedTaskBeginsRegExp);
		let oldEnds = this.createRegularExpression(external.watchedTaskEndsRegExp);
		if (oldBegins && oldEnds) {
			internal.watching = {
				activeOnStart: false,
				beginsPattern: { regexp: oldBegins },
				endsPattern: { regexp: oldEnds }
1484
			};
E
Erich Gamma 已提交
1485 1486
			return;
		}
1487 1488
		let backgroundMonitor = external.background || external.watching;
		if (Types.isUndefinedOrNull(backgroundMonitor)) {
E
Erich Gamma 已提交
1489 1490
			return;
		}
1491 1492
		let begins: WatchingPattern | null = this.createWatchingPattern(backgroundMonitor.beginsPattern);
		let ends: WatchingPattern | null = this.createWatchingPattern(backgroundMonitor.endsPattern);
E
Erich Gamma 已提交
1493 1494
		if (begins && ends) {
			internal.watching = {
1495
				activeOnStart: Types.isBoolean(backgroundMonitor.activeOnStart) ? backgroundMonitor.activeOnStart : false,
E
Erich Gamma 已提交
1496 1497
				beginsPattern: begins,
				endsPattern: ends
1498
			};
E
Erich Gamma 已提交
1499 1500 1501
			return;
		}
		if (begins || ends) {
1502
			this.error(localize('ProblemMatcherParser.problemPattern.watchingMatcher', 'A problem matcher must define both a begin pattern and an end pattern for watching.'));
E
Erich Gamma 已提交
1503 1504 1505
		}
	}

1506
	private createWatchingPattern(external: string | Config.WatchingPattern | undefined): WatchingPattern | null {
E
Erich Gamma 已提交
1507 1508 1509
		if (Types.isUndefinedOrNull(external)) {
			return null;
		}
1510 1511
		let regexp: RegExp | null;
		let file: number | undefined;
E
Erich Gamma 已提交
1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522
		if (Types.isString(external)) {
			regexp = this.createRegularExpression(external);
		} else {
			regexp = this.createRegularExpression(external.regexp);
			if (Types.isNumber(external.file)) {
				file = external.file;
			}
		}
		if (!regexp) {
			return null;
		}
1523
		return file ? { regexp, file } : { regexp, file: 1 };
E
Erich Gamma 已提交
1524 1525
	}

1526
	private createRegularExpression(value: string | undefined): RegExp | null {
1527
		let result: RegExp | null = null;
E
Erich Gamma 已提交
1528 1529 1530 1531 1532 1533
		if (!value) {
			return result;
		}
		try {
			result = new RegExp(value);
		} catch (err) {
1534
			this.error(localize('ProblemMatcherParser.invalidRegexp', 'Error: The string {0} is not a valid regular expression.\n', value));
E
Erich Gamma 已提交
1535 1536 1537 1538 1539
		}
		return result;
	}
}

1540 1541 1542 1543 1544 1545 1546 1547
export namespace Schemas {

	export const WatchingPattern: IJSONSchema = {
		type: 'object',
		additionalProperties: false,
		properties: {
			regexp: {
				type: 'string',
1548
				description: localize('WatchingPatternSchema.regexp', 'The regular expression to detect the begin or end of a background task.')
1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564
			},
			file: {
				type: 'integer',
				description: localize('WatchingPatternSchema.file', 'The match group index of the filename. Can be omitted.')
			},
		}
	};


	export const PatternType: IJSONSchema = {
		anyOf: [
			{
				type: 'string',
				description: localize('PatternTypeSchema.name', 'The name of a contributed or predefined pattern')
			},
			Schemas.ProblemPattern,
1565
			Schemas.MultiLineProblemPattern
1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581
		],
		description: localize('PatternTypeSchema.description', 'A problem pattern or the name of a contributed or predefined problem pattern. Can be omitted if base is specified.')
	};

	export const ProblemMatcher: IJSONSchema = {
		type: 'object',
		additionalProperties: false,
		properties: {
			base: {
				type: 'string',
				description: localize('ProblemMatcherSchema.base', 'The name of a base problem matcher to use.')
			},
			owner: {
				type: 'string',
				description: localize('ProblemMatcherSchema.owner', 'The owner of the problem inside Code. Can be omitted if base is specified. Defaults to \'external\' if omitted and base is not specified.')
			},
1582 1583 1584 1585
			source: {
				type: 'string',
				description: localize('ProblemMatcherSchema.source', 'A human-readable string describing the source of this diagnostic, e.g. \'typescript\' or \'super lint\'.')
			},
1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600
			severity: {
				type: 'string',
				enum: ['error', 'warning', 'info'],
				description: localize('ProblemMatcherSchema.severity', 'The default severity for captures problems. Is used if the pattern doesn\'t define a match group for severity.')
			},
			applyTo: {
				type: 'string',
				enum: ['allDocuments', 'openDocuments', 'closedDocuments'],
				description: localize('ProblemMatcherSchema.applyTo', 'Controls if a problem reported on a text document is applied only to open, closed or all documents.')
			},
			pattern: PatternType,
			fileLocation: {
				oneOf: [
					{
						type: 'string',
1601
						enum: ['absolute', 'relative', 'autoDetect']
1602 1603 1604 1605 1606 1607 1608 1609 1610 1611
					},
					{
						type: 'array',
						items: {
							type: 'string'
						}
					}
				],
				description: localize('ProblemMatcherSchema.fileLocation', 'Defines how file names reported in a problem pattern should be interpreted.')
			},
1612 1613 1614 1615 1616 1617 1618
			background: {
				type: 'object',
				additionalProperties: false,
				description: localize('ProblemMatcherSchema.background', 'Patterns to track the begin and end of a matcher active on a background task.'),
				properties: {
					activeOnStart: {
						type: 'boolean',
1619
						description: localize('ProblemMatcherSchema.background.activeOnStart', 'If set to true the background monitor is in active mode when the task starts. This is equals of issuing a line that matches the beginsPattern')
1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640
					},
					beginsPattern: {
						oneOf: [
							{
								type: 'string'
							},
							Schemas.WatchingPattern
						],
						description: localize('ProblemMatcherSchema.background.beginsPattern', 'If matched in the output the start of a background task is signaled.')
					},
					endsPattern: {
						oneOf: [
							{
								type: 'string'
							},
							Schemas.WatchingPattern
						],
						description: localize('ProblemMatcherSchema.background.endsPattern', 'If matched in the output the end of a background task is signaled.')
					}
				}
			},
1641 1642 1643
			watching: {
				type: 'object',
				additionalProperties: false,
1644 1645
				deprecationMessage: localize('ProblemMatcherSchema.watching.deprecated', 'The watching property is deprecated. Use background instead.'),
				description: localize('ProblemMatcherSchema.watching', 'Patterns to track the begin and end of a watching matcher.'),
1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668
				properties: {
					activeOnStart: {
						type: 'boolean',
						description: localize('ProblemMatcherSchema.watching.activeOnStart', 'If set to true the watcher is in active mode when the task starts. This is equals of issuing a line that matches the beginPattern')
					},
					beginsPattern: {
						oneOf: [
							{
								type: 'string'
							},
							Schemas.WatchingPattern
						],
						description: localize('ProblemMatcherSchema.watching.beginsPattern', 'If matched in the output the start of a watching task is signaled.')
					},
					endsPattern: {
						oneOf: [
							{
								type: 'string'
							},
							Schemas.WatchingPattern
						],
						description: localize('ProblemMatcherSchema.watching.endsPattern', 'If matched in the output the end of a watching task is signaled.')
					}
1669
				}
1670 1671 1672 1673
			}
		}
	};

J
Johannes Rieken 已提交
1674
	export const LegacyProblemMatcher: IJSONSchema = Objects.deepClone(ProblemMatcher);
1675
	LegacyProblemMatcher.properties = Objects.deepClone(LegacyProblemMatcher.properties) || {};
1676 1677
	LegacyProblemMatcher.properties['watchedTaskBeginsRegExp'] = {
		type: 'string',
1678 1679
		deprecationMessage: localize('LegacyProblemMatcherSchema.watchedBegin.deprecated', 'This property is deprecated. Use the watching property instead.'),
		description: localize('LegacyProblemMatcherSchema.watchedBegin', 'A regular expression signaling that a watched tasks begins executing triggered through file watching.')
1680 1681 1682
	};
	LegacyProblemMatcher.properties['watchedTaskEndsRegExp'] = {
		type: 'string',
1683 1684
		deprecationMessage: localize('LegacyProblemMatcherSchema.watchedEnd.deprecated', 'This property is deprecated. Use the watching property instead.'),
		description: localize('LegacyProblemMatcherSchema.watchedEnd', 'A regular expression signaling that a watched tasks ends executing.')
1685 1686
	};

J
Johannes Rieken 已提交
1687
	export const NamedProblemMatcher: IJSONSchema = Objects.deepClone(ProblemMatcher);
1688
	NamedProblemMatcher.properties = Objects.deepClone(NamedProblemMatcher.properties) || {};
1689
	NamedProblemMatcher.properties.name = {
1690
		type: 'string',
1691 1692 1693 1694 1695
		description: localize('NamedProblemMatcherSchema.name', 'The name of the problem matcher used to refer to it.')
	};
	NamedProblemMatcher.properties.label = {
		type: 'string',
		description: localize('NamedProblemMatcherSchema.label', 'A human readable label of the problem matcher.')
1696 1697 1698
	};
}

1699 1700 1701 1702 1703 1704 1705
const problemMatchersExtPoint = ExtensionsRegistry.registerExtensionPoint<Config.NamedProblemMatcher[]>({
	extensionPoint: 'problemMatchers',
	deps: [problemPatternExtPoint],
	jsonSchema: {
		description: localize('ProblemMatcherExtPoint', 'Contributes problem matchers'),
		type: 'array',
		items: Schemas.NamedProblemMatcher
1706
	}
1707 1708 1709
});

export interface IProblemMatcherRegistry {
D
Dirk Baeumer 已提交
1710
	onReady(): Promise<void>;
1711
	get(name: string): NamedProblemMatcher;
1712
	keys(): string[];
M
Matt Bierner 已提交
1713
	readonly onMatcherChanged: Event<void>;
1714 1715 1716
}

class ProblemMatcherRegistryImpl implements IProblemMatcherRegistry {
E
Erich Gamma 已提交
1717

1718
	private matchers: IStringDictionary<NamedProblemMatcher>;
D
Dirk Baeumer 已提交
1719
	private readyPromise: Promise<void>;
M
Matt Bierner 已提交
1720
	private readonly _onMatchersChanged: Emitter<void> = new Emitter<void>();
1721
	public readonly onMatcherChanged: Event<void> = this._onMatchersChanged.event;
1722

E
Erich Gamma 已提交
1723 1724 1725

	constructor() {
		this.matchers = Object.create(null);
1726
		this.fillDefaults();
D
Dirk Baeumer 已提交
1727
		this.readyPromise = new Promise<void>((resolve, reject) => {
1728
			problemMatchersExtPoint.setHandler((extensions, delta) => {
1729
				try {
1730 1731 1732 1733 1734 1735 1736 1737 1738
					delta.removed.forEach(extension => {
						let problemMatchers = extension.value;
						for (let matcher of problemMatchers) {
							if (this.matchers[matcher.name]) {
								delete this.matchers[matcher.name];
							}
						}
					});
					delta.added.forEach(extension => {
1739 1740 1741 1742 1743
						let problemMatchers = extension.value;
						let parser = new ProblemMatcherParser(new ExtensionRegistryReporter(extension.collector));
						for (let matcher of problemMatchers) {
							let result = parser.parse(matcher);
							if (result && isNamedProblemMatcher(result)) {
1744
								this.add(result);
1745 1746 1747
							}
						}
					});
1748 1749 1750
					if ((delta.removed.length > 0) || (delta.added.length > 0)) {
						this._onMatchersChanged.fire();
					}
1751
				} catch (error) {
E
Erich Gamma 已提交
1752
				}
1753 1754 1755 1756 1757
				let matcher = this.get('tsc-watch');
				if (matcher) {
					(<any>matcher).tscWatch = true;
				}
				resolve(undefined);
E
Erich Gamma 已提交
1758
			});
1759
		});
E
Erich Gamma 已提交
1760 1761
	}

D
Dirk Baeumer 已提交
1762
	public onReady(): Promise<void> {
D
Dirk Baeumer 已提交
1763
		ProblemPatternRegistry.onReady();
1764 1765
		return this.readyPromise;
	}
E
Erich Gamma 已提交
1766

1767 1768
	public add(matcher: NamedProblemMatcher): void {
		this.matchers[matcher.name] = matcher;
E
Erich Gamma 已提交
1769 1770
	}

1771
	public get(name: string): NamedProblemMatcher {
E
Erich Gamma 已提交
1772 1773 1774
		return this.matchers[name];
	}

1775 1776 1777 1778
	public keys(): string[] {
		return Object.keys(this.matchers);
	}

1779
	private fillDefaults(): void {
1780 1781 1782
		this.add({
			name: 'msCompile',
			label: localize('msCompile', 'Microsoft compiler problems'),
1783 1784 1785 1786 1787
			owner: 'msCompile',
			applyTo: ApplyToKind.allDocuments,
			fileLocation: FileLocationKind.Absolute,
			pattern: ProblemPatternRegistry.get('msCompile')
		});
E
Erich Gamma 已提交
1788

1789 1790 1791
		this.add({
			name: 'lessCompile',
			label: localize('lessCompile', 'Less problems'),
1792
			deprecated: true,
1793
			owner: 'lessCompile',
1794
			source: 'less',
1795 1796 1797 1798 1799
			applyTo: ApplyToKind.allDocuments,
			fileLocation: FileLocationKind.Absolute,
			pattern: ProblemPatternRegistry.get('lessCompile'),
			severity: Severity.Error
		});
E
Erich Gamma 已提交
1800

1801 1802 1803
		this.add({
			name: 'gulp-tsc',
			label: localize('gulp-tsc', 'Gulp TSC Problems'),
1804
			owner: 'typescript',
1805
			source: 'ts',
1806 1807
			applyTo: ApplyToKind.closedDocuments,
			fileLocation: FileLocationKind.Relative,
1808
			filePrefix: '${workspaceFolder}',
1809 1810
			pattern: ProblemPatternRegistry.get('gulp-tsc')
		});
E
Erich Gamma 已提交
1811

1812 1813 1814
		this.add({
			name: 'jshint',
			label: localize('jshint', 'JSHint problems'),
1815
			owner: 'jshint',
1816
			source: 'jshint',
1817 1818 1819 1820
			applyTo: ApplyToKind.allDocuments,
			fileLocation: FileLocationKind.Absolute,
			pattern: ProblemPatternRegistry.get('jshint')
		});
E
Erich Gamma 已提交
1821

1822 1823 1824
		this.add({
			name: 'jshint-stylish',
			label: localize('jshint-stylish', 'JSHint stylish problems'),
1825
			owner: 'jshint',
1826
			source: 'jshint',
1827 1828 1829 1830
			applyTo: ApplyToKind.allDocuments,
			fileLocation: FileLocationKind.Absolute,
			pattern: ProblemPatternRegistry.get('jshint-stylish')
		});
E
Erich Gamma 已提交
1831

1832 1833 1834
		this.add({
			name: 'eslint-compact',
			label: localize('eslint-compact', 'ESLint compact problems'),
1835
			owner: 'eslint',
1836
			source: 'eslint',
1837
			applyTo: ApplyToKind.allDocuments,
1838
			fileLocation: FileLocationKind.Absolute,
1839
			filePrefix: '${workspaceFolder}',
1840 1841
			pattern: ProblemPatternRegistry.get('eslint-compact')
		});
E
Erich Gamma 已提交
1842

1843 1844 1845
		this.add({
			name: 'eslint-stylish',
			label: localize('eslint-stylish', 'ESLint stylish problems'),
1846
			owner: 'eslint',
1847
			source: 'eslint',
1848 1849 1850 1851
			applyTo: ApplyToKind.allDocuments,
			fileLocation: FileLocationKind.Absolute,
			pattern: ProblemPatternRegistry.get('eslint-stylish')
		});
E
Erich Gamma 已提交
1852

1853 1854 1855
		this.add({
			name: 'go',
			label: localize('go', 'Go problems'),
1856
			owner: 'go',
1857
			source: 'go',
1858 1859
			applyTo: ApplyToKind.allDocuments,
			fileLocation: FileLocationKind.Relative,
1860
			filePrefix: '${workspaceFolder}',
1861 1862 1863 1864
			pattern: ProblemPatternRegistry.get('go')
		});
	}
}
D
Dan Mace 已提交
1865

1866
export const ProblemMatcherRegistry: IProblemMatcherRegistry = new ProblemMatcherRegistryImpl();