rubocop 0.92.0 → 1.2.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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -7
  3. data/config/default.yml +169 -59
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop.rb +15 -3
  6. data/lib/rubocop/cached_data.rb +2 -1
  7. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  8. data/lib/rubocop/cli/command/version.rb +1 -1
  9. data/lib/rubocop/comment_config.rb +1 -1
  10. data/lib/rubocop/config.rb +4 -0
  11. data/lib/rubocop/config_loader.rb +19 -2
  12. data/lib/rubocop/config_loader_resolver.rb +7 -5
  13. data/lib/rubocop/config_validator.rb +7 -6
  14. data/lib/rubocop/cop/badge.rb +9 -24
  15. data/lib/rubocop/cop/base.rb +16 -1
  16. data/lib/rubocop/cop/bundler/duplicated_gem.rb +23 -3
  17. data/lib/rubocop/cop/commissioner.rb +36 -22
  18. data/lib/rubocop/cop/corrector.rb +3 -1
  19. data/lib/rubocop/cop/correctors/line_break_corrector.rb +2 -2
  20. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +1 -1
  21. data/lib/rubocop/cop/force.rb +1 -1
  22. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +10 -10
  23. data/lib/rubocop/cop/layout/class_structure.rb +7 -0
  24. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  25. data/lib/rubocop/cop/layout/dot_position.rb +6 -9
  26. data/lib/rubocop/cop/layout/else_alignment.rb +15 -2
  27. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +7 -7
  28. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -1
  29. data/lib/rubocop/cop/layout/end_alignment.rb +3 -3
  30. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -2
  31. data/lib/rubocop/cop/layout/hash_alignment.rb +4 -4
  32. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +24 -18
  33. data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +2 -11
  34. data/lib/rubocop/cop/layout/space_around_operators.rb +4 -1
  35. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +0 -4
  36. data/lib/rubocop/cop/layout/space_inside_parens.rb +35 -13
  37. data/lib/rubocop/cop/layout/trailing_whitespace.rb +37 -13
  38. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +2 -0
  39. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +18 -1
  40. data/lib/rubocop/cop/lint/boolean_symbol.rb +3 -0
  41. data/lib/rubocop/cop/lint/debugger.rb +2 -3
  42. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +77 -0
  43. data/lib/rubocop/cop/lint/else_layout.rb +29 -3
  44. data/lib/rubocop/cop/lint/empty_block.rb +59 -0
  45. data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
  46. data/lib/rubocop/cop/lint/hash_compare_by_identity.rb +37 -0
  47. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +17 -3
  48. data/lib/rubocop/cop/lint/loop.rb +0 -4
  49. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +1 -0
  50. data/lib/rubocop/cop/lint/nested_percent_literal.rb +14 -0
  51. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +58 -0
  52. data/lib/rubocop/cop/lint/number_conversion.rb +46 -13
  53. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +27 -8
  54. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
  55. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +78 -0
  56. data/lib/rubocop/cop/lint/to_enum_arguments.rb +95 -0
  57. data/lib/rubocop/cop/lint/to_json.rb +1 -1
  58. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +185 -0
  59. data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
  60. data/lib/rubocop/cop/lint/useless_setter_call.rb +6 -1
  61. data/lib/rubocop/cop/metrics/block_length.rb +3 -1
  62. data/lib/rubocop/cop/metrics/class_length.rb +14 -6
  63. data/lib/rubocop/cop/metrics/parameter_lists.rb +4 -1
  64. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -3
  65. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  66. data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
  67. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +12 -2
  68. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +11 -5
  69. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +67 -18
  70. data/lib/rubocop/cop/naming/predicate_name.rb +2 -1
  71. data/lib/rubocop/cop/naming/variable_number.rb +82 -8
  72. data/lib/rubocop/cop/offense.rb +18 -5
  73. data/lib/rubocop/cop/security/open.rb +12 -10
  74. data/lib/rubocop/cop/style/access_modifier_declarations.rb +6 -2
  75. data/lib/rubocop/cop/style/accessor_grouping.rb +3 -0
  76. data/lib/rubocop/cop/style/arguments_forwarding.rb +142 -0
  77. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +0 -4
  78. data/lib/rubocop/cop/style/case_like_if.rb +18 -6
  79. data/lib/rubocop/cop/style/class_equality_comparison.rb +64 -0
  80. data/lib/rubocop/cop/style/collection_compact.rb +85 -0
  81. data/lib/rubocop/cop/style/combinable_loops.rb +8 -1
  82. data/lib/rubocop/cop/style/comment_annotation.rb +6 -0
  83. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +67 -0
  84. data/lib/rubocop/cop/style/double_negation.rb +6 -1
  85. data/lib/rubocop/cop/style/explicit_block_argument.rb +6 -2
  86. data/lib/rubocop/cop/style/for.rb +0 -4
  87. data/lib/rubocop/cop/style/format_string_token.rb +48 -3
  88. data/lib/rubocop/cop/style/hash_syntax.rb +3 -3
  89. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -0
  90. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +10 -13
  91. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +6 -11
  92. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +7 -11
  93. data/lib/rubocop/cop/style/method_def_parentheses.rb +0 -4
  94. data/lib/rubocop/cop/style/mixin_grouping.rb +0 -4
  95. data/lib/rubocop/cop/style/multiple_comparison.rb +54 -7
  96. data/lib/rubocop/cop/style/negated_if_else_condition.rb +99 -0
  97. data/lib/rubocop/cop/style/nested_ternary_operator.rb +2 -0
  98. data/lib/rubocop/cop/style/raise_args.rb +21 -9
  99. data/lib/rubocop/cop/style/redundant_begin.rb +36 -8
  100. data/lib/rubocop/cop/style/redundant_condition.rb +5 -1
  101. data/lib/rubocop/cop/style/redundant_interpolation.rb +6 -1
  102. data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -0
  103. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +45 -24
  104. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -15
  105. data/lib/rubocop/cop/style/redundant_self.rb +3 -0
  106. data/lib/rubocop/cop/style/safe_navigation.rb +16 -4
  107. data/lib/rubocop/cop/style/semicolon.rb +3 -0
  108. data/lib/rubocop/cop/style/string_concatenation.rb +14 -2
  109. data/lib/rubocop/cop/style/swap_values.rb +108 -0
  110. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  111. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +3 -1
  112. data/lib/rubocop/cop/team.rb +6 -1
  113. data/lib/rubocop/cop/util.rb +5 -1
  114. data/lib/rubocop/cop/variable_force/branch.rb +0 -4
  115. data/lib/rubocop/ext/regexp_node.rb +35 -11
  116. data/lib/rubocop/ext/regexp_parser.rb +84 -0
  117. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  118. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +47 -0
  119. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  120. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  121. data/lib/rubocop/magic_comment.rb +2 -2
  122. data/lib/rubocop/options.rb +6 -1
  123. data/lib/rubocop/result_cache.rb +8 -2
  124. data/lib/rubocop/rspec/cop_helper.rb +1 -1
  125. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  126. data/lib/rubocop/runner.rb +4 -4
  127. data/lib/rubocop/target_finder.rb +23 -25
  128. data/lib/rubocop/version.rb +56 -6
  129. metadata +22 -8
  130. data/lib/rubocop/cop/mixin/regexp_literal_help.rb +0 -43
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Looks for `reduce` or `inject` blocks where the value returned (implicitly or
7
+ # explicitly) does not include the accumulator. A block is considered valid as
8
+ # long as at least one return value includes the accumulator.
9
+ #
10
+ # If the accumulator is not included in the return value, then the entire
11
+ # block will just return a transformation of the last element value, and
12
+ # could be rewritten as such without a loop.
13
+ #
14
+ # Also catches instances where an index of the accumulator is returned, as
15
+ # this may change the type of object being retained.
16
+ #
17
+ # NOTE: For the purpose of reducing false positives, this cop only flags
18
+ # returns in `reduce` blocks where the element is the only variable in
19
+ # the expression (since we will not be able to tell what other variables
20
+ # relate to via static analysis).
21
+ #
22
+ # @example
23
+ #
24
+ # # bad
25
+ # (1..4).reduce(0) do |acc, el|
26
+ # el * 2
27
+ # end
28
+ #
29
+ # # bad, may raise a NoMethodError after the first iteration
30
+ # %w(a b c).reduce({}) do |acc, letter|
31
+ # acc[letter] = true
32
+ # end
33
+ #
34
+ # # good
35
+ # (1..4).reduce(0) do |acc, el|
36
+ # acc + el * 2
37
+ # end
38
+ #
39
+ # # good, element is returned but modified using the accumulator
40
+ # values.reduce do |acc, el|
41
+ # el << acc
42
+ # el
43
+ # end
44
+ #
45
+ # # good, returns the accumulator instead of the index
46
+ # %w(a b c).reduce({}) do |acc, letter|
47
+ # acc[letter] = true
48
+ # acc
49
+ # end
50
+ #
51
+ # # good, at least one branch returns the accumulator
52
+ # values.reduce(nil) do |result, value|
53
+ # break result if something?
54
+ # value
55
+ # end
56
+ #
57
+ # # ignored as the return value cannot be determined
58
+ # enum.reduce do |acc, el|
59
+ # x = foo(acc, el)
60
+ # bar(x)
61
+ # end
62
+ class UnmodifiedReduceAccumulator < Base
63
+ MSG = 'Ensure the accumulator `%<accum>s` will be modified by `%<method>s`.'
64
+ MSG_INDEX = 'Do not return an element of the accumulator in `%<method>s`.'
65
+
66
+ def_node_matcher :reduce_with_block?, <<~PATTERN
67
+ (block (send _recv {:reduce :inject} ...) (args arg+) ...)
68
+ PATTERN
69
+
70
+ def_node_matcher :accumulator_index?, <<~PATTERN
71
+ (send (lvar %1) {:[] :[]=} ...)
72
+ PATTERN
73
+
74
+ def_node_search :element_modified?, <<~PATTERN
75
+ {
76
+ (send _receiver !{:[] :[]=} <`(lvar %1) `_ ...>) # method(el, ...)
77
+ (send (lvar %1) _message <{ivar gvar cvar lvar send} ...>) # el.method(...)
78
+ (lvasgn %1 _) # el = ...
79
+ (%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (lvasgn %1) ... _) # el += ...
80
+ }
81
+ PATTERN
82
+
83
+ def_node_matcher :lvar_used?, <<~PATTERN
84
+ {
85
+ (lvar %1)
86
+ (lvasgn %1 ...)
87
+ (send (lvar %1) :<< ...)
88
+ (dstr (begin (lvar %1)))
89
+ (%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (lvasgn %1))
90
+ }
91
+ PATTERN
92
+
93
+ def_node_search :expression_values, <<~PATTERN
94
+ {
95
+ (%RuboCop::AST::Node::VARIABLES $_)
96
+ (%RuboCop::AST::Node::EQUALS_ASSIGNMENTS $_ ...)
97
+ (send (%RuboCop::AST::Node::VARIABLES $_) :<< ...)
98
+ $(send _ _)
99
+ (dstr (begin {(%RuboCop::AST::Node::VARIABLES $_)}))
100
+ (%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (%RuboCop::AST::Node::EQUALS_ASSIGNMENTS $_) ...)
101
+ }
102
+ PATTERN
103
+
104
+ def on_block(node)
105
+ return unless reduce_with_block?(node)
106
+
107
+ check_return_values(node)
108
+ end
109
+
110
+ private
111
+
112
+ # Return values in a block are either the value given to next,
113
+ # the last line of a multiline block, or the only line of the block
114
+ def return_values(block_body_node)
115
+ nodes = [block_body_node.begin_type? ? block_body_node.child_nodes.last : block_body_node]
116
+
117
+ block_body_node.each_descendant(:next, :break) do |n|
118
+ # Ignore `next`/`break` inside an inner block
119
+ next if n.each_ancestor(:block).first != block_body_node.parent
120
+ next unless n.first_argument
121
+
122
+ nodes << n.first_argument
123
+ end
124
+
125
+ nodes
126
+ end
127
+
128
+ def check_return_values(block_node)
129
+ return_values = return_values(block_node.body)
130
+ accumulator_name = block_arg_name(block_node, 0)
131
+ element_name = block_arg_name(block_node, 1)
132
+ message_opts = { method: block_node.method_name, accum: accumulator_name }
133
+
134
+ if (node = returned_accumulator_index(return_values, accumulator_name))
135
+ add_offense(node, message: format(MSG_INDEX, message_opts))
136
+ elsif potential_offense?(return_values, block_node.body, element_name, accumulator_name)
137
+ return_values.each do |return_val|
138
+ unless acceptable_return?(return_val, element_name)
139
+ add_offense(return_val, message: format(MSG, message_opts))
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ def block_arg_name(node, index)
146
+ node.arguments[index].node_parts[0]
147
+ end
148
+
149
+ # Look for an index of the accumulator being returned
150
+ # This is always an offense, in order to try to catch potential exceptions
151
+ # due to type mismatches
152
+ def returned_accumulator_index(return_values, accumulator_name)
153
+ return_values.detect { |val| accumulator_index?(val, accumulator_name) }
154
+ end
155
+
156
+ def potential_offense?(return_values, block_body, element_name, accumulator_name)
157
+ !(element_modified?(block_body, element_name) ||
158
+ returns_accumulator_anywhere?(return_values, accumulator_name))
159
+ end
160
+
161
+ # If the accumulator is used in any return value, the node is acceptable since
162
+ # the accumulator has a chance to change each iteration
163
+ def returns_accumulator_anywhere?(return_values, accumulator_name)
164
+ return_values.any? { |node| lvar_used?(node, accumulator_name) }
165
+ end
166
+
167
+ # Determine if a return value is acceptable for the purposes of this cop
168
+ # If it is an expression containing the accumulator, it is acceptable
169
+ # Otherwise, it is only unacceptable if it contains the iterated element, since we
170
+ # otherwise do not have enough information to prevent false positives.
171
+ def acceptable_return?(return_val, element_name)
172
+ vars = expression_values(return_val).uniq
173
+ return true if vars.none? || (vars - [element_name]).any?
174
+
175
+ false
176
+ end
177
+
178
+ # Exclude `begin` nodes inside a `dstr` from being collected by `return_values`
179
+ def allowed_type?(parent_node)
180
+ !parent_node.dstr_type?
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -255,7 +255,7 @@ module RuboCop
255
255
  PATTERN
256
256
  end
257
257
 
258
- send(matcher_name, child)
258
+ public_send(matcher_name, child)
259
259
  end
260
260
  end
261
261
 
@@ -279,7 +279,7 @@ module RuboCop
279
279
  PATTERN
280
280
  end
281
281
 
282
- send(matcher_name, child)
282
+ public_send(matcher_name, child)
283
283
  end
284
284
  end
285
285
  end
@@ -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
 
@@ -29,6 +29,8 @@ module RuboCop
29
29
  # content.
30
30
  # HEREDOC
31
31
  # end # 5 points
32
+ #
33
+ # NOTE: This cop does not apply for `Struct` definitions.
32
34
  class BlockLength < Base
33
35
  include CodeLength
34
36
 
@@ -36,7 +38,7 @@ module RuboCop
36
38
 
37
39
  def on_block(node)
38
40
  return if excluded_method?(node)
39
- return if node.class_constructor?
41
+ return if node.class_constructor? || node.struct_constructor?
40
42
 
41
43
  check_code_length(node)
42
44
  end
@@ -29,6 +29,8 @@ module RuboCop
29
29
  # HEREDOC
30
30
  # end # 5 points
31
31
  #
32
+ #
33
+ # NOTE: This cop also applies for `Struct` definitions.
32
34
  class ClassLength < Base
33
35
  include CodeLength
34
36
 
@@ -37,17 +39,23 @@ module RuboCop
37
39
  end
38
40
 
39
41
  def on_casgn(node)
40
- class_definition?(node) do
41
- check_code_length(node)
42
+ parent = node.parent
43
+
44
+ if parent&.assignment?
45
+ block_node = parent.children[1]
46
+ elsif parent&.parent&.masgn_type?
47
+ block_node = parent.parent.children[1]
48
+ else
49
+ _scope, _name, block_node = *node
42
50
  end
51
+
52
+ return unless block_node.respond_to?(:class_definition?) && block_node.class_definition?
53
+
54
+ check_code_length(block_node)
43
55
  end
44
56
 
45
57
  private
46
58
 
47
- def_node_matcher :class_definition?, <<~PATTERN
48
- (casgn nil? _ (block (send (const {nil? cbase} :Class) :new) ...))
49
- PATTERN
50
-
51
59
  def message(length, max_length)
52
60
  format('Class has too many lines. [%<length>d/%<max>d]',
53
61
  length: length,
@@ -12,6 +12,9 @@ module RuboCop
12
12
  MSG = 'Avoid parameter lists longer than %<max>d parameters. ' \
13
13
  '[%<count>d/%<max>d]'
14
14
 
15
+ NAMED_KEYWORD_TYPES = %i[kwoptarg kwarg].freeze
16
+ private_constant :NAMED_KEYWORD_TYPES
17
+
15
18
  def on_args(node)
16
19
  count = args_count(node)
17
20
  return unless count > max_params
@@ -33,7 +36,7 @@ module RuboCop
33
36
  if count_keyword_args?
34
37
  node.children.size
35
38
  else
36
- node.children.count { |a| !%i[kwoptarg kwarg].include?(a.type) }
39
+ node.children.count { |a| !NAMED_KEYWORD_TYPES.include?(a.type) }
37
40
  end
38
41
  end
39
42
 
@@ -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+)$/,
12
+ normalcase: /(?:\D|[^_\d]\d+)$/,
13
+ non_integer: /\D$/
14
14
  }.freeze
15
15
  end
16
16
  end
@@ -137,7 +137,7 @@ module RuboCop
137
137
  end
138
138
 
139
139
  # Internal helper class to hold autocorrect data
140
- Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do # rubocop:disable Metrics/BlockLength
140
+ Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do
141
141
  def self.from_each_with_object(node, match)
142
142
  new(match, node, 0, 0)
143
143
  end
@@ -57,7 +57,7 @@ module RuboCop
57
57
  def indentation_difference(line)
58
58
  return 0 unless tab_indentation_width
59
59
 
60
- line.match(/^\t*/)[0].size * (tab_indentation_width - 1)
60
+ (line.index(/[^\t]/) || 0) * (tab_indentation_width - 1)
61
61
  end
62
62
 
63
63
  def tab_indentation_width
@@ -14,11 +14,13 @@ 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
 
20
22
  OP_LIKE_METHODS = %i[eql? equal?].freeze
21
- EXCLUDED = %i[+@ -@ [] []= << === `].freeze
23
+ EXCLUDED = %i[+@ -@ [] []= << === ` =~].freeze
22
24
 
23
25
  def_node_matcher :op_method_candidate?, <<~PATTERN
24
26
  (def [#op_method? $_] (args $(arg [!:other !:_other])) _)
@@ -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