Merge branch 'master' into lint-linter

This commit is contained in:
Andy Hanson
2017-04-11 14:33:46 -07:00
37 changed files with 429 additions and 173 deletions

View File

@@ -8,43 +8,88 @@ export class Rule extends Lint.Rules.AbstractRule {
}
function walk(ctx: Lint.WalkContext<void>): void {
ts.forEachChild(ctx.sourceFile, recur);
function recur(node: ts.Node): void {
const { sourceFile } = ctx;
ts.forEachChild(sourceFile, function recur(node: ts.Node): void {
if (node.kind === ts.SyntaxKind.CallExpression) {
checkCall(node as ts.CallExpression);
}
ts.forEachChild(node, recur);
}
});
function checkCall(node: ts.CallExpression): void {
for (const arg of node.arguments) {
if (arg.kind !== ts.SyntaxKind.TrueKeyword && arg.kind !== ts.SyntaxKind.FalseKeyword) {
continue;
}
if (node.expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
const methodName = (node.expression as ts.PropertyAccessExpression).name.text
// Skip certain method names whose parameter names are not informative
if (methodName === 'set' ||
methodName === 'equal' ||
methodName === 'fail' ||
methodName === 'isTrue' ||
methodName === 'assert') {
continue;
}
}
else if (node.expression.kind === ts.SyntaxKind.Identifier) {
const functionName = (node.expression as ts.Identifier).text;
// Skip certain function names whose parameter names are not informative
if (functionName === 'assert') {
continue;
}
}
const ranges = ts.getLeadingCommentRanges(arg.getFullText(), 0);
if (!(ranges && ranges.length === 1 && ranges[0].kind === ts.SyntaxKind.MultiLineCommentTrivia)) {
ctx.addFailureAtNode(arg, "Tag boolean argument with parameter name");
if (!shouldIgnoreCalledExpression(node.expression)) {
for (const arg of node.arguments) {
checkArg(arg);
}
}
}
/** Skip certain function/method names whose parameter names are not informative. */
function shouldIgnoreCalledExpression(expression: ts.Expression): boolean {
if (expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
const methodName = (expression as ts.PropertyAccessExpression).name.text;
if (methodName.indexOf("set") === 0) {
return true;
}
switch (methodName) {
case "apply":
case "assert":
case "call":
case "equal":
case "fail":
case "isTrue":
case "output":
case "stringify":
return true;
}
}
else if (expression.kind === ts.SyntaxKind.Identifier) {
const functionName = (expression as ts.Identifier).text;
if (functionName.indexOf("set") === 0) {
return true;
}
switch (functionName) {
case "assert":
case "contains":
case "createAnonymousType":
case "createImportSpecifier":
case "createProperty":
case "createSignature":
case "resolveName":
return true;
}
}
return false;
}
function checkArg(arg: ts.Expression): void {
if (!isTrivia(arg)) {
return;
}
const ranges = ts.getTrailingCommentRanges(sourceFile.text, arg.pos) || ts.getLeadingCommentRanges(sourceFile.text, arg.pos);
if (ranges === undefined || ranges.length !== 1 || ranges[0].kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
ctx.addFailureAtNode(arg, "Tag boolean argument with parameter name");
return;
}
const range = ranges[0];
const argStart = arg.getStart(sourceFile);
if (range.end + 1 !== argStart && sourceFile.text.slice(range.end, argStart).indexOf("\n") === -1) {
ctx.addFailureAtNode(arg, "There should be 1 space between an argument and its comment.");
}
}
function isTrivia(arg: ts.Expression): boolean {
switch (arg.kind) {
case ts.SyntaxKind.TrueKeyword:
case ts.SyntaxKind.FalseKeyword:
case ts.SyntaxKind.NullKeyword:
return true;
case ts.SyntaxKind.Identifier:
return (arg as ts.Identifier).originalKeywordKind === ts.SyntaxKind.UndefinedKeyword;
default:
return false;
}
}
}