Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/add-use-imports-first.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@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 [`import/first`](https://github.com/import-js/eslint-plugin-import/blob/HEAD/docs/rules/first.md) rule.
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.

7 changes: 4 additions & 3 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.

94 changes: 94 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,94 @@
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};

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.
///
/// Note that directives such as `"use strict"` are always allowed before
/// imports, since they are parsed separately from module items.
///
/// ## 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 = ();

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 should appear at the top of the module."
},
)
.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 should appear at the top of the module.
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 should appear at the top of the module.
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 should appear at the top of the module.
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 should appear at the top of the module.
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 should appear at the top of the module.
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 should appear at the top of the module.
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,4 @@
/* should not generate diagnostics */
import { foo } from "foo";
export { foo };
const bar = 1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
expression: valid_with_export.js
---
# Input
```js
/* should not generate diagnostics */
import { foo } from "foo";
export { foo };
const bar = 1;

```
Loading