Strengths and Limitations of Knip for Unused Code Detection in Angular

Marcin Licznerski from Iterative Engineering
Marcin Licznerski Senior Software Engineer @ Iterative Engineering

As the codebase grows and evolves, the cost of maintaining it becomes higher. In extreme cases, leading to situations where upkeep takes significantly more time and effort than new developments. While you cannot prevent balancing out of those proportions overtime, there are ways to mitigate the risk of ending up in an endless maintenance loop. One of such is identification and removal of unused or overhead code to decrease complexity.

This article will delve into the capabilities of Knip tool that identifies unused code in Angular based applications. It advertises itself as a tool that “finds unused files, dependencies and exports in your JavaScript and TypeScript projects. Less code and dependencies lead to improved performance, less maintenance and easier refactorings”. Gaining popularity in the JavaScript ecosystem based on NPM weekly downloads.

To fully understand Knip’s effectiveness, we will comprehensively evaluate it in real-world application. In the upcoming section, we will evaluate its performance in identifying unused code in the Adscore project, which we are developing and maintaining at Iterative. Additionally, we will analyze Knip’s ability to navigate complex code within a sandbox Angular project in a controlled environment. This two-pronged approach will check Knip’s versatility and provide insights into its capabilities in Angular codebases.

Real world project

Adscore allows users to categorize web traffic into distinct buckets like organic, paid, bot-generated, and low-quality. The app acts as a shield for ad budgets. The detailed comprehension guarantees that resources are directed towards the intended audience - real users - thereby maximizing campaign efficiency and return on investment.

Projects Statistics

The following statistics provide an idea of the size of the Adscore system.

LanguageFilesLines of code
TypeScript24826995
SCSS11216535
HTML7812822

The total count of Components, Pipes, and Directives is 74.

Ignore File

After several iterations due to Knip incorrectly detecting in-use packages, following ignore file was created:

{
 "angular": {
    "config": ["angular.json"]
  },
  "ignore": [
    // both files used in angular.json file
    "Karma.conf.js",
    "src/polyfills.ts",
  ],
  "ignoreDependencies": [
    // used in angular.json
    "@angular-eslint/schematics",
    "@angular-eslint/template-parser",
    "eslint-config-prettier",
    "jasmine-core",
    "Karma-chrome-launcher", // used by Karma, several more plugins enlisted but removed for brevity
    // imported in global styles.scss, Knip lacks Sass plugin at the time of writing
    "loaders.css",
    "husky", // used in package.json
    "pretty-quick", // used by husky
    "simple-line-icons" // used in custom script
  ],
}

Some dependencies are used in the angular.json file, some in .sass files. This is something that can be improved in the plugins used by Knip.

Knip Scan Results Summary

Knip created following report after creation of ignore file (Knip provides list of unused components for each section, those details are removed for brevity):

Unused files (11)
Unused dependencies (3)
Unused devDependencies (5)
Unused exports (10)
Unused exported types (1)
Configuration issues (1)

In total, Knip allowed us to detect and remove 435 lines of code (which is 1.61% of total LOC) and 16 files.

While integrating Knip with our Angular project, optimizing the knip.jsonc file required multiple iterations to ensure accurate detection of unused code. However, this configuration effort was a one-time investment; moving forward, we can seamlessly integrate Knip into our post-refactoring workflow for recurring analysis. While inclusion in the automated pipeline is pending further evaluation, Knip has already proven its potential as a valuable tool for our codebase optimization. Despite Knip’s detection capabilities, we identified additional unused assets that require further investigation. We’ll be exploring this scenario within a controlled sandbox environment to gain deeper insights.

Sandbox Project

A project containing unused code has been prepared. The objective is to identify all unused cases, which are:

  1. 7 class properties
  2. 10 class methods
  3. 2 interface properties
  4. 2 variables
  5. 1 constant
  6. 5 classes (Angular’s: Component, Directive, Module, Pipe, Service)
  7. 1 dependency (@angular/forms - installed by default but unused in the project, and express)
  8. 1 devDependency (express)
  9. 1 unused export (variable used locally)
  10. 2 NgRx Actions (functions)
  11. 1 NgRx Effect (class)
  12. 2 functions - one is unit tested
  13. 1 asset

