rubocop 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/config/default.yml +47 -7
- data/lib/rubocop.rb +4 -0
- data/lib/rubocop/cli.rb +5 -1
- data/lib/rubocop/cli/command/suggest_extensions.rb +67 -0
- data/lib/rubocop/config_loader.rb +1 -1
- data/lib/rubocop/config_loader_resolver.rb +5 -1
- data/lib/rubocop/config_obsoletion.rb +21 -3
- data/lib/rubocop/config_validator.rb +8 -1
- data/lib/rubocop/cop/generator.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +6 -1
- data/lib/rubocop/cop/layout/end_of_line.rb +5 -5
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +7 -2
- data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +1 -1
- data/lib/rubocop/cop/lint/unexpected_block_arity.rb +85 -0
- data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +8 -5
- data/lib/rubocop/cop/metrics/abc_size.rb +25 -1
- data/lib/rubocop/cop/metrics/block_length.rb +13 -7
- data/lib/rubocop/cop/metrics/method_length.rb +7 -2
- data/lib/rubocop/cop/metrics/parameter_lists.rb +64 -1
- data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +20 -10
- data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +146 -0
- data/lib/rubocop/cop/metrics/utils/repeated_csend_discount.rb +6 -1
- data/lib/rubocop/cop/mixin/ignored_methods.rb +36 -3
- data/lib/rubocop/cop/mixin/method_complexity.rb +6 -0
- data/lib/rubocop/cop/style/and_or.rb +10 -0
- data/lib/rubocop/cop/style/class_and_module_children.rb +8 -3
- data/lib/rubocop/cop/style/if_with_semicolon.rb +39 -4
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -2
- data/lib/rubocop/cop/style/numeric_literals.rb +14 -11
- data/lib/rubocop/cop/style/redundant_argument.rb +1 -1
- data/lib/rubocop/cop/style/redundant_condition.rb +2 -1
- data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +49 -3
- data/lib/rubocop/cop/style/symbol_proc.rb +5 -3
- data/lib/rubocop/core_ext/hash.rb +20 -0
- data/lib/rubocop/ext/regexp_node.rb +5 -10
- data/lib/rubocop/ext/regexp_parser.rb +2 -9
- data/lib/rubocop/version.rb +1 -1
- metadata +11 -7
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# This cop checks for a block that is known to need more positional
|
7
|
+
# block arguments than are given (by default this is configured for
|
8
|
+
# `Enumerable` methods needing 2 arguments). Optional arguments are allowed,
|
9
|
+
# although they don't generally make sense as the default value will
|
10
|
+
# be used. Blocks that have no receiver, or take splatted arguments
|
11
|
+
# (ie. `*args`) are always accepted.
|
12
|
+
#
|
13
|
+
# Keyword arguments (including `**kwargs`) do not get counted towards
|
14
|
+
# this, as they are not used by the methods in question.
|
15
|
+
#
|
16
|
+
# NOTE: This cop matches for method names only and hence cannot tell apart
|
17
|
+
# methods with same name in different classes.
|
18
|
+
#
|
19
|
+
# Method names and their expected arity can be configured like this:
|
20
|
+
#
|
21
|
+
# Methods:
|
22
|
+
# inject: 2
|
23
|
+
# reduce: 2
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# # bad
|
27
|
+
# values.reduce {}
|
28
|
+
# values.min { |a| a }
|
29
|
+
# values.sort { |a; b| a + b }
|
30
|
+
#
|
31
|
+
# # good
|
32
|
+
# values.reduce { |memo, obj| memo << obj }
|
33
|
+
# values.min { |a, b| a <=> b }
|
34
|
+
# values.sort { |*x| x[0] <=> x[1] }
|
35
|
+
#
|
36
|
+
class UnexpectedBlockArity < Base
|
37
|
+
MSG = '`%<method>s` expects at least %<expected>i positional arguments, got %<actual>i.'
|
38
|
+
|
39
|
+
def on_block(node)
|
40
|
+
return if acceptable?(node)
|
41
|
+
|
42
|
+
expected = expected_arity(node.method_name)
|
43
|
+
actual = arg_count(node)
|
44
|
+
return if actual >= expected
|
45
|
+
|
46
|
+
message = format(MSG, method: node.method_name, expected: expected, actual: actual)
|
47
|
+
add_offense(node, message: message)
|
48
|
+
end
|
49
|
+
|
50
|
+
alias on_numblock on_block
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def methods
|
55
|
+
cop_config.fetch('Methods', [])
|
56
|
+
end
|
57
|
+
|
58
|
+
def acceptable?(node)
|
59
|
+
!(included_method?(node.method_name) && node.receiver)
|
60
|
+
end
|
61
|
+
|
62
|
+
def included_method?(name)
|
63
|
+
methods.key?(name.to_s)
|
64
|
+
end
|
65
|
+
|
66
|
+
def expected_arity(method)
|
67
|
+
cop_config['Methods'][method.to_s]
|
68
|
+
end
|
69
|
+
|
70
|
+
def arg_count(node)
|
71
|
+
return node.children[1] if node.numblock_type? # the maximum numbered param for the block
|
72
|
+
|
73
|
+
# Only `arg`, `optarg` and `mlhs` (destructuring) count as arguments that
|
74
|
+
# can be used. Keyword arguments are not used for these methods so are
|
75
|
+
# ignored.
|
76
|
+
node.arguments.count do |arg|
|
77
|
+
return Float::INFINITY if arg.restarg_type?
|
78
|
+
|
79
|
+
arg.arg_type? || arg.optarg_type? || arg.mlhs_type?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -12,8 +12,7 @@ module RuboCop
|
|
12
12
|
# could be rewritten as such without a loop.
|
13
13
|
#
|
14
14
|
# Also catches instances where an index of the accumulator is returned, as
|
15
|
-
# this may change the type of object being retained.
|
16
|
-
# fewer than 2 block arguments are specified.
|
15
|
+
# this may change the type of object being retained.
|
17
16
|
#
|
18
17
|
# NOTE: For the purpose of reducing false positives, this cop only flags
|
19
18
|
# returns in `reduce` blocks where the element is the only variable in
|
@@ -68,7 +67,10 @@ module RuboCop
|
|
68
67
|
MSG_INDEX = 'Do not return an element of the accumulator in `%<method>s`.'
|
69
68
|
|
70
69
|
def_node_matcher :reduce_with_block?, <<~PATTERN
|
71
|
-
|
70
|
+
{
|
71
|
+
(block (send _recv {:reduce :inject} ...) args ...)
|
72
|
+
(numblock (send _recv {:reduce :inject} ...) ...)
|
73
|
+
}
|
72
74
|
PATTERN
|
73
75
|
|
74
76
|
def_node_matcher :accumulator_index?, <<~PATTERN
|
@@ -107,10 +109,11 @@ module RuboCop
|
|
107
109
|
|
108
110
|
def on_block(node)
|
109
111
|
return unless reduce_with_block?(node)
|
110
|
-
return unless node.
|
112
|
+
return unless node.argument_list.length >= 2
|
111
113
|
|
112
114
|
check_return_values(node)
|
113
115
|
end
|
116
|
+
alias on_numblock on_block
|
114
117
|
|
115
118
|
private
|
116
119
|
|
@@ -148,7 +151,7 @@ module RuboCop
|
|
148
151
|
end
|
149
152
|
|
150
153
|
def block_arg_name(node, index)
|
151
|
-
node.
|
154
|
+
node.argument_list[index].name
|
152
155
|
end
|
153
156
|
|
154
157
|
# Look for an index of the accumulator being returned, except where the index
|
@@ -7,6 +7,27 @@ module RuboCop
|
|
7
7
|
# configured maximum. The ABC size is based on assignments, branches
|
8
8
|
# (method calls), and conditions. See http://c2.com/cgi/wiki?AbcMetric
|
9
9
|
# and https://en.wikipedia.org/wiki/ABC_Software_Metric.
|
10
|
+
#
|
11
|
+
# You can have repeated "attributes" calls count as a single "branch".
|
12
|
+
# For this purpose, attributes are any method with no argument; no attempt
|
13
|
+
# is meant to distinguish actual `attr_reader` from other methods.
|
14
|
+
#
|
15
|
+
# @example CountRepeatedAttributes: false (default is true)
|
16
|
+
#
|
17
|
+
# # `model` and `current_user`, refenced 3 times each,
|
18
|
+
# # are each counted as only 1 branch each if
|
19
|
+
# # `CountRepeatedAttributes` is set to 'false'
|
20
|
+
#
|
21
|
+
# def search
|
22
|
+
# @posts = model.active.visible_by(current_user)
|
23
|
+
# .search(params[:q])
|
24
|
+
# @posts = model.some_process(@posts, current_user)
|
25
|
+
# @posts = model.another_process(@posts, current_user)
|
26
|
+
#
|
27
|
+
# render 'pages/search/page'
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# This cop also takes into account `IgnoredMethods` (defaults to `[]`)
|
10
31
|
class AbcSize < Base
|
11
32
|
include MethodComplexity
|
12
33
|
|
@@ -16,7 +37,10 @@ module RuboCop
|
|
16
37
|
private
|
17
38
|
|
18
39
|
def complexity(node)
|
19
|
-
Utils::AbcSizeCalculator.calculate(
|
40
|
+
Utils::AbcSizeCalculator.calculate(
|
41
|
+
node,
|
42
|
+
discount_repeated_attributes: !cop_config['CountRepeatedAttributes']
|
43
|
+
)
|
20
44
|
end
|
21
45
|
end
|
22
46
|
end
|
@@ -12,6 +12,10 @@ module RuboCop
|
|
12
12
|
# Available are: 'array', 'hash', and 'heredoc'. Each literal
|
13
13
|
# will be counted as one line regardless of its actual size.
|
14
14
|
#
|
15
|
+
#
|
16
|
+
# NOTE: The `ExcludedMethods` configuration is deprecated and only kept
|
17
|
+
# for backwards compatibility. Please use `IgnoredMethods` instead.
|
18
|
+
#
|
15
19
|
# @example CountAsOne: ['array', 'heredoc']
|
16
20
|
#
|
17
21
|
# something do
|
@@ -33,11 +37,15 @@ module RuboCop
|
|
33
37
|
# NOTE: This cop does not apply for `Struct` definitions.
|
34
38
|
class BlockLength < Base
|
35
39
|
include CodeLength
|
40
|
+
include IgnoredMethods
|
41
|
+
|
42
|
+
ignored_methods deprecated_key: 'ExcludedMethods'
|
36
43
|
|
37
44
|
LABEL = 'Block'
|
38
45
|
|
39
46
|
def on_block(node)
|
40
|
-
return if
|
47
|
+
return if ignored_method?(node.method_name)
|
48
|
+
return if method_receiver_excluded?(node)
|
41
49
|
return if node.class_constructor? || node.struct_constructor?
|
42
50
|
|
43
51
|
check_code_length(node)
|
@@ -45,11 +53,13 @@ module RuboCop
|
|
45
53
|
|
46
54
|
private
|
47
55
|
|
48
|
-
def
|
56
|
+
def method_receiver_excluded?(node)
|
49
57
|
node_receiver = node.receiver&.source&.gsub(/\s+/, '')
|
50
58
|
node_method = String(node.method_name)
|
51
59
|
|
52
|
-
|
60
|
+
ignored_methods.any? do |config|
|
61
|
+
next unless config.is_a?(String)
|
62
|
+
|
53
63
|
receiver, method = config.split('.')
|
54
64
|
|
55
65
|
unless method
|
@@ -61,10 +71,6 @@ module RuboCop
|
|
61
71
|
end
|
62
72
|
end
|
63
73
|
|
64
|
-
def excluded_methods
|
65
|
-
cop_config['ExcludedMethods'] || []
|
66
|
-
end
|
67
|
-
|
68
74
|
def cop_label
|
69
75
|
LABEL
|
70
76
|
end
|
@@ -11,6 +11,9 @@ module RuboCop
|
|
11
11
|
# Available are: 'array', 'hash', and 'heredoc'. Each literal
|
12
12
|
# will be counted as one line regardless of its actual size.
|
13
13
|
#
|
14
|
+
# NOTE: The `ExcludedMethods` configuration is deprecated and only kept
|
15
|
+
# for backwards compatibility. Please use `IgnoredMethods` instead.
|
16
|
+
#
|
14
17
|
# @example CountAsOne: ['array', 'heredoc']
|
15
18
|
#
|
16
19
|
# def m
|
@@ -31,12 +34,14 @@ module RuboCop
|
|
31
34
|
#
|
32
35
|
class MethodLength < Base
|
33
36
|
include CodeLength
|
37
|
+
include IgnoredMethods
|
38
|
+
|
39
|
+
ignored_methods deprecated_key: 'ExcludedMethods'
|
34
40
|
|
35
41
|
LABEL = 'Method'
|
36
42
|
|
37
43
|
def on_def(node)
|
38
|
-
|
39
|
-
return if excluded_methods.any? { |m| m.match? String(node.method_name) }
|
44
|
+
return if ignored_method?(node.method_name)
|
40
45
|
|
41
46
|
check_code_length(node)
|
42
47
|
end
|
@@ -4,17 +4,76 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module Metrics
|
6
6
|
# This cop checks for methods with too many parameters.
|
7
|
+
#
|
7
8
|
# The maximum number of parameters is configurable.
|
8
|
-
# Keyword arguments can optionally be excluded from the total count
|
9
|
+
# Keyword arguments can optionally be excluded from the total count,
|
10
|
+
# as they add less complexity than positional or optional parameters.
|
11
|
+
#
|
12
|
+
# @example Max: 3
|
13
|
+
# # good
|
14
|
+
# def foo(a, b, c = 1)
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @example Max: 2
|
18
|
+
# # bad
|
19
|
+
# def foo(a, b, c = 1)
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @example CountKeywordArgs: true (default)
|
23
|
+
# # counts keyword args towards the maximum
|
24
|
+
#
|
25
|
+
# # bad (assuming Max is 3)
|
26
|
+
# def foo(a, b, c, d: 1)
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # good (assuming Max is 3)
|
30
|
+
# def foo(a, b, c: 1)
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @example CountKeywordArgs: false
|
34
|
+
# # don't count keyword args towards the maximum
|
35
|
+
#
|
36
|
+
# # good (assuming Max is 3)
|
37
|
+
# def foo(a, b, c, d: 1)
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# This cop also checks for the maximum number of optional parameters.
|
41
|
+
# This can be configured using the `MaxOptionalParameters` config option.
|
42
|
+
#
|
43
|
+
# @example MaxOptionalParameters: 3 (default)
|
44
|
+
# # good
|
45
|
+
# def foo(a = 1, b = 2, c = 3)
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# @example MaxOptionalParameters: 2
|
49
|
+
# # bad
|
50
|
+
# def foo(a = 1, b = 2, c = 3)
|
51
|
+
# end
|
52
|
+
#
|
9
53
|
class ParameterLists < Base
|
10
54
|
include ConfigurableMax
|
11
55
|
|
12
56
|
MSG = 'Avoid parameter lists longer than %<max>d parameters. ' \
|
13
57
|
'[%<count>d/%<max>d]'
|
58
|
+
OPTIONAL_PARAMETERS_MSG = 'Method has too many optional parameters. [%<count>d/%<max>d]'
|
14
59
|
|
15
60
|
NAMED_KEYWORD_TYPES = %i[kwoptarg kwarg].freeze
|
16
61
|
private_constant :NAMED_KEYWORD_TYPES
|
17
62
|
|
63
|
+
def on_def(node)
|
64
|
+
optargs = node.arguments.select(&:optarg_type?)
|
65
|
+
return if optargs.count <= max_optional_parameters
|
66
|
+
|
67
|
+
message = format(
|
68
|
+
OPTIONAL_PARAMETERS_MSG,
|
69
|
+
max: max_optional_parameters,
|
70
|
+
count: optargs.count
|
71
|
+
)
|
72
|
+
|
73
|
+
add_offense(node, message: message)
|
74
|
+
end
|
75
|
+
alias on_defs on_def
|
76
|
+
|
18
77
|
def on_args(node)
|
19
78
|
count = args_count(node)
|
20
79
|
return unless count > max_params
|
@@ -44,6 +103,10 @@ module RuboCop
|
|
44
103
|
cop_config['Max']
|
45
104
|
end
|
46
105
|
|
106
|
+
def max_optional_parameters
|
107
|
+
cop_config['MaxOptionalParameters']
|
108
|
+
end
|
109
|
+
|
47
110
|
def count_keyword_args?
|
48
111
|
cop_config['CountKeywordArgs']
|
49
112
|
end
|
@@ -13,6 +13,7 @@ module RuboCop
|
|
13
13
|
class AbcSizeCalculator
|
14
14
|
include IteratingBlock
|
15
15
|
include RepeatedCsendDiscount
|
16
|
+
prepend RepeatedAttributeDiscount
|
16
17
|
|
17
18
|
# > Branch -- an explicit forward program branch out of scope -- a
|
18
19
|
# > function call, class method call ..
|
@@ -24,8 +25,8 @@ module RuboCop
|
|
24
25
|
# > http://c2.com/cgi/wiki?AbcMetric
|
25
26
|
CONDITION_NODES = CyclomaticComplexity::COUNTED_NODES.freeze
|
26
27
|
|
27
|
-
def self.calculate(node)
|
28
|
-
new(node).calculate
|
28
|
+
def self.calculate(node, discount_repeated_attributes: false)
|
29
|
+
new(node, discount_repeated_attributes: discount_repeated_attributes).calculate
|
29
30
|
end
|
30
31
|
|
31
32
|
# TODO: move to rubocop-ast
|
@@ -42,14 +43,8 @@ module RuboCop
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def calculate
|
45
|
-
@node
|
46
|
-
|
47
|
-
|
48
|
-
if branch?(child)
|
49
|
-
evaluate_branch_nodes(child)
|
50
|
-
elsif condition?(child)
|
51
|
-
evaluate_condition_node(child)
|
52
|
-
end
|
46
|
+
visit_depth_last(@node) do |child|
|
47
|
+
calculate_node(child)
|
53
48
|
end
|
54
49
|
|
55
50
|
[
|
@@ -80,6 +75,21 @@ module RuboCop
|
|
80
75
|
|
81
76
|
private
|
82
77
|
|
78
|
+
def visit_depth_last(node, &block)
|
79
|
+
node.each_child_node { |child| visit_depth_last(child, &block) }
|
80
|
+
yield node
|
81
|
+
end
|
82
|
+
|
83
|
+
def calculate_node(node)
|
84
|
+
@assignment += 1 if assignment?(node)
|
85
|
+
|
86
|
+
if branch?(node)
|
87
|
+
evaluate_branch_nodes(node)
|
88
|
+
elsif condition?(node)
|
89
|
+
evaluate_condition_node(node)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
83
93
|
def assignment?(node)
|
84
94
|
return compound_assignment(node) if node.masgn_type? || node.shorthand_asgn?
|
85
95
|
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Metrics
|
6
|
+
module Utils
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
# Identifies repetitions `{c}send` calls with no arguments:
|
10
|
+
#
|
11
|
+
# foo.bar
|
12
|
+
# foo.bar # => repeated
|
13
|
+
# foo.bar.baz.qux # => inner send repeated
|
14
|
+
# foo.bar.baz.other # => both inner send repeated
|
15
|
+
# foo.bar(2) # => not repeated
|
16
|
+
#
|
17
|
+
# It also invalidates sequences if a receiver is reassigned:
|
18
|
+
#
|
19
|
+
# xx.foo.bar
|
20
|
+
# xx.foo.baz # => inner send repeated
|
21
|
+
# self.xx = any # => invalidates everything so far
|
22
|
+
# xx.foo.baz # => no repetition
|
23
|
+
# self.xx.foo.baz # => all repeated
|
24
|
+
#
|
25
|
+
module RepeatedAttributeDiscount
|
26
|
+
extend NodePattern::Macros
|
27
|
+
include RuboCop::AST::Sexp
|
28
|
+
|
29
|
+
# Plug into the calculator
|
30
|
+
def initialize(node, discount_repeated_attributes: false)
|
31
|
+
super(node)
|
32
|
+
return unless discount_repeated_attributes
|
33
|
+
|
34
|
+
self_attributes = {} # Share hash for `(send nil? :foo)` and `(send (self) :foo)`
|
35
|
+
@known_attributes = {
|
36
|
+
s(:self) => self_attributes,
|
37
|
+
nil => self_attributes
|
38
|
+
}
|
39
|
+
# example after running `obj = foo.bar; obj.baz.qux`
|
40
|
+
# { nil => {foo: {bar: {}}},
|
41
|
+
# s(self) => same hash ^,
|
42
|
+
# s(:lvar, :obj) => {baz: {qux: {}}}
|
43
|
+
# }
|
44
|
+
end
|
45
|
+
|
46
|
+
def discount_repeated_attributes?
|
47
|
+
defined?(@known_attributes)
|
48
|
+
end
|
49
|
+
|
50
|
+
def evaluate_branch_nodes(node)
|
51
|
+
return if discount_repeated_attributes? && discount_repeated_attribute?(node)
|
52
|
+
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
def calculate_node(node)
|
57
|
+
update_repeated_attribute(node) if discount_repeated_attributes?
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def_node_matcher :attribute_call?, <<~PATTERN
|
64
|
+
( {csend send} _receiver _method # and no parameters
|
65
|
+
)
|
66
|
+
PATTERN
|
67
|
+
|
68
|
+
def discount_repeated_attribute?(send_node)
|
69
|
+
return false unless attribute_call?(send_node)
|
70
|
+
|
71
|
+
repeated = true
|
72
|
+
find_attributes(send_node) do |hash, lookup|
|
73
|
+
return false if hash.nil?
|
74
|
+
|
75
|
+
repeated = false
|
76
|
+
hash[lookup] = {}
|
77
|
+
end
|
78
|
+
|
79
|
+
repeated
|
80
|
+
end
|
81
|
+
|
82
|
+
def update_repeated_attribute(node)
|
83
|
+
return unless (receiver, method = setter_to_getter(node))
|
84
|
+
|
85
|
+
calls = find_attributes(receiver) { return }
|
86
|
+
if method # e.g. `self.foo = 42`
|
87
|
+
calls.delete(method)
|
88
|
+
else # e.g. `var = 42`
|
89
|
+
calls.clear
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def_node_matcher :root_node?, <<~PATTERN
|
94
|
+
{ nil? | self # e.g. receiver of `my_method` or `self.my_attr`
|
95
|
+
| lvar | ivar | cvar | gvar # e.g. receiver of `var.my_method`
|
96
|
+
| const } # e.g. receiver of `MyConst.foo.bar`
|
97
|
+
PATTERN
|
98
|
+
|
99
|
+
# Returns the "known_attributes" for the `node` by walking the receiver tree
|
100
|
+
# If at any step the subdirectory does not exist, it is yielded with the
|
101
|
+
# associated key (method_name)
|
102
|
+
# If the node is not a series of `(c)send` calls with no arguments,
|
103
|
+
# then `nil` is yielded
|
104
|
+
def find_attributes(node, &block)
|
105
|
+
if attribute_call?(node)
|
106
|
+
calls = find_attributes(node.receiver, &block)
|
107
|
+
value = node.method_name
|
108
|
+
elsif root_node?(node)
|
109
|
+
calls = @known_attributes
|
110
|
+
value = node
|
111
|
+
else
|
112
|
+
return yield nil
|
113
|
+
end
|
114
|
+
|
115
|
+
calls.fetch(value) do
|
116
|
+
yield [calls, value]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
VAR_SETTER_TO_GETTER = {
|
121
|
+
lvasgn: :lvar,
|
122
|
+
ivasgn: :ivar,
|
123
|
+
cvasgn: :cvar,
|
124
|
+
gvasgn: :gvar
|
125
|
+
}.freeze
|
126
|
+
|
127
|
+
# @returns `[receiver, method | nil]` for the given setter `node`
|
128
|
+
# or `nil` if it is not a setter.
|
129
|
+
def setter_to_getter(node)
|
130
|
+
if (type = VAR_SETTER_TO_GETTER[node.type])
|
131
|
+
# (lvasgn :my_var (int 42)) => [(lvar my_var), nil]
|
132
|
+
[s(type, node.children.first), nil]
|
133
|
+
elsif node.shorthand_asgn? # (or-asgn (send _receiver :foo) _value)
|
134
|
+
# (or-asgn (send _receiver :foo) _value) => [_receiver, :foo]
|
135
|
+
node.children.first.children
|
136
|
+
elsif node.respond_to?(:setter_method?) && node.setter_method?
|
137
|
+
# (send _receiver :foo= (int 42) ) => [_receiver, :foo]
|
138
|
+
method_name = node.method_name[0...-1].to_sym
|
139
|
+
[node.receiver, method_name]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|