Skip to main content

provenant/parsers/
mod.rs

1mod about;
2#[cfg(test)]
3mod about_scan_test;
4#[cfg(test)]
5mod about_test;
6mod alpine;
7#[cfg(test)]
8mod alpine_scan_test;
9mod arch;
10#[cfg(test)]
11mod arch_scan_test;
12#[cfg(test)]
13mod arch_test;
14mod autotools;
15#[cfg(test)]
16mod autotools_test;
17mod bazel;
18#[cfg(test)]
19mod bazel_module_test;
20#[cfg(test)]
21mod bazel_test;
22mod bower;
23#[cfg(test)]
24mod bower_scan_test;
25#[cfg(test)]
26mod bower_test;
27mod buck;
28#[cfg(test)]
29mod buck_test;
30mod bun_lock;
31#[cfg(test)]
32mod bun_lock_test;
33mod bun_lockb;
34#[cfg(test)]
35mod bun_lockb_test;
36mod cargo;
37mod cargo_lock;
38#[cfg(test)]
39mod cargo_lock_test;
40#[cfg(test)]
41mod cargo_scan_test;
42#[cfg(test)]
43mod cargo_test;
44mod chef;
45#[cfg(test)]
46mod chef_scan_test;
47#[cfg(test)]
48mod chef_test;
49mod citation;
50#[cfg(test)]
51mod citation_test;
52mod clojure;
53#[cfg(test)]
54mod clojure_test;
55#[cfg(test)]
56mod cocoapods_scan_test;
57pub(crate) mod compiled_binary;
58mod composer;
59#[cfg(test)]
60mod composer_scan_test;
61#[cfg(test)]
62mod composer_test;
63mod conan;
64mod conan_data;
65#[cfg(test)]
66mod conan_data_test;
67#[cfg(test)]
68mod conan_scan_test;
69#[cfg(test)]
70mod conan_test;
71mod conda;
72mod conda_meta_json;
73#[cfg(test)]
74mod conda_meta_json_test;
75#[cfg(test)]
76mod conda_scan_test;
77#[cfg(test)]
78mod conda_test;
79mod cpan;
80mod cpan_dist_ini;
81#[cfg(test)]
82mod cpan_dist_ini_test;
83mod cpan_makefile_pl;
84#[cfg(test)]
85mod cpan_makefile_pl_test;
86#[cfg(test)]
87mod cpan_scan_test;
88#[cfg(test)]
89mod cpan_test;
90mod cran;
91#[cfg(test)]
92mod cran_scan_test;
93#[cfg(test)]
94mod cran_test;
95mod dart;
96#[cfg(test)]
97mod dart_scan_test;
98#[cfg(test)]
99mod dart_test;
100mod debian;
101#[cfg(test)]
102mod debian_scan_test;
103#[cfg(test)]
104mod debian_test;
105mod deno;
106mod deno_lock;
107#[cfg(test)]
108mod deno_lock_test;
109#[cfg(test)]
110mod deno_scan_test;
111#[cfg(test)]
112mod deno_test;
113mod docker;
114#[cfg(test)]
115mod docker_scan_test;
116#[cfg(test)]
117mod docker_test;
118mod freebsd;
119#[cfg(test)]
120mod freebsd_scan_test;
121#[cfg(test)]
122mod freebsd_test;
123mod gitmodules;
124#[cfg(test)]
125mod gitmodules_scan_test;
126mod go;
127mod go_mod_graph;
128#[cfg(test)]
129mod go_scan_test;
130#[cfg(test)]
131mod go_test;
132#[cfg(test)]
133mod go_work_test;
134#[cfg(all(test, feature = "golden-tests"))]
135pub(crate) mod golden_test_utils;
136mod gradle;
137mod gradle_lock;
138#[cfg(test)]
139mod gradle_lock_test;
140mod gradle_module;
141#[cfg(test)]
142mod gradle_module_scan_test;
143#[cfg(test)]
144mod gradle_module_test;
145#[cfg(test)]
146mod gradle_scan_test;
147mod hackage;
148#[cfg(test)]
149mod hackage_scan_test;
150#[cfg(test)]
151mod hackage_test;
152mod haxe;
153#[cfg(test)]
154mod haxe_scan_test;
155#[cfg(test)]
156mod haxe_test;
157mod helm;
158#[cfg(test)]
159mod helm_scan_test;
160#[cfg(test)]
161mod helm_test;
162mod hex_lock;
163#[cfg(test)]
164mod hex_lock_test;
165mod julia;
166#[cfg(test)]
167mod julia_test;
168mod license_normalization;
169mod maven;
170#[cfg(test)]
171mod maven_scan_test;
172#[cfg(test)]
173mod maven_test;
174mod meson;
175#[cfg(test)]
176mod meson_scan_test;
177#[cfg(test)]
178mod meson_test;
179pub mod metadata;
180mod microsoft_update_manifest;
181#[cfg(test)]
182mod microsoft_update_manifest_test;
183mod misc;
184#[cfg(test)]
185mod misc_test;
186mod nix;
187#[cfg(test)]
188mod nix_scan_test;
189#[cfg(test)]
190mod nix_test;
191mod npm;
192mod npm_lock;
193#[cfg(test)]
194mod npm_lock_test;
195#[cfg(test)]
196mod npm_scan_test;
197#[cfg(test)]
198mod npm_test;
199mod npm_workspace;
200#[cfg(test)]
201mod npm_workspace_test;
202mod nuget;
203#[cfg(test)]
204mod nuget_scan_test;
205#[cfg(test)]
206mod nuget_test;
207mod opam;
208#[cfg(test)]
209mod opam_scan_test;
210mod os_release;
211#[cfg(test)]
212mod os_release_test;
213#[cfg(test)]
214mod osgi_test;
215mod pep508;
216mod pip_inspect_deplock;
217#[cfg(test)]
218mod pip_inspect_deplock_test;
219mod pipfile_lock;
220#[cfg(test)]
221mod pipfile_lock_test;
222mod pixi;
223#[cfg(test)]
224mod pixi_scan_test;
225#[cfg(test)]
226mod pixi_test;
227mod pnpm_lock;
228#[cfg(test)]
229mod pnpm_lock_test;
230mod podfile;
231mod podfile_lock;
232#[cfg(test)]
233mod podfile_lock_test;
234mod podspec;
235mod podspec_json;
236#[cfg(test)]
237mod podspec_json_test;
238mod poetry_lock;
239#[cfg(test)]
240mod poetry_lock_test;
241mod publiccode;
242#[cfg(test)]
243mod publiccode_test;
244mod pylock_toml;
245#[cfg(test)]
246mod pylock_toml_test;
247mod python;
248#[cfg(test)]
249mod python_scan_test;
250#[cfg(test)]
251mod python_test;
252mod readme;
253#[cfg(test)]
254mod readme_test;
255mod requirements_txt;
256#[cfg(test)]
257mod requirements_txt_test;
258pub(crate) mod rfc822;
259mod rpm_db;
260mod rpm_db_native;
261#[cfg(test)]
262mod rpm_db_scan_test;
263mod rpm_license_files;
264#[cfg(test)]
265mod rpm_license_files_test;
266mod rpm_mariner_manifest;
267#[cfg(test)]
268mod rpm_mariner_manifest_test;
269mod rpm_parser;
270#[cfg(test)]
271mod rpm_scan_test;
272mod rpm_specfile;
273#[cfg(test)]
274mod rpm_specfile_test;
275mod rpm_yumdb;
276mod ruby;
277#[cfg(test)]
278mod ruby_scan_test;
279#[cfg(test)]
280mod ruby_test;
281mod sbt;
282#[cfg(test)]
283mod sbt_test;
284#[cfg(test)]
285mod scan_test_utils;
286mod swift_manifest_json;
287#[cfg(test)]
288mod swift_manifest_json_test;
289mod swift_resolved;
290#[cfg(test)]
291mod swift_resolved_test;
292#[cfg(test)]
293mod swift_scan_test;
294mod swift_show_dependencies;
295#[cfg(test)]
296mod swift_show_dependencies_test;
297pub mod utils;
298mod uv_lock;
299#[cfg(test)]
300mod uv_lock_test;
301mod vcpkg;
302#[cfg(test)]
303mod vcpkg_scan_test;
304#[cfg(test)]
305mod vcpkg_test;
306pub(crate) mod windows_executable;
307#[cfg(test)]
308mod windows_executable_golden_test;
309mod yarn_lock;
310#[cfg(test)]
311mod yarn_lock_test;
312mod yarn_pnp;
313#[cfg(test)]
314mod yarn_pnp_test;
315
316#[cfg(all(test, feature = "golden-tests"))]
317mod golden_test;
318
319use std::cell::RefCell;
320use std::panic::{AssertUnwindSafe, catch_unwind};
321use std::path::Path;
322
323use crate::models::{PackageData, PackageType};
324use crate::parsers::license_normalization::finalize_package_declared_license_references;
325
326thread_local! {
327    static PARSER_DIAGNOSTIC_STACK: RefCell<Vec<Vec<String>>> = const { RefCell::new(Vec::new()) };
328}
329
330#[derive(Debug, Default)]
331pub struct ParsePackagesResult {
332    pub packages: Vec<PackageData>,
333    pub scan_errors: Vec<String>,
334}
335
336fn panic_payload_to_string(payload: &(dyn std::any::Any + Send)) -> String {
337    if let Some(message) = payload.downcast_ref::<&str>() {
338        (*message).to_string()
339    } else if let Some(message) = payload.downcast_ref::<String>() {
340        message.clone()
341    } else {
342        "unknown panic payload".to_string()
343    }
344}
345
346pub(crate) fn capture_parser_diagnostics<F>(
347    extract: F,
348    handler_name: &str,
349    path: &Path,
350) -> ParsePackagesResult
351where
352    F: FnOnce() -> Vec<PackageData>,
353{
354    PARSER_DIAGNOSTIC_STACK.with(|stack| {
355        stack.borrow_mut().push(Vec::new());
356    });
357
358    let extract_result = catch_unwind(AssertUnwindSafe(extract));
359    let mut scan_errors =
360        PARSER_DIAGNOSTIC_STACK.with(|stack| stack.borrow_mut().pop().unwrap_or_default());
361
362    match extract_result {
363        Ok(packages) => ParsePackagesResult {
364            packages: packages
365                .into_iter()
366                .map(|mut package| {
367                    finalize_package_declared_license_references(&mut package);
368                    package
369                })
370                .collect(),
371            scan_errors,
372        },
373        Err(payload) => {
374            scan_errors.push(format!(
375                "{} panicked while parsing {}: {}",
376                handler_name,
377                path.display(),
378                panic_payload_to_string(payload.as_ref())
379            ));
380            ParsePackagesResult {
381                packages: Vec::new(),
382                scan_errors,
383            }
384        }
385    }
386}
387
388pub(crate) fn record_parser_diagnostic(message: String) -> bool {
389    PARSER_DIAGNOSTIC_STACK.with(|stack| {
390        let mut stack = stack.borrow_mut();
391        let Some(active) = stack.last_mut() else {
392            return false;
393        };
394        active.push(message);
395        true
396    })
397}
398
399#[macro_export]
400macro_rules! parser_warn {
401    ($($arg:tt)*) => {{
402        let message = format!($($arg)*);
403        if !$crate::parsers::record_parser_diagnostic(message.clone()) {
404            log::warn!("{message}");
405        }
406    }};
407}
408
409/// Package parser trait for extracting metadata from package manifest files.
410///
411/// Each parser implementation handles a specific package manager/ecosystem
412/// (npm, Maven, Python, Cargo, etc.) and extracts standardized metadata into
413/// `PackageData` structures compatible with ScanCode Toolkit JSON output format.
414///
415/// # Implementation Guide
416///
417/// Implementors must provide:
418/// - `PACKAGE_TYPE`: Package URL (purl) type identifier (e.g., "npm", "pypi", "maven")
419/// - `is_match()`: Returns true if the given file path matches this parser's expected format
420/// - `extract_packages()`: Parses the file and returns all extracted package metadata
421///
422/// # Error Handling
423///
424/// Parsers should handle errors gracefully by returning default/empty `PackageData`
425/// and logging warnings with [`crate::parser_warn!`] rather than panicking. Scanner
426/// dispatch captures those warnings and attaches them to `FileInfo.scan_errors` so
427/// CI output and serialized scan results stay aligned.
428/// This allows the scan to continue processing other files even when individual
429/// files fail to parse.
430///
431/// # Example
432///
433/// ```ignore
434/// use provenant::models::{PackageData, PackageType};
435/// use provenant::parsers::PackageParser;
436/// use std::path::Path;
437///
438/// pub struct MyParser;
439///
440/// impl PackageParser for MyParser {
441///     const PACKAGE_TYPE: PackageType = PackageType::Npm;
442///
443///     fn is_match(path: &Path) -> bool {
444///         path.file_name().is_some_and(|name| name == "package.json")
445///     }
446///
447///     fn extract_packages(path: &Path) -> Vec<PackageData> {
448///         vec![PackageData::default()]
449///     }
450/// }
451/// ```
452pub trait PackageParser {
453    /// Package URL type identifier for this parser (e.g., PackageType::Npm, PackageType::Pypi).
454    const PACKAGE_TYPE: PackageType;
455
456    /// Extracts all packages from the given file path.
457    ///
458    /// Returns a vector of `PackageData` structures containing all extracted metadata
459    /// including name, version, dependencies, licenses, etc. Most parsers return a
460    /// single-element vector, but some (e.g., Bazel BUILD, Buck BUCK, Debian control)
461    /// can contain multiple packages in a single file.
462    ///
463    /// On parse errors, returns a vector with a default `PackageData` with minimal or
464    /// no fields populated.
465    fn extract_packages(path: &Path) -> Vec<PackageData>;
466
467    /// Checks if the given file path matches this parser's expected format.
468    ///
469    /// Returns true if the file should be handled by this parser based on filename,
470    /// extension, or path patterns. Used by the scanner to route files to appropriate parsers.
471    fn is_match(path: &Path) -> bool;
472
473    /// Returns the first package from [`extract_packages()`](Self::extract_packages),
474    /// or a default [`PackageData`] if the file contains no packages.
475    fn extract_first_package(path: &Path) -> PackageData {
476        Self::extract_packages(path)
477            .into_iter()
478            .map(|mut package| {
479                finalize_package_declared_license_references(&mut package);
480                package
481            })
482            .next()
483            .unwrap_or_default()
484    }
485}
486
487pub use self::about::AboutFileParser;
488pub use self::alpine::{AlpineApkParser, AlpineApkbuildParser, AlpineInstalledParser};
489pub use self::arch::{ArchPkginfoParser, ArchSrcinfoParser};
490pub use self::autotools::AutotoolsConfigureParser;
491pub use self::bazel::{BazelBuildParser, BazelModuleParser};
492pub use self::bower::BowerJsonParser;
493pub use self::buck::{BuckBuildParser, BuckMetadataBzlParser};
494pub use self::bun_lock::BunLockParser;
495pub use self::bun_lockb::BunLockbParser;
496pub use self::cargo::CargoParser;
497#[cfg_attr(not(test), allow(unused_imports))]
498pub use self::cargo_lock::CargoLockParser;
499pub use self::chef::{ChefMetadataJsonParser, ChefMetadataRbParser};
500pub use self::citation::CitationCffParser;
501pub use self::clojure::{ClojureDepsEdnParser, ClojureProjectCljParser};
502pub use self::composer::{ComposerJsonParser, ComposerLockParser};
503pub use self::conan::{ConanFilePyParser, ConanLockParser, ConanfileTxtParser};
504pub use self::conan_data::ConanDataParser;
505pub use self::conda::{CondaEnvironmentYmlParser, CondaMetaYamlParser};
506pub use self::conda_meta_json::CondaMetaJsonParser;
507pub use self::cpan::{CpanManifestParser, CpanMetaJsonParser, CpanMetaYmlParser};
508pub use self::cpan_dist_ini::CpanDistIniParser;
509pub use self::cpan_makefile_pl::CpanMakefilePlParser;
510pub use self::cran::CranParser;
511pub use self::dart::{PubspecLockParser, PubspecYamlParser};
512pub use self::debian::{
513    DebianControlInExtractedDebParser, DebianControlParser, DebianCopyrightParser, DebianDebParser,
514    DebianDebianTarParser, DebianDistrolessInstalledParser, DebianDscParser,
515    DebianInstalledListParser, DebianInstalledMd5sumsParser, DebianInstalledParser,
516    DebianMd5sumInPackageParser, DebianOrigTarParser,
517};
518pub use self::deno::DenoParser;
519pub use self::deno_lock::DenoLockParser;
520pub use self::docker::DockerfileParser;
521pub use self::freebsd::FreebsdCompactManifestParser;
522pub use self::gitmodules::GitmodulesParser;
523pub use self::go::{GoModParser, GoSumParser, GoWorkParser, GodepsParser};
524pub use self::go_mod_graph::GoModGraphParser;
525pub use self::gradle::GradleParser;
526pub use self::gradle_lock::GradleLockfileParser;
527pub use self::gradle_module::GradleModuleParser;
528pub use self::hackage::{HackageCabalParser, HackageCabalProjectParser, HackageStackYamlParser};
529pub use self::haxe::HaxeParser;
530pub use self::helm::{HelmChartLockParser, HelmChartYamlParser};
531pub use self::hex_lock::HexLockParser;
532pub use self::julia::{JuliaManifestTomlParser, JuliaProjectTomlParser};
533pub use self::maven::MavenParser;
534pub use self::meson::MesonParser;
535pub use self::microsoft_update_manifest::MicrosoftUpdateManifestParser;
536pub use self::misc::{
537    AndroidApkRecognizer, AndroidLibraryRecognizer, AppleDmgRecognizer, Axis2MarRecognizer,
538    Axis2ModuleXmlRecognizer, CabArchiveRecognizer, ChromeCrxRecognizer, InstallShieldRecognizer,
539    IosIpaRecognizer, IsoImageRecognizer, IvyXmlRecognizer, JBossSarRecognizer,
540    JBossServiceXmlRecognizer, JavaEarAppXmlRecognizer, JavaEarRecognizer, JavaJarRecognizer,
541    JavaWarRecognizer, JavaWarWebXmlRecognizer, MeteorPackageRecognizer, MozillaXpiRecognizer,
542    NsisRecognizer, SharArchiveRecognizer, SquashfsRecognizer,
543};
544pub use self::nix::{NixDefaultParser, NixFlakeLockParser, NixFlakeParser};
545pub use self::npm::NpmParser;
546pub use self::npm_lock::NpmLockParser;
547pub use self::npm_workspace::NpmWorkspaceParser;
548pub use self::nuget::{
549    CentralPackageManagementPropsParser, DirectoryBuildPropsParser, DotNetDepsJsonParser,
550    NupkgParser, NuspecParser, PackageReferenceProjectParser, PackagesConfigParser,
551    PackagesLockParser, ProjectJsonParser, ProjectLockJsonParser,
552};
553pub use self::opam::OpamParser;
554pub use self::os_release::OsReleaseParser;
555pub use self::pip_inspect_deplock::PipInspectDeplockParser;
556pub use self::pipfile_lock::PipfileLockParser;
557pub use self::pixi::{PixiLockParser, PixiTomlParser};
558pub use self::pnpm_lock::PnpmLockParser;
559pub use self::podfile::PodfileParser;
560pub use self::podfile_lock::PodfileLockParser;
561pub use self::podspec::PodspecParser;
562pub use self::podspec_json::PodspecJsonParser;
563pub use self::poetry_lock::PoetryLockParser;
564pub use self::publiccode::PubliccodeParser;
565pub use self::pylock_toml::PylockTomlParser;
566pub use self::python::PythonParser;
567pub use self::readme::ReadmeParser;
568pub use self::requirements_txt::RequirementsTxtParser;
569#[cfg(feature = "rpm-sqlite")]
570pub use self::rpm_db::RpmSqliteDatabaseParser;
571pub use self::rpm_db::{RpmBdbDatabaseParser, RpmNdbDatabaseParser};
572pub use self::rpm_license_files::RpmLicenseFilesParser;
573pub use self::rpm_mariner_manifest::RpmMarinerManifestParser;
574pub use self::rpm_parser::RpmParser;
575pub use self::rpm_specfile::RpmSpecfileParser;
576pub use self::rpm_yumdb::RpmYumdbParser;
577pub use self::ruby::{
578    GemArchiveParser, GemMetadataExtractedParser, GemfileLockParser, GemfileParser, GemspecParser,
579};
580pub use self::sbt::SbtParser;
581pub use self::swift_manifest_json::SwiftManifestJsonParser;
582pub use self::swift_resolved::SwiftPackageResolvedParser;
583pub use self::swift_show_dependencies::SwiftShowDependenciesParser;
584pub use self::uv_lock::UvLockParser;
585pub use self::vcpkg::VcpkgManifestParser;
586pub use self::yarn_lock::YarnLockParser;
587pub use self::yarn_pnp::YarnPnpParser;
588
589/// Registers all parsers and recognizers, generating dispatch functions.
590///
591/// Parsers are tried first, then recognizers. This ordering is important because
592/// recognizers match broadly by file extension (e.g., `.jar`) and would shadow
593/// more specific parsers if checked first.
594macro_rules! register_package_handlers {
595    (
596        parsers: [$($(#[$parser_meta:meta])* $parser:ty),* $(,)?],
597        recognizers: [$($recognizer:ty),* $(,)?] $(,)?
598    ) => {
599        pub fn try_parse_file(path: &Path) -> Option<ParsePackagesResult> {
600            $(
601                $(#[$parser_meta])*
602                if <$parser>::is_match(path) {
603                    return Some(capture_parser_diagnostics(
604                        || <$parser>::extract_packages(path),
605                        stringify!($parser),
606                        path,
607                    ));
608                }
609            )*
610            $(
611                if <$recognizer>::is_match(path) {
612                    return Some(capture_parser_diagnostics(
613                        || <$recognizer>::extract_packages(path),
614                        stringify!($recognizer),
615                        path,
616                    ));
617                }
618            )*
619            None
620        }
621
622        // Used by the parser-golden maintenance tool in `xtask`.
623        // Scanner runtime dispatch goes through `try_parse_file()`.
624        #[allow(dead_code)]
625        pub fn parse_by_type_name(type_name: &str, path: &Path) -> Option<PackageData> {
626            match type_name {
627                $(
628                    $(#[$parser_meta])*
629                    stringify!($parser) => Some(<$parser>::extract_first_package(path)),
630                )*
631                $(
632                    stringify!($recognizer) => Some(<$recognizer>::extract_first_package(path)),
633                )*
634                _ => None
635            }
636        }
637
638        // Used by the parser-golden maintenance tool in `xtask` and by
639        // `tests/scanner_integration.rs` to verify parser registration.
640        #[allow(dead_code)]
641        pub fn list_parser_types() -> Vec<&'static str> {
642            vec![
643                $(
644                    $(#[$parser_meta])*
645                    stringify!($parser),
646                )*
647                $(
648                    stringify!($recognizer),
649                )*
650            ]
651        }
652    };
653}
654
655register_package_handlers! {
656    parsers: [
657        AboutFileParser,
658        AlpineApkParser,
659        AlpineApkbuildParser,
660        AlpineInstalledParser,
661        ArchPkginfoParser,
662        ArchSrcinfoParser,
663        AutotoolsConfigureParser,
664        BazelBuildParser,
665        BazelModuleParser,
666        BowerJsonParser,
667        BunLockParser,
668        BunLockbParser,
669        BuckBuildParser,
670        BuckMetadataBzlParser,
671        CargoLockParser,
672        CargoParser,
673        ChefMetadataJsonParser,
674        ChefMetadataRbParser,
675        CitationCffParser,
676        ClojureDepsEdnParser,
677        ClojureProjectCljParser,
678        ComposerJsonParser,
679        ComposerLockParser,
680        ConanDataParser,
681        ConanFilePyParser,
682        ConanfileTxtParser,
683        ConanLockParser,
684        CondaEnvironmentYmlParser,
685        CondaMetaJsonParser,
686        CondaMetaYamlParser,
687        CpanDistIniParser,
688        CpanMakefilePlParser,
689        CpanManifestParser,
690        CpanMetaJsonParser,
691        CpanMetaYmlParser,
692        CranParser,
693        DebianControlInExtractedDebParser,
694        DebianControlParser,
695        DebianCopyrightParser,
696        DebianDebianTarParser,
697        DebianDebParser,
698        DebianDistrolessInstalledParser,
699        DebianDscParser,
700        DebianInstalledListParser,
701        DebianInstalledMd5sumsParser,
702        DebianInstalledParser,
703        DebianMd5sumInPackageParser,
704        DebianOrigTarParser,
705        DenoParser,
706        DenoLockParser,
707        DockerfileParser,
708        FreebsdCompactManifestParser,
709        GemArchiveParser,
710        GemfileLockParser,
711        GemfileParser,
712        GemMetadataExtractedParser,
713        GemspecParser,
714        GitmodulesParser,
715        GodepsParser,
716        GoModParser,
717        GoModGraphParser,
718        GoSumParser,
719        GoWorkParser,
720        GradleLockfileParser,
721        GradleParser,
722        GradleModuleParser,
723        HackageCabalParser,
724        HackageCabalProjectParser,
725        HackageStackYamlParser,
726        HelmChartYamlParser,
727        HelmChartLockParser,
728        HaxeParser,
729        HexLockParser,
730        JuliaManifestTomlParser,
731        JuliaProjectTomlParser,
732        MavenParser,
733        MesonParser,
734        MicrosoftUpdateManifestParser,
735        NixDefaultParser,
736        NixFlakeLockParser,
737        NixFlakeParser,
738        NpmLockParser,
739        NpmParser,
740        NpmWorkspaceParser,
741        DotNetDepsJsonParser,
742        CentralPackageManagementPropsParser,
743        DirectoryBuildPropsParser,
744        NupkgParser,
745        NuspecParser,
746        PackageReferenceProjectParser,
747        OpamParser,
748        OsReleaseParser,
749        PackagesConfigParser,
750        PackagesLockParser,
751        ProjectJsonParser,
752        ProjectLockJsonParser,
753        PipfileLockParser,
754        PipInspectDeplockParser,
755        PixiTomlParser,
756        PixiLockParser,
757        PnpmLockParser,
758        PodfileLockParser,
759        PodfileParser,
760        PodspecJsonParser,
761        PodspecParser,
762        PoetryLockParser,
763        PubliccodeParser,
764        PylockTomlParser,
765        PubspecLockParser,
766        PubspecYamlParser,
767        PythonParser,
768        UvLockParser,
769        VcpkgManifestParser,
770        ReadmeParser,
771        RequirementsTxtParser,
772        RpmBdbDatabaseParser,
773        RpmLicenseFilesParser,
774        RpmMarinerManifestParser,
775        RpmNdbDatabaseParser,
776        RpmParser,
777        RpmSpecfileParser,
778        #[cfg(feature = "rpm-sqlite")]
779        RpmSqliteDatabaseParser,
780        RpmYumdbParser,
781        SbtParser,
782        SwiftManifestJsonParser,
783        SwiftPackageResolvedParser,
784        SwiftShowDependenciesParser,
785        YarnLockParser,
786        YarnPnpParser,
787    ],
788    recognizers: [
789        AndroidApkRecognizer,
790        AndroidLibraryRecognizer,
791        AppleDmgRecognizer,
792        Axis2MarRecognizer,
793        Axis2ModuleXmlRecognizer,
794        CabArchiveRecognizer,
795        ChromeCrxRecognizer,
796        InstallShieldRecognizer,
797        IosIpaRecognizer,
798        IsoImageRecognizer,
799        IvyXmlRecognizer,
800        JavaEarAppXmlRecognizer,
801        JavaEarRecognizer,
802        JavaJarRecognizer,
803        JavaWarRecognizer,
804        JavaWarWebXmlRecognizer,
805        JBossSarRecognizer,
806        JBossServiceXmlRecognizer,
807        MeteorPackageRecognizer,
808        MozillaXpiRecognizer,
809        NsisRecognizer,
810        SharArchiveRecognizer,
811        SquashfsRecognizer,
812    ],
813}
814
815#[cfg(test)]
816mod panic_isolation_tests {
817    use super::*;
818
819    #[test]
820    fn capture_parser_diagnostics_turns_panics_into_scan_errors() {
821        let path = Path::new("fixtures/panic-package.json");
822        let result = capture_parser_diagnostics(
823            || -> Vec<PackageData> { panic!("panic boom") },
824            "PanicParser",
825            path,
826        );
827
828        assert!(result.packages.is_empty());
829        assert_eq!(result.scan_errors.len(), 1);
830        assert!(result.scan_errors[0].contains("PanicParser"));
831        assert!(result.scan_errors[0].contains("fixtures/panic-package.json"));
832        assert!(result.scan_errors[0].contains("panic boom"));
833    }
834
835    #[test]
836    fn capture_parser_diagnostics_recovers_after_panic() {
837        let panic_path = Path::new("fixtures/panic-package.json");
838        let _ = capture_parser_diagnostics(
839            || -> Vec<PackageData> { panic!("panic boom") },
840            "PanicParser",
841            panic_path,
842        );
843
844        let ok_path = Path::new("fixtures/recovered-package.json");
845        let result = capture_parser_diagnostics(
846            || {
847                crate::parser_warn!("recoverable parser warning");
848                vec![PackageData {
849                    package_type: Some(PackageType::Npm),
850                    ..Default::default()
851                }]
852            },
853            "RecoveringParser",
854            ok_path,
855        );
856
857        assert_eq!(result.packages.len(), 1);
858        assert_eq!(result.scan_errors, vec!["recoverable parser warning"]);
859    }
860}