rubocop 1.88.2 → 1.90.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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +139 -16
  3. data/exe/rubocop +1 -1
  4. data/lib/rubocop/cached_data.rb +7 -2
  5. data/lib/rubocop/cli/command/execute_runner.rb +31 -0
  6. data/lib/rubocop/cli.rb +2 -4
  7. data/lib/rubocop/comment_config/directive_range.rb +32 -0
  8. data/lib/rubocop/comment_config/disable_next.rb +97 -0
  9. data/lib/rubocop/comment_config.rb +81 -49
  10. data/lib/rubocop/config.rb +76 -5
  11. data/lib/rubocop/config_loader.rb +1 -2
  12. data/lib/rubocop/config_loader_resolver.rb +1 -2
  13. data/lib/rubocop/config_validator.rb +39 -3
  14. data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
  15. data/lib/rubocop/cop/badge.rb +8 -0
  16. data/lib/rubocop/cop/base.rb +40 -6
  17. data/lib/rubocop/cop/bundler.rb +19 -0
  18. data/lib/rubocop/cop/corrector.rb +4 -4
  19. data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
  20. data/lib/rubocop/cop/gemspec.rb +22 -0
  21. data/lib/rubocop/cop/generator/registration_injector.rb +188 -0
  22. data/lib/rubocop/cop/generator.rb +4 -0
  23. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -2
  24. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -2
  25. data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
  26. data/lib/rubocop/cop/layout/class_structure.rb +108 -26
  27. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -2
  28. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +63 -5
  29. data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
  30. data/lib/rubocop/cop/layout/extra_spacing.rb +105 -3
  31. data/lib/rubocop/cop/layout/first_argument_indentation.rb +21 -1
  32. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
  33. data/lib/rubocop/cop/layout/indentation_width.rb +3 -1
  34. data/lib/rubocop/cop/layout/leading_comment_space.rb +27 -0
  35. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -2
  36. data/lib/rubocop/cop/layout/line_length.rb +84 -12
  37. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
  38. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +12 -2
  39. data/lib/rubocop/cop/layout/redundant_line_break.rb +77 -0
  40. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -2
  41. data/lib/rubocop/cop/layout/space_around_operators.rb +7 -0
  42. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +31 -34
  43. data/lib/rubocop/cop/layout.rb +114 -0
  44. data/lib/rubocop/cop/lazy_loader.rb +41 -0
  45. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +9 -7
  46. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +71 -0
  47. data/lib/rubocop/cop/lint/ambiguous_range.rb +1 -2
  48. data/lib/rubocop/cop/lint/argument_mismatch.rb +89 -0
  49. data/lib/rubocop/cop/lint/constant_reassignment.rb +18 -21
  50. data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
  51. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -8
  52. data/lib/rubocop/cop/lint/deprecated_reference.rb +194 -0
  53. data/lib/rubocop/cop/lint/duplicate_methods.rb +300 -22
  54. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -2
  55. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -2
  56. data/lib/rubocop/cop/lint/inherit_exception.rb +53 -7
  57. data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
  58. data/lib/rubocop/cop/lint/literal_as_condition.rb +8 -6
  59. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +4 -7
  60. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
  61. data/lib/rubocop/cop/lint/missing_super.rb +31 -1
  62. data/lib/rubocop/cop/lint/name_typo.rb +270 -0
  63. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -4
  64. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +98 -19
  65. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +47 -7
  66. data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -2
  67. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -2
  68. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -2
  69. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +1 -2
  70. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -2
  71. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +8 -2
  72. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -4
  73. data/lib/rubocop/cop/lint/super_argument_mismatch.rb +179 -0
  74. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +1 -2
  75. data/lib/rubocop/cop/lint/to_enum_arguments.rb +38 -3
  76. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
  77. data/lib/rubocop/cop/lint/unused_private_method.rb +234 -0
  78. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -2
  79. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
  80. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +36 -0
  81. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  82. data/lib/rubocop/cop/lint/useless_rescue.rb +1 -2
  83. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
  84. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
  85. data/lib/rubocop/cop/lint/void.rb +18 -8
  86. data/lib/rubocop/cop/lint.rb +178 -0
  87. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
  88. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  89. data/lib/rubocop/cop/metrics.rb +31 -0
  90. data/lib/rubocop/cop/migration/department_name.rb +1 -6
  91. data/lib/rubocop/cop/migration.rb +13 -0
  92. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
  93. data/lib/rubocop/cop/mixin/comments_help.rb +1 -2
  94. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -2
  95. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +13 -5
  96. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +23 -6
  97. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +1 -2
  98. data/lib/rubocop/cop/mixin/hash_subset.rb +1 -2
  99. data/lib/rubocop/cop/mixin/indexed_method_arity.rb +94 -0
  100. data/lib/rubocop/cop/mixin/line_length_help.rb +7 -1
  101. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +51 -6
  102. data/lib/rubocop/cop/mixin/project_index_help.rb +140 -0
  103. data/lib/rubocop/cop/mixin/range_help.rb +1 -2
  104. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
  105. data/lib/rubocop/cop/mixin/statement_modifier.rb +46 -4
  106. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
  107. data/lib/rubocop/cop/mixin.rb +82 -77
  108. data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -0
  109. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +16 -0
  110. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -4
  111. data/lib/rubocop/cop/naming/method_name.rb +5 -2
  112. data/lib/rubocop/cop/naming/predicate_prefix.rb +33 -5
  113. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
  114. data/lib/rubocop/cop/naming/variable_number.rb +3 -1
  115. data/lib/rubocop/cop/naming.rb +31 -0
  116. data/lib/rubocop/cop/offense.rb +37 -10
  117. data/lib/rubocop/cop/registry.rb +180 -46
  118. data/lib/rubocop/cop/security.rb +19 -0
  119. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -2
  120. data/lib/rubocop/cop/style/arguments_forwarding.rb +3 -6
  121. data/lib/rubocop/cop/style/array_first_last.rb +1 -2
  122. data/lib/rubocop/cop/style/array_intersect.rb +24 -5
  123. data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -2
  124. data/lib/rubocop/cop/style/block_delimiters.rb +3 -6
  125. data/lib/rubocop/cop/style/case_like_if.rb +2 -4
  126. data/lib/rubocop/cop/style/class_and_module_children.rb +79 -20
  127. data/lib/rubocop/cop/style/combinable_loops.rb +1 -2
  128. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -2
  129. data/lib/rubocop/cop/style/conditional_assignment.rb +23 -2
  130. data/lib/rubocop/cop/style/constant_visibility.rb +1 -2
  131. data/lib/rubocop/cop/style/copyright.rb +5 -1
  132. data/lib/rubocop/cop/style/directive_scope.rb +171 -0
  133. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +76 -12
  134. data/lib/rubocop/cop/style/documentation.rb +47 -0
  135. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
  136. data/lib/rubocop/cop/style/endless_method.rb +0 -10
  137. data/lib/rubocop/cop/style/format_string_token.rb +1 -2
  138. data/lib/rubocop/cop/style/guard_clause.rb +1 -2
  139. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -2
  140. data/lib/rubocop/cop/style/hash_syntax.rb +1 -2
  141. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -4
  142. data/lib/rubocop/cop/style/if_inside_else.rb +1 -2
  143. data/lib/rubocop/cop/style/if_unless_modifier.rb +8 -50
  144. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
  145. data/lib/rubocop/cop/style/lambda.rb +4 -0
  146. data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
  147. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +22 -7
  148. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
  149. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +30 -0
  150. data/lib/rubocop/cop/style/multiline_if_then.rb +7 -1
  151. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -2
  152. data/lib/rubocop/cop/style/multiple_comparison.rb +33 -22
  153. data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
  154. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -2
  155. data/lib/rubocop/cop/style/negative_array_index.rb +3 -3
  156. data/lib/rubocop/cop/style/nil_comparison.rb +1 -2
  157. data/lib/rubocop/cop/style/operator_method_call.rb +1 -2
  158. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -2
  159. data/lib/rubocop/cop/style/redundant_assignment.rb +12 -2
  160. data/lib/rubocop/cop/style/redundant_condition.rb +2 -4
  161. data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
  162. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +1 -2
  163. data/lib/rubocop/cop/style/redundant_each.rb +1 -2
  164. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +9 -0
  165. data/lib/rubocop/cop/style/redundant_format.rb +14 -7
  166. data/lib/rubocop/cop/style/redundant_line_continuation.rb +78 -135
  167. data/lib/rubocop/cop/style/redundant_parentheses.rb +65 -67
  168. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -2
  169. data/lib/rubocop/cop/style/redundant_return.rb +1 -2
  170. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
  171. data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -2
  172. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -2
  173. data/lib/rubocop/cop/style/safe_navigation.rb +11 -2
  174. data/lib/rubocop/cop/style/sample.rb +11 -6
  175. data/lib/rubocop/cop/style/select_by_regexp.rb +1 -2
  176. data/lib/rubocop/cop/style/semicolon.rb +2 -4
  177. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  178. data/lib/rubocop/cop/style/sole_nested_conditional.rb +21 -5
  179. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
  180. data/lib/rubocop/cop/style/static_class.rb +21 -0
  181. data/lib/rubocop/cop/style/string_concatenation.rb +18 -5
  182. data/lib/rubocop/cop/style/super_arguments.rb +1 -2
  183. data/lib/rubocop/cop/style/symbol_proc.rb +1 -2
  184. data/lib/rubocop/cop/style/time_now.rb +41 -0
  185. data/lib/rubocop/cop/style/yoda_condition.rb +1 -2
  186. data/lib/rubocop/cop/style.rb +312 -0
  187. data/lib/rubocop/cop/team.rb +59 -6
  188. data/lib/rubocop/cop/util.rb +2 -4
  189. data/lib/rubocop/cop/variable_force/variable.rb +19 -8
  190. data/lib/rubocop/cops_documentation_generator.rb +4 -7
  191. data/lib/rubocop/directive_comment.rb +39 -3
  192. data/lib/rubocop/feature_loader.rb +0 -6
  193. data/lib/rubocop/formatter/emacs_style_formatter.rb +3 -1
  194. data/lib/rubocop/formatter/html_formatter.rb +2 -4
  195. data/lib/rubocop/formatter/json_formatter.rb +11 -6
  196. data/lib/rubocop/formatter/junit_formatter.rb +26 -9
  197. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -2
  198. data/lib/rubocop/formatter/simple_text_formatter.rb +4 -3
  199. data/lib/rubocop/formatter/tap_formatter.rb +3 -1
  200. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
  201. data/lib/rubocop/lockfile.rb +18 -0
  202. data/lib/rubocop/lsp/diagnostic.rb +4 -8
  203. data/lib/rubocop/lsp/routes.rb +89 -22
  204. data/lib/rubocop/lsp/runtime.rb +4 -0
  205. data/lib/rubocop/lsp/server.rb +4 -0
  206. data/lib/rubocop/lsp/stdin_runner.rb +20 -0
  207. data/lib/rubocop/mcp/server.rb +1 -2
  208. data/lib/rubocop/options.rb +10 -6
  209. data/lib/rubocop/path_util.rb +1 -2
  210. data/lib/rubocop/pending_cops_reporter.rb +1 -6
  211. data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
  212. data/lib/rubocop/project_index_loader.rb +16 -0
  213. data/lib/rubocop/rake_task.rb +1 -2
  214. data/lib/rubocop/result_cache.rb +6 -4
  215. data/lib/rubocop/rspec/expect_offense.rb +2 -4
  216. data/lib/rubocop/rspec/shared_contexts.rb +16 -2
  217. data/lib/rubocop/runner.rb +71 -43
  218. data/lib/rubocop/server/cache.rb +11 -7
  219. data/lib/rubocop/server/cli.rb +2 -4
  220. data/lib/rubocop/server.rb +14 -0
  221. data/lib/rubocop/target_finder.rb +4 -1
  222. data/lib/rubocop/target_ruby.rb +1 -1
  223. data/lib/rubocop/todo_audit.rb +111 -0
  224. data/lib/rubocop/version.rb +3 -5
  225. data/lib/rubocop.rb +20 -626
  226. data/lib/ruby_lsp/rubocop/addon.rb +1 -2
  227. metadata +28 -5
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ class CommentConfig
5
+ # Handling of `disable-next` directives: computing the statement scope
6
+ # they apply to and tracking directives that attached to nothing.
7
+ # The scope is the whole statement starting on the next line bearing
8
+ # code. Comment-only lines between the directive and the code chain (so
9
+ # several directives can stack), while a blank line breaks the attachment.
10
+ # @api private
11
+ module DisableNext
12
+ # `disable-next` directives that suppress nothing: nothing follows
13
+ # them, or they sit at the end of a code line.
14
+ def detached_next_directives
15
+ cop_disabled_line_ranges # ensure the analysis that collects them ran
16
+ @detached_next_directives
17
+ end
18
+
19
+ # The line range of the statement a `disable-next` directive on the
20
+ # given line scopes to (or would scope to), or `nil` when no statement
21
+ # is attached. A directive is only honored on a comment-only line.
22
+ # Stacked directives share the target, so the computation is memoized.
23
+ # @api private
24
+ def statement_scope_after(line)
25
+ return nil unless comment_only_line?(line)
26
+
27
+ code_line = attached_code_line(line)
28
+ return nil unless code_line
29
+
30
+ (@statement_bounds ||= {})[code_line] ||= statement_bounds_at(code_line)
31
+ end
32
+
33
+ private
34
+
35
+ def apply_disable_next(analyses, directive)
36
+ bounds = statement_scope_after(directive.line_number)
37
+ return @detached_next_directives << directive unless bounds
38
+
39
+ range = DirectiveRange.new(bounds.begin, bounds.end, directive)
40
+ directive.cop_names.each do |cop_name|
41
+ add_next_range(analyses, qualified_cop_name(cop_name), range)
42
+ end
43
+ end
44
+
45
+ def add_next_range(analyses, cop_name, range)
46
+ analysis = analyses[cop_name]
47
+ analyses[cop_name] = CopAnalysis.new(analysis.line_ranges + [range],
48
+ analysis.start_line_number, analysis.start_directive)
49
+ end
50
+
51
+ def attached_code_line(directive_line)
52
+ ((directive_line + 1)..processed_source.lines.size).each do |line|
53
+ return line unless comment_only_line?(line)
54
+ return nil if processed_source[line - 1].blank?
55
+ end
56
+
57
+ nil
58
+ end
59
+
60
+ def statement_bounds_at(line)
61
+ statement = statement_starting_at(line)
62
+ # A code line where no statement starts (e.g. a lone `end`) scopes
63
+ # the directive to that line alone.
64
+ return (line..line) unless statement
65
+
66
+ (line..statement_end_line(statement))
67
+ end
68
+
69
+ def statement_start_line?(node, line)
70
+ # Synthetic nodes (e.g. parentheses-less argument lists) have no
71
+ # source range. `begin` nodes are sequences of statements, not
72
+ # statements themselves - taking one would scope the directive to
73
+ # everything up to the last statement of the sequence.
74
+ node.source_range && node.first_line == line && !node.begin_type?
75
+ end
76
+
77
+ def statement_starting_at(line)
78
+ ast = processed_source.ast
79
+ return nil unless ast
80
+
81
+ ast.each_node.select { |node| statement_start_line?(node, line) }.max_by(&:last_line)
82
+ end
83
+
84
+ def statement_end_line(statement)
85
+ statement.each_node.filter_map do |node|
86
+ next unless node.source_range
87
+
88
+ heredoc?(node) ? node.loc.heredoc_end.line : node.last_line
89
+ end.max
90
+ end
91
+
92
+ def heredoc?(node)
93
+ node.respond_to?(:heredoc?) && node.heredoc?
94
+ end
95
+ end
96
+ end
97
+ end
@@ -5,6 +5,7 @@ module RuboCop
5
5
  # and provides a way to check if each cop is enabled at arbitrary line.
6
6
  class CommentConfig
7
7
  extend SimpleForwardable
8
+ include DisableNext
8
9
 
9
10
  CONFIG_DISABLED_LINE_RANGE_MIN = -Float::INFINITY
10
11
 
@@ -25,8 +26,6 @@ module RuboCop
25
26
  end
26
27
  end
27
28
 
28
- CopAnalysis = Struct.new(:line_ranges, :start_line_number)
29
-
30
29
  attr_reader :processed_source
31
30
 
32
31
  def_delegators :@processed_source, :config, :registry
@@ -35,14 +34,25 @@ module RuboCop
35
34
  @processed_source = processed_source
36
35
  @no_directives = !processed_source.raw_source.include?('rubocop')
37
36
  @stack = []
37
+ @detached_next_directives = []
38
38
  end
39
39
 
40
40
  def cop_enabled_at_line?(cop, line_number)
41
+ cop_enabled_at_lines?(cop, line_number, line_number)
42
+ end
43
+
44
+ # Whether the cop is enabled for all of the given line span. The cop
45
+ # counts as disabled for the span when any of its disabled ranges
46
+ # overlaps it, so that a directive on any line of a multi-line offense
47
+ # suppresses the offense.
48
+ def cop_enabled_at_lines?(cop, first_line, last_line)
41
49
  cop = cop.cop_name if cop.respond_to?(:cop_name)
42
50
  disabled_line_ranges = cop_disabled_line_ranges[cop]
43
51
  return true unless disabled_line_ranges
44
52
 
45
- disabled_line_ranges.none? { |range| range.include?(line_number) }
53
+ disabled_line_ranges.none? do |range|
54
+ range.end >= first_line && range.begin <= last_line
55
+ end
46
56
  end
47
57
 
48
58
  def cop_opted_in?(cop)
@@ -55,8 +65,8 @@ module RuboCop
55
65
 
56
66
  def extra_enabled_comments
57
67
  disable_count = Hash.new(0)
58
- registry.disabled(config).each do |cop|
59
- disable_count[cop.cop_name] += 1
68
+ registry.disabled_names(config).each do |cop_name|
69
+ disable_count[cop_name] += 1
60
70
  end
61
71
  extra_enabled_comments_with_names(extras: Hash.new { |h, k| h[k] = [] }, names: disable_count)
62
72
  end
@@ -65,12 +75,32 @@ module RuboCop
65
75
  non_comment_token_line_numbers.none?(line_number)
66
76
  end
67
77
 
78
+ # The names of the cops that are opted in by an `enable` comment directive,
79
+ # used to mobilize cops disabled in the config on demand.
80
+ #
81
+ # @api private
82
+ # @return [Set<String>]
83
+ def opt_in_cops
84
+ @opt_in_cops ||= begin
85
+ cops = Set.new
86
+ each_directive do |directive|
87
+ next unless directive.enabled?
88
+ next if directive.all_cops?
89
+
90
+ cops.merge(directive.raw_cop_names)
91
+ end
92
+ cops
93
+ end
94
+ end
95
+
68
96
  private
69
97
 
70
98
  def extra_enabled_comments_with_names(extras:, names:)
71
99
  each_directive do |directive|
72
100
  next unless comment_only_line?(directive.line_number)
73
- next if directive.push? || directive.pop?
101
+ # Push/pop and next-statement directives close themselves, so they
102
+ # play no part in the disable/enable pairing.
103
+ next if directive.push? || directive.pop? || directive.disable_next?
74
104
 
75
105
  if directive.enabled_all?
76
106
  handle_enable_all(directive, names, extras)
@@ -82,20 +112,7 @@ module RuboCop
82
112
  extras
83
113
  end
84
114
 
85
- def opt_in_cops
86
- @opt_in_cops ||= begin
87
- cops = Set.new
88
- each_directive do |directive|
89
- next unless directive.enabled?
90
- next if directive.all_cops?
91
-
92
- cops.merge(directive.raw_cop_names)
93
- end
94
- cops
95
- end
96
- end
97
-
98
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
115
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
99
116
  def analyze
100
117
  return {} if @no_directives
101
118
 
@@ -106,9 +123,11 @@ module RuboCop
106
123
  if directive.push?
107
124
  restore_point = analyses.transform_values(&:dup)
108
125
  @stack.push(restore_point)
109
- apply_push(analyses, resolve_push_cops(directive), directive.line_number)
126
+ apply_push(analyses, resolve_push_cops(directive), directive)
110
127
  elsif directive.pop?
111
128
  pop_state(analyses, directive.line_number) if @stack.any?
129
+ elsif directive.disable_next?
130
+ apply_disable_next(analyses, directive)
112
131
  else
113
132
  directive.cop_names.each do |cop_name|
114
133
  cop_name = qualified_cop_name(cop_name)
@@ -119,10 +138,11 @@ module RuboCop
119
138
 
120
139
  analyses.each_with_object({}) do |element, hash|
121
140
  cop_name, analysis = *element
141
+ next if prevent_directive_disabling?(cop_name)
142
+
122
143
  hash[cop_name] = cop_line_ranges(analysis)
123
144
  end
124
145
  end
125
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
126
146
 
127
147
  def resolve_push_cops(directive)
128
148
  directive.push_args.transform_values do |names|
@@ -136,37 +156,41 @@ module RuboCop
136
156
  cops.map { |c| qualified_cop_name(c) }
137
157
  end
138
158
 
139
- def apply_push(analyses, resolved_cops, line)
159
+ def apply_push(analyses, resolved_cops, directive)
140
160
  resolved_cops.each do |op, cops|
141
- cops.each { |cop| apply_cop_op(analyses, op, cop, line) }
161
+ cops.each { |cop| apply_cop_op(analyses, op, cop, directive) }
142
162
  end
143
163
  end
144
164
 
145
- def apply_cop_op(analyses, operation, cop, line)
165
+ def apply_cop_op(analyses, operation, cop, directive)
146
166
  analysis = analyses[cop]
167
+ line = directive.line_number
147
168
  if operation == '-' && !analysis.start_line_number
148
- analyses[cop] = CopAnalysis.new(analysis.line_ranges, line)
169
+ analyses[cop] = CopAnalysis.new(analysis.line_ranges, line, directive)
149
170
  elsif operation == '+' && analysis.start_line_number
150
- analyses[cop] =
151
- CopAnalysis.new(analysis.line_ranges + [analysis.start_line_number..line], nil)
171
+ analyses[cop] = CopAnalysis.new(analysis.close(line), nil)
152
172
  end
153
173
  end
154
174
 
155
175
  def pop_state(analyses, line)
156
176
  restore_point = @stack.pop
157
177
  (restore_point.keys | analyses.keys).each do |cop|
158
- current = analyses[cop]
159
- new_range = current.start_line_number ? [current.start_line_number..(line - 1)] : []
160
- new_start = restore_point[cop]&.start_line_number ? line : nil
161
- analyses[cop] = CopAnalysis.new(current.line_ranges + new_range, new_start)
178
+ analyses[cop] = popped_analysis(analyses[cop], restore_point[cop], line)
162
179
  end
163
180
  end
164
181
 
182
+ def popped_analysis(current, restored, line)
183
+ ranges = current.close(line - 1)
184
+ return CopAnalysis.new(ranges, nil) unless restored&.start_line_number
185
+
186
+ CopAnalysis.new(ranges, line, restored.start_directive)
187
+ end
188
+
165
189
  def inject_disabled_cops_directives(analyses)
166
- registry.disabled(config).each do |cop|
167
- analyses[cop.cop_name] = analyze_cop(
168
- analyses[cop.cop_name],
169
- DirectiveComment.new(ConfigDisabledCopDirectiveComment.new(cop.cop_name))
190
+ registry.disabled_names(config).each do |cop_name|
191
+ analyses[cop_name] = analyze_cop(
192
+ analyses[cop_name],
193
+ DirectiveComment.new(ConfigDisabledCopDirectiveComment.new(cop_name))
170
194
  )
171
195
  end
172
196
  end
@@ -186,27 +210,21 @@ module RuboCop
186
210
  return analysis unless directive.disabled?
187
211
 
188
212
  line = directive.line_number
189
- CopAnalysis.new(analysis.line_ranges + [(line..line)], analysis.start_line_number)
213
+ range = DirectiveRange.new(line, line, directive)
214
+ CopAnalysis.new(analysis.line_ranges + [range], analysis.start_line_number,
215
+ analysis.start_directive)
190
216
  end
191
217
 
192
218
  def analyze_disabled(analysis, directive)
193
- line = directive.line_number
194
- start_line = analysis.start_line_number
195
- new_ranges = start_line ? analysis.line_ranges + [start_line..line] : analysis.line_ranges
196
- CopAnalysis.new(new_ranges, line)
219
+ CopAnalysis.new(analysis.close(directive.line_number), directive.line_number, directive)
197
220
  end
198
221
 
199
222
  def analyze_rest(analysis, directive)
200
- line = directive.line_number
201
- start_line = analysis.start_line_number
202
- new_ranges = start_line ? analysis.line_ranges + [start_line..line] : analysis.line_ranges
203
- CopAnalysis.new(new_ranges, nil)
223
+ CopAnalysis.new(analysis.close(directive.line_number), nil)
204
224
  end
205
225
 
206
226
  def cop_line_ranges(analysis)
207
- return analysis.line_ranges unless analysis.start_line_number
208
-
209
- analysis.line_ranges + [(analysis.start_line_number..Float::INFINITY)]
227
+ analysis.close(Float::INFINITY)
210
228
  end
211
229
 
212
230
  def each_directive
@@ -219,7 +237,21 @@ module RuboCop
219
237
  end
220
238
 
221
239
  def qualified_cop_name(cop_name)
222
- Cop::Registry.qualified_cop_name(cop_name.strip, processed_source.file_path)
240
+ # A directive naming a cop under the wrong department is not honored -
241
+ # the mistake is surfaced by `Lint/RedundantCopDisableDirective` (with a
242
+ # did-you-mean hint) instead of a stderr warning that is easy to miss
243
+ # and disappears entirely on cached runs.
244
+ Cop::Registry.qualified_cop_name(cop_name.strip, processed_source.file_path,
245
+ correct_namespace: false)
246
+ end
247
+
248
+ # `Style/DisableCopsWithinSourceCodeDirective` cannot be disabled via
249
+ # directive comments when it is explicitly enabled with `Enabled: true`.
250
+ # This prevents users from bypassing the cop by writing a disable
251
+ # directive that targets this cop itself.
252
+ def prevent_directive_disabling?(cop_name)
253
+ cop_name == DirectiveComment::STYLE_DISABLE_COPS_DIRECTIVE_COP &&
254
+ config.dig(cop_name, 'Enabled') == true
223
255
  end
224
256
 
225
257
  def non_comment_token_line_numbers
@@ -2,7 +2,7 @@
2
2
 
3
3
  # FIXME: Moving Rails department code to RuboCop Rails will remove
4
4
  # the following rubocop:disable comment.
5
- # rubocop:disable Metrics/ClassLength
5
+ # rubocop:disable-next Metrics/ClassLength
6
6
  module RuboCop
7
7
  # This class represents the configuration of the RuboCop application
8
8
  # and all its cops. A Config is associated with a YAML configuration
@@ -27,7 +27,7 @@ module RuboCop
27
27
  config
28
28
  end
29
29
 
30
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
30
+ # rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
31
31
  def initialize(hash = RuboCop::ConfigLoader.default_configuration, loaded_path = nil)
32
32
  @loaded_path = loaded_path
33
33
  @for_cop = Hash.new do |h, cop|
@@ -72,7 +72,6 @@ module RuboCop
72
72
  @badge_config_cache = {}.compare_by_identity
73
73
  @clusivity_config_exists_cache = {}
74
74
  end
75
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
76
75
 
77
76
  def loaded_plugins
78
77
  @loaded_plugins ||= ConfigLoader.loaded_plugins
@@ -168,7 +167,11 @@ module RuboCop
168
167
  department_config = self[badge.department_name]
169
168
  cop_config = for_cop(badge.to_s)
170
169
  if department_config
171
- department_config.merge(cop_config)
170
+ merged_config = department_config.merge(cop_config)
171
+ if department_config['Exclude'] && cop_config['Exclude']
172
+ merged_config['Exclude'] = department_config['Exclude'] | cop_config['Exclude']
173
+ end
174
+ merged_config
172
175
  else
173
176
  cop_config
174
177
  end
@@ -209,6 +212,20 @@ module RuboCop
209
212
  for_all_cops['NewCops'] == 'enable'
210
213
  end
211
214
 
215
+ # Whether the given pending cop should be enabled, based on the `NewCops` setting of
216
+ # its department (if any) or of `AllCops`. A department may set `NewCops` to `enable`,
217
+ # `disable`, `pending`, or a version, in which case pending cops added in that version
218
+ # or earlier are enabled.
219
+ def enabled_new_cop?(qualified_cop_name)
220
+ setting = new_cops_setting_for(qualified_cop_name)
221
+
222
+ case setting.to_s
223
+ when 'enable' then true
224
+ when '', 'pending', 'disable' then false
225
+ else new_cops_version_covers?(setting, qualified_cop_name)
226
+ end
227
+ end
228
+
212
229
  def active_support_extensions_enabled?
213
230
  for_all_cops['ActiveSupportExtensionsEnabled']
214
231
  end
@@ -326,6 +343,7 @@ module RuboCop
326
343
 
327
344
  cop_metadata = self[qualified_cop_name]
328
345
  next unless cop_metadata['Enabled'] == 'pending'
346
+ next if new_cops_covered?(qualified_cop_name)
329
347
 
330
348
  pending_cops << CopConfig.new(qualified_cop_name, cop_metadata)
331
349
  end
@@ -337,6 +355,14 @@ module RuboCop
337
355
  @gem_versions_in_target ||= read_gem_versions_from_target_lockfile
338
356
  end
339
357
 
358
+ # Returns the names of the target's gems that are sourced from a local path
359
+ # (i.e. `path:` dependencies and the project's own gem when the `Gemfile`
360
+ # uses `gemspec`), whose code therefore lives in the project itself.
361
+ # @returns [Array<String>, nil] The gem names, or nil without a lockfile.
362
+ def path_sourced_gems_in_target
363
+ @path_sourced_gems_in_target ||= read_path_sourced_gems_from_target_lockfile
364
+ end
365
+
340
366
  def inspect # :nodoc:
341
367
  "#<#{self.class.name}:#{object_id} @loaded_path=#{loaded_path}>"
342
368
  end
@@ -381,6 +407,14 @@ module RuboCop
381
407
  Lockfile.new(lockfile_path).gem_versions
382
408
  end
383
409
 
410
+ # @returns [Array<String>, nil] The names of the gems sourced from a local path.
411
+ def read_path_sourced_gems_from_target_lockfile
412
+ lockfile_path = bundler_lock_file_path
413
+ return nil unless lockfile_path
414
+
415
+ Lockfile.new(lockfile_path).path_sourced_gem_names
416
+ end
417
+
384
418
  def enable_cop?(qualified_cop_name, cop_options)
385
419
  # If the cop is explicitly enabled or `Lint/Syntax`, the other checks can be skipped.
386
420
  return true if cop_options['Enabled'] == true || qualified_cop_name == 'Lint/Syntax'
@@ -399,6 +433,43 @@ module RuboCop
399
433
 
400
434
  self[cop_department.join('/')]
401
435
  end
436
+
437
+ # The effective `NewCops` setting for the given cop: the department-level
438
+ # setting if present, otherwise the `AllCops` setting.
439
+ def new_cops_setting_for(qualified_cop_name)
440
+ department = department_of(qualified_cop_name)
441
+ setting = department['NewCops'] if department
442
+
443
+ setting || for_all_cops['NewCops']
444
+ end
445
+
446
+ # Whether the cop's pending status is resolved by a `NewCops` setting,
447
+ # so that it should not appear in the pending cops warning.
448
+ # Unlike `enabled_new_cop?`, `disable` counts as covered.
449
+ def new_cops_covered?(qualified_cop_name)
450
+ setting = new_cops_setting_for(qualified_cop_name)
451
+
452
+ case setting.to_s
453
+ when 'enable', 'disable' then true
454
+ when '', 'pending' then false
455
+ else new_cops_version_covers?(setting, qualified_cop_name)
456
+ end
457
+ end
458
+
459
+ def new_cops_version_covers?(new_cops_version, qualified_cop_name)
460
+ cop_metadata = self[qualified_cop_name]
461
+ version_added = comparable_version(cop_metadata['VersionAdded']) if cop_metadata
462
+ pinned_version = comparable_version(new_cops_version)
463
+ return false if version_added.nil? || pinned_version.nil?
464
+
465
+ version_added <= pinned_version
466
+ end
467
+
468
+ # Returns a `Gem::Version` for values like `'1.19'` or `1.19`, and `nil`
469
+ # for non-version values like `'N/A'` or `'<<next>>'`.
470
+ def comparable_version(value)
471
+ value = value.to_s
472
+ Gem::Version.new(value) if value.match?(/\A\d/) && Gem::Version.correct?(value)
473
+ end
402
474
  end
403
475
  end
404
- # rubocop:enable Metrics/ClassLength
@@ -49,7 +49,7 @@ module RuboCop
49
49
  FileFinder.root_level = nil
50
50
  end
51
51
 
52
- # rubocop:disable Metrics/AbcSize
52
+ # rubocop:disable-next Metrics/AbcSize
53
53
  def load_file(file, check: true)
54
54
  path = file_path(file)
55
55
 
@@ -73,7 +73,6 @@ module RuboCop
73
73
 
74
74
  Config.create(hash, path, check: check)
75
75
  end
76
- # rubocop:enable Metrics/AbcSize
77
76
 
78
77
  def load_yaml_configuration(absolute_path)
79
78
  file_contents = read_file(absolute_path)
@@ -115,7 +115,7 @@ module RuboCop
115
115
  # with the addition that any value that is a hash, and occurs in both
116
116
  # arguments, will also be merged. And so on.
117
117
  #
118
- # rubocop:disable Metrics/AbcSize
118
+ # rubocop:disable-next Metrics/AbcSize
119
119
  def merge(base_hash, derived_hash, **opts)
120
120
  result = base_hash.merge(derived_hash)
121
121
  keys_appearing_in_both = base_hash.keys & derived_hash.keys
@@ -132,7 +132,6 @@ module RuboCop
132
132
  end
133
133
  result
134
134
  end
135
- # rubocop:enable Metrics/AbcSize
136
135
 
137
136
  # An `Enabled: true` setting in user configuration for a cop overrides an
138
137
  # `Enabled: false` setting for its department.
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # Handles validation of configuration, for example cop names, parameter
5
5
  # names, and Ruby versions.
6
- # rubocop:disable Metrics/ClassLength
6
+ # rubocop:disable-next Metrics/ClassLength
7
7
  class ConfigValidator
8
8
  extend SimpleForwardable
9
9
 
@@ -16,6 +16,8 @@ module RuboCop
16
16
  Reference References Safe SafeAutoCorrect].freeze
