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