Total: 36

Code is available at: https://github.com/IterativeEngineering/blog.angular-knip-unused-code-article

Intentional Challenges

Some of the parts of the code are directly unused, which should be easy to detect by the tool. Some are trickier like for example: an interface has a property, then the object implementing the interface, has the property defined, but property itself is unused as in the following snippet:

export interface InUseFeatureState {
  definedButUnusedState: string;
}

const initialState: InUseFeatureState = {
  definedButUnusedState: 'unused',
}

Ideally, an unused interface property should be detected on a first run. But iteratively also will do the job:

  1. Run the tool
  2. Identify and remove the detected code
  3. Run the tool again
  4. Repeat until no more unused code is detected

Analyzing Knip Results

Tested with following versions:

  • Angular - v17.0.5
  • Knip - v5.0.0

Command used:

npx knip --include classMembers

Since Knip version 4, class members are opted out by default, but given Angular’s heavy class usage, enabling it seemed beneficial.

Result of the first Knip scan (details of each section removed for brevity):

Unused files (6)
Unused dependencies (3)
Unused devDependencies (8)
Unlisted binaries (2)
Unused exports (4)
Unused exported class members (16)

Let’s review each section’s outcome individually.

Unused files

NoFileDetectionRemarks
1unused-function.tsCorrectEven when in barrel file (index.ts) Knip was able to detect this file correctly.
2unused.service.tsCorrectService is exported but it is unused
3unused-type.tsCorrectTypes are exported but not used anywhere in the code
4unused.util.tsCorrectUtil is exported but not used anywhere in the code
5unused-feature.module.tsCorrectModule is exported but not used anywhere in the cod
6.eslintrc.jsIncorrectUsed by ES Lint

Detected: 5 files of 6 were detected correctly.

Unused dependencies

NoDependencyDetectionRemarks
1@angular/compilerCorrectNot directly used in the project, but for example @angular-devkit/build-angular depends on it, so it will be installed anyway in package-lock.json
2@angular/formsCorrectForms are installed by Angular by default, but this test project does not use them.
3@angular/platform-browser-dynamicCorrectNot directly used in the project but other Angular liblaries depends on it, will be installed anyway in package-lock.json
4unused.util.tsCorrectUtil is exported but not used anywhere in the code
5unused-feature.module.tsCorrectModule is exported but not used anywhere in the code
6.eslintrc.jsIncorrectUsed by ES Lint

Detected: 1 of 1 – 2 other dependencies have been detected as unused, but are used by Angular, won’t be included in the final score.

Unused devDependencies

NoDependencyDetectionRemarks
1expressCorrectUnused in the project
2huskyIncorrectUsed in package.json
3jasmine-coreIncorrectInstalled by Angular and used during testing, but can be removed because it will be installed as a sub-dependency
4karma-chrome-launcherIncorrectInstalled by Angular, cannot run tests without it
5karma-coverageIncorrectCan be used by Karma to generate coverage report, installed by default by Angular
6karma-jasmineIncorrectUsed by Karma
7karma-jasmine-html-reporterIncorrectUsed by Karma
8lint-stagedIncorrectUsed by Husky

Detected: 1 of 1 - All detected but with false positives.

Unlisted binaries

NoBinaryDetectionRemarks
1eslintIncorrectListed as script in package.json and run by lint-staged
2prettierIncorrectListed in lint-staged script

Detected: 0 of 0 - Did not expect to find any binaries, as the code was not prepared for this case. Anyway all were false positives.

Unused exports

NoExportFileDetectionRemarks
1UNUSED_CONSTANTconstants.tsCorrectConstant is exported but unused
2unusedAction1in-use-feature.actions.tsCorrectAction is exported but unused
3unusedSelector1in-use-feature.selectors.tsCorrectSelector is exported but unused
4rootStatein-use-feature.selectors.tsCorrectSelector is exported but only used locally

Detected: 4 of 4 - Found one expected export and 3 unused Actions and Selectors.

Unused exported class members

