rubocop 1.4.2 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|