rubocop 1.88.0 → 1.89.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 (197) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +72 -12
  3. data/lib/rubocop/comment_config.rb +35 -19
  4. data/lib/rubocop/config.rb +53 -0
  5. data/lib/rubocop/config_validator.rb +38 -1
  6. data/lib/rubocop/cop/badge.rb +8 -0
  7. data/lib/rubocop/cop/base.rb +19 -12
  8. data/lib/rubocop/cop/bundler/gem_comment.rb +3 -1
  9. data/lib/rubocop/cop/bundler.rb +19 -0
  10. data/lib/rubocop/cop/commissioner.rb +13 -11
  11. data/lib/rubocop/cop/corrector.rb +1 -1
  12. data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
  13. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  14. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -1
  15. data/lib/rubocop/cop/correctors/ordered_gem_corrector.rb +8 -1
  16. data/lib/rubocop/cop/gemspec/development_dependencies.rb +1 -1
  17. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +12 -2
  18. data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -1
  19. data/lib/rubocop/cop/gemspec.rb +22 -0
  20. data/lib/rubocop/cop/generator/registration_injector.rb +188 -0
  21. data/lib/rubocop/cop/generator.rb +4 -0
  22. data/lib/rubocop/cop/layout/block_alignment.rb +17 -0
  23. data/lib/rubocop/cop/layout/class_structure.rb +14 -4
  24. data/lib/rubocop/cop/layout/comment_indentation.rb +9 -2
  25. data/lib/rubocop/cop/layout/condition_position.rb +13 -3
  26. data/lib/rubocop/cop/layout/else_alignment.rb +1 -14
  27. data/lib/rubocop/cop/layout/empty_comment.rb +8 -10
  28. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +14 -1
  29. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
  30. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +13 -14
  31. data/lib/rubocop/cop/layout/indentation_width.rb +31 -1
  32. data/lib/rubocop/cop/layout/leading_comment_space.rb +27 -0
  33. data/lib/rubocop/cop/layout/line_length.rb +95 -17
  34. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +18 -3
  35. data/lib/rubocop/cop/layout/redundant_line_break.rb +77 -0
  36. data/lib/rubocop/cop/layout/space_around_operators.rb +6 -2
  37. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +33 -34
  38. data/lib/rubocop/cop/layout.rb +114 -0
  39. data/lib/rubocop/cop/lazy_loader.rb +41 -0
  40. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +9 -7
  41. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +71 -0
  42. data/lib/rubocop/cop/lint/assignment_in_condition.rb +13 -1
  43. data/lib/rubocop/cop/lint/constant_reassignment.rb +1 -10
  44. data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
  45. data/lib/rubocop/cop/lint/debugger.rb +13 -1
  46. data/lib/rubocop/cop/lint/deprecated_reference.rb +185 -0
  47. data/lib/rubocop/cop/lint/duplicate_methods.rb +202 -22
  48. data/lib/rubocop/cop/lint/inherit_exception.rb +53 -7
  49. data/lib/rubocop/cop/lint/literal_as_condition.rb +5 -0
  50. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  51. data/lib/rubocop/cop/lint/missing_super.rb +31 -1
  52. data/lib/rubocop/cop/lint/name_typo.rb +223 -0
  53. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +7 -0
  54. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +0 -4
  55. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +7 -0
  56. data/lib/rubocop/cop/lint/to_enum_arguments.rb +44 -2
  57. data/lib/rubocop/cop/lint/to_json.rb +8 -1
  58. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +32 -8
  59. data/lib/rubocop/cop/lint/unreachable_code.rb +9 -4
  60. data/lib/rubocop/cop/lint/unused_private_method.rb +157 -0
  61. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +10 -10
  62. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
  63. data/lib/rubocop/cop/lint/void.rb +19 -8
  64. data/lib/rubocop/cop/lint.rb +176 -0
  65. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  66. data/lib/rubocop/cop/metrics/perceived_complexity.rb +38 -7
  67. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  68. data/lib/rubocop/cop/metrics.rb +31 -0
  69. data/lib/rubocop/cop/migration/department_name.rb +0 -4
  70. data/lib/rubocop/cop/migration.rb +13 -0
  71. data/lib/rubocop/cop/mixin/allowed_methods.rb +5 -5
  72. data/lib/rubocop/cop/mixin/allowed_pattern.rb +5 -1
  73. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
  74. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +13 -5
  75. data/lib/rubocop/cop/mixin/forbidden_pattern.rb +5 -1
  76. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +23 -6
  77. data/lib/rubocop/cop/mixin/hash_subset.rb +8 -0
  78. data/lib/rubocop/cop/mixin/hash_transform_method.rb +4 -0
  79. data/lib/rubocop/cop/mixin/project_index_help.rb +140 -0
  80. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
  81. data/lib/rubocop/cop/mixin/statement_modifier.rb +43 -7
  82. data/lib/rubocop/cop/mixin.rb +78 -77
  83. data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -0
  84. data/lib/rubocop/cop/naming/file_name.rb +4 -3
  85. data/lib/rubocop/cop/naming/inclusive_language.rb +8 -2
  86. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +9 -0
  87. data/lib/rubocop/cop/naming/predicate_prefix.rb +33 -5
  88. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +9 -3
  89. data/lib/rubocop/cop/naming/variable_number.rb +3 -1
  90. data/lib/rubocop/cop/naming.rb +31 -0
  91. data/lib/rubocop/cop/offense.rb +10 -6
  92. data/lib/rubocop/cop/registry.rb +153 -43
  93. data/lib/rubocop/cop/security/io_methods.rb +1 -1
  94. data/lib/rubocop/cop/security/marshal_load.rb +1 -1
  95. data/lib/rubocop/cop/security.rb +19 -0
  96. data/lib/rubocop/cop/style/accessor_grouping.rb +11 -1
  97. data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -1
  98. data/lib/rubocop/cop/style/array_intersect.rb +24 -5
  99. data/lib/rubocop/cop/style/class_and_module_children.rb +78 -18
  100. data/lib/rubocop/cop/style/collection_compact.rb +1 -1
  101. data/lib/rubocop/cop/style/conditional_assignment.rb +22 -0
  102. data/lib/rubocop/cop/style/copyright.rb +6 -1
  103. data/lib/rubocop/cop/style/data_inheritance.rb +4 -0
  104. data/lib/rubocop/cop/style/def_with_parentheses.rb +6 -2
  105. data/lib/rubocop/cop/style/dir_empty.rb +4 -0
  106. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +41 -3
  107. data/lib/rubocop/cop/style/documentation.rb +47 -0
  108. data/lib/rubocop/cop/style/documentation_method.rb +5 -1
  109. data/lib/rubocop/cop/style/empty_case_condition.rb +12 -2
  110. data/lib/rubocop/cop/style/empty_class_definition.rb +8 -1
  111. data/lib/rubocop/cop/style/empty_heredoc.rb +4 -0
  112. data/lib/rubocop/cop/style/empty_literal.rb +7 -2
  113. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +30 -20
  114. data/lib/rubocop/cop/style/endless_method.rb +0 -10
  115. data/lib/rubocop/cop/style/env_home.rb +4 -0
  116. data/lib/rubocop/cop/style/even_odd.rb +11 -1
  117. data/lib/rubocop/cop/style/exact_regexp_match.rb +8 -1
  118. data/lib/rubocop/cop/style/file_null.rb +4 -2
  119. data/lib/rubocop/cop/style/format_string.rb +13 -1
  120. data/lib/rubocop/cop/style/hash_conversion.rb +14 -6
  121. data/lib/rubocop/cop/style/hash_lookup_method.rb +2 -6
  122. data/lib/rubocop/cop/style/hash_syntax.rb +2 -0
  123. data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -4
  124. data/lib/rubocop/cop/style/if_unless_modifier.rb +11 -52
  125. data/lib/rubocop/cop/style/if_with_semicolon.rb +9 -1
  126. data/lib/rubocop/cop/style/inline_comment.rb +1 -1
  127. data/lib/rubocop/cop/style/invertible_unless_condition.rb +25 -5
  128. data/lib/rubocop/cop/style/keyword_arguments_merging.rb +4 -0
  129. data/lib/rubocop/cop/style/keyword_parameters_order.rb +7 -3
  130. data/lib/rubocop/cop/style/lambda.rb +11 -1
  131. data/lib/rubocop/cop/style/lambda_call.rb +11 -0
  132. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +11 -0
  133. data/lib/rubocop/cop/style/map_into_array.rb +1 -1
  134. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +20 -3
  135. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -5
  136. data/lib/rubocop/cop/style/method_def_parentheses.rb +5 -1
  137. data/lib/rubocop/cop/style/min_max_comparison.rb +3 -0
  138. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +40 -7
  139. data/lib/rubocop/cop/style/mixin_usage.rb +1 -1
  140. data/lib/rubocop/cop/style/module_function.rb +4 -2
  141. data/lib/rubocop/cop/style/multiline_if_then.rb +7 -1
  142. data/lib/rubocop/cop/style/multiline_memoization.rb +7 -1
  143. data/lib/rubocop/cop/style/multiline_method_signature.rb +11 -4
  144. data/lib/rubocop/cop/style/multiple_comparison.rb +33 -22
  145. data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
  146. data/lib/rubocop/cop/style/negative_array_index.rb +3 -1
  147. data/lib/rubocop/cop/style/nil_lambda.rb +8 -0
  148. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  149. data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
  150. data/lib/rubocop/cop/style/option_hash.rb +1 -1
  151. data/lib/rubocop/cop/style/optional_arguments.rb +1 -0
  152. data/lib/rubocop/cop/style/parallel_assignment.rb +11 -2
  153. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  154. data/lib/rubocop/cop/style/perl_backrefs.rb +5 -3
  155. data/lib/rubocop/cop/style/redundant_assignment.rb +11 -0
  156. data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
  157. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +13 -1
  158. data/lib/rubocop/cop/style/redundant_exception.rb +6 -0
  159. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +9 -0
  160. data/lib/rubocop/cop/style/redundant_filter_chain.rb +1 -1
  161. data/lib/rubocop/cop/style/redundant_format.rb +40 -3
  162. data/lib/rubocop/cop/style/redundant_heredoc_delimiter_quotes.rb +6 -1
  163. data/lib/rubocop/cop/style/redundant_line_continuation.rb +80 -129
  164. data/lib/rubocop/cop/style/redundant_parentheses.rb +63 -63
  165. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -4
  166. data/lib/rubocop/cop/style/redundant_self.rb +9 -0
  167. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +23 -4
  168. data/lib/rubocop/cop/style/safe_navigation.rb +10 -0
  169. data/lib/rubocop/cop/style/semicolon.rb +4 -4
  170. data/lib/rubocop/cop/style/single_line_do_end_block.rb +17 -4
  171. data/lib/rubocop/cop/style/sole_nested_conditional.rb +20 -3
  172. data/lib/rubocop/cop/style/static_class.rb +21 -0
  173. data/lib/rubocop/cop/style/string_hash_keys.rb +1 -0
  174. data/lib/rubocop/cop/style/struct_inheritance.rb +3 -1
  175. data/lib/rubocop/cop/style/ternary_parentheses.rb +11 -0
  176. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +7 -8
  177. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  178. data/lib/rubocop/cop/style.rb +310 -0
  179. data/lib/rubocop/cop/team.rb +66 -7
  180. data/lib/rubocop/cop/variable_force/variable.rb +18 -6
  181. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  182. data/lib/rubocop/directive_comment.rb +2 -0
  183. data/lib/rubocop/feature_loader.rb +0 -6
  184. data/lib/rubocop/lsp/routes.rb +41 -3
  185. data/lib/rubocop/lsp/runtime.rb +4 -0
  186. data/lib/rubocop/lsp/server.rb +4 -0
  187. data/lib/rubocop/lsp/stdin_runner.rb +20 -0
  188. data/lib/rubocop/pending_cops_reporter.rb +1 -6
  189. data/lib/rubocop/project_index_loader.rb +16 -0
  190. data/lib/rubocop/result_cache.rb +6 -4
  191. data/lib/rubocop/rspec/shared_contexts.rb +14 -2
  192. data/lib/rubocop/runner.rb +100 -33
  193. data/lib/rubocop/server/cache.rb +10 -5
  194. data/lib/rubocop/target_ruby.rb +1 -1
  195. data/lib/rubocop/version.rb +1 -1
  196. data/lib/rubocop.rb +17 -626
  197. metadata +18 -3
@@ -16,11 +16,14 @@ module RuboCop
16
16
  # enumerator.each { |item| item >= 2 } #=> [2, 3]
17
17
  # ----
18
18
  #
19
- # NOTE: Return values in assignment method definitions such as `def foo=(arg)` are
20
- # detected because they are in a void context. However, autocorrection does not remove
21
- # the return value, as that would change behavior. In such cases, whether to remove
22
- # the return value or rename the method to something more appropriate should be left to
23
- # the user.
19
+ # NOTE: The last expression in an assignment method definition such as `def foo=(arg)`
20
+ # is not flagged. Ruby discards it (the method returns its argument), but the method can
21
+ # still be called directly and its return value relied upon, so flagging it would be a
22
+ # false positive for this lint.
23
+ #
24
+ # NOTE: A constant used in a void context is flagged but not autocorrected, since
25
+ # referencing a constant can trigger autoloading side effects (e.g. forcing a file to
26
+ # load before a monkey-patch), so removing it may change behavior.
24
27
  #
25
28
  # @example CheckForMethodsWithNoSideEffects: false (default)
26
29
  # # bad
@@ -109,7 +112,7 @@ module RuboCop
109
112
  def check_begin(node)
110
113
  expressions = *node
111
114
  inside_each_block = node.each_ancestor(:any_block).first&.method?(:each)
