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