rubocop 1.90.0 → 1.91.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +238 -10
  3. data/config/obsoletion.yml +9 -0
  4. data/lib/rubocop/changed_files.rb +112 -0
  5. data/lib/rubocop/cli/command/execute_runner.rb +21 -1
  6. data/lib/rubocop/cli.rb +9 -3
  7. data/lib/rubocop/comment_config/directive_range.rb +3 -1
  8. data/lib/rubocop/comment_config/disable_next.rb +49 -3
  9. data/lib/rubocop/comment_config/push_pop.rb +55 -0
  10. data/lib/rubocop/comment_config.rb +21 -51
  11. data/lib/rubocop/config.rb +10 -0
  12. data/lib/rubocop/config_loader.rb +12 -4
  13. data/lib/rubocop/config_loader_resolver.rb +49 -7
  14. data/lib/rubocop/config_obsoletion/cop_rule.rb +2 -1
  15. data/lib/rubocop/config_obsoletion/removed_cop.rb +8 -0
  16. data/lib/rubocop/config_obsoletion.rb +1 -1
  17. data/lib/rubocop/config_validator.rb +18 -3
  18. data/lib/rubocop/cop/badge.rb +1 -1
  19. data/lib/rubocop/cop/base.rb +15 -3
  20. data/lib/rubocop/cop/correctors.rb +2 -2
  21. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  22. data/lib/rubocop/cop/generator.rb +1 -1
  23. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  24. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -0
  25. data/lib/rubocop/cop/layout/comment_indentation.rb +8 -5
  26. data/lib/rubocop/cop/layout/else_alignment.rb +1 -0
  27. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  28. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +17 -4
  29. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +11 -0
  30. data/lib/rubocop/cop/layout/hash_alignment.rb +6 -5
  31. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  32. data/lib/rubocop/cop/layout/line_length.rb +19 -1
  33. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +14 -3
  34. data/lib/rubocop/cop/layout/space_around_operators.rb +5 -1
  35. data/lib/rubocop/cop/lint/argument_mismatch.rb +14 -4
  36. data/lib/rubocop/cop/lint/assignment_in_condition.rb +2 -0
  37. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +88 -14
  38. data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
  39. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  40. data/lib/rubocop/cop/lint/literal_as_condition.rb +8 -2
  41. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +185 -0
  42. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -1
  43. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
  44. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  45. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +17 -1
  46. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +30 -16
  47. data/lib/rubocop/cop/lint.rb +1 -0
  48. data/lib/rubocop/cop/migration/department_name.rb +1 -1
  49. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  50. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -1
  51. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +11 -1
  52. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +6 -2
  53. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  54. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +12 -1
  55. data/lib/rubocop/cop/mixin.rb +2 -2
  56. data/lib/rubocop/cop/naming/block_forwarding.rb +4 -1
  57. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  58. data/lib/rubocop/cop/registry.rb +9 -1
  59. data/lib/rubocop/cop/severity.rb +12 -0
  60. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -2
  61. data/lib/rubocop/cop/style/arguments_forwarding.rb +2 -2
  62. data/lib/rubocop/cop/style/case_like_if.rb +3 -3
  63. data/lib/rubocop/cop/style/constant_visibility.rb +6 -2
  64. data/lib/rubocop/cop/style/data_inheritance.rb +14 -0
  65. data/lib/rubocop/cop/style/directive_scope.rb +154 -21
  66. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +53 -9
  67. data/lib/rubocop/cop/style/documentation_method.rb +1 -9
  68. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  69. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  70. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  71. data/lib/rubocop/cop/style/empty_method.rb +12 -2
  72. data/lib/rubocop/cop/style/endless_method.rb +31 -12
  73. data/lib/rubocop/cop/style/file_null.rb +2 -2
  74. data/lib/rubocop/cop/style/float_division.rb +10 -16
  75. data/lib/rubocop/cop/style/for.rb +5 -0
  76. data/lib/rubocop/cop/style/format_string_token.rb +1 -1
  77. data/lib/rubocop/cop/style/guard_clause.rb +4 -0
  78. data/lib/rubocop/cop/style/hash_lookup_method.rb +3 -0
  79. data/lib/rubocop/cop/style/magic_comment_format.rb +1 -1
  80. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -1
  81. data/lib/rubocop/cop/style/nested_modifier.rb +9 -4
  82. data/lib/rubocop/cop/style/nil_comparison.rb +36 -8
  83. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  84. data/lib/rubocop/cop/style/non_nil_check.rb +18 -2
  85. data/lib/rubocop/cop/style/not.rb +3 -1
  86. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +11 -1
  87. data/lib/rubocop/cop/style/one_line_conditional.rb +1 -1
  88. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -1
  89. data/lib/rubocop/cop/style/parentheses_around_condition.rb +6 -1
  90. data/lib/rubocop/cop/style/redundant_condition.rb +4 -4
  91. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +10 -1
  92. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  93. data/lib/rubocop/cop/style/redundant_parentheses.rb +11 -1
  94. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +6 -5
  95. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +6 -6
  96. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  97. data/lib/rubocop/cop/style/special_global_vars.rb +1 -1
  98. data/lib/rubocop/cop/style/struct_inheritance.rb +14 -0
  99. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  100. data/lib/rubocop/cop/style.rb +0 -1
  101. data/lib/rubocop/cop/util.rb +1 -1
  102. data/lib/rubocop/cops_documentation_generator.rb +23 -2
  103. data/lib/rubocop/directive_comment.rb +37 -8
  104. data/lib/rubocop/formatter/disabled_config_formatter.rb +19 -1
  105. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  106. data/lib/rubocop/formatter/github_actions_formatter.rb +6 -5
  107. data/lib/rubocop/formatter/html_formatter.rb +2 -2
  108. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  109. data/lib/rubocop/formatter/sarif_formatter.rb +181 -0
  110. data/lib/rubocop/formatter.rb +1 -0
  111. data/lib/rubocop/magic_comment.rb +27 -0
  112. data/lib/rubocop/options.rb +59 -1
  113. data/lib/rubocop/path_util.rb +1 -1
  114. data/lib/rubocop/plugin/loader.rb +1 -1
  115. data/lib/rubocop/rake_task.rb +1 -1
  116. data/lib/rubocop/remote_config.rb +4 -4
  117. data/lib/rubocop/result_cache.rb +16 -5
  118. data/lib/rubocop/runner.rb +64 -21
  119. data/lib/rubocop/unified_diff.rb +257 -0
  120. data/lib/rubocop/version.rb +1 -1
  121. data/lib/rubocop.rb +4 -1
  122. metadata +8 -4
  123. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +0 -46
@@ -107,7 +107,7 @@ module RuboCop
107
107
  remove_to_f_method(corrector, node.receiver)
108
108
  add_to_f_method(corrector, node.first_argument)
109
109
  when :fdiv
110
- correct_from_slash_to_fdiv(corrector, node, node.receiver, node.first_argument)
110
+ correct_from_slash_to_fdiv(corrector, node.receiver, node.first_argument)
111
111
  end
112
112
  end
113
113
  end
@@ -145,23 +145,17 @@ module RuboCop
145
145
  corrector.remove(send_node.loc.selector)
146
146
  end
147
147
 
148
- def correct_from_slash_to_fdiv(corrector, node, receiver, argument)
149
- receiver_source = extract_receiver_source(receiver)
150
- argument_source = extract_receiver_source(argument)
148
+ def correct_from_slash_to_fdiv(corrector, receiver, argument)
149
+ remove_to_f_method(corrector, receiver) if to_f_method?(receiver)
151
150
 
152
- if argument.respond_to?(:parenthesized?) && !argument.parenthesized?
153
- argument_source = "(#{argument_source})"
154
- end
155
-
156
- corrector.replace(node, "#{receiver_source}.fdiv#{argument_source}")
157
- end
151
+ argument_parenthesized = argument.begin_type?
152
+ corrector.replace(
153
+ receiver.source_range.end.join(argument.source_range.begin),
154
+ argument_parenthesized ? '.fdiv' : '.fdiv('
155
+ )
158
156
 
159
- def extract_receiver_source(node)
160
- if node.send_type? && node.method?(:to_f)
161
- node.receiver.source
162
- else
163
- node.source
164
- end
157
+ remove_to_f_method(corrector, argument) if to_f_method?(argument)
158
+ corrector.insert_after(argument, ')') unless argument_parenthesized
165
159
  end
166
160
  end
167
161
  end
@@ -69,6 +69,7 @@ module RuboCop
69
69
 
70
70
  if style == :for
71
71
  return unless node.receiver
72
+ return if rescue_or_ensure_body?(node)
72
73
 
73
74
  add_offense(node, message: PREFER_FOR) do |corrector|
74
75
  EachToForCorrector.new(node).call(corrector)
@@ -87,6 +88,10 @@ module RuboCop
87
88
  def suspect_enumerable?(node)
88
89
  node.multiline? && node.method?(:each) && !node.send_node.arguments?
89
90
  end
91
+
92
+ def rescue_or_ensure_body?(node)
93
+ node.body&.type?(:rescue, :ensure)
94
+ end
90
95
  end
91
96
  end
92
97
  end
@@ -228,7 +228,7 @@ module RuboCop
228
228
  "Prefer #{message_text(style)} over #{message_text(detected_style)}."
229
229
  end
230
230
 
231
- # rubocop:disable-next Style/FormatStringToken
231
+ # rubocop:disable-next Style/FormatStringToken -- the cop needs the token style it is matching
232
232
  def message_text(style)
233
233
  {
234
234
  annotated: 'annotated tokens (like `%<foo>s`)',
@@ -109,6 +109,10 @@ module RuboCop
109
109
  MSG = 'Use a guard clause (`%<example>s`) instead of wrapping the ' \
110
110
  'code inside a conditional expression.'
111
111
 
112
+ def self.autocorrect_incompatible_with
113
+ [Style::MissingElse]
114
+ end
115
+
112
116
  def on_def(node)
113
117
  body = node.body
114
118
 
@@ -57,12 +57,15 @@ module RuboCop
57
57
 
58
58
  def on_send(node)
59
59
  return if (receiver = node.receiver) && allowed_receiver?(receiver)
60
+ return if part_of_ignored_node?(node)
60
61
 
61
62
  if offense_for_brackets?(node)
63
+ ignore_node(node.first_argument)
62
64
  add_offense(node.loc.selector, message: BRACKET_MSG) do |corrector|
63
65
  correct_fetch_to_brackets(corrector, node)
64
66
  end
65
67
  elsif offense_for_fetch?(node)
68
+ ignore_node(node.first_argument)
66
69
  add_offense(node, message: FETCH_MSG) do |corrector|
67
70
  correct_brackets_to_fetch(corrector, node)
68
71
  end
@@ -187,7 +187,7 @@ module RuboCop
187
187
  issues[:directives] << directive if directive_offends?(directive)
188
188
  end
189
189
 
190
- comment.values.each do |value| # rubocop:disable Style/HashEachMethods
190
+ comment.values.each do |value| # rubocop:disable Style/HashEachMethods -- the receiver is a magic comment, not a Hash
191
191
  issues[:values] << value if wrong_capitalization?(value.source, value_capitalization)
192
192
  end
193
193
 
@@ -242,7 +242,7 @@ module RuboCop
242
242
  extend AutoCorrector
243
243
 
244
244
  def self.autocorrect_incompatible_with
245
- [Style::NestedParenthesizedCalls, Style::RescueModifier]
245
+ [Style::NestedParenthesizedCalls, Style::RescueModifier, Style::TrailingCommaInArguments]
246
246
  end
247
247
 
248
248
  def on_send(node)
@@ -70,6 +70,7 @@ module RuboCop
70
70
 
71
71
  def right_hand_operand(node, left_hand_keyword)
72
72
  condition = node.condition
73
+ negated = left_hand_keyword != node.keyword
73
74
 
74
75
  expr = if condition.send_type? && !condition.arguments.empty? &&
75
76
  !condition.operator_method?
@@ -77,8 +78,8 @@ module RuboCop
77
78
  else
78
79
  condition.source
79
80
  end
80
- expr = "(#{expr})" if requires_parens?(condition)
81
- expr = "!#{expr}" unless left_hand_keyword == node.keyword
81
+ expr = "(#{expr})" if requires_parens?(condition, negated)
82
+ expr = "!#{expr}" if negated
82
83
  expr
83
84
  end
84
85
 
@@ -91,8 +92,12 @@ module RuboCop
91
92
  expr
92
93
  end
93
94
 
94
- def requires_parens?(node)
95
- node.or_type? || !(RuboCop::AST::Node::COMPARISON_OPERATORS & node.children).empty?
95
+ def requires_parens?(node, negated)
96
+ # A negated `&&`/`||` operand must be wrapped so the `!` applies to the
97
+ # whole expression (e.g. `!(a && b)`, not `!a && b`).
98
+ (negated && node.operator_keyword?) ||
99
+ node.or_type? ||
100
+ !(RuboCop::AST::Node::COMPARISON_OPERATORS & node.children).empty?
96
101
  end
97
102
  end
98
103
  end
@@ -42,28 +42,56 @@ module RuboCop
42
42
  # @!method nil_check?(node)
43
43
  def_node_matcher :nil_check?, '(send _ :nil?)'
44
44
 
45
- # rubocop:disable-next Metrics/AbcSize
46
45
  def on_send(node)
47
46
  return unless node.receiver
48
47
 
49
48
  style_check?(node) do
50
49
  add_offense(node.loc.selector) do |corrector|
51
50
  if prefer_comparison?
52
- range = node.loc.dot.join(node.loc.selector.end)
53
- corrector.replace(range, ' == nil')
51
+ autocorrect_to_comparison(corrector, node)
54
52
  else
55
- range = node.receiver.source_range.end.join(node.source_range.end)
56
- corrector.replace(range, '.nil?')
53
+ autocorrect_to_predicate(corrector, node)
57
54
  end
58
-
59
- parent = node.parent
60
- corrector.wrap(node, '(', ')') if parent.respond_to?(:method?) && parent.method?(:!)
61
55
  end
62
56
  end
63
57
  end
64
58
 
65
59
  private
66
60
 
61
+ def autocorrect_to_comparison(corrector, node)
62
+ range = node.loc.dot.join(node.loc.selector.end)
63
+ corrector.replace(range, ' == nil')
64
+ # The new `== nil` binds looser than an enclosing operator (e.g. `<<`,
65
+ # `!`), so wrap it to keep the original precedence.
66
+ corrector.wrap(node, '(', ')') if operator_expression?(node.parent)
67
+ end
68
+
69
+ def autocorrect_to_predicate(corrector, node)
70
+ receiver = node.receiver
71
+ if operator_expression?(receiver)
72
+ # A looser-binding receiver (e.g. `!x`, `a + b`) must be parenthesized
73
+ # so the appended `.nil?` applies to the whole expression.
74
+ corrector.replace(node, "(#{receiver.source}).nil?")
75
+ else
76
+ range = receiver.source_range.end.join(node.source_range.end)
77
+ corrector.replace(range, '.nil?')
78
+ end
79
+ end
80
+
81
+ def operator_expression?(node)
82
+ return false unless node
83
+
84
+ operator_send?(node) ||
85
+ node.operator_keyword? ||
86
+ (node.if_type? && node.ternary?) ||
87
+ node.type?(:range, :iflipflop, :eflipflop) ||
88
+ node.assignment?
89
+ end
90
+
91
+ def operator_send?(node)
92
+ node.send_type? && node.operator_method? && !node.method?(:[]) && !node.method?(:[]=)
93
+ end
94
+
67
95
  def message(_node)
68
96
  prefer_comparison? ? EXPLICIT_MSG : PREDICATE_MSG
69
97
  end
@@ -48,7 +48,7 @@ module RuboCop
48
48
  { ({return next break} nil) (nil) }
49
49
  PATTERN
50
50
 
51
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
51
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler -- a lambda whose body is `nil` uses no parameters
52
52
  return unless node.lambda_or_proc?
53
53
  return unless nil_return?(node.body)
54
54
  # `return` inside a non-lambda proc returns from the enclosing method,
@@ -109,7 +109,7 @@ module RuboCop
109
109
 
110
110
  def message(node)
111
111
  if node.method?(:!=) && !include_semantic_changes?
112
- prefer = "!#{node.receiver.source}.nil?"
112
+ prefer = non_nil_check_replacement(node)
113
113
  format(MSG_FOR_REPLACEMENT, prefer: prefer, current: node.source)
114
114
  else
115
115
  MSG_FOR_REDUNDANCY
@@ -120,11 +120,27 @@ module RuboCop
120
120
  cop_config['IncludeSemanticChanges']
121
121
  end
122
122
 
123
+ # An operator-expression receiver (e.g. `a + b`) binds looser than the
124
+ # appended `.nil?`, so it must be parenthesized: `!(a + b).nil?`.
125
+ def non_nil_check_replacement(node)
126
+ receiver = node.receiver
127
+ source = operator_expression?(receiver) ? "(#{receiver.source})" : receiver.source
128
+ "!#{source}.nil?"
129
+ end
130
+
131
+ def operator_expression?(node)
132
+ node.operator_keyword? ||
133
+ (node.send_type? && node.binary_operation?) ||
134
+ (node.if_type? && node.ternary?) ||
135
+ node.type?(:range, :iflipflop, :eflipflop) ||
136
+ node.assignment?
137
+ end
138
+
123
139
  def autocorrect_comparison(corrector, node)
124
140
  if include_semantic_changes?
125
141
  corrector.replace(node, node.receiver.source)
126
142
  else
127
- corrector.replace(node, "!#{node.receiver.source}.nil?")
143
+ corrector.replace(node, non_nil_check_replacement(node))
128
144
  end
129
145
  end
130
146
 
@@ -56,7 +56,9 @@ module RuboCop
56
56
  def requires_parens?(child)
57
57
  child.operator_keyword? ||
58
58
  (child.send_type? && child.binary_operation?) ||
59
- (child.if_type? && child.ternary?)
59
+ (child.if_type? && child.ternary?) ||
60
+ child.type?(:range, :iflipflop, :eflipflop) ||
61
+ child.assignment?
60
62
  end
61
63
 
62
64
  def correct_opposite_method(corrector, range, child)
@@ -55,7 +55,11 @@ module RuboCop
55
55
  return unless type
56
56
 
57
57
  add_offense(node) do |corrector|
58
- corrector.replace(node, send(:"format_#{type}", node.source))
58
+ # The prefix patterns below are anchored, so correct only the digits
59
+ # and leave any sign the literal's source carries in place.
60
+ range = unsigned_range(node)
61
+
62
+ corrector.replace(range, send(:"format_#{type}", range.source))
59
63
  end
60
64
  end
61
65
 
@@ -71,6 +75,12 @@ module RuboCop
71
75
  octal_literal_type(literal) || hex_bin_dec_literal_type(literal)
72
76
  end
73
77
 
78
+ def unsigned_range(node)
79
+ range = node.source_range
80
+
81
+ node.source.start_with?('+', '-') ? range.adjust(begin_pos: 1) : range
82
+ end
83
+
74
84
  def octal_literal_type(literal)
75
85
  if OCTAL_ZERO_ONLY_REGEX.match?(literal) && octal_zero_only?
76
86
  :octal_zero_only
@@ -117,7 +117,7 @@ module RuboCop
117
117
  end
118
118
 
119
119
  def ternary_replacement(node)
120
- condition, if_branch, else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring
120
+ condition, if_branch, else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring -- takes all three branches in one step
121
121
 
122
122
  "#{expr_replacement(condition)} ? " \
123
123
  "#{expr_replacement(if_branch)} : " \
@@ -184,7 +184,7 @@ module RuboCop
184
184
  def accesses?(rhs, lhs)
185
185
  if lhs.method?(:[]=)
186
186
  # FIXME: Workaround `rubocop:disable` comment for JRuby.
187
- # rubocop:disable-next Performance/RedundantEqualityComparisonBlock
187
+ # rubocop:disable-next Performance/RedundantEqualityComparisonBlock -- a JRuby workaround, as the comment above says
188
188
  matching_calls(rhs, lhs.receiver, :[]).any? { |args| args == lhs.arguments }
189
189
  else
190
190
  access_method = lhs.method_name.to_s.chop.to_sym
@@ -123,10 +123,15 @@ module RuboCop
123
123
 
124
124
  def parens_allowed?(node)
125
125
  parens_required?(node) ||
126
- (safe_assignment?(node) && safe_assignment_allowed?) ||
126
+ (safe_assignment?(node) && safe_assignment_kept?) ||
127
127
  (node.multiline? && allow_multiline_conditions?)
128
128
  end
129
129
 
130
+ def safe_assignment_kept?
131
+ safe_assignment_allowed? ||
132
+ config.for_enabled_cop('Lint/AssignmentInCondition')['AllowSafeAssignment']
133
+ end
134
+
130
135
  def allow_multiline_conditions?
131
136
  cop_config['AllowInMultilineConditions']
132
137
  end
@@ -119,7 +119,7 @@ module RuboCop
119
119
  end
120
120
 
121
121
  def offense?(node)
122
- _condition, _if_branch, else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring
122
+ _condition, _if_branch, else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring -- takes all three branches in one step
123
123
  return false if use_if_branch?(else_branch) || use_hash_key_assignment?(else_branch)
124
124
 
125
125
  synonymous_condition_and_branch?(node) && !node.elsif? &&
@@ -143,7 +143,7 @@ module RuboCop
143
143
  end
144
144
 
145
145
  def synonymous_condition_and_branch?(node)
146
- condition, if_branch, _else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring
146
+ condition, if_branch, _else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring -- takes all three branches in one step
147
147
  # e.g.
148
148
  # if var
149
149
  # var
@@ -192,7 +192,7 @@ module RuboCop
192
192
  end
193
193
 
194
194
  def branches_have_assignment?(node)
195
- _condition, if_branch, else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring
195
+ _condition, if_branch, else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring -- takes all three branches in one step
196
196
 
197
197
  return false unless if_branch && else_branch
198
198
 
@@ -294,7 +294,7 @@ module RuboCop
294
294
  end
295
295
 
296
296
  def make_ternary_form(node)
297
- _condition, if_branch, else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring
297
+ _condition, if_branch, else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring -- takes all three branches in one step
298
298
  arithmetic_operation = use_arithmetic_operation?(if_branch)
299
299
 
300
300
  ternary_form = [
@@ -108,7 +108,7 @@ module RuboCop
108
108
 
109
109
  node.arguments.map do |arg|
110
110
  if arg.hash_type?
111
- arg.source
111
+ hash_argument_source(arg)
112
112
  else
113
113
  "**#{arg.source}"
114
114
  end
@@ -122,6 +122,15 @@ module RuboCop
122
122
 
123
123
  mergeable?(parent)
124
124
  end
125
+
126
+ # A braced hash literal passed to `merge` must have its braces stripped so
127
+ # its pairs join the surrounding keyword arguments. Keeping the braces would
128
+ # produce a positional hash after keyword arguments, which is invalid Ruby.
129
+ def hash_argument_source(hash)
130
+ return hash.source unless hash.braces?
131
+
132
+ hash.pairs.map(&:source).join(', ')
133
+ end
125
134
  end
126
135
  end
127
136
  end
@@ -52,7 +52,7 @@ module RuboCop
52
52
  ${nil? basic_literal? const_type?})
53
53
  PATTERN
54
54
 
55
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
55
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler -- the matched body is a literal or a constant
56
56
  redundant_fetch_block_candidate?(node) do |send, body|
57
57
  return if should_not_check?(send, body)
58
58
 
@@ -170,7 +170,10 @@ module RuboCop
170
170
  return 'a one-line pattern matching'
171
171
  end
172
172
  return 'an interpolated expression' if interpolation?(begin_node)
173
- return 'a method argument' if argument_of_parenthesized_method_call?(begin_node, node)
173
+ if argument_of_parenthesized_method_call?(begin_node, node) &&
174
+ !keyword_logical_operator?(node)
175
+ return 'a method argument'
176
+ end
174
177
  return 'a one-line rescue' if oneline_rescue_parentheses_required?(begin_node, node)
175
178
 
176
179
  return if begin_node.chained?
@@ -315,6 +318,13 @@ module RuboCop
315
318
  (node.end.nil? && begin_node == parent.children.last)
316
319
  end
317
320
 
321
+ # `and`/`or` keyword operators bind looser than the method-argument
322
+ # boundary, so `foo((x and y))` cannot drop its parentheses without
323
+ # becoming a syntax error (unlike `&&`/`||`).
324
+ def keyword_logical_operator?(node)
325
+ node.operator_keyword? && node.semantic_operator?
326
+ end
327
+
318
328
  def disallowed_one_line_pattern_matching?(begin_node, node)
319
329
  if (parent = begin_node.parent)
320
330
  return false if parent.any_def_type? && parent.endless?
@@ -74,7 +74,7 @@ module RuboCop
74
74
 
75
75
  non_redundant =
76
76
  whitespace_in_free_space_mode?(node, class_elem) ||
77
- backslash_b?(class_elem) || octal_requiring_char_class?(class_elem) ||
77
+ backslash_b?(class_elem) || backreference_requiring_char_class?(class_elem) ||
78
78
  requires_escape_outside_char_class?(class_elem)
79
79
 
80
80
  !non_redundant
@@ -104,10 +104,11 @@ module RuboCop
104
104
  elem == '\b'
105
105
  end
106
106
 
107
- def octal_requiring_char_class?(elem)
108
- # The octal escapes \1 to \7 only work inside a character class
109
- # because they would be a backreference outside it.
110
- elem.match?(/\A\\[1-7]\z/)
107
+ def backreference_requiring_char_class?(elem)
108
+ # `\1` to `\9` only match a literal digit inside a character class;
109
+ # outside it they are backreferences (and a syntax error when the
110
+ # referenced group does not exist), so the class is not redundant.
111
+ elem.match?(/\A\\[1-9]\z/)
111
112
  end
112
113
 
113
114
  def requires_escape_outside_char_class?(elem)
@@ -27,17 +27,17 @@ module RuboCop
27
27
  def_node_matcher :redundant_regexp_constructor, <<~PATTERN
28
28
  (send
29
29
  (const {nil? cbase} :Regexp) {:new :compile}
30
- (regexp $... (regopt $...)))
30
+ $(regexp _* (regopt _*)))
31
31
  PATTERN
32
32
 
33
33
  def on_send(node)
34
- return unless (regexp, regopt = redundant_regexp_constructor(node))
34
+ return unless (regexp = redundant_regexp_constructor(node))
35
35
 
36
36
  add_offense(node, message: format(MSG, method: node.method_name)) do |corrector|
37
- pattern = regexp.map(&:source).join
38
- regopt = regopt.join
39
-
40
- corrector.replace(node, "/#{pattern}/#{regopt}")
37
+ # Reuse the inner literal's own source so its delimiters are preserved.
38
+ # Forcing `/.../` would break patterns like `%r{foo/bar}` that contain
39
+ # an unescaped slash.
40
+ corrector.replace(node, regexp.source)
41
41
  end
42
42
  end
43
43
  end
@@ -33,7 +33,7 @@ module RuboCop
33
33
 
34
34
  MSG = 'Name `%<method>s` block params `|%<params>s|`.'
35
35
 
36
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
36
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler -- checks block parameter names
37
37
  return unless node.single_line?
38
38
 
39
39
  return unless eligible_method?(node)
@@ -101,7 +101,7 @@ module RuboCop
101
101
  'module (don\'t forget to require it) over `%<global>s`.'
102
102
  MSG_REGULAR = 'Prefer `%<prefer>s` over `%<global>s`.'
103
103
 
104
- ENGLISH_VARS = { # rubocop:disable Style/MutableConstant
104
+ ENGLISH_VARS = { # rubocop:disable Style/MutableConstant -- merged into below, then frozen
105
105
  :$: => [:$LOAD_PATH],
106
106
  :$" => [:$LOADED_FEATURES],
107
107
  :$0 => [:$PROGRAM_NAME],
@@ -10,6 +10,10 @@ module RuboCop
10
10
  # Autocorrection is unsafe because it will change the inheritance
11
11
  # tree (e.g. return value of `Module#ancestors`) of the constant.
12
12
  #
13
+ # It is also unsafe because constants that the class body resolves through its
14
+ # ancestors (e.g. one provided by an included module) fall out of scope inside
15
+ # the block.
16
+ #
13
17
  # @example
14
18
  # # bad
15
19
  # class Person < Struct.new(:first_name, :last_name)
@@ -39,6 +43,7 @@ module RuboCop
39
43
 
40
44
  def on_class(node)
41
45
  return unless struct_constructor?(node.parent_class)
46
+ return if defines_constants?(node.body)
42
47
 
43
48
  add_offense(node.parent_class) do |corrector|
44
49
  corrector.remove(
@@ -58,6 +63,15 @@ module RuboCop
58
63
 
59
64
  private
60
65
 
66
+ # Constants, including nested classes and modules, are scoped to the class when they
67
+ # are defined in a class body, but leak into the enclosing namespace once moved into a
68
+ # `Struct.new` block, so such a class cannot be rewritten as an assignment.
69
+ def defines_constants?(class_body)
70
+ return false unless class_body
71
+
72
+ class_body.each_node(:casgn, :class, :module).any?
73
+ end
74
+
61
75
  def correct_parent(parent, corrector)
62
76
  if parent.block_type?
63
77
  corrector.remove(range_with_surrounding_space(parent.loc.end, newlines: false))
@@ -64,7 +64,7 @@ module RuboCop
64
64
 
65
65
  MSG = 'Useless trailing comma present in block arguments.'
66
66
 
67
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
67
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler -- a trailing comma needs an explicit parameter list
68
68
  # lambda literal (`->`) never has block arguments.
69
69
  return if node.send_node.lambda_literal?
70
70
  return unless useless_trailing_comma?(node)
@@ -63,7 +63,6 @@ module RuboCop
63
63
  register_cop :DocumentationMethod, "#{__dir__}/style/documentation_method"
64
64
  register_cop :Documentation, "#{__dir__}/style/documentation"
65
65
  register_cop :DocumentDynamicEvalDefinition, "#{__dir__}/style/document_dynamic_eval_definition"
66
- register_cop :DoubleCopDisableDirective, "#{__dir__}/style/double_cop_disable_directive"
67
66
  register_cop :DoubleNegation, "#{__dir__}/style/double_negation"
68
67
  register_cop :EachForSimpleLoop, "#{__dir__}/style/each_for_simple_loop"
69
68
  register_cop :EachWithObject, "#{__dir__}/style/each_with_object"
@@ -63,7 +63,7 @@ module RuboCop
63
63
  end
64
64
  else
65
65
  # Use a block version to avoid allocating enumerators.
66
- node.each_descendant do # rubocop:disable Lint/UnreachableLoop
66
+ node.each_descendant do # rubocop:disable Lint/UnreachableLoop -- the block form avoids allocating an enumerator
67
67
  return true
68
68
  end
69
69
  end
@@ -20,6 +20,7 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
20
20
  safety: ->(data) { safety_object(data.safety_objects, data.cop) },
21
21
  examples: ->(data) { examples(data.example_objects, data.cop) },
22
22
  configuration: ->(data) { configurations(data.cop.department, data.cop, data.config) },
23
+ preview: ->(data) { preview_defaults(data.cop) },
23
24
  references: ->(data) { references(data.cop, data.see_objects) }
24
25
  }.freeze
25
26
 
@@ -194,7 +195,7 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
194
195
  def configurations(department, cop, cop_config)
195
196
  header = ['Name', 'Default value', 'Configurable values']
196
197
  configs = cop_config.each_key.reject do |key|
197
- key == 'AllowMultipleStyles' ||
198
+ key == 'AllowMultipleStyles' || key == 'Preview' ||
198
199
  (key != 'SupportedTypes' && key.start_with?('Supported'))
199
200
  end
200
201
  return '' if configs.empty?
@@ -209,6 +210,22 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
209
210
  cop_subsection('Configurable attributes', cop) + to_table(header, content)
210
211
  end
211
212
 
213
+ def preview_defaults(cop)
214
+ cop_config = config.for_cop(cop)
215
+ preview = cop_config['Preview']
216
+ return '' unless preview.is_a?(Hash)
217
+
218
+ header = ['Name', 'Current default', 'Preview default']
219
+ content = preview.map do |name, value|
220
+ [name, format_table_value(cop_config[name]), format_table_value(value)]
221
+ end
222
+
223
+ cop_subsection('Preview defaults', cop) +
224
+ 'These defaults apply under xref:versioning.adoc#preview[Preview] and are ' \
225
+ "expected to become the regular defaults in the next major release.\n\n" +
226
+ to_table(header, content)
227
+ end
228
+
212
229
  def configuration_name(department, name)
213
230
  return name unless name == 'AllowMultilineFinalElement'
214
231
 
@@ -389,7 +406,11 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
389
406
  def cop_status(status)
390
407
  return 'Disabled' unless status
391
408
 
392
- status == 'pending' ? 'Pending' : 'Enabled'
409
+ case status
410
+ when 'pending' then 'Pending'
411
+ when 'preview' then 'Preview'
412
+ else 'Enabled'
413
+ end
393
414
  end
394
415
 
395
416
  # HTML anchor are somewhat limited in what characters they can contain, just