Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .spelling
Original file line number Diff line number Diff line change
Expand Up @@ -521,3 +521,6 @@ deprecations
parallelization
remediate
recency
symlink
symlinked
symlinks
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Please see each crate's change log below:
- [`cargo-ensure-no-cyclic-deps`](./crates/cargo_ensure_no_cyclic_deps/CHANGELOG.md)
- [`cargo-ensure-no-default-features`](./crates/cargo-ensure-no-default-features/CHANGELOG.md)
- [`cargo-heather`](./crates/cargo-heather/CHANGELOG.md)
- [`cargo-unused-deps`](./crates/cargo-unused-deps/CHANGELOG.md)
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ These are the crates built out of this repo:
- [`cargo-ensure-no-cyclic-deps`](./crates/cargo_ensure_no_cyclic_deps/README.md) - A cargo subcommand to detect cyclic dependencies in workspace crates
- [`cargo-ensure-no-default-features`](./crates/cargo-ensure-no-default-features/README.md) - A cargo subcommand that ensures dependencies are declared with default-features = false
- [`cargo-heather`](./crates/cargo-heather/README.md) - A cargo subcommand to validate license headers in Rust, TOML, PowerShell, Just, and env source files
- [`cargo-unused-deps`](./crates/cargo-unused-deps/README.md) - A cargo subcommand that finds unused dependencies, starting with uninherited [workspace.dependencies] entries

## About this Repo

Expand Down
1 change: 1 addition & 0 deletions crates/cargo-unused-deps/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Changelog
Comment thread
martin-kolinek marked this conversation as resolved.
41 changes: 41 additions & 0 deletions crates/cargo-unused-deps/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

[package]
name = "cargo-unused-deps"
description = "A cargo subcommand that finds unused dependencies, starting with uninherited [workspace.dependencies] entries"
version = "0.1.0"
readme = "README.md"
keywords = ["oxidizer", "cargo", "subcommand", "dependencies", "ci"]
categories = ["command-line-utilities", "development-tools::cargo-plugins"]

edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository = "https://github.com/microsoft/ox-tools/tree/main/crates/cargo-unused-deps"

[package.metadata.docs.rs]
all-features = true

[package.metadata.cargo_check_external_types]
# `anyhow::Error` leaks through the `run` entry point that `main` calls. Mirrors the
# allowlist entry in `cargo-ensure-no-default-features`, which has the same shape.
allowed_external_types = ["anyhow::Result"]

[[bin]]
name = "cargo-unused-deps"
path = "src/main.rs"

[dependencies]
anyhow = { workspace = true, features = ["std"] }
cargo_metadata = { workspace = true }
clap = { workspace = true, features = ["std", "derive", "color", "help", "error-context", "usage"] }
tempfile = { workspace = true }
toml_edit = { workspace = true }

# >>> anvil-managed: anvil-lints
[lints]
workspace = true
# <<< anvil-managed: anvil-lints
101 changes: 101 additions & 0 deletions crates/cargo-unused-deps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<div align="center">
<img src="./logo.png" alt="Cargo-Unused-Deps Logo" width="96">

# Cargo-Unused-Deps

