Skip to content

Commit e813963

Browse files
committed
Merge pull request #4458 from weswigham/tslint-rules
Add tslint rules for #3994
2 parents e200dad + 2793bc2 commit e813963

7 files changed

+222
-5
lines changed

Jakefile.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -780,17 +780,36 @@ task("update-sublime", ["local", serverFile], function() {
780780
jake.cpR(serverFile + ".map", "../TypeScript-Sublime-Plugin/tsserver/");
781781
});
782782

783+
var tslintRuleDir = "scripts/tslint";
784+
var tslintRules = ([
785+
"nextLineRule",
786+
"noInferrableTypesRule",
787+
"noNullRule",
788+
"booleanTriviaRule"
789+
]);
790+
var tslintRulesFiles = tslintRules.map(function(p) {
791+
return path.join(tslintRuleDir, p + ".ts");
792+
});
793+
var tslintRulesOutFiles = tslintRules.map(function(p) {
794+
return path.join(builtLocalDirectory, "tslint", p + ".js");
795+
});
796+
desc("Compiles tslint rules to js");
797+
task("build-rules", tslintRulesOutFiles);
798+
tslintRulesFiles.forEach(function(ruleFile, i) {
799+
compileFile(tslintRulesOutFiles[i], [ruleFile], [ruleFile], [], /*useBuiltCompiler*/ true, /*noOutFile*/ true, /*generateDeclarations*/ false, path.join(builtLocalDirectory, "tslint"));
800+
});
801+
783802
// if the codebase were free of linter errors we could make jake runtests
784803
// run this task automatically
785804
desc("Runs tslint on the compiler sources");
786-
task("lint", [], function() {
805+
task("lint", ["build-rules"], function() {
787806
function success(f) { return function() { console.log('SUCCESS: No linter errors in ' + f + '\n'); }};
788807
function failure(f) { return function() { console.log('FAILURE: Please fix linting errors in ' + f + '\n') }};
789808

790809
var lintTargets = compilerSources.concat(harnessCoreSources);
791810
for (var i in lintTargets) {
792811
var f = lintTargets[i];
793-
var cmd = 'tslint -c tslint.json ' + f;
812+
var cmd = 'tslint --rules-dir built/local/tslint -c tslint.json ' + f;
794813
exec(cmd, success(f), failure(f));
795814
}
796815
}, { async: true });

scripts/tslint/booleanTriviaRule.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/// <reference path="../../node_modules/tslint/typings/typescriptServices.d.ts" />
2+
/// <reference path="../../node_modules/tslint/lib/tslint.d.ts" />
3+
4+
5+
export class Rule extends Lint.Rules.AbstractRule {
6+
public static FAILURE_STRING_FACTORY = (name: string, currently: string) => `Tag boolean argument as '${name}' (currently '${currently}')`;
7+
8+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
9+
const program = ts.createProgram([sourceFile.fileName], Lint.createCompilerOptions());
10+
const checker = program.getTypeChecker();
11+
return this.applyWithWalker(new BooleanTriviaWalker(checker, program.getSourceFile(sourceFile.fileName), this.getOptions()));
12+
}
13+
}
14+
15+
class BooleanTriviaWalker extends Lint.RuleWalker {
16+
constructor(private checker: ts.TypeChecker, file: ts.SourceFile, opts: Lint.IOptions) {
17+
super(file, opts);
18+
}
19+
20+
visitCallExpression(node: ts.CallExpression) {
21+
super.visitCallExpression(node);
22+
if (node.arguments) {
23+
const targetCallSignature = this.checker.getResolvedSignature(node);
24+
if (!!targetCallSignature) {
25+
const targetParameters = targetCallSignature.getParameters();
26+
const source = this.getSourceFile();
27+
for (let index = 0; index < targetParameters.length; index++) {
28+
const param = targetParameters[index];
29+
const arg = node.arguments[index];
30+
if (!(arg && param)) continue;
31+
32+
const argType = this.checker.getContextualType(arg);
33+
if (argType && (argType.getFlags() & ts.TypeFlags.Boolean)) {
34+
if (arg.kind !== ts.SyntaxKind.TrueKeyword && arg.kind !== ts.SyntaxKind.FalseKeyword) {
35+
continue;
36+
}
37+
let triviaContent: string;
38+
const ranges = ts.getLeadingCommentRanges(arg.getFullText(), 0);
39+
if (ranges && ranges.length === 1 && ranges[0].kind === ts.SyntaxKind.MultiLineCommentTrivia) {
40+
triviaContent = arg.getFullText().slice(ranges[0].pos + 2, ranges[0].end - 2); //+/-2 to remove /**/
41+
}
42+
if (triviaContent !== param.getName()) {
43+
this.addFailure(this.createFailure(arg.getStart(source), arg.getWidth(source), Rule.FAILURE_STRING_FACTORY(param.getName(), triviaContent)));
44+
}
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}

scripts/tslint/nextLineRule.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/// <reference path="../../node_modules/tslint/typings/typescriptServices.d.ts" />
2+
/// <reference path="../../node_modules/tslint/lib/tslint.d.ts" />
3+
4+
const OPTION_CATCH = "check-catch";
5+
const OPTION_ELSE = "check-else";
6+
7+
export class Rule extends Lint.Rules.AbstractRule {
8+
public static CATCH_FAILURE_STRING = "'catch' should be on the line following the previous block's ending curly brace";
9+
public static ELSE_FAILURE_STRING = "'else' should be on the line following the previous block's ending curly brace";
10+
11+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
12+
return this.applyWithWalker(new NextLineWalker(sourceFile, this.getOptions()));
13+
}
14+
}
15+
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 - 1)) {
29+
const failure = this.createFailure(elseKeyword.getStart(sourceFile), elseKeyword.getWidth(sourceFile), Rule.ELSE_FAILURE_STRING);
30+
this.addFailure(failure);
31+
}
32+
}
33+
}
34+
35+
super.visitIfStatement(node);
36+
}
37+
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 - 1)) {
51+
const failure = this.createFailure(catchKeyword.getStart(sourceFile), catchKeyword.getWidth(sourceFile), Rule.CATCH_FAILURE_STRING);
52+
this.addFailure(failure);
53+
}
54+
}
55+
super.visitTryStatement(node);
56+
}
57+
}
58+
59+
function getFirstChildOfKind(node: ts.Node, kind: ts.SyntaxKind) {
60+
return node.getChildren().filter((child) => child.kind === kind)[0];
61+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/// <reference path="../../node_modules/tslint/typings/typescriptServices.d.ts" />
2+
/// <reference path="../../node_modules/tslint/lib/tslint.d.ts" />
3+
4+
5+
export class Rule extends Lint.Rules.AbstractRule {
6+
public static FAILURE_STRING_FACTORY = (type: string) => `LHS type (${type}) inferred by RHS expression, remove type annotation`;
7+
8+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
9+
return this.applyWithWalker(new InferrableTypeWalker(sourceFile, this.getOptions()));
10+
}
11+
}
12+
13+
class InferrableTypeWalker extends Lint.RuleWalker {
14+
visitVariableStatement(node: ts.VariableStatement) {
15+
node.declarationList.declarations.forEach(e => {
16+
if ((!!e.type) && (!!e.initializer)) {
17+
let failure: string;
18+
switch (e.type.kind) {
19+
case ts.SyntaxKind.BooleanKeyword:
20+
if (e.initializer.kind === ts.SyntaxKind.TrueKeyword || e.initializer.kind === ts.SyntaxKind.FalseKeyword) {
21+
failure = 'boolean';
22+
}
23+
break;
24+
case ts.SyntaxKind.NumberKeyword:
25+
if (e.initializer.kind === ts.SyntaxKind.NumericLiteral) {
26+
failure = 'number';
27+
}
28+
break;
29+
case ts.SyntaxKind.StringKeyword:
30+
switch (e.initializer.kind) {
31+
case ts.SyntaxKind.StringLiteral:
32+
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
33+
case ts.SyntaxKind.TemplateExpression:
34+
failure = 'string';
35+
break;
36+
default:
37+
break;
38+
}
39+
break;
40+
}
41+
if (failure) {
42+
this.addFailure(this.createFailure(e.type.getStart(), e.type.getWidth(), Rule.FAILURE_STRING_FACTORY(failure)));
43+
}
44+
}
45+
});
46+
47+
super.visitVariableStatement(node);
48+
}
49+
}

scripts/tslint/noNullRule.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path="../../node_modules/tslint/typings/typescriptServices.d.ts" />
2+
/// <reference path="../../node_modules/tslint/lib/tslint.d.ts" />
3+
4+
5+
export class Rule extends Lint.Rules.AbstractRule {
6+
public static FAILURE_STRING = "Don't use the 'null' keyword - use 'undefined' for missing values instead";
7+
8+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
9+
return this.applyWithWalker(new NullWalker(sourceFile, this.getOptions()));
10+
}
11+
}
12+
13+
class NullWalker extends Lint.RuleWalker {
14+
visitNode(node: ts.Node) {
15+
super.visitNode(node);
16+
if (node.kind === ts.SyntaxKind.NullKeyword) {
17+
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING));
18+
}
19+
}
20+
}

scripts/tslint/tsconfig.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"noImplicitAny": true,
4+
"module": "commonjs",
5+
"outDir": "../../built/local/tslint"
6+
}
7+
}

tslint.json

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"spaces"
99
],
1010
"one-line": [true,
11-
"check-open-brace"
11+
"check-open-brace",
12+
"check-whitespace"
1213
],
1314
"no-unreachable": true,
1415
"no-use-before-declare": true,
@@ -21,14 +22,24 @@
2122
"check-branch",
2223
"check-operator",
2324
"check-separator",
24-
"check-type"
25+
"check-type",
26+
"check-module"
2527
],
2628
"typedef-whitespace": [true, {
2729
"call-signature": "nospace",
2830
"index-signature": "nospace",
2931
"parameter": "nospace",
3032
"property-declaration": "nospace",
3133
"variable-declaration": "nospace"
32-
}]
34+
}],
35+
"next-line": [true,
36+
"check-catch",
37+
"check-else"
38+
],
39+
"no-internal-module": true,
40+
"no-trailing-whitespace": true,
41+
"no-inferrable-types": true,
42+
"no-null": true,
43+
"boolean-trivia": true
3344
}
3445
}

0 commit comments

Comments
 (0)