1mod about;
5#[cfg(test)]
6mod about_scan_test;
7#[cfg(test)]
8mod about_test;
9mod alpine;
10#[cfg(test)]
11mod alpine_scan_test;
12mod android;
13#[cfg(test)]
14mod android_test;
15mod arch;
16#[cfg(test)]
17mod arch_scan_test;
18#[cfg(test)]
19mod arch_test;
20mod autotools;
21#[cfg(test)]
22mod autotools_test;
23mod bazel;
24#[cfg(test)]
25mod bazel_module_test;
26#[cfg(test)]
27mod bazel_test;
28mod bitbake;
29#[cfg(test)]
30mod bitbake_scan_test;
31#[cfg(test)]
32mod bitbake_test;
33mod bower;
34#[cfg(test)]
35mod bower_scan_test;
36#[cfg(test)]
37mod bower_test;
38mod buck;
39#[cfg(test)]
40mod buck_test;
41mod bun_lock;
42#[cfg(test)]
43mod bun_lock_test;
44mod bun_lockb;
45#[cfg(test)]
46mod bun_lockb_test;
47mod cargo;
48mod cargo_lock;
49#[cfg(test)]
50mod cargo_lock_test;
51#[cfg(test)]
52mod cargo_scan_test;
53#[cfg(test)]
54mod cargo_test;
55mod carthage;
56#[cfg(test)]
57mod carthage_scan_test;
58#[cfg(test)]
59mod carthage_test;
60mod chef;
61#[cfg(test)]
62mod chef_scan_test;
63#[cfg(test)]
64mod chef_test;
65mod citation;
66#[cfg(test)]
67mod citation_test;
68mod clojure;
69#[cfg(test)]
70mod clojure_test;
71#[cfg(test)]
72mod cocoapods_scan_test;
73pub(crate) mod compiled_binary;
74mod composer;
75#[cfg(test)]
76mod composer_scan_test;
77#[cfg(test)]
78mod composer_test;
79mod conan;
80mod conan_data;
81#[cfg(test)]
82mod conan_data_test;
83#[cfg(test)]
84mod conan_scan_test;
85#[cfg(test)]
86mod conan_test;
87mod conda;
88mod conda_meta_json;
89#[cfg(test)]
90mod conda_meta_json_test;
91#[cfg(test)]
92mod conda_scan_test;
93#[cfg(test)]
94mod conda_test;
95mod cpan;
96mod cpan_dist_ini;
97#[cfg(test)]
98mod cpan_dist_ini_test;
99mod cpan_makefile_pl;
100#[cfg(test)]
101mod cpan_makefile_pl_test;
102#[cfg(test)]
103mod cpan_scan_test;
104#[cfg(test)]
105mod cpan_test;
106mod cran;
107#[cfg(test)]
108mod cran_scan_test;
109#[cfg(test)]
110mod cran_test;
111mod dart;
112#[cfg(test)]
113mod dart_scan_test;
114#[cfg(test)]
115mod dart_test;
116mod debian;
117mod deno;
118mod deno_lock;
119#[cfg(test)]
120mod deno_lock_test;
121#[cfg(test)]
122mod deno_scan_test;
123#[cfg(test)]
124mod deno_test;
125mod docker;
126#[cfg(test)]
127mod docker_scan_test;
128#[cfg(test)]
129mod docker_test;
130mod erlang_otp;
131#[cfg(test)]
132mod erlang_otp_scan_test;
133#[cfg(test)]
134mod erlang_otp_test;
135mod freebsd;
136#[cfg(test)]
137mod freebsd_scan_test;
138#[cfg(test)]
139mod freebsd_test;
140mod gitmodules;
141#[cfg(test)]
142mod gitmodules_scan_test;
143mod go;
144mod go_mod_graph;
145#[cfg(test)]
146mod go_scan_test;
147#[cfg(test)]
148mod go_test;
149#[cfg(test)]
150mod go_work_test;
151#[cfg(feature = "golden-tests")]
152pub mod golden_test_utils;
153mod gradle;
154mod gradle_lock;
155#[cfg(test)]
156mod gradle_lock_test;
157mod gradle_module;
158#[cfg(test)]
159mod gradle_module_scan_test;
160#[cfg(test)]
161mod gradle_module_test;
162#[cfg(test)]
163mod gradle_scan_test;
164mod hackage;
165#[cfg(test)]
166mod hackage_scan_test;
167#[cfg(test)]
168mod hackage_test;
169mod haxe;
170#[cfg(test)]
171mod haxe_scan_test;
172#[cfg(test)]
173mod haxe_test;
174mod helm;
175#[cfg(test)]
176mod helm_scan_test;
177#[cfg(test)]
178mod helm_test;
179mod hex_lock;
180#[cfg(test)]
181mod hex_lock_test;
182mod huggingface;
183#[cfg(test)]
184mod huggingface_scan_test;
185#[cfg(test)]
186mod huggingface_test;
187mod julia;
188#[cfg(test)]
189mod julia_test;
190pub(crate) mod license_normalization;
191mod maven;
192mod meson;
193#[cfg(test)]
194mod meson_scan_test;
195#[cfg(test)]
196mod meson_test;
197pub mod metadata;
198mod microsoft_update_manifest;
199#[cfg(test)]
200mod microsoft_update_manifest_test;
201mod misc;
202#[cfg(test)]
203mod misc_test;
204mod nix;
205#[cfg(test)]
206mod nix_scan_test;
207#[cfg(test)]
208mod nix_test;
209mod npm;
210mod npm_lock;
211#[cfg(test)]
212mod npm_lock_test;
213#[cfg(test)]
214mod npm_scan_test;
215#[cfg(test)]
216mod npm_test;
217mod npm_workspace;
218#[cfg(test)]
219mod npm_workspace_test;
220mod nuget;
221mod oci;
222#[cfg(test)]
223mod oci_scan_test;
224#[cfg(test)]
225mod oci_test;
226mod opam;
227#[cfg(test)]
228mod opam_scan_test;
229mod os_release;
230#[cfg(test)]
231mod os_release_test;
232mod pep508;
233mod pip_inspect_deplock;
234#[cfg(test)]
235mod pip_inspect_deplock_test;
236mod pipfile_lock;
237#[cfg(test)]
238mod pipfile_lock_test;
239mod pixi;
240#[cfg(test)]
241mod pixi_scan_test;
242#[cfg(test)]
243mod pixi_test;
244mod pnpm_lock;
245#[cfg(test)]
246mod pnpm_lock_test;
247mod podfile;
248mod podfile_lock;
249#[cfg(test)]
250mod podfile_lock_test;
251mod podspec;
252mod podspec_json;
253#[cfg(test)]
254mod podspec_json_test;
255mod poetry_lock;
256#[cfg(test)]
257mod poetry_lock_test;
258mod publiccode;
259#[cfg(test)]
260mod publiccode_test;
261mod pylock_toml;
262#[cfg(test)]
263mod pylock_toml_test;
264mod python;
265mod readme;
266#[cfg(test)]
267mod readme_test;
268mod requirements_txt;
269#[cfg(test)]
270mod requirements_txt_test;
271pub(crate) mod rfc822;
272mod rpm_db;
273mod rpm_db_native;
274#[cfg(test)]
275mod rpm_db_scan_test;
276mod rpm_license_files;
277#[cfg(test)]
278mod rpm_license_files_test;
279mod rpm_mariner_manifest;
280#[cfg(test)]
281mod rpm_mariner_manifest_test;
282mod rpm_parser;
283#[cfg(test)]
284mod rpm_scan_test;
285mod rpm_specfile;
286#[cfg(test)]
287mod rpm_specfile_test;
288mod rpm_yumdb;
289mod ruby;
290#[cfg(test)]
291mod ruby_scan_test;
292#[cfg(test)]
293mod ruby_test;
294mod sbt;
295#[cfg(test)]
296mod sbt_test;
297#[cfg(test)]
298mod scan_test_utils;
299mod swift_manifest_json;
300#[cfg(test)]
301mod swift_manifest_json_test;
302mod swift_resolved;
303#[cfg(test)]
304mod swift_resolved_test;
305#[cfg(test)]
306mod swift_scan_test;
307mod swift_show_dependencies;
308#[cfg(test)]
309mod swift_show_dependencies_test;
310pub mod utils;
311mod uv_lock;
312#[cfg(test)]
313mod uv_lock_test;
314mod vcpkg;
315#[cfg(test)]
316mod vcpkg_scan_test;
317#[cfg(test)]
318mod vcpkg_test;
319pub(crate) mod windows_executable;
320#[cfg(test)]
321mod windows_executable_golden_test;
322mod yarn_lock;
323#[cfg(test)]
324mod yarn_lock_test;
325mod yarn_pnp;
326#[cfg(test)]
327mod yarn_pnp_test;
328
329use std::cell::RefCell;
330use std::panic::{AssertUnwindSafe, catch_unwind};
331use std::path::Path;
332use std::sync::Arc;
333
334use crate::license_detection::LicenseDetectionEngine;
335use crate::models::{DiagnosticSeverity, PackageData, PackageType, ScanDiagnostic};
336use crate::parsers::license_normalization::finalize_package_declared_license_references;
337use crate::parsers::utils::MAX_ITERATION_COUNT;
338
339thread_local! {
340 static PARSER_DIAGNOSTIC_STACK: RefCell<Vec<Vec<ScanDiagnostic>>> = const { RefCell::new(Vec::new()) };
341 static PARSER_LICENSE_ENGINE_STACK: RefCell<Vec<Option<Arc<LicenseDetectionEngine>>>> = const { RefCell::new(Vec::new()) };
342 static PARSER_SCAN_ROOT_STACK: RefCell<Vec<Option<std::path::PathBuf>>> = const { RefCell::new(Vec::new()) };
343}
344
345#[derive(Debug, Default)]
346pub struct ParsePackagesResult {
347 pub packages: Vec<PackageData>,
348 pub scan_diagnostics: Vec<ScanDiagnostic>,
349}
350
351fn panic_payload_to_string(payload: &(dyn std::any::Any + Send)) -> String {
352 if let Some(message) = payload.downcast_ref::<&str>() {
353 (*message).to_string()
354 } else if let Some(message) = payload.downcast_ref::<String>() {
355 message.clone()
356 } else {
357 "unknown panic payload".to_string()
358 }
359}
360
361pub(crate) fn capture_parser_diagnostics<F>(
362 extract: F,
363 handler_name: &str,
364 path: &Path,
365 license_engine: Option<Arc<LicenseDetectionEngine>>,
366) -> ParsePackagesResult
367where
368 F: FnOnce() -> Vec<PackageData>,
369{
370 PARSER_DIAGNOSTIC_STACK.with(|stack| {
371 stack.borrow_mut().push(Vec::new());
372 });
373 PARSER_LICENSE_ENGINE_STACK.with(|stack| {
374 stack.borrow_mut().push(license_engine);
375 });
376
377 let extract_result = catch_unwind(AssertUnwindSafe(|| {
378 extract()
379 .into_iter()
380 .map(|mut package| {
381 finalize_package_declared_license_references(&mut package);
382 package
383 })
384 .take(MAX_ITERATION_COUNT)
385 .collect::<Vec<_>>()
386 }));
387 PARSER_LICENSE_ENGINE_STACK.with(|stack| {
388 stack.borrow_mut().pop();
389 });
390 let mut scan_diagnostics =
391 PARSER_DIAGNOSTIC_STACK.with(|stack| stack.borrow_mut().pop().unwrap_or_default());
392
393 match extract_result {
394 Ok(packages) => ParsePackagesResult {
395 packages,
396 scan_diagnostics,
397 },
398 Err(payload) => {
399 scan_diagnostics.push(ScanDiagnostic::error(format!(
400 "{} panicked while parsing {}: {}",
401 handler_name,
402 path.display(),
403 panic_payload_to_string(payload.as_ref())
404 )));
405 ParsePackagesResult {
406 packages: Vec::new(),
407 scan_diagnostics,
408 }
409 }
410 }
411}
412
413pub(crate) fn active_parser_license_engine() -> Option<Arc<LicenseDetectionEngine>> {
414 PARSER_LICENSE_ENGINE_STACK.with(|stack| stack.borrow().last().cloned().flatten())
415}
416
417pub(crate) fn active_parser_scan_root() -> Option<std::path::PathBuf> {
418 PARSER_SCAN_ROOT_STACK.with(|stack| stack.borrow().last().cloned().flatten())
419}
420
421pub(crate) fn with_parser_scan_root<T>(scan_root: Option<&Path>, f: impl FnOnce() -> T) -> T {
422 PARSER_SCAN_ROOT_STACK.with(|stack| {
423 stack.borrow_mut().push(scan_root.map(Path::to_path_buf));
424 });
425 let result = f();
426 PARSER_SCAN_ROOT_STACK.with(|stack| {
427 stack.borrow_mut().pop();
428 });
429 result
430}
431
432pub(crate) fn record_parser_diagnostic(message: String, severity: DiagnosticSeverity) -> bool {
433 PARSER_DIAGNOSTIC_STACK.with(|stack| {
434 let mut stack = stack.borrow_mut();
435 let Some(active) = stack.last_mut() else {
436 return false;
437 };
438 active.push(ScanDiagnostic { severity, message });
439 true
440 })
441}
442
443#[macro_export]
444macro_rules! parser_warn {
445 ($($arg:tt)*) => {{
446 let message = format!($($arg)*);
447 if !$crate::parsers::record_parser_diagnostic(
448 message.clone(),
449 $crate::models::DiagnosticSeverity::Warning,
450 ) {
451 log::warn!("{message}");
452 }
453 }};
454}
455
456pub trait PackageParser {
500 const PACKAGE_TYPE: PackageType;
502
503 fn extract_packages(path: &Path) -> Vec<PackageData>;
513
514 fn is_match(path: &Path) -> bool;
519
520 fn extract_first_package(path: &Path) -> PackageData {
523 Self::extract_packages(path)
524 .into_iter()
525 .map(|mut package| {
526 finalize_package_declared_license_references(&mut package);
527 package
528 })
529 .next()
530 .unwrap_or_default()
531 }
532
533 fn metadata() -> Vec<metadata::ParserMetadata> {
540 Vec::new()
541 }
542}
543
544pub fn try_parse_rpm_archive_with_license_engine(
545 path: &Path,
546 license_engine: Option<Arc<LicenseDetectionEngine>>,
547) -> Option<ParsePackagesResult> {
548 if !self::rpm_parser::path_looks_like_rpm_archive(path) {
549 return None;
550 }
551
552 if <RpmParser as PackageParser>::is_match(path) {
553 return Some(capture_parser_diagnostics(
554 || self::rpm_parser::extract_rpm_packages(path),
555 stringify!(RpmParser),
556 path,
557 license_engine,
558 ));
559 }
560
561 None
562}
563
564pub fn try_parse_rpm_archive(path: &Path) -> Option<ParsePackagesResult> {
565 try_parse_rpm_archive_with_license_engine(path, None)
566}
567
568#[cfg(feature = "golden-tests")]
569pub fn try_parse_compiled_bytes(bytes: &[u8]) -> Option<ParsePackagesResult> {
570 self::compiled_binary::try_parse_compiled_bytes(bytes)
571}
572
573#[cfg(feature = "golden-tests")]
574pub fn try_parse_windows_executable_bytes(
575 path: &Path,
576 bytes: &[u8],
577) -> Option<ParsePackagesResult> {
578 self::windows_executable::try_parse_windows_executable_bytes(path, bytes)
579}
580
581pub fn path_looks_like_rpm_archive(path: &Path) -> bool {
582 self::rpm_parser::path_looks_like_rpm_archive(path)
583}
584
585pub fn all_metadata() -> Vec<metadata::ParserMetadata> {
590 let mut entries = collect_parser_metadata();
591 entries.extend_from_slice(self::compiled_binary::COMPILED_BINARY_METADATA);
592 entries.extend_from_slice(self::windows_executable::WINDOWS_EXE_METADATA);
593 entries.extend_from_slice(self::misc::RECOGNIZER_METADATA);
594 entries.extend_from_slice(crate::utils::font::FONT_METADATA);
595 entries
596}
597
598pub use self::about::AboutFileParser;
599pub use self::alpine::{AlpineApkParser, AlpineApkbuildParser, AlpineInstalledParser};
600pub use self::android::{
601 AndroidAabParser, AndroidApkParser, AndroidManifestParser, AndroidSoongMetadataParser,
602};
603#[cfg(feature = "golden-tests")]
604pub use self::android::{
605 ProtoItem, ProtoPrimitive, ProtoRawStringValue, ProtoSourcePosition, ProtoStringValue,
606 ProtoXmlAttribute, ProtoXmlElement, ProtoXmlNamespace, ProtoXmlNode, proto_item,
607 proto_primitive, proto_xml_node,
608};
609pub use self::arch::{ArchPkginfoParser, ArchSrcinfoParser};
610pub use self::autotools::AutotoolsConfigureParser;
611pub use self::bazel::{BazelBuildParser, BazelModuleParser};
612pub use self::bitbake::BitbakeRecipeParser;
613pub use self::bower::BowerJsonParser;
614pub use self::buck::{BuckBuildParser, BuckMetadataBzlParser};
615pub use self::bun_lock::BunLockParser;
616pub use self::bun_lockb::BunLockbParser;
617pub use self::cargo::CargoParser;
618#[cfg_attr(not(test), allow(unused_imports))]
619pub use self::cargo_lock::CargoLockParser;
620pub use self::carthage::{CarthageCartfileParser, CarthageCartfileResolvedParser};
621pub use self::chef::{ChefMetadataJsonParser, ChefMetadataRbParser};
622pub use self::citation::CitationCffParser;
623pub use self::clojure::{ClojureDepsEdnParser, ClojureProjectCljParser};
624pub use self::composer::{ComposerJsonParser, ComposerLockParser};
625pub use self::conan::{ConanFilePyParser, ConanLockParser, ConanfileTxtParser};
626pub use self::conan_data::ConanDataParser;
627pub use self::conda::{CondaEnvironmentYmlParser, CondaMetaYamlParser};
628pub use self::conda_meta_json::CondaMetaJsonParser;
629pub use self::cpan::{CpanManifestParser, CpanMetaJsonParser, CpanMetaYmlParser};
630pub use self::cpan_dist_ini::CpanDistIniParser;
631pub use self::cpan_makefile_pl::CpanMakefilePlParser;
632pub use self::cran::CranParser;
633pub use self::dart::{PubspecLockParser, PubspecYamlParser};
634pub use self::debian::{
635 DebianControlInExtractedDebParser, DebianControlParser, DebianCopyrightParser, DebianDebParser,
636 DebianDebianTarParser, DebianDistrolessInstalledParser, DebianDscParser,
637 DebianInstalledListParser, DebianInstalledMd5sumsParser, DebianInstalledParser,
638 DebianMd5sumInPackageParser, DebianOrigTarParser,
639};
640pub use self::deno::DenoParser;
641pub use self::deno_lock::DenoLockParser;
642pub use self::docker::DockerfileParser;
643pub use self::erlang_otp::{ErlangAppSrcParser, RebarConfigParser, RebarLockParser};
644pub use self::freebsd::FreebsdCompactManifestParser;
645pub use self::gitmodules::GitmodulesParser;
646pub use self::go::{GoModParser, GoSumParser, GoWorkParser, GodepsParser};
647pub use self::go_mod_graph::GoModGraphParser;
648pub use self::gradle::GradleParser;
649pub use self::gradle_lock::GradleLockfileParser;
650pub use self::gradle_module::GradleModuleParser;
651pub use self::hackage::{HackageCabalParser, HackageCabalProjectParser, HackageStackYamlParser};
652pub use self::haxe::HaxeParser;
653pub use self::helm::{HelmChartLockParser, HelmChartYamlParser};
654pub use self::hex_lock::HexLockParser;
655pub use self::huggingface::{
656 HuggingfaceConfigParser, HuggingfaceModelCardParser, HuggingfaceModelIndexParser,
657};
658pub use self::julia::{JuliaManifestTomlParser, JuliaProjectTomlParser};
659pub use self::maven::MavenParser;
660pub use self::meson::MesonParser;
661pub use self::microsoft_update_manifest::MicrosoftUpdateManifestParser;
662pub use self::misc::{
663 AndroidLibraryRecognizer, AppleDmgRecognizer, Axis2MarRecognizer, Axis2ModuleXmlRecognizer,
664 CabArchiveRecognizer, ChromeCrxRecognizer, InstallShieldRecognizer, IosIpaRecognizer,
665 IsoImageRecognizer, IvyXmlRecognizer, JBossSarRecognizer, JBossServiceXmlRecognizer,
666 JavaEarAppXmlRecognizer, JavaEarRecognizer, JavaJarRecognizer, JavaWarRecognizer,
667 JavaWarWebXmlRecognizer, MeteorPackageRecognizer, MozillaXpiRecognizer, NsisRecognizer,
668 SharArchiveRecognizer, SquashfsRecognizer,
669};
670pub use self::nix::{NixDefaultParser, NixFlakeLockParser, NixFlakeParser};
671pub use self::npm::NpmParser;
672pub use self::npm_lock::NpmLockParser;
673pub use self::npm_workspace::NpmWorkspaceParser;
674pub use self::nuget::{
675 CentralPackageManagementPropsParser, DirectoryBuildPropsParser, DotNetDepsJsonParser,
676 NupkgParser, NuspecParser, PackageReferenceProjectParser, PackagesConfigParser,
677 PackagesLockParser, ProjectJsonParser, ProjectLockJsonParser,
678};
679pub use self::oci::OciImageLayoutParser;
680pub use self::opam::OpamParser;
681pub use self::os_release::OsReleaseParser;
682pub use self::pip_inspect_deplock::PipInspectDeplockParser;
683pub use self::pipfile_lock::PipfileLockParser;
684pub use self::pixi::{PixiLockParser, PixiTomlParser};
685pub use self::pnpm_lock::PnpmLockParser;
686pub use self::podfile::PodfileParser;
687pub use self::podfile_lock::PodfileLockParser;
688pub use self::podspec::PodspecParser;
689pub use self::podspec_json::PodspecJsonParser;
690pub use self::poetry_lock::PoetryLockParser;
691pub use self::publiccode::PubliccodeParser;
692pub use self::pylock_toml::PylockTomlParser;
693pub use self::python::PythonParser;
694pub use self::readme::ReadmeParser;
695pub use self::requirements_txt::RequirementsTxtParser;
696#[cfg(feature = "rpm-sqlite")]
697pub use self::rpm_db::RpmSqliteDatabaseParser;
698pub use self::rpm_db::{RpmBdbDatabaseParser, RpmNdbDatabaseParser};
699pub use self::rpm_license_files::RpmLicenseFilesParser;
700pub use self::rpm_mariner_manifest::RpmMarinerManifestParser;
701pub use self::rpm_parser::RpmParser;
702pub use self::rpm_specfile::RpmSpecfileParser;
703pub use self::rpm_yumdb::RpmYumdbParser;
704pub use self::ruby::{
705 GemArchiveParser, GemMetadataExtractedParser, GemfileLockParser, GemfileParser, GemspecParser,
706};
707pub use self::sbt::SbtParser;
708pub use self::swift_manifest_json::SwiftManifestJsonParser;
709pub use self::swift_resolved::SwiftPackageResolvedParser;
710pub use self::swift_show_dependencies::SwiftShowDependenciesParser;
711pub use self::uv_lock::UvLockParser;
712pub use self::vcpkg::VcpkgManifestParser;
713pub use self::yarn_lock::YarnLockParser;
714pub use self::yarn_pnp::YarnPnpParser;
715
716macro_rules! register_package_handlers {
722 (
723 parsers: [$($(#[$parser_meta:meta])* $parser:ty),* $(,)?],
724 recognizers: [$($recognizer:ty),* $(,)?] $(,)?
725 ) => {
726 pub fn try_parse_file_with_license_engine(
727 path: &Path,
728 license_engine: Option<Arc<LicenseDetectionEngine>>,
729 ) -> Option<ParsePackagesResult> {
730 $(
731 $(#[$parser_meta])*
732 if <$parser>::is_match(path) {
733 return Some(capture_parser_diagnostics(
734 || <$parser>::extract_packages(path),
735 stringify!($parser),
736 path,
737 license_engine.clone(),
738 ));
739 }
740 )*
741 $(
742 if <$recognizer>::is_match(path) {
743 return Some(capture_parser_diagnostics(
744 || <$recognizer>::extract_packages(path),
745 stringify!($recognizer),
746 path,
747 license_engine.clone(),
748 ));
749 }
750 )*
751 None
752 }
753
754 pub fn try_parse_file(path: &Path) -> Option<ParsePackagesResult> {
755 try_parse_file_with_license_engine(path, None)
756 }
757
758 #[allow(dead_code)]
761 pub fn parse_by_type_name(type_name: &str, path: &Path) -> Option<PackageData> {
762 match type_name {
763 $(
764 $(#[$parser_meta])*
765 stringify!($parser) => Some(<$parser>::extract_first_package(path)),
766 )*
767 $(
768 stringify!($recognizer) => Some(<$recognizer>::extract_first_package(path)),
769 )*
770 _ => None
771 }
772 }
773
774 #[allow(dead_code)]
777 pub fn list_parser_types() -> Vec<&'static str> {
778 vec![
779 $(
780 $(#[$parser_meta])*
781 stringify!($parser),
782 )*
783 $(
784 stringify!($recognizer),
785 )*
786 ]
787 }
788
789 pub fn collect_parser_metadata() -> Vec<metadata::ParserMetadata> {
791 let mut entries = Vec::new();
792 $(
793 $(#[$parser_meta])*
794 entries.extend(<$parser>::metadata());
795 )*
796 $(
797 entries.extend(<$recognizer>::metadata());
798 )*
799 entries
800 }
801 };
802}
803
804#[cfg(test)]
805mod tests {
806 use std::collections::HashMap;
807
808 use super::{active_parser_license_engine, capture_parser_diagnostics};
809 use crate::license_detection::LicenseDetectionEngine;
810 use crate::models::PackageData;
811 use crate::parsers::license_normalization::{
812 clear_last_parser_license_engine_ptr, last_parser_license_engine_ptr,
813 };
814 use std::path::Path;
815 use std::sync::Arc;
816
817 #[test]
818 fn test_capture_parser_diagnostics_exposes_active_license_engine() {
819 let engine =
820 Arc::new(LicenseDetectionEngine::from_embedded().expect("embedded engine should load"));
821
822 let result = capture_parser_diagnostics(
823 || {
824 assert!(active_parser_license_engine().is_some());
825 vec![PackageData::default()]
826 },
827 "TestParser",
828 Path::new("testdata/package.json"),
829 Some(engine),
830 );
831
832 assert_eq!(result.packages.len(), 1);
833 assert!(active_parser_license_engine().is_none());
834 }
835
836 #[test]
837 fn test_capture_parser_diagnostics_keeps_active_license_engine_for_finalization() {
838 let engine =
839 Arc::new(LicenseDetectionEngine::from_embedded().expect("embedded engine should load"));
840 clear_last_parser_license_engine_ptr();
841
842 let result = capture_parser_diagnostics(
843 || {
844 vec![PackageData {
845 declared_license_expression: Some("mit".to_string()),
846 declared_license_expression_spdx: Some("MIT".to_string()),
847 extracted_license_statement: Some("MIT".to_string()),
848 extra_data: Some(HashMap::from([(
849 "license_file".to_string(),
850 serde_json::Value::String("LICENSE".to_string()),
851 )])),
852 ..Default::default()
853 }]
854 },
855 "TestParser",
856 Path::new("testdata/package.json"),
857 Some(Arc::clone(&engine)),
858 );
859
860 assert_eq!(result.packages.len(), 1);
861 assert_eq!(
862 last_parser_license_engine_ptr(),
863 Some(Arc::as_ptr(&engine) as usize)
864 );
865 assert_eq!(
866 result.packages[0].license_detections[0].matches[0]
867 .referenced_filenames
868 .as_ref(),
869 Some(&vec!["LICENSE".to_string()])
870 );
871 assert!(active_parser_license_engine().is_none());
872 }
873}
874
875register_package_handlers! {
876 parsers: [
877 AboutFileParser,
878 AndroidAabParser,
879 AndroidApkParser,
880 AndroidManifestParser,
881 AndroidSoongMetadataParser,
882 AlpineApkParser,
883 AlpineApkbuildParser,
884 AlpineInstalledParser,
885 ArchPkginfoParser,
886 ArchSrcinfoParser,
887 AutotoolsConfigureParser,
888 BazelBuildParser,
889 BazelModuleParser,
890 BitbakeRecipeParser,
891 BowerJsonParser,
892 BunLockParser,
893 BunLockbParser,
894 BuckBuildParser,
895 BuckMetadataBzlParser,
896 CargoLockParser,
897 CargoParser,
898 CarthageCartfileParser,
899 CarthageCartfileResolvedParser,
900 ChefMetadataJsonParser,
901 ChefMetadataRbParser,
902 CitationCffParser,
903 ClojureDepsEdnParser,
904 ClojureProjectCljParser,
905 ComposerJsonParser,
906 ComposerLockParser,
907 ConanDataParser,
908 ConanFilePyParser,
909 ConanfileTxtParser,
910 ConanLockParser,
911 CondaEnvironmentYmlParser,
912 CondaMetaJsonParser,
913 CondaMetaYamlParser,
914 CpanDistIniParser,
915 CpanMakefilePlParser,
916 CpanManifestParser,
917 CpanMetaJsonParser,
918 CpanMetaYmlParser,
919 CranParser,
920 DebianControlInExtractedDebParser,
921 DebianControlParser,
922 DebianCopyrightParser,
923 DebianDebianTarParser,
924 DebianDebParser,
925 DebianDistrolessInstalledParser,
926 DebianDscParser,
927 DebianInstalledListParser,
928 DebianInstalledMd5sumsParser,
929 DebianInstalledParser,
930 DebianMd5sumInPackageParser,
931 DebianOrigTarParser,
932 DenoParser,
933 DenoLockParser,
934 DockerfileParser,
935 ErlangAppSrcParser,
936 RebarConfigParser,
937 RebarLockParser,
938 FreebsdCompactManifestParser,
939 GemArchiveParser,
940 GemfileLockParser,
941 GemfileParser,
942 GemMetadataExtractedParser,
943 GemspecParser,
944 GitmodulesParser,
945 GodepsParser,
946 GoModParser,
947 GoModGraphParser,
948 GoSumParser,
949 GoWorkParser,
950 GradleLockfileParser,
951 GradleParser,
952 GradleModuleParser,
953 HackageCabalParser,
954 HackageCabalProjectParser,
955 HackageStackYamlParser,
956 HelmChartYamlParser,
957 HelmChartLockParser,
958 HaxeParser,
959 HexLockParser,
960 HuggingfaceModelCardParser,
961 HuggingfaceConfigParser,
962 HuggingfaceModelIndexParser,
963 JuliaManifestTomlParser,
964 JuliaProjectTomlParser,
965 MavenParser,
966 MesonParser,
967 MicrosoftUpdateManifestParser,
968 NixDefaultParser,
969 NixFlakeLockParser,
970 NixFlakeParser,
971 NpmLockParser,
972 NpmParser,
973 NpmWorkspaceParser,
974 DotNetDepsJsonParser,
975 CentralPackageManagementPropsParser,
976 DirectoryBuildPropsParser,
977 NupkgParser,
978 NuspecParser,
979 PackageReferenceProjectParser,
980 OciImageLayoutParser,
981 OpamParser,
982 OsReleaseParser,
983 PackagesConfigParser,
984 PackagesLockParser,
985 ProjectJsonParser,
986 ProjectLockJsonParser,
987 PipfileLockParser,
988 PipInspectDeplockParser,
989 PixiTomlParser,
990 PixiLockParser,
991 PnpmLockParser,
992 PodfileLockParser,
993 PodfileParser,
994 PodspecJsonParser,
995 PodspecParser,
996 PoetryLockParser,
997 PubliccodeParser,
998 PylockTomlParser,
999 PubspecLockParser,
1000 PubspecYamlParser,
1001 PythonParser,
1002 UvLockParser,
1003 VcpkgManifestParser,
1004 ReadmeParser,
1005 RequirementsTxtParser,
1006 RpmBdbDatabaseParser,
1007 RpmLicenseFilesParser,
1008 RpmMarinerManifestParser,
1009 RpmNdbDatabaseParser,
1010 RpmParser,
1011 RpmSpecfileParser,
1012 #[cfg(feature = "rpm-sqlite")]
1013 RpmSqliteDatabaseParser,
1014 RpmYumdbParser,
1015 SbtParser,
1016 SwiftManifestJsonParser,
1017 SwiftPackageResolvedParser,
1018 SwiftShowDependenciesParser,
1019 YarnLockParser,
1020 YarnPnpParser,
1021 ],
1022 recognizers: [
1023 AndroidLibraryRecognizer,
1024 AppleDmgRecognizer,
1025 Axis2MarRecognizer,
1026 Axis2ModuleXmlRecognizer,
1027 CabArchiveRecognizer,
1028 ChromeCrxRecognizer,
1029 InstallShieldRecognizer,
1030 IosIpaRecognizer,
1031 IsoImageRecognizer,
1032 IvyXmlRecognizer,
1033 JavaEarAppXmlRecognizer,
1034 JavaEarRecognizer,
1035 JavaJarRecognizer,
1036 JavaWarRecognizer,
1037 JavaWarWebXmlRecognizer,
1038 JBossSarRecognizer,
1039 JBossServiceXmlRecognizer,
1040 MeteorPackageRecognizer,
1041 MozillaXpiRecognizer,
1042 NsisRecognizer,
1043 SharArchiveRecognizer,
1044 SquashfsRecognizer,
1045 ],
1046}
1047
1048#[cfg(test)]
1049mod panic_isolation_tests {
1050 use super::*;
1051 use crate::models::DiagnosticSeverity;
1052
1053 #[test]
1054 fn capture_parser_diagnostics_turns_panics_into_scan_errors() {
1055 let path = Path::new("fixtures/panic-package.json");
1056 let result = capture_parser_diagnostics(
1057 || -> Vec<PackageData> { panic!("panic boom") },
1058 "PanicParser",
1059 path,
1060 None,
1061 );
1062
1063 assert!(result.packages.is_empty());
1064 assert_eq!(result.scan_diagnostics.len(), 1);
1065 assert_eq!(
1066 result.scan_diagnostics[0].severity,
1067 DiagnosticSeverity::Error
1068 );
1069 assert!(result.scan_diagnostics[0].message.contains("PanicParser"));
1070 assert!(
1071 result.scan_diagnostics[0]
1072 .message
1073 .contains("fixtures/panic-package.json")
1074 );
1075 assert!(result.scan_diagnostics[0].message.contains("panic boom"));
1076 }
1077
1078 #[test]
1079 fn capture_parser_diagnostics_recovers_after_panic() {
1080 let panic_path = Path::new("fixtures/panic-package.json");
1081 let _ = capture_parser_diagnostics(
1082 || -> Vec<PackageData> { panic!("panic boom") },
1083 "PanicParser",
1084 panic_path,
1085 None,
1086 );
1087
1088 let ok_path = Path::new("fixtures/recovered-package.json");
1089 let result = capture_parser_diagnostics(
1090 || {
1091 crate::parser_warn!("recoverable parser warning");
1092 vec![PackageData {
1093 package_type: Some(PackageType::Npm),
1094 ..Default::default()
1095 }]
1096 },
1097 "RecoveringParser",
1098 ok_path,
1099 None,
1100 );
1101
1102 assert_eq!(result.packages.len(), 1);
1103 assert_eq!(result.scan_diagnostics.len(), 1);
1104 assert_eq!(
1105 result.scan_diagnostics[0].message,
1106 "recoverable parser warning"
1107 );
1108 assert_eq!(
1109 result.scan_diagnostics[0].severity,
1110 DiagnosticSeverity::Warning
1111 );
1112 }
1113}