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