rubocop 1.1.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -12
  3. data/config/default.yml +113 -16
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop.rb +9 -0
  6. data/lib/rubocop/cli/command/execute_runner.rb +26 -11
  7. data/lib/rubocop/config_loader.rb +14 -5
  8. data/lib/rubocop/config_regeneration.rb +1 -1
  9. data/lib/rubocop/cop/bundler/duplicated_gem.rb +3 -3
  10. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  11. data/lib/rubocop/cop/commissioner.rb +1 -1
  12. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +1 -1
  13. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +3 -3
  14. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +4 -5
  15. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +1 -1
  16. data/lib/rubocop/cop/generator.rb +2 -9
  17. data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
  18. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +1 -1
  19. data/lib/rubocop/cop/layout/block_alignment.rb +3 -4
  20. data/lib/rubocop/cop/layout/class_structure.rb +15 -3
  21. data/lib/rubocop/cop/layout/else_alignment.rb +15 -2
  22. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +77 -7
  23. data/lib/rubocop/cop/layout/end_alignment.rb +3 -3
  24. data/lib/rubocop/cop/layout/hash_alignment.rb +4 -4
  25. data/lib/rubocop/cop/layout/line_length.rb +8 -1
  26. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +24 -18
  27. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +1 -1
  28. data/lib/rubocop/cop/layout/space_inside_parens.rb +35 -13
  29. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +2 -1
  30. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +26 -2
  31. data/lib/rubocop/cop/lint/debugger.rb +17 -27
  32. data/lib/rubocop/cop/lint/duplicate_branch.rb +93 -0
  33. data/lib/rubocop/cop/lint/duplicate_case_condition.rb +2 -12
  34. data/lib/rubocop/cop/lint/else_layout.rb +29 -3
  35. data/lib/rubocop/cop/lint/empty_block.rb +38 -2
  36. data/lib/rubocop/cop/lint/empty_class.rb +93 -0
  37. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +22 -4
  38. data/lib/rubocop/cop/lint/loop.rb +4 -4
  39. data/lib/rubocop/cop/lint/missing_super.rb +7 -4
  40. data/lib/rubocop/cop/lint/nested_percent_literal.rb +14 -0
  41. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +58 -0
  42. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +19 -16
  43. data/lib/rubocop/cop/lint/shadowed_exception.rb +4 -5
  44. data/lib/rubocop/cop/lint/to_enum_arguments.rb +6 -15
  45. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +13 -4
  46. data/lib/rubocop/cop/lint/useless_method_definition.rb +2 -4
  47. data/lib/rubocop/cop/lint/useless_setter_call.rb +6 -1
  48. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  49. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  50. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -3
  51. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  52. data/lib/rubocop/cop/mixin/statement_modifier.rb +9 -4
  53. data/lib/rubocop/cop/mixin/visibility_help.rb +1 -3
  54. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +11 -1
  55. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +11 -5
  56. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +67 -18
  57. data/lib/rubocop/cop/naming/variable_number.rb +98 -8
  58. data/lib/rubocop/cop/style/and_or.rb +1 -3
  59. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +0 -4
  60. data/lib/rubocop/cop/style/case_like_if.rb +0 -4
  61. data/lib/rubocop/cop/style/collection_compact.rb +91 -0
  62. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +107 -5
  63. data/lib/rubocop/cop/style/documentation.rb +12 -1
  64. data/lib/rubocop/cop/style/double_negation.rb +6 -1
  65. data/lib/rubocop/cop/style/hash_syntax.rb +3 -3
  66. data/lib/rubocop/cop/style/identical_conditional_branches.rb +7 -2
  67. data/lib/rubocop/cop/style/if_inside_else.rb +37 -1
  68. data/lib/rubocop/cop/style/if_unless_modifier.rb +7 -3
  69. data/lib/rubocop/cop/style/infinite_loop.rb +4 -0
  70. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -0
  71. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  72. data/lib/rubocop/cop/style/mixin_grouping.rb +0 -4
  73. data/lib/rubocop/cop/style/multiple_comparison.rb +3 -2
  74. data/lib/rubocop/cop/style/negated_if_else_condition.rb +106 -0
  75. data/lib/rubocop/cop/style/nil_lambda.rb +52 -0
  76. data/lib/rubocop/cop/style/raise_args.rb +21 -6
  77. data/lib/rubocop/cop/style/redundant_argument.rb +73 -0
  78. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  79. data/lib/rubocop/cop/style/static_class.rb +97 -0
  80. data/lib/rubocop/cop/style/while_until_modifier.rb +9 -0
  81. data/lib/rubocop/cop/util.rb +5 -1
  82. data/lib/rubocop/cop/variable_force/branch.rb +1 -1
  83. data/lib/rubocop/cop/variable_force/scope.rb +1 -1
  84. data/lib/rubocop/ext/regexp_node.rb +10 -5
  85. data/lib/rubocop/ext/regexp_parser.rb +9 -2
  86. data/lib/rubocop/formatter/disabled_config_formatter.rb +21 -6
  87. data/lib/rubocop/formatter/formatter_set.rb +1 -0
  88. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +47 -0
  89. data/lib/rubocop/options.rb +7 -0
  90. data/lib/rubocop/rake_task.rb +2 -2
  91. data/lib/rubocop/runner.rb +1 -1
  92. data/lib/rubocop/target_finder.rb +1 -1
  93. data/lib/rubocop/target_ruby.rb +65 -1
  94. data/lib/rubocop/version.rb +1 -1
  95. metadata +14 -8
  96. data/bin/console +0 -10
  97. data/bin/rubocop-profile +0 -32
  98. data/bin/setup +0 -7
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for the presence of a `return` inside a `begin..end` block
7
+ # in assignment contexts.
8
+ # In this situation, the `return` will result in an exit from the current
9
+ # method, possibly leading to unexpected behavior.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ #
15
+ # @some_variable ||= begin
16
+ # return some_value if some_condition_is_met
17
+ #
18
+ # do_something
19
+ # end
20
+ #
21
+ # @example
22
+ #
23
+ # # good
24
+ #
25
+ # @some_variable ||= begin
26
+ # if some_condition_is_met
27
+ # some_value
28
+ # else
29
+ # do_something
30
+ # end
31
+ # end
32
+ #
33
+ # # good
34
+ #
35
+ # some_variable = if some_condition_is_met
36
+ # return if another_condition_is_met
37
+ #
38
+ # some_value
39
+ # else
40
+ # do_something
41
+ # end
42
+ #
43
+ class NoReturnInBeginEndBlocks < Cop
44
+ MSG = 'Do not `return` in `begin..end` blocks in assignment contexts.'
45
+
46
+ def on_lvasgn(node)
47
+ node.each_node(:kwbegin) do |kwbegin_node|
48
+ kwbegin_node.each_node(:return) do |return_node|
49
+ add_offense(return_node)
50
+ end
51
+ end
52
+ end
53
+ alias on_or_asgn on_lvasgn
54
+ alias on_op_asgn on_lvasgn
55
+ end
56
+ end
57
+ end
58
+ end
@@ -88,31 +88,34 @@ module RuboCop
88
88
  begin_pos = reposition(source, begin_pos, -1)
