From c9f784bc0507c14b1726683c33bf01b5e740bbf1 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 19 Aug 2026 21:42:10 +0200 Subject: [PATCH 1/2] Java: don't treat `/*` inside a line comment as a block comment opener `sortedModifiersAndAnnotations` and `collectAnnotations` scan the source between an annotation and the declaration it precedes to recover modifiers in source order. Their hand-rolled comment scanner sets `inMultilineComment` on `/*` without first checking whether it is already inside a `//` comment, so a line comment containing `/*` latches the flag; nothing clears it short of a literal `*/`. The scan then finds no modifier keywords, so the modifiers never make it into the LST and the declaration's type is printed at the modifier's offset: @Deprecated //*not a block comment public String value() { ... } prints as `String String value()`, and with a type shorter than `public` the leftover characters are emitted as the name's prefix (`Step` over `public` yields `Stepic`). Guard both scanners against re-entering comment state, in all five parser modules. --- .../ReloadableJava11ParserVisitor.java | 4 +- .../ReloadableJava17ParserVisitor.java | 4 +- .../ReloadableJava21ParserVisitor.java | 4 +- .../ReloadableJava25ParserVisitor.java | 4 +- .../java/ReloadableJava8ParserVisitor.java | 4 +- .../openrewrite/java/tree/CommentTest.java | 46 +++++++++++++++++++ 6 files changed, 56 insertions(+), 10 deletions(-) diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java index 4c8b2709c61..97b9d5ca77b 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java @@ -2208,7 +2208,7 @@ private ReloadableJava11ModifierResults sortedModifiersAndAnnotations(ModifiersT continue; } char c = source.charAt(i); - if (c == '/' && source.length() > i + 1) { + if (c == '/' && source.length() > i + 1 && !inComment && !inMultilineComment) { char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; @@ -2333,7 +2333,7 @@ private List collectAnnotations(Map annotat continue; } char c = source.charAt(i); - if (c == '/' && source.length() > i + 1) { + if (c == '/' && source.length() > i + 1 && !inComment && !inMultilineComment) { char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java index e857c8edddc..2db16194d9a 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java @@ -2362,7 +2362,7 @@ private ReloadableJava17ModifierResults sortedModifiersAndAnnotations(ModifiersT continue; } char c = source.charAt(i); - if (c == '/' && source.length() > i + 1) { + if (c == '/' && source.length() > i + 1 && !inComment && !inMultilineComment) { char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; @@ -2485,7 +2485,7 @@ private List collectAnnotations(Map annotat continue; } char c = source.charAt(i); - if (c == '/' && source.length() > i + 1) { + if (c == '/' && source.length() > i + 1 && !inComment && !inMultilineComment) { char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java index cd3892c049a..a32a5baf0d7 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java @@ -2400,7 +2400,7 @@ private ReloadableJava21ModifierResults sortedModifiersAndAnnotations(ModifiersT continue; } char c = source.charAt(i); - if (c == '/' && source.length() > i + 1) { + if (c == '/' && source.length() > i + 1 && !inComment && !inMultilineComment) { char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; @@ -2523,7 +2523,7 @@ private List collectAnnotations(Map annotat continue; } char c = source.charAt(i); - if (c == '/' && source.length() > i + 1) { + if (c == '/' && source.length() > i + 1 && !inComment && !inMultilineComment) { char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; diff --git a/rewrite-java-25/src/main/java/org/openrewrite/java/isolated/ReloadableJava25ParserVisitor.java b/rewrite-java-25/src/main/java/org/openrewrite/java/isolated/ReloadableJava25ParserVisitor.java index 22c3fbe09f3..17ecbb32863 100644 --- a/rewrite-java-25/src/main/java/org/openrewrite/java/isolated/ReloadableJava25ParserVisitor.java +++ b/rewrite-java-25/src/main/java/org/openrewrite/java/isolated/ReloadableJava25ParserVisitor.java @@ -2432,7 +2432,7 @@ private ReloadableJava25ModifierResults sortedModifiersAndAnnotations(ModifiersT continue; } char c = source.charAt(i); - if (c == '/' && source.length() > i + 1) { + if (c == '/' && source.length() > i + 1 && !inComment && !inMultilineComment) { char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; @@ -2555,7 +2555,7 @@ private List collectAnnotations(Map annotat continue; } char c = source.charAt(i); - if (c == '/' && source.length() > i + 1) { + if (c == '/' && source.length() > i + 1 && !inComment && !inMultilineComment) { char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java index 719cb1271ca..1fa9d7c6e48 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java @@ -2190,7 +2190,7 @@ private Java8ModifierResults sortedModifiersAndAnnotations(ModifiersTree modifie continue; } char c = source.charAt(i); - if (c == '/' && source.length() > i + 1) { + if (c == '/' && source.length() > i + 1 && !inComment && !inMultilineComment) { char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; @@ -2315,7 +2315,7 @@ private List collectAnnotations(Map annotat continue; } char c = source.charAt(i); - if (c == '/' && source.length() > i + 1) { + if (c == '/' && source.length() > i + 1 && !inComment && !inMultilineComment) { char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/CommentTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/CommentTest.java index 2cc970f222c..6a5e44c934c 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/CommentTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/CommentTest.java @@ -52,6 +52,52 @@ class Test {// /* ); } + @Test + void multilineOpenerInsideSingleLineCommentBeforeMethodModifier() { + rewriteRun( + java( + """ + class Test { + @Deprecated //*not a block comment + public String value() { + return null; + } + } + """ + ) + ); + } + + @Test + void multilineOpenerInsideSingleLineCommentBeforeMultipleModifiers() { + rewriteRun( + java( + """ + class Test { + @Deprecated //*not a block comment + public static String value() { + return null; + } + } + """ + ) + ); + } + + @Test + void multilineOpenerLaterInSingleLineCommentBeforeFieldModifier() { + rewriteRun( + java( + """ + class Test { + @Deprecated //see /*.java + public int value = 1; + } + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/4995") @Test void trailingComment() { From 611efaf97e3bddb8573801bfca1666b368fa1662 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 19 Aug 2026 22:10:54 +0200 Subject: [PATCH 2/2] Java: `/*/` does not close a block comment in the modifier scanner The close condition in `sortedModifiersAndAnnotations` and `collectAnnotations` only checked that the previous character was `*`, so the third character of `/*/` satisfied it and the scanner left comment state while still inside the comment. It then read the comment body as modifiers and dropped the real ones: @Deprecated /*/ not the end of the comment */ public String value() printed as `... String String value()` -- the same silent corruption as the `//*` case, from the opposite direction. Track where the opener started and require the closing `/` to be at least three characters past it, so the opener's own `/` can never close the comment. This also subsumes the `i > 0` bounds check, which was present only in `collectAnnotations`; `sortedModifiersAndAnnotations` could read `source.charAt(-1)`. Adds a regression test for the inverse case too, `/* // */` before modifiers, which the preceding commit fixes but nothing covered. --- .../ReloadableJava11ParserVisitor.java | 10 +++++-- .../ReloadableJava17ParserVisitor.java | 10 +++++-- .../ReloadableJava21ParserVisitor.java | 10 +++++-- .../ReloadableJava25ParserVisitor.java | 10 +++++-- .../java/ReloadableJava8ParserVisitor.java | 10 +++++-- .../openrewrite/java/tree/CommentTest.java | 30 +++++++++++++++++++ 6 files changed, 70 insertions(+), 10 deletions(-) diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java index 97b9d5ca77b..9e7e360216c 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java @@ -2186,6 +2186,7 @@ private ReloadableJava11ModifierResults sortedModifiersAndAnnotations(ModifiersT boolean afterFirstModifier = false; boolean inComment = false; boolean inMultilineComment = false; + int multilineCommentStart = -1; final AtomicReference word = new AtomicReference<>(""); int afterLastModifierPosition = cursor; int lastAnnotationPosition = cursor; @@ -2212,12 +2213,14 @@ private ReloadableJava11ModifierResults sortedModifiersAndAnnotations(ModifiersT char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; + multilineCommentStart = i; } else if (next == '/') { inComment = true; } } - if (inMultilineComment && c == '/' && source.charAt(i - 1) == '*') { + // The closing `/` cannot be part of the opener, so `/*/` does not terminate a block comment. + if (inMultilineComment && c == '/' && i >= multilineCommentStart + 3 && source.charAt(i - 1) == '*') { inMultilineComment = false; } else if (inComment && (c == '\n' || c == '\r')) { inComment = false; @@ -2321,6 +2324,7 @@ private List collectAnnotations(Map annotat List annotations = new ArrayList<>(); boolean inComment = false; boolean inMultilineComment = false; + int multilineCommentStart = -1; for (int i = cursor; i <= maxAnnotationPosition && i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { JCAnnotation jcAnnotation = annotationPosTable.get(i); @@ -2337,12 +2341,14 @@ private List collectAnnotations(Map annotat char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; + multilineCommentStart = i; } else if (next == '/') { inComment = true; } } - if (inMultilineComment && c == '/' && i > 0 && source.charAt(i - 1) == '*') { + // The closing `/` cannot be part of the opener, so `/*/` does not terminate a block comment. + if (inMultilineComment && c == '/' && i >= multilineCommentStart + 3 && source.charAt(i - 1) == '*') { inMultilineComment = false; } else if (inComment && (c == '\n' || c == '\r')) { inComment = false; diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java index 2db16194d9a..e19f03b3699 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java @@ -2341,6 +2341,7 @@ private ReloadableJava17ModifierResults sortedModifiersAndAnnotations(ModifiersT boolean afterFirstModifier = false; boolean inComment = false; boolean inMultilineComment = false; + int multilineCommentStart = -1; int afterLastModifierPosition = cursor; int lastAnnotationPosition = cursor; @@ -2366,12 +2367,14 @@ private ReloadableJava17ModifierResults sortedModifiersAndAnnotations(ModifiersT char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; + multilineCommentStart = i; } else if (next == '/') { inComment = true; } } - if (inMultilineComment && c == '/' && source.charAt(i - 1) == '*') { + // The closing `/` cannot be part of the opener, so `/*/` does not terminate a block comment. + if (inMultilineComment && c == '/' && i >= multilineCommentStart + 3 && source.charAt(i - 1) == '*') { inMultilineComment = false; } else if (inComment && (c == '\n' || c == '\r')) { inComment = false; @@ -2473,6 +2476,7 @@ private List collectAnnotations(Map annotat List annotations = new ArrayList<>(); boolean inComment = false; boolean inMultilineComment = false; + int multilineCommentStart = -1; for (int i = cursor; i <= maxAnnotationPosition && i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { JCAnnotation jcAnnotation = annotationPosTable.get(i); @@ -2489,12 +2493,14 @@ private List collectAnnotations(Map annotat char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; + multilineCommentStart = i; } else if (next == '/') { inComment = true; } } - if (inMultilineComment && c == '/' && i > 0 && source.charAt(i - 1) == '*') { + // The closing `/` cannot be part of the opener, so `/*/` does not terminate a block comment. + if (inMultilineComment && c == '/' && i >= multilineCommentStart + 3 && source.charAt(i - 1) == '*') { inMultilineComment = false; } else if (inComment && (c == '\n' || c == '\r')) { inComment = false; diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java index a32a5baf0d7..5060e081b66 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java @@ -2379,6 +2379,7 @@ private ReloadableJava21ModifierResults sortedModifiersAndAnnotations(ModifiersT boolean afterFirstModifier = false; boolean inComment = false; boolean inMultilineComment = false; + int multilineCommentStart = -1; int afterLastModifierPosition = cursor; int lastAnnotationPosition = cursor; @@ -2404,12 +2405,14 @@ private ReloadableJava21ModifierResults sortedModifiersAndAnnotations(ModifiersT char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; + multilineCommentStart = i; } else if (next == '/') { inComment = true; } } - if (inMultilineComment && c == '/' && source.charAt(i - 1) == '*') { + // The closing `/` cannot be part of the opener, so `/*/` does not terminate a block comment. + if (inMultilineComment && c == '/' && i >= multilineCommentStart + 3 && source.charAt(i - 1) == '*') { inMultilineComment = false; } else if (inComment && (c == '\n' || c == '\r')) { inComment = false; @@ -2511,6 +2514,7 @@ private List collectAnnotations(Map annotat List annotations = new ArrayList<>(); boolean inComment = false; boolean inMultilineComment = false; + int multilineCommentStart = -1; for (int i = cursor; i <= maxAnnotationPosition && i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { JCAnnotation jcAnnotation = annotationPosTable.get(i); @@ -2527,12 +2531,14 @@ private List collectAnnotations(Map annotat char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; + multilineCommentStart = i; } else if (next == '/') { inComment = true; } } - if (inMultilineComment && c == '/' && i > 0 && source.charAt(i - 1) == '*') { + // The closing `/` cannot be part of the opener, so `/*/` does not terminate a block comment. + if (inMultilineComment && c == '/' && i >= multilineCommentStart + 3 && source.charAt(i - 1) == '*') { inMultilineComment = false; } else if (inComment && (c == '\n' || c == '\r')) { inComment = false; diff --git a/rewrite-java-25/src/main/java/org/openrewrite/java/isolated/ReloadableJava25ParserVisitor.java b/rewrite-java-25/src/main/java/org/openrewrite/java/isolated/ReloadableJava25ParserVisitor.java index 17ecbb32863..1ec18d89c04 100644 --- a/rewrite-java-25/src/main/java/org/openrewrite/java/isolated/ReloadableJava25ParserVisitor.java +++ b/rewrite-java-25/src/main/java/org/openrewrite/java/isolated/ReloadableJava25ParserVisitor.java @@ -2411,6 +2411,7 @@ private ReloadableJava25ModifierResults sortedModifiersAndAnnotations(ModifiersT boolean afterFirstModifier = false; boolean inComment = false; boolean inMultilineComment = false; + int multilineCommentStart = -1; int afterLastModifierPosition = cursor; int lastAnnotationPosition = cursor; @@ -2436,12 +2437,14 @@ private ReloadableJava25ModifierResults sortedModifiersAndAnnotations(ModifiersT char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; + multilineCommentStart = i; } else if (next == '/') { inComment = true; } } - if (inMultilineComment && c == '/' && source.charAt(i - 1) == '*') { + // The closing `/` cannot be part of the opener, so `/*/` does not terminate a block comment. + if (inMultilineComment && c == '/' && i >= multilineCommentStart + 3 && source.charAt(i - 1) == '*') { inMultilineComment = false; } else if (inComment && (c == '\n' || c == '\r')) { inComment = false; @@ -2543,6 +2546,7 @@ private List collectAnnotations(Map annotat List annotations = new ArrayList<>(); boolean inComment = false; boolean inMultilineComment = false; + int multilineCommentStart = -1; for (int i = cursor; i <= maxAnnotationPosition && i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { JCAnnotation jcAnnotation = annotationPosTable.get(i); @@ -2559,12 +2563,14 @@ private List collectAnnotations(Map annotat char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; + multilineCommentStart = i; } else if (next == '/') { inComment = true; } } - if (inMultilineComment && c == '/' && i > 0 && source.charAt(i - 1) == '*') { + // The closing `/` cannot be part of the opener, so `/*/` does not terminate a block comment. + if (inMultilineComment && c == '/' && i >= multilineCommentStart + 3 && source.charAt(i - 1) == '*') { inMultilineComment = false; } else if (inComment && (c == '\n' || c == '\r')) { inComment = false; diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java index 1fa9d7c6e48..1a8cc070079 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java @@ -2168,6 +2168,7 @@ private Java8ModifierResults sortedModifiersAndAnnotations(ModifiersTree modifie boolean afterFirstModifier = false; boolean inComment = false; boolean inMultilineComment = false; + int multilineCommentStart = -1; final AtomicReference word = new AtomicReference<>(""); int afterLastModifierPosition = cursor; int lastAnnotationPosition = cursor; @@ -2194,12 +2195,14 @@ private Java8ModifierResults sortedModifiersAndAnnotations(ModifiersTree modifie char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; + multilineCommentStart = i; } else if (next == '/') { inComment = true; } } - if (inMultilineComment && c == '/' && source.charAt(i - 1) == '*') { + // The closing `/` cannot be part of the opener, so `/*/` does not terminate a block comment. + if (inMultilineComment && c == '/' && i >= multilineCommentStart + 3 && source.charAt(i - 1) == '*') { inMultilineComment = false; } else if (inComment && (c == '\n' || c == '\r')) { inComment = false; @@ -2303,6 +2306,7 @@ private List collectAnnotations(Map annotat List annotations = new ArrayList<>(); boolean inComment = false; boolean inMultilineComment = false; + int multilineCommentStart = -1; for (int i = cursor; i <= maxAnnotationPosition && i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { JCAnnotation jcAnnotation = annotationPosTable.get(i); @@ -2319,12 +2323,14 @@ private List collectAnnotations(Map annotat char next = source.charAt(i + 1); if (next == '*') { inMultilineComment = true; + multilineCommentStart = i; } else if (next == '/') { inComment = true; } } - if (inMultilineComment && c == '/' && i > 0 && source.charAt(i - 1) == '*') { + // The closing `/` cannot be part of the opener, so `/*/` does not terminate a block comment. + if (inMultilineComment && c == '/' && i >= multilineCommentStart + 3 && source.charAt(i - 1) == '*') { inMultilineComment = false; } else if (inComment && (c == '\n' || c == '\r')) { inComment = false; diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/CommentTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/CommentTest.java index 6a5e44c934c..fa9410542fd 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/CommentTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/CommentTest.java @@ -98,6 +98,36 @@ class Test { ); } + @Test + void slashImmediatelyAfterMultilineOpenerBeforeModifier() { + rewriteRun( + java( + """ + class Test { + @Deprecated /*/ not the end of the comment */ public String value() { + return null; + } + } + """ + ) + ); + } + + @Test + void singleLineOpenerInsideMultilineCommentBeforeModifiers() { + rewriteRun( + java( + """ + class Test { + @Deprecated /* // */ public static String value() { + return null; + } + } + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/4995") @Test void trailingComment() {