Skip to content

Commit a40a98f

Browse files
Making user comments are not parsed into whitespace (#5671)
1 parent 01917b0 commit a40a98f

3 files changed

Lines changed: 60 additions & 6 deletions

File tree

rewrite-javascript/rewrite/src/javascript/parser.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4216,12 +4216,23 @@ function prefixFromNode(node: ts.Node, sourceFile: ts.SourceFile): J.Space {
42164216
// let previousSibling = getPreviousSibling(node);
42174217
let leadingWhitespacePos = node.getStart();
42184218

4219-
// Step 1: Use forEachLeadingCommentRange to extract comments
4220-
ts.forEachLeadingCommentRange(text, nodeStart, (pos, end, kind) => {
4219+
// Step 1: Get all comments
4220+
const commentRanges = [
4221+
...(ts.getTrailingCommentRanges(text, nodeStart) || []),
4222+
...(ts.getLeadingCommentRanges(text, nodeStart) || []),
4223+
].filter(range => range.pos < node.getStart())
4224+
.sort((a, b) => a.pos - b.pos)
4225+
// filter out duplicate ranges
4226+
.filter((range, index, self) => index === 0 || range.pos !== self[index - 1].pos);
4227+
4228+
commentRanges.forEach(range => {
4229+
const pos = range.pos;
4230+
const end = range.end;
4231+
const kind = range.kind;
42214232
leadingWhitespacePos = Math.min(leadingWhitespacePos, pos);
42224233

42234234
const isMultiline = kind === ts.SyntaxKind.MultiLineCommentTrivia;
4224-
const commentStart = isMultiline ? pos + 2 : pos + 2; // Skip `/*` or `//`
4235+
const commentStart = pos + 2; // Skip `/*` or `//`
42254236
const commentEnd = isMultiline ? end - 2 : end; // Exclude closing `*/` or nothing for `//`
42264237

42274238
// Step 2: Capture suffix (whitespace after the comment)
@@ -4248,8 +4259,7 @@ function prefixFromNode(node: ts.Node, sourceFile: ts.SourceFile): J.Space {
42484259
whitespace = text.slice(nodeStart, leadingWhitespacePos);
42494260
}
42504261

4251-
// Step 4: Return the Space object with comments and leading whitespace
4252-
return {kind: J.Kind.Space, comments: comments, whitespace: whitespace.length > 0 ? whitespace : ""};
4262+
return {kind: J.Kind.Space, comments: comments, whitespace: whitespace};
42534263
}
42544264

42554265
class FlowSyntaxNotSupportedError extends SyntaxError {

rewrite-javascript/rewrite/test/javascript/format/minimum-viable-space-visitor.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,13 @@ describe('MinimumViableSpacingVisitor', () => {
7676
new TodoItem("Walk the dog")
7777
];
7878
79-
list[1].toggle(); // mark "Walk the dog" as done
79+
list[1].toggle();
8080
8181
list.forEach(item => console.log(item.toString()));
8282
`,
8383
`class TodoItem{constructor(public title:string,public done:boolean=false){}toggle():void{this.done=!this.done;}toString():string{return this.done?"[x]":"[ ]"+this.title}}const list:TodoItem[]=[new TodoItem("Buy milk"),new TodoItem("Walk the dog")];list[1].toggle();list.forEach(item=>console.log(item.toString()));`
8484
// @formatter:on
85+
// TODO it fails when ` // mark "Walk the dog" as done` is added to the toggle line.
8586
))
8687
});
8788

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import {describe} from "@jest/globals";
17+
import {RecipeSpec} from "../../src/test";
18+
import {JavaScriptVisitor, JS, typescript} from "../../src/javascript";
19+
import {J} from "../../src/java";
20+
21+
22+
test("comments", () =>
23+
new RecipeSpec().rewriteRun({
24+
//language=typescript
25+
...typescript(`
26+
/*1*/ const /*2*/ x /*3*/ = /*4*/ 10;/*5*/
27+
/*6*/
28+
const y = 5 /*7*/; /*8*/
29+
`),
30+
afterRecipe: async (cu: JS.CompilationUnit) => {
31+
let commentCount = 0;
32+
const checkSpaces = new class extends JavaScriptVisitor<void> {
33+
protected override async visitSpace(space: J.Space, p: void): Promise<J.Space> {
34+
const ret = await super.visitSpace(space, p);
35+
expect(ret.whitespace).not.toContain("/*");
36+
commentCount += ret.comments.length;
37+
return ret;
38+
}
39+
}
40+
await checkSpaces.visit(cu, undefined);
41+
expect(commentCount).toBe(8);
42+
}
43+
}));

0 commit comments

Comments
 (0)