Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
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.

The current kind of suggests the original rule came from Eslint itself

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.

91 changes: 91 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,91 @@
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 = ();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In another PR we are denying Options to be empty, might be good to comply here already


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! {
"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,51 @@
---
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 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 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,51 @@
---
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 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 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;

```
1 change: 1 addition & 0 deletions crates/biome_rule_options/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ pub mod use_iframe_title;
pub mod use_image_size;
pub mod use_import_extensions;
pub mod use_import_type;
pub mod use_imports_first;
pub mod use_index_of;
pub mod use_inline_script_id;
pub mod use_input_name;
Expand Down
6 changes: 6 additions & 0 deletions crates/biome_rule_options/src/use_imports_first.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused, but will be with other comment

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use biome_deserialize_macros::{Deserializable, Merge};
use serde::{Deserialize, Serialize};
#[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields, default)]
pub struct UseImportsFirstOptions {}
26 changes: 26 additions & 0 deletions packages/@biomejs/biome/configuration_schema.json

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