rubocop 1.86.2 → 1.88.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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +82 -71
  3. data/config/obsoletion.yml +21 -1
  4. data/lib/rubocop/cli/command/auto_generate_config.rb +6 -0
  5. data/lib/rubocop/cli.rb +2 -0
  6. data/lib/rubocop/config_loader.rb +17 -2
  7. data/lib/rubocop/config_loader_resolver.rb +11 -3
  8. data/lib/rubocop/config_store.rb +1 -1
  9. data/lib/rubocop/cop/base.rb +25 -4
  10. data/lib/rubocop/cop/bundler/gem_comment.rb +2 -2
  11. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +33 -2
  12. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +2 -2
  13. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  14. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +3 -3
  15. data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +5 -3
  16. data/lib/rubocop/cop/layout/begin_end_alignment.rb +1 -1
  17. data/lib/rubocop/cop/layout/block_alignment.rb +41 -4
  18. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  19. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +14 -5
  20. data/lib/rubocop/cop/layout/end_alignment.rb +2 -2
  21. data/lib/rubocop/cop/layout/indentation_width.rb +13 -1
  22. data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -1
  23. data/lib/rubocop/cop/layout/space_before_brackets.rb +1 -1
  24. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +1 -11
  25. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  26. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +1 -10
  27. data/lib/rubocop/cop/lint/circular_argument_reference.rb +1 -3
  28. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +1 -1
  29. data/lib/rubocop/cop/lint/constant_reassignment.rb +36 -4
  30. data/lib/rubocop/cop/lint/constant_resolution.rb +5 -5
  31. data/lib/rubocop/cop/lint/debugger.rb +0 -1
  32. data/lib/rubocop/cop/lint/deprecated_constants.rb +2 -8
  33. data/lib/rubocop/cop/lint/empty_block.rb +3 -3
  34. data/lib/rubocop/cop/lint/ensure_return.rb +19 -1
  35. data/lib/rubocop/cop/lint/erb_new_arguments.rb +4 -2
  36. data/lib/rubocop/cop/lint/float_comparison.rb +1 -0
  37. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +5 -1
  38. data/lib/rubocop/cop/lint/interpolation_check.rb +18 -3
  39. data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +1 -1
  40. data/lib/rubocop/cop/lint/literal_assignment_in_condition.rb +11 -1
  41. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +8 -11
  42. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +5 -5
  43. data/lib/rubocop/cop/lint/multiple_comparison.rb +2 -2
  44. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +16 -0
  45. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  46. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +1 -1
  47. data/lib/rubocop/cop/lint/number_conversion.rb +18 -9
  48. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +1 -1
  49. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +3 -0
  50. data/lib/rubocop/cop/lint/ordered_magic_comments.rb +7 -7
  51. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
  52. data/lib/rubocop/cop/lint/raise_exception.rb +1 -1
  53. data/lib/rubocop/cop/lint/rand_one.rb +1 -1
  54. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +4 -1
  55. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +6 -3
  56. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +15 -4
  57. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +14 -7
  58. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +4 -0
  59. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +10 -3
  60. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -1
  61. data/lib/rubocop/cop/lint/redundant_with_object.rb +5 -0
  62. data/lib/rubocop/cop/lint/refinement_import_methods.rb +8 -1
  63. data/lib/rubocop/cop/lint/regexp_as_condition.rb +9 -1
  64. data/lib/rubocop/cop/lint/require_parentheses.rb +13 -4
  65. data/lib/rubocop/cop/lint/require_range_parentheses.rb +2 -1
  66. data/lib/rubocop/cop/lint/require_relative_self_path.rb +6 -6
  67. data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
  68. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -0
  69. data/lib/rubocop/cop/lint/safe_navigation_with_empty.rb +1 -1
  70. data/lib/rubocop/cop/lint/script_permission.rb +5 -1
  71. data/lib/rubocop/cop/lint/self_assignment.rb +24 -1
  72. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +1 -1
  73. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -1
  74. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +14 -0
  75. data/lib/rubocop/cop/lint/shared_mutable_default.rb +3 -1
  76. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +12 -0
  77. data/lib/rubocop/cop/lint/symbol_conversion.rb +21 -4
  78. data/lib/rubocop/cop/lint/to_enum_arguments.rb +28 -1
  79. data/lib/rubocop/cop/lint/top_level_return_with_argument.rb +1 -1
  80. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +4 -1
  81. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +4 -2
  82. data/lib/rubocop/cop/lint/unreachable_code.rb +2 -2
  83. data/lib/rubocop/cop/lint/useless_assignment.rb +10 -5
  84. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +8 -4
  85. data/lib/rubocop/cop/lint/useless_setter_call.rb +4 -1
  86. data/lib/rubocop/cop/lint/useless_times.rb +22 -1
  87. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  88. data/lib/rubocop/cop/metrics/collection_literal_length.rb +1 -1
  89. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  90. data/lib/rubocop/cop/metrics/utils/iterating_block.rb +1 -1
  91. data/lib/rubocop/cop/mixin/project_index_help.rb +48 -0
  92. data/lib/rubocop/cop/mixin.rb +1 -0
  93. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +1 -1
  94. data/lib/rubocop/cop/naming/predicate_method.rb +2 -2
  95. data/lib/rubocop/cop/naming/predicate_prefix.rb +1 -1
  96. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  97. data/lib/rubocop/cop/registry.rb +28 -6
  98. data/lib/rubocop/cop/security/io_methods.rb +1 -1
  99. data/lib/rubocop/cop/style/alias.rb +11 -2
  100. data/lib/rubocop/cop/style/and_or.rb +1 -1
  101. data/lib/rubocop/cop/style/array_first_last.rb +12 -1
  102. data/lib/rubocop/cop/style/array_intersect.rb +4 -0
  103. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +3 -0
  104. data/lib/rubocop/cop/style/block_delimiters.rb +16 -2
  105. data/lib/rubocop/cop/style/case_equality.rb +14 -2
  106. data/lib/rubocop/cop/style/character_literal.rb +2 -2
  107. data/lib/rubocop/cop/style/class_and_module_children.rb +8 -0
  108. data/lib/rubocop/cop/style/class_equality_comparison.rb +21 -13
  109. data/lib/rubocop/cop/style/class_methods_definitions.rb +11 -5
  110. data/lib/rubocop/cop/style/colon_method_call.rb +13 -6
  111. data/lib/rubocop/cop/style/combinable_loops.rb +5 -0
  112. data/lib/rubocop/cop/style/comparable_clamp.rb +12 -1
  113. data/lib/rubocop/cop/style/concat_array_literals.rb +5 -1
  114. data/lib/rubocop/cop/style/conditional_assignment.rb +6 -1
  115. data/lib/rubocop/cop/style/constant_visibility.rb +4 -1
  116. data/lib/rubocop/cop/style/date_time.rb +2 -2
  117. data/lib/rubocop/cop/style/dig_chain.rb +5 -0
  118. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +1 -1
  119. data/lib/rubocop/cop/style/fetch_env_var.rb +1 -1
  120. data/lib/rubocop/cop/style/file_write.rb +21 -16
  121. data/lib/rubocop/cop/style/format_string.rb +4 -3
  122. data/lib/rubocop/cop/style/hash_conversion.rb +1 -1
  123. data/lib/rubocop/cop/style/hash_slice.rb +16 -0
  124. data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -1
  125. data/lib/rubocop/cop/style/magic_comment_format.rb +1 -1
  126. data/lib/rubocop/cop/style/min_max_comparison.rb +1 -1
  127. data/lib/rubocop/cop/style/mutable_constant.rb +105 -11
  128. data/lib/rubocop/cop/style/parallel_assignment.rb +8 -1
  129. data/lib/rubocop/cop/style/redundant_array_constructor.rb +2 -2
  130. data/lib/rubocop/cop/style/redundant_constant_base.rb +5 -5
  131. data/lib/rubocop/cop/style/redundant_format.rb +1 -0
  132. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +2 -2
  133. data/lib/rubocop/cop/style/regexp_literal.rb +2 -2
  134. data/lib/rubocop/cop/style/rescue_modifier.rb +3 -3
  135. data/lib/rubocop/cop/style/self_assignment.rb +1 -1
  136. data/lib/rubocop/cop/style/semicolon.rb +16 -1
  137. data/lib/rubocop/cop/style/struct_inheritance.rb +13 -0
  138. data/lib/rubocop/cop/style/top_level_method_definition.rb +2 -2
  139. data/lib/rubocop/cop/style/unless_logical_operators.rb +3 -3
  140. data/lib/rubocop/cop/style/while_until_do.rb +7 -0
  141. data/lib/rubocop/cop/style/word_array.rb +1 -0
  142. data/lib/rubocop/cop/style/yoda_condition.rb +1 -1
  143. data/lib/rubocop/cop/style/zero_length_predicate.rb +6 -3
  144. data/lib/rubocop/file_patterns.rb +9 -1
  145. data/lib/rubocop/formatter/disabled_config_formatter.rb +14 -7
  146. data/lib/rubocop/options.rb +18 -0
  147. data/lib/rubocop/project_index_loader.rb +66 -0
  148. data/lib/rubocop/runner.rb +47 -3
  149. data/lib/rubocop/server/core.rb +6 -0
  150. data/lib/rubocop/version.rb +20 -2
  151. data/lib/rubocop.rb +1 -0
  152. metadata +5 -3
@@ -79,6 +79,11 @@ module RuboCop
79
79
  corrector.replace(range, replacement)
80
80
 
81
81
  comments_in_range(node).reverse_each do |comment|
82
+ # Only relocate comments that the replacement destroys. A trailing
83
+ # comment after the chain survives in place, so moving it would
84
+ # duplicate it (and splitting the line drops the indentation).
85
+ next if comment.source_range.begin_pos >= range.end_pos
86
+
82
87
  corrector.insert_before(node, "#{comment.source}\n")
83
88
  end
84
89
  end
@@ -6,7 +6,7 @@ module RuboCop
6
6
  module Cop
7
7
  module Style
8
8
  # Detects comments to enable/disable RuboCop.
9
- # This is useful if want to make sure that every RuboCop error gets fixed
9
+ # This is useful if you want to make sure that every RuboCop error gets fixed
10
10
  # and not quickly disabled with a comment.
11
11
  #
12
12
  # Specific cops can be allowed with the `AllowedCops` configuration. Note that
@@ -70,7 +70,7 @@ module RuboCop
70
70
 
71
71
  def allowed_var?(node)
72
72
  env_key_node = node.children.last
73
- env_key_node.str_type? && cop_config['AllowedVars'].include?(env_key_node.value)
73
+ env_key_node.str_type? && cop_config['AllowedVariables'].include?(env_key_node.value)
74
74
  end
