Handling Issues
A long report can be frustrating. The list may contain false positives, but also tons of useful information. To get the most value out of Knip, it may require some initial configuration.
This guide helps you deal with false positives to find solutions and create the perfect report, with minimal configuration that will keep your project tidy.
If you start out using Knip in a large project and have a long report, it makes sense to go over the issue types one by one. For instance, reducing the number of unused files will also reduce the number of unused dependencies.
Unused files
Getting the list of unused files right trickles down into the other issue types
as well, so we start here. Files are reported as unused if they are in the set
of project
files, but not in the set of files resolved from the entry
files:
unused files = project files - (entry files + resolved files)
Let’s go over common causes for having to add entry
patterns manually:
- Dynamic import specifiers
- Unsupported arguments in scripts
- Unsupported file formats
- Missing plugin
- Incomplete plugin
- Integrated monorepos
- Build artifacts and ignored files
- Auto-mocking or auto-imports
Use --files
to filter the report and focus only on unused files:
knip --files
This works with other issue types as well. For instance, use --dependencies
to
focus only on dependencies and exclude issues related to unused files and
exports.
Dynamic import specifiers
Dynamic import specifiers aren’t resolved, such as:
const entry = await import(path.join(baseDir, 'entry.ts'));
Solution: add entry.ts
to entry
patterns.
Unsupported arguments in scripts
Some tooling command arguments aren’t recognized:
{ "name": "my-lib", "version": "1.0.0", "scripts": { "build": "unknown-build-cli --entry production.ts" }}
Solution: add production.ts
to entry
patterns.
This works the same for any script, also those in GitHub Actions workflows or Git hooks. See script parser for more details about Knip’s script parser.
Unsupported file formats
Entry files referenced in HTML files (e.g. <script src="production.js">
).
<html> <body> <script type="module" src="production.js"></script> </body></html>
Solution: add production.js
to entry
patterns. Or add an .html
compiler to extract and resolve the value of <script src>
elements.
Knip has support for some popular framework formats through compilers, and
additional compilers can be added for for any file type. The recommended
solution is usually to add the file as shown in each example as an entry
file.
Missing plugin
You might be using a tool or framework for which Knip doesn’t have a plugin (yet). Configuration and entry files (and related dependencies) may be reported as unused because there is no plugin yet that would include those files. Two examples:
- Configuration file
tool.config.js
contains a reference to the package"@tool/plugin"
→ both the file and the dependency are reported as unused. - A framework automatically imports all files matching
src/models/*.ts
→ those files are reported as unused.
Solution: create a new plugin for the tool or framework that’s not in
the list yet. Or work around it and add entry
patterns and maybe ignore a
dependency or two (using ignoreDependencies
).
Incomplete plugin
Files may be reported as unused if existing plugins do not include that entry file pattern yet. See the plugins section of entry files for more details.
Solution: override plugin configuration to customize default patterns for existing plugins. Or even better: send a pull request to improve the plugin.
Integrated monorepos
Multiple instances of configuration files like .eslintrc
and
jest.config.json
across the repository may be reported as unused when working
in a (mono)repo with a single package.json
.
Solution: see integrated monorepos for more details and how to configure plugins to target those configuration files.
Build artifacts and ignored files
In rare cases, build artifacts and .gitignore
files may have a surprising
effects on files reported as unused. Results may be different in separate runs,
depending on the presence of build artifacts.
Solution: Knip tries to do the right thing, but in some cases you may need
to add a file to the entry
file patterns manually for better or more
consistent results.
Auto-mocking or auto-imports
Some frameworks have features like “auto-mocking” or “auto-imports” enabled, such as Jest and Nuxt.
Solution: include such entry files by extending the entry
file patterns.
This is recommended in most cases:
{ "entry": ["src/index.ts", "src/models/*.ts"]}
Alternatively, exceptions and outliers can be excluded from the analysis using
negated project
patterns:
{ "project": ["src/**/*.ts", "!**/__mocks__/**"]}
Unused dependencies
Dependencies imported in unused files are reported as unused dependencies.
That’s why it’s strongly recommended to try and remedy unused files first.
Better entry
and project
file coverage will solve many cases of reported
unused dependencies.
The most common causes for unused dependencies include:
Use --dependencies
to filter the report and focus only on issues related
to dependencies:
knip --dependencies
Missing or incomplete plugin
If a plugin exists and the dependency is referenced in the configuration file, but its custom dependency finder does not detect it, then that’s a false positive. Please open a pull request or issue to fix it.
Solution: adding the configuration file as an entry
file pattern may be a
temporary stopgap that fixes your situation, but it’s better to create a new
plugin or fix an existing one.
Unrecognized reference
Sometimes a reference to a dependency is unrecognizable or unreachable to Knip, so it’s a false positive and incorrectly reported as unused.
Solution: add a new plugin or improve an existing one. If you don’t feel like a plugin could solve it, a last resort is to use ignoreDependencies.
If a binary (or “executable”) is referenced you’ll want to use ignoreBinaries
instead. See unlisted binaries.
Type Definition Packages
For packages that have types integrated in the same package, the separate types package is reported as unused.
This is also useful for future regressions: if a package had a DT package for
its types before and later on integrates those types into its own package, Knip
will start reporting the types dependency as unused. Examples include Webpack v5
and React Router v6 rendering the @types/webpack
and @types/react-router
dependencies obsolete since those versions.
Solution: remove the types dependency (usually @types
scoped DT package).
Unlisted dependencies
This means that a dependency is referenced directly in source code or
configuration, but not listed in package.json
.
An unlisted dependency is usually a transitive dependency that’s imported or
referenced directly. The dependency is installed (since it’s a dependency of
another dependency) and lives in node_modules
, but it’s not listed explicitly
in package.json
.
You should not rely on transitive dependencies for various reasons, including control, security and stability.
Solution: install and list the dependency in dependencies
or
devDependencies
.
Unlisted binaries
Binaries are executable Node.js scripts. Some npm packages, when installed, add
one or more executable files to use from scripts in package.json
. Examples
include TypeScript that comes with the tsc
binary, ESLint comes with eslint
,
Next.js with next
, and so on.
Knip detects such binaries in scripts and checks whether there’s a package
installed that includes that binary. It looks up the bin
field in the
package.json
file of installed packages. If it doesn’t find it, it will be
reported as an unlisted binary as there is no package listed that contains it.
Binaries that are installed on the OS already and not thus likely not meant to be installed from npm are not reported as unlisted (details: list of ignored binaries in source).
Missing binaries
In case unused (dev) dependencies look like a match against unlisted binaries,
then this might be caused by node_modules
not containing the packages. And
this might have been caused by either the way your package manager installs
dependencies and binaries, or by not running Knip from the root of the
repository.
Solution: run Knip from the project root. From there you can lint individual workspaces.
Sometimes binaries and how they’re reported can be a bit confusing. See this example:
{ "name": "lib", "scripts": { "commitlint": "commitlint --edit" }, "devDependencies": { "@commitlint/cli": "*" }}
This example works fine without anything reported, as the @commitlint/cli
package includes the commitlint
binary. However, some script may contain
npx commitlint
and here Knip assumes commitlint
is the name of the package.
This technically works, as commitlint
is a transitive dependency of
@commitlint/cli
. However, correct and recommeded usage is by the package name:
npx @commitlint/cli
.
Unresolved imports
Knip may be unable to resolve an import specifier or dependency reference.
Solution: First look for solutions such as paths or try relative
imports. Otherwise, use ignoreUnresolved
as a last resort.
Unused exports
By default, Knip does not report unused exports of entry
files.
The most common causes for unused exports include:
Use the --exports
flag to filter and focus only on issues related to
exports:
knip --exports
Use includeEntryExports to report unused exports of entry files as well. This can be set per workspace.
Enumerations
For exports on an imported namespace, Knip considers all exports referenced if that namespace is used in certain patterns like destructuring and enumeration. Individual exports are then not reported.
Solution: include the nsExports
issue type to turn off the heuristic and
always consider exports on a imported namespaces individually.
See namepace imports to see all related patterns.
External libraries
Are the exports consumed or imported by an external library, resulting in a non-standard consumption of your exports? Here’s an example:
import loadable from '@loadable/component';
export const DynamicApple = dynamic(() => import('./components.js').then(mod => mod.Apple));
export const LoadableOrange = loadable(() => import('./components.js'), { resolveComponent: components => components.Orange,});
export const Apple = () => 'Apple';export const Orange = () => 'Orange';
Knip understands Apple
is used, since it’s standard usage. But Orange
is
referenced through a function of an external library. For performance reasons,
Knip does not include external type definitions by default so it won’t see the
export being referenced.
Solution: include the type definitions of external libraries with the —include-libs flag:
knip --include-libs
This comes at a performance and memory penalty, but should give better results if you need it. This flag is implied when classMembers are included (that feature comes with roughly the same performance penalty).
Exclude exports from the report
To exclude false positives from the report, there are a few options:
- Ignore exports used in file for exports used internally.
- Individual exports can be tagged using JSDoc syntax.
- Have the export in an entry file:
- Add the file to the
entry
file patterns array in the configuration. - Move the export(s) to an entry file.
- Add the file to the
exports
field ofpackage.json
- Add the file to the
- Re-export the unused export(s) from an entry file.
Missing unused exports?
Did you expect certain exports in the report, but are they missing? They might be exported from an entry file. In that case, use —include-entry-exports to make Knip also report unused exports in entry files.
The exports of non-standard extensions like .astro
, .mdx
, .vue
or
.svelte
are not available by default. See compilers for more details on
how to include them.
Class members
Unused members of exported classes are not reported by default, here’s how to enable them:
knip --include classMembers
This option is also available in the Knip configuration file. Note that this feature comes at a cost: linting will take more time and more memory.
Individual class members can be tagged using JSDoc syntax.
Classes exported from entry files are ignored, and so are their members. Use —include-entry-exports to make Knip also report members of unused exports in entry files.
Enum members
Unused enums and unused members of exported enums are reported by default. Reporting such members can be disabled:
knip --exclude enumMembers
Individual enum members can be tagged using JSDoc syntax.
Enums exported from entry files are ignored, and so are their members. Use —include-entry-exports to make Knip also report members of unused exports in entry files.
Feedback or false positives?
If you believe Knip incorrectly reports something as unused (i.e. there’s a false positive), feel free to create a minimal reproduction and open an issue on GitHub. It’ll make Knip better for everyone! Join the Discord channel to discuss any feedback or questions you may have.
ISC License © 2024 Lars Kappert