rubocop 1.32.0 → 1.34.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/config/default.yml +51 -16
  4. data/config/obsoletion.yml +23 -1
  5. data/lib/rubocop/cache_config.rb +29 -0
  6. data/lib/rubocop/cli/command/auto_genenerate_config.rb +2 -2
  7. data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
  8. data/lib/rubocop/cli/command/suggest_extensions.rb +53 -15
  9. data/lib/rubocop/config_finder.rb +68 -0
  10. data/lib/rubocop/config_loader.rb +12 -40
  11. data/lib/rubocop/config_loader_resolver.rb +1 -5
  12. data/lib/rubocop/config_obsoletion/changed_parameter.rb +5 -0
  13. data/lib/rubocop/config_obsoletion/parameter_rule.rb +4 -0
  14. data/lib/rubocop/config_obsoletion.rb +7 -2
  15. data/lib/rubocop/cop/cop.rb +1 -1
  16. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +28 -0
  17. data/lib/rubocop/cop/internal_affairs/single_line_comparison.rb +61 -0
  18. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  19. data/lib/rubocop/cop/layout/block_end_newline.rb +33 -5
  20. data/lib/rubocop/cop/layout/first_argument_indentation.rb +6 -1
  21. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -1
  22. data/lib/rubocop/cop/legacy/corrections_proxy.rb +1 -1
  23. data/lib/rubocop/cop/legacy/corrector.rb +1 -1
  24. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +21 -8
  25. data/lib/rubocop/cop/lint/debugger.rb +26 -16
  26. data/lib/rubocop/cop/lint/empty_conditional_body.rb +65 -1
  27. data/lib/rubocop/cop/lint/number_conversion.rb +24 -8
  28. data/lib/rubocop/cop/lint/shadowed_exception.rb +15 -0
  29. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +9 -1
  30. data/lib/rubocop/cop/metrics/abc_size.rb +3 -1
  31. data/lib/rubocop/cop/metrics/block_length.rb +6 -7
  32. data/lib/rubocop/cop/metrics/method_length.rb +8 -8
  33. data/lib/rubocop/cop/mixin/allowed_methods.rb +15 -1
  34. data/lib/rubocop/cop/mixin/allowed_pattern.rb +9 -1
  35. data/lib/rubocop/cop/mixin/comments_help.rb +5 -1
  36. data/lib/rubocop/cop/mixin/enforce_superclass.rb +2 -1
  37. data/lib/rubocop/cop/mixin/method_complexity.rb +4 -9
  38. data/lib/rubocop/cop/mixin/range_help.rb +2 -2
  39. data/lib/rubocop/cop/naming/predicate_name.rb +24 -3
  40. data/lib/rubocop/cop/style/block_delimiters.rb +26 -7
  41. data/lib/rubocop/cop/style/class_and_module_children.rb +4 -4
  42. data/lib/rubocop/cop/style/class_equality_comparison.rb +32 -7
  43. data/lib/rubocop/cop/style/double_negation.rb +2 -0
  44. data/lib/rubocop/cop/style/empty_heredoc.rb +15 -1
  45. data/lib/rubocop/cop/style/format_string_token.rb +21 -8
  46. data/lib/rubocop/cop/style/hash_except.rb +0 -4
  47. data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -1
  48. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -1
  49. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +7 -7
  50. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -6
  51. data/lib/rubocop/cop/style/numeric_literals.rb +16 -1
  52. data/lib/rubocop/cop/style/numeric_predicate.rb +28 -8
  53. data/lib/rubocop/cop/style/redundant_condition.rb +19 -4
  54. data/lib/rubocop/cop/style/redundant_parentheses.rb +15 -22
  55. data/lib/rubocop/cop/style/redundant_sort.rb +21 -6
  56. data/lib/rubocop/cop/style/sole_nested_conditional.rb +14 -3
  57. data/lib/rubocop/cop/style/symbol_proc.rb +34 -9
  58. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -13
  59. data/lib/rubocop/ext/range.rb +15 -0
  60. data/lib/rubocop/feature_loader.rb +90 -0
  61. data/lib/rubocop/formatter/clang_style_formatter.rb +1 -1
  62. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
  63. data/lib/rubocop/formatter/html_formatter.rb +1 -1
  64. data/lib/rubocop/formatter/markdown_formatter.rb +1 -1
  65. data/lib/rubocop/formatter/tap_formatter.rb +1 -1
  66. data/lib/rubocop/result_cache.rb +22 -20
  67. data/lib/rubocop/server/cache.rb +33 -1
  68. data/lib/rubocop/server/cli.rb +19 -2
  69. data/lib/rubocop/version.rb +1 -1
  70. data/lib/rubocop.rb +2 -1
  71. metadata +13 -9
  72. data/lib/rubocop/cop/mixin/ignored_methods.rb +0 -52
@@ -8,14 +8,14 @@ module RuboCop
8
8
  # These can be replaced by their respective predicate methods.
9
9
  # This cop can also be configured to do the reverse.
10
10
  #
11
- # This cop can be customized ignored methods with `IgnoredMethods`.
12
- # By default, there are no methods to ignored.
11
+ # This cop can be customized allowed methods with `AllowedMethods`.
12
+ # By default, there are no methods to allowed.
13
13
  #
14
14
  # This cop disregards `#nonzero?` as its value is truthy or falsey,
15
15
  # but not `true` and `false`, and thus not always interchangeable with
16
16
  # `!= 0`.
17
17
  #
18
- # This cop ignores comparisons to global variables, since they are often
18
+ # This cop allows comparisons to global variables, since they are often
19
19
  # populated with objects which can be compared with integers, but are
20
20
  # not themselves `Integer` polymorphic.
21
21
  #
@@ -46,13 +46,13 @@ module RuboCop
46
46
  # 0 > foo
47
47
  # bar.baz > 0
48
48
  #
49
- # @example IgnoredMethods: [] (default) with EnforcedStyle: predicate
49
+ # @example AllowedMethods: [] (default) with EnforcedStyle: predicate
50
50
  # # bad
51
51
  # foo == 0
52
52
  # 0 > foo
53
53
  # bar.baz > 0
54
54
  #
55
- # @example IgnoredMethods: [==] with EnforcedStyle: predicate
55
+ # @example AllowedMethods: [==] with EnforcedStyle: predicate
56
56
  # # good
57
57
  # foo == 0
58
58
  #
@@ -60,9 +60,25 @@ module RuboCop
60
60
  # 0 > foo
61
61
  # bar.baz > 0
62
62
  #
63
+ # @example AllowedPatterns: [] (default) with EnforcedStyle: comparison
64
+ # # bad
65
+ # foo.zero?
66
+ # foo.negative?
67
+ # bar.baz.positive?
68
+ #
69
+ # @example AllowedPatterns: [/zero/] with EnforcedStyle: predicate
70
+ # # good
71
+ # # bad
72
+ # foo.zero?
73
+ #
74
+ # # bad
75
+ # foo.negative?
76
+ # bar.baz.positive?
77
+ #
63
78
  class NumericPredicate < Base
64
79
  include ConfigurableEnforcedStyle
65
- include IgnoredMethods
80
+ include AllowedMethods
81
+ include AllowedPattern
66
82
  extend AutoCorrector
67
83
 
68
84
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
@@ -75,9 +91,9 @@ module RuboCop
75
91
  numeric, replacement = check(node)
76
92
  return unless numeric
77
93
 
78
- return if ignored_method?(node.method_name) ||
94
+ return if allowed_method_name?(node.method_name) ||
79
95
  node.each_ancestor(:send, :block).any? do |ancestor|
80
- ignored_method?(ancestor.method_name)
96
+ allowed_method_name?(ancestor.method_name)
81
97
  end
82
98
 
83
99
  message = format(MSG, prefer: replacement, current: node.source)
@@ -88,6 +104,10 @@ module RuboCop
88
104
 
89
105
  private
90
106
 
107
+ def allowed_method_name?(name)
108
+ allowed_method?(name) || matches_allowed_pattern?(name)
109
+ end
110
+
91
111
  def check(node)
92
112
  numeric, operator =
93
113
  if style == :predicate
@@ -154,16 +154,22 @@ module RuboCop
154
154
  if_branch.method?(else_branch.method_name) && if_branch.receiver == else_branch.receiver
155
155
  end
156
156
 
157
- def if_source(if_branch)
157
+ def if_source(if_branch, arithmetic_operation)
158
158
  if branches_have_method?(if_branch.parent) && if_branch.parenthesized?
159
159
  if_branch.source.delete_suffix(')')
160
+ elsif arithmetic_operation
161
+ argument_source = if_branch.first_argument.source
162
+
163
+ "#{if_branch.receiver.source} #{if_branch.method_name} (#{argument_source}"
160
164
  else
161
165
  if_branch.source
162
166
  end
163
167
  end
164
168
 
165
- def else_source(else_branch)
166
- if branches_have_method?(else_branch.parent)
169
+ def else_source(else_branch, arithmetic_operation) # rubocop:disable Metrics/AbcSize
170
+ if arithmetic_operation
171
+ "#{else_branch.first_argument.source})"
172
+ elsif branches_have_method?(else_branch.parent)
167
173
  else_source_if_has_method(else_branch)
168
174
  elsif require_parentheses?(else_branch)
169
175
  "(#{else_branch.source})"
@@ -198,7 +204,12 @@ module RuboCop
198
204
 
199
205
  def make_ternary_form(node)
200
206
  _condition, if_branch, else_branch = *node
201
- ternary_form = [if_source(if_branch), else_source(else_branch)].join(' || ')
207
+ arithmetic_operation = use_arithmetic_operation?(if_branch)
208
+
209
+ ternary_form = [
210
+ if_source(if_branch, arithmetic_operation),
211
+ else_source(else_branch, arithmetic_operation)
212
+ ].join(' || ')
202
213
  ternary_form += ')' if branches_have_method?(node) && if_branch.parenthesized?
203
214
 
204
215
  if node.parent&.send_type?
@@ -227,6 +238,10 @@ module RuboCop
227
238
  node.hash_type? && !node.braces?
228
239
  end
229
240
 
241
+ def use_arithmetic_operation?(node)
242
+ node.respond_to?(:arithmetic_operation?) && node.arithmetic_operation?
243
+ end
244
+
230
245
  def without_argument_parentheses_method?(node)
231
246
  node.send_type? && !node.arguments.empty? &&
232
247
  !node.parenthesized? && !node.operator_method? && !node.assignment_method?
@@ -57,8 +57,8 @@ module RuboCop
57
57
  def allowed_expression?(node)
58
58
  allowed_ancestor?(node) ||
59
59
  allowed_method_call?(node) ||
60
- allowed_array_or_hash_element?(node) ||
61
- allowed_multiple_expression?(node)
60
+ allowed_multiple_expression?(node) ||
61
+ allowed_ternary?(node)
62
62
  end
63
63
 
64
64
  def allowed_ancestor?(node)
@@ -80,6 +80,19 @@ module RuboCop
80
80
  !ancestor.begin_type? && !ancestor.def_type? && !ancestor.block_type?
81
81
  end
82
82
 
83
+ def allowed_ternary?(node)
84
+ return unless node&.parent&.if_type?
85
+
86
+ node.parent.ternary? && ternary_parentheses_required?
87
+ end
88
+
89
+ def ternary_parentheses_required?
90
+ config = @config.for_cop('Style/TernaryParentheses')
91
+ allowed_styles = %w[require_parentheses require_parentheses_when_complex]
92
+
93
+ config.fetch('Enabled') && allowed_styles.include?(config['EnforcedStyle'])
94
+ end
95
+
83
96
  def like_method_argument_parentheses?(node)
84
97
  node.send_type? && node.arguments.one? &&
85
98
  !node.arithmetic_operation? && node.first_argument.begin_type?
@@ -153,26 +166,6 @@ module RuboCop
153
166
  node.parent&.keyword?
154
167
  end
155
168
 
156
- def allowed_array_or_hash_element?(node)
157
- # Don't flag
158
- # ```
159
- # { a: (1
160
- # ), }
161
- # ```
162
- hash_or_array_element?(node) && only_closing_paren_before_comma?(node)
163
- end
164
-
165
- def hash_or_array_element?(node)
166
- node.each_ancestor(:array, :hash).any?
167
- end
168
-
169
- def only_closing_paren_before_comma?(node)
170
- source_buffer = node.source_range.source_buffer
171
- line_range = source_buffer.line_range(node.loc.end.line)
172
-
173
- /^\s*\)\s*,/.match?(line_range.source)
174
- end
175
-
176
169
  def disallowed_literal?(begin_node, node)
177
170
  node.literal? && !node.range_type? && !raised_to_power_negative_numeric?(begin_node, node)
178
171
  end
@@ -123,13 +123,8 @@ module RuboCop
123
123
 
124
124
  def register_offense(node, sort_node, sorter, accessor)
