rubocop-vibe 0.2.0 → 0.3.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 +39 -4
- data/lib/rubocop/cop/vibe/blank_line_after_assignment.rb +217 -0
- data/lib/rubocop/cop/vibe/blank_line_before_expectation.rb +6 -1
- data/lib/rubocop/cop/vibe/class_organization.rb +20 -1
- data/lib/rubocop/cop/vibe/consecutive_assignment_alignment.rb +5 -102
- data/lib/rubocop/cop/vibe/consecutive_constant_alignment.rb +5 -113
- data/lib/rubocop/cop/vibe/consecutive_indexed_assignment_alignment.rb +145 -0
- data/lib/rubocop/cop/vibe/consecutive_let_alignment.rb +5 -94
- data/lib/rubocop/cop/vibe/describe_block_order.rb +6 -2
- data/lib/rubocop/cop/vibe/explicit_return_conditional.rb +192 -0
- data/lib/rubocop/cop/vibe/is_expected_one_liner.rb +3 -0
- data/lib/rubocop/cop/vibe/mixin/alignment_helpers.rb +92 -0
- data/lib/rubocop/cop/vibe/multiline_hash_argument_style.rb +171 -0
- data/lib/rubocop/cop/vibe/no_compound_conditions.rb +138 -0
- data/lib/rubocop/cop/vibe/no_rubocop_disable.rb +3 -0
- data/lib/rubocop/cop/vibe/no_skipped_tests.rb +1 -0
- data/lib/rubocop/cop/vibe/no_unless_guard_clause.rb +4 -0
- data/lib/rubocop/cop/vibe/prefer_one_liner_expectation.rb +4 -0
- data/lib/rubocop/cop/vibe/raise_unless_block.rb +1 -0
- data/lib/rubocop/cop/vibe/rspec_before_block_style.rb +114 -0
- data/lib/rubocop/cop/vibe/rspec_stub_chain_style.rb +4 -0
- data/lib/rubocop/cop/vibe_cops.rb +7 -0
- data/lib/rubocop/vibe/plugin.rb +4 -4
- data/lib/rubocop/vibe/version.rb +1 -1
- metadata +9 -2
|
@@ -27,6 +27,7 @@ module RuboCop
|
|
|
27
27
|
# DEFAULT_VALUE = 0 # Separate group, not aligned
|
|
28
28
|
class ConsecutiveConstantAlignment < Base
|
|
29
29
|
extend AutoCorrector
|
|
30
|
+
include AlignmentHelpers
|
|
30
31
|
|
|
31
32
|
MSG = "Align consecutive constant assignments at the `=` operator."
|
|
32
33
|
|
|
@@ -58,109 +59,12 @@ module RuboCop
|
|
|
58
59
|
# @return [void]
|
|
59
60
|
def check_constants_in_body(body)
|
|
60
61
|
statements = extract_statements(body)
|
|
61
|
-
return if statements.size < 2
|
|
62
|
-
|
|
63
|
-
groups = group_consecutive_constants(statements)
|
|
64
|
-
groups.each { |group| check_group_alignment(group) }
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Extract statements from a body node.
|
|
68
|
-
#
|
|
69
|
-
# @param [RuboCop::AST::Node] body The body node.
|
|
70
|
-
# @return [Array<RuboCop::AST::Node>]
|
|
71
|
-
def extract_statements(body)
|
|
72
|
-
if body.begin_type?
|
|
73
|
-
body.children
|
|
74
|
-
else
|
|
75
|
-
[body]
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Group consecutive constant assignments together.
|
|
80
|
-
#
|
|
81
|
-
# @param [Array<RuboCop::AST::Node>] statements The statements.
|
|
82
|
-
# @return [Array<Array<RuboCop::AST::Node>>] Groups of consecutive constants.
|
|
83
|
-
def group_consecutive_constants(statements)
|
|
84
|
-
groups = []
|
|
85
|
-
current_group = []
|
|
86
|
-
previous_line = nil
|
|
87
|
-
|
|
88
|
-
statements.each do |statement|
|
|
89
|
-
current_group, previous_line = process_statement(statement, current_group, previous_line, groups)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
finalize_groups(groups, current_group)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Process a single statement for grouping.
|
|
96
|
-
#
|
|
97
|
-
# @param [RuboCop::AST::Node] statement The statement.
|
|
98
|
-
# @param [Array<RuboCop::AST::Node>] current_group The current group.
|
|
99
|
-
# @param [Integer, nil] previous_line The previous line number.
|
|
100
|
-
# @param [Array<Array<RuboCop::AST::Node>>] groups The groups.
|
|
101
|
-
# @return [Array] The updated current_group and previous_line.
|
|
102
|
-
def process_statement(statement, current_group, previous_line, groups)
|
|
103
|
-
if constant_assignment?(statement)
|
|
104
|
-
current_group = handle_constant(statement, current_group, previous_line, groups)
|
|
105
|
-
else
|
|
106
|
-
save_group_if_valid(groups, current_group)
|
|
107
|
-
current_group = []
|
|
108
|
-
end
|
|
109
|
-
[current_group, statement.loc.last_line]
|
|
110
|
-
end
|
|
111
62
|
|
|
112
|
-
|
|
113
|
-
#
|
|
114
|
-
# Only single-line constants are considered for alignment to avoid
|
|
115
|
-
# conflicts with multi-line hash/array constants and Layout/ExtraSpacing.
|
|
116
|
-
#
|
|
117
|
-
# @param [RuboCop::AST::Node] node The node.
|
|
118
|
-
# @return [Boolean]
|
|
119
|
-
def constant_assignment?(node)
|
|
120
|
-
node.casgn_type? && single_line?(node)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Check if node is on a single line.
|
|
124
|
-
#
|
|
125
|
-
# @param [RuboCop::AST::Node] node The node.
|
|
126
|
-
# @return [Boolean]
|
|
127
|
-
def single_line?(node)
|
|
128
|
-
node.single_line?
|
|
129
|
-
end
|
|
63
|
+
return if statements.size < 2
|
|
130
64
|
|
|
131
|
-
|
|
132
|
-
#
|
|
133
|
-
# @param [RuboCop::AST::Node] statement The constant assignment.
|
|
134
|
-
# @param [Array<RuboCop::AST::Node>] current_group The current group.
|
|
135
|
-
# @param [Integer, nil] previous_line The previous line number.
|
|
136
|
-
# @param [Array<Array<RuboCop::AST::Node>>] groups The groups.
|
|
137
|
-
# @return [Array<RuboCop::AST::Node>] The updated current group.
|
|
138
|
-
def handle_constant(statement, current_group, previous_line, groups)
|
|
139
|
-
if previous_line && statement.loc.line - previous_line > 1
|
|
140
|
-
save_group_if_valid(groups, current_group)
|
|
141
|
-
current_group = []
|
|
142
|
-
end
|
|
143
|
-
current_group << statement
|
|
144
|
-
current_group
|
|
145
|
-
end
|
|
65
|
+
groups = group_consecutive_statements(statements) { |s| s.casgn_type? && s.single_line? }
|
|
146
66
|
|
|
147
|
-
|
|
148
|
-
#
|
|
149
|
-
# @param [Array<Array<RuboCop::AST::Node>>] groups The groups.
|
|
150
|
-
# @param [Array<RuboCop::AST::Node>] group The group to potentially save.
|
|
151
|
-
# @return [void]
|
|
152
|
-
def save_group_if_valid(groups, group)
|
|
153
|
-
groups << group if group.size > 1
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
# Finalize groups by adding any remaining valid group.
|
|
157
|
-
#
|
|
158
|
-
# @param [Array<Array<RuboCop::AST::Node>>] groups The groups.
|
|
159
|
-
# @param [Array<RuboCop::AST::Node>] current_group The current group.
|
|
160
|
-
# @return [Array<Array<RuboCop::AST::Node>>] The finalized groups.
|
|
161
|
-
def finalize_groups(groups, current_group)
|
|
162
|
-
save_group_if_valid(groups, current_group)
|
|
163
|
-
groups
|
|
67
|
+
groups.each { |group| check_group_alignment(group) }
|
|
164
68
|
end
|
|
165
69
|
|
|
166
70
|
# Check alignment for a group of constant assignments.
|
|
@@ -173,6 +77,7 @@ module RuboCop
|
|
|
173
77
|
|
|
174
78
|
group.each do |const|
|
|
175
79
|
current_column = const.loc.operator.column
|
|
80
|
+
|
|
176
81
|
next if current_column == target_column
|
|
177
82
|
|
|
178
83
|
add_offense(const.loc.name) do |corrector|
|
|
@@ -210,19 +115,6 @@ module RuboCop
|
|
|
210
115
|
|
|
211
116
|
[1, current_spaces + spaces_needed].max
|
|
212
117
|
end
|
|
213
|
-
|
|
214
|
-
# Create a source range between two positions.
|
|
215
|
-
#
|
|
216
|
-
# @param [Integer] start_pos The start position.
|
|
217
|
-
# @param [Integer] end_pos The end position.
|
|
218
|
-
# @return [Parser::Source::Range]
|
|
219
|
-
def range_between(start_pos, end_pos)
|
|
220
|
-
Parser::Source::Range.new(
|
|
221
|
-
processed_source.buffer,
|
|
222
|
-
start_pos,
|
|
223
|
-
end_pos
|
|
224
|
-
)
|
|
225
|
-
end
|
|
226
118
|
end
|
|
227
119
|
end
|
|
228
120
|
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Enforces alignment of consecutive indexed assignments at the `=` operator.
|
|
7
|
+
#
|
|
8
|
+
# Consecutive indexed assignments (with no blank lines between) should align
|
|
9
|
+
# their `=` operators for better readability. Groups are broken by blank lines.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# # bad
|
|
13
|
+
# response.headers["Cache-Control"] = "public, max-age=3600"
|
|
14
|
+
# response.headers["Content-Type"] = "application/javascript"
|
|
15
|
+
#
|
|
16
|
+
# # good
|
|
17
|
+
# response.headers["Cache-Control"] = "public, max-age=3600"
|
|
18
|
+
# response.headers["Content-Type"] = "application/javascript"
|
|
19
|
+
#
|
|
20
|
+
# # good - blank line breaks the group
|
|
21
|
+
# response.headers["Cache-Control"] = "public, max-age=3600"
|
|
22
|
+
#
|
|
23
|
+
# hash["key"] = "value" # Separate group, not aligned
|
|
24
|
+
class ConsecutiveIndexedAssignmentAlignment < Base
|
|
25
|
+
extend AutoCorrector
|
|
26
|
+
include AlignmentHelpers
|
|
27
|
+
|
|
28
|
+
MSG = "Align consecutive indexed assignments at the = operator."
|
|
29
|
+
|
|
30
|
+
# Check block nodes for indexed assignment alignment.
|
|
31
|
+
#
|
|
32
|
+
# @param [RuboCop::AST::Node] node The block node.
|
|
33
|
+
# @return [void]
|
|
34
|
+
def on_block(node)
|
|
35
|
+
if node.body
|
|
36
|
+
check_indexed_assignments_in_body(node.body)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
alias on_numblock on_block
|
|
40
|
+
|
|
41
|
+
# Check method definitions for indexed assignment alignment.
|
|
42
|
+
#
|
|
43
|
+
# @param [RuboCop::AST::Node] node The def node.
|
|
44
|
+
# @return [void]
|
|
45
|
+
def on_def(node)
|
|
46
|
+
if node.body
|
|
47
|
+
check_indexed_assignments_in_body(node.body)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
alias on_defs on_def
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# Check indexed assignments in a body node.
|
|
55
|
+
#
|
|
56
|
+
# @param [RuboCop::AST::Node] body The body node.
|
|
57
|
+
# @return [void]
|
|
58
|
+
def check_indexed_assignments_in_body(body)
|
|
59
|
+
statements = extract_statements(body)
|
|
60
|
+
|
|
61
|
+
return if statements.size < 2
|
|
62
|
+
|
|
63
|
+
groups = group_consecutive_statements(statements) { |s| indexed_assignment?(s) }
|
|
64
|
+
|
|
65
|
+
groups.each { |group| check_group_alignment(group) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check if a node is an indexed assignment.
|
|
69
|
+
#
|
|
70
|
+
# @param [RuboCop::AST::Node] node The node to check.
|
|
71
|
+
# @return [Boolean]
|
|
72
|
+
def indexed_assignment?(node)
|
|
73
|
+
node.send_type? && node.method?(:[]=)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Check alignment for a group of indexed assignments.
|
|
77
|
+
#
|
|
78
|
+
# @param [Array<RuboCop::AST::Node>] group The assignment group.
|
|
79
|
+
# @return [void]
|
|
80
|
+
def check_group_alignment(group)
|
|
81
|
+
columns = group.map { |asgn| asgn.loc.operator.column }
|
|
82
|
+
target_column = columns.max
|
|
83
|
+
|
|
84
|
+
group.each do |asgn|
|
|
85
|
+
current_column = asgn.loc.operator.column
|
|
86
|
+
|
|
87
|
+
next if current_column == target_column
|
|
88
|
+
|
|
89
|
+
add_offense(offense_location(asgn)) do |corrector|
|
|
90
|
+
autocorrect_alignment(corrector, asgn, target_column)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Get the location to highlight for the offense.
|
|
96
|
+
#
|
|
97
|
+
# @param [RuboCop::AST::Node] asgn The indexed assignment node.
|
|
98
|
+
# @return [Parser::Source::Range]
|
|
99
|
+
def offense_location(asgn)
|
|
100
|
+
asgn.loc.selector
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Auto-correct the alignment of an indexed assignment.
|
|
104
|
+
#
|
|
105
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
106
|
+
# @param [RuboCop::AST::Node] asgn The indexed assignment node.
|
|
107
|
+
# @param [Integer] target_column The target column for alignment.
|
|
108
|
+
# @return [void]
|
|
109
|
+
def autocorrect_alignment(corrector, asgn, target_column)
|
|
110
|
+
bracket_end = closing_bracket_end_pos(asgn)
|
|
111
|
+
operator_start = asgn.loc.operator.begin_pos
|
|
112
|
+
total_spaces = calculate_total_spaces(asgn, target_column, bracket_end, operator_start)
|
|
113
|
+
|
|
114
|
+
corrector.replace(
|
|
115
|
+
range_between(bracket_end, operator_start),
|
|
116
|
+
" " * total_spaces
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Get the position after the closing bracket.
|
|
121
|
+
#
|
|
122
|
+
# @param [RuboCop::AST::Node] asgn The indexed assignment node.
|
|
123
|
+
# @return [Integer]
|
|
124
|
+
def closing_bracket_end_pos(asgn)
|
|
125
|
+
asgn.first_argument.source_range.end_pos + 1
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Calculate total spaces needed for alignment.
|
|
129
|
+
#
|
|
130
|
+
# @param [RuboCop::AST::Node] asgn The indexed assignment node.
|
|
131
|
+
# @param [Integer] target_column The target column for alignment.
|
|
132
|
+
# @param [Integer] bracket_end Position after the closing bracket.
|
|
133
|
+
# @param [Integer] operator_start Position of the operator.
|
|
134
|
+
# @return [Integer] The number of spaces (minimum 1).
|
|
135
|
+
def calculate_total_spaces(asgn, target_column, bracket_end, operator_start)
|
|
136
|
+
current_column = asgn.loc.operator.column
|
|
137
|
+
current_spaces = operator_start - bracket_end
|
|
138
|
+
spaces_needed = target_column - current_column
|
|
139
|
+
|
|
140
|
+
[1, current_spaces + spaces_needed].max
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -28,6 +28,7 @@ module RuboCop
|
|
|
28
28
|
class ConsecutiveLetAlignment < Base
|
|
29
29
|
extend AutoCorrector
|
|
30
30
|
include SpecFileHelper
|
|
31
|
+
include AlignmentHelpers
|
|
31
32
|
|
|
32
33
|
MSG = "Align consecutive `let` declarations at the `{` brace."
|
|
33
34
|
|
|
@@ -66,90 +67,12 @@ module RuboCop
|
|
|
66
67
|
# @return [void]
|
|
67
68
|
def check_lets_in_body(body)
|
|
68
69
|
statements = extract_statements(body)
|
|
69
|
-
return if statements.size < 2
|
|
70
|
-
|
|
71
|
-
groups = group_consecutive_lets(statements)
|
|
72
|
-
groups.each { |group| check_group_alignment(group) }
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Extract statements from a body node.
|
|
76
|
-
#
|
|
77
|
-
# @param [RuboCop::AST::Node] body The body node.
|
|
78
|
-
# @return [Array<RuboCop::AST::Node>]
|
|
79
|
-
def extract_statements(body)
|
|
80
|
-
if body.begin_type?
|
|
81
|
-
body.children
|
|
82
|
-
else
|
|
83
|
-
[body]
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Group consecutive let declarations together.
|
|
88
|
-
#
|
|
89
|
-
# @param [Array<RuboCop::AST::Node>] statements The statements.
|
|
90
|
-
# @return [Array<Array<RuboCop::AST::Node>>] Groups of consecutive lets.
|
|
91
|
-
def group_consecutive_lets(statements)
|
|
92
|
-
groups = []
|
|
93
|
-
current_group = []
|
|
94
|
-
previous_line = nil
|
|
95
|
-
|
|
96
|
-
statements.each do |statement|
|
|
97
|
-
current_group, previous_line = process_statement(statement, current_group, previous_line, groups)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
finalize_groups(groups, current_group)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Process a single statement for grouping.
|
|
104
|
-
#
|
|
105
|
-
# @param [RuboCop::AST::Node] statement The statement.
|
|
106
|
-
# @param [Array<RuboCop::AST::Node>] current_group The current group.
|
|
107
|
-
# @param [Integer, nil] previous_line The previous line number.
|
|
108
|
-
# @param [Array<Array<RuboCop::AST::Node>>] groups The groups.
|
|
109
|
-
# @return [Array] The updated current_group and previous_line.
|
|
110
|
-
def process_statement(statement, current_group, previous_line, groups)
|
|
111
|
-
if let_declaration?(statement)
|
|
112
|
-
current_group = handle_let(statement, current_group, previous_line, groups)
|
|
113
|
-
else
|
|
114
|
-
save_group_if_valid(groups, current_group)
|
|
115
|
-
current_group = []
|
|
116
|
-
end
|
|
117
|
-
[current_group, statement.loc.last_line]
|
|
118
|
-
end
|
|
119
70
|
|
|
120
|
-
|
|
121
|
-
#
|
|
122
|
-
# @param [RuboCop::AST::Node] statement The let declaration.
|
|
123
|
-
# @param [Array<RuboCop::AST::Node>] current_group The current group.
|
|
124
|
-
# @param [Integer, nil] previous_line The previous line number.
|
|
125
|
-
# @param [Array<Array<RuboCop::AST::Node>>] groups The groups.
|
|
126
|
-
# @return [Array<RuboCop::AST::Node>] The updated current group.
|
|
127
|
-
def handle_let(statement, current_group, previous_line, groups)
|
|
128
|
-
if previous_line && statement.loc.line - previous_line > 1
|
|
129
|
-
save_group_if_valid(groups, current_group)
|
|
130
|
-
current_group = []
|
|
131
|
-
end
|
|
132
|
-
current_group << statement
|
|
133
|
-
current_group
|
|
134
|
-
end
|
|
71
|
+
return if statements.size < 2
|
|
135
72
|
|
|
136
|
-
|
|
137
|
-
#
|
|
138
|
-
# @param [Array<Array<RuboCop::AST::Node>>] groups The groups.
|
|
139
|
-
# @param [Array<RuboCop::AST::Node>] group The group to potentially save.
|
|
140
|
-
# @return [void]
|
|
141
|
-
def save_group_if_valid(groups, group)
|
|
142
|
-
groups << group if group.size > 1
|
|
143
|
-
end
|
|
73
|
+
groups = group_consecutive_statements(statements) { |s| let_declaration?(s) }
|
|
144
74
|
|
|
145
|
-
|
|
146
|
-
#
|
|
147
|
-
# @param [Array<Array<RuboCop::AST::Node>>] groups The groups.
|
|
148
|
-
# @param [Array<RuboCop::AST::Node>] current_group The current group.
|
|
149
|
-
# @return [Array<Array<RuboCop::AST::Node>>] The finalized groups.
|
|
150
|
-
def finalize_groups(groups, current_group)
|
|
151
|
-
save_group_if_valid(groups, current_group)
|
|
152
|
-
groups
|
|
75
|
+
groups.each { |group| check_group_alignment(group) }
|
|
153
76
|
end
|
|
154
77
|
|
|
155
78
|
# Check alignment for a group of let declarations.
|
|
@@ -162,6 +85,7 @@ module RuboCop
|
|
|
162
85
|
|
|
163
86
|
group.each do |let|
|
|
164
87
|
current_column = let.loc.begin.column
|
|
88
|
+
|
|
165
89
|
next if current_column == target_column
|
|
166
90
|
|
|
167
91
|
add_offense(let.send_node) do |corrector|
|
|
@@ -200,19 +124,6 @@ module RuboCop
|
|
|
200
124
|
|
|
201
125
|
[1, current_spaces + spaces_needed].max
|
|
202
126
|
end
|
|
203
|
-
|
|
204
|
-
# Create a source range between two positions.
|
|
205
|
-
#
|
|
206
|
-
# @param [Integer] start_pos The start position.
|
|
207
|
-
# @param [Integer] end_pos The end position.
|
|
208
|
-
# @return [Parser::Source::Range]
|
|
209
|
-
def range_between(start_pos, end_pos)
|
|
210
|
-
Parser::Source::Range.new(
|
|
211
|
-
processed_source.buffer,
|
|
212
|
-
start_pos,
|
|
213
|
-
end_pos
|
|
214
|
-
)
|
|
215
|
-
end
|
|
216
127
|
end
|
|
217
128
|
end
|
|
218
129
|
end
|
|
@@ -83,9 +83,11 @@ module RuboCop
|
|
|
83
83
|
return unless top_level_describe?(node)
|
|
84
84
|
|
|
85
85
|
describe_blocks = extract_describe_blocks(node)
|
|
86
|
+
|
|
86
87
|
return if describe_blocks.size < 2
|
|
87
88
|
|
|
88
89
|
violations = find_ordering_violations(describe_blocks)
|
|
90
|
+
|
|
89
91
|
violations.each do |block_info|
|
|
90
92
|
add_offense(block_info[:node]) do |corrector|
|
|
91
93
|
autocorrect(corrector, describe_blocks)
|
|
@@ -153,15 +155,15 @@ module RuboCop
|
|
|
153
155
|
# @return [nil] When description is not a string/symbol literal.
|
|
154
156
|
def extract_description(node)
|
|
155
157
|
first_arg = node.send_node.first_argument
|
|
158
|
+
|
|
156
159
|
return unless first_arg
|
|
157
160
|
|
|
158
161
|
if first_arg.str_type?
|
|
159
162
|
first_arg.value
|
|
160
163
|
elsif first_arg.sym_type?
|
|
161
164
|
first_arg.value.to_s
|
|
162
|
-
# Intentionally returns nil for constants/variables.
|
|
163
|
-
# These will get DEFAULT_PRIORITY.
|
|
164
165
|
end
|
|
166
|
+
# Returns nil for constants/variables - they get DEFAULT_PRIORITY.
|
|
165
167
|
end
|
|
166
168
|
|
|
167
169
|
# Categorize description and assign priority.
|
|
@@ -202,6 +204,7 @@ module RuboCop
|
|
|
202
204
|
def controller_action_priority(description)
|
|
203
205
|
# Strip the # prefix for controller actions.
|
|
204
206
|
action_name = description.start_with?("#") ? description[1..] : description
|
|
207
|
+
|
|
205
208
|
if controller_action?(action_name)
|
|
206
209
|
30 + CONTROLLER_ACTIONS.index(action_name)
|
|
207
210
|
end
|
|
@@ -251,6 +254,7 @@ module RuboCop
|
|
|
251
254
|
|
|
252
255
|
blocks.each_with_index do |block, index|
|
|
253
256
|
sorted_block = sorted_blocks[index]
|
|
257
|
+
|
|
254
258
|
next if block == sorted_block
|
|
255
259
|
|
|
256
260
|
corrector.replace(block[:node], sorted_block[:node].source)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Enforces using explicit `if`/`else`/`end` blocks instead of ternary operators
|
|
7
|
+
# or trailing conditionals when they are the return value of a method.
|
|
8
|
+
#
|
|
9
|
+
# Ternary operators and trailing conditionals can be harder to read when used
|
|
10
|
+
# as method return values. This cop enforces converting them to explicit
|
|
11
|
+
# `if`/`else`/`end` blocks for better readability.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# # bad - ternary as return value
|
|
15
|
+
# def allow_origin
|
|
16
|
+
# origin = request.headers["Origin"]
|
|
17
|
+
# origin_allowed?(origin) ? origin : "*"
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # good - explicit if/else
|
|
21
|
+
# def allow_origin
|
|
22
|
+
# origin = request.headers["Origin"]
|
|
23
|
+
# if origin_allowed?(origin)
|
|
24
|
+
# origin
|
|
25
|
+
# else
|
|
26
|
+
# "*"
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# # bad - trailing conditional as return value
|
|
31
|
+
# def vary
|
|
32
|
+
# "Origin" if website.present?
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# # good - explicit if block
|
|
36
|
+
# def vary
|
|
37
|
+
# if website.present?
|
|
38
|
+
# "Origin"
|
|
39
|
+
# end
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# # good - ternary used in assignment (not as return value)
|
|
43
|
+
# def example
|
|
44
|
+
# result = condition ? "yes" : "no"
|
|
45
|
+
# process(result)
|
|
46
|
+
# end
|
|
47
|
+
class ExplicitReturnConditional < Base
|
|
48
|
+
extend AutoCorrector
|
|
49
|
+
|
|
50
|
+
MSG_TERNARY = "Use explicit `if`/`else`/`end` block instead of ternary operator for return value."
|
|
51
|
+
MSG_MODIFIER = "Use explicit `if`/`end` block instead of trailing conditional for return value."
|
|
52
|
+
|
|
53
|
+
# Check method definitions for conditional return values.
|
|
54
|
+
#
|
|
55
|
+
# @param [RuboCop::AST::Node] node The def node.
|
|
56
|
+
# @return [void]
|
|
57
|
+
def on_def(node)
|
|
58
|
+
if node.body
|
|
59
|
+
check_return_value(node.body)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
alias on_defs on_def
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Check if the return value is a conditional that should be explicit.
|
|
67
|
+
#
|
|
68
|
+
# @param [RuboCop::AST::Node] body The method body node.
|
|
69
|
+
# @return [void]
|
|
70
|
+
def check_return_value(body)
|
|
71
|
+
return_node = find_return_node(body)
|
|
72
|
+
|
|
73
|
+
return unless return_node.if_type?
|
|
74
|
+
|
|
75
|
+
if return_node.ternary?
|
|
76
|
+
register_ternary_offense(return_node)
|
|
77
|
+
elsif return_node.modifier_form?
|
|
78
|
+
register_modifier_offense(return_node)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Find the node that represents the return value.
|
|
83
|
+
#
|
|
84
|
+
# @param [RuboCop::AST::Node] body The method body node.
|
|
85
|
+
# @return [RuboCop::AST::Node, nil]
|
|
86
|
+
def find_return_node(body)
|
|
87
|
+
if body.begin_type?
|
|
88
|
+
body.children.last
|
|
89
|
+
else
|
|
90
|
+
body
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Register offense for ternary operator.
|
|
95
|
+
#
|
|
96
|
+
# @param [RuboCop::AST::Node] node The ternary node.
|
|
97
|
+
# @return [void]
|
|
98
|
+
def register_ternary_offense(node)
|
|
99
|
+
add_offense(node, message: MSG_TERNARY) do |corrector|
|
|
100
|
+
autocorrect_ternary(corrector, node)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Register offense for modifier conditional.
|
|
105
|
+
#
|
|
106
|
+
# @param [RuboCop::AST::Node] node The modifier if node.
|
|
107
|
+
# @return [void]
|
|
108
|
+
def register_modifier_offense(node)
|
|
109
|
+
add_offense(node, message: MSG_MODIFIER) do |corrector|
|
|
110
|
+
autocorrect_modifier(corrector, node)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Autocorrect ternary operator to if/else/end block.
|
|
115
|
+
#
|
|
116
|
+
# @param [RuboCop::Cop::Corrector] corrector The corrector.
|
|
117
|
+
# @param [RuboCop::AST::Node] node The ternary node.
|
|
118
|
+
# @return [void]
|
|
119
|
+
def autocorrect_ternary(corrector, node)
|
|
120
|
+
corrector.replace(node, build_if_else_block(node))
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Autocorrect modifier conditional to if/end block.
|
|
124
|
+
#
|
|
125
|
+
# @param [RuboCop::Cop::Corrector] corrector The corrector.
|
|
126
|
+
# @param [RuboCop::AST::Node] node The modifier if node.
|
|
127
|
+
# @return [void]
|
|
128
|
+
def autocorrect_modifier(corrector, node)
|
|
129
|
+
corrector.replace(node, build_if_block(node))
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Build if/else/end block replacement for ternary.
|
|
133
|
+
#
|
|
134
|
+
# @param [RuboCop::AST::Node] node The ternary node.
|
|
135
|
+
# @return [String]
|
|
136
|
+
def build_if_else_block(node)
|
|
137
|
+
base_indent = " " * node.loc.column
|
|
138
|
+
inner_indent = "#{base_indent} "
|
|
139
|
+
|
|
140
|
+
[
|
|
141
|
+
"if #{node.condition.source}",
|
|
142
|
+
"#{inner_indent}#{node.if_branch.source}",
|
|
143
|
+
"#{base_indent}else",
|
|
144
|
+
"#{inner_indent}#{node.else_branch.source}",
|
|
145
|
+
"#{base_indent}end"
|
|
146
|
+
].join("\n")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Build if/end block replacement for modifier conditional.
|
|
150
|
+
#
|
|
151
|
+
# @param [RuboCop::AST::Node] node The modifier if node.
|
|
152
|
+
# @return [String]
|
|
153
|
+
def build_if_block(node)
|
|
154
|
+
condition = build_condition(node)
|
|
155
|
+
base_indent = " " * node.loc.column
|
|
156
|
+
inner_indent = "#{base_indent} "
|
|
157
|
+
|
|
158
|
+
[
|
|
159
|
+
"if #{condition}",
|
|
160
|
+
"#{inner_indent}#{node.if_branch.source}",
|
|
161
|
+
"#{base_indent}end"
|
|
162
|
+
].join("\n")
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Build the condition for the if block.
|
|
166
|
+
# For unless, negate the condition. For if, keep it as is.
|
|
167
|
+
#
|
|
168
|
+
# @param [RuboCop::AST::Node] node The if node.
|
|
169
|
+
# @return [String] The condition source.
|
|
170
|
+
def build_condition(node)
|
|
171
|
+
if node.unless?
|
|
172
|
+
negate_condition(node.condition)
|
|
173
|
+
else
|
|
174
|
+
node.condition.source
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Negate a condition, handling simple cases cleanly.
|
|
179
|
+
#
|
|
180
|
+
# @param [RuboCop::AST::Node] condition The condition node.
|
|
181
|
+
# @return [String] The negated condition.
|
|
182
|
+
def negate_condition(condition)
|
|
183
|
+
if condition.send_type? && condition.method?(:!)
|
|
184
|
+
condition.receiver.source
|
|
185
|
+
else
|
|
186
|
+
"!#{condition.source}"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -45,6 +45,7 @@ module RuboCop
|
|
|
45
45
|
return unless is_expected_call?(node)
|
|
46
46
|
|
|
47
47
|
example_block = find_example_block(node)
|
|
48
|
+
|
|
48
49
|
return unless example_block
|
|
49
50
|
return unless example_block_with_description?(example_block)
|
|
50
51
|
return if complex_expectation?(example_block)
|
|
@@ -65,6 +66,7 @@ module RuboCop
|
|
|
65
66
|
def find_example_block(node)
|
|
66
67
|
node.each_ancestor(:block).find do |ancestor|
|
|
67
68
|
send_node = ancestor.send_node
|
|
69
|
+
|
|
68
70
|
send_node.method?(:it) || send_node.method?(:specify)
|
|
69
71
|
end
|
|
70
72
|
end
|
|
@@ -100,6 +102,7 @@ module RuboCop
|
|
|
100
102
|
# @return [void]
|
|
101
103
|
def autocorrect(corrector, node)
|
|
102
104
|
expectation_source = node.body.source
|
|
105
|
+
|
|
103
106
|
corrector.replace(node, "it { #{expectation_source} }")
|
|
104
107
|
end
|
|
105
108
|
end
|