cargo-shear 1.13.2

Detect and fix unused/misplaced dependencies from Cargo.toml
Documentation
# Cargo Shear ✂️ 🐑

Detect and fix issues in Rust projects:

- **Unused dependencies** in `Cargo.toml`
- **Misplaced dependencies** (dev/build dependencies in wrong sections)
- **Unlinked source files** (Rust files not reachable from any module tree)

> [!NOTE]
> This tool is considered feature-complete. We continue to welcome contributions that focus on bug fixes, dependency upgrades, and UI/UX improvements.

## Installation

```bash
# Install from pre-built binaries.
cargo binstall cargo-shear

# Build from source.
cargo install cargo-shear

# Install from brew.
brew install cargo-shear
```

## Usage

Check for issues without making changes:

```bash
cargo shear
```

Automatically fix unused dependencies:

```bash
cargo shear --fix
```

Treat warnings as errors (exit with failure code):

```bash
cargo shear --deny-warnings
```

This is useful for CI/CD pipelines to enforce strict checking of warnings such as empty files, unlinked files, and unused optional dependencies.

Generate machine-readable JSON output:

```bash
cargo shear --format=json
```

This is particularly useful for CI/CD pipelines and custom tooling that need to programmatically process the results.

Detect mismatches between `[lib]` target settings and source content:

```bash
cargo shear --check-test-targets
```

When set, cargo-shear warns when `test = false` is paired with source that contains tests (or `doctest = false` with source that contains doc tests), and — within a workspace — when `test` / `doctest` are left at their default of `true` for lib targets that contain none. Disabled by default; pair with `--fix` to automatically reconcile the flags.

## Limitations

> [!IMPORTANT]
> `cargo shear` cannot detect "hidden" imports from macro expansions without the `--expand` flag (nightly only).
> This is because `cargo shear` uses rust-analyzer's parser to parse files and does not expand macros by default.

To expand macros:

```bash
cargo shear --expand --fix
```

The `--expand` flag uses `cargo expand`, which requires nightly and is significantly slower.

> [!IMPORTANT]
> Misplaced dependency detection only works for integration tests, benchmarks, and examples.
> Unit tests dependencies within `#[cfg(test)]` cannot be detected as misplaced.

## Configuration

### Ignore false positives

False positives can be ignored by adding them to the package's `Cargo.toml`:

```toml
[package.metadata.cargo-shear]
ignored = ["crate-name"]
```

### Ignore unlinked files

Unlinked files can be ignored using glob patterns:

```toml
[package.metadata.cargo-shear]
ignored-paths = ["src/proto/*.rs", "examples/old/*"]
```

Both options work in workspace `Cargo.toml` as well:

```toml
[workspace.metadata.cargo-shear]
ignored = ["crate-name"]
ignored-paths = ["*/proto/*.rs"]
```

### Ignore scope

- **Package ignore** (`[package.metadata.cargo-shear]`) applies only to that package.
- **Workspace ignore** (`[workspace.metadata.cargo-shear]`) applies to every member
  *and* to the workspace dependency itself. `ignored-paths` globs are matched relative
  to the workspace root.

An ignore that suppresses nothing is reported as redundant so it can be removed. A
workspace ignore is considered redundant only when no member needs it — a dependency
that is used in one crate but unused in another stays covered by the workspace ignore.

### cargo-hakari `workspace-hack` crates

[`cargo-hakari`](https://docs.rs/cargo-hakari) generates a `workspace-hack` crate that declares many dependencies it never imports (to unify Cargo features) and is depended on by every workspace member without being imported. `cargo shear` detects such a crate automatically — via the `### BEGIN HAKARI SECTION` marker in its `Cargo.toml` — and skips both the crate itself and the dependency edges pointing at it, so no `ignored` configuration is needed.

Otherwise please report the issue as a bug.

## CI

> [!NOTE]
> `cargo shear` uses static analysis and operates on source code without compiling.
> This means it only needs to run once on a single platform (e.g., Linux) to detect issues across all target platforms, including those with platform-specific dependencies and conditional compilation.
>
> The only exception is when using the `--expand` flag, which invokes `cargo build` and may produce platform-specific results.

```yaml
- name: Install cargo-binstall
  uses: cargo-bins/cargo-binstall@main

- name: Install cargo-shear
  run: cargo binstall --no-confirm cargo-shear

- run: cargo shear
```

### JSON Output for CI Integration

For CI systems that require structured output, use the `--format=json` flag:

```yaml
- name: Check for unused dependencies
  run: cargo shear --format=json > shear-results.json
```

The JSON output includes:
- **summary**: Counts of errors, warnings, and fixes
- **findings**: Detailed information about each issue including:
  - `code`: The diagnostic code (e.g., `shear/unused_dependency`)
  - `severity`: Error or warning level
  - `message`: Human-readable description
  - `file`: Path to the file with the issue
  - `location`: Byte offset and length within the file
  - `help`: Suggested fix
  - `fixable`: Boolean indicating if issue can be auto-fixed with `--fix`

## Exit Code (for CI)

| Exit Code | Without `--fix` | With `--fix` |
|-----------|----------------|--------------|
| 0 | No issues found | No issues found, no changes made |
| 1 | Issues found | Issues found and fixed |
| 2 | Error during processing | Error during processing |

### Strict Mode with `--deny-warnings`

By default, warnings (such as empty files, unlinked files, and unused optional dependencies) exit with code 0. Use the `--deny-warnings` flag to treat warnings as errors for stricter CI enforcement:

| Exit Code | Without `--deny-warnings` | With `--deny-warnings` |
|-----------|---------------------------|------------------------|
| 0 | No errors (warnings allowed) | No errors or warnings |
| 1 | Errors found | Errors or warnings found |
| 2 | Error during processing | Error during processing |

**GitHub Actions Example:**

```yaml
- name: cargo-shear
  shell: bash
  run: |
    if ! cargo shear --fix; then
      cargo check
    fi
```

**Strict CI Example with `--deny-warnings`:**

```yaml
- name: cargo-shear (strict)
  run: cargo shear --deny-warnings
```

## Technique

1. Use the `cargo_metadata` crate to list all dependencies specified in `[workspace.dependencies]` and `[dependencies]`
2. Iterate through all package targets (`lib`, `bin`, `example`, `test` and `bench`) to locate all Rust files
3. Use rust-analyzer's parser (`ra_ap_syntax`) to parse these Rust files and extract imports
   - Alternatively, use the `--expand` option with `cargo expand` to first expand macros and then parse the expanded code (though this is significantly slower)
4. Find the difference between the imports and the package dependencies

## Prior Art

* [est31/cargo-udeps]https://github.com/est31/cargo-udeps
    * it collects dependency usage by compiling your project and find them from the `target/` directory
    * does not seem to work anymore with the latest versions of `cargo`
    * does not work with cargo workspaces
* [bnjbvr/cargo-machete]https://github.com/bnjbvr/cargo-machete
    * it collects dependency usage by running regex patterns on source code
    * does not detect all usages of a dependency
    * does not remove unused dependencies from the workspace root
* cargo and clippy
    * There was intention to add similar features to cargo or clippy, but the progress is currently stagnant
    * See https://github.com/rust-lang/rust/issues/57274 and https://github.com/rust-lang/rust-clippy/issues/4341

## Trophy Cases

* -7 lines from [oxc]https://github.com/oxc-project/oxc/pull/2729
* -59 lines from [rspack]https://github.com/web-infra-dev/rspack/pull/5954
* -39 lines from [rolldown]https://github.com/rolldown/rolldown/pull/593
* -12 lines [ast-grep]https://github.com/ast-grep/ast-grep [commit1]https://github.com/ast-grep/ast-grep/commit/c4ef252a71b05193f2ced327666f61836ad515c3 [commit2]https://github.com/ast-grep/ast-grep/commit/43edbc131e68173468e9aa302cab9b45263b1f76
* -66 lines [biome]https://github.com/biomejs/biome/pull/2153
* -164 lines [astral-sh/uv]https://github.com/astral-sh/uv/pull/3527
* -86 lines [reqsign]https://github.com/Xuanwo/reqsign/pull/481
* -184 lines from [turbopack]https://github.com/vercel/next.js/pull/80121
* -625 lines from [openai/codex]https://github.com/openai/codex/pull/3338
* -69 lines from [uutils/coreutils]https://github.com/uutils/coreutils/pull/10404
* -1,588 lines from [vinhnx/vtcode]https://github.com/vinhnx/VTCode/pull/646

## [Sponsored By]https://github.com/sponsors/Boshen

<p align="center">
  <a href="https://github.com/sponsors/Boshen">
    <img src="https://raw.githubusercontent.com/Boshen/sponsors/main/sponsors.svg" alt="My sponsors" />
  </a>
</p>