Skip to content

Add circular dependencies as opt-in cycles issue type (resolve #1734)#1812

Merged
webpro merged 9 commits into
mainfrom
circular-dependencies
Jul 7, 2026
Merged

Add circular dependencies as opt-in cycles issue type (resolve #1734)#1812
webpro merged 9 commits into
mainfrom
circular-dependencies

Conversation

@webpro

@webpro webpro commented Jun 24, 2026

Copy link
Copy Markdown
Member

tl/dr; Try with knip --cycles --reporter cycles:

$ knip --cycles --reporter cycles

Circular dependencies (1)

src/i18n/index.ts:1:28
└── import ./middleware → src/i18n/middleware.ts:3:15
    └── re-export ../core/i18n/handler → src/core/i18n/handler.ts:5:10
        └── import ../../i18n → src/i18n/index.ts:1:28 ↩

Detect circular dependencies across the whole module graph, reported as the opt-in cycles issue type. Enable with --cycles, --include cycles or {"include": ["cycles"]}; defaults to the warn rule. Set rules.cycles to error to fail on them.

Detection is a single-pass back-edge DFS (findAllCycles) over runtime (non-type-only) edges, emitting one cycle per back-edge. Reuses the issue-type machinery: filters, rules, every reporter, counters, exit code.

In the default report each cycle is one row: the route from the anchor file, paths relative to the root. The dedicated cycles reporter prints a verbose multi-line tree per cycle.

Updated docs at https://circular-dependencies.knip.pages.dev/features/reporters#cycles

Detect circular dependencies across the whole module graph, reported as the
opt-in `cycles` issue type. Enable with `--cycles`, `--include cycles` or
`{"include": ["cycles"]}`; defaults to the `warn` rule (set `rules.cycles` to
`error` to fail on them).

Detection is a single-pass back-edge DFS (`findAllCycles`) over runtime
(non-type-only) edges, emitting one cycle per back-edge. Reuses the issue-type
machinery — filters, rules, every reporter, counters, exit code.

In the default report each cycle is one row: the route from the anchor file,
paths relative to the root. The dedicated `cycles` reporter prints a verbose
multi-line tree per cycle.
@pkg-pr-new

pkg-pr-new Bot commented Jun 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/knip@1812
npm i https://pkg.pr.new/@knip/language-server@1812
npm i https://pkg.pr.new/@knip/mcp@1812

commit: faff8b7

@kachkaev

Copy link
Copy Markdown
Contributor

👋 @webpro, great stuff! Excited to try this new feature shortly!

Just to make sure I've understood it: would {"include": ["cycles"]} check only circular deps, or everything by default plus the circular deps? I would like to keep all my knip checks in a single run, but --include and --reporter cycles suggest that we'll need two of them? If it's the latter, what's the reason for checking circurlar deps on their own?

@webpro

webpro commented Jun 24, 2026

Copy link
Copy Markdown
Member Author
  • --include cycles → include the cycles issue type
  • --cycles → shorthand to include only cycle issue type (like --files)
  • --reporter cycles → print only cycles, tree-style with full clickable paths + position

webpro added 7 commits July 4, 2026 21:12
Collect the require.context directory glob during static analysis and
resolve + glob it in the build phase, so aliased directories (tsconfig
`paths`) are picked up as entry files. Register the visitor for rspack,
not only webpack.
Resolve tsconfig `paths` and Vite `resolve.alias` at the head of glob
patterns - wildcard, non-wildcard, and multi-target - while keeping
relative and negated patterns working, and resolve root-relative
patterns against the Vite root.
Storybook processes imports in `.mdx` docs via `@storybook/addon-docs`
(bundled in `@storybook/addon-essentials` before v8). Register the
shared MDX compiler when either is present, so helpers imported only
from MDX docs are no longer reported as unused exports/files.
Both options reference local asset files relative to the config file.
Mark them as used so they are not reported as unused files.
A `babel-jest` transformer may carry an inline Babel config
(presets/plugins) in its options. Resolve those the same way the
Babel plugin does, so they are not reported as unused dependencies.
# Conflicts:
#	packages/knip/src/constants.ts
Dynamic `import()` edges are excluded from cycle detection by default: a deferred
import doesn't cause the synchronous initialization hazard cycles are meant to
catch, and making an edge dynamic is the standard way to break a cycle. Set
`cycles.dynamicImports` to include them; they render with the `dynamic import`
edge kind in the `cycles` reporter.

Consolidate cycle configuration under a single `cycles` key holding
`dynamicImports` and `allow` (accept known cycles by exact path, replacing
`ignoreCycles.paths`). `allow` signals an accepted-cycles allowlist and avoids
collision with the glob-taking `ignore*` options.
@Zamiell

Zamiell commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Interesting feature! Historically, the ecosystem has used the import-x/no-cycle ESLint rule to detect this. Can you elaborate a bit on what the motivation was to have it in Knip?

Also, I think it would be nice to add to the docs a discussion of the no-cycle rule. Specifically: in what situations would you want to use the ESLint rule over Knip and vice versa?

@lishaduck

lishaduck commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Knip has already created a module-graph as part of, well, existing. eslint-plugin-import is slow, incompatible with ESLint's caching (though so is TSESLint), is heavy, and generally doesn't support as much of of the ecosystem (e.g., it's not going to include MDX in the module graph). (That's not meant as a criticism of eslint-plugin-import(-x), it's fundamentally a limitation of how ESLint processes files)

@webpro

webpro commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

Next to what Eli lays out, I can add that it was fun to build and it was already one of the existing operations on the module graph (i.e. before this PR, available in the Editor Extension). The heavy lifting is done building the module graph, such ops are then relatively cheap. By design such graph-based ops spanning multiple files fit Knip better than any file-based linter.

Integrated into Knip also means affordances like support for monorepos/workspaces (including workspace filtering), non-standard file support, etc. etc. as cycles would be just another issue type.

Btw we can add the ESLint rule to https://knip.dev/reference/related-tooling#circular-dependencies when we're at it.

# Conflicts:
#	packages/knip/fixtures/plugins/typedoc/custom.js
#	packages/knip/src/typescript/resolve-module-names.ts
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 6, 2026

Copy link
Copy Markdown

Deploying knip with  Cloudflare Pages  Cloudflare Pages

Latest commit: faff8b7
Status: ✅  Deploy successful!
Preview URL: https://8e18cbe7.knip.pages.dev
Branch Preview URL: https://circular-dependencies.knip.pages.dev

View logs

@kachkaev

kachkaev commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

--reporter cycles → print only cycles, tree-style with full clickable paths + position

Is there a way to have this reporter while running other checks too? I am trying to have only one run of knip in CI which does what it used to plus checks for cycles. I'm a bit confused by what changes to make in knip.config.ts. It also seems like includes: ["cycles"] removes the default checks - not sure have to keep the defaults.

@webpro

webpro commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

In the default report each cycle is one row: the route from the anchor file, paths relative to the root. The dedicated cycles reporter prints a verbose multi-line tree per cycle.

I think you can use multiple reporters if you want. Try it out.

@webpro
webpro merged commit 1b0ed6b into main Jul 7, 2026
61 checks passed
@webpro
webpro deleted the circular-dependencies branch July 7, 2026 08:13
@webpro

webpro commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

🚀 This pull request is included in v6.25.0. See Release 6.25.0 for release notes.

Using Knip in a commercial project? Please consider becoming a sponsor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants