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;
325use crate::parsers::utils::MAX_ITERATION_COUNT;
326
327thread_local! {
328 static PARSER_DIAGNOSTIC_STACK: RefCell<Vec<Vec<String>>> = const { RefCell::new(Vec::new()) };
329}
330
331#[derive(Debug, Default)]
332pub struct ParsePackagesResult {
333 pub packages: Vec<PackageData>,
334 pub scan_errors: Vec<String>,
335}
336
337fn panic_payload_to_string(payload: &(dyn std::any::Any + Send)) -> String {
338 if let Some(message) = payload.downcast_ref::<&str>() {
339 (*message).to_string()
340 } else if let Some(message) = payload.downcast_ref::<String>() {
341 message.clone()
342 } else {
343 "unknown panic payload".to_string()
344 }
345}
346
347pub(crate) fn capture_parser_diagnostics<F>(
348 extract: F,
349 handler_name: &str,
350 path: &Path,
351) -> ParsePackagesResult
352where
353 F: FnOnce() -> Vec<PackageData>,
354{
355 PARSER_DIAGNOSTIC_STACK.with(|stack| {
356 stack.borrow_mut().push(Vec::new());
357 });
358
359 let extract_result = catch_unwind(AssertUnwindSafe(extract));
360 let mut scan_errors =
361 PARSER_DIAGNOSTIC_STACK.with(|stack| stack.borrow_mut().pop().unwrap_or_default());
362
363 match extract_result {
364 Ok(packages) => ParsePackagesResult {
365 packages: packages
366 .into_iter()
367 .map(|mut package| {
368 finalize_package_declared_license_references(&mut package);
369 package
370 })
371 .take(MAX_ITERATION_COUNT)
372 .collect(),
373 scan_errors,
374 },
375 Err(payload) => {
376 scan_errors.push(format!(
377 "{} panicked while parsing {}: {}",
378 handler_name,
379 path.display(),
380 panic_payload_to_string(payload.as_ref())
381 ));
382 ParsePackagesResult {
383 packages: Vec::new(),
384 scan_errors,
385 }
386 }
387 }
388}
389
390pub(crate) fn record_parser_diagnostic(message: String) -> bool {
391 PARSER_DIAGNOSTIC_STACK.with(|stack| {
392 let mut stack = stack.borrow_mut();
393 let Some(active) = stack.last_mut() else {
394 return false;
395 };
396 active.push(message);
397 true
398 })
399}
400
401#[macro_export]
402macro_rules! parser_warn {
403 ($($arg:tt)*) => {{
404 let message = format!($($arg)*);
405 if !$crate::parsers::record_parser_diagnostic(message.clone()) {
406 log::warn!("{message}");
407 }
408 }};
409}
410
411pub trait PackageParser {
455 const PACKAGE_TYPE: PackageType;
457
458 fn extract_packages(path: &Path) -> Vec<PackageData>;
468
469 fn is_match(path: &Path) -> bool;
474
475 fn extract_first_package(path: &Path) -> PackageData {
478 Self::extract_packages(path)
479 .into_iter()
480 .map(|mut package| {
481 finalize_package_declared_license_references(&mut package);
482 package
483 })
484 .next()
485 .unwrap_or_default()
486 }
487}
488
489pub use self::about::AboutFileParser;
490pub use self::alpine::{AlpineApkParser, AlpineApkbuildParser, AlpineInstalledParser};
491pub use self::arch::{ArchPkginfoParser, ArchSrcinfoParser};
492pub use self::autotools::AutotoolsConfigureParser;
493pub use self::bazel::{BazelBuildParser, BazelModuleParser};
494pub use self::bower::BowerJsonParser;
495pub use self::buck::{BuckBuildParser, BuckMetadataBzlParser};
496pub use self::bun_lock::BunLockParser;
497pub use self::bun_lockb::BunLockbParser;
498pub use self::cargo::CargoParser;
499#[cfg_attr(not(test), allow(unused_imports))]
500pub use self::cargo_lock::CargoLockParser;
501pub use self::chef::{ChefMetadataJsonParser, ChefMetadataRbParser};
502pub use self::citation::CitationCffParser;
503pub use self::clojure::{ClojureDepsEdnParser, ClojureProjectCljParser};
504pub use self::composer::{ComposerJsonParser, ComposerLockParser};
505pub use self::conan::{ConanFilePyParser, ConanLockParser, ConanfileTxtParser};
506pub use self::conan_data::ConanDataParser;
507pub use self::conda::{CondaEnvironmentYmlParser, CondaMetaYamlParser};
508pub use self::conda_meta_json::CondaMetaJsonParser;
509pub use self::cpan::{CpanManifestParser, CpanMetaJsonParser, CpanMetaYmlParser};
510pub use self::cpan_dist_ini::CpanDistIniParser;
511pub use self::cpan_makefile_pl::CpanMakefilePlParser;
512pub use self::cran::CranParser;
513pub use self::dart::{PubspecLockParser, PubspecYamlParser};
514pub use self::debian::{
515 DebianControlInExtractedDebParser, DebianControlParser, DebianCopyrightParser, DebianDebParser,
516 DebianDebianTarParser, DebianDistrolessInstalledParser, DebianDscParser,
517 DebianInstalledListParser, DebianInstalledMd5sumsParser, DebianInstalledParser,
518 DebianMd5sumInPackageParser, DebianOrigTarParser,
519};
520pub use self::deno::DenoParser;
521pub use self::deno_lock::DenoLockParser;
522pub use self::docker::DockerfileParser;
523pub use self::freebsd::FreebsdCompactManifestParser;
524pub use self::gitmodules::GitmodulesParser;
525pub use self::go::{GoModParser, GoSumParser, GoWorkParser, GodepsParser};
526pub use self::go_mod_graph::GoModGraphParser;
527pub use self::gradle::GradleParser;
528pub use self::gradle_lock::GradleLockfileParser;
529pub use self::gradle_module::GradleModuleParser;
530pub use self::hackage::{HackageCabalParser, HackageCabalProjectParser, HackageStackYamlParser};
531pub use self::haxe::HaxeParser;
532pub use self::helm::{HelmChartLockParser, HelmChartYamlParser};
533pub use self::hex_lock::HexLockParser;
534pub use self::julia::{JuliaManifestTomlParser, JuliaProjectTomlParser};
535pub use self::maven::MavenParser;
536pub use self::meson::MesonParser;
537pub use self::microsoft_update_manifest::MicrosoftUpdateManifestParser;
538pub use self::misc::{
539 AndroidApkRecognizer, AndroidLibraryRecognizer, AppleDmgRecognizer, Axis2MarRecognizer,
540 Axis2ModuleXmlRecognizer, CabArchiveRecognizer, ChromeCrxRecognizer, InstallShieldRecognizer,
541 IosIpaRecognizer, IsoImageRecognizer, IvyXmlRecognizer, JBossSarRecognizer,
542 JBossServiceXmlRecognizer, JavaEarAppXmlRecognizer, JavaEarRecognizer, JavaJarRecognizer,
543 JavaWarRecognizer, JavaWarWebXmlRecognizer, MeteorPackageRecognizer, MozillaXpiRecognizer,
544 NsisRecognizer, SharArchiveRecognizer, SquashfsRecognizer,
545};
546pub use self::nix::{NixDefaultParser, NixFlakeLockParser, NixFlakeParser};
547pub use self::npm::NpmParser;
548pub use self::npm_lock::NpmLockParser;
549pub use self::npm_workspace::NpmWorkspaceParser;
550pub use self::nuget::{
551 CentralPackageManagementPropsParser, DirectoryBuildPropsParser, DotNetDepsJsonParser,
552 NupkgParser, NuspecParser, PackageReferenceProjectParser, PackagesConfigParser,
553 PackagesLockParser, ProjectJsonParser, ProjectLockJsonParser,
554};
555pub use self::opam::OpamParser;
556pub use self::os_release::OsReleaseParser;
557pub use self::pip_inspect_deplock::PipInspectDeplockParser;
558pub use self::pipfile_lock::PipfileLockParser;
559pub use self::pixi::{PixiLockParser, PixiTomlParser};
560pub use self::pnpm_lock::PnpmLockParser;
561pub use self::podfile::PodfileParser;
562pub use self::podfile_lock::PodfileLockParser;
563pub use self::podspec::PodspecParser;
564pub use self::podspec_json::PodspecJsonParser;
565pub use self::poetry_lock::PoetryLockParser;
566pub use self::publiccode::PubliccodeParser;
567pub use self::pylock_toml::PylockTomlParser;
568pub use self::python::PythonParser;
569pub use self::readme::ReadmeParser;
570pub use self::requirements_txt::RequirementsTxtParser;
571#[cfg(feature = "rpm-sqlite")]
572pub use self::rpm_db::RpmSqliteDatabaseParser;
573pub use self::rpm_db::{RpmBdbDatabaseParser, RpmNdbDatabaseParser};
574pub use self::rpm_license_files::RpmLicenseFilesParser;
575pub use self::rpm_mariner_manifest::RpmMarinerManifestParser;
576pub use self::rpm_parser::RpmParser;
577pub use self::rpm_specfile::RpmSpecfileParser;
578pub use self::rpm_yumdb::RpmYumdbParser;
579pub use self::ruby::{
580 GemArchiveParser, GemMetadataExtractedParser, GemfileLockParser, GemfileParser, GemspecParser,
581};
582pub use self::sbt::SbtParser;
583pub use self::swift_manifest_json::SwiftManifestJsonParser;
584pub use self::swift_resolved::SwiftPackageResolvedParser;
585pub use self::swift_show_dependencies::SwiftShowDependenciesParser;
586pub use self::uv_lock::UvLockParser;
587pub use self::vcpkg::VcpkgManifestParser;
588pub use self::yarn_lock::YarnLockParser;
589pub use self::yarn_pnp::YarnPnpParser;
590
591macro_rules! register_package_handlers {
597 (
598 parsers: [$($(#[$parser_meta:meta])* $parser:ty),* $(,)?],
599 recognizers: [$($recognizer:ty),* $(,)?] $(,)?
600 ) => {
601 pub fn try_parse_file(path: &Path) -> Option<ParsePackagesResult> {
602 $(
603 $(#[$parser_meta])*
604 if <$parser>::is_match(path) {
605 return Some(capture_parser_diagnostics(
606 || <$parser>::extract_packages(path),
607 stringify!($parser),
608 path,
609 ));
610 }
611 )*
612 $(
613 if <$recognizer>::is_match(path) {
614 return Some(capture_parser_diagnostics(
615 || <$recognizer>::extract_packages(path),
616 stringify!($recognizer),
617 path,
618 ));
619 }
620 )*
621 None
622 }
623
624 #[allow(dead_code)]
627 pub fn parse_by_type_name(type_name: &str, path: &Path) -> Option<PackageData> {
628 match type_name {
629 $(
630 $(#[$parser_meta])*
631 stringify!($parser) => Some(<$parser>::extract_first_package(path)),
632 )*
633 $(
634 stringify!($recognizer) => Some(<$recognizer>::extract_first_package(path)),
635 )*
636 _ => None
637 }
638 }
639
640 #[allow(dead_code)]
643 pub fn list_parser_types() -> Vec<&'static str> {
644 vec![
645 $(
646 $(#[$parser_meta])*
647 stringify!($parser),
648 )*
649 $(
650 stringify!($recognizer),
651 )*
652 ]
653 }
654 };
655}
656
657register_package_handlers! {
658 parsers: [
659 AboutFileParser,
660 AlpineApkParser,
661 AlpineApkbuildParser,
662 AlpineInstalledParser,
663 ArchPkginfoParser,
664 ArchSrcinfoParser,
665 AutotoolsConfigureParser,
666 BazelBuildParser,
667 BazelModuleParser,
668 BowerJsonParser,
669 BunLockParser,
670 BunLockbParser,
671 BuckBuildParser,
672 BuckMetadataBzlParser,
673 CargoLockParser,
674 CargoParser,
675 ChefMetadataJsonParser,
676 ChefMetadataRbParser,
677 CitationCffParser,
678 ClojureDepsEdnParser,
679 ClojureProjectCljParser,
680 ComposerJsonParser,
681 ComposerLockParser,
682 ConanDataParser,
683 ConanFilePyParser,
684 ConanfileTxtParser,
685 ConanLockParser,
686 CondaEnvironmentYmlParser,
687 CondaMetaJsonParser,
688 CondaMetaYamlParser,
689 CpanDistIniParser,
690 CpanMakefilePlParser,
691 CpanManifestParser,
692 CpanMetaJsonParser,
693 CpanMetaYmlParser,
694 CranParser,
695 DebianControlInExtractedDebParser,
696 DebianControlParser,
697 DebianCopyrightParser,
698 DebianDebianTarParser,
699 DebianDebParser,
700 DebianDistrolessInstalledParser,
701 DebianDscParser,
702 DebianInstalledListParser,
703 DebianInstalledMd5sumsParser,
704 DebianInstalledParser,
705 DebianMd5sumInPackageParser,
706 DebianOrigTarParser,
707 DenoParser,
708 DenoLockParser,
709 DockerfileParser,
710 FreebsdCompactManifestParser,
711 GemArchiveParser,
712 GemfileLockParser,
713 GemfileParser,
714 GemMetadataExtractedParser,
715 GemspecParser,
716 GitmodulesParser,
717 GodepsParser,
718 GoModParser,
719 GoModGraphParser,
720 GoSumParser,
721 GoWorkParser,
722 GradleLockfileParser,
723 GradleParser,
724 GradleModuleParser,
725 HackageCabalParser,
726 HackageCabalProjectParser,
727 HackageStackYamlParser,
728 HelmChartYamlParser,
729 HelmChartLockParser,
730 HaxeParser,
731 HexLockParser,
732 JuliaManifestTomlParser,
733 JuliaProjectTomlParser,
734 MavenParser,
735 MesonParser,
736 MicrosoftUpdateManifestParser,
737 NixDefaultParser,
738 NixFlakeLockParser,
739 NixFlakeParser,
740 NpmLockParser,
741 NpmParser,
742 NpmWorkspaceParser,
743 DotNetDepsJsonParser,
744 CentralPackageManagementPropsParser,
745 DirectoryBuildPropsParser,
746 NupkgParser,
747 NuspecParser,
748 PackageReferenceProjectParser,
749 OpamParser,
750 OsReleaseParser,
751 PackagesConfigParser,
752 PackagesLockParser,
753 ProjectJsonParser,
754 ProjectLockJsonParser,
755 PipfileLockParser,
756 PipInspectDeplockParser,
757 PixiTomlParser,
758 PixiLockParser,
759 PnpmLockParser,
760 PodfileLockParser,
761 PodfileParser,
762 PodspecJsonParser,
763 PodspecParser,
764 PoetryLockParser,
765 PubliccodeParser,
766 PylockTomlParser,
767 PubspecLockParser,
768 PubspecYamlParser,
769 PythonParser,
770 UvLockParser,
771 VcpkgManifestParser,
772 ReadmeParser,
773 RequirementsTxtParser,
774 RpmBdbDatabaseParser,
775 RpmLicenseFilesParser,
776 RpmMarinerManifestParser,
777 RpmNdbDatabaseParser,
778 RpmParser,
779 RpmSpecfileParser,
780 #[cfg(feature = "rpm-sqlite")]
781 RpmSqliteDatabaseParser,
782 RpmYumdbParser,
783 SbtParser,
784 SwiftManifestJsonParser,
785 SwiftPackageResolvedParser,
786 SwiftShowDependenciesParser,
787 YarnLockParser,
788 YarnPnpParser,
789 ],
790 recognizers: [
791 AndroidApkRecognizer,
792 AndroidLibraryRecognizer,
793 AppleDmgRecognizer,
794 Axis2MarRecognizer,
795 Axis2ModuleXmlRecognizer,
796 CabArchiveRecognizer,
797 ChromeCrxRecognizer,
798 InstallShieldRecognizer,
799 IosIpaRecognizer,
800 IsoImageRecognizer,
801 IvyXmlRecognizer,
802 JavaEarAppXmlRecognizer,
803 JavaEarRecognizer,
804 JavaJarRecognizer,
805 JavaWarRecognizer,
806 JavaWarWebXmlRecognizer,
807 JBossSarRecognizer,
808 JBossServiceXmlRecognizer,
809 MeteorPackageRecognizer,
810 MozillaXpiRecognizer,
811 NsisRecognizer,
812 SharArchiveRecognizer,
813 SquashfsRecognizer,
814 ],
815}
816
817#[cfg(test)]
818mod panic_isolation_tests {
819 use super::*;
820
821 #[test]
822 fn capture_parser_diagnostics_turns_panics_into_scan_errors() {
823 let path = Path::new("fixtures/panic-package.json");
824 let result = capture_parser_diagnostics(
825 || -> Vec<PackageData> { panic!("panic boom") },
826 "PanicParser",
827 path,
828 );
829
830 assert!(result.packages.is_empty());
831 assert_eq!(result.scan_errors.len(), 1);
832 assert!(result.scan_errors[0].contains("PanicParser"));
833 assert!(result.scan_errors[0].contains("fixtures/panic-package.json"));
834 assert!(result.scan_errors[0].contains("panic boom"));
835 }
836
837 #[test]
838 fn capture_parser_diagnostics_recovers_after_panic() {
839 let panic_path = Path::new("fixtures/panic-package.json");
840 let _ = capture_parser_diagnostics(
841 || -> Vec<PackageData> { panic!("panic boom") },
842 "PanicParser",
843 panic_path,
844 );
845
846 let ok_path = Path::new("fixtures/recovered-package.json");
847 let result = capture_parser_diagnostics(
848 || {
849 crate::parser_warn!("recoverable parser warning");
850 vec![PackageData {
851 package_type: Some(PackageType::Npm),
852 ..Default::default()
853 }]
854 },
855 "RecoveringParser",
856 ok_path,
857 );
858
859 assert_eq!(result.packages.len(), 1);
860 assert_eq!(result.scan_errors, vec!["recoverable parser warning"]);
861 }
862}