mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-06 17:34:25 +00:00
fix(formatter): print JSDoc embedded snippets with LF (#27821)
Otherwise, CRLF is appeared in IR, then panics.
This commit is contained in:
1 parent
2c1cc20fa4
commit
a64677e2a7
9 files changed
+91
-40
No files matched your search
@@ -21,7 +21,7 @@ use oxc_formatter_core::{StringEmbedder, TailwindSorter};
|
||||
use crate::core::{
|
||||
embed::{FormatEmbeddedWithConfigCallback, dispatcher, jsdoc_fence},
|
||||
language::{Route, route_embedded},
|
||||
options::inject_print_width,
|
||||
options::inject_string_embed_options,
|
||||
};
|
||||
|
||||
/// Build the napi build's string embedder installed on the session.
|
||||
@@ -62,8 +62,8 @@ pub fn build_string_embedder(
|
||||
let Some(mut options) = dispatch_config.prettier_options_for(prettier_language) else {
|
||||
return Err(format!("No plugin enabled for parser: {parser_name}"));
|
||||
};
|
||||
// The same effective width the native branch prints at
|
||||
inject_print_width(&mut options, print_width);
|
||||
// The same effective width and line ending the native branch prints with
|
||||
inject_string_embed_options(&mut options, print_width);
|
||||
(format_embedded)(options, code)
|
||||
.map(|mut code| {
|
||||
// Remove trailing newline added by Prettier without allocation.
|
||||
|
||||
@@ -37,7 +37,7 @@ pub use to_oxc_formatter_yaml::to_oxc_formatter_yaml;
|
||||
#[cfg(feature = "napi")]
|
||||
pub use to_prettier::{
|
||||
build_prettier_options, inject_filepath, inject_opt_in_plugin_payloads,
|
||||
inject_oxfmt_plugin_payload, inject_parser, inject_print_width, inject_tailwind_plugin_payload,
|
||||
to_prettier,
|
||||
inject_oxfmt_plugin_payload, inject_parser, inject_string_embed_options,
|
||||
inject_tailwind_plugin_payload, to_prettier,
|
||||
};
|
||||
pub use validate::{ValidatedOptions, validate};
|
||||
@@ -172,9 +172,13 @@ pub fn inject_parser(opts: &mut Value, parser_name: &str) {
|
||||
as_object_mut(opts).insert("parser".to_string(), Value::String(parser_name.to_string()));
|
||||
}
|
||||
|
||||
/// Inject `printWidth` key, overriding the configured one.
|
||||
pub fn inject_print_width(opts: &mut Value, print_width: usize) {
|
||||
as_object_mut(opts).insert("printWidth".to_string(), Value::from(print_width));
|
||||
/// Inject the keys for a string re-embedded into the parent's IR, overriding the configured ones:
|
||||
/// - `printWidth`: the effective width at the embedding position
|
||||
/// - `endOfLine`: `lf`, since the IR only takes `\n`
|
||||
pub fn inject_string_embed_options(opts: &mut Value, print_width: usize) {
|
||||
let opts = as_object_mut(opts);
|
||||
opts.insert("printWidth".to_string(), Value::from(print_width));
|
||||
opts.insert("endOfLine".to_string(), Value::from("lf"));
|
||||
}
|
||||
|
||||
/// Inject `filepath` key.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use oxc_formatter_core::{FormatSession, LineWidth};
|
||||
use oxc_formatter_core::{FormatSession, LineEnding, LineWidth};
|
||||
use oxc_span::SourceType;
|
||||
|
||||
use crate::options::TrailingCommas;
|
||||
@@ -25,6 +25,22 @@ fn parse_and_build<'a>(
|
||||
Some(code)
|
||||
}
|
||||
|
||||
/// Options for a JS/TS snippet formatted to a string and re-embedded into the comment text.
|
||||
///
|
||||
/// - `line_ending` is `Lf`: the comment is IR text which only takes `\n`, the host converts once at the end
|
||||
/// - `jsdoc` / `sort_imports` / `sort_tailwindcss` are disabled (top-level concerns)
|
||||
pub(super) fn snippet_options(format_options: &JsFormatOptions, width: usize) -> JsFormatOptions {
|
||||
let width = u16::try_from(width).unwrap_or(80).clamp(1, LineWidth::MAX);
|
||||
JsFormatOptions {
|
||||
line_ending: LineEnding::Lf,
|
||||
line_width: LineWidth::try_from(width).unwrap(),
|
||||
jsdoc: None,
|
||||
sort_imports: None,
|
||||
sort_tailwindcss: None,
|
||||
..format_options.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the fenced code block language is JS/TS/JSX/TSX (case-insensitive).
|
||||
pub(super) fn is_js_ts_lang(lang: &str) -> bool {
|
||||
["js", "javascript", "jsx", "ts", "typescript", "tsx"]
|
||||
@@ -111,16 +127,7 @@ pub(super) fn format_jsdoc_js_snippet(
|
||||
format_options: &JsFormatOptions,
|
||||
session: &FormatSession<'_>,
|
||||
) -> Option<String> {
|
||||
let width = u16::try_from(print_width).unwrap_or(80).clamp(1, 320);
|
||||
let line_width = LineWidth::try_from(width).unwrap();
|
||||
|
||||
let base_options = JsFormatOptions {
|
||||
line_width,
|
||||
jsdoc: None,
|
||||
sort_imports: None,
|
||||
sort_tailwindcss: None,
|
||||
..format_options.clone()
|
||||
};
|
||||
let base_options = snippet_options(format_options, print_width);
|
||||
|
||||
// Try to parse and format with the given source type
|
||||
let try_format = |code: &str, source_type: SourceType| {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use oxc_ast::Comment;
|
||||
use oxc_formatter_core::{FormatSession, LineWidth};
|
||||
use oxc_formatter_core::FormatSession;
|
||||
use oxc_jsdoc::JSDoc;
|
||||
use oxc_span::Span;
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::options::{JsdocOptions, QuoteStyle};
|
||||
use crate::{JsFormatOptions, write};
|
||||
|
||||
use super::{
|
||||
imports::process_import_tags, line_buffer::LineBuffer,
|
||||
embedded::snippet_options, imports::process_import_tags, line_buffer::LineBuffer,
|
||||
mdast_serialize::format_description_mdast, normalize::normalize_tag_kind,
|
||||
param_order::reorder_param_tags,
|
||||
};
|
||||
@@ -80,14 +80,7 @@ impl<'a, 'o> JsdocFormatter<'a, 'o> {
|
||||
// `commentContentPrintWidth` to Prettier's TS formatter. This lets the
|
||||
// formatter wrap complex types (object literals, function types) across
|
||||
// multiple lines when they exceed the available width.
|
||||
let type_width = u16::try_from(wrap_width).unwrap_or(80).clamp(1, LineWidth::MAX);
|
||||
let type_format_options = JsFormatOptions {
|
||||
line_width: LineWidth::try_from(type_width).unwrap(),
|
||||
jsdoc: None,
|
||||
sort_imports: None,
|
||||
sort_tailwindcss: None,
|
||||
..format_options.clone()
|
||||
};
|
||||
let type_format_options = snippet_options(format_options, wrap_width);
|
||||
Self {
|
||||
options,
|
||||
format_options,
|
||||
@@ -1179,14 +1172,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn fmt_type_width(type_str: &str, width: u16) -> Option<String> {
|
||||
let opts = JsFormatOptions {
|
||||
line_width: LineWidth::try_from(width).unwrap(),
|
||||
jsdoc: None,
|
||||
sort_imports: None,
|
||||
sort_tailwindcss: None,
|
||||
..JsFormatOptions::default()
|
||||
};
|
||||
fmt_type_with_opts(type_str, &opts)
|
||||
fmt_type_with_opts(type_str, &snippet_options(&JsFormatOptions::default(), width.into()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* @returns {{ aaaaaaaaaaaaaaaa: string; bbbbbbbbbbbbbbbbbb: number }} x
|
||||
* @example
|
||||
* const x = { aaaaaaaaaaaaaaaa: 1, bbbbbbbbbbbbbbbbbb: 2 };
|
||||
*/
|
||||
function f() {}
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
source: crates/oxc_formatter/tests/fixtures/mod.rs
|
||||
---
|
||||
==================== Input ====================
|
||||
/**
|
||||
* @returns {{ aaaaaaaaaaaaaaaa: string; bbbbbbbbbbbbbbbbbb: number }} x
|
||||
* @example
|
||||
* const x = { aaaaaaaaaaaaaaaa: 1, bbbbbbbbbbbbbbbbbb: 2 };
|
||||
*/
|
||||
function f() {}
|
||||
|
||||
==================== Output ====================
|
||||
--------------------------------------------------
|
||||
{ endOfLine: "crlf", jsdoc: true, printWidth: 40 }
|
||||
--------------------------------------------------
|
||||
/**
|
||||
* @example
|
||||
* const x = { aaaaaaaaaaaaaaaa: 1, bbbbbbbbbbbbbbbbbb: 2 };
|
||||
* @returns {{
|
||||
* aaaaaaaaaaaaaaaa: string;
|
||||
* bbbbbbbbbbbbbbbbbb: number;
|
||||
* }} X
|
||||
*/
|
||||
function f() {}
|
||||
|
||||
--------------------------------------------------
|
||||
{ endOfLine: "crlf", jsdoc: true, printWidth: 80 }
|
||||
--------------------------------------------------
|
||||
/**
|
||||
* @example
|
||||
* const x = { aaaaaaaaaaaaaaaa: 1, bbbbbbbbbbbbbbbbbb: 2 };
|
||||
* @returns {{ aaaaaaaaaaaaaaaa: string; bbbbbbbbbbbbbbbbbb: number }} X
|
||||
*/
|
||||
function f() {}
|
||||
|
||||
---------------------------------------------------
|
||||
{ endOfLine: "crlf", jsdoc: true, printWidth: 100 }
|
||||
---------------------------------------------------
|
||||
/**
|
||||
* @example
|
||||
* const x = { aaaaaaaaaaaaaaaa: 1, bbbbbbbbbbbbbbbbbb: 2 };
|
||||
* @returns {{ aaaaaaaaaaaaaaaa: string; bbbbbbbbbbbbbbbbbb: number }} X
|
||||
*/
|
||||
function f() {}
|
||||
|
||||
===================== End =====================
|
||||
@@ -0,0 +1 @@
|
||||
[{ "jsdoc": true, "endOfLine": "crlf", "printWidth": 40 }]
|
||||
@@ -7,8 +7,8 @@ use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||
use oxc_allocator::Allocator;
|
||||
|
||||
use crate::{
|
||||
DispatchPayload, DispatchRequest, DispatchResponse, Document, FormatDispatcher, PrinterOptions,
|
||||
UniqueGroupIdBuilder,
|
||||
DispatchPayload, DispatchRequest, DispatchResponse, Document, FormatDispatcher, LineEnding,
|
||||
PrinterOptions, UniqueGroupIdBuilder,
|
||||
};
|
||||
|
||||
/// Upper bound on nested embedded dispatches;
|
||||
@@ -184,6 +184,7 @@ impl<'a> FormatSession<'a> {
|
||||
/// must come back as TEXT. The caller supplies `printer_options`, typically its own
|
||||
/// print options with the embedding position's effective width, and owns any
|
||||
/// re-embedding conventions (e.g. trailing-newline handling) on the returned text.
|
||||
/// The line ending is always `Lf`: the text goes back into the parent's IR, which only takes `\n`.
|
||||
/// A text consumer has no parent index space, so the child's Tailwind classes
|
||||
/// collect in a fresh scope and sort locally instead of joining this session's scope.
|
||||
///
|
||||
@@ -205,7 +206,7 @@ impl<'a> FormatSession<'a> {
|
||||
};
|
||||
|
||||
let code = Document::new(doc, scoped.take_sorted_tailwind_classes())
|
||||
.print(text_len, printer_options)
|
||||
.print(text_len, printer_options.with_line_ending(LineEnding::Lf))
|
||||
.map_err(|err| err.to_string())?
|
||||
.into_code();
|
||||
Ok(Some(code))
|
||||
|
||||
Reference in new issue
Block a user