rubocop 1.1.0 → 1.4.1

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.
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