The biggest part of the Knip result, and most likely the biggest part of any Angular application.

NoClass MemberFileDetectionRemarks
1unusedEffect$InUseFeatureEffectsCorrect 
2unusedEmitActionInUseFeatureStateFacadeCorrect 
3unusedPropertyInUseFeatureStateFacadeCorrect 
4unusedPropertyInUseComponentCorrect 
5unusedMethodInUseFeatureStateFacadeCorrect 
6unusedPublicMethodInUseComponentCorrect 
7unusedPropertyUnusedComponentCorrect 
8inUseMethodUnusedComponentCorrectPartially correct, because component is unused, but this variable is used in HTML (which was not recognized)
9unusedPublicMethodUnusedComponentCorrect 
10inUsePropertyUnusedComponentCorrectPartially correct, because component is unused, but this variable is used in HTML (which was not recognized)
11unusedMethodInUseServiceCorrect 
12inUseMethodAppComponentIncorrectUsed in HTML file
13inUseAction1Effect$InUseFeatureEffectsIncorrectUsed by NgRx
14inUsePropertyInUseComponentIncorrectUsed in HTML file
15inUseStoreProperty$InUseComponentIncorrectUsed in HTML file
16inUseMethodInUseComponentIncorrectUsed in HTML file

Detected: 11 of 17

Summary of first run

In its initial run, Knip successfully identified 22 out of 36 cases, demonstrating a promising detection rate. However, it’s important to note that 14 of the findings were classified as false positives, highlighting the need for further refinement.

Code removed after the first run can be found in clean-up-after-knip-v5-detection branch.

Subsequent launches

Knip’s second run yielded an additional 2 detections:

  1. inUseActionInUnusedEffect
  2. unusedSelector2

Total identified cases: 24 out of 36. Subsequent launches did not detect any additional code.

Missed Parts

Let’s check which scenarios were not detected:

  1. UnusedPipe - most probably not found due to being defined in Angular Module which is in use
  2. UnusedPath - same as above
  3. UnusedComponent - same as above
  4. UnusedDirective - same as above
  5. unusedUtilButTestedFunction - function is unused, but unit tested - most probably because Knip does not have (unlike Jest) plugin for Jasmine it recognized this function as in use
  6. unusedPrivateProperty - Haven’t been detected, but ESLint or IDE’s like Visual Studio Code are capable to mark this case as unused
  7. unusedPrivateProperty2 - same as above
  8. unusedPrivateMethod - same as above
  9. definedButUnusedState - same as above
  10. unusedState - same as above
  11. unused.svg - looks like assets files were not checked at all

A Summary of Knip’s Efficiency with Angular

Understanding Knip’s capabilities and limitations is crucial for leveraging its power in Angular. Let’s explore its current performance, areas for improvement, and practical tips to optimize your code analysis process.

Strengths:

  • Detects unused functions and exports efficiently
  • Can help reduce code complexity and improve maintainability

Weaknesses:

  • May require multiple iterations for complex projects
  • In the context of Angular:
    • Cannot find unused Components, Pipes etc if defined in Module because, and currently does not analyze HTML templates
    • Can detect unused Actions/Selectors (NgRx) but not Effects/Reducers
    • Detects class properties used in HTML templates as unused
    • Doesn’t fully support Angular’s default test framework (Jasmine)
    • May misinterpret certain devDependencies as unused
    • Dependencies used only in Sass files are falsely detected as unused
    • Assets are not checked
  • Package.json scripts: limited detection in scripts, it didn’t detect Husky and lint-staged
  • Unit testing: May not detect unused code if tested with unsupported test frameworks

Tips for Efficient Usage:

  • Review and remove unused Angular’s router paths before running Knip to improve Module detection.
  • Consider alternative test frameworks supported by Knip for broader detection - Since Angular 16, Jest has experimental support
  • Be aware of limitations specific to your technology stack and adjust workflow accordingly.

Additional Notes:

  • Knip’s Angular support is evolving, so future updates might address some limitations
  • Explore alternative code analysis tools that might complement Knip and address its shortcomings
By clicking “Accept”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.