75
75
 
76
76
  def used_as_flag?(node)
@@ -104,28 +104,33 @@ module RuboCop
104
104
 
105
105
  def replacement(mode, filename, content, write_node)
106
106
  replacement = "#{write_method(mode)}(#{filename.source}, #{content.source})"
107
+ heredocs = removed_heredocs(filename, content, write_node)
108
+ return replacement if heredocs.empty?
107
109
 
108
- if heredoc?(write_node)
109
- first_argument = write_node.body.first_argument
110
+ [replacement, *heredocs.map { |heredoc| heredoc_range(heredoc).source }].join("\n")
111
+ end
110
112
 
111
- <<~REPLACEMENT.chomp
112
- #{replacement}
113
- #{heredoc_range(first_argument).source}
114
- REPLACEMENT
115
- else
116
- replacement
117
- end
113
+ # Heredocs opened in the arguments keep working in the replacement, but their
114
+ # bodies are lost when they lie within the replaced range, so they need to be
115
+ # restored after the replacement.
116
+ def removed_heredocs(filename, content, write_node)
117
+ [filename, content].flat_map { |argument| find_heredocs(argument) }
118
+ .select { |heredoc| removed?(heredoc, write_node) }
119
+ .sort_by { |heredoc| heredoc.loc.heredoc_body.begin_pos }
118
120
  end
119
121
 
120
- def heredoc?(write_node)
121
- write_node.block_type? && (first_argument = write_node.body.first_argument) &&
122
- first_argument.respond_to?(:heredoc?) && first_argument.heredoc?
122
+ def heredoc_range(heredoc)
123
+ range_between(heredoc.loc.heredoc_body.begin_pos, heredoc.loc.heredoc_end.end_pos)
123
124
  end
124
125
 
125
- def heredoc_range(first_argument)
126
- range_between(
127
- first_argument.loc.heredoc_body.begin_pos, first_argument.loc.heredoc_end.end_pos
128
- )
126
+ def find_heredocs(node)
127
+ [node, *node.each_descendant(:any_str)].select do |child|
128
+ child.respond_to?(:heredoc?) && child.heredoc?
129
+ end
130
+ end
131
+
132
+ def removed?(heredoc, write_node)
133
+ heredoc.loc.heredoc_end.end_pos <= write_node.source_range.end_pos
129
134
  end
130
135
  end
131
136
  end
@@ -11,9 +11,10 @@ module RuboCop
11
11
  # if the first argument is a string literal and if the second
12
12
  # argument is an array literal.
13
13
  #
