Skip to content

Range formatting appends a stray token outside the range when the range starts with a comment in an arrow-function body #19795

Closed

Description

@erraitgrand-del

Prettier 3.8.4, Node 22.23.1, parser: "babel", default options.

Range formatting appends a stray token outside the requested range when the range starts with a comment inside an arrow-function body. Two shapes, two different outcomes — one produces code that no longer parses, the other produces valid-but-wrong code.

This is the same symptom as #12964 (closed, fixed for 2.8 by #13173). That issue's own repro no longer reproduces on 3.8.4 — I verified it — but the two shapes below do. So this looks like variants the fix did not cover rather than a regression of the same input.

Reproduction

import * as prettier from "prettier";

// A — arrow passed as a call argument
const A = "beforeEach(() => {\n  // c\n  a();\n});\n";
console.log(await prettier.format(A, { parser: "babel", rangeStart: 19, rangeEnd: 32 }));

// B — arrow assigned to a const
const B = "const f = () => {\n  // c\n  a();\n};\n";
console.log(await prettier.format(B, { parser: "babel", rangeStart: 18, rangeEnd: 31 }));

In both cases the range covers the comment line plus the statement after it.

Actual output

A — the closing }); becomes };);, which is not parseable:

beforeEach(() => {
  // c
  a();
};);

Re-formatting that output fails with Unexpected token, expected "," (4:2).

B — the closing }; becomes };; — an extra empty statement:

const f = () => {
  // c
  a();
};;

This one does parse, which makes it the quieter of the two: a caller that validates range-format output by re-parsing it will accept the corrupted result.

Expected output

Input unchanged in both cases — the code inside the range is already formatted, and nothing outside the range should be touched.

Notes on the trigger

  • The comment must be the first line of the range. Moving it later in the range, or shrinking the range to the comment alone, avoids the bug.
  • A plain function f() { … } declaration is unaffected; only arrow-function bodies reproduce.
  • Both shapes reproduce with default options and with printWidth: 100, tabWidth: 2, semi: true, singleQuote: false, trailingComma: "all".

Why this bites

We use range formatting in a CI gate that checks whether the lines changed by a PR are formatted. Because the stray token always lands outside the requested range, the gate compared the source against corrupted output and reported correctly formatted code as unformatted — with nothing the author could fix. Worse, the obvious remedy (write the range-format output back to the file) corrupts the source; that happened to us once and was caught by a type check rather than by the formatter.

We have since worked around it by comparing only the lines the author actually changed, so this is not urgent for us — but the "valid but wrong" shape (B) seems worth fixing regardless, since it survives an output-parses-fine sanity check.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions