Skip to content

Add eval for wrapping multi-operation stream chains #45

Description

@martinfrancois

Problem

The Java Streams skill should prefer readable line wrapping for fluent stream chains. When a stream chain has more than one operation after stream() or parallelStream(), the source call should keep .stream() on the source line and each following stream operation should move to its own continuation line.

This is eval-worthy because the code is functionally identical either way, but the one-line form becomes harder to scan as soon as the chain has multiple operations. The skill should treat this as a readability style rule when it writes or refactors Java stream code.

Code before the prompt was executed

A representative one-line stream chain looked like this:

var labels = card.labels().stream().map(StateNames::normalize).collect(Collectors.toCollection(HashSet::new));

The same pattern existed in product code as short one-line mapping chains, for example:

values.put("checklists", checklists.stream().map(Card::checklistMap).toList());
values.put("attachments", attachments.stream().map(Card::attachmentMap).toList());

Prompt that caused the implementation

The maintainer asked:

for better readability, can you split up stream chains so that .stream is still on the same line but other operations are on following lines? for example this isnt very readable: card.labels().stream().map(StateNames::normalize).collect(Collectors.toCollection(HashSet::new)); however i'd be inclined to say that if there is only one operation (meaning .stream is immediately followed by a terminal operation, or a part of a stream is saved to a variable to be continued at a later point, then having it in one line is fine. ideally if possible can we do this with spotless or something else that does it automatically? do that in a separate commit and ensure its done automatically for the entire codebase

Later prompt that exposed the issue

Not applicable. This was a direct maintainer style request, not a later correction of a previous stream refactor.

Prompt-produced code before maintainer correction

There was no rejected intermediate implementation in the final product code. During implementation, the important design finding was that a post-Spotless check alone was not enough because the existing Palantir formatter collapsed the desired wrapping back to one line. The final implementation therefore integrated a custom stream-chain formatter into the Spotless Java pipeline.

Why the one-line code is bad

The one-line chain is not incorrect. The problem is readability:

  1. Multiple operations after stream() are visually compressed into one expression.
  2. The reader has to scan horizontally to distinguish the source, intermediate operations, and terminal operation.
  3. Adding another operation later creates churn because the whole line often needs to be rewrapped.
  4. The style is inconsistent with longer chains that formatter line length already forces to wrap.

The exception is a one-operation chain such as items.stream().findFirst(), where the one-line form is still clear, or a bare stream saved to a variable for later continuation.

Maintainer-preferred code

var labels = card.labels().stream()
        .map(StateNames::normalize)
        .collect(Collectors.toCollection(HashSet::new));

For the product-code examples:

values.put("checklists", checklists.stream()
        .map(Card::checklistMap)
        .toList());
values.put("attachments", attachments.stream()
        .map(Card::attachmentMap)
        .toList());

Why the replacement is better

The replacement keeps the stream source on the first line and makes each operation independently scannable. It preserves behavior, encounter order, terminal operation choice, mutability, and exception behavior. It also makes future stream changes easier to review because adding, removing, or replacing an operation changes one line instead of a dense fluent expression.

Desired eval behavior

  • Reward keeping .stream() or .parallelStream() on the source line.
  • Reward moving each following operation to its own continuation line when there is more than one operation after the stream call.
  • Reward leaving one-operation chains such as items.stream().findFirst() on one line when they remain readable.
  • Reward preserving stream semantics while changing only formatting.
  • Reward checking that the automatic formatter enforces the style instead of relying on one-off manual wrapping.

Anti-patterns the eval should reject

  • Keeping multi-operation chains as a single dense line.
  • Moving .stream() to its own separate line when the maintainer requested it to stay with the source expression.
  • Wrapping only some operations in the chain.
  • Changing stream behavior, terminal operation, ordering, or mutability while attempting a formatting-only cleanup.
  • Adding a check that conflicts with the project formatter instead of integrating with the formatter pipeline.

Suggested eval name

wrap-multi-operation-stream-chains

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions