rubocop 1.89.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 (248) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +309 -14
  3. data/config/obsoletion.yml +9 -0
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop/cached_data.rb +7 -2
  6. data/lib/rubocop/changed_files.rb +112 -0
  7. data/lib/rubocop/cli/command/execute_runner.rb +52 -1
  8. data/lib/rubocop/cli.rb +11 -7
  9. data/lib/rubocop/comment_config/directive_range.rb +34 -0
  10. data/lib/rubocop/comment_config/disable_next.rb +143 -0
  11. data/lib/rubocop/comment_config/push_pop.rb +55 -0
  12. data/lib/rubocop/comment_config.rb +50 -64
  13. data/lib/rubocop/config.rb +33 -5
  14. data/lib/rubocop/config_loader.rb +13 -6
  15. data/lib/rubocop/config_loader_resolver.rb +50 -9
  16. data/lib/rubocop/config_obsoletion/cop_rule.rb +2 -1
  17. data/lib/rubocop/config_obsoletion/removed_cop.rb +8 -0
  18. data/lib/rubocop/config_obsoletion.rb +1 -1
  19. data/lib/rubocop/config_validator.rb +19 -5
  20. data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
  21. data/lib/rubocop/cop/badge.rb +1 -1
  22. data/lib/rubocop/cop/base.rb +49 -5
  23. data/lib/rubocop/cop/corrector.rb +3 -3
  24. data/lib/rubocop/cop/correctors.rb +2 -2
  25. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  26. data/lib/rubocop/cop/generator.rb +1 -1
  27. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -2
  28. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -2
  29. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  30. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -0
  31. data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
  32. data/lib/rubocop/cop/layout/class_structure.rb +102 -26
  33. data/lib/rubocop/cop/layout/comment_indentation.rb +8 -5
  34. data/lib/rubocop/cop/layout/else_alignment.rb +1 -0
  35. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -2
  36. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +63 -5
  37. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  38. data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
  39. data/lib/rubocop/cop/layout/extra_spacing.rb +105 -3
  40. data/lib/rubocop/cop/layout/first_argument_indentation.rb +21 -1
  41. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +17 -4
  42. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +11 -0
  43. data/lib/rubocop/cop/layout/hash_alignment.rb +6 -5
  44. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  45. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -2
  46. data/lib/rubocop/cop/layout/line_length.rb +20 -3
  47. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
  48. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +5 -5
  49. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -2
  50. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +14 -3
  51. data/lib/rubocop/cop/layout/space_around_operators.rb +12 -1
  52. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +2 -4
  53. data/lib/rubocop/cop/lint/ambiguous_range.rb +1 -2
  54. data/lib/rubocop/cop/lint/argument_mismatch.rb +99 -0
  55. data/lib/rubocop/cop/lint/assignment_in_condition.rb +2 -0
  56. data/lib/rubocop/cop/lint/constant_reassignment.rb +17 -11
  57. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +165 -15
  58. data/lib/rubocop/cop/lint/deprecated_reference.rb +11 -2
  59. data/lib/rubocop/cop/lint/duplicate_methods.rb +113 -15
  60. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  61. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -2
  62. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -2
  63. data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
  64. data/lib/rubocop/cop/lint/literal_as_condition.rb +11 -8
  65. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +3 -6
  66. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +185 -0
  67. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
  68. data/lib/rubocop/cop/lint/name_typo.rb +53 -6
  69. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
  70. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  71. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -0
  72. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +17 -1
  73. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +122 -29
  74. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +47 -7
  75. data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -2
  76. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -2
  77. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -2
  78. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +1 -2
  79. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -2
  80. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -2
  81. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -4
  82. data/lib/rubocop/cop/lint/super_argument_mismatch.rb +179 -0
  83. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +1 -2
  84. data/lib/rubocop/cop/lint/to_enum_arguments.rb +1 -2
  85. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
  86. data/lib/rubocop/cop/lint/unused_private_method.rb +94 -17
  87. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -2
  88. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
  89. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +36 -0
  90. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  91. data/lib/rubocop/cop/lint/useless_rescue.rb +1 -2
  92. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
  93. data/lib/rubocop/cop/lint/void.rb +1 -2
  94. data/lib/rubocop/cop/lint.rb +3 -0
  95. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
  96. data/lib/rubocop/cop/migration/department_name.rb +1 -2
  97. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  98. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -1
  99. data/lib/rubocop/cop/mixin/comments_help.rb +1 -2
  100. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -2
  101. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +11 -1
  102. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +7 -4
  103. data/lib/rubocop/cop/mixin/hash_subset.rb +1 -2
  104. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  105. data/lib/rubocop/cop/mixin/indexed_method_arity.rb +94 -0
  106. data/lib/rubocop/cop/mixin/line_length_help.rb +7 -1
  107. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +62 -6
  108. data/lib/rubocop/cop/mixin/range_help.rb +1 -2
  109. data/lib/rubocop/cop/mixin/statement_modifier.rb +13 -7
  110. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
  111. data/lib/rubocop/cop/mixin.rb +6 -2
  112. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +16 -0
  113. data/lib/rubocop/cop/naming/block_forwarding.rb +4 -1
  114. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  115. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -4
  116. data/lib/rubocop/cop/naming/method_name.rb +5 -2
  117. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
  118. data/lib/rubocop/cop/offense.rb +27 -4
  119. data/lib/rubocop/cop/registry.rb +43 -11
  120. data/lib/rubocop/cop/severity.rb +12 -0
  121. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -2
  122. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -2
  123. data/lib/rubocop/cop/style/arguments_forwarding.rb +5 -8
  124. data/lib/rubocop/cop/style/array_first_last.rb +1 -2
  125. data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -2
  126. data/lib/rubocop/cop/style/block_delimiters.rb +3 -6
  127. data/lib/rubocop/cop/style/case_like_if.rb +5 -7
  128. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -2
  129. data/lib/rubocop/cop/style/combinable_loops.rb +1 -2
  130. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -2
  131. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -2
  132. data/lib/rubocop/cop/style/constant_visibility.rb +6 -3
  133. data/lib/rubocop/cop/style/copyright.rb +1 -2
  134. data/lib/rubocop/cop/style/data_inheritance.rb +14 -0
  135. data/lib/rubocop/cop/style/directive_scope.rb +304 -0
  136. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +83 -13
  137. data/lib/rubocop/cop/style/documentation_method.rb +1 -9
  138. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  139. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  140. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
  141. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  142. data/lib/rubocop/cop/style/empty_method.rb +12 -2
  143. data/lib/rubocop/cop/style/endless_method.rb +31 -12
  144. data/lib/rubocop/cop/style/file_null.rb +2 -2
  145. data/lib/rubocop/cop/style/float_division.rb +10 -16
  146. data/lib/rubocop/cop/style/for.rb +5 -0
  147. data/lib/rubocop/cop/style/format_string_token.rb +1 -2
  148. data/lib/rubocop/cop/style/guard_clause.rb +5 -2
  149. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -2
  150. data/lib/rubocop/cop/style/hash_lookup_method.rb +3 -0
  151. data/lib/rubocop/cop/style/hash_syntax.rb +1 -2
  152. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -4
  153. data/lib/rubocop/cop/style/if_inside_else.rb +1 -2
  154. data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -2
  155. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
  156. data/lib/rubocop/cop/style/magic_comment_format.rb +1 -1
  157. data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
  158. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -4
  159. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -1
  160. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
  161. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -2
  162. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -2
  163. data/lib/rubocop/cop/style/negative_array_index.rb +2 -4
  164. data/lib/rubocop/cop/style/nested_modifier.rb +9 -4
  165. data/lib/rubocop/cop/style/nil_comparison.rb +36 -9
  166. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  167. data/lib/rubocop/cop/style/non_nil_check.rb +18 -2
  168. data/lib/rubocop/cop/style/not.rb +3 -1
  169. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +11 -1
  170. data/lib/rubocop/cop/style/one_line_conditional.rb +1 -1
  171. data/lib/rubocop/cop/style/operator_method_call.rb +1 -2
  172. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -2
  173. data/lib/rubocop/cop/style/parentheses_around_condition.rb +6 -1
  174. data/lib/rubocop/cop/style/redundant_assignment.rb +1 -2
  175. data/lib/rubocop/cop/style/redundant_condition.rb +6 -8
  176. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +11 -3
  177. data/lib/rubocop/cop/style/redundant_each.rb +1 -2
  178. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  179. data/lib/rubocop/cop/style/redundant_format.rb +2 -4
  180. data/lib/rubocop/cop/style/redundant_parentheses.rb +13 -5
  181. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -2
  182. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +6 -5
  183. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +6 -6
  184. data/lib/rubocop/cop/style/redundant_return.rb +1 -2
  185. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
  186. data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -2
  187. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -2
  188. data/lib/rubocop/cop/style/safe_navigation.rb +1 -2
  189. data/lib/rubocop/cop/style/sample.rb +11 -6
  190. data/lib/rubocop/cop/style/select_by_regexp.rb +1 -2
  191. data/lib/rubocop/cop/style/semicolon.rb +2 -4
  192. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  193. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  194. data/lib/rubocop/cop/style/sole_nested_conditional.rb +1 -2
  195. data/lib/rubocop/cop/style/special_global_vars.rb +1 -1
  196. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
  197. data/lib/rubocop/cop/style/string_concatenation.rb +18 -5
  198. data/lib/rubocop/cop/style/struct_inheritance.rb +14 -0
  199. data/lib/rubocop/cop/style/super_arguments.rb +1 -2
  200. data/lib/rubocop/cop/style/symbol_proc.rb +1 -2
  201. data/lib/rubocop/cop/style/time_now.rb +41 -0
  202. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  203. data/lib/rubocop/cop/style/yoda_condition.rb +1 -2
  204. data/lib/rubocop/cop/style.rb +2 -1
  205. data/lib/rubocop/cop/team.rb +11 -3
  206. data/lib/rubocop/cop/util.rb +3 -5
  207. data/lib/rubocop/cop/variable_force/variable.rb +1 -2
  208. data/lib/rubocop/cops_documentation_generator.rb +26 -8
  209. data/lib/rubocop/directive_comment.rb +73 -10
  210. data/lib/rubocop/formatter/disabled_config_formatter.rb +19 -1
  211. data/lib/rubocop/formatter/emacs_style_formatter.rb +3 -1
  212. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  213. data/lib/rubocop/formatter/github_actions_formatter.rb +6 -5
  214. data/lib/rubocop/formatter/html_formatter.rb +2 -4
  215. data/lib/rubocop/formatter/json_formatter.rb +11 -6
  216. data/lib/rubocop/formatter/junit_formatter.rb +26 -9
  217. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -2
  218. data/lib/rubocop/formatter/sarif_formatter.rb +181 -0
  219. data/lib/rubocop/formatter/simple_text_formatter.rb +4 -3
  220. data/lib/rubocop/formatter/tap_formatter.rb +3 -1
  221. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
  222. data/lib/rubocop/formatter.rb +1 -0
  223. data/lib/rubocop/lockfile.rb +18 -0
  224. data/lib/rubocop/lsp/diagnostic.rb +4 -8
  225. data/lib/rubocop/lsp/routes.rb +48 -19
  226. data/lib/rubocop/magic_comment.rb +27 -0
  227. data/lib/rubocop/mcp/server.rb +1 -2
  228. data/lib/rubocop/options.rb +69 -7
  229. data/lib/rubocop/path_util.rb +2 -3
  230. data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
  231. data/lib/rubocop/plugin/loader.rb +1 -1
  232. data/lib/rubocop/rake_task.rb +1 -2
  233. data/lib/rubocop/remote_config.rb +4 -4
  234. data/lib/rubocop/result_cache.rb +16 -5
  235. data/lib/rubocop/rspec/expect_offense.rb +2 -4
  236. data/lib/rubocop/rspec/shared_contexts.rb +4 -2
  237. data/lib/rubocop/runner.rb +90 -44
  238. data/lib/rubocop/server/cache.rb +1 -2
  239. data/lib/rubocop/server/cli.rb +2 -4
  240. data/lib/rubocop/server.rb +14 -0
  241. data/lib/rubocop/target_finder.rb +4 -1
  242. data/lib/rubocop/todo_audit.rb +111 -0
  243. data/lib/rubocop/unified_diff.rb +257 -0
  244. data/lib/rubocop/version.rb +3 -5
  245. data/lib/rubocop.rb +7 -1
  246. data/lib/ruby_lsp/rubocop/addon.rb +1 -2
  247. metadata +18 -6
  248. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +0 -46
@@ -80,6 +80,11 @@ removed:
80
80
  `Lint/BinaryOperatorWithIdenticalOperands`. Please use
81
81
  `Lint/BinaryOperatorWithIdenticalOperands` instead
82
82
  Style/BracesAroundHashParameters: true
83
+ Style/DoubleCopDisableDirective:
84
+ severity: warning
85
+ reason: >
86
+ it has been superseded by `Lint/CopDirectiveSyntax`. Please use
87
+ `Lint/CopDirectiveSyntax` instead
83
88
  Style/MethodMissingSuper:
84
89
  reason: >
85
90
  it has been superseded by `Lint/MissingSuper`. Please use
@@ -149,6 +154,10 @@ changed_parameters:
149
154
  parameters: IgnoreCopDirectives
150
155
  alternative: AllowCopDirectives
151
156
  severity: warning
157
+ - cops: Style/DisableCopsWithinSourceCodeDirective
158
+ parameters: AllowTrailingComment
159
+ alternative: AllowWithReason
160
+ severity: warning
152
161
  - cops:
153
162
  - Lint/BlockAlignment
154
163
  - Layout/BlockAlignment
data/exe/rubocop CHANGED
@@ -8,7 +8,7 @@ server_cli = RuboCop::Server::CLI.new
8
8
  exit_status = server_cli.run
9
9
  exit exit_status if server_cli.exit?
10
10
 
11
- if RuboCop::Server.running?
11
+ if RuboCop::Server.forward_to_server?
12
12
  exit_status = RuboCop::Server::ClientCommand::Exec.new.run
13
13
  else
14
14
  require 'rubocop'
@@ -22,7 +22,7 @@ module RuboCop
22
22
 
23
23
  def serialize_offense(offense)
24
24
  status = :uncorrected if %i[corrected corrected_with_todo].include?(offense.status)
25
- {
25
+ hash = {
26
26
  # Calling #to_s here ensures that the serialization works when using
27
27
  # other json serializers such as Oj. Some of these gems do not call
28
28
  # #to_s implicitly.
@@ -35,6 +35,10 @@ module RuboCop
35
35
  cop_name: offense.cop_name,
36
36
  status: status || offense.status
37
37
  }
38
+ # Only offenses suppressed by a directive can carry one, so keep the key out of the
39
+ # common case rather than writing a null for every cached offense.
40
+ hash[:justification] = offense.justification if offense.justification
41
+ hash
38
42
  end
39
43
 
40
44
  def message(offense)
@@ -47,7 +51,8 @@ module RuboCop
47
51
  def deserialize_offenses(offenses)
48
52
  offenses.map! do |o|
49
53
  location = location_from_source_buffer(o)
50
- Cop::Offense.new(o['severity'], location, o['message'], o['cop_name'], o['status'].to_sym)
54
+ Cop::Offense.new(o['severity'], location, o['message'], o['cop_name'], o['status'].to_sym,
55
+ justification: o['justification'])
51
56
  end
52
57
  end
53
58
 
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module RuboCop
6
+ # Lists the files that differ from a git revision, for `--changed`.
7
+ #
8
+ # Shelling out to `git` keeps RuboCop free of a version control dependency,
9
+ # and means the answer is exactly what git would give on the command line.
10
+ #
11
+ # @api private
12
+ class ChangedFiles
13
+ DEFAULT_REVISION = 'HEAD'
14
+ FILESYSTEM_ENCODING = Encoding.find('filesystem')
15
+
16
+ def initialize(revision = nil)
17
+ @revision = revision || DEFAULT_REVISION
18
+ end
19
+
20
+ # Files that were added or modified since the revision, as absolute paths.
21
+ # Deleted files are left out - there is nothing left to inspect - and
22
+ # untracked files are included, since a file that is new to the working
23
+ # tree has changed as surely as one git already knows about.
24
+ #
25
+ # @return [Set<String>]
26
+ def paths
27
+ root = repository_root
28
+
29
+ (modified_paths + untracked_paths).to_set { |path| File.expand_path(path, root) }
30
+ end
31
+
32
+ private
33
+
34
+ def repository_root
35
+ filesystem_path(git('rev-parse', '--show-toplevel').b.chomp)
36
+ end
37
+
38
+ def modified_paths
39
+ # `diff.relative` would make git report paths relative to the current
40
+ # directory rather than to the repository root.
41
+ split_paths(
42
+ git('-c', 'diff.relative=false', 'diff', '--name-only', '--diff-filter=d', '-z', @revision)
43
+ )
44
+ rescue Error
45
+ raise unless unborn_head?
46
+
47
+ # A repository without commits has nothing to diff against, and
48
+ # everything in it is untracked - which is what the caller wants to hear
49
+ # rather than an error about `HEAD` not resolving.
50
+ []
51
+ end
52
+
53
+ def unborn_head?
54
+ return false unless @revision == DEFAULT_REVISION
55
+
56
+ _stdout, _stderr, status = Open3.capture3('git', 'rev-parse', '--verify', '--quiet', 'HEAD')
57
+ !status.success?
58
+ end
59
+
60
+ def untracked_paths
61
+ # `--full-name` anchors the paths to the repository root, the way the
62
+ # paths from `git diff` already are.
63
+ split_paths(git('ls-files', '--others', '--exclude-standard', '--full-name', '-z'))
64
+ end
65
+
66
+ # The `-z` above is what makes these usable: without it git wraps any path
67
+ # that is not plain ASCII in quotes and octal escapes, and those files drop
68
+ # out of the comparison without a word.
69
+ def split_paths(output)
70
+ output.b.split("\0").reject(&:empty?).map { |path| filesystem_path(path) }
71
+ end
72
+
73
+ # git hands back bytes, which Ruby tags with the encoding the process
74
+ # happens to have been started in - splitting those bytes as US-ASCII raises
75
+ # on any path that is not. They have to end up in the encoding `Dir.glob`
76
+ # uses, or a path with a non-ASCII character in it never matches the file
77
+ # list it is compared against.
78
+ #
79
+ # On POSIX those bytes are already the filesystem's own, so the encoding is
80
+ # simply corrected. Windows is the exception: git converts paths to UTF-8 on
81
+ # the way out, while Ruby reports file names in the local code page, so the
82
+ # two have to be reconciled.
83
+ def filesystem_path(path)
84
+ path = path.dup
85
+
86
+ return path.force_encoding(FILESYSTEM_ENCODING) unless Platform.windows?
87
+
88
+ path.force_encoding(Encoding::UTF_8).encode(FILESYSTEM_ENCODING)
89
+ rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
90
+ # A name the local code page cannot represent. Nothing good is left to do
91
+ # with it, and dropping it would be worse than reporting it as-is.
92
+ path.force_encoding(FILESYSTEM_ENCODING)
93
+ end
94
+
95
+ def git(*args)
96
+ stdout, stderr, status = Open3.capture3('git', *args)
97
+
98
+ raise Error, git_error_message(stderr) unless status.success?
99
+
100
+ stdout
101
+ rescue Errno::ENOENT
102
+ raise Error, '--changed requires git to be installed and on your PATH.'
103
+ end
104
+
105
+ def git_error_message(stderr)
106
+ message = stderr.strip
107
+ message = 'git failed with no output on standard error.' if message.empty?
108
+
109
+ "--changed could not ask git which files changed: #{message}"
110
+ end
111
+ end
112
+ end
@@ -28,8 +28,21 @@ module RuboCop
28
28
  all_passed || @options[:auto_gen_config]
29
29
  end
30
30
 
31
- maybe_print_corrected_source
31
+ if @options[:diff]
32
+ print_diffs(runner.diffs)
33
+ else
34
+ maybe_print_corrected_source
35
+ end
36
+
37
+ # A diff means autocorrectable offenses are still in the working
38
+ # tree, so the run has not passed even though they were "corrected"
39
+ # in memory.
40
+ all_pass_or_excluded &&= runner.diffs.empty?
41
+
42
+ merge_todo_audit_status(runner_status(runner, all_pass_or_excluded))
43
+ end
32
44
 
45
+ def runner_status(runner, all_pass_or_excluded)
33
46
  if runner.aborting?
34
47
  STATUS_INTERRUPTED
35
48
  elsif all_pass_or_excluded && runner.errors.empty?
@@ -39,6 +52,33 @@ module RuboCop
39
52
  end
40
53
  end
41
54
 
55
+ def merge_todo_audit_status(status)
56
+ return status if status == STATUS_INTERRUPTED || !@options[:report_unused_todo_entries]
57
+
58
+ audit_status = report_unused_todo_entries
59
+ status == STATUS_SUCCESS ? audit_status : status
60
+ end
61
+
62
+ def report_unused_todo_entries
63
+ audit = TodoAudit.new(@config_store, @options)
64
+ unused = audit.unused_entries
65
+
66
+ if unused.nil?
67
+ warn Rainbow("No `#{audit.todo_file}` found; nothing to audit.").yellow
68
+ return STATUS_SUCCESS
69
+ end
70
+ return STATUS_SUCCESS if unused.empty?
71
+
72
+ print_unused_todo_entries(audit.todo_file, unused)
73
+ STATUS_OFFENSES
74
+ end
75
+
76
+ def print_unused_todo_entries(todo_file, unused)
77
+ noun = unused.size == 1 ? 'entry' : 'entries'
78
+ warn Rainbow("\n#{unused.size} unused todo #{noun} found in `#{todo_file}`:").red
79
+ unused.each { |entry| warn " #{entry.cop_name}: #{entry.path}" }
80
+ end
81
+
42
82
  def with_redirect
43
83
  if @options[:stderr]
44
84
  orig_stdout = $stdout
@@ -88,6 +128,17 @@ module RuboCop
88
128
  "#{Gem.loaded_specs['rubocop'].metadata['bug_tracker_uri']}\n"
89
129
  end
90
130
 
131
+ def print_diffs(diffs)
132
+ return if diffs.empty?
133
+ # Integration tools own stdout when they ask for a machine-readable
134
+ # format, so the diff would only corrupt their input.
135
+ return if INTEGRATION_FORMATTERS.include?(@options[:format])
136
+
137
+ output = @options[:stderr] ? $stderr : $stdout
138
+ output.puts
139
+ diffs.each { |diff| output.print(diff) }
140
+ end
141
+
91
142
  def maybe_print_corrected_source
92
143
  # Integration tools (like RubyMine) expect to have only the JSON result
93
144
  # when specifying JSON format. Similar HTML and JUnit are targeted as well.
data/lib/rubocop/cli.rb CHANGED
@@ -13,7 +13,7 @@ module RuboCop
13
13
  DEFAULT_PARALLEL_OPTIONS = %i[
14
14
  color config debug display_style_guide display_time display_only_fail_level_offenses
15
15
  display_only_failed editor_mode except extra_details fail_level fix_layout format formatters
16
- ignore_disable_comments lint only only_guide_cops out require safe
16
+ ignore_disable_comments lint only only_guide_cops out preview require safe
17
17
  autocorrect safe_autocorrect autocorrect_all
18
18
  ].freeze
19
19
 
@@ -35,7 +35,7 @@ module RuboCop
35
35
  # @param args [Array<String>] command line arguments
36
36
  # @return [Integer] UNIX exit code
37
37
  #
38
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
38
+ # rubocop:disable-next Metrics/MethodLength, Metrics/AbcSize
39
39
  def run(args = ARGV)
40
40
  time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
41
41
 
@@ -80,11 +80,10 @@ module RuboCop
80
80
  puts "Finished in #{elapsed_time.round(5)} seconds"
81
81
  end
82
82
  end
83
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
84
83
 
85
84
  private
86
85
 
87
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
86
+ # rubocop:disable-next Metrics/MethodLength, Metrics/AbcSize
88
87
  def profile_if_needed
89
88
  return yield unless @options[:profile]
90
89
 
@@ -116,7 +115,6 @@ module RuboCop
116
115
  end
117
116
  status
118
117
  end
119
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
120
118
 
121
119
  def require_gem(name)
122
120
  require name
@@ -185,12 +183,18 @@ module RuboCop
185
183
 
186
184
  def set_options_to_config_loader
187
185
  ConfigLoader.debug = @options[:debug]
188
- ConfigLoader.disable_pending_cops = @options[:disable_pending_cops]
189
- ConfigLoader.enable_pending_cops = @options[:enable_pending_cops]
190
186
  ConfigLoader.ignore_parent_exclusion = @options[:ignore_parent_exclusion]
191
187
  ConfigLoader.ignore_unrecognized_cops = @options[:ignore_unrecognized_cops]
188
+ set_cop_selection_options_to_config_loader
189
+ end
190
+
191
+ # Options that decide which cops run, as opposed to how configuration is loaded.
192
+ def set_cop_selection_options_to_config_loader
193
+ ConfigLoader.disable_pending_cops = @options[:disable_pending_cops]
194
+ ConfigLoader.enable_pending_cops = @options[:enable_pending_cops]
192
195
  ConfigLoader.enabled_by_default = @options[:enable_all_cops]
193
196
  ConfigLoader.disabled_by_default = @options[:disable_all_cops]
197
+ ConfigLoader.preview = @options[:preview]
194
198
  end
195
199
 
196
200
  def set_options_to_pending_cops_reporter
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ class CommentConfig
5
+ # A disabled line range that remembers the directive comment that opened
6
+ # it. Value-equal to a plain `Range`, so consumers comparing or hashing
7
+ # ranges are unaffected.
8
+ # @api private
9
+ class DirectiveRange < Range
10
+ attr_reader :directive
11
+
12
+ def initialize(first_line, last_line, directive)
13
+ super(first_line, last_line)
14
+ @directive = directive
15
+ end
16
+ end
17
+
18
+ # The per-cop state accumulated while analyzing directives: the disabled
19
+ # ranges so far, plus the line and directive of the currently open
20
+ # disable, if any.
21
+ # @api private
22
+ CopAnalysis = Struct.new(:line_ranges, :start_line_number, :start_directive) do
23
+ # The open range (if any) closed at the given line, appended to the
24
+ # accumulated ranges, each remembering the directive that opened it.
25
+ # Closing above the opening line (e.g. a `pop` directly below a
26
+ # statement whose disable a `next +Cop` suspended) yields no range.
27
+ def close(line)
28
+ return line_ranges if !start_line_number || line < start_line_number
29
+
30
+ line_ranges + [DirectiveRange.new(start_line_number, line, start_directive)]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ class CommentConfig
5
+ # Handling of next-statement directives (`disable-next`, `todo-next` and
6
+ # `next`): computing the statement scope they apply to and tracking
7
+ # directives that attached to nothing.
8
+ # The scope is the whole statement starting on the next line bearing
9
+ # code. Comment-only lines between the directive and the code chain (so
10
+ # several directives can stack), while a blank line breaks the attachment.
11
+ # @api private
12
+ module DisableNext
13
+ # Next-statement directives that affect nothing: nothing follows
14
+ # them, or they sit at the end of a code line.
15
+ def detached_next_directives
16
+ cop_disabled_line_ranges # ensure the analysis that collects them ran
17
+ @detached_next_directives
18
+ end
19
+
20
+ # The line range of the statement a `disable-next` directive on the
21
+ # given line scopes to (or would scope to), or `nil` when no statement
22
+ # is attached. A directive is only honored on a comment-only line.
23
+ # Stacked directives share the target, so the computation is memoized.
24
+ # @api private
25
+ def statement_scope_after(line)
26
+ return nil unless comment_only_line?(line)
27
+
28
+ code_line = attached_code_line(line)
29
+ return nil unless code_line
30
+
31
+ (@statement_bounds ||= {})[code_line] ||= statement_bounds_at(code_line)
32
+ end
33
+
34
+ private
35
+
36
+ def apply_disable_next(analyses, directive)
37
+ bounds = statement_scope_after(directive.line_number)
38
+ return @detached_next_directives << directive unless bounds
39
+
40
+ range = DirectiveRange.new(bounds.begin, bounds.end, directive)
41
+ directive.cop_names.each do |cop_name|
42
+ add_next_range(analyses, qualified_cop_name(cop_name), range)
43
+ end
44
+ end
45
+
46
+ def add_next_range(analyses, cop_name, range)
47
+ analysis = analyses[cop_name]
48
+ analyses[cop_name] = CopAnalysis.new(analysis.line_ranges + [range],
49
+ analysis.start_line_number, analysis.start_directive)
50
+ end
51
+
52
+ # Applies a `next` directive: `-` cops are disabled for the attached
53
+ # statement (exactly like `disable-next`), `+` cops have any open
54
+ # disable suspended for it. The `resolved_cops` come pre-expanded and
55
+ # qualified, mirroring `push`.
56
+ def apply_next_directive(analyses, resolved_cops, directive)
57
+ bounds = statement_scope_after(directive.line_number)
58
+ return @detached_next_directives << directive unless bounds
59
+
60
+ range = DirectiveRange.new(bounds.begin, bounds.end, directive)
61
+ resolved_cops.each do |op, cops|
62
+ cops.each do |cop|
63
+ if op == '-'
64
+ add_next_range(analyses, cop, range)
65
+ else
66
+ suspend_disable(analyses, cop, bounds)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # Applies an `enable-next` directive: every listed cop (departments
73
+ # and `all` included) has any open disable suspended for the attached
74
+ # statement.
75
+ def apply_enable_next(analyses, directive)
76
+ bounds = statement_scope_after(directive.line_number)
77
+ return @detached_next_directives << directive unless bounds
78
+
79
+ directive.cop_names.each do |cop_name|
80
+ suspend_disable(analyses, qualified_cop_name(cop_name), bounds)
81
+ end
82
+ end
83
+
84
+ # Punches a statement-sized hole into the currently open disable of the
85
+ # cop (whether opened by a directive or injected for a config-disabled
86
+ # cop): the open range closes just above the statement and reopens
87
+ # right below it, still attributed to its opening directive. Aligned
88
+ # with `push`, a `+` for a cop that is not disabled is a no-op.
89
+ def suspend_disable(analyses, cop, bounds)
90
+ analysis = analyses[cop]
91
+ return unless analysis.start_line_number
92
+
93
+ analyses[cop] = CopAnalysis.new(analysis.close(bounds.begin - 1),
94
+ bounds.end + 1, analysis.start_directive)
95
+ end
96
+
97
+ def attached_code_line(directive_line)
98
+ ((directive_line + 1)..processed_source.lines.size).each do |line|
99
+ return line unless comment_only_line?(line)
100
+ return nil if processed_source[line - 1].blank?
101
+ end
102
+
103
+ nil
104
+ end
105
+
106
+ def statement_bounds_at(line)
107
+ statement = statement_starting_at(line)
108
+ # A code line where no statement starts (e.g. a lone `end`) scopes
109
+ # the directive to that line alone.
110
+ return (line..line) unless statement
111
+
112
+ (line..statement_end_line(statement))
113
+ end
114
+
115
+ def statement_start_line?(node, line)
116
+ # Synthetic nodes (e.g. parentheses-less argument lists) have no
117
+ # source range. `begin` nodes are sequences of statements, not
118
+ # statements themselves - taking one would scope the directive to
119
+ # everything up to the last statement of the sequence.
120
+ node.source_range && node.first_line == line && !node.begin_type?
121
+ end
122
+
123
+ def statement_starting_at(line)
124
+ ast = processed_source.ast
125
+ return nil unless ast
126
+
127
+ ast.each_node.select { |node| statement_start_line?(node, line) }.max_by(&:last_line)
128
+ end
129
+
130
+ def statement_end_line(statement)
131
+ statement.each_node.filter_map do |node|
132
+ next unless node.source_range
133
+
134
+ heredoc?(node) ? node.loc.heredoc_end.line : node.last_line
135
+ end.max
136
+ end
137
+
138
+ def heredoc?(node)
139
+ node.respond_to?(:heredoc?) && node.heredoc?
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ class CommentConfig
5
+ # Handling of `push`/`pop` directives and their signed cop arguments:
6
+ # saving and restoring the per-cop analysis state, and resolving the
7
+ # `+`/`-` argument lists (shared with the `next` directive).
8
+ # @api private
9
+ module PushPop
10
+ private
11
+
12
+ def resolve_push_cops(directive)
13
+ directive.push_args.transform_values do |names|
14
+ names.flat_map { |name| expand_cop_name(name) }
15
+ end
16
+ end
17
+
18
+ def expand_cop_name(name)
19
+ registry = Cop::Registry.global
20
+ cops = registry.department?(name) ? registry.names_for_department(name) : [name]
21
+ cops.map { |c| qualified_cop_name(c) }
22
+ end
23
+
24
+ def apply_push(analyses, resolved_cops, directive)
25
+ resolved_cops.each do |op, cops|
26
+ cops.each { |cop| apply_cop_op(analyses, op, cop, directive) }
27
+ end
28
+ end
29
+
30
+ def apply_cop_op(analyses, operation, cop, directive)
31
+ analysis = analyses[cop]
32
+ line = directive.line_number
33
+ if operation == '-' && !analysis.start_line_number
34
+ analyses[cop] = CopAnalysis.new(analysis.line_ranges, line, directive)
35
+ elsif operation == '+' && analysis.start_line_number
36
+ analyses[cop] = CopAnalysis.new(analysis.close(line), nil)
37
+ end
38
+ end
39
+
40
+ def pop_state(analyses, line)
41
+ restore_point = @stack.pop
42
+ (restore_point.keys | analyses.keys).each do |cop|
43
+ analyses[cop] = popped_analysis(analyses[cop], restore_point[cop], line)
44
+ end
45
+ end
46
+
47
+ def popped_analysis(current, restored, line)
48
+ ranges = current.close(line - 1)
49
+ return CopAnalysis.new(ranges, nil) unless restored&.start_line_number
50
+
51
+ CopAnalysis.new(ranges, line, restored.start_directive)
52
+ end
53
+ end
54
+ end
55
+ end