Skip to content

[bot] Fix RSpec/EmptyExampleGroup#864

Merged
6[bot] merged 3 commits intomainfrom
fix/rspec-empty_example_group-23729120326
Mar 30, 2026
Merged

[bot] Fix RSpec/EmptyExampleGroup#864
6[bot] merged 3 commits intomainfrom
fix/rspec-empty_example_group-23729120326

Conversation

@6
Copy link
Copy Markdown
Contributor

@6 6 bot commented Mar 30, 2026

Automated fix fix for RSpec/EmptyExampleGroup corpus conformance.

Details

Cop RSpec/EmptyExampleGroup
Backend claude-oauth / hard
Model Claude Opus 4.6 (OAuth, high)
Mode fix
Turns 49
Run #419

Result

All 5 FNs resolved with 0 regressions. Here's a summary:

**Three root causes fixed in `ExampleFinder`:**

1. **Local variable assignments** (3 FN): Added `visit_local_variable_write_node` to skip `lvasgn` nodes like `examples = [...].each { it ... }` and `meta = example(...)`. RuboCop's `examples?` patterns don't match `lvasgn`.

2. **Method call arguments** (1 FN): Changed the fallthrough from `ruby_prism::visit_call_node` to only visiting the block child. This prevents bare `example` calls used as arguments (e.g., `Pincers.for_nokogiri example` inside a `let` block) from being falsely recognized as RSpec example methods.

3. **For loops** (1 FN): Added `visit_for_node` to skip `for...in` loops. RuboCop's `examples_inside_block?` only matches `(block ...)` nodes, not `(for ...)`.

**Corpus validation:** 0 FP, 0 FN — all 5 FNs resolved, no regressions in the existing 4,435 matches.

Changes

  1 files changed
Agent conversation excerpt

Let me start by reading the required files to understand the problem.

Read: /home/runner/work/nitrocop/nitrocop/docs/agent-ci.md

Read: /home/runner/work/nitrocop/nitrocop/src/cop/rspec/empty_example_group.rs

Read: /home/runner/work/nitrocop/nitrocop/vendor/rubocop-rspec/lib/rubocop/cop/rspec/empty_example_group.rb

Now let me check the utility functions and the RuboCop test file.

Grep: fn is_rspec_example

Read: /home/runner/work/nitrocop/nitrocop/vendor/rubocop-rspec/spec/rubocop/cop/rspec/empty_example_group_spec.rb

Let me check the RSPEC_EXAMPLES constant to understand what methods are recognized as examples.

Grep: RSPEC_EXAMPLES

Let me also look at the RSPEC_EXAMPLE_GROUPS constant to understand what's recognized as an example group.

Grep: RSPEC_EXAMPLE_GROUPS

Let me check the fixture directory structure.

Bash: ls /home/runner/work/nitrocop/nitrocop/tests/fixtures/cops/rspec/empty_example_group/

Bash: ls /home/runner/work/nitrocop/nitrocop/tests/fixtures/cops/rspec/empty_example_group/offense/

Good. Now let me investigate FN #2 further before writing any code.

Bash: python3 scripts/investigate_cop.py RSpec/EmptyExampleGroup --context 2>&1 | head -120

Let me look at the full pincers file to understand FN #2.

Bash: python3 scripts/investigate_cop.py RSpec/EmptyExampleGroup --repos-only 2>&1

Bash: if [ -d /tmp/corpus-repos/platanus__pincers__f652bff ]; then cat /tmp/corpus-repos/platanus__pincers__f652bff/spec/lib/nokogiri/backend_spec.rb; else echo "NOT CACHED"; fi

Bash: python3 scripts/check_cop.py RSpec/EmptyExampleGroup --rerun --clone --sample 5 2>&1 | tail -30

Bash: find /tmp/corpus-repos -path "*pincers*backend_spec.rb" 2>/dev/null | head -5

Bash: find /tmp -name "backend_spec.rb" -path "*pincers*" 2>/dev/null; find /tmp -type d -name "*pincers*" 2>/dev/null | head -5

Read: /tmp/nitrocop_cop_check_7kyjptf4/repos/platanus__pincers__f652bff/spec/lib/nokogiri/backend_spec.rb

Bash: ./target/release/nitrocop --only RSpec/EmptyExampleGroup --force-default-config /tmp/nitrocop_cop_check_7kyjptf4/repos/platanus__pincers__f652bff/spec/lib/nokogiri/backend_spec.rb 2>&1

Bash: cat > /tmp/test_pincers.rb << 'EOF' describe 'Pincers::Nokogiri::Backend' do let!(:example) { ::Nokogiri::HTML "test" } let(:pincers) { "test" } it_should_properly_read_the_example it_should_s

Bash: cat > /tmp/test_pincers2.rb << 'EOF' describe 'Test' do it_should_properly_read_the_example end EOF ./target/release/nitrocop --only RSpec/EmptyExampleGroup --force-default-config /tmp/test_pincers2

Bash: cat > /tmp/test_bare.rb << 'EOF' describe 'Test' do foo_bar end EOF ./target/release/nitrocop --only RSpec/EmptyExampleGroup --force-default-config /tmp/test_bare.rb 2>&1

Bash: cat > /tmp/test_empty.rb << 'EOF' describe 'Test' do let(:x) { 1 } end EOF ./target/release/nitrocop --only RSpec/EmptyExampleGroup --force-default-config /tmp/test_empty.rb 2>&1

Bash: cat > /tmp/test_empty2.rb << 'EOF' describe Foo do context 'when bar' do let(:foo) { bar } end end EOF ./target/release/nitrocop --only RSpec/EmptyExampleGroup --force-default-config /tmp/test

Grep: RSPEC_DEFAULT_INCLUDE

Bash: cat > /tmp/test_spec.rb << 'EOF' describe Foo do context 'when bar' do let(:foo) { bar } end end EOF ./target/release/nitrocop --only RSpec/EmptyExampleGroup --force-default-config /tmp/test_s

Bash: mkdir -p /tmp/spec && cat > /tmp/spec/test_spec.rb << 'EOF' describe Foo do context 'when bar' do let(:foo) { bar } end end EOF ./target/release/nitrocop --only RSpec/EmptyExampleGroup --force

Bash: cargo test --lib -- cop::rspec::empty_example_group 2>&1

Bash: cd /tmp/nitrocop_cop_check_7kyjptf4/repos/platanus__pincers__f652bff && /home/runner/work/nitrocop/nitrocop/target/release/nitrocop --only RSpec/EmptyExampleGroup --force-default-config spec/lib/nokog

Now I have a clear picture of all 5 FNs. Let me read existing fixture files to understand the format.

Read: /home/runner/work/nitrocop/nitrocop/tests/fixtures/cops/rspec/empty_example_group/offense/empty_context.rb

Read: /home/runner/work/nitrocop/nitrocop/tests/fixtures/cops/rspec/empty_example_group/offense/constant_only.rb

Good. Now I understand the format. Let me create the test fixtures and then modify the code. I'll work through this step by step.

Step 1: Create test fixtures for each FN pattern.

Write

Write

Write

Write

Write

Step 2: Verify tests fail before the fix.

Bash: cargo test --lib -- cop::rspec::empty_example_group 2>&1 | tail -20

I need to register the new fixtures in the test macro first.

Edit: /home/runner/work/nitrocop/nitrocop/src/cop/rspec/empty_example_group.rs

Bash: cargo test --lib -- cop::rspec::empty_example_group 2>&1 | tail -25

All 5 new tests fail as expected. Now let me implement the fix.

Step 3: Fix the ExampleFinder in the Rust implementation.

Read: /home/runner/work/nitrocop/nitrocop/src/cop/rspec/empty_example_group.rs

Now apply the three fixes to ExampleFinder:

Edit: /home/runner/work/nitrocop/nitrocop/src/cop/rspec/empty_example_group.rs

Now add the visit_local_variable_write_node and visit_for_node overrides:

Edit: /home/runner/work/nitrocop/nitrocop/src/cop/rspec/empty_example_group.rs

Step 4: Run tests to verify the fix.

Bash: cargo test --lib -- cop::rspec::empty_example_group 2>&1 | tail -25

All 14 tests pass. Step 5: Validate against the corpus.

Bash: python3 scripts/check_cop.py RSpec/EmptyExampleGroup --rerun --clone --sample 15 2>&1

Task prompt (4936 tokens)

Fix RSpec/EmptyExampleGroup — 0 FP, 5 FN

Instructions

You are fixing ONE cop in nitrocop, a Rust Ruby linter that uses Prism for parsing.

Current state: 4,435 matches, 0 false positives, 5 false negatives.
Focus on: FN (RuboCop flags code nitrocop misses).

⚠ 4,435 existing matches must not regress. Validate with check_cop.py before committing.

Workflow

  1. Read the Pre-diagnostic Results and Corpus FP/FN Examples sections below first
  2. Verify with RuboCop first (for FP fixes): before writing any code, confirm RuboCop's
    behavior on BOTH the specific FP case AND the general pattern:
    echo '<specific FP case>' > /tmp/test.rb && rubocop --only RSpec/EmptyExampleGroup /tmp/test.rb
    echo '<general pattern>' > /tmp/test.rb && rubocop --only RSpec/EmptyExampleGroup /tmp/test.rb
    If RuboCop flags the general pattern, your fix must be narrow enough to not suppress it.
  3. Add a test case FIRST:
    • FN fix: add the missed pattern to tests/fixtures/cops/rspec/empty_example_group/offense.rb with ^ annotation
    • FP fix: add the false-positive pattern to tests/fixtures/cops/rspec/empty_example_group/no_offense.rb
  4. Verify test fails: cargo test --lib -- cop::rspec::empty_example_group
  5. Fix src/cop/rspec/empty_example_group.rs
  6. Verify test passes: cargo test --lib -- cop::rspec::empty_example_group
  7. Validate against corpus (REQUIRED before committing):
    python3 scripts/check_cop.py RSpec/EmptyExampleGroup --rerun --clone --sample 15
    If this reports FP or FN regression, your fix is too broad — narrow it down.
  8. Add a /// doc comment on the cop struct documenting what you found and fixed
  9. Commit only your cop's files

Fixture Format

Mark offenses with ^ markers on the line AFTER the offending source line.
The ^ characters must align with the offending columns. The message format is RSpec/EmptyExampleGroup: <message text>.
See the Current Fixture sections below for real examples from this cop.

If your test passes immediately

If you add a test case and it passes without code changes, the corpus mismatch is
caused by config/context differences, not a detection bug.
Do NOT loop trying to make the test fail. Instead:

  1. Investigate config resolution (Include/Exclude, cop enablement, disable comments)
  2. The fix is likely in src/config/ or the cop's config handling, not detection logic
  3. If you cannot determine the root cause within 5 minutes, document your findings as
    a /// comment on the cop struct and commit

CRITICAL: Avoid regressions in the opposite direction

When fixing FPs, your change MUST NOT suppress legitimate detections. When fixing FNs,
your change MUST NOT flag code that RuboCop accepts. A fix that eliminates a few issues
in one direction but introduces hundreds in the other is a catastrophic regression.

Before exempting a category of patterns, verify with RuboCop that the general case
is still an offense:

rubocop --only RSpec/EmptyExampleGroup /tmp/test.rb

If RuboCop flags the general pattern but not your specific case, the difference is in
a narrow context (e.g., enclosing structure, receiver type, argument count) — your fix
must target that specific context, not the broad category.

Rule of thumb: if your fix adds an early return or continue that skips a whole
node type, operator class, or naming pattern, it's probably too broad. Prefer adding a
condition that matches the SPECIFIC differentiating context.

Rules

  • Only modify src/cop/rspec/empty_example_group.rs and tests/fixtures/cops/rspec/empty_example_group/
  • Run cargo test --lib -- cop::rspec::empty_example_group to verify your fix (do NOT run the full test suite)
  • Run python3 scripts/check_cop.py RSpec/EmptyExampleGroup --rerun --clone --sample 15 before committing to catch regressions
  • Do NOT touch unrelated files
  • Do NOT use git stash
  • Do NOT push — you do not have push permission; the workflow handles pushing after you exit

Current Fixture: offense.rb

tests/fixtures/cops/rspec/empty_example_group/offense.rb

# --- begin_block_with_examples.rb ---
# Examples inside explicit begin..end blocks don't count.
# RuboCop's examples? matcher checks (begin ...) for implicit block bodies
# but not (kwbegin ...) for explicit begin..end blocks.
context 'with begin block' do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RSpec/EmptyExampleGroup: Empty example group detected.
  begin
    FileUtils.ln_s "source.txt", "link.txt"
    it "should handle symlinks" do
      expect(true).to be(true)
    end
  end
end


# --- constant_only.rb ---
describe Foo do
  describe '#to_type' do
  ^^^^^^^^^^^^^^^^^^^^ RSpec/EmptyExampleGroup: Empty example group detected.
    FORMATS = {
      'General' => :float,
      '0' => :float,
    }.each do |format, type|
      it "translates #{format} to #{type}" do
        expect(described_class.to_type(format)).to eq(type)
      end
    end
  end
end


# --- def_self_example_factory.rb ---
# Example group where examples are created dynamically via def self.method
# RuboCop considers this empty because examples? doesn't descend into defs
describe 'interpolation' do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RSpec/EmptyExampleGroup: Empty example group detected.
  def self.assert_interpolates(name, code, expected)
    example(name) { expect(wrap code).to eq expected }
  end

  assert_interpolates 'backtick syscall', '`echo`', '<`echo`>'
end


# --- empty_context.rb ---
describe Foo do
  context 'when bar' do
  ^^^^^^^^^^^^^^^^^^ RSpec/EmptyExampleGroup: Empty example group detected.
    let(:foo) { bar }
  end
end


# --- empty_describe.rb ---
describe Foo do
^^^^^^^^^^^^ RSpec/EmptyExampleGroup: Empty example group detected.
end


# --- hooks_only.rb ---
context 'hook with examples inside hook' do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RSpec/EmptyExampleGroup: Empty example group detected.
  before do
    it { is_expected.to never_run }
  end
end


# --- lambda_with_examples.rb ---
# Examples inside lambdas passed as arguments don't count as examples
# for the enclosing group. RuboCop can't statically detect them.
describe 'dynamic examples via lambda' do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RSpec/EmptyExampleGroup: Empty example group detected.
  each_attribute -> (project, object, attrb) do
    next unless attrb.type == :simple
    it "#{attrb.name}=" do
      expect(true).to be(true)
    end
  end
end


# --- qualified_rspec.rb ---
::RSpec.describe Foo do
^^^^^^^^^^^^^^^^^^^^^^ RSpec/EmptyExampleGroup: Empty example group detected.
end

Current Fixture: no_offense.rb

tests/fixtures/cops/rspec/empty_example_group/no_offense.rb

describe Foo do
  context 'when bar' do
    it { expect(true).to be(true) }
  end

  describe '#baz' do
    specify { expect(subject.baz).to eq(1) }
  end

  context 'with includes' do
    include_examples 'shared stuff'
  end

  it 'not implemented'
end

# shared_context and shared_examples are not example groups
# and should not be flagged even without examples
shared_context 'with standard tweet info' do
  before { @link = 'https://example.com' }
  let(:full_name) { 'Test' }
end

shared_examples 'throttled endpoint' do
  let(:limit) { 25 }
  let(:period) { 5 }
end

# `its` is a valid example method (rspec-its gem)
describe Record do
  its(:name) { is_expected.to eq('test') }
end

# `pending` without block counts as example
describe Validator do
  pending 'too hard to specify'
end

# examples inside iterators count
describe 'monthly report' do
  [1, 2, 3].each do |page|
    it { expect(page).to be > 0 }
  end
end

# examples inside custom blocks count
context 'with role' do
  with_permissions :admin do
    it { expect(subject).to be_allowed }
  end
end

# it_should_behave_like counts as content
describe Integration do
  context 'when complete' do
    it_should_behave_like 'a valid record'
  end
end

# example groups inside method definitions are ignored
# (they receive content dynamically via instance_eval/yield)
RSpec.describe Foo do
  def self.with_setup(desc, &block)
    context "when #{desc}" do
      before { setup }
      instance_eval(&block)
    end
  end

  class << self
    def without_setup(&block)
      context 'without setup' do
        module_exec(&block)
      end
    end
  end

  with_setup('ready') do
    it { expect(subject).to be_ready }
  end
end

# example groups inside examples are ignored
RSpec.describe 'meta specs' do
  it 'runs an example group' do
    group = RSpec.describe { }
    group.run
  end
end

Key Source Files

  • Rust implementation: src/cop/rspec/empty_example_group.rs
  • RuboCop Ruby source (ground truth): vendor/rubocop-rspec/lib/rubocop/cop/rspec/empty_example_group.rb
  • RuboCop test excerpts: vendor/rubocop-rspec/spec/rubocop/cop/rspec/empty_example_group_spec.rb

Read these files before making changes.

Start Here

Use the existing corpus data to focus on the most concentrated regressions first.

Helpful local commands:

  • python3 scripts/investigate_cop.py RSpec/EmptyExampleGroup --repos-only
  • python3 scripts/investigate_cop.py RSpec/EmptyExampleGroup --context
  • python3 scripts/verify_cop_locations.py RSpec/EmptyExampleGroup

Top FN repos:

  • rspec__rspec__1559574 (2 FN) — example rspec-core/spec/rspec/core/metadata_filter_spec.rb:180
  • SciRuby__rubex__bf5ee93 (1 FN) — example spec/examples_spec.rb:3
  • platanus__pincers__f652bff (1 FN) — example spec/lib/nokogiri/backend_spec.rb:3

Representative FN examples:

  • SciRuby__rubex__bf5ee93: spec/examples_spec.rb:3 — Empty example group detected.
  • platanus__pincers__f652bff: spec/lib/nokogiri/backend_spec.rb:3 — Empty example group detected.
  • rspec__rspec__1559574: rspec-core/spec/rspec/core/metadata_filter_spec.rb:180 — Empty example group detected.

Pre-diagnostic Results

Diagnosis Summary

Each example was tested by running nitrocop on the extracted source in isolation
with --force-default-config to determine if the issue is a code bug or config issue.
Note: source context is truncated and may not parse perfectly. If a diagnosis
seems wrong (e.g., your test passes immediately for a 'CODE BUG'), treat it as
a config/context issue instead.

  • FN: 5 code bug(s)

FN #1: SciRuby__rubex__bf5ee93: spec/examples_spec.rb:3

NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.

Message: Empty example group detected.

Ready-made test snippet (add to offense.rb, adjust ^ count):

describe Rubex, hell: true do
^ RSpec/EmptyExampleGroup: Empty example group detected.

Full file context (30 lines before offense):

        1: require 'spec_helper'
        2: 
>>>     3: describe Rubex, hell: true do
        4:   test_case = 'examples'
        5: 
        6:   examples = ['rcsv', 'array_to_hash', "blank"].each do |example|
        7:     context "Case: #{test_case}/#{example}" do
        8:       before do
        9:         @path = path_str test_case, example
       10:       end

FN #2: platanus__pincers__f652bff: spec/lib/nokogiri/backend_spec.rb:3

NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.

Message: Empty example group detected.

Ready-made test snippet (add to offense.rb, adjust ^ count):

describe 'Pincers::Nokogiri::Backend' do
^ RSpec/EmptyExampleGroup: Empty example group detected.

Full file context (30 lines before offense):

        1: require 'spec_helper'
        2: 
>>>     3: describe 'Pincers::Nokogiri::Backend' do
        4: 
        5:   let!(:example) { ::Nokogiri::HTML File.read "#{FIXTURE_PATH}/index.html" }
        6:   let(:pincers) { Pincers.for_nokogiri example }
        7: 
        8:   it_should_properly_read_the_example
        9:   it_should_support_jquery_selectors
       10: 

FN #3: rspec__rspec__1559574: rspec-core/spec/rspec/core/metadata_filter_spec.rb:180

NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.

Full-file enclosing chain: block (do..end) (line 178: let(:metadata_with_array) do) > block (do..end) (line 177: context "with an Array" do) > block (do..end) (line 4: describe ".filter_applies?" do) > block (do..end) (line 3: RSpec.describe MetadataFilter do) > module body (line 2: module Core) > module body (line 1: module RSpec)

Message: Empty example group detected.

Ready-made test snippet (add to offense.rb, adjust ^ count):

            RSpec.describe("group") do
^ RSpec/EmptyExampleGroup: Empty example group detected.

Full file context (30 lines before offense):

      150: 
      151:         context "with a nested hash" do
      152:           it 'matches when the nested entry matches' do
      153:             metadata = { :foo => { :bar => "words" } }
      154:             expect(filter_applies?(:foo, { :bar => /wor/ }, metadata)).to be(true)
      155:           end
      156: 
      157:           it 'does not match when the nested entry does not match' do
      158:             metadata = { :foo => { :bar => "words" } }
      159:             expect(filter_applies?(:foo, { :bar => /sword/ }, metadata)).to be(false)
      160:           end
      161: 
      162:           it 'does not match when the metadata lacks the key' do
      163:             expect(filter_applies?(:foo, { :bar => /sword/ }, {})).to be(false)
      164:           end
      165: 
      166:           it 'does not match when the metadata does not have a hash entry for the key' do
      167:             metadata = { :foo => "words" }
      168:             expect(filter_applies?(:foo, { :bar => /word/ }, metadata)).to be(false)
      169:           end
      170: 
      171:           it 'matches when a metadata key is specified without a value and exists in the metadata hash' do
      172:             metadata = { :foo => "words" }
      173:             expect(filter_applies?(:foo, true, metadata)).to be(true)
      174:           end
      175:         end
      176: 
      177:         context "with an Array" do
      178:           let(:metadata_with_array) do
      179:             meta = nil
>>>   180:             RSpec.describe("group") do
      181:               meta = example('example_with_array', :tag => [:one, 2, 'three', /four/]).metadata
      182:             end
      183:             meta
      184:           end
      185: 
      186:           it "matches a symbol" do
      187:             expect(filter_applies?(:tag, 'one', metadata_with_array)).to be(true)

FN #4: rspec__rspec__1559574: rspec-core/spec/rspec/core/notifications_spec.rb:122

NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.

Full-file enclosing chain: block (do..end) (line 120: let(:exception) do) > block (do..end) (line 119: context "due to using :aggregate_failures metadata" do) > block (do..end) (line 42: context "when the exception is a MultipleExpectationsNotMetError" do) > block (do..end) (line 33: describe "fully formatted failure output" do) > block (do..end) (line 10: RSpec.describe "FailedExampleNotification" do)

Message: Empty example group detected.

Ready-made test snippet (add to offense.rb, adjust ^ count):

          RSpec.describe do
^ RSpec/EmptyExampleGroup: Empty example group detected.

Full file context (30 lines before offense):

       92: 
       93:       it 'lists each individual expectation failure, with a backtrace relative to the aggregation block' do
       94:         expect(fully_formatted.lines.to_a.last(8)).to eq(dedent(<<-EOS).lines.to_a)
       95:           |
       96:           |     1.1) Failure/Error: expect(1).to fail_with_description("foo")
       97:           |            expected pass, but foo
       98:           |          # #{RSpec::Core::Metadata.relative_path(__FILE__)}:#{aggregate_line + 1}
       99:           |
      100:           |     1.2) Failure/Error: expect(1).to fail_with_description("bar")
      101:           |            expected pass, but bar
      102:           |          # #{RSpec::Core::Metadata.relative_path(__FILE__)}:#{aggregate_line + 2}
      103:         EOS
      104:       end
      105: 
      106:       it 'uses the `failure` color in the summary output' do
      107:         expect(fully_formatted(TagColorizer)).to include(
      108:           '<red>Got 2 failures from failure aggregation block "multiple expectations".</red>'
      109:         )
      110:       end
      111: 
      112:       it 'uses the `failure` color for the sub-failure messages' do
      113:         expect(fully_formatted(TagColorizer)).to include(
      114:          '<red>  expected pass, but foo</red>',
      115:          '<red>  expected pass, but bar</red>'
      116:         )
      117:       end
      118: 
      119:       context "due to using `:aggregate_failures` metadata" do
      120:         let(:exception) do
      121:           ex = nil
>>>   122:           RSpec.describe do
      123:             ex = it "", :aggregate_failures do
      124:               expect(1).to fail_with_description("foo")
      125:               expect(1).to fail_with_description("bar")
      126:             end
      127:           end.run
      128: 
      129:           capture_and_normalize_aggregation_error { raise ex.execution_result.exception }

FN #5: saulabs__reportable__5aacafa: spec/classes/grouping_spec.rb:39

NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.

Full-file enclosing chain: block (do..end) (line 13: describe '#to_sql' do) > block (do..end) (line 3: describe Saulabs::Reportable::Grouping do)

Message: Empty example group detected.

Ready-made test snippet (add to offense.rb, adjust ^ count):

    describe 'for PostgreSQL' do
^ RSpec/EmptyExampleGroup: Empty example group detected.

Full file context (30 lines before offense):

        9:     end
       10: 
       11:   end
       12: 
       13:   describe '#to_sql' do
       14: 
       15:     describe 'for MySQL' do
       16: 
       17:       before do
       18:         ActiveRecord::Base.connection.stub!(:adapter_name).and_return('MySQL')
       19:       end
       20: 
       21:       it 'should use DATE_FORMAT with format string "%Y/%m/%d/%H" for grouping :hour' do
       22:         Saulabs::Reportable::Grouping.new(:hour).send(:to_sql, 'created_at').should == "DATE_FORMAT(created_at, '%Y/%m/%d/%H')"
       23:       end
       24: 
       25:       it 'should use DATE_FORMAT with format string "%Y/%m/%d" for grouping :day' do
       26:         Saulabs::Reportable::Grouping.new(:day).send(:to_sql, 'created_at').should == "DATE(created_at)"
       27:       end
       28: 
       29:       it 'should use YEARWEEK with mode 3 for grouping :week' do
       30:         Saulabs::Reportable::Grouping.new(:week).send(:to_sql, 'created_at').should == "YEARWEEK(created_at, 3)"
       31:       end
       32: 
       33:       it 'should use DATE_FORMAT with format string "%Y/%m" for grouping :month' do
       34:         Saulabs::Reportable::Grouping.new(:month).send(:to_sql, 'created_at').should == "DATE_FORMAT(created_at, '%Y/%m')"
       35:       end
       36: 
       37:     end
       38: 
>>>    39:     describe 'for PostgreSQL' do
       40: 
       41:       before do
       42:         ActiveRecord::Base.connection.stub!(:adapter_name).and_return('PostgreSQL')
       43:       end
       44: 
       45:       for grouping in [:hour, :day, :week, :month] do
       46: 

github-actions bot and others added 2 commits March 30, 2026 05:44
… call args, and for loops

ExampleFinder was traversing too deeply into three node types that
RuboCop's shallow pattern matching does not reach:

1. LocalVariableWriteNode — skip lvasgn nodes (e.g., `examples = [...].each { it ... }`)
2. Method call arguments — only visit block children in fallthrough, not
   receiver/arguments (e.g., `example` used as arg in `let` block)
3. ForNode — skip `for...in` loops (RuboCop only matches `block` nodes)

Corpus: 0 FP, 0 FN (5 resolved).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@6 6 bot force-pushed the fix/rspec-empty_example_group-23729120326 branch from a3ac963 to 49d2d00 Compare March 30, 2026 05:46
@6 6 bot marked this pull request as ready for review March 30, 2026 05:46
@6 6 bot enabled auto-merge (squash) March 30, 2026 05:46
@github-actions
Copy link
Copy Markdown
Contributor

Cop-check results (8 shards)

Cop Baseline FP Baseline FN Local FP Local FN FP Δ FN Δ Result
RSpec/EmptyExampleGroup 0 0 0 0 0 0
RSpec/EmptyExampleGroup 0 0 0 0 0 0
RSpec/EmptyExampleGroup 0 0 0 0 0 0
RSpec/EmptyExampleGroup 0 0 0 0 0 0
RSpec/EmptyExampleGroup 0 0 0 0 0 0
RSpec/EmptyExampleGroup 0 4 0 0 0 -4
RSpec/EmptyExampleGroup 0 1 0 0 0 -1
RSpec/EmptyExampleGroup 0 0 0 0 0 0

All shards passed — ready to merge.

Copy link
Copy Markdown
Owner

@6 6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed: logic matches vendored RuboCop behavior closely enough, and the added fixtures/tests cover the intended cases.

@6 6 bot merged commit 450fd9d into main Mar 30, 2026
14 checks passed
@6 6 bot deleted the fix/rspec-empty_example_group-23729120326 branch March 30, 2026 12:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant