rubocop 1.66.0 → 1.67.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +15 -6
  4. data/config/internal_affairs.yml +11 -0
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +6 -7
  6. data/lib/rubocop/cli/command/lsp.rb +2 -2
  7. data/lib/rubocop/comment_config.rb +4 -8
  8. data/lib/rubocop/config.rb +4 -16
  9. data/lib/rubocop/config_loader.rb +14 -8
  10. data/lib/rubocop/config_loader_resolver.rb +3 -3
  11. data/lib/rubocop/config_validator.rb +7 -10
  12. data/lib/rubocop/cop/base.rb +6 -2
  13. data/lib/rubocop/cop/bundler/gem_version.rb +1 -0
  14. data/lib/rubocop/cop/cop.rb +8 -0
  15. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +1 -1
  16. data/lib/rubocop/cop/internal_affairs/cop_description.rb +0 -4
  17. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -1
  18. data/lib/rubocop/cop/internal_affairs/redundant_message_argument.rb +6 -21
  19. data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +8 -1
  20. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +0 -5
  21. data/lib/rubocop/cop/internal_affairs.rb +16 -0
  22. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +5 -1
  23. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  24. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  25. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +8 -0
  26. data/lib/rubocop/cop/layout/indentation_width.rb +4 -5
  27. data/lib/rubocop/cop/layout/leading_comment_space.rb +28 -1
  28. data/lib/rubocop/cop/lint/ambiguous_range.rb +4 -1
  29. data/lib/rubocop/cop/lint/big_decimal_new.rb +4 -7
  30. data/lib/rubocop/cop/lint/boolean_symbol.rb +1 -1
  31. data/lib/rubocop/cop/lint/duplicate_set_element.rb +74 -0
  32. data/lib/rubocop/cop/lint/ensure_return.rb +0 -3
  33. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  34. data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
  35. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +10 -4
  36. data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +5 -14
  37. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +25 -2
  38. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +5 -6
  39. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -1
  40. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +105 -41
  41. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  42. data/lib/rubocop/cop/lint/uri_regexp.rb +25 -7
  43. data/lib/rubocop/cop/mixin/percent_array.rb +1 -1
  44. data/lib/rubocop/cop/mixin/statement_modifier.rb +3 -2
  45. data/lib/rubocop/cop/naming/inclusive_language.rb +12 -3
  46. data/lib/rubocop/cop/naming/predicate_name.rb +1 -1
  47. data/lib/rubocop/cop/offense.rb +2 -2
  48. data/lib/rubocop/cop/style/access_modifier_declarations.rb +12 -2
  49. data/lib/rubocop/cop/style/accessor_grouping.rb +10 -2
  50. data/lib/rubocop/cop/style/arguments_forwarding.rb +46 -6
  51. data/lib/rubocop/cop/style/block_delimiters.rb +14 -1
  52. data/lib/rubocop/cop/style/collection_compact.rb +10 -10
  53. data/lib/rubocop/cop/style/combinable_loops.rb +7 -0
  54. data/lib/rubocop/cop/style/commented_keyword.rb +7 -1
  55. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
  56. data/lib/rubocop/cop/style/data_inheritance.rb +1 -1
  57. data/lib/rubocop/cop/style/empty_else.rb +1 -0
  58. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  59. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  60. data/lib/rubocop/cop/style/guard_clause.rb +1 -1
  61. data/lib/rubocop/cop/style/hash_each_methods.rb +6 -0
  62. data/lib/rubocop/cop/style/hash_syntax.rb +2 -2
  63. data/lib/rubocop/cop/style/if_inside_else.rb +1 -1
  64. data/lib/rubocop/cop/style/if_with_semicolon.rb +16 -5
  65. data/lib/rubocop/cop/style/lambda.rb +1 -1
  66. data/lib/rubocop/cop/style/magic_comment_format.rb +3 -8
  67. data/lib/rubocop/cop/style/map_into_array.rb +54 -10
  68. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +12 -7
  69. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  70. data/lib/rubocop/cop/style/nested_modifier.rb +1 -1
  71. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +1 -1
  72. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -0
  73. data/lib/rubocop/cop/style/operator_method_call.rb +25 -6
  74. data/lib/rubocop/cop/style/redundant_begin.rb +4 -0
  75. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  76. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +1 -1
  77. data/lib/rubocop/cop/style/redundant_line_continuation.rb +3 -3
  78. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  79. data/lib/rubocop/cop/style/require_order.rb +1 -1
  80. data/lib/rubocop/cop/style/rescue_modifier.rb +13 -1
  81. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +54 -12
  82. data/lib/rubocop/cop/style/safe_navigation.rb +92 -50
  83. data/lib/rubocop/cop/style/select_by_regexp.rb +9 -6
  84. data/lib/rubocop/cop/style/semicolon.rb +1 -1
  85. data/lib/rubocop/cop/style/struct_inheritance.rb +1 -1
  86. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  87. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  88. data/lib/rubocop/cop/team.rb +8 -1
  89. data/lib/rubocop/cop/util.rb +1 -1
  90. data/lib/rubocop/cops_documentation_generator.rb +73 -34
  91. data/lib/rubocop/file_finder.rb +9 -4
  92. data/lib/rubocop/lsp/runtime.rb +2 -0
  93. data/lib/rubocop/lsp/server.rb +0 -1
  94. data/lib/rubocop/rspec/expect_offense.rb +1 -0
  95. data/lib/rubocop/runner.rb +3 -0
  96. data/lib/rubocop/server/cache.rb +6 -1
  97. data/lib/rubocop/server/core.rb +1 -0
  98. data/lib/rubocop/target_ruby.rb +12 -12
  99. data/lib/rubocop/version.rb +3 -1
  100. data/lib/rubocop/yaml_duplication_checker.rb +20 -26
  101. data/lib/rubocop.rb +2 -0
  102. metadata +12 -10
@@ -81,7 +81,7 @@ module RuboCop
81
81
  # foo.baz = bar if foo
82
82
  # foo.baz + bar if foo
83
83
  # foo.bar > 2 if foo
84
- class SafeNavigation < Base
84
+ class SafeNavigation < Base # rubocop:disable Metrics/ClassLength
85
85
  include NilMethods
86
86
  include RangeHelp
87
87
  extend AutoCorrector
@@ -124,47 +124,112 @@ module RuboCop
124
124
  # @!method not_nil_check?(node)
125
125
  def_node_matcher :not_nil_check?, '(send (send $_ :nil?) :!)'
126
126
 
127
+ # @!method and_inside_begin?(node)
128
+ def_node_matcher :and_inside_begin?, '`(begin and ...)'
129
+
130
+ # @!method strip_begin(node)
131
+ def_node_matcher :strip_begin, '{ (begin $!begin) $!(begin) }'
132
+
127
133
  def on_if(node)
128
134
  return if allowed_if_condition?(node)
129
135
 
130
- check_node(node)
136
+ checked_variable, receiver, method_chain, _method = extract_parts_from_if(node)
137
+ return unless offending_node?(node, checked_variable, method_chain, receiver)
138
+
139
+ body = extract_if_body(node)
140
+ method_call = receiver.parent
141
+
142
+ removal_ranges = [begin_range(node, body), end_range(node, body)]
143
+
144
+ report_offense(node, method_chain, method_call, *removal_ranges) do |corrector|
145
+ corrector.insert_before(method_call.loc.dot, '&') unless method_call.safe_navigation?
146
+ end
147
+ end
148
+
149
+ def on_and(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
150
+ collect_and_clauses(node).each do |(lhs, lhs_operator_range), (rhs, _rhs_operator_range)|
151
+ lhs_not_nil_check = not_nil_check?(lhs)
152
+ lhs_receiver = lhs_not_nil_check || lhs
153
+ rhs_receiver = find_matching_receiver_invocation(strip_begin(rhs), lhs_receiver)
154
+
155
+ next if !cop_config['ConvertCodeThatCanStartToReturnNil'] && lhs_not_nil_check
156
+ next unless offending_node?(node, lhs_receiver, rhs, rhs_receiver)
157
+
158
+ # Since we are evaluating every clause in potentially a complex chain of `and` nodes,
159
+ # we need to ensure that there isn't an object check happening
160
+ lhs_method_chain = find_method_chain(lhs_receiver)
161
+ next unless lhs_method_chain == lhs_receiver || lhs_not_nil_check
162
+
163
+ report_offense(
164
+ node,
165
+ rhs, rhs_receiver,
166
+ range_with_surrounding_space(range: lhs.source_range, side: :right),
167
+ range_with_surrounding_space(range: lhs_operator_range, side: :right),
168
+ offense_range: range_between(lhs.source_range.begin_pos, rhs.source_range.end_pos)
169
+ )
170
+ end
131
171
  end
132
172
 
133
- def on_and(node)
134
- check_node(node)
173
+ def report_offense(node, rhs, rhs_receiver, *removal_ranges, offense_range: node)
174
+ add_offense(offense_range) do |corrector|
175
+ removal_ranges.each { |range| corrector.remove(range) }
176
+ yield corrector if block_given?
177
+
178
+ handle_comments(corrector, node, rhs)
179
+
180
+ add_safe_nav_to_all_methods_in_chain(corrector, rhs_receiver, rhs)
181
+ end
135
182
  end
136
183
 
137
184
  private
138
185
 
139
- def check_node(node)
140
- checked_variable, receiver, method_chain, method = extract_parts(node)
141
- return if receiver != checked_variable || receiver.nil?
142
- return if use_var_only_in_unless_modifier?(node, checked_variable)
143
- return if chain_length(method_chain, method) > max_chain_length
144
- return if unsafe_method_used?(method_chain, method)
145
- return if method_chain.method?(:empty?)
186
+ def find_method_chain(node)
187
+ return node unless node&.parent&.call_type?
146
188
 
147
- add_offense(node) { |corrector| autocorrect(corrector, node) }
189
+ find_method_chain(node.parent)
148
190
  end
149
191
 
150
- def use_var_only_in_unless_modifier?(node, variable)
151
- node.if_type? && node.unless? && !method_called?(variable)
192
+ def collect_and_clauses(node)
193
+ # Collect the lhs, operator and rhs of all `and` nodes
194
+ # `and` nodes can be nested and can contain `begin` nodes
195
+ # This gives us a source-ordered list of clauses that is then used to look
196
+ # for matching receivers as well as operator locations for offense and corrections
197
+ node.each_descendant(:and)
198
+ .inject(and_parts(node)) { |nodes, and_node| concat_nodes(nodes, and_node) }
199
+ .sort_by { |a| a.is_a?(RuboCop::AST::Node) ? a.source_range.begin_pos : a.begin_pos }
200
+ .each_slice(2)
201
+ .each_cons(2)
152
202
  end
153
203
 
154
- def autocorrect(corrector, node)
155
- body = extract_body(node)
156
- method_call = method_call(node)
204
+ def concat_nodes(nodes, and_node)
205
+ return nodes if and_node.each_ancestor(:block).any?
157
206
 
158
- corrector.remove(begin_range(node, body))
159
- corrector.remove(end_range(node, body))
160
- corrector.insert_before(method_call.loc.dot, '&') unless method_call.safe_navigation?
161
- handle_comments(corrector, node, method_call)
207
+ nodes.concat(and_parts(and_node))
208
+ end
162
209
 
163
- add_safe_nav_to_all_methods_in_chain(corrector, method_call, body)
210
+ def and_parts(node)
211
+ parts = [node.loc.operator]
212
+ parts << node.rhs unless and_inside_begin?(node.rhs)
213
+ parts << node.lhs unless node.lhs.and_type? || and_inside_begin?(node.lhs)
214
+ parts
164
215
  end
165
216
 
166
- def extract_body(node)
167
- if node.if_type? && node.ternary?
217
+ def offending_node?(node, lhs_receiver, rhs, rhs_receiver) # rubocop:disable Metrics/CyclomaticComplexity
218
+ return false if lhs_receiver != rhs_receiver || rhs_receiver.nil?
219
+ return false if use_var_only_in_unless_modifier?(node, lhs_receiver)
220
+ return false if chain_length(rhs, rhs_receiver) > max_chain_length
221
+ return false if unsafe_method_used?(rhs, rhs_receiver.parent)
222
+ return false if rhs.send_type? && rhs.method?(:empty?)
223
+
224
+ true
225
+ end
226
+
227
+ def use_var_only_in_unless_modifier?(node, variable)
228
+ node.if_type? && node.unless? && !method_called?(variable)
229
+ end
230
+
231
+ def extract_if_body(node)
232
+ if node.ternary?
168
233
  node.branches.find { |branch| !branch.nil_type? }
169
234
  else
170
235
  node.node_parts[1]
@@ -201,20 +266,6 @@ module RuboCop
201
266
  node.else? || node.elsif?
202
267
  end
203
268
 
204
- def method_call(node)
205
- _checked_variable, matching_receiver, = extract_parts(node)
206
- matching_receiver.parent
207
- end
208
-
209
- def extract_parts(node)
210
- case node.type
211
- when :if
212
- extract_parts_from_if(node)
213
- when :and
214
- extract_parts_from_and(node)
215
- end
216
- end
217
-
218
269
  def extract_parts_from_if(node)
219
270
  variable, receiver =
220
271
  if node.ternary?
@@ -230,16 +281,6 @@ module RuboCop
230
281
  [checked_variable, matching_receiver, receiver, method]
231
282
  end
232
283
 
233
- def extract_parts_from_and(node)
234
- checked_variable, rhs = *node
235
- if cop_config['ConvertCodeThatCanStartToReturnNil']
236
- checked_variable = not_nil_check?(checked_variable) || checked_variable
237
- end
238
-
239
- checked_variable, matching_receiver, method = extract_common_parts(rhs, checked_variable)
240
- [checked_variable, matching_receiver, rhs, method]
241
- end
242
-
243
284
  def extract_common_parts(method_chain, checked_variable)
244
285
  matching_receiver = find_matching_receiver_invocation(method_chain, checked_variable)
245
286
 
@@ -249,7 +290,7 @@ module RuboCop
249
290
  end
250
291
 
251
292
  def find_matching_receiver_invocation(method_chain, checked_variable)
252
- return nil unless method_chain
293
+ return nil unless method_chain.respond_to?(:receiver)
253
294
 
254
295
  receiver = method_chain.receiver
255
296
 
@@ -259,7 +300,7 @@ module RuboCop
259
300
  end
260
301
 
261
302
  def chain_length(method_chain, method)
262
- method.each_ancestor(:send).inject(1) do |total, ancestor|
303
+ method.each_ancestor(:send).inject(0) do |total, ancestor|
263
304
  break total + 1 if ancestor == method_chain
264
305
 
265
306
  total + 1
@@ -310,6 +351,7 @@ module RuboCop
310
351
  start_method.each_ancestor do |ancestor|
311
352
  break unless %i[send block].include?(ancestor.type)
312
353
  next unless ancestor.send_type?
354
+ next if ancestor.safe_navigation?
313
355
 
314
356
  corrector.insert_before(ancestor.loc.dot, '&')
315
357
 
@@ -27,7 +27,7 @@ module RuboCop
27
27
  # so the correction may not be actually equivalent.
28
28
  #
29
29
  # @example
30
- # # bad (select or find_all)
30
+ # # bad (select, filter, or find_all)
31
31
  # array.select { |x| x.match? /regexp/ }
32
32
  # array.select { |x| /regexp/.match?(x) }
33
33
  # array.select { |x| x =~ /regexp/ }
@@ -47,9 +47,11 @@ module RuboCop
47
47
  include RangeHelp
48
48
 
49
49
  MSG = 'Prefer `%<replacement>s` to `%<original_method>s` with a regexp match.'
50
- RESTRICT_ON_SEND = %i[select find_all reject].freeze
51
- REPLACEMENTS = { select: 'grep', find_all: 'grep', reject: 'grep_v' }.freeze
52
- OPPOSITE_REPLACEMENTS = { select: 'grep_v', find_all: 'grep_v', reject: 'grep' }.freeze
50
+ RESTRICT_ON_SEND = %i[select filter find_all reject].freeze
51
+ REPLACEMENTS = { select: 'grep', filter: 'grep', find_all: 'grep', reject: 'grep_v' }.freeze
52
+ OPPOSITE_REPLACEMENTS = {
53
+ select: 'grep_v', filter: 'grep_v', find_all: 'grep_v', reject: 'grep'
54
+ }.freeze
53
55
  REGEXP_METHODS = %i[match? =~ !~].to_set.freeze
54
56
 
55
57
  # @!method regexp_match?(node)
@@ -84,8 +86,9 @@ module RuboCop
84
86
  }
