Skip to content

Rule Composition ​

Each case on this page pairs a small Python source with the rule set it turns on and shows what those rules write when they run together in Pipeline Order. A rule page shows the cases its own rule takes part in, whereas this page runs each case through the whole listed set. The cards below render the previewable cases from crate/tests/fixtures/composition/, and the binary's integration tests run every case in that directory.

The Canonical Case ​

One module-level constant exercises a rule from each family:

  • Its values use the legacy Union[…] form, so modernize-annotations rewrites them to the | operator and removes the typing import they read through.
  • The literal overflows code-line-length on one line, so reflow-collections writes it one entry per line.
  • The entries sit in the order the author wrote them rather than alphabetical, so alphabetize-siblings sorts them.
  • The keys then sit one per line in a column, so align-colons computes the padding from the widths the rewrite leaves.
  • The removed import leaves a gap where it stood, so space-statements closes it.

The rules run against the same block, re-parsing between each so every later rule measures the rewritten source.

PRIMARY overflows its line with values written in the older Optional and Union forms, and target-version = "3.10" allows the | syntax. modernize-annotations rewrites each value to | form first, so every rule downstream measures the shorter text, and the dict then explodes, its keys sort so "delta_long" moves ahead of "gamma", and align-colons pads the : column against the rewritten widths. With nothing left reading either name, from typing import Optional, Union is removed.

PRIMARY = {
    "alpha"      : str | None,
    "beta"       : int | str,
    "delta_long" : bool | None,
    "gamma"      : int | None
}
SECONDARY = "fallback"
python

The Cases ​

  1. _openers is assigned a dict with no space after any :, above CHECK_DELAY = 100. strip-stranded-padding writes a space after each :, which widens the _openers row by three columns.

    align-equals leaves the two = unaligned, because it measures the row at the width it has once that edit is written, and padding _openers onto a shared column with CHECK_DELAY would take the row past the 40-column budget.

  2. The "alpha" and "beta" arms of dispatch each return a long inline dict, and the wildcard arm returns None. Each dict explodes to one entry per line, its keys sort so "comment_text" leads, and the : of each entry pads into one column within its arm, while align-match-case joins the wildcard's return None onto its case _: line.

  3. evaluate opens with three record.get assignments and then guards on a multi-line BoolOp. align-equals pads the assignment run's = into one column, and align-comparisons right-aligns the is, ==, and < operators in a column of their own inside the if, the two columns independent of each other.

  4. padx += and border += sit in one run inside measure, each assigned a nested widget.tk.getint(widget.cget(...)) call, and the border row runs past the 64-column budget. The widget.tk.getint(...) call on the border row explodes, its widget.cget('border') argument moving to a row of its own, because reflow-calls measures the value from the row's own indent rather than one column short, while the padx row fits and stays whole.

  5. from .pgen2 import token is unread, and it is the line the # Local imports comment sits above, with from ..pgen2 import driver below it. band-constants sorts the import band and moves the comment onto whichever import ends up first, so when prune-inert-imports removes token, from ..pgen2 import driver moves up onto the vacated line with the heading still above it, and the comment is never left over a gap.

  6. visit names Node in its annotations and sits above class Node, under from __future__ import annotations. alphabetize-siblings moves the class above visit, and the output reads as if the directive were no longer needed. from __future__ import annotations stays, because prune-inert-imports runs before the sort, reads Node as a binding the sort is free to move either way, and keeps the directive rather than depending on where the sort puts the class.

  7. import sys, import os, and import json arrive out of order, and each module is used once below through a single attribute, os.getcwd(), sys.argv, and json.loads. alphabetize-siblings sorts the statements so json leads, and bare-imports reports all three, each finding landing on the import's row in the sorted output, so json is reported on row 1 and sys on row 3.

  8. The comment # the convergence floor the solver stops at sits on its own line directly below DEFAULT_EPSILON = 0.1, describing it, with MAX_INT = 5 after a blank line. band-constants moves the comment onto the end of the DEFAULT_EPSILON line as it packs and sorts the band, and align-equals then pads all three constants onto one = column, whereas a comment left on its own line would have split the run in two and left DEFAULT_EPSILON out of the alignment.

  9. The if condition is wrapped in a grouping pair, with the line break inside the find call's own parentheses rather than inside the pair. reflow-parentheses joins the interior onto one row and drops the redundant pair in a single pass, because the pair owns none of the breaks, leaving reflow-calls a call that already fits its row, so it changes nothing.

  10. CASES already holds its keys in order, and the "restore-of-both-in-a-cluster" row crosses the code-line-length of 80 with or without its padding, so reflow-collections breaks its tuple open across three rows. The entry keeps its place in the run it shares with "restore-of-the-working-tree" and "shallow-abbreviated-depth", padded to their column with no blank line after it, because alphabetize-siblings reorders nothing here and so keeps the blank lines the source writes rather than setting one on either side of each entry spanning rows.

  11. Padded to the column bbbbbbbbbbbb sets, the c row would reach 88 columns, past the 80-column budget, so reflow-calls explodes the frobnicate call and its opening row joins that column. A value broken open ends its run, so d starts a run of its own beneath the closing ) and keeps its single space. The same statements written with the call already exploded settle to this same output.

  12. A one-line configure call carries a four-entry dict in its settings keyword. reflow-collections explodes the dict, reflow-calls re-indents it to the keyword column, alphabetize-siblings sorts the keywords and the dict entries, and align-equals and align-colons pad the = and : columns. The dict entries sit one indent step past the keyword column, with the closing } back at the keyword column.

  13. deque([(start, 0)]), {start} assigns a call and a one-item set on one row that crosses the 50-column budget. reflow-collections never breaks a one-item set, so the row's tail counts at its full width rather than only up to its {. deque( explodes with its list argument packed on a row of its own, where it fits, rather than the inner tuple exploding under a column the explode would then have moved.

  14. draw(w, h, d, c) passes four positional arguments to a def draw(width, height, depth, color) defined above it. reflow-calls explodes the call to keyword form, naming each argument after its parameter so w becomes width = w, and alphabetize-siblings sorts the keywords so color leads while align-equals pads their = into one column.

  15. The list in the cccc keyword of g crosses the 40-column budget, and its first member is the call fn(100, 1000). reflow-collections explodes the list, and reflow-calls leaves fn(100, 1000) on one line, because exploding it would measure against a tail the explode is about to remove, so the call is measured where its entry ends up and fits there.

  16. some_function( argument_one_here, argument_two_here ) carries a space just inside each parenthesis, and the row crosses the 66-column budget by exactly those two spaces. strip-stranded-padding removes them, and reflow-calls measures the argument list at the width that removal leaves, so the call stays on one row rather than exploding and joining back once the padding is gone.

  17. chr(i) sits ahead of '\\u{0:04x}'.format(i) on a row that fits the 50-column budget once the call becomes an f-string. reflow-calls measures the tail past chr(i) at the width of that f-string and passes over the ( of .format(, which the conversion removes, so chr(i) stays inline and the pass settles.

  18. The # prose: skip[reflow-calls] comment holds HANDLERS from reflow-calls, so reflow-collections leaves the dispatch call on one row rather than exploding it into the column "variable" sets. "cache" and "pr" align on the narrower column their own keys set, and "variable" stands unpadded beneath them.

  19. The dispatch call would cross the budget padded out to the column "variable" sets, yet with reflow-calls off reflow-collections leaves the call on one row, because exploding a call belongs to reflow-calls. "cache" and "pr" align on the narrower column their own keys set, and "variable" stands unpadded beneath them.

  20. At a 52-column budget, stacking the expression statement fetch(...).order_by(name).all() inside f sets the receiver fetch( on a row at indent 8, so the argument list reflow-calls explodes lands at indent 12, where the f-string prefer-fstring writes for "%s:%s" % (gamma.get(key).strip().lower(), delta) ends three columns past the budget. stack-method-chains measures the receiver's template from the indent the stack moves it to rather than the one the statement sits at, so the template stays as written and its three-link inner chain breaks under the count trigger in the same pass.

  21. At a 50-column budget, stacking fetch(...).order_by(name).all() inside a parenthesis pair sets the receiver fetch( on a row at indent 4, so the argument list reflow-calls explodes lands at indent 8, where the f-string prefer-fstring writes for "%s:%s" % (gamma.get(key).strip().lower(), delta) ends one column past the budget. stack-method-chains reads whether a template inside the receiver converts from the reflow-calls walk over the receiver where the stack lands it, so the template stays as written and its three-link inner chain breaks under the count trigger in the same pass.

  22. At a 36-column budget, stacking fetch(...).order_by(name).all() explodes fetch( at indent 8, and reflow-collections expands the operand tuple of "%s=%s" % (field.name.strip().lower(), value) at indent 12, where the 26-column field.name.strip().lower() runs two columns past the budget. stack-method-chains measures a chain inside the tuple from the row the tuple's expansion lands it on, so the chain breaks under the width trigger in the same pass rather than on the next.

  23. At a 39-column budget, advise( explodes, and the f-string prefer-fstring writes for "%s:%s" % (gamma.get(key).strip(), delta) reaches the budget exactly on the row its argument lands on. stack-method-chains leaves gamma.get(key).strip() joined inside a template reflow-calls leaves for prefer-fstring, so the chain lands in a replacement field and the template converts in the same pass.

  24. At a 50-column budget, stacking query.filter(...).order_by(name).all() sets query.filter( on a row at indent 4, so the argument list reflow-calls explodes lands at indent 8, where the f-string prefer-fstring writes for "%s:%s" % (gamma.get(key).strip().lower(), delta) ends one column past the budget. stack-method-chains reads whether a template inside a link converts from the reflow-calls walk over that link's arguments where the stack lands them, so the template stays as written and its three-link inner chain breaks under the count trigger in the same pass.

  25. advise(...) runs past the 88-column budget, so reflow-calls puts each argument on its own row one indent step in. From where it sits as written, the joined payload.get("command_name", "").lstrip("/") would overflow as well, but the chain is measured on the argument row it lands on and stays joined rather than stacking inside a parenthesis pair the source never had.

  26. int(...) crosses the 40-column budget, so reflow-calls puts its argument on its own row. The joined _os.uname().release.split(".") fits there, but the [0] after it takes the row past the budget, so the chain is measured with that trailing text counted in and breaks, rather than staying joined and leaving reflow-calls to explode split(".") around its one argument.

  27. explode_me(...) fits the 88-column budget on its own and overflows only while the joined chain trails it, so reflow-calls explodes it only if the chain stays joined. Since the chain sits behind the call rather than inside one of its arguments, it is measured from the column the source writes it at, where it overflows and breaks, and explode_me(...) then fits and keeps its arguments on one row.

  28. alphabetize-siblings sorts the fields of Palette to a, c, stroke_width, and the column align-equals pads each row to is read in that order. Padded to the column stroke_width sets, the c row would reach 85 columns, past the 80-column budget, yet breaking its call open would gain nothing, because a value broken open ends its run and stroke_width would stand alone below it either way. The call stays on one row, and a and c each keep a single space above stroke_width.

  29. The tuple on the left of the = breaks before its closing parenthesis, which the source indents under the tuple's first element. reflow-collections moves that closer to the indent of the row its opener sits on, the statement's own indent. reflow-calls then explodes the type(self).prepare_context(...) call after the closer with its arguments hanging from the statement's indent, because the call is measured from that row rather than from the continuation row the source wrote.

  30. One key of _hash_action carries a stranded space before its closing parenthesis, (False, False, False, True ), and strip-stranded-padding removes it. align-colons pads the run onto one : column, because it measures each row at the width it has once that removal is written, whereas the padded width would have read as a breach of the 40-column budget and the run would have skipped its column over a breach that never happens.

  31. import sys, os and import myapp.core, abc each name two modules in one statement, with myapp in first-party. reflow-imports splits each into one module per line ahead of group-imports and alphabetize-siblings, so every split-off module reaches its own group and its sorted position, and myapp.core ends up in the local section rather than beside the stdlib abc it was joined to.

  32. _handlers carries a trailing comment written #map of ..., tight against the hash, above _handlerList, and normalize-comment-spacing later writes one space after the #. _handlers and _handlerList stay unaligned, because align-equals counts that space before it sets its column and a shared column would carry the _handlers row past the 79-column budget, whereas _ok and _status below pad onto one column as usual.

    1. reflow-collections
    2. align-colons

    The trailing comment on "cache" keeps MUTATIONS as written, since reflow-collections lays out no literal a comment sits inside. The "pr" set therefore never expands, so "pr" stays unpadded, because padding it to the column of either neighbor would carry its row past the budget. With "pr" out of the run, "cache" and "variable" each stand alone, and every : stays flush against its key.

  33. from pkg import a is unread and sits directly under the # Local imports comment, with a blank line between it and from pkg import b. reflow-imports merges the two statements across that blank line, so when prune-inert-imports removes a, from pkg import b moves up onto the line a had, the blank line goes with b's old position, and the comment ends up heading the import that survives.

  34. from .pgen2 import token is the first line under the # Local imports comment, and nothing in the module reads it. reflow-imports merges that line into its .pgen2 sibling, so once it goes the comment heads from .pgen2 import driver. prune-inert-imports moves the sibling onto the vacated line rather than leaving the move to the merge on a later pass, which keeps a real import under the comment at every step.

  35. zebra, foo, and bar_baz sit above a # fmt: off block that contains the hand-laid matrix. band-constants sorts the three and align-equals pads their = into one column, and the matrix between # fmt: off and # fmt: on stays byte for byte as written, because the directives mark a region no rule edits and the run stops at that boundary.

  36. function_nm takes a single parameter whose default is a nested outer_call_name(...) call, and the signature crosses the 66-column budget. reflow-signatures puts the parameter on its own line at an indent of four, and the call in its default explodes from that column rather than from the column it occupied in the source.

    reflow-calls leaves the parameters of a signature that is about to explode alone, so nothing measures them twice, and the inner inner_call_nm call is measured against the exploded row with the buffer align-equals writes around keyword_name = counted in.

  37. The dispatch call fits the budget beside "pr", but padding "pr" out to the column "variable" sets would carry the call three columns past it. reflow-collections measures the call at that padded column and explodes it one argument per row, its closing ) on a row of its own at the entry's indent, so "pr" joins the : column align-colons pads "cache" and "variable" out to.

  38. CONFIG is written as one overlong line with its keys out of order. The dict explodes to one entry per row, the keys sort, and every : pads into one column one space past the widest key, "beta_extended", so even the short "zeta" row takes the full padding to reach that column.

  39. At a 32-column budget, the f-string prefer-fstring writes for "%(name)s: %(value)s" % {...} runs past the budget, so reflow-collections expands the dict. reflow-collections reads the ( of describe( as a bracket a layout rule can break, since the rewrite around it reaches past the dict it lays out, so align-colons pads "name" and describe( explodes in the same pass.

  40. tagdefs writes two spaces after each key's :, and alphabetize-siblings moves "stderr" ahead of "stdout", the comment above "stdout" moving with it. The "stdout" value, last after the sort, stays on one row at exactly the 40-column budget, because reflow-collections measures a value from the plain ": " past its key rather than from the two spaces the source wrote, and the row is written back at that width, whereas the "stderr" value explodes.

  41. The module top carries from __future__ import annotations, three aliased imports out of order, and three blank lines before def add. The directive is removed, collections, numpy, and requests sort, their as keywords pad into one column, and the gap closes to two blank lines before the function.

  42. __all__ lists six entries on one overlong line, three short names interleaved with three thirty-character ones. alphabetize-siblings sorts them so the short names lead, and reflow-collections packs them onto rows within the 88-column budget. The packing measures the widths the sort leaves, not the widths as written, which is why "cc" shares a row with the first long name rather than joining the two short entries above it, and the six entries settle onto three rows of two with no row widening afterward.

  43. Two tuples sit on one row that crosses the 88-column budget, one inside isinstance(...) and one after in. The tuple inside isinstance comes first, so the overflow goes to that call, and the rest of the row counts at its full width rather than only up to the second tuple's (. isinstance( explodes with toklist and (type(None), basestring, list) each on a row of their own, and (None, "", []) stays on one row, where it already fits, the tuple inside isinstance staying packed as well.

  44. The dict passed to self.call_exception_handler lists 'message', 'exception', and 'loop', each with a trailing comma, and the sort moves 'message' last. alphabetize-siblings settles which entry ends the dict, so reflow-collections measures each entry with the separator that order leaves after it, and the 'message' entry, last after the sort, drops its comma, fits its row within the 79-column budget, and the layout settles once the sort is written.

  45. c's row reaches 77 columns on one line, inside the 80-column budget where it stands, but 88 padded out to the column bbbbbbbbbbbb sets. reflow-calls explodes the frobnicate call so its opening row joins that column, because breaking it open leaves the three rows in one column, whereas keeping it on one row would leave c unpadded beneath the other two.

  46. c's row reaches 79 columns on one line, inside the 80-column budget where it stands, but 90 padded out to the column bbbbbbbbbbbb sets. reflow-collections expands the list so its opening row joins that column, because expanding it leaves the three rows in one column, whereas keeping it on one row would leave c unpadded beneath the other two.

  47. The comprehension on a's row is wrapped by hand, and reflow-collections rejoins it onto one row that reaches 77 columns, inside the 80-column budget. Padded to the column positionals sets, the row would reach 87 columns, but the one bracket a layout rule breaks on it, [PARSER, REMAINDER], sits inside the comprehension rather than opening the value. No layout rule breaks the row open to join that column, so it stays at its own column and settles in one pass.

  48. band-constants seats CHECK_DELAY above _openers, and the column align-equals pads each row to is read in that order. Padded to the column CHECK_DELAY sets, the _openers row would reach 42 columns, past the 40-column budget, so reflow-collections expands the dict in the same pass and its opening row joins that column.

  49. type Handler's row reaches 58 columns on one line, inside the 60-column budget where it stands, but 64 padded out to the column type LongAliasName sets. reflow-calls explodes the make_handler call so its opening row joins that column, because breaking it open leaves both rows in one column, whereas keeping it on one row would leave each row a column of its own.

  50. cls's row reaches 65 columns at its own column, past the 44-column budget, so reflow-calls explodes the first call on it, _simple_enum, rather than the (tmp_cls) call that follows, and the row's short opening line joins tmp_cls's column. tmp_cls therefore keeps its call on one row, because breaking it open to join the column body["__module__"] sets would leave as many columns and as many rows unpadded while breaking one more row.

  51. declaration_match's row reaches 55 columns at its own column, past the 40-column budget, so reflow-calls explodes the compile call inside its .match access and the row spans three rows, which ends its = run. close therefore stands in a run of its own and keeps its call on one row, whereas padded to the column declaration_match would set, its row would reach 42 columns and break open for a column that never forms.

  52. Padded to the column xxxxxxxxxxxx sets, the c row would reach 88 columns, past the 80-column budget, and reflow-calls could explode its call to join that column. Breaking it open would only move which row stands unpadded, because a value broken open ends its run and dd would then stand alone below it, just as xxxxxxxxxxxx stands alone while the call stays on one row. The call therefore stays on one row, and c and dd align as a pair beneath xxxxxxxxxxxx.

  53. Padded to the column "aaaaaaaaaaaaaaaa" sets, the "cc" row would cross the budget, so with every value on one row X splits into two columns, "aaaaaaaaaaaaaaaa" and "bbbbbbbb" in one and "cc" and "dd" in the other. reflow-collections explodes the configure call because that leaves the dict in one column rather than two, even though no row stands alone either way.

  54. aa=lambda: g(111111, 2222, 333333) is the last keyword of f, and alphabetize-siblings moves it ahead of bb=1, which gives its row a trailing comma it did not carry while it was last. The g(...) call inside the lambda explodes, because a nested call ending at the end of its row still counts whatever the enclosing row writes after it, and with the align-equals buffer around the = that comma crosses the 40-column budget.

  55. compose arrives exploded, one parameter per line, with a trailing , after verbose: bool. strip-trailing-commas removes that comma and neither rule changes anything else, because the stripped form is the layout reflow-signatures writes itself.

  56. Service declares three fields and three methods, each set out of order. The fields sort and pad both their : and their = into one column each, the methods sort separately so _cleanup leads restart and shutdown, and space-statements writes one blank line between every member and one directly under the class Service: header.

  57. self.loggerMap is assigned a dict with spaces just inside its braces, and m a dict with no space after any :. strip-stranded-padding removes the spaces from the first and writes a space after each : in the second, and reflow-collections measures each literal at the width those edits leave, so the first stays on its row and only the second, now past the 40-column budget, explodes.

  58. LS32 is built through str.format, a call band-constants pins where the source wrote it. The f-string prefer-fstring writes in its place reads HEX and IPV4, so it climbs into the derived sub-band below VARIATIONS, and SUBS, which reads LS32, follows it there. band-constants runs behind prefer-fstring, so the conversion and the banding settle in one pass, whereas the other order leaves band-constants a second move to make.

  59. "{}:{}".format(alpha, beta).order_by(name).first().all() carries four links as written, but prefer-fstring rewrites "{}:{}".format(alpha, beta) as the 17-column f"{alpha}:{beta}" in the same pass. stack-method-chains reads the chain as it stands once that rewrite lands, the f-string as its receiver and .format no longer a link, so the receiver runs wider than max-shift allows and the three remaining links take the full split, which a second pass leaves unchanged.

  60. args's list rejoins onto one row at the width its two f-strings take together, and each call alone would still leave that row past the 79-column budget. prefer-fstring measures each rewrite with every other rewrite on its row in place, so both calls convert in the pass that joins the list, where holding each call to the budget alone would leave the joined row too wide for either.

  61. fontlist's call is wrapped by hand across two rows. Rejoined onto one row it reaches 75 columns, inside the 80-column budget where the row stands, but 82 padded out to the column font_name_title sets. reflow-calls explodes the call rather than rejoining it, so its opening row joins that column, and the keywords it breaks out align on a column of their own.

  62. The first element of the set s is written across two lines as (1, and 2). alphabetize-siblings sorts each element by the text it has once joined onto one row, not by the broken text in the source, so (1, 2) sorts after the long tuple.

    Sorting puts the long tuple first, which gives it a trailing comma, and measuring it with that comma takes it one column past the 40-column budget, so it explodes rather than staying on one row.

  63. window(seen := len(rows), page_size, start, rows) passes a walrus as its first positional, and reflow-calls names it anchor as it explodes the call. A named expression used as a keyword value takes parentheses of its own, so the value opens one column further right than the plain = gap align-equals writes, and counting that column takes the row past the 30-column budget, so the nested len call explodes.

  64. SecureConnection.__init__ calls super(SecureConnection, self).__init__(...) with host, port, and timeout wrapped one per line under the open parenthesis. reflow-calls joins the three onto one row, and shed-super-args then removes SecureConnection and self from the call, leaving super().__init__(host, port, timeout), because the hand-aligned continuation lines are gone by the time the shortened call is measured.

  65. convert names Alias in its annotations while Alias = int is written below it, under from __future__ import annotations. band-constants moves the assignment into the leading band, and prune-inert-imports removes the directive, because it counts the hoisted binding as written ahead of the annotations that name it.

  66. convert names Sequence in its annotations while from collections.abc import Sequence is written below it, the case the directive exists for. band-constants moves the import above the definition, and from __future__ import annotations is removed, because the relocated binding counts as written ahead of the annotations that name it and they resolve without the directive.

  67. At a 50-column budget, the value "%s:%s" % (type(exc), exc) runs "faultString"'s entry past the budget, whereas the f-string prefer-fstring writes for it fits. reflow-collections measures the value at the width of f"{type(exc)}:{exc}", so the entry stays on one row and a second pass finds nothing left to join.

  68. render's docstring starts its summary on the """ line and carries an Args: section. frame-docstrings moves the summary off the opener line, align-colons pads the : after template, context_map, and escape_html into one column, and wrap-docstrings wraps the summary to the docstring budget across two lines.

  69. The % tuple in the TypeError call crosses the 60-column budget, with type(other).__qualname__ as its first element and two closing parentheses stacked at the end of its last row. reflow-collections explodes the tuple to one element per row with its ) on a row of its own, and reflow-calls moves the TypeError closer onto its own row as well. The type(other).__qualname__ call stays on one line, because the explode moves self.__class__.__qualname__ and the two closers off its row, and type(other).__qualname__, alone fits the budget.

  70. StationSchema is a pandera.DataFrameModel whose columns follow the order its fields are declared in, and its header carries # prose: keep. alphabetize-siblings leaves station, name, and latitude in that order, while align-colons and align-equals still pad the : and the = into one column each. expand-docstrings rewrites the docstring to the three-line form and space-statements writes one blank line below it, so every rule but alphabetize-siblings still runs over the kept body.

  71. d is a dict with # prose: keep at the end of its line, written with "b" before "a" and too wide for the 40-column budget. reflow-collections explodes the dict, which moves the marker onto the closing } line, and the marker still keeps the dict out of alphabetize-siblings, so "b" stays ahead of "a" and the entries keep their order and their separators, because the marker applies from the line of either the { or the }.

  72. advise(flist=..., skip=hide) crosses the 42-column budget, so reflow-calls puts each keyword on its own row and align-equals writes a space on either side of each =. self.folders.listdir(a).sort() and the comma closing its row would fit right after a bare flist=, but the chain is measured past the buffered flist = and breaks, because those two columns take the row past the budget.

  73. process_env_var(tokenizer.read().text.replace(".", "_")) passes a method chain as its one positional. stack-method-chains wraps the chain in parentheses and stacks .text.replace(...) under tokenizer.read(), and reflow-calls then writes the argument in keyword form as env_var = .... The parentheses stay around the value, because the value spans rows, and the stacked chain keeps its layout through the = alignment instead of being re-wrapped.

  74. f(bb=1, *args, aa=lambda: g(111111, 222222, 3333)) puts *args between two keywords. A keyword sort run is bounded by a ** unpacking and not by a positional, so *args sits inside one run rather than splitting it in two. reflow-calls measures aa, the keyword the sort would move first, with the comma it would gain, which takes it past the 40-column budget, so its lambda value explodes, and once a value spans rows the sort leaves the group in place, *args staying exactly where it was written.

  75. _filter(flist=os.listdir(self.left_folders), skip=self.hide) crosses the 40-column budget. reflow-calls puts each keyword on its own row, and align-equals writes a buffer around each =, which moves every value two columns right. The nested os.listdir(...) call explodes, because it is measured from that settled column rather than from where it sat before the buffer, and those two columns take it past the budget.

  76. _FORMATS lists FMT_BINARY and FMT_XML in the order alphabetize-siblings leaves them, so the last entry gains no separator, and align-colons pads FMT_XML onto a : column that puts its row exactly at the 45-column budget. The column stays, because reflow-collections measures that row at exactly the budget rather than past it, and the padding is not stripped back.

  77. At a 71-column budget, the f-string prefer-fstring writes for "%s %s" % ([alpha_value, beta_value, gamma_value], describe(first)) reaches the budget exactly. reflow-collections leaves a literal on its row where the reflow-calls walk leaves the template around it for prefer-fstring, so the conversion is the one edit reported and the list stays joined inside the f-string.

  78. non_adjacent.append( (la, lb, 0) ) carries a space after append( and a space before its closing ), and the row crosses the 40-column budget by exactly those two spaces. strip-stranded-padding removes both, and reflow-collections measures the tuple at the width that removal leaves, so the tuple stays on its row, the first space narrowing the column the tuple starts at and the second narrowing the text trailing it.

  79. LIMIT = 3 sits between from pkg import alpha and from pkg import beta, which would ordinarily keep the two apart. band-constants moves the constant below the imports, and reflow-imports merges the two pkg statements onto one line in the same run, because it measures against the order the move leaves rather than the order as written, so no second pass is needed once the constant has moved.

  80. Three methods of Dispatcher sit out of order, and handle_alpha contains a match whose arms each return a single value. The methods sort so handle_alpha leads, align-match-case joins each return onto its case line and pads the : into one column, and space-statements writes one blank line between the methods.

  81. zeta, alpha, and beta arrive out of order, alpha with a two-line docstring and the other two with one-line docstrings. alphabetize-siblings sorts the methods, expand-docstrings rewrites the one-line docstrings of beta and zeta to multi-line form, and frame-docstrings puts every """ on its own line, with space-statements writing one blank line between the methods.

  82. measurer's Optional[...] annotation spans three rows, leaving getattr(...) on a closing row that fits. modernize-annotations rewrites the annotation as Callable[...] | None on the assignment's own row, which then runs past code-line-length. reflow-calls runs behind modernize-annotations, so it explodes the call in the same pass, whereas the other order leaves reflow-calls a second edit to make.

  83. paired lists three entries with uneven spacing before their :, and single lists one entry written "only_key" : 1. align-colons pads paired's : into one column one space past "second", and strip-stranded-padding removes the stray space in single, leaving "only_key": 1, because a lone entry forms no alignment group.

  84. self.tk.call(_flatten((self._w, cmd)) + self._options(cnf)) passes two calls joined by +, and the row crosses the 40-column budget. _flatten( explodes directly, because reflow-calls measures it with the + self._options( that trails it on the same row counted in, whereas measuring the call alone would have left the row for reflow-collections to break at the tuple first and the call would then have exploded on a later pass anyway.

  85. ENDPOINTS is already aligned, the reviewers entry padded out to the column the attempts key sets, when the one-row comments entry is added beneath it. reflow-collections measures the opening row of reviewers at the canonical ": " rather than with the padding it already holds, so that row stays in the run, and the comments list, whose row would cross the budget at the column, expands so comments joins it. The dict comes out the same as it would from input carrying no padding at all.

  86. MUTATIONS sits on one row far past the budget, so reflow-collections writes one entry per row. Each value is measured at the column align-colons shifts it to once those rows align, so the "pr" set, which fits beside its own key but not at that column, expands in the same pass that writes the rows.

  87. PRIMARY overflows its line with values written in the older Optional and Union forms, and target-version = "3.10" allows the | syntax. modernize-annotations rewrites each value to | form first, so every rule downstream measures the shorter text, and the dict then explodes, its keys sort so "delta_long" moves ahead of "gamma", and align-colons pads the : column against the rewritten widths. With nothing left reading either name, from typing import Optional, Union is removed.

  88. d opens with { # note and lists "zz" before "a": 1, "b": 2 packed on one row. The comment on the { row keeps reflow-collections from splitting that row, and a packed row is never reordered, so the dict keeps its source order under alphabetize-siblings. The "zz" entry is measured with the comma it carries as written rather than one a sort would have moved, its value is too wide for the 40-column budget, so that value explodes while "a": 1, "b": 2 stay packed on their shared row.

  89. s is a set packed across three rows with # note at the end of the first. The comment inside the set keeps its order fixed under alphabetize-siblings, so no element moves, and reflow-collections measures each element with the comma it carries as written rather than the one a sort would have left. The one element too wide for the 40-column budget explodes onto rows of its own, and the packed rows around it keep their commas and their places.

    1. align-colons

    The key ( "pp", "qq" ) carries a space inside each parenthesis, and with strip-stranded-padding off nothing removes them. align-colons measures the key at the 14 columns it is written in, so every : in MUTATIONS lands on one column rather than the key's : standing two columns past the others.

  90. The key ( "pp", "qq" ) is 14 columns as written and 12 once strip-stranded-padding drops the spaces inside its parentheses. reflow-collections measures each value at the column that settled width sets, the same column align-colons pads every key out to, so the "pr" set fits the budget there and stays on one row. Measured at the column the written width would set, two columns further right, the set would cross the budget and expand.

  91. Both Attributes: entries of Process run past the docstring budget before anything wraps them. align-colons pads both : to one column past command_message with no line cap of its own, and wrap-docstrings then reflows each description within docstring-line-length and hangs the wrapped lines from that padded column, so both entries reach their final layout in one pass rather than drifting across several.

  92. x's list carries bracket padding strip-stranded-padding removes, and with that padding gone and "%s" % ( alpha, ) converted, the row takes exactly the 19-column budget. prefer-fstring measures the row with the padding already cleared, as the layout rules forecast it, so the template converts in the pass that keeps the list on one row.

  93. dirlist.extend([ ... ]) writes a space just inside each bracket of its list, and strip-stranded-padding reports both. reflow-collections explodes the list, rebuilding the bracket interior and carrying that padding away before the strip is written, and the first member call ends up alone on a row inside the exploded list with no padding ahead of it. The _os.path.expanduser(...) call is measured from that row and explodes, because at that column it crosses the 60-column budget, rather than being measured from the row the source wrote.

  94. type(exc) sits inside ( type(exc), exc ), the operand of a % template prefer-fstring rewrites as f"{type(exc)}:{exc}", and the tuple's padding falls inside the text that rewrite replaces. reflow-calls measures the template at the width of that f-string, whose row reaches the 73-column budget exactly, and leaves the calls inside it unwalked, so type(exc) stays on its row and the template converts in the same pass.

  95. ( type(exc), exc ) carries padding strip-stranded-padding removes, and the f-string prefer-fstring writes replaces that tuple whole. At a 46-column budget the one-row entry reads one column over with each space counted once, so the value hangs after :, where counting the padding twice would rejoin a row the conversion then leaves too wide to take.

  96. _c asserts two calls joined by and, each wrapped in a redundant grouping pair, on a row past the 60-column budget. reflow-calls explodes the first call and puts its closing ) back at the statement's indent, and reflow-parentheses measures the second pair from the column that closer leaves, where it fits, so the pair's interior joins onto that row and both pairs drop, rather than the second staying wrapped for a width the explode has already removed.

  97. total is assigned a % template whose tuple arrives exploded, first and second each on their own line. reflow-collections condenses the tuple onto one line first, and prefer-fstring then measures the conversion against the budget and rewrites "%s and %s" % (first, second) to `f"{first} and {second}", each member inline in the template.

  98. dispatch opens with the assignments x, yz, and qrs above a match whose arms each return one of those bindings. align-equals pads the three = into one column one space past qrs, and align-match-case joins each single return onto its case line with every arm : padded one space past the widest pattern, "alpha".

  99. prefer-fstring rewrites r"%s:%s" % ( type(exc), exc ) as rf"{type(exc)}:{exc}", an edit that starts past the r both strings share and so inside the string token. reflow-calls measures that edit across the whole token it starts in, and the row the f-string lands on reaches the 74-column budget exactly, so type(exc) stays on its row and the template converts in one pass.

  100. specs is assigned a tuple whose last element is tuple(_mapdict_values(statemap)), on a row past the 40-column budget. reflow-calls leaves a literal that reflow-collections is about to explode unwalked, so reflow-collections explodes the outer tuple and lays out the tuple( and _mapdict_values( calls inside it against the rows the explode leaves. Both calls explode at the column they end up at, because at that deeper column neither fits the budget.

  101. The % tuple in warning ends with a self.userCfg[configType].Get(...) call, and exploding the tuple moves that final item four columns right, past the 79-column budget. reflow-collections reshapes the reflow-calls layout of that call against the row it ends up on, so the call explodes in the same run as the move, rather than on a later pass.

  102. sup.__init__(option_strings=[], dest=dest, help=help, metavar=metavar) is written across two rows, and sorting the keywords produces no row wider than the widest already in the source, so alphabetize-siblings reorders them. align-equals then writes a buffer around the = of option_strings, the keyword left alone on its row, and that buffer does not trigger a second width check, because the reorder was already measured against the source's own widest row.

  103. Inside ensure_running, the cmd %= (...) statement spans two rows between fds_to_pass and main_kws["authkey_r"] above it and exe and args below. align-equals builds its runs the same way when it reserves as when it aligns, so the multi-row statement closes the run above it, and exe and args pad onto a narrow column of their own rather than the wider one fds_to_pass and main_kws["authkey_r"] set. reflow-collections reads that same run boundary and joins the tuple within the 80-column budget, rather than exploding it from a column the aligner never uses.

  104. handler_map's return annotation crosses the 50-column budget, and the only collection literal on that row is the [Values, list[str]] list inside Callable. reflow-collections explodes that literal, and reflow-signatures keeps self inline, because it reads the opening row as ending at the literal's bracket rather than exploding a lone parameter to recover width the explode is about to give back.

  105. Bare, external from, and local imports sit scrambled together, with myapp named in first-party. group-imports partitions them into bare, external, and local sections, alphabetize-siblings sorts the names within each section, space-statements writes one blank line between sections, and align-imports pads the import keyword into a column within each section.

  106. y is a four-element set on one line past the 30-column budget. reflow-collections packs the rows at the widths alphabetize-siblings leaves in each slot, not the widths the source wrote, so the rows fill the budget as densely as a list's would and the sort is written without widening any row.

  107. add_exception calls super(Waiter, self).add_exception(future) on a row past the 40-column budget. shed-super-args runs ahead of reflow-calls, so the bare super() is the text the length trigger measures, and the add_exception call explodes on that width rather than on the wider text the shed removes.

  108. render's parameters template, context_map, and escape_html already list in the same order as the docstring's Args: entries. alphabetize-siblings leaves that order as written, align-colons pads the three entries' : into one column, and wrap-docstrings wraps each description with its continuation lines hanging under that column, because Args: entries mirror the signature rather than sorting alphabetically.

  109. store writes ttl : int with a space before the :, and its one-line signature crosses the 40-column budget by exactly that space. strip-stranded-padding removes it, and reflow-signatures measures the signature at the width that removal leaves, so it stays on one line rather than exploding and joining back on a later pass.

  110. The one Args: entry of fetch is written timeout : Maximum seconds to wait before giving up., with a space before its :, and its description runs past the 40-column docstring budget. strip-stranded-padding removes the space, because a lone entry has no sibling column to share, and wrap-docstrings breaks the description at the budget and hangs the continuation under the start of the text as the strip leaves it, one column narrower than the source, so both rules agree on where the continuation sits.

  111. The # prose: skip[align-colons] comment holds MUTATIONS from align-colons, so no : column forms for reflow-collections to measure a value against. The "pr" set stays on one row once the dict expands, because it fits the budget beside its own key, and every : stays flush against its key.

  112. table carries # prose: skip[alphabetize-siblings] on its closing line, listing "bb" before "aa". reflow-collections ordinarily measures each entry with the comma the sort is about to write, and the skip directive turns the sort off, and with it the measurement against the sorted order, so every entry is measured with the comma it already carries, and "bb" counts a trailing comma and breaks after its : rather than being measured as the last entry.

  113. foo({"alpha": 1, "beta": 2, "gamma": 3, "delta": 4}) passes one dict as its only argument. reflow-collections explodes the dict to one entry per row, and the call explodes around the dict, its ( and { on separate rows and its } and ) on separate rows, because an argument that spans rows explodes the call whatever the argument count and however short the one-line form. Inside, alphabetize-siblings sorts the keys so "delta" moves ahead of "gamma", and align-colons pads each : into one column.

  114. The rows of "pr" and "qq" would both cross the budget at the column "variable" sets. Wherever alphabetize-siblings reorders a dict, it sets a blank line on either side of each entry spanning rows once two do, which would leave each expanded entry alone in a run of its own, and a dict already in order like this one holds to the same limit so the layout a reorder leaves stays put on the next run. reflow-collections therefore expands neither, and the two sets stay on their rows, aligned as a pair on the narrower column their own keys set, with "cache" above and "variable" below left unpadded.

  115. alphabetize-siblings rewrites MUTATIONS in sorted order and drops the blank line above "pr", so "pr", "variable", and "xy" share one run. reflow-collections measures the "pr" set at the column "variable" sets in that run and expands it, whereas the blank line as written would have kept "pr" in a narrower run beside "xy", where the set fits on one row.

  116. alphabetize-siblings sorts MUTATIONS, and the "cache" list already spans rows. Expanding the "pr" set as well would leave two entries spanning rows, and a sort that reorders a dict sets a blank line on either side of each once two do, which would put each expanded entry in a run of its own. reflow-collections therefore expands nothing, and since padding "pr" out to the column "cache" sets would carry its row past the budget, every row is left unpadded.

  117. "pr" is the last entry as written, so it carries no comma, and its row lands exactly on the budget at the column "variable" sets. alphabetize-siblings moves it ahead of "variable", where it gains a comma that carries the row one column past the budget. reflow-collections reads the order and the comma the sort leaves, so it expands the "pr" set in the same pass rather than leaving align-colons a row it cannot pad.

  118. screen._drawpoly(...) is a method call whose signature reflow-calls cannot resolve, so it leaves the positionals unnamed and explodes the arguments in source order. Each keyword value is still measured from the column the align-equals buffer puts it at, with the comma that closes its row counted in, and those two together take the outline value past the 40-column budget, so it explodes, while fill and top fit their rows and width = self._outlinewidth, an attribute access with nothing to explode, stays on its row past the budget.

  119. At an 80-column budget, args's comprehension written across two rows overflows one row while its element calls "{}={!r}".format(name, value), whereas the f-string fits. reflow-collections measures the call at the width of f"{name}={value!r}" and rejoins the comprehension in the same pass that converts it.

  120. The **SHARED unpacking stays transparent to the : run, so "cache", "pr", and "variable" share one column, whereas the blank line above "a" starts a run of its own. reflow-collections measures each value at the column align-colons shifts it to within its own run, so the "pr" set expands because its row crosses the budget at the column "variable" sets, while "a" and "bb" align on a narrower column beneath the blank line.

  121. At a 50-column budget, the f-string prefer-fstring writes for "%s -> %s" % (alias, name) fits its row once the comprehension's (alias, name) target breaks open. The reflow-calls walk reads the operand tuple that rewrite replaces as holding its row, so it measures the template through the target's ( and leaves it for prefer-fstring, and reflow-collections expands the target rather than the operand, which lets the template convert in the same pass.

  122. At a 38-column budget, "faultString"'s value wrap("%s" % (f(x),)) holds a template prefer-fstring rewrites as f"{f(x)}" inside the call, so the rewrite covers the argument rather than the whole value. reflow-collections hangs a value beneath its key only where a rewrite covers that value whole, so the entry stays beside "faultString": and wrap( explodes around the f-string, which a second pass leaves unchanged.

  123. retries, timeout_seconds, and window are annotated assignments with trailing comments, and three alignment rules pad the same rows, each checking its own padding against the 72-column budget. Every rule measures the row's settled form rather than the intermediate text the rule before it left, which is what lets the :, the =, and the # all reach their columns with the widest row, retries, still inside the budget.

  124. zebra, alpha, and mango sit out of alphabetical order at module level with no spacing between them. alphabetize-siblings sorts the definitions and space-statements writes two blank lines between each pair of top-level functions.

  125. configure(...) carries a coords keyword whose tuple of three calls is too wide for one line. reflow-collections explodes the tuple and reflow-calls re-indents its elements under the keyword, alphabetize-siblings sorts more, name, and other, and align-equals pads each = into one column. coords keeps its source position, because its tuple calls functions such as compute_alpha_coordinate() and an entry whose value runs code is never moved, and the tuple's elements sit one indent step deeper than the keyword rows with its ) back at the keyword column, as a list or dict argument would.

  126. configure takes five typed parameters on one line, two with defaults. reflow-signatures explodes the signature to one parameter per line, align-colons pads each name to one : column, and align-equals pads the = of mango and delta into one column, while the parameters keep their source order even with alphabetize-siblings in the run, because reordering parameters would change every positional call.

  127. from pkg import a and from pkg import b are both unread, and a is the line the # Local imports comment heads. from pkg import b carries no comment and is removed, and with it gone there is no sibling left for reflow-imports to merge a into, so from pkg import a stays under its comment rather than being removed too, because removing it would leave the comment over nothing.

  128. from pkg import a is never read and is the line the # Local imports comment heads, with X = 1 between it and from pkg import b. band-constants moves X = 1 below the imports, which brings the two pkg statements together, and with them adjacent from pkg import b moves up onto the line a vacates as a is removed, leaving the comment heading a real import rather than a gap.

  129. With strip-stranded-padding off, the padding inside .format( type(exc), exc ) stays in the source and the row carrying it runs past the 73-column budget. reflow-calls measures the str.format() call at the width of the f-string prefer-fstring rewrites it as, whose row reaches the budget exactly, and leaves type(exc) unwalked, so type(exc) keeps its row and the call converts in one pass.

  130. With strip-stranded-padding off, the padding inside ( type(exc), exc ) stays in the source and the row carrying it runs past the 79-column budget. reflow-calls measures the % template at the width of the f-string prefer-fstring rewrites it as rather than at its padded text, and leaves type(exc) unwalked, so the call keeps its row and the template converts in one pass.

  131. At a 68-column budget, the f-string prefer-fstring writes for "%s:%s" % ( type(exc), exc ) still runs the row results.append( opens past the budget, so the call explodes and its dict lands on a row of its own. reflow-calls measures the template again from the column the dict lands at, where the f-string fits, and leaves type(exc) unwalked, so the template converts on that row in the same pass.

  132. Padded to the column "bbbbbbbb" sets, the "a" row would cross the budget, so with every value on one row "a" stands unpadded above a column "bbbbbbbb", "cccccccccccccccccccc", and "ddddddddddddd" share. Expanding the list still leaves the dict in two columns, because "cccccccccccccccccccc" is 19 columns wider than "a", past the max-shift limit of 16. No row stands alone once the list expands, so reflow-collections expands it, "a" aligns with "bbbbbbbb", and the two wider keys align beneath them.

  133. "python_version" would cross the budget padded to the column "platform_python_implementation" sets, and reflow-collections could explode its ".".join( call to join that column. Expanding it would still leave one row unpadded, because "sys_platform" is 18 columns narrower than "platform_python_implementation", past the max-shift limit of 16, just as "platform_python_implementation" stands unpadded while every value stays on one row. The call therefore stays on one row, and "python_version" and "sys_platform" align as a pair beneath the unpadded row.

  134. configure's signature arrives exploded, its four typed parameters carrying defaults at widths from host to timeout_seconds. align-colons pads every name to one : column set by timeout_seconds, and align-equals pads every annotation to one = column set by float, so all four rows read as name, type, default.

  135. The "pr" set in MUTATIONS fits the budget beside its own key, but padding the : of "pr" out to the column "variable" sets would carry the set six columns past it. reflow-collections measures the set at that padded column and expands it, so "pr" joins the column align-colons pads every other key out to rather than standing unpadded and splitting the run in two.

How Composition Resolves ​

Each case runs its listed rules in canonical order, and the sections below cover the common interactions.

Layout Before Alignment ​

reflow-collections runs before align-colons and writes the one-entry-per-line layout the alignment columns are computed from.

Reorder Before Align ​

alphabetize-siblings runs before align-equals and settles the entry order, so the alignment math measures the final column positions rather than the source ones.

Docstring Discipline Before Wrap ​

expand-docstrings and frame-docstrings run before wrap-docstrings and settle the quote placement before the body rewrap measures its budget.

Module Reorder Around a Block Marker ​

band-constants sorts the constants above a # fmt: off block while the lines inside the block stay as written, so both it and align-equals act freely outside the marked region.

zebra, foo, and bar_baz sit above a # fmt: off block that contains the hand-laid matrix. band-constants sorts the three and align-equals pads their = into one column, and the matrix between # fmt: off and # fmt: on stays byte for byte as written, because the directives mark a region no rule edits and the run stops at that boundary.

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

Click any rule chip above for its canonical case. The Pipeline Order reference lists the order the pipeline runs in, the Pipeline primitive covers the runner, and the Rules catalog lists the rest.