14
- # Autocorrection will be applied when using argument is a literal or known built-in conversion
15
- # methods such as `to_d`, `to_f`, `to_h`, `to_i`, `to_r`, `to_s`, and `to_sym` on variables,
16
- # provided that their return value is not an array. For example, when using `to_s`,
14
+ # Autocorrection will be applied when the argument is a literal or uses a known
15
+ # built-in conversion method such as `to_d`, `to_f`, `to_h`, `to_i`, `to_r`, `to_s`,
16
+ # and `to_sym` on variables, provided that their return value is not an array.
17
+ # For example, when using `to_s`,
17
18
  # `'%s' % [1, 2, 3].to_s` can be autocorrected without any incompatibility:
18
19
  #
19
20
  # [source,ruby]
@@ -73,7 +73,7 @@ module RuboCop
73
73
  first_argument = node.first_argument
74
74
  if first_argument.hash_type?
75
75
  register_offense_for_hash(node, first_argument)
76
- elsif first_argument.splat_type?
76
+ elsif first_argument.type?(:splat, :forwarded_restarg)
77
77
  add_offense(node, message: MSG_SPLAT) unless allowed_splat_argument?
78
78
  elsif use_zip_method_without_argument?(first_argument)
79
79
  register_offense_for_zip_method(node, first_argument)
@@ -19,6 +19,22 @@ module RuboCop
19
19
  # This cop is unsafe because it cannot be guaranteed that the receiver
20
20
  # is a `Hash` or responds to the replacement method.
21
21
  #
22
+ # Additionally, the replacement may change the order of the resulting
23
+ # hash: `Hash#slice` returns entries in the order the keys are given,
24
+ # whereas `select`, `filter`, and `reject` preserve the entry order of
25
+ # the receiver.
26
+ #
27
+ # For example:
28
+ #
29
+ # [source,ruby]
30
+ # ----
31
+ # hash = {foo: 1, bar: 2, baz: 3}
32
+ # keys = %i[baz foo]
33
+ #
34
+ # hash.select { |k, _v| keys.include?(k) } # => {foo: 1, baz: 3}
35
+ # hash.slice(*keys) # => {baz: 3, foo: 1}
36
+ # ----
37
+ #
22
38
  # @example
23
39
  #
24
40
  # # bad
@@ -86,7 +86,7 @@ module RuboCop
86
86
  MSG_USE_NORMAL = 'Modifier form of `%<keyword>s` makes the line too long.'
87
87
 
88
88
  def self.autocorrect_incompatible_with
89
- [Style::SoleNestedConditional]
89
+ [Style::Next, Style::SoleNestedConditional]
90
90
  end
91
91
 
92
92
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # Required capitalization can be set with the `DirectiveCapitalization` and
11
11
  # `ValueCapitalization` configuration keys.
12
12
  #
13
- # NOTE: If one of these configuration is set to nil, any capitalization is allowed.
13
+ # NOTE: If one of these configurations is set to nil, any capitalization is allowed.
14
14
  #
15
15
  # @example EnforcedStyle: snake_case (default)
16
16
  # # The `snake_case` style will enforce that the frozen string literal
@@ -6,7 +6,7 @@ module RuboCop
6
6
  # Enforces the use of `max` or `min` instead of comparison for greater or less.
7
7
  #
8
8
  # NOTE: It can be used if you want to present limit or threshold in Ruby 2.7+.
9
- # That it is slow though. So autocorrection will apply generic `max` or `min`:
9
+ # It is slow though. So autocorrection will apply generic `max` or `min`:
10
10
  #
11
11
  # [source,ruby]
12
12
  # ----
@@ -6,6 +6,14 @@ module RuboCop
6
6
  # Checks whether some constant value isn't a
7
7
  # mutable literal (e.g. array or hash).
8
8
  #
9
+ # When the `Recursive` option is enabled, mutable literals nested inside
10
+ # arrays and hashes are also frozen, so an offense on the outermost
11
+ # unfrozen literal will autocorrect every nested mutable literal as well.
12
+ # When the outer literal already has `.freeze` appended, the cop descends
13
+ # into it and reports each outermost unfrozen literal underneath. The
14
+ # option is disabled by default to preserve existing behavior; opt in to
15
+ # get strict nested freezing.
16
+ #
9
17
  # Strict mode can be used to freeze all constants, rather than
10
18
  # just literals.
11
19
  # Strict mode is considered an experimental feature. It has not been
@@ -49,6 +57,17 @@ module RuboCop
49
57
  # CONST = Something.new
50
58
  #
51
59
  #
