diff --git a/Cargo.lock b/Cargo.lock index fbf9112..acfeb15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,6 +85,28 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +[[package]] +name = "capacity_builder" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2d24a6dcf0cd402a21b65d35340f3a49ff3475dc5fdac91d22d2733e6641c6" +dependencies = [ + "capacity_builder_macros", + "ecow", + "hipstr", + "itoa", +] + +[[package]] +name = "capacity_builder_macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b4a6cae9efc04cc6cbb8faf338d2c497c165c83e74509cf4dbedea948bbf6e5" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "cc" version = "1.1.12" @@ -171,9 +193,9 @@ dependencies = [ [[package]] name = "deno_lockfile" -version = "0.23.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559c19feb00af0c34f0bd4a20e56e12463fafd5c5069d6005f3ce33008027eea" +checksum = "632e835a53ed667d62fdd766c5780fe8361c831d3e3fbf1a760a0b7896657587" dependencies = [ "deno_semver", "serde", @@ -186,6 +208,7 @@ name = "deno_npm" version = "0.26.0" dependencies = [ "async-trait", + "capacity_builder", "deno_error", "deno_lockfile", "deno_semver", @@ -204,11 +227,14 @@ dependencies = [ [[package]] name = "deno_semver" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756be7351289726087408984db18b9eb5e0186907673f39f858d119d0162071" +checksum = "a21aae482ce4a88e8a9f4746f4f93a3069709d297a7a5593bf984f221792d012" dependencies = [ + "capacity_builder", "deno_error", + "ecow", + "hipstr", "monch", "once_cell", "serde", @@ -258,6 +284,15 @@ dependencies = [ "syn", ] +[[package]] +name = "ecow" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42fc0a93992b20c58b99e59d61eaf1635a25bfbe49e4275c34ba0aee98119ba" +dependencies = [ + "serde", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -456,6 +491,17 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hipstr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97971ffc85d4c98de12e2608e992a43f5294ebb625fdb045b27c731b64c4c6d6" +dependencies = [ + "serde", + "serde_bytes", + "sptr", +] + [[package]] name = "http" version = "1.2.0" @@ -725,9 +771,9 @@ checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" @@ -1147,6 +1193,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.208" @@ -1229,6 +1284,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 929b519..484415e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,9 @@ license = "MIT" [dependencies] async-trait = "0.1.68" -deno_semver = "0.6.0" +deno_semver = "0.7.0" deno_error = "0.5.2" -deno_lockfile = "0.23.2" +deno_lockfile = "0.24.0" monch = "0.5.0" log = "0.4" serde = { version = "1.0.130", features = ["derive", "rc"] } @@ -21,6 +21,7 @@ serde_json = { version = "1.0.67", features = ["preserve_order"] } thiserror = "2.0.3" futures = "0.3.28" url = "2" +capacity_builder = "0.5.0" [dev-dependencies] divan = "0.1.17" diff --git a/src/lib.rs b/src/lib.rs index f7ff217..130a802 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,13 +4,18 @@ #![deny(clippy::print_stdout)] #![deny(clippy::unused_async)] -use std::borrow::Cow; use std::cmp::Ordering; use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; +use capacity_builder::CapacityDisplay; +use capacity_builder::StringAppendable; +use capacity_builder::StringBuilder; use deno_semver::package::PackageNv; +use deno_semver::CowVec; +use deno_semver::SmallStackString; +use deno_semver::StackString; use deno_semver::Version; use registry::NpmPackageVersionBinEntry; use registry::NpmPackageVersionDistInfo; @@ -30,12 +35,77 @@ pub struct NpmPackageIdDeserializationError { text: String, } +#[derive( + Clone, + Default, + PartialEq, + Eq, + Hash, + Serialize, + Deserialize, + PartialOrd, + Ord, + CapacityDisplay, +)] +pub struct NpmPackageIdPeerDependencies(CowVec); + +impl From<[NpmPackageId; N]> for NpmPackageIdPeerDependencies { + fn from(value: [NpmPackageId; N]) -> Self { + Self(CowVec::from(value)) + } +} + +impl NpmPackageIdPeerDependencies { + pub fn with_capacity(capacity: usize) -> Self { + Self(CowVec::with_capacity(capacity)) + } + + pub fn as_serialized(&self) -> StackString { + capacity_builder::appendable_to_string(self) + } + + pub fn push(&mut self, id: NpmPackageId) { + self.0.push(id); + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + fn peer_serialized_with_level<'a, TString: capacity_builder::StringType>( + &'a self, + builder: &mut StringBuilder<'a, TString>, + level: usize, + ) { + for peer in &self.0 { + // unfortunately we can't do something like `_3` when + // this gets deep because npm package names can start + // with a number + for _ in 0..level + 1 { + builder.append('_'); + } + peer.as_serialized_with_level(builder, level + 1); + } + } +} + +impl<'a> StringAppendable<'a> for &'a NpmPackageIdPeerDependencies { + fn append_to_builder( + self, + builder: &mut StringBuilder<'a, TString>, + ) { + self.peer_serialized_with_level(builder, 0) + } +} + /// A resolved unique identifier for an npm package. This contains /// the resolved name, version, and peer dependency resolution identifiers. -#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive( + Clone, PartialEq, Eq, Hash, Serialize, Deserialize, CapacityDisplay, +)] pub struct NpmPackageId { pub nv: PackageNv, - pub peer_dependencies: Vec, + pub peer_dependencies: NpmPackageIdPeerDependencies, } // Custom debug implementation for more concise test output @@ -46,38 +116,26 @@ impl std::fmt::Debug for NpmPackageId { } impl NpmPackageId { - pub fn as_serialized(&self) -> String { - self.as_serialized_with_level(0) + pub fn as_serialized(&self) -> StackString { + capacity_builder::appendable_to_string(self) } - pub fn peer_deps_serialized(&self) -> String { - self.peer_serialized_with_level(0) - } - - fn as_serialized_with_level(&self, level: usize) -> String { + fn as_serialized_with_level<'a, TString: capacity_builder::StringType>( + &'a self, + builder: &mut StringBuilder<'a, TString>, + level: usize, + ) { // WARNING: This should not change because it's used in the lockfile - format!( - "{}@{}{}", - if level == 0 { - Cow::Borrowed(&self.nv.name) - } else { - Cow::Owned(self.nv.name.replace('/', "+")) - }, - self.nv.version, - self.peer_serialized_with_level(level) - ) - } - - fn peer_serialized_with_level(&self, level: usize) -> String { - let mut result = String::new(); - for peer in &self.peer_dependencies { - // unfortunately we can't do something like `_3` when - // this gets deep because npm package names can start - // with a number - result.push_str(&"_".repeat(level + 1)); - result.push_str(&peer.as_serialized_with_level(level + 1)); + if level == 0 { + builder.append(self.nv.name.as_str()); + } else { + builder.append_with_replace(self.nv.name.as_str(), "/", "+"); } - result + builder.append('@'); + builder.append(&self.nv.version); + self + .peer_dependencies + .peer_serialized_with_level(builder, level); } pub fn from_serialized( @@ -136,9 +194,9 @@ impl NpmPackageId { fn parse_peers_at_level<'a>( level: usize, - ) -> impl Fn(&'a str) -> ParseResult<'a, Vec> { + ) -> impl Fn(&'a str) -> ParseResult<'a, CowVec> { move |mut input| { - let mut peers = Vec::new(); + let mut peers = CowVec::new(); while let Ok((level_input, _)) = parse_level_at_level(level)(input) { input = level_input; let peer_result = parse_id_at_level(level)(input)?; @@ -155,9 +213,9 @@ impl NpmPackageId { move |input| { let (input, (name, version)) = parse_name_and_version(input)?; let name = if level > 0 { - name.replace('+', "/") + StackString::from_str(name).replace("+", "/") } else { - name.to_string() + StackString::from_str(name) }; let (input, peer_dependencies) = parse_peers_at_level(level + 1)(input)?; @@ -165,7 +223,7 @@ impl NpmPackageId { input, NpmPackageId { nv: PackageNv { name, version }, - peer_dependencies, + peer_dependencies: NpmPackageIdPeerDependencies(peer_dependencies), }, )) } @@ -180,6 +238,15 @@ impl NpmPackageId { } } +impl<'a> capacity_builder::StringAppendable<'a> for &'a NpmPackageId { + fn append_to_builder( + self, + builder: &mut capacity_builder::StringBuilder<'a, TString>, + ) { + self.as_serialized_with_level(builder, 0) + } +} + impl Ord for NpmPackageId { fn cmp(&self, other: &Self) -> Ordering { match self.nv.cmp(&other.nv) { @@ -226,8 +293,8 @@ impl std::fmt::Display for NpmPackageCacheFolderId { #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct NpmResolutionPackageSystemInfo { - pub os: Vec, - pub cpu: Vec, + pub os: Vec, + pub cpu: Vec, } impl NpmResolutionPackageSystemInfo { @@ -257,10 +324,10 @@ pub struct NpmResolutionPackage { pub dist: NpmPackageVersionDistInfo, /// Key is what the package refers to the other package as, /// which could be different from the package name. - pub dependencies: HashMap, - pub optional_dependencies: HashSet, + pub dependencies: HashMap, + pub optional_dependencies: HashSet, pub bin: Option, - pub scripts: HashMap, + pub scripts: HashMap, pub deprecated: Option, } @@ -313,16 +380,16 @@ impl NpmResolutionPackage { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct NpmSystemInfo { /// `process.platform` value from Node.js - pub os: String, + pub os: SmallStackString, /// `process.arch` value from Node.js - pub cpu: String, + pub cpu: SmallStackString, } impl Default for NpmSystemInfo { fn default() -> Self { Self { - os: node_js_os(std::env::consts::OS).to_string(), - cpu: node_js_cpu(std::env::consts::ARCH).to_string(), + os: node_js_os(std::env::consts::OS).into(), + cpu: node_js_cpu(std::env::consts::ARCH).into(), } } } @@ -330,13 +397,13 @@ impl Default for NpmSystemInfo { impl NpmSystemInfo { pub fn from_rust(os: &str, cpu: &str) -> Self { Self { - os: node_js_os(os).to_string(), - cpu: node_js_cpu(cpu).to_string(), + os: node_js_os(os).into(), + cpu: node_js_cpu(cpu).into(), } } } -fn matches_os_or_cpu_vec(items: &[String], target: &str) -> bool { +fn matches_os_or_cpu_vec(items: &[SmallStackString], target: &str) -> bool { if items.is_empty() { return true; } @@ -382,28 +449,30 @@ mod test { fn serialize_npm_package_id() { let id = NpmPackageId { nv: PackageNv::from_str("pkg-a@1.2.3").unwrap(), - peer_dependencies: vec![ + peer_dependencies: NpmPackageIdPeerDependencies::from([ NpmPackageId { nv: PackageNv::from_str("pkg-b@3.2.1").unwrap(), - peer_dependencies: vec![ + peer_dependencies: NpmPackageIdPeerDependencies::from([ NpmPackageId { nv: PackageNv::from_str("pkg-c@1.3.2").unwrap(), - peer_dependencies: vec![], + peer_dependencies: Default::default(), }, NpmPackageId { nv: PackageNv::from_str("pkg-d@2.3.4").unwrap(), - peer_dependencies: vec![], + peer_dependencies: Default::default(), }, - ], + ]), }, NpmPackageId { nv: PackageNv::from_str("pkg-e@2.3.1").unwrap(), - peer_dependencies: vec![NpmPackageId { - nv: PackageNv::from_str("pkg-f@2.3.1").unwrap(), - peer_dependencies: vec![], - }], + peer_dependencies: NpmPackageIdPeerDependencies::from([ + NpmPackageId { + nv: PackageNv::from_str("pkg-f@2.3.1").unwrap(), + peer_dependencies: Default::default(), + }, + ]), }, - ], + ]), }; // this shouldn't change because it's used in the lockfile @@ -455,33 +524,25 @@ mod test { #[test] fn test_matches_os_or_cpu_vec() { assert!(matches_os_or_cpu_vec(&[], "x64")); - assert!(matches_os_or_cpu_vec(&["x64".to_string()], "x64")); - assert!(!matches_os_or_cpu_vec(&["!x64".to_string()], "x64")); - assert!(matches_os_or_cpu_vec(&["!arm64".to_string()], "x64")); + assert!(matches_os_or_cpu_vec(&["x64".into()], "x64")); + assert!(!matches_os_or_cpu_vec(&["!x64".into()], "x64")); + assert!(matches_os_or_cpu_vec(&["!arm64".into()], "x64")); assert!(matches_os_or_cpu_vec( - &["!arm64".to_string(), "!x86".to_string()], + &["!arm64".into(), "!x86".into()], "x64" )); assert!(!matches_os_or_cpu_vec( - &["!arm64".to_string(), "!x86".to_string()], + &["!arm64".into(), "!x86".into()], "x86" )); assert!(!matches_os_or_cpu_vec( - &[ - "!arm64".to_string(), - "!x86".to_string(), - "other".to_string() - ], + &["!arm64".into(), "!x86".into(), "other".into()], "x86" )); // not explicitly excluded and there's an include, so it's considered a match assert!(matches_os_or_cpu_vec( - &[ - "!arm64".to_string(), - "!x86".to_string(), - "other".to_string() - ], + &["!arm64".into(), "!x86".into(), "other".into()], "x64" )); } diff --git a/src/registry.rs b/src/registry.rs index 125ff4f..7968329 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -9,7 +9,10 @@ use std::sync::Arc; use async_trait::async_trait; use deno_semver::npm::NpmVersionReqParseError; +use deno_semver::package::PackageName; use deno_semver::package::PackageNv; +use deno_semver::SmallStackString; +use deno_semver::StackString; use deno_semver::Version; use deno_semver::VersionReq; use serde::Deserialize; @@ -22,7 +25,7 @@ use crate::resolution::NpmPackageVersionNotFound; #[derive(Debug, Default, Deserialize, Serialize, Clone)] pub struct NpmPackageInfo { - pub name: String, + pub name: PackageName, pub versions: HashMap, #[serde(rename = "dist-tags")] pub dist_tags: HashMap, @@ -82,8 +85,8 @@ impl NpmDependencyEntryKind { #[derive(Debug, Clone, Eq, PartialEq)] pub struct NpmDependencyEntry { pub kind: NpmDependencyEntryKind, - pub bare_specifier: String, - pub name: String, + pub bare_specifier: StackString, + pub name: PackageName, pub version_req: VersionReq, /// When the dependency is also marked as a peer dependency, /// use this entry to resolve the dependency when it can't @@ -135,25 +138,25 @@ pub struct NpmPackageVersionInfo { // package and version (ex. `"typescript-3.0.1": "npm:typescript@3.0.1"`). #[serde(default)] #[serde(deserialize_with = "deserializers::hashmap")] - pub dependencies: HashMap, + pub dependencies: HashMap, #[serde(default)] #[serde(deserialize_with = "deserializers::hashmap")] - pub optional_dependencies: HashMap, + pub optional_dependencies: HashMap, #[serde(default)] #[serde(deserialize_with = "deserializers::hashmap")] - pub peer_dependencies: HashMap, + pub peer_dependencies: HashMap, #[serde(default)] #[serde(deserialize_with = "deserializers::hashmap")] - pub peer_dependencies_meta: HashMap, + pub peer_dependencies_meta: HashMap, #[serde(default)] #[serde(deserialize_with = "deserializers::vector")] - pub os: Vec, + pub os: Vec, #[serde(default)] #[serde(deserialize_with = "deserializers::vector")] - pub cpu: Vec, + pub cpu: Vec, #[serde(default)] #[serde(deserialize_with = "deserializers::hashmap")] - pub scripts: HashMap, + pub scripts: HashMap, #[serde(default)] #[serde(deserialize_with = "deserializers::string")] pub deprecated: Option, @@ -166,7 +169,7 @@ impl NpmPackageVersionInfo { package_name: &str, ) -> Result, Box> { fn parse_dep_entry_inner( - (key, value): (&String, &String), + (key, value): (&StackString, &StackString), kind: NpmDependencyEntryKind, ) -> Result { let (name, version_req) = @@ -174,23 +177,23 @@ impl NpmPackageVersionInfo { let version_req = VersionReq::parse_from_npm(version_req)?; Ok(NpmDependencyEntry { kind, - bare_specifier: key.to_string(), - name: name.to_string(), + bare_specifier: key.clone(), + name: PackageName::from_str(name), version_req, peer_dep_version_req: None, }) } fn parse_dep_entry( - nv: (&str, &Version), - key_value: (&String, &String), + parent_nv: (&str, &Version), + key_value: (&StackString, &StackString), kind: NpmDependencyEntryKind, ) -> Result> { parse_dep_entry_inner(key_value, kind).map_err(|source| { Box::new(NpmDependencyEntryError { parent_nv: PackageNv { - name: nv.0.to_string(), - version: nv.1.clone(), + name: parent_nv.0.into(), + version: parent_nv.1.clone(), }, key: key_value.0.to_string(), value: key_value.1.to_string(), @@ -405,7 +408,7 @@ impl TestNpmRegistryApi { self.add_package_info( name, NpmPackageInfo { - name: name.to_string(), + name: name.into(), ..Default::default() }, ); @@ -473,9 +476,7 @@ impl TestNpmRegistryApi { pub fn add_dependency(&self, package: (&str, &str), entry: (&str, &str)) { self.with_version_info(package, |version| { - version - .dependencies - .insert(entry.0.to_string(), entry.1.to_string()); + version.dependencies.insert(entry.0.into(), entry.1.into()); }) } @@ -485,12 +486,10 @@ impl TestNpmRegistryApi { entry: (&str, &str), ) { self.with_version_info(package, |version| { - version - .dependencies - .insert(entry.0.to_string(), entry.1.to_string()); + version.dependencies.insert(entry.0.into(), entry.1.into()); version .optional_dependencies - .insert(entry.0.to_string(), entry.1.to_string()); + .insert(entry.0.into(), entry.1.into()); }) } @@ -498,7 +497,7 @@ impl TestNpmRegistryApi { self.with_version_info(package, |version| { version .optional_dependencies - .insert(entry.0.to_string(), entry.1.to_string()); + .insert(entry.0.into(), entry.1.into()); }) } @@ -510,7 +509,7 @@ impl TestNpmRegistryApi { self.with_version_info(package, |version| { version .peer_dependencies - .insert(entry.0.to_string(), entry.1.to_string()); + .insert(entry.0.into(), entry.1.into()); }); } @@ -522,11 +521,10 @@ impl TestNpmRegistryApi { self.with_version_info(package, |version| { version .peer_dependencies - .insert(entry.0.to_string(), entry.1.to_string()); - version.peer_dependencies_meta.insert( - entry.0.to_string(), - NpmPeerDependencyMeta { optional: true }, - ); + .insert(entry.0.into(), entry.1.into()); + version + .peer_dependencies_meta + .insert(entry.0.into(), NpmPeerDependencyMeta { optional: true }); }); } } @@ -1124,7 +1122,7 @@ mod test { let info: NpmPackageVersionInfo = serde_json::from_str(text).unwrap(); assert_eq!( info.dependencies, - HashMap::from([("value10".to_string(), "valid".to_string())]) + HashMap::from([("value10".into(), "valid".into())]) ); assert_eq!(info.os, Vec::from(["valid".to_string()])); } @@ -1171,15 +1169,21 @@ mod test { ("test", "npm:@scope/package@1", ("@scope/package", "1")), ]; for (key, value, expected_result) in cases { - let result = parse_dep_entry_name_and_raw_version(key, value).unwrap(); - assert_eq!(result, expected_result); + let key = StackString::from(key); + let value = StackString::from(value); + let (name, version) = + parse_dep_entry_name_and_raw_version(&key, &value).unwrap(); + assert_eq!((name, version), expected_result); } } #[test] fn test_parse_dep_entry_name_and_raw_version_error() { - let err = - parse_dep_entry_name_and_raw_version("test", "git:somerepo").unwrap_err(); + let err = parse_dep_entry_name_and_raw_version( + &StackString::from("test"), + &StackString::from("git:somerepo"), + ) + .unwrap_err(); match err { NpmDependencyEntryErrorSource::RemoteDependency { specifier } => { assert_eq!(specifier, "git:somerepo") @@ -1196,7 +1200,7 @@ mod test { "git+ssh://github.com/example/example", ] { let deps = NpmPackageVersionInfo { - dependencies: HashMap::from([("a".to_string(), specifier.to_string())]), + dependencies: HashMap::from([("a".into(), specifier.into())]), ..Default::default() }; let err = deps.dependencies_as_entries("pkg-name").unwrap_err(); diff --git a/src/resolution/common.rs b/src/resolution/common.rs index 3fff615..b20c524 100644 --- a/src/resolution/common.rs +++ b/src/resolution/common.rs @@ -1,6 +1,7 @@ // Copyright 2018-2024 the Deno authors. MIT license. use deno_semver::package::PackageNv; +use deno_semver::StackString; use deno_semver::Version; use deno_semver::VersionReq; use deno_semver::WILDCARD_VERSION_REQ; @@ -23,14 +24,14 @@ pub enum NpmPackageVersionResolutionError { )] DistTagNotFound { dist_tag: String, - package_name: String, + package_name: StackString, }, #[class(type)] #[error( "Could not find version '{version}' referenced in dist-tag '{dist_tag}' for npm package '{package_name}'." )] DistTagVersionNotFound { - package_name: String, + package_name: StackString, dist_tag: String, version: String, }, @@ -42,7 +43,7 @@ pub enum NpmPackageVersionResolutionError { "Could not find npm package '{package_name}' matching '{version_req}'." )] VersionReqNotMatched { - package_name: String, + package_name: StackString, version_req: VersionReq, }, } @@ -224,10 +225,10 @@ mod test { // dist tag where version doesn't exist let package_req = PackageReq::from_str("test@latest").unwrap(); let package_info = NpmPackageInfo { - name: "test".to_string(), + name: "test".into(), versions: HashMap::new(), dist_tags: HashMap::from([( - "latest".to_string(), + "latest".into(), Version::parse_from_npm("1.0.0-alpha").unwrap(), )]), }; @@ -246,7 +247,7 @@ mod test { // dist tag where version is a pre-release let package_req = PackageReq::from_str("test@latest").unwrap(); let package_info = NpmPackageInfo { - name: "test".to_string(), + name: "test".into(), versions: HashMap::from([ ( Version::parse_from_npm("0.1.0").unwrap(), @@ -261,7 +262,7 @@ mod test { ), ]), dist_tags: HashMap::from([( - "latest".to_string(), + "latest".into(), Version::parse_from_npm("1.0.0-alpha").unwrap(), )]), }; @@ -278,7 +279,7 @@ mod test { // for the "types_node_version_req" even though the latest is 1.1.0 let package_req = PackageReq::from_str("@types/node").unwrap(); let package_info = NpmPackageInfo { - name: "@types/node".to_string(), + name: "@types/node".into(), versions: HashMap::from([ ( Version::parse_from_npm("1.0.0").unwrap(), @@ -296,7 +297,7 @@ mod test { ), ]), dist_tags: HashMap::from([( - "latest".to_string(), + "latest".into(), Version::parse_from_npm("1.1.0").unwrap(), )]), }; @@ -316,7 +317,7 @@ mod test { fn test_wildcard_version_req() { let package_req = PackageReq::from_str("some-pkg").unwrap(); let package_info = NpmPackageInfo { - name: "some-pkg".to_string(), + name: "some-pkg".into(), versions: HashMap::from([ ( Version::parse_from_npm("1.0.0-rc.1").unwrap(), @@ -334,7 +335,7 @@ mod test { ), ]), dist_tags: HashMap::from([( - "latest".to_string(), + "latest".into(), Version::parse_from_npm("1.0.0-rc.1").unwrap(), )]), }; @@ -351,7 +352,7 @@ mod test { #[test] fn test_latest_tag_version_req() { let package_info = NpmPackageInfo { - name: "some-pkg".to_string(), + name: "some-pkg".into(), versions: HashMap::from([ ( Version::parse_from_npm("0.1.0-alpha.1").unwrap(), @@ -384,11 +385,11 @@ mod test { ]), dist_tags: HashMap::from([ ( - "latest".to_string(), + "latest".into(), Version::parse_from_npm("0.1.0-alpha.2").unwrap(), ), ( - "dev".to_string(), + "dev".into(), Version::parse_from_npm("0.1.0-beta.2").unwrap(), ), ]), diff --git a/src/resolution/graph.rs b/src/resolution/graph.rs index 2d9a324..4d5fb15 100644 --- a/src/resolution/graph.rs +++ b/src/resolution/graph.rs @@ -1,7 +1,9 @@ // Copyright 2018-2024 the Deno authors. MIT license. +use deno_semver::package::PackageName; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; +use deno_semver::StackString; use deno_semver::Version; use deno_semver::VersionReq; use futures::StreamExt; @@ -64,7 +66,7 @@ struct Node { /// Note: We don't want to store the children as a `NodeRef` because /// multiple paths might visit through the children and we don't want /// to share those references with those paths. - pub children: BTreeMap, + pub children: BTreeMap, /// Whether the node has demonstrated to have no peer dependencies in its /// descendants. If this is true then we can skip analyzing this node /// again when we encounter it another time in the dependency tree, which @@ -213,7 +215,7 @@ enum GraphPathNodeOrRoot { struct GraphPath { previous_node: Option, node_id_ref: NodeIdRef, - specifier: String, + specifier: StackString, // we could consider not storing this here and instead reference the resolved // nodes, but we should performance profile this code first nv: Rc, @@ -228,7 +230,7 @@ impl GraphPath { previous_node: Some(GraphPathNodeOrRoot::Root(nv.clone())), node_id_ref: NodeIdRef::new(node_id), // use an empty specifier - specifier: "".to_string(), + specifier: "".into(), nv, linked_circular_descendants: Default::default(), }) @@ -238,7 +240,7 @@ impl GraphPath { self.node_id_ref.get() } - pub fn specifier(&self) -> &str { + pub fn specifier(&self) -> &StackString { &self.specifier } @@ -249,7 +251,7 @@ impl GraphPath { pub fn with_id( self: &Rc, node_id: NodeId, - specifier: String, + specifier: StackString, nv: Rc, ) -> Rc { Rc::new(Self { @@ -324,7 +326,7 @@ pub struct Graph { /// Note: Uses a BTreeMap in order to create some determinism /// when creating the snapshot. root_packages: BTreeMap, NodeId>, - package_name_versions: HashMap>, + package_name_versions: HashMap>, nodes: HashMap, resolved_node_ids: ResolvedNodeIds, // This will be set when creating from a snapshot, then @@ -467,12 +469,12 @@ impl Graph { if resolved_id.peer_dependencies.is_empty() { NpmPackageId { nv: (*resolved_id.nv).clone(), - peer_dependencies: Vec::new(), + peer_dependencies: Default::default(), } } else { let mut npm_pkg_id = NpmPackageId { nv: (*resolved_id.nv).clone(), - peer_dependencies: Vec::with_capacity( + peer_dependencies: crate::NpmPackageIdPeerDependencies::with_capacity( resolved_id.peer_dependencies.len(), ), }; @@ -498,7 +500,7 @@ impl Graph { } else { npm_pkg_id.peer_dependencies.push(NpmPackageId { nv: (*child_resolved_id.nv).clone(), - peer_dependencies: Vec::new(), + peer_dependencies: Default::default(), }); } } @@ -584,12 +586,12 @@ impl Graph { fn set_child_of_parent_node( &mut self, parent_id: NodeId, - specifier: &str, + specifier: &StackString, child_id: NodeId, ) { assert_ne!(child_id, parent_id); let parent = self.borrow_node_mut(parent_id); - parent.children.insert(specifier.to_string(), child_id); + parent.children.insert(specifier.clone(), child_id); } pub async fn into_snapshot( @@ -608,7 +610,7 @@ impl Graph { ); let mut packages: HashMap = HashMap::with_capacity(self.nodes.len()); - let mut packages_by_name: HashMap> = + let mut packages_by_name: HashMap> = HashMap::with_capacity(self.nodes.len()); // todo(dsherret): there is a lurking bug within the peer dependencies code. @@ -790,7 +792,7 @@ impl DepEntryCache { } struct UnresolvedOptionalPeer { - specifier: String, + specifier: StackString, graph_path: Rc, } @@ -871,11 +873,8 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi> child_id = ancestor.node_id(); } - let new_path = parent_path.with_id( - child_id, - entry.bare_specifier.to_string(), - child_nv, - ); + let new_path = + parent_path.with_id(child_id, entry.bare_specifier.clone(), child_nv); if let Some(ancestor) = maybe_ancestor { // this node is circular, so we link it to the ancestor self.add_linked_circular_descendant(&ancestor, new_path); @@ -910,7 +909,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi> )?; let resolved_id = ResolvedId { nv: Rc::new(PackageNv { - name: package_info.name.to_string(), + name: package_info.name.clone(), version: info.version.clone(), }), peer_dependencies: Vec::new(), @@ -935,7 +934,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi> "{} - Resolved {}@{} to {}", match parent_id { Some(parent_id) => self.graph.get_npm_pkg_id(parent_id).as_serialized(), - None => "".to_string(), + None => "".into(), }, pkg_req_name, version_req.version_text(), @@ -1117,7 +1116,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi> fn resolve_peer_dep( &mut self, - specifier: &str, + specifier: &StackString, peer_dep: &NpmDependencyEntry, peer_package_info: &NpmPackageInfo, ancestor_path: &Rc, @@ -1328,7 +1327,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi> let parent_node = self.graph.borrow_node_mut(parent_node_id); parent_node .children - .insert(graph_path_node.specifier().to_string(), new_node_id); + .insert(graph_path_node.specifier().clone(), new_node_id); } } } @@ -1339,7 +1338,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi> // path from the node above the resolved dep to just above the peer dep path: &[&Rc], peer_dep_parent: GraphPathNodeOrRoot, - peer_dep_specifier: &str, + peer_dep_specifier: &StackString, peer_dep_id: NodeId, ) { debug_assert!(!path.is_empty()); @@ -1380,11 +1379,8 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi> ); // queue next step - let new_path = bottom_node.with_id( - peer_dep_id, - peer_dep_specifier.to_string(), - peer_dep_nv, - ); + let new_path = + bottom_node.with_id(peer_dep_id, peer_dep_specifier.clone(), peer_dep_nv); if let Some(ancestor_node) = maybe_circular_ancestor { // it's circular, so link this in step with the ancestor node ancestor_node @@ -3835,10 +3831,10 @@ mod test { api.add_dependency(("package-c", "1.0.0"), ("package-d", "1")); api.add_optional_dep(("package-d", "1.0.0"), ("package-e", "1")); api.with_version_info(("package-c", "1.0.0"), |info| { - info.os = vec!["win32".to_string(), "darwin".to_string()]; + info.os = vec!["win32".into(), "darwin".into()]; }); api.with_version_info(("package-e", "1.0.0"), |info| { - info.os = vec!["win32".to_string()]; + info.os = vec!["win32".into()]; }); let snapshot = @@ -3846,8 +3842,8 @@ mod test { let packages = package_names_with_info( &snapshot, &NpmSystemInfo { - os: "win32".to_string(), - cpu: "x86".to_string(), + os: "win32".into(), + cpu: "x86".into(), }, ); assert_eq!( @@ -3864,8 +3860,8 @@ mod test { let packages = package_names_with_info( &snapshot, &NpmSystemInfo { - os: "darwin".to_string(), - cpu: "x86".to_string(), + os: "darwin".into(), + cpu: "x86".into(), }, ); assert_eq!( @@ -3881,8 +3877,8 @@ mod test { let packages = package_names_with_info( &snapshot, &NpmSystemInfo { - os: "linux".to_string(), - cpu: "x86".to_string(), + os: "linux".into(), + cpu: "x86".into(), }, ); assert_eq!( @@ -3912,10 +3908,10 @@ mod test { api.add_dep_and_optional_dep(("package-d", "1.0.0"), ("package-e", "1")); api.with_version_info(("package-c", "1.0.0"), |info| { - info.os = vec!["win32".to_string()]; + info.os = vec!["win32".into()]; }); api.with_version_info(("package-e", "1.0.0"), |info| { - info.os = vec!["win32".to_string()]; + info.os = vec!["win32".into()]; }); let snapshot = @@ -3924,8 +3920,8 @@ mod test { let packages = package_names_with_info( &snapshot, &NpmSystemInfo { - os: "darwin".to_string(), - cpu: "x86".to_string(), + os: "darwin".into(), + cpu: "x86".into(), }, ); assert_eq!( @@ -3971,8 +3967,8 @@ mod test { let packages = package_names_with_info( &snapshot, &NpmSystemInfo { - os: "darwin".to_string(), - cpu: "x86_64".to_string(), + os: "darwin".into(), + cpu: "x86_64".into(), }, ); assert_eq!(packages, vec!["package-a@1.0.0".to_string()]); @@ -3993,8 +3989,8 @@ mod test { let packages = package_names_with_info( &snapshot, &NpmSystemInfo { - os: "darwin".to_string(), - cpu: "x86_64".to_string(), + os: "darwin".into(), + cpu: "x86_64".into(), }, ); assert_eq!( @@ -4018,7 +4014,7 @@ mod test { system: Default::default(), dist: Default::default(), dependencies: HashMap::from([( - "package-a".to_string(), + "package-a".into(), NpmPackageId::from_serialized("package-0@1.0.0").unwrap(), )]), optional_dependencies: HashSet::new(), @@ -4056,7 +4052,12 @@ mod test { .map(|(a, b)| { ( a.to_string(), - snapshot.root_packages.get(&b).unwrap().as_serialized(), + snapshot + .root_packages + .get(&b) + .unwrap() + .as_serialized() + .to_string(), ) }) .collect::>(); @@ -4065,12 +4066,14 @@ mod test { let packages = packages .into_iter() .map(|pkg| TestNpmResolutionPackage { - pkg_id: pkg.id.as_serialized(), + pkg_id: pkg.id.as_serialized().to_string(), copy_index: pkg.copy_index, dependencies: pkg .dependencies .into_iter() - .map(|(key, value)| (key, value.as_serialized())) + .map(|(key, value)| { + (key.to_string(), value.as_serialized().to_string()) + }) .collect(), }) .collect(); @@ -4085,7 +4088,7 @@ mod test { let mut packages = snapshot .all_system_packages(system_info) .into_iter() - .map(|p| p.id.as_serialized()) + .map(|p| p.id.as_serialized().to_string()) .collect::>(); packages.sort(); let mut serialized_pkgs = snapshot @@ -4093,7 +4096,7 @@ mod test { .into_serialized() .packages .into_iter() - .map(|p| p.id.as_serialized()) + .map(|p| p.id.as_serialized().to_string()) .collect::>(); serialized_pkgs.sort(); // ensure the output of both of these are the same diff --git a/src/resolution/snapshot.rs b/src/resolution/snapshot.rs index 25c7e72..c0588dd 100644 --- a/src/resolution/snapshot.rs +++ b/src/resolution/snapshot.rs @@ -4,6 +4,8 @@ use deno_error::JsError; use deno_lockfile::Lockfile; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; +use deno_semver::SmallStackString; +use deno_semver::StackString; use deno_semver::VersionReq; use futures::stream::FuturesOrdered; use futures::StreamExt; @@ -112,10 +114,10 @@ pub struct SerializedNpmResolutionSnapshotPackage { pub dist: NpmPackageVersionDistInfo, /// Key is what the package refers to the other package as, /// which could be different from the package name. - pub dependencies: HashMap, - pub optional_dependencies: HashSet, + pub dependencies: HashMap, + pub optional_dependencies: HashSet, pub bin: Option, - pub scripts: HashMap, + pub scripts: HashMap, pub deprecated: Option, } @@ -220,7 +222,7 @@ pub struct NpmResolutionSnapshot { pub(super) package_reqs: HashMap, // Each root level npm package name and version maps to an exact npm package node id. pub(super) root_packages: HashMap, - pub(super) packages_by_name: HashMap>, + pub(super) packages_by_name: HashMap>, pub(super) packages: HashMap, } @@ -234,7 +236,7 @@ impl NpmResolutionSnapshot { snapshot.root_packages.len(), ); let mut packages_by_name = - HashMap::>::with_capacity( + HashMap::>::with_capacity( snapshot.packages.len(), ); // close enough let mut packages = @@ -253,7 +255,7 @@ impl NpmResolutionSnapshot { // then the packages for package in snapshot.packages { packages_by_name - .entry(package.id.nv.name.to_string()) + .entry(package.id.nv.name.clone()) .or_default() .push(package.id.clone()); @@ -370,7 +372,7 @@ impl NpmResolutionSnapshot { pub fn subset(&self, package_reqs: &[PackageReq]) -> Self { let mut new_package_reqs = HashMap::with_capacity(package_reqs.len()); let mut packages = HashMap::with_capacity(package_reqs.len() * 2); - let mut packages_by_name: HashMap> = + let mut packages_by_name: HashMap> = HashMap::with_capacity(package_reqs.len()); let mut root_packages = HashMap::with_capacity(package_reqs.len()); @@ -395,7 +397,7 @@ impl NpmResolutionSnapshot { continue; }; packages_by_name - .entry(package.id.nv.name.to_string()) + .entry(package.id.nv.name.clone()) .or_default() .push(package.id.clone()); let Some(package) = self.package_from_id(id) else { @@ -819,7 +821,7 @@ pub enum IncompleteSnapshotFromLockfileError { struct IncompletePackageInfo { id: NpmPackageId, integrity: String, - dependencies: HashMap, + dependencies: HashMap, } pub struct IncompleteSnapshot { @@ -1101,7 +1103,7 @@ mod tests { dependencies: deps(&[("b", "b@1.0.0"), ("c", "c@1.0.0")]), system: Default::default(), dist: Default::default(), - optional_dependencies: HashSet::from(["c".to_string()]), + optional_dependencies: HashSet::from(["c".into()]), bin: None, scripts: Default::default(), deprecated: Default::default(), @@ -1120,8 +1122,8 @@ mod tests { id: NpmPackageId::from_serialized("c@1.0.0").unwrap(), dependencies: deps(&[("b", "b@1.0.0"), ("d", "d@1.0.0")]), system: NpmResolutionPackageSystemInfo { - os: vec!["win32".to_string()], - cpu: vec!["x64".to_string()], + os: vec!["win32".into()], + cpu: vec!["x64".into()], }, dist: Default::default(), optional_dependencies: Default::default(), @@ -1148,8 +1150,8 @@ mod tests { { let mut actual = snapshot .as_valid_serialized_for_system(&NpmSystemInfo { - os: "win32".to_string(), - cpu: "x64".to_string(), + os: "win32".into(), + cpu: "x64".into(), }) .into_serialized(); actual.packages.sort_by(|a, b| a.id.cmp(&b.id)); @@ -1165,8 +1167,8 @@ mod tests { { let mut actual = snapshot .as_valid_serialized_for_system(&NpmSystemInfo { - os: "darwin".to_string(), - cpu: "x64".to_string(), + os: "darwin".into(), + cpu: "x64".into(), }) .into_serialized(); actual.packages.sort_by(|a, b| a.id.cmp(&b.id)); @@ -1234,7 +1236,7 @@ mod tests { ) -> NpmPackageCacheFolderId { NpmPackageCacheFolderId { nv: PackageNv { - name: name.to_string(), + name: name.into(), version: Version::parse_standard(version).unwrap(), }, copy_index, @@ -1254,12 +1256,12 @@ mod tests { } } - fn deps(deps: &[(&str, &str)]) -> HashMap { + fn deps(deps: &[(&str, &str)]) -> HashMap { deps .iter() .map(|(key, value)| { ( - key.to_string(), + StackString::from(*key), NpmPackageId::from_serialized(value).unwrap(), ) })