Skip to content

[bot] Fix RSpec/SingleArgumentMessageChain#114

Closed
6[bot] wants to merge 1 commit intomainfrom
fix/rspec-single_argument_message_chain-23390456408
Closed

[bot] Fix RSpec/SingleArgumentMessageChain#114
6[bot] wants to merge 1 commit intomainfrom
fix/rspec-single_argument_message_chain-23390456408

Conversation

@6
Copy link
Copy Markdown
Contributor

@6 6 bot commented Mar 21, 2026

Status: Agent is working on this fix...

Cop: RSpec/SingleArgumentMessageChain | Backend: minimax | Mode: fix
Code bugs: 4 | Run: https://github.com/6/nitrocop/actions/runs/23390456408

Task prompt (click to expand)

Fix RSpec/SingleArgumentMessageChain — 0 FP, 3 FN

Instructions

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

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

Workflow

  1. Read the Pre-diagnostic Results and Corpus FP/FN Examples sections below first
  2. Add a test case FIRST:
    • FN fix: add the missed pattern to tests/fixtures/cops/rspec/single_argument_message_chain/offense.rb with ^ annotation
    • FP fix: add the false-positive pattern to tests/fixtures/cops/rspec/single_argument_message_chain/no_offense.rb
  3. Verify test fails: cargo test --lib -- cop::rspec::single_argument_message_chain
  4. Fix src/cop/rspec/single_argument_message_chain.rs
  5. Verify test passes: cargo test --lib -- cop::rspec::single_argument_message_chain
  6. Add a /// doc comment on the cop struct documenting what you found and fixed
  7. Commit only your cop's files

Fixture Format

Mark offenses with ^ markers on the line AFTER the offending source line:

x = 1
     ^^ RSpec/SingleArgumentMessageChain: Trailing whitespace detected.

The ^ characters must align with the offending columns. The message format is RSpec/SingleArgumentMessageChain: <message text>.

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

Rules

  • Only modify src/cop/rspec/single_argument_message_chain.rs and tests/fixtures/cops/rspec/single_argument_message_chain/
  • Run cargo test --lib -- cop::rspec::single_argument_message_chain to verify your fix (do NOT run the full test suite)
  • Do NOT touch unrelated files
  • Do NOT use git stash

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: 3 code bug(s), 0 config/context issue(s)

FN #1: chef__knife-azure__52cef32: spec/unit/query_azure_mock.rb:90

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

Enclosing structure: method body (line: def stub_compute_management_client(user_supplied_value))
The offense is inside this structure — the cop may need
to handle this context to detect the pattern.

Message: Use receiveinstead of callingreceive_message_chain with a single argument.

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

    allow(compute_management_client.virtual_machine_extensions).to receive_message_chain(
^ RSpec/SingleArgumentMessageChain: Use `receive` instead of calling `receive_message_chain` with a single argument.

Full source context:

  end

  def stub_compute_management_client(user_supplied_value)
    compute_management_client = double("ComputeManagementClient",
      virtual_machines: double,
      virtual_machine_extensions: double,
      virtual_machine_extension_images: double)
    allow(compute_management_client.virtual_machine_extensions).to receive_message_chain(
      create_or_update: "create_or_update"
    ).and_return(stub_vm_extension_create_response(user_supplied_value))
    allow(compute_management_client.virtual_machine_extension_images).to receive_message_chain(
      :list_versions,
      :last,
      :name
    ).and_return("1210.12.10.100")

FN #2: rspec__rspec__1559574: rspec-mocks/spec/rspec/mocks/stub_chain_spec.rb:90

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

Enclosing structure: block (do..end) (line: it "returns the value of the key/value pair" do)
The offense is inside this structure — the cop may need
to handle this context to detect the pattern.

Message: Use receiveinstead of callingreceive_message_chain with a single argument.

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

            allow(object).to receive_message_chain("msg1.msg2.msg3.msg4" => :return_value)
^ RSpec/SingleArgumentMessageChain: Use `receive` instead of calling `receive_message_chain` with a single argument.

Full source context:

            allow(object).to receive_message_chain(:msg1, :msg2, :msg3, :msg4 => :return_value)
            expect(object.msg1.msg2.msg3.msg4).to equal(:return_value)
          end
        end

        context "using a hash with a string key" do
          it "returns the value of the key/value pair" do
            allow(object).to receive_message_chain("msg1.msg2.msg3.msg4" => :return_value)
            expect(object.msg1.msg2.msg3.msg4).to equal(:return_value)
          end
        end
      end

      it "returns expected value from chaining four method calls" do
        allow(object).to receive_message_chain(:msg1, :msg2, :msg3, :msg4).and_return(:return_value)

FN #3: rspec__rspec__1559574: rspec-mocks/spec/rspec/mocks/stub_chain_spec.rb:31

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

Enclosing structure: block (do..end) (line: it "returns the value of the key/value pair" do)
The offense is inside this structure — the cop may need
to handle this context to detect the pattern.

Message: Use receiveinstead of callingreceive_message_chain with a single argument.

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

            allow(object).to receive_message_chain(:msg1 => :return_value)
^ RSpec/SingleArgumentMessageChain: Use `receive` instead of calling `receive_message_chain` with a single argument.

Full source context:

            allow(object).to receive_message_chain(:msg1) { :return_value }
            expect(object.msg1).to equal(:return_value)
          end
        end

        context "using a hash" do
          it "returns the value of the key/value pair" do
            allow(object).to receive_message_chain(:msg1 => :return_value)
            expect(object.msg1).to equal(:return_value)
          end
        end
      end

      context "with two methods in chain" do
        it "accepts any number of arguments to the stubbed messages in the chain" do

Current Rust Implementation

src/cop/rspec/single_argument_message_chain.rs

use crate::cop::node_type::{ARRAY_NODE, CALL_NODE, STRING_NODE, SYMBOL_NODE};
use crate::cop::util::RSPEC_DEFAULT_INCLUDE;
use crate::cop::{Cop, CopConfig};
use crate::diagnostic::{Diagnostic, Severity};
use crate::parse::source::SourceFile;

pub struct SingleArgumentMessageChain;

impl Cop for SingleArgumentMessageChain {
    fn name(&self) -> &'static str {
        "RSpec/SingleArgumentMessageChain"
    }

    fn default_severity(&self) -> Severity {
        Severity::Convention
    }

    fn default_include(&self) -> &'static [&'static str] {
        RSPEC_DEFAULT_INCLUDE
    }

    fn interested_node_types(&self) -> &'static [u8] {
        &[ARRAY_NODE, CALL_NODE, STRING_NODE, SYMBOL_NODE]
    }

    fn check_node(
        &self,
        source: &SourceFile,
        node: &ruby_prism::Node<'_>,
        _parse_result: &ruby_prism::ParseResult<'_>,
        _config: &CopConfig,
        diagnostics: &mut Vec<Diagnostic>,
        _corrections: Option<&mut Vec<crate::correction::Correction>>,
    ) {
        let call = match node.as_call_node() {
            Some(c) => c,
            None => return,
        };

        let method = call.name().as_slice();
        let replacement = if method == b"receive_message_chain" {
            "receive"
        } else if method == b"stub_chain" {
            "stub"
        } else {
            return;
        };

        let args = match call.arguments() {
            Some(a) => a,
            None => return,
        };

        let arg_list: Vec<ruby_prism::Node<'_>> = args.arguments().iter().collect();

        let is_single_arg = if arg_list.len() == 1 {
            let arg = &arg_list[0];
            // Single symbol or single string (without dots for strings)
            if arg.as_symbol_node().is_some() {
                true
            } else if let Some(s) = arg.as_string_node() {
                // Multi-part string like "one.two" should not be flagged
                !s.unescaped().contains(&b'.')
            } else if let Some(arr) = arg.as_array_node() {
                // Single-element array
                arr.elements().iter().count() == 1
            } else {
                false
            }
        } else {
            false
        };

        if !is_single_arg {
            return;
        }

        let method_str = std::str::from_utf8(method).unwrap_or("receive_message_chain");
        let loc = call.message_loc().unwrap_or_else(|| call.location());
        let (line, column) = source.offset_to_line_col(loc.start_offset());
        diagnostics.push(self.diagnostic(
            source,
            line,
            column,
            format!(
                "Use `{replacement}` instead of calling `{method_str}` with a single argument."
            ),
        ));
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    crate::cop_fixture_tests!(
        SingleArgumentMessageChain,
        "cops/rspec/single_argument_message_chain"
    );
}

RuboCop Ruby Implementation (ground truth)

vendor/rubocop-rspec/lib/rubocop/cop/rspec/single_argument_message_chain.rb

# frozen_string_literal: true

module RuboCop
  module Cop
    module RSpec
      # Checks that chains of messages contain more than one element.
      #
      # @example
      #   # bad
      #   allow(foo).to receive_message_chain(:bar).and_return(42)
      #
      #   # good
      #   allow(foo).to receive(:bar).and_return(42)
      #
      #   # also good
      #   allow(foo).to receive(:bar, :baz)
      #   allow(foo).to receive("bar.baz")
      #
      class SingleArgumentMessageChain < Base
        extend AutoCorrector

        MSG = 'Use `%<recommended>s` instead of calling ' \
              '`%<called>s` with a single argument.'
        RESTRICT_ON_SEND = %i[receive_message_chain stub_chain].freeze

        # @!method message_chain(node)
        def_node_matcher :message_chain, <<~PATTERN
          (send _ {:receive_message_chain :stub_chain} $_)
        PATTERN

        # @!method single_key_hash?(node)
        def_node_matcher :single_key_hash?, '(hash pair)'

        def on_send(node)
          message_chain(node) do |arg|
            return if valid_usage?(arg)

            method = node.method_name
            msg = format(MSG, recommended: replacement(method), called: method)

            add_offense(node.loc.selector, message: msg) do |corrector|
              autocorrect(corrector, node, method, arg)
            end
          end
        end

        private

        def autocorrect(corrector, node, method, arg)
          corrector.replace(node.loc.selector, replacement(method))
          autocorrect_hash_arg(corrector, arg) if single_key_hash?(arg)
          autocorrect_array_arg(corrector, arg) if arg.array_type?
        end

        def valid_usage?(node)
          return true unless node.literal? || node.array_type?

          case node.type
          when :hash then !single_key_hash?(node)
          when :array then !single_element_array?(node)
          else node.to_s.include?('.')
          end
        end

        def single_element_array?(node)
          node.child_nodes.one?
        end

        def autocorrect_hash_arg(corrector, arg)
          pair = arg.pairs.first
          corrector.replace(arg, key_to_arg(pair.key))
          corrector.insert_after(arg.parent.loc.end,
                                 ".and_return(#{pair.value.source})")
        end

        def autocorrect_array_arg(corrector, arg)
          value = arg.children.first

          corrector.replace(arg, value.source)
        end

        def key_to_arg(node)
          node.sym_type? ? ":#{node.value}" : node.source
        end

        def replacement(method)
          method.equal?(:receive_message_chain) ? 'receive' : 'stub'
        end
      end
    end
  end
end

RuboCop Test Excerpts

vendor/rubocop-rspec/spec/rubocop/cop/rspec/single_argument_message_chain_spec.rb

  describe 'receive_message_chain' do

    it 'reports single-argument calls' do

      expect_offense(<<~RUBY)
        before do
          allow(foo).to receive_message_chain(:one) { :two }
                        ^^^^^^^^^^^^^^^^^^^^^ Use `receive` instead of calling `receive_message_chain` with a single argument.
        end
      RUBY

    it 'accepts multi-argument calls' do

      expect_no_offenses(<<~RUBY)
        before do
          allow(foo).to receive_message_chain(:one, :two) { :three }
        end
      RUBY

    it 'reports single-argument string calls' do

      expect_offense(<<~RUBY)
        before do
          allow(foo).to receive_message_chain("one") { :two }
                        ^^^^^^^^^^^^^^^^^^^^^ Use `receive` instead of calling `receive_message_chain` with a single argument.
        end
      RUBY

    it 'accepts multi-argument string calls' do

      expect_no_offenses(<<~RUBY)
        before do
          allow(foo).to receive_message_chain("one.two") { :three }
        end
      RUBY

    it 'accepts single-argument calls with variable' do

      expect_no_offenses(<<~RUBY)
        before do
          foo = %i[:one :two]
          allow(foo).to receive_message_chain(foo) { :many }
        end
      RUBY

    it 'accepts single-argument calls with send node' do

      expect_no_offenses(<<~RUBY)
        before do
          allow(foo).to receive_message_chain(foo) { :many }
        end
      RUBY

    context 'with single-element array argument' do

      it 'reports an offense' do

        expect_offense(<<~RUBY)
          before do
            allow(foo).to receive_message_chain([:one]) { :two }
                          ^^^^^^^^^^^^^^^^^^^^^ Use `receive` instead of calling `receive_message_chain` with a single argument.
          end
        RUBY

    context 'with multiple-element array argument' do

      it "doesn't report an offense" do

        expect_no_offenses(<<~RUBY)
          before do
            allow(foo).to receive_message_chain([:one, :two]) { :many }
          end
        RUBY

    context 'with single-key hash argument' do

Current Fixture: offense.rb

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

before do
  allow(foo).to receive_message_chain(:one) { :two }
                ^^^^^^^^^^^^^^^^^^^^^ RSpec/SingleArgumentMessageChain: Use `receive` instead of calling `receive_message_chain` with a single argument.
end

before do
  allow(foo).to receive_message_chain("one") { :two }
                ^^^^^^^^^^^^^^^^^^^^^ RSpec/SingleArgumentMessageChain: Use `receive` instead of calling `receive_message_chain` with a single argument.
end

before do
  foo.stub_chain(:one) { :two }
      ^^^^^^^^^^ RSpec/SingleArgumentMessageChain: Use `stub` instead of calling `stub_chain` with a single argument.
end

Current Fixture: no_offense.rb

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

before do
  allow(foo).to receive_message_chain(:one, :two) { :three }
end

before do
  allow(foo).to receive_message_chain("one.two") { :three }
end

before do
  foo.stub_chain(:one, :two) { :three }
end

before do
  allow(foo).to receive(:one) { :two }
end

before do
  allow(controller).to receive_message_chain "forum.moderator?" => false
end

before do
  allow(controller).to stub_chain "admin?" => true
end

@6
Copy link
Copy Markdown
Contributor Author

6 bot commented Mar 21, 2026

Agent failed. See run: https://github.com/6/nitrocop/actions/runs/23390456408

@6 6 bot closed this Mar 21, 2026
@6 6 bot deleted the fix/rspec-single_argument_message_chain-23390456408 branch March 21, 2026 23:34
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.

0 participants