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
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Vibe
6
+ # Enforces extracting compound boolean conditions into named methods.
7
+ #
8
+ # When a conditional has multiple boolean expressions joined by `&&` or
9
+ # `||`, the intent becomes unclear. Extracting to a descriptively-named
10
+ # method documents the business logic.
11
+ #
12
+ # This cop does NOT flag compound conditions that are:
13
+ # - The implied return value of a method (the extraction target)
14
+ #
15
+ # This cop DOES flag compound conditions that are:
16
+ # - In if/unless/while/until/when/ternary conditions
17
+ # - Inside an explicit return statement
18
+ #
19
+ # @example
20
+ # # bad - multiple conditions obscure intent
21
+ # if user.active? && user.verified?
22
+ # grant_access
23
+ # end
24
+ #
25
+ # # bad - mixing operators
26
+ # return if admin? || moderator?
27
+ #
28
+ # # bad - negation wrapping compound
29
+ # if !(order.paid? && order.shipped?)
30
+ # send_reminder
31
+ # end
32
+ #
33
+ # # bad - explicit return with compound
34
+ # def can_participate?
35
+ # return admin? || moderator? if override?
36
+ # check_other_conditions
37
+ # end
38
+ #
39
+ # # good - extracted to named method
40
+ # if user.can_participate?
41
+ # grant_access
42
+ # end
43
+ #
44
+ # # good - compound as implied method return value (this is the extraction)
45
+ # def can_participate?
46
+ # user.active? && user.verified?
47
+ # end
48
+ class NoCompoundConditions < Base
49
+ MSG = "Extract compound conditions into a named method."
50
+
51
+ # Check and/or nodes for conditional context.
52
+ #
53
+ # @param [RuboCop::AST::Node] node The and/or node.
54
+ # @return [void]
55
+ def on_and(node)
56
+ check_compound(node)
57
+ end
58
+ alias on_or on_and
59
+
60
+ private
61
+
62
+ # Check if a compound condition should be flagged.
63
+ #
64
+ # @param [RuboCop::AST::Node] node The and/or node.
65
+ # @return [void]
66
+ def check_compound(node)
67
+ if inside_return_statement?(node) || in_conditional_position?(node)
68
+ add_offense(node)
69
+ end
70
+ end
71
+
72
+ # Check if node is in a conditional position (if/unless/while/until/when condition).
73
+ #
74
+ # @param [RuboCop::AST::Node] node The and/or node.
75
+ # @return [Boolean]
76
+ def in_conditional_position?(node)
77
+ ancestor = conditional_ancestor(node)
78
+
79
+ if ancestor
80
+ condition_of_ancestor?(node, ancestor)
81
+ else
82
+ false
83
+ end
84
+ end
85
+
86
+ # Find the nearest conditional ancestor (if/while/until/when).
87
+ #
88
+ # @param [RuboCop::AST::Node] node The node.
89
+ # @return [RuboCop::AST::Node, nil]
90
+ def conditional_ancestor(node)
91
+ node.each_ancestor.find { |a| a.type?(:if, :while, :until, :when) }
92
+ end
93
+
94
+ # Check if node is (part of) the condition of the ancestor.
95
+ #
96
+ # @param [RuboCop::AST::Node] node The and/or node.
97
+ # @param [RuboCop::AST::Node] ancestor The if/while/until/when ancestor.
98
+ # @return [Boolean]
99
+ def condition_of_ancestor?(node, ancestor)
100
+ condition = condition_node(ancestor)
101
+
102
+ node_within_condition?(node, condition)
103
+ end
104
+
105
+ # Extract the condition node from a conditional ancestor.
106
+ #
107
+ # @param [RuboCop::AST::Node] ancestor The conditional ancestor.
108
+ # @return [RuboCop::AST::Node]
109
+ def condition_node(ancestor)
110
+ if ancestor.type?(:if, :while, :until)
111
+ ancestor.condition
112
+ else
113
+ ancestor
114
+ end
115
+ end
116
+
117
+ # Check if node is within the condition subtree.
118
+ #
119
+ # @param [RuboCop::AST::Node] node The node to find.
120
+ # @param [RuboCop::AST::Node] condition The condition root.
121
+ # @return [Boolean]
122
+ def node_within_condition?(node, condition)
123
+ return true if condition == node
124
+
125
+ condition.each_descendant.any?(node)
126
+ end
127
+
128
+ # Check if node is inside a return statement.
129
+ #
130
+ # @param [RuboCop::AST::Node] node The node.
131
+ # @return [Boolean]
132
+ def inside_return_statement?(node)
133
+ node.each_ancestor.any?(&:return_type?)
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -33,6 +33,9 @@ module RuboCop
33
33
  # task :my_task do
