mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-15 14:49:36 +00:00
fix(parser): classify Unicode line breaks in block comments (#26536)
Block comments containing U+2028 or U+2029 were marked `SingleLineBlock` unless they also contained LF or CR. Switch to the multiline-comment scanner after either Unicode separator so downstream consumers receive the correct comment kind. This prevents the formatter from dropping parentheses around a return argument with such a leading comment, which could change the returned value to `undefined`.
This commit is contained in:
1 parent
75cd919ffb
commit
d7713ad4cb
3 files changed
+45
-5
No files matched your search
@@ -1011,3 +1011,20 @@ fn test_comment_on_paren_protected_prologue_boundary() {
|
||||
test_same("// leading comment\n(\"use strict\");\nfoo();\n");
|
||||
test_same("\"use asm\";\n// leading comment\n(\"use strict\");\nfoo();\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_comment_line_terminators() {
|
||||
for separator in ["\u{2028}", "\u{2029}", "\n", "\r", "\r\n"] {
|
||||
let source = format!("function f(){{return (/* first{separator}second */ {{}});}}");
|
||||
test(&source, "function f() {\n\treturn (/* first\n\tsecond */ {});\n}\n");
|
||||
test_idempotency(&source);
|
||||
}
|
||||
for separator in ["", "\u{2027}", "\u{202a}", "\u{00a0}"] {
|
||||
let source = format!("function f(){{return (/* first{separator}second */ {{}});}}");
|
||||
test(
|
||||
&source,
|
||||
&format!("function f() {{\n\treturn (/* first{separator}second */ {{}});\n}}\n"),
|
||||
);
|
||||
test_idempotency(&source);
|
||||
}
|
||||
}
|
||||
@@ -82,9 +82,8 @@ impl<'a, C: Config> Lexer<'a, C> {
|
||||
|
||||
/// Section 12.4 Multi Line Comment
|
||||
pub(super) fn skip_multi_line_comment(&mut self) -> Kind {
|
||||
// If `is_on_new_line` is already set, go directly to faster search which only looks for `*/`
|
||||
// We need to identify if comment contains line breaks or not
|
||||
// (`CommentKind::Block` or `CommentKind::MultilineBlock`).
|
||||
// (`CommentKind::SingleLineBlock` or `CommentKind::MultiLineBlock`).
|
||||
// So we have to use the loop below for the first line of the comment even if
|
||||
// `Token`'s `is_on_new_line` flag is already set.
|
||||
// If the loop finds a line break before end of the comment, we then switch to
|
||||
@@ -126,10 +125,11 @@ impl<'a, C: Config> Lexer<'a, C> {
|
||||
if matches!(next2, LS_BYTES_2_AND_3 | PS_BYTES_2_AND_3) {
|
||||
// Irregular line break
|
||||
self.token.set_is_on_new_line(true);
|
||||
// Ideally we'd go on to `skip_multi_line_comment_after_line_break` here,
|
||||
// but irregular line breaks are rare anyway.
|
||||
// SAFETY: Consuming this 3-byte UTF-8 char leaves a UTF-8 char boundary.
|
||||
let after_line_break = unsafe { pos.add(3) };
|
||||
return self.skip_multi_line_comment_after_line_break(after_line_break);
|
||||
}
|
||||
// Either way, continue searching.
|
||||
// Some other Unicode char beginning with `0xE2`.
|
||||
// Skip 3 bytes (macro skips 1 already, so skip 2 here), and continue searching.
|
||||
// SAFETY: `0xE2` is always 1st byte of a 3-byte UTF-8 char,
|
||||
// so consuming 3 bytes will place `pos` on next UTF-8 char boundary.
|
||||
|
||||
@@ -1100,6 +1100,29 @@ function bar() {}";
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_comment_line_terminators() {
|
||||
for (separator, kind) in [
|
||||
("\u{2028}", CommentKind::MultiLineBlock),
|
||||
("\u{2029}", CommentKind::MultiLineBlock),
|
||||
("\n", CommentKind::MultiLineBlock),
|
||||
("\r", CommentKind::MultiLineBlock),
|
||||
("\r\n", CommentKind::MultiLineBlock),
|
||||
("", CommentKind::SingleLineBlock),
|
||||
("\u{2027}", CommentKind::SingleLineBlock),
|
||||
("\u{202a}", CommentKind::SingleLineBlock),
|
||||
("\u{00a0}", CommentKind::SingleLineBlock),
|
||||
] {
|
||||
// A newline before the comment must not affect its kind.
|
||||
for prefix in ["", "let a;", "let a;\n"] {
|
||||
let source = format!("{prefix}/* first{separator}second */ let b;");
|
||||
let comments = get_comments(&source);
|
||||
assert_eq!(comments.len(), 1, "{source:?}");
|
||||
assert_eq!(comments[0].kind, kind, "{source:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_parsing() {
|
||||
let data = [
|
||||
|
||||
Reference in new issue
Block a user