Skip to content

Commit

Permalink
[Fix rubocop#912] Enhance Rails/Delegate by adding delegation detec…
Browse files Browse the repository at this point in the history
…tion for `self.class`, constants, instance variables, and class variables
  • Loading branch information
ydakuka committed Feb 9, 2025
1 parent c9a5749 commit 5a211cc
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#912](https://github.com/rubocop/rubocop-rails/issues/912): Enhance `Rails/Delegate` by adding delegation detection for `self.class`, constants, instance variables, and class variables. ([@ydakuka][])
48 changes: 40 additions & 8 deletions lib/rubocop/cop/rails/delegate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class Delegate < Base

def_node_matcher :delegate?, <<~PATTERN
(def _method_name _args
(send {(send nil? _) (self)} _ ...))
(send {(send nil? _) (self) (send (self) :class) ({cvar ivar} _) (const _ _)} _ ...))
PATTERN

def on_def(node)
Expand All @@ -82,17 +82,39 @@ def on_def(node)

def register_offense(node)
add_offense(node.loc.keyword) do |corrector|
body = node.body
receiver = determine_receiver(node.body.receiver)
delegation = build_delegation(node, receiver)

receiver = body.receiver.self_type? ? 'self' : ":#{body.receiver.method_name}"

delegation = ["delegate :#{body.method_name}", "to: #{receiver}"]
delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
corrector.replace(node, delegation)
end
end

corrector.replace(node, delegation.join(', '))
def determine_receiver(receiver)
case receiver.type
when :self
'self'
when :const
full_name = full_const_name(receiver)
full_name.include?('::') ? ":'#{full_name}'" : ":#{full_name}"
when :cvar, :ivar
":#{receiver.source}"
else
":#{receiver.method_name}"
end
end

def build_delegation(node, receiver)
delegation = ["delegate :#{node.body.method_name}", "to: #{receiver}"]
delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
delegation.join(', ')
end

def full_const_name(node)
return node.source unless node.namespace

"#{full_const_name(node.namespace)}::#{node.children.last}"
end

def trivial_delegate?(def_node)
delegate?(def_node) &&
method_name_matches?(def_node.method_name, def_node.body) &&
Expand Down Expand Up @@ -120,7 +142,17 @@ def include_prefix_case?
def prefixed_method_name(body)
return '' if body.receiver.self_type?

[body.receiver.method_name, body.method_name].join('_').to_sym
receiver_name =
case body.receiver.type
when :cvar, :ivar
body.receiver.source
when :const
full_const_name(body.receiver)
else
body.receiver.method_name.to_s
end

[receiver_name, body.method_name].join('_').to_sym
end

def private_or_protected_delegation(node)
Expand Down
74 changes: 65 additions & 9 deletions spec/rubocop/cop/rails/delegate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -211,15 +211,6 @@ def new
RUBY
end

it 'ignores delegation to constant' do
expect_no_offenses(<<~RUBY)
FOO = []
def size
FOO.size
end
RUBY
end

it 'ignores code with no receiver' do
expect_no_offenses(<<~RUBY)
def change
Expand Down Expand Up @@ -249,4 +240,69 @@ def foo
end
RUBY
end

it 'detects delegation to `self.class`' do
expect_offense(<<~RUBY)
def foo
^^^ Use `delegate` to define delegations.
self.class.foo
end
RUBY

expect_correction(<<~RUBY)
delegate :foo, to: :class
RUBY
end

it 'detects delegation to a constant' do
expect_offense(<<~RUBY)
def bar
^^^ Use `delegate` to define delegations.
CONST.bar
end
RUBY

expect_correction(<<~RUBY)
delegate :bar, to: :CONST
RUBY
end

it 'detects delegation to a namespaced constant' do
expect_offense(<<~RUBY)
def bar
^^^ Use `delegate` to define delegations.
SomeModule::CONST.bar
end
RUBY

expect_correction(<<~RUBY)
delegate :bar, to: :'SomeModule::CONST'
RUBY
end

it 'detects delegation to an instance variable' do
expect_offense(<<~RUBY)
def baz
^^^ Use `delegate` to define delegations.
@inst_obj.baz
end
RUBY

expect_correction(<<~RUBY)
delegate :baz, to: :@inst_obj
RUBY
end

it 'detects delegation to a class variable' do
expect_offense(<<~RUBY)
def baz
^^^ Use `delegate` to define delegations.
@@inst_obj.baz
end
RUBY

expect_correction(<<~RUBY)
delegate :baz, to: :@@inst_obj
RUBY
end
end

0 comments on commit 5a211cc

Please sign in to comment.