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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e91f63c7c86e77f854c0dd424a1d40d1fd5ad8f0407836dc74467d59e786e6ab
4
- data.tar.gz: d4cdc851197a8a1fcbbdebe14551c369029469f6c2e6c4a09b50060fb40b39f8
3
+ metadata.gz: e14c979862da1db7feb6dc28697248359d9320450a1bbfca24db50101d9f4c43
4
+ data.tar.gz: e13a6d3fe34d9a24edc775dba8800e901a2a5a59ca5b3f35f516838e4ca79853
5
5
  SHA512:
6
- metadata.gz: eaebb568a44bd514577f6e3dc63a3b4a05d6c78762b5520462a0ba55119549bece813953d35cd2956fb03830a703d7a90e23d6b9c25204a3f41ee40281d87852
7
- data.tar.gz: 0d66492bf54277b78dfa228bfdd7ef3cb2ba46f715ef32dc5304371d6af7ab9eff2e7c1a1ff3ca0fcd1ca4abd82ae8d01e41be9154fdb66a15b1546c504fb2cc
6
+ metadata.gz: 1d89ddcdf3d94bb0aeca2ce767157e305a75990b540f2bd91d1e9255e26e29915da0eb7b3188d293a96b0d4bf2ce4ac25c700f4601238705e82f7c62dbdaaeac
7
+ data.tar.gz: 064b6dfe86613f8af93718a277db85d6bd705d7580fa27fe69469c41579aa0e4653c14dd4bcc48b56dd1d808c752921b2f69336816fe0ef3fcbb1098dd0d22f2
data/config/default.yml CHANGED
@@ -62,12 +62,24 @@ Style/StringLiterals:
62
62
  Style/StringLiteralsInInterpolation:
63
63
  EnforcedStyle: double_quotes
64
64
 
65
+ Vibe/BlankLineAfterAssignment:
66
+ Description: "Enforces a blank line after variable assignments when followed by other code."
67
+ Enabled: true
68
+ SafeAutoCorrect: true
69
+ VersionAdded: "0.3.0"
70
+
65
71
  Vibe/BlankLineBeforeExpectation:
66
72
  Description: "Enforces a blank line before expectation calls when there is setup code above."
67
73
  Enabled: true
68
74
  SafeAutoCorrect: true
69
75
  VersionAdded: "0.1.0"
70
76
 
77
+ Vibe/ClassOrganization:
78
+ Description: "Enforces consistent organization of class definitions."
79
+ Enabled: true
80
+ SafeAutoCorrect: false
81
+ VersionAdded: "0.1.0"
82
+
71
83
  Vibe/ConsecutiveAssignmentAlignment:
72
84
  Description: "Enforces alignment of consecutive variable assignments at the = operator."
73
85
  Enabled: true
@@ -80,6 +92,12 @@ Vibe/ConsecutiveConstantAlignment:
80
92
  SafeAutoCorrect: true
81
93
  VersionAdded: "0.2.0"
82
94
 
95
+ Vibe/ConsecutiveIndexedAssignmentAlignment:
96
+ Description: "Enforces alignment of consecutive indexed assignments at the = operator."
97
+ Enabled: true
98
+ SafeAutoCorrect: true
99
+ VersionAdded: "0.3.0"
100
+
83
101
  Vibe/ConsecutiveLetAlignment:
84
102
  Description: "Enforces alignment of consecutive let declarations at the { brace."
85
103
  Enabled: true
@@ -92,17 +110,23 @@ Vibe/DescribeBlockOrder:
92
110
  SafeAutoCorrect: false
93
111
  VersionAdded: "0.1.0"
94
112
 
113
+ Vibe/ExplicitReturnConditional:
114
+ Description: "Enforces explicit if/else/end blocks instead of ternary or trailing conditionals for return values."
115
+ Enabled: true
116
+ SafeAutoCorrect: true
117
+ VersionAdded: "0.3.0"
118
+
95
119
  Vibe/IsExpectedOneLiner:
96
120
  Description: "Enforces that is_expected is only used in one-liner it { } blocks."
97
121
  Enabled: true
98
122
  SafeAutoCorrect: true
99
123
  VersionAdded: "0.1.0"
100
124
 
101
- Vibe/ClassOrganization:
102
- Description: "Enforces consistent organization of class definitions."
125
+ Vibe/MultilineHashArgumentStyle:
126
+ Description: "Enforces one-per-line and alphabetical ordering for hash arguments in multiline method calls."
103
127
  Enabled: true
104
- SafeAutoCorrect: false
105
- VersionAdded: "0.1.0"
128
+ SafeAutoCorrect: true
129
+ VersionAdded: "0.3.0"
106
130
 
107
131
  Vibe/NoAssignsAttributeTesting:
108
132
  Description: "Enforces that controller specs only test assignment identity, not attributes or associations."
@@ -110,6 +134,11 @@ Vibe/NoAssignsAttributeTesting:
110
134
  SafeAutoCorrect: false
111
135
  VersionAdded: "0.1.0"
112
136
 
137
+ Vibe/NoCompoundConditions:
138
+ Description: "Enforces extracting compound boolean conditions into named methods."
139
+ Enabled: true
140
+ VersionAdded: "0.3.0"
141
+
113
142
  Vibe/NoRubocopDisable:
114
143
  Description: "Enforces that rubocop:disable comments are not used inline."
115
144
  Enabled: true
@@ -139,6 +168,12 @@ Vibe/RaiseUnlessBlock:
139
168
  SafeAutoCorrect: true
140
169
  VersionAdded: "0.2.0"
141
170
 
171
+ Vibe/RspecBeforeBlockStyle:
172
+ Description: "Enforces using do...end syntax instead of braces for RSpec before/after/around blocks."
173
+ Enabled: true
174
+ SafeAutoCorrect: true
175
+ VersionAdded: "0.3.0"
176
+
142
177
  Vibe/RspecStubChainStyle:
143
178
  Description: "Enforces that RSpec stub chains are split across lines when too long."
144
179
  Enabled: true
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Vibe
6
+ # Enforces a blank line after variable assignments when followed by other code.
7
+ #
8
+ # Variable assignments should be visually separated from the code that uses them.
9
+ # Consecutive assignments are allowed without blank lines between them, but there
10
+ # should be a blank line before non-assignment code.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # deleted_count = delete_batch
15
+ # break if deleted_count < BATCH_SIZE
16
+ #
17
+ # # good
18
+ # deleted_count = delete_batch
19
+ #
20
+ # break if deleted_count < BATCH_SIZE
21
+ #
22
+ # # good - consecutive assignments don't need blank lines
23
+ # user = find_user
24
+ # account = user.account
25
+ #
26
+ # process(user, account)
27
+ #
28
+ # # good - next line uses the assigned variable
29
+ # forwarded = request.headers["X-Forwarded-For"]
30
+ # forwarded.to_s.split(",").first.to_s.strip.presence
31
+ #
32
+ # # good - consecutive FactoryBot calls
33
+ # website = create(:website)
34
+ # page_view = create(:page_view, website: website)
35
+ # create(:page_view, website: website)
36
+ class BlankLineAfterAssignment < Base
37
+ extend AutoCorrector
38
+
39
+ MSG = "Add a blank line after variable assignment."
40
+
41
+ FACTORY_BOT_METHODS = %i(
42
+ create build build_stubbed attributes_for
43
+ create_list build_list build_stubbed_list attributes_for_list
44
+ ).freeze
45
+
46
+ # Check block nodes for assignment statements.
47
+ #
48
+ # @param [RuboCop::AST::Node] node The block node.
49
+ # @return [void]
50
+ def on_block(node)
51
+ if node.body
52
+ check_body(node.body)
53
+ end
54
+ end
55
+ alias on_numblock on_block
56
+
57
+ # Check method definitions for assignment statements.
58
+ #
59
+ # @param [RuboCop::AST::Node] node The def node.
60
+ # @return [void]
61
+ def on_def(node)
62
+ if node.body
63
+ check_body(node.body)
64
+ end
65
+ end
66
+ alias on_defs on_def
67
+
68
+ private
69
+
70
+ # Check the body for missing blank lines after assignments.
71
+ #
72
+ # @param [RuboCop::AST::Node] body The body node.
73
+ # @return [void]
74
+ def check_body(body)
75
+ statements = extract_statements(body)
76
+
77
+ return if statements.size < 2
78
+
79
+ statements.each_cons(2) do |current, following|
80
+ check_statement_pair(current, following)
81
+ end
82
+ end
83
+
84
+ # Extract statements from a body node.
85
+ #
86
+ # @param [RuboCop::AST::Node] body The body node.
87
+ # @return [Array<RuboCop::AST::Node>]
88
+ def extract_statements(body)
89
+ if body.begin_type?
90
+ body.children
91
+ else
92
+ [body]
93
+ end
94
+ end
95
+
96
+ # Check a pair of statements for missing blank line after assignment.
97
+ #
98
+ # @param [RuboCop::AST::Node] current The current statement.
99
+ # @param [RuboCop::AST::Node] following The following statement.
100
+ # @return [void]
101
+ def check_statement_pair(current, following)
102
+ return unless assignment?(current)
103
+
104
+ inner_following = inner_statement(following)
105
+
106
+ return if assignment?(inner_following)
107
+ return if blank_line_between?(current, following)
108
+ return if following_uses_assigned_variable?(current, inner_following)
109
+ return if consecutive_factory_bot_calls?(current, following)
110
+
111
+ add_offense(following) do |corrector|
112
+ corrector.insert_after(current, "\n")
113
+ end
114
+ end
115
+
116
+ # Get the inner statement from a modifier node.
117
+ #
118
+ # @param [RuboCop::AST::Node] node The node to unwrap.
119
+ # @return [RuboCop::AST::Node]
120
+ def inner_statement(node)
121
+ return node.if_branch if node.type?(:if) && node.modifier_form?
122
+ return node.body if node.type?(:while, :until) && node.modifier_form?
123
+
124
+ node
125
+ end
126
+
127
+ # Check if both statements are FactoryBot calls.
128
+ #
129
+ # @param [RuboCop::AST::Node] assignment The assignment node.
130
+ # @param [RuboCop::AST::Node] following The following statement.
131
+ # @return [Boolean]
132
+ def consecutive_factory_bot_calls?(assignment, following)
133
+ factory_bot_call?(assignment_value(assignment)) && factory_bot_call?(following)
134
+ end
135
+
136
+ # Get the value being assigned.
137
+ #
138
+ # @param [RuboCop::AST::Node] node The assignment node.
139
+ # @return [RuboCop::AST::Node, nil]
140
+ def assignment_value(node)
141
+ node.children.last
142
+ end
143
+
144
+ # Check if a node is a FactoryBot method call.
145
+ #
146
+ # @param [RuboCop::AST::Node] node The node to check.
147
+ # @return [Boolean]
148
+ def factory_bot_call?(node)
149
+ node.send_type? && FACTORY_BOT_METHODS.include?(node.method_name)
150
+ end
151
+
152
+ # Check if the following statement starts with the assigned variable.
153
+ #
154
+ # @param [RuboCop::AST::Node] assignment The assignment node.
155
+ # @param [RuboCop::AST::Node] following The following statement.
156
+ # @return [Boolean]
157
+ def following_uses_assigned_variable?(assignment, following)
158
+ var_name = assigned_variable_name(assignment)
159
+
160
+ return false unless var_name
161
+
162
+ receiver = leftmost_receiver(following)
163
+
164
+ return false unless receiver
165
+
166
+ receiver.lvar_type? && receiver.children.first == var_name
167
+ end
168
+
169
+ # Get the variable name from an assignment node.
170
+ #
171
+ # @param [RuboCop::AST::Node] node The assignment node.
172
+ # @return [Symbol, nil]
173
+ def assigned_variable_name(node)
174
+ return node.children.first if node.lvasgn_type?
175
+
176
+ target = node.children.first
177
+
178
+ if target.lvasgn_type?
179
+ target.children.first
180
+ end
181
+ end
182
+
183
+ # Get the leftmost receiver in a method chain.
184
+ #
185
+ # @param [RuboCop::AST::Node] node The node to check.
186
+ # @return [RuboCop::AST::Node, nil]
187
+ def leftmost_receiver(node)
188
+ return node if node.lvar_type?
189
+ return unless node.send_type?
190
+
191
+ current = node
192
+
193
+ current = current.receiver while current.send_type? && current.receiver
194
+
195
+ current
196
+ end
197
+
198
+ # Check if a node is a variable assignment.
199
+ #
200
+ # @param [RuboCop::AST::Node] node The node to check.
201
+ # @return [Boolean]
202
+ def assignment?(node)
203
+ node.type?(:lvasgn, :op_asgn, :or_asgn, :and_asgn)
204
+ end
205
+
206
+ # Check if there's a blank line between two statements.
207
+ #
208
+ # @param [RuboCop::AST::Node] previous_node The previous statement.
209
+ # @param [RuboCop::AST::Node] current_node The current statement.
210
+ # @return [Boolean]
211
+ def blank_line_between?(previous_node, current_node)
212
+ current_node.loc.line - previous_node.loc.last_line > 1
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
@@ -67,7 +67,11 @@ module RuboCop
67
67
  # @param [RuboCop::AST::Node] body The block body.
68
68
  # @return [Array<RuboCop::AST::Node>]
69
69
  def extract_statements(body)
70
- body.begin_type? ? body.children : [body]
70
+ if body.begin_type?
71
+ body.children
72
+ else
73
+ [body]
74
+ end
71
75
  end
72
76
 
73
77
  # Check statements for missing blank lines before expectations.
@@ -89,6 +93,7 @@ module RuboCop
89
93
  # @return [void]
90
94
  def check_statement_pair(previous_statement, current_statement)
91
95
  expect_node = find_expect_node(current_statement)
96
+
92
97
  return unless expect_node
93
98
  return if blank_line_between?(previous_statement, current_statement)
94
99
  return if find_expect_node(previous_statement)
@@ -100,6 +100,7 @@ module RuboCop
100
100
  # @return [void]
101
101
  def check_violations(node, elements, is_model)
102
102
  violations = find_violations(elements)
103
+
103
104
  return if violations.empty?
104
105
 
105
106
  message = is_model ? MODEL_MSG : CLASS_MSG
@@ -117,9 +118,11 @@ module RuboCop
117
118
  def on_class(node)
118
119
  is_model = rails_model?(node)
119
120
  is_controller = rails_controller?(node)
121
+
120
122
  return if !is_model && !is_controller && !node.body
121
123
 
122
124
  elements = extract_elements(node, is_model: is_model, is_controller: is_controller)
125
+
123
126
  return if elements.size < 2
124
127
 
125
128
  check_violations(node, elements, is_model)
@@ -135,6 +138,7 @@ module RuboCop
135
138
  return false unless node.parent_class
136
139
 
137
140
  parent_name = node.parent_class.const_name
141
+
138
142
  return false unless parent_name
139
143
 
140
144
  # Check for direct ActiveRecord inheritance.
@@ -151,6 +155,7 @@ module RuboCop
151
155
  return false unless node.parent_class
152
156
 
153
157
  parent_name = node.parent_class.const_name
158
+
154
159
  return false unless parent_name
155
160
 
156
161
  # Check for direct ActionController inheritance.
@@ -183,10 +188,12 @@ module RuboCop
183
188
  visibility = :public
184
189
  elements = []
185
190
  index = 0
191
+
186
192
  process_body_nodes(body).each do |child|
187
193
  visibility = child.method_name if visibility_modifier?(child)
188
194
 
189
195
  element = build_element(child, visibility, index, is_model, is_controller)
196
+
190
197
  elements << element and index += 1 if element
191
198
  end
192
199
 
@@ -206,6 +213,7 @@ module RuboCop
206
213
  return unless categorizable?(child)
207
214
 
208
215
  category = categorize_node(child, visibility, is_model)
216
+
209
217
  return unless category
210
218
 
211
219
  # Skip public instance methods in controllers (Rails/ActionOrder handles them).
@@ -242,6 +250,7 @@ module RuboCop
242
250
  # @return [Array<Hash>] Array of hashes with :text and :column.
243
251
  def extract_source_with_comments(node)
244
252
  lines = extract_comment_lines(node)
253
+
245
254
  lines.concat(extract_node_lines(node))
246
255
  end
247
256
 
@@ -264,6 +273,7 @@ module RuboCop
264
273
 
265
274
  node.source.lines.map.with_index do |line, idx|
266
275
  col = idx.zero? ? node_column : line[/\A\s*/].length
276
+
267
277
  { text: line.chomp, column: col }
268
278
  end
269
279
  end
@@ -384,6 +394,7 @@ module RuboCop
384
394
  # @return [Integer]
385
395
  def priority_for(category, _node, is_model)
386
396
  priorities = is_model ? MODEL_PRIORITIES : CLASS_PRIORITIES
397
+
387
398
  priorities[category] || 999
388
399
  end
389
400
 
@@ -410,6 +421,7 @@ module RuboCop
410
421
  # @return [String]
411
422
  def scope_sort_key(node)
412
423
  first_arg = node.first_argument
424
+
413
425
  return "" if first_arg.nil?
414
426
  return "" unless first_arg.sym_type?
415
427
 
@@ -471,6 +483,7 @@ module RuboCop
471
483
  base_column = calculate_base_indent(elements)
472
484
  replacement = build_replacement(sorted, base_column)
473
485
  range = replacement_range(class_node, elements)
486
+
474
487
  corrector.replace(range, replacement.chomp)
475
488
  end
476
489
 
@@ -544,6 +557,7 @@ module RuboCop
544
557
  # @return [Integer] The base indentation column.
545
558
  def calculate_base_indent(elements)
546
559
  first_elem = elements.min_by { |e| e[:node].source_range.begin_pos }
560
+
547
561
  first_elem[:node].source_range.column
548
562
  end
549
563
 
@@ -558,6 +572,7 @@ module RuboCop
558
572
 
559
573
  source_lines.map do |line|
560
574
  relative_indent = " " * [0, line[:column] - min_column].max
575
+
561
576
  "#{indent}#{relative_indent}#{line[:text].lstrip}"
562
577
  end.join("\n")
563
578
  end
@@ -575,6 +590,7 @@ module RuboCop
575
590
  state[:category] = element[:category]
576
591
 
577
592
  rendered_source = render_source(element[:source], state[:column])
593
+
578
594
  state[:parts] << rendered_source << "\n"
579
595
  end
580
596
 
@@ -587,6 +603,7 @@ module RuboCop
587
603
  return if new_visibility == state[:visibility]
588
604
 
589
605
  indent = " " * state[:column]
606
+
590
607
  state[:parts] << "\n" if state[:parts].any?
591
608
  state[:parts] << "#{indent}#{new_visibility}\n"
592
609
  end
@@ -601,7 +618,9 @@ module RuboCop
601
618
  # @param [Symbol] last_category The last category.
602
619
  # @return [void]
603
620
  def add_category_separator(parts, _category, last_category)
604
- parts << "\n" if last_category
621
+ if last_category
622
+ parts << "\n"
623
+ end
605
624
  end
606
625
  end
607
626
  end
@@ -26,6 +26,7 @@ module RuboCop
26
26
  # service = Users::Activate.new # Separate group, not aligned
27
27
  class ConsecutiveAssignmentAlignment < Base
28
28
  extend AutoCorrector
29
+ include AlignmentHelpers
29
30
 
30
31
  MSG = "Align consecutive assignments at the = operator."
31
32
 
@@ -59,98 +60,12 @@ module RuboCop
59
60
  # @return [void]
60
61
  def check_assignments_in_body(body)
61
62
  statements = extract_statements(body)
