rubocop 0.84.0 → 0.85.1

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -14
  3. data/config/default.yml +33 -15
  4. data/lib/rubocop.rb +6 -0
  5. data/lib/rubocop/cli.rb +2 -2
  6. data/lib/rubocop/cli/command/auto_genenerate_config.rb +2 -2
  7. data/lib/rubocop/comment_config.rb +1 -1
  8. data/lib/rubocop/config.rb +3 -1
  9. data/lib/rubocop/config_loader.rb +1 -1
  10. data/lib/rubocop/config_loader_resolver.rb +18 -2
  11. data/lib/rubocop/config_store.rb +12 -2
  12. data/lib/rubocop/cop/bundler/gem_comment.rb +70 -1
  13. data/lib/rubocop/cop/commissioner.rb +0 -21
  14. data/lib/rubocop/cop/cop.rb +36 -21
  15. data/lib/rubocop/cop/corrector.rb +3 -1
  16. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +1 -1
  17. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +3 -1
  18. data/lib/rubocop/cop/layout/case_indentation.rb +3 -3
  19. data/lib/rubocop/cop/layout/class_structure.rb +19 -16
  20. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +2 -2
  21. data/lib/rubocop/cop/layout/end_of_line.rb +2 -2
  22. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  23. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +1 -1
  24. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +2 -2
  25. data/lib/rubocop/cop/layout/hash_alignment.rb +6 -6
  26. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -1
  27. data/lib/rubocop/cop/layout/heredoc_indentation.rb +20 -103
  28. data/lib/rubocop/cop/layout/line_length.rb +17 -17
  29. data/lib/rubocop/cop/layout/multiline_block_layout.rb +3 -1
  30. data/lib/rubocop/cop/layout/space_around_keyword.rb +2 -2
  31. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +3 -1
  32. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +3 -1
  33. data/lib/rubocop/cop/lint/ambiguous_operator.rb +2 -1
  34. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +8 -4
  35. data/lib/rubocop/cop/lint/ensure_return.rb +1 -1
  36. data/lib/rubocop/cop/lint/erb_new_arguments.rb +3 -1
  37. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +33 -2
  38. data/lib/rubocop/cop/lint/loop.rb +1 -1
  39. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +69 -0
  40. data/lib/rubocop/cop/lint/nested_percent_literal.rb +1 -1
  41. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +7 -7
  42. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -1
  43. data/lib/rubocop/cop/lint/redundant_require_statement.rb +3 -3
  44. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -1
  45. data/lib/rubocop/cop/lint/suppressed_exception.rb +4 -2
  46. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -1
  47. data/lib/rubocop/cop/lint/useless_else_without_rescue.rb +1 -1
  48. data/lib/rubocop/cop/lint/useless_setter_call.rb +1 -1
  49. data/lib/rubocop/cop/migration/department_name.rb +9 -5
  50. data/lib/rubocop/cop/mixin/array_min_size.rb +3 -1
  51. data/lib/rubocop/cop/mixin/check_line_breakable.rb +3 -1
  52. data/lib/rubocop/cop/mixin/configurable_formatting.rb +1 -1
  53. data/lib/rubocop/cop/mixin/ignored_pattern.rb +1 -1
  54. data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
  55. data/lib/rubocop/cop/mixin/multiline_element_indentation.rb +3 -1
  56. data/lib/rubocop/cop/mixin/regexp_literal_help.rb +16 -0
  57. data/lib/rubocop/cop/mixin/surrounding_space.rb +3 -1
  58. data/lib/rubocop/cop/mixin/uncommunicative_name.rb +1 -1
  59. data/lib/rubocop/cop/naming/class_and_module_camel_case.rb +11 -1
  60. data/lib/rubocop/cop/naming/file_name.rb +26 -11
  61. data/lib/rubocop/cop/naming/predicate_name.rb +1 -1
  62. data/lib/rubocop/cop/registry.rb +65 -8
  63. data/lib/rubocop/cop/style/array_join.rb +1 -1
  64. data/lib/rubocop/cop/style/bare_percent_literals.rb +1 -1
  65. data/lib/rubocop/cop/style/copyright.rb +2 -2
  66. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  67. data/lib/rubocop/cop/style/exponential_notation.rb +3 -3
  68. data/lib/rubocop/cop/style/format_string_token.rb +2 -3
  69. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -2
  70. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -1
  71. data/lib/rubocop/cop/style/hash_syntax.rb +5 -3
  72. data/lib/rubocop/cop/style/inline_comment.rb +1 -1
  73. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  74. data/lib/rubocop/cop/style/negated_if.rb +3 -3
  75. data/lib/rubocop/cop/style/negated_unless.rb +3 -3
  76. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  77. data/lib/rubocop/cop/style/redundant_conditional.rb +4 -3
  78. data/lib/rubocop/cop/style/redundant_percent_q.rb +1 -1
  79. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +89 -0
  80. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +130 -0
  81. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  82. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +3 -3
  83. data/lib/rubocop/cop/style/trailing_comma_in_array_literal.rb +3 -3
  84. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +13 -13
  85. data/lib/rubocop/cop/style/trailing_comma_in_hash_literal.rb +3 -3
  86. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +3 -1
  87. data/lib/rubocop/cop/style/unless_else.rb +1 -1
  88. data/lib/rubocop/cop/style/when_then.rb +1 -1
  89. data/lib/rubocop/cop/team.rb +69 -25
  90. data/lib/rubocop/cop/util.rb +1 -1
  91. data/lib/rubocop/cop/utils/format_string.rb +18 -0
  92. data/lib/rubocop/cop/variable_force/branch.rb +3 -1
  93. data/lib/rubocop/formatter/junit_formatter.rb +14 -4
  94. data/lib/rubocop/magic_comment.rb +1 -1
  95. data/lib/rubocop/options.rb +17 -3
  96. data/lib/rubocop/result_cache.rb +4 -4
  97. data/lib/rubocop/rspec/cop_helper.rb +2 -23
  98. data/lib/rubocop/rspec/expect_offense.rb +45 -6
  99. data/lib/rubocop/rspec/shared_contexts.rb +2 -2
  100. data/lib/rubocop/runner.rb +14 -10
  101. data/lib/rubocop/target_finder.rb +3 -1
  102. data/lib/rubocop/target_ruby.rb +4 -1
  103. data/lib/rubocop/version.rb +1 -1
  104. metadata +23 -5
@@ -122,7 +122,9 @@ module RuboCop
122
122
 
123
123
  return unless first_offense
124
124
 
125
- return unused_range(node.type, mlhs_node, right) if unused_variables_only?(first_offense, variables)
125
+ if unused_variables_only?(first_offense, variables)
126
+ return unused_range(node.type, mlhs_node, right)
127
+ end
126
128
 
127
129
  return range_for_parentheses(first_offense, mlhs_node) if Util.parentheses?(mlhs_node)
128
130
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # This cop looks for *unless* expressions with *else* clauses.
6
+ # This cop looks for `unless` expressions with `else` clauses.
7
7
  #
8
8
  # @example
9
9
  # # bad
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # This cop checks for *when;* uses in *case* expressions.
6
+ # This cop checks for `when;` uses in `case` expressions.
7
7
  #
8
8
  # @example
9
9
  # # bad
@@ -2,7 +2,13 @@
2
2
 
3
3
  module RuboCop
4
4
  module Cop
5
- # FIXME
5
+ # A group of cops, ready to be called on duty to inspect files.
6
+ # Team is responsible for selecting only relevant cops to be sent on duty,
7
+ # as well as insuring that the needed forces are sent along with them.
8
+ #
9
+ # For performance reasons, Team will first dispatch cops & forces in two groups,
10
+ # first the ones needed for autocorrection (if any), then the rest
11
+ # (unless autocorrections happened).
6
12
  class Team
7
13
  DEFAULT_OPTIONS = {
8
14
  auto_correct: false,
@@ -11,12 +17,12 @@ module RuboCop
11
17
 
12
18
  Investigation = Struct.new(:offenses, :errors)
13
19
 
14
- attr_reader :errors, :warnings, :updated_source_file
20
+ attr_reader :errors, :warnings, :updated_source_file, :cops
15
21
 
16
22
  alias updated_source_file? updated_source_file
17
23
 
18
- def initialize(cop_classes, config, options = nil)
19
- @cop_classes = cop_classes
24
+ def initialize(cops, config = nil, options = nil)
25
+ @cops = cops
20
26
  @config = config
21
27
  @options = options || DEFAULT_OPTIONS
22
28
  @errors = []
@@ -25,6 +31,31 @@ module RuboCop
25
31
  validate_config
26
32
  end
27
33
 
34
+ # @return [Team]
35
+ def self.new(cop_or_classes, config, options = {})
36
+ # Support v0 api:
37
+ return mobilize(cop_or_classes, config, options) if cop_or_classes.first.is_a?(Class)
38
+
39
+ super
40
+ end
41
+
42
+ # @return [Team] with cops assembled from the given `cop_classes`
43
+ def self.mobilize(cop_classes, config, options = nil)
44
+ options ||= DEFAULT_OPTIONS
45
+ cops = mobilize_cops(cop_classes, config, options)
46
+ new(cops, config, options)
47
+ end
48
+
49
+ # @return [Array<Cop::Cop>]
50
+ def self.mobilize_cops(cop_classes, config, options = nil)
51
+ options ||= DEFAULT_OPTIONS
52
+ only = options.fetch(:only, [])
53
+ safe = options.fetch(:safe, false)
54
+ cop_classes.enabled(config, only, safe).map do |cop_class|
55
+ cop_class.new(config, options)
56
+ end
57
+ end
58
+
28
59
  def autocorrect?
29
60
  @options[:auto_correct]
30
61
  end
@@ -44,14 +75,6 @@ module RuboCop
44
75
  offenses(processed_source)
45
76
  end
46
77
 
47
- def cops
48
- only = @options.fetch(:only, [])
49
- safe = @options.fetch(:safe, false)
50
- @cops ||= @cop_classes.enabled(@config, only, safe).map do |cop_class|
51
- cop_class.new(@config, @options)
52
- end
53
- end
54
-
55
78
  def forces
56
79
  @forces ||= forces_for(cops)
57
80
  end
@@ -93,21 +116,23 @@ module RuboCop
93
116
 
94
117
  private
95
118
 
96
- def offenses(processed_source)
119
+ def offenses(processed_source) # rubocop:disable Metrics/AbcSize
97
120
  # The autocorrection process may have to be repeated multiple times
98
121
  # until there are no corrections left to perform
99
122
  # To speed things up, run auto-correcting cops by themselves, and only
100
123
  # run the other cops when no corrections are left
101
- autocorrect_cops, other_cops = cops.partition(&:autocorrect?)
102
-
103
- autocorrect =
104
- investigate(autocorrect_cops, processed_source) do |offenses|
105
- # We corrected some errors. Another round of inspection will be
106
- # done, and any other offenses will be caught then, so we don't
107
- # need to continue.
108
- return offenses if autocorrect(processed_source.buffer,
109
- autocorrect_cops)
110
- end
124
+ on_duty = roundup_relevant_cops(processed_source.file_path)
125
+
126
+ autocorrect_cops, other_cops = on_duty.partition(&:autocorrect?)
127
+
128
+ autocorrect = investigate(autocorrect_cops, processed_source)
129
+
130
+ if autocorrect(processed_source.buffer, autocorrect_cops)
131
+ # We corrected some errors. Another round of inspection will be
132
+ # done, and any other offenses will be caught then, so we don't
133
+ # need to continue.
134
+ return autocorrect.offenses
135
+ end
111
136
 
112
137
  other = investigate(other_cops, processed_source)
113
138
 
@@ -120,13 +145,32 @@ module RuboCop
120
145
  def investigate(cops, processed_source)
121
146
  return Investigation.new([], {}) if cops.empty?
122
147
 
123
- commissioner = Commissioner.new(cops, forces_for(cops))
148
+ commissioner = Commissioner.new(cops, forces_for(cops), @options)
124
149
  offenses = commissioner.investigate(processed_source)
125
- yield offenses if block_given?
126
150
 
127
151
  Investigation.new(offenses, commissioner.errors)
128
152
  end
129
153
 
154
+ def roundup_relevant_cops(filename)
155
+ cops.reject do |cop|
156
+ cop.excluded_file?(filename) ||
157
+ !support_target_ruby_version?(cop) ||
158
+ !support_target_rails_version?(cop)
159
+ end
160
+ end
161
+
162
+ def support_target_ruby_version?(cop)
163
+ return true unless cop.class.respond_to?(:support_target_ruby_version?)
164
+
165
+ cop.class.support_target_ruby_version?(cop.target_ruby_version)
166
+ end
167
+
168
+ def support_target_rails_version?(cop)
169
+ return true unless cop.class.respond_to?(:support_target_rails_version?)
170
+
171
+ cop.class.support_target_rails_version?(cop.target_rails_version)
172
+ end
173
+
130
174
  def autocorrect_all_cops(buffer, cops)
131
175
  corrector = Corrector.new(buffer)
132
176
 
@@ -108,7 +108,7 @@ module RuboCop
108
108
  end
109
109
 
110
110
  def trim_string_interporation_escape_character(str)
111
- str.gsub(/\\\#{(.*?)\}/) { "\#{#{Regexp.last_match(1)}}" }
111
+ str.gsub(/\\\#\{(.*?)\}/) { "\#{#{Regexp.last_match(1)}}" }
112
112
  end
113
113
 
114
114
  def interpret_string_escapes(string)
@@ -97,6 +97,10 @@ module RuboCop
97
97
  @format_sequences ||= parse
98
98
  end
99
99
 
100
+ def valid?
101
+ !mixed_formats?
102
+ end
103
+
100
104
  def named_interpolation?
101
105
  format_sequences.any?(&:name)
102
106
  end
@@ -114,6 +118,20 @@ module RuboCop
114
118
  )
115
119
  end
116
120
  end
121
+
122
+ def mixed_formats?
123
+ formats = format_sequences.map do |seq|
124
+ if seq.name
125
+ :named
126
+ elsif seq.max_digit_dollar_num
127
+ :numbered
128
+ else
129
+ :unnumbered
130
+ end
131
+ end
132
+
133
+ formats.uniq.size > 1
134
+ end
117
135
  end
118
136
  end
119
137
  end
@@ -109,7 +109,9 @@ module RuboCop
109
109
  return false if may_jump_to_other_branch?
110
110
 
111
111
  other.each_ancestor(include_self: true) do |other_ancestor|
112
- return !child_node.equal?(other_ancestor.child_node) if control_node.equal?(other_ancestor.control_node)
112
+ if control_node.equal?(other_ancestor.control_node)
113
+ return !child_node.equal?(other_ancestor.child_node)
114
+ end
113
115
  end
114
116
 
115
117
  if parent
@@ -35,19 +35,29 @@ module RuboCop
35
35
  #
36
36
  # In the future, it would be preferable to return only enabled cops.
37
37
  Cop::Cop.all.each do |cop|
38
+ target_offenses = offenses_for_cop(offenses, cop)
39
+
40
+ next unless relevant_for_output?(options, target_offenses)
41
+
38
42
  REXML::Element.new('testcase', @testsuite).tap do |testcase|
39
43
  testcase.attributes['classname'] = classname_attribute_value(file)
40
44
  testcase.attributes['name'] = cop.cop_name
41
45
 
42
- target_offenses = offenses.select do |offense|
43
- offense.cop_name == cop.cop_name
44
- end
45
-
46
46
  add_failure_to(testcase, target_offenses, cop.cop_name)
47
47
  end
48
48
  end
49
49
  end
50
50
 
51
+ def relevant_for_output?(options, target_offenses)
52
+ !options[:display_only_failed] || target_offenses.any?
53
+ end
54
+
55
+ def offenses_for_cop(all_offenses, cop)
56
+ all_offenses.select do |offense|
57
+ offense.cop_name == cop.cop_name
58
+ end
59
+ end
60
+
51
61
  def classname_attribute_value(file)
52
62
  file.gsub(/\.rb\Z/, '').gsub("#{Dir.pwd}/", '').tr('/', '.')
53
63
  end
@@ -133,7 +133,7 @@ module RuboCop
133
133
  # @see https://www.gnu.org/software/emacs/manual/html_node/emacs/Specify-Coding.html
134
134
  # @see https://git.io/vMCXh Emacs handling in Ruby's parse.y
135
135
  class EmacsComment < EditorComment
136
- FORMAT = /\-\*\-(.+)\-\*\-/.freeze
136
+ FORMAT = /-\*-(.+)-\*-/.freeze
137
137
  SEPARATOR = ';'
138
138
  OPERATOR = ':'
139
139
 
@@ -143,6 +143,8 @@ module RuboCop
143
143
  @options[:output_path] = path
144
144
  end
145
145
  end
146
+
147
+ option(opts, '--display-only-failed')
146
148
  end
147
149
 
148
150
  def add_severity_option(opts)
@@ -276,7 +278,9 @@ module RuboCop
276
278
  'be used with --only.'
277
279
  end
278
280
  raise OptionArgumentError, 'Syntax checking cannot be turned off.' if except_syntax?
279
- raise OptionArgumentError, '-C/--cache argument must be true or false' unless boolean_or_empty_cache?
281
+ unless boolean_or_empty_cache?
282
+ raise OptionArgumentError, '-C/--cache argument must be true or false'
283
+ end
280
284
 
281
285
  if display_only_fail_level_offenses_with_autocorrect?
282
286
  raise OptionArgumentError, '--autocorrect cannot be used with ' \
@@ -284,6 +288,7 @@ module RuboCop
284
288
  end
285
289
  validate_auto_gen_config
286
290
  validate_auto_correct
291
+ validate_display_only_failed
287
292
  validate_parallel
288
293
 
289
294
  return if incompatible_options.size <= 1
@@ -307,13 +312,20 @@ module RuboCop
307
312
  end
308
313
  end
309
314
 
315
+ def validate_display_only_failed
316
+ return unless @options.key?(:display_only_failed)
317
+ return if @options[:format] == 'junit'
318
+
319
+ raise OptionArgumentError,
320
+ format('--display-only-failed can only be used together with --format junit.')
321
+ end
322
+
310
323
  def validate_auto_correct
311
324
  return if @options.key?(:auto_correct)
312
325
  return unless @options.key?(:disable_uncorrectable)
313
326
 
314
327
  raise OptionArgumentError,
315
- format('--%<flag>s can only be used together with --auto-correct.',
316
- flag: '--disable-uncorrectable')
328
+ format('--disable-uncorrectable can only be used together with --auto-correct.')
317
329
  end
318
330
 
319
331
  def validate_parallel
@@ -429,6 +441,8 @@ module RuboCop
429
441
  'if no format is specified.'],
430
442
  fail_level: ['Minimum severity (A/R/C/W/E/F) for exit',
431
443
  'with error code.'],
444
+ display_only_failed: ['Only output offense messages. Omit passing',
445
+ 'cops. Only valid for --format junit.'],
432
446
  display_only_fail_level_offenses:
433
447
  ['Only output offense messages at',
434
448
  'the specified --fail-level or above'],
@@ -33,7 +33,7 @@ module RuboCop
33
33
 
34
34
  def requires_file_removal?(file_count, config_store)
35
35
  file_count > 1 &&
36
- file_count > config_store.for('.').for_all_cops['MaxFilesInCache']
36
+ file_count > config_store.for_dir('.').for_all_cops['MaxFilesInCache']
37
37
  end
38
38
 
39
39
  def remove_oldest_files(files, dirs, cache_root, verbose)
@@ -60,7 +60,7 @@ module RuboCop
60
60
  end
61
61
 
62
62
  def self.cache_root(config_store)
63
- root = config_store.for('.').for_all_cops['CacheRootDirectory']
63
+ root = config_store.for_dir('.').for_all_cops['CacheRootDirectory']
64
64
  root ||= if ENV.key?('XDG_CACHE_HOME')
65
65
  # Include user ID in the path to make sure the user has write
66
66
  # access.
@@ -72,7 +72,7 @@ module RuboCop
72
72
  end
73
73
 
74
74
  def self.allow_symlinks_in_cache_location?(config_store)
75
- config_store.for('.').for_all_cops['AllowSymlinksInCacheRootDirectory']
75
+ config_store.for_dir('.').for_all_cops['AllowSymlinksInCacheRootDirectory']
76
76
  end
77
77
 
78
78
  def initialize(file, team, options, config_store, cache_root = nil)
@@ -143,7 +143,7 @@ module RuboCop
143
143
  digester = Digest::SHA1.new
144
144
  mode = File.stat(file).mode
145
145
  digester.update(
146
- "#{file}#{mode}#{config_store.for(file).signature}"
146
+ "#{file}#{mode}#{config_store.for_file(file).signature}"
147
147
  )
148
148
  digester.file(file)
149
149
  digester.hexdigest
@@ -48,30 +48,9 @@ module CopHelper
48
48
  corrector.rewrite
49
49
  end
50
50
 
51
- def autocorrect_source_with_loop(source, file = nil)
52
- cnt = 0
53
- loop do
54
- cop.instance_variable_set(:@corrections, [])
55
- new_source = autocorrect_source(source, file)
56
- return new_source if new_source == source
57
-
58
- source = new_source
59
- cnt += 1
60
- raise RuboCop::Runner::InfiniteCorrectionLoop.new(file, []) if cnt > RuboCop::Runner::MAX_ITERATIONS
61
- end
62
- end
63
-
64
51
  def _investigate(cop, processed_source)
65
- forces = RuboCop::Cop::Force.all.each_with_object([]) do |klass, instances|
66
- next unless cop.join_force?(klass)
67
-
68
- instances << klass.new([cop])
69
- end
70
-
71
- commissioner =
72
- RuboCop::Cop::Commissioner.new([cop], forces, raise_error: true)
73
- commissioner.investigate(processed_source)
74
- commissioner
52
+ team = RuboCop::Cop::Team.new([cop], nil, raise_error: true)
53
+ team.inspect_file(processed_source)
75
54
  end
76
55
  end
77
56
 
@@ -71,9 +71,27 @@ module RuboCop
71
71
  # RUBY
72
72
  #
73
73
  # expect_no_corrections
74
+ #
75
+ # If your code has variables of different lengths, you can use `%{foo}`
76
+ # and `^{foo}` to format your template:
77
+ #
78
+ # %w[raise fail].each do |keyword|
79
+ # expect_offense(<<~RUBY, keyword: keyword)
80
+ # %{keyword}(RuntimeError, msg)
81
+ # ^{keyword}^^^^^^^^^^^^^^^^^^^ Redundant `RuntimeError` argument can be removed.
82
+ # RUBY
74
83
  module ExpectOffense
84
+ def format_offense(source, **replacements)
85
+ replacements.each do |keyword, value|
86
+ source = source.gsub("%{#{keyword}}", value)
87
+ .gsub("^{#{keyword}}", '^' * value.size)
88
+ end
89
+ source
90
+ end
91
+
75
92
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
76
- def expect_offense(source, file = nil)
93
+ def expect_offense(source, file = nil, **replacements)
94
+ source = format_offense(source, **replacements)
77
95
  RuboCop::Formatter::DisabledConfigFormatter
78
96
  .config_to_allow_offenses = {}
79
97
  RuboCop::Formatter::DisabledConfigFormatter.detected_styles = {}
@@ -96,17 +114,38 @@ module RuboCop
96
114
 
97
115
  expect(actual_annotations.to_s).to eq(expected_annotations.to_s)
98
116
  end
99
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
100
117
 
101
- def expect_correction(correction)
118
+ def expect_correction(correction, loop: true)
102
119
  raise '`expect_correction` must follow `expect_offense`' unless @processed_source
103
120
 
104
- corrector =
105
- RuboCop::Cop::Corrector.new(@processed_source.buffer, cop.corrections)
106
- new_source = corrector.rewrite
121
+ iteration = 0
122
+ new_source = loop do
123
+ iteration += 1
124
+
125
+ corrector =
126
+ RuboCop::Cop::Corrector.new(@processed_source.buffer, cop.corrections)
127
+ corrected_source = corrector.rewrite
128
+
129
+ break corrected_source unless loop
130
+ break corrected_source if cop.corrections.empty?
131
+ break corrected_source if corrected_source == @processed_source.buffer.source
132
+
133
+ if iteration > RuboCop::Runner::MAX_ITERATIONS
134
+ raise RuboCop::Runner::InfiniteCorrectionLoop.new(@processed_source.path, [])
135
+ end
136
+
137
+ # Prepare for next loop
138
+ cop.instance_variable_set(:@corrections, [])
139
+ # Cache invalidation. This is bad!
140
+ cop.instance_variable_set(:@token_table, nil)
141
+ @processed_source = parse_source(corrected_source,
142
+ @processed_source.path)
143
+ _investigate(cop, @processed_source)
144
+ end
107
145
 
108
146
  expect(new_source).to eq(correction)
109
147
  end
148
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
110
149
 
111
150
  def expect_no_corrections
112
151
  raise '`expect_no_corrections` must follow `expect_offense`' unless @processed_source