rubocop-sorted_methods_by_call 1.1.2 → 1.2.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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +21 -11
- data/Gemfile.lock +1 -1
- data/README.md +47 -0
- data/config/default.yml +2 -1
- data/lib/rubocop/cop/sorted_methods_by_call/waterfall.rb +374 -172
- data/lib/rubocop/sorted_methods_by_call/compare.rb +1 -1
- data/lib/rubocop/sorted_methods_by_call/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 02e794a25b73c4b96c7ec77721e5b5e4c71a3a23e0137011f3d272d27e81121e
|
|
4
|
+
data.tar.gz: 75db5c1df55a2d40a9fdf596b654bafa76eecbb163e16c419b7c0366ef4cd772
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c3e6192757164ad8c877337a9bf60e78ccb2c4b8d8abd2a3f8c15a865cfe7ea4b1440f6c443dd26ca9836951884b229927a718081ab521b64926cc25896bab4
|
|
7
|
+
data.tar.gz: 14f8232209323631fb6a993282e6133907d3937ecf1b639c9e186b79fb835f531a750a4ebdb6e972c3f558921507c7fbb3a954c6c26e7510d19a3ddc3e3cfd7d
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on
|
|
3
|
+
# on 2026-01-13 11:42:57 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
|
|
@@ -19,37 +19,47 @@ Gemspec/RequiredRubyVersion:
|
|
|
19
19
|
Exclude:
|
|
20
20
|
- 'rubocop-sorted_methods_by_call.gemspec'
|
|
21
21
|
|
|
22
|
-
# Offense count:
|
|
22
|
+
# Offense count: 5
|
|
23
23
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
|
24
24
|
Metrics/AbcSize:
|
|
25
|
-
Max:
|
|
25
|
+
Max: 78
|
|
26
26
|
|
|
27
|
-
# Offense count:
|
|
27
|
+
# Offense count: 1
|
|
28
28
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
|
|
29
29
|
# AllowedMethods: refine
|
|
30
30
|
Metrics/BlockLength:
|
|
31
31
|
Max: 36
|
|
32
32
|
|
|
33
|
-
# Offense count:
|
|
33
|
+
# Offense count: 4
|
|
34
34
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
35
35
|
Metrics/CyclomaticComplexity:
|
|
36
|
-
Max:
|
|
36
|
+
Max: 35
|
|
37
37
|
|
|
38
|
-
# Offense count:
|
|
38
|
+
# Offense count: 9
|
|
39
39
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
40
40
|
Metrics/MethodLength:
|
|
41
|
-
Max:
|
|
41
|
+
Max: 70
|
|
42
42
|
|
|
43
|
-
# Offense count:
|
|
43
|
+
# Offense count: 2
|
|
44
|
+
# Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
|
|
45
|
+
Metrics/ParameterLists:
|
|
46
|
+
Max: 6
|
|
47
|
+
|
|
48
|
+
# Offense count: 3
|
|
44
49
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
45
50
|
Metrics/PerceivedComplexity:
|
|
46
|
-
Max:
|
|
51
|
+
Max: 37
|
|
47
52
|
|
|
48
|
-
# Offense count:
|
|
53
|
+
# Offense count: 21
|
|
49
54
|
# Configuration parameters: CountAsOne.
|
|
50
55
|
RSpec/ExampleLength:
|
|
51
56
|
Max: 37
|
|
52
57
|
|
|
58
|
+
# Offense count: 1
|
|
59
|
+
# Configuration parameters: AllowedGroups.
|
|
60
|
+
RSpec/NestedGroups:
|
|
61
|
+
Max: 4
|
|
62
|
+
|
|
53
63
|
# Offense count: 2
|
|
54
64
|
# This cop supports safe autocorrection (--autocorrect).
|
|
55
65
|
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* [Usage Examples](#usage-examples)
|
|
16
16
|
* [Good Code (waterfall order)](#good-code-waterfall-order)
|
|
17
17
|
* [Bad Code (violates waterfall order)](#bad-code-violates-waterfall-order)
|
|
18
|
+
* [Sibling ordering and cycles (why autocorrect can be skipped)](#sibling-ordering-and-cycles-why-autocorrect-can-be-skipped)
|
|
18
19
|
* [Autocorrection](#autocorrection)
|
|
19
20
|
* [Testing](#testing)
|
|
20
21
|
* [Development](#development)
|
|
@@ -76,6 +77,12 @@ SortedMethodsByCall/Waterfall:
|
|
|
76
77
|
Enabled: true
|
|
77
78
|
SafeAutoCorrect: false # Autocorrection requires -A flag
|
|
78
79
|
AllowedRecursion: true # Allow mutual recursion (default: true)
|
|
80
|
+
# If true, the cop will NOT add "called together" sibling-order edges
|
|
81
|
+
# that would introduce a cycle with existing constraints. This reduces
|
|
82
|
+
# impossible-to-fix sibling offenses and makes autocorrect more reliable.
|
|
83
|
+
#
|
|
84
|
+
# Default: false
|
|
85
|
+
SkipCyclicSiblingEdges: false
|
|
79
86
|
```
|
|
80
87
|
|
|
81
88
|
## Usage Examples
|
|
@@ -135,6 +142,46 @@ class Service
|
|
|
135
142
|
end
|
|
136
143
|
```
|
|
137
144
|
|
|
145
|
+
### Sibling ordering and cycles (why autocorrect can be skipped)
|
|
146
|
+
|
|
147
|
+
`SortedMethodsByCall/Waterfall` enforces two kinds of ordering constraints:
|
|
148
|
+
|
|
149
|
+
1. **Direct call edges**: if `caller` calls `callee`, then `caller` must be defined **before** `callee`.
|
|
150
|
+
2. **Sibling ("called together") edges**: in orchestration methods (methods not called by others in the same scope),
|
|
151
|
+
consecutive calls imply an intended order (e.g., `a` then `b`), so `a` should be defined before `b`.
|
|
152
|
+
|
|
153
|
+
Sometimes these constraints can conflict and create a **cycle**, which means there is no valid ordering that satisfies
|
|
154
|
+
all constraints. In this situation, autocorrect may be skipped.
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
class SiblingCycleExample
|
|
160
|
+
def call
|
|
161
|
+
a
|
|
162
|
+
b
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
|
|
167
|
+
def b
|
|
168
|
+
c
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def c
|
|
172
|
+
a
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def a; end
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Here, the direct dependencies imply `b -> c -> a`, but the orchestration method implies `a -> b`,
|
|
180
|
+
which forms the cycle `a -> b -> c -> a`.
|
|
181
|
+
|
|
182
|
+
If you prefer to keep the warning (to encourage refactoring), leave `SkipCyclicSiblingEdges: false`.
|
|
183
|
+
If you prefer the cop to avoid enforcing sibling edges that create cycles, set `SkipCyclicSiblingEdges: true`.
|
|
184
|
+
|
|
138
185
|
### Autocorrection
|
|
139
186
|
|
|
140
187
|
Run with unsafe autocorrection to automatically fix violations:
|
data/config/default.yml
CHANGED
|
@@ -15,10 +15,15 @@ module RuboCop
|
|
|
15
15
|
#
|
|
16
16
|
# Configuration
|
|
17
17
|
# - AllowedRecursion [Boolean] (default: true)
|
|
18
|
-
# If true, the cop ignores violations that are part of a
|
|
19
|
-
#
|
|
18
|
+
# If true, the cop ignores violations that are part of a recursion cycle
|
|
19
|
+
# detectable in the direct call graph (callee → … → caller). If false,
|
|
20
|
+
# such cycles are reported.
|
|
20
21
|
# - SafeAutoCorrect [Boolean] (default: false)
|
|
21
22
|
# Autocorrection is unsafe and only runs under -A, never under -a.
|
|
23
|
+
# - SkipCyclicSiblingEdges [Boolean] (default: false)
|
|
24
|
+
# If true, the cop will not add "called together" sibling-order edges
|
|
25
|
+
# that would introduce a cycle with existing constraints (direct edges +
|
|
26
|
+
# already accepted sibling edges).
|
|
22
27
|
#
|
|
23
28
|
# @example Good (waterfall order)
|
|
24
29
|
# class Service
|
|
@@ -70,9 +75,20 @@ module RuboCop
|
|
|
70
75
|
include ::RuboCop::Cop::RangeHelp
|
|
71
76
|
extend ::RuboCop::Cop::AutoCorrector
|
|
72
77
|
|
|
78
|
+
VISIBILITY_METHODS = %i[private protected public].freeze
|
|
79
|
+
|
|
73
80
|
# Template message for offenses where a callee appears before its caller.
|
|
74
81
|
MSG = 'Define %<callee>s after its caller %<caller>s (waterfall order).'
|
|
75
82
|
|
|
83
|
+
SIBLING_MSG = 'Define %<callee>s after %<caller>s to match the order they are called together.'
|
|
84
|
+
|
|
85
|
+
MSG_CROSS_VISIBILITY_NOTE =
|
|
86
|
+
'%<base>s (Autocorrect not supported across visibility boundaries: ' \
|
|
87
|
+
'%<caller_visibility>s vs %<callee_visibility>s.)'
|
|
88
|
+
|
|
89
|
+
MSG_SIBLING_CYCLE_NOTE =
|
|
90
|
+
'%<base>s (Possible sibling cycle detected; autocorrect may be skipped.)'
|
|
91
|
+
|
|
76
92
|
# Entry point for root :begin nodes (top-level).
|
|
77
93
|
#
|
|
78
94
|
# Whether top-level is analyzed depends on how the code is structured;
|
|
@@ -111,149 +127,297 @@ module RuboCop
|
|
|
111
127
|
|
|
112
128
|
private
|
|
113
129
|
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
# -
|
|
120
|
-
# -
|
|
121
|
-
# to reflect the order of consecutive calls (foo then bar).
|
|
130
|
+
# Analyze a single scope node (:begin, :class, :module, :sclass):
|
|
131
|
+
# - Collect method defs in the scope body
|
|
132
|
+
# - Build direct call edges (caller → callee)
|
|
133
|
+
# - Optionally build sibling-order edges ("called together")
|
|
134
|
+
# - Find the first ordering violation and register an offense
|
|
135
|
+
# - Attempt autocorrect (under -A) within a contiguous visibility section
|
|
136
|
+
# - Recurse into nested scopes inside the body
|
|
122
137
|
#
|
|
123
138
|
# @param scope_node [RuboCop::AST::Node] a :begin, :class, :module, or :sclass node
|
|
124
139
|
# @return [void]
|
|
140
|
+
# @api private
|
|
125
141
|
def analyze_scope(scope_node)
|
|
126
142
|
body_nodes = scope_body_nodes(scope_node)
|
|
127
143
|
return if body_nodes.empty?
|
|
128
144
|
|
|
129
|
-
def_nodes = body_nodes
|
|
145
|
+
def_nodes = method_def_nodes(body_nodes)
|
|
130
146
|
return if def_nodes.size <= 1
|
|
131
147
|
|
|
132
|
-
names
|
|
133
|
-
names_set = names.to_set
|
|
134
|
-
index_of = names.each_with_index.to_h
|
|
148
|
+
names, names_set, index_of = method_name_index(def_nodes)
|
|
135
149
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
direct_edges = build_direct_edges(def_nodes, names_set)
|
|
151
|
+
sibling_edges = build_sibling_edges(def_nodes, names_set, direct_edges, names)
|
|
152
|
+
|
|
153
|
+
edges_for_sort = direct_edges + sibling_edges
|
|
154
|
+
adj_direct = build_adj(names, direct_edges)
|
|
155
|
+
|
|
156
|
+
violation_type, violation = find_violation(direct_edges, sibling_edges, index_of, adj_direct)
|
|
157
|
+
if violation
|
|
158
|
+
_, callee_name = violation
|
|
159
|
+
callee_node = def_nodes[index_of.fetch(callee_name)]
|
|
160
|
+
|
|
161
|
+
message = build_offense_message(
|
|
162
|
+
violation_type: violation_type,
|
|
163
|
+
violation: violation,
|
|
164
|
+
names: names,
|
|
165
|
+
edges_for_sort: edges_for_sort,
|
|
166
|
+
body_nodes: body_nodes
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
add_offense(callee_node, message: message) do |corrector|
|
|
170
|
+
try_autocorrect(corrector, body_nodes, def_nodes, edges_for_sort, violation)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
analyze_nested_scopes(body_nodes)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Return the direct "body statements" inside a scope node.
|
|
178
|
+
#
|
|
179
|
+
# @param node [RuboCop::AST::Node]
|
|
180
|
+
# @return [Array<RuboCop::AST::Node>] direct children inside the scope body
|
|
181
|
+
# @api private
|
|
182
|
+
def scope_body_nodes(node)
|
|
183
|
+
case node.type
|
|
184
|
+
when :begin
|
|
185
|
+
node.children
|
|
186
|
+
when :class, :module, :sclass
|
|
187
|
+
body = node.body
|
|
188
|
+
return [] unless body
|
|
189
|
+
|
|
190
|
+
body.begin_type? ? body.children : [body]
|
|
191
|
+
else
|
|
192
|
+
[]
|
|
141
193
|
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Select only method definition nodes from a scope body.
|
|
197
|
+
#
|
|
198
|
+
# @param body_nodes [Array<RuboCop::AST::Node>]
|
|
199
|
+
# @return [Array<RuboCop::AST::Node>] :def/:defs nodes
|
|
200
|
+
# @api private
|
|
201
|
+
def method_def_nodes(body_nodes)
|
|
202
|
+
body_nodes.select { |n| %i[def defs].include?(n.type) }
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Compute helper structures for method names in this scope.
|
|
206
|
+
#
|
|
207
|
+
# @param def_nodes [Array<RuboCop::AST::Node>]
|
|
208
|
+
# @return [Array<(Array<Symbol>, Set<Symbol>, Hash{Symbol=>Integer})>]
|
|
209
|
+
# @api private
|
|
210
|
+
def method_name_index(def_nodes)
|
|
211
|
+
names = def_nodes.map(&:method_name)
|
|
212
|
+
[names, names.to_set, names.each_with_index.to_h]
|
|
213
|
+
end
|
|
142
214
|
|
|
143
|
-
|
|
215
|
+
# Build direct call edges (caller -> callee) for local calls within each method body.
|
|
216
|
+
#
|
|
217
|
+
# @param def_nodes [Array<RuboCop::AST::Node>]
|
|
218
|
+
# @param names_set [Set<Symbol>]
|
|
219
|
+
# @return [Array<Array(Symbol, Symbol)>]
|
|
220
|
+
# @api private
|
|
221
|
+
def build_direct_edges(def_nodes, names_set)
|
|
222
|
+
def_nodes.flat_map do |def_node|
|
|
223
|
+
local_calls(def_node, names_set)
|
|
224
|
+
.reject { |callee| callee == def_node.method_name }
|
|
225
|
+
.map { |callee| [def_node.method_name, callee] }
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Build sibling-order edges (a -> b) for consecutive calls inside orchestration methods.
|
|
230
|
+
#
|
|
231
|
+
# Orchestration methods are those not called by any other method in this scope.
|
|
232
|
+
#
|
|
233
|
+
# @param def_nodes [Array<RuboCop::AST::Node>]
|
|
234
|
+
# @param names_set [Set<Symbol>]
|
|
235
|
+
# @param direct_edges [Array<Array(Symbol, Symbol)>]
|
|
236
|
+
# @param names [Array<Symbol>]
|
|
237
|
+
# @return [Array<Array(Symbol, Symbol)>]
|
|
238
|
+
# @api private
|
|
239
|
+
def build_sibling_edges(def_nodes, names_set, direct_edges, names)
|
|
144
240
|
all_callees = direct_edges.to_set(&:last)
|
|
241
|
+
direct_pair_set = direct_edges.to_set
|
|
242
|
+
|
|
243
|
+
skip_cyclic_siblings = skip_cyclic_sibling_edges?
|
|
244
|
+
adj_for_siblings = build_adj(names, direct_edges)
|
|
145
245
|
|
|
146
|
-
# Phase 2: sibling-order edges from orchestration methods
|
|
147
246
|
sibling_edges = []
|
|
247
|
+
|
|
148
248
|
def_nodes.each do |def_node|
|
|
149
249
|
next if all_callees.include?(def_node.method_name)
|
|
150
250
|
|
|
151
251
|
calls = local_calls(def_node, names_set)
|
|
152
252
|
calls.each_cons(2) do |a, b|
|
|
153
|
-
|
|
253
|
+
# If there is already a direct relationship between a and b (either direction),
|
|
254
|
+
# do not add a sibling-order edge.
|
|
255
|
+
next if direct_pair_set.include?([a, b]) || direct_pair_set.include?([b, a])
|
|
256
|
+
|
|
257
|
+
# Optional: do not add a sibling edge that would introduce a cycle.
|
|
258
|
+
next if skip_cyclic_siblings && path_exists?(b, a, adj_for_siblings)
|
|
154
259
|
|
|
155
260
|
sibling_edges << [a, b]
|
|
261
|
+
adj_for_siblings[a] << b unless adj_for_siblings[a].include?(b)
|
|
156
262
|
end
|
|
157
263
|
end
|
|
158
264
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
265
|
+
sibling_edges
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Find the first ordering violation. Checks direct edges first, then sibling edges.
|
|
269
|
+
#
|
|
270
|
+
# @param direct_edges [Array<Array(Symbol, Symbol)>]
|
|
271
|
+
# @param sibling_edges [Array<Array(Symbol, Symbol)>]
|
|
272
|
+
# @param index_of [Hash{Symbol=>Integer}]
|
|
273
|
+
# @param adj_direct [Hash{Symbol=>Array<Symbol>}] adjacency list for direct edges
|
|
274
|
+
# @return [Array<(Symbol, Array(Symbol, Symbol))>, Array<(nil, nil)>]
|
|
275
|
+
# @api private
|
|
276
|
+
def find_violation(direct_edges, sibling_edges, index_of, adj_direct)
|
|
277
|
+
allow_recursion = allowed_recursion?
|
|
163
278
|
|
|
164
279
|
violation = first_backward_edge(direct_edges, index_of, adj_direct, allow_recursion)
|
|
165
|
-
|
|
280
|
+
return [:direct, violation] if violation
|
|
166
281
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
violation_type = :sibling if violation
|
|
170
|
-
end
|
|
282
|
+
violation = first_backward_edge(sibling_edges, index_of, adj_direct, allow_recursion)
|
|
283
|
+
return [:sibling, violation] if violation
|
|
171
284
|
|
|
172
|
-
|
|
285
|
+
[nil, nil]
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Return the first backward edge found, optionally skipping edges that participate
|
|
289
|
+
# in recursion/cycles detectable in the direct-call graph (AllowedRecursion).
|
|
290
|
+
#
|
|
291
|
+
# @param edges [Array<Array(Symbol, Symbol)>]
|
|
292
|
+
# @param index_of [Hash{Symbol=>Integer}]
|
|
293
|
+
# @param adj_direct [Hash{Symbol=>Array<Symbol>}] direct-call adjacency for path checks
|
|
294
|
+
# @param allow_recursion [Boolean]
|
|
295
|
+
# @return [Array(Symbol, Symbol), nil]
|
|
296
|
+
# @api private
|
|
297
|
+
def first_backward_edge(edges, index_of, adj_direct, allow_recursion)
|
|
298
|
+
edges.find do |caller, callee|
|
|
299
|
+
next unless index_of.key?(caller) && index_of.key?(callee)
|
|
300
|
+
next if allow_recursion && path_exists?(callee, caller, adj_direct)
|
|
301
|
+
|
|
302
|
+
index_of[callee] < index_of[caller]
|
|
303
|
+
end
|
|
304
|
+
end
|
|
173
305
|
|
|
306
|
+
# Construct the final offense message, including optional notes:
|
|
307
|
+
# - sibling cycle note (for sibling violations)
|
|
308
|
+
# - cross-visibility note (public/private/protected boundary)
|
|
309
|
+
#
|
|
310
|
+
# @param violation_type [Symbol] :direct or :sibling
|
|
311
|
+
# @param violation [Array(Symbol, Symbol)] (caller_name, callee_name)
|
|
312
|
+
# @param names [Array<Symbol>]
|
|
313
|
+
# @param edges_for_sort [Array<Array(Symbol, Symbol)>]
|
|
314
|
+
# @param body_nodes [Array<RuboCop::AST::Node>]
|
|
315
|
+
# @return [String]
|
|
316
|
+
# @api private
|
|
317
|
+
def build_offense_message(violation_type:, violation:, names:, edges_for_sort:, body_nodes:)
|
|
174
318
|
caller_name, callee_name = violation
|
|
175
|
-
callee_node = def_nodes[index_of[callee_name]]
|
|
176
319
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
format(MSG, callee: "##{callee_name}", caller: "##{caller_name}")
|
|
182
|
-
end
|
|
320
|
+
base = base_message_for(violation_type, caller_name, callee_name)
|
|
321
|
+
base = add_sibling_cycle_note_if_needed(base, violation_type, caller_name, callee_name, names, edges_for_sort)
|
|
322
|
+
add_cross_visibility_note_if_needed(base, body_nodes, caller_name, callee_name)
|
|
323
|
+
end
|
|
183
324
|
|
|
184
|
-
|
|
185
|
-
|
|
325
|
+
# @param violation_type [Symbol]
|
|
326
|
+
# @param caller_name [Symbol]
|
|
327
|
+
# @param callee_name [Symbol]
|
|
328
|
+
# @return [String]
|
|
329
|
+
# @api private
|
|
330
|
+
def base_message_for(violation_type, caller_name, callee_name)
|
|
331
|
+
if violation_type == :sibling
|
|
332
|
+
format(SIBLING_MSG, callee: "##{callee_name}", caller: "##{caller_name}")
|
|
333
|
+
else
|
|
334
|
+
format(MSG, callee: "##{callee_name}", caller: "##{caller_name}")
|
|
186
335
|
end
|
|
336
|
+
end
|
|
187
337
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
338
|
+
# Add a note when a sibling-order edge is part of a cycle in the combined graph.
|
|
339
|
+
#
|
|
340
|
+
# @param base_message [String]
|
|
341
|
+
# @param violation_type [Symbol]
|
|
342
|
+
# @param caller_name [Symbol]
|
|
343
|
+
# @param callee_name [Symbol]
|
|
344
|
+
# @param names [Array<Symbol>]
|
|
345
|
+
# @param edges_for_sort [Array<Array(Symbol, Symbol)>]
|
|
346
|
+
# @return [String]
|
|
347
|
+
# @api private
|
|
348
|
+
def add_sibling_cycle_note_if_needed(base_message, violation_type, caller_name, callee_name, names,
|
|
349
|
+
edges_for_sort)
|
|
350
|
+
return base_message unless violation_type == :sibling
|
|
351
|
+
|
|
352
|
+
adj_all = build_adj(names, edges_for_sort)
|
|
353
|
+
if path_exists?(callee_name, caller_name, adj_all)
|
|
354
|
+
format(MSG_SIBLING_CYCLE_NOTE, base: base_message)
|
|
355
|
+
else
|
|
356
|
+
base_message
|
|
191
357
|
end
|
|
192
358
|
end
|
|
193
359
|
|
|
194
|
-
#
|
|
360
|
+
# Add a note when the violation crosses visibility boundaries.
|
|
195
361
|
#
|
|
196
|
-
# @param
|
|
197
|
-
# @
|
|
362
|
+
# @param base_message [String]
|
|
363
|
+
# @param body_nodes [Array<RuboCop::AST::Node>]
|
|
364
|
+
# @param caller_name [Symbol]
|
|
365
|
+
# @param callee_name [Symbol]
|
|
366
|
+
# @return [String]
|
|
198
367
|
# @api private
|
|
199
|
-
def
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
368
|
+
def add_cross_visibility_note_if_needed(base_message, body_nodes, caller_name, callee_name)
|
|
369
|
+
sections = extract_visibility_sections(body_nodes)
|
|
370
|
+
caller_section = section_for_method(sections, caller_name)
|
|
371
|
+
callee_section = section_for_method(sections, callee_name)
|
|
372
|
+
|
|
373
|
+
caller_vis = visibility_label(caller_section)
|
|
374
|
+
callee_vis = visibility_label(callee_section)
|
|
375
|
+
|
|
376
|
+
if caller_section && callee_section && caller_vis != callee_vis
|
|
377
|
+
format(
|
|
378
|
+
MSG_CROSS_VISIBILITY_NOTE,
|
|
379
|
+
base: base_message,
|
|
380
|
+
caller_visibility: caller_vis,
|
|
381
|
+
callee_visibility: callee_vis
|
|
382
|
+
)
|
|
208
383
|
else
|
|
209
|
-
|
|
384
|
+
base_message
|
|
210
385
|
end
|
|
211
386
|
end
|
|
212
387
|
|
|
213
|
-
# UNSAFE:
|
|
214
|
-
# (does not cross private/protected/public boundaries). Skips if defs are not
|
|
215
|
-
# contiguous within the section or if a cycle prevents a consistent topo order.
|
|
388
|
+
# UNSAFE autocorrect: reorder method definitions inside one contiguous visibility section only.
|
|
216
389
|
#
|
|
217
|
-
#
|
|
218
|
-
# -
|
|
219
|
-
#
|
|
220
|
-
# -
|
|
221
|
-
# - Rewrites only the exact contiguous section (plus the visibility line if present).
|
|
222
|
-
# - Preserves leading doc comments for each method.
|
|
390
|
+
# This method intentionally does NOT reorder across:
|
|
391
|
+
# - `private/protected/public` boundaries
|
|
392
|
+
# - nested scopes
|
|
393
|
+
# - non-visibility statements that break contiguity
|
|
223
394
|
#
|
|
224
395
|
# @param corrector [RuboCop::Cop::Corrector]
|
|
225
|
-
# @param body_nodes [Array<RuboCop::AST::Node>]
|
|
226
|
-
# @param def_nodes [Array<RuboCop::AST::Node>]
|
|
396
|
+
# @param body_nodes [Array<RuboCop::AST::Node>]
|
|
397
|
+
# @param def_nodes [Array<RuboCop::AST::Node>]
|
|
227
398
|
# @param edges [Array<Array(Symbol, Symbol)>] direct + sibling edges for this scope
|
|
228
|
-
# @param initial_violation [Array
|
|
399
|
+
# @param initial_violation [Array(Symbol, Symbol), nil]
|
|
229
400
|
# @return [void]
|
|
230
401
|
# @api private
|
|
231
402
|
def try_autocorrect(corrector, body_nodes, def_nodes, edges, initial_violation = nil)
|
|
232
403
|
sections = extract_visibility_sections(body_nodes)
|
|
233
404
|
|
|
234
|
-
names
|
|
235
|
-
idx_of = names.each_with_index.to_h
|
|
405
|
+
names = def_nodes.map(&:method_name)
|
|
236
406
|
names_set = names.to_set
|
|
407
|
+
idx_of = names.each_with_index.to_h
|
|
237
408
|
|
|
238
409
|
# Recompute direct edges; split edges back into direct vs sibling
|
|
239
|
-
direct_edges = def_nodes
|
|
240
|
-
local_calls(def_node, names_set)
|
|
241
|
-
.reject { |callee| callee == def_node.method_name }
|
|
242
|
-
.map { |callee| [def_node.method_name, callee] }
|
|
243
|
-
end
|
|
410
|
+
direct_edges = build_direct_edges(def_nodes, names_set)
|
|
244
411
|
sibling_edges = edges - direct_edges
|
|
245
412
|
|
|
246
|
-
|
|
247
|
-
allow_recursion = cop_config.fetch('AllowedRecursion') { true }
|
|
413
|
+
allow_recursion = allowed_recursion?
|
|
248
414
|
adj_direct = build_adj(names, direct_edges)
|
|
249
415
|
|
|
250
|
-
violation = initial_violation ||
|
|
251
|
-
first_backward_edge(edges, idx_of, adj_direct, allow_recursion)
|
|
416
|
+
violation = initial_violation || first_backward_edge(edges, idx_of, adj_direct, allow_recursion)
|
|
252
417
|
return unless violation
|
|
253
418
|
|
|
254
419
|
caller_name, callee_name = violation
|
|
255
420
|
|
|
256
|
-
# Find the contiguous section containing both caller and callee
|
|
257
421
|
target_section = sections.find do |section|
|
|
258
422
|
section_names = section[:defs].map(&:method_name)
|
|
259
423
|
section_names.include?(caller_name) && section_names.include?(callee_name)
|
|
@@ -263,23 +427,19 @@ module RuboCop
|
|
|
263
427
|
defs = target_section[:defs]
|
|
264
428
|
return if defs.size <= 1
|
|
265
429
|
|
|
266
|
-
section_names
|
|
267
|
-
section_idx_of
|
|
430
|
+
section_names = defs.map(&:method_name)
|
|
431
|
+
section_idx_of = section_names.each_with_index.to_h
|
|
268
432
|
|
|
269
|
-
# Is this a direct-call violation?
|
|
270
433
|
direct_violation = direct_edges.any? { |u, v| u == caller_name && v == callee_name }
|
|
271
434
|
|
|
272
|
-
|
|
273
|
-
section_direct_edges = direct_edges.select { |u, v| section_names.include?(u) && section_names.include?(v) }
|
|
435
|
+
section_direct_edges = direct_edges.select { |u, v| section_names.include?(u) && section_names.include?(v) }
|
|
274
436
|
section_sibling_edges = sibling_edges.select { |u, v| section_names.include?(u) && section_names.include?(v) }
|
|
275
437
|
|
|
276
|
-
# Prune mutual-recursion edges inside the section if allowed
|
|
277
438
|
if allow_recursion
|
|
278
439
|
pair_set = section_direct_edges.to_set
|
|
279
440
|
section_direct_edges = section_direct_edges.reject { |u, v| pair_set.include?([v, u]) }
|
|
280
441
|
end
|
|
281
442
|
|
|
282
|
-
# Sorting edges: direct-only for direct violation, otherwise sibling + pruned direct
|
|
283
443
|
section_edges_for_sort =
|
|
284
444
|
if direct_violation
|
|
285
445
|
section_direct_edges
|
|
@@ -290,9 +450,8 @@ module RuboCop
|
|
|
290
450
|
sorted_names = topo_sort(section_names, section_edges_for_sort, section_idx_of)
|
|
291
451
|
return if sorted_names.nil? || sorted_names == section_names
|
|
292
452
|
|
|
293
|
-
# Rebuild section (preserve per-method leading docs)
|
|
294
453
|
ranges_by_name = defs.to_h { |d| [d.method_name, range_with_leading_comments(d)] }
|
|
295
|
-
sorted_def_sources = sorted_names.map { |n| ranges_by_name
|
|
454
|
+
sorted_def_sources = sorted_names.map { |n| ranges_by_name.fetch(n).source }
|
|
296
455
|
|
|
297
456
|
visibility_node = target_section[:visibility]
|
|
298
457
|
visibility_source = visibility_node&.source.to_s
|
|
@@ -310,14 +469,14 @@ module RuboCop
|
|
|
310
469
|
else
|
|
311
470
|
defs.map { |d| range_with_leading_comments(d).begin_pos }.min
|
|
312
471
|
end
|
|
313
|
-
section_end = target_section[:end_pos]
|
|
314
472
|
|
|
315
|
-
|
|
473
|
+
section_end = target_section[:end_pos]
|
|
474
|
+
region = range_between(section_begin, section_end)
|
|
316
475
|
corrector.replace(region, new_content)
|
|
317
476
|
end
|
|
318
477
|
|
|
319
|
-
#
|
|
320
|
-
#
|
|
478
|
+
# Collect local method calls (receiver is nil/self) from within a def node,
|
|
479
|
+
# restricted to known method names in this scope.
|
|
321
480
|
#
|
|
322
481
|
# @param def_node [RuboCop::AST::Node] :def or :defs
|
|
323
482
|
# @param names_set [Set<Symbol>] known local method names in this scope
|
|
@@ -338,63 +497,48 @@ module RuboCop
|
|
|
338
497
|
res.uniq
|
|
339
498
|
end
|
|
340
499
|
|
|
341
|
-
#
|
|
500
|
+
# Build an adjacency list for a set of edges restricted to known names.
|
|
342
501
|
#
|
|
343
|
-
# @param names [Array<Symbol>]
|
|
344
|
-
# @param edges [Array<Array(Symbol, Symbol)>]
|
|
502
|
+
# @param names [Array<Symbol>]
|
|
503
|
+
# @param edges [Array<Array(Symbol, Symbol)>]
|
|
345
504
|
# @return [Hash{Symbol=>Array<Symbol>}] adjacency list
|
|
346
505
|
# @api private
|
|
347
506
|
def build_adj(names, edges)
|
|
348
507
|
allowed = names.to_set
|
|
349
508
|
adj = Hash.new { |h, k| h[k] = [] }
|
|
509
|
+
|
|
350
510
|
edges.each do |u, v|
|
|
351
511
|
next unless allowed.include?(u) && allowed.include?(v)
|
|
352
512
|
next if u == v
|
|
353
513
|
|
|
354
514
|
adj[u] << v
|
|
355
515
|
end
|
|
356
|
-
adj
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
# Returns the first backward edge found, optionally skipping edges
|
|
360
|
-
# that participate in mutual recursion (when AllowedRecursion is true).
|
|
361
|
-
#
|
|
362
|
-
# @param edges [Array<Array(Symbol, Symbol)>] candidate edges to check
|
|
363
|
-
# @param index_of [Hash{Symbol=>Integer}] current definition order (name -> index)
|
|
364
|
-
# @param adj [Hash{Symbol=>Array<Symbol>}] direct-call adjacency for path checks
|
|
365
|
-
# @param allow_recursion [Boolean] whether mutual recursion suppresses a violation
|
|
366
|
-
# @return [(Symbol, Symbol), nil] the violating (caller, callee) or nil
|
|
367
|
-
# @api private
|
|
368
|
-
def first_backward_edge(edges, index_of, adj, allow_recursion)
|
|
369
|
-
edges.find do |caller, callee|
|
|
370
|
-
next unless index_of.key?(caller) && index_of.key?(callee)
|
|
371
|
-
# If mutual recursion allowed and there is a path callee -> caller, skip
|
|
372
|
-
next if allow_recursion && path_exists?(callee, caller, adj)
|
|
373
516
|
|
|
374
|
-
|
|
375
|
-
index_of[callee] < index_of[caller]
|
|
376
|
-
end
|
|
517
|
+
adj
|
|
377
518
|
end
|
|
378
519
|
|
|
379
|
-
# Breadth-first search to detect
|
|
520
|
+
# Breadth-first search to detect whether a path exists from +src+ to +dst+.
|
|
380
521
|
#
|
|
381
|
-
# @param src [Symbol]
|
|
382
|
-
# @param dst [Symbol]
|
|
522
|
+
# @param src [Symbol]
|
|
523
|
+
# @param dst [Symbol]
|
|
383
524
|
# @param adj [Hash{Symbol=>Array<Symbol>}] adjacency list
|
|
384
525
|
# @param limit [Integer] traversal safety limit
|
|
385
|
-
# @return [Boolean]
|
|
526
|
+
# @return [Boolean]
|
|
386
527
|
# @api private
|
|
387
528
|
def path_exists?(src, dst, adj, limit = 200)
|
|
388
529
|
return true if src == dst
|
|
389
530
|
|
|
390
531
|
visited = {}
|
|
391
532
|
q = [src]
|
|
533
|
+
i = 0
|
|
392
534
|
steps = 0
|
|
393
|
-
|
|
535
|
+
|
|
536
|
+
while i < q.length
|
|
394
537
|
steps += 1
|
|
395
538
|
return false if steps > limit
|
|
396
539
|
|
|
397
|
-
u = q
|
|
540
|
+
u = q[i]
|
|
541
|
+
i += 1
|
|
398
542
|
next if visited[u]
|
|
399
543
|
|
|
400
544
|
visited[u] = true
|
|
@@ -402,22 +546,21 @@ module RuboCop
|
|
|
402
546
|
|
|
403
547
|
adj[u].each { |v| q << v unless visited[v] }
|
|
404
548
|
end
|
|
549
|
+
|
|
405
550
|
false
|
|
406
551
|
end
|
|
407
552
|
|
|
408
|
-
#
|
|
553
|
+
# Split the scope body into contiguous sections of def/defs grouped
|
|
409
554
|
# by the visibility modifier immediately preceding them (private/protected/public).
|
|
410
555
|
#
|
|
411
556
|
# A section is represented as a Hash with:
|
|
412
557
|
# - :visibility [RuboCop::AST::Node, nil] the bare visibility send, or nil
|
|
413
558
|
# - :defs [Array<RuboCop::AST::Node>] contiguous def/defs nodes
|
|
414
|
-
# - :start_pos [Integer]
|
|
415
|
-
# - :end_pos [Integer]
|
|
416
|
-
#
|
|
417
|
-
# Non-visibility sends, constants, and nested scopes break contiguity.
|
|
559
|
+
# - :start_pos [Integer]
|
|
560
|
+
# - :end_pos [Integer]
|
|
418
561
|
#
|
|
419
|
-
# @param body_nodes [Array<RuboCop::AST::Node>]
|
|
420
|
-
# @return [Array<Hash>]
|
|
562
|
+
# @param body_nodes [Array<RuboCop::AST::Node>]
|
|
563
|
+
# @return [Array<Hash>]
|
|
421
564
|
# @api private
|
|
422
565
|
def extract_visibility_sections(body_nodes)
|
|
423
566
|
sections = []
|
|
@@ -430,45 +573,19 @@ module RuboCop
|
|
|
430
573
|
when :def, :defs
|
|
431
574
|
current_defs << node
|
|
432
575
|
section_start ||= node.source_range.begin_pos
|
|
433
|
-
|
|
434
576
|
when :send
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
defs: current_defs.dup,
|
|
440
|
-
start_pos: section_start,
|
|
441
|
-
end_pos: body_nodes[idx - 1].source_range.end_pos
|
|
442
|
-
}
|
|
443
|
-
current_defs = []
|
|
444
|
-
section_start = nil
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
# Bare visibility modifiers: private/protected/public without args
|
|
448
|
-
if node.receiver.nil? && %i[private protected public].include?(node.method_name) && node.arguments.empty?
|
|
449
|
-
current_visibility = node
|
|
450
|
-
else
|
|
451
|
-
# Non-visibility send breaks contiguity and resets visibility context
|
|
452
|
-
current_visibility = nil
|
|
453
|
-
end
|
|
454
|
-
|
|
577
|
+
flush_visibility_section!(sections, current_visibility, current_defs, section_start, body_nodes, idx - 1)
|
|
578
|
+
current_defs = []
|
|
579
|
+
section_start = nil
|
|
580
|
+
current_visibility = node if bare_visibility_send?(node)
|
|
455
581
|
else
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
visibility: current_visibility,
|
|
460
|
-
defs: current_defs.dup,
|
|
461
|
-
start_pos: section_start,
|
|
462
|
-
end_pos: body_nodes[idx - 1].source_range.end_pos
|
|
463
|
-
}
|
|
464
|
-
current_defs = []
|
|
465
|
-
section_start = nil
|
|
466
|
-
end
|
|
582
|
+
flush_visibility_section!(sections, current_visibility, current_defs, section_start, body_nodes, idx - 1)
|
|
583
|
+
current_defs = []
|
|
584
|
+
section_start = nil
|
|
467
585
|
current_visibility = nil
|
|
468
586
|
end
|
|
469
587
|
end
|
|
470
588
|
|
|
471
|
-
# trailing defs
|
|
472
589
|
unless current_defs.empty?
|
|
473
590
|
sections << {
|
|
474
591
|
visibility: current_visibility,
|
|
@@ -481,12 +598,66 @@ module RuboCop
|
|
|
481
598
|
sections
|
|
482
599
|
end
|
|
483
600
|
|
|
484
|
-
#
|
|
601
|
+
# Flush a currently-collected contiguous def/defs group into +sections+.
|
|
485
602
|
#
|
|
486
|
-
# @param
|
|
487
|
-
# @param
|
|
488
|
-
# @param
|
|
489
|
-
# @
|
|
603
|
+
# @param sections [Array<Hash>]
|
|
604
|
+
# @param current_visibility [RuboCop::AST::Node, nil]
|
|
605
|
+
# @param current_defs [Array<RuboCop::AST::Node>]
|
|
606
|
+
# @param section_start [Integer, nil]
|
|
607
|
+
# @param body_nodes [Array<RuboCop::AST::Node>]
|
|
608
|
+
# @param last_idx [Integer]
|
|
609
|
+
# @return [void]
|
|
610
|
+
# @api private
|
|
611
|
+
def flush_visibility_section!(sections, current_visibility, current_defs, section_start, body_nodes, last_idx)
|
|
612
|
+
return if current_defs.empty?
|
|
613
|
+
|
|
614
|
+
sections << {
|
|
615
|
+
visibility: current_visibility,
|
|
616
|
+
defs: current_defs.dup,
|
|
617
|
+
start_pos: section_start,
|
|
618
|
+
end_pos: body_nodes[last_idx].source_range.end_pos
|
|
619
|
+
}
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
# Check if +node+ is a bare visibility modifier send:
|
|
623
|
+
# `private`, `protected`, or `public` (with no args and no receiver).
|
|
624
|
+
#
|
|
625
|
+
# @param node [RuboCop::AST::Node]
|
|
626
|
+
# @return [Boolean]
|
|
627
|
+
# @api private
|
|
628
|
+
def bare_visibility_send?(node)
|
|
629
|
+
node.receiver.nil? &&
|
|
630
|
+
VISIBILITY_METHODS.include?(node.method_name) &&
|
|
631
|
+
node.arguments.empty?
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
# Find the visibility section containing a given method name.
|
|
635
|
+
#
|
|
636
|
+
# @param sections [Array<Hash>]
|
|
637
|
+
# @param method_name [Symbol]
|
|
638
|
+
# @return [Hash, nil]
|
|
639
|
+
# @api private
|
|
640
|
+
def section_for_method(sections, method_name)
|
|
641
|
+
sections.find { |s| s[:defs].any? { |d| d.method_name == method_name } }
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
# Normalize a section to a string visibility label ("public", "private", "protected").
|
|
645
|
+
#
|
|
646
|
+
# @param section [Hash, nil]
|
|
647
|
+
# @return [String]
|
|
648
|
+
# @api private
|
|
649
|
+
def visibility_label(section)
|
|
650
|
+
return 'public' unless section # default visibility
|
|
651
|
+
|
|
652
|
+
(section[:visibility]&.method_name || :public).to_s
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
# Stable topological sort using the current definition order as a tie-breaker.
|
|
656
|
+
#
|
|
657
|
+
# @param names [Array<Symbol>]
|
|
658
|
+
# @param edges [Array<Array(Symbol, Symbol)>]
|
|
659
|
+
# @param idx_of [Hash{Symbol=>Integer}]
|
|
660
|
+
# @return [Array<Symbol>, nil] sorted list, or nil if cycle prevents a full order
|
|
490
661
|
# @api private
|
|
491
662
|
def topo_sort(names, edges, idx_of)
|
|
492
663
|
indegree = Hash.new(0)
|
|
@@ -500,6 +671,7 @@ module RuboCop
|
|
|
500
671
|
indegree[callee] += 1
|
|
501
672
|
indegree[caller] ||= 0
|
|
502
673
|
end
|
|
674
|
+
|
|
503
675
|
names.each { |n| indegree[n] ||= 0 }
|
|
504
676
|
|
|
505
677
|
queue = names.select { |n| indegree[n].zero? }.sort_by { |n| idx_of[n] }
|
|
@@ -508,10 +680,12 @@ module RuboCop
|
|
|
508
680
|
until queue.empty?
|
|
509
681
|
n = queue.shift
|
|
510
682
|
result << n
|
|
683
|
+
|
|
511
684
|
adj[n].each do |m|
|
|
512
685
|
indegree[m] -= 1
|
|
513
686
|
queue << m if indegree[m].zero?
|
|
514
687
|
end
|
|
688
|
+
|
|
515
689
|
queue.sort_by! { |x| idx_of[x] }
|
|
516
690
|
end
|
|
517
691
|
|
|
@@ -520,11 +694,11 @@ module RuboCop
|
|
|
520
694
|
result
|
|
521
695
|
end
|
|
522
696
|
|
|
523
|
-
#
|
|
697
|
+
# Return a range that starts at the first contiguous comment line immediately
|
|
524
698
|
# above the def/defs node and ends at the end of the def. This preserves
|
|
525
|
-
#
|
|
699
|
+
# doc comments when methods are moved during autocorrect.
|
|
526
700
|
#
|
|
527
|
-
# @param node [RuboCop::AST::Node] :def or :defs
|
|
701
|
+
# @param node [RuboCop::AST::Node] :def or :defs
|
|
528
702
|
# @return [Parser::Source::Range]
|
|
529
703
|
# @api private
|
|
530
704
|
def range_with_leading_comments(node)
|
|
@@ -533,6 +707,7 @@ module RuboCop
|
|
|
533
707
|
|
|
534
708
|
start_line = expr.line
|
|
535
709
|
lineno = start_line - 1
|
|
710
|
+
|
|
536
711
|
while lineno >= 1
|
|
537
712
|
line = buffer.source_line(lineno)
|
|
538
713
|
break unless line =~ /\A\s*#/
|
|
@@ -542,7 +717,34 @@ module RuboCop
|
|
|
542
717
|
end
|
|
543
718
|
|
|
544
719
|
start_pos = buffer.line_range(start_line).begin_pos
|
|
545
|
-
|
|
720
|
+
range_between(start_pos, expr.end_pos)
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
# Recurse into nested scopes inside the current scope body.
|
|
724
|
+
#
|
|
725
|
+
# @param body_nodes [Array<RuboCop::AST::Node>]
|
|
726
|
+
# @return [void]
|
|
727
|
+
# @api private
|
|
728
|
+
def analyze_nested_scopes(body_nodes)
|
|
729
|
+
body_nodes.each do |n|
|
|
730
|
+
analyze_scope(n) if n.class_type? || n.module_type? || n.sclass_type?
|
|
731
|
+
end
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
# Read config: AllowedRecursion (default true).
|
|
735
|
+
#
|
|
736
|
+
# @return [Boolean]
|
|
737
|
+
# @api private
|
|
738
|
+
def allowed_recursion?
|
|
739
|
+
cop_config.fetch('AllowedRecursion') { true }
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
# Read config: SkipCyclicSiblingEdges (default false).
|
|
743
|
+
#
|
|
744
|
+
# @return [Boolean]
|
|
745
|
+
# @api private
|
|
746
|
+
def skip_cyclic_sibling_edges?
|
|
747
|
+
cop_config.fetch('SkipCyclicSiblingEdges') { false }
|
|
546
748
|
end
|
|
547
749
|
end
|
|
548
750
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module SortedMethodsByCall
|
|
5
5
|
# +RuboCop::SortedMethodsByCall::Compare+ provides helpers to compare
|
|
6
|
-
# definition orders and call orders using
|
|
6
|
+
# definition orders and call orders using "ordered subsequence" semantics.
|
|
7
7
|
# It’s used by the cop to check that called methods appear in the same
|
|
8
8
|
# relative order as they are defined (not necessarily contiguously).
|
|
9
9
|
module Compare
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-sorted_methods_by_call
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- unurgunite
|
|
@@ -186,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
186
186
|
- !ruby/object:Gem::Version
|
|
187
187
|
version: '0'
|
|
188
188
|
requirements: []
|
|
189
|
-
rubygems_version:
|
|
189
|
+
rubygems_version: 4.0.2
|
|
190
190
|
specification_version: 4
|
|
191
191
|
summary: RuboCop extension for method sorting in AST by stack trace.
|
|
192
192
|
test_files: []
|