diff --git a/crates/rome_diagnostics_categories/src/categories.rs b/crates/rome_diagnostics_categories/src/categories.rs index 8beb8c451f1..5edd251a949 100644 --- a/crates/rome_diagnostics_categories/src/categories.rs +++ b/crates/rome_diagnostics_categories/src/categories.rs @@ -46,6 +46,7 @@ define_dategories! { "lint/nursery/noAssignInExpressions": "https://docs.rome.tools/lint/rules/noAssignInExpressions", "lint/nursery/noWith": "https://docs.rome.tools/lint/rules/noWith", "lint/nursery/noBannedTypes":"https://docs.rome.tools/lint/rules/noBannedTypes", + "lint/nursery/noCommaOperator": "https://docs.rome.tools/lint/rules/noCommaOperator", "lint/nursery/noConstEnum": "https://docs.rome.tools/lint/rules/noConstEnum", "lint/nursery/noConstructorReturn": "https://docs.rome.tools/lint/rules/noConstructorReturn", "lint/nursery/noDistractingElements": "https://docs.rome.tools/lint/rules/noDistractingElements", @@ -59,7 +60,7 @@ define_dategories! { "lint/nursery/noRedundantAlt": "https://docs.rome.tools/lint/rules/noRedundantAlt", "lint/nursery/noRedundantUseStrict": "https://docs.rome.tools/lint/rules/noRedundantUseStrict", "lint/nursery/noRestrictedGlobals": "https://docs.rome.tools/lint/rules/noRestrictedGlobals", - "lint/nursery/noCommaOperator": "https://docs.rome.tools/lint/rules/noCommaOperator", + "lint/nursery/noSelfCompare": "https://docs.rome.tools/lint/rules/noSelfCompare", "lint/nursery/noSetterReturn": "https://docs.rome.tools/lint/rules/noSetterReturn", "lint/nursery/noStringCaseMismatch": "https://docs.rome.tools/lint/rules/noStringCaseMismatch", "lint/nursery/noUnsafeFinally": "https://docs.rome.tools/lint/rules/noUnsafeFinally", diff --git a/crates/rome_js_analyze/src/analyzers/nursery.rs b/crates/rome_js_analyze/src/analyzers/nursery.rs index e5f3250dbd6..47b25368635 100644 --- a/crates/rome_js_analyze/src/analyzers/nursery.rs +++ b/crates/rome_js_analyze/src/analyzers/nursery.rs @@ -17,6 +17,7 @@ mod no_non_null_assertion; mod no_precision_loss; mod no_redundant_alt; mod no_redundant_use_strict; +mod no_self_compare; mod no_setter_return; mod no_string_case_mismatch; mod no_unsafe_finally; @@ -28,4 +29,4 @@ mod use_default_switch_clause_last; mod use_enum_initializers; mod use_exponentiation_operator; mod use_numeric_literals; -declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_access_key :: NoAccessKey , self :: no_assign_in_expressions :: NoAssignInExpressions , self :: no_banned_types :: NoBannedTypes , self :: no_comma_operator :: NoCommaOperator , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_header_scope :: NoHeaderScope , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_precision_loss :: NoPrecisionLoss , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_useless_switch_case :: NoUselessSwitchCase , self :: no_void_type_return :: NoVoidTypeReturn , self :: no_with :: NoWith , self :: use_default_parameter_last :: UseDefaultParameterLast , self :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast , self :: use_enum_initializers :: UseEnumInitializers , self :: use_exponentiation_operator :: UseExponentiationOperator , self :: use_numeric_literals :: UseNumericLiterals ,] } } +declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_access_key :: NoAccessKey , self :: no_assign_in_expressions :: NoAssignInExpressions , self :: no_banned_types :: NoBannedTypes , self :: no_comma_operator :: NoCommaOperator , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_header_scope :: NoHeaderScope , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_precision_loss :: NoPrecisionLoss , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_self_compare :: NoSelfCompare , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_useless_switch_case :: NoUselessSwitchCase , self :: no_void_type_return :: NoVoidTypeReturn , self :: no_with :: NoWith , self :: use_default_parameter_last :: UseDefaultParameterLast , self :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast , self :: use_enum_initializers :: UseEnumInitializers , self :: use_exponentiation_operator :: UseExponentiationOperator , self :: use_numeric_literals :: UseNumericLiterals ,] } } diff --git a/crates/rome_js_analyze/src/analyzers/nursery/no_self_compare.rs b/crates/rome_js_analyze/src/analyzers/nursery/no_self_compare.rs new file mode 100644 index 00000000000..88de622ceb6 --- /dev/null +++ b/crates/rome_js_analyze/src/analyzers/nursery/no_self_compare.rs @@ -0,0 +1,131 @@ +use rome_analyze::context::RuleContext; +use rome_analyze::{declare_rule, Ast, Rule, RuleDiagnostic}; +use rome_js_syntax::{ + JsBinaryExpression, JsBinaryOperator, JsSyntaxNode, JsSyntaxToken, WalkEvent, +}; +use rome_rowan::{AstNode, Direction}; +use std::iter; + +declare_rule! { + /// Disallow comparisons where both sides are exactly the same. + /// + /// > Comparing a variable against itself is usually an error, either a typo or refactoring error. It is confusing to the reader and may potentially introduce a runtime error. + /// + /// > The only time you would compare a variable against itself is when you are testing for `NaN`. However, it is far more appropriate to use `typeof x === 'number' && isNaN(x)` or the [Number.isNaN ES2015 function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) for that use case rather than leaving the reader of the code to determine the intent of self comparison. + /// + /// Source: [no-self-compare](https://eslint.org/docs/latest/rules/no-self-compare). + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// if (x === x) {} + /// ``` + /// + /// ```js,expect_diagnostic + /// if (a.b.c() !== a.b .c()) {} + /// ``` + /// + pub(crate) NoSelfCompare { + version: "12.0.0", + name: "noSelfCompare", + recommended: true, + } +} + +impl Rule for NoSelfCompare { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let node = ctx.query(); + + if !matches!( + &node.operator(), + Ok(JsBinaryOperator::Equality + | JsBinaryOperator::Inequality + | JsBinaryOperator::GreaterThan + | JsBinaryOperator::GreaterThanOrEqual + | JsBinaryOperator::LessThan + | JsBinaryOperator::LessThanOrEqual + | JsBinaryOperator::StrictEquality + | JsBinaryOperator::StrictInequality) + ) { + return None; + } + + let left = node.left().ok()?; + let right = node.right().ok()?; + + if is_node_equal(&left.into_syntax(), &right.into_syntax()) { + return Some(()); + } + + None + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + Some(RuleDiagnostic::new( + rule_category!(), + ctx.query().range(), + "Comparing to itself is potentially pointless.", + )) + } +} + +/// Verifies that both nodes are equal by checking their descendants (nodes included) kinds +/// and tokens (same kind and inner token text). +fn is_node_equal(a_node: &JsSyntaxNode, b_node: &JsSyntaxNode) -> bool { + let a_tree = a_node.preorder_with_tokens(Direction::Next); + let b_tree = b_node.preorder_with_tokens(Direction::Next); + + for (a_child, b_child) in iter::zip(a_tree, b_tree) { + let a_event = match a_child { + WalkEvent::Enter(event) => event, + WalkEvent::Leave(event) => event, + }; + + let b_event = match b_child { + WalkEvent::Enter(event) => event, + WalkEvent::Leave(event) => event, + }; + + if a_event.kind() != b_event.kind() { + return false; + } + + let a_token = a_event.as_token(); + let b_token = b_event.as_token(); + + match (a_token, b_token) { + // both are nodes + (None, None) => continue, + // one of them is a node + (None, Some(_)) | (Some(_), None) => return false, + // both are tokens + (Some(a), Some(b)) => { + if !is_token_text_equal(a, b) { + return false; + } + continue; + } + } + } + + true +} + +/// Verify that tokens' inner text are equal +fn is_token_text_equal(a: &JsSyntaxToken, b: &JsSyntaxToken) -> bool { + static QUOTES: [char; 2] = ['"', '\'']; + + a.token_text_trimmed() + .trim_start_matches(QUOTES) + .trim_end_matches(QUOTES) + == b.token_text_trimmed() + .trim_start_matches(QUOTES) + .trim_end_matches(QUOTES) +} diff --git a/crates/rome_js_analyze/tests/specs/nursery/noSelfCompare.js b/crates/rome_js_analyze/tests/specs/nursery/noSelfCompare.js new file mode 100644 index 00000000000..9b9100e8de7 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noSelfCompare.js @@ -0,0 +1,27 @@ +// valid +if (a || b) { } +if (1 ^ 2) { } +if (x === y) { } +if (1 === 2) { } +y=x*x +foo.bar.baz === foo.bar.qux +class C { #field; foo() { this.#field === this['#field']; } } +class C { #field; foo() { this['#field'] === this.#field; } } + +// invalid +if (x === x) { } +if (x !== x) { } +if (x > x) { } +if ('x' > 'x') { } +if ('x' > "x") { } +do {} while (x === x) +x === x +x !== x +x == x +x != x +x > x +x < x +x >= x +x <= x +foo.bar().baz.qux >= foo.bar ().baz .qux +class C { #field; foo() { this.#field === this.#field; } } diff --git a/crates/rome_js_analyze/tests/specs/nursery/noSelfCompare.js.snap b/crates/rome_js_analyze/tests/specs/nursery/noSelfCompare.js.snap new file mode 100644 index 00000000000..c598fcf2737 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noSelfCompare.js.snap @@ -0,0 +1,277 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +assertion_line: 92 +expression: noSelfCompare.js +--- +# Input +```js +// valid +if (a || b) { } +if (1 ^ 2) { } +if (x === y) { } +if (1 === 2) { } +y=x*x +foo.bar.baz === foo.bar.qux +class C { #field; foo() { this.#field === this['#field']; } } +class C { #field; foo() { this['#field'] === this.#field; } } + +// invalid +if (x === x) { } +if (x !== x) { } +if (x > x) { } +if ('x' > 'x') { } +if ('x' > "x") { } +do {} while (x === x) +x === x +x !== x +x == x +x != x +x > x +x < x +x >= x +x <= x +foo.bar().baz.qux >= foo.bar ().baz .qux +class C { #field; foo() { this.#field === this.#field; } } + +``` + +# Diagnostics +``` +noSelfCompare.js:12:5 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 11 │ // invalid + > 12 │ if (x === x) { } + │ ^^^^^^^ + 13 │ if (x !== x) { } + 14 │ if (x > x) { } + + +``` + +``` +noSelfCompare.js:13:5 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 11 │ // invalid + 12 │ if (x === x) { } + > 13 │ if (x !== x) { } + │ ^^^^^^^ + 14 │ if (x > x) { } + 15 │ if ('x' > 'x') { } + + +``` + +``` +noSelfCompare.js:14:5 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 12 │ if (x === x) { } + 13 │ if (x !== x) { } + > 14 │ if (x > x) { } + │ ^^^^^ + 15 │ if ('x' > 'x') { } + 16 │ if ('x' > "x") { } + + +``` + +``` +noSelfCompare.js:15:5 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 13 │ if (x !== x) { } + 14 │ if (x > x) { } + > 15 │ if ('x' > 'x') { } + │ ^^^^^^^^^ + 16 │ if ('x' > "x") { } + 17 │ do {} while (x === x) + + +``` + +``` +noSelfCompare.js:16:5 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 14 │ if (x > x) { } + 15 │ if ('x' > 'x') { } + > 16 │ if ('x' > "x") { } + │ ^^^^^^^^^ + 17 │ do {} while (x === x) + 18 │ x === x + + +``` + +``` +noSelfCompare.js:17:14 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 15 │ if ('x' > 'x') { } + 16 │ if ('x' > "x") { } + > 17 │ do {} while (x === x) + │ ^^^^^^^ + 18 │ x === x + 19 │ x !== x + + +``` + +``` +noSelfCompare.js:18:1 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 16 │ if ('x' > "x") { } + 17 │ do {} while (x === x) + > 18 │ x === x + │ ^^^^^^^ + 19 │ x !== x + 20 │ x == x + + +``` + +``` +noSelfCompare.js:19:1 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 17 │ do {} while (x === x) + 18 │ x === x + > 19 │ x !== x + │ ^^^^^^^ + 20 │ x == x + 21 │ x != x + + +``` + +``` +noSelfCompare.js:20:1 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 18 │ x === x + 19 │ x !== x + > 20 │ x == x + │ ^^^^^^ + 21 │ x != x + 22 │ x > x + + +``` + +``` +noSelfCompare.js:21:1 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 19 │ x !== x + 20 │ x == x + > 21 │ x != x + │ ^^^^^^ + 22 │ x > x + 23 │ x < x + + +``` + +``` +noSelfCompare.js:22:1 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 20 │ x == x + 21 │ x != x + > 22 │ x > x + │ ^^^^^ + 23 │ x < x + 24 │ x >= x + + +``` + +``` +noSelfCompare.js:23:1 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 21 │ x != x + 22 │ x > x + > 23 │ x < x + │ ^^^^^ + 24 │ x >= x + 25 │ x <= x + + +``` + +``` +noSelfCompare.js:24:1 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 22 │ x > x + 23 │ x < x + > 24 │ x >= x + │ ^^^^^^ + 25 │ x <= x + 26 │ foo.bar().baz.qux >= foo.bar ().baz .qux + + +``` + +``` +noSelfCompare.js:25:1 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 23 │ x < x + 24 │ x >= x + > 25 │ x <= x + │ ^^^^^^ + 26 │ foo.bar().baz.qux >= foo.bar ().baz .qux + 27 │ class C { #field; foo() { this.#field === this.#field; } } + + +``` + +``` +noSelfCompare.js:26:1 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 24 │ x >= x + 25 │ x <= x + > 26 │ foo.bar().baz.qux >= foo.bar ().baz .qux + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 27 │ class C { #field; foo() { this.#field === this.#field; } } + 28 │ + + +``` + +``` +noSelfCompare.js:27:27 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Comparing to itself is potentially pointless. + + 25 │ x <= x + 26 │ foo.bar().baz.qux >= foo.bar ().baz .qux + > 27 │ class C { #field; foo() { this.#field === this.#field; } } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 28 │ + + +``` + + diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index 916e43938b3..852f812f328 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -757,6 +757,8 @@ struct NurserySchema { no_redundant_use_strict: Option, #[doc = "This rule allows you to specify global variable names that you don’t want to use in your application."] no_restricted_globals: Option, + #[doc = "Disallow comparisons where both sides are exactly the same."] + no_self_compare: Option, #[doc = "Disallow returning a value from a setter"] no_setter_return: Option, #[doc = "Disallow comparison of expressions modifying the string case with non-compliant value."] @@ -796,7 +798,7 @@ struct NurserySchema { } impl Nursery { const CATEGORY_NAME: &'static str = "nursery"; - pub(crate) const CATEGORY_RULES: [&'static str; 35] = [ + pub(crate) const CATEGORY_RULES: [&'static str; 36] = [ "noAccessKey", "noAssignInExpressions", "noBannedTypes", @@ -814,6 +816,7 @@ impl Nursery { "noRedundantAlt", "noRedundantUseStrict", "noRestrictedGlobals", + "noSelfCompare", "noSetterReturn", "noStringCaseMismatch", "noUnsafeFinally", @@ -833,7 +836,7 @@ impl Nursery { "useHookAtTopLevel", "useNumericLiterals", ]; - const RECOMMENDED_RULES: [&'static str; 26] = [ + const RECOMMENDED_RULES: [&'static str; 27] = [ "noAssignInExpressions", "noBannedTypes", "noCommaOperator", @@ -846,6 +849,7 @@ impl Nursery { "noHeaderScope", "noInvalidConstructorSuper", "noRedundantAlt", + "noSelfCompare", "noSetterReturn", "noStringCaseMismatch", "noUnsafeFinally", @@ -861,7 +865,7 @@ impl Nursery { "useExhaustiveDependencies", "useNumericLiterals", ]; - const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 26] = [ + const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 27] = [ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[1]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[2]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[3]), @@ -881,13 +885,14 @@ impl Nursery { RuleFilter::Rule("nursery", Self::CATEGORY_RULES[21]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[22]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[23]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[25]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[27]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[24]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[26]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[28]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[29]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[30]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[31]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[34]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[32]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[35]), ]; pub(crate) fn is_recommended(&self) -> bool { !matches!(self.recommended, Some(false)) } pub(crate) fn get_enabled_rules(&self) -> IndexSet { @@ -914,7 +919,7 @@ impl Nursery { pub(crate) fn is_recommended_rule(rule_name: &str) -> bool { Self::RECOMMENDED_RULES.contains(&rule_name) } - pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 26] { + pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 27] { Self::RECOMMENDED_RULES_AS_FILTERS } } diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index ee247f404e2..5afcf83f6e5 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -764,6 +764,17 @@ } ] }, + "noSelfCompare": { + "description": "Disallow comparisons where both sides are exactly the same.", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "noSetterReturn": { "description": "Disallow returning a value from a setter", "anyOf": [ diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index 7375456ebac..f7bf2a1acbb 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -353,6 +353,10 @@ export interface Nursery { * This rule allows you to specify global variable names that you don’t want to use in your application. */ noRestrictedGlobals?: RuleConfiguration; + /** + * Disallow comparisons where both sides are exactly the same. + */ + noSelfCompare?: RuleConfiguration; /** * Disallow returning a value from a setter */ @@ -702,6 +706,7 @@ export type Category = | "lint/nursery/noAssignInExpressions" | "lint/nursery/noWith" | "lint/nursery/noBannedTypes" + | "lint/nursery/noCommaOperator" | "lint/nursery/noConstEnum" | "lint/nursery/noConstructorReturn" | "lint/nursery/noDistractingElements" @@ -715,7 +720,7 @@ export type Category = | "lint/nursery/noRedundantAlt" | "lint/nursery/noRedundantUseStrict" | "lint/nursery/noRestrictedGlobals" - | "lint/nursery/noCommaOperator" + | "lint/nursery/noSelfCompare" | "lint/nursery/noSetterReturn" | "lint/nursery/noStringCaseMismatch" | "lint/nursery/noUnsafeFinally" diff --git a/npm/rome/configuration_schema.json b/npm/rome/configuration_schema.json index ee247f404e2..5afcf83f6e5 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -764,6 +764,17 @@ } ] }, + "noSelfCompare": { + "description": "Disallow comparisons where both sides are exactly the same.", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "noSetterReturn": { "description": "Disallow returning a value from a setter", "anyOf": [ diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index fe63d04c5e6..b91d9919f18 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -578,6 +578,12 @@ Prevents from having redundant "use strict". This rule allows you to specify global variable names that you don’t want to use in your application.
+

+ noSelfCompare +

+Disallow comparisons where both sides are exactly the same. +
+

noSetterReturn

diff --git a/website/src/pages/lint/rules/noSelfCompare.md b/website/src/pages/lint/rules/noSelfCompare.md new file mode 100644 index 00000000000..480f432a90c --- /dev/null +++ b/website/src/pages/lint/rules/noSelfCompare.md @@ -0,0 +1,49 @@ +--- +title: Lint Rule noSelfCompare +parent: lint/rules/index +--- + +# noSelfCompare (since v12.0.0) + +Disallow comparisons where both sides are exactly the same. + +>Comparing a variable against itself is usually an error, either a typo or refactoring error. It is confusing to the reader and may potentially introduce a runtime error. + + +>The only time you would compare a variable against itself is when you are testing for `NaN`. However, it is far more appropriate to use `typeof x === 'number' && isNaN(x)` or the [Number.isNaN ES2015 function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) for that use case rather than leaving the reader of the code to determine the intent of self comparison. + + +Source: [no-self-compare](https://eslint.org/docs/latest/rules/no-self-compare). + +## Examples + +### Invalid + +```jsx +if (x === x) {} +``` + +
nursery/noSelfCompare.js:1:5 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Comparing to itself is potentially pointless.
+  
+  > 1 │ if (x === x) {}
+       ^^^^^^^
+    2 │ 
+  
+
+ +```jsx +if (a.b.c() !== a.b .c()) {} +``` + +
nursery/noSelfCompare.js:1:5 lint/nursery/noSelfCompare ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Comparing to itself is potentially pointless.
+  
+  > 1 │ if (a.b.c() !== a.b .c()) {}
+       ^^^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+
+