Skip to content
Merged
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 @@ -216,6 +216,7 @@ tracing = { workspace = true, features = ["attributes"] }
tracing-subscriber.workspace = true
unicase.workspace = true
unicode-width.workspace = true
unicode-xid.workspace = true
url.workspace = true
walkdir.workspace = true

Expand Down
17 changes: 17 additions & 0 deletions src/bin/cargo/commands/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use cargo::ops;
use cargo::util::IntoUrl;
use cargo::util::VersionExt;
use cargo::CargoResult;
use cargo_util_schemas::manifest::PackageName;
use itertools::Itertools;
use semver::VersionReq;

Expand Down Expand Up @@ -133,6 +134,22 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
.collect::<crate::CargoResult<Vec<_>>>()?;

for (crate_name, _) in krates.iter() {
let package_name = PackageName::new(crate_name);
if !crate_name.contains("@") && package_name.is_err() {
for (idx, ch) in crate_name.char_indices() {
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') {
let mut suggested_crate_name = crate_name.to_string();
suggested_crate_name.insert_str(idx, "@");
if let Ok((_, Some(_))) = parse_crate(&suggested_crate_name.as_str()) {
let err = package_name.unwrap_err();
return Err(
anyhow::format_err!("{err}\n\n\
help: if this is meant to be a package name followed by a version, insert an `@` like `{suggested_crate_name}`").into());
}
}
}
}

if let Some(toolchain) = crate_name.strip_prefix("+") {
return Err(anyhow!(
"invalid character `+` in package name: `+{toolchain}`
Expand Down
18 changes: 17 additions & 1 deletion src/cargo/ops/cargo_add/crate_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,23 @@ impl CrateSpec {
.map(|(n, v)| (n, Some(v)))
.unwrap_or((pkg_id, None));

PackageName::new(name)?;
let package_name = PackageName::new(name);
if !pkg_id.contains("@") && package_name.is_err() {
for (idx, ch) in pkg_id.char_indices() {
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') {
let mut suggested_pkg_id = pkg_id.to_string();
suggested_pkg_id.insert_str(idx, "@");
if let Ok(_) = CrateSpec::resolve(&suggested_pkg_id.as_str()) {
let err = package_name.unwrap_err();
return Err(
anyhow::format_err!("{err}\n\n\
help: if this is meant to be a package name followed by a version, insert an `@` like `{suggested_pkg_id}`").into());
}
}
}
}

package_name?;

if let Some(version) = version {
semver::VersionReq::parse(version)
Expand Down
1 change: 1 addition & 0 deletions tests/testsuite/cargo_add/missing_at_in_crate_spec/in
24 changes: 24 additions & 0 deletions tests/testsuite/cargo_add/missing_at_in_crate_spec/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use cargo_test_support::compare::assert_ui;
use cargo_test_support::current_dir;
use cargo_test_support::file;
use cargo_test_support::prelude::*;
use cargo_test_support::str;
use cargo_test_support::Project;

#[cargo_test]
fn case() {
let project = Project::from_template(current_dir!().join("in"));
let project_root = project.root();
let cwd = &project_root;

snapbox::cmd::Command::cargo_ui()
.arg("add")
.arg_line("my-package=1.0.0")
.current_dir(cwd)
.assert()
.code(101)
.stdout_eq(str![""])
.stderr_eq(file!["stderr.term.svg"]);

assert_ui().subset_matches(current_dir!().join("out"), &project_root);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[workspace]

[package]
name = "cargo-list-test-fixture"
version = "0.0.0"
edition = "2015"
31 changes: 31 additions & 0 deletions tests/testsuite/cargo_add/missing_at_in_crate_spec/stderr.term.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions tests/testsuite/cargo_add/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ mod locked_unchanged;
mod lockfile_updated;
mod manifest_path_package;
mod merge_activated_features;
mod missing_at_in_crate_spec;
mod multiple_conflicts_with_features;
mod multiple_conflicts_with_rename;
mod namever;
Expand Down
14 changes: 14 additions & 0 deletions tests/testsuite/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,20 @@ fn bad_version() {
.run();
}

#[cargo_test]
fn missing_at_symbol_before_version() {
pkg("foo", "0.0.1");
cargo_process("install foo=0.2.0")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] invalid character `=` in package name: `foo=0.2.0`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)

[HELP] if this is meant to be a package name followed by a version, insert an `@` like `foo@=0.2.0`

"#]])
.run();
}

#[cargo_test]
fn bad_paths() {
cargo_process("install")
Expand Down