Add tslint rules for #3994

This commit is contained in:
Wesley Wigham
2015-08-25 18:09:32 -07:00
parent ab9cf955a9
commit c31ad6fb28
5 changed files with 133 additions and 5 deletions

View File

@@ -770,17 +770,34 @@ task("update-sublime", ["local", serverFile], function() {
jake.cpR(serverFile + ".map", "../TypeScript-Sublime-Plugin/tsserver/");
});
var tslintRuleDir = "scripts/tslint";
var tslintRules = ([
"nextLineRule",
"noInferrableTypesRule"
]);
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");
});
desc("Compiles tslint rules to js");
task("build-rules", tslintRulesOutFiles);
tslintRulesFiles.forEach(function(ruleFile, i) {
compileFile(tslintRulesOutFiles[i], [ruleFile], [ruleFile], [], /*useBuiltCompiler*/ true, /*noOutFile*/ true, /*generateDeclarations*/ false, path.join(builtLocalDirectory, "tslint"));
});
// if the codebase were free of linter errors we could make jake runtests
// run this task automatically
desc("Runs tslint on the compiler sources");
task("lint", [], function() {
task("lint", ["build-rules"], function() {
function success(f) { return function() { console.log('SUCCESS: No linter errors in ' + f + '\n'); }};
function failure(f) { return function() { console.log('FAILURE: Please fix linting errors in ' + f + '\n') }};
var lintTargets = compilerSources.concat(harnessCoreSources);
for (var i in lintTargets) {
var f = lintTargets[i];
var cmd = 'tslint -c tslint.json ' + f;
var cmd = 'tslint --rules-dir built/local/tslint -c tslint.json ' + f;
exec(cmd, success(f), failure(f));
}
}, { async: true });

View File

@@ -0,0 +1,63 @@
/// <reference path="../../lib/typescriptServices.d.ts" />
/// <reference path="../../node_modules/tslint/lib/tslint.d.ts" />
const OPTION_CATCH = "check-catch";
const OPTION_ELSE = "check-else";
export class Rule extends Lint.Rules.AbstractRule {
public static CATCH_FAILURE_STRING = "'catch' should be on the line following the previous block's ending curly brace";
public static ELSE_FAILURE_STRING = "'else' should be on the line following the previous block's ending curly brace";
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new NextLineWalker(sourceFile, this.getOptions()));
}
}
class NextLineWalker extends Lint.RuleWalker {
public visitIfStatement(node: ts.IfStatement) {
const sourceFile = node.getSourceFile();
const thenStatement = node.thenStatement;
const elseStatement = node.elseStatement;
if (!!elseStatement) {
// find the else keyword
const elseKeyword = getFirstChildOfKind(node, ts.SyntaxKind.ElseKeyword);
if (this.hasOption(OPTION_ELSE) && !!elseKeyword) {
const thenStatementEndLoc = sourceFile.getLineAndCharacterOfPosition(thenStatement.getEnd());
const elseKeywordLoc = sourceFile.getLineAndCharacterOfPosition(elseKeyword.getStart());
if (thenStatementEndLoc.line !== (elseKeywordLoc.line - 1)) {
const failure = this.createFailure(elseKeyword.getStart(), elseKeyword.getWidth(), Rule.ELSE_FAILURE_STRING);
this.addFailure(failure);
}
}
}
super.visitIfStatement(node);
}
public visitTryStatement(node: ts.TryStatement) {
const sourceFile = node.getSourceFile();
const catchClause = node.catchClause;
// "visit" try block
const tryKeyword = node.getChildAt(0);
const tryBlock = node.tryBlock;
const tryOpeningBrace = tryBlock.getChildAt(0);
if (this.hasOption(OPTION_CATCH) && !!catchClause) {
const tryClosingBrace = node.tryBlock.getChildAt(node.tryBlock.getChildCount() - 1);
const catchKeyword = catchClause.getChildAt(0);
const tryClosingBraceLoc = sourceFile.getLineAndCharacterOfPosition(tryClosingBrace.getEnd());
const catchKeywordLoc = sourceFile.getLineAndCharacterOfPosition(catchKeyword.getStart());
if (tryClosingBraceLoc.line !== (catchKeywordLoc.line - 1)) {
const failure = this.createFailure(catchKeyword.getStart(), catchKeyword.getWidth(), Rule.CATCH_FAILURE_STRING);
this.addFailure(failure);
}
}
super.visitTryStatement(node);
}
}
function getFirstChildOfKind(node: ts.Node, kind: ts.SyntaxKind) {
return node.getChildren().filter((child) => child.kind === kind)[0];
}

View File

@@ -0,0 +1,32 @@
/// <reference path="../../lib/typescriptServices.d.ts" />
/// <reference path="../../node_modules/tslint/lib/tslint.d.ts" />
export class Rule extends Lint.Rules.AbstractRule {
public static FAILURE_STRING = "LHS type inferred by RHS expression, remove type annotation";
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
const program = ts.createProgram([sourceFile.fileName], Lint.createCompilerOptions());
return this.applyWithWalker(new InferrableTypeWalker(sourceFile, this.getOptions(), program));
}
}
class InferrableTypeWalker extends Lint.RuleWalker {
constructor(file: ts.SourceFile, opts: Lint.IOptions, private program: ts.Program) {
super(program.getSourceFile(file.fileName), opts);
}
visitVariableStatement(node: ts.VariableStatement) {
node.declarationList.declarations.forEach(e => {
if (
(!!e.type) &&
(!!e.initializer) &&
(this.program.getTypeChecker().getTypeAtLocation(e.type) === this.program.getTypeChecker().getContextualType(e.initializer))
) {
this.addFailure(this.createFailure(e.type.getStart(), e.type.getWidth(), Rule.FAILURE_STRING));
}
});
super.visitVariableStatement(node);
}
}

View File

@@ -0,0 +1,7 @@
{
"compilerOptions": {
"noImplicitAny": true,
"module": "commonjs",
"outDir": "../../built/local/tslint"
}
}

View File

@@ -8,7 +8,8 @@
"spaces"
],
"one-line": [true,
"check-open-brace"
"check-open-brace",
"check-whitespace"
],
"no-unreachable": true,
"no-use-before-declare": true,
@@ -21,7 +22,8 @@
"check-branch",
"check-operator",
"check-separator",
"check-type"
"check-type",
"check-module"
],
"typedef-whitespace": [true, {
"call-signature": "nospace",
@@ -29,6 +31,13 @@
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}]
}],
"next-line": [true,
"check-catch",
"check-else"
],
"no-internal-module": true,
"no-trailing-whitespace": true,
"no-inferrable-types": true
}
}