Skip to content

Commit 2b88ada

Browse files
committed
feat: parse peerDependencies and peerDependenciesMeta in entries
1 parent d09eaeb commit 2b88ada

File tree

2 files changed

+233
-34
lines changed

2 files changed

+233
-34
lines changed

src/lib.rs

Lines changed: 146 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,16 @@ pub enum Generator {
4646
/// yarn.lock entry.
4747
/// It only shows the name of the dependency and the version.
4848
#[derive(Debug, PartialEq, Eq, Default)]
49+
#[non_exhaustive]
4950
pub struct Entry<'a> {
5051
pub name: &'a str,
5152
pub version: &'a str,
5253
pub resolved: &'a str,
5354
pub integrity: &'a str,
5455
pub dependencies: Vec<(&'a str, &'a str)>,
5556
pub optional_dependencies: Vec<(&'a str, &'a str)>,
57+
pub peer_dependencies: Vec<(&'a str, &'a str)>,
58+
pub peer_dependencies_meta: Vec<(&'a str, PeerMeta)>,
5659
pub descriptors: Vec<(&'a str, &'a str)>,
5760
}
5861

@@ -190,6 +193,8 @@ enum EntryItem<'a> {
190193
Resolved(&'a str),
191194
Dependencies(Vec<(&'a str, &'a str)>),
192195
OptionalDependencies(Vec<(&'a str, &'a str)>),
196+
PeerDependencies(Vec<(&'a str, &'a str)>),
197+
PeersMeta(Vec<(&'a str, PeerMeta)>),
193198
Integrity(&'a str),
194199
Unknown(&'a str),
195200
}
@@ -220,9 +225,9 @@ fn entry_item(input: &str) -> Res<&str, EntryItem<'_>> {
220225
alt((
221226
entry_version,
222227
parse_dependencies,
223-
parse_optional_dependencies,
224228
integrity,
225229
entry_resolved,
230+
parse_peers_meta,
226231
unknown_line,
227232
))
228233
.parse(input)
@@ -243,6 +248,8 @@ fn parse_entry(input: &str) -> Res<&str, Entry<'_>> {
243248
let mut resolved = "";
244249
let mut dependencies = Vec::new();
245250
let mut optional_dependencies = Vec::new();
251+
let mut peer_dependencies = Vec::new();
252+
let mut peer_dependencies_meta = Vec::new();
246253
let mut integrity = "";
247254

248255
for ei in entry_items {
@@ -251,7 +258,9 @@ fn parse_entry(input: &str) -> Res<&str, Entry<'_>> {
251258
EntryItem::Resolved(r) => resolved = r,
252259
EntryItem::Dependencies(d) => dependencies = d,
253260
EntryItem::OptionalDependencies(d) => optional_dependencies = d,
261+
EntryItem::PeerDependencies(d) => peer_dependencies = d,
254262
EntryItem::Integrity(c) => integrity = c,
263+
EntryItem::PeersMeta(m) => peer_dependencies_meta = m,
255264
EntryItem::Unknown(_) => (),
256265
}
257266
}
@@ -272,6 +281,8 @@ fn parse_entry(input: &str) -> Res<&str, Entry<'_>> {
272281
integrity,
273282
dependencies,
274283
optional_dependencies,
284+
peer_dependencies,
285+
peer_dependencies_meta,
275286
descriptors,
276287
},
277288
))
@@ -283,7 +294,16 @@ fn dependency_version(input: &str) -> Res<&str, &str> {
283294
}
284295

285296
fn parse_dependencies(input: &str) -> Res<&str, EntryItem<'_>> {
286-
let (input, (indent, _, _)) = (space1, tag("dependencies:"), line_ending).parse(input)?;
297+
let (input, (indent, key, _)) = (
298+
space1,
299+
alt((
300+
tag("dependencies:"),
301+
tag("optionalDependencies:"),
302+
tag("peerDependencies:"),
303+
)),
304+
line_ending,
305+
)
306+
.parse(input)?;
287307

288308
let dependencies_parser = many1(move |i| {
289309
(
@@ -300,29 +320,79 @@ fn parse_dependencies(input: &str) -> Res<&str, EntryItem<'_>> {
300320
});
301321
context("dependencies", dependencies_parser)
302322
.parse(input)
303-
.map(|(i, res)| (i, EntryItem::Dependencies(res)))
323+
.map(|(i, res)| {
324+
(
325+
i,
326+
match key {
327+
"dependencies:" => EntryItem::Dependencies(res),
328+
"optionalDependencies:" => EntryItem::OptionalDependencies(res),
329+
"peerDependencies:" => EntryItem::PeerDependencies(res),
330+
_ => unreachable!(),
331+
},
332+
)
333+
})
304334
}
305335

306-
fn parse_optional_dependencies(input: &str) -> Res<&str, EntryItem<'_>> {
307-
let (input, (indent, _, _)) =
308-
(space1, tag("optionalDependencies:"), line_ending).parse(input)?;
336+
#[derive(Debug, PartialEq, Eq, Default)]
337+
#[non_exhaustive]
338+
pub struct PeerMeta {
339+
pub optional: Option<bool>,
340+
}
309341

310-
let optional_dependencies_parser = many1(move |i| {
311-
(
312-
tag(indent), // indented as much as the parent...
313-
space1, // ... plus extra indentation
314-
is_not(": "), // package name
315-
one_of(": "),
316-
space0,
317-
dependency_version, // version
318-
alt((line_ending, space0)), // newline or space
319-
)
320-
.parse(i)
321-
.map(|(i, (_, _, p, _, _, v, _))| (i, (p.trim_matches('"'), v)))
322-
});
323-
context("optionalDependencies", optional_dependencies_parser)
342+
fn parse_peers_meta(input: &str) -> Res<&str, EntryItem<'_>> {
343+
let (input, indent_top) = space1(input)?;
344+
let (input, _) = (tag("peerDependenciesMeta:"), line_ending).parse(input)?;
345+
many1(|i| peers_meta_dep(i, indent_top))
346+
.parse(input)
347+
.map(|(i, pm)| (i, EntryItem::PeersMeta(pm)))
348+
}
349+
350+
fn peers_meta_dep<'a>(input: &'a str, indent_top: &'a str) -> Res<&'a str, (&'a str, PeerMeta)> {
351+
let (input, indent_dep) = recognize((tag(indent_top), space1)).parse(input)?;
352+
let (input, dep_name) = take_until(":")(input)?;
353+
let (input, _) = (tag(":"), line_ending).parse(input)?;
354+
many1(|i| peers_meta_dep_prop(i, indent_dep))
324355
.parse(input)
325-
.map(|(i, res)| (i, EntryItem::OptionalDependencies(res)))
356+
.and_then(|(i, props)| {
357+
let mut meta = PeerMeta { optional: None };
358+
for (prop_key, prop_val) in props {
359+
#[allow(clippy::single_match)]
360+
match prop_key {
361+
"optional" => {
362+
meta.optional = Some(match prop_val {
363+
"true" => true,
364+
"false" => false,
365+
_ => {
366+
return Err(nom::Err::Failure(VerboseError::from_error_kind(
367+
"bool property not 'true' or 'false'",
368+
nom::error::ErrorKind::Fail,
369+
)))
370+
}
371+
})
372+
}
373+
_ => {}
374+
}
375+
}
376+
Ok((i, (dep_name, meta)))
377+
})
378+
}
379+
380+
fn peers_meta_dep_prop<'a>(
381+
input: &'a str,
382+
indent_dep: &'a str,
383+
) -> Res<&'a str, (&'a str, &'a str)> {
384+
let (input, _) = recognize((tag(indent_dep), space1)).parse(input)?;
385+
let (input, (prop_key, _, prop_val)) = (
386+
take_until(":"),
387+
tag(": "),
388+
map(take_till_line_end, |v| {
389+
v.strip_suffix("\r\n")
390+
.or_else(|| v.strip_suffix("\n"))
391+
.unwrap()
392+
}),
393+
)
394+
.parse(input)?;
395+
Ok((input, (prop_key, prop_val)))
326396
}
327397

328398
/**
@@ -626,7 +696,8 @@ cli-table3@~0.6.1:
626696
descriptors: vec![("cli-table3", "~0.6.1")],
627697
dependencies: vec![("string-width", "^4.2.0")],
628698
optional_dependencies: vec![("@colors/colors", "1.5.0")],
629-
integrity: "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ=="
699+
integrity: "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==",
700+
..Default::default()
630701
},
631702
Entry {
632703
name: "@babel/helper-validator-identifier",
@@ -769,6 +840,7 @@ __metadata:
769840
resolved: "@babel/plugin-transform-for-of@npm:7.16.7",
770841
descriptors: vec![("@babel/plugin-transform-for-of", "npm:^7.12.1")],
771842
dependencies: vec![("@babel/helper-plugin-utils", "^7.16.7")],
843+
peer_dependencies: vec![("@babel/core", "^7.0.0-0")],
772844
integrity: "35c9264ee4bef814818123d70afe8b2f0a85753a0a9dc7b73f93a71cadc5d7de852f1a3e300a7c69a491705805704611de1e2ccceb5686f7828d6bca2e5a7306",
773845
..Default::default()
774846
},
@@ -1183,12 +1255,20 @@ __metadata:
11831255
"#,
11841256
EntryItem::Dependencies(vec![("foo", "1.0 || 2.0"), ("bar", "0.3-alpha1")]),
11851257
);
1258+
1259+
assert(
1260+
r#" peerDependencies:
1261+
foo: 1.0 || 2.0
1262+
"bar": "0.3-alpha1"
1263+
"#,
1264+
EntryItem::PeerDependencies(vec![("foo", "1.0 || 2.0"), ("bar", "0.3-alpha1")]),
1265+
);
11861266
}
11871267

11881268
#[test]
11891269
fn parse_optional_dependencies_work() {
11901270
fn assert(input: &str, expect: EntryItem) {
1191-
let res = parse_optional_dependencies(input).unwrap();
1271+
let res = parse_dependencies(input).unwrap();
11921272
assert_eq!(res.1, expect);
11931273
}
11941274

@@ -1270,17 +1350,49 @@ __metadata:
12701350
let res = parse_str(&content).unwrap();
12711351

12721352
assert_eq!(
1273-
res.entries.last().unwrap(),
1274-
&Entry {
1275-
name: "node-semver",
1276-
version: "7.6.3",
1277-
resolved: "ssh://[email protected]/npm/node-semver.git#0a12d6c7debb1dc82d8645c770e77c47bac5e1ea",
1278-
integrity: "",
1279-
dependencies: vec![],
1280-
descriptors: vec![(
1281-
"node-semver",
1282-
"ssh://[email protected]/npm/node-semver.git#semver:^7.5.0"
1283-
)],
1353+
res.entries.last().unwrap(),
1354+
&Entry {
1355+
name: "node-semver",
1356+
version: "7.6.3",
1357+
resolved: "ssh://[email protected]/npm/node-semver.git#0a12d6c7debb1dc82d8645c770e77c47bac5e1ea",
1358+
integrity: "",
1359+
dependencies: vec![],
1360+
descriptors: vec![(
1361+
"node-semver",
1362+
"ssh://[email protected]/npm/node-semver.git#semver:^7.5.0"
1363+
)],
1364+
..Default::default()
1365+
}
1366+
);
1367+
}
1368+
1369+
#[test]
1370+
fn supports_peer_dependencies() {
1371+
let content = std::fs::read_to_string("tests/peer_dependencies/yarn.lock").unwrap();
1372+
let res = parse_str(&content).unwrap();
1373+
1374+
assert_eq!(
1375+
res.entries[4],
1376+
Entry {
1377+
name: "react-router",
1378+
version: "7.2.0",
1379+
resolved: "react-router@npm:7.2.0",
1380+
descriptors: vec![("react-router", "npm:^7.2.0")],
1381+
integrity: "05c79d86639f146aafc64351bb042acd785dbb69c7874ad8e0a3f5f3e70890b1b3ee07d0e18f8cebaffd62bca47e58d0645b07d1cc428a73ba449ce378cbef01",
1382+
dependencies: vec![
1383+
("@types/cookie", "^0.6.0"),
1384+
("cookie", "^1.0.1"),
1385+
("set-cookie-parser", "^2.6.0"),
1386+
("turbo-stream", "2.4.0"),
1387+
],
1388+
peer_dependencies: vec![
1389+
("react", ">=18"),
1390+
("react-dom", ">=18"),
1391+
],
1392+
peer_dependencies_meta: vec![("react-dom", PeerMeta {
1393+
optional: Some(true),
1394+
..Default::default()
1395+
})],
12841396
..Default::default()
12851397
}
12861398
);

tests/peer_dependencies/yarn.lock

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# This file is generated by running "yarn install" inside your project.
2+
# Manual changes might be lost - proceed with caution!
3+
4+
__metadata:
5+
version: 4
6+
cacheKey: 7
7+
8+
"@chastelock/testcase@workspace:.":
9+
version: 0.0.0-use.local
10+
resolution: "@chastelock/testcase@workspace:."
11+
dependencies:
12+
react: ^19.0.0
13+
react-dom: ^19.0.0
14+
react-router: ^7.2.0
15+
languageName: unknown
16+
linkType: soft
17+
18+
"@types/cookie@npm:^0.6.0":
19+
version: 0.6.0
20+
resolution: "@types/cookie@npm:0.6.0"
21+
checksum: f4aa264a5af8c63cbb78c4c5de4743ffca9120af6c47ec15d77da97c1caf1cde381776b5c57a03fcd251eeaed81de684a437f3c4d60f7492b4982e0d0f7c99fa
22+
languageName: node
23+
linkType: hard
24+
25+
"cookie@npm:^1.0.1":
26+
version: 1.0.2
27+
resolution: "cookie@npm:1.0.2"
28+
checksum: b0346db1a20fdff372728e55c13d5a4ea5b5c2b06ada0aa8817b2bcec7884eba01484f59e37fec8830d3db2e9178e7ff84eb13e183c87b4ccb3802d61ec81bf2
29+
languageName: node
30+
linkType: hard
31+
32+
"react-dom@npm:^19.0.0":
33+
version: 19.0.0
34+
resolution: "react-dom@npm:19.0.0"
35+
dependencies:
36+
scheduler: ^0.25.0
37+
peerDependencies:
38+
react: ^19.0.0
39+
checksum: 9bd4eef5c92fd3996b4a84ef03e4c03f0b526a574fe24042904470bcdf1304a96e3e46ca8cea89fede3382e8aae4479e1b52649060d1bc7faa6d9dbdcf6ee2c1
40+
languageName: node
41+
linkType: hard
42+
43+
"react-router@npm:^7.2.0":
44+
version: 7.2.0
45+
resolution: "react-router@npm:7.2.0"
46+
dependencies:
47+
"@types/cookie": ^0.6.0
48+
cookie: ^1.0.1
49+
set-cookie-parser: ^2.6.0
50+
turbo-stream: 2.4.0
51+
peerDependencies:
52+
react: ">=18"
53+
react-dom: ">=18"
54+
peerDependenciesMeta:
55+
react-dom:
56+
optional: true
57+
checksum: 05c79d86639f146aafc64351bb042acd785dbb69c7874ad8e0a3f5f3e70890b1b3ee07d0e18f8cebaffd62bca47e58d0645b07d1cc428a73ba449ce378cbef01
58+
languageName: node
59+
linkType: hard
60+
61+
"react@npm:^19.0.0":
62+
version: 19.0.0
63+
resolution: "react@npm:19.0.0"
64+
checksum: 945974326f0c9460c61f85bb4e3029a476302dd9d7262ae252bd9b39496d655315d1bcf9f376206d50f492b39b35b466a29fbd8003f611e4ab9dc407233cfae7
65+
languageName: node
66+
linkType: hard
67+
68+
"scheduler@npm:^0.25.0":
69+
version: 0.25.0
70+
resolution: "scheduler@npm:0.25.0"
71+
checksum: f6898ebe560ae91938c1b533956e510ee88d3ef861c8af006841ec4b477fb0ee194de8fd02642ad824e11c126d4fd6309c5ed3094aae033aacc5e2b517bfc222
72+
languageName: node
73+
linkType: hard
74+
75+
"set-cookie-parser@npm:^2.6.0":
76+
version: 2.7.1
77+
resolution: "set-cookie-parser@npm:2.7.1"
78+
checksum: e026e0a355e1d0462cfb54eb0f429cd167c42da0829396c705469b30c887e8ccb3d32faa22b7823283d21cb5470df67980bc507140bf4ea2c4350083b5102b84
79+
languageName: node
80+
linkType: hard
81+
82+
"turbo-stream@npm:2.4.0":
83+
version: 2.4.0
84+
resolution: "turbo-stream@npm:2.4.0"
85+
checksum: 27b8ca0bfe8ae7f00ca667fef6cf603517a1261ccf8db5f48c0e9e1b05a3ce4ccbd0639a2e4d0e7fe181d603ecc1d06bdf919fec73f53260652d8c651218f1f9
86+
languageName: node
87+
linkType: hard

0 commit comments

Comments
 (0)