summaryrefslogtreecommitdiffstats
path: root/src/tools/cargo/crates
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-19 09:26:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-19 09:26:03 +0000
commit9918693037dce8aa4bb6f08741b6812923486c18 (patch)
tree21d2b40bec7e6a7ea664acee056eb3d08e15a1cf /src/tools/cargo/crates
parentReleasing progress-linux version 1.75.0+dfsg1-5~progress7.99u1. (diff)
downloadrustc-9918693037dce8aa4bb6f08741b6812923486c18.tar.xz
rustc-9918693037dce8aa4bb6f08741b6812923486c18.zip
Merging upstream version 1.76.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/cargo/crates')
-rw-r--r--src/tools/cargo/crates/cargo-platform/Cargo.toml5
-rw-r--r--src/tools/cargo/crates/cargo-platform/examples/matches.rs2
-rw-r--r--src/tools/cargo/crates/cargo-test-macro/Cargo.toml3
-rw-r--r--src/tools/cargo/crates/cargo-test-macro/src/lib.rs9
-rw-r--r--src/tools/cargo/crates/cargo-test-support/Cargo.toml4
-rw-r--r--src/tools/cargo/crates/cargo-test-support/build.rs2
-rw-r--r--src/tools/cargo/crates/cargo-test-support/src/compare.rs37
-rw-r--r--src/tools/cargo/crates/cargo-test-support/src/lib.rs27
-rw-r--r--src/tools/cargo/crates/cargo-test-support/src/paths.rs12
-rw-r--r--src/tools/cargo/crates/cargo-test-support/src/registry.rs59
-rw-r--r--src/tools/cargo/crates/cargo-test-support/src/tools.rs32
-rw-r--r--src/tools/cargo/crates/cargo-util/Cargo.toml6
-rw-r--r--src/tools/cargo/crates/cargo-util/src/du.rs78
-rw-r--r--src/tools/cargo/crates/cargo-util/src/lib.rs4
-rw-r--r--src/tools/cargo/crates/cargo-util/src/paths.rs13
-rw-r--r--src/tools/cargo/crates/crates-io/Cargo.toml5
-rw-r--r--src/tools/cargo/crates/crates-io/lib.rs11
-rw-r--r--src/tools/cargo/crates/home/CHANGELOG.md9
-rw-r--r--src/tools/cargo/crates/home/Cargo.toml7
-rw-r--r--src/tools/cargo/crates/home/src/lib.rs10
-rw-r--r--src/tools/cargo/crates/home/src/windows.rs23
-rw-r--r--src/tools/cargo/crates/mdman/Cargo.toml5
-rw-r--r--src/tools/cargo/crates/mdman/src/main.rs2
-rw-r--r--src/tools/cargo/crates/mdman/tests/compare.rs16
-rw-r--r--src/tools/cargo/crates/mdman/tests/invalid.rs6
-rw-r--r--src/tools/cargo/crates/resolver-tests/Cargo.toml3
-rw-r--r--src/tools/cargo/crates/resolver-tests/src/lib.rs174
-rw-r--r--src/tools/cargo/crates/resolver-tests/tests/resolve.rs225
-rw-r--r--src/tools/cargo/crates/rustfix/Cargo.toml34
-rw-r--r--src/tools/cargo/crates/rustfix/Changelog.md79
l---------src/tools/cargo/crates/rustfix/LICENSE-APACHE1
l---------src/tools/cargo/crates/rustfix/LICENSE-MIT1
-rw-r--r--src/tools/cargo/crates/rustfix/Readme.md29
-rw-r--r--src/tools/cargo/crates/rustfix/examples/fix-json.rs44
-rw-r--r--src/tools/cargo/crates/rustfix/proptest-regressions/replace.txt8
-rw-r--r--src/tools/cargo/crates/rustfix/src/diagnostics.rs115
-rw-r--r--src/tools/cargo/crates/rustfix/src/error.rs21
-rw-r--r--src/tools/cargo/crates/rustfix/src/lib.rs306
-rw-r--r--src/tools/cargo/crates/rustfix/src/replace.rs329
-rw-r--r--src/tools/cargo/crates/rustfix/tests/edge-cases/empty.json42
-rw-r--r--src/tools/cargo/crates/rustfix/tests/edge-cases/empty.rs0
-rw-r--r--src/tools/cargo/crates/rustfix/tests/edge-cases/indented_whitespace.json60
-rw-r--r--src/tools/cargo/crates/rustfix/tests/edge-cases/no_main.json33
-rw-r--r--src/tools/cargo/crates/rustfix/tests/edge-cases/no_main.rs1
-rw-r--r--src/tools/cargo/crates/rustfix/tests/edge-cases/out_of_bounds.recorded.json43
-rw-r--r--src/tools/cargo/crates/rustfix/tests/edge-cases/utf8_idents.recorded.json59
-rw-r--r--src/tools/cargo/crates/rustfix/tests/edge_cases.rs25
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/E0178.fixed.rs10
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/E0178.json70
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/E0178.rs10
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/closure-immutable-outer-variable.fixed.rs10
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/closure-immutable-outer-variable.json70
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/closure-immutable-outer-variable.rs10
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/handle-insert-only.fixed.rs8
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/handle-insert-only.json68
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/handle-insert-only.rs8
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/lt-generic-comp.fixed.rs7
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/lt-generic-comp.json87
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/lt-generic-comp.rs7
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/multiple-solutions.fixed.rs5
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/multiple-solutions.json114
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/multiple-solutions.rs5
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/replace-only-one-char.fixed.rs3
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/replace-only-one-char.json70
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/replace-only-one-char.rs3
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/str-lit-type-mismatch.fixed.rs5
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/str-lit-type-mismatch.json218
-rw-r--r--src/tools/cargo/crates/rustfix/tests/everything/str-lit-type-mismatch.rs5
-rw-r--r--src/tools/cargo/crates/rustfix/tests/parse_and_replace.rs234
-rw-r--r--src/tools/cargo/crates/semver-check/Cargo.toml3
-rw-r--r--src/tools/cargo/crates/semver-check/src/main.rs2
-rw-r--r--src/tools/cargo/crates/xtask-build-man/Cargo.toml3
-rw-r--r--src/tools/cargo/crates/xtask-build-man/src/main.rs2
-rw-r--r--src/tools/cargo/crates/xtask-bump-check/Cargo.toml3
-rw-r--r--src/tools/cargo/crates/xtask-bump-check/src/xtask.rs21
-rw-r--r--src/tools/cargo/crates/xtask-stale-label/Cargo.toml3
-rw-r--r--src/tools/cargo/crates/xtask-stale-label/src/main.rs2
77 files changed, 2624 insertions, 461 deletions
diff --git a/src/tools/cargo/crates/cargo-platform/Cargo.toml b/src/tools/cargo/crates/cargo-platform/Cargo.toml
index 786948ff3..baa179291 100644
--- a/src/tools/cargo/crates/cargo-platform/Cargo.toml
+++ b/src/tools/cargo/crates/cargo-platform/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cargo-platform"
-version = "0.1.6"
+version = "0.1.7"
edition.workspace = true
license.workspace = true
rust-version = "1.70.0" # MSRV:3
@@ -11,3 +11,6 @@ description = "Cargo's representation of a target platform."
[dependencies]
serde.workspace = true
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/cargo-platform/examples/matches.rs b/src/tools/cargo/crates/cargo-platform/examples/matches.rs
index 1b438fb11..11318a7df 100644
--- a/src/tools/cargo/crates/cargo-platform/examples/matches.rs
+++ b/src/tools/cargo/crates/cargo-platform/examples/matches.rs
@@ -1,6 +1,8 @@
//! This example demonstrates how to filter a Platform based on the current
//! host target.
+#![allow(clippy::print_stdout)]
+
use cargo_platform::{Cfg, Platform};
use std::process::Command;
use std::str::FromStr;
diff --git a/src/tools/cargo/crates/cargo-test-macro/Cargo.toml b/src/tools/cargo/crates/cargo-test-macro/Cargo.toml
index 1e81ab314..17ca326f6 100644
--- a/src/tools/cargo/crates/cargo-test-macro/Cargo.toml
+++ b/src/tools/cargo/crates/cargo-test-macro/Cargo.toml
@@ -12,3 +12,6 @@ publish = false
[lib]
proc-macro = true
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/cargo-test-macro/src/lib.rs b/src/tools/cargo/crates/cargo-test-macro/src/lib.rs
index 937fbce6b..14672ab94 100644
--- a/src/tools/cargo/crates/cargo-test-macro/src/lib.rs
+++ b/src/tools/cargo/crates/cargo-test-macro/src/lib.rs
@@ -208,10 +208,11 @@ fn has_command(command: &str) -> bool {
let output = match Command::new(command).arg("--version").output() {
Ok(output) => output,
Err(e) => {
- // hg is not installed on GitHub macOS or certain constrained
- // environments like Docker. Consider installing it if Cargo gains
- // more hg support, but otherwise it isn't critical.
- if is_ci() && command != "hg" {
+ // * hg is not installed on GitHub macOS or certain constrained
+ // environments like Docker. Consider installing it if Cargo
+ // gains more hg support, but otherwise it isn't critical.
+ // * lldb is not pre-installed on Ubuntu and Windows, so skip.
+ if is_ci() && !["hg", "lldb"].contains(&command) {
panic!(
"expected command `{}` to be somewhere in PATH: {}",
command, e
diff --git a/src/tools/cargo/crates/cargo-test-support/Cargo.toml b/src/tools/cargo/crates/cargo-test-support/Cargo.toml
index fc32e1c9c..1098d598d 100644
--- a/src/tools/cargo/crates/cargo-test-support/Cargo.toml
+++ b/src/tools/cargo/crates/cargo-test-support/Cargo.toml
@@ -29,6 +29,10 @@ tar.workspace = true
time.workspace = true
toml.workspace = true
url.workspace = true
+walkdir.workspace = true
[target.'cfg(windows)'.dependencies]
windows-sys = { workspace = true, features = ["Win32_Storage_FileSystem"] }
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/cargo-test-support/build.rs b/src/tools/cargo/crates/cargo-test-support/build.rs
index 478da7d99..8854f461a 100644
--- a/src/tools/cargo/crates/cargo-test-support/build.rs
+++ b/src/tools/cargo/crates/cargo-test-support/build.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::disallowed_methods)]
+
fn main() {
println!(
"cargo:rustc-env=NATIVE_ARCH={}",
diff --git a/src/tools/cargo/crates/cargo-test-support/src/compare.rs b/src/tools/cargo/crates/cargo-test-support/src/compare.rs
index d9e8d5454..fc1663d34 100644
--- a/src/tools/cargo/crates/cargo-test-support/src/compare.rs
+++ b/src/tools/cargo/crates/cargo-test-support/src/compare.rs
@@ -591,15 +591,36 @@ fn find_json_mismatch_r<'a>(
.next()
}
(&Object(ref l), &Object(ref r)) => {
- let same_keys = l.len() == r.len() && l.keys().all(|k| r.contains_key(k));
- if !same_keys {
- return Some((expected, actual));
+ let mut expected_entries = l.iter();
+ let mut actual_entries = r.iter();
+
+ // Compilers older than 1.76 do not produce $message_type.
+ // Treat it as optional for now.
+ let mut expected_entries_without_message_type;
+ let expected_entries: &mut dyn Iterator<Item = _> =
+ if l.contains_key("$message_type") && !r.contains_key("$message_type") {
+ expected_entries_without_message_type =
+ expected_entries.filter(|entry| entry.0 != "$message_type");
+ &mut expected_entries_without_message_type
+ } else {
+ &mut expected_entries
+ };
+
+ loop {
+ match (expected_entries.next(), actual_entries.next()) {
+ (None, None) => return None,
+ (Some((expected_key, expected_value)), Some((actual_key, actual_value)))
+ if expected_key == actual_key =>
+ {
+ if let mismatch @ Some(_) =
+ find_json_mismatch_r(expected_value, actual_value, cwd)
+ {
+ return mismatch;
+ }
+ }
+ _ => return Some((expected, actual)),
+ }
}
-
- l.values()
- .zip(r.values())
- .filter_map(|(l, r)| find_json_mismatch_r(l, r, cwd))
- .next()
}
(&Null, &Null) => None,
// Magic string literal `"{...}"` acts as wildcard for any sub-JSON.
diff --git a/src/tools/cargo/crates/cargo-test-support/src/lib.rs b/src/tools/cargo/crates/cargo-test-support/src/lib.rs
index ec74ce0b2..4e3ef6118 100644
--- a/src/tools/cargo/crates/cargo-test-support/src/lib.rs
+++ b/src/tools/cargo/crates/cargo-test-support/src/lib.rs
@@ -2,7 +2,9 @@
//!
//! See <https://rust-lang.github.io/cargo/contrib/> for a guide on writing tests.
-#![allow(clippy::all)]
+#![allow(clippy::disallowed_methods)]
+#![allow(clippy::print_stderr)]
+#![allow(clippy::print_stdout)]
use std::env;
use std::ffi::OsStr;
@@ -521,29 +523,6 @@ pub fn cargo_exe() -> PathBuf {
snapbox::cmd::cargo_bin("cargo")
}
-/// A wrapper around `rustc` instead of calling `clippy`.
-pub fn wrapped_clippy_driver() -> PathBuf {
- let clippy_driver = project()
- .at(paths::global_root().join("clippy-driver"))
- .file("Cargo.toml", &basic_manifest("clippy-driver", "0.0.1"))
- .file(
- "src/main.rs",
- r#"
- fn main() {
- let mut args = std::env::args_os();
- let _me = args.next().unwrap();
- let rustc = args.next().unwrap();
- let status = std::process::Command::new(rustc).args(args).status().unwrap();
- std::process::exit(status.code().unwrap_or(1));
- }
- "#,
- )
- .build();
- clippy_driver.cargo("build").run();
-
- clippy_driver.bin("clippy-driver")
-}
-
/// This is the raw output from the process.
///
/// This is similar to `std::process::Output`, however the `status` is
diff --git a/src/tools/cargo/crates/cargo-test-support/src/paths.rs b/src/tools/cargo/crates/cargo-test-support/src/paths.rs
index 50040e1d4..a07491bcc 100644
--- a/src/tools/cargo/crates/cargo-test-support/src/paths.rs
+++ b/src/tools/cargo/crates/cargo-test-support/src/paths.rs
@@ -114,6 +114,10 @@ pub trait CargoPathExt {
fn rm_rf(&self);
fn mkdir_p(&self);
+ /// Returns a list of all files and directories underneath the given
+ /// directory, recursively, including the starting path.
+ fn ls_r(&self) -> Vec<PathBuf>;
+
fn move_into_the_past(&self) {
self.move_in_time(|sec, nsec| (sec - 3600, nsec))
}
@@ -155,6 +159,14 @@ impl CargoPathExt for Path {
.unwrap_or_else(|e| panic!("failed to mkdir_p {}: {}", self.display(), e))
}
+ fn ls_r(&self) -> Vec<PathBuf> {
+ walkdir::WalkDir::new(self)
+ .sort_by_file_name()
+ .into_iter()
+ .filter_map(|e| e.map(|e| e.path().to_owned()).ok())
+ .collect()
+ }
+
fn move_in_time<F>(&self, travel_amount: F)
where
F: Fn(i64, u32) -> (i64, u32),
diff --git a/src/tools/cargo/crates/cargo-test-support/src/registry.rs b/src/tools/cargo/crates/cargo-test-support/src/registry.rs
index 853829c56..6f9d558a9 100644
--- a/src/tools/cargo/crates/cargo-test-support/src/registry.rs
+++ b/src/tools/cargo/crates/cargo-test-support/src/registry.rs
@@ -557,6 +557,7 @@ pub struct Dependency {
registry: Option<String>,
package: Option<String>,
optional: bool,
+ default_features: bool,
}
/// Entry with data that corresponds to [`tar::EntryType`].
@@ -1161,12 +1162,15 @@ fn save_new_crate(
"name": name,
"req": dep.version_req,
"features": dep.features,
- "default_features": true,
+ "default_features": dep.default_features,
"target": dep.target,
"optional": dep.optional,
"kind": dep.kind,
"registry": dep.registry,
"package": package,
+ "artifact": dep.artifact,
+ "bindep_target": dep.bindep_target,
+ "lib": dep.lib,
})
})
.collect::<Vec<_>>();
@@ -1179,7 +1183,7 @@ fn save_new_crate(
new_crate.features,
false,
new_crate.links,
- None,
+ new_crate.rust_version.as_deref(),
None,
);
@@ -1415,7 +1419,7 @@ impl Package {
"name": dep.name,
"req": dep.vers,
"features": dep.features,
- "default_features": true,
+ "default_features": dep.default_features,
"target": dep.target,
"artifact": artifact,
"bindep_target": dep.bindep_target,
@@ -1523,6 +1527,30 @@ impl Package {
manifest.push_str(&format!("rust-version = \"{}\"", version));
}
+ if !self.features.is_empty() {
+ let features: Vec<String> = self
+ .features
+ .iter()
+ .map(|(feature, features)| {
+ if features.is_empty() {
+ format!("{} = []", feature)
+ } else {
+ format!(
+ "{} = [{}]",
+ feature,
+ features
+ .iter()
+ .map(|s| format!("\"{}\"", s))
+ .collect::<Vec<_>>()
+ .join(", ")
+ )
+ }
+ })
+ .collect();
+
+ manifest.push_str(&format!("\n[features]\n{}", features.join("\n")));
+ }
+
for dep in self.deps.iter() {
let target = match dep.target {
None => String::new(),
@@ -1540,6 +1568,9 @@ impl Package {
"#,
target, kind, dep.name, dep.vers
));
+ if dep.optional {
+ manifest.push_str("optional = true\n");
+ }
if let Some(artifact) = &dep.artifact {
manifest.push_str(&format!("artifact = \"{}\"\n", artifact));
}
@@ -1553,6 +1584,21 @@ impl Package {
assert_eq!(registry, "alternative");
manifest.push_str(&format!("registry-index = \"{}\"", alt_registry_url()));
}
+ if !dep.default_features {
+ manifest.push_str("default-features = false\n");
+ }
+ if !dep.features.is_empty() {
+ let mut features = String::new();
+ serde::Serialize::serialize(
+ &dep.features,
+ toml::ser::ValueSerializer::new(&mut features),
+ )
+ .unwrap();
+ manifest.push_str(&format!("features = {}\n", features));
+ }
+ if let Some(package) = &dep.package {
+ manifest.push_str(&format!("package = \"{}\"\n", package));
+ }
}
if self.proc_macro {
manifest.push_str("[lib]\nproc-macro = true\n");
@@ -1631,6 +1677,7 @@ impl Dependency {
package: None,
optional: false,
registry: None,
+ default_features: true,
}
}
@@ -1683,4 +1730,10 @@ impl Dependency {
self.optional = optional;
self
}
+
+ /// Adds `default-features = false` if the argument is `false`.
+ pub fn default_features(&mut self, default_features: bool) -> &mut Self {
+ self.default_features = default_features;
+ self
+ }
}
diff --git a/src/tools/cargo/crates/cargo-test-support/src/tools.rs b/src/tools/cargo/crates/cargo-test-support/src/tools.rs
index 2ce2849ae..b6fa4092f 100644
--- a/src/tools/cargo/crates/cargo-test-support/src/tools.rs
+++ b/src/tools/cargo/crates/cargo-test-support/src/tools.rs
@@ -7,6 +7,7 @@ use std::sync::OnceLock;
static ECHO_WRAPPER: OnceLock<Mutex<Option<PathBuf>>> = OnceLock::new();
static ECHO: OnceLock<Mutex<Option<PathBuf>>> = OnceLock::new();
+static CLIPPY_DRIVER: OnceLock<Mutex<Option<PathBuf>>> = OnceLock::new();
/// Returns the path to an executable that works as a wrapper around rustc.
///
@@ -107,3 +108,34 @@ pub fn echo_subcommand() -> Project {
p.cargo("build").run();
p
}
+
+/// A wrapper around `rustc` instead of calling `clippy`.
+pub fn wrapped_clippy_driver() -> PathBuf {
+ let mut lock = CLIPPY_DRIVER
+ .get_or_init(|| Default::default())
+ .lock()
+ .unwrap();
+ if let Some(path) = &*lock {
+ return path.clone();
+ }
+ let clippy_driver = project()
+ .at(paths::global_root().join("clippy-driver"))
+ .file("Cargo.toml", &basic_manifest("clippy-driver", "0.0.1"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ let mut args = std::env::args_os();
+ let _me = args.next().unwrap();
+ let rustc = args.next().unwrap();
+ let status = std::process::Command::new(rustc).args(args).status().unwrap();
+ std::process::exit(status.code().unwrap_or(1));
+ }
+ "#,
+ )
+ .build();
+ clippy_driver.cargo("build").run();
+ let path = clippy_driver.bin("clippy-driver");
+ *lock = Some(path.clone());
+ path
+}
diff --git a/src/tools/cargo/crates/cargo-util/Cargo.toml b/src/tools/cargo/crates/cargo-util/Cargo.toml
index 616a79c5e..3fd6bdeca 100644
--- a/src/tools/cargo/crates/cargo-util/Cargo.toml
+++ b/src/tools/cargo/crates/cargo-util/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cargo-util"
-version = "0.2.8"
+version = "0.2.9"
rust-version.workspace = true
edition.workspace = true
license.workspace = true
@@ -12,6 +12,7 @@ description = "Miscellaneous support code used by Cargo."
anyhow.workspace = true
filetime.workspace = true
hex.workspace = true
+ignore.workspace = true
jobserver.workspace = true
libc.workspace = true
same-file.workspace = true
@@ -27,3 +28,6 @@ core-foundation.workspace = true
[target.'cfg(windows)'.dependencies]
miow.workspace = true
windows-sys = { workspace = true, features = ["Win32_Storage_FileSystem", "Win32_Foundation", "Win32_System_Console"] }
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/cargo-util/src/du.rs b/src/tools/cargo/crates/cargo-util/src/du.rs
new file mode 100644
index 000000000..14634c47b
--- /dev/null
+++ b/src/tools/cargo/crates/cargo-util/src/du.rs
@@ -0,0 +1,78 @@
+//! A simple disk usage estimator.
+
+use anyhow::{Context, Result};
+use ignore::overrides::OverrideBuilder;
+use ignore::{WalkBuilder, WalkState};
+use std::path::Path;
+use std::sync::{Arc, Mutex};
+
+/// Determines the disk usage of all files in the given directory.
+///
+/// The given patterns are gitignore style patterns relative to the given
+/// path. If there are patterns, it will only count things matching that
+/// pattern. `!` can be used to exclude things. See [`OverrideBuilder::add`]
+/// for more info.
+///
+/// This is a primitive implementation that doesn't handle hard links, and
+/// isn't particularly fast (for example, not using `getattrlistbulk` on
+/// macOS). It also only uses actual byte sizes instead of block counts (and
+/// thus vastly undercounts directories with lots of small files). It would be
+/// nice to improve this or replace it with something better.
+pub fn du(path: &Path, patterns: &[&str]) -> Result<u64> {
+ du_inner(path, patterns).with_context(|| format!("failed to walk `{}`", path.display()))
+}
+
+fn du_inner(path: &Path, patterns: &[&str]) -> Result<u64> {
+ let mut builder = OverrideBuilder::new(path);
+ for pattern in patterns {
+ builder.add(pattern)?;
+ }
+ let overrides = builder.build()?;
+
+ let mut builder = WalkBuilder::new(path);
+ builder
+ .overrides(overrides)
+ .hidden(false)
+ .parents(false)
+ .ignore(false)
+ .git_global(false)
+ .git_ignore(false)
+ .git_exclude(false);
+ let walker = builder.build_parallel();
+ let total = Arc::new(Mutex::new(0u64));
+ // A slot used to indicate there was an error while walking.
+ //
+ // It is possible that more than one error happens (such as in different
+ // threads). The error returned is arbitrary in that case.
+ let err = Arc::new(Mutex::new(None));
+ walker.run(|| {
+ Box::new(|entry| {
+ match entry {
+ Ok(entry) => match entry.metadata() {
+ Ok(meta) => {
+ if meta.is_file() {
+ let mut lock = total.lock().unwrap();
+ *lock += meta.len();
+ }
+ }
+ Err(e) => {
+ *err.lock().unwrap() = Some(e.into());
+ return WalkState::Quit;
+ }
+ },
+ Err(e) => {
+ *err.lock().unwrap() = Some(e.into());
+ return WalkState::Quit;
+ }
+ }
+ WalkState::Continue
+ })
+ });
+
+ if let Some(e) = err.lock().unwrap().take() {
+ return Err(e);
+ }
+
+ let total = *total.lock().unwrap();
+ Ok(total)
+}
diff --git a/src/tools/cargo/crates/cargo-util/src/lib.rs b/src/tools/cargo/crates/cargo-util/src/lib.rs
index 0cbc920ec..717e89ba4 100644
--- a/src/tools/cargo/crates/cargo-util/src/lib.rs
+++ b/src/tools/cargo/crates/cargo-util/src/lib.rs
@@ -1,10 +1,14 @@
//! Miscellaneous support code used by Cargo.
+#![allow(clippy::disallowed_methods)]
+
pub use self::read2::read2;
+pub use du::du;
pub use process_builder::ProcessBuilder;
pub use process_error::{exit_status_to_string, is_simple_exit_code, ProcessError};
pub use sha256::Sha256;
+mod du;
pub mod paths;
mod process_builder;
mod process_error;
diff --git a/src/tools/cargo/crates/cargo-util/src/paths.rs b/src/tools/cargo/crates/cargo-util/src/paths.rs
index f405c8f97..743e3f3a8 100644
--- a/src/tools/cargo/crates/cargo-util/src/paths.rs
+++ b/src/tools/cargo/crates/cargo-util/src/paths.rs
@@ -719,14 +719,17 @@ pub fn exclude_from_backups_and_indexing(p: impl AsRef<Path>) {
/// * CACHEDIR.TAG files supported by various tools in a platform-independent way
fn exclude_from_backups(path: &Path) {
exclude_from_time_machine(path);
- let _ = std::fs::write(
- path.join("CACHEDIR.TAG"),
- "Signature: 8a477f597d28d172789f06886806bc55
+ let file = path.join("CACHEDIR.TAG");
+ if !file.exists() {
+ let _ = std::fs::write(
+ file,
+ "Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/
",
- );
- // Similarly to exclude_from_time_machine() we ignore errors here as it's an optional feature.
+ );
+ // Similarly to exclude_from_time_machine() we ignore errors here as it's an optional feature.
+ }
}
/// Marks the directory as excluded from content indexing.
diff --git a/src/tools/cargo/crates/crates-io/Cargo.toml b/src/tools/cargo/crates/crates-io/Cargo.toml
index f1b92602e..bf2b20cf7 100644
--- a/src/tools/cargo/crates/crates-io/Cargo.toml
+++ b/src/tools/cargo/crates/crates-io/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "crates-io"
-version = "0.39.1"
+version = "0.39.2"
rust-version.workspace = true
edition.workspace = true
license.workspace = true
@@ -20,3 +20,6 @@ serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
thiserror.workspace = true
url.workspace = true
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/crates-io/lib.rs b/src/tools/cargo/crates/crates-io/lib.rs
index 1764ce527..468900e55 100644
--- a/src/tools/cargo/crates/crates-io/lib.rs
+++ b/src/tools/cargo/crates/crates-io/lib.rs
@@ -1,5 +1,3 @@
-#![allow(clippy::all)]
-
use std::collections::BTreeMap;
use std::fs::File;
use std::io::prelude::*;
@@ -438,7 +436,8 @@ impl Registry {
.map(|s| s.errors.into_iter().map(|s| s.detail).collect::<Vec<_>>());
match (self.handle.response_code()?, errors) {
- (0, None) | (200, None) => Ok(body),
+ (0, None) => Ok(body),
+ (code, None) if is_success(code) => Ok(body),
(code, Some(errors)) => Err(Error::Api {
code,
headers,
@@ -453,8 +452,12 @@ impl Registry {
}
}
+fn is_success(code: u32) -> bool {
+ code >= 200 && code < 300
+}
+
fn status(code: u32) -> String {
- if code == 200 {
+ if is_success(code) {
String::new()
} else {
let reason = reason(code);
diff --git a/src/tools/cargo/crates/home/CHANGELOG.md b/src/tools/cargo/crates/home/CHANGELOG.md
index 58f960cc3..5b1a2f8ea 100644
--- a/src/tools/cargo/crates/home/CHANGELOG.md
+++ b/src/tools/cargo/crates/home/CHANGELOG.md
@@ -4,7 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## 0.5.6
+## 0.5.9 - 2023-12-15
+
+- Replace SHGetFolderPathW with SHGetKnownFolderPath
+ [#13173](https://github.com/rust-lang/cargo/pull/13173)
+- Update windows-sys to 0.52
+ [#13089](https://github.com/rust-lang/cargo/pull/13089)
+- Set MSRV to 1.70.0
+ [#12654](https://github.com/rust-lang/cargo/pull/12654)
- Fixed & enhanced documentation.
[#12047](https://github.com/rust-lang/cargo/pull/12047)
diff --git a/src/tools/cargo/crates/home/Cargo.toml b/src/tools/cargo/crates/home/Cargo.toml
index 702a14e55..33cd6ba5a 100644
--- a/src/tools/cargo/crates/home/Cargo.toml
+++ b/src/tools/cargo/crates/home/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "home"
-version = "0.5.8"
+version = "0.5.9"
authors = ["Brian Anderson <andersrb@gmail.com>"]
rust-version = "1.70.0" # MSRV:3
documentation = "https://docs.rs/home"
@@ -17,4 +17,7 @@ repository = "https://github.com/rust-lang/cargo"
description = "Shared definitions of home directories."
[target.'cfg(windows)'.dependencies]
-windows-sys = { workspace = true, features = ["Win32_Foundation", "Win32_UI_Shell"] }
+windows-sys = { workspace = true, features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_System_Com"] }
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/home/src/lib.rs b/src/tools/cargo/crates/home/src/lib.rs
index 4aee7383b..bbe7c32ca 100644
--- a/src/tools/cargo/crates/home/src/lib.rs
+++ b/src/tools/cargo/crates/home/src/lib.rs
@@ -18,7 +18,7 @@
//!
//! [discussion]: https://github.com/rust-lang/rust/pull/46799#issuecomment-361156935
-#![deny(rust_2018_idioms)]
+#![allow(clippy::disallowed_methods)]
pub mod env;
@@ -44,11 +44,11 @@ use std::path::{Path, PathBuf};
///
/// Returns the value of the `USERPROFILE` environment variable if it is set
/// **and** it is not an empty string. Otherwise, it tries to determine the
-/// home directory by invoking the [`SHGetFolderPathW`][shgfp] function with
-/// [`CSIDL_PROFILE`][csidl].
+/// home directory by invoking the [`SHGetKnownFolderPath`][shgkfp] function with
+/// [`FOLDERID_Profile`][knownfolderid].
///
-/// [shgfp]: https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetfolderpathw
-/// [csidl]: https://learn.microsoft.com/en-us/windows/win32/shell/csidl
+/// [shgkfp]: https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
+/// [knownfolderid]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
///
/// # Examples
///
diff --git a/src/tools/cargo/crates/home/src/windows.rs b/src/tools/cargo/crates/home/src/windows.rs
index a35dc9c57..c9a63d97b 100644
--- a/src/tools/cargo/crates/home/src/windows.rs
+++ b/src/tools/cargo/crates/home/src/windows.rs
@@ -2,9 +2,12 @@ use std::env;
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::path::PathBuf;
+use std::ptr;
+use std::slice;
-use windows_sys::Win32::Foundation::{MAX_PATH, S_OK};
-use windows_sys::Win32::UI::Shell::{SHGetFolderPathW, CSIDL_PROFILE};
+use windows_sys::Win32::Foundation::S_OK;
+use windows_sys::Win32::System::Com::CoTaskMemFree;
+use windows_sys::Win32::UI::Shell::{FOLDERID_Profile, SHGetKnownFolderPath, KF_FLAG_DONT_VERIFY};
pub fn home_dir_inner() -> Option<PathBuf> {
env::var_os("USERPROFILE")
@@ -16,15 +19,19 @@ pub fn home_dir_inner() -> Option<PathBuf> {
#[cfg(not(target_vendor = "uwp"))]
fn home_dir_crt() -> Option<PathBuf> {
unsafe {
- let mut path: Vec<u16> = Vec::with_capacity(MAX_PATH as usize);
- match SHGetFolderPathW(0, CSIDL_PROFILE as i32, 0, 0, path.as_mut_ptr()) {
+ let mut path = ptr::null_mut();
+ match SHGetKnownFolderPath(&FOLDERID_Profile, KF_FLAG_DONT_VERIFY as u32, 0, &mut path) {
S_OK => {
- let len = wcslen(path.as_ptr());
- path.set_len(len);
- let s = OsString::from_wide(&path);
+ let path_slice = slice::from_raw_parts(path, wcslen(path));
+ let s = OsString::from_wide(&path_slice);
+ CoTaskMemFree(path.cast());
Some(PathBuf::from(s))
}
- _ => None,
+ _ => {
+ // Free any allocated memory even on failure. A null ptr is a no-op for `CoTaskMemFree`.
+ CoTaskMemFree(path.cast());
+ None
+ }
}
}
}
diff --git a/src/tools/cargo/crates/mdman/Cargo.toml b/src/tools/cargo/crates/mdman/Cargo.toml
index fd33da3c2..4e86b8e1a 100644
--- a/src/tools/cargo/crates/mdman/Cargo.toml
+++ b/src/tools/cargo/crates/mdman/Cargo.toml
@@ -16,4 +16,7 @@ serde_json.workspace = true
url.workspace = true
[dev-dependencies]
-pretty_assertions.workspace = true
+snapbox.workspace = true
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/mdman/src/main.rs b/src/tools/cargo/crates/mdman/src/main.rs
index facaa5120..3c09bc4dd 100644
--- a/src/tools/cargo/crates/mdman/src/main.rs
+++ b/src/tools/cargo/crates/mdman/src/main.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::print_stderr)]
+
use anyhow::{bail, format_err, Context, Error};
use mdman::{Format, ManMap};
use std::collections::HashMap;
diff --git a/src/tools/cargo/crates/mdman/tests/compare.rs b/src/tools/cargo/crates/mdman/tests/compare.rs
index 3e679d127..fde2c235d 100644
--- a/src/tools/cargo/crates/mdman/tests/compare.rs
+++ b/src/tools/cargo/crates/mdman/tests/compare.rs
@@ -1,11 +1,8 @@
//! Compares input to expected output.
-//!
-//! Use the MDMAN_BLESS environment variable to automatically update the
-//! expected output.
-use mdman::{Format, ManMap};
-use pretty_assertions::assert_eq;
use std::path::PathBuf;
+
+use mdman::{Format, ManMap};
use url::Url;
fn run(name: &str) {
@@ -25,14 +22,7 @@ fn run(name: &str) {
name,
format.extension(section)
);
- if std::env::var("MDMAN_BLESS").is_ok() {
- std::fs::write(&expected_path, result).unwrap();
- } else {
- let expected = std::fs::read_to_string(&expected_path).unwrap();
- // Fix if Windows checked out with autocrlf.
- let expected = expected.replace("\r\n", "\n");
- assert_eq!(expected, result);
- }
+ snapbox::assert_eq_path(expected_path, result);
}
}
diff --git a/src/tools/cargo/crates/mdman/tests/invalid.rs b/src/tools/cargo/crates/mdman/tests/invalid.rs
index cc81d06c4..b8be1ed24 100644
--- a/src/tools/cargo/crates/mdman/tests/invalid.rs
+++ b/src/tools/cargo/crates/mdman/tests/invalid.rs
@@ -1,9 +1,9 @@
//! Tests for errors and invalid input.
-use mdman::{Format, ManMap};
-use pretty_assertions::assert_eq;
use std::path::PathBuf;
+use mdman::{Format, ManMap};
+
fn run(name: &str, expected_error: &str) {
let input = PathBuf::from(format!("tests/invalid/{}", name));
match mdman::convert(&input, Format::Man, None, ManMap::new()) {
@@ -11,7 +11,7 @@ fn run(name: &str, expected_error: &str) {
panic!("expected {} to fail", name);
}
Err(e) => {
- assert_eq!(expected_error, e.to_string());
+ snapbox::assert_eq(expected_error, e.to_string());
}
}
}
diff --git a/src/tools/cargo/crates/resolver-tests/Cargo.toml b/src/tools/cargo/crates/resolver-tests/Cargo.toml
index 8750a3d97..947c44569 100644
--- a/src/tools/cargo/crates/resolver-tests/Cargo.toml
+++ b/src/tools/cargo/crates/resolver-tests/Cargo.toml
@@ -10,3 +10,6 @@ cargo.workspace = true
cargo-util.workspace = true
proptest.workspace = true
varisat.workspace = true
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/resolver-tests/src/lib.rs b/src/tools/cargo/crates/resolver-tests/src/lib.rs
index e2cbcee62..2df7a36bb 100644
--- a/src/tools/cargo/crates/resolver-tests/src/lib.rs
+++ b/src/tools/cargo/crates/resolver-tests/src/lib.rs
@@ -1,9 +1,9 @@
-#![allow(clippy::all)]
+#![allow(clippy::print_stderr)]
use std::cell::RefCell;
use std::cmp::PartialEq;
use std::cmp::{max, min};
-use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
+use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::fmt::Write;
use std::rc::Rc;
@@ -17,7 +17,9 @@ use cargo::core::Resolve;
use cargo::core::{Dependency, PackageId, Registry, Summary};
use cargo::core::{GitReference, SourceId};
use cargo::sources::source::QueryKind;
-use cargo::util::{CargoResult, Config, Graph, IntoUrl, RustVersion};
+use cargo::sources::IndexSummary;
+use cargo::util::{CargoResult, Config, IntoUrl};
+use cargo::util_schemas::manifest::RustVersion;
use proptest::collection::{btree_map, vec};
use proptest::prelude::*;
@@ -69,33 +71,6 @@ pub fn resolve_and_validated(
let out = resolve.sort();
assert_eq!(out.len(), used.len());
- let mut pub_deps: HashMap<PackageId, HashSet<_>> = HashMap::new();
- for &p in out.iter() {
- // make the list of `p` public dependencies
- let mut self_pub_dep = HashSet::new();
- self_pub_dep.insert(p);
- for (dp, deps) in resolve.deps(p) {
- if deps.iter().any(|d| d.is_public()) {
- self_pub_dep.extend(pub_deps[&dp].iter().cloned())
- }
- }
- pub_deps.insert(p, self_pub_dep);
-
- // check if `p` has a public dependencies conflicts
- let seen_dep: BTreeSet<_> = resolve
- .deps(p)
- .flat_map(|(dp, _)| pub_deps[&dp].iter().cloned())
- .collect();
- let seen_dep: Vec<_> = seen_dep.iter().collect();
- for a in seen_dep.windows(2) {
- if a[0].name() == a[1].name() {
- panic!(
- "the package {:?} can publicly see {:?} and {:?}",
- p, a[0], a[1]
- )
- }
- }
- }
let sat_resolve = sat_resolve.unwrap_or_else(|| SatResolve::new(registry));
if !sat_resolve.sat_is_valid_solution(&out) {
panic!(
@@ -131,7 +106,7 @@ pub fn resolve_with_config_raw(
&mut self,
dep: &Dependency,
kind: QueryKind,
- f: &mut dyn FnMut(Summary),
+ f: &mut dyn FnMut(IndexSummary),
) -> Poll<CargoResult<()>> {
for summary in self.list.iter() {
let matched = match kind {
@@ -140,7 +115,7 @@ pub fn resolve_with_config_raw(
};
if matched {
self.used.insert(summary.package_id());
- f(summary.clone());
+ f(IndexSummary::Candidate(summary.clone()));
}
}
Poll::Ready(Ok(()))
@@ -200,7 +175,6 @@ pub fn resolve_with_config_raw(
&mut registry,
&version_prefs,
Some(config),
- true,
);
// The largest test in our suite takes less then 30 sec.
@@ -294,7 +268,7 @@ impl SatResolve {
);
// no two semver compatible versions of the same package
- let by_activations_keys = sat_at_most_one_by_key(
+ sat_at_most_one_by_key(
&mut cnf,
var_for_is_packages_used
.iter()
@@ -312,119 +286,22 @@ impl SatResolve {
let empty_vec = vec![];
- let mut graph: Graph<PackageId, ()> = Graph::new();
-
- let mut version_selected_for: HashMap<
- PackageId,
- HashMap<Dependency, HashMap<_, varisat::Var>>,
- > = HashMap::new();
// active packages need each of there `deps` to be satisfied
for p in registry.iter() {
- graph.add(p.package_id());
for dep in p.dependencies() {
- // This can more easily be written as:
- // !is_active(p) or one of the things that match dep is_active
- // All the complexity, from here to the end, is to support public and private dependencies!
- let mut by_key: HashMap<_, Vec<varisat::Lit>> = HashMap::new();
- for &m in by_name
+ let mut matches: Vec<varisat::Lit> = by_name
.get(dep.package_name().as_str())
.unwrap_or(&empty_vec)
.iter()
.filter(|&p| dep.matches_id(*p))
- {
- graph.link(p.package_id(), m);
- by_key
- .entry(m.as_activations_key())
- .or_default()
- .push(var_for_is_packages_used[&m].positive());
- }
- let keys: HashMap<_, _> = by_key.keys().map(|&k| (k, cnf.new_var())).collect();
-
- // if `p` is active then we need to select one of the keys
- let matches: Vec<_> = keys
- .values()
- .map(|v| v.positive())
- .chain(Some(var_for_is_packages_used[&p.package_id()].negative()))
+ .map(|p| var_for_is_packages_used[&p].positive())
.collect();
+ // ^ the `dep` is satisfied or `p` is not active
+ matches.push(var_for_is_packages_used[&p.package_id()].negative());
cnf.add_clause(&matches);
-
- // if a key is active then we need to select one of the versions
- for (key, vars) in by_key.iter() {
- let mut matches = vars.clone();
- matches.push(keys[key].negative());
- cnf.add_clause(&matches);
- }
-
- version_selected_for
- .entry(p.package_id())
- .or_default()
- .insert(dep.clone(), keys);
}
}
- let topological_order = graph.sort();
-
- // we already ensure there is only one version for each `activations_key` so we can think of
- // `publicly_exports` as being in terms of a set of `activations_key`s
- let mut publicly_exports: HashMap<_, HashMap<_, varisat::Var>> = HashMap::new();
-
- for &key in by_activations_keys.keys() {
- // everything publicly depends on itself
- let var = publicly_exports
- .entry(key)
- .or_default()
- .entry(key)
- .or_insert_with(|| cnf.new_var());
- cnf.add_clause(&[var.positive()]);
- }
-
- // if a `dep` is public then `p` `publicly_exports` all the things that the selected version `publicly_exports`
- for &p in topological_order.iter() {
- if let Some(deps) = version_selected_for.get(&p) {
- let mut p_exports = publicly_exports.remove(&p.as_activations_key()).unwrap();
- for (_, versions) in deps.iter().filter(|(d, _)| d.is_public()) {
- for (ver, sel) in versions {
- for (&export_pid, &export_var) in publicly_exports[ver].iter() {
- let our_var =
- p_exports.entry(export_pid).or_insert_with(|| cnf.new_var());
- cnf.add_clause(&[
- sel.negative(),
- export_var.negative(),
- our_var.positive(),
- ]);
- }
- }
- }
- publicly_exports.insert(p.as_activations_key(), p_exports);
- }
- }
-
- // we already ensure there is only one version for each `activations_key` so we can think of
- // `can_see` as being in terms of a set of `activations_key`s
- // and if `p` `publicly_exports` `export` then it `can_see` `export`
- let mut can_see: HashMap<_, HashMap<_, varisat::Var>> = HashMap::new();
-
- // if `p` has a `dep` that selected `ver` then it `can_see` all the things that the selected version `publicly_exports`
- for (&p, deps) in version_selected_for.iter() {
- let p_can_see = can_see.entry(p).or_default();
- for (_, versions) in deps.iter() {
- for (&ver, sel) in versions {
- for (&export_pid, &export_var) in publicly_exports[&ver].iter() {
- let our_var = p_can_see.entry(export_pid).or_insert_with(|| cnf.new_var());
- cnf.add_clause(&[
- sel.negative(),
- export_var.negative(),
- our_var.positive(),
- ]);
- }
- }
- }
- }
-
- // a package `can_see` only one version by each name
- for (_, see) in can_see.iter() {
- sat_at_most_one_by_key(&mut cnf, see.iter().map(|((name, _, _), &v)| (name, v)));
- }
let mut solver = varisat::Solver::new();
solver.add_formula(&cnf);
@@ -543,14 +420,14 @@ impl ToPkgId for PackageId {
impl<'a> ToPkgId for &'a str {
fn to_pkgid(&self) -> PackageId {
- PackageId::new(*self, "1.0.0", registry_loc()).unwrap()
+ PackageId::try_new(*self, "1.0.0", registry_loc()).unwrap()
}
}
impl<T: AsRef<str>, U: AsRef<str>> ToPkgId for (T, U) {
fn to_pkgid(&self) -> PackageId {
let (name, vers) = self;
- PackageId::new(name.as_ref(), vers.as_ref(), registry_loc()).unwrap()
+ PackageId::try_new(name.as_ref(), vers.as_ref(), registry_loc()).unwrap()
}
}
@@ -596,7 +473,7 @@ pub fn pkg_dep<T: ToPkgId>(name: T, dep: Vec<Dependency>) -> Summary {
}
pub fn pkg_id(name: &str) -> PackageId {
- PackageId::new(name, "1.0.0", registry_loc()).unwrap()
+ PackageId::try_new(name, "1.0.0", registry_loc()).unwrap()
}
fn pkg_id_loc(name: &str, loc: &str) -> PackageId {
@@ -604,7 +481,7 @@ fn pkg_id_loc(name: &str, loc: &str) -> PackageId {
let master = GitReference::Branch("master".to_string());
let source_id = SourceId::for_git(&remote.unwrap(), master).unwrap();
- PackageId::new(name, "1.0.0", source_id).unwrap()
+ PackageId::try_new(name, "1.0.0", source_id).unwrap()
}
pub fn pkg_loc(name: &str, loc: &str) -> Summary {
@@ -643,10 +520,9 @@ pub fn dep(name: &str) -> Dependency {
pub fn dep_req(name: &str, req: &str) -> Dependency {
Dependency::parse(name, Some(req), registry_loc()).unwrap()
}
-pub fn dep_req_kind(name: &str, req: &str, kind: DepKind, public: bool) -> Dependency {
+pub fn dep_req_kind(name: &str, req: &str, kind: DepKind) -> Dependency {
let mut dep = dep_req(name, req);
dep.set_kind(kind);
- dep.set_public(public);
dep
}
@@ -739,8 +615,8 @@ fn meta_test_deep_pretty_print_registry() {
pkg!(("bar", "2.0.0") => [dep_req("baz", "=1.0.1")]),
pkg!(("baz", "1.0.2") => [dep_req("other", "2")]),
pkg!(("baz", "1.0.1")),
- pkg!(("cat", "1.0.2") => [dep_req_kind("other", "2", DepKind::Build, false)]),
- pkg!(("cat", "1.0.3") => [dep_req_kind("other", "2", DepKind::Development, false)]),
+ pkg!(("cat", "1.0.2") => [dep_req_kind("other", "2", DepKind::Build)]),
+ pkg!(("cat", "1.0.3") => [dep_req_kind("other", "2", DepKind::Development)]),
pkg!(("dep_req", "1.0.0")),
pkg!(("dep_req", "2.0.0")),
])
@@ -803,14 +679,7 @@ pub fn registry_strategy(
let max_deps = max_versions * (max_crates * (max_crates - 1)) / shrinkage;
let raw_version_range = (any::<Index>(), any::<Index>());
- let raw_dependency = (
- any::<Index>(),
- any::<Index>(),
- raw_version_range,
- 0..=1,
- Just(false),
- // TODO: ^ this needs to be set back to `any::<bool>()` and work before public & private dependencies can stabilize
- );
+ let raw_dependency = (any::<Index>(), any::<Index>(), raw_version_range, 0..=1);
fn order_index(a: Index, b: Index, size: usize) -> (usize, usize) {
let (a, b) = (a.index(size), b.index(size));
@@ -837,7 +706,7 @@ pub fn registry_strategy(
.collect();
let len_all_pkgid = list_of_pkgid.len();
let mut dependency_by_pkgid = vec![vec![]; len_all_pkgid];
- for (a, b, (c, d), k, p) in raw_dependencies {
+ for (a, b, (c, d), k) in raw_dependencies {
let (a, b) = order_index(a, b, len_all_pkgid);
let (a, b) = if reverse_alphabetical { (b, a) } else { (a, b) };
let ((dep_name, _), _) = list_of_pkgid[a];
@@ -867,7 +736,6 @@ pub fn registry_strategy(
// => DepKind::Development, // Development has no impact so don't gen
_ => panic!("bad index for DepKind"),
},
- p && k == 0,
))
}
diff --git a/src/tools/cargo/crates/resolver-tests/tests/resolve.rs b/src/tools/cargo/crates/resolver-tests/tests/resolve.rs
index dd21502d8..662bad90f 100644
--- a/src/tools/cargo/crates/resolver-tests/tests/resolve.rs
+++ b/src/tools/cargo/crates/resolver-tests/tests/resolve.rs
@@ -6,8 +6,8 @@ use cargo::util::Config;
use cargo_util::is_ci;
use resolver_tests::{
- assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req, dep_req_kind, loc_names, names,
- pkg, pkg_id, pkg_loc, registry, registry_strategy, remove_dep, resolve, resolve_and_validated,
+ assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req, loc_names, names, pkg, pkg_id,
+ pkg_loc, registry, registry_strategy, remove_dep, resolve, resolve_and_validated,
resolve_with_config, PrettyPrintRegistry, SatResolve, ToDep, ToPkgId,
};
@@ -288,192 +288,6 @@ proptest! {
}
#[test]
-#[should_panic(expected = "pub dep")] // The error handling is not yet implemented.
-fn pub_fail() {
- let input = vec![
- pkg!(("a", "0.0.4")),
- pkg!(("a", "0.0.5")),
- pkg!(("e", "0.0.6") => [dep_req_kind("a", "<= 0.0.4", DepKind::Normal, true),]),
- pkg!(("kB", "0.0.3") => [dep_req("a", ">= 0.0.5"),dep("e"),]),
- ];
- let reg = registry(input);
- assert!(resolve_and_validated(vec![dep("kB")], &reg, None).is_err());
-}
-
-#[test]
-fn basic_public_dependency() {
- let reg = registry(vec![
- pkg!(("A", "0.1.0")),
- pkg!(("A", "0.2.0")),
- pkg!("B" => [dep_req_kind("A", "0.1", DepKind::Normal, true)]),
- pkg!("C" => [dep("A"), dep("B")]),
- ]);
-
- let res = resolve_and_validated(vec![dep("C")], &reg, None).unwrap();
- assert_same(
- &res,
- &names(&[
- ("root", "1.0.0"),
- ("C", "1.0.0"),
- ("B", "1.0.0"),
- ("A", "0.1.0"),
- ]),
- );
-}
-
-#[test]
-fn public_dependency_filling_in() {
- // The resolver has an optimization where if a candidate to resolve a dependency
- // has already bean activated then we skip looking at the candidates dependencies.
- // However, we have to be careful as the new path may make pub dependencies invalid.
-
- // Triggering this case requires dependencies to be resolved in a specific order.
- // Fuzzing found this unintuitive case, that triggers this unfortunate order of operations:
- // 1. `d`'s dep on `c` is resolved
- // 2. `d`'s dep on `a` is resolved with `0.1.1`
- // 3. `c`'s dep on `b` is resolved with `0.0.2`
- // 4. `b`'s dep on `a` is resolved with `0.0.6` no pub dev conflict as `b` is private to `c`
- // 5. `d`'s dep on `b` is resolved with `0.0.2` triggering the optimization.
- // Do we notice that `d` has a pub dep conflict on `a`? Lets try it and see.
- let reg = registry(vec![
- pkg!(("a", "0.0.6")),
- pkg!(("a", "0.1.1")),
- pkg!(("b", "0.0.0") => [dep("bad")]),
- pkg!(("b", "0.0.1") => [dep("bad")]),
- pkg!(("b", "0.0.2") => [dep_req_kind("a", "=0.0.6", DepKind::Normal, true)]),
- pkg!("c" => [dep_req("b", ">=0.0.1")]),
- pkg!("d" => [dep("c"), dep("a"), dep("b")]),
- ]);
-
- let res = resolve_and_validated(vec![dep("d")], &reg, None).unwrap();
- assert_same(
- &res,
- &names(&[
- ("root", "1.0.0"),
- ("d", "1.0.0"),
- ("c", "1.0.0"),
- ("b", "0.0.2"),
- ("a", "0.0.6"),
- ]),
- );
-}
-
-#[test]
-fn public_dependency_filling_in_and_update() {
- // The resolver has an optimization where if a candidate to resolve a dependency
- // has already bean activated then we skip looking at the candidates dependencies.
- // However, we have to be careful as the new path may make pub dependencies invalid.
-
- // Triggering this case requires dependencies to be resolved in a specific order.
- // Fuzzing found this unintuitive case, that triggers this unfortunate order of operations:
- // 1. `D`'s dep on `B` is resolved
- // 2. `D`'s dep on `C` is resolved
- // 3. `B`'s dep on `A` is resolved with `0.0.0`
- // 4. `C`'s dep on `B` triggering the optimization.
- // So did we add `A 0.0.0` to the deps `C` can see?
- // Or are we going to resolve `C`'s dep on `A` with `0.0.2`?
- // Lets try it and see.
- let reg = registry(vec![
- pkg!(("A", "0.0.0")),
- pkg!(("A", "0.0.2")),
- pkg!("B" => [dep_req_kind("A", "=0.0.0", DepKind::Normal, true),]),
- pkg!("C" => [dep("A"),dep("B")]),
- pkg!("D" => [dep("B"),dep("C")]),
- ]);
- let res = resolve_and_validated(vec![dep("D")], &reg, None).unwrap();
- assert_same(
- &res,
- &names(&[
- ("root", "1.0.0"),
- ("D", "1.0.0"),
- ("C", "1.0.0"),
- ("B", "1.0.0"),
- ("A", "0.0.0"),
- ]),
- );
-}
-
-#[test]
-fn public_dependency_skipping() {
- // When backtracking due to a failed dependency, if Cargo is
- // trying to be clever and skip irrelevant dependencies, care must
- // the effects of pub dep must be accounted for.
- let input = vec![
- pkg!(("a", "0.2.0")),
- pkg!(("a", "2.0.0")),
- pkg!(("b", "0.0.0") => [dep("bad")]),
- pkg!(("b", "0.2.1") => [dep_req_kind("a", "0.2.0", DepKind::Normal, true)]),
- pkg!("c" => [dep("a"),dep("b")]),
- ];
- let reg = registry(input);
-
- resolve_and_validated(vec![dep("c")], &reg, None).unwrap();
-}
-
-#[test]
-fn public_dependency_skipping_in_backtracking() {
- // When backtracking due to a failed dependency, if Cargo is
- // trying to be clever and skip irrelevant dependencies, care must
- // the effects of pub dep must be accounted for.
- let input = vec![
- pkg!(("A", "0.0.0") => [dep("bad")]),
- pkg!(("A", "0.0.1") => [dep("bad")]),
- pkg!(("A", "0.0.2") => [dep("bad")]),
- pkg!(("A", "0.0.3") => [dep("bad")]),
- pkg!(("A", "0.0.4")),
- pkg!(("A", "0.0.5")),
- pkg!("B" => [dep_req_kind("A", ">= 0.0.3", DepKind::Normal, true)]),
- pkg!("C" => [dep_req("A", "<= 0.0.4"), dep("B")]),
- ];
- let reg = registry(input);
-
- resolve_and_validated(vec![dep("C")], &reg, None).unwrap();
-}
-
-#[test]
-fn public_sat_topological_order() {
- let input = vec![
- pkg!(("a", "0.0.1")),
- pkg!(("a", "0.0.0")),
- pkg!(("b", "0.0.1") => [dep_req_kind("a", "= 0.0.1", DepKind::Normal, true),]),
- pkg!(("b", "0.0.0") => [dep("bad"),]),
- pkg!("A" => [dep_req("a", "= 0.0.0"),dep_req_kind("b", "*", DepKind::Normal, true)]),
- ];
-
- let reg = registry(input);
- assert!(resolve_and_validated(vec![dep("A")], &reg, None).is_err());
-}
-
-#[test]
-fn public_sat_unused_makes_things_pub() {
- let input = vec![
- pkg!(("a", "0.0.1")),
- pkg!(("a", "0.0.0")),
- pkg!(("b", "8.0.1") => [dep_req_kind("a", "= 0.0.1", DepKind::Normal, true),]),
- pkg!(("b", "8.0.0") => [dep_req("a", "= 0.0.1"),]),
- pkg!("c" => [dep_req("b", "= 8.0.0"),dep_req("a", "= 0.0.0"),]),
- ];
- let reg = registry(input);
-
- resolve_and_validated(vec![dep("c")], &reg, None).unwrap();
-}
-
-#[test]
-fn public_sat_unused_makes_things_pub_2() {
- let input = vec![
- pkg!(("c", "0.0.2")),
- pkg!(("c", "0.0.1")),
- pkg!(("a-sys", "0.0.2")),
- pkg!(("a-sys", "0.0.1") => [dep_req_kind("c", "= 0.0.1", DepKind::Normal, true),]),
- pkg!("P" => [dep_req_kind("a-sys", "*", DepKind::Normal, true),dep_req("c", "= 0.0.1"),]),
- pkg!("A" => [dep("P"),dep_req("c", "= 0.0.2"),]),
- ];
- let reg = registry(input);
-
- resolve_and_validated(vec![dep("A")], &reg, None).unwrap();
-}
-
-#[test]
#[should_panic(expected = "assertion failed: !name.is_empty()")]
fn test_dependency_with_empty_name() {
// Bug 5229, dependency-names must not be empty
@@ -1116,41 +930,6 @@ fn resolving_with_constrained_sibling_backtrack_activation() {
}
#[test]
-fn resolving_with_public_constrained_sibling() {
- // It makes sense to resolve most-constrained deps first, but
- // with that logic the backtrack traps here come between the two
- // attempted resolutions of 'constrained'. When backtracking,
- // cargo should skip past them and resume resolution once the
- // number of activations for 'constrained' changes.
- let mut reglist = vec![
- pkg!(("foo", "1.0.0") => [dep_req("bar", "=1.0.0"),
- dep_req("backtrack_trap1", "1.0"),
- dep_req("backtrack_trap2", "1.0"),
- dep_req("constrained", "<=60")]),
- pkg!(("bar", "1.0.0") => [dep_req_kind("constrained", ">=60", DepKind::Normal, true)]),
- ];
- // Bump these to make the test harder, but you'll also need to
- // change the version constraints on `constrained` above. To correctly
- // exercise Cargo, the relationship between the values is:
- // NUM_CONSTRAINED - vsn < NUM_TRAPS < vsn
- // to make sure the traps are resolved between `constrained`.
- const NUM_TRAPS: usize = 45; // min 1
- const NUM_CONSTRAINED: usize = 100; // min 1
- for i in 0..NUM_TRAPS {
- let vsn = format!("1.0.{}", i);
- reglist.push(pkg!(("backtrack_trap1", vsn.clone())));
- reglist.push(pkg!(("backtrack_trap2", vsn.clone())));
- }
- for i in 0..NUM_CONSTRAINED {
- let vsn = format!("{}.0.0", i);
- reglist.push(pkg!(("constrained", vsn.clone())));
- }
- let reg = registry(reglist);
-
- let _ = resolve_and_validated(vec![dep_req("foo", "1")], &reg, None);
-}
-
-#[test]
fn resolving_with_constrained_sibling_transitive_dep_effects() {
// When backtracking due to a failed dependency, if Cargo is
// trying to be clever and skip irrelevant dependencies, care must
diff --git a/src/tools/cargo/crates/rustfix/Cargo.toml b/src/tools/cargo/crates/rustfix/Cargo.toml
new file mode 100644
index 000000000..7947f0268
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "rustfix"
+version = "0.7.0"
+authors = [
+ "Pascal Hertleif <killercup@gmail.com>",
+ "Oliver Schneider <oli-obk@users.noreply.github.com>",
+]
+rust-version = "1.70.0" # MSRV:3
+edition.workspace = true
+license.workspace = true
+homepage = "https://github.com/rust-lang/cargo"
+repository = "https://github.com/rust-lang/cargo"
+description = "Automatically apply the suggestions made by rustc"
+documentation = "https://docs.rs/rustfix"
+exclude = [
+ "examples/*",
+ "tests/*",
+]
+
+[dependencies]
+serde = { workspace = true, features = ["derive"] }
+serde_json.workspace = true
+thiserror.workspace = true
+tracing.workspace = true
+
+[dev-dependencies]
+anyhow.workspace = true
+proptest.workspace = true
+similar = "2.3.0"
+tempfile.workspace = true
+tracing-subscriber.workspace = true
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/rustfix/Changelog.md b/src/tools/cargo/crates/rustfix/Changelog.md
new file mode 100644
index 000000000..1b57320e6
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/Changelog.md
@@ -0,0 +1,79 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [0.4.6] - 2019-07-16
+
+### Changed
+
+Internal changes:
+
+- Change example to automatically determine filename
+- Migrate to Rust 2018
+- use `derive` feature over `serde_derive` crate
+
+## [0.4.5] - 2019-03-26
+
+### Added
+
+- Implement common traits for Diagnostic and related types
+
+### Fixed
+
+- Fix out of bounds access in parse_snippet
+
+## [0.4.4] - 2018-12-13
+
+### Added
+
+- Make Diagnostic::rendered public.
+
+### Changed
+
+- Revert faulty "Allow multiple solutions in a suggestion"
+
+## [0.4.3] - 2018-12-09 - *yanked!*
+
+### Added
+
+- Allow multiple solutions in a suggestion
+
+### Changed
+
+- use `RUSTC` environment var if present
+
+## [0.4.2] - 2018-07-31
+
+### Added
+
+- Expose an interface to apply fixes on-by-one
+
+### Changed
+
+- Handle invalid snippets instead of panicking
+
+## [0.4.1] - 2018-07-26
+
+### Changed
+
+- Ignore duplicate replacements
+
+## [0.4.0] - 2018-05-23
+
+### Changed
+
+- Filter by machine applicability by default
+
+[Unreleased]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.6...HEAD
+[0.4.6]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.5...rustfix-0.4.6
+[0.4.5]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.4...rustfix-0.4.5
+[0.4.4]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.3...rustfix-0.4.4
+[0.4.3]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.2...rustfix-0.4.3
+[0.4.2]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.1...rustfix-0.4.2
+[0.4.1]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.0...rustfix-0.4.1
+[0.4.0]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.0
diff --git a/src/tools/cargo/crates/rustfix/LICENSE-APACHE b/src/tools/cargo/crates/rustfix/LICENSE-APACHE
new file mode 120000
index 000000000..1cd601d0a
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/LICENSE-APACHE
@@ -0,0 +1 @@
+../../LICENSE-APACHE \ No newline at end of file
diff --git a/src/tools/cargo/crates/rustfix/LICENSE-MIT b/src/tools/cargo/crates/rustfix/LICENSE-MIT
new file mode 120000
index 000000000..b2cfbdc7b
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/LICENSE-MIT
@@ -0,0 +1 @@
+../../LICENSE-MIT \ No newline at end of file
diff --git a/src/tools/cargo/crates/rustfix/Readme.md b/src/tools/cargo/crates/rustfix/Readme.md
new file mode 100644
index 000000000..0546e6018
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/Readme.md
@@ -0,0 +1,29 @@
+# rustfix
+
+[![Latest Version](https://img.shields.io/crates/v/rustfix.svg)](https://crates.io/crates/rustfix)
+[![Rust Documentation](https://docs.rs/rustfix/badge.svg)](https://docs.rs/rustfix)
+
+Rustfix is a library defining useful structures that represent fix suggestions from rustc.
+
+This is a low-level library. You pass it the JSON output from `rustc`, and you can then use it to apply suggestions to in-memory strings. This library doesn't execute commands, or read or write from the filesystem.
+
+If you are looking for the [`cargo fix`] implementation, the core of it is located in [`cargo::ops::fix`].
+
+[`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html
+[`cargo::ops::fix`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/ops/fix.rs
+
+## License
+
+Licensed under either of
+
+- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally
+submitted for inclusion in the work by you, as defined in the Apache-2.0
+license, shall be dual licensed as above, without any additional terms or
+conditions.
diff --git a/src/tools/cargo/crates/rustfix/examples/fix-json.rs b/src/tools/cargo/crates/rustfix/examples/fix-json.rs
new file mode 100644
index 000000000..676171106
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/examples/fix-json.rs
@@ -0,0 +1,44 @@
+#![allow(clippy::disallowed_methods, clippy::print_stdout, clippy::print_stderr)]
+
+use anyhow::Error;
+use std::io::{stdin, BufReader, Read};
+use std::{collections::HashMap, collections::HashSet, env, fs};
+
+fn main() -> Result<(), Error> {
+ let suggestions_file = env::args().nth(1).expect("USAGE: fix-json <file or -->");
+ let suggestions = if suggestions_file == "--" {
+ let mut buffer = String::new();
+ BufReader::new(stdin()).read_to_string(&mut buffer)?;
+ buffer
+ } else {
+ fs::read_to_string(&suggestions_file)?
+ };
+ let suggestions = rustfix::get_suggestions_from_json(
+ &suggestions,
+ &HashSet::new(),
+ rustfix::Filter::Everything,
+ )?;
+
+ let mut files = HashMap::new();
+ for suggestion in suggestions {
+ let file = suggestion.solutions[0].replacements[0]
+ .snippet
+ .file_name
+ .clone();
+ files.entry(file).or_insert_with(Vec::new).push(suggestion);
+ }
+
+ for (source_file, suggestions) in &files {
+ let source = fs::read_to_string(source_file)?;
+ let mut fix = rustfix::CodeFix::new(&source);
+ for suggestion in suggestions.iter().rev() {
+ if let Err(e) = fix.apply(suggestion) {
+ eprintln!("Failed to apply suggestion to {}: {}", source_file, e);
+ }
+ }
+ let fixes = fix.finish()?;
+ fs::write(source_file, fixes)?;
+ }
+
+ Ok(())
+}
diff --git a/src/tools/cargo/crates/rustfix/proptest-regressions/replace.txt b/src/tools/cargo/crates/rustfix/proptest-regressions/replace.txt
new file mode 100644
index 000000000..fc5bd1a8b
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/proptest-regressions/replace.txt
@@ -0,0 +1,8 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+xs 358148376 3634975642 2528447681 3675516813 # shrinks to ref s = ""
+xs 3127423015 3362740891 2605681441 2390162043 # shrinks to ref data = "", ref replacements = [(0..0, [])]
diff --git a/src/tools/cargo/crates/rustfix/src/diagnostics.rs b/src/tools/cargo/crates/rustfix/src/diagnostics.rs
new file mode 100644
index 000000000..ad1899b2c
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/src/diagnostics.rs
@@ -0,0 +1,115 @@
+//! Rustc Diagnostic JSON Output.
+//!
+//! The following data types are copied from [rust-lang/rust](https://github.com/rust-lang/rust/blob/4fd68eb47bad1c121417ac4450b2f0456150db86/compiler/rustc_errors/src/json.rs).
+//!
+//! For examples of the JSON output, see JSON fixture files under `tests/` directory.
+
+use serde::Deserialize;
+
+/// The root diagnostic JSON output emitted by the compiler.
+#[derive(Clone, Deserialize, Debug, Hash, Eq, PartialEq)]
+pub struct Diagnostic {
+ /// The primary error message.
+ pub message: String,
+ pub code: Option<DiagnosticCode>,
+ /// "error: internal compiler error", "error", "warning", "note", "help".
+ level: String,
+ pub spans: Vec<DiagnosticSpan>,
+ /// Associated diagnostic messages.
+ pub children: Vec<Diagnostic>,
+ /// The message as rustc would render it.
+ pub rendered: Option<String>,
+}
+
+/// Span information of a diagnostic item.
+#[derive(Clone, Deserialize, Debug, Hash, Eq, PartialEq)]
+pub struct DiagnosticSpan {
+ pub file_name: String,
+ pub byte_start: u32,
+ pub byte_end: u32,
+ /// 1-based.
+ pub line_start: usize,
+ pub line_end: usize,
+ /// 1-based, character offset.
+ pub column_start: usize,
+ pub column_end: usize,
+ /// Is this a "primary" span -- meaning the point, or one of the points,
+ /// where the error occurred?
+ pub is_primary: bool,
+ /// Source text from the start of line_start to the end of line_end.
+ pub text: Vec<DiagnosticSpanLine>,
+ /// Label that should be placed at this location (if any)
+ label: Option<String>,
+ /// If we are suggesting a replacement, this will contain text
+ /// that should be sliced in atop this span.
+ pub suggested_replacement: Option<String>,
+ /// If the suggestion is approximate
+ pub suggestion_applicability: Option<Applicability>,
+ /// Macro invocations that created the code at this span, if any.
+ expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
+}
+
+/// Indicates the confidence in the correctness of a suggestion.
+///
+/// All suggestions are marked with an `Applicability`. Tools use the applicability of a suggestion
+/// to determine whether it should be automatically applied or if the user should be consulted
+/// before applying the suggestion.
+#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
+pub enum Applicability {
+ /// The suggestion is definitely what the user intended, or maintains the exact meaning of the code.
+ /// This suggestion should be automatically applied.
+ ///
+ /// In case of multiple `MachineApplicable` suggestions (whether as part of
+ /// the same `multipart_suggestion` or not), all of them should be
+ /// automatically applied.
+ MachineApplicable,
+
+ /// The suggestion may be what the user intended, but it is uncertain. The suggestion should
+ /// result in valid Rust code if it is applied.
+ MaybeIncorrect,
+
+ /// The suggestion contains placeholders like `(...)` or `{ /* fields */ }`. The suggestion
+ /// cannot be applied automatically because it will not result in valid Rust code. The user
+ /// will need to fill in the placeholders.
+ HasPlaceholders,
+
+ /// The applicability of the suggestion is unknown.
+ Unspecified,
+}
+
+/// Span information of a single line.
+#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)]
+pub struct DiagnosticSpanLine {
+ pub text: String,
+
+ /// 1-based, character offset in self.text.
+ pub highlight_start: usize,
+
+ pub highlight_end: usize,
+}
+
+/// Span information for macro expansions.
+#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)]
+struct DiagnosticSpanMacroExpansion {
+ /// span where macro was applied to generate this code; note that
+ /// this may itself derive from a macro (if
+ /// `span.expansion.is_some()`)
+ span: DiagnosticSpan,
+
+ /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
+ macro_decl_name: String,
+
+ /// span where macro was defined (if known)
+ def_site_span: Option<DiagnosticSpan>,
+}
+
+/// The error code emitted by the compiler. See [Rust error codes index].
+///
+/// [Rust error codes index]: https://doc.rust-lang.org/error_codes/error-index.html
+#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)]
+pub struct DiagnosticCode {
+ /// The code itself.
+ pub code: String,
+ /// An explanation for the code.
+ explanation: Option<String>,
+}
diff --git a/src/tools/cargo/crates/rustfix/src/error.rs b/src/tools/cargo/crates/rustfix/src/error.rs
new file mode 100644
index 000000000..171864504
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/src/error.rs
@@ -0,0 +1,21 @@
+//! Error types.
+
+use std::ops::Range;
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("invalid range {0:?}, start is larger than end")]
+ InvalidRange(Range<usize>),
+
+ #[error("invalid range {0:?}, original data is only {1} byte long")]
+ DataLengthExceeded(Range<usize>, usize),
+
+ #[error("could not replace range {0:?}, maybe parts of it were already replaced?")]
+ MaybeAlreadyReplaced(Range<usize>),
+
+ #[error("cannot replace slice of data that was already replaced")]
+ AlreadyReplaced,
+
+ #[error(transparent)]
+ Utf8(#[from] std::string::FromUtf8Error),
+}
diff --git a/src/tools/cargo/crates/rustfix/src/lib.rs b/src/tools/cargo/crates/rustfix/src/lib.rs
new file mode 100644
index 000000000..5fdb37b56
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/src/lib.rs
@@ -0,0 +1,306 @@
+//! Library for applying diagnostic suggestions to source code.
+//!
+//! This is a low-level library. You pass it the [JSON output] from `rustc`,
+//! and you can then use it to apply suggestions to in-memory strings.
+//! This library doesn't execute commands, or read or write from the filesystem.
+//!
+//! If you are looking for the [`cargo fix`] implementation, the core of it is
+//! located in [`cargo::ops::fix`].
+//!
+//! [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html
+//! [`cargo::ops::fix`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/ops/fix.rs
+//! [JSON output]: diagnostics
+//!
+//! The general outline of how to use this library is:
+//!
+//! 1. Call `rustc` and collect the JSON data.
+//! 2. Pass the json data to [`get_suggestions_from_json`].
+//! 3. Create a [`CodeFix`] with the source of a file to modify.
+//! 4. Call [`CodeFix::apply`] to apply a change.
+//! 5. Call [`CodeFix::finish`] to get the result and write it back to disk.
+
+use std::collections::HashSet;
+use std::ops::Range;
+
+pub mod diagnostics;
+mod error;
+mod replace;
+
+use diagnostics::Diagnostic;
+use diagnostics::DiagnosticSpan;
+pub use error::Error;
+
+/// A filter to control which suggestion should be applied.
+#[derive(Debug, Clone, Copy)]
+pub enum Filter {
+ /// For [`diagnostics::Applicability::MachineApplicable`] only.
+ MachineApplicableOnly,
+ /// Everything is included. YOLO!
+ Everything,
+}
+
+/// Collects code [`Suggestion`]s from one or more compiler diagnostic lines.
+///
+/// Fails if any of diagnostic line `input` is not a valid [`Diagnostic`] JSON.
+///
+/// * `only` --- only diagnostics with code in a set of error codes would be collected.
+pub fn get_suggestions_from_json<S: ::std::hash::BuildHasher>(
+ input: &str,
+ only: &HashSet<String, S>,
+ filter: Filter,
+) -> serde_json::error::Result<Vec<Suggestion>> {
+ let mut result = Vec::new();
+ for cargo_msg in serde_json::Deserializer::from_str(input).into_iter::<Diagnostic>() {
+ // One diagnostic line might have multiple suggestions
+ result.extend(collect_suggestions(&cargo_msg?, only, filter));
+ }
+ Ok(result)
+}
+
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct LinePosition {
+ pub line: usize,
+ pub column: usize,
+}
+
+impl std::fmt::Display for LinePosition {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}:{}", self.line, self.column)
+ }
+}
+
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct LineRange {
+ pub start: LinePosition,
+ pub end: LinePosition,
+}
+
+impl std::fmt::Display for LineRange {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}-{}", self.start, self.end)
+ }
+}
+
+/// An error/warning and possible solutions for fixing it
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Suggestion {
+ pub message: String,
+ pub snippets: Vec<Snippet>,
+ pub solutions: Vec<Solution>,
+}
+
+/// Solution to a diagnostic item.
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Solution {
+ /// The error message of the diagnostic item.
+ pub message: String,
+ /// Possible solutions to fix the error.
+ pub replacements: Vec<Replacement>,
+}
+
+/// Represents code that will get replaced.
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Snippet {
+ pub file_name: String,
+ pub line_range: LineRange,
+ pub range: Range<usize>,
+ /// leading surrounding text, text to replace, trailing surrounding text
+ ///
+ /// This split is useful for higlighting the part that gets replaced
+ pub text: (String, String, String),
+}
+
+/// Represents a replacement of a `snippet`.
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Replacement {
+ /// Code snippet that gets replaced.
+ pub snippet: Snippet,
+ /// The replacement of the snippet.
+ pub replacement: String,
+}
+
+/// Parses a [`Snippet`] from a diagnostic span item.
+fn parse_snippet(span: &DiagnosticSpan) -> Option<Snippet> {
+ // unindent the snippet
+ let indent = span
+ .text
+ .iter()
+ .map(|line| {
+ let indent = line
+ .text
+ .chars()
+ .take_while(|&c| char::is_whitespace(c))
+ .count();
+ std::cmp::min(indent, line.highlight_start - 1)
+ })
+ .min()?;
+
+ let text_slice = span.text[0].text.chars().collect::<Vec<char>>();
+
+ // We subtract `1` because these highlights are 1-based
+ // Check the `min` so that it doesn't attempt to index out-of-bounds when
+ // the span points to the "end" of the line. For example, a line of
+ // "foo\n" with a highlight_start of 5 is intended to highlight *after*
+ // the line. This needs to compensate since the newline has been removed
+ // from the text slice.
+ let start = (span.text[0].highlight_start - 1).min(text_slice.len());
+ let end = (span.text[0].highlight_end - 1).min(text_slice.len());
+ let lead = text_slice[indent..start].iter().collect();
+ let mut body: String = text_slice[start..end].iter().collect();
+
+ for line in span.text.iter().take(span.text.len() - 1).skip(1) {
+ body.push('\n');
+ body.push_str(&line.text[indent..]);
+ }
+ let mut tail = String::new();
+ let last = &span.text[span.text.len() - 1];
+
+ // If we get a DiagnosticSpanLine where highlight_end > text.len(), we prevent an 'out of
+ // bounds' access by making sure the index is within the array bounds.
+ // `saturating_sub` is used in case of an empty file
+ let last_tail_index = last.highlight_end.min(last.text.len()).saturating_sub(1);
+ let last_slice = last.text.chars().collect::<Vec<char>>();
+
+ if span.text.len() > 1 {
+ body.push('\n');
+ body.push_str(
+ &last_slice[indent..last_tail_index]
+ .iter()
+ .collect::<String>(),
+ );
+ }
+ tail.push_str(&last_slice[last_tail_index..].iter().collect::<String>());
+ Some(Snippet {
+ file_name: span.file_name.clone(),
+ line_range: LineRange {
+ start: LinePosition {
+ line: span.line_start,
+ column: span.column_start,
+ },
+ end: LinePosition {
+ line: span.line_end,
+ column: span.column_end,
+ },
+ },
+ range: (span.byte_start as usize)..(span.byte_end as usize),
+ text: (lead, body, tail),
+ })
+}
+
+/// Converts a [`DiagnosticSpan`] into a [`Replacement`].
+fn collect_span(span: &DiagnosticSpan) -> Option<Replacement> {
+ let snippet = parse_snippet(span)?;
+ let replacement = span.suggested_replacement.clone()?;
+ Some(Replacement {
+ snippet,
+ replacement,
+ })
+}
+
+/// Collects code [`Suggestion`]s from a single compiler diagnostic line.
+///
+/// * `only` --- only diagnostics with code in a set of error codes would be collected.
+pub fn collect_suggestions<S: ::std::hash::BuildHasher>(
+ diagnostic: &Diagnostic,
+ only: &HashSet<String, S>,
+ filter: Filter,
+) -> Option<Suggestion> {
+ if !only.is_empty() {
+ if let Some(ref code) = diagnostic.code {
+ if !only.contains(&code.code) {
+ // This is not the code we are looking for
+ return None;
+ }
+ } else {
+ // No code, probably a weird builtin warning/error
+ return None;
+ }
+ }
+
+ let snippets = diagnostic.spans.iter().filter_map(parse_snippet).collect();
+
+ let solutions: Vec<_> = diagnostic
+ .children
+ .iter()
+ .filter_map(|child| {
+ let replacements: Vec<_> = child
+ .spans
+ .iter()
+ .filter(|span| {
+ use crate::diagnostics::Applicability::*;
+ use crate::Filter::*;
+
+ match (filter, &span.suggestion_applicability) {
+ (MachineApplicableOnly, Some(MachineApplicable)) => true,
+ (MachineApplicableOnly, _) => false,
+ (Everything, _) => true,
+ }
+ })
+ .filter_map(collect_span)
+ .collect();
+ if !replacements.is_empty() {
+ Some(Solution {
+ message: child.message.clone(),
+ replacements,
+ })
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ if solutions.is_empty() {
+ None
+ } else {
+ Some(Suggestion {
+ message: diagnostic.message.clone(),
+ snippets,
+ solutions,
+ })
+ }
+}
+
+/// Represents a code fix. This doesn't write to disks but is only in memory.
+///
+/// The general way to use this is:
+///
+/// 1. Feeds the source of a file to [`CodeFix::new`].
+/// 2. Calls [`CodeFix::apply`] to apply suggestions to the source code.
+/// 3. Calls [`CodeFix::finish`] to get the "fixed" code.
+pub struct CodeFix {
+ data: replace::Data,
+}
+
+impl CodeFix {
+ /// Creates a `CodeFix` with the source of a file to modify.
+ pub fn new(s: &str) -> CodeFix {
+ CodeFix {
+ data: replace::Data::new(s.as_bytes()),
+ }
+ }
+
+ /// Applies a suggestion to the code.
+ pub fn apply(&mut self, suggestion: &Suggestion) -> Result<(), Error> {
+ for sol in &suggestion.solutions {
+ for r in &sol.replacements {
+ self.data
+ .replace_range(r.snippet.range.clone(), r.replacement.as_bytes())?;
+ }
+ }
+ Ok(())
+ }
+
+ /// Gets the result of the "fixed" code.
+ pub fn finish(&self) -> Result<String, Error> {
+ Ok(String::from_utf8(self.data.to_vec())?)
+ }
+}
+
+/// Applies multiple `suggestions` to the given `code`.
+pub fn apply_suggestions(code: &str, suggestions: &[Suggestion]) -> Result<String, Error> {
+ let mut fix = CodeFix::new(code);
+ for suggestion in suggestions.iter().rev() {
+ fix.apply(suggestion)?;
+ }
+ fix.finish()
+}
diff --git a/src/tools/cargo/crates/rustfix/src/replace.rs b/src/tools/cargo/crates/rustfix/src/replace.rs
new file mode 100644
index 000000000..ed467dcba
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/src/replace.rs
@@ -0,0 +1,329 @@
+//! A small module giving you a simple container that allows easy and cheap
+//! replacement of parts of its content, with the ability to prevent changing
+//! the same parts multiple times.
+
+use std::rc::Rc;
+
+use crate::error::Error;
+
+/// Indicates the change state of a [`Span`].
+#[derive(Debug, Clone, PartialEq, Eq)]
+enum State {
+ /// The initial state. No change applied.
+ Initial,
+ /// Has been replaced.
+ Replaced(Rc<[u8]>),
+ /// Has been inserted.
+ Inserted(Rc<[u8]>),
+}
+
+impl State {
+ fn is_inserted(&self) -> bool {
+ matches!(*self, State::Inserted(..))
+ }
+}
+
+/// Span with a change [`State`].
+#[derive(Debug, Clone, PartialEq, Eq)]
+struct Span {
+ /// Start of this span in parent data
+ start: usize,
+ /// up to end excluding
+ end: usize,
+ /// Whether the span is inserted, replaced or still fresh.
+ data: State,
+}
+
+/// A container that allows easily replacing chunks of its data
+#[derive(Debug, Clone, Default)]
+pub struct Data {
+ /// Original data.
+ original: Vec<u8>,
+ /// [`Span`]s covering the full range of the original data.
+ parts: Vec<Span>,
+}
+
+impl Data {
+ /// Create a new data container from a slice of bytes
+ pub fn new(data: &[u8]) -> Self {
+ Data {
+ original: data.into(),
+ parts: vec![Span {
+ data: State::Initial,
+ start: 0,
+ end: data.len(),
+ }],
+ }
+ }
+
+ /// Render this data as a vector of bytes
+ pub fn to_vec(&self) -> Vec<u8> {
+ if self.original.is_empty() {
+ return Vec::new();
+ }
+
+ self.parts.iter().fold(Vec::new(), |mut acc, d| {
+ match d.data {
+ State::Initial => acc.extend_from_slice(&self.original[d.start..d.end]),
+ State::Replaced(ref d) | State::Inserted(ref d) => acc.extend_from_slice(d),
+ };
+ acc
+ })
+ }
+
+ /// Replace a chunk of data with the given slice, erroring when this part
+ /// was already changed previously.
+ pub fn replace_range(
+ &mut self,
+ range: std::ops::Range<usize>,
+ data: &[u8],
+ ) -> Result<(), Error> {
+ if range.start > range.end {
+ return Err(Error::InvalidRange(range));
+ }
+
+ if range.end > self.original.len() {
+ return Err(Error::DataLengthExceeded(range, self.original.len()));
+ }
+
+ let insert_only = range.start == range.end;
+
+ // Since we error out when replacing an already replaced chunk of data,
+ // we can take some shortcuts here. For example, there can be no
+ // overlapping replacements -- we _always_ split a chunk of 'initial'
+ // data into three[^empty] parts, and there can't ever be two 'initial'
+ // parts touching.
+ //
+ // [^empty]: Leading and trailing ones might be empty if we replace
+ // the whole chunk. As an optimization and without loss of generality we
+ // don't add empty parts.
+ let new_parts = {
+ let Some(index_of_part_to_split) = self.parts.iter().position(|p| {
+ !p.data.is_inserted() && p.start <= range.start && p.end >= range.end
+ }) else {
+ if tracing::enabled!(tracing::Level::DEBUG) {
+ let slices = self
+ .parts
+ .iter()
+ .map(|p| {
+ (
+ p.start,
+ p.end,
+ match p.data {
+ State::Initial => "initial",
+ State::Replaced(..) => "replaced",
+ State::Inserted(..) => "inserted",
+ },
+ )
+ })
+ .collect::<Vec<_>>();
+ tracing::debug!(
+ "no single slice covering {}..{}, current slices: {:?}",
+ range.start,
+ range.end,
+ slices,
+ );
+ }
+
+ return Err(Error::MaybeAlreadyReplaced(range));
+ };
+
+ let part_to_split = &self.parts[index_of_part_to_split];
+
+ // If this replacement matches exactly the part that we would
+ // otherwise split then we ignore this for now. This means that you
+ // can replace the exact same range with the exact same content
+ // multiple times and we'll process and allow it.
+ //
+ // This is currently done to alleviate issues like
+ // rust-lang/rust#51211 although this clause likely wants to be
+ // removed if that's fixed deeper in the compiler.
+ if part_to_split.start == range.start && part_to_split.end == range.end {
+ if let State::Replaced(ref replacement) = part_to_split.data {
+ if &**replacement == data {
+ return Ok(());
+ }
+ }
+ }
+
+ if part_to_split.data != State::Initial {
+ return Err(Error::AlreadyReplaced);
+ }
+
+ let mut new_parts = Vec::with_capacity(self.parts.len() + 2);
+
+ // Previous parts
+ if let Some(ps) = self.parts.get(..index_of_part_to_split) {
+ new_parts.extend_from_slice(ps);
+ }
+
+ // Keep initial data on left side of part
+ if range.start > part_to_split.start {
+ new_parts.push(Span {
+ start: part_to_split.start,
+ end: range.start,
+ data: State::Initial,
+ });
+ }
+
+ // New part
+ new_parts.push(Span {
+ start: range.start,
+ end: range.end,
+ data: if insert_only {
+ State::Inserted(data.into())
+ } else {
+ State::Replaced(data.into())
+ },
+ });
+
+ // Keep initial data on right side of part
+ if range.end < part_to_split.end {
+ new_parts.push(Span {
+ start: range.end,
+ end: part_to_split.end,
+ data: State::Initial,
+ });
+ }
+
+ // Following parts
+ if let Some(ps) = self.parts.get(index_of_part_to_split + 1..) {
+ new_parts.extend_from_slice(ps);
+ }
+
+ new_parts
+ };
+
+ self.parts = new_parts;
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use proptest::prelude::*;
+
+ fn str(i: &[u8]) -> &str {
+ ::std::str::from_utf8(i).unwrap()
+ }
+
+ #[test]
+ fn insert_at_beginning() {
+ let mut d = Data::new(b"foo bar baz");
+ d.replace_range(0..0, b"oh no ").unwrap();
+ assert_eq!("oh no foo bar baz", str(&d.to_vec()));
+ }
+
+ #[test]
+ fn insert_at_end() {
+ let mut d = Data::new(b"foo bar baz");
+ d.replace_range(11..11, b" oh no").unwrap();
+ assert_eq!("foo bar baz oh no", str(&d.to_vec()));
+ }
+
+ #[test]
+ fn replace_some_stuff() {
+ let mut d = Data::new(b"foo bar baz");
+ d.replace_range(4..7, b"lol").unwrap();
+ assert_eq!("foo lol baz", str(&d.to_vec()));
+ }
+
+ #[test]
+ fn replace_a_single_char() {
+ let mut d = Data::new(b"let y = true;");
+ d.replace_range(4..5, b"mut y").unwrap();
+ assert_eq!("let mut y = true;", str(&d.to_vec()));
+ }
+
+ #[test]
+ fn replace_multiple_lines() {
+ let mut d = Data::new(b"lorem\nipsum\ndolor");
+
+ d.replace_range(6..11, b"lol").unwrap();
+ assert_eq!("lorem\nlol\ndolor", str(&d.to_vec()));
+
+ d.replace_range(12..17, b"lol").unwrap();
+ assert_eq!("lorem\nlol\nlol", str(&d.to_vec()));
+ }
+
+ #[test]
+ fn replace_multiple_lines_with_insert_only() {
+ let mut d = Data::new(b"foo!");
+
+ d.replace_range(3..3, b"bar").unwrap();
+ assert_eq!("foobar!", str(&d.to_vec()));
+
+ d.replace_range(0..3, b"baz").unwrap();
+ assert_eq!("bazbar!", str(&d.to_vec()));
+
+ d.replace_range(3..4, b"?").unwrap();
+ assert_eq!("bazbar?", str(&d.to_vec()));
+ }
+
+ #[test]
+ fn replace_invalid_range() {
+ let mut d = Data::new(b"foo!");
+
+ assert!(d.replace_range(2..1, b"bar").is_err());
+ assert!(d.replace_range(0..3, b"bar").is_ok());
+ }
+
+ #[test]
+ fn empty_to_vec_roundtrip() {
+ let s = "";
+ assert_eq!(s.as_bytes(), Data::new(s.as_bytes()).to_vec().as_slice());
+ }
+
+ #[test]
+ fn replace_overlapping_stuff_errs() {
+ let mut d = Data::new(b"foo bar baz");
+
+ d.replace_range(4..7, b"lol").unwrap();
+ assert_eq!("foo lol baz", str(&d.to_vec()));
+
+ assert!(matches!(
+ d.replace_range(4..7, b"lol2").unwrap_err(),
+ Error::AlreadyReplaced,
+ ));
+ }
+
+ #[test]
+ fn broken_replacements() {
+ let mut d = Data::new(b"foo");
+ assert!(matches!(
+ d.replace_range(4..8, b"lol").unwrap_err(),
+ Error::DataLengthExceeded(std::ops::Range { start: 4, end: 8 }, 3),
+ ));
+ }
+
+ #[test]
+ fn replace_same_twice() {
+ let mut d = Data::new(b"foo");
+ d.replace_range(0..1, b"b").unwrap();
+ d.replace_range(0..1, b"b").unwrap();
+ assert_eq!("boo", str(&d.to_vec()));
+ }
+
+ proptest! {
+ #[test]
+ fn new_to_vec_roundtrip(ref s in "\\PC*") {
+ assert_eq!(s.as_bytes(), Data::new(s.as_bytes()).to_vec().as_slice());
+ }
+
+ #[test]
+ fn replace_random_chunks(
+ ref data in "\\PC*",
+ ref replacements in prop::collection::vec(
+ (any::<::std::ops::Range<usize>>(), any::<Vec<u8>>()),
+ 1..100,
+ )
+ ) {
+ let mut d = Data::new(data.as_bytes());
+ for &(ref range, ref bytes) in replacements {
+ let _ = d.replace_range(range.clone(), bytes);
+ }
+ }
+ }
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/edge-cases/empty.json b/src/tools/cargo/crates/rustfix/tests/edge-cases/empty.json
new file mode 100644
index 000000000..62df0b936
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/edge-cases/empty.json
@@ -0,0 +1,42 @@
+{
+ "message": "`main` function not found in crate `empty`",
+ "code": {
+ "code": "E0601",
+ "explanation": "No `main` function was found in a binary crate. To fix this error, add a\n`main` function. For example:\n\n```\nfn main() {\n // Your program will start here.\n println!(\"Hello world!\");\n}\n```\n\nIf you don't know the basics of Rust, you can go look to the Rust Book to get\nstarted: https://doc.rust-lang.org/book/\n"
+ },
+ "level": "error",
+ "spans": [
+ {
+ "file_name": "empty.rs",
+ "byte_start": 0,
+ "byte_end": 0,
+ "line_start": 0,
+ "line_end": 0,
+ "column_start": 1,
+ "column_end": 1,
+ "is_primary": true,
+ "text": [
+ {
+ "text": "",
+ "highlight_start": 1,
+ "highlight_end": 1
+ }
+ ],
+ "label": null,
+ "suggested_replacement": null,
+ "suggestion_applicability": null,
+ "expansion": null
+ }
+ ],
+ "children": [
+ {
+ "message": "consider adding a `main` function to `empty.rs`",
+ "code": null,
+ "level": "note",
+ "spans": [],
+ "children": [],
+ "rendered": null
+ }
+ ],
+ "rendered": "error[E0601]: `main` function not found in crate `empty`\n |\n = note: consider adding a `main` function to `empty.rs`\n\n"
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/edge-cases/empty.rs b/src/tools/cargo/crates/rustfix/tests/edge-cases/empty.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/edge-cases/empty.rs
diff --git a/src/tools/cargo/crates/rustfix/tests/edge-cases/indented_whitespace.json b/src/tools/cargo/crates/rustfix/tests/edge-cases/indented_whitespace.json
new file mode 100644
index 000000000..b25189aaf
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/edge-cases/indented_whitespace.json
@@ -0,0 +1,60 @@
+{
+ "message": "non-ASCII whitespace symbol '\\u{a0}' is not skipped",
+ "code": null,
+ "level": "warning",
+ "spans":
+ [
+ {
+ "file_name": "lib.rs",
+ "byte_start": 26,
+ "byte_end": 28,
+ "line_start": 2,
+ "line_end": 2,
+ "column_start": 1,
+ "column_end": 2,
+ "is_primary": false,
+ "text":
+ [
+ {
+ "text": " indented\";",
+ "highlight_start": 1,
+ "highlight_end": 2
+ }
+ ],
+ "label": "non-ASCII whitespace symbol '\\u{a0}' is not skipped",
+ "suggested_replacement": null,
+ "suggestion_applicability": null,
+ "expansion": null
+ },
+ {
+ "file_name": "lib.rs",
+ "byte_start": 24,
+ "byte_end": 28,
+ "line_start": 1,
+ "line_end": 2,
+ "column_start": 25,
+ "column_end": 2,
+ "is_primary": true,
+ "text":
+ [
+ {
+ "text": "pub static FOO: &str = \"\\",
+ "highlight_start": 25,
+ "highlight_end": 26
+ },
+ {
+ "text": " indented\";",
+ "highlight_start": 1,
+ "highlight_end": 2
+ }
+ ],
+ "label": null,
+ "suggested_replacement": null,
+ "suggestion_applicability": null,
+ "expansion": null
+ }
+ ],
+ "children":
+ [],
+ "rendered": "warning: non-ASCII whitespace symbol '\\u{a0}' is not skipped\n --> lib.rs:1:25\n |\n1 | pub static FOO: &str = \"\\\n | _________________________^\n2 | |  indented\";\n | | ^ non-ASCII whitespace symbol '\\u{a0}' is not skipped\n | |_|\n | \n\n"
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/edge-cases/no_main.json b/src/tools/cargo/crates/rustfix/tests/edge-cases/no_main.json
new file mode 100644
index 000000000..e4b1c8f97
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/edge-cases/no_main.json
@@ -0,0 +1,33 @@
+{
+ "message": "`main` function not found in crate `no_main`",
+ "code": {
+ "code": "E0601",
+ "explanation": "No `main` function was found in a binary crate. To fix this error, add a\n`main` function. For example:\n\n```\nfn main() {\n // Your program will start here.\n println!(\"Hello world!\");\n}\n```\n\nIf you don't know the basics of Rust, you can go look to the Rust Book to get\nstarted: https://doc.rust-lang.org/book/\n"
+ },
+ "level": "error",
+ "spans": [
+ {
+ "file_name": "no_main.rs",
+ "byte_start": 26,
+ "byte_end": 26,
+ "line_start": 1,
+ "line_end": 1,
+ "column_start": 27,
+ "column_end": 27,
+ "is_primary": true,
+ "text": [
+ {
+ "text": "// This file has no main.",
+ "highlight_start": 27,
+ "highlight_end": 27
+ }
+ ],
+ "label": "consider adding a `main` function to `no_main.rs`",
+ "suggested_replacement": null,
+ "suggestion_applicability": null,
+ "expansion": null
+ }
+ ],
+ "children": [],
+ "rendered": "error[E0601]: `main` function not found in crate `no_main`\n --> no_main.rs:1:27\n |\n1 | // This file has no main.\n | ^ consider adding a `main` function to `no_main.rs`\n\n"
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/edge-cases/no_main.rs b/src/tools/cargo/crates/rustfix/tests/edge-cases/no_main.rs
new file mode 100644
index 000000000..0147ba726
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/edge-cases/no_main.rs
@@ -0,0 +1 @@
+// This file has no main.
diff --git a/src/tools/cargo/crates/rustfix/tests/edge-cases/out_of_bounds.recorded.json b/src/tools/cargo/crates/rustfix/tests/edge-cases/out_of_bounds.recorded.json
new file mode 100644
index 000000000..147debb6c
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/edge-cases/out_of_bounds.recorded.json
@@ -0,0 +1,43 @@
+{
+ "message": "unterminated double quote string",
+ "code": null,
+ "level": "error",
+ "spans": [
+ {
+ "file_name": "./tests/everything/tab_2.rs",
+ "byte_start": 485,
+ "byte_end": 526,
+ "line_start": 12,
+ "line_end": 13,
+ "column_start": 7,
+ "column_end": 3,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " \"\"\"; //~ ERROR unterminated double quote",
+ "highlight_start": 7,
+ "highlight_end": 45
+ },
+ {
+ "text": "}",
+ "highlight_start": 1,
+ "highlight_end": 3
+ }
+ ],
+ "label": null,
+ "suggested_replacement": null,
+ "suggestion_applicability": null,
+ "expansion": null
+ }
+ ],
+ "children": [],
+ "rendered": "error: unterminated double quote string\n --> ./tests/everything/tab_2.rs:12:7\n |\n12 | \"\"\"; //~ ERROR unterminated double quote\n | _______^\n13 | | }\n | |__^\n\n"
+}
+{
+ "message": "aborting due to previous error",
+ "code": null,
+ "level": "error",
+ "spans": [],
+ "children": [],
+ "rendered": "error: aborting due to previous error\n\n"
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/edge-cases/utf8_idents.recorded.json b/src/tools/cargo/crates/rustfix/tests/edge-cases/utf8_idents.recorded.json
new file mode 100644
index 000000000..28950d694
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/edge-cases/utf8_idents.recorded.json
@@ -0,0 +1,59 @@
+{
+ "message": "expected one of `,`, `:`, `=`, or `>`, found `'β`",
+ "code": null,
+ "level": "error",
+ "spans": [
+ {
+ "file_name": "./tests/everything/utf8_idents.rs",
+ "byte_start": 14,
+ "byte_end": 14,
+ "line_start": 2,
+ "line_end": 2,
+ "column_start": 6,
+ "column_end": 6,
+ "is_primary": false,
+ "text": [
+ {
+ "text": " γ //~ ERROR non-ascii idents are not fully supported",
+ "highlight_start": 6,
+ "highlight_end": 6
+ }
+ ],
+ "label": "expected one of `,`, `:`, `=`, or `>` here",
+ "suggested_replacement": null,
+ "suggestion_applicability": null,
+ "expansion": null
+ },
+ {
+ "file_name": "./tests/everything/utf8_idents.rs",
+ "byte_start": 145,
+ "byte_end": 148,
+ "line_start": 4,
+ "line_end": 4,
+ "column_start": 5,
+ "column_end": 7,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " 'β, //~ ERROR non-ascii idents are not fully supported",
+ "highlight_start": 5,
+ "highlight_end": 7
+ }
+ ],
+ "label": "unexpected token",
+ "suggested_replacement": null,
+ "suggestion_applicability": null,
+ "expansion": null
+ }
+ ],
+ "children": [],
+ "rendered": "error: expected one of `,`, `:`, `=`, or `>`, found `'β`\n --> ./tests/everything/utf8_idents.rs:4:5\n |\n2 | γ //~ ERROR non-ascii idents are not fully supported\n | - expected one of `,`, `:`, `=`, or `>` here\n3 | //~^ WARN type parameter `γ` should have an upper camel case name\n4 | 'β, //~ ERROR non-ascii idents are not fully supported\n | ^^ unexpected token\n\n"
+}
+{
+ "message": "aborting due to previous error",
+ "code": null,
+ "level": "error",
+ "spans": [],
+ "children": [],
+ "rendered": "error: aborting due to previous error\n\n"
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/edge_cases.rs b/src/tools/cargo/crates/rustfix/tests/edge_cases.rs
new file mode 100644
index 000000000..42d1e405a
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/edge_cases.rs
@@ -0,0 +1,25 @@
+use rustfix;
+use std::collections::HashSet;
+use std::fs;
+
+macro_rules! expect_empty_json_test {
+ ($name:ident, $file:expr) => {
+ #[test]
+ fn $name() {
+ let json = fs::read_to_string(concat!("./tests/edge-cases/", $file)).unwrap();
+ let expected_suggestions = rustfix::get_suggestions_from_json(
+ &json,
+ &HashSet::new(),
+ rustfix::Filter::Everything,
+ )
+ .unwrap();
+ assert!(expected_suggestions.is_empty());
+ }
+ };
+}
+
+expect_empty_json_test! {out_of_bounds_test, "out_of_bounds.recorded.json"}
+expect_empty_json_test! {utf8_identifiers_test, "utf8_idents.recorded.json"}
+expect_empty_json_test! {empty, "empty.json"}
+expect_empty_json_test! {no_main, "no_main.json"}
+expect_empty_json_test! {indented_whitespace, "indented_whitespace.json"}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/E0178.fixed.rs b/src/tools/cargo/crates/rustfix/tests/everything/E0178.fixed.rs
new file mode 100644
index 000000000..07e611774
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/E0178.fixed.rs
@@ -0,0 +1,10 @@
+#![allow(dead_code)]
+
+trait Foo {}
+
+struct Bar<'a> {
+ w: &'a (dyn Foo + Send),
+}
+
+fn main() {
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/E0178.json b/src/tools/cargo/crates/rustfix/tests/everything/E0178.json
new file mode 100644
index 000000000..89f15b528
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/E0178.json
@@ -0,0 +1,70 @@
+{
+ "message": "expected a path on the left-hand side of `+`, not `&'a Foo`",
+ "code": {
+ "code": "E0178",
+ "explanation": "\nIn types, the `+` type operator has low precedence, so it is often necessary\nto use parentheses.\n\nFor example:\n\n```compile_fail,E0178\ntrait Foo {}\n\nstruct Bar<'a> {\n w: &'a Foo + Copy, // error, use &'a (Foo + Copy)\n x: &'a Foo + 'a, // error, use &'a (Foo + 'a)\n y: &'a mut Foo + 'a, // error, use &'a mut (Foo + 'a)\n z: fn() -> Foo + 'a, // error, use fn() -> (Foo + 'a)\n}\n```\n\nMore details can be found in [RFC 438].\n\n[RFC 438]: https://github.com/rust-lang/rfcs/pull/438\n"
+ },
+ "level": "error",
+ "spans": [
+ {
+ "file_name": "./tests/everything/E0178.rs",
+ "byte_start": 60,
+ "byte_end": 74,
+ "line_start": 6,
+ "line_end": 6,
+ "column_start": 8,
+ "column_end": 22,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " w: &'a Foo + Send,",
+ "highlight_start": 8,
+ "highlight_end": 22
+ }
+ ],
+ "label": null,
+ "suggested_replacement": null,
+ "expansion": null
+ }
+ ],
+ "children": [
+ {
+ "message": "try adding parentheses",
+ "code": null,
+ "level": "help",
+ "spans": [
+ {
+ "file_name": "./tests/everything/E0178.rs",
+ "byte_start": 60,
+ "byte_end": 74,
+ "line_start": 6,
+ "line_end": 6,
+ "column_start": 8,
+ "column_end": 22,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " w: &'a Foo + Send,",
+ "highlight_start": 8,
+ "highlight_end": 22
+ }
+ ],
+ "label": null,
+ "suggested_replacement": "&'a (Foo + Send)",
+ "expansion": null
+ }
+ ],
+ "children": [],
+ "rendered": null
+ }
+ ],
+ "rendered": "error[E0178]: expected a path on the left-hand side of `+`, not `&'a Foo`\n --> ./tests/everything/E0178.rs:6:8\n |\n6 | w: &'a Foo + Send,\n | ^^^^^^^^^^^^^^ help: try adding parentheses: `&'a (Foo + Send)`\n\nIf you want more information on this error, try using \"rustc --explain E0178\"\n"
+}
+{
+ "message": "aborting due to previous error",
+ "code": null,
+ "level": "error",
+ "spans": [],
+ "children": [],
+ "rendered": "error: aborting due to previous error\n\n"
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/E0178.rs b/src/tools/cargo/crates/rustfix/tests/everything/E0178.rs
new file mode 100644
index 000000000..24226fe0e
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/E0178.rs
@@ -0,0 +1,10 @@
+#![allow(dead_code)]
+
+trait Foo {}
+
+struct Bar<'a> {
+ w: &'a dyn Foo + Send,
+}
+
+fn main() {
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/closure-immutable-outer-variable.fixed.rs b/src/tools/cargo/crates/rustfix/tests/everything/closure-immutable-outer-variable.fixed.rs
new file mode 100644
index 000000000..e443e024b
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/closure-immutable-outer-variable.fixed.rs
@@ -0,0 +1,10 @@
+// Point at the captured immutable outer variable
+
+fn foo(mut f: Box<dyn FnMut()>) {
+ f();
+}
+
+fn main() {
+ let mut y = true;
+ foo(Box::new(move || y = false) as Box<_>); //~ ERROR cannot assign to captured outer variable
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/closure-immutable-outer-variable.json b/src/tools/cargo/crates/rustfix/tests/everything/closure-immutable-outer-variable.json
new file mode 100644
index 000000000..f7afa491d
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/closure-immutable-outer-variable.json
@@ -0,0 +1,70 @@
+{
+ "message": "cannot assign to captured outer variable in an `FnMut` closure",
+ "code": {
+ "code": "E0594",
+ "explanation": null
+ },
+ "level": "error",
+ "spans": [
+ {
+ "file_name": "./tests/everything/closure-immutable-outer-variable.rs",
+ "byte_start": 615,
+ "byte_end": 624,
+ "line_start": 19,
+ "line_end": 19,
+ "column_start": 26,
+ "column_end": 35,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " foo(Box::new(move || y = false) as Box<_>); //~ ERROR cannot assign to captured outer variable",
+ "highlight_start": 26,
+ "highlight_end": 35
+ }
+ ],
+ "label": null,
+ "suggested_replacement": null,
+ "expansion": null
+ }
+ ],
+ "children": [
+ {
+ "message": "consider making `y` mutable",
+ "code": null,
+ "level": "help",
+ "spans": [
+ {
+ "file_name": "./tests/everything/closure-immutable-outer-variable.rs",
+ "byte_start": 580,
+ "byte_end": 581,
+ "line_start": 18,
+ "line_end": 18,
+ "column_start": 9,
+ "column_end": 10,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " let y = true;",
+ "highlight_start": 9,
+ "highlight_end": 10
+ }
+ ],
+ "label": null,
+ "suggested_replacement": "mut y",
+ "expansion": null
+ }
+ ],
+ "children": [],
+ "rendered": null
+ }
+ ],
+ "rendered": "error[E0594]: cannot assign to captured outer variable in an `FnMut` closure\n --> ./tests/everything/closure-immutable-outer-variable.rs:19:26\n |\n18 | let y = true;\n | - help: consider making `y` mutable: `mut y`\n19 | foo(Box::new(move || y = false) as Box<_>); //~ ERROR cannot assign to captured outer variable\n | ^^^^^^^^^\n\nIf you want more information on this error, try using \"rustc --explain E0594\"\n"
+}
+{
+ "message": "aborting due to previous error",
+ "code": null,
+ "level": "error",
+ "spans": [],
+ "children": [],
+ "rendered": "error: aborting due to previous error\n\n"
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/closure-immutable-outer-variable.rs b/src/tools/cargo/crates/rustfix/tests/everything/closure-immutable-outer-variable.rs
new file mode 100644
index 000000000..c97ec3589
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/closure-immutable-outer-variable.rs
@@ -0,0 +1,10 @@
+// Point at the captured immutable outer variable
+
+fn foo(mut f: Box<FnMut()>) {
+ f();
+}
+
+fn main() {
+ let y = true;
+ foo(Box::new(move || y = false) as Box<_>); //~ ERROR cannot assign to captured outer variable
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/handle-insert-only.fixed.rs b/src/tools/cargo/crates/rustfix/tests/everything/handle-insert-only.fixed.rs
new file mode 100644
index 000000000..604bc8503
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/handle-insert-only.fixed.rs
@@ -0,0 +1,8 @@
+fn main() {
+ // insert only fix, adds `,` to first match arm
+ // why doesnt this replace 1 with 1,?
+ match &Some(3) {
+ &None => 1,
+ &Some(x) => x,
+ };
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/handle-insert-only.json b/src/tools/cargo/crates/rustfix/tests/everything/handle-insert-only.json
new file mode 100644
index 000000000..c32a71290
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/handle-insert-only.json
@@ -0,0 +1,68 @@
+{
+ "message": "expected one of `,`, `.`, `?`, `}`, or an operator, found `=>`",
+ "code": null,
+ "level": "error",
+ "spans": [
+ {
+ "file_name": "./tests/everything/handle-insert-only.rs",
+ "byte_start": 163,
+ "byte_end": 165,
+ "line_start": 6,
+ "line_end": 6,
+ "column_start": 18,
+ "column_end": 20,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " &Some(x) => x,",
+ "highlight_start": 18,
+ "highlight_end": 20
+ }
+ ],
+ "label": "expected one of `,`, `.`, `?`, `}`, or an operator here",
+ "suggested_replacement": null,
+ "expansion": null
+ }
+ ],
+ "children": [
+ {
+ "message": "missing a comma here to end this `match` arm",
+ "code": null,
+ "level": "help",
+ "spans": [
+ {
+ "file_name": "./tests/everything/handle-insert-only.rs",
+ "byte_start": 145,
+ "byte_end": 145,
+ "line_start": 5,
+ "line_end": 5,
+ "column_start": 19,
+ "column_end": 19,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " &None => 1",
+ "highlight_start": 19,
+ "highlight_end": 19
+ }
+ ],
+ "label": null,
+ "suggested_replacement": ",",
+ "suggestion_applicability": "Unspecified",
+ "expansion": null
+ }
+ ],
+ "children": [],
+ "rendered": null
+ }
+ ],
+ "rendered": "error: expected one of `,`, `.`, `?`, `}`, or an operator, found `=>`\n --> ./tests/everything/handle-insert-only.rs:6:18\n |\n5 | &None => 1\n | - help: missing a comma here to end this `match` arm\n6 | &Some(x) => x,\n | ^^ expected one of `,`, `.`, `?`, `}`, or an operator here\n\n"
+}
+{
+ "message": "aborting due to previous error",
+ "code": null,
+ "level": "error",
+ "spans": [],
+ "children": [],
+ "rendered": "error: aborting due to previous error\n\n"
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/handle-insert-only.rs b/src/tools/cargo/crates/rustfix/tests/everything/handle-insert-only.rs
new file mode 100644
index 000000000..d42a4caa8
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/handle-insert-only.rs
@@ -0,0 +1,8 @@
+fn main() {
+ // insert only fix, adds `,` to first match arm
+ // why doesnt this replace 1 with 1,?
+ match &Some(3) {
+ &None => 1
+ &Some(x) => x,
+ };
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/lt-generic-comp.fixed.rs b/src/tools/cargo/crates/rustfix/tests/everything/lt-generic-comp.fixed.rs
new file mode 100644
index 000000000..533c91734
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/lt-generic-comp.fixed.rs
@@ -0,0 +1,7 @@
+fn main() {
+ let x = 5i64;
+
+ if (x as u32) < 4 {
+ println!("yay");
+ }
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/lt-generic-comp.json b/src/tools/cargo/crates/rustfix/tests/everything/lt-generic-comp.json
new file mode 100644
index 000000000..0634762e3
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/lt-generic-comp.json
@@ -0,0 +1,87 @@
+{
+ "message": "`<` is interpreted as a start of generic arguments for `u32`, not a comparison",
+ "code": null,
+ "level": "error",
+ "spans": [
+ {
+ "file_name": "./tests/everything/lt-generic-comp.rs",
+ "byte_start": 49,
+ "byte_end": 50,
+ "line_start": 4,
+ "line_end": 4,
+ "column_start": 19,
+ "column_end": 20,
+ "is_primary": false,
+ "text": [
+ {
+ "text": " if x as u32 < 4 {",
+ "highlight_start": 19,
+ "highlight_end": 20
+ }
+ ],
+ "label": "interpreted as generic arguments",
+ "suggested_replacement": null,
+ "expansion": null
+ },
+ {
+ "file_name": "./tests/everything/lt-generic-comp.rs",
+ "byte_start": 47,
+ "byte_end": 48,
+ "line_start": 4,
+ "line_end": 4,
+ "column_start": 17,
+ "column_end": 18,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " if x as u32 < 4 {",
+ "highlight_start": 17,
+ "highlight_end": 18
+ }
+ ],
+ "label": "not interpreted as comparison",
+ "suggested_replacement": null,
+ "expansion": null
+ }
+ ],
+ "children": [
+ {
+ "message": "try comparing the cast value",
+ "code": null,
+ "level": "help",
+ "spans": [
+ {
+ "file_name": "./tests/everything/lt-generic-comp.rs",
+ "byte_start": 38,
+ "byte_end": 46,
+ "line_start": 4,
+ "line_end": 4,
+ "column_start": 8,
+ "column_end": 16,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " if x as u32 < 4 {",
+ "highlight_start": 8,
+ "highlight_end": 16
+ }
+ ],
+ "label": null,
+ "suggested_replacement": "(x as u32)",
+ "expansion": null
+ }
+ ],
+ "children": [],
+ "rendered": null
+ }
+ ],
+ "rendered": "error: `<` is interpreted as a start of generic arguments for `u32`, not a comparison\n --> ./tests/everything/lt-generic-comp.rs:4:17\n |\n4 | if x as u32 < 4 {\n | -------- ^ - interpreted as generic arguments\n | | |\n | | not interpreted as comparison\n | help: try comparing the cast value: `(x as u32)`\n\n"
+}
+{
+ "message": "aborting due to previous error",
+ "code": null,
+ "level": "error",
+ "spans": [],
+ "children": [],
+ "rendered": "error: aborting due to previous error\n\n"
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/lt-generic-comp.rs b/src/tools/cargo/crates/rustfix/tests/everything/lt-generic-comp.rs
new file mode 100644
index 000000000..c279b261f
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/lt-generic-comp.rs
@@ -0,0 +1,7 @@
+fn main() {
+ let x = 5i64;
+
+ if x as u32 < 4 {
+ println!("yay");
+ }
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/multiple-solutions.fixed.rs b/src/tools/cargo/crates/rustfix/tests/everything/multiple-solutions.fixed.rs
new file mode 100644
index 000000000..1a261785d
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/multiple-solutions.fixed.rs
@@ -0,0 +1,5 @@
+use std::collections::{HashSet};
+
+fn main() {
+ let _: HashSet<()>;
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/multiple-solutions.json b/src/tools/cargo/crates/rustfix/tests/everything/multiple-solutions.json
new file mode 100644
index 000000000..89b14ccc8
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/multiple-solutions.json
@@ -0,0 +1,114 @@
+{
+ "message": "unused imports: `HashMap`, `VecDeque`",
+ "code": {
+ "code": "unused_imports",
+ "explanation": null
+ },
+ "level": "warning",
+ "spans": [
+ {
+ "file_name": "src/main.rs",
+ "byte_start": 23,
+ "byte_end": 30,
+ "line_start": 1,
+ "line_end": 1,
+ "column_start": 24,
+ "column_end": 31,
+ "is_primary": true,
+ "text": [
+ {
+ "text": "use std::collections::{HashMap, HashSet, VecDeque};",
+ "highlight_start": 24,
+ "highlight_end": 31
+ }
+ ],
+ "label": null,
+ "suggested_replacement": null,
+ "suggestion_applicability": null,
+ "expansion": null
+ },
+ {
+ "file_name": "src/main.rs",
+ "byte_start": 41,
+ "byte_end": 49,
+ "line_start": 1,
+ "line_end": 1,
+ "column_start": 42,
+ "column_end": 50,
+ "is_primary": true,
+ "text": [
+ {
+ "text": "use std::collections::{HashMap, HashSet, VecDeque};",
+ "highlight_start": 42,
+ "highlight_end": 50
+ }
+ ],
+ "label": null,
+ "suggested_replacement": null,
+ "suggestion_applicability": null,
+ "expansion": null
+ }
+ ],
+ "children": [
+ {
+ "message": "#[warn(unused_imports)] on by default",
+ "code": null,
+ "level": "note",
+ "spans": [],
+ "children": [],
+ "rendered": null
+ },
+ {
+ "message": "remove the unused imports",
+ "code": null,
+ "level": "help",
+ "spans": [
+ {
+ "file_name": "src/main.rs",
+ "byte_start": 23,
+ "byte_end": 32,
+ "line_start": 1,
+ "line_end": 1,
+ "column_start": 24,
+ "column_end": 33,
+ "is_primary": true,
+ "text": [
+ {
+ "text": "use std::collections::{HashMap, HashSet, VecDeque};",
+ "highlight_start": 24,
+ "highlight_end": 33
+ }
+ ],
+ "label": null,
+ "suggested_replacement": "",
+ "suggestion_applicability": "MachineApplicable",
+ "expansion": null
+ },
+ {
+ "file_name": "src/main.rs",
+ "byte_start": 39,
+ "byte_end": 49,
+ "line_start": 1,
+ "line_end": 1,
+ "column_start": 40,
+ "column_end": 50,
+ "is_primary": true,
+ "text": [
+ {
+ "text": "use std::collections::{HashMap, HashSet, VecDeque};",
+ "highlight_start": 40,
+ "highlight_end": 50
+ }
+ ],
+ "label": null,
+ "suggested_replacement": "",
+ "suggestion_applicability": "MachineApplicable",
+ "expansion": null
+ }
+ ],
+ "children": [],
+ "rendered": null
+ }
+ ],
+ "rendered": "warning: unused imports: `HashMap`, `VecDeque`\n --> src/main.rs:1:24\n |\n1 | use std::collections::{HashMap, HashSet, VecDeque};\n | ^^^^^^^ ^^^^^^^^\n |\n = note: #[warn(unused_imports)] on by default\nhelp: remove the unused imports\n |\n1 | use std::collections::{HashSet};\n | -- --\n\n"
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/multiple-solutions.rs b/src/tools/cargo/crates/rustfix/tests/everything/multiple-solutions.rs
new file mode 100644
index 000000000..401198f7e
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/multiple-solutions.rs
@@ -0,0 +1,5 @@
+use std::collections::{HashMap, HashSet, VecDeque};
+
+fn main() {
+ let _: HashSet<()>;
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/replace-only-one-char.fixed.rs b/src/tools/cargo/crates/rustfix/tests/everything/replace-only-one-char.fixed.rs
new file mode 100644
index 000000000..1a9298d5f
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/replace-only-one-char.fixed.rs
@@ -0,0 +1,3 @@
+fn main() {
+ let _x = 42;
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/replace-only-one-char.json b/src/tools/cargo/crates/rustfix/tests/everything/replace-only-one-char.json
new file mode 100644
index 000000000..9a70c0880
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/replace-only-one-char.json
@@ -0,0 +1,70 @@
+{
+ "message": "unused variable: `x`",
+ "code": {
+ "code": "unused_variables",
+ "explanation": null
+ },
+ "level": "warning",
+ "spans": [
+ {
+ "file_name": "replace-only-one-char.rs",
+ "byte_start": 20,
+ "byte_end": 21,
+ "line_start": 2,
+ "line_end": 2,
+ "column_start": 9,
+ "column_end": 10,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " let x = 42;",
+ "highlight_start": 9,
+ "highlight_end": 10
+ }
+ ],
+ "label": null,
+ "suggested_replacement": null,
+ "expansion": null
+ }
+ ],
+ "children": [
+ {
+ "message": "#[warn(unused_variables)] on by default",
+ "code": null,
+ "level": "note",
+ "spans": [],
+ "children": [],
+ "rendered": null
+ },
+ {
+ "message": "consider using `_x` instead",
+ "code": null,
+ "level": "help",
+ "spans": [
+ {
+ "file_name": "replace-only-one-char.rs",
+ "byte_start": 20,
+ "byte_end": 21,
+ "line_start": 2,
+ "line_end": 2,
+ "column_start": 9,
+ "column_end": 10,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " let x = 42;",
+ "highlight_start": 9,
+ "highlight_end": 10
+ }
+ ],
+ "label": null,
+ "suggested_replacement": "_x",
+ "expansion": null
+ }
+ ],
+ "children": [],
+ "rendered": null
+ }
+ ],
+ "rendered": "warning: unused variable: `x`\n --> replace-only-one-char.rs:2:9\n |\n2 | let x = 42;\n | ^ help: consider using `_x` instead\n |\n = note: #[warn(unused_variables)] on by default\n\n"
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/replace-only-one-char.rs b/src/tools/cargo/crates/rustfix/tests/everything/replace-only-one-char.rs
new file mode 100644
index 000000000..36e44936f
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/replace-only-one-char.rs
@@ -0,0 +1,3 @@
+fn main() {
+ let x = 42;
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/str-lit-type-mismatch.fixed.rs b/src/tools/cargo/crates/rustfix/tests/everything/str-lit-type-mismatch.fixed.rs
new file mode 100644
index 000000000..d5a81a8a8
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/str-lit-type-mismatch.fixed.rs
@@ -0,0 +1,5 @@
+fn main() {
+ let x: &[u8] = b"foo"; //~ ERROR mismatched types
+ let y: &[u8; 4] = b"baaa"; //~ ERROR mismatched types
+ let z: &str = "foo"; //~ ERROR mismatched types
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/str-lit-type-mismatch.json b/src/tools/cargo/crates/rustfix/tests/everything/str-lit-type-mismatch.json
new file mode 100644
index 000000000..1c852513a
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/str-lit-type-mismatch.json
@@ -0,0 +1,218 @@
+{
+ "message": "mismatched types",
+ "code": {
+ "code": "E0308",
+ "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n"
+ },
+ "level": "error",
+ "spans": [
+ {
+ "file_name": "./tests/everything/str-lit-type-mismatch.rs",
+ "byte_start": 499,
+ "byte_end": 504,
+ "line_start": 13,
+ "line_end": 13,
+ "column_start": 20,
+ "column_end": 25,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " let x: &[u8] = \"foo\"; //~ ERROR mismatched types",
+ "highlight_start": 20,
+ "highlight_end": 25
+ }
+ ],
+ "label": "expected slice, found str",
+ "suggested_replacement": null,
+ "expansion": null
+ }
+ ],
+ "children": [
+ {
+ "message": "expected type `&[u8]`\n found type `&'static str`",
+ "code": null,
+ "level": "note",
+ "spans": [],
+ "children": [],
+ "rendered": null
+ },
+ {
+ "message": "consider adding a leading `b`",
+ "code": null,
+ "level": "help",
+ "spans": [
+ {
+ "file_name": "./tests/everything/str-lit-type-mismatch.rs",
+ "byte_start": 499,
+ "byte_end": 504,
+ "line_start": 13,
+ "line_end": 13,
+ "column_start": 20,
+ "column_end": 25,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " let x: &[u8] = \"foo\"; //~ ERROR mismatched types",
+ "highlight_start": 20,
+ "highlight_end": 25
+ }
+ ],
+ "label": null,
+ "suggested_replacement": "b\"foo\"",
+ "expansion": null
+ }
+ ],
+ "children": [],
+ "rendered": null
+ }
+ ],
+ "rendered": "error[E0308]: mismatched types\n --> ./tests/everything/str-lit-type-mismatch.rs:13:20\n |\n13 | let x: &[u8] = \"foo\"; //~ ERROR mismatched types\n | ^^^^^\n | |\n | expected slice, found str\n | help: consider adding a leading `b`: `b\"foo\"`\n |\n = note: expected type `&[u8]`\n found type `&'static str`\n\nIf you want more information on this error, try using \"rustc --explain E0308\"\n"
+}
+{
+ "message": "mismatched types",
+ "code": {
+ "code": "E0308",
+ "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n"
+ },
+ "level": "error",
+ "spans": [
+ {
+ "file_name": "./tests/everything/str-lit-type-mismatch.rs",
+ "byte_start": 555,
+ "byte_end": 561,
+ "line_start": 14,
+ "line_end": 14,
+ "column_start": 23,
+ "column_end": 29,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " let y: &[u8; 4] = \"baaa\"; //~ ERROR mismatched types",
+ "highlight_start": 23,
+ "highlight_end": 29
+ }
+ ],
+ "label": "expected array of 4 elements, found str",
+ "suggested_replacement": null,
+ "expansion": null
+ }
+ ],
+ "children": [
+ {
+ "message": "expected type `&[u8; 4]`\n found type `&'static str`",
+ "code": null,
+ "level": "note",
+ "spans": [],
+ "children": [],
+ "rendered": null
+ },
+ {
+ "message": "consider adding a leading `b`",
+ "code": null,
+ "level": "help",
+ "spans": [
+ {
+ "file_name": "./tests/everything/str-lit-type-mismatch.rs",
+ "byte_start": 555,
+ "byte_end": 561,
+ "line_start": 14,
+ "line_end": 14,
+ "column_start": 23,
+ "column_end": 29,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " let y: &[u8; 4] = \"baaa\"; //~ ERROR mismatched types",
+ "highlight_start": 23,
+ "highlight_end": 29
+ }
+ ],
+ "label": null,
+ "suggested_replacement": "b\"baaa\"",
+ "expansion": null
+ }
+ ],
+ "children": [],
+ "rendered": null
+ }
+ ],
+ "rendered": "error[E0308]: mismatched types\n --> ./tests/everything/str-lit-type-mismatch.rs:14:23\n |\n14 | let y: &[u8; 4] = \"baaa\"; //~ ERROR mismatched types\n | ^^^^^^\n | |\n | expected array of 4 elements, found str\n | help: consider adding a leading `b`: `b\"baaa\"`\n |\n = note: expected type `&[u8; 4]`\n found type `&'static str`\n\nIf you want more information on this error, try using \"rustc --explain E0308\"\n"
+}
+{
+ "message": "mismatched types",
+ "code": {
+ "code": "E0308",
+ "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n"
+ },
+ "level": "error",
+ "spans": [
+ {
+ "file_name": "./tests/everything/str-lit-type-mismatch.rs",
+ "byte_start": 608,
+ "byte_end": 614,
+ "line_start": 15,
+ "line_end": 15,
+ "column_start": 19,
+ "column_end": 25,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " let z: &str = b\"foo\"; //~ ERROR mismatched types",
+ "highlight_start": 19,
+ "highlight_end": 25
+ }
+ ],
+ "label": "expected str, found array of 3 elements",
+ "suggested_replacement": null,
+ "expansion": null
+ }
+ ],
+ "children": [
+ {
+ "message": "expected type `&str`\n found type `&'static [u8; 3]`",
+ "code": null,
+ "level": "note",
+ "spans": [],
+ "children": [],
+ "rendered": null
+ },
+ {
+ "message": "consider removing the leading `b`",
+ "code": null,
+ "level": "help",
+ "spans": [
+ {
+ "file_name": "./tests/everything/str-lit-type-mismatch.rs",
+ "byte_start": 608,
+ "byte_end": 614,
+ "line_start": 15,
+ "line_end": 15,
+ "column_start": 19,
+ "column_end": 25,
+ "is_primary": true,
+ "text": [
+ {
+ "text": " let z: &str = b\"foo\"; //~ ERROR mismatched types",
+ "highlight_start": 19,
+ "highlight_end": 25
+ }
+ ],
+ "label": null,
+ "suggested_replacement": "\"foo\"",
+ "expansion": null
+ }
+ ],
+ "children": [],
+ "rendered": null
+ }
+ ],
+ "rendered": "error[E0308]: mismatched types\n --> ./tests/everything/str-lit-type-mismatch.rs:15:19\n |\n15 | let z: &str = b\"foo\"; //~ ERROR mismatched types\n | ^^^^^^\n | |\n | expected str, found array of 3 elements\n | help: consider removing the leading `b`: `\"foo\"`\n |\n = note: expected type `&str`\n found type `&'static [u8; 3]`\n\nIf you want more information on this error, try using \"rustc --explain E0308\"\n"
+}
+{
+ "message": "aborting due to 3 previous errors",
+ "code": null,
+ "level": "error",
+ "spans": [],
+ "children": [],
+ "rendered": "error: aborting due to 3 previous errors\n\n"
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/everything/str-lit-type-mismatch.rs b/src/tools/cargo/crates/rustfix/tests/everything/str-lit-type-mismatch.rs
new file mode 100644
index 000000000..12637c7b9
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/everything/str-lit-type-mismatch.rs
@@ -0,0 +1,5 @@
+fn main() {
+ let x: &[u8] = "foo"; //~ ERROR mismatched types
+ let y: &[u8; 4] = "baaa"; //~ ERROR mismatched types
+ let z: &str = b"foo"; //~ ERROR mismatched types
+}
diff --git a/src/tools/cargo/crates/rustfix/tests/parse_and_replace.rs b/src/tools/cargo/crates/rustfix/tests/parse_and_replace.rs
new file mode 100644
index 000000000..902275b64
--- /dev/null
+++ b/src/tools/cargo/crates/rustfix/tests/parse_and_replace.rs
@@ -0,0 +1,234 @@
+#![allow(clippy::disallowed_methods, clippy::print_stdout, clippy::print_stderr)]
+
+use anyhow::{anyhow, ensure, Context, Error};
+use rustfix::apply_suggestions;
+use std::collections::HashSet;
+use std::env;
+use std::ffi::OsString;
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::process::{Command, Output};
+use tempfile::tempdir;
+use tracing::{debug, info, warn};
+
+mod fixmode {
+ pub const EVERYTHING: &str = "yolo";
+}
+
+mod settings {
+ // can be set as env var to debug
+ pub const CHECK_JSON: &str = "RUSTFIX_TEST_CHECK_JSON";
+ pub const RECORD_JSON: &str = "RUSTFIX_TEST_RECORD_JSON";
+ pub const RECORD_FIXED_RUST: &str = "RUSTFIX_TEST_RECORD_FIXED_RUST";
+}
+
+fn compile(file: &Path) -> Result<Output, Error> {
+ let tmp = tempdir()?;
+
+ let args: Vec<OsString> = vec![
+ file.into(),
+ "--error-format=json".into(),
+ "--emit=metadata".into(),
+ "--crate-name=rustfix_test".into(),
+ "--out-dir".into(),
+ tmp.path().into(),
+ ];
+
+ let res = Command::new(env::var_os("RUSTC").unwrap_or("rustc".into()))
+ .args(&args)
+ .env("CLIPPY_DISABLE_DOCS_LINKS", "true")
+ .env_remove("RUST_LOG")
+ .output()?;
+
+ Ok(res)
+}
+
+fn compile_and_get_json_errors(file: &Path) -> Result<String, Error> {
+ let res = compile(file)?;
+ let stderr = String::from_utf8(res.stderr)?;
+ if stderr.contains("is only accepted on the nightly compiler") {
+ panic!("rustfix tests require a nightly compiler");
+ }
+
+ match res.status.code() {
+ Some(0) | Some(1) | Some(101) => Ok(stderr),
+ _ => Err(anyhow!(
+ "failed with status {:?}: {}",
+ res.status.code(),
+ stderr
+ )),
+ }
+}
+
+fn compiles_without_errors(file: &Path) -> Result<(), Error> {
+ let res = compile(file)?;
+
+ match res.status.code() {
+ Some(0) => Ok(()),
+ _ => {
+ info!(
+ "file {:?} failed to compile:\n{}",
+ file,
+ String::from_utf8(res.stderr)?
+ );
+ Err(anyhow!(
+ "failed with status {:?} (`env RUST_LOG=parse_and_replace=info` for more info)",
+ res.status.code(),
+ ))
+ }
+ }
+}
+
+fn read_file(path: &Path) -> Result<String, Error> {
+ use std::io::Read;
+
+ let mut buffer = String::new();
+ let mut file = fs::File::open(path)?;
+ file.read_to_string(&mut buffer)?;
+ Ok(buffer)
+}
+
+fn diff(expected: &str, actual: &str) -> String {
+ use similar::{ChangeTag, TextDiff};
+ use std::fmt::Write;
+
+ let mut res = String::new();
+ let diff = TextDiff::from_lines(expected.trim(), actual.trim());
+
+ let mut different = false;
+ for op in diff.ops() {
+ for change in diff.iter_changes(op) {
+ let prefix = match change.tag() {
+ ChangeTag::Equal => continue,
+ ChangeTag::Insert => "+",
+ ChangeTag::Delete => "-",
+ };
+ if !different {
+ write!(
+ &mut res,
+ "differences found (+ == actual, - == expected):\n"
+ )
+ .unwrap();
+ different = true;
+ }
+ write!(&mut res, "{} {}", prefix, change.value()).unwrap();
+ }
+ }
+ if different {
+ write!(&mut res, "").unwrap();
+ }
+
+ res
+}
+
+fn test_rustfix_with_file<P: AsRef<Path>>(file: P, mode: &str) -> Result<(), Error> {
+ let file: &Path = file.as_ref();
+ let json_file = file.with_extension("json");
+ let fixed_file = file.with_extension("fixed.rs");
+
+ let filter_suggestions = if mode == fixmode::EVERYTHING {
+ rustfix::Filter::Everything
+ } else {
+ rustfix::Filter::MachineApplicableOnly
+ };
+
+ debug!("next up: {:?}", file);
+ let code = read_file(file).context(format!("could not read {}", file.display()))?;
+ let errors =
+ compile_and_get_json_errors(file).context(format!("could compile {}", file.display()))?;
+ let suggestions =
+ rustfix::get_suggestions_from_json(&errors, &HashSet::new(), filter_suggestions)
+ .context("could not load suggestions")?;
+
+ if std::env::var(settings::RECORD_JSON).is_ok() {
+ use std::io::Write;
+ let mut recorded_json = fs::File::create(&file.with_extension("recorded.json")).context(
+ format!("could not create recorded.json for {}", file.display()),
+ )?;
+ recorded_json.write_all(errors.as_bytes())?;
+ }
+
+ if std::env::var(settings::CHECK_JSON).is_ok() {
+ let expected_json = read_file(&json_file).context(format!(
+ "could not load json fixtures for {}",
+ file.display()
+ ))?;
+ let expected_suggestions =
+ rustfix::get_suggestions_from_json(&expected_json, &HashSet::new(), filter_suggestions)
+ .context("could not load expected suggestions")?;
+
+ ensure!(
+ expected_suggestions == suggestions,
+ "got unexpected suggestions from clippy:\n{}",
+ diff(
+ &format!("{:?}", expected_suggestions),
+ &format!("{:?}", suggestions)
+ )
+ );
+ }
+
+ let fixed = apply_suggestions(&code, &suggestions)
+ .context(format!("could not apply suggestions to {}", file.display()))?;
+
+ if std::env::var(settings::RECORD_FIXED_RUST).is_ok() {
+ use std::io::Write;
+ let mut recorded_rust = fs::File::create(&file.with_extension("recorded.rs"))?;
+ recorded_rust.write_all(fixed.as_bytes())?;
+ }
+
+ let expected_fixed =
+ read_file(&fixed_file).context(format!("could read fixed file for {}", file.display()))?;
+ ensure!(
+ fixed.trim() == expected_fixed.trim(),
+ "file {} doesn't look fixed:\n{}",
+ file.display(),
+ diff(fixed.trim(), expected_fixed.trim())
+ );
+
+ compiles_without_errors(&fixed_file)?;
+
+ Ok(())
+}
+
+fn get_fixture_files(p: &str) -> Result<Vec<PathBuf>, Error> {
+ Ok(fs::read_dir(&p)?
+ .into_iter()
+ .map(|e| e.unwrap().path())
+ .filter(|p| p.is_file())
+ .filter(|p| {
+ let x = p.to_string_lossy();
+ x.ends_with(".rs") && !x.ends_with(".fixed.rs") && !x.ends_with(".recorded.rs")
+ })
+ .collect())
+}
+
+fn assert_fixtures(dir: &str, mode: &str) {
+ let files = get_fixture_files(&dir)
+ .context(format!("couldn't load dir `{}`", dir))
+ .unwrap();
+ let mut failures = 0;
+
+ for file in &files {
+ if let Err(err) = test_rustfix_with_file(file, mode) {
+ println!("failed: {}", file.display());
+ warn!("{:?}", err);
+ failures += 1;
+ }
+ info!("passed: {:?}", file);
+ }
+
+ if failures > 0 {
+ panic!(
+ "{} out of {} fixture asserts failed\n\
+ (run with `env RUST_LOG=parse_and_replace=info` to get more details)",
+ failures,
+ files.len(),
+ );
+ }
+}
+
+#[test]
+fn everything() {
+ tracing_subscriber::fmt::init();
+ assert_fixtures("./tests/everything", fixmode::EVERYTHING);
+}
diff --git a/src/tools/cargo/crates/semver-check/Cargo.toml b/src/tools/cargo/crates/semver-check/Cargo.toml
index 7387c3091..ab13b5730 100644
--- a/src/tools/cargo/crates/semver-check/Cargo.toml
+++ b/src/tools/cargo/crates/semver-check/Cargo.toml
@@ -10,3 +10,6 @@ publish = false
[dependencies]
tempfile.workspace = true
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/semver-check/src/main.rs b/src/tools/cargo/crates/semver-check/src/main.rs
index 9ea0d1244..edcf59957 100644
--- a/src/tools/cargo/crates/semver-check/src/main.rs
+++ b/src/tools/cargo/crates/semver-check/src/main.rs
@@ -13,6 +13,8 @@
//! - `dont-deny`: By default tests have a `#![deny(warnings)]`. This option
//! avoids this attribute. Note that `#![allow(unused)]` is always added.
+#![allow(clippy::print_stderr)]
+
use std::error::Error;
use std::fs;
use std::path::Path;
diff --git a/src/tools/cargo/crates/xtask-build-man/Cargo.toml b/src/tools/cargo/crates/xtask-build-man/Cargo.toml
index 9e92125a1..87ae8c670 100644
--- a/src/tools/cargo/crates/xtask-build-man/Cargo.toml
+++ b/src/tools/cargo/crates/xtask-build-man/Cargo.toml
@@ -6,3 +6,6 @@ edition.workspace = true
publish = false
[dependencies]
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/xtask-build-man/src/main.rs b/src/tools/cargo/crates/xtask-build-man/src/main.rs
index 6680c3783..2ab3f098a 100644
--- a/src/tools/cargo/crates/xtask-build-man/src/main.rs
+++ b/src/tools/cargo/crates/xtask-build-man/src/main.rs
@@ -10,6 +10,8 @@
//! For more, read their doc comments.
//! ```
+#![allow(clippy::print_stderr)]
+
use std::fs;
use std::io;
use std::path::PathBuf;
diff --git a/src/tools/cargo/crates/xtask-bump-check/Cargo.toml b/src/tools/cargo/crates/xtask-bump-check/Cargo.toml
index c8a472adc..989ece4b7 100644
--- a/src/tools/cargo/crates/xtask-bump-check/Cargo.toml
+++ b/src/tools/cargo/crates/xtask-bump-check/Cargo.toml
@@ -14,3 +14,6 @@ git2.workspace = true
semver.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/xtask-bump-check/src/xtask.rs b/src/tools/cargo/crates/xtask-bump-check/src/xtask.rs
index b99ac8b32..db82fff63 100644
--- a/src/tools/cargo/crates/xtask-bump-check/src/xtask.rs
+++ b/src/tools/cargo/crates/xtask-bump-check/src/xtask.rs
@@ -41,7 +41,11 @@ pub fn cli() -> clap::Command {
.action(ArgAction::Count)
.global(true),
)
- .arg_quiet()
+ .arg(
+ flag("quiet", "Do not print cargo log messages")
+ .short('q')
+ .global(true),
+ )
.arg(
opt("color", "Coloring: auto, always, never")
.value_name("WHEN")
@@ -114,6 +118,11 @@ fn bump_check(args: &clap::ArgMatches, config: &cargo::util::Config) -> CargoRes
let changed_members = changed(&ws, &repo, &base_commit, &head_commit)?;
let status = |msg: &str| config.shell().status(STATUS, msg);
+ // Don't check against beta and stable branches,
+ // as the publish of these crates are not tied with Rust release process.
+ // See `TO_PUBLISH` in publish.py.
+ let crates_not_check_against_channels = ["home"];
+
status(&format!("base commit `{}`", base_commit.id()))?;
status(&format!("head commit `{}`", head_commit.id()))?;
@@ -125,6 +134,11 @@ fn bump_check(args: &clap::ArgMatches, config: &cargo::util::Config) -> CargoRes
status(&format!("compare against `{}`", referenced_commit.id()))?;
for referenced_member in checkout_ws(&ws, &repo, referenced_commit)?.members() {
let pkg_name = referenced_member.name().as_str();
+
+ if crates_not_check_against_channels.contains(&pkg_name) {
+ continue;
+ }
+
let Some(changed_member) = changed_members.get(pkg_name) else {
tracing::trace!("skipping {pkg_name}, may be removed or not published");
continue;
@@ -162,8 +176,12 @@ fn bump_check(args: &clap::ArgMatches, config: &cargo::util::Config) -> CargoRes
let mut cmd = ProcessBuilder::new("cargo");
cmd.arg("semver-checks")
.arg("--workspace")
+ .args(&["--exclude", "rustfix"]) // FIXME: Remove once 1.76 is stable
.arg("--baseline-rev")
.arg(referenced_commit.id().to_string());
+ for krate in crates_not_check_against_channels {
+ cmd.args(&["--exclude", krate]);
+ }
config.shell().status("Running", &cmd)?;
cmd.exec()?;
}
@@ -373,6 +391,7 @@ fn check_crates_io<'a>(
"`{name}@{current}` needs a bump because its should have a version newer than crates.io: {:?}`",
possibilities
.iter()
+ .map(|s| s.as_summary())
.map(|s| format!("{}@{}", s.name(), s.version()))
.collect::<Vec<_>>(),
);
diff --git a/src/tools/cargo/crates/xtask-stale-label/Cargo.toml b/src/tools/cargo/crates/xtask-stale-label/Cargo.toml
index 8d68536d2..aff6194b7 100644
--- a/src/tools/cargo/crates/xtask-stale-label/Cargo.toml
+++ b/src/tools/cargo/crates/xtask-stale-label/Cargo.toml
@@ -7,3 +7,6 @@ publish = false
[dependencies]
toml_edit.workspace = true
+
+[lints]
+workspace = true
diff --git a/src/tools/cargo/crates/xtask-stale-label/src/main.rs b/src/tools/cargo/crates/xtask-stale-label/src/main.rs
index 88c044b5b..efcb52d01 100644
--- a/src/tools/cargo/crates/xtask-stale-label/src/main.rs
+++ b/src/tools/cargo/crates/xtask-stale-label/src/main.rs
@@ -10,6 +10,8 @@
//! Probably autofix them in the future.
//! ```
+#![allow(clippy::print_stderr)]
+
use std::fmt::Write as _;
use std::path::PathBuf;
use std::process;