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