rubocop-sorted_methods_by_call 1.0.1 → 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 +7 -7
- data/Gemfile.lock +1 -1
- data/LICENSE.txt +17 -7
- data/README.md +37 -40
- data/config/default.yml +2 -3
- data/lib/rubocop/cop/sorted_methods_by_call/waterfall.rb +122 -85
- 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-
|
|
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
|
|
@@ -24,7 +24,7 @@ Gemspec/RequiredRubyVersion:
|
|
|
24
24
|
Metrics/AbcSize:
|
|
25
25
|
Max: 61
|
|
26
26
|
|
|
27
|
-
# Offense count:
|
|
27
|
+
# Offense count: 2
|
|
28
28
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
|
|
29
29
|
# AllowedMethods: refine
|
|
30
30
|
Metrics/BlockLength:
|
|
@@ -33,22 +33,22 @@ 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
|
-
Max:
|
|
51
|
+
Max: 37
|
|
52
52
|
|
|
53
53
|
# Offense count: 2
|
|
54
54
|
# This cop supports safe autocorrection (--autocorrect).
|
data/Gemfile.lock
CHANGED
data/LICENSE.txt
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Copyright (c) 2025 unurgunite
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
[](https://github.com/unurgunite/rubocop-sorted_methods_by_call/actions)
|
|
5
5
|
[](https://rubygems.org/gems/rubocop-sorted_methods_by_call)
|
|
6
6
|
|
|
7
|
+
**Enforces "waterfall" method ordering**: define methods *after* any method that calls them within the same scope.
|
|
8
|
+
|
|
7
9
|
* [RuboCop::SortedMethodsByCall](#rubocopsortedmethodsbycall)
|
|
8
10
|
* [Features](#features)
|
|
9
11
|
* [Installation](#installation)
|
|
@@ -24,16 +26,14 @@
|
|
|
24
26
|
* [License](#license)
|
|
25
27
|
* [Code of Conduct](#code-of-conduct)
|
|
26
28
|
|
|
27
|
-
**Enforces "waterfall" method ordering**: define methods *after* any method that calls them within the same scope.
|
|
28
|
-
|
|
29
29
|
## Features
|
|
30
30
|
|
|
31
|
-
- **Waterfall ordering enforcement**: Caller methods must be defined before their callees
|
|
32
|
-
- **Smart visibility handling**: Respects `private`/`protected`/`public` sections
|
|
33
|
-
- **Safe mutual recursion**: Handles recursive method calls gracefully
|
|
34
|
-
- **Autocorrection support**: Automatically reorders methods (opt-in with `-A`)
|
|
35
|
-
- **Full RuboCop integration**: Works seamlessly with modern RuboCop plugin system
|
|
36
|
-
- **Comprehensive scope support**: Classes, modules, singleton classes, and top-level
|
|
31
|
+
- **Waterfall ordering enforcement**: Caller methods must be defined before their callees;
|
|
32
|
+
- **Smart visibility handling**: Respects `private`/`protected`/`public` sections;
|
|
33
|
+
- **Safe mutual recursion**: Handles recursive method calls gracefully;
|
|
34
|
+
- **Autocorrection support**: Automatically reorders methods (opt-in with `-A`);
|
|
35
|
+
- **Full RuboCop integration**: Works seamlessly with modern RuboCop plugin system;
|
|
36
|
+
- **Comprehensive scope support**: Classes, modules, singleton classes, and top-level;
|
|
37
37
|
|
|
38
38
|
## Installation
|
|
39
39
|
|
|
@@ -82,19 +82,28 @@ SortedMethodsByCall/Waterfall:
|
|
|
82
82
|
|
|
83
83
|
### Good Code (waterfall order)
|
|
84
84
|
|
|
85
|
+
In waterfall ordering, **callers come before callees**. This creates a top-down reading flow where main logic appears
|
|
86
|
+
before implementation details.
|
|
87
|
+
|
|
85
88
|
```ruby
|
|
89
|
+
|
|
86
90
|
class Service
|
|
87
91
|
def call
|
|
88
|
-
|
|
92
|
+
foo
|
|
93
|
+
bar
|
|
89
94
|
end
|
|
90
95
|
|
|
91
96
|
private
|
|
92
97
|
|
|
93
|
-
def
|
|
94
|
-
|
|
98
|
+
def bar
|
|
99
|
+
method123
|
|
95
100
|
end
|
|
96
101
|
|
|
97
|
-
def
|
|
102
|
+
def method123
|
|
103
|
+
foo
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def foo
|
|
98
107
|
123
|
|
99
108
|
end
|
|
100
109
|
end
|
|
@@ -103,19 +112,25 @@ end
|
|
|
103
112
|
### Bad Code (violates waterfall order)
|
|
104
113
|
|
|
105
114
|
```ruby
|
|
115
|
+
|
|
106
116
|
class Service
|
|
107
117
|
def call
|
|
108
|
-
|
|
118
|
+
foo
|
|
119
|
+
bar
|
|
109
120
|
end
|
|
110
121
|
|
|
111
122
|
private
|
|
112
123
|
|
|
113
|
-
def
|
|
124
|
+
def foo # ❌ Offense: Define #foo after its caller #method123
|
|
114
125
|
123
|
|
115
126
|
end
|
|
116
127
|
|
|
117
|
-
def
|
|
118
|
-
|
|
128
|
+
def bar
|
|
129
|
+
method123
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def method123
|
|
133
|
+
foo
|
|
119
134
|
end
|
|
120
135
|
end
|
|
121
136
|
```
|
|
@@ -128,25 +143,7 @@ Run with unsafe autocorrection to automatically fix violations:
|
|
|
128
143
|
bundle exec rubocop -A
|
|
129
144
|
```
|
|
130
145
|
|
|
131
|
-
This will reorder the methods while preserving comments and visibility modifiers
|
|
132
|
-
|
|
133
|
-
```ruby
|
|
134
|
-
class Service
|
|
135
|
-
def call
|
|
136
|
-
do_smth
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
private
|
|
140
|
-
|
|
141
|
-
def do_smth
|
|
142
|
-
well
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def well
|
|
146
|
-
123
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
```
|
|
146
|
+
This will reorder the methods while preserving comments and visibility modifiers.
|
|
150
147
|
|
|
151
148
|
## Testing
|
|
152
149
|
|
|
@@ -160,7 +157,7 @@ Run RuboCop on the gem itself:
|
|
|
160
157
|
|
|
161
158
|
```bash
|
|
162
159
|
bundle exec rubocop
|
|
163
|
-
bundle exec rubocop --config
|
|
160
|
+
bundle exec rubocop --config test_project/.rubocop.test.yml lib/ -A
|
|
164
161
|
```
|
|
165
162
|
|
|
166
163
|
## Development
|
|
@@ -212,8 +209,7 @@ at https://unurgunite.github.io/rubocop-sorted_methods_by_call_docs/
|
|
|
212
209
|
|
|
213
210
|
## License
|
|
214
211
|
|
|
215
|
-
The gem is available as open source under the terms of
|
|
216
|
-
the [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause).
|
|
212
|
+
The gem is available as open source under the terms of MIT License.
|
|
217
213
|
|
|
218
214
|
## Code of Conduct
|
|
219
215
|
|
|
@@ -221,5 +217,6 @@ Everyone interacting with this project is expected to follow the [Code of Conduc
|
|
|
221
217
|
|
|
222
218
|
---
|
|
223
219
|
|
|
224
|
-
> **Note**: This gem
|
|
225
|
-
>
|
|
220
|
+
> **Note**: This gem implements **true waterfall ordering** that considers the complete call graph across all methods in
|
|
221
|
+
> a scope. Methods are ordered so that every callee appears after all of its callers, creating a natural top-down
|
|
222
|
+
> reading flow.
|
data/config/default.yml
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
# Default configuration for rubocop-sorted_methods_by_call
|
|
2
1
|
SortedMethodsByCall/Waterfall:
|
|
3
|
-
Description: "Enforces
|
|
2
|
+
Description: "Enforces method ordering based on call relationships."
|
|
4
3
|
Enabled: false
|
|
5
|
-
VersionAdded: "1.
|
|
4
|
+
VersionAdded: "1.1.1"
|
|
6
5
|
SafeAutoCorrect: false
|
|
7
6
|
AllowedRecursion: true
|
|
@@ -11,21 +11,32 @@ module RuboCop
|
|
|
11
11
|
# - Autocorrect: UNSAFE; reorders methods within a contiguous visibility section
|
|
12
12
|
#
|
|
13
13
|
# Example (good):
|
|
14
|
-
# def
|
|
14
|
+
# def call
|
|
15
|
+
# foo
|
|
15
16
|
# bar
|
|
16
17
|
# end
|
|
17
18
|
#
|
|
19
|
+
# private
|
|
20
|
+
#
|
|
18
21
|
# def bar
|
|
22
|
+
# method123
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# def method123
|
|
26
|
+
# foo
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# def foo
|
|
19
30
|
# 123
|
|
20
31
|
# end
|
|
21
32
|
#
|
|
22
33
|
# Example (bad):
|
|
23
|
-
# def
|
|
34
|
+
# def foo
|
|
24
35
|
# 123
|
|
25
36
|
# end
|
|
26
37
|
#
|
|
27
|
-
# def
|
|
28
|
-
#
|
|
38
|
+
# def call
|
|
39
|
+
# foo
|
|
29
40
|
# end
|
|
30
41
|
#
|
|
31
42
|
# Autocorrect (unsafe, opt-in via SafeAutoCorrect: false): topologically sorts the contiguous
|
|
@@ -98,35 +109,74 @@ module RuboCop
|
|
|
98
109
|
def_nodes = body_nodes.select { |n| %i[def defs].include?(n.type) }
|
|
99
110
|
return if def_nodes.size <= 1
|
|
100
111
|
|
|
101
|
-
names
|
|
112
|
+
names = def_nodes.map(&:method_name)
|
|
102
113
|
names_set = names.to_set
|
|
103
|
-
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)
|
|
104
126
|
|
|
105
|
-
|
|
127
|
+
# -----------------------------------------------------------
|
|
128
|
+
# Phase 2 Add sibling‑order edges for orchestration methods
|
|
129
|
+
# -----------------------------------------------------------
|
|
130
|
+
sibling_edges = []
|
|
106
131
|
def_nodes.each do |def_node|
|
|
107
|
-
|
|
108
|
-
|
|
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) }
|
|
109
137
|
|
|
110
|
-
|
|
138
|
+
sibling_edges << [a, b]
|
|
111
139
|
end
|
|
112
140
|
end
|
|
113
141
|
|
|
142
|
+
# -----------------------------------------------------------
|
|
143
|
+
# Phase 3 Combine for sorting, but keep direct set for recursion
|
|
144
|
+
# -----------------------------------------------------------
|
|
145
|
+
edges_for_sort = direct_edges + sibling_edges
|
|
114
146
|
allow_recursion = cop_config.fetch('AllowedRecursion') { true }
|
|
115
|
-
adj = build_adj(names, edges)
|
|
116
147
|
|
|
117
|
-
|
|
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
|
+
|
|
118
160
|
return unless violation
|
|
119
161
|
|
|
120
162
|
caller_name, callee_name = violation
|
|
121
163
|
callee_node = def_nodes[index_of[callee_name]]
|
|
122
164
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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)
|
|
126
174
|
end
|
|
127
175
|
|
|
128
176
|
# Recurse into nested scopes
|
|
129
|
-
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
|
|
130
180
|
end
|
|
131
181
|
|
|
132
182
|
# +RuboCop::Cop::SortedMethodsByCall::Waterfall#scope_body_nodes+ -> Array<RuboCop::AST::Node>
|
|
@@ -172,20 +222,6 @@ module RuboCop
|
|
|
172
222
|
res.uniq
|
|
173
223
|
end
|
|
174
224
|
|
|
175
|
-
# +RuboCop::Cop::SortedMethodsByCall::Waterfall#find_violation+ -> [Symbol, Symbol], nil
|
|
176
|
-
#
|
|
177
|
-
# Finds the first backward edge (caller->callee where callee is defined above caller)
|
|
178
|
-
# using the provided index map.
|
|
179
|
-
#
|
|
180
|
-
# @param [Array<Array(Symbol, Symbol)>] edges
|
|
181
|
-
# @param [Hash{Symbol=>Integer}] index_of
|
|
182
|
-
# @return [[Symbol, Symbol], nil] tuple [caller, callee] or nil if none found
|
|
183
|
-
def find_violation(edges, index_of)
|
|
184
|
-
edges.find do |caller, callee|
|
|
185
|
-
index_of.key?(caller) && index_of.key?(callee) && index_of[callee] < index_of[caller]
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
|
|
189
225
|
# +RuboCop::Cop::SortedMethodsByCall::Waterfall#try_autocorrect+ -> void
|
|
190
226
|
#
|
|
191
227
|
# UNSAFE: Reorders method definitions inside the target visibility section only
|
|
@@ -196,75 +232,64 @@ module RuboCop
|
|
|
196
232
|
# @param [Array<RuboCop::AST::Node>] body_nodes
|
|
197
233
|
# @param [Array<RuboCop::AST::Node>] def_nodes
|
|
198
234
|
# @param [Array<Array(Symbol, Symbol)>] edges
|
|
235
|
+
# @param [Array(Symbol, Symbol)] violation The found violation (caller, callee)
|
|
199
236
|
# @return [void]
|
|
200
237
|
#
|
|
201
238
|
# @note Applied only when user asked for autocorrections; with SafeAutoCorrect: false, this runs under -A.
|
|
202
239
|
# @note Also preserves contiguous leading doc comments above each method.
|
|
203
|
-
def try_autocorrect(corrector, body_nodes,
|
|
204
|
-
# Group method definitions into visibility sections
|
|
240
|
+
def try_autocorrect(corrector, body_nodes, _def_nodes, edges, violation)
|
|
205
241
|
sections = extract_visibility_sections(body_nodes)
|
|
206
242
|
|
|
207
|
-
|
|
208
|
-
caller_name, callee_name = first_backward_edge(
|
|
209
|
-
edges,
|
|
210
|
-
def_nodes.map(&:method_name).each_with_index.to_h,
|
|
211
|
-
build_adj(def_nodes.map(&:method_name), edges),
|
|
212
|
-
cop_config.fetch('AllowedRecursion') { true }
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
# No violation -> nothing to do
|
|
216
|
-
return unless caller_name && callee_name
|
|
243
|
+
caller_name, callee_name = violation
|
|
217
244
|
|
|
218
|
-
#
|
|
245
|
+
# find target section containing both defs
|
|
219
246
|
target_section = sections.find do |section|
|
|
220
|
-
|
|
221
|
-
|
|
247
|
+
section_names = section[:defs].map(&:method_name)
|
|
248
|
+
section_names.include?(caller_name) && section_names.include?(callee_name)
|
|
222
249
|
end
|
|
223
|
-
|
|
224
|
-
# If violation spans multiple sections, skip autocorrect
|
|
225
250
|
return unless target_section
|
|
226
251
|
|
|
227
252
|
defs = target_section[:defs]
|
|
228
|
-
return
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
242
270
|
|
|
243
|
-
#
|
|
244
|
-
ranges_by_name = defs.to_h
|
|
245
|
-
|
|
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 }
|
|
246
276
|
|
|
247
|
-
# Reconstruct the section: keep the visibility modifier (if any) above the first def
|
|
248
277
|
visibility_node = target_section[:visibility]
|
|
249
278
|
visibility_source = visibility_node&.source.to_s
|
|
250
279
|
|
|
251
|
-
new_content =
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
256
286
|
|
|
257
|
-
# Expand the replaced region:
|
|
258
|
-
# - if a visibility node exists, start from its begin_pos (so we replace it)
|
|
259
|
-
# - otherwise, start from the earliest leading doc-comment of the defs
|
|
260
287
|
section_begin =
|
|
261
288
|
if visibility_node
|
|
262
289
|
visibility_node.source_range.begin_pos
|
|
263
290
|
else
|
|
264
291
|
defs.map { |d| range_with_leading_comments(d).begin_pos }.min
|
|
265
292
|
end
|
|
266
|
-
|
|
267
|
-
# Always end at the end of the last def
|
|
268
293
|
section_end = defs.last.source_range.end_pos
|
|
269
294
|
|
|
270
295
|
region = Parser::Source::Range.new(processed_source.buffer, section_begin, section_end)
|
|
@@ -306,6 +331,7 @@ module RuboCop
|
|
|
306
331
|
# If mutual recursion allowed and there is a path callee -> caller, skip
|
|
307
332
|
next if allow_recursion && path_exists?(callee, caller, adj)
|
|
308
333
|
|
|
334
|
+
# Violation: callee is defined BEFORE caller (waterfall order)
|
|
309
335
|
index_of[callee] < index_of[caller]
|
|
310
336
|
end
|
|
311
337
|
end
|
|
@@ -363,9 +389,10 @@ module RuboCop
|
|
|
363
389
|
current_defs << node
|
|
364
390
|
section_start ||= node.source_range.begin_pos
|
|
365
391
|
when :send
|
|
366
|
-
#
|
|
367
|
-
if node.receiver.nil? &&
|
|
368
|
-
|
|
392
|
+
# visibility modifier?
|
|
393
|
+
if node.receiver.nil? &&
|
|
394
|
+
%i[private protected public].include?(node.method_name) &&
|
|
395
|
+
node.arguments.empty?
|
|
369
396
|
unless current_defs.empty?
|
|
370
397
|
sections << {
|
|
371
398
|
visibility: current_visibility,
|
|
@@ -378,7 +405,7 @@ module RuboCop
|
|
|
378
405
|
end
|
|
379
406
|
current_visibility = node
|
|
380
407
|
else
|
|
381
|
-
#
|
|
408
|
+
# anything else breaks contiguous run
|
|
382
409
|
unless current_defs.empty?
|
|
383
410
|
sections << {
|
|
384
411
|
visibility: current_visibility,
|
|
@@ -388,11 +415,9 @@ module RuboCop
|
|
|
388
415
|
}
|
|
389
416
|
current_defs = []
|
|
390
417
|
section_start = nil
|
|
391
|
-
current_visibility = nil
|
|
392
418
|
end
|
|
393
419
|
end
|
|
394
420
|
else
|
|
395
|
-
# Any other node type breaks contiguity
|
|
396
421
|
unless current_defs.empty?
|
|
397
422
|
sections << {
|
|
398
423
|
visibility: current_visibility,
|
|
@@ -402,12 +427,11 @@ module RuboCop
|
|
|
402
427
|
}
|
|
403
428
|
current_defs = []
|
|
404
429
|
section_start = nil
|
|
405
|
-
current_visibility = nil
|
|
406
430
|
end
|
|
407
431
|
end
|
|
408
432
|
end
|
|
409
433
|
|
|
410
|
-
#
|
|
434
|
+
# trailing defs
|
|
411
435
|
unless current_defs.empty?
|
|
412
436
|
sections << {
|
|
413
437
|
visibility: current_visibility,
|
|
@@ -417,7 +441,20 @@ module RuboCop
|
|
|
417
441
|
}
|
|
418
442
|
end
|
|
419
443
|
|
|
420
|
-
|
|
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
|
|
421
458
|
end
|
|
422
459
|
|
|
423
460
|
# +RuboCop::Cop::SortedMethodsByCall::Waterfall#topo_sort+ -> Array<Symbol>, nil
|
|
@@ -470,7 +507,7 @@ module RuboCop
|
|
|
470
507
|
# @return [Parser::Source::Range] Range covering leading comments + method body.
|
|
471
508
|
def range_with_leading_comments(node)
|
|
472
509
|
buffer = processed_source.buffer
|
|
473
|
-
expr
|
|
510
|
+
expr = node.source_range
|
|
474
511
|
|
|
475
512
|
start_line = expr.line
|
|
476
513
|
lineno = start_line - 1
|