125
125
  message = message(node, sorter, accessor)
126
-
127
126
  add_offense(offense_range(sort_node, node), message: message) do |corrector|
128
- # Remove accessor, e.g. `first` or `[-1]`.
129
- corrector.remove(range_between(accessor_start(node), node.loc.expression.end_pos))
130
-
131
- # Replace "sort" or "sort_by" with the appropriate min/max method.
132
- corrector.replace(sort_node.loc.selector, suggestion(sorter, accessor, arg_value(node)))
127
+ autocorrect(corrector, node, sort_node, sorter, accessor)
133
128
  end
134
129
  end
135
130
 
@@ -149,6 +144,20 @@ module RuboCop
149
144
  accessor_source: accessor_source)
150
145
  end
151
146
 
147
+ def autocorrect(corrector, node, sort_node, sorter, accessor)
148
+ # Remove accessor, e.g. `first` or `[-1]`.
149
+ corrector.remove(range_between(accessor_start(node), node.loc.expression.end_pos))
150
+ # Replace "sort" or "sort_by" with the appropriate min/max method.
151
+ corrector.replace(sort_node.loc.selector, suggestion(sorter, accessor, arg_value(node)))
152
+ # Replace to avoid syntax errors when followed by a logical operator.
153
+ replace_with_logical_operator(corrector, node) if with_logical_operator?(node)
154
+ end
155
+
156
+ def replace_with_logical_operator(corrector, node)
157
+ corrector.insert_after(node.child_nodes.first, " #{node.parent.loc.operator.source}")
158
+ corrector.remove(node.parent.loc.operator)
159
+ end
160
+
152
161
  def suggestion(sorter, accessor, arg)
153
162
  base(accessor, arg) + suffix(sorter)
154
163
  end
@@ -187,6 +196,12 @@ module RuboCop
187
196
  node.loc.selector.begin_pos
188
197
  end
189
198
  end
199
+
200
+ def with_logical_operator?(node)
201
+ return unless (parent = node.parent)
202
+
203
+ parent.or_type? || parent.and_type?
204
+ end
190
205
  end
191
206
  end
192
207
  end
@@ -148,7 +148,18 @@ module RuboCop
148
148
  )
149
149
  corrector.replace(range, and_operator)
150
150
  corrector.remove(range_by_whole_lines(node.loc.end, include_final_newline: true))
151
- corrector.wrap(if_branch.condition, '(', ')') if wrap_condition?(if_branch.condition)
151
+
152
+ wrap_condition(corrector, if_branch.condition)
153
+ end
154
+
155
+ def wrap_condition(corrector, condition)
156
+ # Handle `send` and `block` nodes that need to be wrapped in parens
157
+ # FIXME: autocorrection prevents syntax errors by wrapping the entire node in parens,
158
+ # but wrapping the argument list would be a more ergonomic correction.
159
+ node_to_check = condition&.block_type? ? condition.send_node : condition
160
+ return unless wrap_condition?(node_to_check)
161
+
162
+ corrector.wrap(condition, '(', ')')
152
163
  end
153
164
 
154
165
  def correct_for_outer_condition_modify_form_style(corrector, node, if_branch)
@@ -207,7 +218,7 @@ module RuboCop
207
218
  end
208
219
 
209
220
  def require_parentheses?(condition)
210
- condition.send_type? && !condition.arguments.empty? && !condition.parenthesized? &&
221
+ condition.call_type? && !condition.arguments.empty? && !condition.parenthesized? &&
211
222
  !condition.comparison_method?
212
223
  end
213
224
 
@@ -219,7 +230,7 @@ module RuboCop
219
230
 
220
231
  def wrap_condition?(node)
221
232
  node.and_type? || node.or_type? ||
222
- (node.send_type? && node.arguments.any? && !node.parenthesized?)
233
+ (node.call_type? && node.arguments.any? && !node.parenthesized?)
223
234
  end
224
235
 
225
236
  def replace_condition(condition)
@@ -7,13 +7,13 @@ module RuboCop
7
7
  #
8
8
  # If you prefer a style that allows block for method with arguments,
9
9
  # please set `true` to `AllowMethodsWithArguments`.
10
- # respond_to , and `define_method?` methods are ignored by default.
11
- # These are customizable with `IgnoredMethods` option.
10
+ # respond_to , and `define_method?` methods are allowed by default.
11
+ # These are customizable with `AllowedMethods` option.
12
12
  #
