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