rubocop 1.66.0 → 1.67.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 (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.