89
89
  end_pos = reposition(source, end_pos, 1)
90
90
 
91
- comma_pos =
92
- if source[begin_pos - 1] == ','
93
- :before
94
- elsif source[end_pos] == ','
95
- :after
96
- else
97
- :none
98
- end
99
-
100
- range_to_remove(begin_pos, end_pos, comma_pos, comment)
91
+ range_to_remove(begin_pos, end_pos, comment)
101
92
  end
102
93
 
103
- def range_to_remove(begin_pos, end_pos, comma_pos, comment)
94
+ def range_to_remove(begin_pos, end_pos, comment)
104
95
  start = comment_start(comment)
96
+ source = comment.loc.expression.source
105
97
 
106
- case comma_pos
107
- when :before
108
- range_between(start + begin_pos - 1, start + end_pos)
109
- when :after
110
- range_between(start + begin_pos, start + end_pos + 1)
98
+ if source[begin_pos - 1] == ','
99
+ range_with_comma_before(start, begin_pos, end_pos)
100
+ elsif source[end_pos] == ','
101
+ range_with_comma_after(comment, start, begin_pos, end_pos)
111
102
  else
112
103
  range_between(start, comment.loc.expression.end_pos)
113
104
  end
114
105
  end
115
106
 
107
+ def range_with_comma_before(start, begin_pos, end_pos)
108
+ range_between(start + begin_pos - 1, start + end_pos)
109
+ end
110
+
111
+ # If the list of cops is comma-separated, but without a empty space after the comma,
112
+ # we should **not** remove the prepending empty space, thus begin_pos += 1
113
+ def range_with_comma_after(comment, start, begin_pos, end_pos)
114
+ begin_pos += 1 if comment.loc.expression.source[end_pos + 1] != ' '
115
+
116
+ range_between(start + begin_pos, start + end_pos + 1)
117
+ end
118
+
116
119
  def all_or_name(name)
