ColonTargets
ColonTargets constructs alignment members at every : context the alignment and singleton rules consume. The distinct contexts in the Python grammar that carry a colon worth aligning are listed below, and the walker that finds them is identical across rules, so the walker lives once in ColonTargets and each consuming rule supplies a receiver that handles the discovered members.
Public Surface
ColonTargets lives at crate/src/primitives/colon_targets.rs and is pub(crate). Two consumers use it today:
At 1.0 the trait promotes to pub, so a downstream can implement a :-context rule of its own.
The Contexts
- Dict items.
{key: value, key: value}literals, where eachkey: valuepair contributes a member. - Annotated assignments. Statements of the form
target: Annotation = defaultin any scope (module, function, or class body), where each annotation colon contributes a member. - Annotated function parameters.
def f(param: T, param: T)signatures, where each annotated parameter contributes a member. - Google-style docstring sections. Every
Args:,Returns:,Raises:, or other Title-case-headed section, where eachname: descriptionentry line contributes a member and each section aligns independently. - Match-arm cases.
match x: case Pattern: ..., where each case's pattern-to-body colon contributes a member.
Each context resolves a ColonMember, pairing the pre-colon alignment member (its width the display-column width of the left-hand side, its gap the whitespace immediately before the colon) with an optional post-colon value_gap an aligned or stripped row rewrites to one space.
Internal Surface
The receiver trait carries the per-context handlers. rule and handle are the required methods, rule naming the consuming rule so the group builders can hold its skip-suppressed rows out of alignment, while match_arms carries a default a consuming rule overrides for match-arm-specific handling:
pub(crate) trait ColonEmitter {
fn handle(&mut self, members: &[ColonMember]);
fn match_arms(&mut self, members: &[ColonMember]) {
self.handle(members);
}
fn rule(&self) -> RuleId;
fn walk(&mut self, source: &Source) where Self: Sized { /* provided */ }
}handle is the catch-all for annotated assignments, docstring entries, dict entries, and parameters. match_arms is split out so a rule can opt out of match-arm alignment by overriding it to a no-op (which is what does, since owns the match-arm context), with its default delegating to handle for any rule that wants the unified callback.
walk(source) is the provided driver across source's module body, recursing into nested classes, functions, matches, and expressions so a single call covers the whole tree. A consuming rule never overrides walk, because calling the provided method is enough to drive the receiver across every relevant context.
match_case(source, case) -> Option<aligner::Member> is exposed pub(crate) alongside the trait for
match_case directly.Build Pattern
A rule implementing ColonEmitter carries a single accumulator (typically Vec<Vec<ColonMember>> for grouped members) and pushes into it from each handler. After walk(source) returns, the accumulator carries every group the rule cares about, and the rule emits Vec<Edit> by calling Aligner's emit_group against each group.
How Grouping Works
Each context defines its own grouping shape, because what counts as "adjacent" inside a dict literal differs from what counts as "adjacent" across class-body statements:
- Dict items group by line-adjacency between one key's end and the next item's start. A trailing comment rides with its entry and a
**spreadentry skips the colon scan, neither breaking the run, so the rest of the dict aligns around them. - Annotated assignments group via
line_adjacent_groupsover each scope's statements, treating any non-target: Tstatement as a divider. - Annotated function parameters group via
parameter_split_groups, splitting at the first parameter that does not qualify (an un-annotated argument, a*argsor**kwargs, a/or*separator). - Match arms group one per
matchstatement, with every arm's colon contributing a member. Patterns may span multiple lines, so the alignment column is per-matchrather than per-line-run. - Docstring sections group one per Google-style section, with the structured-section parser invoked inline to find each section's entries, so a section's entries align without reaching across the section break.
Each group is handed to the receiver as one &[ColonMember] slice, so the consumer aligns within the group without seeing cross-group state. The docstring-args context borrows Docstring's body_docstring to find a body's leading docstring literal, then runs its own line scan for each entry's : position, because the two primitives surface different shapes. Docstring yields entry names with the byte range a reorder carries along, whereas the colon walker yields each line's colon anchor for the aligner's padding math.
Re-Using This Primitive
A new :-context rule implements ColonEmitter, overrides the handlers for the contexts it cares about, and calls walk(source) from inside its apply method. The shared walker, the same-indentation grouping, and the per-context member construction come for free.