JS-1423 Fix S7778 false positives for custom class methods with single argument#6555
Conversation
Tests cover the scenario where consecutive calls to custom class methods named push(), add(), or remove() are incorrectly flagged as combinable, even though the custom methods only accept a single argument. The tests verify that reports are suppressed for non-Array push() receivers and non-DOMTokenList classList receivers when TypeScript type information is available, while true positives (real Array.push, DOM classList, and importScripts) are still reported. Relates to JS-1423
The unicorn prefer-single-call rule flags consecutive method calls by name only, without using TypeScript type information. This causes false positives when custom class methods named push, add, or remove accept only a single argument and are not the built-in Array or DOMTokenList types. Implemented decorator.ts for S7778 that intercepts unicorn reports and uses the TypeScript type checker to verify the receiver is the specific built-in type targeted by the rule: isArray() for push calls, and DOMTokenList symbol check for classList.add/remove calls. importScripts is always reported. When TypeScript parser services are unavailable, the decorator passes reports through unchanged (conservative fallback). Added output fields to all invalid test cases to correctly document the autofix behavior (consecutive calls merged into one). The implementation follows the approved proposal and mirrors the pattern used in S7729 (no-array-method-this-argument). Relates to JS-1423
The implementation now properly detects rest parameters (...args) in method signatures using the dotDotDotToken property. When a method has a rest parameter, it can accept multiple arguments, so the rule correctly reports it as combinable even on custom classes. This fixes the false negative where methods like push(...items: any[]) were incorrectly being suppressed. The ruling analysis confirms 288 legitimate cases across TypeScript, Joust, Ghost, and other projects. The one mismatch (eigen project) represents a correctly- identified single-parameter method that should not be reported.
Ticket: JS-1423 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
s7778-methods-accepting-only-a-single-argument-incorrectly-flagged-as-combinable-sonnet
Replaced the logical AND check `declaration && declaration.dotDotDotToken` with an optional chain expression `declaration?.dotDotDotToken` in the `methodHasSingleParameter` function of decorator.ts. This addresses the S6582 code smell reported by the external SonarQube instance, which requires using optional chain expressions for more concise and readable code. No functional change — the behavior is identical since `declaration?.dotDotDotToken` evaluates to `undefined` (falsy) when `declaration` is nullish, matching the previous guard behavior.
|
Instead of checking call signature arity, use the TypeScript type checker to verify the receiver is one of the built-in types the upstream rule targets: use the existing |
Comment: Instead of checking call signature arity, use the TypeScript type checker to verify the receiver is one of the built-in types the upstream rule targets: use the existing `isArray` helper to guard `push` calls, and check that the receiver type is `DOMTokenList` for `classList.add`/`classList.remove`. The `importScripts` case needs no type check (negligible risk of a user-written single-arg shadow). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
Done. Simplified the decorator to use pure type-checking as suggested:
Removed the |
|
Missing upstream FP assertion (4E) The test file imports This acts as a sentinel: if the upstream unicorn rule is later improved to understand custom class types and the FP disappears naturally, the test will fail and signal that the decorator is no longer needed. Example structure: import { rules } from '../external/unicorn.js';
const upstreamRule = rules['prefer-single-call'];
// upstream rule still flags the FP pattern
upstreamRuleTester.run('upstream raises on custom single-arg push', upstreamRule, {
invalid: [
{
code: `
class CustomClass { push(item: number): void {} }
const instance = new CustomClass();
instance.push(1);
instance.push(2);
`,
errors: 1,
},
],
valid: [],
}); |
Comment: **Missing upstream FP assertion (4E)**
The test file imports `rule` from `./index.js` throughout, which is the already-decorated rule. For decorator fixes, there should also be a test that imports the raw upstream rule directly (before decoration) and asserts that it *does* raise an issue on the FP pattern — e.g., a custom class with a single-arg `push` method.
This acts as a sentinel: if the upstream unicorn rule is later improved to understand custom class types and the FP disappears naturally, the test will fail and signal that the decorator is no longer needed.
Example structure:
```ts
import { rules } from '../external/unicorn.js';
const upstreamRule = rules['prefer-single-call'];
// upstream rule still flags the FP pattern
upstreamRuleTester.run('upstream raises on custom single-arg push', upstreamRule, {
invalid: [
{
code: `
class CustomClass { push(item: number): void {} }
const instance = new CustomClass();
instance.push(1);
instance.push(2);
`,
errors: 1,
},
],
valid: [],
});
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The upstream canary test for the prefer-single-call rule was missing an `output` field. The upstream unicorn rule applies a fix combining consecutive push calls (e.g. `push(1); push(2)` → `push(1, 2)`), and ESLint's rule tester expects the original code when no output is specified, causing a mismatch.
|
Good call. Added an upstream canary |
s7778-methods-accepting-only-a-single-argument-incorrectly-flagged-as-combinable-sonnet
|
|
Done — the upstream canary test was added in the previous commit and is already in the branch. See |




Fixes false positives in rule S7778 (prefer-single-call) where custom class methods named
push,add, orremoveaccepting only a single argument were incorrectly flagged as combinable.Problem
The unicorn prefer-single-call rule flags consecutive method calls by name only, without using TypeScript type information. This caused false positives when custom class methods named
push,add, orremoveaccepted only a single argument and were not the built-inArrayorDOMTokenListtypes.Changes
decorator.tsfor S7778 that intercepts unicorn reports and uses the TypeScript type checker to verify the receiver is the specific built-in type targeted by the rule:isArray()check forpushcallsDOMTokenListsymbol check forclassList.add/removecallsimportScriptsis always reported (no type check needed)...args) can accept multiple arguments and are correctly reported as combinable, fixing a false negative for methods likepush(...items: any[])The implementation mirrors the pattern used in S7729 (no-array-method-this-argument).
Relates to JS-1423