Skip to content

Rule Composition

The per-rule pages walk each rule's canonical case in isolation, but the real question for most projects is what happens when several rules apply to the same block. The composition fixtures answer that question. Each case here pairs a small Python source with the rule set it activates, and the before/after pair shows the combined effect of those rules running together in Pipeline Order.

The cases are the same crate/tests/fixtures/composition/ set the binary's integration tests run against, so the rendered output on this page is the canonical answer to what Prose does when these rules compose.

The Canonical Case

One module-level constant carries the full composition story. The right-hand dict starts in a state that puts five rules in motion:

  • The literal overflows code-line-length on a single line, so breaks it apart.
  • Entries arrive in authorship order rather than alphabetical, so sorts them.
  • Keys and values both want vertical columns, so and compute the padding.
  • Values use the legacy Union[…] form, so rewrites them to the | operator.

The five rules fire in Pipeline Order against the same block, reparsing between each so every rule downstream measures against the rewritten source rather than the original.

A module constant whose overflowing dict carries legacy union values, with target-version pinned to 3.10. The dict expands, sorts, and aligns its : column, while

leaves the Optional/Union values in place because it is a display-only lint that reports findings rather than rewriting them.

from typing import Optional, Union

PRIMARY = {
    "alpha"      : Optional[str],
    "beta"       : Union[int, str],
    "delta_long" : Union[bool, None],
    "gamma"      : Optional[int]
}
SECONDARY = "fallback"
python

All Cases

  1. A run of aliased module imports arrives out of alphabetical order.

    reorders the statements first, then settles the as keyword onto a shared column across the sorted run.

  2. Untyped module constants of varied widths widen onto a shared = column under

    , while flags RATE_LIMIT alone, the one name rebound after its first write, and leaves the write-once neighbors silent. The diagnostic range tracks the source offsets rather than the post-align statement positions.

  3. A function body assigns consecutive locals, some reused and some used once.

    widens the = column across the whole run, and emits one diagnostic per once-used binding whose range tracks the source offset.

  4. Annotated class fields out of alphabetical order with varied name and default widths.

    reorders them, then both the type-annotation : column and the default = column align across the sorted run.

  5. Single-statement match arms each return a long inline dict. Each dict expands one entry per line, its keys sort alphabetically, and the : column aligns within each arm, while the bodiless wildcard arm collapses back onto its pattern line.

  6. Single-statement match arms return inline lists that overflow the line budget.

    expands each list, and leaves those arm bodies wrapped because expansion forces a multi-line statement the arm cannot collapse.

  7. Single-statement match arms return inline collection literals carrying a trailing comma.

    removes the comma and collapses each body onto its pattern line, aligning the arm : column across the run.

  8. HEADER_VALIDATORS fits inline at its unaligned =, but

    shifts that = rightward to clear the longer _VALID_HEADER_VALUE_RE_BYTE. reserves the shifted column, so the dict stays expanded from the first pass rather than collapsing and re-exploding on the next.

  9. A function opens with single-target assignments and guards on a multi-line BoolOp. The assignment run aligns its = column, and the BoolOp's comparison operators right-align in their own independent column below.

  10. A mixed run of bare imports and from imports, each kind out of order.

    sorts each kind in place, aligns the as and import keywords, and inserts one blank at the bare-to-from boundary.

  11. Three bare imports arrive out of order, each reached through a single attribute.

    reorders the statements, and flags every one, each diagnostic anchored to the import as written rather than to its post-reorder position.

  12. A one-line call carrying a multi-entry dict argument settles to the canonical shape in one forward pass, where

    explodes the dict, re-indents it to the keyword column, sorts the entries, and forms the : column. The dict entries land at the keyword's item-indent plus a step and the } at the keyword column.

  13. explodes a crowded call to keyword form, sorts the keywords, and aligns their = on one column, all in the same format pass.

  14. runs ahead of , so a crowded call to a module function explodes to keyword form and then its keywords alphabetize in the same pass. The signature holds its source order, since positional parameters never reorder.

  15. Three top-level class definitions out of alphabetical order with no spacing between them.

    reorders the declarations and settles the canonical two-blank-line cushion between top-level definitions.

  16. A multi-line subscript assignment target condenses onto one line under

    , then aligns its = with the single-line assignments around it under . Condensing first lets the target join the alignment run rather than sitting outside it with an operator stranded across a break.

  17. A multi-line tuple key condenses onto one line under

    , then joins its single-line siblings so orders the keys and settles the : one space past the widest. The condensed key reads as the single clean member the later rules fold into their run rather than one stranded across a break.

  18. Module constants above a # fmt: off block sort alphabetically and align their = column. The suppressed block stays verbatim because the directive bounds its own scope, and the run boundary respects that bracket.

  19. A free-floating comment block, set off by a blank line above and below, sits between the module constants and a def-run that

    reorders. lifts the constants into the leading band over its own pass while the detached block stays with the definitions, so the comment survives the reorder rather than being dropped.

  20. A long inline dict with out-of-order keys. The full dict cycle expands the dict, sorts the keys, and aligns the : column across the wider rows, leaving the much shorter "zeta" row outside the shared column.

  21. A module opens with from __future__ import annotations ahead of a constant and an over-large blank-line gap before the first definition.

    removes the import and collapses the gap down to the canonical two-line cushion between the constant and the def.

  22. A module top carries from __future__ import annotations, out-of-order aliased imports, and irregular spacing. The subset removes the unused future, sorts the imports, aligns the as column, and normalizes the blank-line cushion before the first function.

  23. A module opens with from __future__ import annotations ahead of aliased imports that need no future.

    removes the import, leaving a residual blank line, and aligns the as column across the remaining run.

  24. A from __future__ import annotations precedes step-narration comments in a function body.

    removes the import, and emits one diagnostic per offending comment whose range tracks the post-removal offset.

  25. The same call and dict as the canonical case, but with

    's explode knob cleared. leaves the multi-entry dict inline rather than exploding it, so finds no multi-line block to re-indent and the dict rides its keyword line whole. The freeze reaches the dict argument the canonical shape would otherwise expand to its own column.

  26. An exploded signature carrying a trailing , strips to a shape both rules accept.

    removes the comma and recognizes the bare emission as canonical, so a second pass leaves the output byte-identical.

  27. A block of only from imports, external and local-package mixed.

    keeps external imports ahead of the local group, separates the two with one blank and adds no leading bare group, and aligns within each.

  28. A class with both annotated fields and methods declared out of order. The fields sort and align both : and = as one group, the methods sort separately, and

    settles a one-blank cushion between every member.

  29. resolves a callee defined below the call against the completed module bindings, so the crowded forward call explodes to keyword form and its keywords sort in the same pass that lifts the callee above its caller, leaving the file settled on the first run.

  30. Aligning the import keyword to the wider apps.registry.longpath prefix would carry apps.io's names past import-line-length.

    looks ahead to that aligned prefix and splits the names against it, so the run then aligns with every row inside the cap rather than one member overflowing.

  31. With group-imports = false,

    sorts the imports as one flat block, adds no section dividers, and still aligns the import keyword across the from lines.

  32. A class __init__ opens with a run of self-attribute assignments followed by out-of-order methods. The self-assigns align their = column inside __init__, the later methods sort alphabetically, and

    cushions each one.

  33. A multi-line docstring opens on the triple-quote line and carries an Args: section. The opener and closer move to their own lines, the Args entries align their : column, and the prose paragraph wraps to the docstring budget.

  34. A multi-line docstring opens on the triple-quote line and carries a single overlong prose line.

    moves the opener to its own line and reflows the prose to fit the docstring budget.

  35. An outer class and its nested helper class each carry out-of-order annotated fields.

    and the alignment rules act independently within each scope, giving the inner class its own : and = columns.

  36. The keyword KW sits at the budget before

    spaces its = from the tight KW={...} to KW = {...}. accounts for the two columns that spacing adds, so the value expands once and holds rather than collapsing inline and exploding on the next pass.

  37. splits a long import into repeated-prefix lines, and leaves the run untouched because every line already opens with the same from ... import prefix.

  38. Single-statement match arms return long inline dicts that overflow once on their arm line. Each dict expands and aligns its : column, but the keys keep source order because the rule set excludes

    .

  39. Methods carry multi-line docstrings with opener and closer sharing prose lines, and no spacing between methods. The opener and closer move to their own lines, the prose reflows, and

    cushions the methods.

  40. Out-of-order methods, one holding a match with single-statement return arms. The methods sort alphabetically,

    collapses each arm body onto its pattern line and aligns the : column, and cushions the methods.

  41. Out-of-order methods, one opening its body with a try/except block.

    reorders the methods and cushions them, leaving the compound-statement body of the reordered method untouched.

  42. One signature loses its redundant -> None to

    while flags its unannotated parameter, the two rules acting on the same def in a single pass.

  43. Out-of-order methods carry a mix of single-line and multi-line docstrings. The methods reorder, single-line docstrings expand to multi-line form, multi-line openers and closers move to their own lines, and

    cushions each.

  44. Two dicts in one scope, one multi-entry and one single-entry. The multi-row dict aligns its : column, while the single-row dict has its pre-: padding stripped because a lone entry forms no alignment group.

  45. An overflowing single-line dict whose entries each carry a single-key nested dict. The outer dict expands and sorts, and after expansion each nested single-key dict stays inline as its own one-row group while the outer : column aligns.

  46. Args entries with old-style continuations indented to body_indent + 4 rather than under the description. align_colons shifts every colon to the longest-name column, and docstring_wrap reflows the continuations into the post-align hanging-column shape.

  47. Class fields annotated with Optional and Union. The : and = columns align, but the run splits into two adjacent alignment groups where the shorter host/port names share one column and the longer fallback_host/retry_count names share another.

  48. The nested "b" value fits inline, yet padding its : to the wider "aaaaaaaaaaaaa" column would carry the row past code-line-length.

    partitions the row out unpadded the way an over-max-shift outlier breaks off, and with the lone remaining key no column survives, so the dict stays unaligned within the cap rather than padding past it.

  49. A module constant whose overflowing dict carries legacy union values, with target-version pinned to 3.10. The dict expands, sorts, and aligns its : column, while

    leaves the Optional/Union values in place because it is a display-only lint that reports findings rather than rewriting them.

  50. and agree on where the own-line comment belongs. The comment forward-attaches to MAX_WORKERS, so banding hoists the pair above configure on the first pass rather than relocating the block only after a second format settles the blank-line texture.

  51. Out-of-order class methods each carry a run of body assignments. The methods sort and gain blank-line cushions, and each method's = column aligns independently within its own body.

  52. A function opens with single-target assignments ahead of a match whose arms return one binding each. The pre-match run aligns its = column, and

    collapses each arm body onto its pattern line with the : column aligned.

  53. An out-of-order single-line class stub sandwiched between block-bodied classes reorders into place, and

    settles the canonical two-blank-line cushion on both sides of the stub, so and reach a fixed point in one pass rather than trading the gap back and forth.

  54. Bare, external from, and local-package imports scrambled together, with myapp declared first-party.

    resolves them into the three canonical groups, separates each with one blank, and aligns the keyword within each group.

    1. alphabetize

    A short inline dict with out-of-order keys that fits the line budget.

    reorders the keys in place and the dict stays inline, because the fitting width gives no reason to expand.

  55. A signature holds its parameter order, and its Args: entries follow.

    mirrors the entries against the signature's source order, align_colons aligns the Args: entries' : column, and docstring_wrap wraps each description at its post-align hanging column.

  56. A typed signature and an Args: section whose names diverge by design. align_colons treats the two as independent groups with separate columns, and docstring_wrap wraps the Args descriptions at the Args hanging column rather than the signature's.

  57. A single-line docstring whose content would exceed the docstring budget once expanded. The docstring expands to multi-line form and

    reflows the body across multiple lines to fit the budget.

  58. Methods each carry a single-line docstring with no blank line between them. The docstrings expand to multi-line form and

    inserts the one-blank cushion between the methods.

  59. A single-line docstring in plain double quotes canonicalizes to """ under

    , then drops it to the multi-line shape. The reframe runs first, so the requoted one-liner expands in the same pass.

  60. A dict handed to a call as its only argument hugs the call paren, its entries one per line at the call's item-indent and the } closing against the call.

    leaves the single-argument call inline rather than exploding it, so the dict opens on the call line itself.

  61. An already-expanded dict with out-of-order string keys and a trailing comma.

    sorts the keys, drops the final comma, and aligns the : column across the rows.

  62. Module-level functions out of order with no spacing between them.

    reorders the definitions and settles the canonical two-blank-line cushion around each top-level function.

  63. A one-line call carrying an over-wide tuple argument settles in one forward pass, where

    explodes the tuple, re-indents it to the keyword column, and orders the keywords with aligning their =. The tuple elements land at the keyword's item-indent plus a step and the ) at the keyword column, the same seating a list or dict argument takes.

  64. A five-parameter typed signature holds its source order, since the parameters never reorder. It expands to one per line, aligns the annotation : column, and aligns the default = column across the optional run.

  65. An overflowing single-line dict whose values are Optional and Union expressions. The dict expands, sorts its keys, and aligns the : column, leaving the legacy union values on the right untouched.

  66. An expanded signature with typed parameters of varying widths and defaults. The : and = columns align, but the run splits into two adjacent groups where the narrow host/port share one column and the wider timeout_seconds/retries share another.

How Composition Resolves

Each case's pipeline runs the listed rules in canonical order, reparsing between rules. A rule downstream of another sees the rewritten source from the upstream rule, not the original source. The cases here cover the common interaction shapes.

Layout Before Alignment

running upstream of commits the per-line shape against which the alignment columns are computed.

Reorder Before Align

running upstream of settles the entry order, meaning the alignment math measures against the final column positions rather than the source ones.

Docstring Discipline Before Wrap

and running upstream of settle the quote placement before the body rewrap measures budgets.

Module Reorder Around a Block Marker

's module-level branch reorders the assigns above and below a # fmt: off block while the bracketed lines stay verbatim. The suppression directive bounds its own scope, so and fire freely on every assign outside the bracket and the run boundary respects the marker.

Module constants above a # fmt: off block sort alphabetically and align their = column. The suppressed block stays verbatim because the directive bounds its own scope, and the run boundary respects that bracket.

bar_baz = 3
foo     = 2
zebra   = 1

# fmt: off
matrix = [[0.7, 0.1, 0.1],
          [0.1, 0.7, 0.1],
          [0.1, 0.1, 0.7]]
# fmt: on
python

For the per-rule canonical case, click any rule chip above. For the deterministic order the pipeline runs in, see the Pipeline Order reference. For the runner that drives the composition, see the Pipeline primitive. For the full rule catalog, see the Rules.