13
13
  # @safety
14
14
  # This cop is unsafe because `proc`s and blocks work differently
15
15
  # when additional arguments are passed in. A block will silently
16
- # ignore additional arguments, but a `proc` will raise
16
+ # allow additional arguments, but a `proc` will raise
17
17
  # an `ArgumentError`.
18
18
  #
19
19
  # For example:
@@ -71,15 +71,25 @@ module RuboCop
71
71
  # # some comment
72
72
  # end
73
73
  #
74
- # @example IgnoredMethods: [respond_to, define_method] (default)
74
+ # @example AllowedMethods: [respond_to, define_method] (default)
75
75
  # # good
76
76
  # respond_to { |foo| foo.bar }
77
77
  # define_method(:foo) { |foo| foo.bar }
78
78
  #
79
+ #
80
+ # @example AllowedPatterns: [] (default)
81
+ # # bad
82
+ # something.map { |s| s.upcase }
83
+ #
84
+ # @example AllowedPatterns: [/map/] (default)
85
+ # # good
86
+ # something.map { |s| s.upcase }
87
+ #
79
88
  class SymbolProc < Base
80
89
  include CommentsHelp
81
90
  include RangeHelp
82
- include IgnoredMethods
91
+ include AllowedMethods
92
+ include AllowedPattern
83
93
  extend AutoCorrector
84
94
 
85
95
  MSG = 'Pass `&:%<method>s` as an argument to `%<block_method>s` instead of a block.'
@@ -103,15 +113,17 @@ module RuboCop
103
113
  [Layout::SpaceBeforeBlockBraces]
104
114
  end
105
115
 
106
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
116
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
107
117
  def on_block(node)
108
118
  symbol_proc?(node) do |dispatch_node, arguments_node, method_name|
109
119
  # TODO: Rails-specific handling that we should probably make
110
120
  # configurable - https://github.com/rubocop/rubocop/issues/1485
111
- # we should ignore lambdas & procs
121
+ # we should allow lambdas & procs
112
122
  return if proc_node?(dispatch_node)
123
+ return if unsafe_hash_usage?(dispatch_node)
124
+ return if unsafe_array_usage?(dispatch_node)
113
125
  return if %i[lambda proc].include?(dispatch_node.method_name)
114
- return if ignored_method?(dispatch_node.method_name)
126
+ return if allowed_method_name?(dispatch_node.method_name)
115
127
  return if allow_if_method_has_argument?(node.send_node)
116
128
  return if node.block_type? && destructuring_block_argument?(arguments_node)
117
129
  return if allow_comments? && contains_comments?(node)
@@ -119,7 +131,7 @@ module RuboCop
119
131
  register_offense(node, method_name, dispatch_node.method_name)
120
132
  end
121
133
  end
122
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
134
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
123
135
  alias on_numblock on_block
124
136
 
125
137
  def destructuring_block_argument?(argument_node)
@@ -128,6 +140,19 @@ module RuboCop
128
140
 
129
141
  private
130
142
 
143
+ # See: https://github.com/rubocop/rubocop/issues/10864
144
+ def unsafe_hash_usage?(node)
145
+ node.receiver&.hash_type? && %i[reject select].include?(node.method_name)
146
+ end
147
+
148
+ def unsafe_array_usage?(node)
149
+ node.receiver&.array_type? && %i[min max].include?(node.method_name)
150
+ end
151
+
152
+ def allowed_method_name?(name)
153
+ allowed_method?(name) || matches_allowed_pattern?(name)
154
+ end
155
+
131
156
  def register_offense(node, method_name, block_method_name)
132
157
  block_start = node.loc.begin.begin_pos
133
158
  block_end = node.loc.end.end_pos
@@ -71,7 +71,7 @@ module RuboCop
71
71
 
72
72
  return if only_closing_parenthesis_is_last_line?(condition)
73
73
  return if condition_as_parenthesized_one_line_pattern_matching?(condition)
74
- return unless node.ternary? && !infinite_loop? && offense?(node)
74
+ return unless node.ternary? && offense?(node)
75
75
 
76
76
  message = message(node)
77
77
 
@@ -166,22 +166,10 @@ module RuboCop
166
166
  style == :require_parentheses_when_complex
167
167
  end
168
168
 
169
- def redundant_parentheses_enabled?
170
- @config.for_cop('Style/RedundantParentheses').fetch('Enabled')
171
- end
172
-
173
169
  def parenthesized?(node)
174
170
  node.begin_type?
175
171
  end
176
172
 
177
- # When this cop is configured to enforce parentheses and the
178
- # `RedundantParentheses` cop is enabled, it will cause an infinite loop
179
- # as they compete to add and remove the parentheses respectively.
180
- def infinite_loop?
181
- (require_parentheses? || require_parentheses_when_complex?) &&
182
- redundant_parentheses_enabled?
183
- end
184
-
185
173
  def unsafe_autocorrect?(condition)
186
174
  condition.children.any? do |child|
187
175
  unparenthesized_method_call?(child) || below_ternary_precedence?(child)
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Ext
5
+ # Extensions to Parser::Source::Range
6
+ module Range
7
+ # Adds `Range#single_line?` to parallel `Node#single_line?`
8
+ def single_line?
9
+ first_line == last_line
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Parser::Source::Range.include RuboCop::Ext::Range
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ # This class handles loading files (a.k.a. features in Ruby) specified
5
+ # by `--require` command line option and `require` directive in the config.
6
+ #
7
+ # Normally, the given string is directly passed to `require`. If a string
8
+ # beginning with `.` is given, it is assumed to be relative to the given
9
+ # directory.
10
+ #
11
+ # If a string containing `-` is given, it will be used as is, but if we
12
+ # cannot find the file to load, we will replace `-` with `/` and try it
13
+ # again as when Bundler loads gems.
14
+ #
15
+ # @api private
16
+ class FeatureLoader
17
+ class << self
18
+ # @param [String] config_directory_path
19
+ # @param [String] feature
20
+ def load(config_directory_path:, feature:)
21
+ new(config_directory_path: config_directory_path, feature: feature).load
22
+ end
23
+ end
24
+
25
+ # @param [String] config_directory_path
26
+ # @param [String] feature
27
+ def initialize(config_directory_path:, feature:)
28
+ @config_directory_path = config_directory_path
29
+ @feature = feature
30
+ end
31
+
32
+ def load
33
+ # Don't use `::Kernel.require(target)` to prevent the following error:
34
+ # https://github.com/rubocop/rubocop/issues/10893
35
+ require(target)
36
+ rescue ::LoadError => e
37
+ raise if e.path != target
38
+
39
+ begin
40
+ ::Kernel.require(namespaced_target)
41
+ rescue ::LoadError => error_for_namespaced_target
42
+ raise e if error_for_namespaced_target.path == namespaced_target
43
+
44
+ raise error_for_namespaced_target
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # @return [String]
51
+ def namespaced_feature
52
+ @feature.tr('-', '/')
53
+ end
54
+
55
+ # @return [String]
56
+ def namespaced_target
57
+ if relative?
58
+ relative(namespaced_feature)
59
+ else
60
+ namespaced_feature
61
+ end
62
+ end
63
+
64
+ # @param [String]
65
+ # @return [String]
66
+ def relative(feature)
67
+ ::File.join(@config_directory_path, feature)
68
+ end
69
+
70
+ # @return [Boolean]
71
+ def relative?
72
+ @feature.start_with?('.')
73
+ end
74
+
75
+ # @param [LoadError] error
76
+ # @return [Boolean]
77
+ def seems_cannot_load_such_file_error?(error)
78
+ error.path == target
79
+ end
80
+
81
+ # @return [String]
82
+ def target
83
+ if relative?
84
+ relative(@feature)
85
+ else
86
+ @feature
87
+ end
88
+ end
89
+ end
90
+ end
@@ -41,7 +41,7 @@ module RuboCop
41
41
  def report_line(location)
42
42
  source_line = location.source_line
43
43
 
44
- if location.first_line == location.last_line
44
+ if location.single_line?
45
45
  output.puts(source_line)
46
46
  else
47
47
  output.puts("#{source_line} #{yellow(ELLIPSES)}")
@@ -161,7 +161,7 @@ module RuboCop
161
161
  next unless value.is_a?(Array)
162
162
  next if value.empty?
163
163
 
164
- output_buffer.puts "# #{param}: #{value.join(', ')}"
164
+ output_buffer.puts "# #{param}: #{value.uniq.join(', ')}"
165
165
  end
166
166
  end
167
167
 
@@ -115,7 +115,7 @@ module RuboCop
115
115
  end
116
116
 
117
117
  def possible_ellipses(location)
118
- location.first_line == location.last_line ? '' : " #{ELLIPSES}"
118
+ location.single_line? ? '' : " #{ELLIPSES}"
119
119
  end
120
120
 
121
121
  def escape(string)
@@ -71,7 +71,7 @@ module RuboCop
71
71
  end
72
72
 
73
73
  def possible_ellipses(location)
74
- location.first_line == location.last_line ? '' : ' ...'
74
+ location.single_line? ? '' : ' ...'
75
75
  end
76
76
  end
77
77
  end
@@ -29,7 +29,7 @@ module RuboCop
29
29
  def report_line(location)
30
30
  source_line = location.source_line
31
31
 
32
- if location.first_line == location.last_line
32
+ if location.single_line?
33
33
  output.puts("# #{source_line}")
34
34
  else
35
35
  output.puts("# #{source_line} #{yellow(ELLIPSES)}")
@@ -4,6 +4,7 @@ require 'digest/sha1'
4
4
  require 'find'
5
5
  require 'etc'
6
6
  require 'zlib'
7
+ require_relative 'cache_config'
7
8
 
8
9
  module RuboCop
9
10
  # Provides functionality for caching RuboCop runs.
@@ -13,6 +14,12 @@ module RuboCop
13
14
  fix_layout autocorrect safe_autocorrect autocorrect_all
14
15
  cache fail_fast stdin parallel].freeze
15
16
 
17
+ DL_EXTENSIONS = ::RbConfig::CONFIG
18
+ .values_at('DLEXT', 'DLEXT2')
19
+ .reject { |ext| !ext || ext.empty? }
20
+ .map { |ext| ".#{ext}" }
21
+ .freeze
22
+
16
23
  # Remove old files so that the cache doesn't grow too big. When the
17
24
  # threshold MaxFilesInCache has been exceeded, the oldest 50% of all the
18
25
  # files in the cache are removed. The reason for removing so much is that
@@ -67,24 +74,9 @@ module RuboCop
67
74
  end
68
75
 
69
76
  def self.cache_root(config_store)
70
- root = ENV.fetch('RUBOCOP_CACHE_ROOT', nil)
71
- root ||= config_store.for_pwd.for_all_cops['CacheRootDirectory']
72
- root ||= if ENV.key?('XDG_CACHE_HOME')
73
- # Include user ID in the path to make sure the user has write
74
- # access.
75
- File.join(ENV.fetch('XDG_CACHE_HOME'), Process.uid.to_s)
76
- else
77
- # On FreeBSD, the /home path is a symbolic link to /usr/home
78
- # and the $HOME environment variable returns the /home path.
79
- #
80
- # As $HOME is a built-in environment variable, FreeBSD users
81
- # always get a warning message.
82
- #
83
- # To avoid raising warn log messages on FreeBSD, we retrieve
84
- # the real path of the home folder.
85
- File.join(File.realpath(Dir.home), '.cache')
86
- end
87
- File.join(root, 'rubocop_cache')
77
+ CacheConfig.root_dir do
78
+ config_store.for_pwd.for_all_cops['CacheRootDirectory']
79
+ end
88
80
  end
89
81
 
90
82
  def self.allow_symlinks_in_cache_location?(config_store)
@@ -188,14 +180,24 @@ module RuboCop
188
180
  .select { |path| File.file?(path) }
189
181
  .sort!
190
182
  .each do |path|
191
- content = File.binread(path)
192
- digest << Zlib.crc32(content).to_s # mtime not reliable
183
+ digest << digest(path)
193
184
  end
194
185
  digest << RuboCop::Version::STRING << RuboCop::AST::Version::STRING
195
186
  digest.hexdigest
196
187
  end
197
188
  end
198
189
 
190
+ def digest(path)
191
+ content = if path.end_with?(*DL_EXTENSIONS)
192
+ # Shared libraries often contain timestamps of when
193
+ # they were compiled and other non-stable data.
194
+ File.basename(path)
195
+ else
196
+ File.binread(path) # mtime not reliable
197
+ end
198
+ Zlib.crc32(content).to_s
199
+ end
200
+
199
201
  def rubocop_extra_features
200
202
  lib_root = File.join(File.dirname(__FILE__), '..')
201
203
  exe_root = File.join(lib_root, '..', 'exe')