rubocop 1.66.0 → 1.67.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/config/default.yml +15 -6
- data/config/internal_affairs.yml +11 -0
- data/lib/rubocop/cli/command/auto_generate_config.rb +6 -7
- data/lib/rubocop/cli/command/lsp.rb +2 -2
- data/lib/rubocop/comment_config.rb +4 -8
- data/lib/rubocop/config.rb +4 -16
- data/lib/rubocop/config_loader.rb +14 -8
- data/lib/rubocop/config_loader_resolver.rb +3 -3
- data/lib/rubocop/config_validator.rb +7 -10
- data/lib/rubocop/cop/base.rb +6 -2
- data/lib/rubocop/cop/bundler/gem_version.rb +1 -0
- data/lib/rubocop/cop/cop.rb +8 -0
- data/lib/rubocop/cop/correctors/parentheses_corrector.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/cop_description.rb +0 -4
- data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/redundant_message_argument.rb +6 -21
- data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +8 -1
- data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +0 -5
- data/lib/rubocop/cop/internal_affairs.rb +16 -0
- data/lib/rubocop/cop/layout/access_modifier_indentation.rb +5 -1
- data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
- data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +8 -0
- data/lib/rubocop/cop/layout/indentation_width.rb +4 -5
- data/lib/rubocop/cop/layout/leading_comment_space.rb +28 -1
- data/lib/rubocop/cop/lint/ambiguous_range.rb +4 -1
- data/lib/rubocop/cop/lint/big_decimal_new.rb +4 -7
- data/lib/rubocop/cop/lint/boolean_symbol.rb +1 -1
- data/lib/rubocop/cop/lint/duplicate_set_element.rb +74 -0
- data/lib/rubocop/cop/lint/ensure_return.rb +0 -3
- data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
- data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
- data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +10 -4
- data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +5 -14
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +25 -2
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +5 -6
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -1
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +105 -41
- data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
- data/lib/rubocop/cop/lint/uri_regexp.rb +25 -7
- data/lib/rubocop/cop/mixin/percent_array.rb +1 -1
- data/lib/rubocop/cop/mixin/statement_modifier.rb +3 -2
- data/lib/rubocop/cop/naming/inclusive_language.rb +12 -3
- data/lib/rubocop/cop/naming/predicate_name.rb +1 -1
- data/lib/rubocop/cop/offense.rb +2 -2
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +12 -2
- data/lib/rubocop/cop/style/accessor_grouping.rb +10 -2
- data/lib/rubocop/cop/style/arguments_forwarding.rb +46 -6
- data/lib/rubocop/cop/style/block_delimiters.rb +14 -1
- data/lib/rubocop/cop/style/collection_compact.rb +10 -10
- data/lib/rubocop/cop/style/combinable_loops.rb +7 -0
- data/lib/rubocop/cop/style/commented_keyword.rb +7 -1
- data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
- data/lib/rubocop/cop/style/data_inheritance.rb +1 -1
- data/lib/rubocop/cop/style/empty_else.rb +1 -0
- data/lib/rubocop/cop/style/empty_literal.rb +1 -1
- data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
- data/lib/rubocop/cop/style/guard_clause.rb +1 -1
- data/lib/rubocop/cop/style/hash_each_methods.rb +6 -0
- data/lib/rubocop/cop/style/hash_syntax.rb +2 -2
- data/lib/rubocop/cop/style/if_inside_else.rb +1 -1
- data/lib/rubocop/cop/style/if_with_semicolon.rb +16 -5
- data/lib/rubocop/cop/style/lambda.rb +1 -1
- data/lib/rubocop/cop/style/magic_comment_format.rb +3 -8
- data/lib/rubocop/cop/style/map_into_array.rb +54 -10
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +12 -7
- data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
- data/lib/rubocop/cop/style/nested_modifier.rb +1 -1
- data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +1 -1
- data/lib/rubocop/cop/style/one_line_conditional.rb +4 -0
- data/lib/rubocop/cop/style/operator_method_call.rb +25 -6
- data/lib/rubocop/cop/style/redundant_begin.rb +4 -0
- data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
- data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +1 -1
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +3 -3
- data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/require_order.rb +1 -1
- data/lib/rubocop/cop/style/rescue_modifier.rb +13 -1
- data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +54 -12
- data/lib/rubocop/cop/style/safe_navigation.rb +92 -50
- data/lib/rubocop/cop/style/select_by_regexp.rb +9 -6
- data/lib/rubocop/cop/style/semicolon.rb +1 -1
- data/lib/rubocop/cop/style/struct_inheritance.rb +1 -1
- data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
- data/lib/rubocop/cop/team.rb +8 -1
- data/lib/rubocop/cop/util.rb +1 -1
- data/lib/rubocop/cops_documentation_generator.rb +73 -34
- data/lib/rubocop/file_finder.rb +9 -4
- data/lib/rubocop/lsp/runtime.rb +2 -0
- data/lib/rubocop/lsp/server.rb +0 -1
- data/lib/rubocop/rspec/expect_offense.rb +1 -0
- data/lib/rubocop/runner.rb +3 -0
- data/lib/rubocop/server/cache.rb +6 -1
- data/lib/rubocop/server/core.rb +1 -0
- data/lib/rubocop/target_ruby.rb +12 -12
- data/lib/rubocop/version.rb +3 -1
- data/lib/rubocop/yaml_duplication_checker.rb +20 -26
- data/lib/rubocop.rb +2 -0
- 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
|
-
|
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
|
134
|
-
|
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
|
140
|
-
|
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
|
-
|
189
|
+
find_method_chain(node.parent)
|
148
190
|
end
|
149
191
|
|
150
|
-
def
|
151
|
-
|
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
|
155
|
-
|
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
|
-
|
159
|
-
|
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
|
-
|
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
|
167
|
-
if
|
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(
|
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 = {
|
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(&:
|
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
|
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
|
|
data/lib/rubocop/cop/team.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/rubocop/cop/util.rb
CHANGED
@@ -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
|
-
|
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(
|
41
|
-
check_examples_to_have_the_default_enforced_style!(
|
42
|
-
|
43
|
-
content =
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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!(
|
55
|
-
return if
|
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 =
|
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(
|
70
|
-
|
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 <<
|
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(
|
78
|
-
|
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
|
143
|
+
def cop_header(cop)
|
119
144
|
content = +"\n"
|
120
|
-
content << "
|
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
|
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
|
133
|
-
content =
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
290
|
-
|
291
|
-
|
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
|
-
|
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
|
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
|
data/lib/rubocop/file_finder.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/rubocop/lsp/runtime.rb
CHANGED
data/lib/rubocop/lsp/server.rb
CHANGED
@@ -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
|
|
data/lib/rubocop/runner.rb
CHANGED
@@ -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
|
|
data/lib/rubocop/server/cache.rb
CHANGED
@@ -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(
|
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|
|