85
87
  PATTERN
86
88
 
87
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
89
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
88
90
  def on_send(node)
91
+ return if target_ruby_version < 2.6 && node.method?(:filter)
89
92
  return unless (block_node = node.block_node)
90
93
  return if block_node.body&.begin_type?
91
94
  return if receiver_allowed?(block_node.receiver)
@@ -99,7 +102,7 @@ module RuboCop
99
102
 
100
103
  register_offense(node, block_node, regexp, replacement)
101
104
  end
102
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
105
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
103
106
  alias on_csend on_send
104
107
 
105
108
  private
@@ -141,7 +141,7 @@ module RuboCop
141
141
 
142
142
  def expressions_per_line(exprs)
143
143
  # create a map matching lines to the number of expressions on them
144
- exprs_lines = exprs.map(&:first_line)
144
+ exprs_lines = exprs.map(&:last_line)
145
145
  exprs_lines.group_by(&:itself)
146
146
  end
147
147
 
@@ -33,7 +33,7 @@ module RuboCop
33
33
  def on_class(node)
34
34
  return unless struct_constructor?(node.parent_class)
35
35
 
36
- add_offense(node.parent_class.source_range) do |corrector|
36
+ add_offense(node.parent_class) do |corrector|
37
37
  corrector.remove(range_with_surrounding_space(node.loc.keyword, newlines: false))
38
38
  corrector.replace(node.loc.operator, '=')
39
39
 
@@ -75,7 +75,7 @@ module RuboCop
75
75
 
76
76
  message = message(node)
77
77
 
78
- add_offense(node.source_range, message: message) do |corrector|
78
+ add_offense(node, message: message) do |corrector|
79
79
  autocorrect(corrector, node)
80
80
  end
81
81
  end
@@ -179,7 +179,7 @@ module RuboCop
179
179
  end
180
180
 
181
181
  def looks_like_trivial_reader?(node)
182
- !node.arguments? && node.body && node.body.ivar_type?
182
+ !node.arguments? && node.body&.ivar_type?
183
183
  end
184
184
 
185
185
  def trivial_writer?(node)
@@ -9,11 +9,17 @@ module RuboCop
9
9
  # For performance reasons, Team will first dispatch cops & forces in two groups,
10
10
  # first the ones needed for autocorrection (if any), then the rest
11
11
  # (unless autocorrections happened).
12
+ # rubocop:disable Metrics/ClassLength
12
13
  class Team
13
14
  # @return [Team]
14
15
  def self.new(cop_or_classes, config, options = {})
15
16
  # Support v0 api:
16
- return mobilize(cop_or_classes, config, options) if cop_or_classes.first.is_a?(Class)
17
+ if cop_or_classes.first.is_a?(Class)
18
+ warn Rainbow(<<~WARNING).yellow, uplevel: 1
19
+ `Team.new` with cop classes is deprecated. Use `Team.mobilize` instead.
20
+ WARNING
21
+ return mobilize(cop_or_classes, config, options)
22
+ end
17
23
 
18
24
  super
19
25
  end
@@ -279,5 +285,6 @@ module RuboCop
279
285
  end
280
286
  end
281
287
  end
288
+ # rubocop:enable Metrics/ClassLength
282
289
  end
283
290
  end
@@ -32,7 +32,7 @@ module RuboCop
32
32
  end
33
33
 
34
34
  def parentheses?(node)
35
- node.loc.respond_to?(:end) && node.loc.end && node.loc.end.is?(')')
35
+ node.loc.respond_to?(:end) && node.loc.end&.is?(')')
36
36
  end
37
37
 
38
38
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
@@ -6,14 +6,38 @@ require 'fileutils'
6
6
  # @api private
7
7
  class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
8
8
  include ::RuboCop::Cop::Documentation
9
+ CopData = Struct.new(
10
+ :cop, :description, :example_objects, :safety_objects, :see_objects, :config, keyword_init: true
11
+ )
12
+
13
+ STRUCTURE = {
14
+ name: ->(data) { cop_header(data.cop) },
15
+ required_ruby_version: ->(data) { required_ruby_version(data.cop) },
16
+ properties: ->(data) { properties(data.cop) },
17
+ description: ->(data) { "#{data.description}\n" },
18
+ safety: ->(data) { safety_object(data.safety_objects, data.cop) },
19
+ examples: ->(data) { examples(data.example_objects, data.cop) },
20
+ configuration: ->(data) { configurations(data.cop.department, data.config, data.cop) },
21
+ references: ->(data) { references(data.cop, data.see_objects) }
22
+ }.freeze
23
+
9
24
  # This class will only generate documentation for cops that belong to one of
10
25
  # the departments given in the `departments` array. E.g. if we only wanted
11
26
  # documentation for Lint cops:
12
27
  #
13
28
  # CopsDocumentationGenerator.new(departments: ['Lint']).call
14
29
  #
15
- def initialize(departments: [])
30
+ # You can append additional information:
31
+ #
32
+ # callback = ->(data) { required_rails_version(data.cop) }
33
+ # CopsDocumentationGenerator.new(extra_info: { ruby_version: callback }).call
34
+ #
35
+ # This will insert the string returned from the lambda _after_ the section from RuboCop itself.
36
+ # See `CopsDocumentationGenerator::STRUCTURE` for available sections.
37
+ #
38
+ def initialize(departments: [], extra_info: {})
16
39
  @departments = departments.map(&:to_sym).sort!
40
+ @extra_info = extra_info
17
41
  @cops = RuboCop::Cop::Registry.global
18
42
  @config = RuboCop::ConfigLoader.default_configuration
19
43
  @docs_path = "#{Dir.pwd}/docs/modules/ROOT/pages/"
@@ -37,24 +61,21 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
37
61
  cops.with_department(department).sort!
38
62
  end
39
63
 
40
- def cops_body(cop, description, examples_objects, safety_objects, see_objects, pars) # rubocop:disable Metrics/AbcSize, Metrics/ParameterLists
41
- check_examples_to_have_the_default_enforced_style!(examples_objects, cop)
42
-
43
- content = h2(cop.cop_name)
44
- content << required_ruby_version(cop)
45
- content << properties(cop)
46
- content << "#{description}\n"
47
- content << safety_object(safety_objects) if safety_objects.any? { |s| !s.text.blank? }
48
- content << examples(examples_objects) if examples_objects.any?
49
- content << configurations(cop.department, pars)
50
- content << references(cop, see_objects)
64
+ def cops_body(data)
65
+ check_examples_to_have_the_default_enforced_style!(data.example_objects, data.cop)
66
+
67
+ content = +''
68
+ STRUCTURE.each do |section, block|
69
+ content << instance_exec(data, &block)
70
+ content << @extra_info[section].call(data) if @extra_info[section]
71
+ end
51
72
  content
52
73
  end
53
74
 
54
- def check_examples_to_have_the_default_enforced_style!(examples_object, cop)
55
- return if examples_object.none?
75
+ def check_examples_to_have_the_default_enforced_style!(example_objects, cop)
76
+ return if example_objects.none?
56
77
 
57
- examples_describing_enforced_style = examples_object.map(&:name).grep(/EnforcedStyle:/)
78
+ examples_describing_enforced_style = example_objects.map(&:name).grep(/EnforcedStyle:/)
58
79
  return if examples_describing_enforced_style.none?
59
80
 
60
81
  if examples_describing_enforced_style.index { |name| name.match?('default') }.nonzero?
@@ -66,16 +87,20 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
66
87
  raise "Specify the default EnforcedStyle for #{cop.cop_name}"
67
88
  end
68
89
 
69
- def examples(examples_object)
70
- examples_object.each_with_object(h3('Examples').dup) do |example, content|
90
+ def examples(example_objects, cop)
91
+ return '' if example_objects.none?
92
+
93
+ example_objects.each_with_object(cop_subsection('Examples', cop).dup) do |example, content|
71
94
  content << "\n" unless content.end_with?("\n\n")
72
- content << h4(example.name) unless example.name == ''
95
+ content << example_header(example.name, cop) unless example.name == ''
73
96
  content << code_example(example)
74
97
  end
75
98
  end
76
99
 
77
- def safety_object(safety_object_objects)
78
- safety_object_objects.each_with_object(h3('Safety').dup) do |safety_object, content|
100
+ def safety_object(safety_objects, cop)
101
+ return '' if safety_objects.all? { |s| s.text.blank? }
102
+
103
+ safety_objects.each_with_object(cop_subsection('Safety', cop).dup) do |safety_object, content|
79
104
  next if safety_object.text.blank?
80
105
 
81
106
  content << "\n" unless content.end_with?("\n\n")
@@ -115,22 +140,25 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
115
140
  end
116
141
  # rubocop:enable Metrics/MethodLength
117
142
 
118
- def h2(title)
143
+ def cop_header(cop)
119
144
  content = +"\n"
120
- content << "== #{title}\n"
145
+ content << "[##{to_anchor(cop.cop_name)}]\n"
146
+ content << "== #{cop.cop_name}\n"
121
147
  content << "\n"
122
148
  content
123
149
  end
124
150
 
125
- def h3(title)
151
+ def cop_subsection(title, cop)
126
152
  content = +"\n"
153
+ content << "[##{to_anchor(title)}-#{to_anchor(cop.cop_name)}]\n"
127
154
  content << "=== #{title}\n"
128
155
  content << "\n"
129
156
  content
130
157
  end
131
158
 
132
- def h4(title)
133
- content = +"==== #{title}\n"
159
+ def example_header(title, cop)
160
+ content = "[##{to_anchor(title)}-#{to_anchor(cop.cop_name)}]\n"
161
+ content << +"==== #{title}\n"
134
162
  content << "\n"
135
163
  content
136
164
  end
@@ -142,7 +170,7 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
142
170
  content
143
171
  end
144
172
 
145
- def configurations(department, pars)
173
+ def configurations(department, pars, cop)
146
174
  return '' if pars.empty?
147
175
 
148
176
  header = ['Name', 'Default value', 'Configurable values']
@@ -157,7 +185,7 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
157
185
  [configuration_name(department, name), default, configurable]
158
186
  end
159
187
 
160
- h3('Configurable attributes') + to_table(header, content)
188
+ cop_subsection('Configurable attributes', cop) + to_table(header, content)
161
189
  end
162
190
 
163
191
  def configuration_name(department, name)
@@ -235,7 +263,7 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
235
263
  urls = RuboCop::Cop::MessageAnnotator.new(config, cop.name, cop_config, {}).urls
236
264
  return '' if urls.empty? && see_objects.empty?
237
265
 
238
- content = h3('References')
266
+ content = cop_subsection('References', cop)
239
267
  content << urls.map { |url| "* #{url}" }.join("\n")
240
268
  content << "\n" unless urls.empty?
241
269
  content << see_objects.map { |see| "* #{see.name}" }.join("\n")
@@ -283,14 +311,16 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
283
311
  ]
284
312
  pars = cop_config.reject { |k| non_display_keys.include? k }
285
313
  description = 'No documentation'
286
- examples_object = safety_object = see_object = []
314
+ example_objects = safety_objects = see_objects = []
287
315
  cop_code(cop) do |code_object|
288
316
  description = code_object.docstring unless code_object.docstring.blank?
289
- examples_object = code_object.tags('example')
290
- safety_object = code_object.tags('safety')
291
- see_object = code_object.tags('see')
317
+ example_objects = code_object.tags('example')
318
+ safety_objects = code_object.tags('safety')
319
+ see_objects = code_object.tags('see')
292
320
  end
293
- cops_body(cop, description, examples_object, safety_object, see_object, pars)
321
+ data = CopData.new(cop: cop, description: description, example_objects: example_objects,
322
+ safety_objects: safety_objects, see_objects: see_objects, config: pars)
323
+ cops_body(data)
294
324
  end
295
325
 
296
326
  def cop_code(cop)
@@ -306,7 +336,7 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
306
336
  filename = "#{department_to_basename(department)}.adoc"
307
337
  content = +"=== Department xref:#{filename}[#{type_title}]\n\n"
308
338
  cops_of_department(department).each do |cop|
309
- anchor = cop.cop_name.delete('/').downcase
339
+ anchor = to_anchor(cop.cop_name)
310
340
  content << "* xref:#{filename}##{anchor}[#{cop.cop_name}]\n"