62
- return if statements.size < 2
63
-
64
- groups = group_consecutive_assignments(statements)
65
- groups.each { |group| check_group_alignment(group) }
66
- end
67
-
68
- # Extract statements from a body node.
69
- #
70
- # @param [RuboCop::AST::Node] body The body node.
71
- # @return [Array<RuboCop::AST::Node>]
72
- def extract_statements(body)
73
- if body.begin_type?
74
- body.children
75
- else
76
- [body]
77
- end
78
- end
79
-
80
- # Group consecutive assignments together.
81
- #
82
- # @param [Array<RuboCop::AST::Node>] statements The statements.
83
- # @return [Array<Array<RuboCop::AST::Node>>] Groups of consecutive assignments.
84
- def group_consecutive_assignments(statements)
85
- groups = []
86
- current_group = []
87
- previous_line = nil
88
-
89
- statements.each do |statement|
90
- current_group, previous_line = process_statement(statement, current_group, previous_line, groups)
91
- end
92
-
93
- finalize_groups(groups, current_group)
94
- end
95
-
96
- # Process a single statement for grouping.
97
- #
98
- # @param [RuboCop::AST::Node] statement The statement.
99
- # @param [Array<RuboCop::AST::Node>] current_group The current group.
100
- # @param [Integer, nil] previous_line The previous line number.
101
- # @param [Array<Array<RuboCop::AST::Node>>] groups The groups.
102
- # @return [Array] The updated current_group and previous_line.
103
- def process_statement(statement, current_group, previous_line, groups)
104
- if local_variable_assignment?(statement)
105
- current_group = handle_assignment(statement, current_group, previous_line, groups)
106
- else
107
- save_group_if_valid(groups, current_group)
108
- current_group = []
109
- end
110
- [current_group, statement.loc.last_line]
111
- end
112
63
 
113
- # Handle an assignment statement.
114
- #
115
- # @param [RuboCop::AST::Node] statement The assignment statement.
116
- # @param [Array<RuboCop::AST::Node>] current_group The current group.
117
- # @param [Integer, nil] previous_line The previous line number.
118
- # @param [Array<Array<RuboCop::AST::Node>>] groups The groups.
119
- # @return [Array<RuboCop::AST::Node>] The updated current group.
120
- def handle_assignment(statement, current_group, previous_line, groups)
121
- if previous_line && statement.loc.line - previous_line > 1
122
- save_group_if_valid(groups, current_group)
123
- current_group = []
124
- end
125
- current_group << statement
126
- current_group
127
- end
64
+ return if statements.size < 2
128
65
 
129
- # Save group if it has multiple assignments.
130
- #
131
- # @param [Array<Array<RuboCop::AST::Node>>] groups The groups.
132
- # @param [Array<RuboCop::AST::Node>] group The group to potentially save.
133
- # @return [void]
134
- def save_group_if_valid(groups, group)
135
- groups << group if group.size > 1
136
- end
66
+ groups = group_consecutive_statements(statements, &:lvasgn_type?)
137
67
 
138
- # Finalize groups by adding any remaining valid group.
139
- #
140
- # @param [Array<Array<RuboCop::AST::Node>>] groups The groups.
141
- # @param [Array<RuboCop::AST::Node>] current_group The current group.
142
- # @return [Array<Array<RuboCop::AST::Node>>] The finalized groups.
143
- def finalize_groups(groups, current_group)
144
- save_group_if_valid(groups, current_group)
145
- groups
146
- end
147
-
148
- # Check if node is a local variable assignment.
149
- #
150
- # @param [RuboCop::AST::Node] node The node.
151
- # @return [Boolean]
152
- def local_variable_assignment?(node)
153
- node.lvasgn_type?
68
+ groups.each { |group| check_group_alignment(group) }
154
69
  end
155
70
 
156
71
  # Check alignment for a group of assignments.
@@ -163,6 +78,7 @@ module RuboCop
163
78
 
164
79
  group.each do |asgn|
165
80
  current_column = asgn.loc.operator.column
81
+
166
82
  next if current_column == target_column
167
83
 
168
84
  add_offense(asgn.loc.name) do |corrector|
@@ -200,19 +116,6 @@ module RuboCop
200
116
 
201
117
  [1, current_spaces + spaces_needed].max
202
118
  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
119
  end
217
120
  end
218
121
  end