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