diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e8cba863d9..8153795cece 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### New features * [#8322](https://github.com/rubocop-hq/rubocop/pull/8322): Support autocorrect for `Style/CaseEquality` cop. ([@fatkodima][]) +* [#8291](https://github.com/rubocop-hq/rubocop/pull/8291): Add new `Lint/SelfAssignment` cop. ([@fatkodima][]) * [#8389](https://github.com/rubocop-hq/rubocop/pull/8389): Add new `Lint/DuplicateRescueException` cop. ([@fatkodima][]) * [#8376](https://github.com/rubocop-hq/rubocop/pull/8376): Add new `Lint/MissingSuper` cop. ([@fatkodima][]) * [#8339](https://github.com/rubocop-hq/rubocop/pull/8339): Add `Config#for_badge` as an efficient way to get a cop's config merged with its department's. ([@marcandre][]) diff --git a/config/default.yml b/config/default.yml index 1cbc433883b..87aabea051e 100644 --- a/config/default.yml +++ b/config/default.yml @@ -1770,6 +1770,11 @@ Lint/ScriptPermission: VersionAdded: '0.49' VersionChanged: '0.50' +Lint/SelfAssignment: + Description: 'Checks for self-assignments.' + Enabled: pending + VersionAdded: '0.89' + Lint/SendWithMixinArgument: Description: 'Checks for `send` method when using mixin.' Enabled: true diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 86ed5b96071..d0a42af80f1 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -248,6 +248,7 @@ In the following section you find all available cops: * xref:cops_lint.adoc#lintsafenavigationconsistency[Lint/SafeNavigationConsistency] * xref:cops_lint.adoc#lintsafenavigationwithempty[Lint/SafeNavigationWithEmpty] * xref:cops_lint.adoc#lintscriptpermission[Lint/ScriptPermission] +* xref:cops_lint.adoc#lintselfassignment[Lint/SelfAssignment] * xref:cops_lint.adoc#lintsendwithmixinargument[Lint/SendWithMixinArgument] * xref:cops_lint.adoc#lintshadowedargument[Lint/ShadowedArgument] * xref:cops_lint.adoc#lintshadowedexception[Lint/ShadowedException] diff --git a/docs/modules/ROOT/pages/cops_lint.adoc b/docs/modules/ROOT/pages/cops_lint.adoc index 62a1c3b4988..6941829bf0f 100644 --- a/docs/modules/ROOT/pages/cops_lint.adoc +++ b/docs/modules/ROOT/pages/cops_lint.adoc @@ -3049,6 +3049,35 @@ puts 'hello, world' puts 'hello, world' ---- +== Lint/SelfAssignment + +|=== +| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged + +| Pending +| Yes +| No +| 0.89 +| - +|=== + +This cop checks for self-assignments. + +=== Examples + +[source,ruby] +---- +# bad +foo = foo +foo, bar = foo, bar +Foo = Foo + +# good +foo = bar +foo, bar = bar, foo +Foo = Bar +---- + == Lint/SendWithMixinArgument |=== diff --git a/lib/rubocop.rb b/lib/rubocop.rb index 89f77333c17..0ae870d0f71 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -305,6 +305,7 @@ require_relative 'rubocop/cop/lint/safe_navigation_chain' require_relative 'rubocop/cop/lint/safe_navigation_with_empty' require_relative 'rubocop/cop/lint/script_permission' +require_relative 'rubocop/cop/lint/self_assignment' require_relative 'rubocop/cop/lint/send_with_mixin_argument' require_relative 'rubocop/cop/lint/shadowed_argument' require_relative 'rubocop/cop/lint/shadowed_exception' diff --git a/lib/rubocop/cop/lint/self_assignment.rb b/lib/rubocop/cop/lint/self_assignment.rb new file mode 100644 index 00000000000..1eac628e923 --- /dev/null +++ b/lib/rubocop/cop/lint/self_assignment.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Lint + # This cop checks for self-assignments. + # + # @example + # # bad + # foo = foo + # foo, bar = foo, bar + # Foo = Foo + # + # # good + # foo = bar + # foo, bar = bar, foo + # Foo = Bar + # + class SelfAssignment < Base + MSG = 'Self-assignment detected.' + + ASSIGNMENT_TYPE_TO_RHS_TYPE = { + lvasgn: :lvar, + ivasgn: :ivar, + cvasgn: :cvar, + gvasgn: :gvar + }.freeze + + def on_lvasgn(node) + lhs, rhs = *node + return unless rhs + + rhs_type = ASSIGNMENT_TYPE_TO_RHS_TYPE[node.type] + + add_offense(node) if rhs.type == rhs_type && rhs.source == lhs.to_s + end + alias on_ivasgn on_lvasgn + alias on_cvasgn on_lvasgn + alias on_gvasgn on_lvasgn + + def on_casgn(node) + lhs_scope, lhs_name, rhs = *node + return unless rhs.const_type? + + rhs_scope, rhs_name = *rhs + add_offense(node) if lhs_scope == rhs_scope && lhs_name == rhs_name + end + + def on_masgn(node) + add_offense(node) if multiple_self_assignment?(node) + end + + def on_or_asgn(node) + lhs, rhs = *node + add_offense(node) if rhs_matches_lhs?(rhs, lhs) + end + alias on_and_asgn on_or_asgn + + private + + def multiple_self_assignment?(node) + lhs, rhs = *node + return false unless rhs.array_type? + return false unless lhs.children.size == rhs.children.size + + lhs.children.zip(rhs.children).all? do |lhs_item, rhs_item| + rhs_matches_lhs?(rhs_item, lhs_item) + end + end + + def rhs_matches_lhs?(rhs, lhs) + rhs.type == ASSIGNMENT_TYPE_TO_RHS_TYPE[lhs.type] && + rhs.children.first == lhs.children.first + end + end + end + end +end diff --git a/spec/rubocop/cop/lint/self_assignment_spec.rb b/spec/rubocop/cop/lint/self_assignment_spec.rb new file mode 100644 index 00000000000..203343b63b4 --- /dev/null +++ b/spec/rubocop/cop/lint/self_assignment_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Lint::SelfAssignment do + subject(:cop) { described_class.new } + + it 'registers an offense when using local var self-assignment' do + expect_offense(<<~RUBY) + foo = foo + ^^^^^^^^^ Self-assignment detected. + RUBY + end + + it 'does not register an offense when using local var assignment' do + expect_no_offenses(<<~RUBY) + foo = bar + RUBY + end + + it 'registers an offense when using instance var self-assignment' do + expect_offense(<<~RUBY) + @foo = @foo + ^^^^^^^^^^^ Self-assignment detected. + RUBY + end + + it 'does not register an offense when using instance var assignment' do + expect_no_offenses(<<~RUBY) + @foo = @bar + RUBY + end + + it 'registers an offense when using class var self-assignment' do + expect_offense(<<~RUBY) + @@foo = @@foo + ^^^^^^^^^^^^^ Self-assignment detected. + RUBY + end + + it 'does not register an offense when using class var assignment' do + expect_no_offenses(<<~RUBY) + @@foo = @@bar + RUBY + end + + it 'registers an offense when using global var self-assignment' do + expect_offense(<<~RUBY) + $foo = $foo + ^^^^^^^^^^^ Self-assignment detected. + RUBY + end + + it 'does not register an offense when using global var assignment' do + expect_no_offenses(<<~RUBY) + $foo = $bar + RUBY + end + + it 'registers an offense when using constant var self-assignment' do + expect_offense(<<~RUBY) + Foo = Foo + ^^^^^^^^^ Self-assignment detected. + RUBY + end + + it 'does not register an offense when using constant var assignment for constant from another scope' do + expect_no_offenses(<<~RUBY) + Foo = ::Foo + RUBY + end + + it 'registers an offense when using multiple var self-assignment' do + expect_offense(<<~RUBY) + foo, bar = foo, bar + ^^^^^^^^^^^^^^^^^^^ Self-assignment detected. + RUBY + end + + it 'registers an offense when using multiple var self-assignment through array' do + expect_offense(<<~RUBY) + foo, bar = [foo, bar] + ^^^^^^^^^^^^^^^^^^^^^ Self-assignment detected. + RUBY + end + + it 'does not register an offense when using multiple var assignment' do + expect_no_offenses(<<~RUBY) + foo, bar = bar, foo + RUBY + end + + it 'does not register an offense when using multiple var assignment through splat' do + expect_no_offenses(<<~RUBY) + foo, bar = *something + RUBY + end + + it 'does not register an offense when using multiple var assignment through method call' do + expect_no_offenses(<<~RUBY) + foo, bar = something + RUBY + end + + it 'registers an offense when using shorthand-or var self-assignment' do + expect_offense(<<~RUBY) + foo ||= foo + ^^^^^^^^^^^ Self-assignment detected. + RUBY + end + + it 'does not register an offense when using shorthand-or var assignment' do + expect_no_offenses(<<~RUBY) + foo ||= bar + RUBY + end + + it 'registers an offense when using shorthand-and var self-assignment' do + expect_offense(<<~RUBY) + foo &&= foo + ^^^^^^^^^^^ Self-assignment detected. + RUBY + end + + it 'does not register an offense when using shorthand-and var assignment' do + expect_no_offenses(<<~RUBY) + foo &&= bar + RUBY + end +end