Skip to content

Commit

Permalink
[Fix rubocop#368] Add DelegatePrivate cop
Browse files Browse the repository at this point in the history
  • Loading branch information
povilasjurcys committed Aug 2, 2023
1 parent 34376e0 commit 72401e5
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/new_delegate_private_cop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#368](https://github.com/rubocop/rubocop-rails/issues/368): Add DelegatePrivate cop. ([@povilasjurcys][])
5 changes: 5 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,11 @@ Rails/DelegateAllowBlank:
Enabled: true
VersionAdded: '0.44'

Rails/DelegatePrivate:
Description: 'Use delegate with `private: true` option in private scope.'
Enabled: true
VersionAdded: '2.20'

Rails/DeprecatedActiveModelErrorsMethods:
Description: 'Avoid manipulating ActiveModel errors hash directly.'
Enabled: pending
Expand Down
93 changes: 93 additions & 0 deletions lib/rubocop/cop/rails/delegate_private.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Rails
# Looks for `delegate` in private section without `private: true` option.
#
# @example
# # bad
# private
# delegate :baz, to: :bar
#
# # bad
# delegate :baz, to: :bar, private: true
#
# # good
# private
# delegate :baz, to: :bar, private: true
class DelegatePrivate < Base
extend TargetRailsVersion

MSG_MISSING_PRIVATE = '`delegate` in private section should have `private: true` option'
MSG_WRONG_PRIVATE = 'private `delegate` should be put in private section'

minimum_target_rails_version 6.0

def on_send(node)
mark_scope(node)
return unless delegate_node?(node)

if private_scope?(node) && !private_delegate?(node)
add_offense(node, message: MSG_MISSING_PRIVATE)
elsif public_scope?(node) && private_delegate?(node)
add_offense(node, message: MSG_WRONG_PRIVATE)
end
end

private

def private_delegate?(node)
node.arguments.select(&:hash_type?).each do |hash_node|
hash_node.each_pair do |key_node, value_node|
return true if key_node.value == :private && value_node.true_type?
end
end

false
end

def mark_scope(node)
return if node.receiver || !node.arguments.empty?

@private_ranges ||= []

if node.method?(:private)
add_private_range(node)
elsif node.method?(:public)
cut_private_range_from(node.location.first_line)
end
end

def delegate_node?(node)
return false if node.receiver

node.method?(:delegate)
end

def private_scope?(node)
@private_ranges&.any? { |range| range.include?(node.location.first_line) }
end

def public_scope?(node)
!private_scope?(node)
end

def add_private_range(node)
@private_ranges ||= []
@private_ranges += [node.location.first_line..node.parent.last_line]
end

def cut_private_range_from(from_line)
@private_ranges ||= []
@private_ranges = @private_ranges.each.with_object([]) do |range, new_ranges|
next if range.begin > from_line

new_range = range.include?(from_line) ? (range.begin...from_line) : range
new_ranges << new_range
end
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rails_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
require_relative 'rails/default_scope'
require_relative 'rails/delegate'
require_relative 'rails/delegate_allow_blank'
require_relative 'rails/delegate_private'
require_relative 'rails/deprecated_active_model_errors_methods'
require_relative 'rails/dot_separated_keys'
require_relative 'rails/duplicate_association'
Expand Down
115 changes: 115 additions & 0 deletions spec/rubocop/cop/rails/delegate_private_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Rails::DelegatePrivate, :config, :rails60 do
context 'when no delegate is provided' do
it 'registers no offense' do
expect_no_offenses(<<~RUBY)
class User
end
RUBY
end
end

context 'when delegate is provided in public scope without "private: true"' do
it 'registers no offense' do
expect_no_offenses(<<~RUBY)
class User
delegate :name, to: :user
end
RUBY
end
end

context 'when delegate is provided in public scope with "private: true"' do
it 'registers an offense' do
expect_offense(<<~RUBY)
class User
delegate :name, to: :user, private: true
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ private `delegate` should be put in private section
end
RUBY
end
end

context 'when delegate is provided in private scope without "private: true"' do
it 'registers an offense' do
expect_offense(<<~RUBY)
class User
private
delegate :name, to: :user
^^^^^^^^^^^^^^^^^^^^^^^^^ `delegate` in private section should have `private: true` option
end
RUBY
end
end

context 'when delegate is provided in private scope with "private: true"' do
it 'registers no offense' do
expect_no_offenses(<<~RUBY)
class User
private
delegate :name, to: :user, private: true
end
RUBY
end
end

context 'when delegate is provided in public scope without "private: true" in outer class' do
it 'registers no offense' do
expect_no_offenses(<<~RUBY)
class User
class InnerUser
private
def foo; end
end
delegate :name, to: :user
end
RUBY
end
end

context 'when `private: true` is in explicit public scope' do
it 'registers no offense' do
expect_no_offenses(<<~RUBY)
class User
private
def foo
end
public
delegate :name, to: :user
end
RUBY
end
end

context 'when private scope is set on method, but `private: true` is used in public scope' do
it 'registers no offense' do
expect_no_offenses(<<~RUBY)
class User
private def foo
end
delegate :name, to: :user
end
RUBY
end
end

context 'with rails < 6.0', :rails52 do
it 'registers no offense' do
expect_no_offenses(<<~RUBY)
class User
private
delegate :name, to: :user
end
RUBY
end
end
end

0 comments on commit 72401e5

Please sign in to comment.