rubocop-sorted_methods_by_call 1.1.0 → 1.1.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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +6 -6
- data/Gemfile.lock +1 -1
- data/config/default.yml +1 -1
- data/lib/rubocop/cop/sorted_methods_by_call/waterfall.rb +106 -66
- data/lib/rubocop/sorted_methods_by_call/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1d62de644bc87dd509f3407cc4db3c9e0b4c46991e6a4e682344a7024eae1217
|
|
4
|
+
data.tar.gz: 94858039c6204ae841a595b4e5c13db9cf3a0775cf615fdd4696951d68181303
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 91901d4d06ad36e83396752d9c7b47b3c838f61bb42686351b43f382281136aa219dcbeaaf1d5f0e2007db56728dd9e69dd7ba307f1758b9acc26cadf7b7416a
|
|
7
|
+
data.tar.gz: a06c24a47cf38ea1e06021d54f49a04ed26fb47174f2c4dd1b6491cb768243acbda01fe25933b55895387e58d8f00d7e163c24d08525394f1891953f9a8a6017
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2025-11-06
|
|
3
|
+
# on 2025-11-06 17:19:21 UTC using RuboCop version 1.81.7.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
@@ -22,7 +22,7 @@ Gemspec/RequiredRubyVersion:
|
|
|
22
22
|
# Offense count: 4
|
|
23
23
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
|
24
24
|
Metrics/AbcSize:
|
|
25
|
-
Max:
|
|
25
|
+
Max: 61
|
|
26
26
|
|
|
27
27
|
# Offense count: 2
|
|
28
28
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
|
|
@@ -33,19 +33,19 @@ Metrics/BlockLength:
|
|
|
33
33
|
# Offense count: 5
|
|
34
34
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
35
35
|
Metrics/CyclomaticComplexity:
|
|
36
|
-
Max:
|
|
36
|
+
Max: 26
|
|
37
37
|
|
|
38
38
|
# Offense count: 7
|
|
39
39
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
40
40
|
Metrics/MethodLength:
|
|
41
|
-
Max:
|
|
41
|
+
Max: 68
|
|
42
42
|
|
|
43
43
|
# Offense count: 4
|
|
44
44
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
45
45
|
Metrics/PerceivedComplexity:
|
|
46
|
-
Max:
|
|
46
|
+
Max: 27
|
|
47
47
|
|
|
48
|
-
# Offense count:
|
|
48
|
+
# Offense count: 16
|
|
49
49
|
# Configuration parameters: CountAsOne.
|
|
50
50
|
RSpec/ExampleLength:
|
|
51
51
|
Max: 37
|
data/Gemfile.lock
CHANGED
data/config/default.yml
CHANGED
|
@@ -109,36 +109,74 @@ module RuboCop
|
|
|
109
109
|
def_nodes = body_nodes.select { |n| %i[def defs].include?(n.type) }
|
|
110
110
|
return if def_nodes.size <= 1
|
|
111
111
|
|
|
112
|
-
names
|
|
112
|
+
names = def_nodes.map(&:method_name)
|
|
113
113
|
names_set = names.to_set
|
|
114
|
-
index_of
|
|
114
|
+
index_of = names.each_with_index.to_h
|
|
115
|
+
|
|
116
|
+
# -----------------------------------------------------------
|
|
117
|
+
# Phase 1 Collect direct call edges (caller → callee)
|
|
118
|
+
# -----------------------------------------------------------
|
|
119
|
+
direct_edges = def_nodes.flat_map do |def_node|
|
|
120
|
+
calls = local_calls(def_node, names_set)
|
|
121
|
+
calls.reject { |callee| callee == def_node.method_name }
|
|
122
|
+
.map { |callee| [def_node.method_name, callee] }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
all_callees = direct_edges.to_set(&:last)
|
|
115
126
|
|
|
116
|
-
#
|
|
117
|
-
edges
|
|
127
|
+
# -----------------------------------------------------------
|
|
128
|
+
# Phase 2 Add sibling‑order edges for orchestration methods
|
|
129
|
+
# -----------------------------------------------------------
|
|
130
|
+
sibling_edges = []
|
|
118
131
|
def_nodes.each do |def_node|
|
|
119
|
-
|
|
120
|
-
|
|
132
|
+
next if all_callees.include?(def_node.method_name)
|
|
133
|
+
|
|
134
|
+
calls = local_calls(def_node, names_set)
|
|
135
|
+
calls.each_cons(2) do |a, b|
|
|
136
|
+
next if direct_edges.any? { |u, v| (u == a && v == b) || (u == b && v == a) }
|
|
121
137
|
|
|
122
|
-
|
|
138
|
+
sibling_edges << [a, b]
|
|
123
139
|
end
|
|
124
140
|
end
|
|
125
141
|
|
|
142
|
+
# -----------------------------------------------------------
|
|
143
|
+
# Phase 3 Combine for sorting, but keep direct set for recursion
|
|
144
|
+
# -----------------------------------------------------------
|
|
145
|
+
edges_for_sort = direct_edges + sibling_edges
|
|
126
146
|
allow_recursion = cop_config.fetch('AllowedRecursion') { true }
|
|
127
|
-
adj = build_adj(names, edges)
|
|
128
147
|
|
|
129
|
-
|
|
148
|
+
# Build adjacency only from *direct* calls for recursion checks
|
|
149
|
+
adj_direct = build_adj(names, direct_edges)
|
|
150
|
+
|
|
151
|
+
# Check for violations with edge type tracking
|
|
152
|
+
violation = first_backward_edge(direct_edges, index_of, adj_direct, allow_recursion)
|
|
153
|
+
violation_type = :direct if violation
|
|
154
|
+
|
|
155
|
+
unless violation
|
|
156
|
+
violation = first_backward_edge(sibling_edges, index_of, adj_direct, allow_recursion)
|
|
157
|
+
violation_type = :sibling if violation
|
|
158
|
+
end
|
|
159
|
+
|
|
130
160
|
return unless violation
|
|
131
161
|
|
|
132
162
|
caller_name, callee_name = violation
|
|
133
163
|
callee_node = def_nodes[index_of[callee_name]]
|
|
134
164
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
165
|
+
# Choose message based on violation type
|
|
166
|
+
message = if violation_type == :sibling
|
|
167
|
+
"Define ##{callee_name} after ##{caller_name} to match the order they are called together"
|
|
168
|
+
else
|
|
169
|
+
format(MSG, callee: "##{callee_name}", caller: "##{caller_name}")
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
add_offense(callee_node, message: message) do |corrector|
|
|
173
|
+
try_autocorrect(corrector, body_nodes, def_nodes, edges_for_sort, violation)
|
|
138
174
|
end
|
|
139
175
|
|
|
140
176
|
# Recurse into nested scopes
|
|
141
|
-
body_nodes.each
|
|
177
|
+
body_nodes.each do |n|
|
|
178
|
+
analyze_scope(n) if n.class_type? || n.module_type? || n.sclass_type?
|
|
179
|
+
end
|
|
142
180
|
end
|
|
143
181
|
|
|
144
182
|
# +RuboCop::Cop::SortedMethodsByCall::Waterfall#scope_body_nodes+ -> Array<RuboCop::AST::Node>
|
|
@@ -194,73 +232,64 @@ module RuboCop
|
|
|
194
232
|
# @param [Array<RuboCop::AST::Node>] body_nodes
|
|
195
233
|
# @param [Array<RuboCop::AST::Node>] def_nodes
|
|
196
234
|
# @param [Array<Array(Symbol, Symbol)>] edges
|
|
235
|
+
# @param [Array(Symbol, Symbol)] violation The found violation (caller, callee)
|
|
197
236
|
# @return [void]
|
|
198
237
|
#
|
|
199
238
|
# @note Applied only when user asked for autocorrections; with SafeAutoCorrect: false, this runs under -A.
|
|
200
239
|
# @note Also preserves contiguous leading doc comments above each method.
|
|
201
|
-
def try_autocorrect(corrector, body_nodes,
|
|
202
|
-
# Group method definitions into visibility sections
|
|
240
|
+
def try_autocorrect(corrector, body_nodes, _def_nodes, edges, violation)
|
|
203
241
|
sections = extract_visibility_sections(body_nodes)
|
|
204
242
|
|
|
205
|
-
|
|
206
|
-
caller_name, callee_name = first_backward_edge(
|
|
207
|
-
edges,
|
|
208
|
-
def_nodes.map(&:method_name).each_with_index.to_h,
|
|
209
|
-
build_adj(def_nodes.map(&:method_name), edges),
|
|
210
|
-
cop_config.fetch('AllowedRecursion') { true }
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
# No violation -> nothing to do
|
|
214
|
-
return unless caller_name && callee_name
|
|
243
|
+
caller_name, callee_name = violation
|
|
215
244
|
|
|
216
|
-
#
|
|
245
|
+
# find target section containing both defs
|
|
217
246
|
target_section = sections.find do |section|
|
|
218
|
-
|
|
219
|
-
|
|
247
|
+
section_names = section[:defs].map(&:method_name)
|
|
248
|
+
section_names.include?(caller_name) && section_names.include?(callee_name)
|
|
220
249
|
end
|
|
221
|
-
|
|
222
|
-
# If violation spans multiple sections, skip autocorrect
|
|
223
250
|
return unless target_section
|
|
224
251
|
|
|
225
252
|
defs = target_section[:defs]
|
|
226
|
-
return
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
#
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
sorted_names
|
|
237
|
-
|
|
253
|
+
return if defs.size <= 1
|
|
254
|
+
|
|
255
|
+
section_names = defs.map(&:method_name)
|
|
256
|
+
section_edges = edges.select { |u, v| section_names.include?(u) && section_names.include?(v) }
|
|
257
|
+
section_idx_of = section_names.each_with_index.to_h
|
|
258
|
+
|
|
259
|
+
# sort within section or minimally fix if graph is cyclic
|
|
260
|
+
sorted_names = topo_sort(section_names, section_edges, section_idx_of)
|
|
261
|
+
|
|
262
|
+
# forcibly fix when topo_sort failed or produced same order
|
|
263
|
+
if sorted_names.nil? || sorted_names == section_names
|
|
264
|
+
sorted_names = section_names.dup
|
|
265
|
+
# remove callee and reinsert it after its caller
|
|
266
|
+
sorted_names.delete(callee_name)
|
|
267
|
+
caller_index = sorted_names.index(caller_name) || -1
|
|
268
|
+
sorted_names.insert(caller_index + 1, callee_name)
|
|
269
|
+
end
|
|
238
270
|
|
|
239
|
-
#
|
|
240
|
-
ranges_by_name = defs.to_h
|
|
241
|
-
|
|
271
|
+
# reconstruct source
|
|
272
|
+
ranges_by_name = defs.to_h do |d|
|
|
273
|
+
[d.method_name, range_with_leading_comments(d)]
|
|
274
|
+
end
|
|
275
|
+
sorted_def_sources = sorted_names.map { |n| ranges_by_name[n].source }
|
|
242
276
|
|
|
243
|
-
|
|
244
|
-
visibility_node = target_section[:visibility]
|
|
277
|
+
visibility_node = target_section[:visibility]
|
|
245
278
|
visibility_source = visibility_node&.source.to_s
|
|
246
279
|
|
|
247
|
-
new_content =
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
280
|
+
new_content =
|
|
281
|
+
if visibility_source.empty?
|
|
282
|
+
sorted_def_sources.join("\n\n")
|
|
283
|
+
else
|
|
284
|
+
"#{visibility_source}\n\n#{sorted_def_sources.join("\n\n")}"
|
|
285
|
+
end
|
|
252
286
|
|
|
253
|
-
# Expand the replaced region:
|
|
254
|
-
# - if a visibility node exists, start from its begin_pos
|
|
255
|
-
# - otherwise, start from the earliest leading doc-comment of the defs
|
|
256
287
|
section_begin =
|
|
257
288
|
if visibility_node
|
|
258
289
|
visibility_node.source_range.begin_pos
|
|
259
290
|
else
|
|
260
291
|
defs.map { |d| range_with_leading_comments(d).begin_pos }.min
|
|
261
292
|
end
|
|
262
|
-
|
|
263
|
-
# Always end at the end of the last def
|
|
264
293
|
section_end = defs.last.source_range.end_pos
|
|
265
294
|
|
|
266
295
|
region = Parser::Source::Range.new(processed_source.buffer, section_begin, section_end)
|
|
@@ -360,9 +389,10 @@ module RuboCop
|
|
|
360
389
|
current_defs << node
|
|
361
390
|
section_start ||= node.source_range.begin_pos
|
|
362
391
|
when :send
|
|
363
|
-
#
|
|
364
|
-
if node.receiver.nil? &&
|
|
365
|
-
|
|
392
|
+
# visibility modifier?
|
|
393
|
+
if node.receiver.nil? &&
|
|
394
|
+
%i[private protected public].include?(node.method_name) &&
|
|
395
|
+
node.arguments.empty?
|
|
366
396
|
unless current_defs.empty?
|
|
367
397
|
sections << {
|
|
368
398
|
visibility: current_visibility,
|
|
@@ -375,7 +405,7 @@ module RuboCop
|
|
|
375
405
|
end
|
|
376
406
|
current_visibility = node
|
|
377
407
|
else
|
|
378
|
-
#
|
|
408
|
+
# anything else breaks contiguous run
|
|
379
409
|
unless current_defs.empty?
|
|
380
410
|
sections << {
|
|
381
411
|
visibility: current_visibility,
|
|
@@ -385,11 +415,9 @@ module RuboCop
|
|
|
385
415
|
}
|
|
386
416
|
current_defs = []
|
|
387
417
|
section_start = nil
|
|
388
|
-
current_visibility = nil
|
|
389
418
|
end
|
|
390
419
|
end
|
|
391
420
|
else
|
|
392
|
-
# Any other node type breaks contiguity
|
|
393
421
|
unless current_defs.empty?
|
|
394
422
|
sections << {
|
|
395
423
|
visibility: current_visibility,
|
|
@@ -399,12 +427,11 @@ module RuboCop
|
|
|
399
427
|
}
|
|
400
428
|
current_defs = []
|
|
401
429
|
section_start = nil
|
|
402
|
-
current_visibility = nil
|
|
403
430
|
end
|
|
404
431
|
end
|
|
405
432
|
end
|
|
406
433
|
|
|
407
|
-
#
|
|
434
|
+
# trailing defs
|
|
408
435
|
unless current_defs.empty?
|
|
409
436
|
sections << {
|
|
410
437
|
visibility: current_visibility,
|
|
@@ -414,7 +441,20 @@ module RuboCop
|
|
|
414
441
|
}
|
|
415
442
|
end
|
|
416
443
|
|
|
417
|
-
|
|
444
|
+
# -----------------------------------------------
|
|
445
|
+
# merge consecutive groups with identical visibility
|
|
446
|
+
# -----------------------------------------------
|
|
447
|
+
merged = []
|
|
448
|
+
sections.each do |s|
|
|
449
|
+
if !merged.empty? &&
|
|
450
|
+
merged.last[:visibility]&.source == s[:visibility]&.source
|
|
451
|
+
merged.last[:defs].concat(s[:defs])
|
|
452
|
+
merged.last[:end_pos] = s[:end_pos]
|
|
453
|
+
else
|
|
454
|
+
merged << s
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
merged
|
|
418
458
|
end
|
|
419
459
|
|
|
420
460
|
# +RuboCop::Cop::SortedMethodsByCall::Waterfall#topo_sort+ -> Array<Symbol>, nil
|