Skip to content

Mirror the terminator-comment rule in the JavaScript runner's extents - #5

Merged
samuelduchesne merged 4 commits into
mainfrom
fix-47-runner-extents
Sep 6, 2026
Merged

Mirror the terminator-comment rule in the JavaScript runner's extents#5
samuelduchesne merged 4 commits into
mainfrom
fix-47-runner-extents

Conversation

@samuelduchesne

Copy link
Copy Markdown
Contributor

Companion to idfkit/idfkit-js#48, which closes idfkit/idfkit-js#47.

Both libraries absorb a comment sitting after a statement's terminator on the same line into that statement, rather than leaving it in the gap. Python always did; TypeScript now does too.

This runner infers the extents an edited case excludes from the two texts, and it assumed the old TypeScript behaviour — that the writer copied that comment into the gap. Three edited cases failed against a library that had stopped. The library was right and the runner was wrong; nothing here changes what the corpus asserts.

What changed

Both sides now run to the end of that comment, as the writers do. The written side runs one character further, because writeObject emits the newline that ends its own comment line where the source's statement stops at the comment.

preserve-edit-remove-and-add needed more. When the changed region reaches the last statement, everything after the last statement the two still agree on belongs to it — an object appended at the end, the last statement reformatted, and a removal whose leftover gap the appended object then follows are three shapes of one thing. Counting cannot separate them: removing one object and appending another leaves the same number of statements in the middle on each side. So the boundary is the terminator of the last agreeing statement, the same offset on both sides.

The cost is that the trailing text of a statement the comparison already matched is not compared. That is a statement it agreed on.

The Python runner is untouched

It takes its extents from the concrete syntax tree rather than from the text, so it never made this assumption. Both runners pass at conformance-2026.10: 201 of 211 in TypeScript and 192 in Python, every failure an accepted entry, none stale. 115 Python and 117 JavaScript comparator tests pass, and validate_governance.py is clean.

Both libraries absorb a comment sitting after a statement's terminator on the
same line into that statement rather than leaving it in the gap (idfkit-js#47).
The Python one always did; the TypeScript one now does too.

This runner inferred the extents from the two texts and assumed the old
behaviour, that the writer copied the comment into the gap, so three edited cases
failed against a library that had stopped doing that. The library was right and
the runner was wrong.

Now both sides run to the end of that comment, exactly as the writers do, with
the written side one character further because `writeObject` emits the newline
that ends its own comment line and the source's statement stops at the comment.

One case needed more than that. When the changed region reaches the last
statement, everything after the last statement the two still agree on belongs to
it: an object appended at the end, the last statement reformatted, and a removal
whose leftover gap the appended object then follows are three shapes of the same
thing. Telling them apart by counting does not work, because removing one object
and appending another leaves the same number of statements in the middle on each
side, so the boundary is the terminator of the last agreeing statement instead.

The Python runner is untouched: it takes its extents from the concrete syntax
tree rather than from the text, so it never made this assumption.
@samuelduchesne
samuelduchesne requested review from a team as code owners September 6, 2026 18:10
The Python runner rebuilds the written text to locate the extents an edited case
excludes, and checks its reconstruction against what the library actually wrote.
The writer now reuses an object's own field comments, so a reconstruction that
did not pass the original text built a different object and the self-check
rejected the run, which is what it is for.
The written side was extended one character further than the source, to cover a
newline the writer emitted and the source's statement did not. The writer no
longer emits it: a statement's extent ends at its terminator or at the comment on
that line, on either side, and the break belongs to the gap.

One rule for both sides now, and the gap between two statements is compared
rather than half excluded.
The span reconstruction joined objects with a fixed two newlines. An object at
the end of a file is followed by one, so the last statement's span ran a line
past where the writer actually put it and the self-check rejected runs that were
correct. The node already carries whatever separated it from the next object;
use that, falling back to the blank line only when there is nothing to reuse.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A reformatted object keeps the comment from its terminator line, so a preserving write duplicates it

1 participant