112
- expressions.pop if !in_void_context?(node) || inside_each_block
115
+ expressions.pop if !in_void_context?(node) || inside_each_block || setter_method?(node)
113
116
  expressions.each do |expr|
114
117
  check_void_op(expr) { inside_each_block }
115
118
  check_expression(expr)
@@ -212,7 +215,7 @@ module RuboCop
212
215
  end
213
216
 
214
217
  def check_nonmutating(node)
215
- return unless node.type?(:send, :any_block)
218
+ return unless node.type?(:call, :any_block)
216
219
 
217
220
  method_name = node.method_name
218
221
  return unless NONMUTATING_METHODS.include?(method_name)
@@ -244,6 +247,10 @@ module RuboCop
244
247
  parent.respond_to?(:void_context?) && parent.void_context?
245
248
  end
246
249
 
250
+ def setter_method?(node)
251
+ node.parent&.any_def_type? && node.parent.assignment_method?
252
+ end
253
+
247
254
  def autocorrect_void_op(corrector, node)
248
255
  if node.arguments.empty?
249
256
  corrector.replace(node, node.receiver.source)
@@ -258,6 +265,10 @@ module RuboCop
258
265
  end
259
266
 
260
267
  def autocorrect_void_expression(corrector, node)
268
+ # Referencing a constant can trigger autoloading side effects (e.g. forcing a file
269
+ # to load before a monkey-patch), so removing it may change behavior. Flag but don't
270
+ # autocorrect, leaving it to the user.
271
+ return if node.const_type? && !node.special_keyword?
261
272
  return if node.parent.type?(:if, :case, :when, :case_match, :in_pattern)
262
273
  return if (def_node = node.each_ancestor(:any_def).first) && def_node.assignment_method?
263
274
 
@@ -265,7 +276,7 @@ module RuboCop
265
276
  end
266
277
 
267
278
  def autocorrect_nonmutating_send(corrector, node, suggestion)
268
- send_node = if node.send_type?
279
+ send_node = if node.call_type?
269
280
  node
270
281
  else
271
282
  node.send_node
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Cops for the `Lint` department. The department's cops are registered for lazy loading
6
+ # and their files are loaded on demand.
7
+ module Lint
8
+ extend LazyLoader
9
+
10
+ register_cop :AmbiguousAssignment, "#{__dir__}/lint/ambiguous_assignment"
11
+ register_cop :AmbiguousBlockAssociation, "#{__dir__}/lint/ambiguous_block_association"
12
+ register_cop :AmbiguousOperator, "#{__dir__}/lint/ambiguous_operator"
13
+ register_cop :AmbiguousOperatorPrecedence, "#{__dir__}/lint/ambiguous_operator_precedence"
14
+ register_cop :AmbiguousRange, "#{__dir__}/lint/ambiguous_range"
15
+ register_cop :AmbiguousRegexpLiteral, "#{__dir__}/lint/ambiguous_regexp_literal"
16
+ register_cop :ArrayLiteralInRegexp, "#{__dir__}/lint/array_literal_in_regexp"
17
+ register_cop :AssignmentInCondition, "#{__dir__}/lint/assignment_in_condition"
18
+ register_cop :BigDecimalNew, "#{__dir__}/lint/big_decimal_new"
19
+ register_cop :BinaryOperatorWithIdenticalOperands, "#{__dir__}/lint/binary_operator_with_identical_operands"
20
+ register_cop :BooleanSymbol, "#{__dir__}/lint/boolean_symbol"
21
+ register_cop :CircularArgumentReference, "#{__dir__}/lint/circular_argument_reference"
22
+ register_cop :ConstantDefinitionInBlock, "#{__dir__}/lint/constant_definition_in_block"
23
+ register_cop :ConstantOverwrittenInRescue, "#{__dir__}/lint/constant_overwritten_in_rescue"
24
+ register_cop :ConstantReassignment, "#{__dir__}/lint/constant_reassignment"
25
+ register_cop :ConstantResolution, "#{__dir__}/lint/constant_resolution"
26
+ register_cop :CopDirectiveSyntax, "#{__dir__}/lint/cop_directive_syntax"
27
+ register_cop :DataDefineOverride, "#{__dir__}/lint/data_define_override"
28
+ register_cop :Debugger, "#{__dir__}/lint/debugger"
29
+ register_cop :DeprecatedClassMethods, "#{__dir__}/lint/deprecated_class_methods"
30
+ register_cop :DeprecatedConstants, "#{__dir__}/lint/deprecated_constants"
31
+ register_cop :DeprecatedOpenSSLConstant, "#{__dir__}/lint/deprecated_open_ssl_constant"
32
+ register_cop :DeprecatedReference, "#{__dir__}/lint/deprecated_reference"
33
+ register_cop :DisjunctiveAssignmentInConstructor, "#{__dir__}/lint/disjunctive_assignment_in_constructor"
34
+ register_cop :DuplicateBranch, "#{__dir__}/lint/duplicate_branch"
35
+ register_cop :DuplicateCaseCondition, "#{__dir__}/lint/duplicate_case_condition"
36
+ register_cop :DuplicateElsifCondition, "#{__dir__}/lint/duplicate_elsif_condition"
37
+ register_cop :DuplicateHashKey, "#{__dir__}/lint/duplicate_hash_key"
38
+ register_cop :DuplicateMagicComment, "#{__dir__}/lint/duplicate_magic_comment"
39
+ register_cop :DuplicateMatchPattern, "#{__dir__}/lint/duplicate_match_pattern"
40
+ register_cop :DuplicateMethods, "#{__dir__}/lint/duplicate_methods"
41
+ register_cop :DuplicateRegexpCharacterClassElement, "#{__dir__}/lint/duplicate_regexp_character_class_element"
42
+ register_cop :DuplicateRequire, "#{__dir__}/lint/duplicate_require"
43
+ register_cop :DuplicateRescueException, "#{__dir__}/lint/duplicate_rescue_exception"
44
+ register_cop :DuplicateSetElement, "#{__dir__}/lint/duplicate_set_element"
45
+ register_cop :EachWithObjectArgument, "#{__dir__}/lint/each_with_object_argument"
46
+ register_cop :ElseLayout, "#{__dir__}/lint/else_layout"
47
+ register_cop :EmptyBlock, "#{__dir__}/lint/empty_block"
48
+ register_cop :EmptyClass, "#{__dir__}/lint/empty_class"
49
+ register_cop :EmptyConditionalBody, "#{__dir__}/lint/empty_conditional_body"
50
+ register_cop :EmptyEnsure, "#{__dir__}/lint/empty_ensure"
51
+ register_cop :EmptyExpression, "#{__dir__}/lint/empty_expression"
52
+ register_cop :EmptyFile, "#{__dir__}/lint/empty_file"
53
+ register_cop :EmptyInPattern, "#{__dir__}/lint/empty_in_pattern"
54
+ register_cop :EmptyInterpolation, "#{__dir__}/lint/empty_interpolation"
55
+ register_cop :EmptyWhen, "#{__dir__}/lint/empty_when"
56
+ register_cop :EnsureReturn, "#{__dir__}/lint/ensure_return"
57
+ register_cop :SharedMutableDefault, "#{__dir__}/lint/shared_mutable_default"
58
+ register_cop :ErbNewArguments, "#{__dir__}/lint/erb_new_arguments"
59
+ register_cop :FlipFlop, "#{__dir__}/lint/flip_flop"
60
+ register_cop :FloatComparison, "#{__dir__}/lint/float_comparison"
61
+ register_cop :FloatOutOfRange, "#{__dir__}/lint/float_out_of_range"
62
+ register_cop :FormatParameterMismatch, "#{__dir__}/lint/format_parameter_mismatch"
63
+ register_cop :HashCompareByIdentity, "#{__dir__}/lint/hash_compare_by_identity"
64
+ register_cop :HashNewWithKeywordArgumentsAsDefault, "#{__dir__}/lint/hash_new_with_keyword_arguments_as_default"
65
+ register_cop :HeredocMethodCallPosition, "#{__dir__}/lint/heredoc_method_call_position"
66
+ register_cop :IdentityComparison, "#{__dir__}/lint/identity_comparison"
67
+ register_cop :ImplicitStringConcatenation, "#{__dir__}/lint/implicit_string_concatenation"
68
+ register_cop :IncompatibleIoSelectWithFiberScheduler, "#{__dir__}/lint/incompatible_io_select_with_fiber_scheduler"
69
+ register_cop :IneffectiveAccessModifier, "#{__dir__}/lint/ineffective_access_modifier"
70
+ register_cop :InheritException, "#{__dir__}/lint/inherit_exception"
71
+ register_cop :InterpolationCheck, "#{__dir__}/lint/interpolation_check"
72
+ register_cop :ItWithoutArgumentsInBlock, "#{__dir__}/lint/it_without_arguments_in_block"
73
+ register_cop :LambdaWithoutLiteralBlock, "#{__dir__}/lint/lambda_without_literal_block"
74
+ register_cop :LiteralAsCondition, "#{__dir__}/lint/literal_as_condition"
75
+ register_cop :LiteralAssignmentInCondition, "#{__dir__}/lint/literal_assignment_in_condition"
76
+ register_cop :LiteralInInterpolation, "#{__dir__}/lint/literal_in_interpolation"
77
+ register_cop :Loop, "#{__dir__}/lint/loop"
78
+ register_cop :MissingCopEnableDirective, "#{__dir__}/lint/missing_cop_enable_directive"
79
+ register_cop :MissingSuper, "#{__dir__}/lint/missing_super"
80
+ register_cop :MixedCaseRange, "#{__dir__}/lint/mixed_case_range"
81
+ register_cop :MixedRegexpCaptureTypes, "#{__dir__}/lint/mixed_regexp_capture_types"
82
+ register_cop :MultipleComparison, "#{__dir__}/lint/multiple_comparison"
83
+ register_cop :NameTypo, "#{__dir__}/lint/name_typo"
84
+ register_cop :NestedMethodDefinition, "#{__dir__}/lint/nested_method_definition"
85
+ register_cop :NestedPercentLiteral, "#{__dir__}/lint/nested_percent_literal"
86
+ register_cop :NextWithoutAccumulator, "#{__dir__}/lint/next_without_accumulator"
87
+ register_cop :NoReturnInBeginEndBlocks, "#{__dir__}/lint/no_return_in_begin_end_blocks"
88
+ register_cop :NonAtomicFileOperation, "#{__dir__}/lint/non_atomic_file_operation"
89
+ register_cop :NonDeterministicRequireOrder, "#{__dir__}/lint/non_deterministic_require_order"
90
+ register_cop :NonLocalExitFromIterator, "#{__dir__}/lint/non_local_exit_from_iterator"
91
+ register_cop :NumberConversion, "#{__dir__}/lint/number_conversion"
92
+ register_cop :NumberedParameterAssignment, "#{__dir__}/lint/numbered_parameter_assignment"
93
+ register_cop :NumericOperationWithConstantResult, "#{__dir__}/lint/numeric_operation_with_constant_result"
94
+ register_cop :OrAssignmentToConstant, "#{__dir__}/lint/or_assignment_to_constant"
95
+ register_cop :OrderedMagicComments, "#{__dir__}/lint/ordered_magic_comments"
96
+ register_cop :OutOfRangeRegexpRef, "#{__dir__}/lint/out_of_range_regexp_ref"
97
+ register_cop :ParenthesesAsGroupedExpression, "#{__dir__}/lint/parentheses_as_grouped_expression"
98
+ register_cop :PercentStringArray, "#{__dir__}/lint/percent_string_array"
99
+ register_cop :PercentSymbolArray, "#{__dir__}/lint/percent_symbol_array"
100
+ register_cop :RaiseException, "#{__dir__}/lint/raise_exception"
101
+ register_cop :RandOne, "#{__dir__}/lint/rand_one"
102
+ register_cop :RedundantCopDisableDirective, "#{__dir__}/lint/redundant_cop_disable_directive"
103
+ register_cop :RedundantCopEnableDirective, "#{__dir__}/lint/redundant_cop_enable_directive"
104
+ register_cop :RedundantDirGlobSort, "#{__dir__}/lint/redundant_dir_glob_sort"
105
+ register_cop :RedundantRegexpQuantifiers, "#{__dir__}/lint/redundant_regexp_quantifiers"
106
+ register_cop :RedundantRequireStatement, "#{__dir__}/lint/redundant_require_statement"
107
+ register_cop :RedundantSafeNavigation, "#{__dir__}/lint/redundant_safe_navigation"
108
+ register_cop :RedundantSplatExpansion, "#{__dir__}/lint/redundant_splat_expansion"
109
+ register_cop :RedundantStringCoercion, "#{__dir__}/lint/redundant_string_coercion"
110
+ register_cop :RedundantTypeConversion, "#{__dir__}/lint/redundant_type_conversion"
111
+ register_cop :RedundantWithIndex, "#{__dir__}/lint/redundant_with_index"
112
+ register_cop :RedundantWithObject, "#{__dir__}/lint/redundant_with_object"
113
+ register_cop :RefinementImportMethods, "#{__dir__}/lint/refinement_import_methods"
114
+ register_cop :RegexpAsCondition, "#{__dir__}/lint/regexp_as_condition"
115
+ register_cop :RequireParentheses, "#{__dir__}/lint/require_parentheses"
116
+ register_cop :RequireRangeParentheses, "#{__dir__}/lint/require_range_parentheses"
117
+ register_cop :RequireRelativeSelfPath, "#{__dir__}/lint/require_relative_self_path"
118
+ register_cop :RescueException, "#{__dir__}/lint/rescue_exception"
119
+ register_cop :RescueType, "#{__dir__}/lint/rescue_type"
120
+ register_cop :ReturnInVoidContext, "#{__dir__}/lint/return_in_void_context"
121
+ register_cop :SafeNavigationConsistency, "#{__dir__}/lint/safe_navigation_consistency"
122
+ register_cop :SafeNavigationChain, "#{__dir__}/lint/safe_navigation_chain"
123
+ register_cop :SafeNavigationWithEmpty, "#{__dir__}/lint/safe_navigation_with_empty"
124
+ register_cop :ScriptPermission, "#{__dir__}/lint/script_permission"
125
+ register_cop :SelfAssignment, "#{__dir__}/lint/self_assignment"
126
+ register_cop :SendWithMixinArgument, "#{__dir__}/lint/send_with_mixin_argument"
127
+ register_cop :ShadowedArgument, "#{__dir__}/lint/shadowed_argument"
128
+ register_cop :ShadowedException, "#{__dir__}/lint/shadowed_exception"
129
+ register_cop :ShadowingOuterLocalVariable, "#{__dir__}/lint/shadowing_outer_local_variable"
130
+ register_cop :StructNewOverride, "#{__dir__}/lint/struct_new_override"
131
+ register_cop :SuppressedException, "#{__dir__}/lint/suppressed_exception"
132
+ register_cop :SuppressedExceptionInNumberConversion, "#{__dir__}/lint/suppressed_exception_in_number_conversion"
133
+ register_cop :SymbolConversion, "#{__dir__}/lint/symbol_conversion"
134
+ register_cop :Syntax, "#{__dir__}/lint/syntax"
135
+ register_cop :ToEnumArguments, "#{__dir__}/lint/to_enum_arguments"
136
+ register_cop :ToJSON, "#{__dir__}/lint/to_json"
137
+ register_cop :TopLevelReturnWithArgument, "#{__dir__}/lint/top_level_return_with_argument"
138
+ register_cop :TrailingCommaInAttributeDeclaration, "#{__dir__}/lint/trailing_comma_in_attribute_declaration"
139
+ register_cop :TripleQuotes, "#{__dir__}/lint/triple_quotes"
140
+ register_cop :UnderscorePrefixedVariableName, "#{__dir__}/lint/underscore_prefixed_variable_name"
141
+ register_cop :UnescapedBracketInRegexp, "#{__dir__}/lint/unescaped_bracket_in_regexp"
142
+ register_cop :UnexpectedBlockArity, "#{__dir__}/lint/unexpected_block_arity"
143
+ register_cop :UnifiedInteger, "#{__dir__}/lint/unified_integer"
144
+ register_cop :UnmodifiedReduceAccumulator, "#{__dir__}/lint/unmodified_reduce_accumulator"
145
+ register_cop :UnreachableCode, "#{__dir__}/lint/unreachable_code"
146
+ register_cop :UnreachableLoop, "#{__dir__}/lint/unreachable_loop"
147
+ register_cop :UnreachablePatternBranch, "#{__dir__}/lint/unreachable_pattern_branch"
148
+ register_cop :UnusedBlockArgument, "#{__dir__}/lint/unused_block_argument"
149
+ register_cop :UnusedMethodArgument, "#{__dir__}/lint/unused_method_argument"
150
+ register_cop :UnusedPrivateMethod, "#{__dir__}/lint/unused_private_method"
151
+ register_cop :UriEscapeUnescape, "#{__dir__}/lint/uri_escape_unescape"
152
+ register_cop :UriRegexp, "#{__dir__}/lint/uri_regexp"
153
+ register_cop :UselessAccessModifier, "#{__dir__}/lint/useless_access_modifier"
154
+ register_cop :UselessAssignment, "#{__dir__}/lint/useless_assignment"
155
+ register_cop :UselessConstantScoping, "#{__dir__}/lint/useless_constant_scoping"
156
+ register_cop :UselessDefaultValueArgument, "#{__dir__}/lint/useless_default_value_argument"
157
+ register_cop :UselessDefined, "#{__dir__}/lint/useless_defined"
158
+ register_cop :UselessElseWithoutRescue, "#{__dir__}/lint/useless_else_without_rescue"
159
+ register_cop :UselessMethodDefinition, "#{__dir__}/lint/useless_method_definition"
160
+ register_cop :UselessNumericOperation, "#{__dir__}/lint/useless_numeric_operation"
161
+ register_cop :UselessOr, "#{__dir__}/lint/useless_or"
162
+ register_cop :UselessRescue, "#{__dir__}/lint/useless_rescue"
163
+ register_cop :UselessRuby2Keywords, "#{__dir__}/lint/useless_ruby2_keywords"
164
+ register_cop :UselessSetterCall, "#{__dir__}/lint/useless_setter_call"
165
+ register_cop :UselessTimes, "#{__dir__}/lint/useless_times"
166
+ register_cop :Void, "#{__dir__}/lint/void"
167
+
168
+ autoload :UnusedArgument, "#{__dir__}/mixin/unused_argument"
169
+
170
+ # Utility classes for `Lint` department cops.
171
+ module Utils
172
+ autoload :NilReceiverChecker, "#{__dir__}/lint/utils/nil_receiver_checker"
173
+ end
174
+ end
175
+ end
176
+ end
@@ -58,7 +58,7 @@ module RuboCop
58
58
  return unless node.method?(:define_method)
