diff --git a/crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs b/crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs index c626c8f1f3016..ccb6bdfe5ff7b 100644 --- a/crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs +++ b/crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs @@ -73,6 +73,8 @@ impl SortImportsTransform { let mut in_alignable_block_comment = false; // Track if current line is a standalone alignable comment (no import on same line) let mut is_standalone_alignable_comment = false; + // Track if current line is inside a multiline ImportDeclaration + let mut inside_multiline_import = false; for (idx, el) in prev_elements.iter().enumerate() { // Check for alignable block comment boundaries. @@ -83,6 +85,7 @@ impl SortImportsTransform { in_alignable_block_comment = true; is_standalone_alignable_comment = true; } else if *id == LabelId::of(JsLabels::ImportDeclaration) { + inside_multiline_import = true; // An import on the same line means the comment is attached to it, not standalone is_standalone_alignable_comment = false; } @@ -91,6 +94,10 @@ impl SortImportsTransform { // doesn't nest with other labels in practice, we can safely reset here. if in_alignable_block_comment { in_alignable_block_comment = false; + } else if inside_multiline_import { + // I'm not sure if ImportDeclaration will nest with other labels, + // but this should be enough for now. + inside_multiline_import = false; } } @@ -103,6 +110,19 @@ impl SortImportsTransform { continue; } + // If the linebreak falls within the body of a multiline ImportDeclaration, + // don't fush the line. e.g. + // ``` + // import React { + // useState, + // // this is a comment followed by a FormatElement::Line(LineMode::Hard) + // useEffect, + // } from 'react'; + // ``` + if inside_multiline_import { + continue; + } + // Flush current line if current_line_start < idx { let line = if is_standalone_alignable_comment { @@ -119,6 +139,8 @@ impl SortImportsTransform { } current_line_start = idx + 1; is_standalone_alignable_comment = false; + // Explicitly reset the state after flushing lines to avoid stale state. + inside_multiline_import = false; // We need this explicitly to detect boundaries later. if matches!(mode, LineMode::Empty) { diff --git a/crates/oxc_formatter/tests/ir_transform/sort_imports.rs b/crates/oxc_formatter/tests/ir_transform/sort_imports.rs index 6649fc0c82e3b..e82f648ed5c6d 100644 --- a/crates/oxc_formatter/tests/ir_transform/sort_imports.rs +++ b/crates/oxc_formatter/tests/ir_transform/sort_imports.rs @@ -2050,6 +2050,32 @@ import "node:os"; ); } +#[test] +fn should_sort_multiline_import_containing_comment() { + // "a" -> "b" -> "c" + assert_format( + r#" +import c from "c"; +import b from "b"; +import { + type Data, + // this comment makes the source of this import become "Data" + xyz, +} from "a"; +"#, + r#"{ "experimentalSortImports": {} }"#, + r#" +import { + type Data, + // this comment makes the source of this import become "Data" + xyz, +} from "a"; +import b from "b"; +import c from "c"; +"#, + ); +} + // --- #[test]