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
1 change: 1 addition & 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ log = "0.4.29"
pretty_env_logger = "0.5.0"
rayon = "1.5.2"
serde = "1.0.228"
serde_json = "1.0"
toml_edit = "0.24.0"
walkdir = "2.3.2"

Expand Down
61 changes: 52 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod search_unused;
use crate::search_unused::find_unused;
use anyhow::{Context, bail};
use rayon::prelude::*;
use serde::Serialize;
use std::path::Path;
use std::str::FromStr;
use std::{borrow::Cow, fs, path::PathBuf};
Expand Down Expand Up @@ -54,6 +55,10 @@ struct MacheteArgs {
#[argh(switch)]
version: bool,

/// output results as JSON for tooling integration.
#[argh(switch)]
json: bool,

/// paths to directories that must be scanned.
#[argh(positional, greedy)]
paths: Vec<PathBuf>,
Expand Down Expand Up @@ -115,6 +120,26 @@ fn running_as_cargo_cmd() -> bool {
std::env::var("CARGO").is_ok() && std::env::var("CARGO_PKG_NAME").is_err()
}

/// JSON output structure for unused dependencies.
#[derive(Serialize)]
struct JsonOutput {
/// List of crates with unused dependencies.
crates: Vec<CrateUnusedDeps>,
}

/// JSON structure for a single crate's unused dependencies.
#[derive(Serialize)]
struct CrateUnusedDeps {
/// The name of the package.
package_name: String,
/// Path to the Cargo.toml file.
manifest_path: String,
/// List of unused dependency names.
unused: Vec<String>,
/// List of dependencies marked as ignored but actually used.
ignored_used: Vec<String>,
}

/// Runs `cargo-machete`.
/// Returns Ok with a bool whether any unused dependencies were found, or Err on errors.
fn run_machete() -> anyhow::Result<bool> {
Expand All @@ -127,7 +152,7 @@ fn run_machete() -> anyhow::Result<bool> {
};

if args.version {
println!("{}", env!("CARGO_PKG_VERSION"));
eprintln!("{}", env!("CARGO_PKG_VERSION"));
Copy link
Author

@mgi388 mgi388 Jan 22, 2026

Choose a reason for hiding this comment

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

Note: Printing the version should go to stdout, so this change is wrong and will need to be reverted one way or another in this PR.

std::process::exit(0);
}

Expand All @@ -147,6 +172,7 @@ fn run_machete() -> anyhow::Result<bool> {

let mut has_unused_dependencies = false;
let mut walkdir_errors = Vec::new();
let mut json_output = JsonOutput { crates: Vec::new() };

for path in args.paths {
let manifest_path_entries = match collect_paths(
Expand Down Expand Up @@ -207,15 +233,15 @@ fn run_machete() -> anyhow::Result<bool> {
};

if results.is_empty() {
println!("cargo-machete didn't find any unused dependencies in {location}. Good job!");
eprintln!("cargo-machete didn't find any unused dependencies in {location}. Good job!");
continue;
}

println!("cargo-machete found the following unused dependencies in {location}:");
for (analysis, path) in results {
println!("{} -- {}:", analysis.package_name, path.to_string_lossy());
eprintln!("cargo-machete found the following unused dependencies in {location}:");
for (analysis, path) in &results {
eprintln!("{} -- {}:", analysis.package_name, path.to_string_lossy());
for dep in &analysis.unused {
println!("\t{dep}");
eprintln!("\t{dep}");
has_unused_dependencies = true; // any unused dependency is enough to set flag to true
}

Expand All @@ -228,10 +254,27 @@ fn run_machete() -> anyhow::Result<bool> {
fs::write(path, fixed).expect("Cargo.toml write error");
}
}

if args.json {
// Collect results for JSON output.
for (analysis, path) in &results {
if !analysis.unused.is_empty() {
has_unused_dependencies = true;
}
json_output.crates.push(CrateUnusedDeps {
package_name: analysis.package_name.clone(),
manifest_path: path.to_string_lossy().to_string(),
unused: analysis.unused.clone(),
ignored_used: analysis.ignored_used.clone(),
});
}

println!("{}", serde_json::to_string(&json_output)?);
}
}

if has_unused_dependencies {
println!(
eprintln!(
"\n\
If you believe cargo-machete has detected an unused dependency incorrectly,\n\
you can add the dependency to the list of dependencies to ignore in the\n\
Expand All @@ -243,14 +286,14 @@ fn run_machete() -> anyhow::Result<bool> {
);

if !args.with_metadata {
println!(
eprintln!(
"\n\
You can also try running it with the `--with-metadata` flag for better accuracy,\n\
though this may modify your Cargo.lock files."
);
}

println!();
eprintln!();
}

eprintln!("Done!");
Expand Down