Conversation
…tterns Add detection for (a & b).count/size/length > 0 / == 0 / != 0, .count.positive? / .zero?, and .present? / .blank? (with ActiveSupport). Resolves 41 FN with 0 regressions (636 existing matches preserved). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2dce9b1 to
0b3996f
Compare
|
✅ Cop-check results (8 shards)
All shards passed — ready to merge. |
Auto-repair Started
Task prompt (4672 tokens)PR Repair Task: PR #897Before making changes, read Repair the existing PR branch in place. Do not create a new branch or PR. Context
Required OutcomeFix the currently failing checks shown below. Required Local VerificationThe workflow will rerun these commands before pushing. Your changes should make them pass: cargo clippy --profile ci -- -D warnings
Current PR Diff StatCurrent PR Diff Excerptdiff --git a/src/cop/style/array_intersect.rs b/src/cop/style/array_intersect.rs
index d1f95f178..47c780f24 100644
--- a/src/cop/style/array_intersect.rs
+++ b/src/cop/style/array_intersect.rs
@@ -3,8 +3,70 @@ use crate::cop::{Cop, CopConfig};
use crate::diagnostic::Diagnostic;
use crate::parse::source::SourceFile;
+/// Style/ArrayIntersect detects array intersection patterns replaceable
+/// with `Array#intersect?` (Ruby 3.1+).
+///
+/// Handles three families of patterns:
+/// 1. Direct predicates: `(a & b).any?` / `.empty?` / `.none?`
+/// (plus `.present?` / `.blank?` when `ActiveSupportExtensionsEnabled`)
+/// 2. Size comparisons: `(a & b).count > 0`, `== 0`, `!= 0`
+/// (also `.size` and `.length`)
+/// 3. Size predicates: `(a & b).count.positive?`, `.count.zero?`
+///
+/// All patterns also match the `a.intersection(b)` form (1 argument only).
pub struct ArrayIntersect;
+/// Extract (lhs_source, rhs_source) from an intersection expression node.
+/// Matches `(a & b)` (parenthesized `&` call) and `a.intersection(b)` (1-arg form).
+fn extract_intersection_parts(node: &ruby_prism::Node<'_>) -> Option<(String, String)> {
+ // (a & b) form
+ if let Some(paren) = node.as_parentheses_node() {
+ if let Some(body) = paren.body() {
+ if let Some(stmts) = body.as_statements_node() {
+ let stmt_list: Vec<_> = stmts.body().iter().collect();
+ if stmt_list.len() == 1 {
+ if let Some(inner_call) = stmt_list[0].as_call_node() {
+ let m = std::str::from_utf8(inner_call.name().as_slice()).unwrap_or("");
+ if m == "&" {
+ let recv = inner_call.receiver()?;
+ let args = inner_call.arguments()?;
+ let arg_list: Vec<_> = args.arguments().iter().collect();
+ if arg_list.len() == 1 {
+ let lhs = std::str::from_utf8(recv.location().as_slice())
+ .unwrap_or("")
+ .to_string();
+ let rhs = std::str::from_utf8(arg_list[0].location().as_slice())
+ .unwrap_or("")
+ .to_string();
+ return Some((lhs, rhs));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // a.intersection(b) form
+ if let Some(call) = node.as_call_node() {
+ let m = std::str::from_utf8(call.name().as_slice()).unwrap_or("");
+ if m == "intersection" {
+ let recv = call.receiver()?;
+ let args = call.arguments()?;
+ let arg_list: Vec<_> = args.arguments().iter().collect();
+ if arg_list.len() == 1 {
+ let lhs = std::str::from_utf8(recv.location().as_slice())
+ .unwrap_or("")
+ .to_string();
+ let rhs = std::str::from_utf8(arg_list[0].location().as_slice())
+ .unwrap_or("")
+ .to_string();
+ return Some((lhs, rhs));
+ }
+ }
+ }
+ None
+}
+
impl Cop for ArrayIntersect {
fn name(&self) -> &'static str {
"Style/ArrayIntersect"
@@ -40,8 +102,12 @@ impl Cop for ArrayIntersect {
let method_name = std::str::from_utf8(call.name().as_slice()).unwrap_or("");
- // Pattern: (array1 & array2).any? / .empty? / .none?
- if matches!(method_name, "any?" | "empty?" | "none?") {
+ let active_support = config.get_bool("ActiveSupportExtensionsEnabled", false);
+
+ // Pattern 1: (a & b).any? / .empty? / .none? / .present? / .blank?
+ if matches!(method_name, "any?" | "empty?" | "none?")
+ || (active_support && matches!(method_name, "present?" | "blank?"))
+ {
// Skip if the call has arguments or a block (any? with block)
if call.arguments().is_some() || call.block().is_some() {
return;
@@ -62,12 +128,35 @@ impl Cop for ArrayIntersect {
let loc = node.location();
let (line, column) =
source.offset_to_line_col(loc.start_offset());
- let msg = format!(
- "Use `intersect?` instead of `({}).{}`.",
- std::str::from_utf8(inner_call.location().as_slice())
- .unwrap_or("array1 & array2"),
- method_name
- );
+
+ // Keep backward-compatible message for original patterns
+ let msg =
+ if matches!(method_name, "any?" | "empty?" | "none?") {
+ format!(
+ "Use `intersect?` instead of `({}).{}`.",
+ std::str::from_utf8(
+ inner_call.location().as_slice()
+ )
+ .unwrap_or("array1 & array2"),
+ method_name
+ )
+ } else if let Some((lhs, rhs)) =
+ extract_intersection_parts(&receiver)
+ {
+ let existing = std::str::from_utf8(loc.as_slice())
+ .unwrap_or("");
+ format!(
+ "Use `{}.intersect?({})` instead of `{}`.",
+ lhs, rhs, existing
+ )
+ } else {
+ let existing = std::str::from_utf8(loc.as_slice())
+ .unwrap_or("");
+ format!(
+ "Use `intersect?` instead of `{}`.",
+ existing
+ )
+ };
diagnostics
.push(self.diagnostic(source, line, column, msg));
}
@@ -90,10 +179,27 @@ impl Cop for ArrayIntersect {
let loc = node.location();
let (line, column) =
source.offset_to_line_col(loc.start_offset());
- let msg = format!(
- "Use `intersect?` instead of `intersection(...).{}`.",
- method_name
- );
+
+ let msg = if matches!(method_name, "any?" | "empty?" | "none?")
+ {
+ format!(
+ "Use `intersect?` instead of `intersection(...).{}`.",
+ method_name
+ )
+ } else if let Some((lhs, rhs)) =
+ extract_intersection_parts(&receiver)
+ {
+ let existing =
+ std::str::from_utf8(loc.as_slice()).unwrap_or("");
+ format!(
+ "Use `{}.intersect?({})` instead of `{}`.",
+ lhs, rhs, existing
+ )
+ } else {
+ let existing =
+ std::str::from_utf8(loc.as_slice()).unwrap_or("");
+ format!("Use `intersect?` instead of `{}`.", existing)
+ };
diagnostics.push(self.diagnostic(source, line, column, msg));
}
}
@@ -102,6 +208,82 @@ impl Cop for ArrayIntersect {
}
}
}
+
+ // Pattern 2: (a & b).count > 0 / == 0 / != 0
+ if matches!(method_name, ">" | "==" | "!=") {
+ if let Some(args) = call.arguments() {
+ let arg_list: Vec<_> = args.arguments().iter().collect();
+ if arg_list.len() == 1 {
+ if let Some(int_node) = arg_list[0].as_integer_node() {
+ if int_node.location().as_slice() == b"0" {
+ if let Some(recv) = call.receiver() {
+ if let Some(recv_call) = recv.as_call_node() {
+ let rm = std::str::from_utf8(recv_call.name().as_slice())
+ .unwrap_or("");
+ if matches!(rm, "count" | "size" | "length") {
+ if recv_call.arguments().is_none()
+ && recv_call.block().is_none()
+ {
+ if let Some(inner_recv) = recv_call.receiver() {
+ if let Some((lhs, rhs)) =
+ extract_intersection_parts(&inner_recv)
+ {
+ let loc = node.location();
+ let (line, column) = source
+ .offset_to_line_col(loc.start_offset());
+ let existing =
+ std::str::from_utf8(loc.as_slice())
+ .unwrap_or("");
+ let msg = format!(
+ "Use `{}.intersect?({})` instead of `{}`.",
+ lhs, rhs, existing
+ );
+ diagnostics.push(
+ self.diagnostic(source, line, column, msg),
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Pattern 3: (a & b).count.positive? / .zero?
+ if matches!(method_name, "positive?" | "zero?") {
+ if call.arguments().is_none() && call.block().is_none() {
+ if let Some(recv) = call.receiver() {
+ if let Some(recv_call) = recv.as_call_node() {
+ let rm = std::str::from_utf8(recv_call.name().as_slice()).unwrap_or("");
+ if matches!(rm, "count" | "size" | "length") {
... (151 more lines truncated)Failed Checks Packetbuild-and-test
Constraints
Available Local Helper ScriptsThese helper scripts are available in this CI checkout. Prefer the stable top-level CLI paths shown below over ad hoc commands when they directly help with diagnosis or validation.
Typical usage when present: python3 scripts/check_cop.py Department/CopName --verbose --rerun --clone
python3 scripts/dispatch_cops.py changed --base origin/main --head HEAD
python3 scripts/investigate_cop.py Department/CopName --context
python3 scripts/verify_cop_locations.py Department/CopName
python3 scripts/corpus_smoke_test.py --binary target/release/nitrocop |
Auto-repair Succeeded
Auto-repair succeeded with backend Validated locally before push using |
519c342 to
9624059
Compare
|
✅ Cop-check results (8 shards)
All shards passed — ready to merge. |
Auto-repair Started
Task prompt (4044 tokens)PR Repair Task: PR #897Before making changes, read Repair the existing PR branch in place. Do not create a new branch or PR. Context
Required OutcomeFix the currently failing checks shown below. Required Local VerificationThe workflow will rerun these commands before pushing. Your changes should make them pass: cargo fmt --check
Current PR Diff StatCurrent PR Diff Excerptdiff --git a/src/cop/style/array_intersect.rs b/src/cop/style/array_intersect.rs
index d1f95f178..3a21bae65 100644
--- a/src/cop/style/array_intersect.rs
+++ b/src/cop/style/array_intersect.rs
@@ -3,8 +3,70 @@ use crate::cop::{Cop, CopConfig};
use crate::diagnostic::Diagnostic;
use crate::parse::source::SourceFile;
+/// Style/ArrayIntersect detects array intersection patterns replaceable
+/// with `Array#intersect?` (Ruby 3.1+).
+///
+/// Handles three families of patterns:
+/// 1. Direct predicates: `(a & b).any?` / `.empty?` / `.none?`
+/// (plus `.present?` / `.blank?` when `ActiveSupportExtensionsEnabled`)
+/// 2. Size comparisons: `(a & b).count > 0`, `== 0`, `!= 0`
+/// (also `.size` and `.length`)
+/// 3. Size predicates: `(a & b).count.positive?`, `.count.zero?`
+///
+/// All patterns also match the `a.intersection(b)` form (1 argument only).
pub struct ArrayIntersect;
+/// Extract (lhs_source, rhs_source) from an intersection expression node.
+/// Matches `(a & b)` (parenthesized `&` call) and `a.intersection(b)` (1-arg form).
+fn extract_intersection_parts(node: &ruby_prism::Node<'_>) -> Option<(String, String)> {
+ // (a & b) form
+ if let Some(paren) = node.as_parentheses_node() {
+ if let Some(body) = paren.body() {
+ if let Some(stmts) = body.as_statements_node() {
+ let stmt_list: Vec<_> = stmts.body().iter().collect();
+ if stmt_list.len() == 1 {
+ if let Some(inner_call) = stmt_list[0].as_call_node() {
+ let m = std::str::from_utf8(inner_call.name().as_slice()).unwrap_or("");
+ if m == "&" {
+ let recv = inner_call.receiver()?;
+ let args = inner_call.arguments()?;
+ let arg_list: Vec<_> = args.arguments().iter().collect();
+ if arg_list.len() == 1 {
+ let lhs = std::str::from_utf8(recv.location().as_slice())
+ .unwrap_or("")
+ .to_string();
+ let rhs = std::str::from_utf8(arg_list[0].location().as_slice())
+ .unwrap_or("")
+ .to_string();
+ return Some((lhs, rhs));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // a.intersection(b) form
+ if let Some(call) = node.as_call_node() {
+ let m = std::str::from_utf8(call.name().as_slice()).unwrap_or("");
+ if m == "intersection" {
+ let recv = call.receiver()?;
+ let args = call.arguments()?;
+ let arg_list: Vec<_> = args.arguments().iter().collect();
+ if arg_list.len() == 1 {
+ let lhs = std::str::from_utf8(recv.location().as_slice())
+ .unwrap_or("")
+ .to_string();
+ let rhs = std::str::from_utf8(arg_list[0].location().as_slice())
+ .unwrap_or("")
+ .to_string();
+ return Some((lhs, rhs));
+ }
+ }
+ }
+ None
+}
+
impl Cop for ArrayIntersect {
fn name(&self) -> &'static str {
"Style/ArrayIntersect"
@@ -40,8 +102,12 @@ impl Cop for ArrayIntersect {
let method_name = std::str::from_utf8(call.name().as_slice()).unwrap_or("");
- // Pattern: (array1 & array2).any? / .empty? / .none?
- if matches!(method_name, "any?" | "empty?" | "none?") {
+ let active_support = config.get_bool("ActiveSupportExtensionsEnabled", false);
+
+ // Pattern 1: (a & b).any? / .empty? / .none? / .present? / .blank?
+ if matches!(method_name, "any?" | "empty?" | "none?")
+ || (active_support && matches!(method_name, "present?" | "blank?"))
+ {
// Skip if the call has arguments or a block (any? with block)
if call.arguments().is_some() || call.block().is_some() {
return;
@@ -62,12 +128,35 @@ impl Cop for ArrayIntersect {
let loc = node.location();
let (line, column) =
source.offset_to_line_col(loc.start_offset());
- let msg = format!(
- "Use `intersect?` instead of `({}).{}`.",
- std::str::from_utf8(inner_call.location().as_slice())
- .unwrap_or("array1 & array2"),
- method_name
- );
+
+ // Keep backward-compatible message for original patterns
+ let msg =
+ if matches!(method_name, "any?" | "empty?" | "none?") {
+ format!(
+ "Use `intersect?` instead of `({}).{}`.",
+ std::str::from_utf8(
+ inner_call.location().as_slice()
+ )
+ .unwrap_or("array1 & array2"),
+ method_name
+ )
+ } else if let Some((lhs, rhs)) =
+ extract_intersection_parts(&receiver)
+ {
+ let existing = std::str::from_utf8(loc.as_slice())
+ .unwrap_or("");
+ format!(
+ "Use `{}.intersect?({})` instead of `{}`.",
+ lhs, rhs, existing
+ )
+ } else {
+ let existing = std::str::from_utf8(loc.as_slice())
+ .unwrap_or("");
+ format!(
+ "Use `intersect?` instead of `{}`.",
+ existing
+ )
+ };
diagnostics
.push(self.diagnostic(source, line, column, msg));
}
@@ -90,10 +179,27 @@ impl Cop for ArrayIntersect {
let loc = node.location();
let (line, column) =
source.offset_to_line_col(loc.start_offset());
- let msg = format!(
- "Use `intersect?` instead of `intersection(...).{}`.",
- method_name
- );
+
+ let msg = if matches!(method_name, "any?" | "empty?" | "none?")
+ {
+ format!(
+ "Use `intersect?` instead of `intersection(...).{}`.",
+ method_name
+ )
+ } else if let Some((lhs, rhs)) =
+ extract_intersection_parts(&receiver)
+ {
+ let existing =
+ std::str::from_utf8(loc.as_slice()).unwrap_or("");
+ format!(
+ "Use `{}.intersect?({})` instead of `{}`.",
+ lhs, rhs, existing
+ )
+ } else {
+ let existing =
+ std::str::from_utf8(loc.as_slice()).unwrap_or("");
+ format!("Use `intersect?` instead of `{}`.", existing)
+ };
diagnostics.push(self.diagnostic(source, line, column, msg));
}
}
@@ -102,6 +208,79 @@ impl Cop for ArrayIntersect {
}
}
}
+
+ // Pattern 2: (a & b).count > 0 / == 0 / != 0
+ if matches!(method_name, ">" | "==" | "!=") {
+ if let Some(args) = call.arguments() {
+ let arg_list: Vec<_> = args.arguments().iter().collect();
+ if arg_list.len() == 1 {
+ if let Some(int_node) = arg_list[0].as_integer_node() {
+ if int_node.location().as_slice() == b"0" {
+ if let Some(recv) = call.receiver() {
+ if let Some(recv_call) = recv.as_call_node() {
+ let rm = std::str::from_utf8(recv_call.name().as_slice())
+ .unwrap_or("");
+ if matches!(rm, "count" | "size" | "length")
+ && recv_call.arguments().is_none()
+ && recv_call.block().is_none()
+ {
+ if let Some(inner_recv) = recv_call.receiver() {
+ if let Some((lhs, rhs)) =
+ extract_intersection_parts(&inner_recv)
+ {
+ let loc = node.location();
+ let (line, column) =
+ source.offset_to_line_col(loc.start_offset());
+ let existing = std::str::from_utf8(loc.as_slice())
+ .unwrap_or("");
+ let msg = format!(
+ "Use `{}.intersect?({})` instead of `{}`.",
+ lhs, rhs, existing
+ );
+ diagnostics.push(
+ self.diagnostic(source, line, column, msg),
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Pattern 3: (a & b).count.positive? / .zero?
+ if matches!(method_name, "positive?" | "zero?")
+ && call.arguments().is_none()
+ && call.block().is_none()
+ {
+ if let Some(recv) = call.receiver() {
+ if let Some(recv_call) = recv.as_call_node() {
+ let rm = std::str::from_utf8(recv_call.name().as_slice()).unwrap_or("");
+ if matches!(rm, "count" | "size" | "length")
... (148 more lines truncated)Failed Checks Packetbuild-and-test
Constraints
Available Local Helper ScriptsThese helper scripts are available in this CI checkout. Prefer the stable top-level CLI paths shown below over ad hoc commands when they directly help with diagnosis or validation.
Typical usage when present: python3 scripts/check_cop.py Department/CopName --verbose --rerun --clone
python3 scripts/dispatch_cops.py changed --base origin/main --head HEAD
python3 scripts/investigate_cop.py Department/CopName --context
python3 scripts/verify_cop_locations.py Department/CopName
python3 scripts/corpus_smoke_test.py --binary target/release/nitrocop |
Auto-repair Rejected
The workflow rejected this repair because it edited files outside the allowed scope for route Agent File Scope
Allowed path patterns:
Disallowed changed files:
|
Automated fix fix for
Style/ArrayIntersectcorpus conformance.Refs #321
Details
Style/ArrayIntersectResult
Changes
Agent conversation excerpt
Let me start by reading the key files.
The test fixtures already have FN cases added. Let me verify they fail, then implement the fix.
Now let me check how ActiveSupportExtensionsEnabled is accessed in the codebase.
Good. Now let me also check how
IntegerNodeis accessed to look at integer literal values in the AST.Let me check how the test utility parses fixture markers.
Let me check how the test assertion compares offenses.
Now I have a clear picture. The current implementation only detects
any?/empty?/none?on intersection results. It's missing:(a & b).count > 0,== 0,!= 0(alsosize,length)(a & b).count.positive?,.zero?(a & b).present?,.blank?Let me update the fixture first with proper test cases, then implement the detection.
Now let me implement the detection logic.
Now let me check if
run_cop_fullandrun_cop_full_with_configexist.Good. Now let me run the tests.
All 8 tests pass. Now let me run the corpus validation.
Good - 41 FN resolved, 0 regressions. Let me investigate the remaining FN to see if more can be fixed.
These FN should be caught now. Let me test a specific example directly.
The pattern is detected. The verify script uses cached data. The check_cop with
--rerunconfirmed 41 resolved FN with 0 regressions.Let me verify
present?/blank?work correctly when ActiveSupport is configured:Test with a rubocop config that enables Acti`
All patterns correctly detected. The check_cop passed with 41 resolved FN and 0 regressions. Let me verify the full test suite one more time.
All 8 tests pass. Now let me commit.
Done. Here's what was fixed:
Problem: Style/ArrayIntersect had 170 false negatives — it only detected
(a & b).any?/.empty?/.none?but missed size comparison patterns and ActiveSupport predicates.Changes:
(a & b).count > 0,.size > 0,.length > 0(and== 0,!= 0)(a & b).count.positive?,.count.zero?(and size/length)(a & b).present?,.blank?(whenActiveSupportExtensionsEnabled: true)(a & b)anda.intersection(b)formsResults: 41 FN resolved, 0 FP regressions, all 636 existing matches preserved.
Task prompt (6376 tokens)
Fix Style/ArrayIntersect — 0 FP, 170 FN
Instructions
You are fixing ONE cop in nitrocop, a Rust Ruby linter that uses Prism for parsing.
Current state: 636 matches, 0 false positives, 170 false negatives.
Focus on: FN (RuboCop flags code nitrocop misses).
⚠ 636 existing matches must not regress. Validate with
check_cop.pybefore committing.Workflow
behavior on BOTH the specific FP case AND the general pattern:
tests/fixtures/cops/style/array_intersect/offense.rbwith^annotationtests/fixtures/cops/style/array_intersect/no_offense.rbcargo test --lib -- cop::style::array_intersectsrc/cop/style/array_intersect.rscargo test --lib -- cop::style::array_intersect///doc comment on the cop struct documenting what you found and fixedFixture Format
Mark offenses with
^markers on the line AFTER the offending source line.The
^characters must align with the offending columns. The message format isStyle/ArrayIntersect: <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:
src/config/or the cop's config handling, not detection logica
///comment on the cop struct and commitCRITICAL: 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:
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
returnorcontinuethat skips a wholenode type, operator class, or naming pattern, it's probably too broad. Prefer adding a
condition that matches the SPECIFIC differentiating context.
Rules
src/cop/style/array_intersect.rsandtests/fixtures/cops/style/array_intersect/cargo test --lib -- cop::style::array_intersectto verify your fix (do NOT run the full test suite)python3 scripts/check_cop.py Style/ArrayIntersect --rerun --clone --sample 15before committing to catch regressionsgit stashCurrent Fixture: offense.rb
tests/fixtures/cops/style/array_intersect/offense.rbCurrent Fixture: no_offense.rb
tests/fixtures/cops/style/array_intersect/no_offense.rbKey Source Files
src/cop/style/array_intersect.rsvendor/rubocop/lib/rubocop/cop/style/array_intersect.rbvendor/rubocop/spec/rubocop/cop/style/array_intersect_spec.rbRead 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 Style/ArrayIntersect --repos-onlypython3 scripts/investigate_cop.py Style/ArrayIntersect --contextpython3 scripts/verify_cop_locations.py Style/ArrayIntersectTop FN repos:
otwcode__otwarchive__412f04f(19 FN)discourse__discourse__9c8f125(13 FN) — exampleapp/controllers/invites_controller.rb:604publiclab__plots2__5e1d8c2(12 FN)Representative FN examples:
CoralineAda__alice__8b7efd5: alice/context.rb:253— Usesentence.downcase.split.intersect?(subject.split)instead of(sentence.downcase.split & subject.split).size > 0.CoralineAda__alice__8b7efd5: alice/grammar/dictionary.rb:11— Use['cup', 'cuppa', 'pot', 'kettle', 'mug', 'coffee', 'tea', 'latte', 'cappuccino', 'joe', 'mud'].intersect?(thing)instead of(['cup', 'cuppa', 'pot', 'kettle', 'mug', 'coffee', 'tea', 'latte', 'cappuccino', 'joe', 'mud'] & thing).count > 0.CoralineAda__alice__8b7efd5: alice/grammar/ngram.rb:17— Use[gram.flatten[0]].intersect?(matches)instead of([gram.flatten[0]] & matches).present?.Pre-diagnostic Results
Diagnosis Summary
Each example was tested by running nitrocop on the extracted source in isolation
with
--force-default-configto 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 #1:
CoralineAda__alice__8b7efd5: alice/context.rb:253NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.
Full-file enclosing chain: method body (line 252:
def near_match(subject, sentence)) > class body (line 1:class Context)Message:
Usesentence.downcase.split.intersect?(subject.split)instead of(sentence.downcase.split & subject.split).size > 0.Ready-made test snippet (add to offense.rb, adjust
^count):Full file context (30 lines before offense):
FN #2:
CoralineAda__alice__8b7efd5: alice/grammar/dictionary.rb:11NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.
Full-file enclosing chain: method body (line 10:
def self.coffee_or_tea(thing)) > class body (line 2:class Dictionary) > module body (line 1:module Grammar)Prism AST at offense line:
StatementsNode > CallNode > CallNode > ParenthesesNode > StatementsNode > CallNode > ArrayNode > StringNode > StringNode > StringNode > StringNode > StringNode > StringNode > StringNode > StringNode > StringNode > StringNode > StringNode > ArgumentsNode > LocalVariableReadNode > ArgumentsNode > IntegerNodeMessage:
Use['cup', 'cuppa', 'pot', 'kettle', 'mug', 'coffee', 'tea', 'latte', 'cappuccino', 'joe', 'mud'].intersect?(thing)instead of(['cup', 'cuppa', 'pot', 'kettle', 'mug', 'coffee', 'tea', 'latte', 'cappuccino', 'joe', 'mud'] & thing).count > 0.Ready-made test snippet (add to offense.rb, adjust
^count):Full file context (30 lines before offense):
FN #3:
CoralineAda__alice__8b7efd5: alice/grammar/ngram.rb:17NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.
Full-file enclosing chain: method body (line 16:
def with_leading(matches, args={})) > class body (line 2:class Ngram) > module body (line 1:module Grammar)Message:
Use[gram.flatten[0]].intersect?(matches)instead of([gram.flatten[0]] & matches).present?.Ready-made test snippet (add to offense.rb, adjust
^count):Full file context (30 lines before offense):
FN #4:
CoralineAda__alice__8b7efd5: alice/grammar/ngram.rb:25NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.
Full-file enclosing chain: method body (line 24:
def with(matches)) > class body (line 2:class Ngram) > module body (line 1:module Grammar)Message:
Usegram.flatten.intersect?(matches)instead of(gram.flatten & matches).present?.Ready-made test snippet (add to offense.rb, adjust
^count):Full file context (30 lines before offense):
FN #5:
CoralineAda__alice__8b7efd5: alice/grammar/ngram_factory.rb:31NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.
Full-file enclosing chain: method body (line 29:
def omnigram(args={})) > class body (line 2:class NgramFactory) > module body (line 1:module Grammar)Message:
Useexclude.intersect?(unigram.flatten)instead of(exclude & unigram.flatten).count > 0.Ready-made test snippet (add to offense.rb, adjust
^count):Full file context (30 lines before offense):
FN #6:
CoralineAda__alice__8b7efd5: alice/grammar/ngram_factory.rb:32NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.
Full-file enclosing chain: method body (line 29:
def omnigram(args={})) > class body (line 2:class NgramFactory) > module body (line 1:module Grammar)Message:
Useexclude.intersect?(unigram.flatten)instead of(exclude & unigram.flatten).count > 0.Ready-made test snippet (add to offense.rb, adjust
^count):Full file context (30 lines before offense):
FN #7:
CoralineAda__alice__8b7efd5: alice/grammar/ngram_factory.rb:33NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.
Full-file enclosing chain: method body (line 29:
def omnigram(args={})) > class body (line 2:class NgramFactory) > module body (line 1:module Grammar)Message:
Useexclude.intersect?(unigram.flatten)instead of(exclude & unigram.flatten).count > 0.Ready-made test snippet (add to offense.rb, adjust
^count):Full file context (30 lines before offense):
FN #8:
CoralineAda__alice__8b7efd5: alice/handlers/conversation.rb:99NOT DETECTED — CODE BUG
The cop fails to detect this pattern. Fix the detection logic.
Full-file enclosing chain: method body (line 97:
def set_context_from_predicate) > class body (line 5:class Conversation) > module body (line 3:module Handlers)Message:
Usecommand_string.components.intersect?(Grammar::LanguageHelper::PRONOUNS)instead of(command_string.components & Grammar::LanguageHelper::PRONOUNS).count > 0.Ready-made test snippet (add to offense.rb, adjust
^count):Full file context (30 lines before offense):
Omitted 7 additional diagnosed FN example(s) for brevity.