Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/add-use-imports-first.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@biomejs/biome": patch
---

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.

```js
// Invalid
import { foo } from "foo";
const bar = 1;
import { baz } from "baz"; // ← flagged

// Valid
import { foo } from "foo";
import { baz } from "baz";
const bar = 1;
```
12 changes: 12 additions & 0 deletions crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/biome_configuration/src/analyzer/linter/rules.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/biome_diagnostics_categories/src/categories.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

98 changes: 98 additions & 0 deletions crates/biome_js_analyze/src/lint/nursery/use_imports_first.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use biome_analyze::{
Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
};
use biome_console::markup;
use biome_js_syntax::{AnyJsModuleItem, JsModuleItemList};
use biome_rowan::{AstNode, AstNodeList, TextRange};
use biome_rule_options::use_imports_first::UseImportsFirstOptions;

declare_lint_rule! {
/// Enforce that all imports appear at the top of the module.
///
/// Import statements that appear after non-import statements are harder to
/// find and may indicate disorganized code. Keeping all imports together at
/// the top makes dependencies immediately visible.
///
/// Directives such as `"use strict"` are always allowed before
/// imports, since they are parsed separately from module items.
///
/// This rule only applies to ES module `import` statements. CommonJS
/// `require()` calls are not covered.
///
/// ## Examples
///
/// ### Invalid
///
/// ```js,expect_diagnostic
/// import { foo } from "foo";
/// const bar = 1;
/// import { baz } from "baz";
/// ```
///
/// ### Valid
///
/// ```js
/// import { foo } from "foo";
/// import { bar } from "bar";
/// const baz = 1;
/// ```
///
/// ```js
/// "use strict";
/// import { foo } from "foo";
/// ```
///
pub UseImportsFirst {
version: "next",
name: "useImportsFirst",
language: "js",
recommended: false,
sources: &[RuleSource::EslintImport("first").same()],
}
}

impl Rule for UseImportsFirst {
type Query = Ast<JsModuleItemList>;
type State = TextRange;
type Signals = Vec<Self::State>;
type Options = UseImportsFirstOptions;

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let items = ctx.query();
let mut seen_non_import = false;
let mut signals = Vec::new();

for item in items.iter() {
match item {
AnyJsModuleItem::JsImport(_) => {
if seen_non_import {
signals.push(item.range());
}
}
_ => {
seen_non_import = true;
}
}
}

signals
}

fn diagnostic(_ctx: &RuleContext<Self>, range: &Self::State) -> Option<RuleDiagnostic> {
Some(
RuleDiagnostic::new(
rule_category!(),
range,
markup! {
"This import appears after a non-import statement."
},
)
.note(markup! {
"Scattering imports makes it harder to see the module's dependencies at a glance."
})
.note(markup! {
"Move all import statements before any other statements."
}),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { foo } from "foo";
const bar = 1;
import { baz } from "baz";

import { qux } from "qux";
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
expression: invalid.js
---
# Input
```js
import { foo } from "foo";
const bar = 1;
import { baz } from "baz";
import { qux } from "qux";
```

# Diagnostics
```
invalid.js:3:1 lint/nursery/useImportsFirst ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
i This import appears after a non-import statement.
1 │ import { foo } from "foo";
2 │ const bar = 1;
> 3 │ import { baz } from "baz";
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^
4 │
5 │ import { qux } from "qux";
i Scattering imports makes it harder to see the module's dependencies at a glance.
i Move all import statements before any other statements.
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.
```

```
invalid.js:5:1 lint/nursery/useImportsFirst ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
i This import appears after a non-import statement.
3 │ import { baz } from "baz";
4 │
> 5 │ import { qux } from "qux";
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^
6 │
i Scattering imports makes it harder to see the module's dependencies at a glance.
i Move all import statements before any other statements.
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.
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const foo = 1;
let bar = 2;
function baz() {}
import { qux } from "qux";
import { quux } from "quux";
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
expression: invalid_all_after.js
---
# Input
```js
const foo = 1;
let bar = 2;
function baz() {}
import { qux } from "qux";
import { quux } from "quux";

```

# Diagnostics
```
invalid_all_after.js:4:1 lint/nursery/useImportsFirst ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

i This import appears after a non-import statement.

2 │ let bar = 2;
3 │ function baz() {}
> 4 │ import { qux } from "qux";
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^
5 │ import { quux } from "quux";
6 │

i Scattering imports makes it harder to see the module's dependencies at a glance.

i Move all import statements before any other statements.

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.


```

```
invalid_all_after.js:5:1 lint/nursery/useImportsFirst ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

i This import appears after a non-import statement.

3 │ function baz() {}
4 │ import { qux } from "qux";
> 5 │ import { quux } from "quux";
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6 │

i Scattering imports makes it harder to see the module's dependencies at a glance.

i Move all import statements before any other statements.

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.


```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import foo from "foo";
foo.init();
import bar from "bar";
export { bar };
import baz from "baz";
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
expression: invalid_mixed.js
---
# Input
```js
import foo from "foo";
foo.init();
import bar from "bar";
export { bar };
import baz from "baz";

```

# Diagnostics
```
invalid_mixed.js:3:1 lint/nursery/useImportsFirst ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

i This import appears after a non-import statement.

1 │ import foo from "foo";
2 │ foo.init();
> 3 │ import bar from "bar";
│ ^^^^^^^^^^^^^^^^^^^^^^
4 │ export { bar };
5 │ import baz from "baz";

i Scattering imports makes it harder to see the module's dependencies at a glance.

i Move all import statements before any other statements.

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.


```

```
invalid_mixed.js:5:1 lint/nursery/useImportsFirst ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

i This import appears after a non-import statement.

3 │ import bar from "bar";
4 │ export { bar };
> 5 │ import baz from "baz";
│ ^^^^^^^^^^^^^^^^^^^^^^
6 │

i Scattering imports makes it harder to see the module's dependencies at a glance.

i Move all import statements before any other statements.

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.


```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* should not generate diagnostics */
import { foo } from "foo";
import { bar } from "bar";
import { baz } from "baz";
const qux = 1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
expression: valid.js
---
# Input
```js
/* should not generate diagnostics */
import { foo } from "foo";
import { bar } from "bar";
import { baz } from "baz";
const qux = 1;

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* should not generate diagnostics */
"use strict";
import { foo } from "foo";
import { bar } from "bar";
const baz = 1;
Loading