Skip to content

Canonical Formatting Rules (Normative)

This document is the canonical, normative rule set for Prince of Space formatter behavior.

  • If this document conflicts with any other prose doc, this document wins.
  • Any formatter behavior change or bugfix must align with this document.
  • If behavior changes intentionally, update this document in the same PR and record the rationale in docs/technical-decision-register.md.

Scope

These rules define output shape for Java formatting in modules/core.

  • Public configuration knobs are:
  • indentStyle
  • indentSize
  • lineLength
  • wrapStyle
  • closingParenOnNewLine
  • trailingCommas
  • javaLanguageLevel
  • Continuation indent is derived as 2 * indentSize and is not configurable.
  • Any new public knob requires a TDR entry and updates to this file.

Normative Rules

Rule 1: Idempotency is mandatory

format(format(x)) == format(x) must hold for valid inputs.

Rule 2: Braces and brace placement

  • Control-flow bodies (if/else/for/for-each/while/do) are always braced.
  • Opening braces use K&R style (same line as declaration/header).

Rule 3: Indentation

  • Block indentation is controlled by indentStyle + indentSize.
  • Wrapped continuation lines are indented by 2 * indentSize relative to the current block indentation. This follows the Oracle/IntelliJ convention and ensures parameters are always visually distinct from the method body.
  • For wrapped delimited argument/parameter-like lists that open on an already-continued line, that 2 * indentSize continuation step is measured from the line's effective continuation start column (for example after ? / : or wrapped binary operators), not from natural block indent.
  • Method chain continuations are an exception (see Rule 7): they use a single indentSize step rather than 2 * indentSize, because chain segments are visually self-delimiting (each starts with .).

Rule 4: Line length and wrapping trigger

  • lineLength is the target width.
  • If inline rendering exceeds lineLength, wrapping rules apply.
  • If no safe break point exists, long lines may remain over limit.

Rule 5: WrapStyle must be construct-uniform

wrapStyle semantics are uniform across all wrapped constructs (arguments, parameters, type clauses, type parameters, chain segments, binary chains, array initializers, and similar lists):

  • wide: greedily pack items while respecting width. An item that carries a line comment (//) forces a line break after it, since the comment claims the rest of the line.
  • balanced: either fully inline if it fits, or one item/operator segment per continuation line.
  • narrow: always one item/operator segment per continuation line once in wrapped form.

Enum constant lists (explicit exception): enum constant declarations are never collapsed onto a shared line ({ A, B, C } on one line never appears; wrapStyle does not apply greedy packing across constants). Each constant occupies its own line after the enum’s { (comma-separated, then ; when declarations follow). Empty enums remain enum E { } compatible with Rule 9 (no blank lines inside empty blocks beyond what the formatter already coalesces). See TDR-023.

Rule 6: Wrapped binary/operator chains

  • Wrapped binary/logical chains place operators at the start of continuation lines.
  • Operator chain wrapping must follow Rule 5.

Rule 7: Method chains

  • A wrapped method chain places each chain segment on its own continuation line with leading ..
  • Chain segments are indented by exactly indentSize (one block-indent unit) relative to the receiver's line, rather than the 2 * indentSize continuation indent used for delimited list continuations (Rule 3). The leading . on every segment provides its own visual delimiter, so a single indent step is sufficient and reduces excess depth in deeply chained code (see TDR-015).
  • When a wrapped method chain appears as an operand of a wrapped binary/logical chain (Rule 6), its segments are indented by one additional indentSize past the operator-line indent, so the chain remains visually distinct from the operator that introduces it.
  • Wrapping decisions for chain segments still follow Rule 5 (wide / balanced / narrow).

Rule 8: Closing delimiter placement

For wrapped argument/parameter-like lists:

  • closingParenOnNewLine=true: closing delimiter is on its own line at the opener's indentation column.
  • For nested wrapped calls with co-line openers (for example outer(inner(), closers may compact on one closer line ());, )));) instead of stacking one ) per line.
  • For line-separated nested openers, closers remain on separate lines aligned to their corresponding opener lines.
  • closingParenOnNewLine=false: closing delimiter remains on the final content line.
  • Trailing-lambda exception (TDR-021): when the last argument of a wrapped call is a lambda — block- or expression-bodied — the leading arguments and the lambda header (() -> {, (a, b) -> {, s ->, value ->, etc.) stay on the call line, the lambda body wraps according to its own rules (block body via its block indent; expression body via the receiver chain or other inner wrap mechanic), and the closing ) follows the lambda body inline (});, )), .last()), etc.) at the call's indent column regardless of closingParenOnNewLine. This matches palantir-java-format / Prettier / ktlint conventions and keeps the lambda visually coupled to the call. The opener line may slightly exceed lineLength as a soft tradeoff; the alternative (lambda header on its own line) reads worse. The exception does not apply when a leading argument is itself a block-bodied lambda or carries a leading line/block comment — those calls fall back to the standard per-arg wrapping path.
  • Wrapped lambda parameter list (TDR-022): when a parenthesized lambda's formal parameter list itself wraps (multi-parameter list that does not fit), the closing ) -> always lands on its own line aligned to the opener ( column — same shape as a wrapped declaration parameter list — regardless of closingParenOnNewLine. Formal parameters cannot trail off the end of a long line because the -> arrow needs to read as a continuation marker; pinning the closer to the opener column matches the general Rule 8 placement and keeps the param-list shape consistent with constructor/method declarations.

Rule 9: Blank lines

  • Collapse multiple blank lines to at most one.
  • No blank line immediately after an opening brace.
  • No blank line immediately before a closing brace.

Rule 10: Comments and annotation safety

  • Preserve comment semantics and placement intent.
  • Preserve type-use annotation semantics and declaration annotation correctness.
  • Formatting must not change program meaning.

Change Control

Any PR that changes formatter output shape must include:

  1. tests demonstrating intended behavior,
  2. showroom goldens regenerated when applicable,
  3. updates to this document if the normative rule set changed,
  4. a TDR entry when policy (not just bug parity) changed.