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
@@ -49,9 +49,9 @@ module RuboCop
49
49
  http.use_ssl = uri.instance_of?(URI::HTTPS)
50
50
 
51
51
  generate_request(uri) do |request|
52
- handle_response(http.request(request), limit, &block)
52
+ handle_response(http.request(request), uri, limit, &block)
53
53
  rescue SocketError => e
54
- handle_response(e, limit, &block)
54
+ handle_response(e, uri, limit, &block)
55
55
  end
56
56
  end
57
57
 
@@ -64,12 +64,12 @@ module RuboCop
64
64
  yield request
65
65
  end
66
66
 
67
- def handle_response(response, limit, &block)
67
+ def handle_response(response, uri, limit, &block)
68
68
  case response
69
69
  when Net::HTTPSuccess, Net::HTTPNotModified, SocketError
70
70
  yield response
71
71
  when Net::HTTPRedirection
72
- request(URI.parse(response['location']), limit - 1, &block)
72
+ request(URI.join(uri, response['location']), limit - 1, &block)
73
73
  else
74
74
  begin
75
75
  response.error!
@@ -123,7 +123,7 @@ module RuboCop
123
123
  end
124
124
 
125
125
  def valid?
126
- File.exist?(@path)
126
+ !@checksum_unavailable && File.exist?(@path)
127
127
  end
128
128
 
129
129
  def load
@@ -132,6 +132,8 @@ module RuboCop
132
132
  end
133
133
 
134
134
  def save(offenses)
135
+ return if @checksum_unavailable
136
+
135
137
  dir = File.dirname(@path)
136
138
 
137
139
  begin
@@ -176,14 +178,23 @@ module RuboCop
176
178
  end
177
179
 
178
180
  def file_checksum(file, config_store)
181
+ stat = File.stat(file)
182
+ return unavailable_checksum unless stat.file?
183
+
179
184
  digester = Digest::SHA1.new
180
- mode = File.stat(file).mode
181
- digester.update("#{file}#{mode}#{config_store.for_file(file).signature}")
185
+ digester.update("#{file}#{stat.mode}#{config_store.for_file(file).signature}")
182
186
  digester.file(file)
183
187
  digester.hexdigest
184
188
  rescue Errno::ENOENT
185
- # Spurious files that come and go should not cause a crash, at least not
186
- # here.
189
+ # Spurious files that come and go should not cause a crash, at least not here.
190
+ unavailable_checksum
191
+ end
192
+
193
+ # Every file that fails to checksum shares this sentinel value, so the cache entry
194
+ # must be neither saved nor considered valid, or one file's cached results could be
195
+ # served as another's.
196
+ def unavailable_checksum
197
+ @checksum_unavailable = true
187
198
  '_'
188
199
  end
189
200
 
@@ -53,7 +53,7 @@ module RuboCop
53
53
  Lint/RedundantCopDisableDirective RedundantCopDisableDirective Lint
54
54
  ].freeze
55
55
 
56
- attr_reader :errors, :warnings
56
+ attr_reader :errors, :warnings, :diffs
57
57
  attr_writer :aborting
58
58
 
59
59
  def initialize(options, config_store)
@@ -63,6 +63,7 @@ module RuboCop
63
63
  @warnings = []
64
64
  @aborting = false
65
65
  @inspected_files = []
66
+ @diffs = []
66
67
  @report_queue = {}
67
68
  end
68
69
 
@@ -73,7 +74,7 @@ module RuboCop
73
74
  # inconsistencies between workers.
74
75
  ResultCache.source_checksum
75
76
 
76
- target_files = find_target_files(paths)
77
+ target_files = only_changed(find_target_files(paths))
77
78
  build_project_index(target_files)
78
79
 
79
80
  if @options[:list_target_files]
@@ -95,6 +96,16 @@ module RuboCop
95
96
 
96
97
  private
97
98
 
99
+ # The project index is deliberately built from the unfiltered file list:
100
+ # cross-file offenses must not depend on which files happen to have changed.
101
+ def only_changed(target_files)
102
+ return target_files unless @options[:changed]
103
+
104
+ changed = ChangedFiles.new(@options[:changed]).paths
105
+
106
+ target_files.select { |file| changed.include?(file) }.freeze
107
+ end
108
+
98
109
  def find_target_files(paths)
99
110
  target_finder = TargetFinder.new(@config_store, @options)
100
111
  mode = if @options[:only_recognized_file_types]
@@ -205,8 +216,7 @@ module RuboCop
205
216
  end
206
217
 
207
218
  def run_in_parallel?(files)
208
- return false if @options[:auto_gen_config]
209
- return false unless @options[:parallel]
219
+ return false unless parallel_supported_by_options?
210
220
 
211
221
  if files.size <= 1
212
222
  puts 'Skipping parallel inspection: only a single file needs inspection' if @options[:debug]
@@ -220,6 +230,13 @@ module RuboCop
220
230
  true
221
231
  end
222
232
 
233
+ # `--auto-gen-config` needs the offenses of every file in one place, and
234
+ # `--diff` collects the diffs in this process, where a worker's copy of
235
+ # them would be lost.
236
+ def parallel_supported_by_options?
237
+ @options[:parallel] && !@options[:auto_gen_config] && !@options[:diff]
238
+ end
239
+
223
240
  def project_index_disables_parallel?
224
241
  return false if @project_index.nil? || !Gem.win_platform?
225
242
 
@@ -379,6 +396,7 @@ module RuboCop
379
396
  def do_inspection_loop(file)
380
397
  # We can reuse the prism result since the source did not change yet.
381
398
  processed_source = get_processed_source(file, @prism_result)
399
+ original_source = processed_source.raw_source
382
400
  # This variable is 2d array used to track corrected offenses after each
383
401
  # inspection iteration. This is used to output meaningful infinite loop
384
402
  # error message.
@@ -391,11 +409,7 @@ module RuboCop
391
409
  # once. The corrections are kept in memory while iterating and written
392
410
  # back to the file when the loop is done.
393
411
  iterate_until_no_changes(processed_source, offenses_by_iteration) do
394
- # The offenses that couldn't be corrected will be found again so we
395
- # only keep the corrected ones in order to avoid duplicate reporting.
396
- !offenses_by_iteration.empty? && offenses_by_iteration.last.select!(&:corrected?)
397
- team, new_offenses, updated_source_file = inspect_iteration(processed_source)
398
- offenses_by_iteration.push(new_offenses)
412
+ team, updated_source_file = inspect_and_correct(processed_source, offenses_by_iteration)
399
413
 
400
414
  # We have to reprocess the source to pickup the changes. Since the
401
415
  # change could (theoretically) introduce parsing errors, we break the
@@ -411,15 +425,49 @@ module RuboCop
411
425
  # Return summary of corrected offenses after all iterations
412
426
  [processed_source, offenses_by_iteration.flatten.uniq]
413
427
  ensure
414
- # Write the file once, even when the loop was left through an exception
415
- # (e.g. an infinite correction loop), like the per-iteration writes
416
- # used to be.
417
- File.write(file, corrected_source) if corrected_source
428
+ finalize_corrections(file, original_source, corrected_source)
429
+ end
430
+
431
+ def inspect_and_correct(processed_source, offenses_by_iteration)
432
+ # The offenses that couldn't be corrected will be found again so we
433
+ # only keep the corrected ones in order to avoid duplicate reporting.
434
+ !offenses_by_iteration.empty? && offenses_by_iteration.last.select!(&:corrected?)
435
+ team, new_offenses, updated_source_file = inspect_iteration(processed_source)
436
+ offenses_by_iteration.push(new_offenses)
437
+
438
+ [team, updated_source_file]
439
+ end
440
+
441
+ # Write the file once, even when the loop was left through an exception
442
+ # (e.g. an infinite correction loop), like the per-iteration writes used
443
+ # to be. Under `--diff` nothing is written at all.
444
+ def finalize_corrections(file, original_source, corrected_source)
445
+ if @options[:diff]
446
+ record_diff(file, original_source, corrected_source)
447
+ elsif corrected_source
448
+ File.write(file, corrected_source)
449
+ end
450
+ end
451
+
452
+ # `--diff` reports what autocorrection would do instead of doing it. With
453
+ # `--stdin` the corrected source is handed back through the options rather
454
+ # than deferred, so that is where the new source comes from.
455
+ def record_diff(file, original_source, corrected_source)
456
+ new_source = @options[:stdin] || corrected_source
457
+ return unless original_source && new_source
458
+
459
+ # The original source is the file as it is on disk, so the corrected one
460
+ # has to go through the same line ending conversion `File.write` would
461
+ # apply, or on Windows every single line reads as changed.
462
+ new_source = emulate_write_read_cycle(new_source)
463
+
464
+ diff = UnifiedDiff.new(PathUtil.smart_path(file), original_source, new_source).to_s
465
+ @diffs << diff unless diff.empty?
418
466
  end
419
467
 
420
468
  def inspect_iteration(processed_source)
421
469
  team = mobilize_team(processed_source)
422
- team.defer_corrections = in_memory_corrections_possible?
470
+ team.defer_corrections = @options[:diff] || in_memory_corrections_possible?
423
471
  offenses, updated_source_file = inspect_file(processed_source, team)
424
472
  [team, offenses, updated_source_file]
425
473
  end
@@ -602,13 +650,8 @@ module RuboCop
602
650
  end
603
651
 
604
652
  def minimum_severity_to_fail
605
- @minimum_severity_to_fail ||= begin
606
- # Unless given explicitly as `fail_level`, `:info` severity offenses do not fail
607
- name = @options[:fail_level] || :refactor
608
-
609
- # autocorrect is a fake level - use the default
610
- RuboCop::Cop::Severity.new(name == :autocorrect ? :refactor : name)
611
- end
653
+ @minimum_severity_to_fail ||=
654
+ RuboCop::Cop::Severity.minimum_to_fail(@options, @config_store.for_pwd)
612
655
  end
613
656
 
614
657
  # rubocop:disable-next Metrics/MethodLength
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ # Renders a unified diff between two versions of a file's source.
5
+ #
6
+ # RuboCop has no diffing dependency and this output is meant to be read by
7
+ # people (and occasionally piped into `git apply`), so the goal is a diff
8
+ # that is correct and readable, not one that matches `diff -u` byte for byte.
9
+ #
10
+ # @api private
11
+ class UnifiedDiff
12
+ CONTEXT_LINES = 3
13
+ NO_NEWLINE_MARKER = "\\n"
14
+
15
+ # Finding a minimal diff costs time and memory proportional to the square
16
+ # of the number of edits. Past this many edits the file was rewritten
17
+ # rather than corrected, so the changed region is reported as a single
18
+ # replacement instead.
19
+ MAX_EDIT_DISTANCE = 400
20
+
21
+ def initialize(path, old_source, new_source)
22
+ @path = path
23
+ @old_lines = old_source.lines
24
+ @new_lines = new_source.lines
25
+ end
26
+
27
+ # @return [String] the diff, or an empty string when the sources are identical
28
+ def to_s
29
+ return '' if @old_lines == @new_lines
30
+
31
+ hunks = hunks_for(edit_script)
32
+ return '' if hunks.empty?
33
+
34
+ ["--- a/#{@path}\n", "+++ b/#{@path}\n", *hunks].join
35
+ end
36
+
37
+ private
38
+
39
+ def edit_script
40
+ prefix = common_prefix_length
41
+ suffix = common_suffix_length(prefix)
42
+ old_last = @old_lines.length - suffix
43
+ new_last = @new_lines.length - suffix
44
+
45
+ [
46
+ *@old_lines[0...prefix].map { |line| [:equal, line] },
47
+ *middle_script(@old_lines[prefix...old_last], @new_lines[prefix...new_last]),
48
+ *@old_lines[old_last..].map { |line| [:equal, line] }
49
+ ]
50
+ end
51
+
52
+ def common_prefix_length
53
+ length = 0
54
+ length += 1 while length < @old_lines.length && @old_lines[length] == @new_lines[length]
55
+ length
56
+ end
57
+
58
+ def common_suffix_length(prefix)
59
+ length = 0
60
+ max = [@old_lines.length, @new_lines.length].min - prefix
61
+ while length < max &&
62
+ @old_lines[@old_lines.length - 1 - length] == @new_lines[@new_lines.length - 1 - length]
63
+ length += 1
64
+ end
65
+ length
66
+ end
67
+
68
+ def middle_script(old_lines, new_lines)
69
+ # The region that actually differs is diffed on its own, so the rest of
70
+ # the algorithm works on these two slices rather than the whole file.
71
+ @from_lines = old_lines
72
+ @to_lines = new_lines
73
+
74
+ myers_script ||
75
+ [*old_lines.map { |line| [:delete, line] }, *new_lines.map { |line| [:insert, line] }]
76
+ end
77
+
78
+ # Myers' diff algorithm. Each round `d` extends the furthest reaching path
79
+ # for every diagonal `k`; the first path to reach the end of both sources
80
+ # is a shortest edit script, which is then reconstructed from the saved
81
+ # rounds. Returns `nil` when the sources are too different to bother.
82
+ def myers_script
83
+ trace = []
84
+ furthest = Hash.new(0)
85
+ max_distance = [@from_lines.length + @to_lines.length, MAX_EDIT_DISTANCE].min
86
+
87
+ 0.upto(max_distance) do |distance|
88
+ trace << furthest.dup
89
+
90
+ (-distance).step(distance, 2) do |diagonal|
91
+ furthest[diagonal] = advance(furthest, diagonal, distance)
92
+
93
+ return backtrack(trace) if reached_end?(furthest[diagonal], diagonal)
94
+ end
95
+ end
96
+
97
+ nil
98
+ end
99
+
100
+ def reached_end?(old_index, diagonal)
101
+ old_index >= @from_lines.length && old_index - diagonal >= @to_lines.length
102
+ end
103
+
104
+ # Picks the better of the two paths reaching this diagonal, then follows
105
+ # the diagonal as far as the lines match.
106
+ def advance(furthest, diagonal, distance)
107
+ old_index = start_of_path(furthest, diagonal, distance)
108
+
109
+ follow_diagonal(old_index, old_index - diagonal)
110
+ end
111
+
112
+ # A path arrives at this diagonal either from the one above it (having
113
+ # inserted a line) or the one below it (having deleted one); whichever
114
+ # reached further wins.
115
+ def previous_diagonal(furthest, diagonal, distance)
116
+ if diagonal == -distance ||
117
+ (diagonal != distance && furthest[diagonal - 1] < furthest[diagonal + 1])
118
+ diagonal + 1
119
+ else
120
+ diagonal - 1
121
+ end
122
+ end
123
+
124
+ def start_of_path(furthest, diagonal, distance)
125
+ previous = previous_diagonal(furthest, diagonal, distance)
126
+
127
+ previous > diagonal ? furthest[previous] : furthest[previous] + 1
128
+ end
129
+
130
+ # Matching lines cost nothing, so the path slides along them for free.
131
+ def follow_diagonal(old_index, new_index)
132
+ while old_index < @from_lines.length && new_index < @to_lines.length &&
133
+ @from_lines[old_index] == @to_lines[new_index]
134
+ old_index += 1
135
+ new_index += 1
136
+ end
137
+
138
+ old_index
139
+ end
140
+
141
+ def backtrack(trace)
142
+ script = []
143
+ position = [@from_lines.length, @to_lines.length]
144
+
145
+ trace.each_with_index.reverse_each do |furthest, distance|
146
+ previous = previous_position(furthest, distance, *position)
147
+ position = walk_diagonal(script, position, previous)
148
+ next if distance.zero?
149
+
150
+ position = record_edit(script, position, previous)
151
+ end
152
+
153
+ script
154
+ end
155
+
156
+ # Both sides step back together for as long as their lines match.
157
+ def walk_diagonal(script, position, previous)
158
+ old_index, new_index = position
159
+
160
+ while old_index > previous[0] && new_index > previous[1]
161
+ old_index -= 1
162
+ new_index -= 1
163
+ script.unshift([:equal, @from_lines[old_index]])
164
+ end
165
+
166
+ [old_index, new_index]
167
+ end
168
+
169
+ def record_edit(script, position, previous)
170
+ old_index, new_index = position
171
+
172
+ if old_index == previous[0]
173
+ new_index -= 1
174
+ script.unshift([:insert, @to_lines[new_index]])
175
+ else
176
+ old_index -= 1
177
+ script.unshift([:delete, @from_lines[old_index]])
178
+ end
179
+
180
+ [old_index, new_index]
181
+ end
182
+
183
+ def previous_position(furthest, distance, old_index, new_index)
184
+ previous = previous_diagonal(furthest, old_index - new_index, distance)
185
+ previous_old = furthest[previous]
186
+
187
+ [previous_old, previous_old - previous]
188
+ end
189
+
190
+ def hunks_for(script)
191
+ changed = script.each_index.reject { |index| script[index].first == :equal }
192
+ return [] if changed.empty?
193
+
194
+ ranges_for(changed, script.length).map { |range| hunk(script, range) }
195
+ end
196
+
197
+ # Every change is shown with a few lines of context around it, and changes
198
+ # whose context would overlap are shown as a single hunk.
199
+ def ranges_for(changed, script_length)
200
+ ranges = []
201
+
202
+ changed.each do |index|
203
+ first = [index - CONTEXT_LINES, 0].max
204
+ last = [index + CONTEXT_LINES, script_length - 1].min
205
+
206
+ if ranges.last && first <= ranges.last.last + 1
207
+ ranges[-1] = (ranges.last.first..last)
208
+ else
209
+ ranges << (first..last)
210
+ end
211
+ end
212
+
213
+ ranges
214
+ end
215
+
216
+ def hunk(script, range)
217
+ old_start, new_start = start_positions(script, range.first)
218
+ old_count = script[range].count { |kind, _| kind != :insert }
219
+ new_count = script[range].count { |kind, _| kind != :delete }
220
+ body = script[range].map { |kind, line| formatted_line(kind, line) }
221
+
222
+ [
223
+ "@@ -#{position(old_start, old_count)} +#{position(new_start, new_count)} @@\n",
224
+ *body
225
+ ].join
226
+ end
227
+
228
+ def start_positions(script, index)
229
+ old_line = 1
230
+ new_line = 1
231
+
232
+ script[0...index].each do |entry|
233
+ old_line += 1 unless entry.first == :insert
234
+ new_line += 1 unless entry.first == :delete
235
+ end
236
+
237
+ [old_line, new_line]
238
+ end
239
+
240
+ # An empty side is anchored to the line it follows, which is how `diff`
241
+ # reports a pure insertion or deletion, and a single line drops the count.
242
+ def position(start, count)
243
+ case count
244
+ when 0 then "#{start - 1},0"
245
+ when 1 then start.to_s
246
+ else "#{start},#{count}"
247
+ end
248
+ end
249
+
250
+ def formatted_line(kind, line)
251
+ marker = { equal: ' ', delete: '-', insert: '+' }.fetch(kind)
252
+ return "#{marker}#{line}" if line.end_with?("\n")
253
+
254
+ "#{marker}#{line}\n#{NO_NEWLINE_MARKER}"
255
+ end
256
+ end
257
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.90.0'
6
+ STRING = '1.91.0'
7
7
 
