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::CappedIterExt;
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 .capped("packages emitted by parser")
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 with_parser_license_engine<T>(
445 engine: Option<Arc<LicenseDetectionEngine>>,
446 f: impl FnOnce() -> T,
447) -> T {
448 PARSER_LICENSE_ENGINE_STACK.with(|stack| {
449 stack.borrow_mut().push(engine);
450 });
451 let result = f();
452 PARSER_LICENSE_ENGINE_STACK.with(|stack| {
453 stack.borrow_mut().pop();
454 });
455 result
456}
457
458pub(crate) fn record_parser_diagnostic(message: String, severity: DiagnosticSeverity) -> bool {
459 PARSER_DIAGNOSTIC_STACK.with(|stack| {
460 let mut stack = stack.borrow_mut();
461 let Some(active) = stack.last_mut() else {
462 return false;
463 };
464 active.push(ScanDiagnostic { severity, message });
465 true
466 })
467}
468
469#[macro_export]
470macro_rules! parser_warn {
471 ($($arg:tt)*) => {{
472 let message = format!($($arg)*);
473 if !$crate::parsers::record_parser_diagnostic(
474 message.clone(),
475 $crate::models::DiagnosticSeverity::Warning,
476 ) {
477 log::warn!("{message}");
478 }
479 }};
480}
481
482pub trait PackageParser {
526 const PACKAGE_TYPE: PackageType;
528
529 fn extract_packages(path: &Path) -> Vec<PackageData>;
539
540 fn is_match(path: &Path) -> bool;
545
546 fn extract_first_package(path: &Path) -> PackageData {
549 Self::extract_packages(path)
550 .into_iter()
551 .map(|mut package| {
552 populate_declared_license_and_holder(&mut package);
553 finalize_package_declared_license_references(&mut package);
554 package
555 })
556 .next()
557 .unwrap_or_default()
558 }
559
560 fn metadata() -> Vec<metadata::ParserMetadata> {
567 Vec::new()
568 }
569}
570
571pub fn try_parse_rpm_archive_with_license_engine(
572 path: &Path,
573 license_engine: Option<Arc<LicenseDetectionEngine>>,
574) -> Option<ParsePackagesResult> {
575 if !self::rpm_parser::path_looks_like_rpm_archive(path) {
576 return None;
577 }
578
579 if <RpmParser as PackageParser>::is_match(path) {
580 return Some(capture_parser_diagnostics(
581 || self::rpm_parser::extract_rpm_packages(path),
582 stringify!(RpmParser),
583 path,
584 license_engine,
585 ));
586 }
587
588 None
589}
590
591pub fn try_parse_rpm_archive(path: &Path) -> Option<ParsePackagesResult> {
592 try_parse_rpm_archive_with_license_engine(path, None)
593}
594
595#[cfg(feature = "golden-tests")]
596pub fn try_parse_compiled_bytes(bytes: &[u8]) -> Option<ParsePackagesResult> {
597 self::compiled_binary::try_parse_compiled_bytes(bytes)
598}
599
600#[cfg(feature = "golden-tests")]
601pub fn try_parse_windows_executable_bytes(
602 path: &Path,
603 bytes: &[u8],
604) -> Option<ParsePackagesResult> {
605 self::windows_executable::try_parse_windows_executable_bytes(path, bytes)
606}
607
608pub fn path_looks_like_rpm_archive(path: &Path) -> bool {
609 self::rpm_parser::path_looks_like_rpm_archive(path)
610}
611
612pub fn all_metadata() -> Vec<metadata::ParserMetadata> {
617 let mut entries = collect_parser_metadata();
618 entries.extend_from_slice(self::compiled_binary::COMPILED_BINARY_METADATA);
619 entries.extend_from_slice(self::windows_executable::WINDOWS_EXE_METADATA);
620 entries.extend_from_slice(self::misc::RECOGNIZER_METADATA);
621 entries.extend_from_slice(crate::utils::font::FONT_METADATA);
622 entries
623}
624
625pub use self::about::AboutFileParser;
626pub use self::alpine::{AlpineApkParser, AlpineApkbuildParser, AlpineInstalledParser};
627pub use self::android::{
628 AndroidAabParser, AndroidApkParser, AndroidManifestParser, AndroidSoongMetadataParser,
629};
630#[cfg(feature = "golden-tests")]
631pub use self::android::{
632 ProtoItem, ProtoPrimitive, ProtoRawStringValue, ProtoSourcePosition, ProtoStringValue,
633 ProtoXmlAttribute, ProtoXmlElement, ProtoXmlNamespace, ProtoXmlNode, proto_item,
634 proto_primitive, proto_xml_node,
635};
636pub use self::arch::{ArchPkginfoParser, ArchSrcinfoParser};
637pub use self::autotools::AutotoolsConfigureParser;
638pub use self::bazel::{BazelBuildParser, BazelModuleParser};
639pub use self::bitbake::BitbakeRecipeParser;
640pub use self::bower::BowerJsonParser;
641pub use self::buck::{BuckBuildParser, BuckMetadataBzlParser};
642pub use self::bun_lock::BunLockParser;
643pub use self::bun_lockb::BunLockbParser;
644pub use self::cargo::CargoParser;
645#[cfg_attr(not(test), allow(unused_imports))]
646pub use self::cargo_lock::CargoLockParser;
647pub use self::carthage::{CarthageCartfileParser, CarthageCartfileResolvedParser};
648pub use self::chef::{ChefMetadataJsonParser, ChefMetadataRbParser};
649pub use self::citation::CitationCffParser;
650pub use self::clojure::{ClojureDepsEdnParser, ClojureProjectCljParser};
651pub use self::composer::{ComposerJsonParser, ComposerLockParser};
652pub use self::conan::{ConanFilePyParser, ConanLockParser, ConanfileTxtParser};
653pub use self::conan_data::ConanDataParser;
654pub use self::conda::{CondaEnvironmentYmlParser, CondaMetaYamlParser};
655pub use self::conda_meta_json::CondaMetaJsonParser;
656pub use self::cpan::{CpanManifestParser, CpanMetaJsonParser, CpanMetaYmlParser};
657pub use self::cpan_dist_ini::CpanDistIniParser;
658pub use self::cpan_makefile_pl::CpanMakefilePlParser;
659pub use self::cran::CranParser;
660pub use self::dart::{PubspecLockParser, PubspecYamlParser};
661pub use self::debian::{
662 DebianControlInExtractedDebParser, DebianControlParser, DebianCopyrightParser, DebianDebParser,
663 DebianDebianTarParser, DebianDistrolessInstalledParser, DebianDscParser,
664 DebianInstalledListParser, DebianInstalledMd5sumsParser, DebianInstalledParser,
665 DebianMd5sumInPackageParser, DebianOrigTarParser,
666};
667pub use self::deno::DenoParser;
668pub use self::deno_lock::DenoLockParser;
669pub use self::docker::DockerfileParser;
670pub use self::erlang_otp::{ErlangAppSrcParser, RebarConfigParser, RebarLockParser};
671pub use self::freebsd::FreebsdCompactManifestParser;
672pub use self::gitmodules::GitmodulesParser;
673pub use self::go::{GoModParser, GoSumParser, GoWorkParser, GodepsParser};
674pub use self::go_mod_graph::GoModGraphParser;
675pub use self::gradle::GradleParser;
676pub use self::gradle_lock::GradleLockfileParser;
677pub use self::gradle_module::GradleModuleParser;
678pub use self::hackage::{HackageCabalParser, HackageCabalProjectParser, HackageStackYamlParser};
679pub use self::haxe::HaxeParser;
680pub use self::helm::{HelmChartLockParser, HelmChartYamlParser};
681pub use self::hex_lock::HexLockParser;
682pub use self::huggingface::{
683 HuggingfaceConfigParser, HuggingfaceModelCardParser, HuggingfaceModelIndexParser,
684};
685pub use self::ivy::IvyXmlParser;
686pub use self::julia::{JuliaManifestTomlParser, JuliaProjectTomlParser};
687pub use self::maven::MavenParser;
688pub use self::meson::MesonParser;
689pub use self::microsoft_update_manifest::MicrosoftUpdateManifestParser;
690pub use self::misc::{
691 AndroidLibraryRecognizer, AppleDmgRecognizer, Axis2MarRecognizer, Axis2ModuleXmlRecognizer,
692 CabArchiveRecognizer, ChromeCrxRecognizer, InstallShieldRecognizer, IosIpaRecognizer,
693 IsoImageRecognizer, JBossSarRecognizer, JBossServiceXmlRecognizer, JavaEarAppXmlRecognizer,
694 JavaEarRecognizer, JavaJarRecognizer, JavaWarRecognizer, JavaWarWebXmlRecognizer,
695 MeteorPackageRecognizer, MozillaXpiRecognizer, NsisRecognizer, SharArchiveRecognizer,
696 SquashfsRecognizer,
697};
698pub use self::nix::{NixDefaultParser, NixFlakeLockParser, NixFlakeParser};
699pub use self::npm::NpmParser;
700pub use self::npm_lock::NpmLockParser;
701pub use self::npm_workspace::NpmWorkspaceParser;
702pub use self::nuget::{
703 CentralPackageManagementPropsParser, DirectoryBuildPropsParser, DotNetDepsJsonParser,
704 NupkgParser, NuspecParser, PackageReferenceProjectParser, PackagesConfigParser,
705 PackagesLockParser, ProjectJsonParser, ProjectLockJsonParser,
706};
707pub use self::oci::OciImageLayoutParser;
708pub use self::opam::OpamParser;
709pub use self::os_release::OsReleaseParser;
710pub use self::pip_inspect_deplock::PipInspectDeplockParser;
711pub use self::pipfile_lock::PipfileLockParser;
712pub use self::pixi::{PixiLockParser, PixiTomlParser};
713pub use self::pnpm_lock::PnpmLockParser;
714pub use self::podfile::PodfileParser;
715pub use self::podfile_lock::PodfileLockParser;
716pub use self::podspec::PodspecParser;
717pub use self::podspec_json::PodspecJsonParser;
718pub use self::poetry_lock::PoetryLockParser;
719pub use self::publiccode::PubliccodeParser;
720pub use self::pylock_toml::PylockTomlParser;
721pub use self::python::PythonParser;
722pub use self::readme::ReadmeParser;
723pub use self::requirements_txt::RequirementsTxtParser;
724#[cfg(feature = "rpm-sqlite")]
725pub use self::rpm_db::RpmSqliteDatabaseParser;
726pub use self::rpm_db::{RpmBdbDatabaseParser, RpmNdbDatabaseParser};
727pub use self::rpm_license_files::RpmLicenseFilesParser;
728pub use self::rpm_mariner_manifest::RpmMarinerManifestParser;
729pub use self::rpm_parser::RpmParser;
730pub use self::rpm_specfile::RpmSpecfileParser;
731pub use self::rpm_yumdb::RpmYumdbParser;
732pub use self::ruby::{
733 GemArchiveParser, GemMetadataExtractedParser, GemfileLockParser, GemfileParser, GemspecParser,
734};
735pub use self::sbt::SbtParser;
736pub use self::swift_manifest_json::SwiftManifestJsonParser;
737pub use self::swift_resolved::SwiftPackageResolvedParser;
738pub use self::swift_show_dependencies::SwiftShowDependenciesParser;
739pub use self::uv_lock::UvLockParser;
740pub use self::vcpkg::{VcpkgLockParser, VcpkgManifestParser};
741pub use self::yarn_lock::YarnLockParser;
742pub use self::yarn_pnp::YarnPnpParser;
743
744macro_rules! register_package_handlers {
750 (
751 parsers: [$($(#[$parser_meta:meta])* $parser:ty),* $(,)?],
752 recognizers: [$($recognizer:ty),* $(,)?] $(,)?
753 ) => {
754 pub fn try_parse_file_with_license_engine(
755 path: &Path,
756 license_engine: Option<Arc<LicenseDetectionEngine>>,
757 ) -> Option<ParsePackagesResult> {
758 $(
759 $(#[$parser_meta])*
760 if <$parser>::is_match(path) {
761 return Some(capture_parser_diagnostics(
762 || <$parser>::extract_packages(path),
763 stringify!($parser),
764 path,
765 license_engine.clone(),
766 ));
767 }
768 )*
769 $(
770 if <$recognizer>::is_match(path) {
771 return Some(capture_parser_diagnostics(
772 || <$recognizer>::extract_packages(path),
773 stringify!($recognizer),
774 path,
775 license_engine.clone(),
776 ));
777 }
778 )*
779 None
780 }
781
782 pub fn try_parse_file(path: &Path) -> Option<ParsePackagesResult> {
783 try_parse_file_with_license_engine(path, None)
784 }
785
786 #[allow(dead_code)]
789 pub fn parse_by_type_name(type_name: &str, path: &Path) -> Option<PackageData> {
790 match type_name {
791 $(
792 $(#[$parser_meta])*
793 stringify!($parser) => Some(<$parser>::extract_first_package(path)),
794 )*
795 $(
796 stringify!($recognizer) => Some(<$recognizer>::extract_first_package(path)),
797 )*
798 _ => None
799 }
800 }
801
802 #[allow(dead_code)]
805 pub fn list_parser_types() -> Vec<&'static str> {
806 vec![
807 $(
808 $(#[$parser_meta])*
809 stringify!($parser),
810 )*
811 $(
812 stringify!($recognizer),
813 )*
814 ]
815 }
816
817 pub fn collect_parser_metadata() -> Vec<metadata::ParserMetadata> {
819 let mut entries = Vec::new();
820 $(
821 $(#[$parser_meta])*
822 entries.extend(<$parser>::metadata());
823 )*
824 $(
825 entries.extend(<$recognizer>::metadata());
826 )*
827 entries
828 }
829 };
830}
831
832#[cfg(test)]
833mod tests {
834 use std::collections::HashMap;
835
836 use super::{active_parser_license_engine, capture_parser_diagnostics};
837 use crate::license_detection::LicenseDetectionEngine;
838 use crate::models::PackageData;
839 use crate::parsers::license_normalization::{
840 clear_last_parser_license_engine_ptr, last_parser_license_engine_ptr,
841 };
842 use std::path::Path;
843 use std::sync::Arc;
844
845 #[test]
846 fn test_capture_parser_diagnostics_exposes_active_license_engine() {
847 let engine =
848 Arc::new(LicenseDetectionEngine::from_embedded().expect("embedded engine should load"));
849
850 let result = capture_parser_diagnostics(
851 || {
852 assert!(active_parser_license_engine().is_some());
853 vec![PackageData::default()]
854 },
855 "TestParser",
856 Path::new("testdata/package.json"),
857 Some(engine),
858 );
859
860 assert_eq!(result.packages.len(), 1);
861 assert!(active_parser_license_engine().is_none());
862 }
863
864 #[test]
865 fn test_capture_parser_diagnostics_keeps_active_license_engine_for_finalization() {
866 let engine =
867 Arc::new(LicenseDetectionEngine::from_embedded().expect("embedded engine should load"));
868 clear_last_parser_license_engine_ptr();
869
870 let result = capture_parser_diagnostics(
871 || {
872 vec![PackageData {
873 declared_license_expression: Some("mit".to_string()),
874 declared_license_expression_spdx: Some("MIT".to_string()),
875 extracted_license_statement: Some("MIT".to_string()),
876 extra_data: Some(HashMap::from([(
877 "license_file".to_string(),
878 serde_json::Value::String("LICENSE".to_string()),
879 )])),
880 ..Default::default()
881 }]
882 },
883 "TestParser",
884 Path::new("testdata/package.json"),
885 Some(Arc::clone(&engine)),
886 );
887
888 assert_eq!(result.packages.len(), 1);
889 assert_eq!(
890 last_parser_license_engine_ptr(),
891 Some(Arc::as_ptr(&engine) as usize)
892 );
893 assert_eq!(
894 result.packages[0].license_detections[0].matches[0]
895 .referenced_filenames
896 .as_ref(),
897 Some(&vec!["LICENSE".to_string()])
898 );
899 assert!(active_parser_license_engine().is_none());
900 }
901}
902
903register_package_handlers! {
904 parsers: [
905 AboutFileParser,
906 AndroidAabParser,
907 AndroidApkParser,
908 AndroidManifestParser,
909 AndroidSoongMetadataParser,
910 AlpineApkParser,
911 AlpineApkbuildParser,
912 AlpineInstalledParser,
913 ArchPkginfoParser,
914 ArchSrcinfoParser,
915 AutotoolsConfigureParser,
916 BazelBuildParser,
917 BazelModuleParser,
918 BitbakeRecipeParser,
919 BowerJsonParser,
920 BunLockParser,
921 BunLockbParser,
922 BuckBuildParser,
923 BuckMetadataBzlParser,
924 CargoLockParser,
925 CargoParser,
926 CarthageCartfileParser,
927 CarthageCartfileResolvedParser,
928 ChefMetadataJsonParser,
929 ChefMetadataRbParser,
930 CitationCffParser,
931 ClojureDepsEdnParser,
932 ClojureProjectCljParser,
933 ComposerJsonParser,
934 ComposerLockParser,
935 ConanDataParser,
936 ConanFilePyParser,
937 ConanfileTxtParser,
938 ConanLockParser,
939 CondaEnvironmentYmlParser,
940 CondaMetaJsonParser,
941 CondaMetaYamlParser,
942 CpanDistIniParser,
943 CpanMakefilePlParser,
944 CpanManifestParser,
945 CpanMetaJsonParser,
946 CpanMetaYmlParser,
947 CranParser,
948 DebianControlInExtractedDebParser,
949 DebianControlParser,
950 DebianCopyrightParser,
951 DebianDebianTarParser,
952 DebianDebParser,
953 DebianDistrolessInstalledParser,
954 DebianDscParser,
955 DebianInstalledListParser,
956 DebianInstalledMd5sumsParser,
957 DebianInstalledParser,
958 DebianMd5sumInPackageParser,
959 DebianOrigTarParser,
960 DenoParser,
961 DenoLockParser,
962 DockerfileParser,
963 ErlangAppSrcParser,
964 RebarConfigParser,
965 RebarLockParser,
966 FreebsdCompactManifestParser,
967 GemArchiveParser,
968 GemfileLockParser,
969 GemfileParser,
970 GemMetadataExtractedParser,
971 GemspecParser,
972 GitmodulesParser,
973 GodepsParser,
974 GoModParser,
975 GoModGraphParser,
976 GoSumParser,
977 GoWorkParser,
978 GradleLockfileParser,
979 GradleParser,
980 GradleModuleParser,
981 HackageCabalParser,
982 HackageCabalProjectParser,
983 HackageStackYamlParser,
984 HelmChartYamlParser,
985 HelmChartLockParser,
986 HaxeParser,
987 HexLockParser,
988 HuggingfaceModelCardParser,
989 HuggingfaceConfigParser,
990 HuggingfaceModelIndexParser,
991 IvyXmlParser,
992 JuliaManifestTomlParser,
993 JuliaProjectTomlParser,
994 MavenParser,
995 MesonParser,
996 MicrosoftUpdateManifestParser,
997 NixDefaultParser,
998 NixFlakeLockParser,
999 NixFlakeParser,
1000 NpmLockParser,
1001 NpmParser,
1002 NpmWorkspaceParser,
1003 DotNetDepsJsonParser,
1004 CentralPackageManagementPropsParser,
1005 DirectoryBuildPropsParser,
1006 NupkgParser,
1007 NuspecParser,
1008 PackageReferenceProjectParser,
1009 OciImageLayoutParser,
1010 OpamParser,
1011 OsReleaseParser,
1012 PackagesConfigParser,
1013 PackagesLockParser,
1014 ProjectJsonParser,
1015 ProjectLockJsonParser,
1016 PipfileLockParser,
1017 PipInspectDeplockParser,
1018 PixiTomlParser,
1019 PixiLockParser,
1020 PnpmLockParser,
1021 PodfileLockParser,
1022 PodfileParser,
1023 PodspecJsonParser,
1024 PodspecParser,
1025 PoetryLockParser,
1026 PubliccodeParser,
1027 PylockTomlParser,
1028 PubspecLockParser,
1029 PubspecYamlParser,
1030 PythonParser,
1031 UvLockParser,
1032 VcpkgLockParser,
1033 VcpkgManifestParser,
1034 ReadmeParser,
1035 RequirementsTxtParser,
1036 RpmBdbDatabaseParser,
1037 RpmLicenseFilesParser,
1038 RpmMarinerManifestParser,
1039 RpmNdbDatabaseParser,
1040 RpmParser,
1041 RpmSpecfileParser,
1042 #[cfg(feature = "rpm-sqlite")]
1043 RpmSqliteDatabaseParser,
1044 RpmYumdbParser,
1045 SbtParser,
1046 SwiftManifestJsonParser,
1047 SwiftPackageResolvedParser,
1048 SwiftShowDependenciesParser,
1049 YarnLockParser,
1050 YarnPnpParser,
1051 ],
1052 recognizers: [
1053 AndroidLibraryRecognizer,
1054 AppleDmgRecognizer,
1055 Axis2MarRecognizer,
1056 Axis2ModuleXmlRecognizer,
1057 CabArchiveRecognizer,
1058 ChromeCrxRecognizer,
1059 InstallShieldRecognizer,
1060 IosIpaRecognizer,
1061 IsoImageRecognizer,
1062 JavaEarAppXmlRecognizer,
1063 JavaEarRecognizer,
1064 JavaJarRecognizer,
1065 JavaWarRecognizer,
1066 JavaWarWebXmlRecognizer,
1067 JBossSarRecognizer,
1068 JBossServiceXmlRecognizer,
1069 MeteorPackageRecognizer,
1070 MozillaXpiRecognizer,
1071 NsisRecognizer,
1072 SharArchiveRecognizer,
1073 SquashfsRecognizer,
1074 ],
1075}
1076
1077#[cfg(test)]
1078mod panic_isolation_tests {
1079 use super::*;
1080 use crate::models::DiagnosticSeverity;
1081
1082 #[test]
1083 fn capture_parser_diagnostics_turns_panics_into_scan_errors() {
1084 let path = Path::new("fixtures/panic-package.json");
1085 let result = capture_parser_diagnostics(
1086 || -> Vec<PackageData> { panic!("panic boom") },
1087 "PanicParser",
1088 path,
1089 None,
1090 );
1091
1092 assert!(result.packages.is_empty());
1093 assert_eq!(result.scan_diagnostics.len(), 1);
1094 assert_eq!(
1095 result.scan_diagnostics[0].severity,
1096 DiagnosticSeverity::Error
1097 );
1098 assert!(result.scan_diagnostics[0].message.contains("PanicParser"));
1099 assert!(
1100 result.scan_diagnostics[0]
1101 .message
1102 .contains("fixtures/panic-package.json")
1103 );
1104 assert!(result.scan_diagnostics[0].message.contains("panic boom"));
1105 }
1106
1107 #[test]
1108 fn capture_parser_diagnostics_recovers_after_panic() {
1109 let panic_path = Path::new("fixtures/panic-package.json");
1110 let _ = capture_parser_diagnostics(
1111 || -> Vec<PackageData> { panic!("panic boom") },
1112 "PanicParser",
1113 panic_path,
1114 None,
1115 );
1116
1117 let ok_path = Path::new("fixtures/recovered-package.json");
1118 let result = capture_parser_diagnostics(
1119 || {
1120 crate::parser_warn!("recoverable parser warning");
1121 vec![PackageData {
1122 package_type: Some(PackageType::Npm),
1123 ..Default::default()
1124 }]
1125 },
1126 "RecoveringParser",
1127 ok_path,
1128 None,
1129 );
1130
1131 assert_eq!(result.packages.len(), 1);
1132 assert_eq!(result.scan_diagnostics.len(), 1);
1133 assert_eq!(
1134 result.scan_diagnostics[0].message,
1135 "recoverable parser warning"
1136 );
1137 assert_eq!(
1138 result.scan_diagnostics[0].severity,
1139 DiagnosticSeverity::Warning
1140 );
1141 }
1142}