rubocop-vibe 0.2.0 → 0.4.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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +51 -4
  3. data/lib/rubocop/cop/vibe/blank_line_after_assignment.rb +217 -0
  4. data/lib/rubocop/cop/vibe/blank_line_before_expectation.rb +6 -1
  5. data/lib/rubocop/cop/vibe/class_organization.rb +20 -1
  6. data/lib/rubocop/cop/vibe/consecutive_assignment_alignment.rb +5 -102
  7. data/lib/rubocop/cop/vibe/consecutive_constant_alignment.rb +5 -113
  8. data/lib/rubocop/cop/vibe/consecutive_indexed_assignment_alignment.rb +145 -0
  9. data/lib/rubocop/cop/vibe/consecutive_instance_variable_assignment_alignment.rb +123 -0
  10. data/lib/rubocop/cop/vibe/consecutive_let_alignment.rb +5 -94
  11. data/lib/rubocop/cop/vibe/describe_block_order.rb +6 -2
  12. data/lib/rubocop/cop/vibe/explicit_return_conditional.rb +192 -0
  13. data/lib/rubocop/cop/vibe/is_expected_one_liner.rb +3 -0
  14. data/lib/rubocop/cop/vibe/let_order.rb +147 -0
  15. data/lib/rubocop/cop/vibe/mixin/alignment_helpers.rb +92 -0
  16. data/lib/rubocop/cop/vibe/multiline_hash_argument_style.rb +171 -0
  17. data/lib/rubocop/cop/vibe/no_compound_conditions.rb +138 -0
  18. data/lib/rubocop/cop/vibe/no_rubocop_disable.rb +3 -0
  19. data/lib/rubocop/cop/vibe/no_skipped_tests.rb +1 -0
  20. data/lib/rubocop/cop/vibe/no_unless_guard_clause.rb +4 -0
  21. data/lib/rubocop/cop/vibe/prefer_one_liner_expectation.rb +4 -0
  22. data/lib/rubocop/cop/vibe/raise_unless_block.rb +1 -0
  23. data/lib/rubocop/cop/vibe/rspec_before_block_style.rb +114 -0
  24. data/lib/rubocop/cop/vibe/rspec_stub_chain_style.rb +4 -0
  25. data/lib/rubocop/cop/vibe_cops.rb +9 -0
  26. data/lib/rubocop/vibe/plugin.rb +4 -4
  27. data/lib/rubocop/vibe/version.rb +1 -1
  28. metadata +11 -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
- # Check if node is a single-line constant assignment.
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
- # Handle a constant assignment.
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
- # Save group if it has multiple constant assignments.
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
@@ -0,0 +1,123 @@
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
+
45
+ # Check method definitions for assignment alignment.
46
+ #
47
+ # @param [RuboCop::AST::Node] node The def node.
48
+ # @return [void]
49
+ def on_def(node)
50
+ if node.body
51
+ check_assignments_in_body(node.body)
52
+ end
53
+ end
54
+ alias on_defs on_def
55
+
56
+ private
57
+
58
+ # Check assignments in a body node.
59
+ #
60
+ # @param [RuboCop::AST::Node] body The body node.
61
+ # @return [void]
62
+ def check_assignments_in_body(body)
63
+ statements = extract_statements(body)
64
+
65
+ return if statements.size < 2
66
+
67
+ groups = group_consecutive_statements(statements, &:ivasgn_type?)
68
+
69
+ groups.each { |group| check_group_alignment(group) }
70
+ end
71
+
72
+ # Check alignment for a group of assignments.
73
+ #
74
+ # @param [Array<RuboCop::AST::Node>] group The assignment group.
75
+ # @return [void]
76
+ def check_group_alignment(group)
77
+ columns = group.map { |asgn| asgn.loc.operator.column }
78
+ target_column = columns.max
79
+
80
+ group.each do |asgn|
81
+ current_column = asgn.loc.operator.column
82
+
83
+ next if current_column == target_column
84
+
85
+ add_offense(asgn.loc.name) do |corrector|
86
+ autocorrect_alignment(corrector, asgn, target_column)
87
+ end
88
+ end
89
+ end
90
+
91
+ # Auto-correct the alignment of an assignment.
92
+ #
93
+ # @param [RuboCop::AST::Corrector] corrector The corrector.
94
+ # @param [RuboCop::AST::Node] asgn The assignment node.
95
+ # @param [Integer] target_column The target column for alignment.
96
+ # @return [void]
97
+ def autocorrect_alignment(corrector, asgn, target_column)
98
+ variable_name_end = asgn.loc.name.end_pos
99
+ operator_start = asgn.loc.operator.begin_pos
100
+ total_spaces = calculate_total_spaces(asgn, target_column)
101
+
102
+ corrector.replace(
103
+ range_between(variable_name_end, operator_start),
104
+ " " * total_spaces
105
+ )
106
+ end
107
+
108
+ # Calculate total spaces needed for alignment.
109
+ #
110
+ # @param [RuboCop::AST::Node] asgn The assignment node.
111
+ # @param [Integer] target_column The target column for alignment.
112
+ # @return [Integer] The number of spaces (minimum 1).
113
+ def calculate_total_spaces(asgn, target_column)
114
+ current_column = asgn.loc.operator.column
115
+ current_spaces = asgn.loc.operator.begin_pos - asgn.loc.name.end_pos
116
+ spaces_needed = target_column - current_column
117
+
118
+ [1, current_spaces + spaces_needed].max
119
+ end
120
+ end
121
+ end
122
+ end
123
+ 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
- # Handle a let declaration.
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
- # Save group if it has multiple let declarations.
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
- # Finalize groups by adding any remaining valid group.
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)