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
@@ -25,21 +25,21 @@ module RuboCop
25
25
  super
26
26
 
27
27
  @test_case_elements = []
28
+ @config_store = nil
28
29
 
29
30
  reset_count
30
31
  end
31
32
 
33
+ def file_started(_file, options)
34
+ @config_store = options[:config_store]
35
+ end
36
+
32
37
  def file_finished(file, offenses)
33
38
  @inspected_file_count += 1
39
+ @offense_count += offenses.count
34
40
 
35
- # TODO: Returns all cops with the same behavior as
36
- # the original rubocop-junit-formatter.
37
- # https://github.com/mikian/rubocop-junit-formatter/blob/v0.1.4/lib/rubocop/formatter/junit_formatter.rb#L9
38
- #
39
- # In the future, it would be preferable to return only enabled cops.
40
- Cop::Registry.all.each do |cop|
41
+ cops_for(file).each do |cop|
41
42
  target_offenses = offenses_for_cop(offenses, cop)
42
- @offense_count += target_offenses.count
43
43
 
44
44
  next unless relevant_for_output?(options, target_offenses)
45
45
 
@@ -47,7 +47,7 @@ module RuboCop
47
47
  end
48
48
  end
49
49
 
50
- # rubocop:disable Layout/LineLength,Metrics/AbcSize,Metrics/MethodLength
50
+ # rubocop:disable-next Layout/LineLength,Metrics/AbcSize,Metrics/MethodLength -- the XML is emitted line by line
51
51
  def finished(_inspected_files)
52
52
  output.puts %(<?xml version='1.0'?>)
53
53
  output.puts %(<testsuites>)
@@ -70,10 +70,17 @@ module RuboCop
70
70
  output.puts %( </testsuite>)
71
71
  output.puts %(</testsuites>)
72
72
  end
73
- # rubocop:enable Layout/LineLength,Metrics/AbcSize,Metrics/MethodLength
74
73
 
75
74
  private
76
75
 
76
+ def cops_for(file)
77
+ # `file_started` is not called when this formatter is used directly through the API,
78
+ # in which case all cops are returned for compatibility.
79
+ return Cop::Registry.all if @config_store.nil?
80
+
81
+ registry.enabled(@config_store.for_file(file))
82
+ end
83
+
77
84
  def relevant_for_output?(options, target_offenses)
78
85
  !options[:display_only_failed] || target_offenses.any?
79
86
  end
@@ -91,6 +98,16 @@ module RuboCop
91
98
  end
92
99
  end
93
100
 
101
+ # Filtering by badge keeps lazy-loaded cops unloaded; `Registry#enabled` then loads only
102
+ # the enabled cops, the same set the runner mobilizes.
103
+ def registry
104
+ @registry ||= Cop::Registry.global.filter_by_badge(options) do |badge|
105
+ next false if badge.department == :Test || badge.match_name?(options[:except])
106
+
107
+ options[:only].nil? || badge.match_name?(options[:only])
108
+ end
109
+ end
110
+
94
111
  def classname_attribute_value(file)
95
112
  @classname_attribute_value_cache ||= Hash.new do |hash, key|
96
113
  hash[key] = key.delete_suffix('.rb').gsub("#{PathUtil.pwd}/", '').tr('/', '.')
@@ -52,7 +52,7 @@ module RuboCop
52
52
  report_summary(@offense_counts, @offending_files_count)
53
53
  end
54
54
 
55
- # rubocop:disable Metrics/AbcSize
55
+ # rubocop:disable-next Metrics/AbcSize
56
56
  def report_summary(offense_counts, offending_files_count)
57
57
  per_cop_counts = ordered_offense_counts(offense_counts)
58
58
  total_count = total_offense_count(offense_counts)
@@ -68,7 +68,6 @@ module RuboCop
68
68
 
69
69
  output.puts
70
70
  end
71
- # rubocop:enable Metrics/AbcSize
72
71
 
73
72
  def ordered_offense_counts(offense_counts)
74
73
  offense_counts.sort_by { |k, v| [-v, k] }.to_h
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module RuboCop
6
+ module Formatter
7
+ # This formatter formats the report data in SARIF 2.1.0, the OASIS standard
8
+ # for static analysis results. It's understood by GitHub code scanning,
9
+ # Azure DevOps, and other tools that aggregate analyzer output.
10
+ #
11
+ # SARIF only knows three severity levels, so RuboCop's `info`, `refactor`
12
+ # and `convention` severities are all reported as `note`.
13
+ class SARIFFormatter < BaseFormatter
14
+ SCHEMA_URI = 'https://json.schemastore.org/sarif-2.1.0.json'
15
+ SARIF_VERSION = '2.1.0'
16
+ INFORMATION_URI = 'https://rubocop.org'
17
+
18
+ SEVERITY_LEVELS = {
19
+ info: 'note',
20
+ refactor: 'note',
21
+ convention: 'note',
22
+ warning: 'warning',
23
+ error: 'error',
24
+ fatal: 'error'
25
+ }.freeze
26
+
27
+ def initialize(output, options = {})
28
+ super
29
+
30
+ @rules = []
31
+ @rule_indexes = {}
32
+ @results = []
33
+ @config_store = nil
34
+ end
35
+
36
+ def file_started(_file, options)
37
+ @config_store = options[:config_store]
38
+ end
39
+
40
+ def file_finished(file, offenses)
41
+ offenses.each do |offense|
42
+ @results << result_for(file, offense)
43
+ end
44
+ end
45
+
46
+ def finished(_inspected_files)
47
+ output.write(report.to_json)
48
+ end
49
+
50
+ private
51
+
52
+ def report
53
+ {
54
+ '$schema': SCHEMA_URI,
55
+ version: SARIF_VERSION,
56
+ runs: [{ tool: { driver: driver }, results: @results }]
57
+ }
58
+ end
59
+
60
+ def driver
61
+ {
62
+ name: 'RuboCop',
63
+ version: RuboCop::Version::STRING,
64
+ semanticVersion: RuboCop::Version::STRING,
65
+ informationUri: INFORMATION_URI,
66
+ rules: @rules
67
+ }
68
+ end
69
+
70
+ def result_for(file, offense)
71
+ result = {
72
+ ruleId: offense.cop_name,
73
+ ruleIndex: rule_index_for(file, offense),
74
+ level: SEVERITY_LEVELS.fetch(offense.severity.name, 'warning'),
75
+ message: { text: offense.message },
76
+ locations: [location_for(file, offense)],
77
+ partialFingerprints: fingerprints_for(offense)
78
+ }
79
+
80
+ # Suppressed offenses are only reported under `--display-suppressed`.
81
+ # SARIF has a first-class representation for them, so consumers can
82
+ # tell them apart from offenses that were never triggered.
83
+ result[:suppressions] = [suppression_for(offense)] if offense.disabled?
84
+ result
85
+ end
86
+
87
+ # The URI is relative to the working directory, which is what consumers
88
+ # expect to resolve against their own checkout. No `uriBaseId`: it would
89
+ # have to be declared under `originalUriBaseIds` to mean anything, and
90
+ # every consumer that matters resolves relative paths without it.
91
+ def location_for(file, offense)
92
+ {
93
+ physicalLocation: {
94
+ artifactLocation: { uri: uri_for(file) },
95
+ region: region_for(offense)
96
+ }
97
+ }
98
+ end
99
+
100
+ def region_for(offense)
101
+ # SARIF columns are 1-based and `endColumn` points at the character
102
+ # *after* the region, while `real_last_column` is the last character.
103
+ {
104
+ startLine: offense.first_line,
105
+ startColumn: offense.real_column,
106
+ endLine: offense.last_line,
107
+ endColumn: offense.real_last_column + 1
108
+ }
109
+ end
110
+
111
+ # GitHub uses these to match an alert across runs. Without them it falls
112
+ # back to the file path, which makes alerts churn whenever a file moves.
113
+ def fingerprints_for(offense)
114
+ source_line = offense.location.source_line
115
+ { primaryLocationLineHash: Digest::SHA256.hexdigest("#{offense.cop_name}#{source_line}") }
116
+ end
117
+
118
+ def suppression_for(offense)
119
+ suppression = { kind: 'inSource' }
120
+ suppression[:justification] = offense.justification if offense.justification
121
+ suppression
122
+ end
123
+
124
+ def rule_index_for(file, offense)
125
+ @rule_indexes[offense.cop_name] ||= begin
126
+ @rules << rule_for(file, offense)
127
+ @rules.size - 1
128
+ end
129
+ end
130
+
131
+ def rule_for(file, offense)
132
+ cop_class = Cop::Registry.global.find_by_cop_name(offense.cop_name)
133
+ description = description_for(file, offense)
134
+ help_uri = cop_class && Cop::Documentation.url_for(cop_class, config_for(file))
135
+
136
+ rule = {
137
+ id: offense.cop_name,
138
+ name: offense.cop_name,
139
+ shortDescription: { text: description },
140
+ fullDescription: { text: description },
141
+ help: help_for(description, help_uri),
142
+ defaultConfiguration: { level: default_level_for(file, offense, cop_class) },
143
+ properties: { tags: [offense.cop_name.split('/').first] }
144
+ }
145
+ rule[:helpUri] = help_uri if help_uri
146
+ rule
147
+ end
148
+
149
+ def help_for(description, help_uri)
150
+ help = { text: description }
151
+ help[:markdown] = "#{description}\n\n[Documentation](#{help_uri})" if help_uri
152
+ help
153
+ end
154
+
155
+ def description_for(file, offense)
156
+ cop_config(file, offense)['Description'] || offense.cop_name
157
+ end
158
+
159
+ def default_level_for(file, offense, cop_class)
160
+ severity = cop_config(file, offense)['Severity'] ||
161
+ (cop_class&.lint? ? :warning : :convention)
162
+
163
+ SEVERITY_LEVELS.fetch(severity.to_sym, 'warning')
164
+ end
165
+
166
+ def cop_config(file, offense)
167
+ config_for(file)&.for_cop(offense.cop_name) || {}
168
+ end
169
+
170
+ # `file_started` is not called when a formatter is used directly through
171
+ # the API, so there may be no config to consult.
172
+ def config_for(file)
173
+ @config_store&.for_file(file)
174
+ end
175
+
176
+ def uri_for(file)
177
+ PathUtil.smart_path(file).tr('\\', '/')
178
+ end
179
+ end
180
+ end
181
+ end
@@ -86,7 +86,9 @@ module RuboCop
86
86
 
87
87
  def message(offense)
88
88
  message =
89
- if offense.corrected_with_todo?
89
+ if offense.disabled?
90
+ magenta('[Suppressed] ')
91
+ elsif offense.corrected_with_todo?
90
92
  green('[Todo] ')
91
93
  elsif offense.corrected?
92
94
  green('[Corrected] ')
@@ -104,7 +106,7 @@ module RuboCop
104
106
  include Colorizable
105
107
  include TextUtil
106
108
 
107
- # rubocop:disable Metrics/ParameterLists
109
+ # rubocop:disable-next Metrics/ParameterLists
108
110
  def initialize(
109
111
  file_count, offense_count, correction_count, correctable_count, rainbow,
110
112
  safe_autocorrect: false
@@ -116,7 +118,6 @@ module RuboCop
116
118
  @rainbow = rainbow
117
119
  @safe_autocorrect = safe_autocorrect
118
120
  end
119
- # rubocop:enable Metrics/ParameterLists
120
121
 
121
122
  def summary
122
123
  if @correction_count.positive?
@@ -68,7 +68,9 @@ module RuboCop
68
68
 
69
69
  def message(offense)
70
70
  message =
71
- if offense.corrected_with_todo?
71
+ if offense.disabled?
72
+ '[Suppressed] '
73
+ elsif offense.corrected_with_todo?
72
74
  '[Todo] '
73
75
  elsif offense.corrected?
74
76
  '[Corrected] '
@@ -32,7 +32,7 @@ module RuboCop
32
32
  report_summary(@offense_counts)
33
33
  end
34
34
 
35
- # rubocop:disable Metrics/AbcSize
35
+ # rubocop:disable-next Metrics/AbcSize
36
36
  def report_summary(offense_counts)
37
37
  per_file_counts = ordered_offense_counts(offense_counts)
38
38
  total_count = total_offense_count(offense_counts)
@@ -50,7 +50,6 @@ module RuboCop
50
50
 
51
51
  output.puts
52
52
  end
53
- # rubocop:enable Metrics/AbcSize
54
53
 
55
54
  def ordered_offense_counts(offense_counts)
56
55
  offense_counts.sort_by { |k, v| [-v, k] }.to_h
@@ -24,6 +24,7 @@ module RuboCop
24
24
  autoload :PacmanFormatter, 'rubocop/formatter/pacman_formatter'
25
25
  autoload :ProgressFormatter, 'rubocop/formatter/progress_formatter'
26
26
  autoload :QuietFormatter, 'rubocop/formatter/quiet_formatter'
27
+ autoload :SARIFFormatter, 'rubocop/formatter/sarif_formatter'
27
28
  autoload :TapFormatter, 'rubocop/formatter/tap_formatter'
28
29
  autoload :WorstOffendersFormatter, 'rubocop/formatter/worst_offenders_formatter'
29
30
 
@@ -59,6 +59,18 @@ module RuboCop
59
59
  end
60
60
  end
61
61
 
62
+ # Names of gems sourced from a local path, i.e. `path:` dependencies and the
63
+ # project's own gem when the `Gemfile` uses `gemspec`. Git sources are not
64
+ # included: their code lives outside the project, like a released gem's.
65
+ # @return [Array<String>]
66
+ def path_sourced_gem_names
67
+ return [] unless parser
68
+
69
+ parser.specs.filter_map do |spec|
70
+ spec.name if path_source?(spec.source)
71
+ end
72
+ end
73
+
62
74
  # Whether this lockfile includes the named gem, directly or indirectly.
63
75
  # @param [String] name
64
76
  # @return [Boolean]
@@ -68,6 +80,12 @@ module RuboCop
68
80
 
69
81
  private
70
82
 
83
+ # `Bundler::Source::Git` is a subclass of `Bundler::Source::Path`, but a git
84
+ # source is a remote one.
85
+ def path_source?(source)
86
+ source.is_a?(::Bundler::Source::Path) && !source.is_a?(::Bundler::Source::Git)
87
+ end
88
+
71
89
  # @return [Bundler::LockfileParser, nil]
72
90
  def parser
73
91
  return @parser if defined?(@parser)
@@ -35,7 +35,7 @@ module RuboCop
35
35
  code_actions
36
36
  end
37
37
 
38
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
38
+ # rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
39
39
  def to_lsp_diagnostic(config)
40
40
  highlighted = @offense.highlighted_area
41
41
 
@@ -61,7 +61,6 @@ module RuboCop
61
61
  }
62
62
  )
63
63
  end
64
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
65
64
 
66
65
  private
67
66
 
@@ -82,7 +81,7 @@ module RuboCop
82
81
  LanguageServer::Protocol::Interface::CodeDescription.new(href: doc_url)
83
82
  end
84
83
 
85
- # rubocop:disable Metrics/MethodLength
84
+ # rubocop:disable-next Metrics/MethodLength
86
85
  def autocorrect_action
87
86
  LanguageServer::Protocol::Interface::CodeAction.new(
88
87
  title: "Autocorrect #{@offense.cop_name}",
@@ -101,9 +100,8 @@ module RuboCop
101
100
  is_preferred: true
102
101
  )
103
102
  end
104
- # rubocop:enable Metrics/MethodLength
105
103
 
106
- # rubocop:disable Metrics/MethodLength
104
+ # rubocop:disable-next Metrics/MethodLength
107
105
  def offense_replacements
108
106
  @offense.corrector.as_replacements.map do |range, replacement|
109
107
  LanguageServer::Protocol::Interface::TextEdit.new(
@@ -121,9 +119,8 @@ module RuboCop
121
119
  )
122
120
  end
123
121
  end
124
- # rubocop:enable Metrics/MethodLength
125
122
 
126
- # rubocop:disable Metrics/MethodLength
123
+ # rubocop:disable-next Metrics/MethodLength
127
124
  def disable_line_action
128
125
  LanguageServer::Protocol::Interface::CodeAction.new(
129
126
  title: "Disable #{@offense.cop_name} for this line",
@@ -141,7 +138,6 @@ module RuboCop
141
138
  )
142
139
  )
143
140
  end
144
- # rubocop:enable Metrics/MethodLength
145
141
 
146
142
  def line_disable_comment
147
143
  DisableCommentEdits.new(
@@ -151,25 +151,13 @@ module RuboCop
151
151
  return
152
152
  end
153
153
 
154
- uri = request[:params][:arguments][0][:uri]
155
- formatted = nil
156
-
157
- # The `workspace/executeCommand` is an LSP method triggered by intentional user actions,
158
- # so the user's intention for autocorrection is respected.
159
- LSP.disable { formatted = format_file(uri, command: command) }
160
-
161
- @server.write(
162
- id: request[:id],
163
- method: 'workspace/applyEdit',
164
- params: {
165
- label: label,
166
- edit: {
167
- changes: {
168
- uri => formatted
169
- }
170
- }
171
- }
172
- )
154
+ # Clients such as Eglot can invoke a command without arguments, so respond with
155
+ # an error instead of letting the server crash on a missing URI.
156
+ if (uri = document_uri_from(request))
157
+ write_formatting_edits(request, uri: uri, command: command, label: label)
158
+ else
159
+ write_invalid_arguments_error(request, command)
160
+ end
173
161
  end
174
162
 
175
163
  handle 'textDocument/willSave' do |_request|
@@ -219,6 +207,47 @@ module RuboCop
219
207
  }
220
208
  end
221
209
 
210
+ def document_uri_from(request)
211
+ argument = request.dig(:params, :arguments, 0)
212
+
213
+ argument[:uri] if argument.is_a?(Hash)
214
+ end
215
+
216
+ def write_formatting_edits(request, uri:, command:, label:)
217
+ formatted = nil
218
+
219
+ # The `workspace/executeCommand` is an LSP method triggered by intentional user actions,
220
+ # so the user's intention for autocorrection is respected.
221
+ LSP.disable { formatted = format_file(uri, command: command) }
222
+
223
+ @server.write(
224
+ id: request[:id],
225
+ method: 'workspace/applyEdit',
226
+ params: {
227
+ label: label,
228
+ edit: {
229
+ changes: {
230
+ uri => formatted
231
+ }
232
+ }
233
+ }
234
+ )
235
+ end
236
+
237
+ def write_invalid_arguments_error(request, command)
238
+ message = "Missing document URI in arguments for #{command}"
239
+
240
+ @server.write(
241
+ id: request[:id],
242
+ error: LanguageServer::Protocol::Interface::ResponseError.new(
243
+ code: LanguageServer::Protocol::Constant::ErrorCodes::INVALID_PARAMS,
244
+ message: message
245
+ )
246
+ )
247
+
248
+ Logger.log(message)
249
+ end
250
+
222
251
  def format_file(file_uri, command: nil)
223
252
  unless (text = @text_cache[file_uri])
224
253
  Logger.log("Format request arrived before text synchronized; skipping: `#{file_uri}'")
@@ -12,6 +12,7 @@ module RuboCop
12
12
  encoding: '(?:en)?coding',
13
13
  frozen_string_literal: 'frozen[_-]string[_-]literal',
14
14
  rbs_inline: 'rbs_inline',
15
+ warn_indent: 'warn[_-]indent',
15
16
  shareable_constant_value: 'shareable[_-]constant[_-]value',
16
17
  typed: 'typed'
17
18
  }.freeze
@@ -38,6 +39,7 @@ module RuboCop
38
39
  frozen_string_literal_specified? ||
39
40
  encoding_specified? ||
40
41
  rbs_inline_specified? ||
42
+ warn_indent_specified? ||
41
43
  shareable_constant_value_specified? ||
42
44
  typed_specified?
43
45
  end
@@ -77,6 +79,13 @@ module RuboCop
77
79
  specified?(frozen_string_literal)
78
80
  end
79
81
 
82
+ # Was a warn_indent specified?
83
+ #
84
+ # @return [Boolean]
85
+ def warn_indent_specified?
86
+ specified?(warn_indent)
87
+ end
88
+
80
89
  # Was a shareable_constant_value specified?
81
90
  #
82
91
  # @return [Boolean]
@@ -100,6 +109,13 @@ module RuboCop
100
109
  end
101
110
  end
102
111
 
112
+ # Expose the `warn_indent` value.
113
+ #
114
+ # @return [String] for warn_indent config
115
+ def warn_indent
116
+ extract_warn_indent
117
+ end
118
+
103
119
  # Expose the `shareable_constant_value` value coerced to a boolean if possible.
104
120
  #
105
121
  # @return [String] for shareable_constant_value config
@@ -216,6 +232,10 @@ module RuboCop
216
232
  # Emacs comments cannot specify RBS::inline behavior.
217
233
  def extract_rbs_inline_value; end
218
234
 
235
+ def extract_warn_indent
236
+ match(KEYWORDS[:warn_indent])
237
+ end
238
+
219
239
  def extract_shareable_constant_value
220
240
  match(KEYWORDS[:shareable_constant_value])
221
241
  end
@@ -258,6 +278,9 @@ module RuboCop
258
278
  # Vim comments cannot specify RBS::inline behavior.
259
279
  def extract_rbs_inline_value; end
260
280
 
281
+ # Vim comments cannot specify indentation warning behavior.
282
+ def warn_indent; end
283
+
261
284
  # Vim comments cannot specify shareable constant values behavior.
262
285
  def shareable_constant_value; end
263
286
 
@@ -316,6 +339,10 @@ module RuboCop
316
339
  extract(/\A\s*#\s*#{KEYWORDS[:rbs_inline]}:\s*#{TOKEN}\s*\z/io)
317
340
  end
318
341
 
342
+ def extract_warn_indent
343
+ extract(/\A\s*#\s*#{KEYWORDS[:warn_indent]}:\s*#{TOKEN}\s*\z/io)
344
+ end
345
+
319
346
  def extract_shareable_constant_value
320
347
  extract(/\A\s*#\s*#{KEYWORDS[:shareable_constant_value]}:\s*#{TOKEN}\s*\z/io)
321
348
  end
@@ -156,7 +156,7 @@ module RuboCop
156
156
  summary
157
157
  end
158
158
 
159
- # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
159
+ # rubocop:disable-next Metrics/MethodLength, Metrics/ParameterLists
160
160
  def build_tool(
161
161
  name:, description:,
162
162
  title:, destructive_hint:, idempotent_hint:, read_only_hint:, safety_required:
@@ -194,7 +194,6 @@ module RuboCop
194
194
  ::MCP::Tool::Response.new([{ type: 'text', text: e.message }], error: true)
195
195
  end
196
196
  end
197
- # rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
198
197
  end
199
198
  end
200
199
  end