117
120
  name == 'all' ? 'all cops' : name
118
121
  end
@@ -140,11 +140,10 @@ module RuboCop
140
140
  rescued_groups.each_cons(2).all? do |x, y|
141
141
  if x.include?(Exception)
142
142
  false
143
- elsif y.include?(Exception)
144
- true
145
- elsif x.none? || y.none?
146
- # consider sorted if a group is empty or only contains
147
- # `nil`s
143
+ elsif y.include?(Exception) ||
144
+ # consider sorted if a group is empty or only contains
145
+ # `nil`s
146
+ x.none? || y.none?
148
147
  true
149
148
  else
150
149
  (x <=> y || 0) <= 0
@@ -8,23 +8,14 @@ module RuboCop
8
8
  #
9
9
  # @example
10
10
  # # bad
11
- # def method(x, y = 1)
12
- # return to_enum(__method__, x) # `y` is missing
11
+ # def foo(x, y = 1)
12
+ # return to_enum(__callee__, x) # `y` is missing
13
13
  # end
14
14
  #
15
15
  # # good
16
- # def method(x, y = 1)
17
- # return to_enum(__method__, x, y)
18
- # end
19
- #
20
- # # bad
21
- # def method(required:)
22
- # return to_enum(:method, required: something) # `required` has incorrect value
23
- # end
24
- #
25
- # # good
26
- # def method(required:)
27
- # return to_enum(:method, required: required)
16
+ # def foo(x, y = 1)
17
+ # return to_enum(__callee__, x, y)
18
+ # # alternatives to `__callee__` are `__method__` and `:foo`
28
19
  # end
29
20
  #
30
21
  class ToEnumArguments < Base
@@ -37,7 +28,7 @@ module RuboCop
37
28
  PATTERN
38
29
 
39
30
  def_node_matcher :method_name?, <<~PATTERN
40
- {(send nil? :__method__) (sym %1)}
31
+ {(send nil? {:__method__ :__callee__}) (sym %1)}
41
32
  PATTERN
42
33
 
43
34
  def_node_matcher :passing_keyword_arg?, <<~PATTERN
@@ -54,6 +54,9 @@ module RuboCop
54
54
  # value
55
55
  # end
56
56
  #
57
+ # # good, recursive
58
+ # keys.reduce(self) { |result, key| result[key] }
59
+ #
57
60
  # # ignored as the return value cannot be determined
58
61
  # enum.reduce do |acc, el|
59
62
  # x = foo(acc, el)
@@ -131,7 +134,7 @@ module RuboCop
131
134
  element_name = block_arg_name(block_node, 1)
132
135
  message_opts = { method: block_node.method_name, accum: accumulator_name }
133
136
 
134
- if (node = returned_accumulator_index(return_values, accumulator_name))
137
+ if (node = returned_accumulator_index(return_values, accumulator_name, element_name))
135
138
  add_offense(node, message: format(MSG_INDEX, message_opts))
136
139
  elsif potential_offense?(return_values, block_node.body, element_name, accumulator_name)
137
140
  return_values.each do |return_val|
@@ -146,11 +149,17 @@ module RuboCop
146
149
  node.arguments[index].node_parts[0]
147
150
  end
148
151
 
149
- # Look for an index of the accumulator being returned
152
+ # Look for an index of the accumulator being returned, except where the index
153
+ # is the element.
150
154
  # This is always an offense, in order to try to catch potential exceptions
151
155
  # due to type mismatches
152
- def returned_accumulator_index(return_values, accumulator_name)
153
- return_values.detect { |val| accumulator_index?(val, accumulator_name) }
156
+ def returned_accumulator_index(return_values, accumulator_name, element_name)
157
+ return_values.detect do |val|
158
+ next unless accumulator_index?(val, accumulator_name)
159
+ next true if val.method?(:[]=)
160
+
161
+ val.arguments.none? { |arg| lvar_used?(arg, element_name) }
162
+ end
154
163
  end
155
164
 
156
165
  def potential_offense?(return_values, block_body, element_name, accumulator_name)
@@ -54,11 +54,9 @@ module RuboCop
54
54
  end
55
55
 
56
56
  def delegating?(node, def_node)
57
- if node.nil?
58
- false
59
- elsif node.zsuper_type?
57
+ if node&.zsuper_type?
60
58
  true
61
- elsif node.super_type?
59
+ elsif node&.super_type?
62
60
  node.arguments.map(&:source) == def_node.arguments.map(&:source)
63
61
  else
64
62
  false
@@ -5,6 +5,7 @@ module RuboCop
5
5
  module Lint
6
6
  # This cop checks for setter call to local variable as the final
7
7
  # expression of a function definition.
8
+ # Its auto-correction is marked as unsafe because return value will be changed.
8
9
  #
9
10
  # NOTE: There are edge cases in which the local variable references a
10
11
  # value that is also accessible outside the local scope. This is not
@@ -29,6 +30,8 @@ module RuboCop
29
30
  # x
30
31
  # end
31
32
  class UselessSetterCall < Base
33
+ extend AutoCorrector
34
+
32
35
  MSG = 'Useless setter call to local variable `%<variable>s`.'
33
36
  ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn].freeze
34
37
 
@@ -45,7 +48,9 @@ module RuboCop
45
48
 
46
49
  loc_name = receiver.loc.name
47
50
 
48
- add_offense(loc_name, message: format(MSG, variable: loc_name.source))
51
+ add_offense(loc_name, message: format(MSG, variable: loc_name.source)) do |corrector|
52
+ corrector.insert_after(last_expr, "\n#{indent(last_expr)}#{loc_name.source}")
53
+ end
49
54
  end
50
55
  alias on_defs on_def
51
56
 
@@ -36,7 +36,7 @@ module RuboCop
36
36
 
37
37
  def on_def(node)
38
38
  excluded_methods = cop_config['ExcludedMethods']
39
- return if excluded_methods.include?(String(node.method_name))
39
+ return if excluded_methods.any? { |m| m.match? String(node.method_name) }
40
40
 
41
41
  check_code_length(node)
42
42
  end
@@ -103,7 +103,7 @@ module RuboCop
103
103
  # hashes wrapped in a set of curly braces like {foo: 1}.
104
104
  # That is, not a kwargs hash. For method calls, this ensures
105
105
  # the method call is made with parens.
106
- starts_with_bracket = node.loc.begin
106
+ starts_with_bracket = !node.hash_type? || node.loc.begin
107
107
 
108
108
  # If the call has a second argument, we can insert a line
109
109
  # break before the second argument and the rest of the
@@ -8,9 +8,9 @@ module RuboCop
8
8
  include ConfigurableFormatting
9
9
 
10
10
  FORMATS = {
11
- snake_case: /(?:[[[:lower:]]_]|_\d+)$/,
12
- normalcase: /(?:_\D*|[[[:upper:]][[:lower:]]]\d*)$/,
13
- non_integer: /[[[:upper:]][[:lower:]]_]$/
11
+ snake_case: /(?:\D|_\d+|\A\d+)\z/,
12
+ normalcase: /(?:\D|[^_\d]\d+|\A\d+)\z/,
13
+ non_integer: /(\D|\A\d+)\z/
14
14
  }.freeze
15
15
  end
16
16
  end
@@ -31,7 +31,7 @@ module RuboCop
31
31
  # b c { block }. <-- b is indented relative to a
32
32
  # d <-- d is indented relative to a
33
33
  def left_hand_side(lhs)
34
- lhs = lhs.parent while lhs.parent&.send_type?
34
+ lhs = lhs.parent while lhs.parent&.send_type? && lhs.parent.loc.dot
35
35
  lhs
36
36
  end
37
37
 
@@ -19,7 +19,8 @@ module RuboCop
19
19
  def non_eligible_node?(node)
20
20
  node.modifier_form? ||
21
21
  node.nonempty_line_count > 3 ||
22
- processed_source.line_with_comment?(node.loc.last_line)
22
+ processed_source.line_with_comment?(node.loc.last_line) ||
23
+ (first_line_comment(node) && code_after(node))
23
24
  end
24
25
 
25
26
  def non_eligible_body?(body)
@@ -41,11 +42,9 @@ module RuboCop
41
42
 
42
43
  def length_in_modifier_form(node)
43
44
  keyword_element = node.loc.keyword
44
- end_element = node.loc.end
45
45
  code_before = keyword_element.source_line[0...keyword_element.column]
46
- code_after = end_element.source_line[end_element.last_column..-1]
47
46
  expression = to_modifier_form(node)
48
- line_length("#{code_before}#{expression}#{code_after}")
47
+ line_length("#{code_before}#{expression}#{code_after(node)}")
49
48
  end
50
49
 
51
50
  def to_modifier_form(node)
@@ -64,6 +63,12 @@ module RuboCop
64
63
  comment_source unless comment_disables_cop?(comment_source)
65
64
  end
66
65
 
66
+ def code_after(node)
67
+ end_element = node.loc.end
68
+ code = end_element.source_line[end_element.last_column..-1]
69
+ code unless code.empty?
70
+ end
71
+
67
72
  def parenthesize?(node)
68
73
  # Parenthesize corrected expression if changing to modifier-if form
69
74
  # would change the meaning of the parent expression
@@ -16,9 +16,7 @@ module RuboCop
16
16
  end
17
17
 
18
18
  def find_visibility_start(node)
19
- node.left_siblings
20
- .reverse
21
- .find(&method(:visibility_block?))
19
+ node.left_siblings.reverse.find { |sibling| visibility_block?(sibling) }
22
20
  end
23
21
 
24
22
  # Navigate to find the last protected method
@@ -14,6 +14,8 @@ module RuboCop
14
14
  # # good
15
15
  # def +(other); end
16
16
  class BinaryOperatorParameterName < Base
17
+ extend AutoCorrector
18
+
17
19
  MSG = 'When defining the `%<opr>s` operator, ' \
18
20
  'name its argument `other`.'
19
21
 
@@ -26,7 +28,15 @@ module RuboCop
26
28
 
27
29
  def on_def(node)
28
30
  op_method_candidate?(node) do |name, arg|
29
- add_offense(arg, message: format(MSG, opr: name))
31
+ add_offense(arg, message: format(MSG, opr: name)) do |corrector|
32
+ corrector.replace(arg, 'other')
33
+ node.each_descendant(:lvar, :lvasgn) do |lvar|
34
+ lvar_location = lvar.loc.name
35
+ next unless lvar_location.source == arg.source
36
+
37
+ corrector.replace(lvar_location, 'other')
38
+ end
39
+ end
30
40
  end
31
41
  end
32
42
 
@@ -30,13 +30,19 @@ module RuboCop
30
30
  class HeredocDelimiterCase < Base
31
31
  include Heredoc
32
32
  include ConfigurableEnforcedStyle
33
+ extend AutoCorrector
33
34
 
34
35
  MSG = 'Use %<style>s heredoc delimiters.'
35
36
 
36
37
  def on_heredoc(node)
37
38
  return if correct_case_delimiters?(node)
38
39
 
39
- add_offense(node.loc.heredoc_end)
40
+ add_offense(node.loc.heredoc_end) do |corrector|
41
+ expr = node.loc.expression
42
+
43
+ corrector.replace(expr, correct_delimiters(expr.source))
44
+ corrector.replace(node.loc.heredoc_end, correct_delimiters(delimiter_string(expr)))
45
+ end
40
46
  end
41
47
 
42
48
  private
@@ -46,14 +52,14 @@ module RuboCop
46
52
  end
47
53
 
48
54
  def correct_case_delimiters?(node)
49
- delimiter_string(node) == correct_delimiters(node)
55
+ delimiter_string(node) == correct_delimiters(delimiter_string(node))
50
56
  end
51
57
 
52
- def correct_delimiters(node)
58
+ def correct_delimiters(source)
53
59
  if style == :uppercase
54
- delimiter_string(node).upcase
60
+ source.upcase
55
61
  else
56
- delimiter_string(node).downcase
62
+ source.downcase
57
63
  end
58
64
  end
59
65
  end
@@ -20,6 +20,11 @@ module RuboCop
20
20
  # @something ||= calculate_expensive_thing
21
21
  # end
22
22
  #
23
+ # def foo
24
+ # return @something if defined?(@something)
25
+ # @something = calculate_expensive_thing
26
+ # end
27
+ #
23
28
  # # good
24
29
  # def _foo
25
30
  # @foo ||= calculate_expensive_thing
@@ -54,6 +59,11 @@ module RuboCop
54
59
  # @foo ||= calculate_expensive_thing
55
60
  # end
56
61
  #
