diff --git a/crates/rome_diagnostics_categories/src/categories.rs b/crates/rome_diagnostics_categories/src/categories.rs index 9d30220d40c..a1096bc6776 100644 --- a/crates/rome_diagnostics_categories/src/categories.rs +++ b/crates/rome_diagnostics_categories/src/categories.rs @@ -103,6 +103,7 @@ define_dategories! { "lint/nursery/useYield": "https://docs.rome.tools/lint/rules/useYield", "lint/nursery/noGlobalObjectCalls": "https://docs.rome.tools/lint/rules/noGlobalObjectCalls", "lint/nursery/noPrototypeBuiltins": "https://docs.rome.tools/lint/rules/noPrototypeBuiltins", + "lint/nursery/noSelfAssignment": "https://docs.rome.tools/lint/rules/noSelfAssignment", // 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 0fc32064226..c15a7654723 100644 --- a/crates/rome_js_analyze/src/analyzers/nursery.rs +++ b/crates/rome_js_analyze/src/analyzers/nursery.rs @@ -28,6 +28,7 @@ mod no_precision_loss; mod no_prototype_builtins; mod no_redundant_alt; mod no_redundant_use_strict; +mod no_self_assignment; mod no_self_compare; mod no_setter_return; mod no_string_case_mismatch; @@ -47,4 +48,4 @@ mod use_is_nan; mod use_media_caption; mod use_numeric_literals; mod use_yield; -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_confusing_labels :: NoConfusingLabels , 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_labels :: NoExtraLabels , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_extra_semicolons :: NoExtraSemicolons , self :: no_global_object_calls :: NoGlobalObjectCalls , self :: no_header_scope :: NoHeaderScope , self :: no_inferrable_types :: NoInferrableTypes , self :: no_inner_declarations :: NoInnerDeclarations , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_parameter_properties :: NoParameterProperties , self :: no_precision_loss :: NoPrecisionLoss , self :: no_prototype_builtins :: NoPrototypeBuiltins , 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_switch_declarations :: NoSwitchDeclarations , self :: no_unreachable_super :: NoUnreachableSuper , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_unused_labels :: NoUnusedLabels , self :: no_useless_rename :: NoUselessRename , 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 , self :: use_yield :: UseYield ,] } } +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_confusing_labels :: NoConfusingLabels , 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_labels :: NoExtraLabels , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_extra_semicolons :: NoExtraSemicolons , self :: no_global_object_calls :: NoGlobalObjectCalls , self :: no_header_scope :: NoHeaderScope , self :: no_inferrable_types :: NoInferrableTypes , self :: no_inner_declarations :: NoInnerDeclarations , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_parameter_properties :: NoParameterProperties , self :: no_precision_loss :: NoPrecisionLoss , self :: no_prototype_builtins :: NoPrototypeBuiltins , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_self_assignment :: NoSelfAssignment , self :: no_self_compare :: NoSelfCompare , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_switch_declarations :: NoSwitchDeclarations , self :: no_unreachable_super :: NoUnreachableSuper , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_unused_labels :: NoUnusedLabels , self :: no_useless_rename :: NoUselessRename , 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 , self :: use_yield :: UseYield ,] } } diff --git a/crates/rome_js_analyze/src/analyzers/nursery/no_self_assignment.rs b/crates/rome_js_analyze/src/analyzers/nursery/no_self_assignment.rs new file mode 100644 index 00000000000..3e30e9200a5 --- /dev/null +++ b/crates/rome_js_analyze/src/analyzers/nursery/no_self_assignment.rs @@ -0,0 +1,730 @@ +use rome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic}; +use rome_console::markup; +use rome_js_syntax::{ + AnyJsArrayAssignmentPatternElement, AnyJsArrayElement, AnyJsAssignment, AnyJsAssignmentPattern, + AnyJsExpression, AnyJsName, AnyJsObjectAssignmentPatternMember, AnyJsObjectMember, + JsAssignmentExpression, JsAssignmentOperator, JsComputedMemberAssignment, + JsComputedMemberExpression, JsIdentifierAssignment, JsIdentifierExpression, JsLanguage, JsName, + JsPrivateName, JsReferenceIdentifier, JsStaticMemberAssignment, JsStaticMemberExpression, + JsSyntaxToken, +}; +use rome_rowan::{ + declare_node_union, AstNode, AstSeparatedList, AstSeparatedListNodesIterator, SyntaxError, + SyntaxResult, TextRange, +}; +use std::collections::VecDeque; +use std::iter::FusedIterator; + +declare_rule! { + /// Put your description here + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// a = a; + /// ``` + /// + /// ```js,expect_diagnostic + /// [a] = [a]; + /// ``` + /// + /// ```js,expect_diagnostic + /// ({a: b} = {a: b}); + /// ``` + /// + /// ```js,expect_diagnostic + /// a.b = a.b; + /// ``` + /// + /// ```js,expect_diagnostic + /// a[b] = a[b]; + /// ``` + /// + /// ## Valid + /// + /// ```js + /// a &= a; + /// var a = a; + /// let a = a; + /// const a = a; + /// [a, b] = [b, a]; + /// ``` + /// + pub(crate) NoSelfAssignment { + version: "12.0.0", + name: "noSelfAssignment", + recommended: true, + } +} + +impl Rule for NoSelfAssignment { + type Query = Ast; + type State = IdentifiersLike; + type Signals = Vec; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let node = ctx.query(); + let left = node.left().ok(); + let right = node.right().ok(); + let operator = node.operator().ok(); + + let mut state = vec![]; + if let Some(operator) = operator { + if matches!( + operator, + JsAssignmentOperator::Assign + | JsAssignmentOperator::LogicalAndAssign + | JsAssignmentOperator::LogicalOrAssign + | JsAssignmentOperator::NullishCoalescingAssign + ) { + if let (Some(left), Some(right)) = (left, right) { + if let Ok(pair) = AnyAssignmentLike::try_from((left, right)) { + compare_assignment_like(pair, &mut state); + } + } + } + } + state + } + + fn diagnostic(_: &RuleContext, identifier_like: &Self::State) -> Option { + let name = identifier_like.name()?; + Some( + RuleDiagnostic::new( + rule_category!(), + identifier_like.right_range(), + markup! { + {{name.text_trimmed()}}" is assigned to itself." + }, + ) + .detail( + identifier_like.left_range(), + markup! { + "This is where is assigned." + }, + ), + ) + } +} + +/// It traverses an [AnyAssignmentLike] and tracks the identifiers that have the same name +fn compare_assignment_like( + any_assignment_like: AnyAssignmentLike, + incorrect_identifiers: &mut Vec, +) { + let same_identifiers = SameIdentifiers { + current_assignment_like: any_assignment_like, + assignment_queue: VecDeque::new(), + }; + + for identifier_like in same_identifiers { + if with_same_identifiers(&identifier_like).is_some() { + incorrect_identifiers.push(identifier_like); + } + } +} + +/// Convenient type to iterate through all the identifiers that can be found +/// inside an assignment expression. +struct SameIdentifiers { + /// The current assignment-like that is being inspected + current_assignment_like: AnyAssignmentLike, + /// A queue of assignments-like that are inspected during the traversal. + /// + /// The queue is used to "save" the current traversal when it's needed to start a new one. + /// + /// These kind of cases happen, for example, when we have a code like + /// + /// ```js + /// [ a, [b, c], d ] + /// ``` + /// + /// After `a`, we find a new assignment-like pattern that requires a new traversal, so we save the + /// current traversal in the queue and we start a new one. When the inner traversal is finished, + /// we resume the previous one. + assignment_queue: VecDeque, +} + +impl SameIdentifiers { + /// Any assignment-like has a left arm and a right arm. Both arms needs to be "similar" + /// in order to be compared. If during the traversal part of each arm differ, they are then ignored + /// + /// The iterator logic makes sure to return the next eligible assignment-like. + fn next_assignment_like(&mut self) -> Option { + let current_assignment_like = &mut self.current_assignment_like; + match current_assignment_like { + AnyAssignmentLike::Arrays { left, right } => { + let new_assignment_like = Self::next_array_assignment(left, right); + // In case we have nested array/object structures, we save the current + // pair and we restore it once this iterator is consumed + if let Some(new_assignment_like) = new_assignment_like.as_ref() { + if new_assignment_like.has_sub_structures() { + self.assignment_queue + .push_back(self.current_assignment_like.clone()); + } + } + new_assignment_like + } + AnyAssignmentLike::Object { left, right } => { + let new_assignment_like = Self::next_object_assignment(left, right); + // In case we have nested array/object structures, we save the current + // pair and we restore it once this iterator is consumed + if let Some(new_assignment_like) = new_assignment_like.as_ref() { + if new_assignment_like.has_sub_structures() { + self.assignment_queue + .push_back(self.current_assignment_like.clone()); + } + } + new_assignment_like + } + AnyAssignmentLike::StaticExpression { left, right } => { + Self::next_static_expression(left, right) + } + AnyAssignmentLike::None | AnyAssignmentLike::Identifiers { .. } => { + let new_assignment = self.current_assignment_like.clone(); + self.current_assignment_like = AnyAssignmentLike::None; + Some(new_assignment) + } + } + } + + /// Handles cases where the assignment is something like + /// ```js + /// [a] = [a] + /// ``` + fn next_array_assignment( + left: &mut AstSeparatedListNodesIterator, + right: &mut AstSeparatedListNodesIterator, + ) -> Option { + if let (Some(left_element), Some(right_element)) = (left.next(), right.next()) { + let left_element = left_element.ok()?; + let right_element = right_element.ok()?; + + if let ( + AnyJsArrayAssignmentPatternElement::AnyJsAssignmentPattern(left), + AnyJsArrayElement::AnyJsExpression(right), + ) = (left_element, right_element) + { + let new_assignment_like = AnyAssignmentLike::try_from((left, right)).ok()?; + + return Some(new_assignment_like); + } + } + Some(AnyAssignmentLike::None) + } + + /// Computes the next assignment like. + /// + /// It handles code like: + /// + /// ```js + /// {a} = {b} + /// ``` + fn next_object_assignment( + left: &mut AstSeparatedListNodesIterator, + right: &mut AstSeparatedListNodesIterator, + ) -> Option { + let result = if let (Some(left_element), Some(right_element)) = (left.next(), right.next()) + { + let left_element = left_element.ok()?; + let right_element = right_element.ok()?; + + match (left_element, right_element) { + // matches {a} = {a} + ( + AnyJsObjectAssignmentPatternMember::JsObjectAssignmentPatternShorthandProperty( + left, + ), + AnyJsObjectMember::JsShorthandPropertyObjectMember(right), + ) => AnyAssignmentLike::Identifiers(IdentifiersLike::IdentifierAndReference( + left.identifier().ok()?, + right.name().ok()?, + )), + + ( + AnyJsObjectAssignmentPatternMember::JsObjectAssignmentPatternProperty(left), + AnyJsObjectMember::JsPropertyObjectMember(right), + ) => { + let left = left.pattern().ok()?; + let right = right.value().ok()?; + match (left, right) { + // matches {a: b} = {a: b} + ( + AnyJsAssignmentPattern::AnyJsAssignment( + AnyJsAssignment::JsIdentifierAssignment(left), + ), + AnyJsExpression::JsIdentifierExpression(right), + ) => AnyAssignmentLike::Identifiers( + IdentifiersLike::IdentifierAndReference(left, right.name().ok()?), + ), + // matches {a: [b]} = {a: [b]} + ( + AnyJsAssignmentPattern::JsArrayAssignmentPattern(left), + AnyJsExpression::JsArrayExpression(right), + ) => AnyAssignmentLike::Arrays { + left: left.elements().iter(), + right: right.elements().iter(), + }, + // matches {a: {b}} = {a: {b}} + ( + AnyJsAssignmentPattern::JsObjectAssignmentPattern(left), + AnyJsExpression::JsObjectExpression(right), + ) => AnyAssignmentLike::Object { + left: left.properties().iter(), + right: right.members().iter(), + }, + _ => AnyAssignmentLike::None, + } + } + _ => AnyAssignmentLike::None, + } + } else { + AnyAssignmentLike::None + }; + + Some(result) + } + + /// Computes the next static expression. + /// + /// It handles codes like: + /// + /// ```js + /// a.b = a.b; + /// a[b] = a[b]; + /// ``` + fn next_static_expression( + left: &mut AnyJsAssignmentExpressionLikeIterator, + right: &mut AnyJsAssignmentExpressionLikeIterator, + ) -> Option { + if let (Some(left), Some(right)) = (left.next(), right.next()) { + let (left_name, left_reference) = left; + let (right_name, right_reference) = right; + if let Ok(identifier_like) = IdentifiersLike::try_from((left_name, right_name)) { + if with_same_identifiers(&identifier_like).is_some() { + if let (Some(left_reference), Some(right_reference)) = + (left_reference, right_reference) + { + if with_same_identifiers(&IdentifiersLike::References( + left_reference, + right_reference, + )) + .is_some() + { + return Some(AnyAssignmentLike::Identifiers(identifier_like)); + } + } + } + } + } + Some(AnyAssignmentLike::None) + } +} + +impl Iterator for SameIdentifiers { + type Item = IdentifiersLike; + + fn next(&mut self) -> Option { + if matches!(self.current_assignment_like, AnyAssignmentLike::None) { + return None; + } + + loop { + let new_assignment_like = self.next_assignment_like()?; + + match new_assignment_like { + // if we are here, it's plausible that we consumed the current iterator and we have to + // resume the previous one + AnyAssignmentLike::None => { + // we still have assignments-like to complete, so we continue the loop + if let Some(pair) = self.assignment_queue.pop_front() { + self.current_assignment_like = pair; + continue; + } + // the queue is empty + else { + return None; + } + } + AnyAssignmentLike::Identifiers(identifier_like) => { + return Some(identifier_like); + } + + // we have a sub structure, which means we queue the current assignment, + // and inspect the sub structure + AnyAssignmentLike::StaticExpression { .. } + | AnyAssignmentLike::Object { .. } + | AnyAssignmentLike::Arrays { .. } => { + self.assignment_queue + .push_back(self.current_assignment_like.clone()); + self.current_assignment_like = new_assignment_like; + continue; + } + } + } + } +} + +impl FusedIterator for SameIdentifiers {} + +/// A convenient iterator that continues to return the nested [JsStaticMemberExpression] +#[derive(Debug, Clone)] +struct AnyJsAssignmentExpressionLikeIterator { + source_member: AnyNameLike, + source_object: AnyJsExpression, + current_member_expression: Option, + drained: bool, +} + +impl AnyJsAssignmentExpressionLikeIterator { + fn from_static_member_expression(source: JsStaticMemberExpression) -> SyntaxResult { + Ok(Self { + source_member: source.member().map(AnyNameLike::from)?, + source_object: source.object()?, + current_member_expression: None, + drained: false, + }) + } + + fn from_static_member_assignment(source: JsStaticMemberAssignment) -> SyntaxResult { + Ok(Self { + source_member: source.member().map(AnyNameLike::from)?, + source_object: source.object()?, + current_member_expression: None, + drained: false, + }) + } + + fn from_computed_member_assignment(source: JsComputedMemberAssignment) -> SyntaxResult { + Ok(Self { + source_member: source.member().and_then(|expression| match expression { + AnyJsExpression::JsIdentifierExpression(node) => Ok(AnyNameLike::from(node)), + _ => Err(SyntaxError::MissingRequiredChild), + })?, + source_object: source.object()?, + current_member_expression: None, + drained: false, + }) + } + + fn from_computed_member_expression(source: JsComputedMemberExpression) -> SyntaxResult { + Ok(Self { + source_member: source.member().and_then(|expression| match expression { + AnyJsExpression::JsIdentifierExpression(node) => Ok(AnyNameLike::from(node)), + _ => Err(SyntaxError::MissingRequiredChild), + })?, + source_object: source.object()?, + current_member_expression: None, + drained: false, + }) + } +} + +impl Iterator for AnyJsAssignmentExpressionLikeIterator { + type Item = (AnyNameLike, Option); + + fn next(&mut self) -> Option { + if self.drained { + return None; + } + + let (name, object) = + if let Some(current_member_expression) = self.current_member_expression.as_ref() { + ( + current_member_expression.member()?, + current_member_expression.object()?, + ) + } else { + (self.source_member.clone(), self.source_object.clone()) + }; + + let reference = match object { + AnyJsExpression::JsStaticMemberExpression(expression) => { + self.current_member_expression = + Some(AnyAssignmentExpressionLike::from(expression)); + None + } + AnyJsExpression::JsIdentifierExpression(identifier) => { + // the left side of the static member expression is an identifier, which means that we can't + // go any further and we should mark the iterator and drained + self.drained = true; + Some(identifier.name().ok()?) + } + _ => return None, + }; + Some((name, reference)) + } +} + +impl FusedIterator for AnyJsAssignmentExpressionLikeIterator {} + +/// Convenient type to map assignments that have similar arms +#[derive(Debug, Clone)] +enum AnyAssignmentLike { + /// No assignments. This variant is used to signal that there aren't any more assignments + /// to inspect + None, + /// To track identifiers that will be compared and check if they are the same. + Identifiers(IdentifiersLike), + /// To track array assignment-likes + /// ```js + /// [a] = [a] + /// ``` + /// + /// It stores a left iterator and a right iterator. Using iterators is useful to signal when + /// there aren't any more elements to inspect. + Arrays { + left: AstSeparatedListNodesIterator, + right: AstSeparatedListNodesIterator, + }, + /// To track assignments like + /// ```js + /// {a} = {a} + /// ``` + /// + /// It stores a left iterator and a right iterator. Using iterators is useful to signal when + /// there aren't any more elements to inspect. + Object { + left: AstSeparatedListNodesIterator, + right: AstSeparatedListNodesIterator, + }, + /// To track static expressions + /// ```js + /// a.b = a.b; + /// a[b] = a[b]; + /// ``` + /// + /// It stores a left iterator and a right iterator. Using iterators is useful to signal when + /// there aren't any more elements to inspect. + StaticExpression { + left: AnyJsAssignmentExpressionLikeIterator, + right: AnyJsAssignmentExpressionLikeIterator, + }, +} + +declare_node_union! { + pub(crate) AnyNameLike = AnyJsName | JsReferenceIdentifier | JsIdentifierExpression +} + +declare_node_union! { + pub(crate) AnyAssignmentExpressionLike = JsStaticMemberExpression | JsComputedMemberExpression +} + +impl AnyAssignmentExpressionLike { + fn member(&self) -> Option { + match self { + AnyAssignmentExpressionLike::JsStaticMemberExpression(node) => { + node.member().ok().map(AnyNameLike::from) + } + AnyAssignmentExpressionLike::JsComputedMemberExpression(node) => { + node.member().ok().and_then(|node| { + Some(AnyNameLike::from( + node.as_js_identifier_expression()?.name().ok()?, + )) + }) + } + } + } + + fn object(&self) -> Option { + match self { + AnyAssignmentExpressionLike::JsStaticMemberExpression(node) => node.object().ok(), + AnyAssignmentExpressionLike::JsComputedMemberExpression(node) => node.object().ok(), + } + } +} + +impl AnyAssignmentLike { + const fn has_sub_structures(&self) -> bool { + matches!( + self, + AnyAssignmentLike::Arrays { .. } | AnyAssignmentLike::Object { .. } + ) + } +} + +impl TryFrom<(AnyJsAssignmentPattern, AnyJsExpression)> for AnyAssignmentLike { + type Error = SyntaxError; + + fn try_from( + (left, right): (AnyJsAssignmentPattern, AnyJsExpression), + ) -> Result { + Ok(match (left, right) { + ( + AnyJsAssignmentPattern::JsArrayAssignmentPattern(left), + AnyJsExpression::JsArrayExpression(right), + ) => AnyAssignmentLike::Arrays { + left: left.elements().iter(), + right: right.elements().iter(), + }, + + ( + AnyJsAssignmentPattern::JsObjectAssignmentPattern(left), + AnyJsExpression::JsObjectExpression(right), + ) => AnyAssignmentLike::Object { + left: left.properties().iter(), + right: right.members().iter(), + }, + + ( + AnyJsAssignmentPattern::AnyJsAssignment(AnyJsAssignment::JsIdentifierAssignment( + left, + )), + AnyJsExpression::JsIdentifierExpression(right), + ) => AnyAssignmentLike::Identifiers(IdentifiersLike::IdentifierAndReference( + left, + right.name()?, + )), + ( + AnyJsAssignmentPattern::AnyJsAssignment(AnyJsAssignment::JsStaticMemberAssignment( + left, + )), + AnyJsExpression::JsStaticMemberExpression(right), + ) => AnyAssignmentLike::StaticExpression { + left: AnyJsAssignmentExpressionLikeIterator::from_static_member_assignment(left)?, + right: AnyJsAssignmentExpressionLikeIterator::from_static_member_expression(right)?, + }, + + ( + AnyJsAssignmentPattern::AnyJsAssignment( + AnyJsAssignment::JsComputedMemberAssignment(left), + ), + AnyJsExpression::JsComputedMemberExpression(right), + ) => AnyAssignmentLike::StaticExpression { + left: AnyJsAssignmentExpressionLikeIterator::from_computed_member_assignment(left)?, + right: AnyJsAssignmentExpressionLikeIterator::from_computed_member_expression( + right, + )?, + }, + _ => AnyAssignmentLike::None, + }) + } +} + +/// Convenient type that pair possible combination of "identifiers" like that we can find. +/// +/// Each variant has two types: +/// - the first one is the identifier found in the left arm of the assignment; +/// - the second one is the identifier found in the right arm of the assignment; +#[derive(Debug, Clone)] +pub(crate) enum IdentifiersLike { + /// To store identifiers found in code like: + /// + /// ```js + /// a = a; + /// [a] = [a]; + /// {a} = {a}; + /// ``` + IdentifierAndReference(JsIdentifierAssignment, JsReferenceIdentifier), + /// To store identifiers found in code like: + /// + /// ```js + /// a[b] = a[b]; + /// ``` + References(JsReferenceIdentifier, JsReferenceIdentifier), + /// To store identifiers found in code like: + /// + /// ```js + /// a.b = a.b; + /// ``` + Name(JsName, JsName), + /// To store identifiers found in code like: + /// + /// ```js + /// a.#b = a.#b; + /// ``` + PrivateName(JsPrivateName, JsPrivateName), +} + +impl TryFrom<(AnyNameLike, AnyNameLike)> for IdentifiersLike { + type Error = SyntaxError; + + fn try_from((left, right): (AnyNameLike, AnyNameLike)) -> Result { + match (left, right) { + ( + AnyNameLike::AnyJsName(AnyJsName::JsName(left)), + AnyNameLike::AnyJsName(AnyJsName::JsName(right)), + ) => Ok(Self::Name(left, right)), + ( + AnyNameLike::AnyJsName(AnyJsName::JsPrivateName(left)), + AnyNameLike::AnyJsName(AnyJsName::JsPrivateName(right)), + ) => Ok(Self::PrivateName(left, right)), + + ( + AnyNameLike::JsReferenceIdentifier(left), + AnyNameLike::JsReferenceIdentifier(right), + ) => Ok(Self::References(left, right)), + + ( + AnyNameLike::JsIdentifierExpression(left), + AnyNameLike::JsIdentifierExpression(right), + ) => Ok(Self::References(left.name()?, right.name()?)), + + _ => unreachable!("you should map the correct references"), + } + } +} + +impl IdentifiersLike { + fn left_range(&self) -> TextRange { + match self { + IdentifiersLike::IdentifierAndReference(left, _) => left.range(), + IdentifiersLike::Name(left, _) => left.range(), + IdentifiersLike::PrivateName(left, _) => left.range(), + IdentifiersLike::References(left, _) => left.range(), + } + } + + fn right_range(&self) -> TextRange { + match self { + IdentifiersLike::IdentifierAndReference(_, right) => right.range(), + IdentifiersLike::Name(_, right) => right.range(), + IdentifiersLike::PrivateName(_, right) => right.range(), + IdentifiersLike::References(_, right) => right.range(), + } + } + + fn name(&self) -> Option { + match self { + IdentifiersLike::IdentifierAndReference(_, right) => right.value_token().ok(), + IdentifiersLike::Name(_, right) => right.value_token().ok(), + IdentifiersLike::PrivateName(_, right) => right.value_token().ok(), + IdentifiersLike::References(_, right) => right.value_token().ok(), + } + } +} + +/// Checks if the left identifier and the right reference have the same name +fn with_same_identifiers(identifiers_like: &IdentifiersLike) -> Option<()> { + let (left_value, right_value) = match &identifiers_like { + IdentifiersLike::IdentifierAndReference(left, right) => { + let left_value = left.name_token().ok()?; + let right_value = right.value_token().ok()?; + (left_value, right_value) + } + IdentifiersLike::Name(left, right) => { + let left_value = left.value_token().ok()?; + let right_value = right.value_token().ok()?; + (left_value, right_value) + } + IdentifiersLike::PrivateName(left, right) => { + let left_value = left.value_token().ok()?; + let right_value = right.value_token().ok()?; + (left_value, right_value) + } + IdentifiersLike::References(left, right) => { + let left_value = left.value_token().ok()?; + let right_value = right.value_token().ok()?; + (left_value, right_value) + } + }; + + if left_value.text_trimmed() == right_value.text_trimmed() { + Some(()) + } else { + None + } +} diff --git a/crates/rome_js_analyze/src/lib.rs b/crates/rome_js_analyze/src/lib.rs index ab14e20a0bb..6a55132660d 100644 --- a/crates/rome_js_analyze/src/lib.rs +++ b/crates/rome_js_analyze/src/lib.rs @@ -173,13 +173,13 @@ mod tests { String::from_utf8(buffer).unwrap() } - const SOURCE: &str = r#" ;"#; + const SOURCE: &str = r#"a = a"#; let parsed = parse(SOURCE, SourceType::jsx()); let mut error_ranges: Vec = Vec::new(); let options = AnalyzerOptions::default(); - let rule_filter = RuleFilter::Rule("nursery", "useAriaPropTypes"); + let rule_filter = RuleFilter::Rule("nursery", "noSelfAssignment"); analyze( &parsed.tree(), AnalysisFilter { diff --git a/crates/rome_js_analyze/tests/specs/nursery/noSelfAssignment/invalid.js b/crates/rome_js_analyze/tests/specs/nursery/noSelfAssignment/invalid.js new file mode 100644 index 00000000000..d05f87c4cd3 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noSelfAssignment/invalid.js @@ -0,0 +1,21 @@ +a = a; +[a] = [a]; +[a, b] = [a, b]; +[a, ...b] = [a, ...b]; +({a, b} = {a, b}); +[[a], [b]] = [[a], [b]]; +[{a}, {b}] = [{a}, {b}]; +[{a}, [b]] = [{a}, [b]]; +({a: b} = {a: b}); +({'a': b} = {'a': b}); +({a: b} = {'a': b}); +({1: b} = {'1': b}); +({1: b} = {1: b}); +({['a']: b} = {a: b}); +({1: b} = {[1]: b}); +({a: {b}, c: [d]} = {a: {b}, c: [d]}); +({a, b} = {a, ...x, b}); +a.b = a.b; +a.#b = a.#b; +a.c.b = a.ZZ.b; +a[b] = a[b]; diff --git a/crates/rome_js_analyze/tests/specs/nursery/noSelfAssignment/invalid.js.snap b/crates/rome_js_analyze/tests/specs/nursery/noSelfAssignment/invalid.js.snap new file mode 100644 index 00000000000..52f0bd3b5e3 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noSelfAssignment/invalid.js.snap @@ -0,0 +1,744 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```js +a = a; +[a] = [a]; +[a, b] = [a, b]; +[a, ...b] = [a, ...b]; +({a, b} = {a, b}); +[[a], [b]] = [[a], [b]]; +[{a}, {b}] = [{a}, {b}]; +[{a}, [b]] = [{a}, [b]]; +({a: b} = {a: b}); +({'a': b} = {'a': b}); +({a: b} = {'a': b}); +({1: b} = {'1': b}); +({1: b} = {1: b}); +({['a']: b} = {a: b}); +({1: b} = {[1]: b}); +({a: {b}, c: [d]} = {a: {b}, c: [d]}); +({a, b} = {a, ...x, b}); +a.b = a.b; +a.#b = a.#b; +a.c.b = a.ZZ.b; +a[b] = a[b]; + +``` + +# Diagnostics +``` +invalid.js:1:5 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! a is assigned to itself. + + > 1 │ a = a; + │ ^ + 2 │ [a] = [a]; + 3 │ [a, b] = [a, b]; + + i This is where is assigned. + + > 1 │ a = a; + │ ^ + 2 │ [a] = [a]; + 3 │ [a, b] = [a, b]; + + +``` + +``` +invalid.js:2:8 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! a is assigned to itself. + + 1 │ a = a; + > 2 │ [a] = [a]; + │ ^ + 3 │ [a, b] = [a, b]; + 4 │ [a, ...b] = [a, ...b]; + + i This is where is assigned. + + 1 │ a = a; + > 2 │ [a] = [a]; + │ ^ + 3 │ [a, b] = [a, b]; + 4 │ [a, ...b] = [a, ...b]; + + +``` + +``` +invalid.js:3:11 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! a is assigned to itself. + + 1 │ a = a; + 2 │ [a] = [a]; + > 3 │ [a, b] = [a, b]; + │ ^ + 4 │ [a, ...b] = [a, ...b]; + 5 │ ({a, b} = {a, b}); + + i This is where is assigned. + + 1 │ a = a; + 2 │ [a] = [a]; + > 3 │ [a, b] = [a, b]; + │ ^ + 4 │ [a, ...b] = [a, ...b]; + 5 │ ({a, b} = {a, b}); + + +``` + +``` +invalid.js:3:14 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 1 │ a = a; + 2 │ [a] = [a]; + > 3 │ [a, b] = [a, b]; + │ ^ + 4 │ [a, ...b] = [a, ...b]; + 5 │ ({a, b} = {a, b}); + + i This is where is assigned. + + 1 │ a = a; + 2 │ [a] = [a]; + > 3 │ [a, b] = [a, b]; + │ ^ + 4 │ [a, ...b] = [a, ...b]; + 5 │ ({a, b} = {a, b}); + + +``` + +``` +invalid.js:4:14 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! a is assigned to itself. + + 2 │ [a] = [a]; + 3 │ [a, b] = [a, b]; + > 4 │ [a, ...b] = [a, ...b]; + │ ^ + 5 │ ({a, b} = {a, b}); + 6 │ [[a], [b]] = [[a], [b]]; + + i This is where is assigned. + + 2 │ [a] = [a]; + 3 │ [a, b] = [a, b]; + > 4 │ [a, ...b] = [a, ...b]; + │ ^ + 5 │ ({a, b} = {a, b}); + 6 │ [[a], [b]] = [[a], [b]]; + + +``` + +``` +invalid.js:5:12 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! a is assigned to itself. + + 3 │ [a, b] = [a, b]; + 4 │ [a, ...b] = [a, ...b]; + > 5 │ ({a, b} = {a, b}); + │ ^ + 6 │ [[a], [b]] = [[a], [b]]; + 7 │ [{a}, {b}] = [{a}, {b}]; + + i This is where is assigned. + + 3 │ [a, b] = [a, b]; + 4 │ [a, ...b] = [a, ...b]; + > 5 │ ({a, b} = {a, b}); + │ ^ + 6 │ [[a], [b]] = [[a], [b]]; + 7 │ [{a}, {b}] = [{a}, {b}]; + + +``` + +``` +invalid.js:5:15 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 3 │ [a, b] = [a, b]; + 4 │ [a, ...b] = [a, ...b]; + > 5 │ ({a, b} = {a, b}); + │ ^ + 6 │ [[a], [b]] = [[a], [b]]; + 7 │ [{a}, {b}] = [{a}, {b}]; + + i This is where is assigned. + + 3 │ [a, b] = [a, b]; + 4 │ [a, ...b] = [a, ...b]; + > 5 │ ({a, b} = {a, b}); + │ ^ + 6 │ [[a], [b]] = [[a], [b]]; + 7 │ [{a}, {b}] = [{a}, {b}]; + + +``` + +``` +invalid.js:6:16 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! a is assigned to itself. + + 4 │ [a, ...b] = [a, ...b]; + 5 │ ({a, b} = {a, b}); + > 6 │ [[a], [b]] = [[a], [b]]; + │ ^ + 7 │ [{a}, {b}] = [{a}, {b}]; + 8 │ [{a}, [b]] = [{a}, [b]]; + + i This is where is assigned. + + 4 │ [a, ...b] = [a, ...b]; + 5 │ ({a, b} = {a, b}); + > 6 │ [[a], [b]] = [[a], [b]]; + │ ^ + 7 │ [{a}, {b}] = [{a}, {b}]; + 8 │ [{a}, [b]] = [{a}, [b]]; + + +``` + +``` +invalid.js:6:21 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 4 │ [a, ...b] = [a, ...b]; + 5 │ ({a, b} = {a, b}); + > 6 │ [[a], [b]] = [[a], [b]]; + │ ^ + 7 │ [{a}, {b}] = [{a}, {b}]; + 8 │ [{a}, [b]] = [{a}, [b]]; + + i This is where is assigned. + + 4 │ [a, ...b] = [a, ...b]; + 5 │ ({a, b} = {a, b}); + > 6 │ [[a], [b]] = [[a], [b]]; + │ ^ + 7 │ [{a}, {b}] = [{a}, {b}]; + 8 │ [{a}, [b]] = [{a}, [b]]; + + +``` + +``` +invalid.js:6:21 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 4 │ [a, ...b] = [a, ...b]; + 5 │ ({a, b} = {a, b}); + > 6 │ [[a], [b]] = [[a], [b]]; + │ ^ + 7 │ [{a}, {b}] = [{a}, {b}]; + 8 │ [{a}, [b]] = [{a}, [b]]; + + i This is where is assigned. + + 4 │ [a, ...b] = [a, ...b]; + 5 │ ({a, b} = {a, b}); + > 6 │ [[a], [b]] = [[a], [b]]; + │ ^ + 7 │ [{a}, {b}] = [{a}, {b}]; + 8 │ [{a}, [b]] = [{a}, [b]]; + + +``` + +``` +invalid.js:7:16 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! a is assigned to itself. + + 5 │ ({a, b} = {a, b}); + 6 │ [[a], [b]] = [[a], [b]]; + > 7 │ [{a}, {b}] = [{a}, {b}]; + │ ^ + 8 │ [{a}, [b]] = [{a}, [b]]; + 9 │ ({a: b} = {a: b}); + + i This is where is assigned. + + 5 │ ({a, b} = {a, b}); + 6 │ [[a], [b]] = [[a], [b]]; + > 7 │ [{a}, {b}] = [{a}, {b}]; + │ ^ + 8 │ [{a}, [b]] = [{a}, [b]]; + 9 │ ({a: b} = {a: b}); + + +``` + +``` +invalid.js:7:21 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 5 │ ({a, b} = {a, b}); + 6 │ [[a], [b]] = [[a], [b]]; + > 7 │ [{a}, {b}] = [{a}, {b}]; + │ ^ + 8 │ [{a}, [b]] = [{a}, [b]]; + 9 │ ({a: b} = {a: b}); + + i This is where is assigned. + + 5 │ ({a, b} = {a, b}); + 6 │ [[a], [b]] = [[a], [b]]; + > 7 │ [{a}, {b}] = [{a}, {b}]; + │ ^ + 8 │ [{a}, [b]] = [{a}, [b]]; + 9 │ ({a: b} = {a: b}); + + +``` + +``` +invalid.js:7:21 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 5 │ ({a, b} = {a, b}); + 6 │ [[a], [b]] = [[a], [b]]; + > 7 │ [{a}, {b}] = [{a}, {b}]; + │ ^ + 8 │ [{a}, [b]] = [{a}, [b]]; + 9 │ ({a: b} = {a: b}); + + i This is where is assigned. + + 5 │ ({a, b} = {a, b}); + 6 │ [[a], [b]] = [[a], [b]]; + > 7 │ [{a}, {b}] = [{a}, {b}]; + │ ^ + 8 │ [{a}, [b]] = [{a}, [b]]; + 9 │ ({a: b} = {a: b}); + + +``` + +``` +invalid.js:8:16 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! a is assigned to itself. + + 6 │ [[a], [b]] = [[a], [b]]; + 7 │ [{a}, {b}] = [{a}, {b}]; + > 8 │ [{a}, [b]] = [{a}, [b]]; + │ ^ + 9 │ ({a: b} = {a: b}); + 10 │ ({'a': b} = {'a': b}); + + i This is where is assigned. + + 6 │ [[a], [b]] = [[a], [b]]; + 7 │ [{a}, {b}] = [{a}, {b}]; + > 8 │ [{a}, [b]] = [{a}, [b]]; + │ ^ + 9 │ ({a: b} = {a: b}); + 10 │ ({'a': b} = {'a': b}); + + +``` + +``` +invalid.js:8:21 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 6 │ [[a], [b]] = [[a], [b]]; + 7 │ [{a}, {b}] = [{a}, {b}]; + > 8 │ [{a}, [b]] = [{a}, [b]]; + │ ^ + 9 │ ({a: b} = {a: b}); + 10 │ ({'a': b} = {'a': b}); + + i This is where is assigned. + + 6 │ [[a], [b]] = [[a], [b]]; + 7 │ [{a}, {b}] = [{a}, {b}]; + > 8 │ [{a}, [b]] = [{a}, [b]]; + │ ^ + 9 │ ({a: b} = {a: b}); + 10 │ ({'a': b} = {'a': b}); + + +``` + +``` +invalid.js:8:21 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 6 │ [[a], [b]] = [[a], [b]]; + 7 │ [{a}, {b}] = [{a}, {b}]; + > 8 │ [{a}, [b]] = [{a}, [b]]; + │ ^ + 9 │ ({a: b} = {a: b}); + 10 │ ({'a': b} = {'a': b}); + + i This is where is assigned. + + 6 │ [[a], [b]] = [[a], [b]]; + 7 │ [{a}, {b}] = [{a}, {b}]; + > 8 │ [{a}, [b]] = [{a}, [b]]; + │ ^ + 9 │ ({a: b} = {a: b}); + 10 │ ({'a': b} = {'a': b}); + + +``` + +``` +invalid.js:9:15 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 7 │ [{a}, {b}] = [{a}, {b}]; + 8 │ [{a}, [b]] = [{a}, [b]]; + > 9 │ ({a: b} = {a: b}); + │ ^ + 10 │ ({'a': b} = {'a': b}); + 11 │ ({a: b} = {'a': b}); + + i This is where is assigned. + + 7 │ [{a}, {b}] = [{a}, {b}]; + 8 │ [{a}, [b]] = [{a}, [b]]; + > 9 │ ({a: b} = {a: b}); + │ ^ + 10 │ ({'a': b} = {'a': b}); + 11 │ ({a: b} = {'a': b}); + + +``` + +``` +invalid.js:10:19 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 8 │ [{a}, [b]] = [{a}, [b]]; + 9 │ ({a: b} = {a: b}); + > 10 │ ({'a': b} = {'a': b}); + │ ^ + 11 │ ({a: b} = {'a': b}); + 12 │ ({1: b} = {'1': b}); + + i This is where is assigned. + + 8 │ [{a}, [b]] = [{a}, [b]]; + 9 │ ({a: b} = {a: b}); + > 10 │ ({'a': b} = {'a': b}); + │ ^ + 11 │ ({a: b} = {'a': b}); + 12 │ ({1: b} = {'1': b}); + + +``` + +``` +invalid.js:11:17 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 9 │ ({a: b} = {a: b}); + 10 │ ({'a': b} = {'a': b}); + > 11 │ ({a: b} = {'a': b}); + │ ^ + 12 │ ({1: b} = {'1': b}); + 13 │ ({1: b} = {1: b}); + + i This is where is assigned. + + 9 │ ({a: b} = {a: b}); + 10 │ ({'a': b} = {'a': b}); + > 11 │ ({a: b} = {'a': b}); + │ ^ + 12 │ ({1: b} = {'1': b}); + 13 │ ({1: b} = {1: b}); + + +``` + +``` +invalid.js:12:17 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 10 │ ({'a': b} = {'a': b}); + 11 │ ({a: b} = {'a': b}); + > 12 │ ({1: b} = {'1': b}); + │ ^ + 13 │ ({1: b} = {1: b}); + 14 │ ({['a']: b} = {a: b}); + + i This is where is assigned. + + 10 │ ({'a': b} = {'a': b}); + 11 │ ({a: b} = {'a': b}); + > 12 │ ({1: b} = {'1': b}); + │ ^ + 13 │ ({1: b} = {1: b}); + 14 │ ({['a']: b} = {a: b}); + + +``` + +``` +invalid.js:13:15 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 11 │ ({a: b} = {'a': b}); + 12 │ ({1: b} = {'1': b}); + > 13 │ ({1: b} = {1: b}); + │ ^ + 14 │ ({['a']: b} = {a: b}); + 15 │ ({1: b} = {[1]: b}); + + i This is where is assigned. + + 11 │ ({a: b} = {'a': b}); + 12 │ ({1: b} = {'1': b}); + > 13 │ ({1: b} = {1: b}); + │ ^ + 14 │ ({['a']: b} = {a: b}); + 15 │ ({1: b} = {[1]: b}); + + +``` + +``` +invalid.js:14:19 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 12 │ ({1: b} = {'1': b}); + 13 │ ({1: b} = {1: b}); + > 14 │ ({['a']: b} = {a: b}); + │ ^ + 15 │ ({1: b} = {[1]: b}); + 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + + i This is where is assigned. + + 12 │ ({1: b} = {'1': b}); + 13 │ ({1: b} = {1: b}); + > 14 │ ({['a']: b} = {a: b}); + │ ^ + 15 │ ({1: b} = {[1]: b}); + 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + + +``` + +``` +invalid.js:15:17 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 13 │ ({1: b} = {1: b}); + 14 │ ({['a']: b} = {a: b}); + > 15 │ ({1: b} = {[1]: b}); + │ ^ + 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + 17 │ ({a, b} = {a, ...x, b}); + + i This is where is assigned. + + 13 │ ({1: b} = {1: b}); + 14 │ ({['a']: b} = {a: b}); + > 15 │ ({1: b} = {[1]: b}); + │ ^ + 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + 17 │ ({a, b} = {a, ...x, b}); + + +``` + +``` +invalid.js:16:26 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 14 │ ({['a']: b} = {a: b}); + 15 │ ({1: b} = {[1]: b}); + > 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + │ ^ + 17 │ ({a, b} = {a, ...x, b}); + 18 │ a.b = a.b; + + i This is where is assigned. + + 14 │ ({['a']: b} = {a: b}); + 15 │ ({1: b} = {[1]: b}); + > 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + │ ^ + 17 │ ({a, b} = {a, ...x, b}); + 18 │ a.b = a.b; + + +``` + +``` +invalid.js:16:34 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! d is assigned to itself. + + 14 │ ({['a']: b} = {a: b}); + 15 │ ({1: b} = {[1]: b}); + > 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + │ ^ + 17 │ ({a, b} = {a, ...x, b}); + 18 │ a.b = a.b; + + i This is where is assigned. + + 14 │ ({['a']: b} = {a: b}); + 15 │ ({1: b} = {[1]: b}); + > 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + │ ^ + 17 │ ({a, b} = {a, ...x, b}); + 18 │ a.b = a.b; + + +``` + +``` +invalid.js:16:34 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! d is assigned to itself. + + 14 │ ({['a']: b} = {a: b}); + 15 │ ({1: b} = {[1]: b}); + > 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + │ ^ + 17 │ ({a, b} = {a, ...x, b}); + 18 │ a.b = a.b; + + i This is where is assigned. + + 14 │ ({['a']: b} = {a: b}); + 15 │ ({1: b} = {[1]: b}); + > 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + │ ^ + 17 │ ({a, b} = {a, ...x, b}); + 18 │ a.b = a.b; + + +``` + +``` +invalid.js:17:12 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! a is assigned to itself. + + 15 │ ({1: b} = {[1]: b}); + 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + > 17 │ ({a, b} = {a, ...x, b}); + │ ^ + 18 │ a.b = a.b; + 19 │ a.#b = a.#b; + + i This is where is assigned. + + 15 │ ({1: b} = {[1]: b}); + 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + > 17 │ ({a, b} = {a, ...x, b}); + │ ^ + 18 │ a.b = a.b; + 19 │ a.#b = a.#b; + + +``` + +``` +invalid.js:18:9 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + 17 │ ({a, b} = {a, ...x, b}); + > 18 │ a.b = a.b; + │ ^ + 19 │ a.#b = a.#b; + 20 │ a.c.b = a.ZZ.b; + + i This is where is assigned. + + 16 │ ({a: {b}, c: [d]} = {a: {b}, c: [d]}); + 17 │ ({a, b} = {a, ...x, b}); + > 18 │ a.b = a.b; + │ ^ + 19 │ a.#b = a.#b; + 20 │ a.c.b = a.ZZ.b; + + +``` + +``` +invalid.js:19:10 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 17 │ ({a, b} = {a, ...x, b}); + 18 │ a.b = a.b; + > 19 │ a.#b = a.#b; + │ ^^ + 20 │ a.c.b = a.ZZ.b; + 21 │ a[b] = a[b]; + + i This is where is assigned. + + 17 │ ({a, b} = {a, ...x, b}); + 18 │ a.b = a.b; + > 19 │ a.#b = a.#b; + │ ^^ + 20 │ a.c.b = a.ZZ.b; + 21 │ a[b] = a[b]; + + +``` + +``` +invalid.js:21:10 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! b is assigned to itself. + + 19 │ a.#b = a.#b; + 20 │ a.c.b = a.ZZ.b; + > 21 │ a[b] = a[b]; + │ ^ + 22 │ + + i This is where is assigned. + + 19 │ a.#b = a.#b; + 20 │ a.c.b = a.ZZ.b; + > 21 │ a[b] = a[b]; + │ ^ + 22 │ + + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/noSelfAssignment/valid.js b/crates/rome_js_analyze/tests/specs/nursery/noSelfAssignment/valid.js new file mode 100644 index 00000000000..72f7bc9f7aa --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noSelfAssignment/valid.js @@ -0,0 +1,16 @@ +/* should not generate diagnostics */ +var a = a; +let a = a; +const a = a; +a = b; +a += a; +a = +a; +a = [a]; +a &= a; +a |= a; +[a, b] = [b, a]; +[a = 1] = [a]; +[x, a] = [...x, a]; +a.c = b.c; +a.b.c = a.Z.c; +a[b] = a[c]; diff --git a/crates/rome_js_analyze/tests/specs/nursery/noSelfAssignment/valid.js.snap b/crates/rome_js_analyze/tests/specs/nursery/noSelfAssignment/valid.js.snap new file mode 100644 index 00000000000..96310e9bed1 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noSelfAssignment/valid.js.snap @@ -0,0 +1,26 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```js +/* should not generate diagnostics */ +var a = a; +let a = a; +const a = a; +a = b; +a += a; +a = +a; +a = [a]; +a &= a; +a |= a; +[a, b] = [b, a]; +[a = 1] = [a]; +[x, a] = [...x, a]; +a.c = b.c; +a.b.c = a.Z.c; +a[b] = a[c]; + +``` + + diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index 8b5ff769662..1de4970607f 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -1023,6 +1023,9 @@ pub struct Nursery { #[doc = "This rule allows you to specify global variable names that you don’t want to use in your application."] #[serde(skip_serializing_if = "Option::is_none")] pub no_restricted_globals: Option, + #[doc = "Put your description here"] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_self_assignment: Option, #[doc = "Disallow comparisons where both sides are exactly the same."] #[serde(skip_serializing_if = "Option::is_none")] pub no_self_compare: Option, @@ -1113,7 +1116,7 @@ pub struct Nursery { } impl Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 59] = [ + pub(crate) const GROUP_RULES: [&'static str; 60] = [ "noAccessKey", "noAssignInExpressions", "noBannedTypes", @@ -1144,6 +1147,7 @@ impl Nursery { "noRedundantAlt", "noRedundantUseStrict", "noRestrictedGlobals", + "noSelfAssignment", "noSelfCompare", "noSetterReturn", "noStringCaseMismatch", @@ -1248,7 +1252,6 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]), @@ -1260,19 +1263,20 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59]), ]; pub(crate) fn is_recommended(&self) -> bool { !matches!(self.recommended, Some(false)) } pub(crate) fn get_enabled_rules(&self) -> IndexSet { @@ -1427,151 +1431,156 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.no_self_compare.as_ref() { + if let Some(rule) = self.no_self_assignment.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } } - if let Some(rule) = self.no_setter_return.as_ref() { + if let Some(rule) = self.no_self_compare.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.no_string_case_mismatch.as_ref() { + if let Some(rule) = self.no_setter_return.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.no_switch_declarations.as_ref() { + if let Some(rule) = self.no_string_case_mismatch.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - if let Some(rule) = self.no_unreachable_super.as_ref() { + if let Some(rule) = self.no_switch_declarations.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } } - if let Some(rule) = self.no_unsafe_finally.as_ref() { + if let Some(rule) = self.no_unreachable_super.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.no_unused_labels.as_ref() { + if let Some(rule) = self.no_unsafe_finally.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.no_useless_rename.as_ref() { + if let Some(rule) = self.no_unused_labels.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.no_useless_switch_case.as_ref() { + if let Some(rule) = self.no_useless_rename.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.no_var.as_ref() { + if let Some(rule) = self.no_useless_switch_case.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.no_void_type_return.as_ref() { + if let Some(rule) = self.no_var.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - if let Some(rule) = self.no_with.as_ref() { + if let Some(rule) = self.no_void_type_return.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } } - if let Some(rule) = self.use_aria_prop_types.as_ref() { + if let Some(rule) = self.no_with.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_aria_props_for_role.as_ref() { + if let Some(rule) = self.use_aria_prop_types.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_camel_case.as_ref() { + if let Some(rule) = self.use_aria_props_for_role.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_const.as_ref() { + if let Some(rule) = self.use_camel_case.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } - if let Some(rule) = self.use_default_parameter_last.as_ref() { + if let Some(rule) = self.use_const.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } } - if let Some(rule) = self.use_default_switch_clause_last.as_ref() { + if let Some(rule) = self.use_default_parameter_last.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } } - if let Some(rule) = self.use_enum_initializers.as_ref() { + if let Some(rule) = self.use_default_switch_clause_last.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); } } - if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { + if let Some(rule) = self.use_enum_initializers.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49])); } } - if let Some(rule) = self.use_exponentiation_operator.as_ref() { + if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50])); } } - if let Some(rule) = self.use_hook_at_top_level.as_ref() { + if let Some(rule) = self.use_exponentiation_operator.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51])); } } - if let Some(rule) = self.use_iframe_title.as_ref() { + if let Some(rule) = self.use_hook_at_top_level.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52])); } } - if let Some(rule) = self.use_is_nan.as_ref() { + if let Some(rule) = self.use_iframe_title.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53])); } } - if let Some(rule) = self.use_media_caption.as_ref() { + if let Some(rule) = self.use_is_nan.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54])); } } - if let Some(rule) = self.use_numeric_literals.as_ref() { + if let Some(rule) = self.use_media_caption.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55])); } } - if let Some(rule) = self.use_valid_aria_props.as_ref() { + if let Some(rule) = self.use_numeric_literals.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56])); } } - if let Some(rule) = self.use_valid_lang.as_ref() { + if let Some(rule) = self.use_valid_aria_props.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57])); } } - if let Some(rule) = self.use_yield.as_ref() { + if let Some(rule) = self.use_valid_lang.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); } } + if let Some(rule) = self.use_yield.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -1726,151 +1735,156 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.no_self_compare.as_ref() { + if let Some(rule) = self.no_self_assignment.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } } - if let Some(rule) = self.no_setter_return.as_ref() { + if let Some(rule) = self.no_self_compare.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.no_string_case_mismatch.as_ref() { + if let Some(rule) = self.no_setter_return.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.no_switch_declarations.as_ref() { + if let Some(rule) = self.no_string_case_mismatch.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - if let Some(rule) = self.no_unreachable_super.as_ref() { + if let Some(rule) = self.no_switch_declarations.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } } - if let Some(rule) = self.no_unsafe_finally.as_ref() { + if let Some(rule) = self.no_unreachable_super.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.no_unused_labels.as_ref() { + if let Some(rule) = self.no_unsafe_finally.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.no_useless_rename.as_ref() { + if let Some(rule) = self.no_unused_labels.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.no_useless_switch_case.as_ref() { + if let Some(rule) = self.no_useless_rename.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.no_var.as_ref() { + if let Some(rule) = self.no_useless_switch_case.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.no_void_type_return.as_ref() { + if let Some(rule) = self.no_var.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - if let Some(rule) = self.no_with.as_ref() { + if let Some(rule) = self.no_void_type_return.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } } - if let Some(rule) = self.use_aria_prop_types.as_ref() { + if let Some(rule) = self.no_with.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_aria_props_for_role.as_ref() { + if let Some(rule) = self.use_aria_prop_types.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_camel_case.as_ref() { + if let Some(rule) = self.use_aria_props_for_role.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_const.as_ref() { + if let Some(rule) = self.use_camel_case.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } - if let Some(rule) = self.use_default_parameter_last.as_ref() { + if let Some(rule) = self.use_const.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } } - if let Some(rule) = self.use_default_switch_clause_last.as_ref() { + if let Some(rule) = self.use_default_parameter_last.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } } - if let Some(rule) = self.use_enum_initializers.as_ref() { + if let Some(rule) = self.use_default_switch_clause_last.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); } } - if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { + if let Some(rule) = self.use_enum_initializers.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49])); } } - if let Some(rule) = self.use_exponentiation_operator.as_ref() { + if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50])); } } - if let Some(rule) = self.use_hook_at_top_level.as_ref() { + if let Some(rule) = self.use_exponentiation_operator.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51])); } } - if let Some(rule) = self.use_iframe_title.as_ref() { + if let Some(rule) = self.use_hook_at_top_level.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52])); } } - if let Some(rule) = self.use_is_nan.as_ref() { + if let Some(rule) = self.use_iframe_title.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53])); } } - if let Some(rule) = self.use_media_caption.as_ref() { + if let Some(rule) = self.use_is_nan.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54])); } } - if let Some(rule) = self.use_numeric_literals.as_ref() { + if let Some(rule) = self.use_media_caption.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55])); } } - if let Some(rule) = self.use_valid_aria_props.as_ref() { + if let Some(rule) = self.use_numeric_literals.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56])); } } - if let Some(rule) = self.use_valid_lang.as_ref() { + if let Some(rule) = self.use_valid_aria_props.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57])); } } - if let Some(rule) = self.use_yield.as_ref() { + if let Some(rule) = self.use_valid_lang.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); } } + if let Some(rule) = self.use_yield.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -1916,6 +1930,7 @@ impl Nursery { "noRedundantAlt" => self.no_redundant_alt.as_ref(), "noRedundantUseStrict" => self.no_redundant_use_strict.as_ref(), "noRestrictedGlobals" => self.no_restricted_globals.as_ref(), + "noSelfAssignment" => self.no_self_assignment.as_ref(), "noSelfCompare" => self.no_self_compare.as_ref(), "noSetterReturn" => self.no_setter_return.as_ref(), "noStringCaseMismatch" => self.no_string_case_mismatch.as_ref(), diff --git a/crates/rome_service/src/configuration/parse/json/rules.rs b/crates/rome_service/src/configuration/parse/json/rules.rs index ae405aec710..156be8ceb5d 100644 --- a/crates/rome_service/src/configuration/parse/json/rules.rs +++ b/crates/rome_service/src/configuration/parse/json/rules.rs @@ -733,6 +733,7 @@ impl VisitNode for Nursery { "noRedundantAlt", "noRedundantUseStrict", "noRestrictedGlobals", + "noSelfAssignment", "noSelfCompare", "noSetterReturn", "noStringCaseMismatch", @@ -1318,6 +1319,24 @@ impl VisitNode for Nursery { )); } }, + "noSelfAssignment" => match value { + AnyJsonValue::JsonStringValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_known_string(&value, name_text, &mut configuration, diagnostics)?; + self.no_self_assignment = Some(configuration); + } + AnyJsonValue::JsonObjectValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_object(&value, name_text, &mut configuration, diagnostics)?; + self.no_self_assignment = Some(configuration); + } + _ => { + diagnostics.push(DeserializationDiagnostic::new_incorrect_type( + "object or string", + value.range(), + )); + } + }, "noSelfCompare" => match value { AnyJsonValue::JsonStringValue(_) => { let mut configuration = RuleConfiguration::default(); diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 513c46e67fa..f75b006850d 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -599,6 +599,13 @@ { "type": "null" } ] }, + "noSelfAssignment": { + "description": "Put your description here", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noSelfCompare": { "description": "Disallow comparisons where both sides are exactly the same.", "anyOf": [ diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index 3ec8e462725..aea0ac8923f 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -404,6 +404,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; + /** + * Put your description here + */ + noSelfAssignment?: RuleConfiguration; /** * Disallow comparisons where both sides are exactly the same. */ @@ -854,6 +858,7 @@ export type Category = | "lint/nursery/useYield" | "lint/nursery/noGlobalObjectCalls" | "lint/nursery/noPrototypeBuiltins" + | "lint/nursery/noSelfAssignment" | "lint/performance/noDelete" | "lint/security/noDangerouslySetInnerHtml" | "lint/security/noDangerouslySetInnerHtmlWithChildren" diff --git a/npm/rome/configuration_schema.json b/npm/rome/configuration_schema.json index 513c46e67fa..f75b006850d 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -599,6 +599,13 @@ { "type": "null" } ] }, + "noSelfAssignment": { + "description": "Put your description here", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noSelfCompare": { "description": "Disallow comparisons where both sides are exactly the same.", "anyOf": [ diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index 15028a805bb..0bca0f170d9 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -666,6 +666,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.
+

+ noSelfAssignment +

+Put your description here +
+

noSelfCompare

diff --git a/website/src/pages/lint/rules/noSelfAssignment.md b/website/src/pages/lint/rules/noSelfAssignment.md new file mode 100644 index 00000000000..662c7ae5adb --- /dev/null +++ b/website/src/pages/lint/rules/noSelfAssignment.md @@ -0,0 +1,127 @@ +--- +title: Lint Rule noSelfAssignment +parent: lint/rules/index +--- + +# noSelfAssignment (since v12.0.0) + +Put your description here + +## Examples + +### Invalid + +```jsx +a = a; +``` + +
nursery/noSelfAssignment.js:1:5 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   a is assigned to itself.
+  
+  > 1 │ a = a;
+       ^
+    2 │ 
+  
+   This is where is assigned.
+  
+  > 1 │ a = a;
+   ^
+    2 │ 
+  
+
+ +```jsx +[a] = [a]; +``` + +
nursery/noSelfAssignment.js:1:8 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   a is assigned to itself.
+  
+  > 1 │ [a] = [a];
+          ^
+    2 │ 
+  
+   This is where is assigned.
+  
+  > 1 │ [a] = [a];
+    ^
+    2 │ 
+  
+
+ +```jsx +({a: b} = {a: b}); +``` + +
nursery/noSelfAssignment.js:1:15 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   b is assigned to itself.
+  
+  > 1 │ ({a: b} = {a: b});
+                 ^
+    2 │ 
+  
+   This is where is assigned.
+  
+  > 1 │ ({a: b} = {a: b});
+        ^
+    2 │ 
+  
+
+ +```jsx +a.b = a.b; +``` + +
nursery/noSelfAssignment.js:1:9 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   b is assigned to itself.
+  
+  > 1 │ a.b = a.b;
+           ^
+    2 │ 
+  
+   This is where is assigned.
+  
+  > 1 │ a.b = a.b;
+     ^
+    2 │ 
+  
+
+ +```jsx +a[b] = a[b]; +``` + +
nursery/noSelfAssignment.js:1:10 lint/nursery/noSelfAssignment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   b is assigned to itself.
+  
+  > 1 │ a[b] = a[b];
+            ^
+    2 │ 
+  
+   This is where is assigned.
+  
+  > 1 │ a[b] = a[b];
+     ^
+    2 │ 
+  
+
+ +## Valid + +```jsx +a &= a; +var a = a; +let a = a; +const a = a; +[a, b] = [b, a]; +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options)