From 18217351366774f8cc31967f1116f28001367b3f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 21 Sep 2017 08:36:50 -0700 Subject: [PATCH] Add custom formatter which has clickable links, reduce error duplication in gulp output (#18613) --- Gulpfile.ts | 12 +-- Jakefile.js | 26 +++-- package.json | 4 +- .../autolinkableStylishFormatter.ts | 97 +++++++++++++++++++ .../tslint/{ => rules}/booleanTriviaRule.ts | 0 scripts/tslint/{ => rules}/debugAssertRule.ts | 0 scripts/tslint/{ => rules}/nextLineRule.ts | 0 scripts/tslint/{ => rules}/noBomRule.ts | 0 .../tslint/{ => rules}/noInOperatorRule.ts | 0 .../{ => rules}/noIncrementDecrementRule.ts | 0 .../noTypeAssertionWhitespaceRule.ts | 0 .../objectLiteralSurroundingSpaceRule.ts | 0 .../{ => rules}/typeOperatorSpacingRule.ts | 0 tslint.json | 2 +- 14 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 scripts/tslint/formatters/autolinkableStylishFormatter.ts rename scripts/tslint/{ => rules}/booleanTriviaRule.ts (100%) rename scripts/tslint/{ => rules}/debugAssertRule.ts (100%) rename scripts/tslint/{ => rules}/nextLineRule.ts (100%) rename scripts/tslint/{ => rules}/noBomRule.ts (100%) rename scripts/tslint/{ => rules}/noInOperatorRule.ts (100%) rename scripts/tslint/{ => rules}/noIncrementDecrementRule.ts (100%) rename scripts/tslint/{ => rules}/noTypeAssertionWhitespaceRule.ts (100%) rename scripts/tslint/{ => rules}/objectLiteralSurroundingSpaceRule.ts (100%) rename scripts/tslint/{ => rules}/typeOperatorSpacingRule.ts (100%) diff --git a/Gulpfile.ts b/Gulpfile.ts index 676d07ec57..4ca099b56e 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -674,11 +674,10 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done: }); function failWithStatus(err?: any, status?: number) { - if (err) { - console.log(err); + if (err || status) { + process.exit(typeof status === "number" ? status : 2); } - done(err || status); - process.exit(status); + done(); } function lintThenFinish() { @@ -1051,10 +1050,11 @@ gulp.task("lint", "Runs tslint on the compiler sources. Optional arguments are: const fileMatcher = cmdLineOptions["files"]; const files = fileMatcher ? `src/**/${fileMatcher}` - : "Gulpfile.ts 'scripts/tslint/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'"; - const cmd = `node node_modules/tslint/bin/tslint ${files} --format stylish`; + : "Gulpfile.ts 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'"; + const cmd = `node node_modules/tslint/bin/tslint ${files} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish`; console.log("Linting: " + cmd); child_process.execSync(cmd, { stdio: [0, 1, 2] }); + if (fold.isTravis()) console.log(fold.end("lint")); }); gulp.task("default", "Runs 'local'", ["local"]); diff --git a/Jakefile.js b/Jakefile.js index 6fd2f54901..7de0635542 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -1121,7 +1121,7 @@ task("update-sublime", ["local", serverFile], function () { jake.cpR(serverFile + ".map", "../TypeScript-Sublime-Plugin/tsserver/"); }); -var tslintRuleDir = "scripts/tslint"; +var tslintRuleDir = "scripts/tslint/rules"; var tslintRules = [ "booleanTriviaRule", "debugAssertRule", @@ -1137,13 +1137,27 @@ var tslintRulesFiles = tslintRules.map(function (p) { return path.join(tslintRuleDir, p + ".ts"); }); var tslintRulesOutFiles = tslintRules.map(function (p) { - return path.join(builtLocalDirectory, "tslint", p + ".js"); + return path.join(builtLocalDirectory, "tslint/rules", p + ".js"); +}); +var tslintFormattersDir = "scripts/tslint/formatters"; +var tslintFormatters = [ + "autolinkableStylishFormatter", +]; +var tslintFormatterFiles = tslintFormatters.map(function (p) { + return path.join(tslintFormattersDir, p + ".ts"); +}); +var tslintFormattersOutFiles = tslintFormatters.map(function (p) { + return path.join(builtLocalDirectory, "tslint/formatters", p + ".js"); }); desc("Compiles tslint rules to js"); -task("build-rules", ["build-rules-start"].concat(tslintRulesOutFiles).concat(["build-rules-end"])); +task("build-rules", ["build-rules-start"].concat(tslintRulesOutFiles).concat(tslintFormattersOutFiles).concat(["build-rules-end"])); tslintRulesFiles.forEach(function (ruleFile, i) { compileFile(tslintRulesOutFiles[i], [ruleFile], [ruleFile], [], /*useBuiltCompiler*/ false, - { noOutFile: true, generateDeclarations: false, outDir: path.join(builtLocalDirectory, "tslint"), lib: "es6" }); + { noOutFile: true, generateDeclarations: false, outDir: path.join(builtLocalDirectory, "tslint/rules"), lib: "es6" }); +}); +tslintFormatterFiles.forEach(function (ruleFile, i) { + compileFile(tslintFormattersOutFiles[i], [ruleFile], [ruleFile], [], /*useBuiltCompiler*/ false, + { noOutFile: true, generateDeclarations: false, outDir: path.join(builtLocalDirectory, "tslint/formatters"), lib: "es6" }); }); desc("Emit the start of the build-rules fold"); @@ -1211,8 +1225,8 @@ task("lint", ["build-rules"], () => { const fileMatcher = process.env.f || process.env.file || process.env.files; const files = fileMatcher ? `src/**/${fileMatcher}` - : "Gulpfile.ts 'scripts/tslint/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'"; - const cmd = `node node_modules/tslint/bin/tslint ${files} --format stylish`; + : "Gulpfile.ts 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'"; + const cmd = `node node_modules/tslint/bin/tslint ${files} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish`; console.log("Linting: " + cmd); jake.exec([cmd], { interactive: true }, () => { if (fold.isTravis()) console.log(fold.end("lint")); diff --git a/package.json b/package.json index 9e4b323477..ca7d48c777 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "devDependencies": { "@types/browserify": "latest", "@types/chai": "latest", + "@types/colors": "latest", "@types/convert-source-map": "latest", "@types/del": "latest", "@types/glob": "latest", @@ -48,8 +49,8 @@ "@types/q": "latest", "@types/run-sequence": "latest", "@types/through2": "latest", - "browserify": "latest", "browser-resolve": "^1.11.2", + "browserify": "latest", "chai": "latest", "convert-source-map": "latest", "del": "latest", @@ -75,6 +76,7 @@ "travis-fold": "latest", "ts-node": "latest", "tslint": "latest", + "colors": "latest", "typescript": "next" }, "scripts": { diff --git a/scripts/tslint/formatters/autolinkableStylishFormatter.ts b/scripts/tslint/formatters/autolinkableStylishFormatter.ts new file mode 100644 index 0000000000..6a02ec24f0 --- /dev/null +++ b/scripts/tslint/formatters/autolinkableStylishFormatter.ts @@ -0,0 +1,97 @@ +import * as Lint from "tslint"; +import * as colors from "colors"; +import { sep } from "path"; +function groupBy(array: ReadonlyArray | undefined, getGroupId: (elem: T, index: number) => number | string): T[][] { + if (!array) { + return []; + } + + const groupIdToGroup: { [index: string]: T[] } = {}; + let result: T[][] | undefined; // Compacted array for return value + for (let index = 0; index < array.length; index++) { + const value = array[index]; + const key = getGroupId(value, index); + if (groupIdToGroup[key]) { + groupIdToGroup[key].push(value); + } + else { + const newGroup = [value]; + groupIdToGroup[key] = newGroup; + if (!result) { + result = [newGroup]; + } + else { + result.push(newGroup); + } + } + } + + return result || []; +} + +function max(array: ReadonlyArray | undefined, selector: (elem: T) => number): number { + if (!array) { + return 0; + } + + let max = 0; + for (const item of array) { + const scalar = selector(item); + if (scalar > max) { + max = scalar; + } + } + return max; +} + +function getLink(failure: Lint.RuleFailure, color: boolean): string { + const lineAndCharacter = failure.getStartPosition().getLineAndCharacter(); + const sev = failure.getRuleSeverity().toUpperCase(); + let path = failure.getFileName(); + // Most autolinks only become clickable if they contain a slash in some way; so we make a top level file into a relative path here + if (path.indexOf("/") === -1 && path.indexOf("\\") === -1) { + path = `.${sep}${path}`; + } + return `${color ? (sev === "WARNING" ? colors.blue(sev) : colors.red(sev)) : sev}: ${path}:${lineAndCharacter.line + 1}:${lineAndCharacter.character + 1}`; +} + +function getLinkMaxSize(failures: Lint.RuleFailure[]): number { + return max(failures, f => getLink(f, /*color*/ false).length); +} + +function getNameMaxSize(failures: Lint.RuleFailure[]): number { + return max(failures, f => f.getRuleName().length); +} + +function pad(str: string, visiblelen: number, len: number) { + if (visiblelen >= len) return str; + const count = len - visiblelen; + for (let i = 0; i < count; i++) { + str += " "; + } + return str; +} + +export class Formatter extends Lint.Formatters.AbstractFormatter { + public static metadata: Lint.IFormatterMetadata = { + formatterName: "autolinkableStylish", + description: "Human-readable formatter which creates stylish messages with autolinkable filepaths.", + descriptionDetails: Lint.Utils.dedent` + Colorized output grouped by file, with autolinkable filepaths containing line and column information + `, + sample: Lint.Utils.dedent` + src/myFile.ts + ERROR: src/myFile.ts:1:14 semicolon Missing semicolon`, + consumer: "human" + }; + public format(failures: Lint.RuleFailure[]): string { + return groupBy(failures, f => f.getFileName()).map(group => { + const currentFile = group[0].getFileName(); + const linkMaxSize = getLinkMaxSize(group); + const nameMaxSize = getNameMaxSize(group); + return ` +${currentFile} +${group.map(f => `${pad(getLink(f, /*color*/ true), getLink(f, /*color*/ false).length, linkMaxSize)} ${colors.grey(pad(f.getRuleName(), f.getRuleName().length, nameMaxSize))} ${colors.yellow(f.getFailure())}`).join("\n")}`; + }).join("\n"); + } +} \ No newline at end of file diff --git a/scripts/tslint/booleanTriviaRule.ts b/scripts/tslint/rules/booleanTriviaRule.ts similarity index 100% rename from scripts/tslint/booleanTriviaRule.ts rename to scripts/tslint/rules/booleanTriviaRule.ts diff --git a/scripts/tslint/debugAssertRule.ts b/scripts/tslint/rules/debugAssertRule.ts similarity index 100% rename from scripts/tslint/debugAssertRule.ts rename to scripts/tslint/rules/debugAssertRule.ts diff --git a/scripts/tslint/nextLineRule.ts b/scripts/tslint/rules/nextLineRule.ts similarity index 100% rename from scripts/tslint/nextLineRule.ts rename to scripts/tslint/rules/nextLineRule.ts diff --git a/scripts/tslint/noBomRule.ts b/scripts/tslint/rules/noBomRule.ts similarity index 100% rename from scripts/tslint/noBomRule.ts rename to scripts/tslint/rules/noBomRule.ts diff --git a/scripts/tslint/noInOperatorRule.ts b/scripts/tslint/rules/noInOperatorRule.ts similarity index 100% rename from scripts/tslint/noInOperatorRule.ts rename to scripts/tslint/rules/noInOperatorRule.ts diff --git a/scripts/tslint/noIncrementDecrementRule.ts b/scripts/tslint/rules/noIncrementDecrementRule.ts similarity index 100% rename from scripts/tslint/noIncrementDecrementRule.ts rename to scripts/tslint/rules/noIncrementDecrementRule.ts diff --git a/scripts/tslint/noTypeAssertionWhitespaceRule.ts b/scripts/tslint/rules/noTypeAssertionWhitespaceRule.ts similarity index 100% rename from scripts/tslint/noTypeAssertionWhitespaceRule.ts rename to scripts/tslint/rules/noTypeAssertionWhitespaceRule.ts diff --git a/scripts/tslint/objectLiteralSurroundingSpaceRule.ts b/scripts/tslint/rules/objectLiteralSurroundingSpaceRule.ts similarity index 100% rename from scripts/tslint/objectLiteralSurroundingSpaceRule.ts rename to scripts/tslint/rules/objectLiteralSurroundingSpaceRule.ts diff --git a/scripts/tslint/typeOperatorSpacingRule.ts b/scripts/tslint/rules/typeOperatorSpacingRule.ts similarity index 100% rename from scripts/tslint/typeOperatorSpacingRule.ts rename to scripts/tslint/rules/typeOperatorSpacingRule.ts diff --git a/tslint.json b/tslint.json index 30e71eb5e0..be289ae132 100644 --- a/tslint.json +++ b/tslint.json @@ -1,5 +1,5 @@ { - "rulesDirectory": "built/local/tslint", + "rulesDirectory": "built/local/tslint/rules", "rules": { "boolean-trivia": true, "class-name": true,