rubocop-vibe 0.4.0 → 0.5.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 +36 -0
- data/lib/rubocop/cop/vibe/attr_order.rb +87 -0
- data/lib/rubocop/cop/vibe/blank_line_after_assignment.rb +1 -0
- data/lib/rubocop/cop/vibe/blank_line_before_expectation.rb +1 -0
- data/lib/rubocop/cop/vibe/class_organization.rb +18 -18
- data/lib/rubocop/cop/vibe/consecutive_assignment_alignment.rb +1 -0
- data/lib/rubocop/cop/vibe/consecutive_indexed_assignment_alignment.rb +1 -0
- data/lib/rubocop/cop/vibe/consecutive_instance_variable_assignment_alignment.rb +1 -0
- data/lib/rubocop/cop/vibe/consecutive_let_alignment.rb +1 -0
- data/lib/rubocop/cop/vibe/consecutive_scope_alignment.rb +132 -0
- data/lib/rubocop/cop/vibe/constant_alpha_order.rb +144 -0
- data/lib/rubocop/cop/vibe/describe_block_order.rb +2 -1
- data/lib/rubocop/cop/vibe/explicit_return_conditional.rb +4 -3
- data/lib/rubocop/cop/vibe/keyword_argument_order.rb +190 -0
- data/lib/rubocop/cop/vibe/let_order.rb +1 -0
- data/lib/rubocop/cop/vibe/no_rubocop_disable.rb +2 -2
- data/lib/rubocop/cop/vibe/no_skipped_tests.rb +2 -2
- data/lib/rubocop/cop/vibe/prefer_one_liner_expectation.rb +1 -0
- data/lib/rubocop/cop/vibe/raise_unless_block.rb +7 -4
- data/lib/rubocop/cop/vibe/rspec_before_block_style.rb +1 -0
- data/lib/rubocop/cop/vibe/validate_after_validates.rb +155 -0
- data/lib/rubocop/cop/vibe/validates_alpha_order.rb +166 -0
- data/lib/rubocop/cop/vibe_cops.rb +6 -0
- data/lib/rubocop/vibe/version.rb +1 -1
- metadata +12 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f214588afca7a72328401c384b819415ddae3ac7e4fa2c214bfb7225f4253b56
|
|
4
|
+
data.tar.gz: d4a9267839bcd11320ca97f7ef64f17134618f7efee156b4f35b8f1a2a07ad1e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb99fe49c2b921d70e07a6d2c6c4ea035fa76867a3391803f586575a7631d19d176af76791778aedf33178bfa6cb8f9fa3aebe1a66950b14b88c7dfd3823b101
|
|
7
|
+
data.tar.gz: 4527a16179dbb9b2fa026639f47409a706ba393564b50cc0f6f5544df4b23c9faf6abfc720e319c1a88e92b7f40e2eed553eb720853345134d6d8f754791a9c9
|
data/config/default.yml
CHANGED
|
@@ -62,6 +62,12 @@ Style/StringLiterals:
|
|
|
62
62
|
Style/StringLiteralsInInterpolation:
|
|
63
63
|
EnforcedStyle: double_quotes
|
|
64
64
|
|
|
65
|
+
Vibe/AttrOrder:
|
|
66
|
+
Description: "Enforces alphabetical ordering of attr_reader, attr_writer, and attr_accessor arguments."
|
|
67
|
+
Enabled: true
|
|
68
|
+
SafeAutoCorrect: true
|
|
69
|
+
VersionAdded: "0.5.0"
|
|
70
|
+
|
|
65
71
|
Vibe/BlankLineAfterAssignment:
|
|
66
72
|
Description: "Enforces a blank line after variable assignments when followed by other code."
|
|
67
73
|
Enabled: true
|
|
@@ -110,6 +116,18 @@ Vibe/ConsecutiveLetAlignment:
|
|
|
110
116
|
SafeAutoCorrect: true
|
|
111
117
|
VersionAdded: "0.2.0"
|
|
112
118
|
|
|
119
|
+
Vibe/ConsecutiveScopeAlignment:
|
|
120
|
+
Description: "Enforces alignment of consecutive scope declarations at the -> arrow."
|
|
121
|
+
Enabled: true
|
|
122
|
+
SafeAutoCorrect: true
|
|
123
|
+
VersionAdded: "0.5.0"
|
|
124
|
+
|
|
125
|
+
Vibe/ConstantAlphaOrder:
|
|
126
|
+
Description: "Enforces alphabetical ordering of consecutive constant declarations by name."
|
|
127
|
+
Enabled: true
|
|
128
|
+
SafeAutoCorrect: false
|
|
129
|
+
VersionAdded: "0.5.0"
|
|
130
|
+
|
|
113
131
|
Vibe/DescribeBlockOrder:
|
|
114
132
|
Description: "Enforces consistent ordering of describe blocks in RSpec files."
|
|
115
133
|
Enabled: true
|
|
@@ -128,6 +146,12 @@ Vibe/IsExpectedOneLiner:
|
|
|
128
146
|
SafeAutoCorrect: true
|
|
129
147
|
VersionAdded: "0.1.0"
|
|
130
148
|
|
|
149
|
+
Vibe/KeywordArgumentOrder:
|
|
150
|
+
Description: "Enforces alphabetical ordering of keyword arguments and their YARD documentation."
|
|
151
|
+
Enabled: true
|
|
152
|
+
SafeAutoCorrect: true
|
|
153
|
+
VersionAdded: "0.5.0"
|
|
154
|
+
|
|
131
155
|
Vibe/LetOrder:
|
|
132
156
|
Description: "Enforces alphabetical ordering of consecutive let declarations."
|
|
133
157
|
Enabled: true
|
|
@@ -196,3 +220,15 @@ Vibe/ServiceCallMethod:
|
|
|
196
220
|
Description: "Service objects should define `self.call` and `call` methods."
|
|
197
221
|
Enabled: true
|
|
198
222
|
VersionAdded: "0.1.0"
|
|
223
|
+
|
|
224
|
+
Vibe/ValidateAfterValidates:
|
|
225
|
+
Description: "Enforces that validate calls appear after validates declarations in Rails models."
|
|
226
|
+
Enabled: true
|
|
227
|
+
SafeAutoCorrect: true
|
|
228
|
+
VersionAdded: "0.5.0"
|
|
229
|
+
|
|
230
|
+
Vibe/ValidatesAlphaOrder:
|
|
231
|
+
Description: "Enforces alphabetical ordering of consecutive validates declarations by attribute name."
|
|
232
|
+
Enabled: true
|
|
233
|
+
SafeAutoCorrect: true
|
|
234
|
+
VersionAdded: "0.5.0"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Enforces alphabetical ordering of arguments to `attr_reader`,
|
|
7
|
+
# `attr_writer`, and `attr_accessor` declarations.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# # bad
|
|
11
|
+
# attr_reader :id, :content, :timestamp, :raw
|
|
12
|
+
#
|
|
13
|
+
# # good
|
|
14
|
+
# attr_reader :content, :id, :raw, :timestamp
|
|
15
|
+
#
|
|
16
|
+
# # bad
|
|
17
|
+
# attr_accessor :zebra, :apple
|
|
18
|
+
#
|
|
19
|
+
# # good
|
|
20
|
+
# attr_accessor :apple, :zebra
|
|
21
|
+
class AttrOrder < Base
|
|
22
|
+
extend AutoCorrector
|
|
23
|
+
|
|
24
|
+
MSG = "Order `%<method>s` arguments alphabetically."
|
|
25
|
+
|
|
26
|
+
ATTR_METHODS = %i(attr_reader attr_writer attr_accessor).freeze
|
|
27
|
+
|
|
28
|
+
# Check attr_* method calls for alphabetical ordering.
|
|
29
|
+
#
|
|
30
|
+
# @param [RuboCop::AST::Node] node The send node.
|
|
31
|
+
# @return [void]
|
|
32
|
+
def on_send(node)
|
|
33
|
+
return unless attr_method?(node)
|
|
34
|
+
return unless node.arguments.size > 1
|
|
35
|
+
return if all_symbols?(node.arguments) && alphabetically_ordered?(node.arguments)
|
|
36
|
+
|
|
37
|
+
add_offense(node, message: format(MSG, method: node.method_name)) do |corrector|
|
|
38
|
+
autocorrect(corrector, node)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
alias on_csend on_send
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Check if the node is an attr_* method call.
|
|
46
|
+
#
|
|
47
|
+
# @param [RuboCop::AST::Node] node The send node.
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def attr_method?(node)
|
|
50
|
+
node.receiver.nil? && ATTR_METHODS.include?(node.method_name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Check if all arguments are symbols.
|
|
54
|
+
#
|
|
55
|
+
# @param [Array<RuboCop::AST::Node>] arguments The arguments.
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def all_symbols?(arguments)
|
|
58
|
+
arguments.all?(&:sym_type?)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Check if arguments are alphabetically ordered.
|
|
62
|
+
#
|
|
63
|
+
# @param [Array<RuboCop::AST::Node>] arguments The arguments.
|
|
64
|
+
# @return [Boolean]
|
|
65
|
+
def alphabetically_ordered?(arguments)
|
|
66
|
+
names = arguments.map { |arg| arg.value.to_s }
|
|
67
|
+
|
|
68
|
+
names == names.sort
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Auto-correct by reordering arguments alphabetically.
|
|
72
|
+
#
|
|
73
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
74
|
+
# @param [RuboCop::AST::Node] node The send node.
|
|
75
|
+
# @return [void]
|
|
76
|
+
def autocorrect(corrector, node)
|
|
77
|
+
sorted_args = node.arguments.sort_by { |arg| arg.value.to_s }
|
|
78
|
+
sorted_source = sorted_args.map(&:source).join(", ")
|
|
79
|
+
|
|
80
|
+
args_range = node.first_argument.source_range.join(node.last_argument.source_range)
|
|
81
|
+
|
|
82
|
+
corrector.replace(args_range, sorted_source)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -36,20 +36,13 @@ module RuboCop
|
|
|
36
36
|
class ClassOrganization < Base
|
|
37
37
|
extend AutoCorrector
|
|
38
38
|
|
|
39
|
+
CLASS_MSG = "Class elements should be ordered: includes → constants → initialize → " \
|
|
40
|
+
"class methods → instance methods → protected → private."
|
|
39
41
|
MODEL_MSG = "Model elements should be ordered: concerns → constants → associations → " \
|
|
40
42
|
"validations → callbacks → scopes → class methods → instance methods → " \
|
|
41
43
|
"protected → private."
|
|
42
|
-
CLASS_MSG = "Class elements should be ordered: includes → constants → initialize → " \
|
|
43
|
-
"class methods → instance methods → protected → private."
|
|
44
44
|
|
|
45
45
|
ASSOCIATIONS = %i(belongs_to has_one has_many has_and_belongs_to_many).freeze
|
|
46
|
-
VALIDATIONS = %i(
|
|
47
|
-
validates validate validates_each validates_with
|
|
48
|
-
validates_absence_of validates_acceptance_of validates_confirmation_of
|
|
49
|
-
validates_exclusion_of validates_format_of validates_inclusion_of
|
|
50
|
-
validates_length_of validates_numericality_of validates_presence_of
|
|
51
|
-
validates_size_of validates_uniqueness_of validates_associated
|
|
52
|
-
).freeze
|
|
53
46
|
CALLBACKS = %i(
|
|
54
47
|
before_validation after_validation
|
|
55
48
|
before_save after_save around_save
|
|
@@ -59,6 +52,15 @@ module RuboCop
|
|
|
59
52
|
after_commit after_rollback
|
|
60
53
|
after_initialize after_find after_touch
|
|
61
54
|
).freeze
|
|
55
|
+
CLASS_PRIORITIES = {
|
|
56
|
+
concerns: 10,
|
|
57
|
+
constants: 20,
|
|
58
|
+
initialize: 30,
|
|
59
|
+
class_methods: 40,
|
|
60
|
+
instance_methods: 50,
|
|
61
|
+
protected_methods: 60,
|
|
62
|
+
private_methods: 70
|
|
63
|
+
}.freeze
|
|
62
64
|
MODEL_PRIORITIES = {
|
|
63
65
|
concerns: 10,
|
|
64
66
|
constants: 20,
|
|
@@ -71,15 +73,13 @@ module RuboCop
|
|
|
71
73
|
protected_methods: 90,
|
|
72
74
|
private_methods: 100
|
|
73
75
|
}.freeze
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
private_methods: 70
|
|
82
|
-
}.freeze
|
|
76
|
+
VALIDATIONS = %i(
|
|
77
|
+
validates validate validates_each validates_with
|
|
78
|
+
validates_absence_of validates_acceptance_of validates_confirmation_of
|
|
79
|
+
validates_exclusion_of validates_format_of validates_inclusion_of
|
|
80
|
+
validates_length_of validates_numericality_of validates_presence_of
|
|
81
|
+
validates_size_of validates_uniqueness_of validates_associated
|
|
82
|
+
).freeze
|
|
83
83
|
VISIBILITY_CATEGORIES = {
|
|
84
84
|
protected: :protected_methods,
|
|
85
85
|
private: :private_methods,
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Enforces alignment of consecutive `scope` declarations at the `->` arrow.
|
|
7
|
+
#
|
|
8
|
+
# Consecutive `scope` declarations (with no blank lines between) should align
|
|
9
|
+
# their `->` arrows for better readability. Groups are broken by blank lines or
|
|
10
|
+
# non-scope statements.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad
|
|
14
|
+
# scope :between, ->(start, stop) { where(created_at: start..stop) }
|
|
15
|
+
# scope :for_website, ->(website_id) { where(website_id: website_id) }
|
|
16
|
+
#
|
|
17
|
+
# # good
|
|
18
|
+
# scope :between, ->(start, stop) { where(created_at: start..stop) }
|
|
19
|
+
# scope :for_website, ->(website_id) { where(website_id: website_id) }
|
|
20
|
+
#
|
|
21
|
+
# # good - blank line breaks the group
|
|
22
|
+
# scope :between, ->(start, stop) { where(created_at: start..stop) }
|
|
23
|
+
#
|
|
24
|
+
# scope :for_website, ->(website_id) { where(website_id: website_id) }
|
|
25
|
+
class ConsecutiveScopeAlignment < Base
|
|
26
|
+
extend AutoCorrector
|
|
27
|
+
include AlignmentHelpers
|
|
28
|
+
|
|
29
|
+
MSG = "Align consecutive scope declarations at the `->` arrow."
|
|
30
|
+
|
|
31
|
+
# Check class nodes for scope alignment.
|
|
32
|
+
#
|
|
33
|
+
# @param [RuboCop::AST::Node] node The class node.
|
|
34
|
+
# @return [void]
|
|
35
|
+
def on_class(node)
|
|
36
|
+
if node.body
|
|
37
|
+
check_scopes_in_body(node.body)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# Check scope declarations in a body node.
|
|
44
|
+
#
|
|
45
|
+
# @param [RuboCop::AST::Node] body The body node.
|
|
46
|
+
# @return [void]
|
|
47
|
+
def check_scopes_in_body(body)
|
|
48
|
+
statements = extract_statements(body)
|
|
49
|
+
|
|
50
|
+
return if statements.size < 2
|
|
51
|
+
|
|
52
|
+
groups = group_consecutive_statements(statements) { |s| scope_declaration?(s) }
|
|
53
|
+
|
|
54
|
+
groups.each { |group| check_group_alignment(group) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Check if a node is a scope declaration with a lambda.
|
|
58
|
+
#
|
|
59
|
+
# @param [RuboCop::AST::Node] node The node to check.
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def scope_declaration?(node)
|
|
62
|
+
return false unless node.send_type?
|
|
63
|
+
return false unless node.method?(:scope)
|
|
64
|
+
return false unless node.receiver.nil?
|
|
65
|
+
return false unless node.arguments[1]&.block_type?
|
|
66
|
+
|
|
67
|
+
node.arguments[1].method?(:lambda)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Get the source range of the `->` arrow in a scope declaration.
|
|
71
|
+
#
|
|
72
|
+
# @param [RuboCop::AST::Node] scope_node The scope send node.
|
|
73
|
+
# @return [Parser::Source::Range]
|
|
74
|
+
def arrow_range(scope_node)
|
|
75
|
+
scope_node.arguments[1].send_node.loc.selector
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check alignment for a group of scope declarations.
|
|
79
|
+
#
|
|
80
|
+
# @param [Array<RuboCop::AST::Node>] group The scope group.
|
|
81
|
+
# @return [void]
|
|
82
|
+
def check_group_alignment(group)
|
|
83
|
+
columns = group.map { |scope| arrow_range(scope).column }
|
|
84
|
+
target_column = columns.max
|
|
85
|
+
|
|
86
|
+
group.each do |scope|
|
|
87
|
+
current_column = arrow_range(scope).column
|
|
88
|
+
|
|
89
|
+
next if current_column == target_column
|
|
90
|
+
|
|
91
|
+
add_offense(scope.first_argument) do |corrector|
|
|
92
|
+
autocorrect_alignment(corrector, scope, target_column)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Auto-correct the alignment of a scope declaration.
|
|
98
|
+
#
|
|
99
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
100
|
+
# @param [RuboCop::AST::Node] scope_node The scope send node.
|
|
101
|
+
# @param [Integer] target_column The target column for alignment.
|
|
102
|
+
# @return [void]
|
|
103
|
+
def autocorrect_alignment(corrector, scope_node, target_column)
|
|
104
|
+
arrow = arrow_range(scope_node)
|
|
105
|
+
comma_end = scope_node.first_argument.source_range.end_pos + 1
|
|
106
|
+
arrow_start = arrow.begin_pos
|
|
107
|
+
total_spaces = calculate_total_spaces(scope_node, target_column)
|
|
108
|
+
|
|
109
|
+
corrector.replace(
|
|
110
|
+
range_between(comma_end, arrow_start),
|
|
111
|
+
" " * total_spaces
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Calculate total spaces needed for alignment.
|
|
116
|
+
#
|
|
117
|
+
# @param [RuboCop::AST::Node] scope_node The scope send node.
|
|
118
|
+
# @param [Integer] target_column The target column for alignment.
|
|
119
|
+
# @return [Integer] The number of spaces (minimum 1).
|
|
120
|
+
def calculate_total_spaces(scope_node, target_column)
|
|
121
|
+
arrow = arrow_range(scope_node)
|
|
122
|
+
comma_end = scope_node.first_argument.source_range.end_pos + 1
|
|
123
|
+
current_column = arrow.column
|
|
124
|
+
current_spaces = arrow.begin_pos - comma_end
|
|
125
|
+
spaces_needed = target_column - current_column
|
|
126
|
+
|
|
127
|
+
[1, current_spaces + spaces_needed].max
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Enforces alphabetical ordering of consecutive constant declarations by name.
|
|
7
|
+
#
|
|
8
|
+
# Consecutive constant declarations (with no blank lines between) should be
|
|
9
|
+
# alphabetically ordered by constant name for better readability and
|
|
10
|
+
# easier scanning. Groups are broken by blank lines or non-constant statements.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad
|
|
14
|
+
# class MyClass
|
|
15
|
+
# ZEBRA = 1
|
|
16
|
+
# ALPHA = 2
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# # good
|
|
20
|
+
# class MyClass
|
|
21
|
+
# ALPHA = 2
|
|
22
|
+
# ZEBRA = 1
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# # good - blank line breaks the group
|
|
26
|
+
# class MyClass
|
|
27
|
+
# ZEBRA = 1
|
|
28
|
+
#
|
|
29
|
+
# ALPHA = 2
|
|
30
|
+
# end
|
|
31
|
+
class ConstantAlphaOrder < Base
|
|
32
|
+
extend AutoCorrector
|
|
33
|
+
include AlignmentHelpers
|
|
34
|
+
|
|
35
|
+
MSG = "Order constants alphabetically by name."
|
|
36
|
+
|
|
37
|
+
# Check block nodes for constant ordering.
|
|
38
|
+
#
|
|
39
|
+
# @param [RuboCop::AST::Node] node The block node.
|
|
40
|
+
# @return [void]
|
|
41
|
+
def on_block(node)
|
|
42
|
+
if node.body
|
|
43
|
+
check_constants_in_body(node.body)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
alias on_itblock on_block
|
|
47
|
+
alias on_numblock on_block
|
|
48
|
+
|
|
49
|
+
# Check class nodes for constant ordering.
|
|
50
|
+
#
|
|
51
|
+
# @param [RuboCop::AST::Node] node The class node.
|
|
52
|
+
# @return [void]
|
|
53
|
+
def on_class(node)
|
|
54
|
+
if node.body
|
|
55
|
+
check_constants_in_body(node.body)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Check module nodes for constant ordering.
|
|
60
|
+
#
|
|
61
|
+
# @param [RuboCop::AST::Node] node The module node.
|
|
62
|
+
# @return [void]
|
|
63
|
+
def on_module(node)
|
|
64
|
+
if node.body
|
|
65
|
+
check_constants_in_body(node.body)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# Check constant declarations in a body node.
|
|
72
|
+
#
|
|
73
|
+
# @param [RuboCop::AST::Node] body The body node.
|
|
74
|
+
# @return [void]
|
|
75
|
+
def check_constants_in_body(body)
|
|
76
|
+
statements = extract_statements(body)
|
|
77
|
+
|
|
78
|
+
return if statements.size < 2
|
|
79
|
+
|
|
80
|
+
groups = group_consecutive_statements(statements, &:casgn_type?)
|
|
81
|
+
|
|
82
|
+
groups.each { |group| check_group_order(group) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Check ordering for a group of constant declarations.
|
|
86
|
+
#
|
|
87
|
+
# @param [Array<RuboCop::AST::Node>] group The constants group.
|
|
88
|
+
# @return [void]
|
|
89
|
+
def check_group_order(group)
|
|
90
|
+
return if alphabetically_ordered?(group)
|
|
91
|
+
|
|
92
|
+
violations = find_ordering_violations(group)
|
|
93
|
+
|
|
94
|
+
violations.each do |constant|
|
|
95
|
+
add_offense(constant) do |corrector|
|
|
96
|
+
autocorrect(corrector, group)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Check if constant declarations are alphabetically ordered.
|
|
102
|
+
#
|
|
103
|
+
# @param [Array<RuboCop::AST::Node>] group The constants group.
|
|
104
|
+
# @return [Boolean]
|
|
105
|
+
def alphabetically_ordered?(group)
|
|
106
|
+
names = group.map { |c| c.name.to_s }
|
|
107
|
+
|
|
108
|
+
names == names.sort
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Find constant declarations that violate ordering.
|
|
112
|
+
#
|
|
113
|
+
# @param [Array<RuboCop::AST::Node>] group The constants group.
|
|
114
|
+
# @return [Array<RuboCop::AST::Node>] Constants that violate ordering.
|
|
115
|
+
def find_ordering_violations(group)
|
|
116
|
+
violations = []
|
|
117
|
+
|
|
118
|
+
group.each_cons(2) do |current, following|
|
|
119
|
+
violations << following if current.name.to_s > following.name.to_s
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
violations.uniq
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Auto-correct by reordering constant declarations.
|
|
126
|
+
#
|
|
127
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
128
|
+
# @param [Array<RuboCop::AST::Node>] group The constants group.
|
|
129
|
+
# @return [void]
|
|
130
|
+
def autocorrect(corrector, group)
|
|
131
|
+
sorted = group.sort_by { |c| c.name.to_s }
|
|
132
|
+
|
|
133
|
+
group.each_with_index do |constant, index|
|
|
134
|
+
sorted_constant = sorted[index]
|
|
135
|
+
|
|
136
|
+
next if constant == sorted_constant
|
|
137
|
+
|
|
138
|
+
corrector.replace(constant, sorted_constant.source)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -59,8 +59,8 @@ module RuboCop
|
|
|
59
59
|
# Default priority for descriptions that can't be categorized (e.g., constants, variables)
|
|
60
60
|
DEFAULT_PRIORITY = 999
|
|
61
61
|
|
|
62
|
-
MODEL_ORDER = %w(class associations validations).freeze
|
|
63
62
|
CONTROLLER_ACTIONS = %w(index show new create edit update destroy).freeze
|
|
63
|
+
MODEL_ORDER = %w(class associations validations).freeze
|
|
64
64
|
SPECIAL_SECTIONS = {
|
|
65
65
|
"class" => 0,
|
|
66
66
|
"constants" => 5,
|
|
@@ -95,6 +95,7 @@ module RuboCop
|
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
alias on_numblock on_block
|
|
98
|
+
alias on_itblock on_block
|
|
98
99
|
|
|
99
100
|
private
|
|
100
101
|
|
|
@@ -47,8 +47,8 @@ module RuboCop
|
|
|
47
47
|
class ExplicitReturnConditional < Base
|
|
48
48
|
extend AutoCorrector
|
|
49
49
|
|
|
50
|
-
MSG_TERNARY = "Use explicit `if`/`else`/`end` block instead of ternary operator for return value."
|
|
51
50
|
MSG_MODIFIER = "Use explicit `if`/`end` block instead of trailing conditional for return value."
|
|
51
|
+
MSG_TERNARY = "Use explicit `if`/`else`/`end` block instead of ternary operator for return value."
|
|
52
52
|
|
|
53
53
|
# Check method definitions for conditional return values.
|
|
54
54
|
#
|
|
@@ -151,12 +151,13 @@ module RuboCop
|
|
|
151
151
|
# @param [RuboCop::AST::Node] node The modifier if node.
|
|
152
152
|
# @return [String]
|
|
153
153
|
def build_if_block(node)
|
|
154
|
+
keyword = node.unless? && node.condition.type?(:and, :or) ? "unless" : "if"
|
|
154
155
|
condition = build_condition(node)
|
|
155
156
|
base_indent = " " * node.loc.column
|
|
156
157
|
inner_indent = "#{base_indent} "
|
|
157
158
|
|
|
158
159
|
[
|
|
159
|
-
"
|
|
160
|
+
"#{keyword} #{condition}",
|
|
160
161
|
"#{inner_indent}#{node.if_branch.source}",
|
|
161
162
|
"#{base_indent}end"
|
|
162
163
|
].join("\n")
|
|
@@ -168,7 +169,7 @@ module RuboCop
|
|
|
168
169
|
# @param [RuboCop::AST::Node] node The if node.
|
|
169
170
|
# @return [String] The condition source.
|
|
170
171
|
def build_condition(node)
|
|
171
|
-
if node.unless?
|
|
172
|
+
if node.unless? && !node.condition.type?(:and, :or)
|
|
172
173
|
negate_condition(node.condition)
|
|
173
174
|
else
|
|
174
175
|
node.condition.source
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Enforces alphabetical ordering of keyword arguments in method definitions
|
|
7
|
+
# and their associated YARD `@param` documentation tags.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# # bad
|
|
11
|
+
# # @param id [String] the identifier
|
|
12
|
+
# # @param content [String] the body
|
|
13
|
+
# def initialize(id:, content:)
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# # good
|
|
17
|
+
# # @param content [String] the body
|
|
18
|
+
# # @param id [String] the identifier
|
|
19
|
+
# def initialize(content:, id:)
|
|
20
|
+
# end
|
|
21
|
+
class KeywordArgumentOrder < Base
|
|
22
|
+
extend AutoCorrector
|
|
23
|
+
|
|
24
|
+
MSG = "Order keyword arguments alphabetically."
|
|
25
|
+
|
|
26
|
+
# Check method definitions for keyword argument ordering.
|
|
27
|
+
#
|
|
28
|
+
# @param [RuboCop::AST::Node] node The def node.
|
|
29
|
+
# @return [void]
|
|
30
|
+
def on_def(node)
|
|
31
|
+
return unless node.arguments?
|
|
32
|
+
|
|
33
|
+
kwargs = extract_keyword_arguments(node)
|
|
34
|
+
|
|
35
|
+
return if kwargs.size < 2
|
|
36
|
+
return if alphabetically_ordered?(kwargs)
|
|
37
|
+
|
|
38
|
+
add_offense(node.arguments) do |corrector|
|
|
39
|
+
autocorrect(corrector, node, kwargs)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
alias on_defs on_def
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# Extract keyword arguments from a method definition.
|
|
47
|
+
#
|
|
48
|
+
# @param [RuboCop::AST::Node] node The def node.
|
|
49
|
+
# @return [Array<RuboCop::AST::Node>]
|
|
50
|
+
def extract_keyword_arguments(node)
|
|
51
|
+
node.arguments.select { |arg| arg.type?(:kwarg, :kwoptarg) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check if keyword arguments are correctly ordered.
|
|
55
|
+
#
|
|
56
|
+
# Required kwargs come first (alphabetically), then optional kwargs (alphabetically).
|
|
57
|
+
#
|
|
58
|
+
# @param [Array<RuboCop::AST::Node>] kwargs The keyword arguments.
|
|
59
|
+
# @return [Boolean]
|
|
60
|
+
def alphabetically_ordered?(kwargs)
|
|
61
|
+
kwargs == kwargs.sort_by { |arg| kwarg_sort_key(arg) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Generate a sort key for a keyword argument.
|
|
65
|
+
#
|
|
66
|
+
# Required kwargs come first alphabetically, then optional kwargs alphabetically.
|
|
67
|
+
#
|
|
68
|
+
# @param [RuboCop::AST::Node] arg The argument node.
|
|
69
|
+
# @return [Array]
|
|
70
|
+
def kwarg_sort_key(arg)
|
|
71
|
+
# kwargs array is pre-filtered to only include kwarg/kwoptarg types
|
|
72
|
+
if arg.kwarg_type?
|
|
73
|
+
[0, arg.name.to_s]
|
|
74
|
+
else
|
|
75
|
+
[1, arg.name.to_s]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Auto-correct by reordering keyword arguments and documentation.
|
|
80
|
+
#
|
|
81
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
82
|
+
# @param [RuboCop::AST::Node] node The def node.
|
|
83
|
+
# @param [Array<RuboCop::AST::Node>] kwargs The keyword arguments.
|
|
84
|
+
# @return [void]
|
|
85
|
+
def autocorrect(corrector, node, kwargs)
|
|
86
|
+
autocorrect_arguments(corrector, node)
|
|
87
|
+
autocorrect_documentation(corrector, node, kwargs)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Auto-correct keyword arguments in the method signature.
|
|
91
|
+
#
|
|
92
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
93
|
+
# @param [RuboCop::AST::Node] node The def node.
|
|
94
|
+
# @return [void]
|
|
95
|
+
def autocorrect_arguments(corrector, node)
|
|
96
|
+
args = node.arguments.to_a
|
|
97
|
+
sorted_args = args.sort_by { |arg| sort_key(arg) }
|
|
98
|
+
sorted_source = sorted_args.map(&:source).join(", ")
|
|
99
|
+
|
|
100
|
+
args_range = args.first.source_range.join(args.last.source_range)
|
|
101
|
+
|
|
102
|
+
corrector.replace(args_range, sorted_source)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Generate a sort key for an argument.
|
|
106
|
+
#
|
|
107
|
+
# Positional arguments come first, then required keyword arguments
|
|
108
|
+
# alphabetically, then optional keyword arguments alphabetically.
|
|
109
|
+
#
|
|
110
|
+
# @param [RuboCop::AST::Node] arg The argument node.
|
|
111
|
+
# @return [Array]
|
|
112
|
+
def sort_key(arg)
|
|
113
|
+
case arg.type
|
|
114
|
+
when :kwarg
|
|
115
|
+
[1, arg.name.to_s]
|
|
116
|
+
when :kwoptarg
|
|
117
|
+
[2, arg.name.to_s]
|
|
118
|
+
else
|
|
119
|
+
[0, ""]
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Auto-correct YARD @param documentation.
|
|
124
|
+
#
|
|
125
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
126
|
+
# @param [RuboCop::AST::Node] node The def node.
|
|
127
|
+
# @param [Array<RuboCop::AST::Node>] kwargs The keyword arguments.
|
|
128
|
+
# @return [void]
|
|
129
|
+
def autocorrect_documentation(corrector, node, kwargs)
|
|
130
|
+
comments = preceding_comments(node)
|
|
131
|
+
|
|
132
|
+
return if comments.empty?
|
|
133
|
+
|
|
134
|
+
param_comments = extract_param_comments(comments, kwargs)
|
|
135
|
+
|
|
136
|
+
return if param_comments.empty?
|
|
137
|
+
|
|
138
|
+
sorted_kwarg_names = kwargs.sort_by { |arg| sort_key(arg) }.map { |arg| arg.name.to_s }
|
|
139
|
+
|
|
140
|
+
reorder_param_comments(corrector, param_comments, sorted_kwarg_names)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Get comments preceding a node.
|
|
144
|
+
#
|
|
145
|
+
# @param [RuboCop::AST::Node] node The node.
|
|
146
|
+
# @return [Array<Parser::Source::Comment>]
|
|
147
|
+
def preceding_comments(node)
|
|
148
|
+
processed_source.ast_with_comments[node] || []
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Extract @param comments for keyword arguments.
|
|
152
|
+
#
|
|
153
|
+
# @param [Array<Parser::Source::Comment>] comments The comments.
|
|
154
|
+
# @param [Array<RuboCop::AST::Node>] kwargs The keyword arguments.
|
|
155
|
+
# @return [Array<Parser::Source::Comment>]
|
|
156
|
+
def extract_param_comments(comments, kwargs)
|
|
157
|
+
kwarg_names = kwargs.map { |arg| arg.name.to_s }
|
|
158
|
+
|
|
159
|
+
comments.select do |comment|
|
|
160
|
+
match = comment.text.match(/@param\s+(\w+)/)
|
|
161
|
+
|
|
162
|
+
match && kwarg_names.include?(match[1])
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Reorder @param comments to match argument order.
|
|
167
|
+
#
|
|
168
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
169
|
+
# @param [Array<Parser::Source::Comment>] param_comments The param comments.
|
|
170
|
+
# @param [Array<String>] sorted_kwarg_names The sorted keyword argument names.
|
|
171
|
+
# @return [void]
|
|
172
|
+
def reorder_param_comments(corrector, param_comments, sorted_kwarg_names)
|
|
173
|
+
sorted = param_comments.sort_by do |comment|
|
|
174
|
+
match = comment.text.match(/@param\s+(\w+)/)
|
|
175
|
+
|
|
176
|
+
sorted_kwarg_names.index(match[1]) || Float::INFINITY
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
param_comments.each_with_index do |comment, index|
|
|
180
|
+
sorted_comment = sorted[index]
|
|
181
|
+
|
|
182
|
+
next if comment == sorted_comment
|
|
183
|
+
|
|
184
|
+
corrector.replace(comment, sorted_comment.text)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -40,9 +40,9 @@ module RuboCop
|
|
|
40
40
|
MSG = "Do not disable `%<cops>s`. Fix the issue or configure globally in `.rubocop.yml`."
|
|
41
41
|
MSG_NO_COP = "Do not use `# rubocop:disable`. Fix the issue or configure globally in `.rubocop.yml`."
|
|
42
42
|
|
|
43
|
-
DISABLE_PATTERN = /\A#\s*rubocop\s*:\s*disable\b/i
|
|
44
|
-
COP_NAME_PATTERN = %r{[A-Za-z]+/[A-Za-z0-9]+}
|
|
45
43
|
ALL_PATTERN = /\brubocop\s*:\s*disable\s+all\b/i
|
|
44
|
+
COP_NAME_PATTERN = %r{[A-Za-z]+/[A-Za-z0-9]+}
|
|
45
|
+
DISABLE_PATTERN = /\A#\s*rubocop\s*:\s*disable\b/i
|
|
46
46
|
|
|
47
47
|
# Check for rubocop:disable comments.
|
|
48
48
|
#
|
|
@@ -38,12 +38,12 @@ module RuboCop
|
|
|
38
38
|
class NoSkippedTests < Base
|
|
39
39
|
include SpecFileHelper
|
|
40
40
|
|
|
41
|
-
MSG_SKIP = "Do not skip tests. Implement or delete the test."
|
|
42
41
|
MSG_PENDING = "Do not mark tests as pending. Implement or delete the test."
|
|
42
|
+
MSG_SKIP = "Do not skip tests. Implement or delete the test."
|
|
43
43
|
MSG_XMETHOD = "Do not use `%<method>s`. Implement or delete the test."
|
|
44
44
|
|
|
45
|
-
SKIP_METHODS = %i(skip).freeze
|
|
46
45
|
PENDING_METHODS = %i(pending).freeze
|
|
46
|
+
SKIP_METHODS = %i(skip).freeze
|
|
47
47
|
X_METHODS = %i(xit xspecify xexample xscenario xdescribe xcontext xfeature).freeze
|
|
48
48
|
|
|
49
49
|
# @!method skip_call?(node)
|
|
@@ -72,25 +72,28 @@ module RuboCop
|
|
|
72
72
|
# @param [RuboCop::AST::Node] node The if node.
|
|
73
73
|
# @return [String] The replacement code.
|
|
74
74
|
def build_replacement(node)
|
|
75
|
+
keyword = node.unless? && node.condition.type?(:and, :or) ? "unless" : "if"
|
|
75
76
|
condition = build_condition(node)
|
|
76
77
|
raise_source = node.body.source
|
|
77
78
|
base_indent = " " * node.loc.column
|
|
78
79
|
inner_indent = "#{base_indent} "
|
|
79
80
|
|
|
80
81
|
[
|
|
81
|
-
"
|
|
82
|
+
"#{keyword} #{condition}",
|
|
82
83
|
"#{inner_indent}#{raise_source}",
|
|
83
84
|
"#{base_indent}end"
|
|
84
85
|
].join("\n")
|
|
85
86
|
end
|
|
86
87
|
|
|
87
|
-
# Build the condition for the if block.
|
|
88
|
-
# For unless
|
|
88
|
+
# Build the condition for the if/unless block.
|
|
89
|
+
# For unless with a compound condition, keep it as-is (paired with unless keyword).
|
|
90
|
+
# For unless with a simple condition, negate it.
|
|
91
|
+
# For if, keep as-is.
|
|
89
92
|
#
|
|
90
93
|
# @param [RuboCop::AST::Node] node The if node.
|
|
91
94
|
# @return [String] The condition source.
|
|
92
95
|
def build_condition(node)
|
|
93
|
-
if node.unless?
|
|
96
|
+
if node.unless? && !node.condition.type?(:and, :or)
|
|
94
97
|
"!#{node.condition.source}"
|
|
95
98
|
else
|
|
96
99
|
node.condition.source
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Enforces that `validate` calls appear after `validates` declarations.
|
|
7
|
+
#
|
|
8
|
+
# Within a consecutive block of validates-family calls (no blank lines
|
|
9
|
+
# between), all `validate :method` calls must come after all `validates*`
|
|
10
|
+
# declarations. The order of `validate` calls relative to each other is
|
|
11
|
+
# not enforced.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# # bad
|
|
15
|
+
# class User < ApplicationRecord
|
|
16
|
+
# validate :check_expiry
|
|
17
|
+
# validates :name, presence: true
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # good
|
|
21
|
+
# class User < ApplicationRecord
|
|
22
|
+
# validates :name, presence: true
|
|
23
|
+
# validate :check_expiry
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# # good - blank line breaks the group
|
|
27
|
+
# class User < ApplicationRecord
|
|
28
|
+
# validate :check_expiry
|
|
29
|
+
#
|
|
30
|
+
# validates :name, presence: true
|
|
31
|
+
# end
|
|
32
|
+
class ValidateAfterValidates < Base
|
|
33
|
+
extend AutoCorrector
|
|
34
|
+
include AlignmentHelpers
|
|
35
|
+
|
|
36
|
+
MSG = "Place `validate` calls after `validates` declarations."
|
|
37
|
+
|
|
38
|
+
VALIDATES_METHODS = %i(
|
|
39
|
+
validates validates_each validates_with
|
|
40
|
+
validates_absence_of validates_acceptance_of validates_confirmation_of
|
|
41
|
+
validates_exclusion_of validates_format_of validates_inclusion_of
|
|
42
|
+
validates_length_of validates_numericality_of validates_presence_of
|
|
43
|
+
validates_size_of validates_uniqueness_of validates_associated
|
|
44
|
+
).freeze
|
|
45
|
+
|
|
46
|
+
VALIDATE_METHOD = :validate
|
|
47
|
+
|
|
48
|
+
# Check class nodes for validate/validates ordering.
|
|
49
|
+
#
|
|
50
|
+
# @param [RuboCop::AST::Node] node The class node.
|
|
51
|
+
# @return [void]
|
|
52
|
+
def on_class(node)
|
|
53
|
+
return unless rails_model?(node)
|
|
54
|
+
return unless node.body
|
|
55
|
+
|
|
56
|
+
check_validates_in_body(node.body)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# Check validates-family declarations in a body node.
|
|
62
|
+
#
|
|
63
|
+
# @param [RuboCop::AST::Node] body The body node.
|
|
64
|
+
# @return [void]
|
|
65
|
+
def check_validates_in_body(body)
|
|
66
|
+
statements = extract_statements(body)
|
|
67
|
+
|
|
68
|
+
return if statements.size < 2
|
|
69
|
+
|
|
70
|
+
groups = group_consecutive_statements(statements) { |s| validates_family?(s) }
|
|
71
|
+
|
|
72
|
+
groups.each { |group| check_group_order(group) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Check if a node is a validates-family declaration.
|
|
76
|
+
#
|
|
77
|
+
# @param [RuboCop::AST::Node] node The node to check.
|
|
78
|
+
# @return [Boolean]
|
|
79
|
+
def validates_family?(node)
|
|
80
|
+
if node.send_type?
|
|
81
|
+
VALIDATES_METHODS.include?(node.method_name) || node.method?(VALIDATE_METHOD)
|
|
82
|
+
else
|
|
83
|
+
false
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Check ordering for a group of validates-family declarations.
|
|
88
|
+
#
|
|
89
|
+
# @param [Array<RuboCop::AST::Node>] group The validates group.
|
|
90
|
+
# @return [void]
|
|
91
|
+
def check_group_order(group)
|
|
92
|
+
violations = find_violations(group)
|
|
93
|
+
|
|
94
|
+
return if violations.empty?
|
|
95
|
+
|
|
96
|
+
violations.each do |validate|
|
|
97
|
+
add_offense(validate) do |corrector|
|
|
98
|
+
autocorrect(corrector, group)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Find validate calls that appear before validates* declarations.
|
|
104
|
+
#
|
|
105
|
+
# @param [Array<RuboCop::AST::Node>] group The validates group.
|
|
106
|
+
# @return [Array<RuboCop::AST::Node>] Validate nodes that violate ordering.
|
|
107
|
+
def find_violations(group)
|
|
108
|
+
last_validates_index = group.rindex { |n| VALIDATES_METHODS.include?(n.method_name) }
|
|
109
|
+
|
|
110
|
+
return [] if last_validates_index.nil?
|
|
111
|
+
|
|
112
|
+
group.each_with_index.filter_map do |node, index|
|
|
113
|
+
node if index < last_validates_index && node.method?(VALIDATE_METHOD)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Auto-correct by moving validate calls after validates* declarations.
|
|
118
|
+
#
|
|
119
|
+
# Preserves relative order within each tier.
|
|
120
|
+
#
|
|
121
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
122
|
+
# @param [Array<RuboCop::AST::Node>] group The validates group.
|
|
123
|
+
# @return [void]
|
|
124
|
+
def autocorrect(corrector, group)
|
|
125
|
+
validates_nodes = group.select { |n| VALIDATES_METHODS.include?(n.method_name) }
|
|
126
|
+
validate_nodes = group.select { |n| n.method?(VALIDATE_METHOD) }
|
|
127
|
+
sorted = validates_nodes + validate_nodes
|
|
128
|
+
|
|
129
|
+
group.each_with_index do |node, index|
|
|
130
|
+
sorted_node = sorted[index]
|
|
131
|
+
|
|
132
|
+
next if node == sorted_node
|
|
133
|
+
|
|
134
|
+
corrector.replace(node, sorted_node.source)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Check if this is a Rails model.
|
|
139
|
+
#
|
|
140
|
+
# @param [RuboCop::AST::Node] node The class node.
|
|
141
|
+
# @return [Boolean]
|
|
142
|
+
def rails_model?(node)
|
|
143
|
+
if node.parent_class
|
|
144
|
+
parent_name = node.parent_class.const_name.to_s
|
|
145
|
+
parent_name == "ApplicationRecord" ||
|
|
146
|
+
parent_name == "ActiveRecord::Base" ||
|
|
147
|
+
parent_name.end_with?("::ApplicationRecord")
|
|
148
|
+
else
|
|
149
|
+
false
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Enforces alphabetical ordering of consecutive `validates` declarations.
|
|
7
|
+
#
|
|
8
|
+
# Consecutive `validates` declarations (with no blank lines between) should be
|
|
9
|
+
# alphabetically ordered by the first attribute name for better readability and
|
|
10
|
+
# easier scanning. Groups are broken by blank lines or non-validates statements.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad
|
|
14
|
+
# class User < ApplicationRecord
|
|
15
|
+
# validates :name, presence: true
|
|
16
|
+
# validates :age, numericality: true
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# # good
|
|
20
|
+
# class User < ApplicationRecord
|
|
21
|
+
# validates :age, numericality: true
|
|
22
|
+
# validates :name, presence: true
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# # good - blank line breaks the group
|
|
26
|
+
# class User < ApplicationRecord
|
|
27
|
+
# validates :z, presence: true
|
|
28
|
+
#
|
|
29
|
+
# validates :a, presence: true
|
|
30
|
+
# end
|
|
31
|
+
class ValidatesAlphaOrder < Base
|
|
32
|
+
extend AutoCorrector
|
|
33
|
+
include AlignmentHelpers
|
|
34
|
+
|
|
35
|
+
MSG = "Order validates declarations alphabetically by attribute name."
|
|
36
|
+
|
|
37
|
+
VALIDATES_METHODS = %i(
|
|
38
|
+
validates validates_each validates_with
|
|
39
|
+
validates_absence_of validates_acceptance_of validates_confirmation_of
|
|
40
|
+
validates_exclusion_of validates_format_of validates_inclusion_of
|
|
41
|
+
validates_length_of validates_numericality_of validates_presence_of
|
|
42
|
+
validates_size_of validates_uniqueness_of validates_associated
|
|
43
|
+
).freeze
|
|
44
|
+
|
|
45
|
+
# Check class nodes for validates ordering.
|
|
46
|
+
#
|
|
47
|
+
# @param [RuboCop::AST::Node] node The class node.
|
|
48
|
+
# @return [void]
|
|
49
|
+
def on_class(node)
|
|
50
|
+
return unless rails_model?(node)
|
|
51
|
+
return unless node.body
|
|
52
|
+
|
|
53
|
+
check_validates_in_body(node.body)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# Check validates declarations in a body node.
|
|
59
|
+
#
|
|
60
|
+
# @param [RuboCop::AST::Node] body The body node.
|
|
61
|
+
# @return [void]
|
|
62
|
+
def check_validates_in_body(body)
|
|
63
|
+
statements = extract_statements(body)
|
|
64
|
+
|
|
65
|
+
return if statements.size < 2
|
|
66
|
+
|
|
67
|
+
groups = group_consecutive_statements(statements) { |s| validates_declaration?(s) }
|
|
68
|
+
|
|
69
|
+
groups.each { |group| check_group_order(group) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check if a node is a validates declaration.
|
|
73
|
+
#
|
|
74
|
+
# @param [RuboCop::AST::Node] node The node to check.
|
|
75
|
+
# @return [Boolean]
|
|
76
|
+
def validates_declaration?(node)
|
|
77
|
+
if node.send_type?
|
|
78
|
+
VALIDATES_METHODS.include?(node.method_name)
|
|
79
|
+
else
|
|
80
|
+
false
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Check ordering for a group of validates declarations.
|
|
85
|
+
#
|
|
86
|
+
# @param [Array<RuboCop::AST::Node>] group The validates group.
|
|
87
|
+
# @return [void]
|
|
88
|
+
def check_group_order(group)
|
|
89
|
+
return if alphabetically_ordered?(group)
|
|
90
|
+
|
|
91
|
+
violations = find_ordering_violations(group)
|
|
92
|
+
|
|
93
|
+
violations.each do |validates|
|
|
94
|
+
add_offense(validates) do |corrector|
|
|
95
|
+
autocorrect(corrector, group)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Check if validates declarations are alphabetically ordered.
|
|
101
|
+
#
|
|
102
|
+
# @param [Array<RuboCop::AST::Node>] group The validates group.
|
|
103
|
+
# @return [Boolean]
|
|
104
|
+
def alphabetically_ordered?(group)
|
|
105
|
+
names = group.map { |v| extract_validates_name(v) }
|
|
106
|
+
|
|
107
|
+
names == names.sort
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Extract the attribute name from a validates declaration.
|
|
111
|
+
#
|
|
112
|
+
# @param [RuboCop::AST::Node] node The validates node.
|
|
113
|
+
# @return [String]
|
|
114
|
+
def extract_validates_name(node)
|
|
115
|
+
node.first_argument.value.to_s
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Find validates declarations that violate ordering.
|
|
119
|
+
#
|
|
120
|
+
# @param [Array<RuboCop::AST::Node>] group The validates group.
|
|
121
|
+
# @return [Array<RuboCop::AST::Node>] Validates that violate ordering.
|
|
122
|
+
def find_ordering_violations(group)
|
|
123
|
+
violations = []
|
|
124
|
+
|
|
125
|
+
group.each_cons(2) do |current, following|
|
|
126
|
+
violations << following if extract_validates_name(current) > extract_validates_name(following)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
violations.uniq
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Auto-correct by reordering validates declarations.
|
|
133
|
+
#
|
|
134
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
135
|
+
# @param [Array<RuboCop::AST::Node>] group The validates group.
|
|
136
|
+
# @return [void]
|
|
137
|
+
def autocorrect(corrector, group)
|
|
138
|
+
sorted = group.sort_by { |v| extract_validates_name(v) }
|
|
139
|
+
|
|
140
|
+
group.each_with_index do |validates, index|
|
|
141
|
+
sorted_validates = sorted[index]
|
|
142
|
+
|
|
143
|
+
next if validates == sorted_validates
|
|
144
|
+
|
|
145
|
+
corrector.replace(validates, sorted_validates.source)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Check if this is a Rails model.
|
|
150
|
+
#
|
|
151
|
+
# @param [RuboCop::AST::Node] node The class node.
|
|
152
|
+
# @return [Boolean]
|
|
153
|
+
def rails_model?(node)
|
|
154
|
+
if node.parent_class
|
|
155
|
+
parent_name = node.parent_class.const_name.to_s
|
|
156
|
+
parent_name == "ApplicationRecord" ||
|
|
157
|
+
parent_name == "ActiveRecord::Base" ||
|
|
158
|
+
parent_name.end_with?("::ApplicationRecord")
|
|
159
|
+
else
|
|
160
|
+
false
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "vibe/mixin/alignment_helpers"
|
|
4
4
|
require_relative "vibe/mixin/spec_file_helper"
|
|
5
5
|
|
|
6
|
+
require_relative "vibe/attr_order"
|
|
6
7
|
require_relative "vibe/blank_line_after_assignment"
|
|
7
8
|
require_relative "vibe/blank_line_before_expectation"
|
|
8
9
|
require_relative "vibe/class_organization"
|
|
@@ -11,9 +12,12 @@ require_relative "vibe/consecutive_constant_alignment"
|
|
|
11
12
|
require_relative "vibe/consecutive_indexed_assignment_alignment"
|
|
12
13
|
require_relative "vibe/consecutive_instance_variable_assignment_alignment"
|
|
13
14
|
require_relative "vibe/consecutive_let_alignment"
|
|
15
|
+
require_relative "vibe/consecutive_scope_alignment"
|
|
16
|
+
require_relative "vibe/constant_alpha_order"
|
|
14
17
|
require_relative "vibe/describe_block_order"
|
|
15
18
|
require_relative "vibe/explicit_return_conditional"
|
|
16
19
|
require_relative "vibe/is_expected_one_liner"
|
|
20
|
+
require_relative "vibe/keyword_argument_order"
|
|
17
21
|
require_relative "vibe/let_order"
|
|
18
22
|
require_relative "vibe/multiline_hash_argument_style"
|
|
19
23
|
require_relative "vibe/no_assigns_attribute_testing"
|
|
@@ -26,3 +30,5 @@ require_relative "vibe/raise_unless_block"
|
|
|
26
30
|
require_relative "vibe/rspec_before_block_style"
|
|
27
31
|
require_relative "vibe/rspec_stub_chain_style"
|
|
28
32
|
require_relative "vibe/service_call_method"
|
|
33
|
+
require_relative "vibe/validate_after_validates"
|
|
34
|
+
require_relative "vibe/validates_alpha_order"
|
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.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tristan Dunn
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 1.
|
|
32
|
+
version: 1.85.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 1.
|
|
39
|
+
version: 1.85.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: rubocop-performance
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -71,14 +71,14 @@ dependencies:
|
|
|
71
71
|
requirements:
|
|
72
72
|
- - ">="
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: 3.
|
|
74
|
+
version: 3.9.0
|
|
75
75
|
type: :runtime
|
|
76
76
|
prerelease: false
|
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: 3.
|
|
81
|
+
version: 3.9.0
|
|
82
82
|
description: A set of custom cops to use on AI generated code.
|
|
83
83
|
email: hello@tristandunn.com
|
|
84
84
|
executables: []
|
|
@@ -87,6 +87,7 @@ extra_rdoc_files: []
|
|
|
87
87
|
files:
|
|
88
88
|
- config/default.yml
|
|
89
89
|
- lib/rubocop-vibe.rb
|
|
90
|
+
- lib/rubocop/cop/vibe/attr_order.rb
|
|
90
91
|
- lib/rubocop/cop/vibe/blank_line_after_assignment.rb
|
|
91
92
|
- lib/rubocop/cop/vibe/blank_line_before_expectation.rb
|
|
92
93
|
- lib/rubocop/cop/vibe/class_organization.rb
|
|
@@ -95,9 +96,12 @@ files:
|
|
|
95
96
|
- lib/rubocop/cop/vibe/consecutive_indexed_assignment_alignment.rb
|
|
96
97
|
- lib/rubocop/cop/vibe/consecutive_instance_variable_assignment_alignment.rb
|
|
97
98
|
- lib/rubocop/cop/vibe/consecutive_let_alignment.rb
|
|
99
|
+
- lib/rubocop/cop/vibe/consecutive_scope_alignment.rb
|
|
100
|
+
- lib/rubocop/cop/vibe/constant_alpha_order.rb
|
|
98
101
|
- lib/rubocop/cop/vibe/describe_block_order.rb
|
|
99
102
|
- lib/rubocop/cop/vibe/explicit_return_conditional.rb
|
|
100
103
|
- lib/rubocop/cop/vibe/is_expected_one_liner.rb
|
|
104
|
+
- lib/rubocop/cop/vibe/keyword_argument_order.rb
|
|
101
105
|
- lib/rubocop/cop/vibe/let_order.rb
|
|
102
106
|
- lib/rubocop/cop/vibe/mixin/alignment_helpers.rb
|
|
103
107
|
- lib/rubocop/cop/vibe/mixin/spec_file_helper.rb
|
|
@@ -112,6 +116,8 @@ files:
|
|
|
112
116
|
- lib/rubocop/cop/vibe/rspec_before_block_style.rb
|
|
113
117
|
- lib/rubocop/cop/vibe/rspec_stub_chain_style.rb
|
|
114
118
|
- lib/rubocop/cop/vibe/service_call_method.rb
|
|
119
|
+
- lib/rubocop/cop/vibe/validate_after_validates.rb
|
|
120
|
+
- lib/rubocop/cop/vibe/validates_alpha_order.rb
|
|
115
121
|
- lib/rubocop/cop/vibe_cops.rb
|
|
116
122
|
- lib/rubocop/vibe.rb
|
|
117
123
|
- lib/rubocop/vibe/plugin.rb
|
|
@@ -138,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
138
144
|
- !ruby/object:Gem::Version
|
|
139
145
|
version: '0'
|
|
140
146
|
requirements: []
|
|
141
|
-
rubygems_version: 4.0.
|
|
147
|
+
rubygems_version: 4.0.5
|
|
142
148
|
specification_version: 4
|
|
143
149
|
summary: A set of custom cops to use on AI generated code.
|
|
144
150
|
test_files: []
|