rubocop 0.90.0 → 0.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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/config/default.yml +35 -0
  4. data/lib/rubocop.rb +10 -1
  5. data/lib/rubocop/cli/command/execute_runner.rb +8 -0
  6. data/lib/rubocop/config_loader.rb +3 -3
  7. data/lib/rubocop/config_store.rb +3 -3
  8. data/lib/rubocop/cop/bundler/duplicated_gem.rb +5 -1
  9. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -0
  10. data/lib/rubocop/cop/bundler/insecure_protocol_source.rb +2 -0
  11. data/lib/rubocop/cop/generator.rb +1 -1
  12. data/lib/rubocop/cop/internal_affairs/method_name_equal.rb +1 -0
  13. data/lib/rubocop/cop/internal_affairs/node_type_predicate.rb +1 -0
  14. data/lib/rubocop/cop/internal_affairs/offense_location_keyword.rb +1 -0
  15. data/lib/rubocop/cop/internal_affairs/redundant_location_argument.rb +1 -0
  16. data/lib/rubocop/cop/internal_affairs/redundant_message_argument.rb +1 -0
  17. data/lib/rubocop/cop/layout/begin_end_alignment.rb +77 -0
  18. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  19. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +6 -5
  20. data/lib/rubocop/cop/layout/end_alignment.rb +5 -10
  21. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +17 -4
  22. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -3
  23. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +1 -6
  24. data/lib/rubocop/cop/lint/ambiguous_operator.rb +2 -0
  25. data/lib/rubocop/cop/lint/big_decimal_new.rb +1 -2
  26. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +54 -0
  27. data/lib/rubocop/cop/lint/debugger.rb +2 -3
  28. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +1 -3
  29. data/lib/rubocop/cop/lint/duplicate_methods.rb +2 -4
  30. data/lib/rubocop/cop/lint/duplicate_require.rb +7 -2
  31. data/lib/rubocop/cop/lint/each_with_object_argument.rb +1 -0
  32. data/lib/rubocop/cop/lint/empty_file.rb +1 -4
  33. data/lib/rubocop/cop/lint/erb_new_arguments.rb +2 -0
  34. data/lib/rubocop/cop/lint/float_comparison.rb +2 -2
  35. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +2 -2
  36. data/lib/rubocop/cop/lint/identity_comparison.rb +49 -0
  37. data/lib/rubocop/cop/lint/inherit_exception.rb +2 -2
  38. data/lib/rubocop/cop/lint/multiple_comparison.rb +3 -1
  39. data/lib/rubocop/cop/lint/number_conversion.rb +1 -0
  40. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +1 -2
  41. data/lib/rubocop/cop/lint/raise_exception.rb +1 -0
  42. data/lib/rubocop/cop/lint/rand_one.rb +2 -1
  43. data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -0
  44. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +3 -1
  45. data/lib/rubocop/cop/lint/struct_new_override.rb +1 -0
  46. data/lib/rubocop/cop/lint/to_json.rb +16 -5
  47. data/lib/rubocop/cop/lint/unreachable_loop.rb +2 -1
  48. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +3 -1
  49. data/lib/rubocop/cop/lint/uri_regexp.rb +2 -1
  50. data/lib/rubocop/cop/lint/useless_method_definition.rb +20 -27
  51. data/lib/rubocop/cop/lint/useless_times.rb +97 -0
  52. data/lib/rubocop/cop/mixin/comments_help.rb +3 -9
  53. data/lib/rubocop/cop/mixin/configurable_naming.rb +2 -2
  54. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +9 -0
  55. data/lib/rubocop/cop/mixin/hash_transform_method.rb +9 -1
  56. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +1 -1
  57. data/lib/rubocop/cop/naming/file_name.rb +1 -1
  58. data/lib/rubocop/cop/security/eval.rb +1 -0
  59. data/lib/rubocop/cop/security/json_load.rb +1 -0
  60. data/lib/rubocop/cop/security/marshal_load.rb +1 -0
  61. data/lib/rubocop/cop/security/open.rb +1 -0
  62. data/lib/rubocop/cop/security/yaml_load.rb +1 -0
  63. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -11
  64. data/lib/rubocop/cop/style/alias.rb +2 -0
  65. data/lib/rubocop/cop/style/array_join.rb +1 -0
  66. data/lib/rubocop/cop/style/attr.rb +1 -0
  67. data/lib/rubocop/cop/style/auto_resource_cleanup.rb +2 -0
  68. data/lib/rubocop/cop/style/case_equality.rb +3 -0
  69. data/lib/rubocop/cop/style/class_and_module_children.rb +2 -0
  70. data/lib/rubocop/cop/style/class_check.rb +6 -9
  71. data/lib/rubocop/cop/style/class_methods_definitions.rb +42 -16
  72. data/lib/rubocop/cop/style/class_vars.rb +1 -2
  73. data/lib/rubocop/cop/style/conditional_assignment.rb +49 -60
  74. data/lib/rubocop/cop/style/dir.rb +1 -0
  75. data/lib/rubocop/cop/style/double_negation.rb +1 -0
  76. data/lib/rubocop/cop/style/empty_literal.rb +3 -1
  77. data/lib/rubocop/cop/style/eval_with_location.rb +1 -3
  78. data/lib/rubocop/cop/style/even_odd.rb +1 -0
  79. data/lib/rubocop/cop/style/expand_path_arguments.rb +2 -2
  80. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  81. data/lib/rubocop/cop/style/float_division.rb +2 -0
  82. data/lib/rubocop/cop/style/format_string.rb +1 -4
  83. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +12 -2
  84. data/lib/rubocop/cop/style/hash_transform_keys.rb +5 -11
  85. data/lib/rubocop/cop/style/hash_transform_values.rb +5 -11
  86. data/lib/rubocop/cop/style/implicit_runtime_error.rb +1 -0
  87. data/lib/rubocop/cop/style/lambda_call.rb +3 -1
  88. data/lib/rubocop/cop/style/mixin_usage.rb +1 -0
  89. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +14 -1
  90. data/lib/rubocop/cop/style/nil_comparison.rb +2 -0
  91. data/lib/rubocop/cop/style/non_nil_check.rb +2 -0
  92. data/lib/rubocop/cop/style/not.rb +1 -0
  93. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -3
  94. data/lib/rubocop/cop/style/preferred_hash_methods.rb +2 -0
  95. data/lib/rubocop/cop/style/raise_args.rb +2 -0
  96. data/lib/rubocop/cop/style/random_with_offset.rb +1 -0
  97. data/lib/rubocop/cop/style/redundant_exception.rb +1 -3
  98. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +1 -0
  99. data/lib/rubocop/cop/style/redundant_freeze.rb +2 -1
  100. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -3
  101. data/lib/rubocop/cop/style/redundant_self_assignment.rb +2 -2
  102. data/lib/rubocop/cop/style/redundant_sort.rb +1 -7
  103. data/lib/rubocop/cop/style/safe_navigation.rb +5 -0
  104. data/lib/rubocop/cop/style/sample.rb +2 -1
  105. data/lib/rubocop/cop/style/send.rb +2 -3
  106. data/lib/rubocop/cop/style/signal_exception.rb +2 -0
  107. data/lib/rubocop/cop/style/single_argument_dig.rb +1 -0
  108. data/lib/rubocop/cop/style/slicing_with_range.rb +2 -1
  109. data/lib/rubocop/cop/style/stderr_puts.rb +1 -0
  110. data/lib/rubocop/cop/style/string_concatenation.rb +16 -2
  111. data/lib/rubocop/cop/style/strip.rb +1 -0
  112. data/lib/rubocop/cop/style/unpack_first.rb +1 -0
  113. data/lib/rubocop/cop/style/zero_length_predicate.rb +1 -5
  114. data/lib/rubocop/core_ext/string.rb +1 -1
  115. data/lib/rubocop/ext/regexp_node.rb +4 -4
  116. data/lib/rubocop/options.rb +19 -1
  117. data/lib/rubocop/result_cache.rb +30 -13
  118. data/lib/rubocop/runner.rb +29 -14
  119. data/lib/rubocop/version.rb +1 -1
  120. metadata +8 -4
@@ -49,6 +49,10 @@ module RuboCop
49
49
  # foo && foo < bar
50
50
  # foo < bar if foo
51
51
  #
52
+ # # When checking `foo&.empty?` in a conditional, `foo` being `nil` will actually
53
+ # # do the opposite of what the author intends.
54
+ # foo && foo.empty?
55
+ #
52
56
  # # This could start returning `nil` as well as the return of the method
53
57
  # foo.nil? || foo.bar
54
58
  # !foo || foo.bar
@@ -104,6 +108,7 @@ module RuboCop
104
108
  # chain greater than 2
105
109
  return if chain_size(method_chain, method) > 1
106
110
  return if unsafe_method_used?(method_chain, method)
111
+ return if method_chain.method?(:empty?)
107
112
 
108
113
  add_offense(node) do |corrector|
109
114
  autocorrect(corrector, node)
@@ -31,9 +31,10 @@ module RuboCop
31
31
  extend AutoCorrector
32
32
 
33
33
  MSG = 'Use `%<correct>s` instead of `%<incorrect>s`.'
34
+ RESTRICT_ON_SEND = %i[first last [] at slice].freeze
34
35
 
35
36
  def_node_matcher :sample_candidate?, <<~PATTERN
36
- (send $(send _ :shuffle $...) ${:first :last :[] :at :slice} $...)
37
+ (send $(send _ :shuffle $...) ${:#{RESTRICT_ON_SEND.join(' :')}} $...)
37
38
  PATTERN
38
39
 
39
40
  def on_send(node)
@@ -16,11 +16,10 @@ module RuboCop
16
16
  class Send < Base
17
17
  MSG = 'Prefer `Object#__send__` or `Object#public_send` to ' \
18
18
  '`send`.'
19
-
20
- def_node_matcher :sending?, '({send csend} _ :send ...)'
19
+ RESTRICT_ON_SEND = %i[send].freeze
21
20
 
22
21
  def on_send(node)
23
- return unless sending?(node) && node.arguments?
22
+ return unless node.arguments?
24
23
 
25
24
  add_offense(node.loc.selector)
26
25
  end
@@ -112,6 +112,8 @@ module RuboCop
112
112
  RAISE_MSG = 'Use `raise` instead of `fail` to ' \
113
113
  'rethrow exceptions.'
114
114
 
115
+ RESTRICT_ON_SEND = %i[raise fail].freeze
116
+
115
117
  def_node_matcher :kernel_call?, '(send (const {nil? cbase} :Kernel) %1 ...)'
116
118
  def_node_search :custom_fail_methods,
117
119
  '{(def :fail ...) (defs _ :fail ...)}'
@@ -27,6 +27,7 @@ module RuboCop
27
27
  extend AutoCorrector
28
28
 
29
29
  MSG = 'Use `%<receiver>s[%<argument>s]` instead of `%<original>s`.'
30
+ RESTRICT_ON_SEND = %i[dig].freeze
30
31
 
31
32
  def_node_matcher :single_argument_dig?, <<~PATTERN
32
33
  (send _ :dig $!splat)
@@ -19,11 +19,12 @@ module RuboCop
19
19
  minimum_target_ruby_version 2.6
20
20
 
21
21
  MSG = 'Prefer ary[n..] over ary[n..-1].'
22
+ RESTRICT_ON_SEND = %i[[]].freeze
22
23
 
23
24
  def_node_matcher :range_till_minus_one?, '(irange !nil? (int -1))'
24
25
 
25
26
  def on_send(node)
26
- return unless node.method?(:[]) && node.arguments.count == 1
27
+ return unless node.arguments.count == 1
27
28
  return unless range_till_minus_one?(node.arguments.first)
28
29
 
29
30
  add_offense(node.first_argument) do |corrector|
@@ -20,6 +20,7 @@ module RuboCop
20
20
 
21
21
  MSG =
22
22
  'Use `warn` instead of `%<bad>s` to allow such output to be disabled.'
23
+ RESTRICT_ON_SEND = %i[puts].freeze
23
24
 
24
25
  def_node_matcher :stderr_puts?, <<~PATTERN
25
26
  (send
@@ -6,6 +6,11 @@ module RuboCop
6
6
  # This cop checks for places where string concatenation
7
7
  # can be replaced with string interpolation.
8
8
  #
9
+ # The cop can autocorrect simple cases but will skip autocorrecting
10
+ # more complex cases where the resulting code would be harder to read.
11
+ # In those cases, it might be useful to extract statements to local
12
+ # variables or methods which you can then interpolate in a string.
13
+ #
9
14
  # @example
10
15
  # # bad
11
16
  # email_with_name = user.name + ' <' + user.email + '>'
@@ -19,6 +24,7 @@ module RuboCop
19
24
  extend AutoCorrector
20
25
 
21
26
  MSG = 'Prefer string interpolation to string concatenation.'
27
+ RESTRICT_ON_SEND = %i[+].freeze
22
28
 
23
29
  def_node_matcher :string_concatenation?, <<~PATTERN
24
30
  {
@@ -28,7 +34,6 @@ module RuboCop
28
34
  PATTERN
29
35
 
30
36
  def on_send(node)
31
- return unless node.method?(:+)
32
37
  return unless string_concatenation?(node)
33
38
 
34
39
  topmost_plus_node = find_topmost_plus_node(node)
@@ -37,7 +42,9 @@ module RuboCop
37
42
  collect_parts(topmost_plus_node, parts)
38
43
 
39
44
  add_offense(topmost_plus_node) do |corrector|
40
- corrector.replace(topmost_plus_node, replacement(parts))
45
+ if parts.none? { |part| uncorrectable?(part) }
46
+ corrector.replace(topmost_plus_node, replacement(parts))
47
+ end
41
48
  end
42
49
  end
43
50
 
@@ -66,6 +73,13 @@ module RuboCop
66
73
  node.send_type? && node.method?(:+)
67
74
  end
68
75
 
76
+ def uncorrectable?(part)
77
+ part.multiline? ||
78
+ part.dstr_type? ||
79
+ (part.str_type? && part.heredoc?) ||
80
+ part.each_descendant(:block).any?
81
+ end
82
+
69
83
  def replacement(parts)
70
84
  interpolated_parts =
71
85
  parts.map do |part|
@@ -18,6 +18,7 @@ module RuboCop
18
18
  extend AutoCorrector
19
19
 
20
20
  MSG = 'Use `strip` instead of `%<methods>s`.'
21
+ RESTRICT_ON_SEND = %i[lstrip rstrip].freeze
21
22
 
22
23
  def_node_matcher :lstrip_rstrip, <<~PATTERN
23
24
  {(send $(send _ $:rstrip) $:lstrip)
@@ -22,6 +22,7 @@ module RuboCop
22
22
 
23
23
  MSG = 'Use `%<receiver>s.unpack1(%<format>s)` instead of '\
24
24
  '`%<receiver>s.unpack(%<format>s)%<method>s`.'
25
+ RESTRICT_ON_SEND = %i[first [] slice at].freeze
25
26
 
26
27
  def_node_matcher :unpack_and_first_element?, <<~PATTERN
27
28
  {
@@ -32,7 +32,7 @@ module RuboCop
32
32
  NONZERO_MSG = 'Use `!empty?` instead of ' \
33
33
  '`%<lhs>s %<opr>s %<rhs>s`.'
34
34
 
35
- LENGTH_METHODS = %i[size length].freeze
35
+ RESTRICT_ON_SEND = %i[size length].freeze
36
36
 
37
37
  def on_send(node)
38
38
  check_zero_length_predicate(node)
@@ -42,8 +42,6 @@ module RuboCop
42
42
  private
43
43
 
44
44
  def check_zero_length_predicate(node)
45
- return unless LENGTH_METHODS.include?(node.method_name)
46
-
47
45
  zero_length_predicate = zero_length_predicate(node.parent)
48
46
  return unless zero_length_predicate
49
47
 
@@ -59,8 +57,6 @@ module RuboCop
59
57
  end
60
58
 
61
59
  def check_nonzero_length_predicate(node)
62
- return unless LENGTH_METHODS.include?(node.method_name)
63
-
64
60
  nonzero_length_predicate = nonzero_length_predicate(node.parent)
65
61
  return unless nonzero_length_predicate
66
62
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Extensions to the core String class
4
4
  class String
5
- unless method_defined? :blank?
5
+ unless method_defined?(:blank?) && ' '.blank?
6
6
  # Checks whether a string is blank. A string is considered blank if it
7
7
  # is either empty or contains only whitespace characters.
8
8
  #
@@ -21,10 +21,10 @@ module RuboCop
21
21
 
22
22
  str = content
23
23
  Ext::RegexpNode.parsed_cache[str] ||= begin
24
- Regexp::Parser.parse(str)
25
- rescue StandardError
26
- nil
27
- end
24
+ Regexp::Parser.parse(str)
25
+ rescue StandardError
26
+ nil
27
+ end
28
28
  end
29
29
 
30
30
  def each_capture(named: ANY)
@@ -69,6 +69,7 @@ module RuboCop
69
69
 
70
70
  add_severity_option(opts)
71
71
  add_flags_with_optional_args(opts)
72
+ add_cache_options(opts)
72
73
  add_boolean_flags(opts)
73
74
  add_aliases(opts)
74
75
 
@@ -164,10 +165,16 @@ module RuboCop
164
165
  end
165
166
  end
166
167
 
168
+ def add_cache_options(opts)
169
+ option(opts, '-C', '--cache FLAG')
170
+ option(opts, '--cache-root DIR') do
171
+ @validator.validate_cache_enabled_for_cache_root
172
+ end
173
+ end
174
+
167
175
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
168
176
  def add_boolean_flags(opts)
169
177
  option(opts, '-F', '--fail-fast')
170
- option(opts, '-C', '--cache FLAG')
171
178
  option(opts, '-d', '--debug')
172
179
  option(opts, '-D', '--[no-]display-cop-names')
173
180
  option(opts, '-E', '--extra-details')
@@ -392,6 +399,13 @@ module RuboCop
392
399
  # of option order.
393
400
  raise OptionParser::MissingArgument
394
401
  end
402
+
403
+ def validate_cache_enabled_for_cache_root
404
+ return unless @options[:cache] == 'false'
405
+
406
+ raise OptionArgumentError, '--cache-root can not be used with ' \
407
+ '--cache false'
408
+ end
395
409
  end
396
410
 
397
411
  # This module contains help texts for command line options.
@@ -464,6 +478,10 @@ module RuboCop
464
478
  cache: ["Use result caching (FLAG=true) or don't",
465
479
  '(FLAG=false), default determined by',
466
480
  'configuration parameter AllCops: UseCache.'],
481
+ cache_root: ['Set the cache root directory.',
482
+ 'Takes precedence over the configuration',
483
+ 'parameter AllCops: CacheRootDirectory and',
484
+ 'the $RUBOCOP_CACHE_ROOT environment variable.'],
467
485
  debug: 'Display debug info.',
468
486
  display_cop_names: ['Display cop names in offense messages.',
469
487
  'Default is true.'],
@@ -3,6 +3,7 @@
3
3
  require 'digest/sha1'
4
4
  require 'find'
5
5
  require 'etc'
6
+ require 'zlib'
6
7
 
7
8
  module RuboCop
8
9
  # Provides functionality for caching rubocop runs.
@@ -30,6 +31,11 @@ module RuboCop
30
31
  end
31
32
 
32
33
  class << self
34
+ # @api private
35
+ attr_accessor :rubocop_required_features
36
+
37
+ ResultCache.rubocop_required_features = []
38
+
33
39
  private
34
40
 
35
41
  def requires_file_removal?(file_count, config_store)
@@ -61,7 +67,8 @@ module RuboCop
61
67
  end
62
68
 
63
69
  def self.cache_root(config_store)
64
- root = config_store.for_pwd.for_all_cops['CacheRootDirectory']
70
+ root = ENV['RUBOCOP_CACHE_ROOT']
71
+ root ||= config_store.for_pwd.for_all_cops['CacheRootDirectory']
65
72
  root ||= if ENV.key?('XDG_CACHE_HOME')
66
73
  # Include user ID in the path to make sure the user has write
67
74
  # access.
@@ -76,7 +83,10 @@ module RuboCop
76
83
  config_store.for_pwd.for_all_cops['AllowSymlinksInCacheRootDirectory']
77
84
  end
78
85
 
86
+ attr :path
87
+
79
88
  def initialize(file, team, options, config_store, cache_root = nil)
89
+ cache_root ||= options[:cache_root]
80
90
  cache_root ||= ResultCache.cache_root(config_store)
81
91
  @allow_symlinks_in_cache_location =
82
92
  ResultCache.allow_symlinks_in_cache_location?(config_store)
@@ -159,27 +169,34 @@ module RuboCop
159
169
  end
160
170
 
161
171
  # The checksum of the rubocop program running the inspection.
162
- # rubocop:disable Metrics/AbcSize
163
172
  def rubocop_checksum
164
173
  ResultCache.source_checksum ||=
165
174
  begin
166
- lib_root = File.join(File.dirname(__FILE__), '..')
167
- exe_root = File.join(lib_root, '..', 'exe')
168
-
169
- # These are all the files we have `require`d plus everything in the
170
- # exe directory. A change to any of them could affect the cop output
171
- # so we include them in the cache hash.
172
- source_files = $LOADED_FEATURES + Find.find(exe_root).to_a
173
-
174
175
  digest = Digest::SHA1.new
175
- source_files
176
+ rubocop_extra_features
176
177
  .select { |path| File.file?(path) }
177
178
  .sort!
178
- .each { |path| digest << File.mtime(path).to_s }
179
+ .each do |path|
180
+ content = File.open(path, 'rb', &:read)
181
+ digest << Zlib.crc32(content).to_s # mtime not reliable
182
+ end
183
+ digest << RuboCop::Version::STRING << RuboCop::AST::Version::STRING
179
184
  digest.hexdigest
180
185
  end
181
186
  end
182
- # rubocop:enable Metrics/AbcSize
187
+
188
+ def rubocop_extra_features
189
+ lib_root = File.join(File.dirname(__FILE__), '..')
190
+ exe_root = File.join(lib_root, '..', 'exe')
191
+
192
+ # These are all the files we have `require`d plus everything in the
193
+ # exe directory. A change to any of them could affect the cop output
194
+ # so we include them in the cache hash.
195
+ source_files = $LOADED_FEATURES + Find.find(exe_root).to_a
196
+ source_files -= ResultCache.rubocop_required_features # Rely on gem versions
197
+
198
+ source_files
199
+ end
183
200
 
184
201
  # Return a hash of the options given at invocation, minus the ones that have
185
202
  # no effect on which offenses and disabled line ranges are found, and thus
@@ -11,9 +11,12 @@ module RuboCop
11
11
  class InfiniteCorrectionLoop < RuntimeError
12
12
  attr_reader :offenses
13
13
 
14
- def initialize(path, offenses)
15
- super "Infinite loop detected in #{path}."
16
- @offenses = offenses
14
+ def initialize(path, offenses_by_iteration, loop_start: -1)
15
+ @offenses = offenses_by_iteration.flatten.uniq
16
+ root_cause = offenses_by_iteration[loop_start..-1]
17
+ .map { |x| x.map(&:cop_name).uniq.join(', ') }
18
+ .join(' -> ')
19
+ super "Infinite loop detected in #{path} and caused by #{root_cause}"
17
20
  end
18
21
  end
19
22
 
@@ -81,7 +84,10 @@ module RuboCop
81
84
  # OPTIMIZE: Calling `ResultCache.cleanup` takes time. This optimization
82
85
  # mainly targets editors that integrates RuboCop. When RuboCop is run
83
86
  # by an editor, it should be inspecting only one file.
84
- ResultCache.cleanup(@config_store, @options[:debug]) if files.size > 1 && cached_run?
87
+ if files.size > 1 && cached_run?
88
+ ResultCache.cleanup(@config_store, @options[:debug], @options[:cache_root])
89
+ end
90
+
85
91
  formatter_set.finished(inspected_files.freeze)
86
92
  formatter_set.close_output_files
87
93
  end
@@ -233,18 +239,21 @@ module RuboCop
233
239
 
234
240
  def do_inspection_loop(file)
235
241
  processed_source = get_processed_source(file)
236
- offenses = []
242
+ # This variable is 2d array used to track corrected offenses after each
243
+ # inspection iteration. This is used to output meaningful infinite loop
244
+ # error message.
245
+ offenses_by_iteration = []
237
246
 
238
247
  # When running with --auto-correct, we need to inspect the file (which
239
248
  # includes writing a corrected version of it) until no more corrections
240
249
  # are made. This is because automatic corrections can introduce new
241
250
  # offenses. In the normal case the loop is only executed once.
242
- iterate_until_no_changes(processed_source, offenses) do
251
+ iterate_until_no_changes(processed_source, offenses_by_iteration) do
243
252
  # The offenses that couldn't be corrected will be found again so we
244
253
  # only keep the corrected ones in order to avoid duplicate reporting.
245
- offenses.select!(&:corrected?)
254
+ !offenses_by_iteration.empty? && offenses_by_iteration.last.select!(&:corrected?)
246
255
  new_offenses, updated_source_file = inspect_file(processed_source)
247
- offenses.concat(new_offenses).uniq!
256
+ offenses_by_iteration.push(new_offenses)
248
257
 
249
258
  # We have to reprocess the source to pickup the changes. Since the
250
259
  # change could (theoretically) introduce parsing errors, we break the
@@ -254,10 +263,12 @@ module RuboCop
254
263
  processed_source = get_processed_source(file)
255
264
  end
256
265
 
266
+ # Return summary of corrected offenses after all iterations
267
+ offenses = offenses_by_iteration.flatten.uniq
257
268
  [processed_source, offenses]
258
269
  end
259
270
 
260
- def iterate_until_no_changes(source, offenses)
271
+ def iterate_until_no_changes(source, offenses_by_iteration)
261
272
  # Keep track of the state of the source. If a cop modifies the source
262
273
  # and another cop undoes it producing identical source we have an
263
274
  # infinite loop.
@@ -269,10 +280,10 @@ module RuboCop
269
280
  iterations = 0
270
281
 
271
282
  loop do
272
- check_for_infinite_loop(source, offenses)
283
+ check_for_infinite_loop(source, offenses_by_iteration)
273
284
 
274
285
  if (iterations += 1) > MAX_ITERATIONS
275
- raise InfiniteCorrectionLoop.new(source.path, offenses)
286
+ raise InfiniteCorrectionLoop.new(source.path, offenses_by_iteration)
276
287
  end
277
288
 
278
289
  source = yield
@@ -282,11 +293,15 @@ module RuboCop
282
293
 
283
294
  # Check whether a run created source identical to a previous run, which
284
295
  # means that we definitely have an infinite loop.
285
- def check_for_infinite_loop(processed_source, offenses)
296
+ def check_for_infinite_loop(processed_source, offenses_by_iteration)
286
297
  checksum = processed_source.checksum
287
298
 
288
- if @processed_sources.include?(checksum)
289
- raise InfiniteCorrectionLoop.new(processed_source.path, offenses)
299
+ if (loop_start_index = @processed_sources.index(checksum))
300
+ raise InfiniteCorrectionLoop.new(
301
+ processed_source.path,
302
+ offenses_by_iteration,
303
+ loop_start: loop_start_index
304
+ )
290
305
  end
291
306
 
292
307
  @processed_sources << checksum
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '0.90.0'
6
+ STRING = '0.91.0'
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, '\
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \