Class: RuboCop::Cop::RSpecGuide::InvariantExamples
- Inherits:
-
RuboCop::Cop::RSpec::Base
- Object
- RuboCop::Cop::RSpec::Base
- RuboCop::Cop::RSpecGuide::InvariantExamples
- Defined in:
- lib/rubocop/cop/rspec_guide/invariant_examples.rb
Overview
Detects examples that repeat in all leaf contexts.
When the same test appears in all leaf contexts, it indicates an invariant - a property that holds true regardless of the context. These invariants represent interface contracts and should be extracted to shared_examples for reusability and clarity.
The cop only reports when examples appear in MinLeafContexts or more contexts (default: 3) to avoid false positives.
Constant Summary collapse
- MSG =
"Example `%<description>s` repeats in all %<count>d leaf contexts. " \ "Consider extracting to shared_examples as an interface invariant."
Instance Method Summary collapse
-
#context_or_describe_block?(node) ⇒ Object
Fast local matcher for nested context/describe checks (performance-critical).
- #example_with_description?(node) ⇒ Object
- #on_block(node) ⇒ Object
Instance Method Details
#context_or_describe_block?(node) ⇒ Object
Fast local matcher for nested context/describe checks (performance-critical)
102 103 104 105 106 |
# File 'lib/rubocop/cop/rspec_guide/invariant_examples.rb', line 102 def_node_matcher :context_or_describe_block?, "(block\n (send nil? {:describe :context} ...)\n ...)\n" |
#example_with_description?(node) ⇒ Object
109 110 111 112 113 |
# File 'lib/rubocop/cop/rspec_guide/invariant_examples.rb', line 109 def_node_matcher :example_with_description?, "(block\n (send nil? {:it :specify :example} (str $_description))\n ...)\n" |
#on_block(node) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/rubocop/cop/rspec_guide/invariant_examples.rb', line 115 def on_block(node) # Fast pre-check: only process top-level describe (not context) return unless node.method?(:describe) return unless example_group?(node) # Find all leaf contexts (contexts with no nested contexts) leaf_contexts = find_leaf_contexts(node) min_leaf_contexts = cop_config["MinLeafContexts"] || 3 return if leaf_contexts.size < min_leaf_contexts # Collect example descriptions from each leaf examples_by_leaf = leaf_contexts.map do |leaf| collect_example_descriptions(leaf) end # Find descriptions that appear in ALL leaves common_descriptions = examples_by_leaf.reduce(:&) return if common_descriptions.nil? || common_descriptions.empty? # Add offenses for all examples with common descriptions leaf_contexts.each do |leaf| leaf.each_descendant(:block) do |example_node| example_with_description?(example_node) do |description| if common_descriptions.include?(description) add_offense( example_node, message: format(MSG, description: description, count: leaf_contexts.size) ) end end end end end |