60
+ # @example Recursive: false (default)
61
+ # # good - only the outer container needs to be frozen
62
+ # CONST = [{ a: [], b: 'foo' }].freeze
63
+ #
64
+ # @example Recursive: true
65
+ # # bad - nested mutable literals must be frozen too
66
+ # CONST = [{ a: [], b: 'foo' }].freeze
67
+ #
68
+ # # good
69
+ # CONST = [{ a: [].freeze, b: 'foo'.freeze }.freeze].freeze
70
+ #
52
71
  # @example EnforcedStyle: strict
53
72
  # # bad
54
73
  # CONST = Something.new
@@ -138,10 +157,30 @@ module RuboCop
138
157
  private
139
158
 
140
159
  def on_assignment(value)
141
- if style == :strict
142
- strict_check(value)
160
+ nodes = mutable_nodes(value) do |node|
161
+ if style == :strict
162
+ strict_check(node)
163
+ else
164
+ literal_check(node)
165
+ end
166
+ end
167
+
168
+ nodes.each do |node|
169
+ add_offense(node) { |corrector| autocorrect(corrector, node) }
170
+ end
171
+ end
172
+
173
+ def mutable_nodes(value, &block)
174
+ if recursive? && explicitly_frozen_literal?(value)
175
+ literal_children(value.receiver).flat_map { |c| mutable_nodes(c, &block) }
143
176
  else
144
- check(value)
177
+ node_offending = yield(value)
178
+
179
+ if node_offending
180
+ [value]
181
+ else
182
+ []
183
+ end
145
184
  end
146
185
  end
147
186
 
@@ -151,18 +190,20 @@ module RuboCop
151
190
  return if frozen_string_literal?(value)
152
191
  return if shareable_constant_value?(value)
153
192
 
154
- add_offense(value) { |corrector| autocorrect(corrector, value) }
193
+ true
155
194
  end
156
195
 
157
- def check(value)
158
- range_enclosed_in_parentheses = range_enclosed_in_parentheses?(value)
159
- return unless mutable_literal?(value) ||
160
- (target_ruby_version <= 2.7 && range_enclosed_in_parentheses)
161
-
196
+ def literal_check(value)
197
+ return unless mutable_or_unfrozen_range?(value)
162
198
  return if frozen_string_literal?(value)
163
199
  return if shareable_constant_value?(value)
164
200
 
165
- add_offense(value) { |corrector| autocorrect(corrector, value) }
201
+ true
202
+ end
203
+
204
+ def mutable_or_unfrozen_range?(value)
205
+ mutable_literal?(value) ||
206
+ (target_ruby_version <= 2.7 && range_enclosed_in_parentheses?(value))
166
207
  end
167
208
 
168
209
  def autocorrect(corrector, node)
@@ -171,13 +212,66 @@ module RuboCop
171
212
  splat_value = splat_value(node)
172
213
  if splat_value
173
214
  correct_splat_expansion(corrector, expr, splat_value)
174
- elsif node.array_type? && !node.bracketed?
215
+ corrector.insert_after(expr, '.freeze')
216
+ return
217
+ end
218
+
219
+ if node.array_type? && !node.bracketed?
175
220
  corrector.wrap(expr, '[', ']')
176
221
  elsif requires_parentheses?(node)
177
222
  corrector.wrap(expr, '(', ')')
178
223
  end
179
224
 
180
225
  corrector.insert_after(expr, '.freeze')
226
+
227
+ freeze_nested_literals(corrector, node) if recursive?
228
+ end
229
+
230
+ # Recursively freezes every nested mutable literal inside an array or
231
+ # hash literal. Already-frozen subtrees are not re-frozen, but their
232
+ # children are still inspected for unfrozen literals deeper down.
233
+ def freeze_nested_literals(corrector, node)
234
+ literal_children(node).each do |child|
235
+ if explicitly_frozen_literal?(child)
236
+ freeze_nested_literals(corrector, child.receiver)
237
+ elsif freezable_nested_literal?(child)
238
+ autocorrect(corrector, child)
239
+ end
240
+ end
241
+ end
242
+
243
+ def freezable_nested_literal?(node)
244
+ return false if frozen_string_literal?(node)
245
+ return false if shareable_constant_value?(node)
246
+
247
+ mutable_literal?(node)
248
+ end
249
+
250
+ # Returns the child literals of an array or hash node that may
251
+ # themselves need freezing. For hashes, both keys and values are
252
+ # included. Percent-literal arrays (e.g. `%w(a b)`) are skipped because
253
+ # `.freeze` cannot be appended to their contents.
254
+ def literal_children(node)
255
+ case node.type
256
+ when :array
257
+ return [] if node.percent_literal?
258
+
259
+ node.children
260
+ when :hash
261
+ node.children.flat_map { |child| child.pair_type? ? child.children : [] }
262
+ else
263
+ []
264
+ end
265
+ end
266
+
267
+ def explicitly_frozen_literal?(node)
268
+ return false unless node.send_type? && node.method?(:freeze)
269
+
270
+ node.receiver && mutable_literal?(node.receiver)
271
+ end
272
+
273
+ def recursive?
274
+ cop_config.fetch('Recursive', false)
181
275
  end
