diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts
index 842e73d00f553..b39a802a0b528 100644
--- a/src/compiler/commandLineParser.ts
+++ b/src/compiler/commandLineParser.ts
@@ -1,6 +1,7 @@
///
///
///
+///
///
namespace ts {
@@ -150,6 +151,12 @@ namespace ts {
type: "boolean",
description: Diagnostics.Do_not_erase_const_enum_declarations_in_generated_code
},
+ {
+ name: "pretty",
+ paramType: Diagnostics.KIND,
+ description: Diagnostics.Stylize_errors_and_messages_using_colors_if_available_experimental,
+ type: "boolean"
+ },
{
name: "project",
shortName: "p",
diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index dd7fbe14d6eab..401cbaf8ddaed 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -2258,14 +2258,6 @@
"code": 6063
},
- "Specify JSX code generation: 'preserve' or 'react'": {
- "category": "Message",
- "code": 6080
- },
- "Argument for '--jsx' must be 'preserve' or 'react'.": {
- "category": "Message",
- "code": 6081
- },
"Enables experimental support for ES7 decorators.": {
"category": "Message",
"code": 6065
@@ -2293,8 +2285,8 @@
"Suppress excess property checks for object literals.": {
"category": "Message",
"code": 6072
- },
- "Disallow inconsistently-cased references to the same file.": {
+ },
+ "Stylize errors and messages using colors if available. (experimental)": {
"category": "Message",
"code": 6073
},
@@ -2314,6 +2306,20 @@
"category": "Message",
"code": 6077
},
+ "Disallow inconsistently-cased references to the same file.": {
+ "category": "Message",
+ "code": 6078
+ },
+
+ "Specify JSX code generation: 'preserve' or 'react'": {
+ "category": "Message",
+ "code": 6080
+ },
+ "Argument for '--jsx' must be 'preserve' or 'react'.": {
+ "category": "Message",
+ "code": 6081
+ },
+
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
"code": 7005
diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts
index 3eeb126c065b6..6263f7237f609 100644
--- a/src/compiler/sys.ts
+++ b/src/compiler/sys.ts
@@ -198,6 +198,7 @@ namespace ts {
const _fs = require("fs");
const _path = require("path");
const _os = require("os");
+ const _tty = require("tty");
// average async stat takes about 30 microseconds
// set chunk size to do 30 files in < 1 millisecond
@@ -375,15 +376,7 @@ namespace ts {
newLine: _os.EOL,
useCaseSensitiveFileNames: useCaseSensitiveFileNames,
write(s: string): void {
- const buffer = new Buffer(s, "utf8");
- let offset = 0;
- let toWrite: number = buffer.length;
- let written = 0;
- // 1 is a standard descriptor for stdout
- while ((written = _fs.writeSync(1, buffer, offset, toWrite)) < toWrite) {
- offset += written;
- toWrite -= written;
- }
+ process.stdout.write(s);
},
readFile,
writeFile,
diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts
index 232360116f34e..083df70be0f76 100644
--- a/src/compiler/tsc.ts
+++ b/src/compiler/tsc.ts
@@ -6,6 +6,14 @@ namespace ts {
fileWatcher?: FileWatcher;
}
+ let reportDiagnostic = reportDiagnosticSimply;
+
+ function reportDiagnostics(diagnostics: Diagnostic[], host: CompilerHost): void {
+ for (let diagnostic of diagnostics) {
+ reportDiagnostic(diagnostic, host);
+ }
+ }
+
/**
* Checks to see if the locale is in the appropriate format,
* and if it is, attempts to set the appropriate language.
@@ -81,16 +89,16 @@ namespace ts {
return diagnostic.messageText;
}
- function reportDiagnostic(diagnostic: Diagnostic, host: CompilerHost) {
+ function reportDiagnosticSimply(diagnostic: Diagnostic, host: CompilerHost): void {
let output = "";
if (diagnostic.file) {
- let loc = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
+ const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
const relativeFileName = host
? convertToRelativePath(diagnostic.file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName))
: diagnostic.file.fileName;
- output += `${ relativeFileName }(${ loc.line + 1 },${ loc.character + 1 }): `;
+ output += `${ diagnostic.file.fileName }(${ line + 1 },${ character + 1 }): `;
}
let category = DiagnosticCategory[diagnostic.category].toLowerCase();
@@ -99,10 +107,91 @@ namespace ts {
sys.write(output);
}
- function reportDiagnostics(diagnostics: Diagnostic[], host: CompilerHost) {
- for (let i = 0; i < diagnostics.length; i++) {
- reportDiagnostic(diagnostics[i], host);
+
+ const redForegroundEscapeSequence = "\u001b[91m";
+ const yellowForegroundEscapeSequence = "\u001b[93m";
+ const blueForegroundEscapeSequence = "\u001b[93m";
+ const gutterStyleSequence = "\u001b[100;30m";
+ const gutterSeparator = " ";
+ const resetEscapeSequence = "\u001b[0m";
+ const elipsis = "...";
+ const categoryFormatMap: Map = {
+ [DiagnosticCategory.Warning]: yellowForegroundEscapeSequence,
+ [DiagnosticCategory.Error]: redForegroundEscapeSequence,
+ [DiagnosticCategory.Message]: blueForegroundEscapeSequence,
+ };
+
+ function formatAndReset(text: string, formatStyle: string) {
+ return formatStyle + text + resetEscapeSequence;
+ }
+
+ function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: CompilerHost): void {
+ let output = "";
+
+ if (diagnostic.file) {
+ let { start, length, file } = diagnostic;
+ let { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start);
+ let { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length);
+ const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line;
+
+ let hasMoreThanFiveLines = (lastLine - firstLine) >= 4;
+ let gutterWidth = (lastLine + 1 + "").length;
+ if (hasMoreThanFiveLines) {
+ gutterWidth = Math.max(elipsis.length, gutterWidth);
+ }
+
+ output += sys.newLine;
+ for (let i = firstLine; i <= lastLine; i++) {
+ // If the error spans over 5 lines, we'll only show the first 2 and last 2 lines,
+ // so we'll skip ahead to the second-to-last line.
+ if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) {
+ output += formatAndReset(padLeft(elipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + sys.newLine;
+ i = lastLine - 1;
+ }
+
+ let lineStart = getPositionOfLineAndCharacter(file, i, 0);
+ let lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length;
+ let lineContent = file.text.slice(lineStart, lineEnd);
+ lineContent = lineContent.replace(/\s+$/g, ""); // trim from end
+ lineContent = lineContent.replace("\t", " "); // convert tabs to single spaces
+
+ // Output the gutter and the actual contents of the line.
+ output += formatAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator;
+ output += lineContent + sys.newLine;
+
+ // Output the gutter and the error span for the line using tildes.
+ output += formatAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator;
+ output += redForegroundEscapeSequence;
+ if (i === firstLine) {
+ // If we're on the last line, then limit it to the last character of the last line.
+ // Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position.
+ const lastCharForLine = i === lastLine ? lastLineChar : undefined;
+
+ output += lineContent.slice(0, firstLineChar).replace(/\S/g, " ");
+ output += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~");
+ }
+ else if (i === lastLine) {
+ output += lineContent.slice(0, lastLineChar).replace(/./g, "~");
+ }
+ else {
+ // Squiggle the entire line.
+ output += lineContent.replace(/./g, "~");
+ }
+ output += resetEscapeSequence;
+
+ output += sys.newLine;
+ }
+
+ output += sys.newLine;
+ output += `${ file.fileName }(${ firstLine + 1 },${ firstLineChar + 1 }): `;
}
+
+ const categoryColor = categoryFormatMap[diagnostic.category];
+ const category = DiagnosticCategory[diagnostic.category].toLowerCase();
+ output += `${ formatAndReset(category, categoryColor) } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }`;
+ output += sys.newLine + sys.newLine;
+
+ sys.write(output);
}
function reportWatchDiagnostic(diagnostic: Diagnostic) {
@@ -288,6 +377,10 @@ namespace ts {
compilerHost.fileExists = cachedFileExists;
}
+ if (compilerOptions.pretty) {
+ reportDiagnostic = reportDiagnosticWithColorAndContext;
+ }
+
// reset the cache of existing files
cachedExistingFiles = {};
diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json
index fd541a8ca80c1..ca297c087cf0c 100644
--- a/src/compiler/tsconfig.json
+++ b/src/compiler/tsconfig.json
@@ -11,6 +11,7 @@
"core.ts",
"sys.ts",
"types.ts",
+ "diagnosticInformationMap.generated.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
@@ -19,7 +20,6 @@
"emitter.ts",
"program.ts",
"commandLineParser.ts",
- "tsc.ts",
- "diagnosticInformationMap.generated.ts"
+ "tsc.ts"
]
}
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index 4044f90b9706c..f536a1e469307 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -2087,6 +2087,7 @@ namespace ts {
outFile?: string;
outDir?: string;
preserveConstEnums?: boolean;
+ /* @internal */ pretty?: DiagnosticStyle;
project?: string;
removeComments?: boolean;
rootDir?: string;
@@ -2156,6 +2157,12 @@ namespace ts {
JSX,
}
+ /* @internal */
+ export const enum DiagnosticStyle {
+ Simple,
+ Pretty,
+ }
+
export interface ParsedCommandLine {
options: CompilerOptions;
fileNames: string[];