From f49d432fdc6e0cbe5cca574159e7e2f857daa542 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 16 May 2018 14:57:28 +0200 Subject: Implement custom no-magic-numbers rule that doesn't include magic numbers passed into BigNumber instantiations (e.g const amount = new BigNumber(5); ) --- packages/tslint-config/package.json | 3 +- .../rules/customNoMagicNumbersRule.ts | 76 ++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 packages/tslint-config/rules/customNoMagicNumbersRule.ts (limited to 'packages/tslint-config') diff --git a/packages/tslint-config/package.json b/packages/tslint-config/package.json index 04e1ba2b6..33132210c 100644 --- a/packages/tslint-config/package.json +++ b/packages/tslint-config/package.json @@ -41,7 +41,8 @@ "lodash": "^4.17.4", "tslint": "5.8.0", "tslint-eslint-rules": "^4.1.1", - "tslint-react": "^3.2.0" + "tslint-react": "^3.2.0", + "tsutils": "^2.12.1" }, "publishConfig": { "access": "public" diff --git a/packages/tslint-config/rules/customNoMagicNumbersRule.ts b/packages/tslint-config/rules/customNoMagicNumbersRule.ts new file mode 100644 index 000000000..e358221eb --- /dev/null +++ b/packages/tslint-config/rules/customNoMagicNumbersRule.ts @@ -0,0 +1,76 @@ +import * as Lint from 'tslint'; +import { isPrefixUnaryExpression } from 'tsutils'; +import * as ts from 'typescript'; + +/** + * A modified version of the no-magic-numbers rule that allows for magic numbers + * when instantiating a BigNumber instance. + * E.g We want to be able to write: + * const amount = new BigNumber(5); + * Original source: https://github.com/palantir/tslint/blob/42b058a6baa688f8be8558b277eb056c3ff79818/src/rules/noMagicNumbersRule.ts + */ +export class Rule extends Lint.Rules.AbstractRule { + public static ALLOWED_NODES = new Set([ + ts.SyntaxKind.ExportAssignment, + ts.SyntaxKind.FirstAssignment, + ts.SyntaxKind.LastAssignment, + ts.SyntaxKind.PropertyAssignment, + ts.SyntaxKind.ShorthandPropertyAssignment, + ts.SyntaxKind.VariableDeclaration, + ts.SyntaxKind.VariableDeclarationList, + ts.SyntaxKind.EnumMember, + ts.SyntaxKind.PropertyDeclaration, + ts.SyntaxKind.Parameter, + ]); + + public static DEFAULT_ALLOWED = [-1, 0, 1]; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + const allowedNumbers = this.ruleArguments.length > 0 ? this.ruleArguments : Rule.DEFAULT_ALLOWED; + return this.applyWithWalker( + new CustomNoMagicNumbersWalker(sourceFile, this.ruleName, new Set(allowedNumbers.map(String))), + ); + } +} + +// tslint:disable-next-line:max-classes-per-file +class CustomNoMagicNumbersWalker extends Lint.AbstractWalker> { + public static FAILURE_STRING = "'magic numbers' are not allowed"; + private static _isNegativeNumberLiteral( + node: ts.Node, + ): node is ts.PrefixUnaryExpression & { operand: ts.NumericLiteral } { + return ( + isPrefixUnaryExpression(node) && + node.operator === ts.SyntaxKind.MinusToken && + node.operand.kind === ts.SyntaxKind.NumericLiteral + ); + } + public walk(sourceFile: ts.SourceFile): void { + const cb = (node: ts.Node): void => { + if (node.kind === ts.SyntaxKind.NumericLiteral) { + return this.checkNumericLiteral(node, (node as ts.NumericLiteral).text); + } + if (CustomNoMagicNumbersWalker._isNegativeNumberLiteral(node)) { + return this.checkNumericLiteral(node, `-${(node.operand as ts.NumericLiteral).text}`); + } + return ts.forEachChild(node, cb); + }; + return ts.forEachChild(sourceFile, cb); + } + + // tslint:disable:no-non-null-assertion + // tslint:disable-next-line:underscore-private-and-protected + private checkNumericLiteral(node: ts.Node, num: string): void { + if (!Rule.ALLOWED_NODES.has(node.parent!.kind) && !this.options.has(num)) { + if (node.parent!.kind === ts.SyntaxKind.NewExpression) { + const className = (node.parent! as any).expression.escapedText; + const BIG_NUMBER_NEW_EXPRESSION = 'BigNumber'; + if (className === BIG_NUMBER_NEW_EXPRESSION) { + return; // noop + } + } + this.addFailureAtNode(node, CustomNoMagicNumbersWalker.FAILURE_STRING); + } + } + // tslint:enable:no-non-null-assertion +} -- cgit From 45fa26dc6cf48d8b8baf123429f0b64ad0a431c9 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 16 May 2018 14:58:01 +0200 Subject: Implement boolean variable naming custom TSLint rule --- packages/tslint-config/rules/booleanNamingRule.ts | 69 +++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 packages/tslint-config/rules/booleanNamingRule.ts (limited to 'packages/tslint-config') diff --git a/packages/tslint-config/rules/booleanNamingRule.ts b/packages/tslint-config/rules/booleanNamingRule.ts new file mode 100644 index 000000000..bc0b42e71 --- /dev/null +++ b/packages/tslint-config/rules/booleanNamingRule.ts @@ -0,0 +1,69 @@ +import * as _ from 'lodash'; +import * as Lint from 'tslint'; +import * as ts from 'typescript'; + +const VALID_BOOLEAN_PREFIXES = ['is', 'does', 'should', 'was', 'has', 'can', 'did', 'would']; + +export class Rule extends Lint.Rules.TypedRule { + public static FAILURE_STRING = `Boolean variable names should begin with: ${VALID_BOOLEAN_PREFIXES.join(', ')}`; + + public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { + return this.applyWithFunction(sourceFile, walk, undefined, program.getTypeChecker()); + } +} + +function walk(ctx: Lint.WalkContext, tc: ts.TypeChecker): void { + traverse(ctx.sourceFile); + + function traverse(node: ts.Node): void { + checkNodeForViolations(ctx, node, tc); + return ts.forEachChild(node, traverse); + } +} + +function checkNodeForViolations(ctx: Lint.WalkContext, node: ts.Node, tc: ts.TypeChecker): void { + switch (node.kind) { + // Handle: const { timestamp } = ... + case ts.SyntaxKind.BindingElement: { + const bindingElementNode = node as ts.BindingElement; + if (bindingElementNode.name.kind === ts.SyntaxKind.Identifier) { + handleBooleanNaming(bindingElementNode, tc, ctx); + } + break; + } + + // Handle regular assignments: const block = ... + case ts.SyntaxKind.VariableDeclaration: + const variableDeclarationNode = node as ts.VariableDeclaration; + if (variableDeclarationNode.name.kind === ts.SyntaxKind.Identifier) { + handleBooleanNaming(node as ts.VariableDeclaration, tc, ctx); + } + break; + + default: + // noop + } +} + +function handleBooleanNaming( + node: ts.VariableDeclaration | ts.BindingElement, + tc: ts.TypeChecker, + ctx: Lint.WalkContext, +): void { + const nodeName = node.name; + const variableName = nodeName.getText(); + const lowercasedName = _.toLower(variableName); + // tslint:disable-next-line + const typeNode = tc.getTypeAtLocation(node); + const typeName = (typeNode as any).intrinsicName; + if (typeName === 'boolean') { + const hasProperName = !_.isUndefined( + _.find(VALID_BOOLEAN_PREFIXES, prefix => { + return _.startsWith(lowercasedName, prefix); + }), + ); + if (!hasProperName) { + ctx.addFailureAtNode(node, Rule.FAILURE_STRING); + } + } +} -- cgit From fec6ac3ff0c4781cea0b3f42e287aee2b8aa2a79 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 16 May 2018 14:58:28 +0200 Subject: Enforce new rules, including no-switch-case-fall-through --- packages/tslint-config/tslint.json | 3 +++ 1 file changed, 3 insertions(+) (limited to 'packages/tslint-config') diff --git a/packages/tslint-config/tslint.json b/packages/tslint-config/tslint.json index 1d717430d..77a1f41cc 100644 --- a/packages/tslint-config/tslint.json +++ b/packages/tslint-config/tslint.json @@ -5,7 +5,10 @@ "arrow-parens": [true, "ban-single-arg-parens"], "arrow-return-shorthand": true, "async-suffix": true, + "boolean-naming": true, + "no-switch-case-fall-through": true, "await-promise": true, + "custom-no-magic-numbers": [true, 0, 1, 2, 3, -1], "binary-expression-operand-order": true, "callable-types": true, "class-name": true, -- cgit From b2f362225c5c03526852fc6b997241faba6d1231 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 16 May 2018 15:14:31 +0200 Subject: Fix comments --- packages/tslint-config/rules/booleanNamingRule.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages/tslint-config') diff --git a/packages/tslint-config/rules/booleanNamingRule.ts b/packages/tslint-config/rules/booleanNamingRule.ts index bc0b42e71..f673afc6a 100644 --- a/packages/tslint-config/rules/booleanNamingRule.ts +++ b/packages/tslint-config/rules/booleanNamingRule.ts @@ -41,7 +41,7 @@ function checkNodeForViolations(ctx: Lint.WalkContext, node: ts.Node, tc: break; default: - // noop + _.noop(); } } @@ -53,7 +53,6 @@ function handleBooleanNaming( const nodeName = node.name; const variableName = nodeName.getText(); const lowercasedName = _.toLower(variableName); - // tslint:disable-next-line const typeNode = tc.getTypeAtLocation(node); const typeName = (typeNode as any).intrinsicName; if (typeName === 'boolean') { -- cgit