59
59
 
60
60
  method_name = node.send_node.first_argument
61
- return if method_name.basic_literal? && allowed?(method_name.value)
61
+ return if method_name&.basic_literal? && allowed?(method_name.value)
62
62
 
63
63
  check_code_length(node)
64
64
  end
@@ -12,9 +12,15 @@ module RuboCop
12
12
  # nodes count. In contrast to the CyclomaticComplexity cop, this cop
13
13
  # considers `else` nodes as adding complexity.
14
14
  #
15
+ # A `case`/`in` branch whose pattern is a simple literal (e.g. `in 1`, `in "red"`, `in 1..10`)
16
+ # or a constant/type (e.g. `in Integer`) and has no guard is just as easy to read as a `when`
17
+ # branch, so it is discounted the same way. Branches with structural patterns (e.g. array,
18
+ # hash, or find patterns), bindings, alternatives, or a guard add the full complexity of
19
+ # a decision point.
20
+ #
15
21
  # @example
16
22
  #
17
- # def my_method # 1
23
+ # def example_1 # 1
18
24
  # if cond # 1
19
25
  # case var # 2 (0.8 + 4 * 0.2, rounded)
20
26
  # when 1 then func_one
@@ -26,33 +32,58 @@ module RuboCop
26
32
  # do_something until a && b # 2
