Skip to content

Commit f1a5cec

Browse files
authored
Merge pull request #8 from stormslowly/fix/parse_ident
fix: specifier subpath parse error
2 parents 5d51bfb + d79c245 commit f1a5cec

File tree

4 files changed

+89
-33
lines changed

4 files changed

+89
-33
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pnp"
3-
version = "0.9.3"
3+
version = "0.9.4"
44
edition = "2021"
55
license = "BSD-2-Clause"
66
description = "Resolution primitives for Yarn PnP"

src/lib.rs

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -168,28 +168,54 @@ pub struct Manifest {
168168
package_registry_data: HashMap<String, IndexMap<String, PackageInformation>>,
169169
}
170170

171-
pub fn parse_bare_identifier(specifier: &str) -> Result<(String, Option<String>), Error> {
172-
let mut segments = specifier.splitn(3, '/');
173-
let mut ident_option: Option<String> = None;
171+
fn parse_scoped_package_name(specifier: &str) -> Option<(String, Option<String>)> {
172+
let mut segments
173+
= specifier.splitn(3, '/');
174174

175-
if let Some(first) = segments.next() {
176-
if first.starts_with('@') {
177-
if let Some(second) = segments.next() {
178-
ident_option = Some(format!("{}/{}", first, second));
179-
}
180-
} else {
181-
ident_option = Some(first.to_string());
182-
}
183-
}
175+
let Some(scope) = segments.next() else {
176+
return None;
177+
};
184178

185-
if let Some(ident) = ident_option {
186-
Ok((ident, segments.next().map(|v| v.to_string())))
187-
} else {
188-
Err(Error::BadSpecifier {
189-
message: String::from("Invalid specifier"),
190-
specifier: specifier.to_string(),
191-
})
192-
}
179+
let Some(name) = segments.next() else {
180+
return None;
181+
};
182+
183+
let package_name
184+
= specifier[..scope.len() + name.len() + 1].to_string();
185+
186+
let subpath
187+
= segments.next().map(|v| v.to_string());
188+
189+
Some((package_name, subpath))
190+
}
191+
192+
fn parse_global_package_name(specifier: &str) -> Option<(String, Option<String>)> {
193+
let mut segments
194+
= specifier.splitn(2, '/');
195+
196+
let Some(name) = segments.next() else {
197+
return None;
198+
};
199+
200+
let package_name
201+
= name.to_string();
202+
203+
let subpath
204+
= segments.next().map(|v| v.to_string());
205+
206+
Some((package_name, subpath))
207+
}
208+
209+
pub fn parse_bare_identifier(specifier: &str) -> Result<(String, Option<String>), Error> {
210+
let name = match specifier.starts_with("@") {
211+
true => parse_scoped_package_name(specifier),
212+
false => parse_global_package_name(specifier),
213+
};
214+
215+
name.ok_or_else(|| Error::BadSpecifier {
216+
message: String::from("Invalid specifier"),
217+
specifier: specifier.to_string(),
218+
})
193219
}
194220

195221
pub fn find_closest_pnp_manifest_path<P: AsRef<Path>>(p: P) -> Option<PathBuf> {

src/lib_tests.rs

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,13 @@ mod tests {
2222

2323
use super::*;
2424
use crate::{
25-
init_pnp_manifest, load_pnp_manifest, resolve_to_unqualified,
25+
init_pnp_manifest, load_pnp_manifest, parse_bare_identifier, resolve_to_unqualified,
2626
resolve_to_unqualified_via_manifest, ResolutionHost,
2727
};
2828

2929
#[test]
3030
fn example() {
31-
let manifest
32-
= load_pnp_manifest("data/pnp-yarn-v3.cjs").unwrap();
31+
let manifest = load_pnp_manifest("data/pnp-yarn-v3.cjs").unwrap();
3332

3433
let host = ResolutionHost {
3534
find_pnp_manifest: Box::new(move |_| Ok(Some(manifest.clone()))),
@@ -51,16 +50,16 @@ mod tests {
5150
Ok(Resolution::Resolved(_path, _subpath)) => {
5251
// path = "/path/to/lodash.zip"
5352
// subpath = "cloneDeep"
54-
},
53+
}
5554
Ok(Resolution::Skipped) => {
5655
// This is returned when the PnP resolver decides that it shouldn't
5756
// handle the resolution for this particular specifier. In that case,
5857
// the specifier should be forwarded to the default resolver.
59-
},
58+
}
6059
Err(_err) => {
6160
// An error happened during the resolution. Falling back to the default
6261
// resolver isn't recommended.
63-
},
62+
}
6463
};
6564
}
6665

@@ -110,23 +109,24 @@ mod tests {
110109
match resolution {
111110
Ok(Resolution::Resolved(path, _subpath)) => {
112111
assert_eq!(path.to_string_lossy(), test.expected, "{}", test.it);
113-
},
112+
}
114113
Ok(Resolution::Skipped) => {
115114
assert_eq!(specifier, &test.expected, "{}", test.it);
116-
},
115+
}
117116
Err(err) => {
118117
assert_eq!(test.expected, "error!", "{}: {}", test.it, err.to_string());
119-
},
118+
}
120119
}
121-
122120
}
123121
}
124122
}
125123

126124
#[test]
127125
fn test_edge_case_one_pkg_cached_and_unplugged() {
128126
let manifest = {
129-
let manifest_json_path = std::env::current_dir().unwrap().join("./data/edge_case_manifest_state.json");
127+
let manifest_json_path = std::env::current_dir()
128+
.unwrap()
129+
.join("./data/edge_case_manifest_state.json");
130130
let manifest_content = fs::read_to_string(&manifest_json_path).unwrap();
131131
let mut manifest = serde_json::from_str::<Manifest>(&manifest_content).unwrap();
132132
init_pnp_manifest(&mut manifest, manifest_json_path);
@@ -149,4 +149,34 @@ mod tests {
149149
}
150150
}
151151
}
152+
153+
#[test]
154+
fn test_parse_single_package_name() {
155+
let parsed = parse_bare_identifier("pkg");
156+
assert_eq!(parsed, Ok(("pkg".to_string(), None)));
157+
}
158+
159+
#[test]
160+
fn test_parse_scoped_package_name() {
161+
let parsed = parse_bare_identifier("@scope/pkg");
162+
assert_eq!(parsed, Ok(("@scope/pkg".to_string(), None)));
163+
}
164+
165+
#[test]
166+
fn test_parse_package_name_with_long_subpath() {
167+
let parsed = parse_bare_identifier("pkg/a/b/c/index.js");
168+
assert_eq!(
169+
parsed,
170+
Ok(("pkg".to_string(), Some("a/b/c/index.js".to_string())))
171+
);
172+
}
173+
174+
#[test]
175+
fn test_parse_scoped_package_with_long_subpath() {
176+
let parsed = parse_bare_identifier("@scope/pkg/a/b/c/index.js");
177+
assert_eq!(
178+
parsed,
179+
Ok(("@scope/pkg".to_string(), Some("a/b/c/index.js".to_string())))
180+
);
181+
}
152182
}

0 commit comments

Comments
 (0)