From 17fafabdb30984e419f3c27084e3057560489cca Mon Sep 17 00:00:00 2001 From: Nicholas Jamieson Date: Thu, 27 May 2021 19:23:00 +1000 Subject: [PATCH] feat: Add prefer-less-than rule. --- README.md | 1 + docs/rules/prefer-less-than.md | 25 +++++++ source/rules/prefer-less-than.ts | 65 ++++++++++++++++++ tests/rules/prefer-less-than.ts | 112 +++++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 docs/rules/prefer-less-than.md create mode 100644 source/rules/prefer-less-than.ts create mode 100644 tests/rules/prefer-less-than.ts diff --git a/README.md b/README.md index 07ad4e5..a2ae605 100644 --- a/README.md +++ b/README.md @@ -78,5 +78,6 @@ Rules marked with ✅ are recommended and rules marked with 🔧 have fixers. | [`no-misused-generics`](https://github.com/cartant/eslint-plugin-etc/blob/main/docs/rules/no-misused-generics.md) | Forbids type parameters without inference sites and type parameters that don't add type safety to declarations. This is an ESLint port of [Wotan's `no-misused-generics` rule](https://github.com/fimbullinter/wotan/blob/11368a193ba90a9e79b9f6ab530be1b434b122de/packages/mimir/docs/no-misused-generics.md). See also ["The Golden Rule of Generics"](https://effectivetypescript.com/2020/08/12/generics-golden-rule/). | | | | [`no-t`](https://github.com/cartant/eslint-plugin-etc/blob/main/docs/rules/no-t.md) | Forbids single-character type parameters. | | | | [`prefer-interface`](https://github.com/cartant/eslint-plugin-etc/blob/main/docs/rules/prefer-interface.md) | Forbids type aliases where interfaces can be used. | | 🔧 | +| [`prefer-less-than`](https://github.com/cartant/eslint-plugin-etc/blob/main/docs/rules/prefer-less-than.md) | Forbids greater-than comparisons. (Yes, this is the rule for [Ben Lesh comparisons](https://twitter.com/BenLesh/status/1397593619096166400).) | | 🔧 | | [`throw-error`](https://github.com/cartant/eslint-plugin-etc/blob/main/docs/rules/throw-error.md) | Forbids throwing - or rejecting with - non-`Error` values. | | | | [`underscore-internal`](https://github.com/cartant/eslint-plugin-etc/blob/main/docs/rules/underscore-internal.md) | Forbids internal APIs that are not prefixed with underscores. | | | diff --git a/docs/rules/prefer-less-than.md b/docs/rules/prefer-less-than.md new file mode 100644 index 0000000..d19d5ef --- /dev/null +++ b/docs/rules/prefer-less-than.md @@ -0,0 +1,25 @@ +# Use less-than instead of greater-than comparisons (`prefer-less-than`) + +Yes, this is the rule for [Ben Lesh comparisons](https://twitter.com/BenLesh/status/1397593619096166400). + +## Rule details + +Examples of **incorrect** code for this rule: + +```ts +if (x > a && x < b) { /* .. */ } +``` + +```ts +if (x >= a && x =< b) { /* .. */ } +``` + +Examples of **correct** code for this rule: + +```ts +if (a < x && x < b) { /* .. */ } +``` + +```ts +if (a <= x && x =< b) { /* .. */ } +``` \ No newline at end of file diff --git a/source/rules/prefer-less-than.ts b/source/rules/prefer-less-than.ts new file mode 100644 index 0000000..5565eb5 --- /dev/null +++ b/source/rules/prefer-less-than.ts @@ -0,0 +1,65 @@ +/** + * @license Use of this source code is governed by an MIT-style license that + * can be found in the LICENSE file at https://github.com/cartant/eslint-plugin-etc + */ + +import { + TSESLint as eslint, + TSESTree as es, +} from "@typescript-eslint/experimental-utils"; +import { ruleCreator } from "../utils"; + +const rule = ruleCreator({ + defaultOptions: [], + meta: { + docs: { + category: "Best Practices", + description: "Forbids greater-than comparisons.", + recommended: false, + }, + fixable: "code", + messages: { + forbiddenGT: "Greater-than comparisons are forbidden.", + forbiddenGTE: "Greater-than-or-equal comparisons are forbidden.", + suggestLT: "Use a less-than comparison instead.", + suggestLTE: "Use a less-than-or-equal comparison instead.", + }, + schema: [], + type: "suggestion", + }, + name: "prefer-less-than", + create: (context) => { + return { + "BinaryExpression[operator=/^(>|>=)$/]": ( + expression: es.BinaryExpression + ) => { + const gte = expression.operator === ">="; + function fix(fixer: eslint.RuleFixer) { + const { left, right } = expression; + const sourceCode = context.getSourceCode(); + const operator = sourceCode.getTokenAfter(left); + return operator + ? [ + fixer.replaceText(left, sourceCode.getText(right)), + fixer.replaceTextRange(operator.range, gte ? "<=" : "<"), + fixer.replaceText(right, sourceCode.getText(left)), + ] + : []; + } + context.report({ + fix, + messageId: gte ? "forbiddenGTE" : "forbiddenGT", + node: expression, + suggest: [ + { + fix, + messageId: gte ? "suggestLTE" : "suggestLT", + }, + ], + }); + }, + }; + }, +}); + +export = rule; diff --git a/tests/rules/prefer-less-than.ts b/tests/rules/prefer-less-than.ts new file mode 100644 index 0000000..8f9a297 --- /dev/null +++ b/tests/rules/prefer-less-than.ts @@ -0,0 +1,112 @@ +/** + * @license Use of this source code is governed by an MIT-style license that + * can be found in the LICENSE file at https://github.com/cartant/eslint-plugin-etc + */ + +import { stripIndent } from "common-tags"; +import { fromFixture } from "eslint-etc"; +import rule = require("../../source/rules/prefer-less-than"); +import { ruleTester } from "../utils"; + +ruleTester({ types: false }).run("prefer-less-than", rule, { + valid: [ + `const result = 42 < 54;`, + `const result = 42 <= 54;`, + `const result = 42 == 54;`, + `const result = 42 === 54;`, + `const result = 42 != 54;`, + `const result = 42 !== 54;`, + `if (a < x && x < b) { /* .. */ }`, + `if (a <= x && x <= b) { /* .. */ }`, + ], + invalid: [ + fromFixture( + stripIndent` + const result = 54 > 42; + ~~~~~~~ [forbiddenGT] + `, + { + output: stripIndent` + const result = 42 < 54; + `, + } + ), + fromFixture( + stripIndent` + const result = 54 >= 42; + ~~~~~~~~ [forbiddenGTE] + `, + { + output: stripIndent` + const result = 42 <= 54; + `, + } + ), + fromFixture( + stripIndent` + const result = 54.0 > 42; + ~~~~~~~~~ [forbiddenGT] + `, + { + output: stripIndent` + const result = 42 < 54.0; + `, + } + ), + fromFixture( + stripIndent` + const result = 54.0 >= 42; + ~~~~~~~~~~ [forbiddenGTE] + `, + { + output: stripIndent` + const result = 42 <= 54.0; + `, + } + ), + fromFixture( + stripIndent` + const result = 54 > 42.0; + ~~~~~~~~~ [forbiddenGT] + `, + { + output: stripIndent` + const result = 42.0 < 54; + `, + } + ), + fromFixture( + stripIndent` + const result = 54 >= 42.0; + ~~~~~~~~~~ [forbiddenGTE] + `, + { + output: stripIndent` + const result = 42.0 <= 54; + `, + } + ), + fromFixture( + stripIndent` + if (x > a && x < b) { /* .. */ } + ~~~~~ [forbiddenGT] + `, + { + output: stripIndent` + if (a < x && x < b) { /* .. */ } + `, + } + ), + fromFixture( + stripIndent` + if (x >= a && x <= b) { /* .. */ } + ~~~~~~ [forbiddenGTE] + `, + { + output: stripIndent` + if (a <= x && x <= b) { /* .. */ } + `, + } + ), + ], +});