diff --git a/crates/oxc_linter/fixtures/import/cycles/typescript/ts-types-depth-two.ts b/crates/oxc_linter/fixtures/import/cycles/typescript/ts-types-depth-two.ts new file mode 100644 index 0000000000000..e4a0b9c3e088c --- /dev/null +++ b/crates/oxc_linter/fixtures/import/cycles/typescript/ts-types-depth-two.ts @@ -0,0 +1,2 @@ +// @ts-ignore +import type { foo } from '../es6/depth-one'; diff --git a/crates/oxc_linter/fixtures/import/cycles/typescript/ts-types-only-importing-multiple-types.ts b/crates/oxc_linter/fixtures/import/cycles/typescript/ts-types-only-importing-multiple-types.ts new file mode 100644 index 0000000000000..7cffbb5b875b1 --- /dev/null +++ b/crates/oxc_linter/fixtures/import/cycles/typescript/ts-types-only-importing-multiple-types.ts @@ -0,0 +1,2 @@ +// @ts-ignore +import { type foo, type bar } from '../depth-zero'; diff --git a/crates/oxc_linter/fixtures/import/cycles/typescript/ts-types-only-importing-type.ts b/crates/oxc_linter/fixtures/import/cycles/typescript/ts-types-only-importing-type.ts new file mode 100644 index 0000000000000..c3b016f823e5c --- /dev/null +++ b/crates/oxc_linter/fixtures/import/cycles/typescript/ts-types-only-importing-type.ts @@ -0,0 +1,2 @@ +// @ts-ignore +import type { foo } from '../depth-zero'; diff --git a/crates/oxc_linter/fixtures/import/cycles/typescript/ts-types-some-type-imports.ts b/crates/oxc_linter/fixtures/import/cycles/typescript/ts-types-some-type-imports.ts new file mode 100644 index 0000000000000..dff253bb3d920 --- /dev/null +++ b/crates/oxc_linter/fixtures/import/cycles/typescript/ts-types-some-type-imports.ts @@ -0,0 +1,3 @@ +// @ts-ignore + +import { foo, type BarType } from '../depth-zero'; diff --git a/crates/oxc_linter/src/rules/import/no_cycle.rs b/crates/oxc_linter/src/rules/import/no_cycle.rs index b677d09ac7d76..87a3ca01b9247 100644 --- a/crates/oxc_linter/src/rules/import/no_cycle.rs +++ b/crates/oxc_linter/src/rules/import/no_cycle.rs @@ -26,6 +26,8 @@ struct NoCycleDiagnostic(#[label] Span, String); pub struct NoCycle { /// maximum dependency depth to traverse max_depth: u32, + /// ignore type only imports + ignore_types: bool, /// ignore external modules #[allow(unused)] ignore_external: bool, @@ -38,6 +40,7 @@ impl Default for NoCycle { fn default() -> Self { Self { max_depth: u32::MAX, + ignore_types: false, ignore_external: false, allow_unsafe_dynamic_cyclic_dependency: false, } @@ -82,6 +85,10 @@ impl Rule for NoCycle { .and_then(serde_json::Value::as_number) .and_then(serde_json::Number::as_u64) .map_or(u32::MAX, |n| n as u32), + ignore_types: obj + .and_then(|v| v.get("ignoreTypes")) + .and_then(serde_json::Value::as_bool) + .unwrap_or_default(), ignore_external: obj .and_then(|v| v.get("ignoreExternal")) .and_then(serde_json::Value::as_bool) @@ -140,6 +147,11 @@ impl NoCycle { for module_record_ref in &module_record.loaded_modules { let resolved_absolute_path = &module_record_ref.resolved_absolute_path; + let was_imported_as_type = + &module_record_ref.import_entries.iter().all(|entry| entry.is_type); + if self.ignore_types && *was_imported_as_type { + continue; + } if !state.traversed.insert(resolved_absolute_path.clone()) { continue; } @@ -205,6 +217,18 @@ fn test() { r#"import { foo } from "./es6/depth-one-dynamic"; // #2265 4"#, Some(json!([{"allowUnsafeDynamicCyclicDependency":true}])), ), + ( + r#"import { foo } from "./typescript/ts-types-only-importing-type";"#, + Some(json!([{"ignoreTypes":true}])), + ), + ( + r#"import { foo } from "./typescript/ts-types-only-importing-multiple-types";"#, + Some(json!([{"ignoreTypes":true}])), + ), + ( + r#"import { foo } from "./typescript/ts-types-depth-two";"#, + Some(json!([{"ignoreTypes":true}])), + ), // Flow not supported // (r#"import { bar } from "./flow-types""#, None), // (r#"import { bar } from "./flow-types-only-importing-type""#, None), @@ -309,6 +333,11 @@ fn test() { // (r#"import { bar } from "./flow-types-depth-one""#, None), (r#"import { foo } from "./intermediate-ignore""#, None), (r#"import { foo } from "./ignore""#, None), + (r#"import { foo } from "./typescript/ts-types-only-importing-type";"#, None), + ( + r#"import { foo } from "./typescript/ts-types-some-type-imports";"#, + Some(json!([{"ignoreTypes":true}])), + ), ]; Tester::new(NoCycle::NAME, pass, fail) diff --git a/crates/oxc_linter/src/snapshots/no_cycle.snap b/crates/oxc_linter/src/snapshots/no_cycle.snap index 15ba125ddd9c6..f51099a469201 100644 --- a/crates/oxc_linter/src/snapshots/no_cycle.snap +++ b/crates/oxc_linter/src/snapshots/no_cycle.snap @@ -253,3 +253,21 @@ expression: no_cycle help: These paths form a cycle: -> ./ignore - fixtures/import/cycles/ignore/index.js -> ../depth-zero - fixtures/import/cycles/depth-zero.js + + ⚠ eslint-plugin-import(no-cycle): Dependency cycle detected + ╭─[cycles/depth-zero.js:1:21] + 1 │ import { foo } from "./typescript/ts-types-only-importing-type"; + · ─────────────────────────────────────────── + ╰──── + help: These paths form a cycle: + -> ./typescript/ts-types-only-importing-type - fixtures/import/cycles/typescript/ts-types-only-importing-type.ts + -> ../depth-zero - fixtures/import/cycles/depth-zero.js + + ⚠ eslint-plugin-import(no-cycle): Dependency cycle detected + ╭─[cycles/depth-zero.js:1:21] + 1 │ import { foo } from "./typescript/ts-types-some-type-imports"; + · ───────────────────────────────────────── + ╰──── + help: These paths form a cycle: + -> ./typescript/ts-types-some-type-imports - fixtures/import/cycles/typescript/ts-types-some-type-imports.ts + -> ../depth-zero - fixtures/import/cycles/depth-zero.js