Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize Commissioner callbacks #8882

Merged
merged 4 commits into from Oct 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,10 @@

* [#8892](https://github.com/rubocop-hq/rubocop/issues/8892): Fix an error for `Style/StringConcatenation` when correcting nested concatenable parts. ([@fatkodima][])

### Changes

* [#8882](https://github.com/rubocop-hq/rubocop/pull/8882): **(Potentially breaking)** RuboCop assumes that Cop classes do not define new `on_<type>` methods at runtime (e.g. via `extend` in `initialize`). ([@marcandre][])

## 0.93.1 (2020-10-12)

### Bug fixes
Expand Down
2 changes: 0 additions & 2 deletions lib/rubocop.rb
Expand Up @@ -471,8 +471,6 @@
require_relative 'rubocop/cop/style/redundant_file_extension_in_require'
require_relative 'rubocop/cop/style/redundant_self_assignment'
require_relative 'rubocop/cop/style/sole_nested_conditional'
require_relative 'rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses'
require_relative 'rubocop/cop/style/method_call_with_args_parentheses/require_parentheses'
require_relative 'rubocop/cop/style/method_called_on_do_end_block'
require_relative 'rubocop/cop/style/method_def_parentheses'
require_relative 'rubocop/cop/style/min_max'
Expand Down
17 changes: 16 additions & 1 deletion lib/rubocop/cop/base.rb
Expand Up @@ -261,6 +261,21 @@ def offenses
'they are returned as the result of the investigation'
end

### Reserved for Commissioner

# @api private
def callbacks_needed
self.class.callbacks_needed
end

# @api private
def self.callbacks_needed
@callbacks_needed ||= public_instance_methods.select do |m|
m.match?(/^on_/) &&
!Base.method_defined?(m) # exclude standard "callbacks" like 'on_begin_investigation'
end
end

private

### Reserved for Cop::Cop
Expand Down Expand Up @@ -291,7 +306,7 @@ def currently_disabled_lines
end

private_class_method def self.restrict_on_send
@restrict_on_send ||= self::RESTRICT_ON_SEND.to_set.freeze
@restrict_on_send ||= self::RESTRICT_ON_SEND.to_a.freeze
end

# Called before any investigation
Expand Down
34 changes: 23 additions & 11 deletions lib/rubocop/cop/commissioner.rb
Expand Up @@ -45,8 +45,7 @@ def initialize(cops, forces = [], options = {})
@cops = cops
@forces = forces
@options = options
@callbacks = Hash.new { |h, k| h[k] = cops_callbacks_for(k) }
@restricted_map = {}
initialize_callbacks

reset
end
Expand Down Expand Up @@ -94,7 +93,7 @@ def investigate(processed_source)
private

def trigger_responding_cops(callback, node)
@callbacks[callback].each do |cop|
@callbacks[callback]&.each do |cop|
with_cop_error_handling(cop, node) do
cop.send(callback, node)
end
Expand All @@ -105,19 +104,32 @@ def reset
@errors = []
end

def cops_callbacks_for(callback)
callbacks = @cops.select do |cop|
cop.respond_to?(callback)
end
if RESTRICTED_CALLBACKS.include?(callback)
@restricted_map[callback] = restricted_map(callbacks)
def initialize_callbacks
@callbacks = build_callbacks(@cops)
@restricted_map = restrict_callbacks(@callbacks)
end

def build_callbacks(cops)
callbacks = {}
cops.each do |cop|
cop.callbacks_needed.each do |callback|
(callbacks[callback] ||= []) << cop
end
end
callbacks
end

def restrict_callbacks(callbacks)
restricted = {}
RESTRICTED_CALLBACKS.each do |callback|
restricted[callback] = restricted_map(callbacks[callback])
end
restricted
end

def trigger_restricted_cops(event, node)
name = node.method_name
@restricted_map.fetch(event)[name]&.each do |cop|
@restricted_map[event][name]&.each do |cop|
with_cop_error_handling(cop, node) do
cop.send(event, node)
end
Expand All @@ -127,7 +139,7 @@ def trigger_restricted_cops(event, node)
# Note: mutates `callbacks` in place
def restricted_map(callbacks)
map = {}
callbacks.select! do |cop|
callbacks&.select! do |cop|
restrictions = cop.class.send :restrict_on_send
restrictions.each do |name|
(map[name] ||= []) << cop
Expand Down
23 changes: 10 additions & 13 deletions lib/rubocop/cop/style/method_call_with_args_parentheses.rb
Expand Up @@ -144,25 +144,22 @@ module Style
# # good
# Array 1
class MethodCallWithArgsParentheses < Base
require_relative 'method_call_with_args_parentheses/omit_parentheses'
require_relative 'method_call_with_args_parentheses/require_parentheses'

include ConfigurableEnforcedStyle
include IgnoredMethods
include IgnoredPattern
include RequireParentheses
include OmitParentheses
extend AutoCorrector

def initialize(*)
super
return unless style_configured?

case style
when :require_parentheses
extend RequireParentheses
when :omit_parentheses
extend OmitParentheses
end
def on_send(node)
send(style, node) # call require_parentheses or omit_parentheses
end

# @abstract Overridden in style modules
def autocorrect(_node); end
alias on_csend on_send
alias on_super on_send
alias on_yield on_send

private

Expand Down
Expand Up @@ -7,15 +7,19 @@ class MethodCallWithArgsParentheses
# Style omit_parentheses
module OmitParentheses
TRAILING_WHITESPACE_REGEX = /\s+\Z/.freeze
OMIT_MSG = 'Omit parentheses for method calls with arguments.'
private_constant :OMIT_MSG

def on_send(node)
private

def omit_parentheses(node)
return unless node.parenthesized?
return if node.implicit_call?
return if super_call_without_arguments?(node)
return if allowed_camel_case_method_call?(node)
return if legitimate_call_with_parentheses?(node)

add_offense(offense_range(node)) do |corrector|
add_offense(offense_range(node), message: OMIT_MSG) do |corrector|
if parentheses_at_the_end_of_multiline_call?(node)
corrector.replace(args_begin(node), ' \\')
else
Expand All @@ -24,20 +28,11 @@ def on_send(node)
corrector.remove(node.loc.end)
end
end
alias on_csend on_send
alias on_super on_send
alias on_yield on_send

private

def offense_range(node)
node.loc.begin.join(node.loc.end)
end

def message(_range = nil)
'Omit parentheses for method calls with arguments.'
end

def super_call_without_arguments?(node)
node.super_type? && node.arguments.none?
end
Expand Down
Expand Up @@ -6,27 +6,23 @@ module Style
class MethodCallWithArgsParentheses
# Style require_parentheses
module RequireParentheses
def on_send(node)
REQUIRE_MSG = 'Use parentheses for method calls with arguments.'
private_constant :REQUIRE_MSG

private

def require_parentheses(node)
return if ignored_method?(node.method_name)
return if matches_ignored_pattern?(node.method_name)
return if eligible_for_parentheses_omission?(node)
return unless node.arguments? && !node.parenthesized?

add_offense(node) do |corrector|
add_offense(node, message: REQUIRE_MSG) do |corrector|
corrector.replace(args_begin(node), '(')

corrector.insert_after(args_end(node), ')') unless args_parenthesized?(node)
end
end
alias on_csend on_send
alias on_super on_send
alias on_yield on_send

def message(_node = nil)
'Use parentheses for method calls with arguments.'
end

private

def eligible_for_parentheses_omission?(node)
node.operator_method? || node.setter_method? || ignored_macro?(node)
Expand Down