311
341
  end
312
342
 
@@ -338,4 +368,13 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
338
368
 
339
369
  status == 'pending' ? 'Pending' : 'Enabled'
340
370
  end
371
+
372
+ # HTML anchor are somewhat limited in what characters they can contain, just
373
+ # accept a known-good subset. As long as it's consistent it doesn't matter.
374
+ #
375
+ # Style/AccessModifierDeclarations => styleaccessmodifierdeclarations
376
+ # OnlyFor: [] (default) => onlyfor_-__-_default_
377
+ def to_anchor(title)
378
+ title.delete('/').tr(' ', '-').gsub(/[^a-zA-Z0-9-]/, '_').downcase
379
+ end
341
380
  end
@@ -23,15 +23,20 @@ module RuboCop
23
23
  last_file
24
24
  end
25
25
 
26
+ def traverse_directories_upwards(start_dir, stop_dir = nil)
27
+ Pathname.new(start_dir).expand_path.ascend do |dir|
28
+ yield(dir)
29
+ dir = dir.to_s
30
+ break if dir == stop_dir || dir == FileFinder.root_level
31
+ end
32
+ end
33
+
26
34
  private
27
35
 
28
36
  def traverse_files_upwards(filename, start_dir, stop_dir)
29
- Pathname.new(start_dir).expand_path.ascend do |dir|
37
+ traverse_directories_upwards(start_dir, stop_dir) do |dir|
30
38
  file = dir + filename
31
39
  yield(file.to_s) if file.exist?
32
-
33
- dir = dir.to_s
34
- break if dir == stop_dir || dir == FileFinder.root_level
35
40
  end
36
41
  end
37
42
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'stringio'
4
+
3
5
  #
4
6
  # This code is based on https://github.com/standardrb/standard.
5
7
  #
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'language_server-protocol'
4
- require_relative '../lsp'
5
4
  require_relative 'logger'
6
5
  require_relative 'routes'
7
6
  require_relative 'runtime'
@@ -169,6 +169,7 @@ module RuboCop
169
169
  raise 'Expected correction but no corrections were made' if new_source == source
170
170
 
171
171
  expect(new_source).to eq(correction)
172
+ expect(@processed_source).to be_valid_syntax, 'Expected correction to be valid syntax'
172
173
  end
173
174
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
174
175
 
@@ -362,6 +362,9 @@ module RuboCop
362
362
  self.class.ruby_extractors.find do |ruby_extractor|
363
363
  result = ruby_extractor.call(processed_source)
364
364
  break result if result
365
+ rescue StandardError
366
+ raise Error, "Ruby extractor #{ruby_extractor.source_location[0]} failed to process " \
367
+ "#{processed_source.path}."
365
368
  end
366
369
  end
367
370
 
@@ -43,13 +43,18 @@ module RuboCop
43
43
  @project_dir_cache_key ||= project_dir[1..].tr('/', '+')
44
44
  end
45
45
 
46
+ # rubocop:disable Metrics/AbcSize
46
47
  def restart_key
47
48
  lockfile_path = LOCKFILE_NAMES.map do |lockfile_name|
48
49
  Pathname(project_dir).join(lockfile_name)
49
50
  end.find(&:exist?)
51
+ version_data = lockfile_path&.read || RuboCop::Version::STRING
52
+ config_data = Pathname(ConfigFinder.find_config_path(Dir.pwd)).read
53
+ todo_data = (rubocop_todo = Pathname('.rubocop_todo.yml')).exist? ? rubocop_todo.read : ''
50
54
 
51
- Digest::SHA1.hexdigest(lockfile_path&.read || RuboCop::Version::STRING)
55
+ Digest::SHA1.hexdigest(version_data + config_data + todo_data)
52
56
  end
57
+ # rubocop:enable Metrics/AbcSize
53
58
 
54
59
  def dir
55
60
  Pathname.new(File.join(cache_path, project_dir_cache_key)).tap do |d|
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'securerandom'
4
4
  require 'socket'
5
+ require 'stringio'
5
6
 
6
7
  #
7
8
  # This code is based on https://github.com/fohte/rubocop-daemon.