-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
…attributes"
- Loading branch information
1 parent
0647c04
commit 345be1d
Showing
8 changed files
with
280 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* [#8564](https://github.com/rubocop-hq/rubocop/issues/8564): `Metrics/AbcSize`: Add optional discount for repeated "attributes". ([@marcandre][]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
146 changes: 146 additions & 0 deletions
146
lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Metrics | ||
module Utils | ||
# @api private | ||
# | ||
# Identifies repetitions `{c}send` calls with no arguments: | ||
# | ||
# foo.bar | ||
# foo.bar # => repeated | ||
# foo.bar.baz.qux # => inner send repeated | ||
# foo.bar.baz.other # => both inner send repeated | ||
# foo.bar(2) # => not repeated | ||
# | ||
# It also invalidates sequences if a receiver is reassigned: | ||
# | ||
# xx.foo.bar | ||
# xx.foo.baz # => inner send repeated | ||
# self.xx = any # => invalidates everything so far | ||
# xx.foo.baz # => no repetition | ||
# self.xx.foo.baz # => all repeated | ||
# | ||
module RepeatedAttributeDiscount | ||
extend NodePattern::Macros | ||
include RuboCop::AST::Sexp | ||
|
||
# Plug into the calculator | ||
def initialize(node, discount_repeated_attributes: false) | ||
super(node) | ||
return unless discount_repeated_attributes | ||
|
||
self_attributes = {} # Share hash for `(send nil? :foo)` and `(send (self) :foo)` | ||
@known_attributes = { | ||
s(:self) => self_attributes, | ||
nil => self_attributes | ||
} | ||
# example after running `obj = foo.bar; obj.baz.qux` | ||
# { nil => {foo: {bar: {}}}, | ||
# s(self) => same hash ^, | ||
# s(:lvar, :obj) => {baz: {qux: {}}} | ||
# } | ||
end | ||
|
||
def discount_repeated_attributes? | ||
defined?(@known_attributes) | ||
end | ||
|
||
def evaluate_branch_nodes(node) | ||
return if discount_repeated_attributes? && discount_repeated_attribute?(node) | ||
|
||
super | ||
end | ||
|
||
def calculate_node(node) | ||
update_repeated_attribute(node) if discount_repeated_attributes? | ||
super | ||
end | ||
|
||
private | ||
|
||
def_node_matcher :attribute_call?, <<~PATTERN | ||
( {csend send} _receiver _method # and no parameters | ||
) | ||
PATTERN | ||
|
||
def discount_repeated_attribute?(send_node) | ||
return false unless attribute_call?(send_node) | ||
|
||
repeated = true | ||
find_attributes(send_node) do |hash, lookup| | ||
return false if hash.nil? | ||
|
||
repeated = false | ||
hash[lookup] = {} | ||
end | ||
|
||
repeated | ||
end | ||
|
||
def update_repeated_attribute(node) | ||
return unless (receiver, method = setter_to_getter(node)) | ||
|
||
calls = find_attributes(receiver) { return } | ||
if method # e.g. `self.foo = 42` | ||
calls.delete(method) | ||
else # e.g. `var = 42` | ||
calls.clear | ||
end | ||
end | ||
|
||
def_node_matcher :root_node?, <<~PATTERN | ||
{ nil? | self # e.g. receiver of `my_method` or `self.my_attr` | ||
| lvar | ivar | cvar | gvar # e.g. receiver of `var.my_method` | ||
| const } # e.g. receiver of `MyConst.foo.bar` | ||
PATTERN | ||
|
||
# Returns the "known_attributes" for the `node` by walking the receiver tree | ||
# If at any step the subdirectory does not exist, it is yielded with the | ||
# associated key (method_name) | ||
# If the node is not a series of `(c)send` calls with no arguments, | ||
# then `nil` is yielded | ||
def find_attributes(node, &block) | ||
if attribute_call?(node) | ||
calls = find_attributes(node.receiver, &block) | ||
value = node.method_name | ||
elsif root_node?(node) | ||
calls = @known_attributes | ||
value = node | ||
else | ||
return yield nil | ||
end | ||
|
||
calls.fetch(value) do | ||
yield [calls, value] | ||
end | ||
end | ||
|
||
VAR_SETTER_TO_GETTER = { | ||
lvasgn: :lvar, | ||
ivasgn: :ivar, | ||
cvasgn: :cvar, | ||
gvasgn: :gvar | ||
}.freeze | ||
|
||
# @returns `[receiver, method | nil]` for the given setter `node` | ||
# or `nil` if it is not a setter. | ||
def setter_to_getter(node) | ||
if (type = VAR_SETTER_TO_GETTER[node.type]) | ||
# (lvasgn :my_var (int 42)) => [(lvar my_var), nil] | ||
[s(type, node.children.first), nil] | ||
elsif node.shorthand_asgn? # (or-asgn (send _receiver :foo) _value) | ||
# (or-asgn (send _receiver :foo) _value) => [_receiver, :foo] | ||
node.children.first.children | ||
elsif node.respond_to?(:setter_method?) && node.setter_method? | ||
# (send _receiver :foo= (int 42) ) => [_receiver, :foo] | ||
method_name = node.method_name[0...-1].to_sym | ||
[node.receiver, method_name] | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters