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
@@ -435,7 +435,7 @@ module RuboCop
435
435
 
436
436
  def anon_block_scope_id(anon_block)
437
437
  parent = anon_block.parent
438
- return unless parent&.type?(:any_block, :begin, :call, :casgn, :any_def)
438
+ return unless parent&.type?(:any_block, :begin, :call, :casgn, :ivasgn, :any_def)
439
439
 
440
440
  if (receiver = scope_receiver(parent, anon_block))
441
441
  "#{receiver.source}.#{parent.method_name}"
@@ -63,7 +63,7 @@ module RuboCop
63
63
  class EmptyBlock < Base
64
64
  MSG = 'Empty block detected.'
65
65
 
66
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
66
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler -- an empty block has no parameters to number
67
67
  return if node.body
68
68
  return if allow_empty_lambdas? && node.lambda_or_proc?
69
69
  return if cop_config['AllowComments'] && allow_comment?(node)
@@ -48,7 +48,7 @@ module RuboCop
48
48
  add_offense(node.lhs) do |corrector|
49
49
  # Don't autocorrect `'foo' && return` because having `return` as
50
50
  # the leftmost node can lead to a void value expression syntax error.
51
- next if node.rhs.type?(:return, :break, :next)
51
+ next if void_value_expression?(node.rhs)
52
52
 
53
53
  corrector.replace(node, node.rhs.source)
54
54
  end
@@ -60,7 +60,7 @@ module RuboCop
60
60
  add_offense(node.lhs) do |corrector|
61
61
  # Don't autocorrect `'foo' && return` because having `return` as
62
62
  # the leftmost node can lead to a void value expression syntax error.
63
- next if node.rhs.type?(:return, :break, :next)
63
+ next if void_value_expression?(node.rhs)
64
64
 
65
65
  corrector.replace(node, node.rhs.source)
66
66
  end
@@ -175,6 +175,12 @@ module RuboCop
175
175
 
176
176
  private
177
177
 
178
+ def void_value_expression?(node)
179
+ node = node.children.last while node&.begin_type?
180
+
181
+ node&.type?(:return, :break, :next)
182
+ end
183
+
178
184
  def check_for_literal(node)
179
185
  cond = condition(node)
180
186
  if cond.literal?
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for magic comments placed where Ruby silently ignores them.
7
+ #
8
+ # The `encoding` magic comment is only honored on the first line of a file,
9
+ # or on the second line when the first line is a shebang. Anywhere else it
10
+ # is silently ignored - even directly below another comment or a blank line.
11
+ #
12
+ # Other magic comments (such as `frozen_string_literal`) are honored
13
+ # anywhere before the first token of code, but are ignored after any code,
14
+ # with a warning emitted only when running Ruby with `-w`.
15
+ #
16
+ # A magic comment misplaced ahead of a shebang also renders the shebang
17
+ # ineffective, since a shebang is only recognized on the first line.
18
+ #
19
+ # NOTE: An `encoding` comment that is only preceded by other magic
20
+ # comments is not flagged by this cop; that case is handled by
21
+ # `Lint/OrderedMagicComments`. `shareable_constant_value` is never
22
+ # flagged, as Ruby intentionally allows it mid-file with block scoping.
23
+ #
24
+ # @safety
25
+ # This cop's autocorrection is unsafe because moving the magic comment
26
+ # to its effective position activates it, which changes runtime
27
+ # behavior (e.g. the source encoding or string mutability).
28
+ #
29
+ # @example
30
+ # # bad
31
+ # # Documentation comment
32
+ # # encoding: ascii-8bit
33
+ # puts 'hello'
34
+ #
35
+ # # good
36
+ # # encoding: ascii-8bit
37
+ # # Documentation comment
38
+ # puts 'hello'
39
+ #
40
+ # # bad
41
+ # require 'foo'
42
+ # # frozen_string_literal: true
43
+ #
44
+ # # good
45
+ # # frozen_string_literal: true
46
+ # require 'foo'
47
+ #
48
+ # # bad
49
+ # # frozen_string_literal: true
50
+ # #!/usr/bin/env ruby
51
+ # puts 'hello'
52
+ #
53
+ # # good
54
+ # #!/usr/bin/env ruby
55
+ # # frozen_string_literal: true
56
+ # puts 'hello'
57
+ class MisplacedMagicComment < Base
58
+ include RangeHelp
59
+ extend AutoCorrector
60
+
61
+ MSG_ENCODING = 'The `encoding` magic comment is ignored unless placed on the first ' \
62
+ 'line (or below a shebang on the first line).'
63
+ MSG_AFTER_CODE = 'The `%<directive>s` magic comment is ignored after any code.'
64
+ MSG_ABOVE_SHEBANG = 'A magic comment above a shebang renders the shebang ineffective.'
65
+
66
+ def on_new_investigation
67
+ return if processed_source.buffer.source.empty?
68
+
69
+ check_magic_comment_above_shebang
70
+ processed_source.comments.each { |comment| check_comment(comment) }
71
+ end
72
+
73
+ private
74
+
75
+ def check_comment(comment)
76
+ return unless magic_comment_shaped?(comment.text)
77
+
78
+ magic_comment = MagicComment.parse(comment.text)
79
+ return unless magic_comment.valid?
80
+
81
+ if magic_comment.encoding_specified?
82
+ return unless known_encoding?(magic_comment.encoding)
83
+
84
+ check_encoding_comment(comment)
85
+ elsif magic_comment.frozen_string_literal_specified?
86
+ check_top_block_comment(comment, 'frozen_string_literal')
87
+ end
88
+ end
89
+
90
+ # The Emacs and Vim regexps in `MagicComment` match anywhere inside a
91
+ # comment, which would flag documentation that merely quotes a magic
92
+ # comment (e.g. `# # -*- coding: UTF-8 -*-`). Require the magic
93
+ # comment to start right after the `#`.
94
+ def magic_comment_shaped?(text)
95
+ /\A#(?![^#]*#)/.match?(text)
96
+ end
97
+
98
+ # Prose that happens to open with `Encoding:` parses as an encoding
99
+ # comment (`# Encoding: force given encoding` yields `force`). Ruby
100
+ # ignores a misplaced encoding comment entirely, so a name no encoding
101
+ # answers to means the line is a comment, not a directive.
102
+ def known_encoding?(name)
103
+ Encoding.find(name)
104
+ true
105
+ rescue ArgumentError
106
+ false
107
+ end
108
+
109
+ def shebang?
110
+ processed_source.lines.first.to_s.start_with?('#!')
111
+ end
112
+
113
+ # First line that may carry an effective encoding comment.
114
+ def effective_encoding_line
115
+ shebang? ? 2 : 1
116
+ end
117
+
118
+ def first_code_token
119
+ @first_code_token ||= processed_source.sorted_tokens.find { |token| !token.comment? }
120
+ end
121
+
122
+ def check_encoding_comment(comment)
123
+ line = comment.source_range.line
124
+ return if line == effective_encoding_line && comment_starts_line?(comment)
125
+ # Leave `# frozen_string_literal: true` + `# encoding: x` runs at the
126
+ # very top to Lint/OrderedMagicComments, which already reorders them.
127
+ return if preceded_only_by_magic_comments?(comment)
128
+
129
+ add_offense(comment, message: MSG_ENCODING) do |corrector|
130
+ move_comment(corrector, comment, effective_encoding_line)
131
+ end
132
+ end
133
+
134
+ def check_top_block_comment(comment, directive)
135
+ return unless first_code_token
136
+ return unless comment.source_range.begin_pos > first_code_token.begin_pos
137
+
138
+ message = format(MSG_AFTER_CODE, directive: directive)
139
+ add_offense(comment, message: message) do |corrector|
140
+ move_comment(corrector, comment, effective_encoding_line)
141
+ end
142
+ end
143
+
144
+ def check_magic_comment_above_shebang
145
+ shebang_comment = processed_source.comments.find do |comment|
146
+ comment.source_range.line > 1 && comment.text.start_with?('#!') &&
147
+ comment.source_range.column.zero?
148
+ end
149
+ return unless shebang_comment
150
+ # Only flag when what precedes the shebang is magic comments -
151
+ # a `#!` comment deep inside a file is not an intended shebang.
152
+ return unless preceded_only_by_magic_comments?(shebang_comment)
153
+
154
+ add_offense(shebang_comment, message: MSG_ABOVE_SHEBANG)
155
+ end
156
+
157
+ def preceded_only_by_magic_comments?(comment)
158
+ (1...comment.source_range.line).all? do |line_number|
159
+ text = processed_source.lines[line_number - 1]
160
+ (line_number == 1 && text.start_with?('#!')) || MagicComment.parse(text).valid?
161
+ end
162
+ end
163
+
164
+ def comment_starts_line?(comment)
165
+ processed_source.lines[comment.source_range.line - 1].lstrip.start_with?('#')
166
+ end
167
+
168
+ def move_comment(corrector, comment, target_line)
169
+ removal_range = if comment_starts_line?(comment)
170
+ range_by_whole_lines(comment.source_range, include_final_newline: true)
171
+ else
172
+ range_with_surrounding_space(comment.source_range, side: :left)
173
+ end
174
+ corrector.remove(removal_range)
175
+ target_range = processed_source.buffer.line_range(target_line)
176
+ if comment.source_range.line < target_line
177
+ corrector.insert_after(target_range, "\n#{comment.text}")
178
+ else
179
+ corrector.insert_before(target_range, "#{comment.text}\n")
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable-next Lint/RedundantCopDisableDirective
3
+ # rubocop:disable-next Lint/RedundantCopDisableDirective -- the examples below read as real directives
4
4
  module RuboCop
5
5
  module Cop
6
6
  module Lint
@@ -95,7 +95,7 @@ module RuboCop
95
95
  MSG = 'Method definitions must not be nested. Use `lambda` instead.'
96
96
 
97
97
  def on_def(node)
98
- subject, = *node # rubocop:disable InternalAffairs/NodeDestructuring
98
+ subject, = *node # rubocop:disable InternalAffairs/NodeDestructuring -- the first child differs between `def` and `defs`
99
99
  return if node.defs_type? && allowed_subject_type?(subject)
100
100
 
101
101
  def_ancestor = node.each_ancestor(:any_def).first
@@ -67,7 +67,7 @@ module RuboCop
67
67
 
68
68
  # NOTE: itblock is not handled because this cop is limited to Ruby <= 2.7
69
69
  # via `maximum_target_ruby_version`, so itblock nodes (Ruby 3.4+) are never encountered.
70
- def on_block(node) # rubocop:disable InternalAffairs/ItblockHandler
70
+ def on_block(node) # rubocop:disable InternalAffairs/ItblockHandler -- limited to Ruby <= 2.7, where `it` blocks do not exist
71
71
  return unless node.body
72
72
  return unless unsorted_dir_loop?(node.send_node)
73
73
 
@@ -46,13 +46,29 @@ module RuboCop
46
46
 
47
47
  def valid_first_argument?(first_arg)
48
48
  first_arg.operator_keyword? || first_arg.hash_type? || ternary_expression?(first_arg) ||
49
- compound_range?(first_arg)
49
+ compound_range?(first_arg) || invalid_bare_argument?(first_arg)
50
50
  end
51
51
 
52
52
  def compound_range?(first_arg)
53
53
  first_arg.range_type? && first_arg.parenthesized_call?
54
54
  end
55
55
 
56
+ def invalid_bare_argument?(node)
57
+ node = node.children.first while node&.begin_type? && node.children.one?
58
+ return false unless node
59
+
60
+ keyword_operator?(node) || modifier_expression?(node)
61
+ end
62
+
63
+ def keyword_operator?(node)
64
+ (node.operator_keyword? && node.semantic_operator?) ||
65
+ node.rescue_type? || (node.send_type? && node.prefix_not?)
66
+ end
67
+
68
+ def modifier_expression?(node)
69
+ node.type?(:if, :while, :until) && node.modifier_form?
70
+ end
71
+
56
72
  def chained_calls?(node)
57
73
  first_argument = node.first_argument
58
74
  first_argument.call_type? && (node.children.last&.children&.count || 0) > 1
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The Lint/RedundantCopDisableDirective cop needs to be disabled so as
4
4
  # to be able to provide a (bad) example of a redundant disable.
5
- # rubocop:disable-next Lint/RedundantCopDisableDirective
5
+ # rubocop:disable-next Lint/RedundantCopDisableDirective -- the examples below read as real directives
6
6
  module RuboCop
7
7
  module Cop
8
8
  module Lint
@@ -155,17 +155,29 @@ module RuboCop
155
155
  end
156
156
  end
157
157
 
158
- # A `disable-next` with no statement attached (or misplaced at the end
159
- # of a code line) suppresses nothing, so it is redundant by definition.
158
+ # A next-statement directive with no statement attached (or misplaced
159
+ # at the end of a code line) affects nothing, so it is redundant by
160
+ # definition.
160
161
  def each_detached_next_directive
161
162
  processed_source.comment_config.detached_next_directives.each do |directive|
162
- directive.raw_cop_names.each { |cop| yield directive.comment, cop }
163
+ names = if directive.next?
164
+ directive.signed_args.values.flatten
165
+ else
166
+ directive.raw_cop_names
167
+ end
168
+ names.each { |cop| yield directive.comment, cop }
163
169
  end
164
170
  end
165
171
 
166
172
  def skip_directive?(comment)
173
+ # A cop disabled in the configuration is represented by a synthetic directive that
174
+ # has no comment in the source, so there is nothing to report or to remove.
175
+ # Its range starts at `CONFIG_DISABLED_LINE_RANGE_MIN` and runs to the end of the file,
176
+ # but a `pop` splits it, and the pieces in between are bounded on both sides.
177
+ return true if comment.is_a?(CommentConfig::ConfigDisabledCopDirectiveComment)
178
+
167
179
  directive = DirectiveComment.new(comment)
168
- directive.push? || directive.pop?
180
+ directive.push? || directive.pop? || directive.next?
169
181
  end
170
182
 
171
183
  def find_redundant_directive(cop, comment, line_range, next_range)
@@ -190,11 +202,11 @@ module RuboCop
190
202
 
191
203
  comment = directive_comment(range)
192
204
 
193
- next unless comment
194
- # Comments disabling all cops don't count since it's reasonable
195
- # to disable a few select cops first and then all cops further
196
- # down in the code.
197
- next if all_disabled?(comment)
205
+ # `push`/`next` signed arguments are not analyzed for redundancy
206
+ # (yet), and comments disabling all cops don't count since it's
207
+ # reasonable to disable a few select cops first and then all cops
208
+ # further down in the code.
209
+ next if comment.nil? || skip_directive?(comment) || all_disabled?(comment)
198
210
 
199
211
  redundant =
200
212
  if department_disabled?(cop, comment)
@@ -280,18 +292,20 @@ module RuboCop
280
292
  end
281
293
 
282
294
  def add_offense_for_entire_comment(comment, cops)
283
- location = DirectiveComment.new(comment).range
295
+ directive = DirectiveComment.new(comment)
296
+ location = directive.range
284
297
  cop_names = cops.sort.map { |c| describe(c) }.join(', ')
285
298
 
286
299
  # An unknown cop may just not be loaded in this run (e.g. a custom
287
300
  # cop whose configuration failed to load) - removing its directive
288
301
  # would destroy something that cannot be restored, so only report.
289
302
  # A misplaced EOL `disable-next` should be moved, not deleted.
303
+ message = message(cop_names, enabling: directive.enable_next?)
290
304
  if any_unknown_cop?(cops) || misplaced_next_directive?(comment)
291
- return add_offense(location, message: message(cop_names))
305
+ return add_offense(location, message: message)
292
306
  end
293
307
 
294
- add_offense(location, message: message(cop_names)) do |corrector|
308
+ add_offense(location, message: message) do |corrector|
295
309
  remove_entire_comment(corrector, comment)
296
310
  end
297
311
  end
@@ -333,7 +347,7 @@ module RuboCop
333
347
 
334
348
  def misplaced_next_directive?(comment)
335
349
  directive = DirectiveComment.new(comment)
336
- directive.disable_next? &&
350
+ (directive.disable_next? || directive.next? || directive.enable_next?) &&
337
351
  !processed_source.comment_config.comment_only_line?(directive.line_number)
338
352
  end
339
353
 
@@ -396,8 +410,8 @@ module RuboCop
396
410
  similar ? "`#{cop}` (did you mean `#{similar}`?)" : "`#{cop}` (unknown cop)"
397
411
  end
398
412
 
399
- def message(cop_names)
400
- "Unnecessary disabling of #{cop_names}."
413
+ def message(cop_names, enabling: false)
414
+ "Unnecessary #{enabling ? 'enabling' : 'disabling'} of #{cop_names}."
401
415
  end
402
416
 
403
417
  def all_cop_names
@@ -76,6 +76,7 @@ module RuboCop
76
76
  register_cop :LiteralAssignmentInCondition, "#{__dir__}/lint/literal_assignment_in_condition"
77
77
  register_cop :LiteralInInterpolation, "#{__dir__}/lint/literal_in_interpolation"
78
78
  register_cop :Loop, "#{__dir__}/lint/loop"
79
+ register_cop :MisplacedMagicComment, "#{__dir__}/lint/misplaced_magic_comment"
79
80
  register_cop :MissingCopEnableDirective, "#{__dir__}/lint/missing_cop_enable_directive"
80
81
  register_cop :MissingSuper, "#{__dir__}/lint/missing_super"
81
82
  register_cop :MixedCaseRange, "#{__dir__}/lint/mixed_case_range"
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Lint/RedundantCopDisableDirective needs to be disabled so as
4
4
  # to be able to provide examples of rubocop:disable comments.
5
- # rubocop:disable-next Lint/RedundantCopDisableDirective
5
+ # rubocop:disable-next Lint/RedundantCopDisableDirective -- the examples below read as real directives
6
6
  module RuboCop
7
7
  module Cop
8
8
  module Migration
@@ -66,7 +66,7 @@ module RuboCop
66
66
  end
67
67
 
68
68
  # @deprecated Use processed_source.line_with_comment?(line)
69
- def end_of_line_comment(line) # rubocop:disable Naming/PredicateMethod
69
+ def end_of_line_comment(line) # rubocop:disable Naming/PredicateMethod -- a deprecated shim kept under its original name
70
70
  warn Rainbow(<<~WARNING).yellow, uplevel: 1
71
71
  `end_of_line_comment` is deprecated. Use `processed_source.line_with_comment?` instead.
72
72
  WARNING
@@ -45,7 +45,7 @@ module RuboCop
45
45
  match.captures
46
46
  end
47
47
 
48
- KEYWORDS_REGEX_CACHE = {} # rubocop:disable Style/MutableConstant
48
+ KEYWORDS_REGEX_CACHE = {} # rubocop:disable Style/MutableConstant -- a cache, written to at runtime
49
49
  private_constant :KEYWORDS_REGEX_CACHE
50
50
 
51
51
  def regex
@@ -55,7 +55,7 @@ module RuboCop
55
55
  # Common functionality for checking alignment of hash values.
56
56
  module ValueAlignment
57
57
  def checkable_layout?(node)
58
- !node.pairs_on_same_line? && !node.mixed_delimiters?
58
+ !node.pairs_on_same_line? && !node.mixed_delimiters? && alignable_values?(node)
59
59
  end
60
60
 
61
61
  def deltas(first_pair, current_pair)
@@ -68,6 +68,16 @@ module RuboCop
68
68
 
69
69
  private
70
70
 
71
+ def alignable_values?(node)
72
+ return false if node.pairs.first.value_omission?
73
+
74
+ node.pairs.none? { |pair| value_on_later_line?(pair) }
75
+ end
76
+
77
+ def value_on_later_line?(pair)
78
+ pair.value && pair.key.last_line != pair.value.first_line
79
+ end
80
+
71
81
  def separator_delta(first_pair, current_pair, key_delta)
72
82
  if current_pair.hash_rocket?
73
83
  hash_rocket_delta(first_pair, current_pair) - key_delta
@@ -87,13 +87,17 @@ module RuboCop
87
87
  def ignore_mixed_hash_shorthand_syntax?(hash_node)
88
88
  target_ruby_version <= 3.0 ||
89
89
  !%w[consistent either_consistent].include?(enforced_shorthand_syntax) ||
90
- !hash_node.hash_type?
90
+ !hash_node.hash_type? || hash_rockets_enforced?(hash_node)
91
91
  end
92
92
 
93
93
  def ignore_hash_shorthand_syntax?(pair_node)
94
94
  target_ruby_version <= 3.0 || enforced_shorthand_syntax == 'either' ||
95
95
  %w[consistent either_consistent].include?(enforced_shorthand_syntax) ||
96
- !pair_node.parent.hash_type?
96
+ !pair_node.parent.hash_type? || hash_rockets_enforced?(pair_node.parent)
97
+ end
98
+
99
+ def hash_rockets_enforced?(hash_node)
100
+ style == :hash_rockets || force_hash_rockets?(hash_node.pairs)
97
101
  end
98
102
 
99
103
  def enforced_shorthand_syntax
@@ -38,7 +38,7 @@ module RuboCop
38
38
  (block (send _ :each_with_object (hash)) ...)}
39
39
  PATTERN
40
40
 
41
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
41
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler -- the patterns matched here all name their block parameters
42
42
  on_bad_each_with_object(node) do |*match|
43
43
  handle_possible_offense(node, match, 'each_with_object')
44
44
  end
@@ -182,7 +182,18 @@ module RuboCop
182
182
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
183
183
  # rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength
184
184
  def relevant_assignment_lines(line_range)
185
- relevant_lines(line_range) { |line_number| assignment_lines.include?(line_number) }
185
+ relevant_lines(line_range, interrupting_operator_lines) do |line_number|
186
+ assignment_lines.include?(line_number)
187
+ end
188
+ end
189
+
190
+ def interrupting_operator_lines
191
+ @interrupting_operator_lines ||=
192
+ processed_source.tokens.each_with_object(Set.new) do |token, lines|
193
+ next unless ASSIGNMENT_OR_COMPARISON_TOKENS.include?(token.type)
194
+
195
+ lines << token.line unless assignment_lines.include?(token.line)
196
+ end
186
197
  end
187
198
 
188
199
  def alignment_lines(line_number)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Cop # rubocop:disable Style/Documentation
4
+ module Cop # rubocop:disable Style/Documentation -- a namespace that only autoloads the mixins below
5
5
  # Autoloads mixin modules included by cops. Mixins are autoloaded to reduce the number of
6
6
  # requires because they're used only when the relevant cop class is loaded.
7
7
 
@@ -14,7 +14,7 @@ module RuboCop
14
14
  autoload :AllowedReceivers, "#{__dir__}/mixin/allowed_receivers"
15
15
  autoload :ForbiddenIdentifiers, "#{__dir__}/mixin/forbidden_identifiers"
16
16
  autoload :ForbiddenPattern, "#{__dir__}/mixin/forbidden_pattern"
17
- autoload :AutoCorrector, "#{__dir__}/mixin/auto_corrector" # rubocop:todo Naming/InclusiveLanguage
17
+ autoload :AutoCorrector, "#{__dir__}/mixin/auto_corrector" # rubocop:todo Naming/InclusiveLanguage -- the file is named `auto_corrector.rb`
18
18
  autoload :CheckAssignment, "#{__dir__}/mixin/check_assignment"
19
19
  autoload :CheckLineBreakable, "#{__dir__}/mixin/check_line_breakable"
20
20
  autoload :CheckSingleLineSuitability, "#{__dir__}/mixin/check_single_line_suitability"
@@ -62,7 +62,10 @@ module RuboCop
62
62
  MSG = 'Use %<style>s block forwarding.'
63
63
 
64
64
  def self.autocorrect_incompatible_with
65
- [Lint::AmbiguousOperator, Style::ArgumentsForwarding, Style::ExplicitBlockArgument]
65
+ [
66
+ Lint::AmbiguousOperator, Style::ArgumentsForwarding,
67
+ Style::ExplicitBlockArgument, Style::MethodDefParentheses
68
+ ]
66
69
  end
67
70
 
68
71
  def on_def(node)
@@ -38,7 +38,7 @@ module RuboCop
38
38
  class BlockParameterName < Base
39
39
  include UncommunicativeName
40
40
 
41
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
41
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler -- there are no parameter names to check
42
42
  return unless node.arguments?
43
43
 
44
44
  check(node, node.arguments)
@@ -280,6 +280,12 @@ module RuboCop
280
280
  config.enabled_new_cop?(cop_name)
281
281
  end
282
282
 
283
+ # Preview cops are opt-in and stay silent otherwise, so unlike pending cops
284
+ # they are never reported as needing a decision.
285
+ def enabled_preview_cop?(cop_cfg, config)
286
+ cop_cfg.fetch('Enabled') == 'preview' && config.preview?(@options)
287
+ end
288
+
283
289
  def names
284
290
  clear_enrollment_queue
285
291
  @cops_by_badge.keys.map(&:to_s)
@@ -420,7 +426,9 @@ module RuboCop
420
426
  # caching which expects cop names or cop classes as keys.
421
427
  cfg = config.for_cop(cop_name)
422
428
 
423
- cop_enabled = cfg.fetch('Enabled') == true || enabled_pending_cop?(cfg, config, cop_name)
429
+ cop_enabled = cfg.fetch('Enabled') == true ||
430
+ enabled_pending_cop?(cfg, config, cop_name) ||
431
+ enabled_preview_cop?(cfg, config)
424
432
 
425
433
  if options.fetch(:safe, false)
426
434
  cop_enabled && cfg.fetch('Safe', true)
@@ -21,6 +21,18 @@ module RuboCop
21
21
  # any of `:info`, `:refactor`, `:convention`, `:warning`, `:error` or `:fatal`.
22
22
  attr_reader :name
23
23
 
24
+ # The lowest severity that makes a run fail: `--fail-level` when given,
25
+ # otherwise `AllCops: FailLevel`. `autocorrect` is a pseudo level on the
26
+ # command line that gates on corrections instead, so it leaves the
27
+ # threshold at the configured one.
28
+ def self.minimum_to_fail(options, config)
29
+ name = options[:fail_level]
30
+ if name.nil? || name == :autocorrect
31
+ name = (config&.for_all_cops&.[]('FailLevel') || :refactor).to_sym
32
+ end
33
+ new(name)
34
+ end
35
+
24
36
  def self.name_from_code(code)
25
37
  name = code.to_sym
26
38
  CODE_TABLE[name] || name
@@ -316,7 +316,7 @@ module RuboCop
316
316
  end
317
317
 
318
318
  def select_grouped_def_nodes(node)
319
- node.right_siblings.take_while do |sibling|
319
+ node.right_siblings.compact.take_while do |sibling|
320
320
  !(sibling.send_type? && sibling.bare_access_modifier_declaration?)
321
321
  end.select(&:def_type?)
322
322
  end
@@ -347,7 +347,8 @@ module RuboCop
347
347
  end
348
348
 
349
349
  def remove_modifier_node_within_begin(corrector, modifier_node, begin_node)
350
- def_node = begin_node.children[begin_node.children.index(modifier_node) + 1]
350
+ modifier_index = begin_node.children.find_index { |child| child.equal?(modifier_node) }
351
+ def_node = begin_node.children[modifier_index + 1]
351
352
  # Stop the removal range at the first comment that precedes the def, if
352
353
  # any exist. Without this, comments between the modifier and the def are
353
354
  # dropped because they fall inside the removed range.
@@ -202,7 +202,7 @@ module RuboCop
202
202
  rest_arg, kwrest_arg, block_arg = *forwardable_args
203
203
  registered_block_arg_offense = false
204
204
 
205
- send_classifications.each do |send_node, c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength
205
+ send_classifications.each do |send_node, c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength -- the block parameter list does not wrap
206
206
  if !forward_rest && !forward_kwrest && c != :all_anonymous
207
207
  if allow_anonymous_forwarding_in_block?(forward_block_arg)
208
208
  register_forward_block_arg_offense(!forward_rest, node.arguments, block_arg)
@@ -232,7 +232,7 @@ module RuboCop
232
232
 
233
233
  rest_arg, kwrest_arg, block_arg = *forwardable_args
234
234
 
235
- send_classifications.each do |send_node, _c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength
235
+ send_classifications.each do |send_node, _c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength -- the block parameter list does not wrap
236
236
  if allow_anonymous_forwarding_in_block?(forward_rest)
237
237
  register_forward_args_offense(def_node.arguments, rest_arg)
238
238
  register_forward_args_offense(send_node, forward_rest)