Skip to content

Commit 2de8362

Browse files
feat(lint): add nursery rule useImportsFirst (#9272)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 2669381 commit 2de8362

21 files changed

Lines changed: 411 additions & 0 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Added the nursery rule [`useImportsFirst`](https://biomejs.dev/linter/rules/use-imports-first/) that enforces all import statements appear before any non-import statements in a module. Inspired by the eslint-plugin-import [`import/first`](https://github.com/import-js/eslint-plugin-import/blob/HEAD/docs/rules/first.md) rule.
6+
7+
```js
8+
// Invalid
9+
import { foo } from "foo";
10+
const bar = 1;
11+
import { baz } from "baz"; // ← flagged
12+
13+
// Valid
14+
import { foo } from "foo";
15+
import { baz } from "baz";
16+
const bar = 1;
17+
```

crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_configuration/src/analyzer/linter/rules.rs

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_diagnostics_categories/src/categories.rs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use biome_analyze::{
2+
Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
3+
};
4+
use biome_console::markup;
5+
use biome_js_syntax::{AnyJsModuleItem, JsModuleItemList};
6+
use biome_rowan::{AstNode, AstNodeList, TextRange};
7+
use biome_rule_options::use_imports_first::UseImportsFirstOptions;
8+
9+
declare_lint_rule! {
10+
/// Enforce that all imports appear at the top of the module.
11+
///
12+
/// Import statements that appear after non-import statements are harder to
13+
/// find and may indicate disorganized code. Keeping all imports together at
14+
/// the top makes dependencies immediately visible.
15+
///
16+
/// Directives such as `"use strict"` are always allowed before
17+
/// imports, since they are parsed separately from module items.
18+
///
19+
/// This rule only applies to ES module `import` statements. CommonJS
20+
/// `require()` calls are not covered.
21+
///
22+
/// ## Examples
23+
///
24+
/// ### Invalid
25+
///
26+
/// ```js,expect_diagnostic
27+
/// import { foo } from "foo";
28+
/// const bar = 1;
29+
/// import { baz } from "baz";
30+
/// ```
31+
///
32+
/// ### Valid
33+
///
34+
/// ```js
35+
/// import { foo } from "foo";
36+
/// import { bar } from "bar";
37+
/// const baz = 1;
38+
/// ```
39+
///
40+
/// ```js
41+
/// "use strict";
42+
/// import { foo } from "foo";
43+
/// ```
44+
///
45+
pub UseImportsFirst {
46+
version: "next",
47+
name: "useImportsFirst",
48+
language: "js",
49+
recommended: false,
50+
sources: &[RuleSource::EslintImport("first").same()],
51+
}
52+
}
53+
54+
impl Rule for UseImportsFirst {
55+
type Query = Ast<JsModuleItemList>;
56+
type State = TextRange;
57+
type Signals = Vec<Self::State>;
58+
type Options = UseImportsFirstOptions;
59+
60+
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
61+
let items = ctx.query();
62+
let mut seen_non_import = false;
63+
let mut signals = Vec::new();
64+
65+
for item in items.iter() {
66+
match item {
67+
AnyJsModuleItem::JsImport(_) => {
68+
if seen_non_import {
69+
signals.push(item.range());
70+
}
71+
}
72+
_ => {
73+
seen_non_import = true;
74+
}
75+
}
76+
}
77+
78+
signals
79+
}
80+
81+
fn diagnostic(_ctx: &RuleContext<Self>, range: &Self::State) -> Option<RuleDiagnostic> {
82+
Some(
83+
RuleDiagnostic::new(
84+
rule_category!(),
85+
range,
86+
markup! {
87+
"This import appears after a non-import statement."
88+
},
89+
)
90+
.note(markup! {
91+
"Scattering imports makes it harder to see the module's dependencies at a glance."
92+
})
93+
.note(markup! {
94+
"Move all import statements before any other statements."
95+
}),
96+
)
97+
}
98+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { foo } from "foo";
2+
const bar = 1;
3+
import { baz } from "baz";
4+
5+
import { qux } from "qux";
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: invalid.js
4+
---
5+
# Input
6+
```js
7+
import { foo } from "foo";
8+
const bar = 1;
9+
import { baz } from "baz";
10+
11+
import { qux } from "qux";
12+
13+
```
14+
15+
# Diagnostics
16+
```
17+
invalid.js:3:1 lint/nursery/useImportsFirst ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
18+
19+
i This import appears after a non-import statement.
20+
21+
1 │ import { foo } from "foo";
22+
2 │ const bar = 1;
23+
> 3 │ import { baz } from "baz";
24+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^
25+
4 │
26+
5 │ import { qux } from "qux";
27+
28+
i Scattering imports makes it harder to see the module's dependencies at a glance.
29+
30+
i Move all import statements before any other statements.
31+
32+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
33+
34+
35+
```
36+
37+
```
38+
invalid.js:5:1 lint/nursery/useImportsFirst ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
39+
40+
i This import appears after a non-import statement.
41+
42+
3 │ import { baz } from "baz";
43+
4 │
44+
> 5 │ import { qux } from "qux";
45+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^
46+
6 │
47+
48+
i Scattering imports makes it harder to see the module's dependencies at a glance.
49+
50+
i Move all import statements before any other statements.
51+
52+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
53+
54+
55+
```
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const foo = 1;
2+
let bar = 2;
3+
function baz() {}
4+
import { qux } from "qux";
5+
import { quux } from "quux";
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: invalid_all_after.js
4+
---
5+
# Input
6+
```js
7+
const foo = 1;
8+
let bar = 2;
9+
function baz() {}
10+
import { qux } from "qux";
11+
import { quux } from "quux";
12+
13+
```
14+
15+
# Diagnostics
16+
```
17+
invalid_all_after.js:4:1 lint/nursery/useImportsFirst ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
18+
19+
i This import appears after a non-import statement.
20+
21+
2 │ let bar = 2;
22+
3 │ function baz() {}
23+
> 4 │ import { qux } from "qux";
24+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^
25+
5 │ import { quux } from "quux";
26+
6 │
27+
28+
i Scattering imports makes it harder to see the module's dependencies at a glance.
29+
30+
i Move all import statements before any other statements.
31+
32+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
33+
34+
35+
```
36+
37+
```
38+
invalid_all_after.js:5:1 lint/nursery/useImportsFirst ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
39+
40+
i This import appears after a non-import statement.
41+
42+
3 │ function baz() {}
43+
4 │ import { qux } from "qux";
44+
> 5 │ import { quux } from "quux";
45+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46+
6 │
47+
48+
i Scattering imports makes it harder to see the module's dependencies at a glance.
49+
50+
i Move all import statements before any other statements.
51+
52+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
53+
54+
55+
```
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import foo from "foo";
2+
foo.init();
3+
import bar from "bar";
4+
export { bar };
5+
import baz from "baz";

0 commit comments

Comments
 (0)