Skip to content

stack-adjacent-strings

stack-adjacent-strings breaks a run of implicitly concatenated string literals to one literal per line once the joined line overflows code-line-length, so the seam between one literal and the next falls at a line end rather than wherever the author stopped typing.

A run already inside a bracket pair breaks in place, each later literal written at the indent of the row the run opens on, which is how a call argument, a collection element, and a dict value take the break. A run standing where no bracket encloses it, a return value or an assignment's right side among them, gains the parentheses the continuation needs, its literals one indent step in and the closing ) back at the statement's indent.

emit's call parentheses already enclose the two-literal run, which overflows the code-line-length = 60 the case sets. The run breaks in place, one literal per line inside emit(...), with "and the trailing clause" written at the indent of the row the run opens on, because a run already inside a bracket pair takes no nested pair of its own.

emit(
    "the opening clause of this notice "
    "and the trailing clause"
)
python

The rule only ever breaks a run, so a run already written one literal per line keeps that layout whatever its width, backslash-continued and parenthesized alike. A run spanning several lines with two literals still sharing one is rewritten to one per line whatever the width, because the ragged seam is the defect rather than the line count.

A run keeps its line however wide in each of the following cases:

  1. A run standing as a body's leading expression, because parenthesizing it would leave a docstring that no longer reads as one.
  2. A run with a triple-quoted part that spans lines, because moving that part would shift its opening line and change the interior the source pinned.
  3. A run with a comment anywhere inside the enclosing pair, the comment keeping the run in place.

Bytes runs and runs mixing an f-string or t-string with a plain literal all break the same way, since each is one implicitly concatenated expression. The break falls between the parts and never inside one, so a replacement field keeps its own text as written. A run in a docstring slot and a line no break can bring within budget are both left for line-overflow to report.

Configuration

KeyTypeDefaultMeaning
enabledbooltrueTurns the rule on or off.

The break reads the top-level code-line-length key, and the width is measured from the column the run sits at once align-equals settles the row that carries it.

The Canonical Case

message's run joins two adjacent string literals on one line that overflows code-line-length. The run is wrapped in parentheses and broken to one literal per line, so the seam between "the opening clause of this notice " and "and the trailing clause" falls at a line end.

message = (
    "the opening clause of this notice "
    "and the trailing clause"
)
python

More Examples

payload's two bytes literals concatenate the way adjacent str literals do, and together they overflow the line. The run is wrapped in parentheses and broken to one literal per line, the same break an overflowing str run gets, because a bytes run is one implicitly concatenated expression.

table's single entry has a run as its key rather than its value, and the entry overflows the line. The run breaks at the entry's own indent rather than hanging at the :, so the run breaks between its two literals at the entry's four-space indent, the first literal on the entry's opening row and the second followed by : 1 on the next.

table's single entry has a run as its value, and the entry overflows the line. The run breaks at the indent of the entry's own row rather than at the column the value opens on, so the entry keeps its key: value line whole instead of hanging at the :.

build's return value is a two-literal run with no bracket of its own around it, and it overflows the line. The run is wrapped in parentheses and broken to one literal per line, with the closing ) written back at the statement's indent.

helper(alpha) opens and closes before the run in emit(helper(alpha), ...), so the bracket enclosing the run is emit's own parentheses rather than the nested helper(...) pair that already closed. The run breaks in place, the second literal dropping to the argument row's indent, leaving helper(alpha) and the first literal together on the row where they started.

emit's call parentheses already enclose the two-literal run, which overflows the code-line-length = 60 the case sets. The run breaks in place, one literal per line inside emit(...), with "and the trailing clause" written at the indent of the row the run opens on, because a run already inside a bracket pair takes no nested pair of its own.

The subscript in value = registry[...] never expands onto lines of its own, so the run inside it would otherwise break onto the statement's own row. The run is wrapped in its own parentheses rather than broken in place, because a continuation at the statement's indent would read as the next statement.

label's run mixes an f-string, f"the opening clause {value} of this notice ", with the plain literal "and the trailer", and together they overflow the line. The run breaks between the two parts and leaves the {value} replacement field untouched inside its f-string, because the break falls between parts and never inside one.

message's run spans two lines, with "the opening clause " and "and a middle clause" sharing the first line and a third literal on its own. The whole run is rewritten to one literal per line, because the ragged seam is the defect the rule removes, whatever the line count.

label's two t"..." template-string literals, parsed under the target-version = "3.14" the case sets, concatenate the way str and bytes literals do, and together they overflow the line. The run is wrapped in parentheses and broken to one literal per line, the same break any other over-budget run gets.

No Change

message's run is already written one literal per line, continued across the line break with a trailing backslash rather than parentheses. The backslash continuation stays exactly as written, because the rule keeps any run already at one literal per line, backslash-continued and parenthesized alike.

No Change

The # keep the seam here comment sits inside the parentheses enclosing message's run. The run stays at its source layout, because rewriting the pair would move the comment onto a different line from the one its author put it on.

No Change

message's run is already written one literal per line, "a short clause " then "and another", even though the joined form would fit code-line-length. The run keeps its layout rather than rejoining, because the rule only ever breaks a run and never rejoins one.

No Change

message's run has a triple-quoted part already spanning two lines beside the plain literal "and the trailing clause". The run is left exactly as written, because moving the triple-quoted part would shift its opening line and change the interior the source pinned.

No Change

The two adjacent string literals in handler's body sit in the docstring slot, as the function's first statement. The run is kept on one line however wide it runs, because wrapping it in parentheses would leave a value that no longer reads as a docstring, and the over-budget line is left for line-overflow to report.

No Change

message's run joins two literals on one line that fits code-line-length, "a short clause " "and another". The run is left on one line, because the break reads the budget rather than the mere presence of a concatenation.