Skip to content

Commit

Permalink
Added increment-decrement rule (palantir#3575)
Browse files Browse the repository at this point in the history
* Added increment-decrement rule

Bans ++/-- unary expressions in favor of the += equivalents. Allows an `"allow-post"` option for the i++ scenario.

* Used tsutils' expresion checkers

* Added increment-decrement to all.ts

I always forget to do this...

* Added test fix checks and parenthesis for binary expression children

* A new lint fix in src

* Test commit: hello

* Added extra tests; undid repo changes

* Undid husky changes

* Undid whitespace changes to tslint.json

* Also test/utils.ts

* There's always room for a quick typo edit :)
  • Loading branch information
Josh Goldberg authored and johnwiseheart committed Aug 22, 2018
1 parent 52643a0 commit 98f8dce
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ export const rules = {
"encoding": true,
"file-name-casing": [true, "camel-case"],
"import-spacing": true,
"increment-decrement": true,
"interface-name": true,
"interface-over-type-literal": true,
"jsdoc-format": [true, "check-multiline-start"],
Expand Down
117 changes: 117 additions & 0 deletions src/rules/incrementDecrementRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* @license
* Copyright 2013 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as tsutils from "tsutils";
import * as ts from "typescript";

import * as Lint from "../index";

interface Options {
allowPost: boolean;
}

const OPTION_ALLOW_POST = "allow-post";

export class Rule extends Lint.Rules.AbstractRule {
public static metadata: Lint.IRuleMetadata = {
description: "Enforces using explicit += 1 or -= 1 operators.",
optionExamples: [
true,
[true, OPTION_ALLOW_POST],
],
options: {
items: {
enum: [OPTION_ALLOW_POST],
type: "string",
},
maxLength: 1,
minLength: 0,
type: "array",
},
optionsDescription: Lint.Utils.dedent`
If no arguments are provided, both pre- and post-unary operators are banned.
If \`"${OPTION_ALLOW_POST}"\` is provided, post-unary operators will be allowed.
`,
rationale: Lint.Utils.dedent`
It's easy to type +i or -i instead of --i or ++i, and won't always result in invalid code.
Prefer standardizing small arithmetic operations with the explicit += and -= operators.
`,
ruleName: "increment-decrement",
type: "style",
typescriptOnly: false,
};

public static FAILURE_STRING_FACTORY = (newOperatorText: string) =>
`Use an explicit ${newOperatorText} operator.`

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
const options: Options = {
allowPost: this.ruleArguments.indexOf(OPTION_ALLOW_POST) !== -1,
};

return this.applyWithFunction(sourceFile, walk, options);
}
}

function walk(context: Lint.WalkContext<Options>) {
function createReplacement(node: ts.PostfixUnaryExpression | ts.PrefixUnaryExpression, newOperatorText: string): Lint.Replacement {
let text = `${node.operand.getText(context.sourceFile)} ${newOperatorText}`;

if (node.parent !== undefined && tsutils.isBinaryExpression(node.parent)) {
text = `(${text})`;
}

return Lint.Replacement.replaceNode(node, text);
}

function complainOnNode(node: ts.PostfixUnaryExpression | ts.PrefixUnaryExpression) {
const newOperatorText = node.operator === ts.SyntaxKind.PlusPlusToken
? "+= 1"
: "-= 1";
const replacement = createReplacement(node, newOperatorText);

const failure = Rule.FAILURE_STRING_FACTORY(newOperatorText);

context.addFailureAtNode(node, failure, replacement);
}

function checkPostfixUnaryExpression(node: ts.PostfixUnaryExpression): void {
if (!context.options.allowPost && isIncrementOrDecrementOperator(node.operator)) {
complainOnNode(node);
}
}

function checkPrefixUnaryExpression(node: ts.PrefixUnaryExpression): void {
if (isIncrementOrDecrementOperator(node.operator)) {
complainOnNode(node);
}
}

return ts.forEachChild(context.sourceFile, function callback(node: ts.Node): void {
if (tsutils.isPostfixUnaryExpression(node)) {
checkPostfixUnaryExpression(node);
} else if (tsutils.isPrefixUnaryExpression(node)) {
checkPrefixUnaryExpression(node);
}

return ts.forEachChild(node, callback);
});
}

function isIncrementOrDecrementOperator(operator: ts.PostfixUnaryOperator | ts.PrefixUnaryOperator) {
return operator === ts.SyntaxKind.PlusPlusToken || operator === ts.SyntaxKind.MinusMinusToken;
}
20 changes: 20 additions & 0 deletions test/rules/increment-decrement/allow-post/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
let x = 7;

x += 1;
x++;

x -= 1;
x--;

+x;
-x;
x + 1;
x - 1;
1 + x;
1 - x;

x + (x += 1);
x + x++;

x - (x -= 1);
x - x--;
26 changes: 26 additions & 0 deletions test/rules/increment-decrement/allow-post/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
let x = 7;

++x;
~~~ [plus]
x++;

--x;
~~~ [minus]
x--;

+x;
-x;
x + 1;
x - 1;
1 + x;
1 - x;

x + ++x;
~~~ [plus]
x + x++;

x - --x;
~~~ [minus]
x - x--;
[plus]: Use an explicit += 1 operator.
[minus]: Use an explicit -= 1 operator.
5 changes: 5 additions & 0 deletions test/rules/increment-decrement/allow-post/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"increment-decrement": [true, "allow-post"]
}
}
26 changes: 26 additions & 0 deletions test/rules/increment-decrement/default/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
let x = 7;

x += 1;
x += 1;

x -= 1;
x -= 1;

+x;
-x;
x + 1;
x - 1;
1 + x;
1 - x;

x + (x += 1);
x + (x += 1);

x - (x -= 1);
x - (x -= 1);

(x += 1) + x;
(x += 1) + x;

(x -= 1) - x;
(x -= 1) - x;
40 changes: 40 additions & 0 deletions test/rules/increment-decrement/default/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
let x = 7;

++x;
~~~ [plus]
x++;
~~~ [plus]

--x;
~~~ [minus]
x--;
~~~ [minus]

+x;
-x;
x + 1;
x - 1;
1 + x;
1 - x;

x + ++x;
~~~ [plus]
x + x++;
~~~ [plus]

x - --x;
~~~ [minus]
x - x--;
~~~ [minus]

++x + x;
~~~ [plus]
x++ + x;
~~~ [plus]

--x - x;
~~~ [minus]
x-- - x;
~~~ [minus]
[plus]: Use an explicit += 1 operator.
[minus]: Use an explicit -= 1 operator.
5 changes: 5 additions & 0 deletions test/rules/increment-decrement/default/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"increment-decrement": true
}
}
1 change: 1 addition & 0 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"rules": {
// Don't want these
"cyclomatic-complexity": false,
"increment-decrement": false,
"newline-before-return": false,
"no-parameter-properties": false,
"no-parameter-reassignment": false,
Expand Down

0 comments on commit 98f8dce

Please sign in to comment.