rubocop 1.90.0 → 1.91.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +238 -10
  3. data/config/obsoletion.yml +9 -0
  4. data/lib/rubocop/changed_files.rb +112 -0
  5. data/lib/rubocop/cli/command/execute_runner.rb +21 -1
  6. data/lib/rubocop/cli.rb +9 -3
  7. data/lib/rubocop/comment_config/directive_range.rb +3 -1
  8. data/lib/rubocop/comment_config/disable_next.rb +49 -3
  9. data/lib/rubocop/comment_config/push_pop.rb +55 -0
  10. data/lib/rubocop/comment_config.rb +21 -51
  11. data/lib/rubocop/config.rb +10 -0
  12. data/lib/rubocop/config_loader.rb +12 -4
  13. data/lib/rubocop/config_loader_resolver.rb +49 -7
  14. data/lib/rubocop/config_obsoletion/cop_rule.rb +2 -1
  15. data/lib/rubocop/config_obsoletion/removed_cop.rb +8 -0
  16. data/lib/rubocop/config_obsoletion.rb +1 -1
  17. data/lib/rubocop/config_validator.rb +18 -3
  18. data/lib/rubocop/cop/badge.rb +1 -1
  19. data/lib/rubocop/cop/base.rb +15 -3
  20. data/lib/rubocop/cop/correctors.rb +2 -2
  21. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  22. data/lib/rubocop/cop/generator.rb +1 -1
  23. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  24. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -0
  25. data/lib/rubocop/cop/layout/comment_indentation.rb +8 -5
  26. data/lib/rubocop/cop/layout/else_alignment.rb +1 -0
  27. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  28. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +17 -4
  29. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +11 -0
  30. data/lib/rubocop/cop/layout/hash_alignment.rb +6 -5
  31. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  32. data/lib/rubocop/cop/layout/line_length.rb +19 -1
  33. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +14 -3
  34. data/lib/rubocop/cop/layout/space_around_operators.rb +5 -1
  35. data/lib/rubocop/cop/lint/argument_mismatch.rb +14 -4
  36. data/lib/rubocop/cop/lint/assignment_in_condition.rb +2 -0
  37. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +88 -14
  38. data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
  39. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  40. data/lib/rubocop/cop/lint/literal_as_condition.rb +8 -2
  41. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +185 -0
  42. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -1
  43. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
  44. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  45. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +17 -1
  46. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +30 -16
  47. data/lib/rubocop/cop/lint.rb +1 -0
  48. data/lib/rubocop/cop/migration/department_name.rb +1 -1
  49. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  50. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -1
  51. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +11 -1
  52. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +6 -2
  53. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  54. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +12 -1
  55. data/lib/rubocop/cop/mixin.rb +2 -2
  56. data/lib/rubocop/cop/naming/block_forwarding.rb +4 -1
  57. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  58. data/lib/rubocop/cop/registry.rb +9 -1
  59. data/lib/rubocop/cop/severity.rb +12 -0
  60. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -2
  61. data/lib/rubocop/cop/style/arguments_forwarding.rb +2 -2
  62. data/lib/rubocop/cop/style/case_like_if.rb +3 -3
  63. data/lib/rubocop/cop/style/constant_visibility.rb +6 -2
  64. data/lib/rubocop/cop/style/data_inheritance.rb +14 -0
  65. data/lib/rubocop/cop/style/directive_scope.rb +154 -21
  66. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +53 -9
  67. data/lib/rubocop/cop/style/documentation_method.rb +1 -9
  68. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  69. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  70. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  71. data/lib/rubocop/cop/style/empty_method.rb +12 -2
  72. data/lib/rubocop/cop/style/endless_method.rb +31 -12
  73. data/lib/rubocop/cop/style/file_null.rb +2 -2
  74. data/lib/rubocop/cop/style/float_division.rb +10 -16
  75. data/lib/rubocop/cop/style/for.rb +5 -0
  76. data/lib/rubocop/cop/style/format_string_token.rb +1 -1
  77. data/lib/rubocop/cop/style/guard_clause.rb +4 -0
  78. data/lib/rubocop/cop/style/hash_lookup_method.rb +3 -0
  79. data/lib/rubocop/cop/style/magic_comment_format.rb +1 -1
  80. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -1
  81. data/lib/rubocop/cop/style/nested_modifier.rb +9 -4
  82. data/lib/rubocop/cop/style/nil_comparison.rb +36 -8
  83. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  84. data/lib/rubocop/cop/style/non_nil_check.rb +18 -2
  85. data/lib/rubocop/cop/style/not.rb +3 -1
  86. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +11 -1
  87. data/lib/rubocop/cop/style/one_line_conditional.rb +1 -1
  88. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -1
  89. data/lib/rubocop/cop/style/parentheses_around_condition.rb +6 -1
  90. data/lib/rubocop/cop/style/redundant_condition.rb +4 -4
  91. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +10 -1
  92. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  93. data/lib/rubocop/cop/style/redundant_parentheses.rb +11 -1
  94. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +6 -5
  95. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +6 -6
  96. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  97. data/lib/rubocop/cop/style/special_global_vars.rb +1 -1
  98. data/lib/rubocop/cop/style/struct_inheritance.rb +14 -0
  99. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  100. data/lib/rubocop/cop/style.rb +0 -1
  101. data/lib/rubocop/cop/util.rb +1 -1
  102. data/lib/rubocop/cops_documentation_generator.rb +23 -2
  103. data/lib/rubocop/directive_comment.rb +37 -8
  104. data/lib/rubocop/formatter/disabled_config_formatter.rb +19 -1
  105. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  106. data/lib/rubocop/formatter/github_actions_formatter.rb +6 -5
  107. data/lib/rubocop/formatter/html_formatter.rb +2 -2
  108. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  109. data/lib/rubocop/formatter/sarif_formatter.rb +181 -0
  110. data/lib/rubocop/formatter.rb +1 -0
  111. data/lib/rubocop/magic_comment.rb +27 -0
  112. data/lib/rubocop/options.rb +59 -1
  113. data/lib/rubocop/path_util.rb +1 -1
  114. data/lib/rubocop/plugin/loader.rb +1 -1
  115. data/lib/rubocop/rake_task.rb +1 -1
  116. data/lib/rubocop/remote_config.rb +4 -4
  117. data/lib/rubocop/result_cache.rb +16 -5
  118. data/lib/rubocop/runner.rb +64 -21
  119. data/lib/rubocop/unified_diff.rb +257 -0
  120. data/lib/rubocop/version.rb +1 -1
  121. data/lib/rubocop.rb +4 -1
  122. metadata +8 -4
  123. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +0 -46
@@ -26,7 +26,8 @@ module RuboCop
26
26
  # @api private
27
27
  PUSH_POP_ARGS_PATTERN = "([+\\-]#{COP_NAME_PATTERN_NC}(?:\\s+[+\\-]#{COP_NAME_PATTERN_NC})*)"
28
28
  # @api private
29
- AVAILABLE_MODES = %w[disable enable todo push pop disable-next todo-next].freeze
29
+ AVAILABLE_MODES = %w[disable enable todo push pop disable-next todo-next enable-next
30
+ next].freeze
30
31
  # @api private
31
32
  # Longest first, so a `-next` mode is not matched as its prefix
32
33
  # (`-` is a word boundary).
@@ -43,6 +44,8 @@ module RuboCop
43
44
  .gsub(' ', '\s*')
44
45
  )
45
46
  # @api private
47
+ SIGNED_OPERATIONS = %w[+ -].freeze
48
+ # @api private
46
49
  TRAILING_COMMENT_MARKER = '--'
47
50
  # @api private
48
51
  MALFORMED_DIRECTIVE_WITHOUT_COP_NAME_REGEXP = Regexp.new(
@@ -71,7 +74,7 @@ module RuboCop
71
74
  # Checks if the comment is malformed as a `# rubocop:` directive
72
75
  def malformed?
73
76
  return true if !start_with_marker? || @match_data.nil?
74
- return true if missing_cop_name?
77
+ return true if missing_cop_name? || invalid_signed_args?
75
78
 
76
79
  tail = @match_data.post_match.lstrip
77
80
  !(tail.empty? || tail.start_with?(TRAILING_COMMENT_MARKER))
@@ -84,6 +87,16 @@ module RuboCop
84
87
  MALFORMED_DIRECTIVE_WITHOUT_COP_NAME_REGEXP.match?(comment.text)
85
88
  end
86
89
 
90
+ # `push` and `next` arguments must be `+`/`-` prefixed cop names, and
91
+ # `pop` takes no arguments at all.
92
+ def invalid_signed_args?
93
+ return cops ? !cops.empty? : false if pop?
94
+ return false unless push? || next?
95
+ return false unless cops
96
+
97
+ cops.split.any? { |cop_spec| !cop_spec.start_with?('+', '-') }
98
+ end
99
+
87
100
  # The text of the directive's optional `--` trailing comment, or `nil`
88
101
  # when there is none.
89
102
  def reason
@@ -150,7 +163,12 @@ module RuboCop
150
163
 
151
164
  # Checks if this directive enables cops
152
165
  def enabled?
153
- mode == 'enable'
166
+ mode == 'enable' || enable_next?
167
+ end
168
+
169
+ # Checks if this directive enables cops for the next statement only
170
+ def enable_next?
171
+ mode == 'enable-next'
154
172
  end
155
173
 
156
174
  # Checks if this directive is a push
@@ -163,10 +181,17 @@ module RuboCop
163
181
  mode == 'pop'
164
182
  end
165
183
 
166
- # Returns the push arguments as a hash of cop names with their operations
167
- def push_args
168
- @push_args ||= parse_push_args
184
+ # Checks if this directive toggles cops for the next statement only
185
+ def next?
186
+ mode == 'next'
187
+ end
188
+
189
+ # Returns the `+`/`-` arguments of a `push` or `next` directive as a hash
190
+ # of operations to cop names
191
+ def signed_args
192
+ @signed_args ||= parse_signed_args
169
193
  end
194
+ alias push_args signed_args
170
195
 
171
196
  # Checks if this directive enables all cops
172
197
  def enabled_all?
@@ -210,6 +235,8 @@ module RuboCop
210
235
  end
211
236
 
212
237
  def directive_count
238
+ return signed_args.values.sum(&:count) if push? || next?
239
+
213
240
  raw_cop_names.count
214
241
  end
215
242
 
@@ -244,13 +271,15 @@ module RuboCop
244
271
  cops - [LINT_REDUNDANT_DIRECTIVE_COP, LINT_SYNTAX_COP]
245
272
  end
246
273
 
247
- def parse_push_args
248
- return {} unless push? && cops
274
+ def parse_signed_args
275
+ return {} unless (push? || next?) && cops
249
276
 
250
277
  args = {}
251
278
  cops.split.each do |cop_spec|
252
279
  op = cop_spec[0]
253
280
  cop_name = cop_spec[1..]
281
+ next unless SIGNED_OPERATIONS.include?(op)
282
+
254
283
  args[op] ||= []
255
284
  args[op] << cop_name
256
285
  end
@@ -23,6 +23,7 @@ module RuboCop
23
23
  Enabled
24
24
  Exclude
25
25
  Include
26
+ Preview
26
27
  Reference
27
28
  References
28
29
  Safe
@@ -93,6 +94,7 @@ module RuboCop
93
94
  command += ' --auto-gen-only-exclude' if @options[:auto_gen_only_exclude]
94
95
  command += ' --disable-pending-cops' if @options[:disable_pending_cops]
95
96
  command += ' --enable-pending-cops' if @options[:enable_pending_cops]
97
+ command += preview_option
96
98
  command += exclude_limit_option
97
99
  command += ' --no-offense-counts' unless show_offense_counts?
98
100
 
@@ -103,6 +105,14 @@ module RuboCop
103
105
  command
104
106
  end
105
107
 
108
+ def preview_option
109
+ case @options[:preview]
110
+ when true then ' --preview'
111
+ when false then ' --no-preview'
112
+ else ''
113
+ end
114
+ end
115
+
106
116
  def exclude_limit_option
107
117
  if no_exclude_limit?
108
118
  ' --no-exclude-limit'
@@ -259,7 +269,7 @@ module RuboCop
259
269
  config = ConfigStore.new.for(parent)
260
270
  cfg = config[cop_name] || {}
261
271
 
262
- if merge_mode_for_exclude?(config) || merge_mode_for_exclude?(cfg)
272
+ if exclude_merges?(config, cfg)
263
273
  offending_files
264
274
  else
265
275
  cop_exclude = cfg['Exclude']
@@ -272,6 +282,14 @@ module RuboCop
272
282
  end
273
283
  end
274
284
 
285
+ # `Preview` merges `Exclude` with the default configuration, so the excludes
286
+ # already in the configuration do not have to be copied into the todo file.
287
+ def exclude_merges?(config, cop_config)
288
+ merge_mode_for_exclude?(config) ||
289
+ merge_mode_for_exclude?(cop_config) ||
290
+ config.preview?(@options)
291
+ end
292
+
275
293
  def merge_mode_for_exclude?(cfg)
276
294
  Array(cfg.to_h.dig('inherit_mode', 'merge')).include?('Exclude')
277
295
  end
@@ -23,6 +23,7 @@ module RuboCop
23
23
  '[pa]cman' => 'PacmanFormatter',
24
24
  '[p]rogress' => 'ProgressFormatter',
25
25
  '[q]uiet' => 'QuietFormatter',
26
+ '[sa]rif' => 'SARIFFormatter',
26
27
  '[s]imple' => 'SimpleTextFormatter',
27
28
  '[t]ap' => 'TapFormatter',
28
29
  '[w]orst' => 'WorstOffendersFormatter'
@@ -57,7 +58,7 @@ module RuboCop
57
58
  if output_path
58
59
  dir_path = File.dirname(output_path)
59
60
  FileUtils.mkdir_p(dir_path)
60
- output = File.open(output_path, 'w') # rubocop:disable Style/FileOpen
61
+ output = File.open(output_path, 'w') # rubocop:disable Style/FileOpen -- the file is closed by `close_output_files`, not by a block
61
62
  else
62
63
  output = $stdout
63
64
  end
@@ -15,6 +15,10 @@ module RuboCop
15
15
  @offenses_for_files[file] = offenses unless offenses.empty?
16
16
  end
17
17
 
18
+ def file_started(_file, options)
19
+ @config_store = options[:config_store]
20
+ end
21
+
18
22
  def finished(_inspected_files)
19
23
  @offenses_for_files.each do |file, offenses|
20
24
  offenses.each do |offense|
@@ -31,11 +35,8 @@ module RuboCop
31
35
  end
32
36
 
33
37
  def minimum_severity_to_fail
34
- @minimum_severity_to_fail ||= begin
35
- # Unless given explicitly as `fail_level`, `:info` severity offenses do not fail
36
- name = options.fetch(:fail_level, :refactor)
37
- RuboCop::Cop::Severity.new(name)
38
- end
38
+ @minimum_severity_to_fail ||=
39
+ RuboCop::Cop::Severity.minimum_to_fail(options, @config_store&.for_pwd)
39
40
  end
40
41
 
41
42
  def github_severity(offense)
@@ -74,7 +74,7 @@ module RuboCop
74
74
  end
75
75
 
76
76
  # Make Kernel#binding public.
77
- # rubocop:disable-next Lint/UselessMethodDefinition
77
+ # rubocop:disable-next Lint/UselessMethodDefinition -- redefined only to make `Kernel#binding` public
78
78
  def binding
79
79
  super
80
80
  end
@@ -143,7 +143,7 @@ module RuboCop
143
143
  }.freeze
144
144
 
145
145
  # Make Kernel#binding public.
146
- # rubocop:disable-next Lint/UselessMethodDefinition
146
+ # rubocop:disable-next Lint/UselessMethodDefinition -- redefined only to make `Kernel#binding` public
147
147
  def binding
148
148
  super
149
149
  end
@@ -47,7 +47,7 @@ module RuboCop
47
47
  end
48
48
  end
49
49
 
50
- # rubocop:disable-next 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>)
@@ -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
@@ -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
 
@@ -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
@@ -33,6 +33,8 @@ module RuboCop
33
33
 
34
34
  define_options.parse!(args)
35
35
 
36
+ imply_autocorrect_for_diff
37
+
36
38
  @validator.validate_compatibility
37
39
 
38
40
  if @options[:stdin]
@@ -51,6 +53,15 @@ module RuboCop
51
53
 
52
54
  private
53
55
 
56
+ # `--diff` is a dry run of autocorrection, so it turns autocorrection on
57
+ # unless the user already picked a mode with `-a`, `-A` or `-x`.
58
+ def imply_autocorrect_for_diff
59
+ return unless @options[:diff] && !@options[:autocorrect]
60
+
61
+ @options[:safe_autocorrect] = true
62
+ @options[:autocorrect] = true
63
+ end
64
+
54
65
  # rubocop:disable-next Metrics/AbcSize
55
66
  def define_options
56
67
  OptionParser.new do |opts|
@@ -92,12 +103,17 @@ module RuboCop
92
103
  option(opts, '--enable-pending-cops')
93
104
  option(opts, '--disable-all-cops')
94
105
  option(opts, '--enable-all-cops')
106
+ option(opts, '--[no-]preview')
95
107
  option(opts, '--ignore-disable-comments')
96
108
  option(opts, '--force-exclusion')
97
109
  option(opts, '--only-recognized-file-types')
98
110
  option(opts, '--ignore-parent-exclusion')
99
111
  option(opts, '--ignore-unrecognized-cops')
100
112
  option(opts, '--force-default-config')
113
+ option(opts, '--changed [REVISION]') do |revision|
114
+ @options[:changed] = revision || ChangedFiles::DEFAULT_REVISION
115
+ @validator.validate_changed_revision(revision)
116
+ end
101
117
  option(opts, '-s', '--stdin FILE')
102
118
  option(opts, '--editor-mode')
103
119
  option(opts, '-P', '--[no-]parallel')
@@ -135,7 +151,7 @@ module RuboCop
135
151
  end
136
152
  end
137
153
 
138
- # rubocop:todo Naming/InclusiveLanguage
154
+ # rubocop:todo Naming/InclusiveLanguage -- `--auto-correct` is the deprecated flag name
139
155
  # the autocorrect command-line arguments map to the autocorrect @options values like so:
140
156
  # :fix_layout :autocorrect :safe_autocorrect :autocorrect_all
141
157
  # -x, --fix-layout true true - -
@@ -161,6 +177,8 @@ module RuboCop
161
177
  end
162
178
 
163
179
  option(opts, '--disable-uncorrectable')
180
+
181
+ option(opts, '--diff')
164
182
  end
165
183
  end
166
184
  # rubocop:enable Naming/InclusiveLanguage
@@ -400,6 +418,8 @@ module RuboCop
400
418
  validate_display_only_failed_and_display_only_correctable
401
419
  validate_display_only_correctable_and_autocorrect
402
420
  validate_lsp_and_editor_mode
421
+ validate_diff
422
+ validate_changed_and_stdin
403
423
  validate_enable_all_cops_and_disable_all_cops
404
424
  disable_parallel_when_invalid_option_combo
405
425
 
@@ -447,6 +467,20 @@ module RuboCop
447
467
  '--display-only-failed cannot be used together with other display options.'
448
468
  end
449
469
 
470
+ # `--diff` promises not to write anything, and `--auto-gen-config` exists to
471
+ # write a file.
472
+ def validate_diff
473
+ return unless @options.key?(:diff) && @options.key?(:auto_gen_config)
474
+
475
+ raise OptionArgumentError, '--diff cannot be used with --auto-gen-config.'
476
+ end
477
+
478
+ def validate_changed_and_stdin
479
+ return unless @options.key?(:changed) && @options.key?(:stdin)
480
+
481
+ raise OptionArgumentError, '--changed cannot be used with --stdin.'
482
+ end
483
+
450
484
  def validate_lsp_and_editor_mode
451
485
  return if !@options.key?(:lsp) || !@options.key?(:editor_mode)
452
486
 
@@ -511,6 +545,17 @@ module RuboCop
511
545
  @incompatible_options ||= @options.keys & Options::EXITING_OPTIONS
512
546
  end
513
547
 
548
+ # OptionParser hands `--changed lib/` the path as the revision, which is a
549
+ # natural thing to type, so point at the fix rather than at git's error.
550
+ def validate_changed_revision(revision)
551
+ return unless revision && File.exist?(revision)
552
+
553
+ raise OptionArgumentError,
554
+ "--changed takes a git revision, but `#{revision}` is a path. Write the revision " \
555
+ "as `--changed=#{revision}` if that is really what you meant, or drop it to " \
556
+ 'compare against HEAD.'
557
+ end
558
+
514
559
  def validate_exclude_limit_option
515
560
  return if /^\d+$/.match?(@options[:exclude_limit])
516
561
 
@@ -572,6 +617,10 @@ module RuboCop
572
617
  disable_uncorrectable: ['Used with --autocorrect to annotate any',
573
618
  'offenses that do not support autocorrect',
574
619
  'with `rubocop:todo` comments.'],
620
+ diff: ['Print a unified diff of what autocorrection',
621
+ 'would change, without writing any files.',
622
+ 'Turns on safe autocorrection unless a mode',
623
+ 'was already given with -a, -A or -x.'],
575
624
  no_exclude_limit: ['Do not set the limit for how many files to exclude.'],
576
625
  force_exclusion: ['Any files excluded by `Exclude` in configuration',
577
626
  'files will be excluded, even if given explicitly',
@@ -584,6 +633,10 @@ module RuboCop
584
633
  ignore_parent_exclusion: ['Prevent from inheriting `AllCops/Exclude` from',
585
634
  'parent folders.'],
586
635
  ignore_unrecognized_cops: ['Ignore unrecognized cops or departments in the config.'],
636
+ changed: ['Inspect only the files that differ from a git',
637
+ 'revision, defaulting to HEAD. Untracked files',
638
+ 'count as changed. Pass a revision with',
639
+ '`--changed=REVISION`.'],
587
640
  force_default_config: ['Use default configuration even if configuration',
588
641
  'files are present in the directory tree.'],
589
642
  format: ['Choose an output formatter. This option',
@@ -598,6 +651,7 @@ module RuboCop
598
651
  'specified --format, or the default format',
599
652
  'if no format is specified.'],
600
653
  fail_level: ['Minimum severity for exit with error code.',
654
+ 'Overrides `AllCops: FailLevel` in the configuration.',
601
655
  ' [A] autocorrect',
602
656
  ' [I] info',
603
657
  ' [R] refactor',
@@ -642,6 +696,10 @@ module RuboCop
642
696
  '`AllCops/DisabledByDefault` in config files.'],
643
697
  display_style_guide: 'Display style guide URLs in offense messages.',
644
698
  enable_pending_cops: 'Run with pending cops.',
699
+ preview: ['Opt in to unstable behavior: cops that are',
700
+ '`Enabled: preview`, and changes to existing',
701
+ 'cops that are not the default yet.',
702
+ 'Overrides `AllCops: Preview`.'],
645
703
  enable_all_cops: ['Run with all cops enabled, including those',
646
704
  'disabled by default. Overrides',
647
705
  '`AllCops/EnabledByDefault` and',
@@ -44,7 +44,7 @@ module RuboCop
44
44
  uri.start_with?('http://', 'https://')
45
45
  end
46
46
 
47
- SMART_PATH_CACHE = {} # rubocop:disable Style/MutableConstant
47
+ SMART_PATH_CACHE = {} # rubocop:disable Style/MutableConstant -- a cache, written to at runtime
48
48
  private_constant :SMART_PATH_CACHE
49
49
 
50
50
  def smart_path(path)
@@ -8,7 +8,7 @@ module RuboCop
8
8
  # A class for loading and resolving plugins.
9
9
  # @api private
10
10
  class Loader
11
- # rubocop:disable Layout/LineLength
11
+ # rubocop:disable Layout/LineLength -- the comment on each key does not wrap
12
12
  DEFAULT_PLUGIN_CONFIG = {
13
13
  'enabled' => true,
14
14
  'require_path' => nil, # If not set, will be set to the plugin name
@@ -74,7 +74,7 @@ module RuboCop
74
74
 
75
75
  def setup_subtasks(name, *args, &task_block) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
76
76
  namespace(name) do
77
- # rubocop:todo-next Naming/InclusiveLanguage
77
+ # rubocop:todo-next Naming/InclusiveLanguage -- the deprecated flag spelling is the subject here
78
78
  task(:auto_correct, *args) do |_, task_args|
79
79
  require 'rainbow'
80
80
  warn Rainbow(