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, normalize } 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;
	}
223
	fullPath = normalize(fullPath);
R
Rob Lourens 已提交
224
	if (matcher.uriProvider !== undefined) {
225
		return matcher.uriProvider(fullPath);
226 227 228
	} else {
		return URI.file(fullPath);
	}
E
Erich Gamma 已提交
229 230 231 232
}

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

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

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

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

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

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

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

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

267
	protected fillProblemData(data: ProblemData | undefined, pattern: ProblemPattern, matches: RegExpExecArray): data is ProblemData {
268 269 270 271 272 273 274 275 276 277 278 279 280 281
		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 已提交
282 283
	}

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

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

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

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

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

362
	private parseLocationInfo(value: string): Location | null {
E
Erich Gamma 已提交
363 364 365 366 367 368 369 370 371 372 373 374 375
		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);
		}
	}

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

J
Johannes Rieken 已提交
386
	private getSeverity(data: ProblemData): MarkerSeverity {
387
		let result: Severity | null = null;
E
Erich Gamma 已提交
388 389
		if (data.severity) {
			let value = data.severity;
390
			if (value) {
E
Erich Gamma 已提交
391
				result = Severity.fromValue(value);
392 393 394 395 396 397 398 399 400 401 402 403 404
				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 已提交
405 406 407 408 409
			}
		}
		if (result === null || result === Severity.Ignore) {
			result = this.matcher.severity || Severity.Error;
		}
J
Johannes Rieken 已提交
410
		return MarkerSeverity.fromSeverity(result);
E
Erich Gamma 已提交
411 412 413 414 415 416 417
	}
}

class SingleLineMatcher extends AbstractLineMatcher {

	private pattern: ProblemPattern;

418 419
	constructor(matcher: ProblemMatcher, fileService?: IFileService) {
		super(matcher, fileService);
E
Erich Gamma 已提交
420 421 422 423 424 425 426 427 428 429
		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 已提交
430
		if (this.pattern.kind !== undefined) {
431 432
			data.kind = this.pattern.kind;
		}
E
Erich Gamma 已提交
433 434 435 436 437 438 439 440 441 442 443
		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 };
	}

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

class MultiLineMatcher extends AbstractLineMatcher {

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

454 455
	constructor(matcher: ProblemMatcher, fileService?: IFileService) {
		super(matcher, fileService);
E
Erich Gamma 已提交
456 457 458 459 460 461 462 463 464 465
		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);
466
		let data = this.data!;
467
		data.kind = this.patterns[0].kind;
E
Erich Gamma 已提交
468 469 470 471
		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 已提交
472
				return { match: null, continue: false };
E
Erich Gamma 已提交
473 474 475
			} else {
				// Only the last pattern can loop
				if (pattern.loop && i === this.patterns.length - 1) {
J
Johannes Rieken 已提交
476
					data = Objects.deepClone(data);
E
Erich Gamma 已提交
477 478 479 480
				}
				this.fillProblemData(data, pattern, matches);
			}
		}
481
		let loop = !!this.patterns[this.patterns.length - 1].loop;
E
Erich Gamma 已提交
482
		if (!loop) {
483
			this.data = undefined;
E
Erich Gamma 已提交
484
		}
485 486
		const markerMatch = data ? this.getMarkerMatch(data) : null;
		return { match: markerMatch ? markerMatch : null, continue: loop };
E
Erich Gamma 已提交
487 488
	}

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

export namespace Config {

	export interface ProblemPattern {

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

516 517 518 519 520 521 522 523
		/**
		* 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 已提交
524 525 526 527 528 529 530
		/**
		* The match group index of the filename.
		* If omitted 1 is used.
		*/
		file?: number;

		/**
531
		* The match group index of the problem's location. Valid location
E
Erich Gamma 已提交
532
		* patterns are: (line), (line,column) and (startLine,startColumn,endLine,endColumn).
533
		* If omitted the line and column properties are used.
E
Erich Gamma 已提交
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 573
		*/
		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;

		/**
574
		* The match group index of the problem's code.
E
Erich Gamma 已提交
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
		*
		* 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;
	}

594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
	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);
		}
	}

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

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

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

628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
	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);
		}
	}

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

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

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

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

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

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

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

684 685 686 687
	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);
688 689 690
		}
	}

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

E
Erich Gamma 已提交
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
	/**
	* 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.
	*/
712
	export interface BackgroundMonitor {
E
Erich Gamma 已提交
713 714 715 716

		/**
		* If set to true the watcher is in active mode when the task
		* starts. This is equals of issuing a line that matches the
717
		* beginsPattern.
E
Erich Gamma 已提交
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738
		*/
		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 {

		/**
739 740 741 742 743
		 * 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 已提交
744 745 746
		base?: string;

		/**
747 748 749 750 751
		 * 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 已提交
752 753
		owner?: string;

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

E
Erich Gamma 已提交
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
		/**
		* 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[];

		/**
797
		* The name of a predefined problem pattern, the inline definition
E
Erich Gamma 已提交
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
		* 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;

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

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

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

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

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

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

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

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

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

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

891
	private createMultiLineProblemPattern(values: Config.MultiLineCheckedProblemPattern): MultiLineProblemPattern | null {
892 893 894
		let result: MultiLineProblemPattern = [];
		for (let i = 0; i < values.length; i++) {
			let pattern = this.doCreateSingleProblemPattern(values[i], false);
895 896 897
			if (pattern === undefined) {
				return null;
			}
898 899 900 901 902 903 904 905
			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);
		}
906 907 908
		if (result[0].kind === undefined) {
			result[0].kind = ProblemLocationKind.Location;
		}
909 910 911
		return this.validateProblemPattern(result) ? result : null;
	}

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

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

	private validateProblemPattern(values: ProblemPattern[]): boolean {
961
		let file: boolean = false, message: boolean = false, location: boolean = false, line: boolean = false;
962 963 964 965 966 967
		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'));
			}
968 969 970 971 972
			file = file || !Types.isUndefined(pattern.file);
			message = message || !Types.isUndefined(pattern.message);
			location = location || !Types.isUndefined(pattern.location);
			line = line || !Types.isUndefined(pattern.line);
		});
973 974 975 976 977 978
		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.'));
979 980 981 982 983
			return false;
		}
		return true;
	}

984 985
	private createRegularExpression(value: string): RegExp | undefined {
		let result: RegExp | undefined;
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 1039
		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.')
			},
1040 1041 1042 1043
			kind: {
				type: 'string',
				description: localize('ProblemPatternSchema.kind', 'whether the pattern matches a location (file and line) or only a file.')
			},
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 1086
			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 已提交
1087
	export const NamedProblemPattern: IJSONSchema = Objects.deepClone(ProblemPattern);
1088
	NamedProblemPattern.properties = Objects.deepClone(NamedProblemPattern.properties) || {};
1089 1090 1091 1092 1093
	NamedProblemPattern.properties['name'] = {
		type: 'string',
		description: localize('NamedProblemPatternSchema.name', 'The name of the problem pattern.')
	};

S
Sylvain Joyeux 已提交
1094
	export const MultiLineProblemPattern: IJSONSchema = {
1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115
		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
			}
		}
	};
}

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

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

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

class ProblemPatternRegistryImpl implements IProblemPatternRegistry {

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

	constructor() {
		this.patterns = Object.create(null);
		this.fillDefaults();
D
Dirk Baeumer 已提交
1144
		this.readyPromise = new Promise<void>((resolve, reject) => {
1145
			problemPatternExtPoint.setHandler((extensions, delta) => {
1146 1147
				// We get all statically know extension during startup in one batch
				try {
1148 1149 1150 1151 1152 1153 1154 1155 1156
					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 => {
1157 1158 1159
						let problemPatterns = extension.value as Config.NamedProblemPatterns;
						let parser = new ProblemPatternParser(new ExtensionRegistryReporter(extension.collector));
						for (let pattern of problemPatterns) {
1160
							if (Config.NamedMultiLineCheckedProblemPattern.is(pattern)) {
1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185
								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);
			});
1186
		});
1187 1188
	}

D
Dirk Baeumer 已提交
1189
	public onReady(): Promise<void> {
1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202
		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', {
1203
			regexp: /^(?:\s+\d+\>)?([^\s].*)\((\d+|\d+,\d+|\d+,\d+,\d+,\d+)\)\s*:\s+(error|warning|info)\s+(\w{1,2}\d+)\s*:\s*(.*)$/,
1204
			kind: ProblemLocationKind.Location,
1205 1206 1207 1208 1209 1210 1211 1212
			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+(.*)$/,
1213
			kind: ProblemLocationKind.Location,
1214 1215 1216 1217 1218 1219 1220
			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*(.*)$/,
1221
			kind: ProblemLocationKind.Location,
1222 1223 1224 1225 1226 1227 1228 1229
			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*(.*)$/,
1230
			kind: ProblemLocationKind.Location,
1231 1232 1233 1234 1235 1236 1237 1238
			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*(.*)$/,
1239
			kind: ProblemLocationKind.Location,
1240 1241 1242 1243 1244 1245 1246 1247
			file: 1,
			location: 2,
			severity: 3,
			code: 4,
			message: 5
		});
		this.add('lessCompile', {
			regexp: /^\s*(.*) in file (.*) line no. (\d+)$/,
1248
			kind: ProblemLocationKind.Location,
1249 1250 1251 1252 1253 1254
			message: 1,
			file: 2,
			line: 3
		});
		this.add('jshint', {
			regexp: /^(.*):\s+line\s+(\d+),\s+col\s+(\d+),\s(.+?)(?:\s+\((\w)(\d+)\))?$/,
1255
			kind: ProblemLocationKind.Location,
1256 1257
			file: 1,
			line: 2,
1258
			character: 3,
1259 1260 1261 1262 1263 1264 1265
			message: 4,
			severity: 5,
			code: 6
		});
		this.add('jshint-stylish', [
			{
				regexp: /^(.+)$/,
1266
				kind: ProblemLocationKind.Location,
1267 1268 1269 1270 1271
				file: 1
			},
			{
				regexp: /^\s+line\s+(\d+)\s+col\s+(\d+)\s+(.+?)(?:\s+\((\w)(\d+)\))?$/,
				line: 1,
1272
				character: 2,
1273 1274 1275 1276 1277 1278 1279 1280 1281
				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,
1282
			kind: ProblemLocationKind.Location,
1283
			line: 2,
1284
			character: 3,
1285 1286 1287 1288 1289 1290 1291
			severity: 4,
			message: 5,
			code: 6
		});
		this.add('eslint-stylish', [
			{
				regexp: /^([^\s].*)$/,
1292
				kind: ProblemLocationKind.Location,
1293 1294 1295
				file: 1
			},
			{
1296
				regexp: /^\s+(\d+):(\d+)\s+(error|warning|info)\s+(.+?)(?:\s\s+(.*))?$/,
1297
				line: 1,
1298
				character: 2,
1299 1300 1301 1302 1303 1304 1305 1306
				severity: 3,
				message: 4,
				code: 5,
				loop: true
			}
		]);
		this.add('go', {
			regexp: /^([^:]*: )?((.:)?[^:]*):(\d+)(:(\d+))?: (.*)$/,
1307
			kind: ProblemLocationKind.Location,
1308 1309
			file: 2,
			line: 4,
1310
			character: 6,
1311 1312 1313 1314 1315 1316
			message: 7
		});
	}
}

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

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

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

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

		return result;
	}

1334
	private checkProblemMatcherValid(externalProblemMatcher: Config.ProblemMatcher, problemMatcher: ProblemMatcher | null): problemMatcher is ProblemMatcher {
1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348
		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 已提交
1349 1350 1351 1352 1353
			return false;
		}
		return true;
	}

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

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

1366
		let kind: FileLocationKind | undefined;
E
Erich Gamma 已提交
1367 1368
		if (Types.isUndefined(description.fileLocation)) {
			fileLocation = FileLocationKind.Relative;
1369
			filePrefix = '${workspaceFolder}';
E
Erich Gamma 已提交
1370 1371 1372 1373
		} else if (Types.isString(description.fileLocation)) {
			kind = FileLocationKind.fromString(<string>description.fileLocation);
			if (kind) {
				fileLocation = kind;
1374
				if ((kind === FileLocationKind.Relative) || (kind === FileLocationKind.AutoDetect)) {
1375
					filePrefix = '${workspaceFolder}';
E
Erich Gamma 已提交
1376 1377 1378 1379 1380 1381 1382 1383
				}
			}
		} 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;
1384
				} else if (values.length === 2 && (kind === FileLocationKind.Relative || kind === FileLocationKind.AutoDetect) && values[1]) {
E
Erich Gamma 已提交
1385 1386 1387 1388 1389 1390 1391 1392 1393 1394
					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) {
1395
			this.info(localize('ProblemMatcherParser.unknownSeverity', 'Info: unknown severity {0}. Valid values are error, warning and info.\n', description.severity));
E
Erich Gamma 已提交
1396 1397 1398 1399 1400 1401
			severity = Severity.Error;
		}

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

1450
	private createProblemPattern(value: string | Config.ProblemPattern | Config.MultiLineProblemPattern): ProblemPattern | ProblemPattern[] | null {
E
Erich Gamma 已提交
1451
		if (Types.isString(value)) {
D
Dirk Baeumer 已提交
1452
			let variableName: string = <string>value;
E
Erich Gamma 已提交
1453
			if (variableName.length > 1 && variableName[0] === '$') {
1454 1455
				let result = ProblemPatternRegistry.get(variableName.substring(1));
				if (!result) {
D
Dirk Baeumer 已提交
1456
					this.error(localize('ProblemMatcherParser.noDefinedPatter', 'Error: the pattern with the identifier {0} doesn\'t exist.', variableName));
1457 1458 1459 1460 1461 1462
				}
				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 已提交
1463
					this.error(localize('ProblemMatcherParser.noValidIdentifier', 'Error: the pattern property {0} is not a valid pattern variable name.', variableName));
1464
				}
E
Erich Gamma 已提交
1465
			}
1466 1467
		} else if (value) {
			let problemPatternParser = new ProblemPatternParser(this.problemReporter);
1468 1469 1470 1471 1472
			if (Array.isArray(value)) {
				return problemPatternParser.parse(value);
			} else {
				return problemPatternParser.parse(value);
			}
E
Erich Gamma 已提交
1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484
		}
		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 }
1485
			};
E
Erich Gamma 已提交
1486 1487
			return;
		}
1488 1489
		let backgroundMonitor = external.background || external.watching;
		if (Types.isUndefinedOrNull(backgroundMonitor)) {
E
Erich Gamma 已提交
1490 1491
			return;
		}
1492 1493
		let begins: WatchingPattern | null = this.createWatchingPattern(backgroundMonitor.beginsPattern);
		let ends: WatchingPattern | null = this.createWatchingPattern(backgroundMonitor.endsPattern);
E
Erich Gamma 已提交
1494 1495
		if (begins && ends) {
			internal.watching = {
1496
				activeOnStart: Types.isBoolean(backgroundMonitor.activeOnStart) ? backgroundMonitor.activeOnStart : false,
E
Erich Gamma 已提交
1497 1498
				beginsPattern: begins,
				endsPattern: ends
1499
			};
E
Erich Gamma 已提交
1500 1501 1502
			return;
		}
		if (begins || ends) {
1503
			this.error(localize('ProblemMatcherParser.problemPattern.watchingMatcher', 'A problem matcher must define both a begin pattern and an end pattern for watching.'));
E
Erich Gamma 已提交
1504 1505 1506
		}
	}

1507
	private createWatchingPattern(external: string | Config.WatchingPattern | undefined): WatchingPattern | null {
E
Erich Gamma 已提交
1508 1509 1510
		if (Types.isUndefinedOrNull(external)) {
			return null;
		}
1511 1512
		let regexp: RegExp | null;
		let file: number | undefined;
E
Erich Gamma 已提交
1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523
		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;
		}
1524
		return file ? { regexp, file } : { regexp, file: 1 };
E
Erich Gamma 已提交
1525 1526
	}

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

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

	export const WatchingPattern: IJSONSchema = {
		type: 'object',
		additionalProperties: false,
		properties: {
			regexp: {
				type: 'string',
1549
				description: localize('WatchingPatternSchema.regexp', 'The regular expression to detect the begin or end of a background task.')
1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565
			},
			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,
1566
			Schemas.MultiLineProblemPattern
1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582
		],
		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.')
			},
1583 1584 1585 1586
			source: {
				type: 'string',
				description: localize('ProblemMatcherSchema.source', 'A human-readable string describing the source of this diagnostic, e.g. \'typescript\' or \'super lint\'.')
			},
1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601
			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',
1602
						enum: ['absolute', 'relative', 'autoDetect']
1603 1604 1605 1606 1607 1608 1609 1610 1611 1612
					},
					{
						type: 'array',
						items: {
							type: 'string'
						}
					}
				],
				description: localize('ProblemMatcherSchema.fileLocation', 'Defines how file names reported in a problem pattern should be interpreted.')
			},
1613 1614 1615 1616 1617 1618 1619
			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',
1620
						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')
1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641
					},
					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.')
					}
				}
			},
1642 1643 1644
			watching: {
				type: 'object',
				additionalProperties: false,
1645 1646
				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.'),
1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669
				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.')
					}
1670
				}
1671 1672 1673 1674
			}
		}
	};

J
Johannes Rieken 已提交
1675
	export const LegacyProblemMatcher: IJSONSchema = Objects.deepClone(ProblemMatcher);
1676
	LegacyProblemMatcher.properties = Objects.deepClone(LegacyProblemMatcher.properties) || {};
1677 1678
	LegacyProblemMatcher.properties['watchedTaskBeginsRegExp'] = {
		type: 'string',
1679 1680
		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.')
1681 1682 1683
	};
	LegacyProblemMatcher.properties['watchedTaskEndsRegExp'] = {
		type: 'string',
1684 1685
		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.')
1686 1687
	};

J
Johannes Rieken 已提交
1688
	export const NamedProblemMatcher: IJSONSchema = Objects.deepClone(ProblemMatcher);
1689
	NamedProblemMatcher.properties = Objects.deepClone(NamedProblemMatcher.properties) || {};
1690
	NamedProblemMatcher.properties.name = {
1691
		type: 'string',
1692 1693 1694 1695 1696
		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.')
1697 1698 1699
	};
}

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

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

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

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

E
Erich Gamma 已提交
1724 1725 1726

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

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

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

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

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

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

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

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

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

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

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

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

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

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