Skip to content

Commit 6117ed7

Browse files
author
Andy
authored
Merge pull request #14391 from Microsoft/lint_better
Update tslint to `latest` (`next` is still on 4.3) and lint for BOM
2 parents 89974bd + cc77225 commit 6117ed7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+248
-195
lines changed

Jakefile.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,8 @@ var tslintRules = [
11041104
"noInOperatorRule",
11051105
"noIncrementDecrementRule",
11061106
"objectLiteralSurroundingSpaceRule",
1107-
"noTypeAssertionWhitespaceRule"
1107+
"noTypeAssertionWhitespaceRule",
1108+
"noBomRule"
11081109
];
11091110
var tslintRulesFiles = tslintRules.map(function (p) {
11101111
return path.join(tslintRuleDir, p + ".ts");

scripts/tslint/booleanTriviaRule.ts

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,62 @@ import * as Lint from "tslint/lib";
22
import * as ts from "typescript";
33

44
export class Rule extends Lint.Rules.AbstractRule {
5-
public static FAILURE_STRING_FACTORY = (name: string, currently: string) => `Tag boolean argument as '${name}' (currently '${currently}')`;
5+
public static FAILURE_STRING_FACTORY(name: string, currently?: string): string {
6+
const current = currently ? ` (currently '${currently}')` : "";
7+
return `Tag boolean argument as '${name}'${current}`;
8+
}
69

710
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
11+
// Cheat to get type checker
812
const program = ts.createProgram([sourceFile.fileName], Lint.createCompilerOptions());
913
const checker = program.getTypeChecker();
10-
return this.applyWithWalker(new BooleanTriviaWalker(checker, program.getSourceFile(sourceFile.fileName), this.getOptions()));
14+
return this.applyWithFunction(program.getSourceFile(sourceFile.fileName), ctx => walk(ctx, checker));
1115
}
1216
}
1317

14-
class BooleanTriviaWalker extends Lint.RuleWalker {
15-
constructor(private checker: ts.TypeChecker, file: ts.SourceFile, opts: Lint.IOptions) {
16-
super(file, opts);
18+
function walk(ctx: Lint.WalkContext<void>, checker: ts.TypeChecker): void {
19+
ts.forEachChild(ctx.sourceFile, recur);
20+
function recur(node: ts.Node): void {
21+
if (node.kind === ts.SyntaxKind.CallExpression) {
22+
checkCall(node as ts.CallExpression);
23+
}
24+
ts.forEachChild(node, recur);
1725
}
1826

19-
visitCallExpression(node: ts.CallExpression) {
20-
super.visitCallExpression(node);
21-
if (node.arguments && node.arguments.some(arg => arg.kind === ts.SyntaxKind.TrueKeyword || arg.kind === ts.SyntaxKind.FalseKeyword)) {
22-
const targetCallSignature = this.checker.getResolvedSignature(node);
23-
if (!!targetCallSignature) {
24-
const targetParameters = targetCallSignature.getParameters();
25-
const source = this.getSourceFile();
26-
for (let index = 0; index < targetParameters.length; index++) {
27-
const param = targetParameters[index];
28-
const arg = node.arguments[index];
29-
if (!(arg && param)) {
30-
continue;
31-
}
32-
33-
const argType = this.checker.getContextualType(arg);
34-
if (argType && (argType.getFlags() & ts.TypeFlags.Boolean)) {
35-
if (arg.kind !== ts.SyntaxKind.TrueKeyword && arg.kind !== ts.SyntaxKind.FalseKeyword) {
36-
continue;
37-
}
38-
let triviaContent: string;
39-
const ranges = ts.getLeadingCommentRanges(arg.getFullText(), 0);
40-
if (ranges && ranges.length === 1 && ranges[0].kind === ts.SyntaxKind.MultiLineCommentTrivia) {
41-
triviaContent = arg.getFullText().slice(ranges[0].pos + 2, ranges[0].end - 2); // +/-2 to remove /**/
42-
}
43-
44-
const paramName = param.getName();
45-
if (triviaContent !== paramName && triviaContent !== paramName + ":") {
46-
this.addFailure(this.createFailure(arg.getStart(source), arg.getWidth(source), Rule.FAILURE_STRING_FACTORY(param.getName(), triviaContent)));
47-
}
48-
}
27+
function checkCall(node: ts.CallExpression): void {
28+
if (!node.arguments || !node.arguments.some(arg => arg.kind === ts.SyntaxKind.TrueKeyword || arg.kind === ts.SyntaxKind.FalseKeyword)) {
29+
return;
30+
}
31+
32+
const targetCallSignature = checker.getResolvedSignature(node);
33+
if (!targetCallSignature) {
34+
return;
35+
}
36+
37+
const targetParameters = targetCallSignature.getParameters();
38+
for (let index = 0; index < targetParameters.length; index++) {
39+
const param = targetParameters[index];
40+
const arg = node.arguments[index];
41+
if (!(arg && param)) {
42+
continue;
43+
}
44+
45+
const argType = checker.getContextualType(arg);
46+
if (argType && (argType.getFlags() & ts.TypeFlags.Boolean)) {
47+
if (arg.kind !== ts.SyntaxKind.TrueKeyword && arg.kind !== ts.SyntaxKind.FalseKeyword) {
48+
continue;
49+
}
50+
let triviaContent: string | undefined;
51+
const ranges = ts.getLeadingCommentRanges(arg.getFullText(), 0);
52+
if (ranges && ranges.length === 1 && ranges[0].kind === ts.SyntaxKind.MultiLineCommentTrivia) {
53+
triviaContent = arg.getFullText().slice(ranges[0].pos + 2, ranges[0].end - 2); // +/-2 to remove /**/
54+
}
55+
56+
const paramName = param.getName();
57+
if (triviaContent !== paramName && triviaContent !== paramName + ":") {
58+
ctx.addFailureAtNode(arg, Rule.FAILURE_STRING_FACTORY(param.getName(), triviaContent));
4959
}
5060
}
5161
}
5262
}
53-
}
63+
}

scripts/tslint/nextLineRule.ts

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,50 +9,56 @@ export class Rule extends Lint.Rules.AbstractRule {
99
public static ELSE_FAILURE_STRING = "'else' should not be on the same line as the preceeding block's curly brace";
1010

1111
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
12-
return this.applyWithWalker(new NextLineWalker(sourceFile, this.getOptions()));
12+
const options = this.getOptions().ruleArguments;
13+
const checkCatch = options.indexOf(OPTION_CATCH) !== -1;
14+
const checkElse = options.indexOf(OPTION_ELSE) !== -1;
15+
return this.applyWithFunction(sourceFile, ctx => walk(ctx, checkCatch, checkElse));
1316
}
1417
}
1518

16-
class NextLineWalker extends Lint.RuleWalker {
17-
public visitIfStatement(node: ts.IfStatement) {
18-
const sourceFile = node.getSourceFile();
19-
const thenStatement = node.thenStatement;
20-
21-
const elseStatement = node.elseStatement;
22-
if (!!elseStatement) {
23-
// find the else keyword
24-
const elseKeyword = getFirstChildOfKind(node, ts.SyntaxKind.ElseKeyword);
25-
if (this.hasOption(OPTION_ELSE) && !!elseKeyword) {
26-
const thenStatementEndLoc = sourceFile.getLineAndCharacterOfPosition(thenStatement.getEnd());
27-
const elseKeywordLoc = sourceFile.getLineAndCharacterOfPosition(elseKeyword.getStart(sourceFile));
28-
if (thenStatementEndLoc.line === elseKeywordLoc.line) {
29-
const failure = this.createFailure(elseKeyword.getStart(sourceFile), elseKeyword.getWidth(sourceFile), Rule.ELSE_FAILURE_STRING);
30-
this.addFailure(failure);
31-
}
32-
}
19+
function walk(ctx: Lint.WalkContext<void>, checkCatch: boolean, checkElse: boolean): void {
20+
const { sourceFile } = ctx;
21+
function recur(node: ts.Node): void {
22+
switch (node.kind) {
23+
case ts.SyntaxKind.IfStatement:
24+
checkIf(node as ts.IfStatement);
25+
break;
26+
case ts.SyntaxKind.TryStatement:
27+
checkTry(node as ts.TryStatement);
28+
break;
3329
}
34-
35-
super.visitIfStatement(node);
30+
ts.forEachChild(node, recur);
3631
}
3732

38-
public visitTryStatement(node: ts.TryStatement) {
39-
const sourceFile = node.getSourceFile();
40-
const catchClause = node.catchClause;
41-
42-
// "visit" try block
43-
const tryBlock = node.tryBlock;
44-
45-
if (this.hasOption(OPTION_CATCH) && !!catchClause) {
46-
const tryClosingBrace = tryBlock.getLastToken(sourceFile);
47-
const catchKeyword = catchClause.getFirstToken(sourceFile);
48-
const tryClosingBraceLoc = sourceFile.getLineAndCharacterOfPosition(tryClosingBrace.getEnd());
49-
const catchKeywordLoc = sourceFile.getLineAndCharacterOfPosition(catchKeyword.getStart(sourceFile));
50-
if (tryClosingBraceLoc.line === catchKeywordLoc.line) {
51-
const failure = this.createFailure(catchKeyword.getStart(sourceFile), catchKeyword.getWidth(sourceFile), Rule.CATCH_FAILURE_STRING);
52-
this.addFailure(failure);
33+
function checkIf(node: ts.IfStatement): void {
34+
const { thenStatement, elseStatement } = node;
35+
if (!elseStatement) {
36+
return;
37+
}
38+
39+
// find the else keyword
40+
const elseKeyword = getFirstChildOfKind(node, ts.SyntaxKind.ElseKeyword);
41+
if (checkElse && !!elseKeyword) {
42+
const thenStatementEndLoc = sourceFile.getLineAndCharacterOfPosition(thenStatement.getEnd());
43+
const elseKeywordLoc = sourceFile.getLineAndCharacterOfPosition(elseKeyword.getStart(sourceFile));
44+
if (thenStatementEndLoc.line === elseKeywordLoc.line) {
45+
ctx.addFailureAtNode(elseKeyword, Rule.ELSE_FAILURE_STRING);
5346
}
5447
}
55-
super.visitTryStatement(node);
48+
}
49+
50+
function checkTry({ tryBlock, catchClause }: ts.TryStatement): void {
51+
if (!checkCatch || !catchClause) {
52+
return;
53+
}
54+
55+
const tryClosingBrace = tryBlock.getLastToken(sourceFile);
56+
const catchKeyword = catchClause.getFirstToken(sourceFile);
57+
const tryClosingBraceLoc = sourceFile.getLineAndCharacterOfPosition(tryClosingBrace.getEnd());
58+
const catchKeywordLoc = sourceFile.getLineAndCharacterOfPosition(catchKeyword.getStart(sourceFile));
59+
if (tryClosingBraceLoc.line === catchKeywordLoc.line) {
60+
ctx.addFailureAtNode(catchKeyword, Rule.CATCH_FAILURE_STRING);
61+
}
5662
}
5763
}
5864

scripts/tslint/noBomRule.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as Lint from "tslint/lib";
2+
import * as ts from "typescript";
3+
4+
export class Rule extends Lint.Rules.AbstractRule {
5+
public static FAILURE_STRING = "This file has a BOM.";
6+
7+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
8+
return this.applyWithFunction(sourceFile, walk);
9+
}
10+
}
11+
12+
function walk(ctx: Lint.WalkContext<void>): void {
13+
if (ctx.sourceFile.text[0] === "\ufeff") {
14+
ctx.addFailure(0, 1, Rule.FAILURE_STRING);
15+
}
16+
}

scripts/tslint/noInOperatorRule.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import * as Lint from "tslint/lib";
22
import * as ts from "typescript";
33

4-
54
export class Rule extends Lint.Rules.AbstractRule {
65
public static FAILURE_STRING = "Don't use the 'in' keyword - use 'hasProperty' to check for key presence instead";
76

87
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
9-
return this.applyWithWalker(new InWalker(sourceFile, this.getOptions()));
8+
return this.applyWithFunction(sourceFile, walk);
109
}
1110
}
1211

13-
class InWalker extends Lint.RuleWalker {
14-
visitNode(node: ts.Node) {
15-
super.visitNode(node);
16-
if (node.kind === ts.SyntaxKind.InKeyword && node.parent && node.parent.kind === ts.SyntaxKind.BinaryExpression) {
17-
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING));
12+
function walk(ctx: Lint.WalkContext<void>): void {
13+
ts.forEachChild(ctx.sourceFile, recur);
14+
function recur(node: ts.Node): void {
15+
if (node.kind === ts.SyntaxKind.InKeyword && node.parent.kind === ts.SyntaxKind.BinaryExpression) {
16+
ctx.addFailureAtNode(node, Rule.FAILURE_STRING);
1817
}
1918
}
2019
}
Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,55 @@
11
import * as Lint from "tslint/lib";
22
import * as ts from "typescript";
33

4-
54
export class Rule extends Lint.Rules.AbstractRule {
65
public static POSTFIX_FAILURE_STRING = "Don't use '++' or '--' postfix operators outside statements or for loops.";
76
public static PREFIX_FAILURE_STRING = "Don't use '++' or '--' prefix operators.";
87

98
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
10-
return this.applyWithWalker(new IncrementDecrementWalker(sourceFile, this.getOptions()));
9+
return this.applyWithFunction(sourceFile, walk);
1110
}
1211
}
1312

14-
class IncrementDecrementWalker extends Lint.RuleWalker {
13+
function walk(ctx: Lint.WalkContext<void>): void {
14+
ts.forEachChild(ctx.sourceFile, recur);
15+
function recur(node: ts.Node): void {
16+
switch (node.kind) {
17+
case ts.SyntaxKind.PrefixUnaryExpression:
18+
const { operator } = node as ts.PrefixUnaryExpression;
19+
if (operator === ts.SyntaxKind.PlusPlusToken || operator === ts.SyntaxKind.MinusMinusToken) {
20+
check(node as ts.PrefixUnaryExpression);
21+
}
22+
break;
1523

16-
visitPostfixUnaryExpression(node: ts.PostfixUnaryExpression) {
17-
super.visitPostfixUnaryExpression(node);
18-
if (node.operator === ts.SyntaxKind.PlusPlusToken || node.operator == ts.SyntaxKind.MinusMinusToken) {
19-
this.visitIncrementDecrement(node);
24+
case ts.SyntaxKind.PostfixUnaryExpression:
25+
check(node as ts.PostfixUnaryExpression);
26+
break;
2027
}
2128
}
2229

23-
visitPrefixUnaryExpression(node: ts.PrefixUnaryExpression) {
24-
super.visitPrefixUnaryExpression(node);
25-
if (node.operator === ts.SyntaxKind.PlusPlusToken || node.operator == ts.SyntaxKind.MinusMinusToken) {
26-
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.PREFIX_FAILURE_STRING));
30+
function check(node: ts.UnaryExpression): void {
31+
if (!isAllowedLocation(node.parent!)) {
32+
ctx.addFailureAtNode(node, Rule.POSTFIX_FAILURE_STRING);
2733
}
2834
}
35+
}
2936

30-
visitIncrementDecrement(node: ts.UnaryExpression) {
31-
if (node.parent && (
32-
// Can be a statement
33-
node.parent.kind === ts.SyntaxKind.ExpressionStatement ||
34-
// Can be directly in a for-statement
35-
node.parent.kind === ts.SyntaxKind.ForStatement ||
36-
// Can be in a comma operator in a for statement (`for (let a = 0, b = 10; a < b; a++, b--)`)
37-
node.parent.kind === ts.SyntaxKind.BinaryExpression &&
38-
(<ts.BinaryExpression>node.parent).operatorToken.kind === ts.SyntaxKind.CommaToken &&
39-
node.parent.parent.kind === ts.SyntaxKind.ForStatement)) {
40-
return;
41-
}
42-
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.POSTFIX_FAILURE_STRING));
37+
function isAllowedLocation(node: ts.Node): boolean {
38+
switch (node.kind) {
39+
// Can be a statement
40+
case ts.SyntaxKind.ExpressionStatement:
41+
return true;
42+
43+
// Can be directly in a for-statement
44+
case ts.SyntaxKind.ForStatement:
45+
return true;
46+
47+
// Can be in a comma operator in a for statement (`for (let a = 0, b = 10; a < b; a++, b--)`)
48+
case ts.SyntaxKind.BinaryExpression:
49+
return (node as ts.BinaryExpression).operatorToken.kind === ts.SyntaxKind.CommaToken &&
50+
node.parent!.kind === ts.SyntaxKind.ForStatement;
51+
52+
default:
53+
return false;
4354
}
4455
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
import * as Lint from "tslint/lib";
22
import * as ts from "typescript";
33

4-
54
export class Rule extends Lint.Rules.AbstractRule {
65
public static TRAILING_FAILURE_STRING = "Excess trailing whitespace found around type assertion.";
76

87
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
9-
return this.applyWithWalker(new TypeAssertionWhitespaceWalker(sourceFile, this.getOptions()));
8+
return this.applyWithFunction(sourceFile, walk);
109
}
1110
}
1211

13-
class TypeAssertionWhitespaceWalker extends Lint.RuleWalker {
14-
public visitNode(node: ts.Node) {
12+
function walk(ctx: Lint.WalkContext<void>): void {
13+
ts.forEachChild(ctx.sourceFile, recur);
14+
function recur(node: ts.Node) {
1515
if (node.kind === ts.SyntaxKind.TypeAssertionExpression) {
1616
const refined = node as ts.TypeAssertion;
1717
const leftSideWhitespaceStart = refined.type.getEnd() + 1;
1818
const rightSideWhitespaceEnd = refined.expression.getStart();
1919
if (leftSideWhitespaceStart !== rightSideWhitespaceEnd) {
20-
this.addFailure(this.createFailure(leftSideWhitespaceStart, rightSideWhitespaceEnd, Rule.TRAILING_FAILURE_STRING));
20+
ctx.addFailure(leftSideWhitespaceStart, rightSideWhitespaceEnd, Rule.TRAILING_FAILURE_STRING);
2121
}
2222
}
23-
super.visitNode(node);
23+
ts.forEachChild(node, recur);
2424
}
2525
}

0 commit comments

Comments
 (0)