34
34
  # # ...
35
35
  # end
36
+ #
37
+ # NOTE: Examples use underscore (rubocop_disable) to prevent RuboCop from
38
+ # parsing them as actual directives. In real code, use colon (rubocop:disable).
36
39
  class NoRubocopDisable < Base
37
40
  MSG = "Do not disable `%<cops>s`. Fix the issue or configure globally in `.rubocop.yml`."
38
41
  MSG_NO_COP = "Do not use `# rubocop:disable`. Fix the issue or configure globally in `.rubocop.yml`."
@@ -105,6 +105,7 @@ module RuboCop
105
105
  # @return [void]
106
106
  def check_x_method(node)
107
107
  method_name = x_method_call?(node)
108
+
108
109
  if method_name
109
110
  add_offense(node, message: format(MSG_XMETHOD, method: method_name))
110
111
  end
@@ -98,6 +98,7 @@ module RuboCop
98
98
  def following_statements?(parent, node)
99
99
  siblings = parent.children
100
100
  node_index = siblings.index(node)
101
+
101
102
  siblings[(node_index + 1)..].any?
102
103
  end
103
104
 
@@ -151,6 +152,7 @@ module RuboCop
151
152
  def autocorrect(corrector, node)
152
153
  replacement = build_replacement(node)
153
154
  range = replacement_range(node)
155
+
154
156
  corrector.replace(range, replacement)
155
157
  end
156
158
 
@@ -212,6 +214,7 @@ module RuboCop
212
214
  def get_remaining_code(node)
213
215
  siblings = node.parent.children
214
216
  node_index = siblings.index(node)
217
+
215
218
  siblings[(node_index + 1)..].map(&:source).join("\n")
216
219
  end
217
220
 
@@ -223,6 +226,7 @@ module RuboCop
223
226
  siblings = node.parent.children
224
227
  start_pos = node.source_range.begin_pos
225
228
  end_pos = siblings.last.source_range.end_pos
229
+
226
230
  Parser::Source::Range.new(node.source_range.source_buffer, start_pos, end_pos)
227
231
  end
228
232
 
@@ -57,6 +57,7 @@ module RuboCop
57
57
  return unless single_statement?(node.body)
58
58
 
59
59
  expectation = extract_expectation(node.body)
60
+
60
61
  return unless simple_expectation?(expectation)
61
62
  return if complex_expectation?(node)
62
63
 
@@ -99,6 +100,7 @@ module RuboCop
99
100
  # @return [void]
100
101
  def autocorrect(corrector, node)
101
102
  expectation_source = node.body.source
103
+
102
104
  corrector.replace(node, "it { #{expectation_source} }")
103
105
  end
104
106
 
@@ -141,6 +143,7 @@ module RuboCop
141
143
  def find_expectation_in_chain(node)
142
144
  # Traverse up the chain looking for expect or is_expected.
143
145
  current = node
146
+
144
147
  while current&.send_type?
145
148
  return current if expectation_method?(current)
146
149
 
@@ -174,6 +177,7 @@ module RuboCop
174
177
  # @return [Boolean]
175
178
  def expect_subject?(node)
176
179
  argument = node.first_argument
180
+
177
181
  return false unless argument
178
182
  return false unless argument.send_type?
179
183
 
@@ -63,6 +63,7 @@ module RuboCop
63
63
  # @return [void]
64
64
  def autocorrect(corrector, node)
65
65
  replacement = build_replacement(node)
66
+
66
67
  corrector.replace(node, replacement)
67
68
  end