62
+ # def foo
63
+ # return @foo if defined?(@foo)
64
+ # @foo = calculate_expensive_thing
65
+ # end
66
+ #
57
67
  # # good
58
68
  # def foo
59
69
  # @_foo ||= calculate_expensive_thing
@@ -64,6 +74,11 @@ module RuboCop
64
74
  # @_foo ||= calculate_expensive_thing
65
75
  # end
66
76
  #
77
+ # def foo
78
+ # return @_foo if defined?(@_foo)
79
+ # @_foo = calculate_expensive_thing
80
+ # end
81
+ #
67
82
  # @example EnforcedStyleForLeadingUnderscores :optional
68
83
  # # bad
69
84
  # def foo
@@ -84,6 +99,12 @@ module RuboCop
84
99
  # def _foo
85
100
  # @_foo ||= calculate_expensive_thing
86
101
  # end
102
+ #
103
+ # # good
104
+ # def foo
105
+ # return @_foo if defined?(@_foo)
106
+ # @_foo = calculate_expensive_thing
107
+ # end
87
108
  class MemoizedInstanceVariableName < Base
88
109
  include ConfigurableEnforcedStyle
89
110
 
@@ -92,32 +113,60 @@ module RuboCop
92
113
  UNDERSCORE_REQUIRED = 'Memoized variable `%<var>s` does not start ' \
93
114
  'with `_`. Use `@%<suggested_var>s` instead.'
94
115
 
95
- def self.node_pattern
96
- memo_assign = '(or_asgn $(ivasgn _) _)'
97
- memoized_at_end_of_method = "(begin ... #{memo_assign})"
98
- instance_method =
99
- "(def $_ _ {#{memo_assign} #{memoized_at_end_of_method}})"
100
- class_method =
101
- "(defs self $_ _ {#{memo_assign} #{memoized_at_end_of_method}})"
102
- "{#{instance_method} #{class_method}}"
103
- end
116
+ # rubocop:disable Metrics/AbcSize
117
+ def on_or_asgn(node)
118
+ lhs, _value = *node
119
+ return unless lhs.ivasgn_type?
120
+ return unless (method_node = node.each_ancestor(:def, :defs).first)
104
121
 
105
- private_class_method :node_pattern
106
- def_node_matcher :memoized?, node_pattern
122
+ body = method_node.body
123
+ return unless body == node || body.children.last == node
107
124
 
108
- def on_def(node)
109
- (method_name, ivar_assign) = memoized?(node)
110
- return if matches?(method_name, ivar_assign)
125
+ method_name = method_node.method_name
126
+ return if matches?(method_name, lhs)
111
127
 
112
128
  msg = format(
113
- message(ivar_assign.children.first.to_s),
114
- var: ivar_assign.children.first.to_s,
129
+ message(lhs.children.first.to_s),
130
+ var: lhs.children.first.to_s,
115
131
  suggested_var: suggested_var(method_name),
116
132
  method: method_name
117
133
  )
118
- add_offense(ivar_assign.source_range, message: msg)
134
+ add_offense(lhs, message: msg)
135
+ end
136
+ # rubocop:enable Metrics/AbcSize
137
+
138
+ def_node_matcher :defined_memoized?, <<~PATTERN
139
+ (begin
140
+ (if (defined $(ivar %1)) (return $(ivar %1)) nil?)
141
+ ...
142
+ $(ivasgn %1 _))
143
+ PATTERN
144
+
145
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
146
+ def on_defined?(node)
147
+ arg = node.arguments.first
148
+ return unless arg.ivar_type?
149
+
150
+ method_node = node.each_ancestor(:def, :defs).first
151
+ return unless method_node
152
+
153
+ var_name = arg.children.first
154
+ method_name = method_node.method_name
155
+ defined_memoized?(method_node.body, var_name) do |defined_ivar, return_ivar, ivar_assign|
156
+ return if matches?(method_name, ivar_assign)
157
+
158
+ msg = format(
159
+ message(var_name.to_s),
160
+ var: var_name.to_s,
161
+ suggested_var: suggested_var(method_name),
162
+ method: method_name
163
+ )
164
+ add_offense(defined_ivar, message: msg)
165
+ add_offense(return_ivar, message: msg)
166
+ add_offense(ivar_assign.loc.name, message: msg)
167
+ end
119
168
  end
120
- alias on_defs on_def
169
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
121
170
 
122
171
  private
123
172