From 913625f26ccdb29780b0c3f541b5f26b893f6776 Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Sun, 1 Jan 2023 19:51:41 +0100 Subject: [PATCH] feat(rome_js_analyze): `noDuplicateClassMembers` --- .../src/categories.rs | 1 + .../rome_js_analyze/src/analyzers/nursery.rs | 3 +- .../nursery/no_duplicate_class_members.rs | 216 ++++++++++ .../noDuplicateClassMembers/invalid.jsonc | 17 + .../invalid.jsonc.snap | 392 ++++++++++++++++++ .../noDuplicateClassMembers/valid.jsonc | 60 +++ .../noDuplicateClassMembers/valid.jsonc.snap | 246 +++++++++++ crates/rome_js_unicode_table/src/tables.rs | 52 +-- .../src/configuration/linter/rules.rs | 25 +- editors/vscode/configuration_schema.json | 11 + npm/backend-jsonrpc/src/workspace.ts | 5 + npm/rome/configuration_schema.json | 11 + website/src/pages/lint/rules/index.mdx | 6 + .../lint/rules/noDuplicateClassMembers.md | 250 +++++++++++ .../pages/lint/rules/noDuplicateJsxProps.md | 4 + 15 files changed, 1262 insertions(+), 37 deletions(-) create mode 100644 crates/rome_js_analyze/src/analyzers/nursery/no_duplicate_class_members.rs create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/invalid.jsonc create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/invalid.jsonc.snap create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/valid.jsonc create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/valid.jsonc.snap create mode 100644 website/src/pages/lint/rules/noDuplicateClassMembers.md diff --git a/crates/rome_diagnostics_categories/src/categories.rs b/crates/rome_diagnostics_categories/src/categories.rs index ebb4189c75e..3c1acaf7a94 100644 --- a/crates/rome_diagnostics_categories/src/categories.rs +++ b/crates/rome_diagnostics_categories/src/categories.rs @@ -92,6 +92,7 @@ define_dategories! { "lint/nursery/useValidForDirection": "https://docs.rome.tools/lint/rules/useValidForDirection", "lint/nursery/useHookAtTopLevel": "https://docs.rome.tools/lint/rules/useHookAtTopLevel", "lint/nursery/noDuplicateJsxProps": "https://docs.rome.tools/lint/rules/noDuplicateJsxProps", +"lint/nursery/noDuplicateClassMembers": "https://docs.rome.tools/lint/rules/noDuplicateClassMembers", // Insert new nursery rule here // performance diff --git a/crates/rome_js_analyze/src/analyzers/nursery.rs b/crates/rome_js_analyze/src/analyzers/nursery.rs index 9f3b9c96dce..a56c88bdd7e 100644 --- a/crates/rome_js_analyze/src/analyzers/nursery.rs +++ b/crates/rome_js_analyze/src/analyzers/nursery.rs @@ -9,6 +9,7 @@ mod no_const_enum; mod no_constructor_return; mod no_distracting_elements; mod no_duplicate_case; +mod no_duplicate_class_members; mod no_duplicate_jsx_props; mod no_duplicate_object_keys; mod no_empty_interface; @@ -36,4 +37,4 @@ mod use_exponentiation_operator; mod use_is_nan; mod use_media_caption; 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_case :: NoDuplicateCase , self :: no_duplicate_jsx_props :: NoDuplicateJsxProps , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_extra_semicolons :: NoExtraSemicolons , self :: no_header_scope :: NoHeaderScope , self :: no_inner_declarations :: NoInnerDeclarations , 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_unreachable_super :: NoUnreachableSuper , 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_is_nan :: UseIsNan , self :: use_media_caption :: UseMediaCaption , 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_case :: NoDuplicateCase , self :: no_duplicate_class_members :: NoDuplicateClassMembers , self :: no_duplicate_jsx_props :: NoDuplicateJsxProps , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_extra_semicolons :: NoExtraSemicolons , self :: no_header_scope :: NoHeaderScope , self :: no_inner_declarations :: NoInnerDeclarations , 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_unreachable_super :: NoUnreachableSuper , 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_is_nan :: UseIsNan , self :: use_media_caption :: UseMediaCaption , self :: use_numeric_literals :: UseNumericLiterals ,] } } diff --git a/crates/rome_js_analyze/src/analyzers/nursery/no_duplicate_class_members.rs b/crates/rome_js_analyze/src/analyzers/nursery/no_duplicate_class_members.rs new file mode 100644 index 00000000000..e2e6749cc17 --- /dev/null +++ b/crates/rome_js_analyze/src/analyzers/nursery/no_duplicate_class_members.rs @@ -0,0 +1,216 @@ +use rome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic}; +use rome_console::markup; +use rome_js_syntax::{AnyJsClassMember, JsClassMemberList}; +use rome_rowan::{AstNode, BatchMutationExt}; +use rustc_hash::FxHashMap; +use std::iter::Iterator; + +use crate::JsRuleAction; + +declare_rule! { + /// Disallow duplicate class members. + /// + /// If there are declarations of the same name in class members, + /// then the last declaration overwrites other declarations silently. + /// It can cause unexpected behaviors. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// class Foo { + /// bar() { } + /// bar() { } + /// } + /// ``` + /// + /// ```js,expect_diagnostic + /// class Foo { + /// bar() { } + /// get bar() { } + /// } + /// ``` + /// + /// ```js,expect_diagnostic + /// class Foo { + /// bar; + /// bar; + /// } + /// ``` + /// + /// ```js,expect_diagnostic + /// class Foo { + /// bar; + /// bar() { } + /// } + /// ``` + /// + /// ```js,expect_diagnostic + /// class Foo { + /// static bar() { } + /// static bar() { } + /// } + /// ``` + /// + /// ## Valid + /// + /// ```js + /// class Foo { + /// bar() { } + /// qux() { } + /// } + /// ``` + /// + /// ```js + /// class Foo { + /// get bar() { } + /// set bar(value) { } + /// } + /// ``` + /// + /// ```js + /// class Foo { + /// bar; + /// qux; + /// } + /// ``` + /// + /// ```js + /// class Foo { + /// bar; + /// qux() { } + /// } + /// ``` + /// + /// ```js + /// class Foo { + /// static bar() { } + /// bar() { } + /// } + /// ``` + pub(crate) NoDuplicateClassMembers { + version: "next", + name: "noDuplicateClassMembers", + recommended: true, + } +} + +pub(crate) struct DuplicateClassMember { + overwritten: AnyJsClassMember, + effective: AnyJsClassMember, +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +struct MemberInfo { + is_static: bool, + name: String, +} + +impl MemberInfo { + fn from(member: &AnyJsClassMember) -> Option { + let name = member + .name() + .ok()?? + .as_js_literal_member_name()? + .name() + .ok()?; + let is_static = match member { + AnyJsClassMember::JsConstructorClassMember(_) => false, + AnyJsClassMember::JsGetterClassMember(x) => x + .modifiers() + .into_iter() + .any(|y| y.as_js_static_modifier().is_some()), + AnyJsClassMember::JsMethodClassMember(x) => x + .modifiers() + .into_iter() + .any(|y| y.as_js_static_modifier().is_some()), + AnyJsClassMember::JsPropertyClassMember(x) => x + .modifiers() + .into_iter() + .any(|y| y.as_js_static_modifier().is_some()), + AnyJsClassMember::JsSetterClassMember(x) => x + .modifiers() + .into_iter() + .any(|y| y.as_js_static_modifier().is_some()), + AnyJsClassMember::JsBogusMember(_) + | AnyJsClassMember::JsEmptyClassMember(_) + | AnyJsClassMember::JsStaticInitializationBlockClassMember(_) + | AnyJsClassMember::TsConstructorSignatureClassMember(_) + | AnyJsClassMember::TsGetterSignatureClassMember(_) + | AnyJsClassMember::TsIndexSignatureClassMember(_) + | AnyJsClassMember::TsMethodSignatureClassMember(_) + | AnyJsClassMember::TsPropertySignatureClassMember(_) + | AnyJsClassMember::TsSetterSignatureClassMember(_) => return None, + }; + Some(MemberInfo { is_static, name }) + } +} + +impl Rule for NoDuplicateClassMembers { + type Query = Ast; + type State = DuplicateClassMember; + type Signals = Vec; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let members = ctx.query(); + let mut effective_members = FxHashMap::::default(); + members + .into_iter() + .rev() + .filter_map(|member| { + let member_info = MemberInfo::from(&member)?; + if let Some(effective_member) = effective_members.get(&member_info) { + let is_paired = (effective_member.as_js_getter_class_member().is_some() + && member.as_js_setter_class_member().is_some()) + || (member.as_js_getter_class_member().is_some() + && effective_member.as_js_setter_class_member().is_some()); + if !is_paired { + return Some(DuplicateClassMember { + overwritten: member, + effective: effective_member.clone(), + }); + } + } else { + effective_members.insert(member_info, member); + } + None + }) + .collect() + } + + fn diagnostic(_: &RuleContext, state: &Self::State) -> Option { + Some( + RuleDiagnostic::new( + rule_category!(), + state.overwritten.syntax().text_trimmed_range(), + markup! { + "This class member is later overwritten by another class member." + }, + ) + .detail( + state.effective.syntax().text_trimmed_range(), + markup! { + "Overwritten with this class member:" + } + ).note( + markup! { + "If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored." + } + ) + ) + } + + fn action(ctx: &RuleContext, state: &Self::State) -> Option { + let mut mutation = ctx.root().begin(); + mutation.remove_node(state.overwritten.clone()); + Some(JsRuleAction { + category: rome_analyze::ActionCategory::QuickFix, + // The property initialization could contain side effects + applicability: rome_diagnostics::Applicability::MaybeIncorrect, + message: markup!("Remove the overwritten class member.").to_owned(), + mutation, + }) + } +} diff --git a/crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/invalid.jsonc b/crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/invalid.jsonc new file mode 100644 index 00000000000..4341c877a1c --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/invalid.jsonc @@ -0,0 +1,17 @@ +[ + "class A { foo() {} foo() {} }", + "const C = class A { foo() {} foo() {} };", + "class A { 'foo'() {} 'foo'() {} }", + "class A { static constructor() {} static 'constructor'() {} }", + "class A { foo() {} foo() {} foo() {} }", + "class A { static foo() {} static foo() {} }", + "class A { foo() {} get foo() {} }", + "class A { set foo(value) {} foo() {} }", + "class A { get foo() {} foo() {} set foo(value) {} }", + "class A { foo; foo; }", + "class A { foo; accessor foo; }", + "class A { get foo () {} accessor foo; }", + "class A { set foo () {} accessor foo; }" + // This is syntax error + // "class A { #foo; #foo; }" +] diff --git a/crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/invalid.jsonc.snap b/crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/invalid.jsonc.snap new file mode 100644 index 00000000000..c92814f0ba9 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/invalid.jsonc.snap @@ -0,0 +1,392 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +assertion_line: 92 +expression: invalid.jsonc +--- +# Input +```js +class A { foo() {} foo() {} } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { foo() {} foo() {} } + │ ^^^^^^^^ + + i Overwritten with this class member: + + > 1 │ class A { foo() {} foo() {} } + │ ^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·foo()·{}·foo()·{}·} + │ --------- + +``` + +# Input +```js +const C = class A { foo() {} foo() {} }; +``` + +# Diagnostics +``` +invalid.jsonc:1:21 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ const C = class A { foo() {} foo() {} }; + │ ^^^^^^^^ + + i Overwritten with this class member: + + > 1 │ const C = class A { foo() {} foo() {} }; + │ ^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ const·C·=·class·A·{·foo()·{}·foo()·{}·}; + │ --------- + +``` + +# Input +```js +class A { 'foo'() {} 'foo'() {} } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { 'foo'() {} 'foo'() {} } + │ ^^^^^^^^^^ + + i Overwritten with this class member: + + > 1 │ class A { 'foo'() {} 'foo'() {} } + │ ^^^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·'foo'()·{}·'foo'()·{}·} + │ ----------- + +``` + +# Input +```js +class A { static constructor() {} static 'constructor'() {} } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { static constructor() {} static 'constructor'() {} } + │ ^^^^^^^^^^^^^^^^^^^^^^^ + + i Overwritten with this class member: + + > 1 │ class A { static constructor() {} static 'constructor'() {} } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·static·constructor()·{}·static·'constructor'()·{}·} + │ ------------------------ + +``` + +# Input +```js +class A { foo() {} foo() {} foo() {} } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { foo() {} foo() {} foo() {} } + │ ^^^^^^^^ + + i Overwritten with this class member: + + > 1 │ class A { foo() {} foo() {} foo() {} } + │ ^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·foo()·{}·foo()·{}·foo()·{}·} + │ --------- + +``` + +``` +invalid.jsonc:1:20 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { foo() {} foo() {} foo() {} } + │ ^^^^^^^^ + + i Overwritten with this class member: + + > 1 │ class A { foo() {} foo() {} foo() {} } + │ ^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·foo()·{}·foo()·{}·foo()·{}·} + │ --------- + +``` + +# Input +```js +class A { static foo() {} static foo() {} } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { static foo() {} static foo() {} } + │ ^^^^^^^^^^^^^^^ + + i Overwritten with this class member: + + > 1 │ class A { static foo() {} static foo() {} } + │ ^^^^^^^^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·static·foo()·{}·static·foo()·{}·} + │ ---------------- + +``` + +# Input +```js +class A { foo() {} get foo() {} } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { foo() {} get foo() {} } + │ ^^^^^^^^ + + i Overwritten with this class member: + + > 1 │ class A { foo() {} get foo() {} } + │ ^^^^^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·foo()·{}·get·foo()·{}·} + │ --------- + +``` + +# Input +```js +class A { set foo(value) {} foo() {} } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { set foo(value) {} foo() {} } + │ ^^^^^^^^^^^^^^^^^ + + i Overwritten with this class member: + + > 1 │ class A { set foo(value) {} foo() {} } + │ ^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·set·foo(value)·{}·foo()·{}·} + │ ------------------ + +``` + +# Input +```js +class A { get foo() {} foo() {} set foo(value) {} } +``` + +# Diagnostics +``` +invalid.jsonc:1:24 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { get foo() {} foo() {} set foo(value) {} } + │ ^^^^^^^^ + + i Overwritten with this class member: + + > 1 │ class A { get foo() {} foo() {} set foo(value) {} } + │ ^^^^^^^^^^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·get·foo()·{}·foo()·{}·set·foo(value)·{}·} + │ --------- + +``` + +# Input +```js +class A { foo; foo; } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { foo; foo; } + │ ^^^^ + + i Overwritten with this class member: + + > 1 │ class A { foo; foo; } + │ ^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·foo;·foo;·} + │ ----- + +``` + +# Input +```js +class A { foo; accessor foo; } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { foo; accessor foo; } + │ ^^^^ + + i Overwritten with this class member: + + > 1 │ class A { foo; accessor foo; } + │ ^^^^^^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·foo;·accessor·foo;·} + │ ----- + +``` + +# Input +```js +class A { get foo () {} accessor foo; } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { get foo () {} accessor foo; } + │ ^^^^^^^^^^^^^ + + i Overwritten with this class member: + + > 1 │ class A { get foo () {} accessor foo; } + │ ^^^^^^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·get·foo·()·{}·accessor·foo;·} + │ -------------- + +``` + +# Input +```js +class A { set foo () {} accessor foo; } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noDuplicateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This class member is later overwritten by another class member. + + > 1 │ class A { set foo () {} accessor foo; } + │ ^^^^^^^^^^^^^ + + i Overwritten with this class member: + + > 1 │ class A { set foo () {} accessor foo; } + │ ^^^^^^^^^^^^^ + + i If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored. + + i Suggested fix: Remove the overwritten class member. + + 1 │ class·A·{·set·foo·()·{}·accessor·foo;·} + │ -------------- + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/valid.jsonc b/crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/valid.jsonc new file mode 100644 index 00000000000..dd819307113 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/valid.jsonc @@ -0,0 +1,60 @@ +[ + "class A { foo() {} bar() {} }", + "class A { static foo() {} foo() {} }", + "class A { get foo() {} set foo(value) {} }", + "class A { static foo() {} get foo() {} set foo(value) {} }", + "class A { foo() { } } class B { foo() { } }", + "class A { [foo]() {} foo() {} }", + "class A { 'foo'() {} 'bar'() {} baz() {} }", + "class A { *'foo'() {} *'bar'() {} *baz() {} }", + "class A { get 'foo'() {} get 'bar'() {} get baz() {} }", + "class A { 1() {} 2() {} }", + "class A { ['foo']() {} ['bar']() {} }", + "class A { [`foo`]() {} [`bar`]() {} }", + "class A { [12]() {} [123]() {} }", + "class A { [1.0]() {} ['1.0']() {} }", + "class A { [0x1]() {} [`0x1`]() {} }", + "class A { [null]() {} ['']() {} }", + "class A { get ['foo']() {} set ['foo'](value) {} }", + "class A { ['foo']() {} static ['foo']() {} }", + + // computed "constructor" key doesn't create constructor + "class A { ['constructor']() {} constructor() {} }", + "class A { 'constructor'() {} [`constructor`]() {} }", + "class A { constructor() {} get [`constructor`]() {} }", + "class A { 'constructor'() {} set ['constructor'](value) {} }", + + // not assumed to be statically-known values + "class A { ['foo' + '']() {} ['foo']() {} }", + "class A { [`foo${''}`]() {} [`foo`]() {} }", + "class A { [-1]() {} ['-1']() {} }", + + // not supported by this rule + "class A { [foo]() {} [foo]() {} }", + + // supported by ESLint + "class A { 10() {} 1e1() {} }", + "class A { ['foo']() {} ['foo']() {} }", + "class A { static ['foo']() {} static foo() {} }", + "class A { [`foo`]() {} [`foo`]() {} }", + "class A { get [`foo`]() {} 'foo'() {} }", + "class A { static [`constructor`]() {} static constructor() {} }", + "class A { [123]() {} [123]() {} }", + "class A { [0x10]() {} 16() {} }", + "class A { [100]() {} [1e2]() {} }", + "class A { [123.00]() {} [`123`]() {} }", + "class A { [123n]() {} 123() {} }", + "class A { [null]() {} 'null'() {} }", + "class A { set 'foo'(value) {} set ['foo'](val) {} }", + "class A { ''() {} ['']() {} }", + "class A { static get [`foo`]() {} static get ['foo']() {} }", + "class A { foo() {} [`foo`]() {} }", + "class A { static 'foo'() {} static [`foo`]() {} }", + "class A { ['constructor']() {} ['constructor']() {} }", + "class A { static '65'() {} static [0o101]() {} }", + + // private and public + "class A { foo; static foo; }", + "class A { foo; #foo; }", + "class A { '#foo'; #foo; }" +] diff --git a/crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/valid.jsonc.snap b/crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/valid.jsonc.snap new file mode 100644 index 00000000000..9b23086fd4a --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noDuplicateClassMembers/valid.jsonc.snap @@ -0,0 +1,246 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +assertion_line: 92 +expression: valid.jsonc +--- +# Input +```js +class A { foo() {} bar() {} } +``` + +# Input +```js +class A { static foo() {} foo() {} } +``` + +# Input +```js +class A { get foo() {} set foo(value) {} } +``` + +# Input +```js +class A { static foo() {} get foo() {} set foo(value) {} } +``` + +# Input +```js +class A { foo() { } } class B { foo() { } } +``` + +# Input +```js +class A { [foo]() {} foo() {} } +``` + +# Input +```js +class A { 'foo'() {} 'bar'() {} baz() {} } +``` + +# Input +```js +class A { *'foo'() {} *'bar'() {} *baz() {} } +``` + +# Input +```js +class A { get 'foo'() {} get 'bar'() {} get baz() {} } +``` + +# Input +```js +class A { 1() {} 2() {} } +``` + +# Input +```js +class A { ['foo']() {} ['bar']() {} } +``` + +# Input +```js +class A { [`foo`]() {} [`bar`]() {} } +``` + +# Input +```js +class A { [12]() {} [123]() {} } +``` + +# Input +```js +class A { [1.0]() {} ['1.0']() {} } +``` + +# Input +```js +class A { [0x1]() {} [`0x1`]() {} } +``` + +# Input +```js +class A { [null]() {} ['']() {} } +``` + +# Input +```js +class A { get ['foo']() {} set ['foo'](value) {} } +``` + +# Input +```js +class A { ['foo']() {} static ['foo']() {} } +``` + +# Input +```js +class A { ['constructor']() {} constructor() {} } +``` + +# Input +```js +class A { 'constructor'() {} [`constructor`]() {} } +``` + +# Input +```js +class A { constructor() {} get [`constructor`]() {} } +``` + +# Input +```js +class A { 'constructor'() {} set ['constructor'](value) {} } +``` + +# Input +```js +class A { ['foo' + '']() {} ['foo']() {} } +``` + +# Input +```js +class A { [`foo${''}`]() {} [`foo`]() {} } +``` + +# Input +```js +class A { [-1]() {} ['-1']() {} } +``` + +# Input +```js +class A { [foo]() {} [foo]() {} } +``` + +# Input +```js +class A { 10() {} 1e1() {} } +``` + +# Input +```js +class A { ['foo']() {} ['foo']() {} } +``` + +# Input +```js +class A { static ['foo']() {} static foo() {} } +``` + +# Input +```js +class A { [`foo`]() {} [`foo`]() {} } +``` + +# Input +```js +class A { get [`foo`]() {} 'foo'() {} } +``` + +# Input +```js +class A { static [`constructor`]() {} static constructor() {} } +``` + +# Input +```js +class A { [123]() {} [123]() {} } +``` + +# Input +```js +class A { [0x10]() {} 16() {} } +``` + +# Input +```js +class A { [100]() {} [1e2]() {} } +``` + +# Input +```js +class A { [123.00]() {} [`123`]() {} } +``` + +# Input +```js +class A { [123n]() {} 123() {} } +``` + +# Input +```js +class A { [null]() {} 'null'() {} } +``` + +# Input +```js +class A { set 'foo'(value) {} set ['foo'](val) {} } +``` + +# Input +```js +class A { ''() {} ['']() {} } +``` + +# Input +```js +class A { static get [`foo`]() {} static get ['foo']() {} } +``` + +# Input +```js +class A { foo() {} [`foo`]() {} } +``` + +# Input +```js +class A { static 'foo'() {} static [`foo`]() {} } +``` + +# Input +```js +class A { ['constructor']() {} ['constructor']() {} } +``` + +# Input +```js +class A { static '65'() {} static [0o101]() {} } +``` + +# Input +```js +class A { foo; static foo; } +``` + +# Input +```js +class A { foo; #foo; } +``` + +# Input +```js +class A { '#foo'; #foo; } +``` + + diff --git a/crates/rome_js_unicode_table/src/tables.rs b/crates/rome_js_unicode_table/src/tables.rs index 7f67057597c..3ed00b03214 100644 --- a/crates/rome_js_unicode_table/src/tables.rs +++ b/crates/rome_js_unicode_table/src/tables.rs @@ -176,7 +176,7 @@ pub mod derived_property { ('ೝ', 'ೞ'), ('ೠ', '\u{ce3}'), ('೦', '೯'), - ('ೱ', '\u{cf3}'), + ('ೱ', 'ೳ'), ('\u{d00}', 'ഌ'), ('എ', 'ഐ'), ('ഒ', '\u{d44}'), @@ -626,16 +626,16 @@ pub mod derived_property { ('𑶓', '𑶘'), ('𑶠', '𑶩'), ('𑻠', '𑻶'), - ('\u{11f00}', '\u{11f10}'), - ('\u{11f12}', '\u{11f3a}'), - ('\u{11f3e}', '\u{11f42}'), - ('\u{11f50}', '\u{11f59}'), + ('\u{11f00}', '𑼐'), + ('𑼒', '\u{11f3a}'), + ('𑼾', '\u{11f42}'), + ('𑽐', '𑽙'), ('𑾰', '𑾰'), ('𒀀', '𒎙'), ('𒐀', '𒑮'), ('𒒀', '𒕃'), ('𒾐', '𒿰'), - ('𓀀', '\u{1342f}'), + ('𓀀', '𓐯'), ('\u{13440}', '\u{13455}'), ('𔐀', '𔙆'), ('𖠀', '𖨸'), @@ -664,9 +664,9 @@ pub mod derived_property { ('𚿵', '𚿻'), ('𚿽', '𚿾'), ('𛀀', '𛄢'), - ('\u{1b132}', '\u{1b132}'), + ('𛄲', '𛄲'), ('𛅐', '𛅒'), - ('\u{1b155}', '\u{1b155}'), + ('𛅕', '𛅕'), ('𛅤', '𛅧'), ('𛅰', '𛋻'), ('𛰀', '𛱪'), @@ -720,13 +720,13 @@ pub mod derived_property { ('\u{1da9b}', '\u{1da9f}'), ('\u{1daa1}', '\u{1daaf}'), ('𝼀', '𝼞'), - ('\u{1df25}', '\u{1df2a}'), + ('𝼥', '𝼪'), ('\u{1e000}', '\u{1e006}'), ('\u{1e008}', '\u{1e018}'), ('\u{1e01b}', '\u{1e021}'), ('\u{1e023}', '\u{1e024}'), ('\u{1e026}', '\u{1e02a}'), - ('\u{1e030}', '\u{1e06d}'), + ('𞀰', '𞁭'), ('\u{1e08f}', '\u{1e08f}'), ('𞄀', '𞄬'), ('\u{1e130}', '𞄽'), @@ -734,7 +734,7 @@ pub mod derived_property { ('𞅎', '𞅎'), ('𞊐', '\u{1e2ae}'), ('𞋀', '𞋹'), - ('\u{1e4d0}', '\u{1e4f9}'), + ('𞓐', '𞓹'), ('𞟠', '𞟦'), ('𞟨', '𞟫'), ('𞟭', '𞟮'), @@ -778,13 +778,13 @@ pub mod derived_property { ('𞺫', '𞺻'), ('🯰', '🯹'), ('𠀀', '𪛟'), - ('𪜀', '\u{2b739}'), + ('𪜀', '𫜹'), ('𫝀', '𫠝'), ('𫠠', '𬺡'), ('𬺰', '𮯠'), ('丽', '𪘀'), ('𰀀', '𱍊'), - ('\u{31350}', '\u{323af}'), + ('𱍐', '𲎯'), ('\u{e0100}', '\u{e01ef}'), ]; pub fn ID_Continue(c: char) -> bool { @@ -1259,7 +1259,7 @@ pub mod derived_property { ('𑇜', '𑇜'), ('𑈀', '𑈑'), ('𑈓', '𑈫'), - ('\u{1123f}', '\u{11240}'), + ('𑈿', '𑉀'), ('𑊀', '𑊆'), ('𑊈', '𑊈'), ('𑊊', '𑊍'), @@ -1322,16 +1322,16 @@ pub mod derived_property { ('𑵪', '𑶉'), ('𑶘', '𑶘'), ('𑻠', '𑻲'), - ('\u{11f02}', '\u{11f02}'), - ('\u{11f04}', '\u{11f10}'), - ('\u{11f12}', '\u{11f33}'), + ('𑼂', '𑼂'), + ('𑼄', '𑼐'), + ('𑼒', '𑼳'), ('𑾰', '𑾰'), ('𒀀', '𒎙'), ('𒐀', '𒑮'), ('𒒀', '𒕃'), ('𒾐', '𒿰'), - ('𓀀', '\u{1342f}'), - ('\u{13441}', '\u{13446}'), + ('𓀀', '𓐯'), + ('𓑁', '𓑆'), ('𔐀', '𔙆'), ('𖠀', '𖨸'), ('𖩀', '𖩞'), @@ -1354,9 +1354,9 @@ pub mod derived_property { ('𚿵', '𚿻'), ('𚿽', '𚿾'), ('𛀀', '𛄢'), - ('\u{1b132}', '\u{1b132}'), + ('𛄲', '𛄲'), ('𛅐', '𛅒'), - ('\u{1b155}', '\u{1b155}'), + ('𛅕', '𛅕'), ('𛅤', '𛅧'), ('𛅰', '𛋻'), ('𛰀', '𛱪'), @@ -1394,14 +1394,14 @@ pub mod derived_property { ('𝞪', '𝟂'), ('𝟄', '𝟋'), ('𝼀', '𝼞'), - ('\u{1df25}', '\u{1df2a}'), - ('\u{1e030}', '\u{1e06d}'), + ('𝼥', '𝼪'), + ('𞀰', '𞁭'), ('𞄀', '𞄬'), ('𞄷', '𞄽'), ('𞅎', '𞅎'), ('𞊐', '𞊭'), ('𞋀', '𞋫'), - ('\u{1e4d0}', '\u{1e4eb}'), + ('𞓐', '𞓫'), ('𞟠', '𞟦'), ('𞟨', '𞟫'), ('𞟭', '𞟮'), @@ -1443,13 +1443,13 @@ pub mod derived_property { ('𞺥', '𞺩'), ('𞺫', '𞺻'), ('𠀀', '𪛟'), - ('𪜀', '\u{2b739}'), + ('𪜀', '𫜹'), ('𫝀', '𫠝'), ('𫠠', '𬺡'), ('𬺰', '𮯠'), ('丽', '𪘀'), ('𰀀', '𱍊'), - ('\u{31350}', '\u{323af}'), + ('𱍐', '𲎯'), ]; pub fn ID_Start(c: char) -> bool { super::bsearch_range_table(c, ID_Start_table) diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index 771f9cba46a..512b085f4f5 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -749,6 +749,8 @@ struct NurserySchema { no_distracting_elements: Option, #[doc = "Disallow duplicate case labels. If a switch statement has duplicate test expressions in case clauses, it is likely that a programmer copied a case clause but forgot to change the test expression."] no_duplicate_case: Option, + #[doc = "Disallow duplicate class members."] + no_duplicate_class_members: Option, #[doc = "Prevents JSX properties to be assigned multiple times."] no_duplicate_jsx_props: Option, #[doc = "Prevents object literals having more than one property declaration for the same name. If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored, which is likely a mistake."] @@ -830,7 +832,7 @@ struct NurserySchema { } impl Nursery { const CATEGORY_NAME: &'static str = "nursery"; - pub(crate) const CATEGORY_RULES: [&'static str; 48] = [ + pub(crate) const CATEGORY_RULES: [&'static str; 49] = [ "noAccessKey", "noAssignInExpressions", "noBannedTypes", @@ -840,6 +842,7 @@ impl Nursery { "noConstructorReturn", "noDistractingElements", "noDuplicateCase", + "noDuplicateClassMembers", "noDuplicateJsxProps", "noDuplicateObjectKeys", "noEmptyInterface", @@ -880,7 +883,7 @@ impl Nursery { "useValidAriaProps", "useValidLang", ]; - const RECOMMENDED_RULES: [&'static str; 39] = [ + const RECOMMENDED_RULES: [&'static str; 40] = [ "noAssignInExpressions", "noBannedTypes", "noClassAssign", @@ -889,6 +892,7 @@ impl Nursery { "noConstructorReturn", "noDistractingElements", "noDuplicateCase", + "noDuplicateClassMembers", "noDuplicateJsxProps", "noDuplicateObjectKeys", "noEmptyInterface", @@ -921,7 +925,7 @@ impl Nursery { "useValidAriaProps", "useValidLang", ]; - const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 39] = [ + const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 40] = [ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[1]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[2]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[3]), @@ -938,9 +942,9 @@ impl Nursery { RuleFilter::Rule("nursery", Self::CATEGORY_RULES[14]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[15]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[16]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[18]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[20]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[23]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[17]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[19]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[21]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[24]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[25]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[26]), @@ -949,18 +953,19 @@ impl Nursery { 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[33]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[35]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[32]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[34]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[36]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[37]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[38]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[39]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[42]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[40]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[43]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[44]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[45]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[46]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[47]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[48]), ]; pub(crate) fn is_recommended(&self) -> bool { !matches!(self.recommended, Some(false)) } pub(crate) fn get_enabled_rules(&self) -> IndexSet { @@ -987,7 +992,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>; 39] { + pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 40] { Self::RECOMMENDED_RULES_AS_FILTERS } } diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 9d908bff5ba..8f728737d5d 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -687,6 +687,17 @@ } ] }, + "noDuplicateClassMembers": { + "description": "Disallow duplicate class members.", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "noDuplicateJsxProps": { "description": "Prevents JSX properties to be assigned multiple times.", "anyOf": [ diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index 14954751210..bd7aa018df1 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -325,6 +325,10 @@ export interface Nursery { * Disallow duplicate case labels. If a switch statement has duplicate test expressions in case clauses, it is likely that a programmer copied a case clause but forgot to change the test expression. */ noDuplicateCase?: RuleConfiguration; + /** + * Disallow duplicate class members. + */ + noDuplicateClassMembers?: RuleConfiguration; /** * Prevents JSX properties to be assigned multiple times. */ @@ -804,6 +808,7 @@ export type Category = | "lint/nursery/useValidForDirection" | "lint/nursery/useHookAtTopLevel" | "lint/nursery/noDuplicateJsxProps" + | "lint/nursery/noDuplicateClassMembers" | "lint/performance/noDelete" | "lint/security/noDangerouslySetInnerHtml" | "lint/security/noDangerouslySetInnerHtmlWithChildren" diff --git a/npm/rome/configuration_schema.json b/npm/rome/configuration_schema.json index 9d908bff5ba..8f728737d5d 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -687,6 +687,17 @@ } ] }, + "noDuplicateClassMembers": { + "description": "Disallow duplicate class members.", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "noDuplicateJsxProps": { "description": "Prevents JSX properties to be assigned multiple times.", "anyOf": [ diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index 1213efb58e1..5d611a2bc89 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -537,6 +537,12 @@ Disallow duplicate case labels. If a switch statement has duplicate test expressions in case clauses, it is likely that a programmer copied a case clause but forgot to change the test expression.
+

+ noDuplicateClassMembers +

+Disallow duplicate class members. +
+

noDuplicateJsxProps

diff --git a/website/src/pages/lint/rules/noDuplicateClassMembers.md b/website/src/pages/lint/rules/noDuplicateClassMembers.md new file mode 100644 index 00000000000..abc28bcc36c --- /dev/null +++ b/website/src/pages/lint/rules/noDuplicateClassMembers.md @@ -0,0 +1,250 @@ +--- +title: Lint Rule noDuplicateClassMembers +parent: lint/rules/index +--- + +# noDuplicateClassMembers (since vnext) + +Disallow duplicate class members. + +If there are declarations of the same name in class members, +then the last declaration overwrites other declarations silently. +It can cause unexpected behaviors. + +## Examples + +### Invalid + +```jsx +class Foo { + bar() { } + bar() { } +} +``` + +
nursery/noDuplicateClassMembers.js:2:3 lint/nursery/noDuplicateClassMembers  FIXABLE  ━━━━━━━━━━━━━━
+
+   This class member is later overwritten by another class member.
+  
+    1 │ class Foo {
+  > 2 │   bar() { }
+     ^^^^^^^^^
+    3 │   bar() { }
+    4 │ }
+  
+   Overwritten with this class member:
+  
+    1 │ class Foo {
+    2 │   bar() { }
+  > 3 │   bar() { }
+     ^^^^^^^^^
+    4 │ }
+    5 │ 
+  
+   If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored.
+  
+   Suggested fix: Remove the overwritten class member.
+  
+    1 1  class Foo {
+    2 2    bar() { }
+    3  - ··bar()·{·}
+    4 3  }
+    5 4  
+  
+
+ +```jsx +class Foo { + bar() { } + get bar() { } +} +``` + +
nursery/noDuplicateClassMembers.js:2:3 lint/nursery/noDuplicateClassMembers  FIXABLE  ━━━━━━━━━━━━━━
+
+   This class member is later overwritten by another class member.
+  
+    1 │ class Foo {
+  > 2 │   bar() { }
+     ^^^^^^^^^
+    3 │   get bar() { }
+    4 │ }
+  
+   Overwritten with this class member:
+  
+    1 │ class Foo {
+    2 │   bar() { }
+  > 3 │   get bar() { }
+     ^^^^^^^^^^^^^
+    4 │ }
+    5 │ 
+  
+   If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored.
+  
+   Suggested fix: Remove the overwritten class member.
+  
+    1 1  class Foo {
+    2  - ··bar()·{·}
+    3  - ··get·bar()·{·}
+      2+ ··get·bar()·{·}
+    4 3  }
+    5 4  
+  
+
+ +```jsx +class Foo { + bar; + bar; +} +``` + +
nursery/noDuplicateClassMembers.js:2:3 lint/nursery/noDuplicateClassMembers  FIXABLE  ━━━━━━━━━━━━━━
+
+   This class member is later overwritten by another class member.
+  
+    1 │ class Foo {
+  > 2 │   bar;
+     ^^^^
+    3 │   bar;
+    4 │ }
+  
+   Overwritten with this class member:
+  
+    1 │ class Foo {
+    2 │   bar;
+  > 3 │   bar;
+     ^^^^
+    4 │ }
+    5 │ 
+  
+   If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored.
+  
+   Suggested fix: Remove the overwritten class member.
+  
+    1 1  class Foo {
+    2 2    bar;
+    3  - ··bar;
+    4 3  }
+    5 4  
+  
+
+ +```jsx +class Foo { + bar; + bar() { } +} +``` + +
nursery/noDuplicateClassMembers.js:2:3 lint/nursery/noDuplicateClassMembers  FIXABLE  ━━━━━━━━━━━━━━
+
+   This class member is later overwritten by another class member.
+  
+    1 │ class Foo {
+  > 2 │   bar;
+     ^^^^
+    3 │   bar() { }
+    4 │ }
+  
+   Overwritten with this class member:
+  
+    1 │ class Foo {
+    2 │   bar;
+  > 3 │   bar() { }
+     ^^^^^^^^^
+    4 │ }
+    5 │ 
+  
+   If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored.
+  
+   Suggested fix: Remove the overwritten class member.
+  
+    1 1  class Foo {
+    2  - ··bar;
+    3  - ··bar()·{·}
+      2+ ··bar()·{·}
+    4 3  }
+    5 4  
+  
+
+ +```jsx +class Foo { + static bar() { } + static bar() { } +} +``` + +
nursery/noDuplicateClassMembers.js:2:3 lint/nursery/noDuplicateClassMembers  FIXABLE  ━━━━━━━━━━━━━━
+
+   This class member is later overwritten by another class member.
+  
+    1 │ class Foo {
+  > 2 │   static bar() { }
+     ^^^^^^^^^^^^^^^^
+    3 │   static bar() { }
+    4 │ }
+  
+   Overwritten with this class member:
+  
+    1 │ class Foo {
+    2 │   static bar() { }
+  > 3 │   static bar() { }
+     ^^^^^^^^^^^^^^^^
+    4 │ }
+    5 │ 
+  
+   If a class member with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the class and previous definitions are ignored.
+  
+   Suggested fix: Remove the overwritten class member.
+  
+    1 1  class Foo {
+    2 2    static bar() { }
+    3  - ··static·bar()·{·}
+    4 3  }
+    5 4  
+  
+
+ +## Valid + +```jsx +class Foo { + bar() { } + qux() { } +} +``` + +```jsx +class Foo { + get bar() { } + set bar(value) { } +} +``` + +```jsx +class Foo { + bar; + qux; +} +``` + +```jsx +class Foo { + bar; + qux() { } +} +``` + +```jsx +class Foo { + static bar() { } + bar() { } +} +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options) diff --git a/website/src/pages/lint/rules/noDuplicateJsxProps.md b/website/src/pages/lint/rules/noDuplicateJsxProps.md index 5fb8a2c2159..f7ef5630a11 100644 --- a/website/src/pages/lint/rules/noDuplicateJsxProps.md +++ b/website/src/pages/lint/rules/noDuplicateJsxProps.md @@ -61,3 +61,7 @@ Prevents JSX properties to be assigned multiple times. ``` +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options)