Skip to content

JavaScript: spacing owns the operators TypeScript adds to the Java model - #8797

Open
MBoegers wants to merge 1 commit into
mainfrom
3071
Open

MBoegers wants to merge 1 commit into
mainfrom
3071

Conversation

@MBoegers

@MBoegers MBoegers commented Sep 7, 2026

Copy link
Copy Markdown
Contributor
  • autoFormat normalizes the spacing around every operator the Java model already had and leaves every operator TypeScript added alone. x == y comes back as x == y; x === y comes back untouched. The aroundOperators settings that name equality, relational and logical do nothing for those operators in either direction — with equality: false, x == y collapses to x==y while x === y is left as written. Measured on main at ee6e1f0718.
Operator LST x ⋯ y after autoFormat before this change
=== !== JS.Binary x === y
in JS.Binary "k" in o
?? JS.Binary x ?? y
, JS.Binary x , y
instanceof J.InstanceOf x instanceof Y
as JS.As x as any
satisfies JS.SatisfiesExpression obj satisfies Shape

The mechanism

SpacesVisitor implements visitBinary and nothing dispatches on the four kinds beside it. The parser decides which one you get: ===, !==, in, ?? and the comma operator are JS.Binary, while instanceof, as and satisfies have kinds of their own. visitBinary's switch ends in default: throw new Error("Unsupported operator type " + ...), so it was written to be reachable only from J.Binary$Type — the JS half was never a gap someone left open, it was never in scope.

Four overrides close it. The punctuators read the same style fields visitBinary already reads.

Why the word operators take no setting

in, instanceof, as and satisfies normalize to exactly one space and consult nothing. MinimumViableSpacingVisitor sits at index 1 of the AutoformatVisitor pipeline and guarantees their separator, but its ensureSpace only fills whitespace that is empty — it never collapses x as any. SpacesVisitor sits at index 4, so a setting honoured here would undo that guarantee with nothing left to repair it, and print "k"ino. SpacesVisitor is also constructed directly by recipes that want spacing without a full autoformat, so it cannot assume the earlier visitor ran at all.

That leaves it one job on those operators, which is turning two spaces into one. WORD_OPERATOR_KEEPS_ITS_SEPARATOR names the constant so the next reader doesn't wire the setting in.

One deliberate difference from visitBinary

The new switch's default arm returns the node unchanged where visitBinary throws. An operator kind added later should cost a line of unnormalized spacing, not an exception in the middle of formatting a file. Worth a second opinion if you'd rather the pair be identical.

Derived styles reach this the same way they reach ==

spacesFrom overrides within.objectLiteralBraces and within.es6ImportExportBraces from bracketSpacing and spreads the fallback for the rest, and SpacesStatistics.getSpacesStyle samples indentation and the three brace cases only. Neither derives aroundOperators, so === now resolves to the same value == already resolved to, from the same field. A project with a Prettier configuration reprints through applyPrettierFormatting and returns before any of this runs.

Tests

Four in spaces-visitor.test.ts: the normalization above; the settings being consulted, with equality and logical off collapsing ==, ===, !==, && and ??; the word operators keeping their separator with equality, relational and logical all off; and a line break around ?? surviving. Each was confirmed to fail without the corresponding override.

Scope

The keywords MinimumViableSpacingVisitor also protects — typeof, void, delete, await, yield, keyof, is — are prefix forms that no aroundOperators setting names, so normalizing their spacing is a separate question rather than part of this defect.

Typecheck, build and the whole of test/javascript are green: 156 files, 1916 passed, 21 skipped, nothing re-baselined. The diff adds 162 lines and changes none.

SpacesVisitor implements visitBinary and nothing dispatches on JS.Binary,
J.InstanceOf, JS.As or JS.SatisfiesExpression, so `===`, `!==`, `in`, `??`,
the comma operator, `instanceof`, `as` and `satisfies` keep whatever spacing
the source had, and the aroundOperators settings that name them do nothing.

The punctuators take their setting the way the Java-model operators already
do. The word operators take none: MinimumViableSpacingVisitor guarantees
their separator earlier in the pipeline and this visitor runs after it, so
honouring `relational: false` here would print `"k"ino`. Normalizing two
spaces to one is what is left for it to do.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

1 participant