Skip to content

Commit 1821735

Browse files
authored
Add custom formatter which has clickable links, reduce error duplication in gulp output (microsoft#18613)
1 parent d9951cb commit 1821735

14 files changed

+127
-14
lines changed

Gulpfile.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -674,11 +674,10 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done:
674674
});
675675

676676
function failWithStatus(err?: any, status?: number) {
677-
if (err) {
678-
console.log(err);
677+
if (err || status) {
678+
process.exit(typeof status === "number" ? status : 2);
679679
}
680-
done(err || status);
681-
process.exit(status);
680+
done();
682681
}
683682

684683
function lintThenFinish() {
@@ -1051,10 +1050,11 @@ gulp.task("lint", "Runs tslint on the compiler sources. Optional arguments are:
10511050
const fileMatcher = cmdLineOptions["files"];
10521051
const files = fileMatcher
10531052
? `src/**/${fileMatcher}`
1054-
: "Gulpfile.ts 'scripts/tslint/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'";
1055-
const cmd = `node node_modules/tslint/bin/tslint ${files} --format stylish`;
1053+
: "Gulpfile.ts 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'";
1054+
const cmd = `node node_modules/tslint/bin/tslint ${files} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish`;
10561055
console.log("Linting: " + cmd);
10571056
child_process.execSync(cmd, { stdio: [0, 1, 2] });
1057+
if (fold.isTravis()) console.log(fold.end("lint"));
10581058
});
10591059

10601060
gulp.task("default", "Runs 'local'", ["local"]);

Jakefile.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ task("update-sublime", ["local", serverFile], function () {
11211121
jake.cpR(serverFile + ".map", "../TypeScript-Sublime-Plugin/tsserver/");
11221122
});
11231123

1124-
var tslintRuleDir = "scripts/tslint";
1124+
var tslintRuleDir = "scripts/tslint/rules";
11251125
var tslintRules = [
11261126
"booleanTriviaRule",
11271127
"debugAssertRule",
@@ -1137,13 +1137,27 @@ var tslintRulesFiles = tslintRules.map(function (p) {
11371137
return path.join(tslintRuleDir, p + ".ts");
11381138
});
11391139
var tslintRulesOutFiles = tslintRules.map(function (p) {
1140-
return path.join(builtLocalDirectory, "tslint", p + ".js");
1140+
return path.join(builtLocalDirectory, "tslint/rules", p + ".js");
1141+
});
1142+
var tslintFormattersDir = "scripts/tslint/formatters";
1143+
var tslintFormatters = [
1144+
"autolinkableStylishFormatter",
1145+
];
1146+
var tslintFormatterFiles = tslintFormatters.map(function (p) {
1147+
return path.join(tslintFormattersDir, p + ".ts");
1148+
});
1149+
var tslintFormattersOutFiles = tslintFormatters.map(function (p) {
1150+
return path.join(builtLocalDirectory, "tslint/formatters", p + ".js");
11411151
});
11421152
desc("Compiles tslint rules to js");
1143-
task("build-rules", ["build-rules-start"].concat(tslintRulesOutFiles).concat(["build-rules-end"]));
1153+
task("build-rules", ["build-rules-start"].concat(tslintRulesOutFiles).concat(tslintFormattersOutFiles).concat(["build-rules-end"]));
11441154
tslintRulesFiles.forEach(function (ruleFile, i) {
11451155
compileFile(tslintRulesOutFiles[i], [ruleFile], [ruleFile], [], /*useBuiltCompiler*/ false,
1146-
{ noOutFile: true, generateDeclarations: false, outDir: path.join(builtLocalDirectory, "tslint"), lib: "es6" });
1156+
{ noOutFile: true, generateDeclarations: false, outDir: path.join(builtLocalDirectory, "tslint/rules"), lib: "es6" });
1157+
});
1158+
tslintFormatterFiles.forEach(function (ruleFile, i) {
1159+
compileFile(tslintFormattersOutFiles[i], [ruleFile], [ruleFile], [], /*useBuiltCompiler*/ false,
1160+
{ noOutFile: true, generateDeclarations: false, outDir: path.join(builtLocalDirectory, "tslint/formatters"), lib: "es6" });
11471161
});
11481162

11491163
desc("Emit the start of the build-rules fold");
@@ -1211,8 +1225,8 @@ task("lint", ["build-rules"], () => {
12111225
const fileMatcher = process.env.f || process.env.file || process.env.files;
12121226
const files = fileMatcher
12131227
? `src/**/${fileMatcher}`
1214-
: "Gulpfile.ts 'scripts/tslint/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'";
1215-
const cmd = `node node_modules/tslint/bin/tslint ${files} --format stylish`;
1228+
: "Gulpfile.ts 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'";
1229+
const cmd = `node node_modules/tslint/bin/tslint ${files} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish`;
12161230
console.log("Linting: " + cmd);
12171231
jake.exec([cmd], { interactive: true }, () => {
12181232
if (fold.isTravis()) console.log(fold.end("lint"));

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"devDependencies": {
3232
"@types/browserify": "latest",
3333
"@types/chai": "latest",
34+
"@types/colors": "latest",
3435
"@types/convert-source-map": "latest",
3536
"@types/del": "latest",
3637
"@types/glob": "latest",
@@ -48,8 +49,8 @@
4849
"@types/q": "latest",
4950
"@types/run-sequence": "latest",
5051
"@types/through2": "latest",
51-
"browserify": "latest",
5252
"browser-resolve": "^1.11.2",
53+
"browserify": "latest",
5354
"chai": "latest",
5455
"convert-source-map": "latest",
5556
"del": "latest",
@@ -75,6 +76,7 @@
7576
"travis-fold": "latest",
7677
"ts-node": "latest",
7778
"tslint": "latest",
79+
"colors": "latest",
7880
"typescript": "next"
7981
},
8082
"scripts": {
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import * as Lint from "tslint";
2+
import * as colors from "colors";
3+
import { sep } from "path";
4+
function groupBy<T>(array: ReadonlyArray<T> | undefined, getGroupId: (elem: T, index: number) => number | string): T[][] {
5+
if (!array) {
6+
return [];
7+
}
8+
9+
const groupIdToGroup: { [index: string]: T[] } = {};
10+
let result: T[][] | undefined; // Compacted array for return value
11+
for (let index = 0; index < array.length; index++) {
12+
const value = array[index];
13+
const key = getGroupId(value, index);
14+
if (groupIdToGroup[key]) {
15+
groupIdToGroup[key].push(value);
16+
}
17+
else {
18+
const newGroup = [value];
19+
groupIdToGroup[key] = newGroup;
20+
if (!result) {
21+
result = [newGroup];
22+
}
23+
else {
24+
result.push(newGroup);
25+
}
26+
}
27+
}
28+
29+
return result || [];
30+
}
31+
32+
function max<T>(array: ReadonlyArray<T> | undefined, selector: (elem: T) => number): number {
33+
if (!array) {
34+
return 0;
35+
}
36+
37+
let max = 0;
38+
for (const item of array) {
39+
const scalar = selector(item);
40+
if (scalar > max) {
41+
max = scalar;
42+
}
43+
}
44+
return max;
45+
}
46+
47+
function getLink(failure: Lint.RuleFailure, color: boolean): string {
48+
const lineAndCharacter = failure.getStartPosition().getLineAndCharacter();
49+
const sev = failure.getRuleSeverity().toUpperCase();
50+
let path = failure.getFileName();
51+
// 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
52+
if (path.indexOf("/") === -1 && path.indexOf("\\") === -1) {
53+
path = `.${sep}${path}`;
54+
}
55+
return `${color ? (sev === "WARNING" ? colors.blue(sev) : colors.red(sev)) : sev}: ${path}:${lineAndCharacter.line + 1}:${lineAndCharacter.character + 1}`;
56+
}
57+
58+
function getLinkMaxSize(failures: Lint.RuleFailure[]): number {
59+
return max(failures, f => getLink(f, /*color*/ false).length);
60+
}
61+
62+
function getNameMaxSize(failures: Lint.RuleFailure[]): number {
63+
return max(failures, f => f.getRuleName().length);
64+
}
65+
66+
function pad(str: string, visiblelen: number, len: number) {
67+
if (visiblelen >= len) return str;
68+
const count = len - visiblelen;
69+
for (let i = 0; i < count; i++) {
70+
str += " ";
71+
}
72+
return str;
73+
}
74+
75+
export class Formatter extends Lint.Formatters.AbstractFormatter {
76+
public static metadata: Lint.IFormatterMetadata = {
77+
formatterName: "autolinkableStylish",
78+
description: "Human-readable formatter which creates stylish messages with autolinkable filepaths.",
79+
descriptionDetails: Lint.Utils.dedent`
80+
Colorized output grouped by file, with autolinkable filepaths containing line and column information
81+
`,
82+
sample: Lint.Utils.dedent`
83+
src/myFile.ts
84+
ERROR: src/myFile.ts:1:14 semicolon Missing semicolon`,
85+
consumer: "human"
86+
};
87+
public format(failures: Lint.RuleFailure[]): string {
88+
return groupBy(failures, f => f.getFileName()).map(group => {
89+
const currentFile = group[0].getFileName();
90+
const linkMaxSize = getLinkMaxSize(group);
91+
const nameMaxSize = getNameMaxSize(group);
92+
return `
93+
${currentFile}
94+
${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")}`;
95+
}).join("\n");
96+
}
97+
}
File renamed without changes.

0 commit comments

Comments
 (0)