{"record":{"id":"cc5b83285e7454e9","repo":"oxc-project/oxc","slug":"dependency-cycle-detected","errorCode":null,"errorMessage":"Dependency cycle detected","messagePattern":"Dependency cycle detected","errorType":"validation","errorClass":"OxcDiagnostic","httpStatus":null,"severity":"error","filePath":"crates/oxc_linter/src/rules/import/no_cycle.rs","lineNumber":25,"sourceCode":"use cow_utils::CowUtils;\nuse oxc_diagnostics::OxcDiagnostic;\nuse oxc_macros::declare_oxc_lint;\nuse oxc_span::Span;\nuse oxc_str::CompactStr;\nuse rustc_hash::FxHashSet;\nuse schemars::JsonSchema;\nuse serde::Deserialize;\n\nuse crate::{\n    ModuleRecord,\n    context::LintContext,\n    module_graph_visitor::{ModuleGraphVisitorBuilder, ModuleGraphVisitorEvent, VisitFoldWhile},\n    rule::{DefaultRuleConfig, Rule},\n};\n\nfn no_cycle_diagnostic(span: Span, stack: &[(CompactStr, PathBuf)], cwd: &Path) -> OxcDiagnostic {\n    let cycle_description = format_cycle(stack, cwd);\n    OxcDiagnostic::warn(\"Dependency cycle detected\")\n        .with_help(\"Refactor to remove the cycle. Consider extracting shared code into a separate module that both files can import.\")\n        .with_note(format!(\"These paths form a cycle:\\n{cycle_description}\"))\n        .with_label(span)\n}\n\nfn self_referencing_cycle_diagnostic(span: Span, is_import: bool) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\"Dependency cycle detected\")\n        .with_help(if is_import {\n            \"Remove the self-referencing import.\"\n        } else {\n            \"Remove the self-referencing export and consider using a named export instead.\"\n        })\n        .with_label(span.primary_label(\"this module references itself\"))\n}\n\nfn format_cycle(stack: &[(CompactStr, PathBuf)], cwd: &Path) -> String {\n    let mut lines = Vec::with_capacity(stack.len() * 2 + 1);\n","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/oxc-project/oxc/blob/e1e7af627c8843ab64044ed466b128fcc21a035b/crates/oxc_linter/src/rules/import/no_cycle.rs#L7-L43","documentation":"Diagnostic from the oxlint rule import/no-cycle (restriction category). It reports that the current module can be reached again by following its own imports: the linter walks the resolved module graph and, when a path returns to the current file, flags the import statement that starts the cycle, attaching a note that prints the full cycle with resolved paths. Cycles are a real hazard — it is common to import an `undefined` binding because the other module has not finished evaluating. Options: `maxDepth` (default unlimited), `ignoreTypes` (default true, type-only imports ignored), `ignoreExternal` (default false), `allowUnsafeDynamicCyclicDependency` (default false).","triggerScenarios":"A imports B and B (directly or transitively) imports A; the diagnostic is emitted on A's first import statement of that chain, with the stack printed via format_cycle. Reported from run_once in crates/oxc_linter/src/rules/import/no_cycle.rs:25 when the module-graph visitor finds resolved_absolute_path equal to the current file.","commonSituations":"Barrel files (index.ts re-exports siblings that also import from the index); mutual imports created during refactoring (utils imports constants, constants imports utils); type-only cycles that developers expect to be ignored (covered by the default ignoreTypes:true only when `import type` is actually used).","solutions":["Break the cycle structurally: extract the shared code both modules need into a third module both import","Convert value imports that only carry types to `import type { ... }` — ignoreTypes defaults to true so those no longer count","Defer one direction with a dynamic `await import(...)` at the use site; if acceptable, set `allowUnsafeDynamicCyclicDependency: true`","For noisy node_modules or deep graphs, set `ignoreExternal: true` and/or a bounded `maxDepth`"],"exampleFix":"// before (dep-a.js)\nimport { b } from './dep-b.js';\nexport function a() { return b(); }\n// dep-b.js\nimport './dep-a.js';\nexport function b() { return 1; }\n\n// after (dep-b.js no longer imports dep-a.js)\nexport function b() { return 1; }","handlingStrategy":"validation","validationCode":"// independent pre-check with madge (catches cycles oxlint's resolver might miss)\n// npx madge --circular --extensions js,ts,tsx src/\n// .oxlintrc.json guardrails: { \"rules\": { \"import/no-cycle\": [\"warn\", { \"ignoreExternal\": true, \"maxDepth\": 50 }] } }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Avoid barrel index modules that siblings import back from; export directly instead","Use `import type` for type-only cross-references (ignoreTypes defaults to true)","Run madge --circular in CI as a second detector","When adding a back-edge feels necessary, prefer dynamic import() at the use site"],"tags":["lint","oxlint","imports","circular-dependency","module-graph","architecture"],"backgroundTag":"circular-dependency","analyzedSha":"e1e7af627c8843ab64044ed466b128fcc21a035b","analyzedAt":"2026-08-20T07:01:07.079Z","contentChangedAt":"2026-08-20T07:01:07.079Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}