Skip to content

Conditions that exceed max-line-length by only a few columns get backslash + attribute-dot wrapping instead of the parenthesized and-wrap used for larger overflows #270

Description

@minami110

Impact

The wrapper has two strategies for an overlong if condition. When the line exceeds max-line-length by a lot, it produces the style-guide form: parenthesize the condition and break before and/or. But when the line exceeds the limit by only a few columns, it instead emits backslash continuations split at attribute-access dots — fragments like .Kind \ / .RECALIBRATED: — which goes against the style guide twice (parentheses are favored over backslashes, and and/or should start continuation lines, not attribute dots).

The trigger is the amount of overflow, not any particular limit value, so any codebase accumulates these sporadically: lines that almost fit are common, and the author cannot see why one condition wraps nicely while a nearly identical one turns into dot fragments.

Minimal repro

repro.gd (the if line is 104 columns — just over the default limit):

func resolve(ctx: Node) -> bool:
	if ctx.get_loc() == _LOC_TERMINAL and _tel.get_activity() == MachineState.Kind.RECALIBRATED_FOR_UPKEEP:
		return true
	return false
gdscript-formatter repro.gd

(default settings — no flags, no .editorconfig)

Actual output

func resolve(ctx: Node) -> bool:
	if ctx.get_loc() == _LOC_TERMINAL and _tel.get_activity() == MachineState \
			.Kind \
			.RECALIBRATED_FOR_UPKEEP:
		return true
	return false

Expected

The parenthesized and-wrap that the formatter itself produces once the same condition is a bit longer (e.g. with a 28-character enum member instead of 23, same file, same default settings):

func resolve(ctx: Node) -> bool:
	if (
		ctx.get_loc() == _LOC_TERMINAL
		and _tel.get_activity() == MachineState.Kind.RECALIBRATED_FOR_MAINTENANCE
	):
		return true
	return false

This matches the official style guide ("Format multiline statements for readability"): parentheses are favored over backslashes, and and/or go at the beginning of continuation lines.

Boundary (verified individually on 0.21.0)

Sweeping --max-line-length against fixed inputs shows the strategy flips on the overflow amount, independent of the limit value:

  • 126-column condition: limits 100–118 → parenthesized and-wrap; limits 120–126 (overflow ≤ ~7) → backslash + dot splits; limits ≥ 128 → left on one line
  • 104-column condition: limits 100–102 → parenthesized and-wrap; limits 104–110 (small overflow) → backslash + dot splits; and, as above, the default limit is affected too

Additional observations:

  • An input already wrapped in the parenthesized form, formatted at a limit that puts it in the small-overflow window when re-joined → re-joined past the limit and re-split at the dots inside the parentheses (no backslashes, same dot fragments) — pre-formatting to the good shape does not protect against this
  • The identical pattern also occurs with a.b().size()-style call chains (split the same way at the dots)
  • An input already in the parenthesized form whose lines all fit → fully idempotent (left unchanged)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions