Skip to content

Commit b52fc0a

Browse files
committed
Auto merge of #9095 - ehuss:spec-suggestion, r=alexcrichton
Add suggestion for bad package id. This adds some suggestions to the error message if a pkgid spec does not match any packages.
2 parents 24cf9c5 + b04c7fb commit b52fc0a

File tree

6 files changed

+147
-13
lines changed

6 files changed

+147
-13
lines changed

src/cargo/core/package_id_spec.rs

+52-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
use std::collections::HashMap;
22
use std::fmt;
33

4+
use anyhow::bail;
45
use semver::Version;
56
use serde::{de, ser};
67
use url::Url;
78

89
use crate::core::PackageId;
910
use crate::util::errors::{CargoResult, CargoResultExt};
1011
use crate::util::interning::InternedString;
12+
use crate::util::lev_distance;
1113
use crate::util::{validate_package_name, IntoUrl, ToSemver};
1214

1315
/// Some or all of the data required to identify a package:
@@ -96,7 +98,7 @@ impl PackageIdSpec {
9698
/// Tries to convert a valid `Url` to a `PackageIdSpec`.
9799
fn from_url(mut url: Url) -> CargoResult<PackageIdSpec> {
98100
if url.query().is_some() {
99-
anyhow::bail!("cannot have a query string in a pkgid: {}", url)
101+
bail!("cannot have a query string in a pkgid: {}", url)
100102
}
101103
let frag = url.fragment().map(|s| s.to_owned());
102104
url.set_fragment(None);
@@ -180,14 +182,57 @@ impl PackageIdSpec {
180182
where
181183
I: IntoIterator<Item = PackageId>,
182184
{
183-
let mut ids = i.into_iter().filter(|p| self.matches(*p));
185+
let all_ids: Vec<_> = i.into_iter().collect();
186+
let mut ids = all_ids.iter().copied().filter(|&id| self.matches(id));
184187
let ret = match ids.next() {
185188
Some(id) => id,
186-
None => anyhow::bail!(
187-
"package ID specification `{}` \
188-
matched no packages",
189-
self
190-
),
189+
None => {
190+
let mut suggestion = String::new();
191+
let try_spec = |spec: PackageIdSpec, suggestion: &mut String| {
192+
let try_matches: Vec<_> = all_ids
193+
.iter()
194+
.copied()
195+
.filter(|&id| spec.matches(id))
196+
.collect();
197+
if !try_matches.is_empty() {
198+
suggestion.push_str("\nDid you mean one of these?\n");
199+
minimize(suggestion, &try_matches, self);
200+
}
201+
};
202+
if self.url.is_some() {
203+
try_spec(
204+
PackageIdSpec {
205+
name: self.name,
206+
version: self.version.clone(),
207+
url: None,
208+
},
209+
&mut suggestion,
210+
);
211+
}
212+
if suggestion.is_empty() && self.version.is_some() {
213+
try_spec(
214+
PackageIdSpec {
215+
name: self.name,
216+
version: None,
217+
url: None,
218+
},
219+
&mut suggestion,
220+
);
221+
}
222+
if suggestion.is_empty() {
223+
suggestion.push_str(&lev_distance::closest_msg(
224+
&self.name,
225+
all_ids.iter(),
226+
|id| id.name().as_str(),
227+
));
228+
}
229+
230+
bail!(
231+
"package ID specification `{}` did not match any packages{}",
232+
self,
233+
suggestion
234+
);
235+
}
191236
};
192237
return match ids.next() {
193238
Some(other) => {

src/cargo/ops/cargo_clean.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::core::{PackageIdSpec, TargetKind, Workspace};
44
use crate::ops;
55
use crate::util::errors::{CargoResult, CargoResultExt};
66
use crate::util::interning::InternedString;
7+
use crate::util::lev_distance;
78
use crate::util::paths;
89
use crate::util::Config;
910
use std::fs;
@@ -119,7 +120,17 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
119120
}
120121
let matches: Vec<_> = resolve.iter().filter(|id| spec.matches(*id)).collect();
121122
if matches.is_empty() {
122-
anyhow::bail!("package ID specification `{}` matched no packages", spec);
123+
let mut suggestion = String::new();
124+
suggestion.push_str(&lev_distance::closest_msg(
125+
&spec.name(),
126+
resolve.iter(),
127+
|id| id.name().as_str(),
128+
));
129+
anyhow::bail!(
130+
"package ID specification `{}` did not match any packages{}",
131+
spec,
132+
suggestion
133+
);
123134
}
124135
pkg_ids.extend(matches);
125136
}

tests/testsuite/build.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3063,12 +3063,12 @@ fn invalid_spec() {
30633063

30643064
p.cargo("build -p notAValidDep")
30653065
.with_status(101)
3066-
.with_stderr("[ERROR] package ID specification `notAValidDep` matched no packages")
3066+
.with_stderr("[ERROR] package ID specification `notAValidDep` did not match any packages")
30673067
.run();
30683068

30693069
p.cargo("build -p d1 -p notAValidDep")
30703070
.with_status(101)
3071-
.with_stderr("[ERROR] package ID specification `notAValidDep` matched no packages")
3071+
.with_stderr("[ERROR] package ID specification `notAValidDep` did not match any packages")
30723072
.run();
30733073
}
30743074

tests/testsuite/clean.rs

+13
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,19 @@ fn clean_spec_multiple() {
466466
.build();
467467

468468
p.cargo("build").run();
469+
470+
// Check suggestion for bad pkgid.
471+
p.cargo("clean -p baz")
472+
.with_status(101)
473+
.with_stderr(
474+
"\
475+
error: package ID specification `baz` did not match any packages
476+
477+
<tab>Did you mean `bar`?
478+
",
479+
)
480+
.run();
481+
469482
p.cargo("clean -p bar:1.0.0")
470483
.with_stderr(
471484
"warning: version qualifier in `-p bar:1.0.0` is ignored, \

tests/testsuite/install.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ Caused by:
757757
fn uninstall_pkg_does_not_exist() {
758758
cargo_process("uninstall foo")
759759
.with_status(101)
760-
.with_stderr("[ERROR] package ID specification `foo` matched no packages")
760+
.with_stderr("[ERROR] package ID specification `foo` did not match any packages")
761761
.run();
762762
}
763763

@@ -797,7 +797,7 @@ fn uninstall_piecemeal() {
797797

798798
cargo_process("uninstall foo")
799799
.with_status(101)
800-
.with_stderr("[ERROR] package ID specification `foo` matched no packages")
800+
.with_stderr("[ERROR] package ID specification `foo` did not match any packages")
801801
.run();
802802
}
803803

@@ -1205,7 +1205,7 @@ fn uninstall_multiple_and_some_pkg_does_not_exist() {
12051205
.with_stderr(
12061206
"\
12071207
[REMOVING] [CWD]/home/.cargo/bin/foo[EXE]
1208-
error: package ID specification `bar` matched no packages
1208+
error: package ID specification `bar` did not match any packages
12091209
[SUMMARY] Successfully uninstalled foo! Failed to uninstall bar (see error(s) above).
12101210
error: some packages failed to uninstall
12111211
",

tests/testsuite/pkgid.rs

+65
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,68 @@ fn simple() {
3232
.with_stdout("https://github.com/rust-lang/crates.io-index#bar:0.1.0")
3333
.run();
3434
}
35+
36+
#[cargo_test]
37+
fn suggestion_bad_pkgid() {
38+
Package::new("crates-io", "0.1.0").publish();
39+
Package::new("two-ver", "0.1.0").publish();
40+
Package::new("two-ver", "0.2.0").publish();
41+
let p = project()
42+
.file(
43+
"Cargo.toml",
44+
r#"
45+
[package]
46+
name = "foo"
47+
version = "0.1.0"
48+
edition = "2018"
49+
50+
[dependencies]
51+
crates-io = "0.1.0"
52+
two-ver = "0.1.0"
53+
two-ver2 = { package = "two-ver", version = "0.2.0" }
54+
"#,
55+
)
56+
.file("src/lib.rs", "")
57+
.build();
58+
59+
p.cargo("generate-lockfile").run();
60+
61+
// Bad URL.
62+
p.cargo("pkgid https://example.com/crates-io")
63+
.with_status(101)
64+
.with_stderr(
65+
"\
66+
error: package ID specification `https://example.com/crates-io` did not match any packages
67+
Did you mean one of these?
68+
69+
crates-io:0.1.0
70+
",
71+
)
72+
.run();
73+
74+
// Bad name.
75+
p.cargo("pkgid crates_io")
76+
.with_status(101)
77+
.with_stderr(
78+
"\
79+
error: package ID specification `crates_io` did not match any packages
80+
81+
<tab>Did you mean `crates-io`?
82+
",
83+
)
84+
.run();
85+
86+
// Bad version.
87+
p.cargo("pkgid two-ver:0.3.0")
88+
.with_status(101)
89+
.with_stderr(
90+
"\
91+
error: package ID specification `two-ver:0.3.0` did not match any packages
92+
Did you mean one of these?
93+
94+
two-ver:0.1.0
95+
two-ver:0.2.0
96+
",
97+
)
98+
.run();
99+
}

0 commit comments

Comments
 (0)