rubocop-vibe 0.3.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 +48 -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 +124 -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 +148 -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 +8 -0
- data/lib/rubocop/vibe/version.rb +1 -1
- metadata +14 -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
|
|
@@ -98,12 +104,30 @@ Vibe/ConsecutiveIndexedAssignmentAlignment:
|
|
|
98
104
|
SafeAutoCorrect: true
|
|
99
105
|
VersionAdded: "0.3.0"
|
|
100
106
|
|
|
107
|
+
Vibe/ConsecutiveInstanceVariableAssignmentAlignment:
|
|
108
|
+
Description: "Enforces alignment of consecutive instance variable assignments at the = operator."
|
|
109
|
+
Enabled: true
|
|
110
|
+
SafeAutoCorrect: true
|
|
111
|
+
VersionAdded: "0.4.0"
|
|
112
|
+
|
|
101
113
|
Vibe/ConsecutiveLetAlignment:
|
|
102
114
|
Description: "Enforces alignment of consecutive let declarations at the { brace."
|
|
103
115
|
Enabled: true
|
|
104
116
|
SafeAutoCorrect: true
|
|
105
117
|
VersionAdded: "0.2.0"
|
|
106
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
|
+
|
|
107
131
|
Vibe/DescribeBlockOrder:
|
|
108
132
|
Description: "Enforces consistent ordering of describe blocks in RSpec files."
|
|
109
133
|
Enabled: true
|
|
@@ -122,6 +146,18 @@ Vibe/IsExpectedOneLiner:
|
|
|
122
146
|
SafeAutoCorrect: true
|
|
123
147
|
VersionAdded: "0.1.0"
|
|
124
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
|
+
|
|
155
|
+
Vibe/LetOrder:
|
|
156
|
+
Description: "Enforces alphabetical ordering of consecutive let declarations."
|
|
157
|
+
Enabled: true
|
|
158
|
+
SafeAutoCorrect: true
|
|
159
|
+
VersionAdded: "0.4.0"
|
|
160
|
+
|
|
125
161
|
Vibe/MultilineHashArgumentStyle:
|
|
126
162
|
Description: "Enforces one-per-line and alphabetical ordering for hash arguments in multiline method calls."
|
|
127
163
|
Enabled: true
|
|
@@ -184,3 +220,15 @@ Vibe/ServiceCallMethod:
|
|
|
184
220
|
Description: "Service objects should define `self.call` and `call` methods."
|
|
185
221
|
Enabled: true
|
|
186
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,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Enforces alignment of consecutive instance variable assignments at the `=` operator.
|
|
7
|
+
#
|
|
8
|
+
# Consecutive assignments (with no blank lines between) should align their
|
|
9
|
+
# `=` operators for better readability. Groups are broken by blank lines.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# # bad
|
|
13
|
+
# @user = create(:user)
|
|
14
|
+
# @character = create(:character)
|
|
15
|
+
# @input = "test"
|
|
16
|
+
#
|
|
17
|
+
# # good
|
|
18
|
+
# @user = create(:user)
|
|
19
|
+
# @character = create(:character)
|
|
20
|
+
# @input = "test"
|
|
21
|
+
#
|
|
22
|
+
# # good - blank line breaks the group
|
|
23
|
+
# @user = create(:user)
|
|
24
|
+
# @character = create(:character)
|
|
25
|
+
#
|
|
26
|
+
# @service = Users::Activate.new
|
|
27
|
+
# @activation = service.call
|
|
28
|
+
class ConsecutiveInstanceVariableAssignmentAlignment < Base
|
|
29
|
+
extend AutoCorrector
|
|
30
|
+
include AlignmentHelpers
|
|
31
|
+
|
|
32
|
+
MSG = "Align consecutive instance variable assignments at the = operator."
|
|
33
|
+
|
|
34
|
+
# Check block nodes for assignment alignment.
|
|
35
|
+
#
|
|
36
|
+
# @param [RuboCop::AST::Node] node The block node.
|
|
37
|
+
# @return [void]
|
|
38
|
+
def on_block(node)
|
|
39
|
+
if node.body
|
|
40
|
+
check_assignments_in_body(node.body)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
alias on_numblock on_block
|
|
44
|
+
alias on_itblock on_block
|
|
45
|
+
|
|
46
|
+
# Check method definitions for assignment alignment.
|
|
47
|
+
#
|
|
48
|
+
# @param [RuboCop::AST::Node] node The def node.
|
|
49
|
+
# @return [void]
|
|
50
|
+
def on_def(node)
|
|
51
|
+
if node.body
|
|
52
|
+
check_assignments_in_body(node.body)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
alias on_defs on_def
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Check assignments in a body node.
|
|
60
|
+
#
|
|
61
|
+
# @param [RuboCop::AST::Node] body The body node.
|
|
62
|
+
# @return [void]
|
|
63
|
+
def check_assignments_in_body(body)
|
|
64
|
+
statements = extract_statements(body)
|
|
65
|
+
|
|
66
|
+
return if statements.size < 2
|
|
67
|
+
|
|
68
|
+
groups = group_consecutive_statements(statements, &:ivasgn_type?)
|
|
69
|
+
|
|
70
|
+
groups.each { |group| check_group_alignment(group) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check alignment for a group of assignments.
|
|
74
|
+
#
|
|
75
|
+
# @param [Array<RuboCop::AST::Node>] group The assignment group.
|
|
76
|
+
# @return [void]
|
|
77
|
+
def check_group_alignment(group)
|
|
78
|
+
columns = group.map { |asgn| asgn.loc.operator.column }
|
|
79
|
+
target_column = columns.max
|
|
80
|
+
|
|
81
|
+
group.each do |asgn|
|
|
82
|
+
current_column = asgn.loc.operator.column
|
|
83
|
+
|
|
84
|
+
next if current_column == target_column
|
|
85
|
+
|
|
86
|
+
add_offense(asgn.loc.name) do |corrector|
|
|
87
|
+
autocorrect_alignment(corrector, asgn, target_column)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Auto-correct the alignment of an assignment.
|
|
93
|
+
#
|
|
94
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
95
|
+
# @param [RuboCop::AST::Node] asgn The assignment node.
|
|
96
|
+
# @param [Integer] target_column The target column for alignment.
|
|
97
|
+
# @return [void]
|
|
98
|
+
def autocorrect_alignment(corrector, asgn, target_column)
|
|
99
|
+
variable_name_end = asgn.loc.name.end_pos
|
|
100
|
+
operator_start = asgn.loc.operator.begin_pos
|
|
101
|
+
total_spaces = calculate_total_spaces(asgn, target_column)
|
|
102
|
+
|
|
103
|
+
corrector.replace(
|
|
104
|
+
range_between(variable_name_end, operator_start),
|
|
105
|
+
" " * total_spaces
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Calculate total spaces needed for alignment.
|
|
110
|
+
#
|
|
111
|
+
# @param [RuboCop::AST::Node] asgn The assignment node.
|
|
112
|
+
# @param [Integer] target_column The target column for alignment.
|
|
113
|
+
# @return [Integer] The number of spaces (minimum 1).
|
|
114
|
+
def calculate_total_spaces(asgn, target_column)
|
|
115
|
+
current_column = asgn.loc.operator.column
|
|
116
|
+
current_spaces = asgn.loc.operator.begin_pos - asgn.loc.name.end_pos
|
|
117
|
+
spaces_needed = target_column - current_column
|
|
118
|
+
|
|
119
|
+
[1, current_spaces + spaces_needed].max
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -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
|