Skip to content

Commit 6958079

Browse files
committed
[Fix #368] Add DelegatePrivate cop
1 parent 34376e0 commit 6958079

File tree

5 files changed

+241
-0
lines changed

5 files changed

+241
-0
lines changed

changelog/new_delegate_private_cop.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#368](https://github.com/rubocop/rubocop-rails/issues/368): Add DelegatePrivate cop. ([@povilasjurcys][])

config/default.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,11 @@ Rails/DelegateAllowBlank:
336336
Enabled: true
337337
VersionAdded: '0.44'
338338

339+
Rails/DelegatePrivate:
340+
Description: 'Use delegate with `private: true` option in private scope.'
341+
Enabled: true
342+
VersionAdded: '2.20'
343+
339344
Rails/DeprecatedActiveModelErrorsMethods:
340345
Description: 'Avoid manipulating ActiveModel errors hash directly.'
341346
Enabled: pending
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Rails
6+
# Looks for `delegate` in private section without `private: true` option.
7+
#
8+
# @example
9+
# # bad
10+
# private
11+
# delegate :baz, to: :bar
12+
#
13+
# # bad
14+
# delegate :baz, to: :bar, private: true
15+
#
16+
# # good
17+
# private
18+
# delegate :baz, to: :bar, private: true
19+
class DelegatePrivate < Base
20+
extend TargetRailsVersion
21+
22+
MSG_MISSING_PRIVATE = '`delegate` in private section should have `private: true` option'
23+
MSG_WRONG_PRIVATE = 'private `delegate` should be put in private section'
24+
25+
minimum_target_rails_version 6.0
26+
27+
def on_send(node)
28+
mark_scope(node)
29+
return unless delegate_node?(node)
30+
31+
if private_scope?(node) && !private_delegate?(node)
32+
add_offense(node, message: MSG_MISSING_PRIVATE)
33+
elsif public_scope?(node) && private_delegate?(node)
34+
add_offense(node, message: MSG_WRONG_PRIVATE)
35+
end
36+
end
37+
38+
def on_class(node)
39+
cut_from_private_range(node.location.first_line..node.location.last_line)
40+
end
41+
42+
private
43+
44+
def private_delegate?(node)
45+
node.arguments.select(&:hash_type?).each do |hash_node|
46+
hash_node.each_pair do |key_node, value_node|
47+
return true if key_node.value == :private && value_node.true_type?
48+
end
49+
end
50+
51+
false
52+
end
53+
54+
def mark_scope(node)
55+
return if node.receiver || !node.arguments.empty?
56+
57+
@private_ranges ||= []
58+
59+
scope_range = node.parent.location.first_line..node.parent.location.last_line
60+
if node.method?(:private)
61+
add_to_private_range(scope_range)
62+
elsif node.method?(:public)
63+
cut_from_private_range(scope_range)
64+
end
65+
end
66+
67+
def delegate_node?(node)
68+
return false if node.receiver
69+
70+
node.method?(:delegate)
71+
end
72+
73+
def private_scope?(node)
74+
@private_ranges&.any? { |range| range.include?(node.location.first_line) }
75+
end
76+
77+
def public_scope?(node)
78+
!private_scope?(node)
79+
end
80+
81+
def add_to_private_range(scope_range)
82+
@private_ranges ||= []
83+
@private_ranges += [scope_range]
84+
end
85+
86+
def cut_from_private_range(scope_range)
87+
@private_ranges ||= []
88+
89+
@private_ranges = @private_ranges.each.with_object([]) do |private_range, new_ranges|
90+
next if scope_range.cover?(private_range)
91+
92+
if private_range.cover?(scope_range)
93+
new_ranges << (private_range.begin...scope_range.begin)
94+
new_ranges << ((scope_range.end + 1)..private_range.end)
95+
else
96+
new_ranges << private_range
97+
end
98+
end
99+
end
100+
end
101+
end
102+
end
103+
end

lib/rubocop/cop/rails_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
require_relative 'rails/default_scope'
3737
require_relative 'rails/delegate'
3838
require_relative 'rails/delegate_allow_blank'
39+
require_relative 'rails/delegate_private'
3940
require_relative 'rails/deprecated_active_model_errors_methods'
4041
require_relative 'rails/dot_separated_keys'
4142
require_relative 'rails/duplicate_association'
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Rails::DelegatePrivate, :config, :rails60 do
4+
context 'when no delegate is provided' do
5+
it 'registers no offense' do
6+
expect_no_offenses(<<~RUBY)
7+
class User
8+
end
9+
RUBY
10+
end
11+
end
12+
13+
context 'when delegate is provided in public scope without "private: true"' do
14+
it 'registers no offense' do
15+
expect_no_offenses(<<~RUBY)
16+
class User
17+
delegate :name, to: :user
18+
end
19+
RUBY
20+
end
21+
end
22+
23+
context 'when delegate is provided in public scope with "private: true"' do
24+
it 'registers an offense' do
25+
expect_offense(<<~RUBY)
26+
class User
27+
delegate :name, to: :user, private: true
28+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ private `delegate` should be put in private section
29+
end
30+
RUBY
31+
end
32+
end
33+
34+
context 'when delegate is provided in private scope without "private: true"' do
35+
it 'registers an offense' do
36+
expect_offense(<<~RUBY)
37+
class User
38+
private
39+
40+
delegate :name, to: :user
41+
^^^^^^^^^^^^^^^^^^^^^^^^^ `delegate` in private section should have `private: true` option
42+
end
43+
RUBY
44+
end
45+
end
46+
47+
context 'when delegate is provided in private scope with "private: true"' do
48+
it 'registers no offense' do
49+
expect_no_offenses(<<~RUBY)
50+
class User
51+
private
52+
53+
delegate :name, to: :user, private: true
54+
end
55+
RUBY
56+
end
57+
end
58+
59+
context 'when delegate is provided in public scope without "private: true" in outer class' do
60+
it 'registers no offense' do
61+
expect_no_offenses(<<~RUBY)
62+
class User
63+
class InnerUser
64+
private
65+
def foo; end
66+
end
67+
68+
delegate :name, to: :user
69+
end
70+
RUBY
71+
end
72+
end
73+
74+
context 'when inner class is put in private scope and has delegate in public scope without "private: true"' do
75+
it 'does not register an offense' do
76+
expect_no_offenses(<<~RUBY)
77+
class User
78+
private
79+
80+
class InnerUser
81+
delegate :name, to: :user
82+
end
83+
84+
delegate :foo, to: :bar, private: true
85+
end
86+
RUBY
87+
end
88+
end
89+
90+
context 'when `private: true` is in explicit public scope' do
91+
it 'registers no offense' do
92+
expect_no_offenses(<<~RUBY)
93+
class User
94+
private
95+
96+
def foo
97+
end
98+
99+
public
100+
101+
delegate :name, to: :user
102+
end
103+
RUBY
104+
end
105+
end
106+
107+
context 'when private scope is set on method, but `private: true` is used in public scope' do
108+
it 'registers no offense' do
109+
expect_no_offenses(<<~RUBY)
110+
class User
111+
private def foo
112+
end
113+
114+
delegate :name, to: :user
115+
end
116+
RUBY
117+
end
118+
end
119+
120+
context 'with rails < 6.0', :rails52 do
121+
it 'registers no offense' do
122+
expect_no_offenses(<<~RUBY)
123+
class User
124+
private
125+
126+
delegate :name, to: :user
127+
end
128+
RUBY
129+
end
130+
end
131+
end

0 commit comments

Comments
 (0)