Skip to content

Commit 72401e5

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

File tree

5 files changed

+215
-0
lines changed

5 files changed

+215
-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: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
private
39+
40+
def private_delegate?(node)
41+
node.arguments.select(&:hash_type?).each do |hash_node|
42+
hash_node.each_pair do |key_node, value_node|
43+
return true if key_node.value == :private && value_node.true_type?
44+
end
45+
end
46+
47+
false
48+
end
49+
50+
def mark_scope(node)
51+
return if node.receiver || !node.arguments.empty?
52+
53+
@private_ranges ||= []
54+
55+
if node.method?(:private)
56+
add_private_range(node)
57+
elsif node.method?(:public)
58+
cut_private_range_from(node.location.first_line)
59+
end
60+
end
61+
62+
def delegate_node?(node)
63+
return false if node.receiver
64+
65+
node.method?(:delegate)
66+
end
67+
68+
def private_scope?(node)
69+
@private_ranges&.any? { |range| range.include?(node.location.first_line) }
70+
end
71+
72+
def public_scope?(node)
73+
!private_scope?(node)
74+
end
75+
76+
def add_private_range(node)
77+
@private_ranges ||= []
78+
@private_ranges += [node.location.first_line..node.parent.last_line]
79+
end
80+
81+
def cut_private_range_from(from_line)
82+
@private_ranges ||= []
83+
@private_ranges = @private_ranges.each.with_object([]) do |range, new_ranges|
84+
next if range.begin > from_line
85+
86+
new_range = range.include?(from_line) ? (range.begin...from_line) : range
87+
new_ranges << new_range
88+
end
89+
end
90+
end
91+
end
92+
end
93+
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: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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 `private: true` is in explicit public scope' do
75+
it 'registers no offense' do
76+
expect_no_offenses(<<~RUBY)
77+
class User
78+
private
79+
80+
def foo
81+
end
82+
83+
public
84+
85+
delegate :name, to: :user
86+
end
87+
RUBY
88+
end
89+
end
90+
91+
context 'when private scope is set on method, but `private: true` is used in public scope' do
92+
it 'registers no offense' do
93+
expect_no_offenses(<<~RUBY)
94+
class User
95+
private def foo
96+
end
97+
98+
delegate :name, to: :user
99+
end
100+
RUBY
101+
end
102+
end
103+
104+
context 'with rails < 6.0', :rails52 do
105+
it 'registers no offense' do
106+
expect_no_offenses(<<~RUBY)
107+
class User
108+
private
109+
110+
delegate :name, to: :user
111+
end
112+
RUBY
113+
end
114+
end
115+
end

0 commit comments

Comments
 (0)