diff --git a/Cargo.toml b/Cargo.toml index 4ed0c770..c95590cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,3 +47,6 @@ members = ["component", "ci/build-channel", "ci/compare-versions"] [dev-dependencies] chrono = "0.4.33" strip-ansi-escapes = "0.2.0" + +[lints.clippy] +indexing_slicing = "warn" \ No newline at end of file diff --git a/src/download.rs b/src/download.rs index 84293114..d0c9aadb 100644 --- a/src/download.rs +++ b/src/download.rs @@ -135,7 +135,7 @@ pub fn get_latest_version(name: &str) -> Result { let response: LatestReleaseApiResponse = serde_json::from_str(&String::from_utf8_lossy(&data))?; - let version_str = &response.tag_name["v".len()..]; + let version_str = response.tag_name.trim_start_matches('v'); let version = Version::parse(version_str)?; Ok(version) } else { @@ -333,7 +333,10 @@ fn write_response_with_progress_bar( if bytes_read == 0 { break; } - if let Err(e) = writer.write_all(&buffer[..bytes_read]) { + let buf = buffer + .get(..bytes_read) + .ok_or_else(|| anyhow!("Failed to read buffer"))?; + if let Err(e) = writer.write_all(buf) { log_progress_bar(&progress_bar); if target.is_empty() { bail!("Something went wrong writing data: {}", e) diff --git a/src/lib.rs b/src/lib.rs index a00756d8..e581350a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(test, allow(clippy::indexing_slicing))] + pub mod channel; pub mod commands; pub mod config; diff --git a/src/ops/fuelup_check.rs b/src/ops/fuelup_check.rs index 00854602..0a100187 100644 --- a/src/ops/fuelup_check.rs +++ b/src/ops/fuelup_check.rs @@ -9,7 +9,7 @@ use crate::{ toolchain::{DistToolchainDescription, Toolchain}, }; use ansiterm::Color; -use anyhow::Result; +use anyhow::{anyhow, Result}; use component::{self, Components}; use semver::Version; use std::collections::HashMap; @@ -114,7 +114,10 @@ fn check_toolchain(toolchain: &str, verbose: bool) -> Result<()> { if !plugin.is_main_executable() { print!("{:>2}", ""); - plugin_name = &plugin.executables[index]; + plugin_name = plugin + .executables + .get(index) + .ok_or_else(|| anyhow!("Plugin name not found"))?; } let maybe_latest_version = plugin.publish.map_or_else( diff --git a/src/proxy_cli.rs b/src/proxy_cli.rs index a0c35c5e..12e93a3b 100644 --- a/src/proxy_cli.rs +++ b/src/proxy_cli.rs @@ -18,10 +18,10 @@ pub fn proxy_run(arg0: &str) -> Result { let cmd_args: Vec<_> = env::args_os().skip(1).collect(); let toolchain = Toolchain::from_settings()?; - if !cmd_args.is_empty() { - let plugin = format!("{}-{}", arg0, &cmd_args[0].to_string_lossy()); + if let Some(first_arg) = cmd_args.first() { + let plugin = format!("{}-{}", arg0, first_arg.to_string_lossy()); if Components::collect_plugin_executables()?.contains(&plugin) { - direct_proxy(&plugin, &cmd_args[1..], &toolchain)?; + direct_proxy(&plugin, cmd_args.get(1..).unwrap_or_default(), &toolchain)?; } } diff --git a/src/toolchain.rs b/src/toolchain.rs index 743a2a49..ccd9c873 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -104,15 +104,23 @@ fn consume_back(parts: &mut VecDeque, number: usize) { } } -/// Attempts to parse a date from the front of the parts list, returning the date and consuming the -/// date parts if they are available +/// Attempts to parse a date from the end of the parts list, returning the date and consuming the +/// date parts if they are available. fn extract_date(parts: &mut VecDeque<&str>) -> Option { let len = parts.len(); if len < 3 { return None; } - let date_str = format!("{}-{}-{}", parts[len - 3], parts[len - 2], parts[len - 1]); + let date_str = parts + .iter() + .rev() + .take(3) + .cloned() + .rev() + .collect::>() + .join("-"); + match Date::parse(&date_str, DATE_FORMAT) { Ok(d) => { consume_back(parts, 3); @@ -122,41 +130,33 @@ fn extract_date(parts: &mut VecDeque<&str>) -> Option { } } -/// Attemps to parse the target from a vector of parts, returning the target and consuming the -/// target parts if they are available +/// Attempts to parse the target from the end of the parts list, returning the target and consuming the +/// target parts if they are available. fn extract_target(parts: &mut VecDeque<&str>) -> Option { - if parts.len() < 3 { - return None; - } - - let len = parts.len(); - let target_str = format!("{}-{}-{}", parts[len - 3], parts[len - 2], parts[len - 1]); - match TargetTriple::new(&target_str) { - Ok(t) => { - consume_back(parts, 3); - Some(t) + fn try_extract(parts: &mut VecDeque<&str>, count: usize) -> Option { + if parts.len() < count { + return None; } - Err(_) => { - if parts.len() < 4 { - return None; - } - let target_str = format!( - "{}-{}-{}-{}", - parts[len - 4], - parts[len - 3], - parts[len - 2], - parts[len - 1] - ); - match TargetTriple::new(&target_str) { - Ok(t) => { - consume_back(parts, 4); - Some(t) - } - Err(_) => None, + let target_str: String = parts + .iter() + .rev() + .take(count) + .cloned() + .rev() + .collect::>() + .join("-"); + + match TargetTriple::new(&target_str) { + Ok(t) => { + consume_back(parts, count); + Some(t) } + Err(_) => None, } } + + try_extract(parts, 3).or_else(|| try_extract(parts, 4)) } /// Parses a distributable toolchain description from a string. @@ -177,12 +177,16 @@ impl FromStr for DistToolchainDescription { } let mut parts = s.split('-').collect::>(); + match parts.len() { - 1 => Ok(Self { - name: DistToolchainName::from_str(parts[0])?, - target: TargetTriple::from_host().ok(), - date: None, - }), + 1 => { + let first_part = *parts.front().unwrap_or(&""); + Ok(Self { + name: DistToolchainName::from_str(first_part)?, + target: TargetTriple::from_host().ok(), + date: None, + }) + } _ => { let date = extract_date(&mut parts); let target = extract_target(&mut parts); @@ -420,11 +424,13 @@ impl Toolchain { Ok(()) } - fn remove_executables(&self, component: &str) -> Result<()> { - let executables = &Components::collect().unwrap().component[component].executables; - for executable in executables { - remove_file(self.bin_path.join(executable)) - .with_context(|| format!("failed to remove executable '{executable}'"))?; + fn remove_executables(&self, component_name: &str) -> Result<()> { + let components = Components::collect()?; + if let Some(component) = components.component.get(component_name) { + for executable in &component.executables { + remove_file(self.bin_path.join(executable)) + .with_context(|| format!("failed to remove executable '{executable}'"))?; + } } Ok(()) } @@ -673,4 +679,78 @@ mod tests { DistToolchainDescription::from_str(channel).expect_err("invalid channel"); } } + + #[test] + fn test_extract_target_with_three_parts() { + let mut parts: VecDeque<&str> = VecDeque::from(vec!["aarch64", "apple", "darwin"]); + let target = extract_target(&mut parts).expect("target triple"); + assert_eq!(target.to_string(), "aarch64-apple-darwin"); + assert!(parts.is_empty()); // Ensure parts are consumed + } + + #[test] + fn test_extract_target_with_four_parts() { + let mut parts: VecDeque<&str> = VecDeque::from(vec!["x86_64", "unknown", "linux", "gnu"]); + let target = extract_target(&mut parts).expect("target triple"); + assert_eq!(target.to_string(), "x86_64-unknown-linux-gnu"); + assert!(parts.is_empty()); // Ensure parts are consumed + } + + #[test] + fn test_extract_target_with_five_parts() { + let mut parts: VecDeque<&str> = + VecDeque::from(vec!["my", "custom", "aarch64", "apple", "darwin"]); + let target = extract_target(&mut parts).expect("target triple"); + assert_eq!(target.to_string(), "aarch64-apple-darwin"); + assert_eq!(parts.len(), 2); // Ensure 3 parts were consumed + } + + #[test] + fn test_extract_target_with_insufficient_parts() { + let mut parts: VecDeque<&str> = VecDeque::from(vec!["apple", "darwin"]); + assert!(extract_target(&mut parts).is_none()); + assert_eq!(parts.len(), 2); // Ensure parts are not consumed + } + + #[test] + fn test_extract_target_with_invalid_target() { + let mut parts: VecDeque<&str> = VecDeque::from(vec!["invalid", "target", "string"]); + assert!(extract_target(&mut parts).is_none()); + assert_eq!(parts.len(), 3); // Ensure parts are not consumed + + let mut parts: VecDeque<&str> = + VecDeque::from(vec!["still", "invalid", "target", "string"]); + assert!(extract_target(&mut parts).is_none()); + assert_eq!(parts.len(), 4); // Ensure parts are not consumed + } + + #[test] + fn test_extract_date_with_valid_date() { + let mut parts: VecDeque<&str> = VecDeque::from(vec!["2022", "12", "25"]); + let date = extract_date(&mut parts).expect("date"); + assert_eq!(date.to_string(), "2022-12-25"); + assert!(parts.is_empty()); // Ensure all parts are consumed + } + + #[test] + fn test_extract_date_with_insufficient_parts() { + let mut parts: VecDeque<&str> = VecDeque::from(vec!["2022", "12"]); + assert!(extract_date(&mut parts).is_none()); + assert_eq!(parts.len(), 2); // Ensure parts are not consumed + } + + #[test] + fn test_extract_date_with_invalid_date() { + let mut parts: VecDeque<&str> = VecDeque::from(vec!["12", "25", "2022"]); + assert!(extract_date(&mut parts).is_none()); + assert_eq!(parts.len(), 3); // Ensure parts are not consumed + } + + #[test] + fn test_extract_date_with_extra_parts() { + let mut parts: VecDeque<&str> = VecDeque::from(vec!["extra", "2022", "12", "25"]); + let date = extract_date(&mut parts).expect("date"); + assert_eq!(date.to_string(), "2022-12-25"); + assert_eq!(parts.len(), 1); // Ensure only the date parts are consumed + } } diff --git a/src/toolchain_override.rs b/src/toolchain_override.rs index 66945372..2cae3c7c 100644 --- a/src/toolchain_override.rs +++ b/src/toolchain_override.rs @@ -118,6 +118,7 @@ impl ToolchainOverride { Ok(Self { cfg, path }) } + #[allow(clippy::indexing_slicing)] pub fn to_toml(&self) -> Document { let mut document = toml_edit::Document::new();