27
33
  # end # ===
28
34
  # end # 7 complexity points
35
+ #
36
+ # def example_2 # 1
37
+ # case color # 1 (3 * 0.2, rounded)
38
+ # in "red" then func_red
39
+ # in "blue" then func_blue
40
+ # in "green" then func_green
41
+ # end # ===
42
+ # end # 2 complexity points
29
43
  class PerceivedComplexity < CyclomaticComplexity
30
44
  MSG = 'Perceived complexity for `%<method>s` is too high. [%<complexity>d/%<max>d]'
31
45
 
32
- COUNTED_NODES = (CyclomaticComplexity::COUNTED_NODES - [:when] + [:case]).freeze
46
+ COUNTED_NODES = (
47
+ CyclomaticComplexity::COUNTED_NODES - %i[when in_pattern] + %i[case case_match]
48
+ ).freeze
33
49
 
34
50
  private
35
51
 
52
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
36
53
  def complexity_score_for(node)
37
54
  case node.type
38
55
  when :case
39
- # If cond is nil, that means each when has an expression that
40
- # evaluates to true or false. It's just an alternative to
41
- # if/elsif/elsif... so the when nodes count.
56
+ # If cond is nil, that means each when has an expression that evaluates to true or
57
+ # false. It's just an alternative to if/elsif/elsif... so the when nodes count.
42
58
  nb_branches = node.when_branches.length + (node.else_branch ? 1 : 0)
43
59
  if node.condition.nil?
44
60
  nb_branches
45
61
  else
46
- # Otherwise, the case node gets 0.8 complexity points and each
47
- # when gets 0.2.
62
+ # Otherwise, the case node gets 0.8 complexity points and each when gets 0.2.
48
63
  ((nb_branches * 0.2) + 0.8).round
49
64
  end
65
+ when :case_match
66
+ # Simple `in` branches are discounted like `when`, while structural patterns keep
67
+ # the full complexity of a decision point.
68
+ score = node.in_pattern_branches.sum { |branch| simple_in_pattern?(branch) ? 0.2 : 1 }
69
+ score += 0.2 if node.else_branch
70
+ score.round
50
71
  when :if
51
72
  node.else? && !node.elsif? ? 2 : 1
52
73
  else
53
74
  super
54
75
  end
55
76
  end
77
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
78
+
79
+ def simple_in_pattern?(in_pattern_node)
80
+ # `in_pattern_node.children[1]` is the guard (`if`/`unless`), or `nil`.
81
+ return false unless in_pattern_node.children[1].nil?
82
+
83
+ # A scalar literal, a literal range, or a constant/type is as easy to read as a `when`.
84
+ pattern = in_pattern_node.pattern
85
+ pattern.literal? || pattern.const_type?
86
+ end
56
87
  end
57
88
  end
58
89
  end
@@ -189,7 +189,7 @@ module RuboCop
189
189
  def source_from_node_with_heredoc(node)
190
190
  last_line = -1
191
191
  node.each_descendant do |descendant|
192
- next unless descendant.source
192
+ next if descendant.loc.nil? || descendant.source_range.nil?
193
193
 
194
194
  descendant_last_line =
195
195
  if heredoc_node?(descendant)
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Cops for the `Metrics` department. The department's cops are registered for lazy loading
6
+ # and their files are loaded on demand.
7
+ module Metrics
8
+ extend LazyLoader
9
+
10
+ register_cop :CyclomaticComplexity, "#{__dir__}/metrics/cyclomatic_complexity"
11
+ register_cop :AbcSize, "#{__dir__}/metrics/abc_size"
12
+ register_cop :BlockLength, "#{__dir__}/metrics/block_length"
13
+ register_cop :BlockNesting, "#{__dir__}/metrics/block_nesting"
14
+ register_cop :ClassLength, "#{__dir__}/metrics/class_length"
15
+ register_cop :CollectionLiteralLength, "#{__dir__}/metrics/collection_literal_length"
16
+ register_cop :MethodLength, "#{__dir__}/metrics/method_length"
17
+ register_cop :ModuleLength, "#{__dir__}/metrics/module_length"
18
+ register_cop :ParameterLists, "#{__dir__}/metrics/parameter_lists"
19
+ register_cop :PerceivedComplexity, "#{__dir__}/metrics/perceived_complexity"
20
+
21
+ # Utility classes for `Metrics` department cops.
22
+ module Utils
23
+ autoload :AbcSizeCalculator, "#{__dir__}/metrics/utils/abc_size_calculator"
24
+ autoload :CodeLengthCalculator, "#{__dir__}/metrics/utils/code_length_calculator"
25
+ autoload :IteratingBlock, "#{__dir__}/metrics/utils/iterating_block"
26
+ autoload :RepeatedAttributeDiscount, "#{__dir__}/metrics/utils/repeated_attribute_discount"
27
+ autoload :RepeatedCsendDiscount, "#{__dir__}/metrics/utils/repeated_csend_discount"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -50,10 +50,6 @@ module RuboCop
50
50
 
51
51
  private
52
52
 
53
- def disable_comment_offset
54
- Regexp.last_match(1).length
55
- end
56
-
57
53
  def check_cop_name(name, comment, offset)
58
54
  start = comment.source_range.begin_pos + offset
59
55
  range = range_between(start, start + name.length)
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Cops for the `Migration` department. The department's cops are registered for lazy loading
6
+ # and their files are loaded on demand.
7
+ module Migration
8
+ extend LazyLoader
9
+
10
+ register_cop :DepartmentName, "#{__dir__}/migration/department_name"
11
+ end
12
+ end
13
+ end
@@ -25,11 +25,11 @@ module RuboCop
25
25
 
26
26
  # @api public
27
27
  def allowed_methods
28
- if cop_config_deprecated_values.any?(Regexp)
29
- cop_config_allowed_methods
30
- else
31
- cop_config_allowed_methods + cop_config_deprecated_values
32
- end
28
+ @allowed_methods ||= if cop_config_deprecated_values.any?(Regexp)
29
+ cop_config_allowed_methods
30
+ else
31
+ cop_config_allowed_methods + cop_config_deprecated_values
32
+ end
33
33
  end
34
34
 
35
35
  def cop_config_allowed_methods
@@ -27,7 +27,7 @@ module RuboCop
27
27
  end
28
28
 
29
29
  def matches_allowed_pattern?(line)
30
- allowed_patterns.any? { |pattern| Regexp.new(pattern).match?(line) }
30
+ allowed_pattern_regexps.any? { |pattern| pattern.match?(line) }
31
31
  end
32
32
 
33
33
  # @deprecated Use matches_allowed_pattern? instead
@@ -49,6 +49,10 @@ module RuboCop
49
49
  end
50
50
  end
51
51
 
52
+ def allowed_pattern_regexps
53
+ @allowed_pattern_regexps ||= allowed_patterns.map { |pattern| Regexp.new(pattern) }
54
+ end
55
+
52
56
  def cop_config_patterns_values
53
57
  @cop_config_patterns_values ||=
54
58
  Array(cop_config.fetch('AllowedPatterns', [])) +
@@ -61,11 +61,13 @@ module RuboCop
61
61
  # @api private
62
62
  def extract_breakable_node_from_elements(node, elements, max)
63
63
  return unless breakable_collection?(node, elements)
64
- return if safe_to_ignore?(node)
65
64
 
65
+ # Check the cheap conditions first, `safe_to_ignore?` may traverse all elements of
66
+ # a containing collection.
66
67
  line = processed_source.lines[node.first_line - 1]
68
+ return if max && line.length <= max
67
69
  return if processed_source.line_with_comment?(node.loc.line)
68
- return if line.length <= max
70
+ return if safe_to_ignore?(node)
69
71
 
70
72
  extract_first_element_over_column_limit(node, elements, max)
71
73
  end
@@ -173,20 +175,36 @@ module RuboCop
173
175
  node.each_ancestor.find do |ancestor|
174
176
  if ancestor.type?(:hash, :array) &&
175
177
  breakable_collection?(ancestor, ancestor.children)
176
- return children_could_be_broken_up?(ancestor.children)
178
+ return children_could_be_broken_up?(ancestor, ancestor.children)
177
179
  end
178
180
 
179
181
  next unless ancestor.call_type?
180
182
 
181
183
  args = process_args(ancestor.arguments)
182
- return children_could_be_broken_up?(args) if breakable_collection?(ancestor, args)
184
+ if breakable_collection?(ancestor, args)
185
+ return children_could_be_broken_up?(ancestor, args)
186
+ end
183
187
  end
184
188
 
185
189
  false
186
190
  end
187
191
 
188
192
  # @api private
