Skip to content

🔄 knip fails to detect missing dependency declarations for sibling workspace packages when there is hoisting #1792

Description

@astegmaier

Prerequisites

Reproduction url

https://github.com/astegmaier/playground-knip-undetected-dependency-bug

Reproduction access

  • I've made sure the reproduction is publicly accessible

Good version

5.6.1 / 6.13.1 (see "Regression history" below)

Bad version

5.7.0 / 6.14.0 (see "Regression history" below)

Description of the regression

Knip fails to flag an undeclared sibling-workspace import, provided the install layout hoists sibling workspaces to root node_modules/ — i.e. npm workspaces or yarn berry with nodeLinker: node-modules. In --strict mode, it correctly flags the undeclared dependency.

The bug also surfaces in per-package mode (cd packages/consumer && knip), even with --strict. We use per-package invocation in our monorepo for the performance reasons discussed in #1642 / #1711 — knip's docs call it a "last resort" for slow CIs — but the root-invocation case demonstrates the bug independently.

Example

Given this simplified repro structure, installed with latest yarn (4.12.0) with nodeLinker: node-modules (i.e. hoisting)...

packages/
  fruit/
    src/index.js       # export const apple = 'apple';
    package.json       # name: "@scope/fruit"
  consumer/
    src/index.js       # import { apple } from '@scope/fruit'; ← undeclared usage
    package.json       # does NOT declare `@scope/fruit` as a dependency
package.json           # workspaces: ["packages/*"]
.yarnrc.yml            # nodeLinker: node-modules

...you'll get these results with the latest version of knip (6.16.1)...

Run knip from root — silently passes (BUG)

yarn knip
# ✂️  Excellent, Knip found no issues.

Run knip from root + --strict — correctly detects ✓

yarn knip --strict
# → Unlisted dependencies (1)
#     @scope/fruit  packages/consumer/src/index.js:1:10

Run knip from workspace package — silently passes (BUG)

cd packages/consumer
yarn knip
# ✂️  Excellent, Knip found no issues.

Run knip from workspace package + --strict — silently passes (BUG)

cd packages/consumer
yarn knip --strict
# ✂️  Excellent, Knip found no issues.

Regression history

I bisected this against npm-published versions using a minimal harness in the reproduction repo. The table below summarizes the results across all four invocations:

Range Run knip from root Run knip from root + --strict Run knip from workspace package Run knip from workspace package + --strict Trigger
2.x – 5.6.1 All patterns detect
5.7.0 – 5.16.x Release notes: "Start using resolve as the default module resolver"
5.17.0 – 6.13.1 A/B silently recovered (incidental side effect of a refactor); C/D still broken
6.14.0 – 6.16.1 A regressed again — commit e7122a1ae deliberately suppressed sibling-workspace imports in non-strict mode

Metadata

Metadata

Assignees

No one assigned

    Labels

    regressionSomething is no longer working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions