1use nom::IResult;
2use nom::{
3 Parser,
4 branch::alt,
5 bytes::complete::{is_not, tag, take_until, take_while, take_while1},
6 character::complete::{char, newline, space0, space1},
7 combinator::{map, opt, recognize},
8 multi::fold_many0,
9 sequence::{delimited, preceded, terminated},
10};
11use smallvec::SmallVec;
12
13use crate::ident::{Descriptor, Ident};
14use crate::locator::Locator;
15use crate::lockfile::{
16 Entry, Lockfile, parse_constraints, parse_metadata, parse_resolutions, parse_yarn_header,
17};
18use crate::metadata::{DependencyMeta, PeerDependencyMeta};
19use crate::package::{LinkType, Package};
20
21fn parse_entry(input: &str) -> IResult<&str, Entry<'_>> {
23 map(parse_package_entry, |(descriptors, package)| {
24 Entry::new(descriptors, package)
25 })
26 .parse(input)
27}
28
29pub fn parse_lockfile(file_contents: &str) -> IResult<&str, Lockfile<'_>> {
31 let (rest, (_, _)) = parse_yarn_header(file_contents)?;
32 let (rest, metadata) = parse_metadata(rest)?;
33
34 let (rest, _) = opt(newline).parse(rest)?;
36
37 let (rest, resolutions) = match parse_resolutions(rest) {
39 Ok((rest2, r)) => (rest2, Some(r)),
40 Err(_) => (rest, None),
41 };
42
43 let (rest, constraints) = match parse_constraints(rest) {
44 Ok((rest2, c)) => (rest2, Some(c)),
45 Err(_) => (rest, None),
46 };
47
48 let (rest, entries) = fold_many0(parse_entry, Vec::new, |mut entries, entry| {
51 entries.push(entry);
52 entries
53 })
54 .parse(rest)?;
55
56 let (rest, _) =
59 take_while(|c: char| c == '`' || c == ';' || c == '\n' || c == ' ' || c == '\t' || c == '\r')
60 .parse(rest)?;
61
62 Ok((
63 rest,
64 Lockfile {
65 metadata,
66 entries,
67 resolutions,
68 constraints,
69 },
70 ))
71}
72
73pub fn parse_package_entry(
88 input: &str,
89) -> IResult<&str, (SmallVec<[Descriptor<'_>; 4]>, Package<'_>)> {
90 let (rest, descriptors) = parse_descriptor_line(input)?;
91 let (rest, _) = newline.parse(rest)?; let (rest, package) = parse_package_properties(rest)?;
93
94 Ok((rest, (descriptors, package)))
95}
96
97pub fn parse_descriptor_line(input: &str) -> IResult<&str, SmallVec<[Descriptor<'_>; 4]>> {
100 let (rest, has_line_wrap_marker) = opt(tag("? ")).parse(input)?;
102 let is_wrapped_line = has_line_wrap_marker.is_some();
103
104 let (rest, descriptor_string) = if is_wrapped_line {
107 alt((
109 delimited(
111 char('"'),
112 take_until("\""),
113 terminated(char('"'), preceded(newline, char(':'))),
114 ),
115 delimited(char('"'), take_until("\":"), tag("\":")), terminated(take_until(":"), char(':')), ))
118 .parse(rest)?
119 } else {
120 alt((
122 delimited(char('"'), take_until("\":"), tag("\":")), terminated(take_until(":"), char(':')), ))
125 .parse(rest)?
126 };
127
128 let (remaining, first_data) = parse_single_descriptor(descriptor_string)?;
130
131 let (remaining, additional_data) = fold_many0(
134 preceded((space0, char(','), space0), parse_single_descriptor),
135 SmallVec::<[_; 3]>::new,
136 |mut acc, data| {
137 acc.push(data);
138 acc
139 },
140 )
141 .parse(remaining)?;
142
143 if !remaining.is_empty() {
144 return Err(nom::Err::Error(nom::error::Error::new(
147 remaining,
148 nom::error::ErrorKind::Complete,
149 )));
150 }
151
152 let mut descriptors: SmallVec<[Descriptor<'_>; 4]> =
155 SmallVec::with_capacity(1 + additional_data.len());
156 descriptors.push(convert_to_descriptor(first_data));
157 for data in additional_data {
158 descriptors.push(convert_to_descriptor(data));
159 }
160
161 Ok((rest, descriptors))
162}
163
164#[inline]
167fn convert_to_descriptor<'a>((name_part, full_range): (&'a str, &'a str)) -> Descriptor<'a> {
168 let ident = parse_name_to_ident(name_part);
169 Descriptor::new(ident, full_range)
170}
171
172fn parse_single_descriptor(input: &str) -> IResult<&str, (&str, &str)> {
176 let (after_name, name_part) = parse_package_name(input)?;
178
179 let (after_at, _) = char('@').parse(after_name)?;
181
182 let (remaining, full_range) = take_while1(|c: char| c != ',' && c != '"').parse(after_at)?;
184
185 let full_range = full_range.trim_end();
187
188 Ok((remaining, (name_part, full_range)))
189}
190
191#[inline]
193fn parse_name_to_ident(name_part: &str) -> Ident<'_> {
194 name_part.strip_prefix('@').map_or_else(
195 || Ident::new(None, name_part),
196 |stripped| {
197 stripped.split_once('/').map_or_else(
199 || Ident::new(None, name_part),
201 |(scope_without_at, name)| {
202 let scope_end = 1 + scope_without_at.len();
206 let scope = &name_part[..scope_end];
207 Ident::new(Some(scope), name)
208 },
209 )
210 },
211 )
212}
213
214fn parse_package_name(input: &str) -> IResult<&str, &str> {
216 alt((
217 recognize((
219 char('@'),
220 take_while1(|c: char| c.is_alphanumeric() || c == '-' || c == '_' || c == '.'),
221 char('/'),
222 take_while1(|c: char| c.is_alphanumeric() || c == '-' || c == '_' || c == '.'),
223 )),
224 take_while1(|c: char| c.is_alphanumeric() || c == '-' || c == '_' || c == '.'),
226 ))
227 .parse(input)
228}
229
230pub fn parse_package_properties(input: &str) -> IResult<&str, Package<'_>> {
233 let (rest, package) = fold_many0(
235 parse_property_line,
236 || Package::new("unknown", LinkType::Hard),
237 |mut package, property_value| {
238 match property_value {
239 PropertyValue::Simple(key, value) => match key {
240 "version" => {
241 package.version = Some(value.trim_matches('"'));
242 }
243 "resolution" => {
244 let raw = value.trim_matches('"');
245 let at_index = if raw.starts_with('@') {
249 raw
251 .find('/')
252 .and_then(|slash| raw[slash..].find('@').map(|i| slash + i))
253 } else {
254 raw.find('@')
255 };
256 if let Some(at_index) = at_index {
257 let (name_part, reference_with_at) = raw.split_at(at_index);
258 let ident = parse_name_to_ident(name_part);
259 let reference = reference_with_at.trim_start_matches('@');
261 package.resolution_locator = Some(Locator::new(ident, reference));
262 }
263 package.resolution = Some(raw);
264 }
265 "languageName" => {
266 package.language_name = value;
267 }
268 "linkType" => {
269 package.link_type =
270 LinkType::try_from(value).unwrap_or_else(|()| panic!("Invalid link type: {value}"));
271 }
272 "checksum" => {
273 package.checksum = Some(value);
274 }
275 "conditions" => {
276 package.conditions = Some(value);
277 }
278 _ => {
279 panic!("Unknown property encountered in package entry: {key}");
280 }
281 },
282 PropertyValue::Dependencies(dependencies) => {
283 for (dep_name, dep_range) in dependencies {
285 let ident = parse_name_to_ident(dep_name);
286 let descriptor = Descriptor::new(ident, dep_range);
287 package.dependencies.insert(ident, descriptor);
288 }
289 }
290 PropertyValue::PeerDependencies(peer_dependencies) => {
291 for (dep_name, dep_range) in peer_dependencies {
293 let ident = parse_name_to_ident(dep_name);
294 let descriptor = Descriptor::new(ident, dep_range);
295 package.peer_dependencies.insert(ident, descriptor);
296 }
297 }
298 PropertyValue::Bin(binaries) => {
299 for (bin_name, bin_path) in binaries {
301 package.bin.insert(bin_name, bin_path);
302 }
303 }
304 PropertyValue::DependenciesMeta(meta) => {
305 for (dep_name, dep_meta) in meta {
307 let ident = parse_name_to_ident(dep_name);
308 package.dependencies_meta.insert(ident, Some(dep_meta));
309 }
310 }
311 PropertyValue::PeerDependenciesMeta(meta) => {
312 for (dep_name, dep_meta) in meta {
314 let ident = parse_name_to_ident(dep_name);
315 package.peer_dependencies_meta.insert(ident, dep_meta);
316 }
317 }
318 }
319 package
320 },
321 )
322 .parse(input)?;
323
324 let (rest, ()) = fold_many0(
326 alt((tag("\n"), tag(" "), tag("\t"), tag("\r"))),
327 || (),
328 |(), _| (),
329 )
330 .parse(rest)?;
331
332 Ok((rest, package))
333}
334
335fn parse_property_line(input: &str) -> IResult<&str, PropertyValue<'_>> {
337 if let Ok((rest, (key, value))) = parse_simple_property(input) {
339 return Ok((rest, PropertyValue::Simple(key, value)));
340 }
341
342 if let Ok((rest, dependencies)) = parse_dependencies_block(input) {
344 return Ok((rest, PropertyValue::Dependencies(dependencies)));
345 }
346
347 if let Ok((rest, peer_dependencies)) = parse_peer_dependencies_block(input) {
349 return Ok((rest, PropertyValue::PeerDependencies(peer_dependencies)));
350 }
351
352 if let Ok((rest, binaries)) = parse_bin_block(input) {
354 return Ok((rest, PropertyValue::Bin(binaries)));
355 }
356
357 if let Ok((rest, meta)) = parse_dependencies_meta_block(input) {
359 return Ok((rest, PropertyValue::DependenciesMeta(meta)));
360 }
361
362 if let Ok((rest, meta)) = parse_peer_dependencies_meta_block(input) {
364 return Ok((rest, PropertyValue::PeerDependenciesMeta(meta)));
365 }
366
367 Err(nom::Err::Error(nom::error::Error::new(
369 input,
370 nom::error::ErrorKind::Alt,
371 )))
372}
373
374#[derive(Debug)]
376enum PropertyValue<'a> {
377 Simple(&'a str, &'a str),
378 Dependencies(Vec<(&'a str, &'a str)>),
379 PeerDependencies(Vec<(&'a str, &'a str)>),
380 Bin(Vec<(&'a str, &'a str)>),
381 DependenciesMeta(Vec<(&'a str, DependencyMeta)>),
382 PeerDependenciesMeta(Vec<(&'a str, PeerDependencyMeta)>),
383}
384
385pub fn parse_simple_property(input: &str) -> IResult<&str, (&str, &str)> {
387 let (rest, (_, key, _, _, value, _)) = (
388 tag(" "), take_while1(|c: char| c.is_alphanumeric() || c == '_'),
390 char(':'),
391 space1,
392 is_not("\r\n"),
393 opt(newline),
394 )
395 .parse(input)?;
396
397 Ok((rest, (key, value)))
398}
399
400fn parse_dependencies_block(input: &str) -> IResult<&str, Vec<(&str, &str)>> {
402 let (rest, (_, _, dependencies)) = (
403 tag(" dependencies:"),
404 newline,
405 fold_many0(parse_dependency_line, Vec::new, |mut acc, item| {
406 acc.push(item);
407 acc
408 }),
409 )
410 .parse(input)?;
411
412 Ok((rest, dependencies))
413}
414
415fn parse_peer_dependencies_block(input: &str) -> IResult<&str, Vec<(&str, &str)>> {
417 let (rest, (_, _, peer_dependencies)) = (
418 tag(" peerDependencies:"),
419 newline,
420 fold_many0(parse_dependency_line, Vec::new, |mut acc, item| {
421 acc.push(item);
422 acc
423 }),
424 )
425 .parse(input)?;
426
427 Ok((rest, peer_dependencies))
428}
429
430fn parse_bin_block(input: &str) -> IResult<&str, Vec<(&str, &str)>> {
432 let (rest, (_, _, binaries)) = (
433 tag(" bin:"),
434 newline,
435 fold_many0(parse_bin_line, Vec::new, |mut acc, item| {
436 acc.push(item);
437 acc
438 }),
439 )
440 .parse(input)?;
441
442 Ok((rest, binaries))
443}
444
445fn parse_dependencies_meta_block(input: &str) -> IResult<&str, Vec<(&str, DependencyMeta)>> {
447 let (rest, (_, _, meta)) = (
448 tag(" dependenciesMeta:"),
449 newline,
450 fold_many0(
451 alt((
452 parse_dependency_meta_entry_inline,
453 parse_dependency_meta_entry_nested,
454 )),
455 Vec::new,
456 |mut acc, item| {
457 acc.push(item);
458 acc
459 },
460 ),
461 )
462 .parse(input)?;
463
464 Ok((rest, meta))
465}
466
467fn parse_peer_dependencies_meta_block(
469 input: &str,
470) -> IResult<&str, Vec<(&str, PeerDependencyMeta)>> {
471 let (rest, (_, _, meta)) = (
472 tag(" peerDependenciesMeta:"),
473 newline,
474 fold_many0(
475 alt((
476 parse_peer_dependency_meta_entry_inline,
477 parse_peer_dependency_meta_entry_nested,
478 )),
479 Vec::new,
480 |mut acc, item| {
481 acc.push(item);
482 acc
483 },
484 ),
485 )
486 .parse(input)?;
487
488 Ok((rest, meta))
489}
490
491fn parse_dependency_line(input: &str) -> IResult<&str, (&str, &str)> {
493 let (rest, (_, dep_name, _, _, dep_range, _)) = (
494 tag(" "),
495 alt((
496 delimited(
497 char('"'),
498 take_while1(|c: char| {
499 c.is_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '/' || c == '.'
500 }),
501 char('"'),
502 ),
503 take_while1(|c: char| {
504 c.is_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '/' || c == '.'
505 }),
506 )),
507 char(':'),
508 space1,
509 take_until("\n"),
510 opt(newline),
511 )
512 .parse(input)?;
513
514 let clean_range = dep_range.trim().trim_matches('"');
515 Ok((rest, (dep_name, clean_range)))
516}
517
518fn parse_bin_line(input: &str) -> IResult<&str, (&str, &str)> {
520 let (rest, (_, bin_name, _, _, bin_path, _)) = (
521 tag(" "),
522 take_while1(|c: char| {
523 c.is_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '/' || c == '.'
524 }),
525 char(':'),
526 space1,
527 is_not("\r\n"),
528 newline,
529 )
530 .parse(input)?;
531
532 let clean_path = bin_path.trim().trim_matches('"');
533 Ok((rest, (bin_name, clean_path)))
534}
535
536fn parse_dependency_meta_entry_inline(input: &str) -> IResult<&str, (&str, DependencyMeta)> {
538 let (rest, (_, dep_name, _, _, meta_content, _)) = (
539 tag(" "),
540 alt((
541 delimited(
542 char('"'),
543 take_while1(|c: char| {
544 c.is_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '/' || c == '.'
545 }),
546 char('"'),
547 ),
548 take_while1(|c: char| {
549 c.is_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '/' || c == '.'
550 }),
551 )),
552 char(':'),
553 space1,
554 parse_meta_object,
555 opt(newline),
556 )
557 .parse(input)?;
558
559 Ok((rest, (dep_name, meta_content)))
560}
561
562fn parse_dependency_meta_entry_nested(input: &str) -> IResult<&str, (&str, DependencyMeta)> {
564 let (rest, (_, dep_name, _, _, meta_content, _)) = (
565 tag(" "),
566 alt((
567 delimited(
568 char('"'),
569 take_while1(|c: char| {
570 c.is_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '/' || c == '.'
571 }),
572 char('"'),
573 ),
574 take_while1(|c: char| {
575 c.is_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '/' || c == '.'
576 }),
577 )),
578 char(':'),
579 newline,
580 parse_dependency_meta_object_indented,
581 opt(newline),
582 )
583 .parse(input)?;
584
585 Ok((rest, (dep_name, meta_content)))
586}
587
588fn parse_dependency_meta_object_indented(input: &str) -> IResult<&str, DependencyMeta> {
590 fn parse_indented_bool_line<'a>(
591 prop: &'static str,
592 ) -> impl Fn(&'a str) -> IResult<&'a str, bool> {
593 move |input: &str| {
594 let (rest, (_, _, _, _, value, _)) = (
595 tag(" "),
596 tag(prop),
597 char(':'),
598 space1,
599 alt((tag("true"), tag("false"))),
600 newline,
601 )
602 .parse(input)?;
603 Ok((rest, value == "true"))
604 }
605 }
606
607 let init = || (None, None, None);
608 let (rest, (built, optional, unplugged)) = fold_many0(
609 alt((
610 map(parse_indented_bool_line("built"), |v| (Some(v), None, None)),
611 map(parse_indented_bool_line("optional"), |v| {
612 (None, Some(v), None)
613 }),
614 map(parse_indented_bool_line("unplugged"), |v| {
615 (None, None, Some(v))
616 }),
617 )),
618 init,
619 |(b_acc, o_acc, u_acc), (b, o, u)| (b.or(b_acc), o.or(o_acc), u.or(u_acc)),
620 )
621 .parse(input)?;
622
623 Ok((
624 rest,
625 DependencyMeta {
626 built,
627 optional,
628 unplugged,
629 },
630 ))
631}
632
633fn parse_peer_dependency_meta_entry_inline(
635 input: &str,
636) -> IResult<&str, (&str, PeerDependencyMeta)> {
637 let (rest, (_, dep_name, _, _, meta_content, _)) = (
638 tag(" "),
639 alt((
640 delimited(
641 char('"'),
642 take_while1(|c: char| {
643 c.is_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '/' || c == '.'
644 }),
645 char('"'),
646 ),
647 take_while1(|c: char| {
648 c.is_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '/' || c == '.'
649 }),
650 )),
651 char(':'),
652 space1,
653 parse_peer_meta_object,
654 opt(newline),
655 )
656 .parse(input)?;
657
658 Ok((rest, (dep_name, meta_content)))
659}
660
661fn parse_peer_dependency_meta_entry_nested(
663 input: &str,
664) -> IResult<&str, (&str, PeerDependencyMeta)> {
665 let (rest, (_, dep_name, _, _, meta_content, _)) = (
666 tag(" "),
667 alt((
668 delimited(
669 char('"'),
670 take_while1(|c: char| {
671 c.is_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '/' || c == '.'
672 }),
673 char('"'),
674 ),
675 take_while1(|c: char| {
676 c.is_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '/' || c == '.'
677 }),
678 )),
679 char(':'),
680 newline,
681 parse_peer_meta_object_indented,
682 opt(newline),
683 )
684 .parse(input)?;
685
686 Ok((rest, (dep_name, meta_content)))
687}
688
689fn parse_peer_meta_object(input: &str) -> IResult<&str, PeerDependencyMeta> {
691 let (rest, _) = char('{')(input)?;
692 let (rest, _) = space0(rest)?;
693 let (rest, optional) = parse_bool_property_inline("optional")(rest)?;
694 let (rest, _) = space0(rest)?;
695 let (rest, _) = char('}')(rest)?;
696
697 Ok((rest, PeerDependencyMeta { optional }))
698}
699
700fn parse_bool_property_inline(prop_name: &str) -> impl Fn(&str) -> IResult<&str, bool> + '_ {
702 move |input| {
703 let (rest, (_, _, _, value)) = (
704 tag(prop_name),
705 char(':'),
706 space1,
707 alt((tag("true"), tag("false"))),
708 )
709 .parse(input)?;
710
711 Ok((rest, value == "true"))
712 }
713}
714
715fn parse_peer_meta_object_indented(input: &str) -> IResult<&str, PeerDependencyMeta> {
717 let (rest, (_, _, _, optional, _)) = (
718 tag(" "),
719 tag("optional:"),
720 space1,
721 alt((tag("true"), tag("false"))),
722 newline,
723 )
724 .parse(input)?;
725
726 Ok((
727 rest,
728 PeerDependencyMeta {
729 optional: optional == "true",
730 },
731 ))
732}
733
734fn parse_meta_object(input: &str) -> IResult<&str, DependencyMeta> {
736 let (rest, _) = char('{')(input)?;
737 let (rest, _) = space0(rest)?;
738
739 let (rest, built) = opt(parse_bool_property_inline("built")).parse(rest)?;
740 let (rest, _) = opt((space0, char(','), space0)).parse(rest)?;
741
742 let (rest, optional) = opt(parse_bool_property_inline("optional")).parse(rest)?;
743 let (rest, _) = opt((space0, char(','), space0)).parse(rest)?;
744
745 let (rest, unplugged) = opt(parse_bool_property_inline("unplugged")).parse(rest)?;
746 let (rest, _) = space0(rest)?;
747
748 let (rest, _) = char('}')(rest)?;
749
750 Ok((
751 rest,
752 DependencyMeta {
753 built,
754 optional,
755 unplugged,
756 },
757 ))
758}
759
760#[cfg(test)]
761mod tests {
762 use super::*;
763
764 #[test]
765 fn test_parse_dependency_line_simple() {
766 let input = " ms: 0.6.2\n";
767 let result = parse_dependency_line(input);
768 assert!(result.is_ok());
769 let (remaining, (dep_name, dep_range)) = result.unwrap();
770 assert_eq!(remaining, "");
771 assert_eq!(dep_name, "ms");
772 assert_eq!(dep_range, "0.6.2");
773 }
774
775 #[test]
776 fn test_parse_dependency_line_scoped_package() {
777 let input = " @babel/code-frame: ^7.12.11\n";
778 let result = parse_dependency_line(input);
779 assert!(result.is_ok());
780 let (remaining, (dep_name, dep_range)) = result.unwrap();
781 assert_eq!(remaining, "");
782 assert_eq!(dep_name, "@babel/code-frame");
783 assert_eq!(dep_range, "^7.12.11");
784 }
785
786 #[test]
787 fn test_parse_descriptor_line_simple() {
788 let input = r#""debug@npm:1.0.0":"#;
789 let result = parse_descriptor_line(input);
790 assert!(result.is_ok());
791 let (remaining, descriptors) = result.unwrap();
792 assert_eq!(remaining, "");
793 assert_eq!(descriptors.len(), 1);
794
795 let descriptor = &descriptors[0];
796 assert_eq!(descriptor.ident().name(), "debug");
797 assert_eq!(descriptor.ident().scope(), None);
798 }
799
800 #[test]
801 fn test_parse_descriptor_line_scoped_package() {
802 let input = r#""@babel/code-frame@npm:7.12.11":"#;
803 let result = parse_descriptor_line(input);
804 assert!(result.is_ok());
805 let (remaining, descriptors) = result.unwrap();
806 assert_eq!(remaining, "");
807 assert_eq!(descriptors.len(), 1);
808
809 let descriptor = &descriptors[0];
810 assert_eq!(descriptor.ident().name(), "code-frame");
811 assert_eq!(descriptor.ident().scope(), Some("@babel"));
812 }
813
814 #[test]
815 fn test_parse_package_properties_minimal() {
816 let input = r#" version: 1.0.0
817 resolution: "debug@npm:1.0.0"
818 languageName: node
819 linkType: hard
820"#;
821 let result = parse_package_properties(input);
822 assert!(result.is_ok());
823 let (remaining, package) = result.unwrap();
824 assert_eq!(remaining, "");
825 assert_eq!(package.version, Some("1.0.0"));
826 assert_eq!(package.resolution, Some("debug@npm:1.0.0"));
827 assert_eq!(package.language_name, "node");
828 assert_eq!(package.link_type, LinkType::Hard);
829 }
830
831 #[test]
832 fn test_parse_multiple_packages() {
833 let input = r#"# This file is generated by running "yarn install" inside your project.
834# Manual changes might be lost - proceed with caution!
835
836__metadata:
837 version: 8
838 cacheKey: 10
839
840"@actions/http-client@npm:^2.2.0":
841 version: 2.2.3
842 resolution: "@actions/http-client@npm:2.2.3"
843 languageName: node
844 linkType: hard
845
846"@actions/io@npm:^1.0.1, @actions/io@npm:^1.1.3":
847 version: 1.1.3
848 resolution: "@actions/io@npm:1.1.3"
849 languageName: node
850 linkType: hard
851"#;
852
853 let result = parse_lockfile(input);
854 assert!(result.is_ok());
855 let (remaining, lockfile) = result.unwrap();
856 assert_eq!(lockfile.entries.len(), 2);
857 assert!(remaining.is_empty());
858 }
859
860 #[test]
861 fn test_parse_descriptor_line_multi_descriptor() {
862 let input = r#""c@*, c@workspace:packages/c":"#;
863 let result = parse_descriptor_line(input);
864 assert!(result.is_ok());
865 let (remaining, descriptors) = result.unwrap();
866 assert_eq!(remaining, "");
867 assert_eq!(descriptors.len(), 2);
868
869 assert_eq!(descriptors[0].ident().name(), "c");
870 assert_eq!(descriptors[1].ident().name(), "c");
871 }
872
873 #[test]
874 fn test_parse_peer_dependencies_meta_real_format() {
875 let input = r" peerDependenciesMeta:
876 graphql-ws:
877 optional: true
878 react:
879 optional: true
880";
881
882 let result = parse_peer_dependencies_meta_block(input);
883 assert!(result.is_ok());
884 let (remaining, meta) = result.unwrap();
885 assert_eq!(meta.len(), 2);
886 assert!(remaining.is_empty());
887 }
888
889 #[test]
890 fn test_descriptor_range_includes_npm_protocol() {
891 let input = r#""debug@npm:^4.3.0":"#;
892 let result = parse_descriptor_line(input);
893 assert!(result.is_ok());
894 let (_, descriptors) = result.unwrap();
895 assert_eq!(descriptors.len(), 1);
896
897 let descriptor = &descriptors[0];
898 assert_eq!(descriptor.ident().name(), "debug");
899 assert_eq!(descriptor.range(), "npm:^4.3.0");
900 }
901
902 #[test]
903 fn test_descriptor_range_includes_npm_protocol_scoped() {
904 let input = r#""@babel/core@npm:^7.0.0":"#;
905 let result = parse_descriptor_line(input);
906 assert!(result.is_ok());
907 let (_, descriptors) = result.unwrap();
908 assert_eq!(descriptors.len(), 1);
909
910 let descriptor = &descriptors[0];
911 assert_eq!(descriptor.ident().name(), "core");
912 assert_eq!(descriptor.ident().scope(), Some("@babel"));
913 assert_eq!(descriptor.range(), "npm:^7.0.0");
914 }
915
916 #[test]
917 fn test_descriptor_range_no_protocol() {
918 let input = r#""c@*":"#;
919 let result = parse_descriptor_line(input);
920 assert!(result.is_ok());
921 let (_, descriptors) = result.unwrap();
922 assert_eq!(descriptors.len(), 1);
923
924 let descriptor = &descriptors[0];
925 assert_eq!(descriptor.ident().name(), "c");
926 assert_eq!(descriptor.range(), "*");
927 }
928
929 #[test]
930 fn test_descriptor_range_workspace_protocol() {
931 let input = r#""my-package@workspace:packages/my-package":"#;
932 let result = parse_descriptor_line(input);
933 assert!(result.is_ok());
934 let (_, descriptors) = result.unwrap();
935 assert_eq!(descriptors.len(), 1);
936
937 let descriptor = &descriptors[0];
938 assert_eq!(descriptor.ident().name(), "my-package");
939 assert_eq!(descriptor.range(), "workspace:packages/my-package");
940 }
941
942 #[test]
943 fn test_descriptor_range_multiple_with_npm_protocol() {
944 let input = r#""prompts@npm:^2.0.1, prompts@npm:^2.4.2":"#;
945 let result = parse_descriptor_line(input);
946 assert!(result.is_ok());
947 let (_, descriptors) = result.unwrap();
948 assert_eq!(descriptors.len(), 2);
949
950 assert_eq!(descriptors[0].ident().name(), "prompts");
951 assert_eq!(descriptors[0].range(), "npm:^2.0.1");
952
953 assert_eq!(descriptors[1].ident().name(), "prompts");
954 assert_eq!(descriptors[1].range(), "npm:^2.4.2");
955 }
956
957 #[test]
958 fn test_descriptor_range_mixed_protocols() {
959 let input = r#""c@*, c@workspace:packages/c":"#;
960 let result = parse_descriptor_line(input);
961 assert!(result.is_ok());
962 let (_, descriptors) = result.unwrap();
963 assert_eq!(descriptors.len(), 2);
964
965 assert_eq!(descriptors[0].ident().name(), "c");
966 assert_eq!(descriptors[0].range(), "*");
967
968 assert_eq!(descriptors[1].ident().name(), "c");
969 assert_eq!(descriptors[1].range(), "workspace:packages/c");
970 }
971
972 #[test]
973 fn test_descriptor_range_patch_protocol() {
974 let input = r#""is-odd@patch:is-odd@npm%3A3.0.1#~/.yarn/patches/is-odd.patch":"#;
975 let result = parse_descriptor_line(input);
976 assert!(result.is_ok());
977 let (_, descriptors) = result.unwrap();
978 assert_eq!(descriptors.len(), 1);
979
980 let descriptor = &descriptors[0];
981 assert_eq!(descriptor.ident().name(), "is-odd");
982 assert_eq!(
983 descriptor.range(),
984 "patch:is-odd@npm%3A3.0.1#~/.yarn/patches/is-odd.patch"
985 );
986 }
987
988 #[test]
989 fn test_descriptor_range_trims_trailing_whitespace() {
990 let input = r#""@oclif/screen@npm:^1.0.4 ":"#;
992 let result = parse_descriptor_line(input);
993 assert!(result.is_ok());
994 let (_, descriptors) = result.unwrap();
995 assert_eq!(descriptors.len(), 1);
996
997 let descriptor = &descriptors[0];
998 assert_eq!(descriptor.ident().name(), "screen");
999 assert_eq!(descriptor.ident().scope(), Some("@oclif"));
1000 assert_eq!(descriptor.range(), "npm:^1.0.4");
1002 }
1003}