182
276
 
183
277
  def mutable_literal?(value)
@@ -38,7 +38,7 @@ module RuboCop
38
38
  rhs_elements = Array(rhs).compact # edge case for one constant
39
39
 
40
40
  return if allowed_lhs?(node.assignments) || allowed_rhs?(rhs) ||
41
- allowed_masign?(node.assignments, rhs_elements)
41
+ allowed_masign?(node.assignments, rhs_elements) || contains_heredoc?(rhs)
42
42
 
43
43
  range = node.source_range.begin.join(rhs.source_range.end)
44
44
 
@@ -77,6 +77,13 @@ module RuboCop
77
77
  !node.array_type? || elements.any?(&:splat_type?)
78
78
  end
79
79
 
80
+ # Autocorrection splits the assignment into single assignments on
81
+ # consecutive lines, which would put following assignments into the
82
+ # heredoc body unless the heredoc bodies were moved along.
83
+ def contains_heredoc?(node)
84
+ node.each_descendant(:any_str).any?(&:heredoc?)
85
+ end
86
+
80
87
  def assignment_corrector(node, rhs, order)
81
88
  if node.parent&.rescue_type?
82
89
  _assignment, modifier = *node.parent
@@ -3,8 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for the instantiation of array using redundant `Array` constructor.
7
- # Autocorrect replaces to array literal which is the simplest and fastest.
6
+ # Checks for the instantiation of an array using a redundant `Array` constructor.
7
+ # Autocorrect replaces it with an array literal which is the simplest and fastest.
8
8
  #
9
9
  # @example
10
10
  #
@@ -3,16 +3,16 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Avoid redundant `::` prefix on constant.
6
+ # Avoid redundant `::` prefix on a constant.
7
7
  #
8
- # How Ruby searches constant is a bit complicated, and it can often be difficult to
8
+ # How Ruby searches constants is a bit complicated, and it can often be difficult to
9
9
  # understand from the code whether the `::` is intended or not. Where `Module.nesting`
10
10
  # is empty, there is no need to prepend `::`, so it would be nice to consistently
11
11
  # avoid such meaningless `::` prefix to avoid confusion.
12
12
  #
13
- # NOTE: This cop is disabled if `Lint/ConstantResolution` cop is enabled to prevent
14
- # conflicting rules. Because it respects user configurations that want to enable
15
- # `Lint/ConstantResolution` cop which is disabled by default.
13
+ # NOTE: This cop is disabled if `Lint/ConstantResolution` cop is enabled,
14
+ # to prevent conflicting rules. This is because it respects user configurations
15
+ # that want to enable `Lint/ConstantResolution` cop which is disabled by default.
16
16
  #
17
17
  # @example
18
18
  # # bad
@@ -108,6 +108,7 @@ module RuboCop
108
108
 
109
109
  def detect_unnecessary_fields(node)
110
110
  return unless node.first_argument&.str_type?
111
+ return if node.first_argument.heredoc?
111
112
 
112
113
  string = node.first_argument.value
113
114
  arguments = node.arguments[1..]
@@ -3,8 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for the instantiation of regexp using redundant `Regexp.new` or `Regexp.compile`.
7
- # Autocorrect replaces to regexp literal which is the simplest and fastest.
6
+ # Checks for the instantiation of a regexp using a redundant `Regexp.new` or `Regexp.compile`.
7
+ # Autocorrect replaces it with a regexp literal which is the simplest and fastest.
8
8
  #
9
9
  # @example
10
10
  #
@@ -5,8 +5,8 @@ module RuboCop
5
5
  module Style
6
6
  # Enforces using `//` or `%r` around regular expressions.
7
7
  #
8
- # NOTE: The following `%r` cases using a regexp starts with a blank or `=`
9
- # as a method argument allowed to prevent syntax errors.
8
+ # NOTE: The following `%r` cases using a regexp that starts with a blank or `=`
9
+ # as a method argument are allowed to prevent syntax errors.
10
10
  #
11
11
  # [source,ruby]
12
12
  # ----
@@ -3,17 +3,17 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for uses of `rescue` in its modifier form is added for following
6
+ # Checks for uses of `rescue` in its modifier form. It is added for the following
7
7
  # reasons:
8
8
  #
9
9
  # * The syntax of modifier form `rescue` can be misleading because it
10
10
  # might lead us to believe that `rescue` handles the given exception
11
- # but it actually rescue all exceptions to return the given rescue
11
+ # but it actually rescues all exceptions to return the given rescue
12
12
  # block. In this case, value returned by handle_error or
13
13
  # SomeException.
14
14
  #
15
15
  # * Modifier form `rescue` would rescue all the exceptions. It would
16
- # silently skip all exception or errors and handle the error.
16
+ # silently skip all exceptions or errors and handle the error.
17
17
  # Example: If `NoMethodError` is raised, modifier form rescue would
18
18
  # handle the exception.
19
19
  #
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Enforces the use the shorthand for self-assignment.
6
+ # Enforces the use of the shorthand for self-assignment.
7
7
  #
8
8
  # @example
9
9
  #
@@ -128,7 +128,7 @@ module RuboCop
128
128
 
129
129
  add_offense(range) do |corrector|
130
130
  if after_expression
131
- corrector.replace(range, "\n")
131
+ replace_semicolon_with_line_break(corrector, range)
132
132
  else
133
133
  # Prevents becoming one range instance with subsequent line when endless range
134
134
  # without parentheses.
@@ -148,6 +148,21 @@ module RuboCop
148
148
  end
149
149
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
150
150
 
151
+ def replace_semicolon_with_line_break(corrector, range)
152
+ # Replacing the semicolon with a newline would move the rest of the
153
+ # line into the body of a heredoc opened earlier on that line.
154
+ return if heredoc_opened_before_semicolon?(range)
155
+
156
+ corrector.replace(range, "\n")
157
+ end
158
+
159
+ def heredoc_opened_before_semicolon?(semicolon_range)
160
+ processed_source.ast.each_descendant(:any_str).select(&:heredoc?).any? do |heredoc|
161
+ heredoc.first_line == semicolon_range.line &&
162
+ heredoc.source_range.end_pos <= semicolon_range.begin_pos
163
+ end
164
+ end
165
+
151
166
  def expressions_per_line(exprs)
152
167
  # create a map matching lines to the number of expressions on them
153
168
  exprs_lines = exprs.map(&:last_line)
@@ -61,6 +61,8 @@ module RuboCop
61
61
  corrector.remove(range_with_surrounding_space(parent.loc.end, newlines: false))
62
62
  elsif (class_node = parent.parent).body.nil?
63
63
  corrector.remove(range_for_empty_class_body(class_node, parent))
64
+ elsif unparenthesized_struct_new?(parent)
65
+ wrap_unparenthesized_call_with_do(corrector, parent)
64
66
  else
65
67
  corrector.insert_after(parent, ' do')
66
68
  end
@@ -73,6 +75,17 @@ module RuboCop
73
75
  range_by_whole_lines(class_node.loc.end, include_final_newline: true)
74
76
  end
75
77
  end
78
+
79
+ def unparenthesized_struct_new?(parent)
80
+ parent.send_type? && parent.arguments.any? && !parent.parenthesized?
81
+ end
82
+
83
+ def wrap_unparenthesized_call_with_do(corrector, parent)
84
+ args_source = parent.arguments.map(&:source).join(', ')
85
+ range = parent.loc.selector.end.join(parent.source_range.end)
86
+
87
+ corrector.replace(range, "(#{args_source}) do")
88
+ end
76
89
  end
77
90
  end
78
91
  end
@@ -3,11 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Newcomers to ruby applications may write top-level methods,
6
+ # Newcomers to Ruby applications may write top-level methods,
7
7
  # when ideally they should be organized in appropriate classes or modules.
8
8
  # This cop looks for definitions of top-level methods and warns about them.
9
9
  #
10
- # However for ruby scripts it is perfectly fine to use top-level methods.
10
+ # However, for Ruby scripts it is perfectly fine to use top-level methods.
11
11
  # Hence this cop is disabled by default.
12
12
  #
13
13
  # @example
@@ -14,11 +14,11 @@ module RuboCop
14
14
  #
15
15
  # `forbid_mixed_logical_operators` style forbids the use of more than one type
16
16
  # of logical operators. This makes the `unless` condition easier to read
17
- # because either all conditions need to be met or any condition need to be met
17
+ # because either all conditions need to be met or any condition needs to be met
18
18
  # in order for the expression to be truthy or falsey.
19
19
  #
20
- # `forbid_logical_operators` style forbids any use of logical operator.
21
- # This makes it even more easy to read the `unless` condition as
20
+ # `forbid_logical_operators` style forbids any use of logical operators.
21
+ # This makes it even easier to read the `unless` condition as
22
22
  # there is only one condition in the expression.
23
23
  #
24
24
  # @example EnforcedStyle: forbid_mixed_logical_operators (default)
@@ -33,6 +33,7 @@ module RuboCop
33
33
 
34
34
  def on_while(node)
35
35
  return unless node.multiline? && node.do?
36
+ return if same_line_body?(node)
36
37
 
37
38
  add_offense(node.loc.begin, message: format(MSG, keyword: node.keyword)) do |corrector|
38
39
  do_range = node.condition.source_range.end.join(node.loc.begin)
@@ -41,6 +42,12 @@ module RuboCop
41
42
  end
42
43
  end
43
44
  alias on_until on_while
45
+
46
+ private
47
+
48
+ def same_line_body?(node)
49
+ node.body && same_line?(node.loc.begin, node.body)
50
+ end
44
51
  end
45
52
  end
46
53
  end
@@ -93,6 +93,7 @@ module RuboCop
93
93
 
94
94
  def on_array(node)
95
95
  if bracketed_array_of?(:str, node)
96
+ return if node.values.any?(&:heredoc?)
96
97
  return if complex_content?(node.values)
97
98
  return if within_matrix_of_complex_content?(node)
98
99
 
@@ -147,7 +147,7 @@ module RuboCop
147
147
  end
148
148
 
149
149
  def constant_portion?(node)
150
- node.literal? || node.const_type?
150
+ node.recursive_literal? || node.const_type?
151
151
  end
152
152
 
153
153
  def actual_code_range(node)
@@ -9,8 +9,10 @@ module RuboCop
9
9
  # `receiver.length < 1` and `receiver.size == 0` that can be
10
10
  # replaced by `receiver.empty?` and `!receiver.empty?`.
11
11
  #
12
- # NOTE: `File`, `Tempfile`, and `StringIO` do not have `empty?`
13
- # so allow `size == 0` and `size.zero?`.
12
+ # NOTE: `File`, `Tempfile`, `StringIO`, and `File::Stat` do not have `empty?`
13
+ # so allow `size == 0` and `size.zero?`. Note that when a `File::Stat` object
14
+ # is stored in a variable (e.g. `stat = File.stat(path); stat.size.zero?`),
15
+ # the cop cannot detect the type and may still register a false positive.
14
16
  #
15
17
  # @safety
16
18
  # This cop is unsafe because it cannot be guaranteed that the receiver
@@ -146,7 +148,8 @@ module RuboCop
146
148
  # @!method non_polymorphic_collection?(node)
147
149
  def_node_matcher :non_polymorphic_collection?, <<~PATTERN
148
150
  {(send (send (send (const {nil? cbase} :File) :stat _) ...) ...)
149
- (send (send (send (const {nil? cbase} {:File :Tempfile :StringIO}) {:new :open} ...) ...) ...)}
151
+ (send (send (send (const {nil? cbase} {:File :Tempfile :StringIO}) {:new :open} ...) ...) ...)
152
+ (send (send (send (const (const {nil? cbase} :File) :Stat) :new ...) ...) ...)}
150
153
  PATTERN
151
154
  end
152
155
  end
@@ -21,11 +21,19 @@ module RuboCop
21
21
  def initialize(patterns)
22
22
  @strings = Set.new
23
23
  @patterns = []
24
+ @match_cache = {}
24
25
  partition_patterns(patterns)
25
26
  end
26
27
 
27
28
  def match?(path)
28
- @strings.include?(path) || @patterns.any? { |pattern| PathUtil.match_path?(pattern, path) }
29
+ # `FilePatterns.from` memoizes one instance per pattern array (by identity),
30
+ # so this cache is shared across every cop using the same Include/Exclude
31
+ # list. Patterns are immutable within a run, so caching by path is safe.
32
+ cached = @match_cache[path]
33
+ return cached unless cached.nil?
34
+
35
+ @match_cache[path] =
36
+ @strings.include?(path) || @patterns.any? { |pattern| PathUtil.match_path?(pattern, path) }
29
37
  end
30
38
 
31
39
  private