Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Support for ERROR & WARN per rule #1738

Merged
merged 52 commits into from Mar 1, 2017
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
b002f7c
Support config object in tslint.json
olore Nov 15, 2016
c597b28
Use arrayify to make tslint.json cleaner if only 1 option, remove con…
olore Nov 15, 2016
9afc794
Handle warnings in stylishFormatter, verboseFormatter & vsoFormatter
olore Nov 16, 2016
35add24
* Formatter#format now takes failures,warnings,fixes (was failures, f…
olore Nov 16, 2016
ba2b4e2
Fix formatter tests
olore Nov 16, 2016
47e610e
Set warnings to default to [] if none passed in
olore Nov 16, 2016
135f215
Merge branch 'master' into enhanced-config-options
olore Nov 16, 2016
7c5e844
cleanup
olore Nov 17, 2016
024570a
Merge branch 'master' into enhanced-config-options
olore Nov 17, 2016
2c74270
Merge branch 'master' into enhanced-config-options
olore Nov 17, 2016
0af2c4c
cleanup for public unveiling, prepping for critique
olore Nov 17, 2016
f666d7c
RuleFailure -> RuleViolation
olore Nov 18, 2016
8fd7469
WIP - broken tests
olore Nov 21, 2016
9ab5e56
Merge branch 'master' into extend-rule-failure-to-include-warnings
olore Nov 22, 2016
4e6c572
Merge branch 'master' into extend-rule-failure-to-include-warnings
olore Nov 22, 2016
5087498
Cleanup after comparison with master
olore Nov 22, 2016
cc31e19
Merge branch 'master' into enhanced-config-options
olore Nov 22, 2016
2868256
revert RuleViolation -> RuleFailure because it would break all custom…
olore Nov 23, 2016
556fd4b
Complete the reverting of 'violation' back to 'failure'
olore Nov 23, 2016
d427ae3
Reverting some changes after comparing to master
olore Nov 23, 2016
7700ba1
Merge branch 'master' into enhanced-config-options
olore Nov 24, 2016
2302949
Revert tslint.json as we're still using the 'latest' from npm to lint…
olore Nov 24, 2016
bb5d808
Merge branch 'master' into enhanced-config-options
olore Nov 30, 2016
ec26ccc
Merge branch 'master' into enhanced-config-options
olore Dec 5, 2016
86e535f
Merge branch 'master' into enhanced-config-options
olore Jan 3, 2017
2401d38
Merge branch 'master' into enhanced-config-options
olore Jan 5, 2017
258c41e
Merge branch 'master' into fix
olore Jan 17, 2017
2ce7074
post-merge cleanup
olore Jan 17, 2017
ac388b8
pmdFormatter - Change priority depending on level (3 = error, 4 = war…
olore Jan 17, 2017
dcc3916
msbuildFormatter - output 'error' and 'warning' based on RuleLevel
olore Jan 17, 2017
68d94a3
checkstyleFormatter - output 'error' and 'warning' based on RuleLevel
olore Jan 17, 2017
33959e2
proseFormatter - output 'error' and 'warning' based on RuleLevel
olore Jan 17, 2017
b7aabcb
Accept warn/WARN/warning/WARNING in config
olore Jan 17, 2017
c0ac041
Add breaking changes to CHANGELOG.md
olore Jan 18, 2017
4d97b14
Merge branch 'master' into enhanced-config-options
olore Jan 19, 2017
dec3e12
Refactor - move config/ tests to config-legacy/
olore Jan 20, 2017
bb86f73
Create config tests for new tslint.json format and split legacy tests…
olore Jan 20, 2017
05d324e
Add valid values for to changelog
olore Jan 20, 2017
0683ae0
Merge branch 'master' into enhanced-config-options-tests
olore Jan 30, 2017
6e9967b
Cleanup after merge
olore Jan 30, 2017
1b0aedc
Merge branch 'master' into enhanced-config-options
olore Jan 31, 2017
18fb432
RuleLevel -> RuleSeverity to align with ESLint
olore Feb 6, 2017
f73e930
Tests for setting severity with and without options
olore Feb 6, 2017
8e826ff
Merge branch 'master' into enhanced-config-options
olore Feb 6, 2017
1999a4e
Merge branch 'master' into enhanced-config-options
olore Feb 11, 2017
1d0a6e0
Update scripts for config-legacy/
olore Feb 11, 2017
d1016da
remove legacy tests and add boolean config inheirt test
Feb 28, 2017
82c7036
Merge branch 'master' of github.com:palantir/tslint into enhanced-con…
Feb 28, 2017
e7e99cc
fix up constructor args
Feb 28, 2017
7d38ffb
use string literal union instead of enum
Feb 28, 2017
96c1f6f
populate rule severity in failures without specifying as argument for…
Mar 1, 2017
c15eb34
only exit(2) if errors > 0. Otherwise, exit(0) even with warnings
Mar 1, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/_data/formatters.json
Expand Up @@ -35,7 +35,7 @@
{
"formatterName": "prose",
"description": "The default formatter which outputs simple human-readable messages.",
"sample": "myFile.ts[1, 14]: Missing semicolon",
"sample": "ERROR: myFile.ts[1, 14]: Missing semicolon",
"consumer": "human"
},
{
Expand All @@ -49,7 +49,7 @@
"formatterName": "verbose",
"description": "The human-readable formatter which includes the rule name in messages.",
"descriptionDetails": "The output is the same as the prose formatter with the rule name included",
"sample": "(semicolon) myFile.ts[1, 14]: Missing semicolon",
"sample": "ERROR: (semicolon) myFile.ts[1, 14]: Missing semicolon",
"consumer": "human"
},
{
Expand Down
2 changes: 1 addition & 1 deletion docs/formatters/prose/index.html
@@ -1,7 +1,7 @@
---
formatterName: prose
description: The default formatter which outputs simple human-readable messages.
sample: 'myFile.ts[1, 14]: Missing semicolon'
sample: 'ERROR: myFile.ts[1, 14]: Missing semicolon'
consumer: human
layout: formatter
title: 'Formatter: prose'
Expand Down
2 changes: 1 addition & 1 deletion docs/formatters/verbose/index.html
Expand Up @@ -2,7 +2,7 @@
formatterName: verbose
description: The human-readable formatter which includes the rule name in messages.
descriptionDetails: The output is the same as the prose formatter with the rule name included
sample: '(semicolon) myFile.ts[1, 14]: Missing semicolon'
sample: 'ERROR: (semicolon) myFile.ts[1, 14]: Missing semicolon'
consumer: human
layout: formatter
title: 'Formatter: verbose'
Expand Down
20 changes: 13 additions & 7 deletions src/formatters/proseFormatter.ts
Expand Up @@ -24,14 +24,14 @@ export class Formatter extends AbstractFormatter {
public static metadata: IFormatterMetadata = {
formatterName: "prose",
description: "The default formatter which outputs simple human-readable messages.",
sample: "myFile.ts[1, 14]: Missing semicolon",
sample: "ERROR: myFile.ts[1, 14]: Missing semicolon",
consumer: "human",
};
/* tslint:enable:object-literal-sort-keys */

public format(failures: RuleFailure[], fixes?: RuleFailure[]): string {
if (failures.length === 0 && (!fixes || fixes.length === 0)) {
return "";
public format(failures: RuleFailure[], warnings: RuleFailure[] = [], fixes?: RuleFailure[]): string {
if ((warnings && warnings.length === 0 && failures && failures.length === 0) && (!fixes || fixes.length === 0)) {
return "\n";
}

let fixLines: string[] = [];
Expand All @@ -52,16 +52,22 @@ export class Formatter extends AbstractFormatter {
fixLines.push(""); // add a blank line between fixes and failures
}

let errorLines = failures.map((failure: RuleFailure) => {
return fixLines.concat(this.mapToMessages("WARNING", warnings))
.concat(this.mapToMessages("ERROR", failures))
.join("\n") + "\n";

}

private mapToMessages(mode: string, failures: RuleFailure[]): string[] {
return failures.map((failure: RuleFailure) => {
const fileName = failure.getFileName();
const failureString = failure.getFailure();

const lineAndCharacter = failure.getStartPosition().getLineAndCharacter();
const positionTuple = `[${lineAndCharacter.line + 1}, ${lineAndCharacter.character + 1}]`;

return `${fileName}${positionTuple}: ${failureString}`;
return `${mode}: ${fileName}${positionTuple}: ${failureString}`;
});

return fixLines.concat(errorLines).join("\n") + "\n";
}
}
36 changes: 23 additions & 13 deletions src/formatters/stylishFormatter.ts
Expand Up @@ -38,11 +38,22 @@ export class Formatter extends AbstractFormatter {
};
/* tslint:enable:object-literal-sort-keys */

public format(failures: RuleFailure[]): string {
if (typeof failures[0] === "undefined") {
return "\n";
public format(failures: RuleFailure[], warnings: RuleFailure[] = []): string {
let outputLines = this.mapToMessages("WARNING", warnings)
.concat(this.mapToMessages("ERROR", failures));

// Removes initial blank line
if (outputLines[0] === "") {
outputLines.shift();
}

return outputLines.join("\n") + "\n";
}

private mapToMessages(mode: string, failures: RuleFailure[]): string[] {
if (!failures) {
return [];
}
const outputLines: string[] = [];
const positionMaxSize = this.getPositionMaxSize(failures);
const ruleMaxSize = this.getRuleMaxSize(failures);
Expand Down Expand Up @@ -71,21 +82,20 @@ export class Formatter extends AbstractFormatter {
const lineAndCharacter = failure.getStartPosition().getLineAndCharacter();

let positionTuple = `${lineAndCharacter.line + 1}:${lineAndCharacter.character + 1}`;
positionTuple = this.pad(positionTuple, positionMaxSize);
positionTuple = colors.red(positionTuple);
positionTuple = this.pad(positionTuple, positionMaxSize);

// Ouput
if (mode === "WARNING") {
positionTuple = colors.blue("WARNING: " + positionTuple);
} else {
positionTuple = colors.red("ERROR: " + positionTuple);
}

// Output
const output = `${positionTuple} ${ruleName} ${failureString}`;

outputLines.push(output);
}

// Removes initial blank line
if (outputLines[0] === "") {
outputLines.shift();
}

return outputLines.join("\n") + "\n\n";
return outputLines;
}

private pad(str: string, len: number): string {
Expand Down
16 changes: 11 additions & 5 deletions src/formatters/verboseFormatter.ts
Expand Up @@ -25,23 +25,29 @@ export class Formatter extends AbstractFormatter {
formatterName: "verbose",
description: "The human-readable formatter which includes the rule name in messages.",
descriptionDetails: "The output is the same as the prose formatter with the rule name included",
sample: "(semicolon) myFile.ts[1, 14]: Missing semicolon",
sample: "ERROR: (semicolon) myFile.ts[1, 14]: Missing semicolon",
consumer: "human",
};
/* tslint:enable:object-literal-sort-keys */

public format(failures: RuleFailure[]): string {
const outputLines = failures.map((failure: RuleFailure) => {
public format(failures: RuleFailure[], warnings: RuleFailure[] = []): string {

return this.mapToMessages("WARNING", warnings)
.concat(this.mapToMessages("ERROR", failures))
.join("\n") + "\n";
}

private mapToMessages(mode: string, failures: RuleFailure[]): string[] {
return failures.map((failure: RuleFailure) => {
const fileName = failure.getFileName();
const failureString = failure.getFailure();
const ruleName = failure.getRuleName();

const lineAndCharacter = failure.getStartPosition().getLineAndCharacter();
const positionTuple = "[" + (lineAndCharacter.line + 1) + ", " + (lineAndCharacter.character + 1) + "]";

return `(${ruleName}) ${fileName}${positionTuple}: ${failureString}`;
return `${mode}: (${ruleName}) ${fileName}${positionTuple}: ${failureString}`;
});

return outputLines.join("\n") + "\n";
}
}
6 changes: 4 additions & 2 deletions src/formatters/vsoFormatter.ts
Expand Up @@ -33,8 +33,10 @@ export class Formatter extends AbstractFormatter {
};
/* tslint:enable:object-literal-sort-keys */

public format(failures: RuleFailure[]): string {
const outputLines = failures.map((failure: RuleFailure) => {
public format(failures: RuleFailure[], warnings: RuleFailure[] = []): string {
const all = failures.concat(warnings);

const outputLines = all.map((failure: RuleFailure) => {
const fileName = failure.getFileName();
const failureString = failure.getFailure();
const lineAndCharacter = failure.getStartPosition().getLineAndCharacter();
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Expand Up @@ -35,6 +35,8 @@ export * from "./language/walker";
export * from "./language/formatter/formatter";

export interface LintResult {
warningCount: number;
warnings: RuleFailure[];
failureCount: number;
failures: RuleFailure[];
fixes?: RuleFailure[];
Expand Down
3 changes: 2 additions & 1 deletion src/language/formatter/formatter.ts
Expand Up @@ -50,7 +50,8 @@ export interface IFormatter {
/**
* Formats linter results
* @param {RuleFailure[]} failures Linter errors that were not fixed
* @param {RuleFailure[]} warnings Linter warnings that were not fixed
* @param {RuleFailure[]} fixes Fixed linter errors. Available when the `--fix` argument is used on the command line
*/
format(failures: RuleFailure[], fixes?: RuleFailure[]): string;
format(failures: RuleFailure[], warnings?: RuleFailure[], fixes?: RuleFailure[]): string;
}
11 changes: 11 additions & 0 deletions src/language/rule/abstractRule.ts
Expand Up @@ -17,6 +17,7 @@

import * as ts from "typescript";

import {arrayify} from "../../utils";
import {RuleWalker} from "../walker/ruleWalker";
import {IDisabledInterval, IOptions, IRule, IRuleMetadata, RuleFailure} from "./rule";

Expand All @@ -29,6 +30,8 @@ export abstract class AbstractRule implements IRule {

if (Array.isArray(value) && value.length > 1) {
ruleArguments = value.slice(1);
} else if (value.options) {
ruleArguments = arrayify(value.options);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allow the tslint.json maintainer to no be forced to put a single value in an array

}

this.options = {
Expand All @@ -49,6 +52,10 @@ export abstract class AbstractRule implements IRule {
return walker.getFailures();
}

public isWarning(): boolean {
return this.value.mode === "warn";
}

public isEnabled(): boolean {
const value = this.value;

Expand All @@ -60,6 +67,10 @@ export abstract class AbstractRule implements IRule {
return value[0];
}

if (value.mode !== "off") {
return true;
}

return false;
}
}
1 change: 1 addition & 0 deletions src/language/rule/rule.ts
Expand Up @@ -95,6 +95,7 @@ export interface IDisabledInterval {
export interface IRule {
getOptions(): IOptions;
isEnabled(): boolean;
isWarning(): boolean;
apply(sourceFile: ts.SourceFile): RuleFailure[];
applyWithWalker(walker: RuleWalker): RuleFailure[];
}
Expand Down
20 changes: 17 additions & 3 deletions src/linter.ts
Expand Up @@ -49,6 +49,7 @@ class Linter {
public static loadConfigurationFromPath = loadConfigurationFromPath;

private failures: RuleFailure[] = [];
private warnings: RuleFailure[] = [];
private fixes: RuleFailure[] = [];

/**
Expand Down Expand Up @@ -100,6 +101,7 @@ class Linter {
let sourceFile = this.getSourceFile(fileName, source);
let hasLinterRun = false;
let fileFailures: RuleFailure[] = [];
let fileWarnings: RuleFailure[] = [];

if (this.options.fix) {
this.fixes = [];
Expand All @@ -115,22 +117,32 @@ class Linter {
// reload AST if file is modified
sourceFile = this.getSourceFile(fileName, source);
}
fileFailures = fileFailures.concat(ruleFailures);
if (rule.isWarning()) {
fileWarnings = fileWarnings.concat(ruleFailures);
} else {
fileFailures = fileFailures.concat(ruleFailures);
}
}
hasLinterRun = true;
}

// make a 1st pass or make a 2nd pass if there were any fixes because the positions may be off
if (!hasLinterRun || this.fixes.length > 0) {
fileFailures = [];
fileWarnings = [];
for (let rule of enabledRules) {
const ruleFailures = this.applyRule(rule, sourceFile);
if (ruleFailures.length > 0) {
fileFailures = fileFailures.concat(ruleFailures);
if (rule.isWarning()) {
fileWarnings = fileWarnings.concat(ruleFailures);
} else {
fileFailures = fileFailures.concat(ruleFailures);
}
}
}
}
this.failures = this.failures.concat(fileFailures);
this.warnings = this.warnings.concat(fileWarnings);
}

public getResult(): LintResult {
Expand All @@ -145,14 +157,16 @@ class Linter {
throw new Error(`formatter '${formatterName}' not found`);
}

const output = formatter.format(this.failures, this.fixes);
const output = formatter.format(this.failures, this.warnings, this.fixes);

return {
failureCount: this.failures.length,
failures: this.failures,
fixes: this.fixes,
format: formatterName,
output,
warningCount: this.warnings.length,
warnings: this.warnings,
};
}

Expand Down
Expand Up @@ -12,6 +12,7 @@ var Rule = (function (_super) {
Rule.prototype.apply = function (sourceFile) {
return this.applyWithWalker(new NoFailWalker(sourceFile, this.getOptions()));
};
Rule.prototype.isWarning = function() { return false; };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure out where these files were generated from, so I just modified by hand.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the .ts files were checked in

return Rule;
})(Lint.Rules.AbstractRule);
exports.Rule = Rule;
Expand Down
Expand Up @@ -12,6 +12,7 @@ var Rule = (function (_super) {
Rule.prototype.apply = function (sourceFile) {
return this.applyWithWalker(new NoFailWalker(sourceFile, this.getOptions()));
};
Rule.prototype.isWarning = function() { return false; };
return Rule;
})(Lint.Rules.AbstractRule);
exports.Rule = Rule;
Expand Down
Expand Up @@ -12,6 +12,7 @@ var Rule = (function (_super) {
Rule.prototype.apply = function (sourceFile) {
return this.applyWithWalker(new NoFailWalker(sourceFile, this.getOptions()));
};
Rule.prototype.isWarning = function() { return false; };
return Rule;
})(Lint.Rules.AbstractRule);
exports.Rule = Rule;
Expand Down
1 change: 1 addition & 0 deletions test/files/custom-rules-2/noFailRule.js
Expand Up @@ -12,6 +12,7 @@ var Rule = (function (_super) {
Rule.prototype.apply = function (sourceFile) {
return this.applyWithWalker(new NoFailWalker(sourceFile, this.getOptions()));
};
Rule.prototype.isWarning = function() { return false; };
return Rule;
})(Lint.Rules.AbstractRule);
exports.Rule = Rule;
Expand Down
1 change: 1 addition & 0 deletions test/files/custom-rules/alwaysFailRule.js
Expand Up @@ -12,6 +12,7 @@ var Rule = (function (_super) {
Rule.prototype.apply = function (sourceFile) {
return this.applyWithWalker(new AlwaysFailWalker(sourceFile, this.getOptions()));
};
Rule.prototype.isWarning = function() { return false; };
return Rule;
})(Lint.Rules.AbstractRule);
exports.Rule = Rule;
Expand Down
12 changes: 6 additions & 6 deletions test/formatters/proseFormatterTests.ts
Expand Up @@ -39,9 +39,9 @@ describe("Prose Formatter", () => {
];

const expectedResult =
TEST_FILE + getPositionString(1, 1) + "first failure\n" +
TEST_FILE + getPositionString(2, 12) + "mid failure\n" +
TEST_FILE + getPositionString(9, 2) + "last failure\n";
"ERROR: " + TEST_FILE + getPositionString(1, 1) + "first failure\n" +
"ERROR: " + TEST_FILE + getPositionString(2, 12) + "mid failure\n" +
"ERROR: " + TEST_FILE + getPositionString(9, 2) + "last failure\n";

const actualResult = formatter.format(failures);
assert.equal(actualResult, expectedResult);
Expand All @@ -63,15 +63,15 @@ describe("Prose Formatter", () => {
const expectedResult =
`Fixed 2 error(s) in ${TEST_FILE}\n` +
`Fixed 1 error(s) in file2\n\n` +
`${TEST_FILE}${getPositionString(1, 1)}first failure\n`;
`ERROR: ${TEST_FILE}${getPositionString(1, 1)}first failure\n`;

const actualResult = formatter.format(failures, fixes);
const actualResult = formatter.format(failures, [], fixes);
assert.equal(actualResult, expectedResult);
});

it("handles no failures", () => {
const result = formatter.format([]);
assert.equal(result, "");
assert.equal(result, "\n");
});

function getPositionString(line: number, character: number) {
Expand Down