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