1mod about;
2#[cfg(test)]
3mod about_scan_test;
4#[cfg(test)]
5mod about_test;
6mod alpine;
7#[cfg(test)]
8mod alpine_scan_test;
9mod arch;
10#[cfg(test)]
11mod arch_scan_test;
12#[cfg(test)]
13mod arch_test;
14mod autotools;
15#[cfg(test)]
16mod autotools_test;
17mod bazel;
18#[cfg(test)]
19mod bazel_module_test;
20#[cfg(test)]
21mod bazel_test;
22mod bower;
23#[cfg(test)]
24mod bower_scan_test;
25#[cfg(test)]
26mod bower_test;
27mod buck;
28#[cfg(test)]
29mod buck_test;
30mod bun_lock;
31#[cfg(test)]
32mod bun_lock_test;
33mod bun_lockb;
34#[cfg(test)]
35mod bun_lockb_test;
36mod cargo;
37mod cargo_lock;
38#[cfg(test)]
39mod cargo_lock_test;
40#[cfg(test)]
41mod cargo_scan_test;
42#[cfg(test)]
43mod cargo_test;
44mod chef;
45#[cfg(test)]
46mod chef_scan_test;
47#[cfg(test)]
48mod chef_test;
49mod citation;
50#[cfg(test)]
51mod citation_test;
52mod clojure;
53#[cfg(test)]
54mod clojure_test;
55#[cfg(test)]
56mod cocoapods_scan_test;
57pub(crate) mod compiled_binary;
58mod composer;
59#[cfg(test)]
60mod composer_scan_test;
61#[cfg(test)]
62mod composer_test;
63mod conan;
64mod conan_data;
65#[cfg(test)]
66mod conan_data_test;
67#[cfg(test)]
68mod conan_scan_test;
69#[cfg(test)]
70mod conan_test;
71mod conda;
72mod conda_meta_json;
73#[cfg(test)]
74mod conda_meta_json_test;
75#[cfg(test)]
76mod conda_scan_test;
77#[cfg(test)]
78mod conda_test;
79mod cpan;
80mod cpan_dist_ini;
81#[cfg(test)]
82mod cpan_dist_ini_test;
83mod cpan_makefile_pl;
84#[cfg(test)]
85mod cpan_makefile_pl_test;
86#[cfg(test)]
87mod cpan_scan_test;
88#[cfg(test)]
89mod cpan_test;
90mod cran;
91#[cfg(test)]
92mod cran_scan_test;
93#[cfg(test)]
94mod cran_test;
95mod dart;
96#[cfg(test)]
97mod dart_scan_test;
98#[cfg(test)]
99mod dart_test;
100mod debian;
101#[cfg(test)]
102mod debian_scan_test;
103#[cfg(test)]
104mod debian_test;
105mod deno;
106mod deno_lock;
107#[cfg(test)]
108mod deno_lock_test;
109#[cfg(test)]
110mod deno_scan_test;
111#[cfg(test)]
112mod deno_test;
113mod docker;
114#[cfg(test)]
115mod docker_scan_test;
116#[cfg(test)]
117mod docker_test;
118mod freebsd;
119#[cfg(test)]
120mod freebsd_scan_test;
121#[cfg(test)]
122mod freebsd_test;
123mod gitmodules;
124#[cfg(test)]
125mod gitmodules_scan_test;
126mod go;
127mod go_mod_graph;
128#[cfg(test)]
129mod go_scan_test;
130#[cfg(test)]
131mod go_test;
132#[cfg(test)]
133mod go_work_test;
134#[cfg(all(test, feature = "golden-tests"))]
135pub(crate) mod golden_test_utils;
136mod gradle;
137mod gradle_lock;
138#[cfg(test)]
139mod gradle_lock_test;
140mod gradle_module;
141#[cfg(test)]
142mod gradle_module_scan_test;
143#[cfg(test)]
144mod gradle_module_test;
145#[cfg(test)]
146mod gradle_scan_test;
147mod hackage;
148#[cfg(test)]
149mod hackage_scan_test;
150#[cfg(test)]
151mod hackage_test;
152mod haxe;
153#[cfg(test)]
154mod haxe_scan_test;
155#[cfg(test)]
156mod haxe_test;
157mod helm;
158#[cfg(test)]
159mod helm_scan_test;
160#[cfg(test)]
161mod helm_test;
162mod hex_lock;
163#[cfg(test)]
164mod hex_lock_test;
165mod julia;
166#[cfg(test)]
167mod julia_test;
168mod license_normalization;
169mod maven;
170#[cfg(test)]
171mod maven_scan_test;
172#[cfg(test)]
173mod maven_test;
174mod meson;
175#[cfg(test)]
176mod meson_scan_test;
177#[cfg(test)]
178mod meson_test;
179pub mod metadata;
180mod microsoft_update_manifest;
181#[cfg(test)]
182mod microsoft_update_manifest_test;
183mod misc;
184#[cfg(test)]
185mod misc_test;
186mod nix;
187#[cfg(test)]
188mod nix_scan_test;
189#[cfg(test)]
190mod nix_test;
191mod npm;
192mod npm_lock;
193#[cfg(test)]
194mod npm_lock_test;
195#[cfg(test)]
196mod npm_scan_test;
197#[cfg(test)]
198mod npm_test;
199mod npm_workspace;
200#[cfg(test)]
201mod npm_workspace_test;
202mod nuget;
203#[cfg(test)]
204mod nuget_scan_test;
205#[cfg(test)]
206mod nuget_test;
207mod opam;
208#[cfg(test)]
209mod opam_scan_test;
210mod os_release;
211#[cfg(test)]
212mod os_release_test;
213#[cfg(test)]
214mod osgi_test;
215mod pep508;
216mod pip_inspect_deplock;
217#[cfg(test)]
218mod pip_inspect_deplock_test;
219mod pipfile_lock;
220#[cfg(test)]
221mod pipfile_lock_test;
222mod pixi;
223#[cfg(test)]
224mod pixi_scan_test;
225#[cfg(test)]
226mod pixi_test;
227mod pnpm_lock;
228#[cfg(test)]
229mod pnpm_lock_test;
230mod podfile;
231mod podfile_lock;
232#[cfg(test)]
233mod podfile_lock_test;
234mod podspec;
235mod podspec_json;
236#[cfg(test)]
237mod podspec_json_test;
238mod poetry_lock;
239#[cfg(test)]
240mod poetry_lock_test;
241mod publiccode;
242#[cfg(test)]
243mod publiccode_test;
244mod pylock_toml;
245#[cfg(test)]
246mod pylock_toml_test;
247mod python;
248#[cfg(test)]
249mod python_scan_test;
250#[cfg(test)]
251mod python_test;
252mod readme;
253#[cfg(test)]
254mod readme_test;
255mod requirements_txt;
256#[cfg(test)]
257mod requirements_txt_test;
258pub(crate) mod rfc822;
259mod rpm_db;
260mod rpm_db_native;
261#[cfg(test)]
262mod rpm_db_scan_test;
263mod rpm_license_files;
264#[cfg(test)]
265mod rpm_license_files_test;
266mod rpm_mariner_manifest;
267#[cfg(test)]
268mod rpm_mariner_manifest_test;
269mod rpm_parser;
270#[cfg(test)]
271mod rpm_scan_test;
272mod rpm_specfile;
273#[cfg(test)]
274mod rpm_specfile_test;
275mod rpm_yumdb;
276mod ruby;
277#[cfg(test)]
278mod ruby_scan_test;
279#[cfg(test)]
280mod ruby_test;
281mod sbt;
282#[cfg(test)]
283mod sbt_test;
284#[cfg(test)]
285mod scan_test_utils;
286mod swift_manifest_json;
287#[cfg(test)]
288mod swift_manifest_json_test;
289mod swift_resolved;
290#[cfg(test)]
291mod swift_resolved_test;
292#[cfg(test)]
293mod swift_scan_test;
294mod swift_show_dependencies;
295#[cfg(test)]
296mod swift_show_dependencies_test;
297pub mod utils;
298mod uv_lock;
299#[cfg(test)]
300mod uv_lock_test;
301mod vcpkg;
302#[cfg(test)]
303mod vcpkg_scan_test;
304#[cfg(test)]
305mod vcpkg_test;
306pub(crate) mod windows_executable;
307#[cfg(test)]
308mod windows_executable_golden_test;
309mod yarn_lock;
310#[cfg(test)]
311mod yarn_lock_test;
312mod yarn_pnp;
313#[cfg(test)]
314mod yarn_pnp_test;
315
316#[cfg(all(test, feature = "golden-tests"))]
317mod golden_test;
318
319use std::cell::RefCell;
320use std::panic::{AssertUnwindSafe, catch_unwind};
321use std::path::Path;
322
323use crate::models::{PackageData, PackageType};
324use crate::parsers::license_normalization::finalize_package_declared_license_references;
325
326thread_local! {
327 static PARSER_DIAGNOSTIC_STACK: RefCell<Vec<Vec<String>>> = const { RefCell::new(Vec::new()) };
328}
329
330#[derive(Debug, Default)]
331pub struct ParsePackagesResult {
332 pub packages: Vec<PackageData>,
333 pub scan_errors: Vec<String>,
334}
335
336fn panic_payload_to_string(payload: &(dyn std::any::Any + Send)) -> String {
337 if let Some(message) = payload.downcast_ref::<&str>() {
338 (*message).to_string()
339 } else if let Some(message) = payload.downcast_ref::<String>() {
340 message.clone()
341 } else {
342 "unknown panic payload".to_string()
343 }
344}
345
346pub(crate) fn capture_parser_diagnostics<F>(
347 extract: F,
348 handler_name: &str,
349 path: &Path,
350) -> ParsePackagesResult
351where
352 F: FnOnce() -> Vec<PackageData>,
353{
354 PARSER_DIAGNOSTIC_STACK.with(|stack| {
355 stack.borrow_mut().push(Vec::new());
356 });
357
358 let extract_result = catch_unwind(AssertUnwindSafe(extract));
359 let mut scan_errors =
360 PARSER_DIAGNOSTIC_STACK.with(|stack| stack.borrow_mut().pop().unwrap_or_default());
361
362 match extract_result {
363 Ok(packages) => ParsePackagesResult {
364 packages: packages
365 .into_iter()
366 .map(|mut package| {
367 finalize_package_declared_license_references(&mut package);
368 package
369 })
370 .collect(),
371 scan_errors,
372 },
373 Err(payload) => {
374 scan_errors.push(format!(
375 "{} panicked while parsing {}: {}",
376 handler_name,
377 path.display(),
378 panic_payload_to_string(payload.as_ref())
379 ));
380 ParsePackagesResult {
381 packages: Vec::new(),
382 scan_errors,
383 }
384 }
385 }
386}
387
388pub(crate) fn record_parser_diagnostic(message: String) -> bool {
389 PARSER_DIAGNOSTIC_STACK.with(|stack| {
390 let mut stack = stack.borrow_mut();
391 let Some(active) = stack.last_mut() else {
392 return false;
393 };
394 active.push(message);
395 true
396 })
397}
398
399#[macro_export]
400macro_rules! parser_warn {
401 ($($arg:tt)*) => {{
402 let message = format!($($arg)*);
403 if !$crate::parsers::record_parser_diagnostic(message.clone()) {
404 log::warn!("{message}");
405 }
406 }};
407}
408
409pub trait PackageParser {
453 const PACKAGE_TYPE: PackageType;
455
456 fn extract_packages(path: &Path) -> Vec<PackageData>;
466
467 fn is_match(path: &Path) -> bool;
472
473 fn extract_first_package(path: &Path) -> PackageData {
476 Self::extract_packages(path)
477 .into_iter()
478 .map(|mut package| {
479 finalize_package_declared_license_references(&mut package);
480 package
481 })
482 .next()
483 .unwrap_or_default()
484 }
485}
486
487pub use self::about::AboutFileParser;
488pub use self::alpine::{AlpineApkParser, AlpineApkbuildParser, AlpineInstalledParser};
489pub use self::arch::{ArchPkginfoParser, ArchSrcinfoParser};
490pub use self::autotools::AutotoolsConfigureParser;
491pub use self::bazel::{BazelBuildParser, BazelModuleParser};
492pub use self::bower::BowerJsonParser;
493pub use self::buck::{BuckBuildParser, BuckMetadataBzlParser};
494pub use self::bun_lock::BunLockParser;
495pub use self::bun_lockb::BunLockbParser;
496pub use self::cargo::CargoParser;
497#[cfg_attr(not(test), allow(unused_imports))]
498pub use self::cargo_lock::CargoLockParser;
499pub use self::chef::{ChefMetadataJsonParser, ChefMetadataRbParser};
500pub use self::citation::CitationCffParser;
501pub use self::clojure::{ClojureDepsEdnParser, ClojureProjectCljParser};
502pub use self::composer::{ComposerJsonParser, ComposerLockParser};
503pub use self::conan::{ConanFilePyParser, ConanLockParser, ConanfileTxtParser};
504pub use self::conan_data::ConanDataParser;
505pub use self::conda::{CondaEnvironmentYmlParser, CondaMetaYamlParser};
506pub use self::conda_meta_json::CondaMetaJsonParser;
507pub use self::cpan::{CpanManifestParser, CpanMetaJsonParser, CpanMetaYmlParser};
508pub use self::cpan_dist_ini::CpanDistIniParser;
509pub use self::cpan_makefile_pl::CpanMakefilePlParser;
510pub use self::cran::CranParser;
511pub use self::dart::{PubspecLockParser, PubspecYamlParser};
512pub use self::debian::{
513 DebianControlInExtractedDebParser, DebianControlParser, DebianCopyrightParser, DebianDebParser,
514 DebianDebianTarParser, DebianDistrolessInstalledParser, DebianDscParser,
515 DebianInstalledListParser, DebianInstalledMd5sumsParser, DebianInstalledParser,
516 DebianMd5sumInPackageParser, DebianOrigTarParser,
517};
518pub use self::deno::DenoParser;
519pub use self::deno_lock::DenoLockParser;
520pub use self::docker::DockerfileParser;
521pub use self::freebsd::FreebsdCompactManifestParser;
522pub use self::gitmodules::GitmodulesParser;
523pub use self::go::{GoModParser, GoSumParser, GoWorkParser, GodepsParser};
524pub use self::go_mod_graph::GoModGraphParser;
525pub use self::gradle::GradleParser;
526pub use self::gradle_lock::GradleLockfileParser;
527pub use self::gradle_module::GradleModuleParser;
528pub use self::hackage::{HackageCabalParser, HackageCabalProjectParser, HackageStackYamlParser};
529pub use self::haxe::HaxeParser;
530pub use self::helm::{HelmChartLockParser, HelmChartYamlParser};
531pub use self::hex_lock::HexLockParser;
532pub use self::julia::{JuliaManifestTomlParser, JuliaProjectTomlParser};
533pub use self::maven::MavenParser;
534pub use self::meson::MesonParser;
535pub use self::microsoft_update_manifest::MicrosoftUpdateManifestParser;
536pub use self::misc::{
537 AndroidApkRecognizer, AndroidLibraryRecognizer, AppleDmgRecognizer, Axis2MarRecognizer,
538 Axis2ModuleXmlRecognizer, CabArchiveRecognizer, ChromeCrxRecognizer, InstallShieldRecognizer,
539 IosIpaRecognizer, IsoImageRecognizer, IvyXmlRecognizer, JBossSarRecognizer,
540 JBossServiceXmlRecognizer, JavaEarAppXmlRecognizer, JavaEarRecognizer, JavaJarRecognizer,
541 JavaWarRecognizer, JavaWarWebXmlRecognizer, MeteorPackageRecognizer, MozillaXpiRecognizer,
542 NsisRecognizer, SharArchiveRecognizer, SquashfsRecognizer,
543};
544pub use self::nix::{NixDefaultParser, NixFlakeLockParser, NixFlakeParser};
545pub use self::npm::NpmParser;
546pub use self::npm_lock::NpmLockParser;
547pub use self::npm_workspace::NpmWorkspaceParser;
548pub use self::nuget::{
549 CentralPackageManagementPropsParser, DirectoryBuildPropsParser, DotNetDepsJsonParser,
550 NupkgParser, NuspecParser, PackageReferenceProjectParser, PackagesConfigParser,
551 PackagesLockParser, ProjectJsonParser, ProjectLockJsonParser,
552};
553pub use self::opam::OpamParser;
554pub use self::os_release::OsReleaseParser;
555pub use self::pip_inspect_deplock::PipInspectDeplockParser;
556pub use self::pipfile_lock::PipfileLockParser;
557pub use self::pixi::{PixiLockParser, PixiTomlParser};
558pub use self::pnpm_lock::PnpmLockParser;
559pub use self::podfile::PodfileParser;
560pub use self::podfile_lock::PodfileLockParser;
561pub use self::podspec::PodspecParser;
562pub use self::podspec_json::PodspecJsonParser;
563pub use self::poetry_lock::PoetryLockParser;
564pub use self::publiccode::PubliccodeParser;
565pub use self::pylock_toml::PylockTomlParser;
566pub use self::python::PythonParser;
567pub use self::readme::ReadmeParser;
568pub use self::requirements_txt::RequirementsTxtParser;
569#[cfg(feature = "rpm-sqlite")]
570pub use self::rpm_db::RpmSqliteDatabaseParser;
571pub use self::rpm_db::{RpmBdbDatabaseParser, RpmNdbDatabaseParser};
572pub use self::rpm_license_files::RpmLicenseFilesParser;
573pub use self::rpm_mariner_manifest::RpmMarinerManifestParser;
574pub use self::rpm_parser::RpmParser;
575pub use self::rpm_specfile::RpmSpecfileParser;
576pub use self::rpm_yumdb::RpmYumdbParser;
577pub use self::ruby::{
578 GemArchiveParser, GemMetadataExtractedParser, GemfileLockParser, GemfileParser, GemspecParser,
579};
580pub use self::sbt::SbtParser;
581pub use self::swift_manifest_json::SwiftManifestJsonParser;
582pub use self::swift_resolved::SwiftPackageResolvedParser;
583pub use self::swift_show_dependencies::SwiftShowDependenciesParser;
584pub use self::uv_lock::UvLockParser;
585pub use self::vcpkg::VcpkgManifestParser;
586pub use self::yarn_lock::YarnLockParser;
587pub use self::yarn_pnp::YarnPnpParser;
588
589macro_rules! register_package_handlers {
595 (
596 parsers: [$($(#[$parser_meta:meta])* $parser:ty),* $(,)?],
597 recognizers: [$($recognizer:ty),* $(,)?] $(,)?
598 ) => {
599 pub fn try_parse_file(path: &Path) -> Option<ParsePackagesResult> {
600 $(
601 $(#[$parser_meta])*
602 if <$parser>::is_match(path) {
603 return Some(capture_parser_diagnostics(
604 || <$parser>::extract_packages(path),
605 stringify!($parser),
606 path,
607 ));
608 }
609 )*
610 $(
611 if <$recognizer>::is_match(path) {
612 return Some(capture_parser_diagnostics(
613 || <$recognizer>::extract_packages(path),
614 stringify!($recognizer),
615 path,
616 ));
617 }
618 )*
619 None
620 }
621
622 #[allow(dead_code)]
625 pub fn parse_by_type_name(type_name: &str, path: &Path) -> Option<PackageData> {
626 match type_name {
627 $(
628 $(#[$parser_meta])*
629 stringify!($parser) => Some(<$parser>::extract_first_package(path)),
630 )*
631 $(
632 stringify!($recognizer) => Some(<$recognizer>::extract_first_package(path)),
633 )*
634 _ => None
635 }
636 }
637
638 #[allow(dead_code)]
641 pub fn list_parser_types() -> Vec<&'static str> {
642 vec![
643 $(
644 $(#[$parser_meta])*
645 stringify!($parser),
646 )*
647 $(
648 stringify!($recognizer),
649 )*
650 ]
651 }
652 };
653}
654
655register_package_handlers! {
656 parsers: [
657 AboutFileParser,
658 AlpineApkParser,
659 AlpineApkbuildParser,
660 AlpineInstalledParser,
661 ArchPkginfoParser,
662 ArchSrcinfoParser,
663 AutotoolsConfigureParser,
664 BazelBuildParser,
665 BazelModuleParser,
666 BowerJsonParser,
667 BunLockParser,
668 BunLockbParser,
669 BuckBuildParser,
670 BuckMetadataBzlParser,
671 CargoLockParser,
672 CargoParser,
673 ChefMetadataJsonParser,
674 ChefMetadataRbParser,
675 CitationCffParser,
676 ClojureDepsEdnParser,
677 ClojureProjectCljParser,
678 ComposerJsonParser,
679 ComposerLockParser,
680 ConanDataParser,
681 ConanFilePyParser,
682 ConanfileTxtParser,
683 ConanLockParser,
684 CondaEnvironmentYmlParser,
685 CondaMetaJsonParser,
686 CondaMetaYamlParser,
687 CpanDistIniParser,
688 CpanMakefilePlParser,
689 CpanManifestParser,
690 CpanMetaJsonParser,
691 CpanMetaYmlParser,
692 CranParser,
693 DebianControlInExtractedDebParser,
694 DebianControlParser,
695 DebianCopyrightParser,
696 DebianDebianTarParser,
697 DebianDebParser,
698 DebianDistrolessInstalledParser,
699 DebianDscParser,
700 DebianInstalledListParser,
701 DebianInstalledMd5sumsParser,
702 DebianInstalledParser,
703 DebianMd5sumInPackageParser,
704 DebianOrigTarParser,
705 DenoParser,
706 DenoLockParser,
707 DockerfileParser,
708 FreebsdCompactManifestParser,
709 GemArchiveParser,
710 GemfileLockParser,
711 GemfileParser,
712 GemMetadataExtractedParser,
713 GemspecParser,
714 GitmodulesParser,
715 GodepsParser,
716 GoModParser,
717 GoModGraphParser,
718 GoSumParser,
719 GoWorkParser,
720 GradleLockfileParser,
721 GradleParser,
722 GradleModuleParser,
723 HackageCabalParser,
724 HackageCabalProjectParser,
725 HackageStackYamlParser,
726 HelmChartYamlParser,
727 HelmChartLockParser,
728 HaxeParser,
729 HexLockParser,
730 JuliaManifestTomlParser,
731 JuliaProjectTomlParser,
732 MavenParser,
733 MesonParser,
734 MicrosoftUpdateManifestParser,
735 NixDefaultParser,
736 NixFlakeLockParser,
737 NixFlakeParser,
738 NpmLockParser,
739 NpmParser,
740 NpmWorkspaceParser,
741 DotNetDepsJsonParser,
742 CentralPackageManagementPropsParser,
743 DirectoryBuildPropsParser,
744 NupkgParser,
745 NuspecParser,
746 PackageReferenceProjectParser,
747 OpamParser,
748 OsReleaseParser,
749 PackagesConfigParser,
750 PackagesLockParser,
751 ProjectJsonParser,
752 ProjectLockJsonParser,
753 PipfileLockParser,
754 PipInspectDeplockParser,
755 PixiTomlParser,
756 PixiLockParser,
757 PnpmLockParser,
758 PodfileLockParser,
759 PodfileParser,
760 PodspecJsonParser,
761 PodspecParser,
762 PoetryLockParser,
763 PubliccodeParser,
764 PylockTomlParser,
765 PubspecLockParser,
766 PubspecYamlParser,
767 PythonParser,
768 UvLockParser,
769 VcpkgManifestParser,
770 ReadmeParser,
771 RequirementsTxtParser,
772 RpmBdbDatabaseParser,
773 RpmLicenseFilesParser,
774 RpmMarinerManifestParser,
775 RpmNdbDatabaseParser,
776 RpmParser,
777 RpmSpecfileParser,
778 #[cfg(feature = "rpm-sqlite")]
779 RpmSqliteDatabaseParser,
780 RpmYumdbParser,
781 SbtParser,
782 SwiftManifestJsonParser,
783 SwiftPackageResolvedParser,
784 SwiftShowDependenciesParser,
785 YarnLockParser,
786 YarnPnpParser,
787 ],
788 recognizers: [
789 AndroidApkRecognizer,
790 AndroidLibraryRecognizer,
791 AppleDmgRecognizer,
792 Axis2MarRecognizer,
793 Axis2ModuleXmlRecognizer,
794 CabArchiveRecognizer,
795 ChromeCrxRecognizer,
796 InstallShieldRecognizer,
797 IosIpaRecognizer,
798 IsoImageRecognizer,
799 IvyXmlRecognizer,
800 JavaEarAppXmlRecognizer,
801 JavaEarRecognizer,
802 JavaJarRecognizer,
803 JavaWarRecognizer,
804 JavaWarWebXmlRecognizer,
805 JBossSarRecognizer,
806 JBossServiceXmlRecognizer,
807 MeteorPackageRecognizer,
808 MozillaXpiRecognizer,
809 NsisRecognizer,
810 SharArchiveRecognizer,
811 SquashfsRecognizer,
812 ],
813}
814
815#[cfg(test)]
816mod panic_isolation_tests {
817 use super::*;
818
819 #[test]
820 fn capture_parser_diagnostics_turns_panics_into_scan_errors() {
821 let path = Path::new("fixtures/panic-package.json");
822 let result = capture_parser_diagnostics(
823 || -> Vec<PackageData> { panic!("panic boom") },
824 "PanicParser",
825 path,
826 );
827
828 assert!(result.packages.is_empty());
829 assert_eq!(result.scan_errors.len(), 1);
830 assert!(result.scan_errors[0].contains("PanicParser"));
831 assert!(result.scan_errors[0].contains("fixtures/panic-package.json"));
832 assert!(result.scan_errors[0].contains("panic boom"));
833 }
834
835 #[test]
836 fn capture_parser_diagnostics_recovers_after_panic() {
837 let panic_path = Path::new("fixtures/panic-package.json");
838 let _ = capture_parser_diagnostics(
839 || -> Vec<PackageData> { panic!("panic boom") },
840 "PanicParser",
841 panic_path,
842 );
843
844 let ok_path = Path::new("fixtures/recovered-package.json");
845 let result = capture_parser_diagnostics(
846 || {
847 crate::parser_warn!("recoverable parser warning");
848 vec![PackageData {
849 package_type: Some(PackageType::Npm),
850 ..Default::default()
851 }]
852 },
853 "RecoveringParser",
854 ok_path,
855 );
856
857 assert_eq!(result.packages.len(), 1);
858 assert_eq!(result.scan_errors, vec!["recoverable parser warning"]);
859 }
860}