This repository has been archived by the owner on Mar 25, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 886
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added no-tautology-expressions rule (#4470)
* add new rule implementation * add some basic unit tests * fix some issues in rule * add noTautologyExpressionRule, add unit tests * add examples to rule description, limit rule to validate only relational/logical expressions, add tests * fix typo in isRelationalOrLogicalOperator * modify isLiteral to use tsutils modified walk to cover additional cases add property access unit tests * modify error message * add checks for ts.NullLiteral, ts.BooleanLiteral add unit tests
- Loading branch information
1 parent
c5da14a
commit 55160ae
Showing
4 changed files
with
266 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/** | ||
* @license | ||
* Copyright 2019 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"; | ||
|
||
const TAUTOLOGY_DISCOVERED_ERROR_STRING = | ||
"Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction."; | ||
export class Rule extends Lint.Rules.AbstractRule { | ||
public static metadata: Lint.IRuleMetadata = { | ||
description: Lint.Utils.dedent` | ||
Enforces that relational/equality binary operators does not take two equal variables/literals as operands. | ||
Expression like 3 === 3, someVar === someVar, "1" > "1" are either a tautology or contradiction, and will produce an error. | ||
`, | ||
optionExamples: [true], | ||
options: null, | ||
optionsDescription: "Not configurable.", | ||
rationale: `Clean redundant code and unnecessary comparison of objects and literals.`, | ||
ruleName: "no-tautology-expression", | ||
type: "functionality", | ||
typescriptOnly: false, | ||
}; | ||
|
||
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { | ||
return this.applyWithFunction(sourceFile, walk); | ||
} | ||
} | ||
|
||
function walk(context: Lint.WalkContext<void>) { | ||
const cb = (node: ts.Node): void => { | ||
if (tsutils.isBinaryExpression(node) && isRelationalOrLogicalOperator(node.operatorToken)) { | ||
if ( | ||
(tsutils.isStringLiteral(node.left) && tsutils.isStringLiteral(node.right)) || | ||
(tsutils.isNumericLiteral(node.left) && tsutils.isNumericLiteral(node.right)) | ||
) { | ||
if (node.left.text === node.right.text) { | ||
context.addFailureAtNode(node, TAUTOLOGY_DISCOVERED_ERROR_STRING); | ||
} | ||
} else if (tsutils.isIdentifier(node.left) && tsutils.isIdentifier(node.right)) { | ||
if (node.left.text === node.right.text) { | ||
context.addFailureAtNode(node, TAUTOLOGY_DISCOVERED_ERROR_STRING); | ||
} | ||
} else if ( | ||
tsutils.isPropertyAccessExpression(node.left) && | ||
tsutils.isPropertyAccessExpression(node.right) | ||
) { | ||
if (node.left.expression.getText() === node.right.expression.getText()) { | ||
if (node.left.name.text === node.right.name.text) { | ||
context.addFailureAtNode(node, TAUTOLOGY_DISCOVERED_ERROR_STRING); | ||
} | ||
} | ||
} else if ( | ||
(isBooleanLiteral(node.left) && isBooleanLiteral(node.right)) || | ||
(isNullLiteral(node.left) && isNullLiteral(node.right)) | ||
) { | ||
context.addFailureAtNode(node, TAUTOLOGY_DISCOVERED_ERROR_STRING); | ||
} | ||
} | ||
return ts.forEachChild(node, cb); | ||
}; | ||
return ts.forEachChild(context.sourceFile, cb); | ||
} | ||
|
||
function isNullLiteral(node: ts.Node): node is ts.NullLiteral { | ||
return node.kind === ts.SyntaxKind.NullKeyword; | ||
} | ||
|
||
function isBooleanLiteral(node: ts.Node): node is ts.BooleanLiteral { | ||
return node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword; | ||
} | ||
|
||
function isRelationalOrLogicalOperator(operator: ts.BinaryOperatorToken): boolean { | ||
return new Set([ | ||
ts.SyntaxKind.LessThanToken, | ||
ts.SyntaxKind.GreaterThanToken, | ||
ts.SyntaxKind.LessThanEqualsToken, | ||
ts.SyntaxKind.GreaterThanEqualsToken, | ||
ts.SyntaxKind.EqualsEqualsToken, | ||
ts.SyntaxKind.EqualsEqualsEqualsToken, | ||
ts.SyntaxKind.ExclamationEqualsToken, | ||
ts.SyntaxKind.ExclamationEqualsEqualsToken, | ||
ts.SyntaxKind.AmpersandAmpersandToken, | ||
ts.SyntaxKind.BarBarToken, | ||
]).has(operator.kind); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
|
||
const expr = "someStr" === "someStr"; | ||
~~~~~~~~~~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
const expr = 123 === 123; | ||
~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
const someVar = 100; | ||
const expr = someVar === someVar; | ||
~~~~~~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
|
||
const someFunc = () => { | ||
if ("SomeStr" === "SomeStr") { | ||
~~~~~~~~~~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
if (100 === 100) { | ||
~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
let someVar = 100; | ||
if (someVar === someVar) { | ||
~~~~~~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
if (1 !== 1) { | ||
~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
if (1 > 1) { | ||
~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
const someVar = 123; | ||
if (someVar < someVar) { | ||
~~~~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
if ("str" == "str") { | ||
~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
if (123 != 123) { | ||
~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
if ("str" <= "str") { | ||
~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
if ("str" >= "str") { | ||
~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
const someVar = true; | ||
if (someVar || someVar) { | ||
~~~~~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
const someVar = true; | ||
if (someVar && someVar) { | ||
~~~~~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
const someObj = { "name" : "moses the great" }; | ||
if (someObj.name === someObj.name) { | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
const someObj = { "name" : "moses the great", "address" : "king's road 10" }; | ||
if (someObj.name === someObj.address) { | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
const objOne = { "name" : "moses the great" }; | ||
const objTwo = { "name" : "moses the great" }; | ||
if (objOne.name === objTwo.name) { | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
if (true === true) { | ||
~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
if (false === false) { | ||
~~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
if (true === false) { | ||
~~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
if (null === null) { | ||
~~~~~~~~~~~~~ [Both sides of this equality comparison are the same, so the expression is either a tautology or a contradiction.] | ||
return true; | ||
} | ||
} | ||
|
||
const someFunc = () => { | ||
if (null === false) { | ||
return true; | ||
} | ||
} | ||
|
||
const someVar1 = 3 + 3; | ||
const someVar2 = 3 - 3; | ||
const someVar3 = 3 * 3; | ||
const someVar4 = 3 % 3; | ||
const someVar5 = 3 / 3; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"rules": { | ||
"no-tautology-expression": true | ||
} | ||
} |