68
69
 
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Vibe
6
+ # Enforces using `do...end` syntax instead of braces for RSpec `before` blocks.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # before { setup_data }
11
+ #
12
+ # # bad
13
+ # before(:each) { setup_data }
14
+ #
15
+ # # good
16
+ # before do
17
+ # setup_data
18
+ # end
19
+ #
20
+ # # good
21
+ # before(:each) do
22
+ # setup_data
23
+ # end
24
+ class RspecBeforeBlockStyle < Base
25
+ extend AutoCorrector
26
+ include SpecFileHelper
27
+
28
+ MSG = "Use `do...end` block syntax instead of braces for `%<method>s` blocks."
29
+
30
+ HOOK_METHODS = %i(before after around).freeze
31
+
32
+ # Check block nodes for brace-style hooks.
33
+ #
34
+ # @param [RuboCop::AST::Node] node The block node.
35
+ # @return [void]
36
+ def on_block(node)
37
+ return unless spec_file?
38
+ return unless hook_block?(node)
39
+ return unless node.braces?
40
+
41
+ method_name = node.method_name
42
+
43
+ add_offense(node.loc.begin, message: format(MSG, method: method_name)) do |corrector|
44
+ autocorrect(corrector, node)
45
+ end
46
+ end
47
+ alias on_numblock on_block
48
+
49
+ private
50
+
51
+ # Check if the block is a hook block (before, after, around).
52
+ #
53
+ # @param [RuboCop::AST::Node] node The block node.
54
+ # @return [Boolean]
55
+ def hook_block?(node)
56
+ send_node = node.send_node
57
+
58
+ send_node.receiver.nil? && HOOK_METHODS.include?(send_node.method_name)
59
+ end
60
+
61
+ # Autocorrect the offense by converting braces to do...end.
62
+ #
63
+ # @param [RuboCop::Cop::Corrector] corrector The corrector.
64
+ # @param [RuboCop::AST::Node] node The block node.
65
+ # @return [void]
66
+ def autocorrect(corrector, node)
67
+ base_indent = " " * node.loc.column
68
+ replacement = build_replacement(node, base_indent)
69
+
70
+ corrector.replace(block_range(node), replacement)
71
+ end
72
+
73
+ # Build the replacement string for the block.
74
+ #
75
+ # @param [RuboCop::AST::Node] node The block node.
76
+ # @param [String] base_indent The base indentation.
77
+ # @return [String]
78
+ def build_replacement(node, base_indent)
79
+ parts = ["do"]
80
+
81
+ if node.arguments.source_range
82
+ parts << " #{node.arguments.source}"
83
+ end
84
+
85
+ if node.body
86
+ body_source = format_body(node.body, "#{base_indent} ")
87
+ parts << "\n#{body_source}"
88
+ end
89
+
90
+ parts << "\n#{base_indent}end"
91
+
92
+ parts.join
93
+ end
94
+
95
+ # Get the range from the opening brace to the closing brace.
96
+ #
97
+ # @param [RuboCop::AST::Node] node The block node.
98
+ # @return [Parser::Source::Range]
99
+ def block_range(node)
100
+ node.loc.begin.join(node.loc.end)
101
+ end
102
+
103
+ # Format the body with proper indentation.
104
+ #
105
+ # @param [RuboCop::AST::Node] body The body node.
106
+ # @param [String] indent The indentation string.
107
+ # @return [String]
108
+ def format_body(body, indent)
109
+ body.source.lines.map { |line| "#{indent}#{line.strip}" }.join("\n")
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -85,11 +85,13 @@ module RuboCop
85
85
  def check_receive_chain(to_node, receive_chain)
86
86
  chain = extract_chain(receive_chain)
87
87
  receive_index = find_receive_index(chain)
88
+
88
89
  return unless receive_index
89
90
  return unless chain_has_with?(chain)
90
91
  return unless line_exceeds_max_length?(to_node)
91
92
 
92
93
  methods = chain[(receive_index + 1)..]
94
+
93
95
  check_methods_alignment(to_node, chain[receive_index], methods)
94
96
  end
95
97
 
@@ -170,6 +172,7 @@ module RuboCop
170
172
  # @return [Boolean]
171
173
  def should_flag?(previous_node, method_node, is_first)
172
174
  reference_line = is_first ? previous_node.loc.last_line : previous_node.loc.selector.line
175
+
173
176
  reference_line == method_node.loc.selector.line
174
177
  end
175
178
 
@@ -223,6 +226,7 @@ module RuboCop
223
226
  # @return [String]
224
227
  def calculate_indentation(node)
225
228
  base_column = find_chain_start_column(node)
229
+
226
230
  " " * (base_column + 2)
227
231
  end
228
232
 
@@ -1,19 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "vibe/mixin/alignment_helpers"
3
4
  require_relative "vibe/mixin/spec_file_helper"
4
5
 
6
+ require_relative "vibe/blank_line_after_assignment"
5
7
  require_relative "vibe/blank_line_before_expectation"
6
8
  require_relative "vibe/class_organization"
7
9
  require_relative "vibe/consecutive_assignment_alignment"
8
10
  require_relative "vibe/consecutive_constant_alignment"
11
+ require_relative "vibe/consecutive_indexed_assignment_alignment"
12
+ require_relative "vibe/consecutive_instance_variable_assignment_alignment"
9
13
  require_relative "vibe/consecutive_let_alignment"
10
14
  require_relative "vibe/describe_block_order"
15
+ require_relative "vibe/explicit_return_conditional"
11
16
  require_relative "vibe/is_expected_one_liner"
