summaryrefslogtreecommitdiff
path: root/cli/npm
diff options
context:
space:
mode:
Diffstat (limited to 'cli/npm')
-rw-r--r--cli/npm/cache.rs28
-rw-r--r--cli/npm/mod.rs3
-rw-r--r--cli/npm/registry.rs15
-rw-r--r--cli/npm/resolution/graph.rs73
-rw-r--r--cli/npm/resolution/mod.rs35
-rw-r--r--cli/npm/resolution/reference.rs298
-rw-r--r--cli/npm/resolution/snapshot.rs14
-rw-r--r--cli/npm/resolution/specifier.rs310
-rw-r--r--cli/npm/semver/mod.rs1152
-rw-r--r--cli/npm/semver/range.rs509
-rw-r--r--cli/npm/semver/specifier.rs299
-rw-r--r--cli/npm/tarball.rs9
12 files changed, 388 insertions, 2357 deletions
diff --git a/cli/npm/cache.rs b/cli/npm/cache.rs
index 0d07d27b2..888975926 100644
--- a/cli/npm/cache.rs
+++ b/cli/npm/cache.rs
@@ -17,13 +17,13 @@ use deno_core::url::Url;
use crate::args::CacheSetting;
use crate::cache::DenoDir;
use crate::http_util::HttpClient;
+use crate::semver::Version;
use crate::util::fs::canonicalize_path;
use crate::util::fs::hard_link_dir_recursive;
use crate::util::path::root_url_to_safe_local_dirname;
use crate::util::progress_bar::ProgressBar;
use super::registry::NpmPackageVersionDistInfo;
-use super::semver::NpmVersion;
use super::tarball::verify_and_extract_tarball;
/// For some of the tests, we want downloading of packages
@@ -35,7 +35,7 @@ pub fn should_sync_download() -> bool {
const NPM_PACKAGE_SYNC_LOCK_FILENAME: &str = ".deno_sync_lock";
pub fn with_folder_sync_lock(
- package: (&str, &NpmVersion),
+ package: (&str, &Version),
output_folder: &Path,
action: impl FnOnce() -> Result<(), AnyError>,
) -> Result<(), AnyError> {
@@ -108,7 +108,7 @@ pub fn with_folder_sync_lock(
pub struct NpmPackageCacheFolderId {
pub name: String,
- pub version: NpmVersion,
+ pub version: Version,
/// Peer dependency resolution may require us to have duplicate copies
/// of the same package.
pub copy_index: usize,
@@ -202,7 +202,7 @@ impl ReadonlyNpmCache {
pub fn package_folder_for_name_and_version(
&self,
name: &str,
- version: &NpmVersion,
+ version: &Version,
registry_url: &Url,
) -> PathBuf {
self
@@ -305,7 +305,7 @@ impl ReadonlyNpmCache {
};
Some(NpmPackageCacheFolderId {
name,
- version: NpmVersion::parse(version).ok()?,
+ version: Version::parse_from_npm(version).ok()?,
copy_index,
})
}
@@ -357,7 +357,7 @@ impl NpmCache {
/// and imports a dynamic import that imports the same package again for example.
fn should_use_global_cache_for_package(
&self,
- package: (&str, &NpmVersion),
+ package: (&str, &Version),
) -> bool {
self.cache_setting.should_use_for_npm_package(package.0)
|| !self
@@ -368,7 +368,7 @@ impl NpmCache {
pub async fn ensure_package(
&self,
- package: (&str, &NpmVersion),
+ package: (&str, &Version),
dist: &NpmPackageVersionDistInfo,
registry_url: &Url,
) -> Result<(), AnyError> {
@@ -382,7 +382,7 @@ impl NpmCache {
async fn ensure_package_inner(
&self,
- package: (&str, &NpmVersion),
+ package: (&str, &Version),
dist: &NpmPackageVersionDistInfo,
registry_url: &Url,
) -> Result<(), AnyError> {
@@ -467,7 +467,7 @@ impl NpmCache {
pub fn package_folder_for_name_and_version(
&self,
name: &str,
- version: &NpmVersion,
+ version: &Version,
registry_url: &Url,
) -> PathBuf {
self.readonly.package_folder_for_name_and_version(
@@ -517,7 +517,7 @@ mod test {
use super::ReadonlyNpmCache;
use crate::npm::cache::NpmPackageCacheFolderId;
- use crate::npm::semver::NpmVersion;
+ use crate::semver::Version;
#[test]
fn should_get_package_folder() {
@@ -530,7 +530,7 @@ mod test {
cache.package_folder_for_id(
&NpmPackageCacheFolderId {
name: "json".to_string(),
- version: NpmVersion::parse("1.2.5").unwrap(),
+ version: Version::parse_from_npm("1.2.5").unwrap(),
copy_index: 0,
},
&registry_url,
@@ -545,7 +545,7 @@ mod test {
cache.package_folder_for_id(
&NpmPackageCacheFolderId {
name: "json".to_string(),
- version: NpmVersion::parse("1.2.5").unwrap(),
+ version: Version::parse_from_npm("1.2.5").unwrap(),
copy_index: 1,
},
&registry_url,
@@ -560,7 +560,7 @@ mod test {
cache.package_folder_for_id(
&NpmPackageCacheFolderId {
name: "JSON".to_string(),
- version: NpmVersion::parse("2.1.5").unwrap(),
+ version: Version::parse_from_npm("2.1.5").unwrap(),
copy_index: 0,
},
&registry_url,
@@ -575,7 +575,7 @@ mod test {
cache.package_folder_for_id(
&NpmPackageCacheFolderId {
name: "@types/JSON".to_string(),
- version: NpmVersion::parse("2.1.5").unwrap(),
+ version: Version::parse_from_npm("2.1.5").unwrap(),
copy_index: 0,
},
&registry_url,
diff --git a/cli/npm/mod.rs b/cli/npm/mod.rs
index 9f41e508a..b9a4f493a 100644
--- a/cli/npm/mod.rs
+++ b/cli/npm/mod.rs
@@ -4,11 +4,8 @@ mod cache;
mod registry;
mod resolution;
mod resolvers;
-mod semver;
mod tarball;
-#[cfg(test)]
-pub use self::semver::NpmVersion;
pub use cache::NpmCache;
#[cfg(test)]
pub use registry::NpmPackageVersionDistInfo;
diff --git a/cli/npm/registry.rs b/cli/npm/registry.rs
index 9598feba1..fea6996ab 100644
--- a/cli/npm/registry.rs
+++ b/cli/npm/registry.rs
@@ -25,13 +25,12 @@ use serde::Serialize;
use crate::args::CacheSetting;
use crate::cache::CACHE_PERM;
use crate::http_util::HttpClient;
+use crate::semver::Version;
+use crate::semver::VersionReq;
use crate::util::fs::atomic_write_file;
use crate::util::progress_bar::ProgressBar;
use super::cache::NpmCache;
-use super::resolution::NpmVersionMatcher;
-use super::semver::NpmVersion;
-use super::semver::NpmVersionReq;
// npm registry docs: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md
@@ -61,11 +60,11 @@ pub struct NpmDependencyEntry {
pub kind: NpmDependencyEntryKind,
pub bare_specifier: String,
pub name: String,
- pub version_req: NpmVersionReq,
+ 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
/// be resolved as a peer dependency.
- pub peer_dep_version_req: Option<NpmVersionReq>,
+ pub peer_dep_version_req: Option<VersionReq>,
}
impl PartialOrd for NpmDependencyEntry {
@@ -82,7 +81,7 @@ impl Ord for NpmDependencyEntry {
Ordering::Equal => other
.version_req
.version_text()
- .cmp(&self.version_req.version_text()),
+ .cmp(self.version_req.version_text()),
ordering => ordering,
}
}
@@ -129,7 +128,7 @@ impl NpmPackageVersionInfo {
(entry.0.clone(), entry.1.clone())
};
let version_req =
- NpmVersionReq::parse(&version_req).with_context(|| {
+ VersionReq::parse_from_npm(&version_req).with_context(|| {
format!(
"error parsing version requirement for dependency: {bare_specifier}@{version_req}"
)
@@ -217,7 +216,7 @@ pub trait NpmRegistryApi: Clone + Sync + Send + 'static {
fn package_version_info(
&self,
name: &str,
- version: &NpmVersion,
+ version: &Version,
) -> BoxFuture<'static, Result<Option<NpmPackageVersionInfo>, AnyError>> {
let api = self.clone();
let name = name.to_string();
diff --git a/cli/npm/resolution/graph.rs b/cli/npm/resolution/graph.rs
index e21048149..20f192fbf 100644
--- a/cli/npm/resolution/graph.rs
+++ b/cli/npm/resolution/graph.rs
@@ -14,22 +14,25 @@ use deno_core::futures;
use deno_core::parking_lot::Mutex;
use deno_core::parking_lot::MutexGuard;
use log::debug;
+use once_cell::sync::Lazy;
use crate::npm::cache::should_sync_download;
use crate::npm::registry::NpmDependencyEntry;
use crate::npm::registry::NpmDependencyEntryKind;
use crate::npm::registry::NpmPackageInfo;
use crate::npm::registry::NpmPackageVersionInfo;
-use crate::npm::semver::NpmVersion;
-use crate::npm::semver::NpmVersionReq;
use crate::npm::NpmRegistryApi;
+use crate::semver::Version;
+use crate::semver::VersionReq;
use super::snapshot::NpmResolutionSnapshot;
use super::snapshot::SnapshotPackageCopyIndexResolver;
use super::NpmPackageId;
use super::NpmPackageReq;
use super::NpmResolutionPackage;
-use super::NpmVersionMatcher;
+
+pub static LATEST_VERSION_REQ: Lazy<VersionReq> =
+ Lazy::new(|| VersionReq::parse_from_specifier("latest").unwrap());
/// A memory efficient path of visited name and versions in the graph
/// which is used to detect cycles.
@@ -419,11 +422,11 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
fn resolve_best_package_version_and_info<'info>(
&self,
- version_matcher: &impl NpmVersionMatcher,
+ version_req: &VersionReq,
package_info: &'info NpmPackageInfo,
) -> Result<VersionAndInfo<'info>, AnyError> {
if let Some(version) =
- self.resolve_best_package_version(package_info, version_matcher)?
+ self.resolve_best_package_version(package_info, version_req)?
{
match package_info.versions.get(&version.to_string()) {
Some(version_info) => Ok(VersionAndInfo {
@@ -440,20 +443,19 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
}
} else {
// get the information
- get_resolved_package_version_and_info(version_matcher, package_info, None)
+ get_resolved_package_version_and_info(version_req, package_info, None)
}
}
fn resolve_best_package_version(
&self,
package_info: &NpmPackageInfo,
- version_matcher: &impl NpmVersionMatcher,
- ) -> Result<Option<NpmVersion>, AnyError> {
- let mut maybe_best_version: Option<&NpmVersion> = None;
+ version_req: &VersionReq,
+ ) -> Result<Option<Version>, AnyError> {
+ let mut maybe_best_version: Option<&Version> = None;
if let Some(ids) = self.graph.packages_by_name.get(&package_info.name) {
for version in ids.iter().map(|id| &id.version) {
- if version_req_satisfies(version_matcher, version, package_info, None)?
- {
+ if version_req_satisfies(version_req, version, package_info, None)? {
let is_best_version = maybe_best_version
.as_ref()
.map(|best_version| (*best_version).cmp(version).is_lt())
@@ -478,7 +480,10 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
) -> Result<(), AnyError> {
let (_, node) = self.resolve_node_from_info(
&package_req.name,
- package_req,
+ package_req
+ .version_req
+ .as_ref()
+ .unwrap_or(&*LATEST_VERSION_REQ),
package_info,
None,
)?;
@@ -557,12 +562,12 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
fn resolve_node_from_info(
&mut self,
pkg_req_name: &str,
- version_matcher: &impl NpmVersionMatcher,
+ version_req: &VersionReq,
package_info: &NpmPackageInfo,
parent_id: Option<&NpmPackageId>,
) -> Result<(NpmPackageId, Arc<Mutex<Node>>), AnyError> {
- let version_and_info = self
- .resolve_best_package_version_and_info(version_matcher, package_info)?;
+ let version_and_info =
+ self.resolve_best_package_version_and_info(version_req, package_info)?;
let id = NpmPackageId {
name: package_info.name.to_string(),
version: version_and_info.version.clone(),
@@ -575,7 +580,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
None => "<package-req>".to_string(),
},
pkg_req_name,
- version_matcher.version_text(),
+ version_req.version_text(),
id.as_serialized(),
);
let (created, node) = self.graph.get_or_create_for_id(&id);
@@ -996,22 +1001,22 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
#[derive(Clone)]
struct VersionAndInfo<'a> {
- version: NpmVersion,
+ version: Version,
info: &'a NpmPackageVersionInfo,
}
fn get_resolved_package_version_and_info<'a>(
- version_matcher: &impl NpmVersionMatcher,
+ version_req: &VersionReq,
info: &'a NpmPackageInfo,
parent: Option<&NpmPackageId>,
) -> Result<VersionAndInfo<'a>, AnyError> {
- if let Some(tag) = version_matcher.tag() {
+ if let Some(tag) = version_req.tag() {
tag_to_version_info(info, tag, parent)
} else {
let mut maybe_best_version: Option<VersionAndInfo> = None;
for version_info in info.versions.values() {
- let version = NpmVersion::parse(&version_info.version)?;
- if version_matcher.matches(&version) {
+ let version = Version::parse_from_npm(&version_info.version)?;
+ if version_req.matches(&version) {
let is_best_version = maybe_best_version
.as_ref()
.map(|best_version| best_version.version.cmp(&version).is_lt())
@@ -1042,7 +1047,7 @@ fn get_resolved_package_version_and_info<'a>(
"Try retrieving the latest npm package information by running with --reload",
),
info.name,
- version_matcher.version_text(),
+ version_req.version_text(),
match parent {
Some(id) => format!(" as specified in {}", id.display()),
None => String::new(),
@@ -1053,17 +1058,17 @@ fn get_resolved_package_version_and_info<'a>(
}
fn version_req_satisfies(
- matcher: &impl NpmVersionMatcher,
- version: &NpmVersion,
+ version_req: &VersionReq,
+ version: &Version,
package_info: &NpmPackageInfo,
parent: Option<&NpmPackageId>,
) -> Result<bool, AnyError> {
- match matcher.tag() {
+ match version_req.tag() {
Some(tag) => {
let tag_version = tag_to_version_info(package_info, tag, parent)?.version;
Ok(tag_version == *version)
}
- None => Ok(matcher.matches(version)),
+ None => Ok(version_req.matches(version)),
}
}
@@ -1081,7 +1086,7 @@ fn tag_to_version_info<'a>(
// explicit version.
if tag == "latest" && info.name == "@types/node" {
return get_resolved_package_version_and_info(
- &NpmVersionReq::parse("18.0.0 - 18.11.18").unwrap(),
+ &VersionReq::parse_from_npm("18.0.0 - 18.11.18").unwrap(),
info,
parent,
);
@@ -1090,7 +1095,7 @@ fn tag_to_version_info<'a>(
if let Some(version) = info.dist_tags.get(tag) {
match info.versions.get(version) {
Some(info) => Ok(VersionAndInfo {
- version: NpmVersion::parse(version)?,
+ version: Version::parse_from_npm(version)?,
info,
}),
None => {
@@ -1128,7 +1133,11 @@ mod test {
)]),
};
let result = get_resolved_package_version_and_info(
- &package_ref.req,
+ package_ref
+ .req
+ .version_req
+ .as_ref()
+ .unwrap_or(&*LATEST_VERSION_REQ),
&package_info,
None,
);
@@ -1157,7 +1166,11 @@ mod test {
)]),
};
let result = get_resolved_package_version_and_info(
- &package_ref.req,
+ package_ref
+ .req
+ .version_req
+ .as_ref()
+ .unwrap_or(&*LATEST_VERSION_REQ),
&package_info,
None,
);
diff --git a/cli/npm/resolution/mod.rs b/cli/npm/resolution/mod.rs
index 407651ccb..990ad8d06 100644
--- a/cli/npm/resolution/mod.rs
+++ b/cli/npm/resolution/mod.rs
@@ -11,6 +11,7 @@ use serde::Deserialize;
use serde::Serialize;
use crate::args::Lockfile;
+use crate::semver::Version;
use self::graph::GraphDependencyResolver;
use self::snapshot::NpmPackagesPartitioned;
@@ -19,33 +20,25 @@ use super::cache::should_sync_download;
use super::cache::NpmPackageCacheFolderId;
use super::registry::NpmPackageVersionDistInfo;
use super::registry::RealNpmRegistryApi;
-use super::semver::NpmVersion;
use super::NpmRegistryApi;
mod graph;
+mod reference;
mod snapshot;
mod specifier;
use graph::Graph;
+pub use reference::NpmPackageReference;
+pub use reference::NpmPackageReq;
pub use snapshot::NpmResolutionSnapshot;
pub use specifier::resolve_graph_npm_info;
-pub use specifier::NpmPackageReference;
-pub use specifier::NpmPackageReq;
-
-/// The version matcher used for npm schemed urls is more strict than
-/// the one used by npm packages and so we represent either via a trait.
-pub trait NpmVersionMatcher {
- fn tag(&self) -> Option<&str>;
- fn matches(&self, version: &NpmVersion) -> bool;
- fn version_text(&self) -> String;
-}
#[derive(
Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize,
)]
pub struct NpmPackageId {
pub name: String,
- pub version: NpmVersion,
+ pub version: Version,
pub peer_dependencies: Vec<NpmPackageId>,
}
@@ -103,14 +96,12 @@ impl NpmPackageId {
if_not_empty(substring(skip_while(|c| c != '_')))(input)
}
- fn parse_name_and_version(
- input: &str,
- ) -> ParseResult<(String, NpmVersion)> {
+ fn parse_name_and_version(input: &str) -> ParseResult<(String, Version)> {
let (input, name) = parse_name(input)?;
let (input, _) = ch('@')(input)?;
let at_version_input = input;
let (input, version) = parse_version(input)?;
- match NpmVersion::parse(version) {
+ match Version::parse_from_npm(version) {
Ok(version) => Ok((input, (name.to_string(), version))),
Err(err) => ParseError::fail(at_version_input, format!("{err:#}")),
}
@@ -417,30 +408,30 @@ mod tests {
fn serialize_npm_package_id() {
let id = NpmPackageId {
name: "pkg-a".to_string(),
- version: NpmVersion::parse("1.2.3").unwrap(),
+ version: Version::parse_from_npm("1.2.3").unwrap(),
peer_dependencies: vec![
NpmPackageId {
name: "pkg-b".to_string(),
- version: NpmVersion::parse("3.2.1").unwrap(),
+ version: Version::parse_from_npm("3.2.1").unwrap(),
peer_dependencies: vec![
NpmPackageId {
name: "pkg-c".to_string(),
- version: NpmVersion::parse("1.3.2").unwrap(),
+ version: Version::parse_from_npm("1.3.2").unwrap(),
peer_dependencies: vec![],
},
NpmPackageId {
name: "pkg-d".to_string(),
- version: NpmVersion::parse("2.3.4").unwrap(),
+ version: Version::parse_from_npm("2.3.4").unwrap(),
peer_dependencies: vec![],
},
],
},
NpmPackageId {
name: "pkg-e".to_string(),
- version: NpmVersion::parse("2.3.1").unwrap(),
+ version: Version::parse_from_npm("2.3.1").unwrap(),
peer_dependencies: vec![NpmPackageId {
name: "pkg-f".to_string(),
- version: NpmVersion::parse("2.3.1").unwrap(),
+ version: Version::parse_from_npm("2.3.1").unwrap(),
peer_dependencies: vec![],
}],
},
diff --git a/cli/npm/resolution/reference.rs b/cli/npm/resolution/reference.rs
new file mode 100644
index 000000000..2d34bcc34
--- /dev/null
+++ b/cli/npm/resolution/reference.rs
@@ -0,0 +1,298 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use deno_ast::ModuleSpecifier;
+use deno_core::anyhow::bail;
+use deno_core::anyhow::Context;
+use deno_core::error::generic_error;
+use deno_core::error::AnyError;
+use serde::Deserialize;
+use serde::Serialize;
+
+use crate::semver::VersionReq;
+
+/// A reference to an npm package's name, version constraint, and potential sub path.
+///
+/// This contains all the information found in an npm specifier.
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct NpmPackageReference {
+ pub req: NpmPackageReq,
+ pub sub_path: Option<String>,
+}
+
+impl NpmPackageReference {
+ pub fn from_specifier(
+ specifier: &ModuleSpecifier,
+ ) -> Result<NpmPackageReference, AnyError> {
+ Self::from_str(specifier.as_str())
+ }
+
+ pub fn from_str(specifier: &str) -> Result<NpmPackageReference, AnyError> {
+ let original_text = specifier;
+ let specifier = match specifier.strip_prefix("npm:") {
+ Some(s) => {
+ // Strip leading slash, which might come from import map
+ s.strip_prefix('/').unwrap_or(s)
+ }
+ None => {
+ // don't allocate a string here and instead use a static string
+ // because this is hit a lot when a url is not an npm specifier
+ return Err(generic_error("Not an npm specifier"));
+ }
+ };
+ let parts = specifier.split('/').collect::<Vec<_>>();
+ let name_part_len = if specifier.starts_with('@') { 2 } else { 1 };
+ if parts.len() < name_part_len {
+ return Err(generic_error(format!("Not a valid package: {specifier}")));
+ }
+ let name_parts = &parts[0..name_part_len];
+ let req = match NpmPackageReq::parse_from_parts(name_parts) {
+ Ok(pkg_req) => pkg_req,
+ Err(err) => {
+ return Err(generic_error(format!(
+ "Invalid npm specifier '{original_text}'. {err:#}"
+ )))
+ }
+ };
+ let sub_path = if parts.len() == name_parts.len() {
+ None
+ } else {
+ let sub_path = parts[name_part_len..].join("/");
+ if sub_path.is_empty() {
+ None
+ } else {
+ Some(sub_path)
+ }
+ };
+
+ if let Some(sub_path) = &sub_path {
+ if let Some(at_index) = sub_path.rfind('@') {
+ let (new_sub_path, version) = sub_path.split_at(at_index);
+ let msg = format!(
+ "Invalid package specifier 'npm:{req}/{sub_path}'. Did you mean to write 'npm:{req}{version}/{new_sub_path}'?"
+ );
+ return Err(generic_error(msg));
+ }
+ }
+
+ Ok(NpmPackageReference { req, sub_path })
+ }
+}
+
+impl std::fmt::Display for NpmPackageReference {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if let Some(sub_path) = &self.sub_path {
+ write!(f, "npm:{}/{}", self.req, sub_path)
+ } else {
+ write!(f, "npm:{}", self.req)
+ }
+ }
+}
+
+/// The name and version constraint component of an `NpmPackageReference`.
+#[derive(
+ Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize,
+)]
+pub struct NpmPackageReq {
+ pub name: String,
+ pub version_req: Option<VersionReq>,
+}
+
+impl std::fmt::Display for NpmPackageReq {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match &self.version_req {
+ Some(req) => write!(f, "{}@{}", self.name, req),
+ None => write!(f, "{}", self.name),
+ }
+ }
+}
+
+impl NpmPackageReq {
+ pub fn from_str(text: &str) -> Result<Self, AnyError> {
+ let parts = text.split('/').collect::<Vec<_>>();
+ match NpmPackageReq::parse_from_parts(&parts) {
+ Ok(req) => Ok(req),
+ Err(err) => {
+ let msg = format!("Invalid npm package requirement '{text}'. {err:#}");
+ Err(generic_error(msg))
+ }
+ }
+ }
+
+ fn parse_from_parts(name_parts: &[&str]) -> Result<Self, AnyError> {
+ assert!(!name_parts.is_empty()); // this should be provided the result of a string split
+ let last_name_part = &name_parts[name_parts.len() - 1];
+ let (name, version_req) = if let Some(at_index) = last_name_part.rfind('@')
+ {
+ let version = &last_name_part[at_index + 1..];
+ let last_name_part = &last_name_part[..at_index];
+ let version_req = VersionReq::parse_from_specifier(version)
+ .with_context(|| "Invalid version requirement.")?;
+ let name = if name_parts.len() == 1 {
+ last_name_part.to_string()
+ } else {
+ format!("{}/{}", name_parts[0], last_name_part)
+ };
+ (name, Some(version_req))
+ } else {
+ (name_parts.join("/"), None)
+ };
+ if name.is_empty() {
+ bail!("Did not contain a package name.")
+ }
+ Ok(Self { name, version_req })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use pretty_assertions::assert_eq;
+
+ use super::*;
+
+ #[test]
+ fn parse_npm_package_ref() {
+ assert_eq!(
+ NpmPackageReference::from_str("npm:@package/test").unwrap(),
+ NpmPackageReference {
+ req: NpmPackageReq {
+ name: "@package/test".to_string(),
+ version_req: None,
+ },
+ sub_path: None,
+ }
+ );
+
+ assert_eq!(
+ NpmPackageReference::from_str("npm:@package/test@1").unwrap(),
+ NpmPackageReference {
+ req: NpmPackageReq {
+ name: "@package/test".to_string(),
+ version_req: Some(VersionReq::parse_from_specifier("1").unwrap()),
+ },
+ sub_path: None,
+ }
+ );
+
+ assert_eq!(
+ NpmPackageReference::from_str("npm:@package/test@~1.1/sub_path").unwrap(),
+ NpmPackageReference {
+ req: NpmPackageReq {
+ name: "@package/test".to_string(),
+ version_req: Some(VersionReq::parse_from_specifier("~1.1").unwrap()),
+ },
+ sub_path: Some("sub_path".to_string()),
+ }
+ );
+
+ assert_eq!(
+ NpmPackageReference::from_str("npm:@package/test/sub_path").unwrap(),
+ NpmPackageReference {
+ req: NpmPackageReq {
+ name: "@package/test".to_string(),
+ version_req: None,
+ },
+ sub_path: Some("sub_path".to_string()),
+ }
+ );
+
+ assert_eq!(
+ NpmPackageReference::from_str("npm:test").unwrap(),
+ NpmPackageReference {
+ req: NpmPackageReq {
+ name: "test".to_string(),
+ version_req: None,
+ },
+ sub_path: None,
+ }
+ );
+
+ assert_eq!(
+ NpmPackageReference::from_str("npm:test@^1.2").unwrap(),
+ NpmPackageReference {
+ req: NpmPackageReq {
+ name: "test".to_string(),
+ version_req: Some(VersionReq::parse_from_specifier("^1.2").unwrap()),
+ },
+ sub_path: None,
+ }
+ );
+
+ assert_eq!(
+ NpmPackageReference::from_str("npm:test@~1.1/sub_path").unwrap(),
+ NpmPackageReference {
+ req: NpmPackageReq {
+ name: "test".to_string(),
+ version_req: Some(VersionReq::parse_from_specifier("~1.1").unwrap()),
+ },
+ sub_path: Some("sub_path".to_string()),
+ }
+ );
+
+ assert_eq!(
+ NpmPackageReference::from_str("npm:@package/test/sub_path").unwrap(),
+ NpmPackageReference {
+ req: NpmPackageReq {
+ name: "@package/test".to_string(),
+ version_req: None,
+ },
+ sub_path: Some("sub_path".to_string()),
+ }
+ );
+
+ assert_eq!(
+ NpmPackageReference::from_str("npm:@package")
+ .err()
+ .unwrap()
+ .to_string(),
+ "Not a valid package: @package"
+ );
+
+ // should parse leading slash
+ assert_eq!(
+ NpmPackageReference::from_str("npm:/@package/test/sub_path").unwrap(),
+ NpmPackageReference {
+ req: NpmPackageReq {
+ name: "@package/test".to_string(),
+ version_req: None,
+ },
+ sub_path: Some("sub_path".to_string()),
+ }
+ );
+ assert_eq!(
+ NpmPackageReference::from_str("npm:/test").unwrap(),
+ NpmPackageReference {
+ req: NpmPackageReq {
+ name: "test".to_string(),
+ version_req: None,
+ },
+ sub_path: None,
+ }
+ );
+ assert_eq!(
+ NpmPackageReference::from_str("npm:/test/").unwrap(),
+ NpmPackageReference {
+ req: NpmPackageReq {
+ name: "test".to_string(),
+ version_req: None,
+ },
+ sub_path: None,
+ }
+ );
+
+ // should error for no name
+ assert_eq!(
+ NpmPackageReference::from_str("npm:/")
+ .err()
+ .unwrap()
+ .to_string(),
+ "Invalid npm specifier 'npm:/'. Did not contain a package name."
+ );
+ assert_eq!(
+ NpmPackageReference::from_str("npm://test")
+ .err()
+ .unwrap()
+ .to_string(),
+ "Invalid npm specifier 'npm://test'. Did not contain a package name."
+ );
+ }
+}
diff --git a/cli/npm/resolution/snapshot.rs b/cli/npm/resolution/snapshot.rs
index be64ea611..934320a1d 100644
--- a/cli/npm/resolution/snapshot.rs
+++ b/cli/npm/resolution/snapshot.rs
@@ -19,11 +19,11 @@ use crate::npm::cache::NpmPackageCacheFolderId;
use crate::npm::registry::NpmPackageVersionDistInfo;
use crate::npm::registry::NpmRegistryApi;
use crate::npm::registry::RealNpmRegistryApi;
+use crate::semver::VersionReq;
use super::NpmPackageId;
use super::NpmPackageReq;
use super::NpmResolutionPackage;
-use super::NpmVersionMatcher;
/// Packages partitioned by if they are "copy" packages or not.
pub struct NpmPackagesPartitioned {
@@ -159,12 +159,8 @@ impl NpmResolutionSnapshot {
// TODO(bartlomieju): this should use a reverse lookup table in the
// snapshot instead of resolving best version again.
- let req = NpmPackageReq {
- name: name.to_string(),
- version_req: None,
- };
-
- if let Some(id) = self.resolve_best_package_id(name, &req) {
+ let any_version_req = VersionReq::parse_from_npm("*").unwrap();
+ if let Some(id) = self.resolve_best_package_id(name, &any_version_req) {
if let Some(pkg) = self.packages.get(&id) {
return Ok(pkg);
}
@@ -201,14 +197,14 @@ impl NpmResolutionSnapshot {
pub fn resolve_best_package_id(
&self,
name: &str,
- version_matcher: &impl NpmVersionMatcher,
+ version_req: &VersionReq,
) -> Option<NpmPackageId> {
// todo(dsherret): this is not exactly correct because some ids
// will be better than others due to peer dependencies
let mut maybe_best_id: Option<&NpmPackageId> = None;
if let Some(ids) = self.packages_by_name.get(name) {
for id in ids {
- if version_matcher.matches(&id.version) {
+ if version_req.matches(&id.version) {
let is_best_version = maybe_best_id
.as_ref()
.map(|best_id| best_id.version.cmp(&id.version).is_lt())
diff --git a/cli/npm/resolution/specifier.rs b/cli/npm/resolution/specifier.rs
index 0aa693472..78d313412 100644
--- a/cli/npm/resolution/specifier.rs
+++ b/cli/npm/resolution/specifier.rs
@@ -6,165 +6,13 @@ use std::collections::HashSet;
use std::collections::VecDeque;
use deno_ast::ModuleSpecifier;
-use deno_core::anyhow::Context;
-use deno_core::error::generic_error;
-use deno_core::error::AnyError;
use deno_graph::ModuleGraph;
use deno_graph::Resolved;
-use serde::Deserialize;
-use serde::Serialize;
-use super::super::semver::NpmVersion;
-use super::super::semver::SpecifierVersionReq;
-use super::NpmVersionMatcher;
+use crate::semver::VersionReq;
-#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub struct NpmPackageReference {
- pub req: NpmPackageReq,
- pub sub_path: Option<String>,
-}
-
-impl NpmPackageReference {
- pub fn from_specifier(
- specifier: &ModuleSpecifier,
- ) -> Result<NpmPackageReference, AnyError> {
- Self::from_str(specifier.as_str())
- }
-
- pub fn from_str(specifier: &str) -> Result<NpmPackageReference, AnyError> {
- let original_text = specifier;
- let specifier = match specifier.strip_prefix("npm:") {
- Some(s) => {
- // Strip leading slash, which might come from import map
- s.strip_prefix('/').unwrap_or(s)
- }
- None => {
- // don't allocate a string here and instead use a static string
- // because this is hit a lot when a url is not an npm specifier
- return Err(generic_error("Not an npm specifier"));
- }
- };
- let parts = specifier.split('/').collect::<Vec<_>>();
- let name_part_len = if specifier.starts_with('@') { 2 } else { 1 };
- if parts.len() < name_part_len {
- return Err(generic_error(format!("Not a valid package: {specifier}")));
- }
- let name_parts = &parts[0..name_part_len];
- let last_name_part = &name_parts[name_part_len - 1];
- let (name, version_req) = if let Some(at_index) = last_name_part.rfind('@')
- {
- let version = &last_name_part[at_index + 1..];
- let last_name_part = &last_name_part[..at_index];
- let version_req = SpecifierVersionReq::parse(version)
- .with_context(|| "Invalid version requirement.")?;
- let name = if name_part_len == 1 {
- last_name_part.to_string()
- } else {
- format!("{}/{}", name_parts[0], last_name_part)
- };
- (name, Some(version_req))
- } else {
- (name_parts.join("/"), None)
- };
- let sub_path = if parts.len() == name_parts.len() {
- None
- } else {
- let sub_path = parts[name_part_len..].join("/");
- if sub_path.is_empty() {
- None
- } else {
- Some(sub_path)
- }
- };
-
- if let Some(sub_path) = &sub_path {
- if let Some(at_index) = sub_path.rfind('@') {
- let (new_sub_path, version) = sub_path.split_at(at_index);
- let msg = format!(
- "Invalid package specifier 'npm:{name}/{sub_path}'. Did you mean to write 'npm:{name}{version}/{new_sub_path}'?"
- );
- return Err(generic_error(msg));
- }
- }
-
- if name.is_empty() {
- let msg = format!(
- "Invalid npm specifier '{original_text}'. Did not contain a package name."
- );
- return Err(generic_error(msg));
- }
-
- Ok(NpmPackageReference {
- req: NpmPackageReq { name, version_req },
- sub_path,
- })
- }
-}
-
-impl std::fmt::Display for NpmPackageReference {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- if let Some(sub_path) = &self.sub_path {
- write!(f, "npm:{}/{}", self.req, sub_path)
- } else {
- write!(f, "npm:{}", self.req)
- }
- }
-}
-
-#[derive(
- Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize,
-)]
-pub struct NpmPackageReq {
- pub name: String,
- pub version_req: Option<SpecifierVersionReq>,
-}
-
-impl std::fmt::Display for NpmPackageReq {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match &self.version_req {
- Some(req) => write!(f, "{}@{}", self.name, req),
- None => write!(f, "{}", self.name),
- }
- }
-}
-
-impl NpmPackageReq {
- pub fn from_str(text: &str) -> Result<Self, AnyError> {
- // probably should do something more targeted in the future
- let reference = NpmPackageReference::from_str(&format!("npm:{text}"))?;
- Ok(reference.req)
- }
-}
-
-impl NpmVersionMatcher for NpmPackageReq {
- fn tag(&self) -> Option<&str> {
- match &self.version_req {
- Some(version_req) => version_req.tag(),
- None => Some("latest"),
- }
- }
-
- fn matches(&self, version: &NpmVersion) -> bool {
- match self.version_req.as_ref() {
- Some(req) => {
- assert_eq!(self.tag(), None);
- match req.range() {
- Some(range) => range.satisfies(version),
- None => false,
- }
- }
- None => version.pre.is_empty(),
- }
- }
-
- fn version_text(&self) -> String {
- self
- .version_req
- .as_ref()
- .map(|v| format!("{v}"))
- .unwrap_or_else(|| "non-prerelease".to_string())
- }
-}
+use super::NpmPackageReference;
+use super::NpmPackageReq;
pub struct GraphNpmInfo {
/// The order of these package requirements is the order they
@@ -537,10 +385,7 @@ fn cmp_folder_specifiers(a: &ModuleSpecifier, b: &ModuleSpecifier) -> Ordering {
// duplicate packages (so sort None last since it's `*`), but
// mostly to create some determinism around how these are resolved.
fn cmp_package_req(a: &NpmPackageReq, b: &NpmPackageReq) -> Ordering {
- fn cmp_specifier_version_req(
- a: &SpecifierVersionReq,
- b: &SpecifierVersionReq,
- ) -> Ordering {
+ fn cmp_specifier_version_req(a: &VersionReq, b: &VersionReq) -> Ordering {
match a.tag() {
Some(a_tag) => match b.tag() {
Some(b_tag) => b_tag.cmp(a_tag), // sort descending
@@ -582,153 +427,6 @@ mod tests {
use super::*;
#[test]
- fn parse_npm_package_ref() {
- assert_eq!(
- NpmPackageReference::from_str("npm:@package/test").unwrap(),
- NpmPackageReference {
- req: NpmPackageReq {
- name: "@package/test".to_string(),
- version_req: None,
- },
- sub_path: None,
- }
- );
-
- assert_eq!(
- NpmPackageReference::from_str("npm:@package/test@1").unwrap(),
- NpmPackageReference {
- req: NpmPackageReq {
- name: "@package/test".to_string(),
- version_req: Some(SpecifierVersionReq::parse("1").unwrap()),
- },
- sub_path: None,
- }
- );
-
- assert_eq!(
- NpmPackageReference::from_str("npm:@package/test@~1.1/sub_path").unwrap(),
- NpmPackageReference {
- req: NpmPackageReq {
- name: "@package/test".to_string(),
- version_req: Some(SpecifierVersionReq::parse("~1.1").unwrap()),
- },
- sub_path: Some("sub_path".to_string()),
- }
- );
-
- assert_eq!(
- NpmPackageReference::from_str("npm:@package/test/sub_path").unwrap(),
- NpmPackageReference {
- req: NpmPackageReq {
- name: "@package/test".to_string(),
- version_req: None,
- },
- sub_path: Some("sub_path".to_string()),
- }
- );
-
- assert_eq!(
- NpmPackageReference::from_str("npm:test").unwrap(),
- NpmPackageReference {
- req: NpmPackageReq {
- name: "test".to_string(),
- version_req: None,
- },
- sub_path: None,
- }
- );
-
- assert_eq!(
- NpmPackageReference::from_str("npm:test@^1.2").unwrap(),
- NpmPackageReference {
- req: NpmPackageReq {
- name: "test".to_string(),
- version_req: Some(SpecifierVersionReq::parse("^1.2").unwrap()),
- },
- sub_path: None,
- }
- );
-
- assert_eq!(
- NpmPackageReference::from_str("npm:test@~1.1/sub_path").unwrap(),
- NpmPackageReference {
- req: NpmPackageReq {
- name: "test".to_string(),
- version_req: Some(SpecifierVersionReq::parse("~1.1").unwrap()),
- },
- sub_path: Some("sub_path".to_string()),
- }
- );
-
- assert_eq!(
- NpmPackageReference::from_str("npm:@package/test/sub_path").unwrap(),
- NpmPackageReference {
- req: NpmPackageReq {
- name: "@package/test".to_string(),
- version_req: None,
- },
- sub_path: Some("sub_path".to_string()),
- }
- );
-
- assert_eq!(
- NpmPackageReference::from_str("npm:@package")
- .err()
- .unwrap()
- .to_string(),
- "Not a valid package: @package"
- );
-
- // should parse leading slash
- assert_eq!(
- NpmPackageReference::from_str("npm:/@package/test/sub_path").unwrap(),
- NpmPackageReference {
- req: NpmPackageReq {
- name: "@package/test".to_string(),
- version_req: None,
- },
- sub_path: Some("sub_path".to_string()),
- }
- );
- assert_eq!(
- NpmPackageReference::from_str("npm:/test").unwrap(),
- NpmPackageReference {
- req: NpmPackageReq {
- name: "test".to_string(),
- version_req: None,
- },
- sub_path: None,
- }
- );
- assert_eq!(
- NpmPackageReference::from_str("npm:/test/").unwrap(),
- NpmPackageReference {
- req: NpmPackageReq {
- name: "test".to_string(),
- version_req: None,
- },
- sub_path: None,
- }
- );
-
- // should error for no name
- assert_eq!(
- NpmPackageReference::from_str("npm:/")
- .err()
- .unwrap()
- .to_string(),
- "Invalid npm specifier 'npm:/'. Did not contain a package name."
- );
- assert_eq!(
- NpmPackageReference::from_str("npm://test")
- .err()
- .unwrap()
- .to_string(),
- "Invalid npm specifier 'npm://test'. Did not contain a package name."
- );
- }
-
- #[test]
fn sorting_folder_specifiers() {
fn cmp(a: &str, b: &str) -> Ordering {
let a = ModuleSpecifier::parse(a).unwrap();
diff --git a/cli/npm/semver/mod.rs b/cli/npm/semver/mod.rs
deleted file mode 100644
index b532835e6..000000000
--- a/cli/npm/semver/mod.rs
+++ /dev/null
@@ -1,1152 +0,0 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-
-use std::cmp::Ordering;
-use std::fmt;
-
-use deno_core::anyhow::Context;
-use deno_core::error::AnyError;
-use monch::*;
-use serde::Deserialize;
-use serde::Serialize;
-
-use crate::npm::resolution::NpmVersionMatcher;
-
-use self::range::Partial;
-use self::range::VersionBoundKind;
-use self::range::VersionRange;
-use self::range::VersionRangeSet;
-
-use self::range::XRange;
-pub use self::specifier::SpecifierVersionReq;
-
-mod range;
-mod specifier;
-
-// A lot of the below is a re-implementation of parts of https://github.com/npm/node-semver
-// which is Copyright (c) Isaac Z. Schlueter and Contributors (ISC License)
-
-pub fn is_valid_npm_tag(value: &str) -> bool {
- // a valid tag is anything that doesn't get url encoded
- // https://github.com/npm/npm-package-arg/blob/103c0fda8ed8185733919c7c6c73937cfb2baf3a/lib/npa.js#L399-L401
- value
- .chars()
- .all(|c| c.is_alphanumeric() || matches!(c, '-' | '_' | '.' | '~'))
-}
-
-#[derive(
- Clone, Debug, PartialEq, Eq, Default, Hash, Serialize, Deserialize,
-)]
-pub struct NpmVersion {
- pub major: u64,
- pub minor: u64,
- pub patch: u64,
- pub pre: Vec<String>,
- pub build: Vec<String>,
-}
-
-impl fmt::Display for NpmVersion {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
- if !self.pre.is_empty() {
- write!(f, "-")?;
- for (i, part) in self.pre.iter().enumerate() {
- if i > 0 {
- write!(f, ".")?;
- }
- write!(f, "{part}")?;
- }
- }
- if !self.build.is_empty() {
- write!(f, "+")?;
- for (i, part) in self.build.iter().enumerate() {
- if i > 0 {
- write!(f, ".")?;
- }
- write!(f, "{part}")?;
- }
- }
- Ok(())
- }
-}
-
-impl std::cmp::PartialOrd for NpmVersion {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl std::cmp::Ord for NpmVersion {
- fn cmp(&self, other: &Self) -> Ordering {
- let cmp_result = self.major.cmp(&other.major);
- if cmp_result != Ordering::Equal {
- return cmp_result;
- }
-
- let cmp_result = self.minor.cmp(&other.minor);
- if cmp_result != Ordering::Equal {
- return cmp_result;
- }
-
- let cmp_result = self.patch.cmp(&other.patch);
- if cmp_result != Ordering::Equal {
- return cmp_result;
- }
-
- // only compare the pre-release and not the build as node-semver does
- if self.pre.is_empty() && other.pre.is_empty() {
- Ordering::Equal
- } else if !self.pre.is_empty() && other.pre.is_empty() {
- Ordering::Less
- } else if self.pre.is_empty() && !other.pre.is_empty() {
- Ordering::Greater
- } else {
- let mut i = 0;
- loop {
- let a = self.pre.get(i);
- let b = other.pre.get(i);
- if a.is_none() && b.is_none() {
- return Ordering::Equal;
- }
-
- // https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/internal/identifiers.js
- let a = match a {
- Some(a) => a,
- None => return Ordering::Less,
- };
- let b = match b {
- Some(b) => b,
- None => return Ordering::Greater,
- };
-
- // prefer numbers
- if let Ok(a_num) = a.parse::<u64>() {
- if let Ok(b_num) = b.parse::<u64>() {
- return a_num.cmp(&b_num);
- } else {
- return Ordering::Less;
- }
- } else if b.parse::<u64>().is_ok() {
- return Ordering::Greater;
- }
-
- let cmp_result = a.cmp(b);
- if cmp_result != Ordering::Equal {
- return cmp_result;
- }
- i += 1;
- }
- }
- }
-}
-
-impl NpmVersion {
- pub fn parse(text: &str) -> Result<Self, AnyError> {
- let text = text.trim();
- with_failure_handling(parse_npm_version)(text)
- .with_context(|| format!("Invalid npm version '{text}'."))
- }
-}
-
-fn parse_npm_version(input: &str) -> ParseResult<NpmVersion> {
- let (input, _) = maybe(ch('='))(input)?; // skip leading =
- let (input, _) = skip_whitespace(input)?;
- let (input, _) = maybe(ch('v'))(input)?; // skip leading v
- let (input, _) = skip_whitespace(input)?;
- let (input, major) = nr(input)?;
- let (input, _) = ch('.')(input)?;
- let (input, minor) = nr(input)?;
- let (input, _) = ch('.')(input)?;
- let (input, patch) = nr(input)?;
- let (input, q) = maybe(qualifier)(input)?;
- let q = q.unwrap_or_default();
-
- Ok((
- input,
- NpmVersion {
- major,
- minor,
- patch,
- pre: q.pre,
- build: q.build,
- },
- ))
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
-enum NpmVersionReqInner {
- RangeSet(VersionRangeSet),
- Tag(String),
-}
-
-/// A version requirement found in an npm package's dependencies.
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct NpmVersionReq {
- raw_text: String,
- inner: NpmVersionReqInner,
-}
-
-impl NpmVersionMatcher for NpmVersionReq {
- fn tag(&self) -> Option<&str> {
- match &self.inner {
- NpmVersionReqInner::RangeSet(_) => None,
- NpmVersionReqInner::Tag(tag) => Some(tag.as_str()),
- }
- }
-
- fn matches(&self, version: &NpmVersion) -> bool {
- match &self.inner {
- NpmVersionReqInner::RangeSet(range_set) => range_set.satisfies(version),
- NpmVersionReqInner::Tag(_) => panic!(
- "programming error: cannot use matches with a tag: {}",
- self.raw_text
- ),
- }
- }
-
- fn version_text(&self) -> String {
- self.raw_text.clone()
- }
-}
-
-impl fmt::Display for NpmVersionReq {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}", &self.raw_text)
- }
-}
-
-impl NpmVersionReq {
- pub fn parse(text: &str) -> Result<Self, AnyError> {
- let text = text.trim();
- with_failure_handling(parse_npm_version_req)(text)
- .with_context(|| format!("Invalid npm version requirement '{text}'."))
- }
-}
-
-fn parse_npm_version_req(input: &str) -> ParseResult<NpmVersionReq> {
- map(inner, |inner| NpmVersionReq {
- raw_text: input.to_string(),
- inner,
- })(input)
-}
-
-// https://github.com/npm/node-semver/tree/4907647d169948a53156502867ed679268063a9f#range-grammar
-// range-set ::= range ( logical-or range ) *
-// logical-or ::= ( ' ' ) * '||' ( ' ' ) *
-// range ::= hyphen | simple ( ' ' simple ) * | ''
-// hyphen ::= partial ' - ' partial
-// simple ::= primitive | partial | tilde | caret
-// primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial
-// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
-// xr ::= 'x' | 'X' | '*' | nr
-// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
-// tilde ::= '~' partial
-// caret ::= '^' partial
-// qualifier ::= ( '-' pre )? ( '+' build )?
-// pre ::= parts
-// build ::= parts
-// parts ::= part ( '.' part ) *
-// part ::= nr | [-0-9A-Za-z]+
-
-// range-set ::= range ( logical-or range ) *
-fn inner(input: &str) -> ParseResult<NpmVersionReqInner> {
- if input.is_empty() {
- return Ok((
- input,
- NpmVersionReqInner::RangeSet(VersionRangeSet(vec![VersionRange::all()])),
- ));
- }
-
- let (input, mut ranges) =
- separated_list(range_or_invalid, logical_or)(input)?;
-
- if ranges.len() == 1 {
- match ranges.remove(0) {
- RangeOrInvalid::Invalid(invalid) => {
- if is_valid_npm_tag(invalid.text) {
- return Ok((
- input,
- NpmVersionReqInner::Tag(invalid.text.to_string()),
- ));
- } else {
- return Err(invalid.failure);
- }
- }
- RangeOrInvalid::Range(range) => {
- // add it back
- ranges.push(RangeOrInvalid::Range(range));
- }
- }
- }
-
- let ranges = ranges
- .into_iter()
- .filter_map(|r| r.into_range())
- .collect::<Vec<_>>();
- Ok((input, NpmVersionReqInner::RangeSet(VersionRangeSet(ranges))))
-}
-
-enum RangeOrInvalid<'a> {
- Range(VersionRange),
- Invalid(InvalidRange<'a>),
-}
-
-impl<'a> RangeOrInvalid<'a> {
- pub fn into_range(self) -> Option<VersionRange> {
- match self {
- RangeOrInvalid::Range(r) => {
- if r.is_none() {
- None
- } else {
- Some(r)
- }
- }
- RangeOrInvalid::Invalid(_) => None,
- }
- }
-}
-
-struct InvalidRange<'a> {
- failure: ParseError<'a>,
- text: &'a str,
-}
-
-fn range_or_invalid(input: &str) -> ParseResult<RangeOrInvalid> {
- let range_result =
- map_res(map(range, RangeOrInvalid::Range), |result| match result {
- Ok((input, range)) => {
- let is_end = input.is_empty() || logical_or(input).is_ok();
- if is_end {
- Ok((input, range))
- } else {
- ParseError::backtrace()
- }
- }
- Err(err) => Err(err),
- })(input);
- match range_result {
- Ok(result) => Ok(result),
- Err(failure) => {
- let (input, text) = invalid_range(input)?;
- Ok((
- input,
- RangeOrInvalid::Invalid(InvalidRange { failure, text }),
- ))
- }
- }
-}
-
-fn invalid_range(input: &str) -> ParseResult<&str> {
- let end_index = input.find("||").unwrap_or(input.len());
- let text = input[..end_index].trim();
- Ok((&input[end_index..], text))
-}
-
-// range ::= hyphen | simple ( ' ' simple ) * | ''
-fn range(input: &str) -> ParseResult<VersionRange> {
- or(
- map(hyphen, |hyphen| VersionRange {
- start: hyphen.start.as_lower_bound(),
- end: hyphen.end.as_upper_bound(),
- }),
- map(separated_list(simple, whitespace), |ranges| {
- let mut final_range = VersionRange::all();
- for range in ranges {
- final_range = final_range.clamp(&range);
- }
- final_range
- }),
- )(input)
-}
-
-#[derive(Debug, Clone)]
-struct Hyphen {
- start: Partial,
- end: Partial,
-}
-
-// hyphen ::= partial ' - ' partial
-fn hyphen(input: &str) -> ParseResult<Hyphen> {
- let (input, first) = partial(input)?;
- let (input, _) = whitespace(input)?;
- let (input, _) = tag("-")(input)?;
- let (input, _) = whitespace(input)?;
- let (input, second) = partial(input)?;
- Ok((
- input,
- Hyphen {
- start: first,
- end: second,
- },
- ))
-}
-
-// logical-or ::= ( ' ' ) * '||' ( ' ' ) *
-fn logical_or(input: &str) -> ParseResult<&str> {
- delimited(skip_whitespace, tag("||"), skip_whitespace)(input)
-}
-
-fn skip_whitespace_or_v(input: &str) -> ParseResult<()> {
- map(
- pair(skip_whitespace, pair(maybe(ch('v')), skip_whitespace)),
- |_| (),
- )(input)
-}
-
-// simple ::= primitive | partial | tilde | caret
-fn simple(input: &str) -> ParseResult<VersionRange> {
- or4(
- map(preceded(tilde, partial), |partial| {
- partial.as_tilde_version_range()
- }),
- map(preceded(caret, partial), |partial| {
- partial.as_caret_version_range()
- }),
- map(primitive, |primitive| {
- let partial = primitive.partial;
- match primitive.kind {
- PrimitiveKind::Equal => partial.as_equal_range(),
- PrimitiveKind::GreaterThan => {
- partial.as_greater_than(VersionBoundKind::Exclusive)
- }
- PrimitiveKind::GreaterThanOrEqual => {
- partial.as_greater_than(VersionBoundKind::Inclusive)
- }
- PrimitiveKind::LessThan => {
- partial.as_less_than(VersionBoundKind::Exclusive)
- }
- PrimitiveKind::LessThanOrEqual => {
- partial.as_less_than(VersionBoundKind::Inclusive)
- }
- }
- }),
- map(partial, |partial| partial.as_equal_range()),
- )(input)
-}
-
-fn tilde(input: &str) -> ParseResult<()> {
- fn raw_tilde(input: &str) -> ParseResult<()> {
- map(pair(or(tag("~>"), tag("~")), skip_whitespace_or_v), |_| ())(input)
- }
-
- or(
- preceded(terminated(primitive_kind, whitespace), raw_tilde),
- raw_tilde,
- )(input)
-}
-
-fn caret(input: &str) -> ParseResult<()> {
- fn raw_caret(input: &str) -> ParseResult<()> {
- map(pair(ch('^'), skip_whitespace_or_v), |_| ())(input)
- }
-
- or(
- preceded(terminated(primitive_kind, whitespace), raw_caret),
- raw_caret,
- )(input)
-}
-
-#[derive(Debug, Clone, Copy)]
-enum PrimitiveKind {
- GreaterThan,
- LessThan,
- GreaterThanOrEqual,
- LessThanOrEqual,
- Equal,
-}
-
-#[derive(Debug, Clone)]
-struct Primitive {
- kind: PrimitiveKind,
- partial: Partial,
-}
-
-fn primitive(input: &str) -> ParseResult<Primitive> {
- let (input, kind) = primitive_kind(input)?;
- let (input, _) = skip_whitespace(input)?;
- let (input, partial) = partial(input)?;
- Ok((input, Primitive { kind, partial }))
-}
-
-fn primitive_kind(input: &str) -> ParseResult<PrimitiveKind> {
- or5(
- map(tag(">="), |_| PrimitiveKind::GreaterThanOrEqual),
- map(tag("<="), |_| PrimitiveKind::LessThanOrEqual),
- map(ch('<'), |_| PrimitiveKind::LessThan),
- map(ch('>'), |_| PrimitiveKind::GreaterThan),
- map(ch('='), |_| PrimitiveKind::Equal),
- )(input)
-}
-
-// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
-fn partial(input: &str) -> ParseResult<Partial> {
- let (input, _) = maybe(ch('v'))(input)?; // skip leading v
- let (input, major) = xr()(input)?;
- let (input, maybe_minor) = maybe(preceded(ch('.'), xr()))(input)?;
- let (input, maybe_patch) = if maybe_minor.is_some() {
- maybe(preceded(ch('.'), xr()))(input)?
- } else {
- (input, None)
- };
- let (input, qual) = if maybe_patch.is_some() {
- maybe(qualifier)(input)?
- } else {
- (input, None)
- };
- let qual = qual.unwrap_or_default();
- Ok((
- input,
- Partial {
- major,
- minor: maybe_minor.unwrap_or(XRange::Wildcard),
- patch: maybe_patch.unwrap_or(XRange::Wildcard),
- pre: qual.pre,
- build: qual.build,
- },
- ))
-}
-
-// xr ::= 'x' | 'X' | '*' | nr
-fn xr<'a>() -> impl Fn(&'a str) -> ParseResult<'a, XRange> {
- or(
- map(or3(tag("x"), tag("X"), tag("*")), |_| XRange::Wildcard),
- map(nr, XRange::Val),
- )
-}
-
-// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
-fn nr(input: &str) -> ParseResult<u64> {
- // we do loose parsing to support people doing stuff like 01.02.03
- let (input, result) =
- if_not_empty(substring(skip_while(|c| c.is_ascii_digit())))(input)?;
- let val = match result.parse::<u64>() {
- Ok(val) => val,
- Err(err) => {
- return ParseError::fail(
- input,
- format!("Error parsing '{result}' to u64.\n\n{err:#}"),
- )
- }
- };
- Ok((input, val))
-}
-
-#[derive(Debug, Clone, Default)]
-struct Qualifier {
- pre: Vec<String>,
- build: Vec<String>,
-}
-
-// qualifier ::= ( '-' pre )? ( '+' build )?
-fn qualifier(input: &str) -> ParseResult<Qualifier> {
- let (input, pre_parts) = maybe(pre)(input)?;
- let (input, build_parts) = maybe(build)(input)?;
- Ok((
- input,
- Qualifier {
- pre: pre_parts.unwrap_or_default(),
- build: build_parts.unwrap_or_default(),
- },
- ))
-}
-
-// pre ::= parts
-fn pre(input: &str) -> ParseResult<Vec<String>> {
- preceded(maybe(ch('-')), parts)(input)
-}
-
-// build ::= parts
-fn build(input: &str) -> ParseResult<Vec<String>> {
- preceded(ch('+'), parts)(input)
-}
-
-// parts ::= part ( '.' part ) *
-fn parts(input: &str) -> ParseResult<Vec<String>> {
- if_not_empty(map(separated_list(part, ch('.')), |text| {
- text.into_iter().map(ToOwned::to_owned).collect()
- }))(input)
-}
-
-// part ::= nr | [-0-9A-Za-z]+
-fn part(input: &str) -> ParseResult<&str> {
- // nr is in the other set, so don't bother checking for it
- if_true(
- take_while(|c| c.is_ascii_alphanumeric() || c == '-'),
- |result| !result.is_empty(),
- )(input)
-}
-
-#[cfg(test)]
-mod tests {
- use pretty_assertions::assert_eq;
- use std::cmp::Ordering;
-
- use super::*;
-
- struct NpmVersionReqTester(NpmVersionReq);
-
- impl NpmVersionReqTester {
- fn new(text: &str) -> Self {
- Self(NpmVersionReq::parse(text).unwrap())
- }
-
- fn matches(&self, version: &str) -> bool {
- self.0.matches(&NpmVersion::parse(version).unwrap())
- }
- }
-
- #[test]
- pub fn npm_version_req_with_v() {
- assert!(NpmVersionReq::parse("v1.0.0").is_ok());
- }
-
- #[test]
- pub fn npm_version_req_exact() {
- let tester = NpmVersionReqTester::new("2.1.2");
- assert!(!tester.matches("2.1.1"));
- assert!(tester.matches("2.1.2"));
- assert!(!tester.matches("2.1.3"));
-
- let tester = NpmVersionReqTester::new("2.1.2 || 2.1.5");
- assert!(!tester.matches("2.1.1"));
- assert!(tester.matches("2.1.2"));
- assert!(!tester.matches("2.1.3"));
- assert!(!tester.matches("2.1.4"));
- assert!(tester.matches("2.1.5"));
- assert!(!tester.matches("2.1.6"));
- }
-
- #[test]
- pub fn npm_version_req_minor() {
- let tester = NpmVersionReqTester::new("1.1");
- assert!(!tester.matches("1.0.0"));
- assert!(tester.matches("1.1.0"));
- assert!(tester.matches("1.1.1"));
- assert!(!tester.matches("1.2.0"));
- assert!(!tester.matches("1.2.1"));
- }
-
- #[test]
- pub fn npm_version_req_ranges() {
- let tester = NpmVersionReqTester::new(
- ">= 2.1.2 < 3.0.0 || 5.x || ignored-invalid-range || $#$%^#$^#$^%@#$%SDF|||",
- );
- assert!(!tester.matches("2.1.1"));
- assert!(tester.matches("2.1.2"));
- assert!(tester.matches("2.9.9"));
- assert!(!tester.matches("3.0.0"));
- assert!(tester.matches("5.0.0"));
- assert!(tester.matches("5.1.0"));
- assert!(!tester.matches("6.1.0"));
- }
-
- #[test]
- pub fn npm_version_req_with_tag() {
- let req = NpmVersionReq::parse("latest").unwrap();
- assert_eq!(req.inner, NpmVersionReqInner::Tag("latest".to_string()));
- }
-
- macro_rules! assert_cmp {
- ($a:expr, $b:expr, $expected:expr) => {
- assert_eq!(
- $a.cmp(&$b),
- $expected,
- "expected {} to be {:?} {}",
- $a,
- $expected,
- $b
- );
- };
- }
-
- macro_rules! test_compare {
- ($a:expr, $b:expr, $expected:expr) => {
- let a = NpmVersion::parse($a).unwrap();
- let b = NpmVersion::parse($b).unwrap();
- assert_cmp!(a, b, $expected);
- };
- }
-
- #[test]
- fn version_compare() {
- test_compare!("1.2.3", "2.3.4", Ordering::Less);
- test_compare!("1.2.3", "1.2.4", Ordering::Less);
- test_compare!("1.2.3", "1.2.3", Ordering::Equal);
- test_compare!("1.2.3", "1.2.2", Ordering::Greater);
- test_compare!("1.2.3", "1.1.5", Ordering::Greater);
- }
-
- #[test]
- fn version_compare_equal() {
- // https://github.com/npm/node-semver/blob/bce42589d33e1a99454530a8fd52c7178e2b11c1/test/fixtures/equality.js
- let fixtures = &[
- ("1.2.3", "v1.2.3"),
- ("1.2.3", "=1.2.3"),
- ("1.2.3", "v 1.2.3"),
- ("1.2.3", "= 1.2.3"),
- ("1.2.3", " v1.2.3"),
- ("1.2.3", " =1.2.3"),
- ("1.2.3", " v 1.2.3"),
- ("1.2.3", " = 1.2.3"),
- ("1.2.3-0", "v1.2.3-0"),
- ("1.2.3-0", "=1.2.3-0"),
- ("1.2.3-0", "v 1.2.3-0"),
- ("1.2.3-0", "= 1.2.3-0"),
- ("1.2.3-0", " v1.2.3-0"),
- ("1.2.3-0", " =1.2.3-0"),
- ("1.2.3-0", " v 1.2.3-0"),
- ("1.2.3-0", " = 1.2.3-0"),
- ("1.2.3-1", "v1.2.3-1"),
- ("1.2.3-1", "=1.2.3-1"),
- ("1.2.3-1", "v 1.2.3-1"),
- ("1.2.3-1", "= 1.2.3-1"),
- ("1.2.3-1", " v1.2.3-1"),
- ("1.2.3-1", " =1.2.3-1"),
- ("1.2.3-1", " v 1.2.3-1"),
- ("1.2.3-1", " = 1.2.3-1"),
- ("1.2.3-beta", "v1.2.3-beta"),
- ("1.2.3-beta", "=1.2.3-beta"),
- ("1.2.3-beta", "v 1.2.3-beta"),
- ("1.2.3-beta", "= 1.2.3-beta"),
- ("1.2.3-beta", " v1.2.3-beta"),
- ("1.2.3-beta", " =1.2.3-beta"),
- ("1.2.3-beta", " v 1.2.3-beta"),
- ("1.2.3-beta", " = 1.2.3-beta"),
- ("1.2.3-beta+build", " = 1.2.3-beta+otherbuild"),
- ("1.2.3+build", " = 1.2.3+otherbuild"),
- ("1.2.3-beta+build", "1.2.3-beta+otherbuild"),
- ("1.2.3+build", "1.2.3+otherbuild"),
- (" v1.2.3+build", "1.2.3+otherbuild"),
- ];
- for (a, b) in fixtures {
- test_compare!(a, b, Ordering::Equal);
- }
- }
-
- #[test]
- fn version_comparisons_test() {
- // https://github.com/npm/node-semver/blob/bce42589d33e1a99454530a8fd52c7178e2b11c1/test/fixtures/comparisons.js
- let fixtures = &[
- ("0.0.0", "0.0.0-foo"),
- ("0.0.1", "0.0.0"),
- ("1.0.0", "0.9.9"),
- ("0.10.0", "0.9.0"),
- ("0.99.0", "0.10.0"),
- ("2.0.0", "1.2.3"),
- ("v0.0.0", "0.0.0-foo"),
- ("v0.0.1", "0.0.0"),
- ("v1.0.0", "0.9.9"),
- ("v0.10.0", "0.9.0"),
- ("v0.99.0", "0.10.0"),
- ("v2.0.0", "1.2.3"),
- ("0.0.0", "v0.0.0-foo"),
- ("0.0.1", "v0.0.0"),
- ("1.0.0", "v0.9.9"),
- ("0.10.0", "v0.9.0"),
- ("0.99.0", "v0.10.0"),
- ("2.0.0", "v1.2.3"),
- ("1.2.3", "1.2.3-asdf"),
- ("1.2.3", "1.2.3-4"),
- ("1.2.3", "1.2.3-4-foo"),
- ("1.2.3-5-foo", "1.2.3-5"),
- ("1.2.3-5", "1.2.3-4"),
- ("1.2.3-5-foo", "1.2.3-5-Foo"),
- ("3.0.0", "2.7.2+asdf"),
- ("1.2.3-a.10", "1.2.3-a.5"),
- ("1.2.3-a.b", "1.2.3-a.5"),
- ("1.2.3-a.b", "1.2.3-a"),
- ("1.2.3-a.b.c.10.d.5", "1.2.3-a.b.c.5.d.100"),
- ("1.2.3-r2", "1.2.3-r100"),
- ("1.2.3-r100", "1.2.3-R2"),
- ];
- for (a, b) in fixtures {
- let a = NpmVersion::parse(a).unwrap();
- let b = NpmVersion::parse(b).unwrap();
- assert_cmp!(a, b, Ordering::Greater);
- assert_cmp!(b, a, Ordering::Less);
- assert_cmp!(a, a, Ordering::Equal);
- assert_cmp!(b, b, Ordering::Equal);
- }
- }
-
- #[test]
- fn range_parse() {
- // https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/test/fixtures/range-parse.js
- let fixtures = &[
- ("1.0.0 - 2.0.0", ">=1.0.0 <=2.0.0"),
- ("1 - 2", ">=1.0.0 <3.0.0-0"),
- ("1.0 - 2.0", ">=1.0.0 <2.1.0-0"),
- ("1.0.0", "1.0.0"),
- (">=*", "*"),
- ("", "*"),
- ("*", "*"),
- ("*", "*"),
- (">=1.0.0", ">=1.0.0"),
- (">1.0.0", ">1.0.0"),
- ("<=2.0.0", "<=2.0.0"),
- ("1", ">=1.0.0 <2.0.0-0"),
- ("<=2.0.0", "<=2.0.0"),
- ("<=2.0.0", "<=2.0.0"),
- ("<2.0.0", "<2.0.0"),
- ("<2.0.0", "<2.0.0"),
- (">= 1.0.0", ">=1.0.0"),
- (">= 1.0.0", ">=1.0.0"),
- (">= 1.0.0", ">=1.0.0"),
- ("> 1.0.0", ">1.0.0"),
- ("> 1.0.0", ">1.0.0"),
- ("<= 2.0.0", "<=2.0.0"),
- ("<= 2.0.0", "<=2.0.0"),
- ("<= 2.0.0", "<=2.0.0"),
- ("< 2.0.0", "<2.0.0"),
- ("<\t2.0.0", "<2.0.0"),
- (">=0.1.97", ">=0.1.97"),
- (">=0.1.97", ">=0.1.97"),
- ("0.1.20 || 1.2.4", "0.1.20||1.2.4"),
- (">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"),
- (">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"),
- (">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"),
- ("||", "*"),
- ("2.x.x", ">=2.0.0 <3.0.0-0"),
- ("1.2.x", ">=1.2.0 <1.3.0-0"),
- ("1.2.x || 2.x", ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0"),
- ("1.2.x || 2.x", ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0"),
- ("x", "*"),
- ("2.*.*", ">=2.0.0 <3.0.0-0"),
- ("1.2.*", ">=1.2.0 <1.3.0-0"),
- ("1.2.* || 2.*", ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0"),
- ("*", "*"),
- ("2", ">=2.0.0 <3.0.0-0"),
- ("2.3", ">=2.3.0 <2.4.0-0"),
- ("~2.4", ">=2.4.0 <2.5.0-0"),
- ("~2.4", ">=2.4.0 <2.5.0-0"),
- ("~>3.2.1", ">=3.2.1 <3.3.0-0"),
- ("~1", ">=1.0.0 <2.0.0-0"),
- ("~>1", ">=1.0.0 <2.0.0-0"),
- ("~> 1", ">=1.0.0 <2.0.0-0"),
- ("~1.0", ">=1.0.0 <1.1.0-0"),
- ("~ 1.0", ">=1.0.0 <1.1.0-0"),
- ("^0", "<1.0.0-0"),
- ("^ 1", ">=1.0.0 <2.0.0-0"),
- ("^0.1", ">=0.1.0 <0.2.0-0"),
- ("^1.0", ">=1.0.0 <2.0.0-0"),
- ("^1.2", ">=1.2.0 <2.0.0-0"),
- ("^0.0.1", ">=0.0.1 <0.0.2-0"),
- ("^0.0.1-beta", ">=0.0.1-beta <0.0.2-0"),
- ("^0.1.2", ">=0.1.2 <0.2.0-0"),
- ("^1.2.3", ">=1.2.3 <2.0.0-0"),
- ("^1.2.3-beta.4", ">=1.2.3-beta.4 <2.0.0-0"),
- ("<1", "<1.0.0-0"),
- ("< 1", "<1.0.0-0"),
- (">=1", ">=1.0.0"),
- (">= 1", ">=1.0.0"),
- ("<1.2", "<1.2.0-0"),
- ("< 1.2", "<1.2.0-0"),
- ("1", ">=1.0.0 <2.0.0-0"),
- ("^ 1.2 ^ 1", ">=1.2.0 <2.0.0-0 >=1.0.0"),
- ("1.2 - 3.4.5", ">=1.2.0 <=3.4.5"),
- ("1.2.3 - 3.4", ">=1.2.3 <3.5.0-0"),
- ("1.2 - 3.4", ">=1.2.0 <3.5.0-0"),
- (">1", ">=2.0.0"),
- (">1.2", ">=1.3.0"),
- (">X", "<0.0.0-0"),
- ("<X", "<0.0.0-0"),
- ("<x <* || >* 2.x", "<0.0.0-0"),
- (">x 2.x || * || <x", "*"),
- (">01.02.03", ">1.2.3"),
- ("~1.2.3beta", ">=1.2.3-beta <1.3.0-0"),
- (">=09090", ">=9090.0.0"),
- ];
- for (range_text, expected) in fixtures {
- let range = NpmVersionReq::parse(range_text).unwrap();
- let expected_range = NpmVersionReq::parse(expected).unwrap();
- assert_eq!(
- range.inner, expected_range.inner,
- "failed for {} and {}",
- range_text, expected
- );
- }
- }
-
- #[test]
- fn range_satisfies() {
- // https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/test/fixtures/range-include.js
- let fixtures = &[
- ("1.0.0 - 2.0.0", "1.2.3"),
- ("^1.2.3+build", "1.2.3"),
- ("^1.2.3+build", "1.3.0"),
- ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3"),
- ("1.2.3pre+asdf - 2.4.3-pre+asdf", "1.2.3"),
- ("1.2.3-pre+asdf - 2.4.3pre+asdf", "1.2.3"),
- ("1.2.3pre+asdf - 2.4.3pre+asdf", "1.2.3"),
- ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre.2"),
- ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "2.4.3-alpha"),
- ("1.2.3+asdf - 2.4.3+asdf", "1.2.3"),
- ("1.0.0", "1.0.0"),
- (">=*", "0.2.4"),
- ("", "1.0.0"),
- ("*", "1.2.3"),
- ("*", "v1.2.3"),
- (">=1.0.0", "1.0.0"),
- (">=1.0.0", "1.0.1"),
- (">=1.0.0", "1.1.0"),
- (">1.0.0", "1.0.1"),
- (">1.0.0", "1.1.0"),
- ("<=2.0.0", "2.0.0"),
- ("<=2.0.0", "1.9999.9999"),
- ("<=2.0.0", "0.2.9"),
- ("<2.0.0", "1.9999.9999"),
- ("<2.0.0", "0.2.9"),
- (">= 1.0.0", "1.0.0"),
- (">= 1.0.0", "1.0.1"),
- (">= 1.0.0", "1.1.0"),
- ("> 1.0.0", "1.0.1"),
- ("> 1.0.0", "1.1.0"),
- ("<= 2.0.0", "2.0.0"),
- ("<= 2.0.0", "1.9999.9999"),
- ("<= 2.0.0", "0.2.9"),
- ("< 2.0.0", "1.9999.9999"),
- ("<\t2.0.0", "0.2.9"),
- (">=0.1.97", "v0.1.97"),
- (">=0.1.97", "0.1.97"),
- ("0.1.20 || 1.2.4", "1.2.4"),
- (">=0.2.3 || <0.0.1", "0.0.0"),
- (">=0.2.3 || <0.0.1", "0.2.3"),
- (">=0.2.3 || <0.0.1", "0.2.4"),
- ("||", "1.3.4"),
- ("2.x.x", "2.1.3"),
- ("1.2.x", "1.2.3"),
- ("1.2.x || 2.x", "2.1.3"),
- ("1.2.x || 2.x", "1.2.3"),
- ("x", "1.2.3"),
- ("2.*.*", "2.1.3"),
- ("1.2.*", "1.2.3"),
- ("1.2.* || 2.*", "2.1.3"),
- ("1.2.* || 2.*", "1.2.3"),
- ("*", "1.2.3"),
- ("2", "2.1.2"),
- ("2.3", "2.3.1"),
- ("~0.0.1", "0.0.1"),
- ("~0.0.1", "0.0.2"),
- ("~x", "0.0.9"), // >=2.4.0 <2.5.0
- ("~2", "2.0.9"), // >=2.4.0 <2.5.0
- ("~2.4", "2.4.0"), // >=2.4.0 <2.5.0
- ("~2.4", "2.4.5"),
- ("~>3.2.1", "3.2.2"), // >=3.2.1 <3.3.0,
- ("~1", "1.2.3"), // >=1.0.0 <2.0.0
- ("~>1", "1.2.3"),
- ("~> 1", "1.2.3"),
- ("~1.0", "1.0.2"), // >=1.0.0 <1.1.0,
- ("~ 1.0", "1.0.2"),
- ("~ 1.0.3", "1.0.12"),
- ("~ 1.0.3alpha", "1.0.12"),
- (">=1", "1.0.0"),
- (">= 1", "1.0.0"),
- ("<1.2", "1.1.1"),
- ("< 1.2", "1.1.1"),
- ("~v0.5.4-pre", "0.5.5"),
- ("~v0.5.4-pre", "0.5.4"),
- ("=0.7.x", "0.7.2"),
- ("<=0.7.x", "0.7.2"),
- (">=0.7.x", "0.7.2"),
- ("<=0.7.x", "0.6.2"),
- ("~1.2.1 >=1.2.3", "1.2.3"),
- ("~1.2.1 =1.2.3", "1.2.3"),
- ("~1.2.1 1.2.3", "1.2.3"),
- ("~1.2.1 >=1.2.3 1.2.3", "1.2.3"),
- ("~1.2.1 1.2.3 >=1.2.3", "1.2.3"),
- ("~1.2.1 1.2.3", "1.2.3"),
- (">=1.2.1 1.2.3", "1.2.3"),
- ("1.2.3 >=1.2.1", "1.2.3"),
- (">=1.2.3 >=1.2.1", "1.2.3"),
- (">=1.2.1 >=1.2.3", "1.2.3"),
- (">=1.2", "1.2.8"),
- ("^1.2.3", "1.8.1"),
- ("^0.1.2", "0.1.2"),
- ("^0.1", "0.1.2"),
- ("^0.0.1", "0.0.1"),
- ("^1.2", "1.4.2"),
- ("^1.2 ^1", "1.4.2"),
- ("^1.2.3-alpha", "1.2.3-pre"),
- ("^1.2.0-alpha", "1.2.0-pre"),
- ("^0.0.1-alpha", "0.0.1-beta"),
- ("^0.0.1-alpha", "0.0.1"),
- ("^0.1.1-alpha", "0.1.1-beta"),
- ("^x", "1.2.3"),
- ("x - 1.0.0", "0.9.7"),
- ("x - 1.x", "0.9.7"),
- ("1.0.0 - x", "1.9.7"),
- ("1.x - x", "1.9.7"),
- ("<=7.x", "7.9.9"),
- // additional tests
- ("1.0.0-alpha.13", "1.0.0-alpha.13"),
- ];
- for (req_text, version_text) in fixtures {
- let req = NpmVersionReq::parse(req_text).unwrap();
- let version = NpmVersion::parse(version_text).unwrap();
- assert!(
- req.matches(&version),
- "Checking {req_text} satisfies {version_text}"
- );
- }
- }
-
- #[test]
- fn range_not_satisfies() {
- let fixtures = &[
- ("1.0.0 - 2.0.0", "2.2.3"),
- ("1.2.3+asdf - 2.4.3+asdf", "1.2.3-pre.2"),
- ("1.2.3+asdf - 2.4.3+asdf", "2.4.3-alpha"),
- ("^1.2.3+build", "2.0.0"),
- ("^1.2.3+build", "1.2.0"),
- ("^1.2.3", "1.2.3-pre"),
- ("^1.2", "1.2.0-pre"),
- (">1.2", "1.3.0-beta"),
- ("<=1.2.3", "1.2.3-beta"),
- ("^1.2.3", "1.2.3-beta"),
- ("=0.7.x", "0.7.0-asdf"),
- (">=0.7.x", "0.7.0-asdf"),
- ("<=0.7.x", "0.7.0-asdf"),
- ("1", "1.0.0beta"),
- ("<1", "1.0.0beta"),
- ("< 1", "1.0.0beta"),
- ("1.0.0", "1.0.1"),
- (">=1.0.0", "0.0.0"),
- (">=1.0.0", "0.0.1"),
- (">=1.0.0", "0.1.0"),
- (">1.0.0", "0.0.1"),
- (">1.0.0", "0.1.0"),
- ("<=2.0.0", "3.0.0"),
- ("<=2.0.0", "2.9999.9999"),
- ("<=2.0.0", "2.2.9"),
- ("<2.0.0", "2.9999.9999"),
- ("<2.0.0", "2.2.9"),
- (">=0.1.97", "v0.1.93"),
- (">=0.1.97", "0.1.93"),
- ("0.1.20 || 1.2.4", "1.2.3"),
- (">=0.2.3 || <0.0.1", "0.0.3"),
- (">=0.2.3 || <0.0.1", "0.2.2"),
- ("2.x.x", "1.1.3"),
- ("2.x.x", "3.1.3"),
- ("1.2.x", "1.3.3"),
- ("1.2.x || 2.x", "3.1.3"),
- ("1.2.x || 2.x", "1.1.3"),
- ("2.*.*", "1.1.3"),
- ("2.*.*", "3.1.3"),
- ("1.2.*", "1.3.3"),
- ("1.2.* || 2.*", "3.1.3"),
- ("1.2.* || 2.*", "1.1.3"),
- ("2", "1.1.2"),
- ("2.3", "2.4.1"),
- ("~0.0.1", "0.1.0-alpha"),
- ("~0.0.1", "0.1.0"),
- ("~2.4", "2.5.0"), // >=2.4.0 <2.5.0
- ("~2.4", "2.3.9"),
- ("~>3.2.1", "3.3.2"), // >=3.2.1 <3.3.0
- ("~>3.2.1", "3.2.0"), // >=3.2.1 <3.3.0
- ("~1", "0.2.3"), // >=1.0.0 <2.0.0
- ("~>1", "2.2.3"),
- ("~1.0", "1.1.0"), // >=1.0.0 <1.1.0
- ("<1", "1.0.0"),
- (">=1.2", "1.1.1"),
- ("1", "2.0.0beta"),
- ("~v0.5.4-beta", "0.5.4-alpha"),
- ("=0.7.x", "0.8.2"),
- (">=0.7.x", "0.6.2"),
- ("<0.7.x", "0.7.2"),
- ("<1.2.3", "1.2.3-beta"),
- ("=1.2.3", "1.2.3-beta"),
- (">1.2", "1.2.8"),
- ("^0.0.1", "0.0.2-alpha"),
- ("^0.0.1", "0.0.2"),
- ("^1.2.3", "2.0.0-alpha"),
- ("^1.2.3", "1.2.2"),
- ("^1.2", "1.1.9"),
- ("*", "v1.2.3-foo"),
- ("^1.0.0", "2.0.0-rc1"),
- ("1 - 2", "2.0.0-pre"),
- ("1 - 2", "1.0.0-pre"),
- ("1.0 - 2", "1.0.0-pre"),
- ("1.1.x", "1.0.0-a"),
- ("1.1.x", "1.1.0-a"),
- ("1.1.x", "1.2.0-a"),
- ("1.x", "1.0.0-a"),
- ("1.x", "1.1.0-a"),
- ("1.x", "1.2.0-a"),
- (">=1.0.0 <1.1.0", "1.1.0"),
- (">=1.0.0 <1.1.0", "1.1.0-pre"),
- (">=1.0.0 <1.1.0-pre", "1.1.0-pre"),
- ];
-
- for (req_text, version_text) in fixtures {
- let req = NpmVersionReq::parse(req_text).unwrap();
- let version = NpmVersion::parse(version_text).unwrap();
- assert!(
- !req.matches(&version),
- "Checking {req_text} not satisfies {version_text}"
- );
- }
- }
-
- #[test]
- fn range_primitive_kind_beside_caret_or_tilde_with_whitespace() {
- // node semver should have enforced strictness, but it didn't
- // and so we end up with a system that acts this way
- let fixtures = &[
- (">= ^1.2.3", "1.2.3", true),
- (">= ^1.2.3", "1.2.4", true),
- (">= ^1.2.3", "1.9.3", true),
- (">= ^1.2.3", "2.0.0", false),
- (">= ^1.2.3", "1.2.2", false),
- // this is considered the same as the above by node semver
- ("> ^1.2.3", "1.2.3", true),
- ("> ^1.2.3", "1.2.4", true),
- ("> ^1.2.3", "1.9.3", true),
- ("> ^1.2.3", "2.0.0", false),
- ("> ^1.2.3", "1.2.2", false),
- // this is also considered the same
- ("< ^1.2.3", "1.2.3", true),
- ("< ^1.2.3", "1.2.4", true),
- ("< ^1.2.3", "1.9.3", true),
- ("< ^1.2.3", "2.0.0", false),
- ("< ^1.2.3", "1.2.2", false),
- // same with this
- ("<= ^1.2.3", "1.2.3", true),
- ("<= ^1.2.3", "1.2.4", true),
- ("<= ^1.2.3", "1.9.3", true),
- ("<= ^1.2.3", "2.0.0", false),
- ("<= ^1.2.3", "1.2.2", false),
- // now try a ~, which should work the same as above, but for ~
- ("<= ~1.2.3", "1.2.3", true),
- ("<= ~1.2.3", "1.2.4", true),
- ("<= ~1.2.3", "1.9.3", false),
- ("<= ~1.2.3", "2.0.0", false),
- ("<= ~1.2.3", "1.2.2", false),
- ];
-
- for (req_text, version_text, satisfies) in fixtures {
- let req = NpmVersionReq::parse(req_text).unwrap();
- let version = NpmVersion::parse(version_text).unwrap();
- assert_eq!(
- req.matches(&version),
- *satisfies,
- "Checking {} {} satisfies {}",
- req_text,
- if *satisfies { "true" } else { "false" },
- version_text
- );
- }
- }
-
- #[test]
- fn range_primitive_kind_beside_caret_or_tilde_no_whitespace() {
- let fixtures = &[
- ">=^1.2.3", ">^1.2.3", "<^1.2.3", "<=^1.2.3", ">=~1.2.3", ">~1.2.3",
- "<~1.2.3", "<=~1.2.3",
- ];
-
- for req_text in fixtures {
- // when it has no space, this is considered invalid
- // by node semver so we should error
- assert!(NpmVersionReq::parse(req_text).is_err());
- }
- }
-}
diff --git a/cli/npm/semver/range.rs b/cli/npm/semver/range.rs
deleted file mode 100644
index 07ee2d62a..000000000
--- a/cli/npm/semver/range.rs
+++ /dev/null
@@ -1,509 +0,0 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-
-use std::cmp::Ordering;
-
-use serde::Deserialize;
-use serde::Serialize;
-
-use super::NpmVersion;
-
-/// Collection of ranges.
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct VersionRangeSet(pub Vec<VersionRange>);
-
-impl VersionRangeSet {
- pub fn satisfies(&self, version: &NpmVersion) -> bool {
- self.0.iter().any(|r| r.satisfies(version))
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub enum RangeBound {
- Version(VersionBound),
- Unbounded, // matches everything
-}
-
-impl RangeBound {
- pub fn inclusive(version: NpmVersion) -> Self {
- Self::version(VersionBoundKind::Inclusive, version)
- }
-
- pub fn exclusive(version: NpmVersion) -> Self {
- Self::version(VersionBoundKind::Exclusive, version)
- }
-
- pub fn version(kind: VersionBoundKind, version: NpmVersion) -> Self {
- Self::Version(VersionBound::new(kind, version))
- }
-
- pub fn clamp_start(&self, other: &RangeBound) -> RangeBound {
- match &self {
- RangeBound::Unbounded => other.clone(),
- RangeBound::Version(self_bound) => RangeBound::Version(match &other {
- RangeBound::Unbounded => self_bound.clone(),
- RangeBound::Version(other_bound) => {
- match self_bound.version.cmp(&other_bound.version) {
- Ordering::Greater => self_bound.clone(),
- Ordering::Less => other_bound.clone(),
- Ordering::Equal => match self_bound.kind {
- VersionBoundKind::Exclusive => self_bound.clone(),
- VersionBoundKind::Inclusive => other_bound.clone(),
- },
- }
- }
- }),
- }
- }
-
- pub fn clamp_end(&self, other: &RangeBound) -> RangeBound {
- match &self {
- RangeBound::Unbounded => other.clone(),
- RangeBound::Version(self_bound) => {
- RangeBound::Version(match other {
- RangeBound::Unbounded => self_bound.clone(),
- RangeBound::Version(other_bound) => {
- match self_bound.version.cmp(&other_bound.version) {
- // difference with above is the next two lines are switched
- Ordering::Greater => other_bound.clone(),
- Ordering::Less => self_bound.clone(),
- Ordering::Equal => match self_bound.kind {
- VersionBoundKind::Exclusive => self_bound.clone(),
- VersionBoundKind::Inclusive => other_bound.clone(),
- },
- }
- }
- })
- }
- }
- }
-
- pub fn has_pre_with_exact_major_minor_patch(
- &self,
- version: &NpmVersion,
- ) -> bool {
- if let RangeBound::Version(self_version) = &self {
- if !self_version.version.pre.is_empty()
- && self_version.version.major == version.major
- && self_version.version.minor == version.minor
- && self_version.version.patch == version.patch
- {
- return true;
- }
- }
- false
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub enum VersionBoundKind {
- Inclusive,
- Exclusive,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub struct VersionBound {
- pub kind: VersionBoundKind,
- pub version: NpmVersion,
-}
-
-impl VersionBound {
- pub fn new(kind: VersionBoundKind, version: NpmVersion) -> Self {
- Self { kind, version }
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub struct VersionRange {
- pub start: RangeBound,
- pub end: RangeBound,
-}
-
-impl VersionRange {
- pub fn all() -> VersionRange {
- VersionRange {
- start: RangeBound::Version(VersionBound {
- kind: VersionBoundKind::Inclusive,
- version: NpmVersion::default(),
- }),
- end: RangeBound::Unbounded,
- }
- }
-
- pub fn none() -> VersionRange {
- VersionRange {
- start: RangeBound::Version(VersionBound {
- kind: VersionBoundKind::Inclusive,
- version: NpmVersion::default(),
- }),
- end: RangeBound::Version(VersionBound {
- kind: VersionBoundKind::Exclusive,
- version: NpmVersion::default(),
- }),
- }
- }
-
- /// If this range won't match anything.
- pub fn is_none(&self) -> bool {
- if let RangeBound::Version(end) = &self.end {
- end.kind == VersionBoundKind::Exclusive
- && end.version.major == 0
- && end.version.minor == 0
- && end.version.patch == 0
- } else {
- false
- }
- }
-
- pub fn satisfies(&self, version: &NpmVersion) -> bool {
- let satisfies = self.min_satisfies(version) && self.max_satisfies(version);
- if satisfies && !version.pre.is_empty() {
- // check either side of the range has a pre and same version
- self.start.has_pre_with_exact_major_minor_patch(version)
- || self.end.has_pre_with_exact_major_minor_patch(version)
- } else {
- satisfies
- }
- }
-
- fn min_satisfies(&self, version: &NpmVersion) -> bool {
- match &self.start {
- RangeBound::Unbounded => true,
- RangeBound::Version(bound) => match version.cmp(&bound.version) {
- Ordering::Less => false,
- Ordering::Equal => bound.kind == VersionBoundKind::Inclusive,
- Ordering::Greater => true,
- },
- }
- }
-
- fn max_satisfies(&self, version: &NpmVersion) -> bool {
- match &self.end {
- RangeBound::Unbounded => true,
- RangeBound::Version(bound) => match version.cmp(&bound.version) {
- Ordering::Less => true,
- Ordering::Equal => bound.kind == VersionBoundKind::Inclusive,
- Ordering::Greater => false,
- },
- }
- }
-
- pub fn clamp(&self, range: &VersionRange) -> VersionRange {
- let start = self.start.clamp_start(&range.start);
- let end = self.end.clamp_end(&range.end);
- // clamp the start range to the end when greater
- let start = start.clamp_end(&end);
- VersionRange { start, end }
- }
-}
-
-/// A range that could be a wildcard or number value.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum XRange {
- Wildcard,
- Val(u64),
-}
-
-/// A partial version.
-#[derive(Debug, Clone)]
-pub struct Partial {
- pub major: XRange,
- pub minor: XRange,
- pub patch: XRange,
- pub pre: Vec<String>,
- pub build: Vec<String>,
-}
-
-impl Partial {
- pub fn as_tilde_version_range(&self) -> VersionRange {
- // tilde ranges allow patch-level changes
- let end = match self.major {
- XRange::Wildcard => return VersionRange::all(),
- XRange::Val(major) => match self.minor {
- XRange::Wildcard => NpmVersion {
- major: major + 1,
- minor: 0,
- patch: 0,
- pre: Vec::new(),
- build: Vec::new(),
- },
- XRange::Val(minor) => NpmVersion {
- major,
- minor: minor + 1,
- patch: 0,
- pre: Vec::new(),
- build: Vec::new(),
- },
- },
- };
- VersionRange {
- start: self.as_lower_bound(),
- end: RangeBound::exclusive(end),
- }
- }
-
- pub fn as_caret_version_range(&self) -> VersionRange {
- // partial ranges allow patch and minor updates, except when
- // leading parts are < 1 in which case it will only bump the
- // first non-zero or patch part
- let end = match self.major {
- XRange::Wildcard => return VersionRange::all(),
- XRange::Val(major) => {
- let next_major = NpmVersion {
- major: major + 1,
- ..Default::default()
- };
- if major > 0 {
- next_major
- } else {
- match self.minor {
- XRange::Wildcard => next_major,
- XRange::Val(minor) => {
- let next_minor = NpmVersion {
- minor: minor + 1,
- ..Default::default()
- };
- if minor > 0 {
- next_minor
- } else {
- match self.patch {
- XRange::Wildcard => next_minor,
- XRange::Val(patch) => NpmVersion {
- patch: patch + 1,
- ..Default::default()
- },
- }
- }
- }
- }
- }
- }
- };
- VersionRange {
- start: self.as_lower_bound(),
- end: RangeBound::Version(VersionBound {
- kind: VersionBoundKind::Exclusive,
- version: end,
- }),
- }
- }
-
- pub fn as_lower_bound(&self) -> RangeBound {
- RangeBound::inclusive(NpmVersion {
- major: match self.major {
- XRange::Val(val) => val,
- XRange::Wildcard => 0,
- },
- minor: match self.minor {
- XRange::Val(val) => val,
- XRange::Wildcard => 0,
- },
- patch: match self.patch {
- XRange::Val(val) => val,
- XRange::Wildcard => 0,
- },
- pre: self.pre.clone(),
- build: self.build.clone(),
- })
- }
-
- pub fn as_upper_bound(&self) -> RangeBound {
- let mut end = NpmVersion::default();
- let mut kind = VersionBoundKind::Inclusive;
- match self.patch {
- XRange::Wildcard => {
- end.minor += 1;
- kind = VersionBoundKind::Exclusive;
- }
- XRange::Val(val) => {
- end.patch = val;
- }
- }
- match self.minor {
- XRange::Wildcard => {
- end.minor = 0;
- end.major += 1;
- kind = VersionBoundKind::Exclusive;
- }
- XRange::Val(val) => {
- end.minor += val;
- }
- }
- match self.major {
- XRange::Wildcard => {
- return RangeBound::Unbounded;
- }
- XRange::Val(val) => {
- end.major += val;
- }
- }
-
- if kind == VersionBoundKind::Inclusive {
- end.pre = self.pre.clone();
- }
-
- RangeBound::version(kind, end)
- }
-
- pub fn as_equal_range(&self) -> VersionRange {
- let major = match self.major {
- XRange::Wildcard => {
- return self.as_greater_range(VersionBoundKind::Inclusive)
- }
- XRange::Val(val) => val,
- };
- let minor = match self.minor {
- XRange::Wildcard => {
- return self.as_greater_range(VersionBoundKind::Inclusive)
- }
- XRange::Val(val) => val,
- };
- let patch = match self.patch {
- XRange::Wildcard => {
- return self.as_greater_range(VersionBoundKind::Inclusive)
- }
- XRange::Val(val) => val,
- };
- let version = NpmVersion {
- major,
- minor,
- patch,
- pre: self.pre.clone(),
- build: self.build.clone(),
- };
- VersionRange {
- start: RangeBound::inclusive(version.clone()),
- end: RangeBound::inclusive(version),
- }
- }
-
- pub fn as_greater_than(
- &self,
- mut start_kind: VersionBoundKind,
- ) -> VersionRange {
- let major = match self.major {
- XRange::Wildcard => match start_kind {
- VersionBoundKind::Inclusive => return VersionRange::all(),
- VersionBoundKind::Exclusive => return VersionRange::none(),
- },
- XRange::Val(major) => major,
- };
- let mut start = NpmVersion::default();
-
- if start_kind == VersionBoundKind::Inclusive {
- start.pre = self.pre.clone();
- }
-
- start.major = major;
- match self.minor {
- XRange::Wildcard => {
- if start_kind == VersionBoundKind::Exclusive {
- start_kind = VersionBoundKind::Inclusive;
- start.major += 1;
- }
- }
- XRange::Val(minor) => {
- start.minor = minor;
- }
- }
- match self.patch {
- XRange::Wildcard => {
- if start_kind == VersionBoundKind::Exclusive {
- start_kind = VersionBoundKind::Inclusive;
- start.minor += 1;
- }
- }
- XRange::Val(patch) => {
- start.patch = patch;
- }
- }
-
- VersionRange {
- start: RangeBound::version(start_kind, start),
- end: RangeBound::Unbounded,
- }
- }
-
- pub fn as_less_than(&self, mut end_kind: VersionBoundKind) -> VersionRange {
- let major = match self.major {
- XRange::Wildcard => match end_kind {
- VersionBoundKind::Inclusive => return VersionRange::all(),
- VersionBoundKind::Exclusive => return VersionRange::none(),
- },
- XRange::Val(major) => major,
- };
- let mut end = NpmVersion {
- major,
- ..Default::default()
- };
- match self.minor {
- XRange::Wildcard => {
- if end_kind == VersionBoundKind::Inclusive {
- end.major += 1;
- }
- end_kind = VersionBoundKind::Exclusive;
- }
- XRange::Val(minor) => {
- end.minor = minor;
- }
- }
- match self.patch {
- XRange::Wildcard => {
- if end_kind == VersionBoundKind::Inclusive {
- end.minor += 1;
- }
- end_kind = VersionBoundKind::Exclusive;
- }
- XRange::Val(patch) => {
- end.patch = patch;
- }
- }
- if end_kind == VersionBoundKind::Inclusive {
- end.pre = self.pre.clone();
- }
- VersionRange {
- start: RangeBound::Unbounded,
- end: RangeBound::version(end_kind, end),
- }
- }
-
- pub fn as_greater_range(&self, start_kind: VersionBoundKind) -> VersionRange {
- let major = match self.major {
- XRange::Wildcard => return VersionRange::all(),
- XRange::Val(major) => major,
- };
- let mut start = NpmVersion::default();
- let mut end = NpmVersion::default();
- start.major = major;
- end.major = major;
- match self.patch {
- XRange::Wildcard => {
- if self.minor != XRange::Wildcard {
- end.minor += 1;
- }
- }
- XRange::Val(patch) => {
- start.patch = patch;
- end.patch = patch;
- }
- }
- match self.minor {
- XRange::Wildcard => {
- end.major += 1;
- }
- XRange::Val(minor) => {
- start.minor = minor;
- end.minor += minor;
- }
- }
- let end_kind = if start_kind == VersionBoundKind::Inclusive && start == end
- {
- VersionBoundKind::Inclusive
- } else {
- VersionBoundKind::Exclusive
- };
- VersionRange {
- start: RangeBound::version(start_kind, start),
- end: RangeBound::version(end_kind, end),
- }
- }
-}
diff --git a/cli/npm/semver/specifier.rs b/cli/npm/semver/specifier.rs
deleted file mode 100644
index b12a5c308..000000000
--- a/cli/npm/semver/specifier.rs
+++ /dev/null
@@ -1,299 +0,0 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-
-use deno_core::anyhow::Context;
-use deno_core::error::AnyError;
-use monch::*;
-use serde::Deserialize;
-use serde::Serialize;
-
-use super::is_valid_npm_tag;
-use super::range::Partial;
-use super::range::VersionRange;
-use super::range::XRange;
-
-#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
-enum SpecifierVersionReqInner {
- Range(VersionRange),
- Tag(String),
-}
-
-/// Version requirement found in npm specifiers.
-#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub struct SpecifierVersionReq {
- raw_text: String,
- inner: SpecifierVersionReqInner,
-}
-
-impl std::fmt::Display for SpecifierVersionReq {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.raw_text)
- }
-}
-
-impl SpecifierVersionReq {
- pub fn parse(text: &str) -> Result<Self, AnyError> {
- with_failure_handling(parse_npm_specifier)(text).with_context(|| {
- format!("Invalid npm specifier version requirement '{text}'.")
- })
- }
-
- pub fn range(&self) -> Option<&VersionRange> {
- match &self.inner {
- SpecifierVersionReqInner::Range(range) => Some(range),
- SpecifierVersionReqInner::Tag(_) => None,
- }
- }
-
- pub fn tag(&self) -> Option<&str> {
- match &self.inner {
- SpecifierVersionReqInner::Range(_) => None,
- SpecifierVersionReqInner::Tag(tag) => Some(tag.as_str()),
- }
- }
-}
-
-fn parse_npm_specifier(input: &str) -> ParseResult<SpecifierVersionReq> {
- map_res(version_range, |result| {
- let (new_input, range_result) = match result {
- Ok((input, range)) => (input, Ok(range)),
- // use an empty string because we'll consider it a tag
- Err(err) => ("", Err(err)),
- };
- Ok((
- new_input,
- SpecifierVersionReq {
- raw_text: input.to_string(),
- inner: match range_result {
- Ok(range) => SpecifierVersionReqInner::Range(range),
- Err(err) => {
- if !is_valid_npm_tag(input) {
- return Err(err);
- } else {
- SpecifierVersionReqInner::Tag(input.to_string())
- }
- }
- },
- },
- ))
- })(input)
-}
-
-// Note: Although the code below looks very similar to what's used for
-// parsing npm version requirements, the code here is more strict
-// in order to not allow for people to get ridiculous when using
-// npm specifiers.
-
-// version_range ::= partial | tilde | caret
-fn version_range(input: &str) -> ParseResult<VersionRange> {
- or3(
- map(preceded(ch('~'), partial), |partial| {
- partial.as_tilde_version_range()
- }),
- map(preceded(ch('^'), partial), |partial| {
- partial.as_caret_version_range()
- }),
- map(partial, |partial| partial.as_equal_range()),
- )(input)
-}
-
-// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
-fn partial(input: &str) -> ParseResult<Partial> {
- let (input, major) = xr()(input)?;
- let (input, maybe_minor) = maybe(preceded(ch('.'), xr()))(input)?;
- let (input, maybe_patch) = if maybe_minor.is_some() {
- maybe(preceded(ch('.'), xr()))(input)?
- } else {
- (input, None)
- };
- let (input, qual) = if maybe_patch.is_some() {
- maybe(qualifier)(input)?
- } else {
- (input, None)
- };
- let qual = qual.unwrap_or_default();
- Ok((
- input,
- Partial {
- major,
- minor: maybe_minor.unwrap_or(XRange::Wildcard),
- patch: maybe_patch.unwrap_or(XRange::Wildcard),
- pre: qual.pre,
- build: qual.build,
- },
- ))
-}
-
-// xr ::= 'x' | 'X' | '*' | nr
-fn xr<'a>() -> impl Fn(&'a str) -> ParseResult<'a, XRange> {
- or(
- map(or3(tag("x"), tag("X"), tag("*")), |_| XRange::Wildcard),
- map(nr, XRange::Val),
- )
-}
-
-// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
-fn nr(input: &str) -> ParseResult<u64> {
- or(map(tag("0"), |_| 0), move |input| {
- let (input, result) = if_not_empty(substring(pair(
- if_true(next_char, |c| c.is_ascii_digit() && *c != '0'),
- skip_while(|c| c.is_ascii_digit()),
- )))(input)?;
- let val = match result.parse::<u64>() {
- Ok(val) => val,
- Err(err) => {
- return ParseError::fail(
- input,
- format!("Error parsing '{result}' to u64.\n\n{err:#}"),
- )
- }
- };
- Ok((input, val))
- })(input)
-}
-
-#[derive(Debug, Clone, Default)]
-struct Qualifier {
- pre: Vec<String>,
- build: Vec<String>,
-}
-
-// qualifier ::= ( '-' pre )? ( '+' build )?
-fn qualifier(input: &str) -> ParseResult<Qualifier> {
- let (input, pre_parts) = maybe(pre)(input)?;
- let (input, build_parts) = maybe(build)(input)?;
- Ok((
- input,
- Qualifier {
- pre: pre_parts.unwrap_or_default(),
- build: build_parts.unwrap_or_default(),
- },
- ))
-}
-
-// pre ::= parts
-fn pre(input: &str) -> ParseResult<Vec<String>> {
- preceded(ch('-'), parts)(input)
-}
-
-// build ::= parts
-fn build(input: &str) -> ParseResult<Vec<String>> {
- preceded(ch('+'), parts)(input)
-}
-
-// parts ::= part ( '.' part ) *
-fn parts(input: &str) -> ParseResult<Vec<String>> {
- if_not_empty(map(separated_list(part, ch('.')), |text| {
- text.into_iter().map(ToOwned::to_owned).collect()
- }))(input)
-}
-
-// part ::= nr | [-0-9A-Za-z]+
-fn part(input: &str) -> ParseResult<&str> {
- // nr is in the other set, so don't bother checking for it
- if_true(
- take_while(|c| c.is_ascii_alphanumeric() || c == '-'),
- |result| !result.is_empty(),
- )(input)
-}
-
-#[cfg(test)]
-mod tests {
- use crate::npm::semver::NpmVersion;
-
- use super::*;
-
- struct VersionReqTester(SpecifierVersionReq);
-
- impl VersionReqTester {
- fn new(text: &str) -> Self {
- Self(SpecifierVersionReq::parse(text).unwrap())
- }
-
- fn matches(&self, version: &str) -> bool {
- self
- .0
- .range()
- .map(|r| r.satisfies(&NpmVersion::parse(version).unwrap()))
- .unwrap_or(false)
- }
- }
-
- #[test]
- fn version_req_exact() {
- let tester = VersionReqTester::new("1.0.1");
- assert!(!tester.matches("1.0.0"));
- assert!(tester.matches("1.0.1"));
- assert!(!tester.matches("1.0.2"));
- assert!(!tester.matches("1.1.1"));
-
- // pre-release
- let tester = VersionReqTester::new("1.0.0-alpha.13");
- assert!(tester.matches("1.0.0-alpha.13"));
- }
-
- #[test]
- fn version_req_minor() {
- let tester = VersionReqTester::new("1.1");
- assert!(!tester.matches("1.0.0"));
- assert!(tester.matches("1.1.0"));
- assert!(tester.matches("1.1.1"));
- assert!(!tester.matches("1.2.0"));
- assert!(!tester.matches("1.2.1"));
- }
-
- #[test]
- fn version_req_caret() {
- let tester = VersionReqTester::new("^1.1.1");
- assert!(!tester.matches("1.1.0"));
- assert!(tester.matches("1.1.1"));
- assert!(tester.matches("1.1.2"));
- assert!(tester.matches("1.2.0"));
- assert!(!tester.matches("2.0.0"));
-
- let tester = VersionReqTester::new("^0.1.1");
- assert!(!tester.matches("0.0.0"));
- assert!(!tester.matches("0.1.0"));
- assert!(tester.matches("0.1.1"));
- assert!(tester.matches("0.1.2"));
- assert!(!tester.matches("0.2.0"));
- assert!(!tester.matches("1.0.0"));
-
- let tester = VersionReqTester::new("^0.0.1");
- assert!(!tester.matches("0.0.0"));
- assert!(tester.matches("0.0.1"));
- assert!(!tester.matches("0.0.2"));
- assert!(!tester.matches("0.1.0"));
- assert!(!tester.matches("1.0.0"));
- }
-
- #[test]
- fn version_req_tilde() {
- let tester = VersionReqTester::new("~1.1.1");
- assert!(!tester.matches("1.1.0"));
- assert!(tester.matches("1.1.1"));
- assert!(tester.matches("1.1.2"));
- assert!(!tester.matches("1.2.0"));
- assert!(!tester.matches("2.0.0"));
-
- let tester = VersionReqTester::new("~0.1.1");
- assert!(!tester.matches("0.0.0"));
- assert!(!tester.matches("0.1.0"));
- assert!(tester.matches("0.1.1"));
- assert!(tester.matches("0.1.2"));
- assert!(!tester.matches("0.2.0"));
- assert!(!tester.matches("1.0.0"));
-
- let tester = VersionReqTester::new("~0.0.1");
- assert!(!tester.matches("0.0.0"));
- assert!(tester.matches("0.0.1"));
- assert!(tester.matches("0.0.2")); // for some reason this matches, but not with ^
- assert!(!tester.matches("0.1.0"));
- assert!(!tester.matches("1.0.0"));
- }
-
- #[test]
- fn parses_tag() {
- let latest_tag = SpecifierVersionReq::parse("latest").unwrap();
- assert_eq!(latest_tag.tag().unwrap(), "latest");
- }
-}
diff --git a/cli/npm/tarball.rs b/cli/npm/tarball.rs
index 758ac3ded..3abf4f12f 100644
--- a/cli/npm/tarball.rs
+++ b/cli/npm/tarball.rs
@@ -13,10 +13,10 @@ use tar::EntryType;
use super::cache::with_folder_sync_lock;
use super::registry::NpmPackageVersionDistInfo;
-use super::semver::NpmVersion;
+use crate::semver::Version;
pub fn verify_and_extract_tarball(
- package: (&str, &NpmVersion),
+ package: (&str, &Version),
data: &[u8],
dist_info: &NpmPackageVersionDistInfo,
output_folder: &Path,
@@ -29,7 +29,7 @@ pub fn verify_and_extract_tarball(
}
fn verify_tarball_integrity(
- package: (&str, &NpmVersion),
+ package: (&str, &Version),
data: &[u8],
npm_integrity: &str,
) -> Result<(), AnyError> {
@@ -120,12 +120,11 @@ fn extract_tarball(data: &[u8], output_folder: &Path) -> Result<(), AnyError> {
#[cfg(test)]
mod test {
use super::*;
- use crate::npm::semver::NpmVersion;
#[test]
pub fn test_verify_tarball() {
let package_name = "package".to_string();
- let package_version = NpmVersion::parse("1.0.0").unwrap();
+ let package_version = Version::parse_from_npm("1.0.0").unwrap();
let package = (package_name.as_str(), &package_version);
let actual_checksum =
"z4phnx7vul3xvchq1m2ab9yg5aulvxxcg/spidns6c5h0ne8xyxysp+dgnkhfuwvy7kxvudbeoglodj6+sfapg==";