Skip to content

align-equals

A run of consecutive bindings sits at the same indentation, the eye walks down the page, and every = sign lands at a different column. The reader stops at each line to find where the assignment splits.

gathers those runs into a single column, so a stretch of bindings reads as a list of values rather than a stack of expressions.

The rule walks consecutive single-target assignments at the same indentation level, picking up type annotations when present and folding augmented assignments (+=, |=) into the run with the operator one column before the shared =, while walrus operators (:=) stay non-members. Every aligned row reads as name = value, the name side padded to its column and the value side collapsed to one space after the operator. The same alignment also runs across consecutive annotated function-parameter default values, so a signature with several param: type = default entries aligns its = column the same way a stretch of module-level bindings does. It reaches the keyword arguments that sit alone on their line in an exploded call, so a call written one keyword per line at a shared column aligns its name = value column the way a signature aligns its defaults. A keyword that sits alone on its line but shares no column still takes the one-space buffer, whereas a keyword condensed onto a line with another argument keeps its tight name=value form the way PEP 8 writes a call-site keyword. A positional argument, a ** unpacking, an interior comment, or a condensed keyword ends the run, whereas a multi-line value or default joins its run and then closes it, so the entries past it align as a separate group. A blank line, a comment line, or a non-assignment statement resets the group, leaving each contiguous run aligned in isolation. Once an alignment group lands,

prunes any one-member residue so a lone binding reads as plain code.

Configuration

KeyTypeDefaultMeaning
enabledbooltrueToggles the rule on or off.
max-shiftpositive int | 0 | false16The width-spread budget a contiguous run may shift to reach the shared column. A positive N caps the spread, 0 forbids any shift so every row sits flush, and false lifts the cap so a contiguous run folds into one column. To hold one row out of an otherwise-aligned group, mark it with # prose: skip.

max-shift bounds how far a row may shift to align. The rule walks each run of assignments in source order and grows a column while its width spread stays within the cap, breaking a fresh column at the first row that would exceed it. A max-shift of false lifts the cap so a contiguous run folds into one column, and 0 forbids any shift so every = sits flush. The per-rule facets reference covers the full semantics.

The Canonical Case

Three consecutive bindings with varying left-hand widths align on the = sign. The eye drops down the column of equals signs and reads the right-hand sides as a parallel list.

x       = 1
foo     = 2
bar_baz = 3
python

More Examples

An adjacent run of assignments written with no space after = pads each name to the shared column and inserts the one-space value-side buffer, so every row reads as name = value.

The value-side rewrite stops at the value's parenthesis-inclusive start, so a wrapped value keeps its ( rather than surrendering it to the gap.

A leading positional argument ends the run the way an undefaulted parameter does, leaving only the trailing keyword block to align on its own column.

A wide assignment target overshoots max-shift against the rows that read before it, so it breaks the run and keeps its one-space gap. The rows on each side align their = in their own column.

A call written one keyword per line aligns its = the same way a multi-line signature aligns its defaults, with each row padded between the keyword name and the =.

A keyword argument whose value is wrapped in parentheses keeps them through the value-side normalization, since the gap rewrite stops before the (.

A comment between two keywords closes the active run, so the keywords on each side of it align as two independent columns.

q's width sits well within max-shift of the run, but padding its = to the widest name would carry its long value past code-line-length. q partitions out unpadded while the two narrower rows align beneath the cap, so the alignment holds without any line crossing the budget.

An augmented assignment like beta += 2 aligns its = on the shared column, so the + sits one column before the equals shared by the neighboring plain bindings.

A blank line between two runs splits them into independent groups, so each side settles on its own = column rather than aligning across the gap.

A comment line between two runs splits them into independent groups, mirroring the blank-line boundary, so each side lands on its own = column.

Annotated parameter defaults align across a multi-line signature. The = column sits one space past the widest annotation, with shorter rows padded between the annotation and =.

A keyword alone on its line takes the = buffer while keywords condensed onto one line keep their tight name=value, so a partly-exploded call buffers only the rows that stand on their own.

An annotated assignment carrying an initializer like beta: int = 2 joins the run, its effective left-hand width extending through the annotation so its = lands on the shared column with the plain bindings alongside.

No Change

A run whose = already share a column is idempotent under the rule, so no edit fires and the group passes through unchanged.

No Change

A chained assignment like a = b = 1 carries two = tokens, so the rule skips it rather than guess which one to align, while the surrounding single-target binding aligns around it.

No Change

A parameter whose annotation spans multiple lines breaks the parameter run. The surrounding parameters have widths that would align if grouped, yet they stay unpadded as singletons.