diff --git a/crates/oxc_linter/src/disable_directives.rs b/crates/oxc_linter/src/disable_directives.rs index 78fd65e31..e14ab2c28 100644 --- a/crates/oxc_linter/src/disable_directives.rs +++ b/crates/oxc_linter/src/disable_directives.rs @@ -113,7 +113,7 @@ impl<'a, 'b> DisableDirectivesBuilder<'a, 'b> { // Get the span up to the next new line let stop = self.source_text[span.end as usize..] .lines() - .take(if comment.is_single_line() { 1 } else { 2 }) + .take(2) .fold(span.end, |acc, line| acc + line.len() as u32); if text.trim().is_empty() { self.add_interval(span.end, stop, DisabledRule::All); diff --git a/crates/oxc_linter/src/snapshots/ban_ts_comment.snap b/crates/oxc_linter/src/snapshots/ban_ts_comment.snap index 74697d845..d04f3e0c2 100644 --- a/crates/oxc_linter/src/snapshots/ban_ts_comment.snap +++ b/crates/oxc_linter/src/snapshots/ban_ts_comment.snap @@ -45,9 +45,8 @@ expression: ban_ts_comment ╭─[ban_ts_comment.tsx:2:1] 2 │ if (false) { 3 │ // @ts-expect-error: Unreachable code error - · ────────────────────────────────────────── + · ───────────────────────────────────────── 4 │ console.log('hello'); - 5 │ } ╰──── ⚠ Include a description after the @ts-expect-error directive to explain why the @ts-expect-error is necessary. The description must be 3 characters or longer. @@ -123,9 +122,8 @@ expression: ban_ts_comment ╭─[ban_ts_comment.tsx:2:1] 2 │ if (false) { 3 │ // @ts-ignore: Unreachable code error - · ──────────────────────────────────── + · ─────────────────────────────────── 4 │ console.log('hello'); - 5 │ } ╰──── ⚠ Include a description after the @ts-ignore directive to explain why the @ts-ignore is necessary. The description must be 3 characters or longer. @@ -201,9 +199,8 @@ expression: ban_ts_comment ╭─[ban_ts_comment.tsx:2:1] 2 │ if (false) { 3 │ // @ts-nocheck: Unreachable code error - · ───────────────────────────────────── + · ──────────────────────────────────── 4 │ console.log('hello'); - 5 │ } ╰──── ⚠ Include a description after the @ts-nocheck directive to explain why the @ts-nocheck is necessary. The description must be 3 characters or longer. @@ -267,9 +264,8 @@ expression: ban_ts_comment ╭─[ban_ts_comment.tsx:2:1] 2 │ if (false) { 3 │ // @ts-check: Unreachable code error - · ─────────────────────────────────── + · ────────────────────────────────── 4 │ console.log('hello'); - 5 │ } ╰──── ⚠ Include a description after the @ts-check directive to explain why the @ts-check is necessary. The description must be 3 characters or longer. diff --git a/crates/oxc_linter/src/snapshots/no_abusive_eslint_disable.snap b/crates/oxc_linter/src/snapshots/no_abusive_eslint_disable.snap index 3c33afcc4..746672ef0 100644 --- a/crates/oxc_linter/src/snapshots/no_abusive_eslint_disable.snap +++ b/crates/oxc_linter/src/snapshots/no_abusive_eslint_disable.snap @@ -1,15 +1,13 @@ --- source: crates/oxc_linter/src/tester.rs -assertion_line: 105 expression: no_abusive_eslint_disable --- ⚠ eslint-plugin-unicorn(no-abusive-eslint-disable): Unexpected `eslint-disable` comment that does not specify any rules to disable. ╭─[no_abusive_eslint_disable.tsx:1:1] 1 │ 2 │ // eslint-disable-next-line @scopewithoutplugin - · ────────────────────────────────────────────── + · ───────────────────────────────────────────── 3 │ eval(); - 4 │ ╰──── help: Specify the rules you want to disable. @@ -24,7 +22,7 @@ expression: no_abusive_eslint_disable ╭─[no_abusive_eslint_disable.tsx:2:1] 2 │ foo(); 3 │ eval(); // eslint-disable-line - · ───────────────────── + · ──────────────────── 4 │ ╰──── help: Specify the rules you want to disable. @@ -58,9 +56,8 @@ expression: no_abusive_eslint_disable ╭─[no_abusive_eslint_disable.tsx:1:1] 1 │ 2 │ // eslint-disable-next-line - · ────────────────────────── + · ───────────────────────── 3 │ eval(); - 4 │ ╰──── help: Specify the rules you want to disable. diff --git a/crates/oxc_linter/src/snapshots/no_commented_out_tests.snap b/crates/oxc_linter/src/snapshots/no_commented_out_tests.snap index d9cd090f9..05f37260d 100644 --- a/crates/oxc_linter/src/snapshots/no_commented_out_tests.snap +++ b/crates/oxc_linter/src/snapshots/no_commented_out_tests.snap @@ -104,9 +104,8 @@ expression: no_commented_out_tests ╭─[no_commented_out_tests.tsx:1:1] 1 │ 2 │ // test( - · ─────── + · ────── 3 │ // "foo", function () {} - 4 │ // ) ╰──── help: Remove or uncomment this comment diff --git a/crates/oxc_parser/src/lexer/mod.rs b/crates/oxc_parser/src/lexer/mod.rs index ff347a416..cc7c4be3c 100644 --- a/crates/oxc_parser/src/lexer/mod.rs +++ b/crates/oxc_parser/src/lexer/mod.rs @@ -375,14 +375,19 @@ impl<'a> Lexer<'a> { } /// Section 12.4 Single Line Comment + #[allow(clippy::cast_possible_truncation)] fn skip_single_line_comment(&mut self) -> Kind { - while let Some(c) = self.current.chars.next().as_ref() { - if is_line_terminator(*c) { - break; + let start = self.current.token.start; + while let Some(c) = self.current.chars.next() { + if is_line_terminator(c) { + self.current.token.is_on_new_line = true; + self.trivia_builder + .add_single_line_comment(start, self.offset() - c.len_utf8() as u32); + return Kind::Comment; } } - self.current.token.is_on_new_line = true; - self.trivia_builder.add_single_line_comment(self.current.token.start, self.offset()); + // EOF + self.trivia_builder.add_single_line_comment(start, self.offset()); Kind::Comment } diff --git a/crates/oxc_prettier/src/comment.rs b/crates/oxc_prettier/src/comment.rs index 515bd7922..9b1f94a9f 100644 --- a/crates/oxc_prettier/src/comment.rs +++ b/crates/oxc_prettier/src/comment.rs @@ -1,7 +1,7 @@ -#![allow(non_upper_case_globals)] - //! Comment helpers +#![allow(non_upper_case_globals)] + use bitflags::bitflags; use oxc_ast::CommentKind; @@ -9,7 +9,7 @@ use oxc_span::Span; use crate::{ doc::{Doc, Separator}, - hardline, indent, Prettier, + hardline, indent, line, ss, Prettier, }; bitflags! { @@ -50,11 +50,28 @@ impl<'a> Prettier<'a> { while let Some((start, end, kind)) = self.trivias.peek().copied() { // Comment before the span if end <= range.start { + self.trivias.next(); + parts.push(self.print_comment(start, end, kind)); if kind.is_multi_line() { + let line_break = if self.has_newline(end) { + if self.has_newline(start) { + hardline!() + } else { + line!() + } + } else { + ss!(" ") + }; + parts.push(line_break); + } else { + parts.push(hardline!()); + } + + let end = self.get_comment_end(kind, end); + if self.skip_newline(self.skip_spaces(end)).is_some_and(|i| self.has_newline(i)) { parts.push(hardline!()); } - self.trivias.next(); } else { break; } @@ -96,4 +113,13 @@ impl<'a> Prettier<'a> { let comment = Span::new(start - 2, end + end_offset).source_text(self.source_text); Doc::Str(comment) } + + #[allow(clippy::cast_possible_truncation)] + fn get_comment_end(&self, kind: CommentKind, end: u32) -> Option { + if kind.is_single_line() { + self.source_text[end as usize..].chars().next().map(|c| end + c.len_utf8() as u32) + } else { + Some(end) + } + } } diff --git a/crates/oxc_prettier/src/lib.rs b/crates/oxc_prettier/src/lib.rs index 156790050..81ade9e08 100644 --- a/crates/oxc_prettier/src/lib.rs +++ b/crates/oxc_prettier/src/lib.rs @@ -15,6 +15,7 @@ use std::{iter::Peekable, vec}; use oxc_allocator::Allocator; use oxc_ast::{ast::Program, AstKind, CommentKind, Trivias}; +use oxc_syntax::identifier::is_line_terminator; use crate::{doc::Doc, format::Format, printer::Printer}; @@ -107,4 +108,29 @@ impl<'a> Prettier<'a> { fn is_next_line_empty(&self, end: u32) -> bool { self.source_text[end as usize..].chars().nth(1).is_some_and(|c| c == '\n') } + + #[allow(clippy::cast_possible_truncation)] + fn skip_newline(&self, start_index: Option) -> Option { + let start_index = start_index?; + let c = self.source_text[start_index as usize..].chars().next()?; + is_line_terminator(c).then(|| start_index + c.len_utf8() as u32) + } + + fn skip_spaces(&self, start_index: Option) -> Option { + let mut start_index = start_index?; + for c in self.source_text[start_index as usize..].chars() { + if matches!(c, ' ' | '\t') { + start_index += 1; + } else { + break; + } + } + Some(start_index) + } + + fn has_newline(&self, start_index: u32) -> bool { + let idx = self.skip_spaces(Some(start_index)); + let idx2 = self.skip_newline(idx); + idx != idx2 + } } diff --git a/tasks/prettier_conformance/prettier.snap.md b/tasks/prettier_conformance/prettier.snap.md index 22e2da6f7..256f88f81 100644 --- a/tasks/prettier_conformance/prettier.snap.md +++ b/tasks/prettier_conformance/prettier.snap.md @@ -1,4 +1,4 @@ -Compatibility: 115/771 (14.92%) +Compatibility: 120/771 (15.56%) # Failed @@ -99,7 +99,6 @@ Compatibility: 115/771 (14.92%) ### babel-plugins * babel-plugins/async-do-expressions.js -* babel-plugins/async-generators.js * babel-plugins/bigint.js * babel-plugins/class-properties.js * babel-plugins/class-static-block.js @@ -109,7 +108,6 @@ Compatibility: 115/771 (14.92%) * babel-plugins/deferred-import-evaluation.js * babel-plugins/destructuring-private.js * babel-plugins/do-expressions.js -* babel-plugins/dynamic-import.js * babel-plugins/explicit-resource-management.js * babel-plugins/export-default-from.js * babel-plugins/export-namespace-from.js @@ -121,7 +119,6 @@ Compatibility: 115/771 (14.92%) * babel-plugins/import-meta.js * babel-plugins/import-reflection.js * babel-plugins/jsx.js -* babel-plugins/logical-assignment-operators.js * babel-plugins/module-blocks.js * babel-plugins/nullish-coalescing-operator.js * babel-plugins/numeric-separator.js @@ -508,7 +505,6 @@ Compatibility: 115/771 (14.92%) ### generator * generator/anonymous.js * generator/async.js -* generator/function-name-starts-with-get.js ### identifier/for-of * identifier/for-of/let.js @@ -636,9 +632,6 @@ Compatibility: 115/771 (14.92%) ### nullish-coalescing * nullish-coalescing/nullish_coalesing_operator.js -### numeric-separators -* numeric-separators/number.js - ### object-prop-break-in * object-prop-break-in/comment.js * object-prop-break-in/short-keys.js