From f4ce855cad2d8740d8ee8c88f8dcdb5052e4b15b Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 2 Jul 2016 09:37:50 -0700 Subject: [PATCH] Added an optional `TSLintErrorSeverity` flag (#45) * Added an optional `TSLintErrorSeverity` flag Optional MSBuild error severity override, as `"error"` or `"warning"`. Fixes #19. * Fixed README.md ordering for error severity * Moved the "X errors found" message above errors Now that error printing is a source of errors (albeit very unlikely) it makes sense to have the summary first. --- README.md | 1 + src/ArgumentsCollection.ts | 14 ++++- src/ErrorsPrinter.ts | 110 +++++++++++++++++++++++++++++++++++++ src/LintRunner.ts | 13 +++-- src/TSLint.MSBuild.targets | 3 +- src/index.ts | 12 ++-- tsconfig.json | 1 + 7 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 src/ErrorsPrinter.ts diff --git a/README.md b/README.md index 453cbce..351298d 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ The following properties may be overidden via your targets: * **TSLintBreakBuildOnError** - Whether linting failures should break the build. Defaults to `false`. * **TSLintConfig** - Path to a specific tslint.json. Defaults to blank, for any tslint.json on the path. * **TSLintDeleteFileListFile** - Whether to delete the file list file when done. Defaults to `true`. +* **TSLintErrorSeverity** - Optional MSBuild error severity override, as `"error"` or `"warning"`. * **TSLintExclude** - Blob of matching file names to exclude. Defaults to none. * **TSLintFilesRootDir** - Root directory to work within. Defaults to `$(MSBuildProjectDirectory)`. * **TSLintFileListDir** - Directory to put the file list in. Defaults to `$(IntermediateOutDir)`. diff --git a/src/ArgumentsCollection.ts b/src/ArgumentsCollection.ts index d96085c..44761ed 100644 --- a/src/ArgumentsCollection.ts +++ b/src/ArgumentsCollection.ts @@ -7,6 +7,11 @@ interface ICollected { */ "--config"?: string; + /** + * Override for MSBuild error severity level. + */ + "--error-severity"?: "error" | "warning"; + /** * A glob path to exclude from linting. */ @@ -36,7 +41,7 @@ export class ArgumentsCollection { * Whitelist of allowed keys from the .targets file. */ private static allowedKeys: Set = new Set([ - "--config", "--exclude", "--file-list-file", "--files-root-dir", "--rules-directory" + "--config", "--error-severity", "--exclude", "--file-list-file", "--files-root-dir", "--rules-directory" ]); /** @@ -92,6 +97,13 @@ export class ArgumentsCollection { return this.collected["--config"]; } + /** + * @returns The override for MSBuild error severity level. + */ + public getErrorSeverity(): "error" | "warning" { + return this.collected["--error-severity"]; + } + /** * @returns The root directory to work within. */ diff --git a/src/ErrorsPrinter.ts b/src/ErrorsPrinter.ts new file mode 100644 index 0000000..ce0ec5e --- /dev/null +++ b/src/ErrorsPrinter.ts @@ -0,0 +1,110 @@ +/** + * MSBuild error printers, keyed by severity level. + */ +interface IErrorPrinters { + [i: string]: IErrorPrinter; +} + +/** + * Prints a single pre-formatted MSBuild error. + * + * @param error A pre-formatted MSBuild error. + */ +interface IErrorPrinter { + (error: string): void; +} + +/** + * Prints MSBuild errors from the TSLint CLI with a severity level. + */ +export class ErrorsPrinter { + /** + * MSBuild error printers, keyed by severity level. + */ + private errorPrinters: IErrorPrinters = { + error: (error: string): void => console.error(error), + warning: (error: string): void => console.warn(error) + }; + + /** + * Overridden severity level for errors, if provided. + */ + private severityOverride: string; + + /** + * Initializes a new instance of the ErrorsPrinter class. + * + * @param severityOverride Overridden severity level for errors, if provided. + */ + public constructor(severityOverride?: string) { + this.severityOverride = severityOverride; + } + + /** + * Prints MSBuild errors. + * + * @param errors MSBuild errors. + */ + public print(errors: string[]): void { + for (const error of errors) { + this.printError(error); + } + } + + /** + * Prints an MSBuild error. + * + * @param error An MSBuild error. + */ + public printError(error: string): void { + const errorSeverity = this.getSeverityFromError(error); + + if (this.severityOverride && this.severityOverride !== errorSeverity) { + error = this.replaceErrorSeverity(error, errorSeverity, this.severityOverride); + } + + const severity = this.severityOverride || errorSeverity; + const printer = this.errorPrinters[severity]; + + if (!printer) { + throw new Error(`Unknown error severity: '${severity}'.`); + } + + printer(error); + } + + /** + * @param error An MSBuild error. + * @returns The error's severity. + */ + private getSeverityFromError(error: string): string { + return error + .match(/\):\s.+:/) + [0] + .replace(/\W/g, ""); + } + + /** + * Replaces an error's severity with a new severity. + * + * @param error An MSBuild error. + * @param originalSeverity The current severity level of the error. + * @param newSeverity A new severity level for the error. + * @returns A copy of the error with thenew severity level. + */ + private replaceErrorSeverity(error: string, originalSeverity: string, newSeverity: string): string { + return error.replace( + this.wrapErrorFormat(originalSeverity), + this.wrapErrorFormat(newSeverity)); + } + + /** + * Wraps a severity string with find-and-replace safe markers. + * + * @param severity A severity level. + * @returns The severity with find-and-replace safe markers. + */ + private wrapErrorFormat(severity: string): string { + return `): ${severity}`; + } +} \ No newline at end of file diff --git a/src/LintRunner.ts b/src/LintRunner.ts index bfb56b4..7f31d66 100644 --- a/src/LintRunner.ts +++ b/src/LintRunner.ts @@ -40,7 +40,7 @@ export class LintRunner { * * @returns A promise for TSLint errors, in alphabetical order of file path. */ - public runTSLint(): Promise { + public runTSLint(): Promise { const linter: ChildProcess = this.runSpawn( "node", [ @@ -50,11 +50,16 @@ export class LintRunner { "msbuild", ...this.filePaths ]); - let errors: string = ""; + let errors: string[] = []; - return new Promise((resolve: (errors: string) => void, reject: (error: string) => void): void => { + return new Promise((resolve: (errors: string[]) => void, reject: (error: string) => void): void => { linter.stdout.on("data", (data: Buffer): void => { - errors += data.toString(); + errors.push( + ...data + .toString() + .replace(/\r/g, "") + .split(/\n/g) + .filter((error: string): boolean => !!error)); }); linter.stderr.on("data", (data: Buffer): void => { diff --git a/src/TSLint.MSBuild.targets b/src/TSLint.MSBuild.targets index 8e07b1d..ea69f16 100644 --- a/src/TSLint.MSBuild.targets +++ b/src/TSLint.MSBuild.targets @@ -13,6 +13,7 @@ false true + $(MSBuildProjectDirectory) $(IntermediateOutDir) @@ -36,7 +37,7 @@ diff --git a/src/index.ts b/src/index.ts index 43b9489..16c1d85 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ console.log("Starting TSLint runner."); import * as fs from "fs"; import * as path from "path"; import { ArgumentsCollection } from "./ArgumentsCollection"; +import { ErrorsPrinter } from "./ErrorsPrinter"; import { LintRunner } from "./LintRunner"; /** @@ -33,14 +34,9 @@ function getInputFilesList(filePath): string[] { console.log(`Running TSLint on ${filePaths.length} file(s).`); runner.runTSLint() - .then(lintErrors => { - const numErrors = lintErrors.match(/\n/g).length; - - if (numErrors !== 0) { - console.error(lintErrors); - } - - console.log(`${numErrors} error(s) found in ${filePaths.length} file(s).`); + .then((lintErrors: string[]): void => { + console.log(`${lintErrors.length} error(s) found in ${filePaths.length} file(s).`); + new ErrorsPrinter(argumentsCollection.getErrorSeverity()).print(lintErrors); }) .catch(error => { console.error("Error running TSLint!"); diff --git a/tsconfig.json b/tsconfig.json index fa4da4e..f9429a1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ }, "files": [ "src/ArgumentsCollection.ts", + "src/ErrorsPrinter.ts", "src/index.ts", "src/LintRunner.ts", "src/TSLintSearcher.ts"