forked from palantir/tslint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
parse.ts
173 lines (150 loc) · 6.68 KB
/
parse.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/*
* Copyright 2016 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
CodeLine,
EndErrorLine,
ErrorLine,
Line,
MessageSubstitutionLine,
MultilineErrorLine,
parseLine,
printLine,
} from "./lines";
import {errorComparator, LintError, lintSyntaxError} from "./lintError";
export function getTypescriptVersionRequirement(text: string): string | undefined {
const lines = text.split(/\r?\n/);
const firstLine = parseLine(lines[0]);
if (firstLine instanceof MessageSubstitutionLine && firstLine.key === "typescript") {
return firstLine.message;
}
return undefined;
}
/**
* Takes the full text of a .lint file and returns the contents of the file
* with all error markup removed
*/
export function removeErrorMarkup(text: string): string {
const textWithMarkup = text.split("\n");
const lines = textWithMarkup.map(parseLine);
const codeText = lines.filter((line) => (line instanceof CodeLine)).map((line) => (line as CodeLine).contents);
return codeText.join("\n");
}
/* tslint:disable:object-literal-sort-keys */
/**
* Takes the full text of a .lint file and returns an array of LintErrors
* corresponding to the error markup in the file.
*/
export function parseErrorsFromMarkup(text: string): LintError[] {
const textWithMarkup = text.split("\n");
const lines = textWithMarkup.map(parseLine);
if (lines.length > 0 && !(lines[0] instanceof CodeLine)) {
throw lintSyntaxError(`text cannot start with an error mark line.`);
}
const messageSubstitutionLines = lines.filter((l) => l instanceof MessageSubstitutionLine) as MessageSubstitutionLine[];
const messageSubstitutions = new Map(messageSubstitutionLines.map(({ key, message }) =>
[key, message] as [string, string]));
// errorLineForCodeLine[5] contains all the ErrorLine objects associated with the 5th line of code, for example
const errorLinesForCodeLines = createCodeLineNoToErrorsMap(lines);
const lintErrors: LintError[] = [];
function addError(errorLine: EndErrorLine, errorStartPos: { line: number, col: number }, lineNo: number) {
lintErrors.push({
startPos: errorStartPos,
endPos: { line: lineNo, col: errorLine.endCol },
message: messageSubstitutions.get(errorLine.message) || errorLine.message,
});
}
// for each line of code...
errorLinesForCodeLines.forEach((errorLinesForLineOfCode, lineNo) => {
// for each error marking on that line...
while (errorLinesForLineOfCode.length > 0) {
const errorLine = errorLinesForLineOfCode.shift();
const errorStartPos = { line: lineNo, col: errorLine!.startCol };
// if the error starts and ends on this line, add it now to list of errors
if (errorLine instanceof EndErrorLine) {
addError(errorLine, errorStartPos, lineNo);
// if the error is the start of a multiline error
} else if (errorLine instanceof MultilineErrorLine) {
// iterate through the MultilineErrorLines until we get to an EndErrorLine
for (let nextLineNo = lineNo + 1; ; ++nextLineNo) {
if (!isValidErrorMarkupContinuation(errorLinesForCodeLines, nextLineNo)) {
throw lintSyntaxError(
`Error mark starting at ${errorStartPos.line}:${errorStartPos.col} does not end correctly.`,
);
} else {
const nextErrorLine = errorLinesForCodeLines[nextLineNo].shift();
// if end of multiline error, add it it list of errors
if (nextErrorLine instanceof EndErrorLine) {
addError(nextErrorLine, errorStartPos, nextLineNo);
break;
}
}
}
}
}
});
lintErrors.sort(errorComparator);
return lintErrors;
}
export function createMarkupFromErrors(code: string, lintErrors: LintError[]) {
lintErrors.sort(errorComparator);
const codeText = code.split("\n");
const errorLinesForCodeText: ErrorLine[][] = codeText.map(() => []);
for (const error of lintErrors) {
const {startPos, endPos, message} = error;
if (startPos.line === endPos.line) {
// single line error
errorLinesForCodeText[startPos.line].push(new EndErrorLine(
startPos.col,
endPos.col,
message,
));
} else {
// multiline error
errorLinesForCodeText[startPos.line].push(new MultilineErrorLine(startPos.col));
for (let lineNo = startPos.line + 1; lineNo < endPos.line; ++lineNo) {
errorLinesForCodeText[lineNo].push(new MultilineErrorLine(0));
}
errorLinesForCodeText[endPos.line].push(new EndErrorLine(0, endPos.col, message));
}
}
const finalText = combineCodeTextAndErrorLines(codeText, errorLinesForCodeText);
return finalText.join("\n");
}
/* tslint:enable:object-literal-sort-keys */
function combineCodeTextAndErrorLines(codeText: string[], errorLinesForCodeText: ErrorLine[][]) {
return codeText.reduce<string[]>((resultText, code, i) => {
resultText.push(code);
const errorPrintLines = errorLinesForCodeText[i].map((line) => printLine(line, code)).filter((line) => line !== null) as string[];
resultText.push(...errorPrintLines);
return resultText;
}, []);
}
function createCodeLineNoToErrorsMap(lines: Line[]) {
const errorLinesForCodeLine: ErrorLine[][] = [];
for (const line of lines) {
if (line instanceof CodeLine) {
errorLinesForCodeLine.push([]);
} else if (line instanceof ErrorLine) {
errorLinesForCodeLine[errorLinesForCodeLine.length - 1].push(line);
}
}
return errorLinesForCodeLine;
}
function isValidErrorMarkupContinuation(errorLinesForCodeLines: ErrorLine[][], lineNo: number) {
return lineNo < errorLinesForCodeLines.length
&& errorLinesForCodeLines[lineNo].length !== 0
&& errorLinesForCodeLines[lineNo][0].startCol === 0;
}