diff --git a/README.md b/README.md index ea6689a38..96f4fd425 100644 --- a/README.md +++ b/README.md @@ -722,6 +722,15 @@ We recommend you specify exact versions of lint libraries, including `tslint-mic 4.0.2 + + + void-zero + + + void 0, which resolves to undefined, can be confusing to newcomers. Exclusively use undefined to reduce ambiguity. + + 6.1.0 + no-var-self diff --git a/recommended_ruleset.js b/recommended_ruleset.js index 4d523fdbf..a62c1cfac 100644 --- a/recommended_ruleset.js +++ b/recommended_ruleset.js @@ -181,6 +181,7 @@ module.exports = { 'unified-signatures': true, 'use-default-type-parameter': true, 'variable-name': true, + 'void-zero': true, /** * Accessibility. The following rules should be turned on to guarantee the best user diff --git a/src/voidZeroRule.ts b/src/voidZeroRule.ts new file mode 100644 index 000000000..4252033a0 --- /dev/null +++ b/src/voidZeroRule.ts @@ -0,0 +1,47 @@ +import * as ts from 'typescript'; +import * as Lint from 'tslint'; +import * as tsutils from 'tsutils'; + +import { ExtendedMetadata } from './utils/ExtendedMetadata'; + +const FAILURE_STRING: string = 'Replace void 0 with undefined'; + +export class Rule extends Lint.Rules.AbstractRule { + public static metadata: ExtendedMetadata = { + ruleName: 'void-zero', + type: 'maintainability', + description: 'Avoid using void 0; use undefined instead.', + hasFix: true, + rationale: 'void 0, which resolves to undefined, can be confusing to newcomers. Exclusively use undefined to reduce ambiguity.', + options: null, // tslint:disable-line:no-null-keyword + optionsDescription: '', + typescriptOnly: true, + issueClass: 'Non-SDL', + issueType: 'Warning', + severity: 'Low', + level: 'Opportunity for Excellence', + group: 'Clarity', + commonWeaknessEnumeration: '480' + }; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithFunction(sourceFile, walk); + } +} + +function walk(ctx: Lint.WalkContext) { + function cb(node: ts.Node): void { + if (tsutils.isVoidExpression(node)) { + if (node.expression !== undefined && node.expression.getText() === '0') { + const nodeStart = node.getStart(); + const nodeWidth = node.getWidth(); + const fix = new Lint.Replacement(nodeStart, nodeWidth, 'undefined'); + + ctx.addFailureAt(nodeStart, nodeWidth, FAILURE_STRING, fix); + } + } + return ts.forEachChild(node, cb); + } + + return ts.forEachChild(ctx.sourceFile, cb); +} diff --git a/tests/void-zero/test.ts.fix b/tests/void-zero/test.ts.fix new file mode 100644 index 000000000..8c5023dcc --- /dev/null +++ b/tests/void-zero/test.ts.fix @@ -0,0 +1,22 @@ +new Array(undefined); +function mockFunction(arg1: undefined, arg2: string) {} +mockFunction(undefined, 'arg2String') +const bar = undefined; +const foo = { + bar: undefined, +}; +class MockClass { + private foo = undefined + static bar = undefined +} + +new Array(undefined); +mockFunction(undefined, 'arg2String') +const bar = undefined; +const foo = { + bar: undefined, +}; +class MockClass { + private foo = undefined + static bar = undefined +} diff --git a/tests/void-zero/test.ts.lint b/tests/void-zero/test.ts.lint new file mode 100644 index 000000000..2c15c56da --- /dev/null +++ b/tests/void-zero/test.ts.lint @@ -0,0 +1,28 @@ +new Array(undefined); +function mockFunction(arg1: undefined, arg2: string) {} +mockFunction(undefined, 'arg2String') +const bar = undefined; +const foo = { + bar: undefined, +}; +class MockClass { + private foo = undefined + static bar = undefined +} + +new Array(void 0); + ~~~~~~ [Replace void 0 with undefined] +mockFunction(void 0, 'arg2String') + ~~~~~~ [Replace void 0 with undefined] +const bar = void 0; + ~~~~~~ [Replace void 0 with undefined] +const foo = { + bar: void 0, + ~~~~~~ [Replace void 0 with undefined] +}; +class MockClass { + private foo = void 0 + ~~~~~~ [Replace void 0 with undefined] + static bar = void 0 + ~~~~~~ [Replace void 0 with undefined] +} diff --git a/tests/void-zero/tslint.json b/tests/void-zero/tslint.json new file mode 100644 index 000000000..dc0c36389 --- /dev/null +++ b/tests/void-zero/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "void-zero": true + } +} diff --git a/tslint-warnings.csv b/tslint-warnings.csv index 2570d126a..5e3be3356 100644 --- a/tslint-warnings.csv +++ b/tslint-warnings.csv @@ -319,5 +319,6 @@ use-simple-attributes,Enforce usage of only simple attribute types.,TSLINT1PG0L9 valid-typeof,Ensures that the results of typeof are compared against a valid string.,TSLINT1IB59P1,tslint,Non-SDL,Error,Critical,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,, variable-name,Checks variable names for various errors.,TSLINT1CIV7K3,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,"398, 710","CWE 398 - Indicator of Poor Code Quality CWE 710 - Coding Standards Violation" +void-zero,Avoid using void 0; use undefined instead.,TSLINT1BDGNG2,tslint,Non-SDL,Warning,Low,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,480,"CWE 480 - Use of Incorrect Operator" whitespace,Enforces whitespace style conventions.,TSLINTC35UUS,tslint,Non-SDL,Warning,Low,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,"398, 710","CWE 398 - Indicator of Poor Code Quality CWE 710 - Coding Standards Violation" \ No newline at end of file diff --git a/tslint.json b/tslint.json index 7ef1eb817..a1e66a427 100644 --- a/tslint.json +++ b/tslint.json @@ -148,6 +148,7 @@ "switch-final-break": true, "type-literal-delimiter": false, "underscore-consistent-invocation": true, + "void-zero": true, "use-default-type-parameter": false, "use-named-parameter": true, "use-simple-attributes": true,