17
+ require_relative "vibe/let_order"
18
+ require_relative "vibe/multiline_hash_argument_style"
12
19
  require_relative "vibe/no_assigns_attribute_testing"
20
+ require_relative "vibe/no_compound_conditions"
13
21
  require_relative "vibe/no_rubocop_disable"
14
22
  require_relative "vibe/no_skipped_tests"
15
23
  require_relative "vibe/no_unless_guard_clause"
16
24
  require_relative "vibe/prefer_one_liner_expectation"
17
25
  require_relative "vibe/raise_unless_block"
26
+ require_relative "vibe/rspec_before_block_style"
18
27
  require_relative "vibe/rspec_stub_chain_style"
19
28
  require_relative "vibe/service_call_method"
@@ -10,10 +10,10 @@ module RuboCop
10
10
  # @return [LintRoller::About] Information about the plug-in.
11
11
  def about
12
12
  LintRoller::About.new(
13
- name: "rubocop-vibe",
14
- version: VERSION,
13
+ description: "A set of custom cops to use on AI generated code.",
15
14
  homepage: "https://github.com/tristandunn/rubocop-vibe",
16
- description: "A set of custom cops to use on AI generated code."
15
+ name: "rubocop-vibe",
16
+ version: VERSION
17
17
  )
18
18
  end
19
19
 
@@ -23,8 +23,8 @@ module RuboCop
23
23
  # @return [LintRoller::Rules] The rules for this plug-in.
24
24
  def rules(_context)
25
25
  LintRoller::Rules.new(
26
- type: :path,
27
26
  config_format: :rubocop,
27
+ type: :path,
28
28
  value: Pathname.new(__dir__).join("../../../config/default.yml")
29
29
  )
30
30
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Vibe
5
- VERSION = "0.2.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-vibe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tristan Dunn
@@ -87,20 +87,29 @@ extra_rdoc_files: []
87
87
  files:
88
88
  - config/default.yml
89
89
  - lib/rubocop-vibe.rb
90
+ - lib/rubocop/cop/vibe/blank_line_after_assignment.rb
90
91
  - lib/rubocop/cop/vibe/blank_line_before_expectation.rb
91
92
  - lib/rubocop/cop/vibe/class_organization.rb
92
93
  - lib/rubocop/cop/vibe/consecutive_assignment_alignment.rb
93
94
  - lib/rubocop/cop/vibe/consecutive_constant_alignment.rb
95
+ - lib/rubocop/cop/vibe/consecutive_indexed_assignment_alignment.rb
96
+ - lib/rubocop/cop/vibe/consecutive_instance_variable_assignment_alignment.rb
94
97
  - lib/rubocop/cop/vibe/consecutive_let_alignment.rb
95
98
  - lib/rubocop/cop/vibe/describe_block_order.rb
99
+ - lib/rubocop/cop/vibe/explicit_return_conditional.rb
96
100
  - lib/rubocop/cop/vibe/is_expected_one_liner.rb
101
+ - lib/rubocop/cop/vibe/let_order.rb
102
+ - lib/rubocop/cop/vibe/mixin/alignment_helpers.rb
97
103
  - lib/rubocop/cop/vibe/mixin/spec_file_helper.rb
104
+ - lib/rubocop/cop/vibe/multiline_hash_argument_style.rb
98
105
  - lib/rubocop/cop/vibe/no_assigns_attribute_testing.rb
106
+ - lib/rubocop/cop/vibe/no_compound_conditions.rb
99
107
  - lib/rubocop/cop/vibe/no_rubocop_disable.rb
100
108
  - lib/rubocop/cop/vibe/no_skipped_tests.rb
101
109
  - lib/rubocop/cop/vibe/no_unless_guard_clause.rb
102
110
  - lib/rubocop/cop/vibe/prefer_one_liner_expectation.rb
103
111
  - lib/rubocop/cop/vibe/raise_unless_block.rb
112
+ - lib/rubocop/cop/vibe/rspec_before_block_style.rb
104
113
  - lib/rubocop/cop/vibe/rspec_stub_chain_style.rb
105
114
  - lib/rubocop/cop/vibe/service_call_method.rb
106
115
  - lib/rubocop/cop/vibe_cops.rb
@@ -129,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
138
  - !ruby/object:Gem::Version
130
139
  version: '0'
131
140
  requirements: []
132
- rubygems_version: 4.0.3
141
+ rubygems_version: 4.0.4
133
142
  specification_version: 4
134
143
  summary: A set of custom cops to use on AI generated code.
135
144
  test_files: []