Skip to content

Commit

Permalink
Add new Lint/SelfAssignment cop (#8387)
Browse files Browse the repository at this point in the history
Co-authored-by: Bozhidar Batsov <bozhidar@batsov.com>
  • Loading branch information
fatkodima and bbatsov committed Jul 25, 2020
1 parent 91b05d5 commit de012cc
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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][])
Expand Down
5 changes: 5 additions & 0 deletions config/default.yml
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Expand Up @@ -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]
Expand Down
29 changes: 29 additions & 0 deletions docs/modules/ROOT/pages/cops_lint.adoc
Expand Up @@ -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

|===
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Expand Up @@ -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'
Expand Down
78 changes: 78 additions & 0 deletions 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
128 changes: 128 additions & 0 deletions 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

0 comments on commit de012cc

Please sign in to comment.