189
- def children_could_be_broken_up?(children)
193
+ # The result depends only on the given node, but the check may traverse all children of
194
+ # a large collection and is repeated for each contained element, so memoize it per node.
195
+ def children_could_be_broken_up?(node, children)
196
+ if @check_line_breakable_processed_source != processed_source
197
+ @check_line_breakable_processed_source = processed_source
198
+ @children_could_be_broken_up_cache = {}.compare_by_identity
199
+ end
200
+
201
+ @children_could_be_broken_up_cache.fetch(node) do
202
+ @children_could_be_broken_up_cache[node] = compute_children_could_be_broken_up?(children)
203
+ end
204
+ end
205
+
206
+ # @api private
207
+ def compute_children_could_be_broken_up?(children)
190
208
  return false if all_on_same_line?(children)
191
209
 
192
210
  last_seen_line = -1
@@ -5,20 +5,28 @@ module RuboCop
5
5
  # Common functionality for rewriting endless methods to normal method definitions
6
6
  module EndlessMethodRewriter
7
7
  def correct_to_multiline(corrector, node)
8
- replacement = <<~RUBY.strip
9
- def #{node.method_name}#{arguments(node)}
10
- #{node.body.source}
11
- end
12
- RUBY
8
+ replacement = [
9
+ "def #{receiver(node)}#{node.method_name}#{arguments(node)}",
10
+ "#{indent(node, offset: configured_indentation_width)}#{node.body.source}",
11
+ "#{indent(node)}end"
12
+ ].join("\n")
13
13
 
14
14
  corrector.replace(node, replacement)
15
15
  end
16
16
 
17
17
  private
18
18
 
19
+ def receiver(node)
20
+ node.receiver ? "#{node.receiver.source}#{node.loc.operator.source}" : ''
21
+ end
22
+
19
23
  def arguments(node, missing = '')
20
24
  node.arguments.any? ? node.arguments.source : missing
21
25
  end
26
+
27
+ def configured_indentation_width
28
+ cop_config['IndentationWidth'] || config.for_cop('Layout/IndentationWidth')['Width'] || 2
29
+ end
22
30
  end
23
31
  end
24
32
  end
@@ -5,12 +5,16 @@ module RuboCop
5
5
  # This module encapsulates the ability to forbid certain patterns in a cop.
6
6
  module ForbiddenPattern
7
7
  def forbidden_pattern?(name)
8
- forbidden_patterns.any? { |pattern| Regexp.new(pattern).match?(name) }
8
+ forbidden_pattern_regexps.any? { |pattern| pattern.match?(name) }
9
9
  end
10
10
 
11
11
  def forbidden_patterns
12
12
  cop_config.fetch('ForbiddenPatterns', [])
13
13
  end
14
+
15
+ def forbidden_pattern_regexps
16
+ @forbidden_pattern_regexps ||= forbidden_patterns.map { |pattern| Regexp.new(pattern) }
17
+ end
14
18
  end
15
19
  end
16
20
  end
@@ -96,20 +96,37 @@ module RuboCop
96
96
  end
97
97
 
98
98
  def hash_rocket_delta(first_pair, current_pair)
99
- first_pair.loc.column + max_key_width(first_pair.parent) + 1 -
100
- current_pair.loc.operator.column
99
+ target_operator_column(first_pair) - current_pair.loc.operator.column
101
100
  end
102
101
 
103
102
  def value_delta(first_pair, current_pair)
104
- correct_value_column = first_pair.key.loc.column +
105
- max_key_width(first_pair.parent) +
106
- max_delimiter_width(first_pair.parent)
103
+ correct_value_column = target_operator_column(first_pair) +
104
+ max_delimiter_width(first_pair.parent) - 1
107
105
 
108
106
  current_pair.value_omission? ? 0 : correct_value_column - current_pair.value.loc.column
109
107
  end
110
108
 
109
+ # The column the separator should land on: the shared key margin
110
+ # plus the widest single-line key, or, if larger, a multiline key's
111
+ # own last-line end column. The latter can't be measured from a
112
+ # multiline key's (newline-including) source length, so it's a
113
+ # separate candidate rather than folded into `max_key_width`.
114
+ def target_operator_column(first_pair)
115
+ hash_node = first_pair.parent
116
+ candidates = multiline_key_end_columns(hash_node)
117
+
118
+ key_width = max_key_width(hash_node)
119
+ candidates << (first_pair.loc.column + key_width + 1) if key_width.positive?
120
+
121
+ candidates.max
122
+ end
123
+
124
+ def multiline_key_end_columns(hash_node)
125
+ hash_node.keys.reject(&:single_line?).map { |key| key.source_range.end.column + 1 }
126
+ end
127
+
111
128
  def max_key_width(hash_node)
112
- hash_node.keys.map { |key| key.source.length }.max
129
+ hash_node.keys.select(&:single_line?).map { |key| key.source.length }.max || 0
113
130
  end
114
131
 
115
132
  def max_delimiter_width(hash_node)
@@ -182,10 +182,18 @@ module RuboCop
182
182
  return ":\"#{value.source}\"" if value.dsym_type?
183
183
  return "\"#{value.source}\"" if value.dstr_type?
184
184
  return ":#{value.source}" if value.sym_type?
185
+ # The element of a `%w` array can contain characters that are special
186
+ # inside a single-quoted string (e.g. a `'`), so escape them rather than
187
+ # wrapping the raw source.
188
+ return to_single_quoted(value.value) if value.str_type?
185
189
 
186
190
  "'#{value.source}'"
187
191
  end
188
192
 
193
+ def to_single_quoted(string)
194
+ "'#{string.gsub(/['\\]/) { |character| "\\#{character}" }}'"
195
+ end
196
+
189
197
  def except_key(node)
190
198
  key_arg = node.argument_list.first.source
191
199
  body, = extract_body_if_negated(node.body)
@@ -94,6 +94,10 @@ module RuboCop
94
94
 
95
95
  return unless captures.use_transformed_argname?
96
96
 
97
+ # A splat transforming expression (e.g. `[k, *v]`) can't be used as a
98
+ # standalone block return value, so the rewrite would produce invalid Ruby.
99
+ return if captures.transforming_body_expr.splat_type?
100
+
97
101
  message = "Prefer `#{new_method_name}` over `#{match_desc}`."
98
102
  add_offense(node, message: message) do |corrector|
99
103
  correction = prepare_correction(node)