Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Commit

Permalink
Added no-tautology-expressions rule (#4470)
Browse files Browse the repository at this point in the history
* 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
noamyogev84 authored and Josh Goldberg committed Mar 11, 2019
1 parent c5da14a commit 55160ae
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/configs/all.ts
Expand Up @@ -133,6 +133,7 @@ export const rules = {
"no-string-throw": true,
"no-sparse-arrays": true,
"no-submodule-imports": true,
"no-tautology-expression": true,
"no-unbound-method": true,
"no-unnecessary-class": { options: ["allow-empty-class"] },
"no-unsafe-any": true,
Expand Down
101 changes: 101 additions & 0 deletions src/rules/noTautologyExpressionRule.ts
@@ -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);
}
159 changes: 159 additions & 0 deletions test/rules/no-tautology-expression/test.ts.lint
@@ -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;
5 changes: 5 additions & 0 deletions test/rules/no-tautology-expression/tslint.json
@@ -0,0 +1,5 @@
{
"rules": {
"no-tautology-expression": true
}
}

0 comments on commit 55160ae

Please sign in to comment.