[![crates.io](https://img.shields.io/crates/v/cargo-unused-deps.svg)](https://crates.io/crates/cargo-unused-deps)
[![docs.rs](https://docs.rs/cargo-unused-deps/badge.svg)](https://docs.rs/cargo-unused-deps)
[![MSRV](https://img.shields.io/crates/msrv/cargo-unused-deps)](https://crates.io/crates/cargo-unused-deps)
[![CI](https://github.com/microsoft/ox-tools/actions/workflows/main.yml/badge.svg?event=push)](https://github.com/microsoft/ox-tools/actions/workflows/main.yml)
[![Coverage](https://codecov.io/gh/microsoft/ox-tools/graph/badge.svg?token=FCUG0EL5TI)](https://codecov.io/gh/microsoft/ox-tools)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](../../LICENSE)
<a href="../.."><img src="../../logo.svg" alt="This crate was developed as part of the Oxidizer project" width="20"></a>

</div>

A cargo sub-command that ensures every `[workspace.dependencies]` entry is

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Copilot speaking]

Standardize the Cargo subcommand terminology

The new package describes the same concept as a "cargo subcommand" in package metadata, a "cargo sub-command" in the README introduction, and a "cargo workspace" in the usage section. Elsewhere in the crate, including its CLI type documentation, the established form is "Cargo subcommand". Using one spelling and capitalization avoids presenting the Cargo product name and its subcommand concept as several different terms.

Reproducible reasoning: Cargo is the name of the Rust package manager, so prose uses it as a proper name, while Cargo's own documentation spells "subcommand" as one word. The reviewed files introduce lowercase and hyphenated variants for that same concept: Cargo.toml line 6 uses "cargo subcommand", README line 16 uses "cargo sub-command", and README line 34 uses "cargo workspace". These are not command invocations, where lowercase cargo would be correct; they are prose naming Cargo and its concepts. The variation is therefore terminology drift rather than a meaningful distinction.

Consequence: Package registry metadata and the README present inconsistent names for the tool category, and readers may reasonably wonder whether "sub-command" denotes something different from the standard Cargo "subcommand" term.

Recommended action: Use "Cargo subcommand" consistently in the package description and README prose, and capitalize Cargo in "Cargo workspace". Preserve lowercase cargo only in command examples and executable names.

References:

Impacted locations:

  • crates/cargo-ensure-no-unused-workspace-deps/Cargo.toml:6
  • crates/cargo-ensure-no-unused-workspace-deps/README.md:16
  • crates/cargo-ensure-no-unused-workspace-deps/README.md:34

inherited by at least one workspace member.
Comment on lines +16 to +17

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Copilot speaking]

Condense rustdoc summaries before their detailed paragraphs

The crate summary and several item summaries render as first paragraphs longer than one 80-character line. Wrapping consecutive //! or /// source lines does not create a paragraph break, so the rendered summary remains long. This affects the crate overview and the catalog, inheritance, partitioning, comment-prepending, check, and write-back documentation.

Reproducible reasoning: Rustdoc uses the first paragraph as an item's concise summary in indexes and overview contexts. Each cited block contains consecutive documentation lines without a blank line, so its detailed explanation is folded into that summary instead of appearing as supporting prose.

Consequence: The opening text wraps in generated documentation and makes item indexes and crate overviews less scannable than a short summary followed by detail.

Recommended action: Condense each cited first paragraph to a short rendered summary, then move useful detail into a following paragraph separated by a blank documentation line. Regenerate README.md after changing the crate-level documentation.

Impacted locations:

  • crates/cargo-ensure-no-unused-workspace-deps/README.md:16-17
  • crates/cargo-ensure-no-unused-workspace-deps/src/lib.rs:4-5
  • crates/cargo-ensure-no-unused-workspace-deps/src/main.rs:4-5
  • crates/cargo-ensure-no-unused-workspace-deps/src/detect.rs:27-28
  • crates/cargo-ensure-no-unused-workspace-deps/src/detect.rs:53-54
  • crates/cargo-ensure-no-unused-workspace-deps/src/detect.rs:126-127
  • crates/cargo-ensure-no-unused-workspace-deps/src/detect.rs:136-137
  • crates/cargo-ensure-no-unused-workspace-deps/src/fix.rs:170-171
  • crates/cargo-ensure-no-unused-workspace-deps/src/lib.rs:169-170
  • crates/cargo-ensure-no-unused-workspace-deps/src/lib.rs:231-232


A workspace root declares a dependency catalog that members draw from with
`dep = { workspace = true }`. Nothing requires an entry to be drawn from, so
an entry nobody inherits stays in the manifest forever: it never enters the
dependency graph, and no build fails because of it. It still carries a
version requirement, so it keeps attracting dependency-bump traffic and keeps
misleading readers about what the workspace depends on.

Unused-dependency tools resolve the crate graph and ask which *declared*
dependencies go unused, so an entry that no member declares is invisible to
them. This tool answers the prior question – is the entry inherited at all?
– from the manifests alone, which makes it free of false positives and cheap
enough to run on every pull request.

## Usage

Run in a cargo workspace:

```bash
cargo unused-deps
```

Remove what it finds:

```bash
cargo unused-deps --fix
```

`--manifest-path` points at an explicit workspace root, defaulting to the
`Cargo.toml` in the current directory. A manifest with no `[workspace]` table
declares no catalog and passes with a note; `--require-workspace` turns that
into an error for callers that know they are pointing at a workspace root.

## Configuration

An entry kept on purpose is exempted in the workspace manifest:

```toml
[workspace.metadata.unused-deps]
allowed = ["kept-on-purpose"]
```

An `allowed` name that suppresses nothing is reported as stale, on stderr,
without failing the run.

## Fixing

`--fix` replaces the manifest atomically – a temporary file in the same
directory, renamed over the original, carrying the permissions of the
manifest it replaces and following a symlinked manifest to its target – and
refuses to write at all if the file changed after it was read, so a
concurrent edit is never clobbered.
Comment on lines +65 to +69

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Copilot speaking]

State each --fix safeguard in a direct sentence

The --fix paragraph compresses atomic replacement, same-directory staging, permission preservation, symlink-target handling, and concurrent-edit detection into one heavily parenthesized sentence. It also uses the informal verb “clobbered” for overwriting an edit.

Reproducible reasoning: The participial clauses do not consistently name whether the temporary file, the rename, or the overall operation carries permissions and follows a symlink. These are independent safeguards with different mechanisms, so separate subject-and-verb sentences would make the contract easier to read and would remain clearer if one safeguard changes.

Consequence: Readers can misinterpret which operation preserves each file property, and informal wording makes a publication-facing safety statement less precise.

Recommended action: Rewrite the canonical crate-level documentation as short, direct sentences that name the operation responsible for atomic replacement, permission preservation, symlink-target handling, and change detection. Use “overwritten” instead of “clobbered”, then regenerate the README.

References:

  • crates/cargo-ensure-no-unused-workspace-deps/src/lib.rs

Impacted locations:

  • crates/cargo-ensure-no-unused-workspace-deps/README.md:65-69


Comments on a removed entry are carried to the next surviving entry, which
keeps a group header attached to the group it introduces. A note about one
specific dependency is indistinguishable from such a header, so every move
is reported on stderr: check that carried text still describes the entry it
landed on. Comments that cannot be placed – the removal emptied the table,
or left a trailing survivor with nothing to append to – are reported as
dropped.

## Installation

```bash
cargo install cargo-unused-deps
```

## Example output

```text
Found 2 unused workspace dependencies in Cargo.toml:

- once_cell
- smallvec

Re-run with --fix to remove them.
```


<hr/>
<sub>
This crate was developed as part of <a href="../..">The Oxidizer Project</a>. Browse this crate's <a href="https://github.com/microsoft/ox-tools/tree/main/crates/cargo-unused-deps">source code</a>.
</sub>

Loading