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

Added no-tautology-expression rule #4470

Merged
merged 11 commits into from
Mar 11, 2019
1 change: 1 addition & 0 deletions src/configs/all.ts
Original file line number Diff line number Diff line change
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
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 = {
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continuing the previous conversation: this doesn't capture numeric literals or undefined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

numeric and string literals are covered earlier.
what about undefined? what do you mean by covering this ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, wow, I should review when fully awake. This looks great. I had forgotten undefined is an identifier.

) {
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@

JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
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" };
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"no-tautology-expression": true
}
}