17
17
  # @api private
18
18
  NEW_COPS_VALUES = %w[pending disable enable].freeze
19
+ # @api private
20
+ NEW_COPS_VERSION_PATTERN = /\A\d+(\.\d+)*\z/.freeze
19
21
 
20
22
  # @api private
21
23
  CONFIG_CHECK_KEYS = %w[Enabled Safe SafeAutoCorrect AutoCorrect References].to_set.freeze
@@ -164,16 +166,48 @@ module RuboCop
164
166
  end
165
167
 
166
168
  def validate_new_cops_parameter
169
+ validate_all_cops_new_cops_parameter
170
+ validate_department_new_cops_parameters
171
+ end
172
+
173
+ def validate_all_cops_new_cops_parameter
167
174
  new_cop_parameter = @config.for_all_cops['NewCops']
168
175
  return if new_cop_parameter.nil? || NEW_COPS_VALUES.include?(new_cop_parameter)
169
176
 
170
- message = "invalid #{new_cop_parameter} for `NewCops` found in" \
177
+ message = "invalid #{new_cop_parameter} for `NewCops` found in " \
171
178
  "#{smart_loaded_path}\n" \
172
179
  "Valid choices are: #{NEW_COPS_VALUES.join(', ')}"
173
180
 
174
181
  raise ValidationError, message
175
182
  end
176
183
 
184
+ def validate_department_new_cops_parameters
185
+ @config.each do |name, section|
186
+ value = new_cops_value_for_department(name, section)
187
+ next if value.nil? || NEW_COPS_VALUES.include?(value) || new_cops_version_value?(value)
188
+
189
+ raise ValidationError,
190
+ "invalid #{value} for `NewCops` found in #{smart_loaded_path}\n" \
191
+ "Valid choices for a department are: #{NEW_COPS_VALUES.join(', ')}, " \
192
+ "or a version string like '1.50'"
193
+ end
194
+ end
195
+
196
+ def new_cops_value_for_department(name, section)
197
+ return nil if name == 'AllCops' || !section.is_a?(Hash)
198
+ return nil unless Cop::Registry.global.department?(name)
199
+
200
+ section['NewCops']
201
+ end
202
+
203
+ def new_cops_version_value?(value)
204
+ case value
205
+ when Float, Integer then true
206
+ when String then NEW_COPS_VERSION_PATTERN.match?(value)
207
+ else false
208
+ end
209
+ end
210
+
177
211
  def validate_parameter_shape(valid_cop_names)
178
212
  valid_cop_names.each do |name|
179
213
  if @config[name].nil?
@@ -207,6 +241,9 @@ module RuboCop
207
241
 
208
242
  @config[cop_name].each_key do |param|
209
243
  next if COMMON_PARAMS.include?(param) || default_config.key?(param)
244
+ # Departments shipped as top-level sections in an extension's default configuration accept
245
+ # `NewCops`, like any other department.
246
+ next if param == 'NewCops' && Cop::Registry.global.department?(cop_name)
210
247
 
211
248
  supported_params = default_config.keys - INTERNAL_PARAMS
212
249
 
@@ -287,5 +324,4 @@ module RuboCop
287
324
  "is supposed to be #{supposed_values} and #{Rainbow(value).yellow} is not."
288
325
  end
289
326
  end
290
- # rubocop:enable Metrics/ClassLength
291
327
  end
@@ -12,6 +12,14 @@ module RuboCop
12
12
  autocorrect_requested? && disable_uncorrectable? && autocorrect_enabled?
13
13
  end
14
14
 
15
+ # Whether a correction exists but is skipped because it is unsafe in a
16
+ # safe autocorrect run, while `--disable-uncorrectable` asks for a todo
17
+ # comment on everything this run cannot correct.
18
+ def skipped_unsafe_correction_with_disable_uncorrectable?
19
+ autocorrect_requested? && disable_uncorrectable? &&
20
+ @options.fetch(:safe_autocorrect, false) && !safe_autocorrect?
21
+ end
22
+
15
23
  def autocorrect_requested?
16
24
  @options.fetch(:autocorrect, false)
17
25
  end
@@ -52,6 +52,14 @@ module RuboCop
52
52
  cop_name == other.cop_name && (!qualified? || department == other.department)
53
53
  end
54
54
 
55
+ # Returns true if the badge's qualified name or the badge's department
56
+ # matches any of the given names.
57
+ def match_name?(given_names)
58
+ return false unless given_names
59
+
60
+ given_names.include?(to_s) || given_names.include?(department_name)
61
+ end
62
+
55
63
  def to_s
56
64
  @to_s ||= qualified? ? "#{department}/#{cop_name}" : cop_name
57
65
  end