Class: RuboCop::Cop::RSpecGuide::DuplicateLetValues

Inherits:
RuboCop::Cop::RSpec::Base
  • Object
show all
Defined in:
lib/rubocop/cop/rspec_guide/duplicate_let_values.rb

Overview

Detects duplicate let declarations with identical values across sibling contexts.

When the same let with the same value appears in multiple sibling contexts, it indicates one of two problems:

  1. ERROR: Duplicate in ALL contexts → must extract to parent
  2. WARNING: Duplicate in SOME contexts → suggests poor test hierarchy

Examples:

ERROR - duplicate in ALL contexts

# bad - let(:currency) duplicated in all 2 contexts
describe 'Calculator' do
  context 'with addition' do
    let(:currency) { :usd }
    it { expect(result).to eq(10) }
  end

  context 'with subtraction' do
    let(:currency) { :usd }  # ERROR: in all contexts!
    it { expect(result).to eq(5) }
  end
end

# good - extracted to parent
describe 'Calculator' do
  let(:currency) { :usd }  # Moved to parent

  context 'with addition' do
    it { expect(result).to eq(10) }
  end

  context 'with subtraction' do
    it { expect(result).to eq(5) }
  end
end

WARNING - duplicate in SOME contexts

# bad - let(:mode) duplicated in 2/3 contexts (code smell)
describe 'Processor' do
  context 'scenario A' do
    let(:mode) { :standard }
    it { expect(result).to eq('A') }
  end

  context 'scenario B' do
    let(:mode) { :standard }  # WARNING: duplicated in 2/3
    it { expect(result).to eq('B') }
  end

  context 'scenario C' do
    let(:mode) { :advanced }  # Different value
    it { expect(result).to eq('C') }
  end
end

# good - refactor hierarchy
describe 'Processor' do
  context 'with standard mode' do
    let(:mode) { :standard }

    context 'scenario A' do
      it { expect(result).to eq('A') }
    end

    context 'scenario B' do
      it { expect(result).to eq('B') }
    end
  end

  context 'with advanced mode' do
    let(:mode) { :advanced }

    context 'scenario C' do
      it { expect(result).to eq('C') }
    end
  end
end

Configuration

# To disable warnings for partial duplicates:
RSpecGuide/DuplicateLetValues:
  WarnOnPartialDuplicates: false  # Only report full duplicates

Edge case - different values

# good - same let name but different values (no duplicate)
describe 'Converter' do
  context 'to USD' do
    let(:currency) { :usd }
    it { expect(convert).to eq(100) }
  end

  context 'to EUR' do
    let(:currency) { :eur }  # Different value, OK
    it { expect(convert).to eq(85) }
  end
end

Constant Summary collapse

MSG_ERROR =
"Duplicate `let(:%<name>s)` with same value `%<value>s` " \
"in ALL sibling contexts. Extract to parent context."
MSG_WARNING =
"Let `:%<name>s` with value `%<value>s` duplicated in %<count>d/%<total>d contexts. " \
"Consider refactoring test hierarchy - this suggests poor organization."

Instance Method Summary collapse

Instance Method Details

#context_only?(node) ⇒ Object



115
116
117
# File 'lib/rubocop/cop/rspec_guide/duplicate_let_values.rb', line 115

def_node_matcher :context_only?, "(block (send nil? :context ...) ...)\n"

#let_with_name_and_value?(node) ⇒ Object



120
121
122
123
124
125
# File 'lib/rubocop/cop/rspec_guide/duplicate_let_values.rb', line 120

def_node_matcher :let_with_name_and_value?, "(block\n  (send nil? {:let :let!} (sym $_name))\n  (args)\n  $_value)\n"

#on_block(node) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/rubocop/cop/rspec_guide/duplicate_let_values.rb', line 127

def on_block(node)
  # Fast pre-check: only process describe/context blocks
  return unless node.method?(:describe) || node.method?(:context)
  return unless example_group?(node)

  # Collect all sibling contexts
  sibling_contexts = collect_sibling_contexts(node)
  return if sibling_contexts.size < 2

  # Collect let declarations from each context
  lets_by_context = sibling_contexts.map do |ctx|
    collect_lets_in_context(ctx)
  end

  # Find duplicates
  find_duplicate_lets(lets_by_context)
end