8
8
  MSG = '%<version>s (using %<parser_version>s, ' \
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
data/lib/rubocop.rb CHANGED
@@ -27,6 +27,7 @@ require_relative 'rubocop/ext/regexp_parser'
27
27
  require_relative 'rubocop/core_ext/string'
28
28
  require_relative 'rubocop/ext/processed_source'
29
29
 
30
+ require_relative 'rubocop/changed_files'
30
31
  require_relative 'rubocop/error'
31
32
  require_relative 'rubocop/file_finder'
32
33
  require_relative 'rubocop/file_patterns'
@@ -34,10 +35,11 @@ require_relative 'rubocop/name_similarity'
34
35
  require_relative 'rubocop/path_util'
35
36
  require_relative 'rubocop/platform'
36
37
  require_relative 'rubocop/string_interpreter'
38
+ require_relative 'rubocop/unified_diff'
37
39
  require_relative 'rubocop/util'
38
40
  require_relative 'rubocop/warning'
39
41
 
40
- # rubocop:disable Style/RequireOrder
42
+ # rubocop:disable Style/RequireOrder -- ordered by load dependency, not alphabetically
41
43
 
42
44
  require_relative 'rubocop/project_index_loader'
43
45
  require_relative 'rubocop/cop/util'
@@ -112,6 +114,7 @@ require_relative 'rubocop/target_finder'
112
114
  require_relative 'rubocop/directive_comment'
113
115
  require_relative 'rubocop/comment_config/directive_range'
114
116
  require_relative 'rubocop/comment_config/disable_next'
117
+ require_relative 'rubocop/comment_config/push_pop'
115
118
  require_relative 'rubocop/comment_config'
116
119
  require_relative 'rubocop/result_cache'
117
120
  require_relative 'rubocop/runner'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.90.0
4
+ version: 1.91.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -201,6 +201,7 @@ files:
201
201
  - lib/rubocop/ast_aliases.rb
202
202
  - lib/rubocop/cache_config.rb
203
203
  - lib/rubocop/cached_data.rb
204
+ - lib/rubocop/changed_files.rb
204
205
  - lib/rubocop/cli.rb
205
206
  - lib/rubocop/cli/command.rb
206
207
  - lib/rubocop/cli/command/auto_generate_config.rb
@@ -218,6 +219,7 @@ files:
218
219
  - lib/rubocop/comment_config.rb
219
220
  - lib/rubocop/comment_config/directive_range.rb
220
221
  - lib/rubocop/comment_config/disable_next.rb
222
+ - lib/rubocop/comment_config/push_pop.rb
221
223
  - lib/rubocop/config.rb
222
224
  - lib/rubocop/config_finder.rb
223
225
  - lib/rubocop/config_loader.rb
@@ -502,6 +504,7 @@ files:
502
504
  - lib/rubocop/cop/lint/literal_assignment_in_condition.rb
503
505
  - lib/rubocop/cop/lint/literal_in_interpolation.rb
504
506
  - lib/rubocop/cop/lint/loop.rb
507
+ - lib/rubocop/cop/lint/misplaced_magic_comment.rb
505
508
  - lib/rubocop/cop/lint/missing_cop_enable_directive.rb
506
509
  - lib/rubocop/cop/lint/missing_super.rb
507
510
  - lib/rubocop/cop/lint/mixed_case_range.rb
@@ -785,7 +788,6 @@ files:
785
788
  - lib/rubocop/cop/style/document_dynamic_eval_definition.rb
786
789
  - lib/rubocop/cop/style/documentation.rb
787
790
  - lib/rubocop/cop/style/documentation_method.rb
788
- - lib/rubocop/cop/style/double_cop_disable_directive.rb
789
791
  - lib/rubocop/cop/style/double_negation.rb
790
792
  - lib/rubocop/cop/style/each_for_simple_loop.rb
791
793
  - lib/rubocop/cop/style/each_with_object.rb
@@ -1073,6 +1075,7 @@ files:
1073
1075
  - lib/rubocop/formatter/pacman_formatter.rb
1074
1076
  - lib/rubocop/formatter/progress_formatter.rb
1075
1077
  - lib/rubocop/formatter/quiet_formatter.rb
1078
+ - lib/rubocop/formatter/sarif_formatter.rb
1076
1079
  - lib/rubocop/formatter/simple_text_formatter.rb
1077
1080
  - lib/rubocop/formatter/tap_formatter.rb
1078
1081
  - lib/rubocop/formatter/text_util.rb
@@ -1131,6 +1134,7 @@ files:
1131
1134
  - lib/rubocop/target_finder.rb
1132
1135
  - lib/rubocop/target_ruby.rb
1133
1136
  - lib/rubocop/todo_audit.rb
1137
+ - lib/rubocop/unified_diff.rb
1134
1138
  - lib/rubocop/util.rb
1135
1139
  - lib/rubocop/version.rb
1136
1140
  - lib/rubocop/warning.rb
@@ -1142,9 +1146,9 @@ licenses:
1142
1146
  - MIT
1143
1147
  metadata:
1144
1148
  homepage_uri: https://rubocop.org/
1145
- changelog_uri: https://github.com/rubocop/rubocop/releases/tag/v1.90.0
1149
+ changelog_uri: https://github.com/rubocop/rubocop/releases/tag/v1.91.0
1146
1150
  source_code_uri: https://github.com/rubocop/rubocop/
1147
- documentation_uri: https://docs.rubocop.org/rubocop/1.90/
1151
+ documentation_uri: https://docs.rubocop.org/rubocop/1.91/
1148
1152
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues
1149
1153
  rubygems_mfa_required: 'true'
1150
1154
  rdoc_options: []
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # rubocop:disable Lint/RedundantCopDisableDirective
4
- # rubocop:disable Style/DoubleCopDisableDirective
5
-
6
- module RuboCop
7
- module Cop
8
- module Style
9
- # Detects double disable comments on one line. This is mostly to catch
10
- # automatically generated comments that need to be regenerated.
11
- #
12
- # @example
13
- # # bad
14
- # def f # rubocop:disable Style/For # rubocop:disable Metrics/AbcSize
15
- # end
16
- #
17
- # # good
18
- # # rubocop:disable Metrics/AbcSize
19
- # def f # rubocop:disable Style/For
20
- # end
21
- # # rubocop:enable Metrics/AbcSize
22
- #
23
- # # if both fit on one line
24
- # def f # rubocop:disable Style/For, Metrics/AbcSize
25
- # end
26
- #
27
- class DoubleCopDisableDirective < Base
28
- extend AutoCorrector
29
-
30
- # rubocop:enable Style/For, Style/DoubleCopDisableDirective
31
- # rubocop:enable Lint/RedundantCopDisableDirective, Metrics/AbcSize
32
- MSG = 'More than one disable comment on one line.'
33
-
34
- def on_new_investigation
35
- processed_source.comments.each do |comment|
36
- next unless comment.text.scan(/# rubocop:(?:disable|todo)/).size > 1
37
-
38
- add_offense(comment) do |corrector|
39
- corrector.replace(comment, comment.text.gsub(%r{ # rubocop:(disable|todo)}, ','))
40
- end
41
- end
42
- end
43
- end
44
- end
45
- end
46
- end