rubocop-asjer 0.4.0 → 0.4.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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +7 -0
- data/lib/rubocop/asjer/version.rb +1 -1
- data/lib/rubocop/cop/asjer/rails_class_order.rb +71 -57
- 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: 6401e9b5d10c63a9c3ff7f3292aac36e8368a54fdd4df3062efda177ee331c11
|
|
4
|
+
data.tar.gz: a7e90b0a705e59b161b575fdedb087d82b54dd8af3cfec7509bc54553012c5f5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a0f223e4e32b83736f21750b9ebb06ab49eacd9e2ccb45d37f047b231d763cb4f4dd1effa7bb87e0376f04db89287b48a7727c544b84b96585b657a02d2fa092
|
|
7
|
+
data.tar.gz: '0810af3f8f3b56e7ae3b4ee4295805a9a01ee0cb34911da8d6197db60ff1d0f8ad3a37d596d8411ced98e2eae0fb13c60f904903bebea26b2302b52cdc66d3de'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.1](https://github.com/asjer/rubocop-asjer/compare/v0.4.0...v0.4.1) (2026-01-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* improve autocorrect functionality for `RailsClassOrder` cop and add Ruby 4.0 to ci ([#23](https://github.com/asjer/rubocop-asjer/issues/23)) ([16fc637](https://github.com/asjer/rubocop-asjer/commit/16fc63770f05f74e2bc005becd136af7d8ccb0d1))
|
|
9
|
+
|
|
3
10
|
## [0.4.0](https://github.com/asjer/rubocop-asjer/compare/v0.3.1...v0.4.0) (2026-01-23)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -56,34 +56,96 @@ module RuboCop
|
|
|
56
56
|
OTHERS = %w[attr_readonly serialize].freeze
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
# Autocorrect helpers for RailsClassOrder cop
|
|
60
|
+
module RailsClassOrderCorrector
|
|
61
|
+
def autocorrect(corrector, body, original, sorted)
|
|
62
|
+
first_target = original.min_by { |m| body.children.index(m) }
|
|
63
|
+
new_source = build_sorted_source(sorted, original)
|
|
64
|
+
corrector.replace(range_with_comments(first_target), new_source.rstrip)
|
|
65
|
+
|
|
66
|
+
(original - [first_target]).each do |method|
|
|
67
|
+
corrector.remove(full_method_range(method))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def range_with_comments(node)
|
|
72
|
+
comments = preceding_comments(node)
|
|
73
|
+
start_pos = comments.empty? ? node.loc.expression.begin_pos : comments.first.loc.expression.begin_pos
|
|
74
|
+
range_between(start_pos, node.loc.expression.end_pos)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def preceding_comments(node)
|
|
78
|
+
collect_adjacent_comments(node.loc.expression)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def collect_adjacent_comments(node_pos)
|
|
82
|
+
expected_line = node_pos.first_line - 1
|
|
83
|
+
comments_before_node(node_pos).take_while do |comment|
|
|
84
|
+
pos = comment.loc.expression
|
|
85
|
+
(pos.last_line == expected_line).tap { expected_line = pos.first_line - 1 }
|
|
86
|
+
end.reverse
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def comments_before_node(node_pos)
|
|
90
|
+
processed_source.comments.select { |c| c.loc.expression.end_pos < node_pos.begin_pos }.reverse
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def full_method_range(node)
|
|
94
|
+
range = range_with_comments(node)
|
|
95
|
+
source = processed_source.buffer.source
|
|
96
|
+
line_start = source.rindex("\n", range.begin_pos - 1)&.+(1) || 0
|
|
97
|
+
end_pos = source[range.end_pos] == "\n" ? range.end_pos + 1 : range.end_pos
|
|
98
|
+
range_between(line_start, end_pos)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def build_sorted_source(sorted, original)
|
|
102
|
+
indent = ' ' * original.first.loc.column
|
|
103
|
+
grouped = sorted.group_by { |m| method_type(m) }
|
|
104
|
+
|
|
105
|
+
%i[association callback other].filter_map do |type|
|
|
106
|
+
next unless grouped[type]&.any?
|
|
107
|
+
|
|
108
|
+
grouped[type].map { |m| source_with_comments(m) }.join("\n#{indent}")
|
|
109
|
+
end.join("\n\n#{indent}")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def source_with_comments(node)
|
|
113
|
+
range = range_with_comments(node)
|
|
114
|
+
processed_source.buffer.source[range.begin_pos...range.end_pos].lstrip
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
59
118
|
# Enforces consistent ordering of declarative methods in Rails models.
|
|
60
119
|
#
|
|
61
120
|
# @see RailsClassOrderDefaults for default method lists
|
|
62
121
|
class RailsClassOrder < Base
|
|
63
122
|
extend AutoCorrector
|
|
64
123
|
include RangeHelp
|
|
124
|
+
include RailsClassOrderCorrector
|
|
65
125
|
|
|
66
126
|
MSG = 'Declarative methods should be sorted by type: associations, callbacks, then others.'
|
|
67
|
-
|
|
68
127
|
TYPE_ORDER = { association: 0, callback: 1, other: 2 }.freeze
|
|
69
128
|
|
|
70
129
|
def on_class(node)
|
|
71
130
|
_name, _superclass, body = *node
|
|
72
131
|
return unless body&.begin_type?
|
|
73
132
|
|
|
133
|
+
check_order(body)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
def check_order(body)
|
|
74
139
|
targets = target_methods(body)
|
|
75
140
|
return if targets.empty?
|
|
76
141
|
|
|
77
142
|
sorted = sort_methods(targets)
|
|
78
143
|
return if targets == sorted
|
|
79
144
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
end
|
|
145
|
+
first_misplaced = targets.zip(sorted).find { |a, e| a != e }&.first
|
|
146
|
+
add_offense(first_misplaced) { |corrector| autocorrect(corrector, body, targets, sorted) }
|
|
83
147
|
end
|
|
84
148
|
|
|
85
|
-
private
|
|
86
|
-
|
|
87
149
|
def associations
|
|
88
150
|
@associations ||= cop_config.fetch('Associations', RailsClassOrderDefaults::ASSOCIATIONS).map(&:to_sym)
|
|
89
151
|
end
|
|
@@ -101,20 +163,11 @@ module RuboCop
|
|
|
101
163
|
end
|
|
102
164
|
|
|
103
165
|
def target_methods(body)
|
|
104
|
-
body.children.select
|
|
105
|
-
child.send_type? && all_target_methods.include?(child.method_name)
|
|
106
|
-
end
|
|
166
|
+
body.children.select { |child| child.send_type? && all_target_methods.include?(child.method_name) }
|
|
107
167
|
end
|
|
108
168
|
|
|
109
169
|
def sort_methods(methods)
|
|
110
|
-
|
|
111
|
-
methods.each_with_index.sort_by do |method, index|
|
|
112
|
-
[
|
|
113
|
-
method_type_order(method),
|
|
114
|
-
method_position_in_type(method),
|
|
115
|
-
index
|
|
116
|
-
]
|
|
117
|
-
end.map(&:first)
|
|
170
|
+
methods.each_with_index.sort_by { |m, i| [method_type_order(m), method_position_in_type(m), i] }.map(&:first)
|
|
118
171
|
end
|
|
119
172
|
|
|
120
173
|
def method_type(method)
|
|
@@ -130,51 +183,12 @@ module RuboCop
|
|
|
130
183
|
end
|
|
131
184
|
|
|
132
185
|
def method_position_in_type(method)
|
|
133
|
-
|
|
134
|
-
list = method_list_for_type(method_type(method))
|
|
135
|
-
list.index(name) || list.size
|
|
186
|
+
method_list_for_type(method_type(method)).index(method.method_name) || 999
|
|
136
187
|
end
|
|
137
188
|
|
|
138
189
|
def method_list_for_type(type)
|
|
139
190
|
{ association: associations, callback: callbacks, other: others }[type]
|
|
140
191
|
end
|
|
141
|
-
|
|
142
|
-
def contiguous?(body, targets)
|
|
143
|
-
indices = targets.map { |t| body.children.index(t) }
|
|
144
|
-
indices.max - indices.min + 1 == indices.size
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def autocorrect(corrector, _body, original, sorted)
|
|
148
|
-
grouped = group_by_type(sorted)
|
|
149
|
-
new_source = build_grouped_source(grouped, original)
|
|
150
|
-
range = methods_range(original)
|
|
151
|
-
corrector.replace(range, new_source)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def group_by_type(methods)
|
|
155
|
-
methods.group_by { |m| method_type(m) }
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def build_grouped_source(grouped, original)
|
|
159
|
-
indent = ' ' * original.first.loc.column
|
|
160
|
-
|
|
161
|
-
groups = []
|
|
162
|
-
%i[association callback other].each do |type|
|
|
163
|
-
next unless grouped[type]&.any?
|
|
164
|
-
|
|
165
|
-
group_lines = grouped[type].map(&:source)
|
|
166
|
-
groups << group_lines.join("\n#{indent}")
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
groups.join("\n\n#{indent}")
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def methods_range(methods)
|
|
173
|
-
first = methods.min_by { |m| m.loc.expression.begin_pos }
|
|
174
|
-
last = methods.max_by { |m| m.loc.expression.end_pos }
|
|
175
|
-
|
|
176
|
-
range_between(first.loc.expression.begin_pos, last.loc.expression.end_pos)
|
|
177
|
-
end
|
|
178
192
|
end
|
|
179
193
|
end
|
|
180
194
|
end
|