Skip to content

Commit 4240f6f

Browse files
committed
Extract diagnostics formatter used by --pretty
This allows compilers other than tsc.js to display nice in-context diagnostics
1 parent 2300a99 commit 4240f6f

File tree

2 files changed

+96
-86
lines changed

2 files changed

+96
-86
lines changed

src/compiler/program.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,101 @@ namespace ts {
241241
return output;
242242
}
243243

244+
const redForegroundEscapeSequence = "\u001b[91m";
245+
const yellowForegroundEscapeSequence = "\u001b[93m";
246+
const blueForegroundEscapeSequence = "\u001b[93m";
247+
const gutterStyleSequence = "\u001b[100;30m";
248+
const gutterSeparator = " ";
249+
const resetEscapeSequence = "\u001b[0m";
250+
const ellipsis = "...";
251+
function getCategoryFormat(category: DiagnosticCategory): string {
252+
switch (category) {
253+
case DiagnosticCategory.Warning: return yellowForegroundEscapeSequence;
254+
case DiagnosticCategory.Error: return redForegroundEscapeSequence;
255+
case DiagnosticCategory.Message: return blueForegroundEscapeSequence;
256+
}
257+
}
258+
259+
function formatAndReset(text: string, formatStyle: string) {
260+
return formatStyle + text + resetEscapeSequence;
261+
}
262+
263+
function padLeft(s: string, length: number) {
264+
while (s.length < length) {
265+
s = " " + s;
266+
}
267+
return s;
268+
}
269+
270+
export function formatDiagnosticsWithColorAndContext(diagnostics: Diagnostic[], host: FormatDiagnosticsHost): string {
271+
let output = "";
272+
for (const diagnostic of diagnostics) {
273+
if (diagnostic.file) {
274+
const { start, length, file } = diagnostic;
275+
const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start);
276+
const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length);
277+
const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line;
278+
const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName;
279+
280+
const hasMoreThanFiveLines = (lastLine - firstLine) >= 4;
281+
let gutterWidth = (lastLine + 1 + "").length;
282+
if (hasMoreThanFiveLines) {
283+
gutterWidth = Math.max(ellipsis.length, gutterWidth);
284+
}
285+
286+
output += sys.newLine;
287+
for (let i = firstLine; i <= lastLine; i++) {
288+
// If the error spans over 5 lines, we'll only show the first 2 and last 2 lines,
289+
// so we'll skip ahead to the second-to-last line.
290+
if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) {
291+
output += formatAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + sys.newLine;
292+
i = lastLine - 1;
293+
}
294+
295+
const lineStart = getPositionOfLineAndCharacter(file, i, 0);
296+
const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length;
297+
let lineContent = file.text.slice(lineStart, lineEnd);
298+
lineContent = lineContent.replace(/\s+$/g, ""); // trim from end
299+
lineContent = lineContent.replace("\t", " "); // convert tabs to single spaces
300+
301+
// Output the gutter and the actual contents of the line.
302+
output += formatAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator;
303+
output += lineContent + sys.newLine;
304+
305+
// Output the gutter and the error span for the line using tildes.
306+
output += formatAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator;
307+
output += redForegroundEscapeSequence;
308+
if (i === firstLine) {
309+
// If we're on the last line, then limit it to the last character of the last line.
310+
// Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position.
311+
const lastCharForLine = i === lastLine ? lastLineChar : undefined;
312+
313+
output += lineContent.slice(0, firstLineChar).replace(/\S/g, " ");
314+
output += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~");
315+
}
316+
else if (i === lastLine) {
317+
output += lineContent.slice(0, lastLineChar).replace(/./g, "~");
318+
}
319+
else {
320+
// Squiggle the entire line.
321+
output += lineContent.replace(/./g, "~");
322+
}
323+
output += resetEscapeSequence;
324+
325+
output += sys.newLine;
326+
}
327+
328+
output += sys.newLine;
329+
output += `${ relativeFileName }(${ firstLine + 1 },${ firstLineChar + 1 }): `;
330+
}
331+
332+
const categoryColor = getCategoryFormat(diagnostic.category);
333+
const category = DiagnosticCategory[diagnostic.category].toLowerCase();
334+
output += `${ formatAndReset(category, categoryColor) } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }`;
335+
}
336+
return output;
337+
}
338+
244339
export function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string {
245340
if (typeof messageText === "string") {
246341
return messageText;

src/compiler/tsc.ts

Lines changed: 1 addition & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -60,93 +60,8 @@ namespace ts {
6060
sys.write(ts.formatDiagnostics([diagnostic], host));
6161
}
6262

63-
const redForegroundEscapeSequence = "\u001b[91m";
64-
const yellowForegroundEscapeSequence = "\u001b[93m";
65-
const blueForegroundEscapeSequence = "\u001b[93m";
66-
const gutterStyleSequence = "\u001b[100;30m";
67-
const gutterSeparator = " ";
68-
const resetEscapeSequence = "\u001b[0m";
69-
const ellipsis = "...";
70-
function getCategoryFormat(category: DiagnosticCategory): string {
71-
switch (category) {
72-
case DiagnosticCategory.Warning: return yellowForegroundEscapeSequence;
73-
case DiagnosticCategory.Error: return redForegroundEscapeSequence;
74-
case DiagnosticCategory.Message: return blueForegroundEscapeSequence;
75-
}
76-
}
77-
78-
function formatAndReset(text: string, formatStyle: string) {
79-
return formatStyle + text + resetEscapeSequence;
80-
}
81-
8263
function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void {
83-
let output = "";
84-
85-
if (diagnostic.file) {
86-
const { start, length, file } = diagnostic;
87-
const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start);
88-
const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length);
89-
const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line;
90-
const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName;
91-
92-
const hasMoreThanFiveLines = (lastLine - firstLine) >= 4;
93-
let gutterWidth = (lastLine + 1 + "").length;
94-
if (hasMoreThanFiveLines) {
95-
gutterWidth = Math.max(ellipsis.length, gutterWidth);
96-
}
97-
98-
output += sys.newLine;
99-
for (let i = firstLine; i <= lastLine; i++) {
100-
// If the error spans over 5 lines, we'll only show the first 2 and last 2 lines,
101-
// so we'll skip ahead to the second-to-last line.
102-
if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) {
103-
output += formatAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + sys.newLine;
104-
i = lastLine - 1;
105-
}
106-
107-
const lineStart = getPositionOfLineAndCharacter(file, i, 0);
108-
const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length;
109-
let lineContent = file.text.slice(lineStart, lineEnd);
110-
lineContent = lineContent.replace(/\s+$/g, ""); // trim from end
111-
lineContent = lineContent.replace("\t", " "); // convert tabs to single spaces
112-
113-
// Output the gutter and the actual contents of the line.
114-
output += formatAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator;
115-
output += lineContent + sys.newLine;
116-
117-
// Output the gutter and the error span for the line using tildes.
118-
output += formatAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator;
119-
output += redForegroundEscapeSequence;
120-
if (i === firstLine) {
121-
// If we're on the last line, then limit it to the last character of the last line.
122-
// Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position.
123-
const lastCharForLine = i === lastLine ? lastLineChar : undefined;
124-
125-
output += lineContent.slice(0, firstLineChar).replace(/\S/g, " ");
126-
output += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~");
127-
}
128-
else if (i === lastLine) {
129-
output += lineContent.slice(0, lastLineChar).replace(/./g, "~");
130-
}
131-
else {
132-
// Squiggle the entire line.
133-
output += lineContent.replace(/./g, "~");
134-
}
135-
output += resetEscapeSequence;
136-
137-
output += sys.newLine;
138-
}
139-
140-
output += sys.newLine;
141-
output += `${ relativeFileName }(${ firstLine + 1 },${ firstLineChar + 1 }): `;
142-
}
143-
144-
const categoryColor = getCategoryFormat(diagnostic.category);
145-
const category = DiagnosticCategory[diagnostic.category].toLowerCase();
146-
output += `${ formatAndReset(category, categoryColor) } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }`;
147-
output += sys.newLine + sys.newLine;
148-
149-
sys.write(output);
64+
sys.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + sys.newLine + sys.newLine);
15065
}
15166

15267
function reportWatchDiagnostic(diagnostic: Diagnostic) {

0 commit comments

Comments
 (0)