From 88380cb4c89c703eee51ecdd9eac47868e1c5fa4 Mon Sep 17 00:00:00 2001 From: emilyinure <114822181+emilyinure@users.noreply.github.com> Date: Mon, 7 Jul 2025 19:03:26 -0400 Subject: [PATCH 01/12] fix(noImportCycles): prevent flagging on single file import cycling fixes #6569 --- .../src/lint/nursery/no_import_cycles.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs index 50dec47636fe..c4e632e7f52f 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs @@ -171,12 +171,23 @@ fn find_cycle( if resolved_path == ctx.file_path() { // Return all the paths from `start_path` to `resolved_path`: - let paths = Some(start_path.as_str()) + let paths: Box<[Box]> = Some(start_path.as_str()) .into_iter() .map(Box::from) - .chain(stack.into_iter().map(|(path, _)| path)) + .chain(stack.clone().into_iter().map(|(path, _)| path)) .chain(Some(Box::from(resolved_path.as_str()))) .collect(); + + // #6569 + // I believe we only care about the first two imports here, if they are they are + // the same, they are the only chain, or further iterations will find the shorter + // import chain + if let (Some(first), Some(second)) = (paths.get(0), paths.get(1)) + && *first == *second + { + continue; + } + return Some(paths); } From 27490b30e13c947bb8ac8062ef84bf29d88f7c62 Mon Sep 17 00:00:00 2001 From: emilyinure <114822181+emilyinure@users.noreply.github.com> Date: Mon, 7 Jul 2025 19:41:52 -0400 Subject: [PATCH 02/12] Test cases for single import cycles --- .../tests/specs/nursery/noImportCycles/valid.js | 10 ++++++++++ .../tests/specs/nursery/noImportCycles/valid.js.snap | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/crates/biome_js_analyze/tests/specs/nursery/noImportCycles/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noImportCycles/valid.js index 3088aadc2998..7a76d07fdf68 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noImportCycles/valid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noImportCycles/valid.js @@ -3,3 +3,13 @@ import { foo } from "./invalidFoobar"; foo(); + +export function bar() { + return 1; +} + +export * as bar from "./valid" + +import { foobar } from "./valid" + +foobar.bar(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noImportCycles/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noImportCycles/valid.js.snap index fef0753daec3..bf0b9c767d35 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noImportCycles/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noImportCycles/valid.js.snap @@ -10,4 +10,14 @@ import { foo } from "./invalidFoobar"; foo(); +export function bar() { + return 1; +} + +export * as bar from "./valid" + +import { foobar } from "./valid" + +foobar.bar(); + ``` From 201f62aff93a662a9e9255be6ab9f95131424ae8 Mon Sep 17 00:00:00 2001 From: emilyinure <114822181+emilyinure@users.noreply.github.com> Date: Mon, 7 Jul 2025 19:43:13 -0400 Subject: [PATCH 03/12] Satisfy clippy linting for single import cycles --- crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs index c4e632e7f52f..eaccae082dfa 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs @@ -182,7 +182,7 @@ fn find_cycle( // I believe we only care about the first two imports here, if they are they are // the same, they are the only chain, or further iterations will find the shorter // import chain - if let (Some(first), Some(second)) = (paths.get(0), paths.get(1)) + if let (Some(first), Some(second)) = (paths.first(), paths.get(1)) && *first == *second { continue; From eb47749832bfbde3492e9dd25923a42c00c80c32 Mon Sep 17 00:00:00 2001 From: emilyinure <114822181+emilyinure@users.noreply.github.com> Date: Mon, 7 Jul 2025 19:56:50 -0400 Subject: [PATCH 04/12] Changeset for #6765 --- .changeset/icy-results-wonder.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/icy-results-wonder.md diff --git a/.changeset/icy-results-wonder.md b/.changeset/icy-results-wonder.md new file mode 100644 index 000000000000..5670701e3a36 --- /dev/null +++ b/.changeset/icy-results-wonder.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Allow cyclical imports with noImportCycles when they are contained to a single file, ex: test.js includes test.js From 4c1385a04d9018aa0e871dfd49f00c9a52fb1918 Mon Sep 17 00:00:00 2001 From: emilyinure <114822181+emilyinure@users.noreply.github.com> Date: Tue, 8 Jul 2025 07:14:52 -0400 Subject: [PATCH 05/12] More detailed changeset for single file import chains Co-authored-by: Arend van Beelen jr. --- .changeset/icy-results-wonder.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.changeset/icy-results-wonder.md b/.changeset/icy-results-wonder.md index 5670701e3a36..cc1c41d78b3e 100644 --- a/.changeset/icy-results-wonder.md +++ b/.changeset/icy-results-wonder.md @@ -2,4 +2,18 @@ "@biomejs/biome": patch --- -Allow cyclical imports with noImportCycles when they are contained to a single file, ex: test.js includes test.js +Fixed [#6569](https://github.com/biomejs/biome/issues/6569): Allow files to export from themselves with `noImportCycles`. + +This means the following is now allowed: + +**example.js** +```js +export function example1() { + return 1; +} + +export function example2() { + return 2; +} + +export * as Example from './test'; From 29162e9c4abf1f84b9dbe452d5a4d20b4a5abe7e Mon Sep 17 00:00:00 2001 From: emi Date: Thu, 20 Nov 2025 01:58:56 -0500 Subject: [PATCH 06/12] More preformant check for discarding single import cycles --- .../src/lint/nursery/no_import_cycles.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs index eaccae082dfa..dc15d99363fc 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs @@ -170,24 +170,20 @@ fn find_cycle( }; if resolved_path == ctx.file_path() { + // https://github.com/biomejs/biome/issues/6569 + // prevent flagging on import cycles when they are isolated to a single file + if stack.is_empty() && start_path.as_str() == resolved_path { + continue; + } + // Return all the paths from `start_path` to `resolved_path`: let paths: Box<[Box]> = Some(start_path.as_str()) .into_iter() .map(Box::from) - .chain(stack.clone().into_iter().map(|(path, _)| path)) + .chain(stack.into_iter().map(|(path, _)| path)) .chain(Some(Box::from(resolved_path.as_str()))) .collect(); - // #6569 - // I believe we only care about the first two imports here, if they are they are - // the same, they are the only chain, or further iterations will find the shorter - // import chain - if let (Some(first), Some(second)) = (paths.first(), paths.get(1)) - && *first == *second - { - continue; - } - return Some(paths); } From 1fbea3c096261f24dfc9b8976f141c49481d68f3 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 07:18:13 +0000 Subject: [PATCH 07/12] [autofix.ci] apply automated fixes --- crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs index f8b109ef2b38..bf2e39f50704 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs @@ -241,14 +241,14 @@ fn find_cycle( if !seen.insert(resolved_path.clone()) { continue; } - + if path == ctx.file_path() { // https://github.com/biomejs/biome/issues/6569 // prevent flagging on import cycles when they are isolated to a single file if stack.is_empty() && start_path == path { continue; } - + // Return all the paths from `start_path` to `resolved_path`: let paths = Some(start_path.to_string()) .into_iter() From 0fc3ad28f4ac71087aefdcc8e6a14662d5cb4c11 Mon Sep 17 00:00:00 2001 From: emi Date: Thu, 20 Nov 2025 03:19:38 -0500 Subject: [PATCH 08/12] Update changeset to explain example --- .changeset/icy-results-wonder.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.changeset/icy-results-wonder.md b/.changeset/icy-results-wonder.md index cc1c41d78b3e..d1bfaed7550c 100644 --- a/.changeset/icy-results-wonder.md +++ b/.changeset/icy-results-wonder.md @@ -8,12 +8,14 @@ This means the following is now allowed: **example.js** ```js -export function example1() { +export function example() { return 1; } -export function example2() { - return 2; -} +// Re-exports all named exports from the current module under a single namespace +// and then imports the namespace from the current module. +// Allows for encapsulating functions/variables into a namespace instead +// of using a static class. +export * as Example from './example.js'; -export * as Example from './test'; +import { Example } from './example.js'; From 45a4f252587067a8ece1a52051c5f8ce3d93583c Mon Sep 17 00:00:00 2001 From: emi Date: Thu, 20 Nov 2025 03:22:28 -0500 Subject: [PATCH 09/12] Adding explanation for ignoring single import cycles, and added valid example --- .../src/lint/nursery/no_import_cycles.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs index bf2e39f50704..3d2d6e4ccfb4 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs @@ -25,6 +25,11 @@ declare_lint_rule! { /// only go in a single direction, i.e. they don't point "back" to the /// importing file. /// + /// If a cycle is contained to a single file, i.e. a file imports from + /// itself, no warning is issued. This allows for encapsulation of + /// functions/variables into a namespace instead of using a static class + /// (triggers [no-static-only-class](https://biomejs.dev/linter/rules/no-static-only-class)). + /// /// :::note /// This rule is computationally expensive. If you are particularly /// pressed for lint time, or don't think you have an issue with dependency @@ -79,6 +84,16 @@ declare_lint_rule! { /// } /// ``` /// + /// ```js,file=foobar.js + /// export function foo() { + /// console.log("foobar"); + /// } + /// + /// export * as bar from './foobar.js'; + /// + /// import { bar } from './foobar.js'; + /// ``` + /// /// ```ts,file=types.ts /// import type { bar } from "./qux.ts"; /// From 0f4e413a9f3aa1da9d078fd57bf3ec7be9ada2c9 Mon Sep 17 00:00:00 2001 From: emi Date: Thu, 20 Nov 2025 03:27:49 -0500 Subject: [PATCH 10/12] Update naming in noImportCycles doc --- .../biome_js_analyze/src/lint/nursery/no_import_cycles.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs index 3d2d6e4ccfb4..9f879db51622 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs @@ -84,14 +84,14 @@ declare_lint_rule! { /// } /// ``` /// - /// ```js,file=foobar.js + /// ```js,file=foobaz.js /// export function foo() { - /// console.log("foobar"); + /// console.log("foobaz"); /// } /// - /// export * as bar from './foobar.js'; + /// export * as baz from './foobaz.js'; /// - /// import { bar } from './foobar.js'; + /// import { baz } from './foobaz.js'; /// ``` /// /// ```ts,file=types.ts From 00ed23de2609571c6ef9e1d7538e86c0fa14b584 Mon Sep 17 00:00:00 2001 From: emi Date: Thu, 20 Nov 2025 15:02:46 -0500 Subject: [PATCH 11/12] Update wording in noImportCycles docs and changeset to be clearer --- .changeset/icy-results-wonder.md | 1 + .../biome_js_analyze/src/lint/nursery/no_import_cycles.rs | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.changeset/icy-results-wonder.md b/.changeset/icy-results-wonder.md index d1bfaed7550c..d08f528e0d2f 100644 --- a/.changeset/icy-results-wonder.md +++ b/.changeset/icy-results-wonder.md @@ -19,3 +19,4 @@ export function example() { export * as Example from './example.js'; import { Example } from './example.js'; +``` diff --git a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs index 9f879db51622..b8ddc1706939 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs @@ -25,10 +25,9 @@ declare_lint_rule! { /// only go in a single direction, i.e. they don't point "back" to the /// importing file. /// - /// If a cycle is contained to a single file, i.e. a file imports from - /// itself, no warning is issued. This allows for encapsulation of - /// functions/variables into a namespace instead of using a static class - /// (triggers [no-static-only-class](https://biomejs.dev/linter/rules/no-static-only-class)). + /// However, files that import themselves are allowed, and the rule won't trigger for these use cases. + /// This allows for encapsulation of functions/variables into a namespace instead of using a + /// static class (triggers [noStaticOnlyClass](https://biomejs.dev/linter/rules/no-static-only-class)). /// /// :::note /// This rule is computationally expensive. If you are particularly From 92d3e18b051f91f2a08151164f8ab8e3da8c271d Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Fri, 21 Nov 2025 10:59:53 +0000 Subject: [PATCH 12/12] Update .changeset/icy-results-wonder.md --- .changeset/icy-results-wonder.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/icy-results-wonder.md b/.changeset/icy-results-wonder.md index d08f528e0d2f..0b6c4a62de79 100644 --- a/.changeset/icy-results-wonder.md +++ b/.changeset/icy-results-wonder.md @@ -6,8 +6,8 @@ Fixed [#6569](https://github.com/biomejs/biome/issues/6569): Allow files to expo This means the following is now allowed: -**example.js** ```js +// example.js export function example() { return 1; } @@ -20,3 +20,4 @@ export * as Example from './example.js'; import { Example } from './example.js'; ``` +```