rubocop-vibe 0.6.0 → 0.7.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/config/default.yml +13 -0
- data/lib/rubocop/cop/vibe/blank_line_after_assignment.rb +2 -2
- data/lib/rubocop/cop/vibe/class_organization.rb +40 -322
- data/lib/rubocop/cop/vibe/consecutive_assignment_alignment.rb +3 -1
- data/lib/rubocop/cop/vibe/consecutive_constant_alignment.rb +3 -1
- data/lib/rubocop/cop/vibe/consecutive_indexed_assignment_alignment.rb +1 -1
- data/lib/rubocop/cop/vibe/consecutive_let_alignment.rb +1 -1
- data/lib/rubocop/cop/vibe/consecutive_scope_alignment.rb +1 -1
- data/lib/rubocop/cop/vibe/constant_alpha_order.rb +2 -2
- data/lib/rubocop/cop/vibe/describe_block_order.rb +1 -1
- data/lib/rubocop/cop/vibe/let_order.rb +1 -1
- data/lib/rubocop/cop/vibe/mixin/alignment_helpers.rb +24 -24
- data/lib/rubocop/cop/vibe/mixin/body_organization.rb +130 -0
- data/lib/rubocop/cop/vibe/mixin/organization_helpers.rb +84 -0
- data/lib/rubocop/cop/vibe/module_organization.rb +109 -0
- data/lib/rubocop/cop/vibe/no_compound_conditions.rb +1 -1
- data/lib/rubocop/cop/vibe/no_single_character_variable_names.rb +91 -0
- data/lib/rubocop/cop/vibe/rspec_stub_chain_style.rb +2 -2
- data/lib/rubocop/cop/vibe/validate_after_validates.rb +4 -4
- data/lib/rubocop/cop/vibe/validates_alpha_order.rb +3 -3
- data/lib/rubocop/cop/vibe_cops.rb +4 -0
- data/lib/rubocop/vibe/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 51eb5df4d2c3cea10d5c91ffdda0196ffd78d4863f677209daf4485fb2982386
|
|
4
|
+
data.tar.gz: 90ead0406cb08cf50c7b894b3f7031480cf04736cf9ae790496b55d175489deb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a51750ef0ed7e271fcc054e4492506e9296fad7cdd605543e9e7e1baa20f7a7be4eb7fcf5429551417cd21422326abb74735603474579c37f767478ecb4e47d
|
|
7
|
+
data.tar.gz: 900cd97045e70e6fbedb103245d20b5453f85a59b6eade6c1df1efd783407024289f7f611e78f1ee10739bb322a3a8fe3e796d990930bf9dc0b96a27f84c7830
|
data/config/default.yml
CHANGED
|
@@ -160,6 +160,12 @@ Vibe/LetOrder:
|
|
|
160
160
|
SafeAutoCorrect: true
|
|
161
161
|
VersionAdded: "0.4.0"
|
|
162
162
|
|
|
163
|
+
Vibe/ModuleOrganization:
|
|
164
|
+
Description: "Enforces consistent organization of module definitions."
|
|
165
|
+
Enabled: true
|
|
166
|
+
SafeAutoCorrect: false
|
|
167
|
+
VersionAdded: "0.7.0"
|
|
168
|
+
|
|
163
169
|
Vibe/MultilineHashArgumentStyle:
|
|
164
170
|
Description: "Enforces one-per-line and alphabetical ordering for hash arguments in multiline method calls."
|
|
165
171
|
Enabled: true
|
|
@@ -183,6 +189,13 @@ Vibe/NoRubocopDisable:
|
|
|
183
189
|
AllowedCops: []
|
|
184
190
|
VersionAdded: "0.1.0"
|
|
185
191
|
|
|
192
|
+
Vibe/NoSingleCharacterVariableNames:
|
|
193
|
+
Description: "Disallows single character variable names."
|
|
194
|
+
Enabled: true
|
|
195
|
+
AllowedNames:
|
|
196
|
+
- "_"
|
|
197
|
+
VersionAdded: "0.7.0"
|
|
198
|
+
|
|
186
199
|
Vibe/NoSkippedTests:
|
|
187
200
|
Description: "Enforces that tests are not skipped or marked as pending."
|
|
188
201
|
Enabled: true
|
|
@@ -204,11 +204,11 @@ module RuboCop
|
|
|
204
204
|
# @return [RuboCop::AST::Node, nil]
|
|
205
205
|
def leftmost_receiver(node)
|
|
206
206
|
return node if node.lvar_type?
|
|
207
|
-
return unless node.
|
|
207
|
+
return unless node.call_type?
|
|
208
208
|
|
|
209
209
|
current = node
|
|
210
210
|
|
|
211
|
-
current = current.receiver while current.
|
|
211
|
+
current = current.receiver while current.call_type? && current.receiver
|
|
212
212
|
|
|
213
213
|
current
|
|
214
214
|
end
|
|
@@ -39,9 +39,9 @@ module RuboCop
|
|
|
39
39
|
# end
|
|
40
40
|
class ClassOrganization < Base
|
|
41
41
|
extend AutoCorrector
|
|
42
|
+
include BodyOrganization
|
|
42
43
|
|
|
43
|
-
ASSOCIATIONS
|
|
44
|
-
ATTR_SORT_ORDER = { attr_accessor: "!0", attr_reader: "!1", attr_writer: "!2" }.freeze
|
|
44
|
+
ASSOCIATIONS = %i(belongs_to has_one has_many has_and_belongs_to_many).freeze
|
|
45
45
|
CALLBACKS = %i(
|
|
46
46
|
before_validation after_validation
|
|
47
47
|
before_save after_save around_save
|
|
@@ -86,288 +86,78 @@ module RuboCop
|
|
|
86
86
|
validates_length_of validates_numericality_of validates_presence_of
|
|
87
87
|
validates_size_of validates_uniqueness_of validates_associated
|
|
88
88
|
).freeze
|
|
89
|
-
VISIBILITY_CATEGORIES = {
|
|
90
|
-
protected: :protected_methods,
|
|
91
|
-
private: :private_methods,
|
|
92
|
-
public: :instance_methods
|
|
93
|
-
}.freeze
|
|
94
|
-
|
|
95
|
-
# Check and register violations.
|
|
96
|
-
#
|
|
97
|
-
# @param [RuboCop::AST::Node] class_node The class node.
|
|
98
|
-
# @param [Array<Hash>] elements The elements.
|
|
99
|
-
# @param [Boolean] is_model Whether this is a Rails model.
|
|
100
|
-
# @return [void]
|
|
101
|
-
def check_violations(class_node, elements, is_model)
|
|
102
|
-
violations = find_violations(elements)
|
|
103
|
-
|
|
104
|
-
return if violations.empty?
|
|
105
|
-
|
|
106
|
-
message = is_model ? MODEL_MSG : CLASS_MSG
|
|
107
|
-
|
|
108
|
-
add_offense(violations.first[:node], message: message) do |corrector|
|
|
109
|
-
autocorrect(corrector, class_node, elements)
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
89
|
|
|
113
90
|
# Check class nodes for organization.
|
|
114
91
|
#
|
|
115
92
|
# @param [RuboCop::AST::Node] node The class node.
|
|
116
93
|
# @return [void]
|
|
117
94
|
def on_class(node)
|
|
118
|
-
is_model = rails_model?(node)
|
|
119
|
-
is_controller = rails_controller?(node)
|
|
95
|
+
@is_model = rails_model?(node)
|
|
96
|
+
@is_controller = rails_controller?(node)
|
|
120
97
|
|
|
121
|
-
return if
|
|
98
|
+
return if !@is_model && !@is_controller && !node.body
|
|
99
|
+
return unless node.body
|
|
122
100
|
|
|
123
|
-
elements =
|
|
101
|
+
elements = collect_class_elements(node.body)
|
|
124
102
|
|
|
125
103
|
return if elements.size < 2
|
|
126
104
|
|
|
127
|
-
|
|
105
|
+
check_body_violations(node, elements)
|
|
128
106
|
end
|
|
129
107
|
|
|
130
108
|
private
|
|
131
109
|
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
# @param [Symbol] visibility The current visibility.
|
|
135
|
-
# @return [Symbol]
|
|
136
|
-
def attr_category(visibility)
|
|
137
|
-
if visibility == :public
|
|
138
|
-
:attr_methods
|
|
139
|
-
else
|
|
140
|
-
visibility_method_category(visibility)
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# Check if node is an attr method.
|
|
145
|
-
#
|
|
146
|
-
# @param [RuboCop::AST::Node] node The node to check.
|
|
147
|
-
# @return [Boolean]
|
|
148
|
-
def attr_method?(node)
|
|
149
|
-
node.send_type? && ATTR_SORT_ORDER.key?(node.method_name)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Auto-correct by sorting elements within each visibility section.
|
|
153
|
-
#
|
|
154
|
-
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
155
|
-
# @param [RuboCop::AST::Node] _class_node The class node.
|
|
156
|
-
# @param [Array<Hash>] elements The list of elements.
|
|
157
|
-
# @return [void]
|
|
158
|
-
def autocorrect(corrector, _class_node, elements)
|
|
159
|
-
elements.group_by { |e| e[:visibility] }.each_value do |section|
|
|
160
|
-
sorted = sort_elements(section)
|
|
161
|
-
|
|
162
|
-
next if sorted == section
|
|
163
|
-
|
|
164
|
-
autocorrect_group(corrector, section, sorted)
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# Auto-correct a single consecutive group.
|
|
169
|
-
#
|
|
170
|
-
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
171
|
-
# @param [Array<Hash>] group The original elements.
|
|
172
|
-
# @param [Array<Hash>] sorted The sorted elements.
|
|
173
|
-
# @return [void]
|
|
174
|
-
def autocorrect_group(corrector, group, sorted)
|
|
175
|
-
sorted.each_with_index do |element, idx|
|
|
176
|
-
original = group[idx]
|
|
177
|
-
|
|
178
|
-
next if element[:node] == original[:node]
|
|
179
|
-
|
|
180
|
-
corrector.replace(
|
|
181
|
-
node_range_with_comments(original[:node]),
|
|
182
|
-
source_with_comments(element[:node])
|
|
183
|
-
)
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# Build element hash for a node.
|
|
188
|
-
#
|
|
189
|
-
# @param [RuboCop::AST::Node] child The node.
|
|
190
|
-
# @param [Symbol] visibility The current visibility.
|
|
191
|
-
# @param [Integer] index The original index.
|
|
192
|
-
# @param [Boolean] is_model Whether this is a Rails model.
|
|
193
|
-
# @param [Boolean] is_controller Whether this is a Rails controller.
|
|
194
|
-
# @return [Hash]
|
|
195
|
-
# @return [nil]
|
|
196
|
-
def build_element(child, visibility, index, is_model, is_controller)
|
|
197
|
-
return unless categorizable?(child)
|
|
198
|
-
|
|
199
|
-
category = categorize_node(child, visibility, is_model)
|
|
200
|
-
|
|
201
|
-
return unless category
|
|
202
|
-
|
|
203
|
-
# Skip public instance methods in controllers (Rails/ActionOrder handles them).
|
|
204
|
-
return if is_controller && category == :instance_methods && visibility == :public
|
|
205
|
-
|
|
206
|
-
element_hash(child, category, visibility, index, is_model)
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
# Check if node should be categorized.
|
|
210
|
-
#
|
|
211
|
-
# @param [RuboCop::AST::Node] node The node to check.
|
|
212
|
-
# @return [Boolean]
|
|
213
|
-
def categorizable?(node)
|
|
214
|
-
node.type?(:send, :any_def, :casgn)
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
# Categorize a node.
|
|
110
|
+
# Categorize send nodes for classes.
|
|
218
111
|
#
|
|
219
112
|
# @param [RuboCop::AST::Node] node The node to categorize.
|
|
220
|
-
# @param [Symbol]
|
|
221
|
-
# @param [Boolean] is_model Whether this is a Rails model.
|
|
113
|
+
# @param [Symbol] _visibility The current visibility.
|
|
222
114
|
# @return [Symbol]
|
|
223
|
-
# @return [nil]
|
|
224
|
-
def
|
|
225
|
-
return
|
|
226
|
-
return
|
|
115
|
+
# @return [nil]
|
|
116
|
+
def categorize_send_node(node, _visibility)
|
|
117
|
+
return :concerns if node.method?(:include)
|
|
118
|
+
return nil unless @is_model
|
|
227
119
|
|
|
228
|
-
return
|
|
229
|
-
return
|
|
120
|
+
return :associations if ASSOCIATIONS.include?(node.method_name)
|
|
121
|
+
return :validations if validation_method?(node)
|
|
122
|
+
return :callbacks if CALLBACKS.include?(node.method_name)
|
|
123
|
+
return :scopes if node.method?(:scope)
|
|
230
124
|
|
|
231
|
-
|
|
125
|
+
nil
|
|
232
126
|
end
|
|
233
127
|
|
|
234
|
-
# Collect elements from body
|
|
128
|
+
# Collect elements from class body with controller filtering.
|
|
235
129
|
#
|
|
236
130
|
# @param [RuboCop::AST::Node] body The body node.
|
|
237
|
-
# @param [Boolean] is_model Whether this is a Rails model.
|
|
238
|
-
# @param [Boolean] is_controller Whether this is a Rails controller.
|
|
239
131
|
# @return [Array<Hash>] Array of element hashes.
|
|
240
|
-
def
|
|
132
|
+
def collect_class_elements(body)
|
|
241
133
|
visibility = :public
|
|
242
134
|
elements = []
|
|
243
|
-
index = 0
|
|
244
135
|
|
|
245
136
|
process_body_nodes(body).each do |child|
|
|
246
137
|
visibility = child.method_name if visibility_modifier?(child)
|
|
138
|
+
element = build_body_element(child, visibility, elements.size)
|
|
247
139
|
|
|
248
|
-
element
|
|
140
|
+
next if element.nil? || controller_public_instance_method?(element, visibility)
|
|
249
141
|
|
|
250
|
-
elements << element
|
|
142
|
+
elements << element
|
|
251
143
|
end
|
|
252
144
|
|
|
253
145
|
elements
|
|
254
146
|
end
|
|
255
147
|
|
|
256
|
-
#
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
# @return [Array<Parser::Source::Comment>] Consecutive comments before node.
|
|
260
|
-
def comments_before(node)
|
|
261
|
-
consecutive = []
|
|
262
|
-
expected_line = node.first_line - 1
|
|
263
|
-
|
|
264
|
-
while (comment = comments_by_line[expected_line])
|
|
265
|
-
consecutive.unshift(comment)
|
|
266
|
-
expected_line -= 1
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
consecutive
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
# Get comments indexed by line number for fast lookup.
|
|
273
|
-
#
|
|
274
|
-
# @return [Hash<Integer, Parser::Source::Comment>] Comments keyed by line.
|
|
275
|
-
def comments_by_line
|
|
276
|
-
@comments_by_line ||= processed_source.comments.to_h { |c| [c.location.line, c] }
|
|
148
|
+
# Check if element is a public instance method in a controller.
|
|
149
|
+
def controller_public_instance_method?(element, visibility)
|
|
150
|
+
@is_controller && element[:category] == :instance_methods && visibility == :public
|
|
277
151
|
end
|
|
278
152
|
|
|
279
|
-
#
|
|
153
|
+
# Get the priority hash for the current context.
|
|
280
154
|
#
|
|
281
|
-
# @param [RuboCop::AST::Node] node The node.
|
|
282
|
-
# @param [Symbol] category The category.
|
|
283
|
-
# @param [Symbol] visibility The visibility.
|
|
284
|
-
# @param [Integer] index The original index.
|
|
285
|
-
# @param [Boolean] is_model Whether this is a Rails model.
|
|
286
155
|
# @return [Hash]
|
|
287
|
-
def
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
category: category,
|
|
291
|
-
visibility: visibility,
|
|
292
|
-
original_index: index,
|
|
293
|
-
priority: priority_for(category, node, is_model),
|
|
294
|
-
sort_key: sort_key_for(category, node)
|
|
295
|
-
}
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
# Extract and categorize elements from the class.
|
|
299
|
-
#
|
|
300
|
-
# @param [RuboCop::AST::Node] node The class node.
|
|
301
|
-
# @param [Boolean] is_model Whether this is a Rails model.
|
|
302
|
-
# @param [Boolean] is_controller Whether this is a Rails controller.
|
|
303
|
-
# @return [Array<Hash>] Array of element info.
|
|
304
|
-
def extract_elements(node, is_model:, is_controller: false)
|
|
305
|
-
if node.body
|
|
306
|
-
collect_elements(node.body, is_model, is_controller)
|
|
156
|
+
def priorities
|
|
157
|
+
if @is_model
|
|
158
|
+
MODEL_PRIORITIES
|
|
307
159
|
else
|
|
308
|
-
|
|
309
|
-
end
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
# Find elements that violate ordering.
|
|
313
|
-
#
|
|
314
|
-
# @param [Array<Hash>] elements The list of elements.
|
|
315
|
-
# @return [Array<Hash>] Elements that violate ordering.
|
|
316
|
-
def find_violations(elements)
|
|
317
|
-
violations = []
|
|
318
|
-
|
|
319
|
-
elements.each_cons(2) do |current, following|
|
|
320
|
-
violations << following if violates_order?(current, following)
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
violations.uniq
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# Categorize method nodes.
|
|
327
|
-
#
|
|
328
|
-
# @param [RuboCop::AST::Node] node The node to categorize.
|
|
329
|
-
# @param [Symbol] visibility The current visibility.
|
|
330
|
-
# @return [Symbol]
|
|
331
|
-
def method_category(node, visibility)
|
|
332
|
-
return :class_methods if node.defs_type?
|
|
333
|
-
return :initialize if node.method?(:initialize) && visibility == :public
|
|
334
|
-
|
|
335
|
-
visibility_method_category(visibility)
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
# Get the source range of a node including preceding comments.
|
|
339
|
-
#
|
|
340
|
-
# @param [RuboCop::AST::Node] node The node.
|
|
341
|
-
# @return [Parser::Source::Range]
|
|
342
|
-
def node_range_with_comments(node)
|
|
343
|
-
comment_list = comments_before(node)
|
|
344
|
-
|
|
345
|
-
start_range = comment_list.any? ? comment_list.first.source_range : node.source_range
|
|
346
|
-
|
|
347
|
-
start_range.join(node.source_range)
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
# Get priority for a category.
|
|
351
|
-
#
|
|
352
|
-
# @param [Symbol] category The category.
|
|
353
|
-
# @param [RuboCop::AST::Node] _node The node.
|
|
354
|
-
# @param [Boolean] is_model Whether this is a Rails model.
|
|
355
|
-
# @return [Integer]
|
|
356
|
-
def priority_for(category, _node, is_model)
|
|
357
|
-
priorities = is_model ? MODEL_PRIORITIES : CLASS_PRIORITIES
|
|
358
|
-
|
|
359
|
-
priorities[category] || 999
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
# Process body nodes to get a flat list.
|
|
363
|
-
#
|
|
364
|
-
# @param [RuboCop::AST::Node] body The body node.
|
|
365
|
-
# @return [Array<RuboCop::AST::Node>]
|
|
366
|
-
def process_body_nodes(body)
|
|
367
|
-
if body.begin_type?
|
|
368
|
-
body.children
|
|
369
|
-
else
|
|
370
|
-
[body]
|
|
160
|
+
CLASS_PRIORITIES
|
|
371
161
|
end
|
|
372
162
|
end
|
|
373
163
|
|
|
@@ -416,33 +206,6 @@ module RuboCop
|
|
|
416
206
|
first_arg.value.to_s
|
|
417
207
|
end
|
|
418
208
|
|
|
419
|
-
# Categorize send nodes.
|
|
420
|
-
#
|
|
421
|
-
# @param [RuboCop::AST::Node] node The node to categorize.
|
|
422
|
-
# @param [Symbol] _visibility The current visibility.
|
|
423
|
-
# @param [Boolean] is_model Whether this is a Rails model.
|
|
424
|
-
# @return [Symbol]
|
|
425
|
-
# @return [nil]
|
|
426
|
-
def send_category(node, _visibility, is_model)
|
|
427
|
-
return :concerns if node.method?(:include)
|
|
428
|
-
return nil unless is_model
|
|
429
|
-
|
|
430
|
-
return :associations if ASSOCIATIONS.include?(node.method_name)
|
|
431
|
-
return :validations if validation_method?(node)
|
|
432
|
-
return :callbacks if CALLBACKS.include?(node.method_name)
|
|
433
|
-
return :scopes if node.method?(:scope)
|
|
434
|
-
|
|
435
|
-
nil
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
# Sort elements by priority, sort key, and original index.
|
|
439
|
-
#
|
|
440
|
-
# @param [Array<Hash>] elements The list of elements.
|
|
441
|
-
# @return [Array<Hash>]
|
|
442
|
-
def sort_elements(elements)
|
|
443
|
-
elements.sort_by { |e| [e[:priority], e[:sort_key], e[:original_index]] }
|
|
444
|
-
end
|
|
445
|
-
|
|
446
209
|
# Get sort key for alphabetical ordering within category.
|
|
447
210
|
#
|
|
448
211
|
# @param [Symbol] category The category.
|
|
@@ -459,14 +222,6 @@ module RuboCop
|
|
|
459
222
|
node.method_name.to_s
|
|
460
223
|
end
|
|
461
224
|
|
|
462
|
-
# Get the source of a node including preceding comments.
|
|
463
|
-
#
|
|
464
|
-
# @param [RuboCop::AST::Node] node The node.
|
|
465
|
-
# @return [String]
|
|
466
|
-
def source_with_comments(node)
|
|
467
|
-
node_range_with_comments(node).source
|
|
468
|
-
end
|
|
469
|
-
|
|
470
225
|
# Check if node is a validation method.
|
|
471
226
|
#
|
|
472
227
|
# @param [RuboCop::AST::Node] node The node to check.
|
|
@@ -476,52 +231,15 @@ module RuboCop
|
|
|
476
231
|
node.method_name.to_s.start_with?("validates_")
|
|
477
232
|
end
|
|
478
233
|
|
|
479
|
-
#
|
|
234
|
+
# Get the violation message for the current context.
|
|
480
235
|
#
|
|
481
|
-
# @
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
# Check if element violates category ordering.
|
|
491
|
-
#
|
|
492
|
-
# @param [Hash] current The current element.
|
|
493
|
-
# @param [Hash] following The following element.
|
|
494
|
-
# @return [Boolean]
|
|
495
|
-
def violates_category_order?(current, following)
|
|
496
|
-
current[:priority] > following[:priority]
|
|
497
|
-
end
|
|
498
|
-
|
|
499
|
-
# Check if element violates ordering.
|
|
500
|
-
#
|
|
501
|
-
# @param [Hash] current The current element.
|
|
502
|
-
# @param [Hash] following The following element.
|
|
503
|
-
# @return [Boolean]
|
|
504
|
-
def violates_order?(current, following)
|
|
505
|
-
violates_category_order?(current, following) ||
|
|
506
|
-
violates_alphabetical_order?(current, following)
|
|
507
|
-
end
|
|
508
|
-
|
|
509
|
-
# Get category for visibility-based instance methods.
|
|
510
|
-
#
|
|
511
|
-
# @param [Symbol] visibility The visibility.
|
|
512
|
-
# @return [Symbol]
|
|
513
|
-
# @return [nil]
|
|
514
|
-
def visibility_method_category(visibility)
|
|
515
|
-
VISIBILITY_CATEGORIES[visibility]
|
|
516
|
-
end
|
|
517
|
-
|
|
518
|
-
# Check if node is a visibility modifier (public, protected, private).
|
|
519
|
-
#
|
|
520
|
-
# @param [RuboCop::AST::Node] node The node to check.
|
|
521
|
-
# @return [Boolean]
|
|
522
|
-
def visibility_modifier?(node)
|
|
523
|
-
node.send_type? && node.receiver.nil? &&
|
|
524
|
-
%i(public protected private).include?(node.method_name)
|
|
236
|
+
# @return [String]
|
|
237
|
+
def violation_message
|
|
238
|
+
if @is_model
|
|
239
|
+
MODEL_MSG
|
|
240
|
+
else
|
|
241
|
+
CLASS_MSG
|
|
242
|
+
end
|
|
525
243
|
end
|
|
526
244
|
end
|
|
527
245
|
end
|
|
@@ -94,7 +94,9 @@ module RuboCop
|
|
|
94
94
|
|
|
95
95
|
return if statements.size < 2
|
|
96
96
|
|
|
97
|
-
groups = group_consecutive_statements(statements
|
|
97
|
+
groups = group_consecutive_statements(statements) do |statement|
|
|
98
|
+
statement.lvasgn_type? && !statement.children.last.block_type?
|
|
99
|
+
end
|
|
98
100
|
|
|
99
101
|
groups.each { |group| check_group_alignment(group) }
|
|
100
102
|
end
|
|
@@ -92,7 +92,9 @@ module RuboCop
|
|
|
92
92
|
|
|
93
93
|
return if statements.size < 2
|
|
94
94
|
|
|
95
|
-
groups = group_consecutive_statements(statements)
|
|
95
|
+
groups = group_consecutive_statements(statements) do |statement|
|
|
96
|
+
statement.casgn_type? && statement.single_line?
|
|
97
|
+
end
|
|
96
98
|
|
|
97
99
|
groups.each { |group| check_group_alignment(group) }
|
|
98
100
|
end
|
|
@@ -112,7 +112,7 @@ module RuboCop
|
|
|
112
112
|
|
|
113
113
|
return if statements.size < 2
|
|
114
114
|
|
|
115
|
-
groups = group_consecutive_statements(statements) { |
|
|
115
|
+
groups = group_consecutive_statements(statements) { |statement| indexed_assignment?(statement) }
|
|
116
116
|
|
|
117
117
|
groups.each { |group| check_group_alignment(group) }
|
|
118
118
|
end
|
|
@@ -113,7 +113,7 @@ module RuboCop
|
|
|
113
113
|
|
|
114
114
|
return if statements.size < 2
|
|
115
115
|
|
|
116
|
-
groups = group_consecutive_statements(statements) { |
|
|
116
|
+
groups = group_consecutive_statements(statements) { |statement| let_declaration?(statement) }
|
|
117
117
|
|
|
118
118
|
groups.each { |group| check_group_alignment(group) }
|
|
119
119
|
end
|
|
@@ -109,7 +109,7 @@ module RuboCop
|
|
|
109
109
|
|
|
110
110
|
return if statements.size < 2
|
|
111
111
|
|
|
112
|
-
groups = group_consecutive_statements(statements) { |
|
|
112
|
+
groups = group_consecutive_statements(statements) { |statement| scope_declaration?(statement) }
|
|
113
113
|
|
|
114
114
|
groups.each { |group| check_group_alignment(group) }
|
|
115
115
|
end
|
|
@@ -73,7 +73,7 @@ module RuboCop
|
|
|
73
73
|
# @param [Array<RuboCop::AST::Node>] group The constants group.
|
|
74
74
|
# @return [Boolean]
|
|
75
75
|
def alphabetically_ordered?(group)
|
|
76
|
-
names = group.map { |
|
|
76
|
+
names = group.map { |constant| constant.name.to_s }
|
|
77
77
|
|
|
78
78
|
names == names.sort
|
|
79
79
|
end
|
|
@@ -84,7 +84,7 @@ module RuboCop
|
|
|
84
84
|
# @param [Array<RuboCop::AST::Node>] group The constants group.
|
|
85
85
|
# @return [void]
|
|
86
86
|
def autocorrect(corrector, group)
|
|
87
|
-
sorted = group.sort_by { |
|
|
87
|
+
sorted = group.sort_by { |constant| constant.name.to_s }
|
|
88
88
|
|
|
89
89
|
group.each_with_index do |constant, index|
|
|
90
90
|
sorted_constant = sorted[index]
|
|
@@ -105,7 +105,7 @@ module RuboCop
|
|
|
105
105
|
# @param [Array<Hash>] blocks The list of describe blocks.
|
|
106
106
|
# @return [void]
|
|
107
107
|
def autocorrect(corrector, blocks)
|
|
108
|
-
sorted_blocks = blocks.sort_by { |
|
|
108
|
+
sorted_blocks = blocks.sort_by { |block| [block[:priority], block[:original_index]] }
|
|
109
109
|
|
|
110
110
|
blocks.each_with_index do |block, index|
|
|
111
111
|
sorted_block = sorted_blocks[index]
|
|
@@ -105,7 +105,7 @@ module RuboCop
|
|
|
105
105
|
|
|
106
106
|
return if statements.size < 2
|
|
107
107
|
|
|
108
|
-
groups = group_consecutive_statements(statements) { |
|
|
108
|
+
groups = group_consecutive_statements(statements) { |statement| let_declaration?(statement) }
|
|
109
109
|
|
|
110
110
|
groups.each { |group| check_group_order(group) }
|
|
111
111
|
end
|
|
@@ -10,6 +10,18 @@ module RuboCop
|
|
|
10
10
|
module AlignmentHelpers
|
|
11
11
|
private
|
|
12
12
|
|
|
13
|
+
# Check if two matched statements are consecutive (no gaps).
|
|
14
|
+
#
|
|
15
|
+
# @param [Array] first First [index, statement] pair.
|
|
16
|
+
# @param [Array] second Second [index, statement] pair.
|
|
17
|
+
# @param [Array<RuboCop::AST::Node>] statements Original statements.
|
|
18
|
+
# @return [Boolean]
|
|
19
|
+
def consecutive?(first, second, statements)
|
|
20
|
+
idx_a, stmt_a = first
|
|
21
|
+
idx_b, = second
|
|
22
|
+
idx_b == idx_a + 1 && no_blank_line_between?(stmt_a, statements[idx_b])
|
|
23
|
+
end
|
|
24
|
+
|
|
13
25
|
# Extract statements from a body node.
|
|
14
26
|
#
|
|
15
27
|
# @param [RuboCop::AST::Node] body The body node.
|
|
@@ -22,20 +34,6 @@ module RuboCop
|
|
|
22
34
|
end
|
|
23
35
|
end
|
|
24
36
|
|
|
25
|
-
# Group consecutive statements based on a predicate.
|
|
26
|
-
#
|
|
27
|
-
# Iterates through statements and groups consecutive ones where the block
|
|
28
|
-
# returns true. Groups are broken by non-matching statements or blank lines.
|
|
29
|
-
#
|
|
30
|
-
# @param [Array<RuboCop::AST::Node>] statements The statements.
|
|
31
|
-
# @yield [RuboCop::AST::Node] Block to determine if statement matches criteria.
|
|
32
|
-
# @return [Array<Array<RuboCop::AST::Node>>] Groups of consecutive matching statements.
|
|
33
|
-
def group_consecutive_statements(statements, &)
|
|
34
|
-
matching_with_indices = find_matching_statements(statements, &)
|
|
35
|
-
|
|
36
|
-
group_by_consecutive_lines(matching_with_indices, statements)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
37
|
# Find statements matching the predicate with their indices.
|
|
40
38
|
#
|
|
41
39
|
# @param [Array<RuboCop::AST::Node>] statements The statements.
|
|
@@ -53,20 +51,22 @@ module RuboCop
|
|
|
53
51
|
# @return [Array<Array<RuboCop::AST::Node>>] Groups with 2+ consecutive statements.
|
|
54
52
|
def group_by_consecutive_lines(matches, statements)
|
|
55
53
|
matches
|
|
56
|
-
.chunk_while { |
|
|
54
|
+
.chunk_while { |previous, current| consecutive?(previous, current, statements) }
|
|
57
55
|
.filter_map { |group| group.map(&:last) if group.size > 1 }
|
|
58
56
|
end
|
|
59
57
|
|
|
60
|
-
#
|
|
58
|
+
# Group consecutive statements based on a predicate.
|
|
61
59
|
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# @
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
# Iterates through statements and groups consecutive ones where the block
|
|
61
|
+
# returns true. Groups are broken by non-matching statements or blank lines.
|
|
62
|
+
#
|
|
63
|
+
# @param [Array<RuboCop::AST::Node>] statements The statements.
|
|
64
|
+
# @yield [RuboCop::AST::Node] Block to determine if statement matches criteria.
|
|
65
|
+
# @return [Array<Array<RuboCop::AST::Node>>] Groups of consecutive matching statements.
|
|
66
|
+
def group_consecutive_statements(statements, &)
|
|
67
|
+
matching_with_indices = find_matching_statements(statements, &)
|
|
68
|
+
|
|
69
|
+
group_by_consecutive_lines(matching_with_indices, statements)
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
# Check if there's no blank line between two statements.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Shared helper methods for organizing class and module bodies.
|
|
7
|
+
#
|
|
8
|
+
# Including cops must implement:
|
|
9
|
+
# - categorize_send_node(node, visibility) -> Symbol or nil
|
|
10
|
+
# - priorities -> Hash
|
|
11
|
+
# - sort_key_for(category, node) -> String
|
|
12
|
+
# - violation_message -> String
|
|
13
|
+
module BodyOrganization
|
|
14
|
+
include OrganizationHelpers
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# Get category for attr methods based on visibility.
|
|
19
|
+
def attr_category(visibility)
|
|
20
|
+
if visibility == :public
|
|
21
|
+
:attr_methods
|
|
22
|
+
else
|
|
23
|
+
visibility_method_category(visibility)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Auto-correct by sorting elements within each visibility section.
|
|
28
|
+
def autocorrect(corrector, _container_node, elements)
|
|
29
|
+
elements.group_by { |element| element[:visibility] }.each_value do |section|
|
|
30
|
+
sorted = sort_elements(section)
|
|
31
|
+
|
|
32
|
+
next if sorted == section
|
|
33
|
+
|
|
34
|
+
replace_nodes(corrector, section, sorted)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Categorize a node using shared logic and cop-specific send categorization.
|
|
39
|
+
def base_categorize_node(node, visibility)
|
|
40
|
+
return method_category(node, visibility) if node.any_def_type?
|
|
41
|
+
return :constants if node.casgn_type?
|
|
42
|
+
return nil if visibility_modifier?(node)
|
|
43
|
+
return attr_category(visibility) if attr_method?(node)
|
|
44
|
+
|
|
45
|
+
categorize_send_node(node, visibility)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Build element hash for a node.
|
|
49
|
+
def build_body_element(child, visibility, index)
|
|
50
|
+
return unless child.type?(:send, :any_def, :casgn)
|
|
51
|
+
|
|
52
|
+
category = base_categorize_node(child, visibility)
|
|
53
|
+
|
|
54
|
+
return unless category
|
|
55
|
+
|
|
56
|
+
element_hash(child, category, visibility, index)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Check and register violations.
|
|
60
|
+
def check_body_violations(container_node, elements)
|
|
61
|
+
violations = find_violations(elements)
|
|
62
|
+
|
|
63
|
+
return if violations.empty?
|
|
64
|
+
|
|
65
|
+
add_offense(violations.first[:node], message: violation_message) do |corrector|
|
|
66
|
+
autocorrect(corrector, container_node, elements)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Collect elements from body nodes.
|
|
71
|
+
def collect_body_elements(body)
|
|
72
|
+
visibility = :public
|
|
73
|
+
elements = []
|
|
74
|
+
|
|
75
|
+
process_body_nodes(body).each do |child|
|
|
76
|
+
visibility = child.method_name if visibility_modifier?(child)
|
|
77
|
+
element = build_body_element(child, visibility, elements.size)
|
|
78
|
+
|
|
79
|
+
elements << element if element
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
elements
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Create element hash.
|
|
86
|
+
def element_hash(node, category, visibility, index)
|
|
87
|
+
{
|
|
88
|
+
node:, category:, visibility:,
|
|
89
|
+
original_index: index,
|
|
90
|
+
priority: priorities[category] || 999,
|
|
91
|
+
sort_key: sort_key_for(category, node)
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Find elements that violate ordering.
|
|
96
|
+
def find_violations(elements)
|
|
97
|
+
elements.each_cons(2).filter_map do |current, following|
|
|
98
|
+
following if violates_order?(current, following)
|
|
99
|
+
end.uniq
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Categorize method nodes.
|
|
103
|
+
def method_category(node, visibility)
|
|
104
|
+
return :class_methods if node.defs_type?
|
|
105
|
+
return :initialize if node.method?(:initialize) && visibility == :public
|
|
106
|
+
|
|
107
|
+
visibility_method_category(visibility)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Sort elements by priority, sort key, and original index.
|
|
111
|
+
def sort_elements(elements)
|
|
112
|
+
elements.sort_by { |element| [element[:priority], element[:sort_key], element[:original_index]] }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Check if element violates alphabetical ordering.
|
|
116
|
+
def violates_alphabetical_order?(current, following)
|
|
117
|
+
current[:priority] == following[:priority] &&
|
|
118
|
+
!current[:sort_key].empty? &&
|
|
119
|
+
current[:sort_key] > following[:sort_key]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Check if element violates ordering.
|
|
123
|
+
def violates_order?(current, following)
|
|
124
|
+
current[:priority] > following[:priority] ||
|
|
125
|
+
violates_alphabetical_order?(current, following)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Low-level helpers for node ordering and comment handling.
|
|
7
|
+
#
|
|
8
|
+
# Used by BodyOrganization to handle source ranges, comments,
|
|
9
|
+
# visibility modifiers, and node replacement during autocorrect.
|
|
10
|
+
module OrganizationHelpers
|
|
11
|
+
ATTR_SORT_ORDER = { attr_accessor: "!0", attr_reader: "!1", attr_writer: "!2" }.freeze
|
|
12
|
+
VISIBILITY_CATEGORIES = {
|
|
13
|
+
protected: :protected_methods,
|
|
14
|
+
private: :private_methods,
|
|
15
|
+
public: :instance_methods
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# Check if node is an attr method.
|
|
21
|
+
def attr_method?(node)
|
|
22
|
+
node.send_type? && ATTR_SORT_ORDER.key?(node.method_name)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Get comments immediately before a node.
|
|
26
|
+
def comments_before(node)
|
|
27
|
+
consecutive = []
|
|
28
|
+
expected_line = node.first_line - 1
|
|
29
|
+
|
|
30
|
+
while (comment = comments_by_line[expected_line])
|
|
31
|
+
consecutive.unshift(comment)
|
|
32
|
+
expected_line -= 1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
consecutive
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Get comments indexed by line number for fast lookup.
|
|
39
|
+
def comments_by_line
|
|
40
|
+
@comments_by_line ||= processed_source.comments.to_h { |comment| [comment.location.line, comment] }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get the source range of a node including preceding comments.
|
|
44
|
+
def node_range_with_comments(node)
|
|
45
|
+
comment_list = comments_before(node)
|
|
46
|
+
start_range = comment_list.any? ? comment_list.first.source_range : node.source_range
|
|
47
|
+
start_range.join(node.source_range)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Process body nodes to get a flat list.
|
|
51
|
+
def process_body_nodes(body)
|
|
52
|
+
if body.begin_type?
|
|
53
|
+
body.children
|
|
54
|
+
else
|
|
55
|
+
[body]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Replace nodes during auto-correct.
|
|
60
|
+
def replace_nodes(corrector, group, sorted)
|
|
61
|
+
sorted.each_with_index do |element, idx|
|
|
62
|
+
original = group[idx]
|
|
63
|
+
|
|
64
|
+
next if element[:node] == original[:node]
|
|
65
|
+
|
|
66
|
+
corrector.replace(node_range_with_comments(original[:node]),
|
|
67
|
+
node_range_with_comments(element[:node]).source)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Get category for visibility-based instance methods.
|
|
72
|
+
def visibility_method_category(visibility)
|
|
73
|
+
VISIBILITY_CATEGORIES[visibility]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Check if node is a visibility modifier.
|
|
77
|
+
def visibility_modifier?(node)
|
|
78
|
+
node.send_type? && node.receiver.nil? &&
|
|
79
|
+
%i(public protected private).include?(node.method_name)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Enforces consistent organization of module definitions.
|
|
7
|
+
#
|
|
8
|
+
# Enforces: includes/extends → constants → attr → class methods →
|
|
9
|
+
# instance methods → protected → private.
|
|
10
|
+
#
|
|
11
|
+
# Within each category, methods are sorted alphabetically.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# # bad
|
|
15
|
+
# module Helper
|
|
16
|
+
# def zebra
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# def alpha
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# include Foo
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# # good
|
|
26
|
+
# module Helper
|
|
27
|
+
# include Foo
|
|
28
|
+
#
|
|
29
|
+
# def alpha
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# def zebra
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
class ModuleOrganization < Base
|
|
36
|
+
extend AutoCorrector
|
|
37
|
+
include BodyOrganization
|
|
38
|
+
|
|
39
|
+
MODULE_MSG = "Module elements should be ordered: includes/extends → constants → attr → " \
|
|
40
|
+
"class methods → instance methods → protected → private."
|
|
41
|
+
MODULE_PRIORITIES = {
|
|
42
|
+
concerns: 10,
|
|
43
|
+
constants: 20,
|
|
44
|
+
attr_methods: 25,
|
|
45
|
+
class_methods: 35,
|
|
46
|
+
instance_methods: 40,
|
|
47
|
+
protected_methods: 50,
|
|
48
|
+
private_methods: 60
|
|
49
|
+
}.freeze
|
|
50
|
+
|
|
51
|
+
# Check module nodes for organization.
|
|
52
|
+
#
|
|
53
|
+
# @param [RuboCop::AST::Node] node The module node.
|
|
54
|
+
# @return [void]
|
|
55
|
+
def on_module(node)
|
|
56
|
+
return unless node.body
|
|
57
|
+
|
|
58
|
+
elements = collect_body_elements(node.body)
|
|
59
|
+
|
|
60
|
+
return if elements.size < 2
|
|
61
|
+
|
|
62
|
+
check_body_violations(node, elements)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
# Categorize send nodes for modules.
|
|
68
|
+
#
|
|
69
|
+
# @param [RuboCop::AST::Node] node The node to categorize.
|
|
70
|
+
# @param [Symbol] _visibility The current visibility.
|
|
71
|
+
# @return [Symbol]
|
|
72
|
+
# @return [nil]
|
|
73
|
+
def categorize_send_node(node, _visibility)
|
|
74
|
+
return :concerns if node.method?(:include) || node.method?(:extend)
|
|
75
|
+
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Get the priority hash.
|
|
80
|
+
#
|
|
81
|
+
# @return [Hash]
|
|
82
|
+
def priorities
|
|
83
|
+
MODULE_PRIORITIES
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Get sort key for alphabetical ordering within category.
|
|
87
|
+
#
|
|
88
|
+
# @param [Symbol] category The category.
|
|
89
|
+
# @param [RuboCop::AST::Node] node The node.
|
|
90
|
+
# @return [String]
|
|
91
|
+
def sort_key_for(category, node)
|
|
92
|
+
sortable = %i(class_methods instance_methods protected_methods private_methods)
|
|
93
|
+
|
|
94
|
+
return "" unless sortable.include?(category)
|
|
95
|
+
return ATTR_SORT_ORDER[node.method_name] if attr_method?(node)
|
|
96
|
+
|
|
97
|
+
node.method_name.to_s
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Get the violation message.
|
|
101
|
+
#
|
|
102
|
+
# @return [String]
|
|
103
|
+
def violation_message
|
|
104
|
+
MODULE_MSG
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -97,7 +97,7 @@ module RuboCop
|
|
|
97
97
|
# @param [RuboCop::AST::Node] node The node.
|
|
98
98
|
# @return [RuboCop::AST::Node, nil]
|
|
99
99
|
def conditional_ancestor(node)
|
|
100
|
-
node.each_ancestor.find { |
|
|
100
|
+
node.each_ancestor.find { |ancestor| ancestor.type?(:if, :while, :until, :when) }
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
# Check if node is in a conditional position (if/unless/while/until/when condition).
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Disallows single character variable names.
|
|
7
|
+
#
|
|
8
|
+
# Single character names tend to obscure intent. The cop flags local
|
|
9
|
+
# variables, instance variables, class variables, method and block
|
|
10
|
+
# parameters, and rescue variables. The leading `@` or `@@` sigil is
|
|
11
|
+
# ignored when measuring length, so `@a` and `@@a` are treated as
|
|
12
|
+
# single character names.
|
|
13
|
+
#
|
|
14
|
+
# Names listed in `AllowedNames` are ignored. The default list contains
|
|
15
|
+
# `_`, which conventionally marks an intentionally unused value.
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# # bad
|
|
19
|
+
# x = 1
|
|
20
|
+
# @a = 1
|
|
21
|
+
# def foo(n); end
|
|
22
|
+
# [1].each { |i| i }
|
|
23
|
+
#
|
|
24
|
+
# # good
|
|
25
|
+
# count = 1
|
|
26
|
+
# @account = 1
|
|
27
|
+
# def foo(name); end
|
|
28
|
+
# [1].each { |index| index }
|
|
29
|
+
#
|
|
30
|
+
# @example AllowedNames: ['_', 'i', 'j']
|
|
31
|
+
# # good
|
|
32
|
+
# 2.times { |i| i }
|
|
33
|
+
#
|
|
34
|
+
# The block parameter `t` is allowed in files under `db/`, where it is a
|
|
35
|
+
# convention for Rails migrations (e.g. `create_table :users do |t|`).
|
|
36
|
+
class NoSingleCharacterVariableNames < Base
|
|
37
|
+
MIGRATION_BLOCK_VARIABLE = "t"
|
|
38
|
+
MIGRATION_PATH_PATTERN = %r{(?:\A|/)db/}
|
|
39
|
+
MSG = "Avoid single character variable name `%<name>s`."
|
|
40
|
+
|
|
41
|
+
# @param node [RuboCop::AST::Node]
|
|
42
|
+
# @return [void]
|
|
43
|
+
def on_arg(node)
|
|
44
|
+
check(node, node.name)
|
|
45
|
+
end
|
|
46
|
+
alias on_blockarg on_arg
|
|
47
|
+
alias on_cvasgn on_arg
|
|
48
|
+
alias on_ivasgn on_arg
|
|
49
|
+
alias on_kwarg on_arg
|
|
50
|
+
alias on_kwoptarg on_arg
|
|
51
|
+
alias on_kwrestarg on_arg
|
|
52
|
+
alias on_lvasgn on_arg
|
|
53
|
+
alias on_optarg on_arg
|
|
54
|
+
alias on_restarg on_arg
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# @return [Array<String>]
|
|
59
|
+
def allowed_names
|
|
60
|
+
@allowed_names ||= Array(cop_config.fetch("AllowedNames", ["_"]))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @param node [RuboCop::AST::Node]
|
|
64
|
+
# @param name [Symbol, nil]
|
|
65
|
+
# @return [void]
|
|
66
|
+
def check(node, name)
|
|
67
|
+
return if name.nil?
|
|
68
|
+
|
|
69
|
+
bare = name.to_s.sub(/\A@@?/, "")
|
|
70
|
+
|
|
71
|
+
return if bare.length != 1
|
|
72
|
+
return if allowed_names.include?(bare)
|
|
73
|
+
return if migration_block_variable?(bare)
|
|
74
|
+
|
|
75
|
+
add_offense(node.loc.name, message: format(MSG, name: name))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @param bare [String] the variable name without sigils.
|
|
79
|
+
# @return [Boolean]
|
|
80
|
+
def migration_block_variable?(bare)
|
|
81
|
+
bare == MIGRATION_BLOCK_VARIABLE && migration_file?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [Boolean]
|
|
85
|
+
def migration_file?
|
|
86
|
+
processed_source.file_path.match?(MIGRATION_PATH_PATTERN)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -109,7 +109,7 @@ module RuboCop
|
|
|
109
109
|
# @param [Array<RuboCop::AST::Node>] chain The method chain.
|
|
110
110
|
# @return [Boolean]
|
|
111
111
|
def chain_has_with?(chain)
|
|
112
|
-
chain.any? { |
|
|
112
|
+
chain.any? { |node| node.method?(:with) }
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
# Check if the method is one we care about for chaining.
|
|
@@ -190,7 +190,7 @@ module RuboCop
|
|
|
190
190
|
# @param [Array<RuboCop::AST::Node>] chain The method chain.
|
|
191
191
|
# @return [Integer, nil]
|
|
192
192
|
def find_receive_index(chain)
|
|
193
|
-
chain.index { |
|
|
193
|
+
chain.index { |node| RECEIVE_METHODS.include?(node.method_name) }
|
|
194
194
|
end
|
|
195
195
|
|
|
196
196
|
# Find the starting position for the replacement.
|
|
@@ -66,8 +66,8 @@ module RuboCop
|
|
|
66
66
|
# @param [Array<RuboCop::AST::Node>] group The validates group.
|
|
67
67
|
# @return [void]
|
|
68
68
|
def autocorrect(corrector, group)
|
|
69
|
-
validates_nodes = group.select { |
|
|
70
|
-
validate_nodes = group.select { |
|
|
69
|
+
validates_nodes = group.select { |node| VALIDATES_METHODS.include?(node.method_name) }
|
|
70
|
+
validate_nodes = group.select { |node| node.method?(VALIDATE_METHOD) }
|
|
71
71
|
sorted = validates_nodes + validate_nodes
|
|
72
72
|
|
|
73
73
|
group.each_with_index do |node, index|
|
|
@@ -104,7 +104,7 @@ module RuboCop
|
|
|
104
104
|
|
|
105
105
|
return if statements.size < 2
|
|
106
106
|
|
|
107
|
-
groups = group_consecutive_statements(statements) { |
|
|
107
|
+
groups = group_consecutive_statements(statements) { |statement| validates_family?(statement) }
|
|
108
108
|
|
|
109
109
|
groups.each { |group| check_group_order(group) }
|
|
110
110
|
end
|
|
@@ -114,7 +114,7 @@ module RuboCop
|
|
|
114
114
|
# @param [Array<RuboCop::AST::Node>] group The validates group.
|
|
115
115
|
# @return [Array<RuboCop::AST::Node>] Validate nodes that violate ordering.
|
|
116
116
|
def find_violations(group)
|
|
117
|
-
last_validates_index = group.rindex { |
|
|
117
|
+
last_validates_index = group.rindex { |node| VALIDATES_METHODS.include?(node.method_name) }
|
|
118
118
|
|
|
119
119
|
return [] if last_validates_index.nil?
|
|
120
120
|
|
|
@@ -60,7 +60,7 @@ module RuboCop
|
|
|
60
60
|
# @param [Array<RuboCop::AST::Node>] group The validates group.
|
|
61
61
|
# @return [Boolean]
|
|
62
62
|
def alphabetically_ordered?(group)
|
|
63
|
-
names = group.map { |
|
|
63
|
+
names = group.map { |validates| extract_validates_name(validates) }
|
|
64
64
|
|
|
65
65
|
names == names.sort
|
|
66
66
|
end
|
|
@@ -71,7 +71,7 @@ module RuboCop
|
|
|
71
71
|
# @param [Array<RuboCop::AST::Node>] group The validates group.
|
|
72
72
|
# @return [void]
|
|
73
73
|
def autocorrect(corrector, group)
|
|
74
|
-
sorted = group.sort_by { |
|
|
74
|
+
sorted = group.sort_by { |validates| extract_validates_name(validates) }
|
|
75
75
|
|
|
76
76
|
group.each_with_index do |validates, index|
|
|
77
77
|
sorted_validates = sorted[index]
|
|
@@ -107,7 +107,7 @@ module RuboCop
|
|
|
107
107
|
|
|
108
108
|
return if statements.size < 2
|
|
109
109
|
|
|
110
|
-
groups = group_consecutive_statements(statements) { |
|
|
110
|
+
groups = group_consecutive_statements(statements) { |statement| validates_declaration?(statement) }
|
|
111
111
|
|
|
112
112
|
groups.each { |group| check_group_order(group) }
|
|
113
113
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "vibe/mixin/alignment_helpers"
|
|
4
|
+
require_relative "vibe/mixin/organization_helpers"
|
|
5
|
+
require_relative "vibe/mixin/body_organization"
|
|
4
6
|
require_relative "vibe/mixin/spec_file_helper"
|
|
5
7
|
|
|
6
8
|
require_relative "vibe/attr_order"
|
|
@@ -19,10 +21,12 @@ require_relative "vibe/explicit_return_conditional"
|
|
|
19
21
|
require_relative "vibe/is_expected_one_liner"
|
|
20
22
|
require_relative "vibe/keyword_argument_order"
|
|
21
23
|
require_relative "vibe/let_order"
|
|
24
|
+
require_relative "vibe/module_organization"
|
|
22
25
|
require_relative "vibe/multiline_hash_argument_style"
|
|
23
26
|
require_relative "vibe/no_assigns_attribute_testing"
|
|
24
27
|
require_relative "vibe/no_compound_conditions"
|
|
25
28
|
require_relative "vibe/no_rubocop_disable"
|
|
29
|
+
require_relative "vibe/no_single_character_variable_names"
|
|
26
30
|
require_relative "vibe/no_skipped_tests"
|
|
27
31
|
require_relative "vibe/no_unless_guard_clause"
|
|
28
32
|
require_relative "vibe/prefer_one_liner_expectation"
|
data/lib/rubocop/vibe/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-vibe
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tristan Dunn
|
|
@@ -104,11 +104,15 @@ files:
|
|
|
104
104
|
- lib/rubocop/cop/vibe/keyword_argument_order.rb
|
|
105
105
|
- lib/rubocop/cop/vibe/let_order.rb
|
|
106
106
|
- lib/rubocop/cop/vibe/mixin/alignment_helpers.rb
|
|
107
|
+
- lib/rubocop/cop/vibe/mixin/body_organization.rb
|
|
108
|
+
- lib/rubocop/cop/vibe/mixin/organization_helpers.rb
|
|
107
109
|
- lib/rubocop/cop/vibe/mixin/spec_file_helper.rb
|
|
110
|
+
- lib/rubocop/cop/vibe/module_organization.rb
|
|
108
111
|
- lib/rubocop/cop/vibe/multiline_hash_argument_style.rb
|
|
109
112
|
- lib/rubocop/cop/vibe/no_assigns_attribute_testing.rb
|
|
110
113
|
- lib/rubocop/cop/vibe/no_compound_conditions.rb
|
|
111
114
|
- lib/rubocop/cop/vibe/no_rubocop_disable.rb
|
|
115
|
+
- lib/rubocop/cop/vibe/no_single_character_variable_names.rb
|
|
112
116
|
- lib/rubocop/cop/vibe/no_skipped_tests.rb
|
|
113
117
|
- lib/rubocop/cop/vibe/no_unless_guard_clause.rb
|
|
114
118
|
- lib/rubocop/cop/vibe/prefer_one_liner_expectation.rb
|
|
@@ -144,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
144
148
|
- !ruby/object:Gem::Version
|
|
145
149
|
version: '0'
|
|
146
150
|
requirements: []
|
|
147
|
-
rubygems_version: 4.0.
|
|
151
|
+
rubygems_version: 4.0.23
|
|
148
152
|
specification_version: 4
|
|
149
153
|
summary: A set of custom cops to use on AI generated code.
|
|
150
154
|
test_files: []
|