Skip to content

Commit 1b0ed6b

Browse files
authored
Add circular dependencies as opt-in cycles issue type (resolve #1734) (#1812)
1 parent 20cd970 commit 1b0ed6b

47 files changed

Lines changed: 830 additions & 62 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/docs/src/content/docs/features/reporters.md

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ Knip provides the following built-in reporters:
1010
- [`codeclimate`][1]
1111
- [`codeowners`][2]
1212
- `compact`
13-
- [`disclosure`][3]
14-
- [`github-actions`][4]
15-
- [`json`][5]
16-
- [`markdown`][6]
13+
- [`cycles`][3]
14+
- [`disclosure`][4]
15+
- [`github-actions`][5]
16+
- [`json`][6]
17+
- [`markdown`][7]
1718
- `symbols` (default)
1819

1920
Example usage:
@@ -54,16 +55,44 @@ $ knip --reporter codeclimate
5455
### CODEOWNERS
5556

5657
When a `.github/CODEOWNERS` file exists, each entry gains an `owners` array.
57-
Point the reporter at a different path through [`--reporter-options`][7]:
58+
Point the reporter at a different path through [`--reporter-options`][8]:
5859

5960
```sh
6061
knip --reporter json --reporter-options '{"codeowners":"docs/CODEOWNERS"}'
6162
```
6263

63-
For a typed object instead of JSON to parse, write a [custom reporter][8].
64-
Coding agents can also call Knip through the [MCP server][9], which returns
64+
For a typed object instead of JSON to parse, write a [custom reporter][9].
65+
Coding agents can also call Knip through the [MCP server][10], which returns
6566
structured results and configuration hints directly.
6667

68+
### Cycles
69+
70+
A verbose, multi-line tree view of [circular dependencies][11]. Each file path
71+
is suffixed with the location of the import that continues the cycle. Each edge
72+
shows the import kind and specifier, descends one file per level, and closes
73+
(``) back to the file it started from. Enable the `cycles` issue type (e.g.
74+
with `--cycles`) and select the reporter:
75+
76+
```text
77+
$ knip --cycles --reporter cycles
78+
79+
Circular dependencies (1)
80+
81+
src/i18n/index.ts:1:28
82+
└── import ./middleware → src/i18n/middleware.ts:3:15
83+
└── re-export ../core/i18n/handler → src/core/i18n/handler.ts:5:10
84+
└── import ../../i18n → src/i18n/index.ts:1:28 ↩
85+
```
86+
87+
Knip reports representative cycle paths found while walking the module graph.
88+
This is not an exhaustive list of every possible simple cycle in a cyclic
89+
subgraph, because that can produce a noisy and very large report. If the same
90+
file participates in multiple distinct reported paths, each path is shown
91+
separately. Dynamic imports are ignored by default because they are commonly
92+
used to avoid synchronous circular loads; set [`cycles.dynamicImports`][12] to
93+
include them. Repeated starting imports are grouped under one heading. Use
94+
[`cycles.allow`][12] to accept known cycle paths.
95+
6796
### Disclosure
6897

6998
This reporter is useful for sharing large reports. Groups of issues are rendered
@@ -169,9 +198,9 @@ every issue found in one file:
169198
| `owners` | `{ name }[]` | Code owners, only when a `CODEOWNERS` file is found |
170199
| _issue type_ | array | One key per enabled issue type (see below) |
171200

172-
Each entry carries a key for **every enabled [issue type][10]**, so the keys are
201+
Each entry carries a key for **every enabled [issue type][11]**, so the keys are
173202
the same across entries. An array is empty when that file has no issues of that
174-
type. Drop a type's key by disabling it with [filters or rules][11].
203+
type. Drop a type's key by disabling it with [filters or rules][13].
175204

176205
Issue-type items are objects with position info:
177206

@@ -183,7 +212,7 @@ Issue-type items are objects with position info:
183212
| `col` | `number?` | 1-based column |
184213
| `pos` | `number?` | Character offset |
185214

186-
See [Issue types][10] for the full set of issue-type keys.
215+
See [Issue types][11] for the full set of issue-type keys.
187216

188217
### Markdown
189218

@@ -311,12 +340,14 @@ knip --preprocessor ./preprocess.ts
311340

312341
[1]: #codeclimate
313342
[2]: #codeowners
314-
[3]: #disclosure
315-
[4]: #github-actions
316-
[5]: #json
317-
[6]: #markdown
318-
[7]: ../reference/cli.md#--reporter-options-json
319-
[8]: #custom-reporters
320-
[9]: ../reference/integrations.md
321-
[10]: ../reference/issue-types.md
322-
[11]: ./rules-and-filters.md
343+
[3]: #cycles
344+
[4]: #disclosure
345+
[5]: #github-actions
346+
[6]: #json
347+
[7]: #markdown
348+
[8]: ../reference/cli.md#--reporter-options-json
349+
[9]: #custom-reporters
350+
[10]: ../reference/integrations.md
351+
[11]: ../reference/issue-types.md
352+
[12]: ../reference/configuration.md#cycles
353+
[13]: ./rules-and-filters.md

packages/docs/src/content/docs/features/rules-and-filters.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ Knip has shortcuts to include only specific issue types.
5656

5757
3. The `--files` flag is a shortcut for `--include files`
5858

59+
4. The `--cycles` flag is a shortcut for `--include cycles` (circular
60+
dependencies, off by default)
61+
5962
## Rules
6063

6164
Use `rules` in the configuration to customize the issue types that count towards

packages/docs/src/content/docs/reference/cli.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,14 @@ Shortcut to include file issues:
233233
--include files
234234
```
235235

236+
### `--cycles`
237+
238+
Shortcut to include circular dependencies (off by default):
239+
240+
```sh
241+
--include cycles
242+
```
243+
236244
### `--tags`
237245

238246
Exports can be tagged with known or arbitrary JSDoc/TSDoc tags:

packages/docs/src/content/docs/reference/configuration.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,27 @@ reporting other issues in those same files.
386386
}
387387
```
388388

389+
### `cycles`
390+
391+
Configure circular dependency (`cycles`) detection.
392+
393+
Set `dynamicImports` to include dynamic `import()` edges, which are excluded by
394+
default (a dynamic import defers evaluation, so it does not cause the
395+
initialization hazard cycles are meant to catch).
396+
397+
Use `allow` to accept specific cycles by exact path while keeping other `cycles`
398+
issues enabled. Path entries are relative to the project root and omit the
399+
closing repeat of the first file.
400+
401+
```json title="knip.json"
402+
{
403+
"cycles": {
404+
"dynamicImports": true,
405+
"allow": [["src/i18n/index.ts", "src/i18n/middleware.ts"]]
406+
}
407+
}
408+
```
409+
389410
## Exports
390411

391412
### `ignoreExportsUsedInFile`

packages/docs/src/content/docs/reference/issue-types.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Knip reports the following types of issues:
2323
| Unused exported enum members | Unable to find a reference to this enum member | 🔧 | `enumMembers` |
2424
| Unused exported namespace members | Unable to find a reference to this namespace member | 🔧 | `namespaceMembers` |
2525
| Duplicate exports | This is exported more than once | | `duplicates` |
26+
| Circular dependencies | These files (in)directly import each other at runtime | 🟠 | `cycles` |
2627

2728
## Legend
2829

@@ -39,6 +40,8 @@ Knip reports the following types of issues:
3940
`optionalPeerDependencies`. In [rules][3], each key can be set individually.
4041
3. In [strict production mode][4], `devDependencies` are not included.
4142
4. The `types` issue type includes `enum`, `interface` and `type` exports.
43+
5. `cycles` defaults to the `warn` [rule][3] (reported, but not counted as an
44+
error). Set `rules.cycles` to `error` to fail on circular dependencies.
4245

4346
[1]: ../features/auto-fix.mdx
4447
[2]: ../features/rules-and-filters.md#filters

packages/docs/src/content/docs/reference/related-tooling.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,23 @@ values and types on imported namespaces.
2828

2929
## Circular dependencies
3030

31-
See [Circular Dependencies][5].
32-
33-
Other tools that support this include [DPDM][6], [Madge][7] and [skott][8].
31+
Knip detects circular dependencies as an opt-in [`cycles`][5] issue type, and
32+
the [editor integrations][6] visualize them inline. Other dedicated tools
33+
include [DPDM][7], [Madge][8] and [skott][9].
3434

3535
## Cleanup
3636

37-
The [e18e.dev][9] website and in particular the [Cleanup][10] section is a great
38-
resource when dealing with technical debt.
37+
The [e18e.dev][10] website and in particular the [Cleanup][11] section is a
38+
great resource when dealing with technical debt.
3939

4040
[1]: https://eslint.org
4141
[2]: https://biomejs.dev/linter/
4242
[3]: https://oxc.rs/docs/guide/usage/linter.html
4343
[4]: https://github.com/webpro-nl/remove-unused-vars
44-
[5]: ./integrations.md#circular-dependencies
45-
[6]: https://github.com/acrazing/dpdm
46-
[7]: https://github.com/pahen/madge
47-
[8]: https://github.com/antoine-coulon/skott
48-
[9]: https://e18e.dev
49-
[10]: https://e18e.dev/guide/cleanup.html
44+
[5]: ./issue-types.md
45+
[6]: ./integrations.md
46+
[7]: https://github.com/acrazing/dpdm
47+
[8]: https://github.com/pahen/madge
48+
[9]: https://github.com/antoine-coulon/skott
49+
[10]: https://e18e.dev
50+
[11]: https://e18e.dev/guide/cleanup.html
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"cycles": {
3+
"allow": [["apricot.ts", "banana.ts"]]
4+
}
5+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { banana } from './banana';
2+
3+
export const apricot = () => banana();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { apricot } from './apricot';
2+
3+
export const banana = () => apricot();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { lemon } from './lemon';
2+
3+
export const citrus = () => lemon;

0 commit comments

Comments
 (0)