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[];