rubocop-vibe 0.1.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 +64 -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 +121 -0
- data/lib/rubocop/cop/vibe/consecutive_indexed_assignment_alignment.rb +145 -0
- data/lib/rubocop/cop/vibe/consecutive_let_alignment.rb +130 -0
- data/lib/rubocop/cop/vibe/describe_block_order.rb +7 -3
- 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 +66 -16
- 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 +102 -0
- data/lib/rubocop/cop/vibe/rspec_before_block_style.rb +114 -0
- data/lib/rubocop/cop/vibe/rspec_stub_chain_style.rb +260 -0
- data/lib/rubocop/cop/vibe_cops.rb +11 -0
- data/lib/rubocop/vibe/plugin.rb +4 -4
- data/lib/rubocop/vibe/version.rb +1 -1
- metadata +13 -2
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Enforces alignment of consecutive constant assignments at the `=` operator.
|
|
7
|
+
#
|
|
8
|
+
# Consecutive constant assignments (with no blank lines between) should align their
|
|
9
|
+
# `=` operators for better readability. Groups are broken by blank lines or non-constant
|
|
10
|
+
# statements.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad
|
|
14
|
+
# MINIMUM_NAME_LENGTH = 3
|
|
15
|
+
# MAXIMUM_NAME_LENGTH = 12
|
|
16
|
+
# ACTIVE_DURATION = 15.minutes
|
|
17
|
+
#
|
|
18
|
+
# # good
|
|
19
|
+
# MINIMUM_NAME_LENGTH = 3
|
|
20
|
+
# MAXIMUM_NAME_LENGTH = 12
|
|
21
|
+
# ACTIVE_DURATION = 15.minutes
|
|
22
|
+
#
|
|
23
|
+
# # good - blank line breaks the group
|
|
24
|
+
# THROTTLE_LIMIT = 10
|
|
25
|
+
# THROTTLE_PERIOD = 5
|
|
26
|
+
#
|
|
27
|
+
# DEFAULT_VALUE = 0 # Separate group, not aligned
|
|
28
|
+
class ConsecutiveConstantAlignment < Base
|
|
29
|
+
extend AutoCorrector
|
|
30
|
+
include AlignmentHelpers
|
|
31
|
+
|
|
32
|
+
MSG = "Align consecutive constant assignments at the `=` operator."
|
|
33
|
+
|
|
34
|
+
# Check class nodes for constant alignment.
|
|
35
|
+
#
|
|
36
|
+
# @param [RuboCop::AST::Node] node The class node.
|
|
37
|
+
# @return [void]
|
|
38
|
+
def on_class(node)
|
|
39
|
+
if node.body
|
|
40
|
+
check_constants_in_body(node.body)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Check module nodes for constant alignment.
|
|
45
|
+
#
|
|
46
|
+
# @param [RuboCop::AST::Node] node The module node.
|
|
47
|
+
# @return [void]
|
|
48
|
+
def on_module(node)
|
|
49
|
+
if node.body
|
|
50
|
+
check_constants_in_body(node.body)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Check constants in a body node.
|
|
57
|
+
#
|
|
58
|
+
# @param [RuboCop::AST::Node] body The body node.
|
|
59
|
+
# @return [void]
|
|
60
|
+
def check_constants_in_body(body)
|
|
61
|
+
statements = extract_statements(body)
|
|
62
|
+
|
|
63
|
+
return if statements.size < 2
|
|
64
|
+
|
|
65
|
+
groups = group_consecutive_statements(statements) { |s| s.casgn_type? && s.single_line? }
|
|
66
|
+
|
|
67
|
+
groups.each { |group| check_group_alignment(group) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Check alignment for a group of constant assignments.
|
|
71
|
+
#
|
|
72
|
+
# @param [Array<RuboCop::AST::Node>] group The constant group.
|
|
73
|
+
# @return [void]
|
|
74
|
+
def check_group_alignment(group)
|
|
75
|
+
columns = group.map { |const| const.loc.operator.column }
|
|
76
|
+
target_column = columns.max
|
|
77
|
+
|
|
78
|
+
group.each do |const|
|
|
79
|
+
current_column = const.loc.operator.column
|
|
80
|
+
|
|
81
|
+
next if current_column == target_column
|
|
82
|
+
|
|
83
|
+
add_offense(const.loc.name) do |corrector|
|
|
84
|
+
autocorrect_alignment(corrector, const, target_column)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Auto-correct the alignment of a constant assignment.
|
|
90
|
+
#
|
|
91
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
92
|
+
# @param [RuboCop::AST::Node] const The constant assignment node.
|
|
93
|
+
# @param [Integer] target_column The target column for alignment.
|
|
94
|
+
# @return [void]
|
|
95
|
+
def autocorrect_alignment(corrector, const, target_column)
|
|
96
|
+
name_end = const.loc.name.end_pos
|
|
97
|
+
operator_start = const.loc.operator.begin_pos
|
|
98
|
+
total_spaces = calculate_total_spaces(const, target_column)
|
|
99
|
+
|
|
100
|
+
corrector.replace(
|
|
101
|
+
range_between(name_end, operator_start),
|
|
102
|
+
" " * total_spaces
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Calculate total spaces needed for alignment.
|
|
107
|
+
#
|
|
108
|
+
# @param [RuboCop::AST::Node] const The constant assignment node.
|
|
109
|
+
# @param [Integer] target_column The target column for alignment.
|
|
110
|
+
# @return [Integer] The number of spaces (minimum 1).
|
|
111
|
+
def calculate_total_spaces(const, target_column)
|
|
112
|
+
current_column = const.loc.operator.column
|
|
113
|
+
current_spaces = const.loc.operator.begin_pos - const.loc.name.end_pos
|
|
114
|
+
spaces_needed = target_column - current_column
|
|
115
|
+
|
|
116
|
+
[1, current_spaces + spaces_needed].max
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
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
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Vibe
|
|
6
|
+
# Enforces alignment of consecutive `let` declarations at the `{` brace.
|
|
7
|
+
#
|
|
8
|
+
# Consecutive `let` declarations (with no blank lines between) should align their
|
|
9
|
+
# `{` braces for better readability. Groups are broken by blank lines or non-let
|
|
10
|
+
# statements.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad
|
|
14
|
+
# let(:character) { instance_double(Character) }
|
|
15
|
+
# let(:damage) { 1 }
|
|
16
|
+
# let(:instance) { described_class.new }
|
|
17
|
+
#
|
|
18
|
+
# # good
|
|
19
|
+
# let(:character) { instance_double(Character) }
|
|
20
|
+
# let(:damage) { 1 }
|
|
21
|
+
# let(:instance) { described_class.new }
|
|
22
|
+
#
|
|
23
|
+
# # good - blank line breaks the group
|
|
24
|
+
# let(:user) { create(:user) }
|
|
25
|
+
# let(:character) { create(:character) }
|
|
26
|
+
#
|
|
27
|
+
# let(:service) { Users::Activate.new } # Separate group, not aligned
|
|
28
|
+
class ConsecutiveLetAlignment < Base
|
|
29
|
+
extend AutoCorrector
|
|
30
|
+
include SpecFileHelper
|
|
31
|
+
include AlignmentHelpers
|
|
32
|
+
|
|
33
|
+
MSG = "Align consecutive `let` declarations at the `{` brace."
|
|
34
|
+
|
|
35
|
+
# @!method let_declaration?(node)
|
|
36
|
+
# Check if node is a let/let! declaration.
|
|
37
|
+
def_node_matcher :let_declaration?, <<~PATTERN
|
|
38
|
+
(block (send nil? {:let :let!} (sym _)) ...)
|
|
39
|
+
PATTERN
|
|
40
|
+
|
|
41
|
+
# Check describe/context blocks for let alignment.
|
|
42
|
+
#
|
|
43
|
+
# @param [RuboCop::AST::Node] node The block node.
|
|
44
|
+
# @return [void]
|
|
45
|
+
def on_block(node)
|
|
46
|
+
return unless spec_file?
|
|
47
|
+
return unless describe_or_context?(node)
|
|
48
|
+
return unless node.body
|
|
49
|
+
|
|
50
|
+
check_lets_in_body(node.body)
|
|
51
|
+
end
|
|
52
|
+
alias on_numblock on_block
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Check if block is a describe or context block.
|
|
57
|
+
#
|
|
58
|
+
# @param [RuboCop::AST::Node] node The block node.
|
|
59
|
+
# @return [Boolean]
|
|
60
|
+
def describe_or_context?(node)
|
|
61
|
+
node.send_node && %i(describe context).include?(node.method_name)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Check let declarations in a body node.
|
|
65
|
+
#
|
|
66
|
+
# @param [RuboCop::AST::Node] body The body node.
|
|
67
|
+
# @return [void]
|
|
68
|
+
def check_lets_in_body(body)
|
|
69
|
+
statements = extract_statements(body)
|
|
70
|
+
|
|
71
|
+
return if statements.size < 2
|
|
72
|
+
|
|
73
|
+
groups = group_consecutive_statements(statements) { |s| let_declaration?(s) }
|
|
74
|
+
|
|
75
|
+
groups.each { |group| check_group_alignment(group) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check alignment for a group of let declarations.
|
|
79
|
+
#
|
|
80
|
+
# @param [Array<RuboCop::AST::Node>] group The let group.
|
|
81
|
+
# @return [void]
|
|
82
|
+
def check_group_alignment(group)
|
|
83
|
+
columns = group.map { |let| let.loc.begin.column }
|
|
84
|
+
target_column = columns.max
|
|
85
|
+
|
|
86
|
+
group.each do |let|
|
|
87
|
+
current_column = let.loc.begin.column
|
|
88
|
+
|
|
89
|
+
next if current_column == target_column
|
|
90
|
+
|
|
91
|
+
add_offense(let.send_node) do |corrector|
|
|
92
|
+
autocorrect_alignment(corrector, let, target_column)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Auto-correct the alignment of a let declaration.
|
|
98
|
+
#
|
|
99
|
+
# @param [RuboCop::AST::Corrector] corrector The corrector.
|
|
100
|
+
# @param [RuboCop::AST::Node] let The let block node.
|
|
101
|
+
# @param [Integer] target_column The target column for alignment.
|
|
102
|
+
# @return [void]
|
|
103
|
+
def autocorrect_alignment(corrector, let, target_column)
|
|
104
|
+
send_node = let.send_node
|
|
105
|
+
send_end = send_node.source_range.end_pos
|
|
106
|
+
brace_start = let.loc.begin.begin_pos
|
|
107
|
+
total_spaces = calculate_total_spaces(let, target_column)
|
|
108
|
+
|
|
109
|
+
corrector.replace(
|
|
110
|
+
range_between(send_end, brace_start),
|
|
111
|
+
" " * total_spaces
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Calculate total spaces needed for alignment.
|
|
116
|
+
#
|
|
117
|
+
# @param [RuboCop::AST::Node] let The let block 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(let, target_column)
|
|
121
|
+
current_column = let.loc.begin.column
|
|
122
|
+
current_spaces = let.loc.begin.begin_pos - let.send_node.source_range.end_pos
|
|
123
|
+
spaces_needed = target_column - current_column
|
|
124
|
+
|
|
125
|
+
[1, current_spaces + spaces_needed].max
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -59,7 +59,7 @@ 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
|
|
62
|
+
MODEL_ORDER = %w(class associations validations).freeze
|
|
63
63
|
CONTROLLER_ACTIONS = %w(index show new create edit update destroy).freeze
|
|
64
64
|
SPECIAL_SECTIONS = {
|
|
65
65
|
"class" => 0,
|
|
@@ -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
|