Skip to content

Commit 0f6a3e6

Browse files
committed
feat: add blank line group separator
1 parent ab1dbae commit 0f6a3e6

13 files changed

+274
-114
lines changed

crates/biome_js_analyze/src/assist/source/organize_imports.rs

+142-86
Large diffs are not rendered by default.

crates/biome_js_analyze/src/assist/source/organize_imports/import_groups.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,33 @@ use crate::globals::is_node_builtin_module;
1010
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1111
pub struct ImportGroups(Box<[ImportGroup]>);
1212
impl ImportGroups {
13+
/// Returns `true` if no explicit group is set.
14+
pub fn is_empty(&self) -> bool {
15+
self.0.is_empty()
16+
}
17+
1318
/// Returns the index of the first group containing `candidate`.
1419
/// If no group contains `candidate`, then the returned value corresponds to the index of the implicit group.
1520
/// The index of the implicit group correspond to the number of groups.
16-
pub fn index(&self, candidate: &ImportSourceCandidate) -> usize {
21+
pub fn index(&self, candidate: &ImportSourceCandidate) -> u16 {
1722
self.0
1823
.iter()
1924
.position(|group| group.contains(candidate))
20-
.unwrap_or(self.0.len())
25+
.unwrap_or(self.0.len()) as u16
26+
}
27+
28+
/// Returns how many blank lines must separate `first_group` and `second_group`.
29+
pub fn separated_by_blank_line(&self, first_group: u16, second_group: u16) -> bool {
30+
self.0
31+
.get((first_group as usize)..(second_group as usize))
32+
.is_some_and(|groups| {
33+
groups.iter().any(|group| {
34+
matches!(
35+
group,
36+
ImportGroup::Predefined(PredefinedImportGroup::BlankLine)
37+
)
38+
})
39+
})
2140
}
2241
}
2342

@@ -62,6 +81,8 @@ impl Deserializable for ImportGroup {
6281
#[derive(Clone, Debug, Deserializable, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
6382
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
6483
pub enum PredefinedImportGroup {
84+
#[serde(rename = ":BLANK-LINE:")]
85+
BlankLine,
6586
#[serde(rename = ":BUN:")]
6687
Bun,
6788
#[serde(rename = ":NODE:")]
@@ -71,6 +92,7 @@ impl PredefinedImportGroup {
7192
fn contains(&self, candidate: &ImportSourceCandidate) -> bool {
7293
let import_source = candidate.as_str();
7394
match self {
95+
Self::BlankLine => false,
7496
Self::Bun => import_source == "bun" || import_source.starts_with("bun:"),
7597
Self::Node => {
7698
import_source.starts_with("node:") || is_node_builtin_module(import_source)

crates/biome_js_analyze/src/assist/source/organize_imports/import_key.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ pub struct ImportKey {
2121
pub slot_index: u32,
2222
}
2323
impl ImportKey {
24-
pub fn new(info: ImportInfo, groups: Option<&import_groups::ImportGroups>) -> Self {
24+
pub fn new(info: ImportInfo, groups: &import_groups::ImportGroups) -> Self {
2525
let candidate = import_groups::ImportSourceCandidate::new(info.source.inner().0.text());
2626
Self {
27-
group: groups.map_or(0, |groups| groups.index(&candidate) as u16),
27+
group: groups.index(&candidate),
2828
source: info.source,
2929
has_no_attributes: info.has_no_attributes,
3030
kind: info.kind,

crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs

+1-10
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ pub fn sort_import_specifiers(
5959
named_specifiers: JsNamedImportSpecifiers,
6060
) -> Option<JsNamedImportSpecifiers> {
6161
let list = named_specifiers.specifiers();
62-
if list.len() <= 1 {
63-
return Some(named_specifiers);
64-
}
6562
let mut last_has_separator = false;
6663
let mut sorted = Vec::with_capacity(list.len());
6764
for AstSeparatedElement {
@@ -117,11 +114,8 @@ pub fn are_export_specifiers_sorted(specifiers: &JsExportNamedFromSpecifierList)
117114
}
118115

119116
pub fn sort_export_specifiers(
120-
named_specifiers: JsExportNamedFromSpecifierList,
117+
named_specifiers: &JsExportNamedFromSpecifierList,
121118
) -> Option<JsExportNamedFromSpecifierList> {
122-
if named_specifiers.len() <= 1 {
123-
return Some(named_specifiers);
124-
}
125119
let mut last_has_separator = false;
126120
let mut sorted = Vec::with_capacity(named_specifiers.len());
127121
for AstSeparatedElement {
@@ -178,9 +172,6 @@ pub fn are_import_attributes_sorted(attributes: &JsImportAssertion) -> Option<bo
178172

179173
pub fn sort_attributes(attributes: JsImportAssertion) -> Option<JsImportAssertion> {
180174
let attributes_list = attributes.assertions();
181-
if attributes_list.len() <= 1 {
182-
return Some(attributes);
183-
}
184175
let mut last_has_separator = false;
185176
let mut sorted = Vec::new();
186177
for AstSeparatedElement {

crates/biome_js_analyze/src/assist/source/organize_imports/util.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use biome_js_syntax::{JsLanguage, JsSyntaxNode, JsSyntaxTrivia};
22
use biome_rowan::SyntaxTriviaPiece;
33

4-
pub fn leading_newline_count(syntax: &JsSyntaxNode) -> usize {
5-
syntax.first_leading_trivia().map_or(0, |trivia| {
6-
trivia
7-
.pieces()
8-
.take_while(|piece| piece.is_newline())
9-
.count()
10-
})
4+
pub fn leading_newlines(
5+
syntax: &JsSyntaxNode,
6+
) -> impl Iterator<Item = SyntaxTriviaPiece<JsLanguage>> {
7+
syntax
8+
.first_leading_trivia()
9+
.into_iter()
10+
.flat_map(|trivia| trivia.pieces().take_while(|piece| piece.is_newline()))
1111
}
1212

1313
/// Returns the index of the last blank line of `trivia`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { test as testBun } from "bun:test"
2+
import { test as testNode } from "node:test"
3+
import bare from "bare"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: custom-order-sorted-imports-missing-blank-lines.js
4+
---
5+
# Input
6+
```js
7+
import { test as testBun } from "bun:test"
8+
import { test as testNode } from "node:test"
9+
import bare from "bare"
10+
11+
```
12+
13+
# Actions
14+
```diff
15+
@@ -1,3 +1,5 @@
16+
import { test as testBun } from "bun:test"
17+
+
18+
import { test as testNode } from "node:test"
19+
+
20+
import bare from "bare"
21+
22+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "../../../../../packages/@biomejs/biome/configuration_schema.json",
3+
"assist": {
4+
"actions": {
5+
"source": {
6+
"organizeImports": {
7+
"level": "on",
8+
"options": {
9+
"groups": [
10+
":BUN:",
11+
":BLANK-LINE:",
12+
":NODE:",
13+
":BLANK-LINE:"
14+
]
15+
}
16+
}
17+
}
18+
}
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { test as testBun } from "bun:test"
2+
import { test as testNode } from "node:test"
3+
import path from "node:path"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: custom-order-unsorted-imports-missing-blank-lines.js
4+
---
5+
# Input
6+
```js
7+
import { test as testBun } from "bun:test"
8+
import { test as testNode } from "node:test"
9+
import path from "node:path"
10+
11+
```
12+
13+
# Actions
14+
```diff
15+
@@ -1,3 +1,4 @@
16+
import { test as testBun } from "bun:test"
17+
-import { test as testNode } from "node:test"
18+
+
19+
import path from "node:path"
20+
+import { test as testNode } from "node:test"
21+
22+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "../../../../../packages/@biomejs/biome/configuration_schema.json",
3+
"assist": {
4+
"actions": {
5+
"source": {
6+
"organizeImports": {
7+
"level": "on",
8+
"options": {
9+
"groups": [
10+
":BUN:",
11+
":BLANK-LINE:",
12+
":NODE:",
13+
":BLANK-LINE:"
14+
]
15+
}
16+
}
17+
}
18+
}
19+
}
20+
}

crates/biome_js_analyze/tests/specs/source/organizeImports/custom-order.js.snap

+6-6
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@ import { test as testNode } from "@scopeB/lib"
1616

1717
# Actions
1818
```diff
19-
@@ -1,7 +1,7 @@
20-
+import { test as testNode } from "node:test"
19+
@@ -1,7 +1,8 @@
2120
import { test as testBun } from "bun:test"
22-
-import { test as testNode } from "node:test"
21+
import { test as testNode } from "node:test"
2322
-import { test as testNode } from "@scopeX/special/subpath"
2423
-import { test as testNode } from "@scopeX/special"
2524
import { test as testNode } from "@scopeX/lib"
26-
+import { test as testNode } from "@scopeB/lib"
27-
import { test as testNode } from "@scopeA/lib"
28-
-import { test as testNode } from "@scopeB/lib"
25+
-import { test as testNode } from "@scopeA/lib"
26+
+
27+
import { test as testNode } from "@scopeB/lib"
28+
+import { test as testNode } from "@scopeA/lib"
2929
+import { test as testNode } from "@scopeX/special"
3030
+import { test as testNode } from "@scopeX/special/subpath"
3131

crates/biome_js_analyze/tests/specs/source/organizeImports/custom-order.options.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
"level": "on",
88
"options": {
99
"groups": [
10-
":NODE:",
1110
":BUN:",
11+
":NODE:",
1212
["@scopeX/**", "!@scopeX/special", "!@scopeX/special/**"],
13+
":BLANK-LINE:",
1314
"@scopeB/**",
1415
"@scopeA/**"
1516
]

0 commit comments

Comments
 (0)