Sdlc/Structural/ArrayLiteralOfLocalsReturn: carve out tuples consumed as arrays; fix message accessor name #1349

Open
opened 2026-10-25 21:22:15 +00:00 by jared · 0 comments
Owner

Problem

The cop Sdlc/Structural/ArrayLiteralOfLocalsReturn flags a def whose final expression is an array literal of two or more lvars.
The prescribed fix (return a Data.define value object) is not always correct.
A tuple that the caller consumes as an array breaks when it becomes a value object.
The message also always says location.<name> accessors (array_literal_of_locals_return/message.rb:27), whatever the object is.

Found by the #1325 zero-false-positive audit (docs/plans/class-length-family/sdlc-cop-audit.md).

Observed

Counterexample 1: a pair consumed by to_h. The cop flags pair, but to_h needs the array.

class Pairs
  def to_hash(items) = items.to_h { |item| pair(item) }

  def pair(item)
    key = item.name
    value = item.size
    [key, value]
  end
end

Counterexample 2: plugins/os-adr/lib/adr/index.rb:36 returns a tuple with an _stale slot. The caller discards that slot, so a value object adds a member nobody reads.
Of 20 hits, 13 are test helpers that mirror Open3.capture3 tuples (for example plugins/os-me/test/prediction_cli_test.rb:17).

Reproduce

n/a: the cop matcher is at plugins/os-sdlc/lib/os_sdlc/cops/array_literal_of_locals_return.rb:38-51; run the cop on the sample above.

Approach

Carve-outs go in the cop matcher, never in a config Exclude list or a test-path exclusion.
Skip a two-element return whose call sites feed to_h, Hash[], or each_with_object pairs.
Skip a return with any _-prefixed element (the caller discards it; drop it instead).
Make the message use the real accessor name, not a fixed location..

Required work:

  1. Add the carve-outs to the matcher — check: present to_h in plugins/os-sdlc/lib/os_sdlc/cops/array_literal_of_locals_return/*.rb

Acceptance criteria

  1. Given a def returning [key, value] whose result feeds to_h, when the cop runs, then it reports no offense.
  2. Given a def returning an array with a _-prefixed lvar, when the cop runs, then it reports no offense.
  3. Given an offense, when the message prints, then it does not hard-code location. accessors.

Origin

  • Trigger: #1325 zero-false-positive audit of the Sdlc cops
  • Improvised this session: none
  • Chain: false positive on consumed tuples ← matcher has no consumer check ← DESIGN (plugins/os-sdlc/lib/os_sdlc/cops/array_literal_of_locals_return.rb)
  • Root candidate: none found; searched ArrayLiteralOfLocalsReturn
  • Where: plugins/os-sdlc/lib/os_sdlc/cops/array_literal_of_locals_return.rb
  • Session: eb98cdcd-6783-46a0-b7df-cf18a8fe2a56
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-ticket-1325/eb98cdcd-6783-46a0-b7df-cf18a8fe2a56.jsonl
    VERDICT: CREATE
## Problem The cop `Sdlc/Structural/ArrayLiteralOfLocalsReturn` flags a def whose final expression is an array literal of two or more lvars. The prescribed fix (return a `Data.define` value object) is not always correct. A tuple that the caller consumes as an array breaks when it becomes a value object. The message also always says `location.<name>` accessors (`array_literal_of_locals_return/message.rb:27`), whatever the object is. Found by the #1325 zero-false-positive audit (docs/plans/class-length-family/sdlc-cop-audit.md). ## Observed Counterexample 1: a pair consumed by `to_h`. The cop flags `pair`, but `to_h` needs the array. ```ruby class Pairs def to_hash(items) = items.to_h { |item| pair(item) } def pair(item) key = item.name value = item.size [key, value] end end ``` Counterexample 2: `plugins/os-adr/lib/adr/index.rb:36` returns a tuple with an `_stale` slot. The caller discards that slot, so a value object adds a member nobody reads. Of 20 hits, 13 are test helpers that mirror `Open3.capture3` tuples (for example `plugins/os-me/test/prediction_cli_test.rb:17`). ## Reproduce n/a: the cop matcher is at `plugins/os-sdlc/lib/os_sdlc/cops/array_literal_of_locals_return.rb:38-51`; run the cop on the sample above. ## Approach Carve-outs go in the cop matcher, never in a config Exclude list or a test-path exclusion. Skip a two-element return whose call sites feed `to_h`, `Hash[]`, or `each_with_object` pairs. Skip a return with any `_`-prefixed element (the caller discards it; drop it instead). Make the message use the real accessor name, not a fixed `location.`. Required work: 1. Add the carve-outs to the matcher — check: present `to_h` in `plugins/os-sdlc/lib/os_sdlc/cops/array_literal_of_locals_return/*.rb` ## Acceptance criteria 1. Given a def returning `[key, value]` whose result feeds `to_h`, when the cop runs, then it reports no offense. 2. Given a def returning an array with a `_`-prefixed lvar, when the cop runs, then it reports no offense. 3. Given an offense, when the message prints, then it does not hard-code `location.` accessors. ## Origin - Trigger: #1325 zero-false-positive audit of the Sdlc cops - Improvised this session: none - Chain: false positive on consumed tuples ← matcher has no consumer check ← DESIGN (plugins/os-sdlc/lib/os_sdlc/cops/array_literal_of_locals_return.rb) - Root candidate: none found; searched ArrayLiteralOfLocalsReturn - Where: plugins/os-sdlc/lib/os_sdlc/cops/array_literal_of_locals_return.rb - Session: eb98cdcd-6783-46a0-b7df-cf18a8fe2a56 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-ticket-1325/eb98cdcd-6783-46a0-b7df-cf18a8fe2a56.jsonl VERDICT: CREATE
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#1349
No description provided.