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 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 legacy-union-syntax leaves the Optional/Union values in place because it is a display-only lint that reports findings rather than rewriting them.

python
from typing import Optional, Union

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

All Cases

  1. A run of aliased module imports arrives out of alphabetical order. alphabetize reorders the statements first, then align-imports 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 align-equals, while reassigned-constants 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. align-equals widens the = column across the whole run, and single-use-variables 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. alphabetize 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. collection-layout expands each list, and align-match-case 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. strip-trailing-commas removes the comma and align-match-case collapses each body onto its pattern line, aligning the arm : column across the run.

  8. 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.

  9. A mixed run of bare imports and from imports, each kind out of order. alphabetize sorts each kind in place, align-imports aligns the as and import keywords, and blank-lines inserts one blank at the bare-to-from boundary.

  10. Three bare imports arrive out of order, each reached through a single attribute. alphabetize reorders the statements, and bare-imports flags every one, each diagnostic anchored to the import as written rather than to its post-reorder position.

  11. Three top-level class definitions out of alphabetical order with no spacing between them. alphabetize reorders the declarations and blank-lines settles the canonical two-blank-line cushion between top-level definitions.

  12. 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.

  13. 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.

  14. A module opens with from __future__ import annotations ahead of a constant and an over-large blank-line gap before the first definition. unused-future-annotations removes the import and blank-lines collapses the gap down to the canonical two-line cushion between the constant and the def.

  15. 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.

  16. A module opens with from __future__ import annotations ahead of aliased imports that need no future. unused-future-annotations removes the import, leaving a residual blank line, and align-imports aligns the as column across the remaining run.

  17. A from __future__ import annotations precedes step-narration comments in a function body. unused-future-annotations removes the import, and step-narration emits one diagnostic per offending comment whose range tracks the post-removal offset.

  18. A block of only from imports, external and local-package mixed. alphabetize keeps external imports ahead of the local group, blank-lines separates the two with one blank and adds no leading bare group, and align-imports aligns within each.

  19. 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 blank-lines settles a one-blank cushion between every member.

  20. 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 blank-lines cushions each one.

  21. 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.

  22. A multi-line docstring opens on the triple-quote line and carries a single overlong prose line. docstring-frame moves the opener to its own line and docstring-wrap reflows the prose to fit the docstring budget.

  23. An outer class and its nested helper class each carry out-of-order annotated fields. alphabetize and the alignment rules act independently within each scope, giving the inner class its own : and = columns.

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

  25. 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 alphabetize.

  26. 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 blank-lines cushions the methods.

  27. Out-of-order methods, one holding a match with single-statement return arms. The methods sort alphabetically, align-match-case collapses each arm body onto its pattern line and aligns the : column, and blank-lines cushions the methods.

  28. Out-of-order methods, one opening its body with a try/except block. alphabetize reorders the methods and blank-lines cushions them, leaving the compound-statement body of the reordered method untouched.

  29. 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 blank-lines cushions each.

  30. 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.

  31. 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.

  32. 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.

  33. 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.

  34. 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 legacy-union-syntax leaves the Optional/Union values in place because it is a display-only lint that reports findings rather than rewriting them.

  35. A five-parameter signature with out-of-order typed parameters. The parameters regroup into required-then-optional alphabetical runs, expand to one per line, align the annotation : column, and align the default = column across the optional run.

  36. 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.

  37. 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 align-match-case collapses each arm body onto its pattern line with the : column aligned.

  38. Bare, external from, and local-package imports scrambled together, with myapp declared first-party. alphabetize resolves them into the three canonical groups, blank-lines separates each with one blank, and align-imports aligns the keyword within each group.

    1. alphabetize

    A short inline dict with out-of-order keys that fits the line budget. alphabetize reorders the keys in place and the dict stays inline, because the fitting width gives collection-layout no reason to expand.

  39. Parameters and matching Args: entries both in non-alphabetical order. alphabetize reorders the signature and the Args entries against the same key, align_colons aligns both : columns, and docstring_wrap wraps each description at its post-align hanging column.

  40. 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.

  41. A single-line docstring whose content would exceed the docstring budget once expanded. The docstring expands to multi-line form and docstring-wrap reflows the body across multiple lines to fit the budget.

  42. Methods each carry a single-line docstring with no blank line between them. The docstrings expand to multi-line form and blank-lines inserts the one-blank cushion between the methods.

  43. An already-expanded dict with out-of-order string keys and a trailing comma. alphabetize sorts the keys, strip-trailing-commas drops the final comma, and align-colons aligns the : column across the rows.

  44. Module-level functions out of order with no spacing between them. alphabetize reorders the definitions and blank-lines settles the canonical two-blank-line cushion around each top-level function.

  45. 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.

  46. 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.

python
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

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.