234 lines
9.0 KiB
JavaScript
234 lines
9.0 KiB
JavaScript
|
|
"use strict";
|
||
|
|
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||
|
|
// See LICENSE in the project root for license information.
|
||
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
|
exports.parseProblemMatchersJson = parseProblemMatchersJson;
|
||
|
|
/**
|
||
|
|
* Parse VS Code problem matcher JSON definitions into {@link IProblemMatcher} objects.
|
||
|
|
*
|
||
|
|
* @public
|
||
|
|
*/
|
||
|
|
function parseProblemMatchersJson(problemMatchers) {
|
||
|
|
const result = [];
|
||
|
|
for (const matcher of problemMatchers) {
|
||
|
|
const problemPatterns = Array.isArray(matcher.pattern)
|
||
|
|
? matcher.pattern
|
||
|
|
: [matcher.pattern];
|
||
|
|
if (problemPatterns.length === 0) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const name = matcher.name;
|
||
|
|
const defaultSeverity = matcher.severity;
|
||
|
|
const compiled = compileProblemPatterns(problemPatterns);
|
||
|
|
if (compiled.length === 1) {
|
||
|
|
result.push(createSingleLineMatcher(name, compiled[0], defaultSeverity));
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
result.push(createMultiLineMatcher(name, compiled, defaultSeverity));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
function toNumber(text) {
|
||
|
|
if (!text) {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
const n = parseInt(text, 10);
|
||
|
|
return isNaN(n) ? undefined : n;
|
||
|
|
}
|
||
|
|
function normalizeSeverity(raw) {
|
||
|
|
if (!raw) {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
const lowered = raw.toLowerCase();
|
||
|
|
// Support full words as well as common abbreviations (e.g. single-letter tokens)
|
||
|
|
if (lowered.indexOf('err') === 0)
|
||
|
|
return 'error';
|
||
|
|
if (lowered.indexOf('warn') === 0)
|
||
|
|
return 'warning';
|
||
|
|
if (lowered.indexOf('info') === 0)
|
||
|
|
return 'info';
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
function compileProblemPatterns(problemPatterns) {
|
||
|
|
return problemPatterns.map((problemPattern) => {
|
||
|
|
let reStr = problemPattern.regexp;
|
||
|
|
if (/\\r?\\n\$/.test(reStr) || /\\n\$/.test(reStr)) {
|
||
|
|
// already newline aware
|
||
|
|
}
|
||
|
|
else if (reStr.length > 0 && reStr.charAt(reStr.length - 1) === '$') {
|
||
|
|
reStr = reStr.slice(0, -1) + '\\r?\\n$';
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
reStr = reStr + '(?:\\r?\\n)';
|
||
|
|
}
|
||
|
|
const re = new RegExp(reStr);
|
||
|
|
return { re, spec: problemPattern };
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function createEmptyCaptures() {
|
||
|
|
return { messageParts: [] };
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Apply one pattern's regex match to the (possibly accumulating) captures.
|
||
|
|
*/
|
||
|
|
function applyPatternCaptures(spec, reMatch, captures, defaultSeverity) {
|
||
|
|
if (spec.file && reMatch[spec.file]) {
|
||
|
|
captures.file = reMatch[spec.file];
|
||
|
|
}
|
||
|
|
if (spec.location && reMatch[spec.location]) {
|
||
|
|
const loc = reMatch[spec.location];
|
||
|
|
const parts = loc.split(/[,.:]/).filter((s) => s.length > 0);
|
||
|
|
if (parts.length === 1) {
|
||
|
|
captures.line = toNumber(parts[0]);
|
||
|
|
}
|
||
|
|
else if (parts.length === 2) {
|
||
|
|
captures.line = toNumber(parts[0]);
|
||
|
|
captures.column = toNumber(parts[1]);
|
||
|
|
}
|
||
|
|
else if (parts.length === 4) {
|
||
|
|
captures.line = toNumber(parts[0]);
|
||
|
|
captures.column = toNumber(parts[1]);
|
||
|
|
captures.endLine = toNumber(parts[2]);
|
||
|
|
captures.endColumn = toNumber(parts[3]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
if (spec.line && reMatch[spec.line]) {
|
||
|
|
captures.line = toNumber(reMatch[spec.line]);
|
||
|
|
}
|
||
|
|
if (spec.column && reMatch[spec.column]) {
|
||
|
|
captures.column = toNumber(reMatch[spec.column]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (spec.endLine && reMatch[spec.endLine]) {
|
||
|
|
captures.endLine = toNumber(reMatch[spec.endLine]);
|
||
|
|
}
|
||
|
|
if (spec.endColumn && reMatch[spec.endColumn]) {
|
||
|
|
captures.endColumn = toNumber(reMatch[spec.endColumn]);
|
||
|
|
}
|
||
|
|
if (spec.severity && reMatch[spec.severity]) {
|
||
|
|
captures.severity = normalizeSeverity(reMatch[spec.severity]) || defaultSeverity;
|
||
|
|
}
|
||
|
|
else if (!captures.severity && defaultSeverity) {
|
||
|
|
captures.severity = defaultSeverity;
|
||
|
|
}
|
||
|
|
if (spec.code && reMatch[spec.code]) {
|
||
|
|
captures.code = reMatch[spec.code];
|
||
|
|
}
|
||
|
|
if (spec.message && reMatch[spec.message]) {
|
||
|
|
captures.messageParts.push(reMatch[spec.message]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function finalizeProblem(matcherName, captures, defaultSeverity) {
|
||
|
|
// For multi-line patterns, use only the last non-empty message part
|
||
|
|
const message = captures.messageParts.length > 0 ? captures.messageParts[captures.messageParts.length - 1] : '';
|
||
|
|
return {
|
||
|
|
matcherName,
|
||
|
|
file: captures.file,
|
||
|
|
line: captures.line,
|
||
|
|
column: captures.column,
|
||
|
|
endLine: captures.endLine,
|
||
|
|
endColumn: captures.endColumn,
|
||
|
|
severity: captures.severity || defaultSeverity,
|
||
|
|
code: captures.code,
|
||
|
|
message: message
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function createSingleLineMatcher(name, compiled, defaultSeverity) {
|
||
|
|
const { re, spec } = compiled;
|
||
|
|
return {
|
||
|
|
name,
|
||
|
|
exec(line) {
|
||
|
|
const match = re.exec(line);
|
||
|
|
if (!match) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const captures = createEmptyCaptures();
|
||
|
|
applyPatternCaptures(spec, match, captures, defaultSeverity);
|
||
|
|
return finalizeProblem(name, captures, defaultSeverity);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function createMultiLineMatcher(name, compiled, defaultSeverity) {
|
||
|
|
// currentIndex points to the next pattern we expect to match. When it equals compiled.length
|
||
|
|
// and the last pattern is a loop, we are in a special "loop state" where additional lines
|
||
|
|
// should be attempted against only the last pattern to emit more problems.
|
||
|
|
let currentIndex = 0;
|
||
|
|
const lastSpec = compiled[compiled.length - 1].spec;
|
||
|
|
const lastIsLoop = !!lastSpec.loop;
|
||
|
|
let captures = createEmptyCaptures();
|
||
|
|
return {
|
||
|
|
name,
|
||
|
|
exec(line) {
|
||
|
|
let effectiveMatch = null;
|
||
|
|
let effectiveSpec;
|
||
|
|
// Determine matching behavior based on current state
|
||
|
|
if (currentIndex === compiled.length && lastIsLoop) {
|
||
|
|
// Loop state: only try to match the last pattern
|
||
|
|
const lastPattern = compiled[compiled.length - 1];
|
||
|
|
effectiveMatch = lastPattern.re.exec(line);
|
||
|
|
if (!effectiveMatch) {
|
||
|
|
// Exit loop state and reset for a potential new sequence
|
||
|
|
currentIndex = 0;
|
||
|
|
captures = createEmptyCaptures();
|
||
|
|
// Attempt to treat this line as a fresh start (pattern 0)
|
||
|
|
const first = compiled[0];
|
||
|
|
const fresh = first.re.exec(line);
|
||
|
|
if (!fresh) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
effectiveMatch = fresh;
|
||
|
|
effectiveSpec = first.spec;
|
||
|
|
currentIndex = compiled.length > 1 ? 1 : compiled.length;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
effectiveSpec = lastPattern.spec;
|
||
|
|
// currentIndex remains compiled.length (loop state) until we decide to emit
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
// Normal multi-line progression state
|
||
|
|
const active = compiled[currentIndex];
|
||
|
|
const reMatch = active.re.exec(line);
|
||
|
|
if (!reMatch) {
|
||
|
|
// Reset and maybe attempt new start
|
||
|
|
currentIndex = 0;
|
||
|
|
captures = createEmptyCaptures();
|
||
|
|
const { re: re0, spec: spec0 } = compiled[0];
|
||
|
|
const restartMatch = re0.exec(line);
|
||
|
|
if (!restartMatch) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
effectiveMatch = restartMatch;
|
||
|
|
effectiveSpec = spec0;
|
||
|
|
currentIndex = compiled.length > 1 ? 1 : compiled.length;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
effectiveMatch = reMatch;
|
||
|
|
effectiveSpec = active.spec;
|
||
|
|
currentIndex++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
applyPatternCaptures(effectiveSpec, effectiveMatch, captures, defaultSeverity);
|
||
|
|
// If we haven't matched all patterns yet (and not in loop state), wait for more lines
|
||
|
|
if (currentIndex < compiled.length) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// We have matched the full sequence (either first completion or a loop iteration)
|
||
|
|
const problem = finalizeProblem(name, captures, defaultSeverity);
|
||
|
|
if (lastIsLoop) {
|
||
|
|
// Stay in loop state; reset fields that accumulate per problem but retain other context (e.g., file if first pattern captured it?)
|
||
|
|
// For safety, if the last pattern provided the file each iteration we keep overwriting anyway.
|
||
|
|
captures.messageParts = [];
|
||
|
|
// Do not clear entire captures to allow preceding pattern data (e.g., summary) to persist if desirable.
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
currentIndex = 0;
|
||
|
|
captures = createEmptyCaptures();
|
||
|
|
}
|
||
|
|
return problem;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=ProblemMatcher.js.map
|