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;
563pub use self::rpm_db::{RpmBdbDatabaseParser, RpmNdbDatabaseParser, RpmSqliteDatabaseParser};
564pub use self::rpm_license_files::RpmLicenseFilesParser;
565pub use self::rpm_mariner_manifest::RpmMarinerManifestParser;
566pub use self::rpm_parser::RpmParser;
567pub use self::rpm_specfile::RpmSpecfileParser;
568pub use self::rpm_yumdb::RpmYumdbParser;
569pub use self::ruby::{
570    GemArchiveParser, GemMetadataExtractedParser, GemfileLockParser, GemfileParser, GemspecParser,
571};
572pub use self::sbt::SbtParser;
573pub use self::swift_manifest_json::SwiftManifestJsonParser;
574pub use self::swift_resolved::SwiftPackageResolvedParser;
575pub use self::swift_show_dependencies::SwiftShowDependenciesParser;
576pub use self::uv_lock::UvLockParser;
577pub use self::vcpkg::VcpkgManifestParser;
578pub use self::yarn_lock::YarnLockParser;
579pub use self::yarn_pnp::YarnPnpParser;
580
581/// Registers all parsers and recognizers, generating dispatch functions.
582///
583/// Parsers are tried first, then recognizers. This ordering is important because
584/// recognizers match broadly by file extension (e.g., `.jar`) and would shadow
585/// more specific parsers if checked first.
586macro_rules! register_package_handlers {
587    (
588        parsers: [$($parser:ty),* $(,)?],
589        recognizers: [$($recognizer:ty),* $(,)?] $(,)?
590    ) => {
591        pub fn try_parse_file(path: &Path) -> Option<ParsePackagesResult> {
592            $(
593                if <$parser>::is_match(path) {
594                    return Some(capture_parser_diagnostics(
595                        || <$parser>::extract_packages(path),
596                        stringify!($parser),
597                        path,
598                    ));
599                }
600            )*
601            $(
602                if <$recognizer>::is_match(path) {
603                    return Some(capture_parser_diagnostics(
604                        || <$recognizer>::extract_packages(path),
605                        stringify!($recognizer),
606                        path,
607                    ));
608                }
609            )*
610            None
611        }
612
613        // Used by the parser-golden maintenance tool in `xtask`.
614        // Scanner runtime dispatch goes through `try_parse_file()`.
615        #[allow(dead_code)]
616        pub fn parse_by_type_name(type_name: &str, path: &Path) -> Option<PackageData> {
617            match type_name {
618                $(
619                    stringify!($parser) => Some(<$parser>::extract_first_package(path)),
620                )*
621                $(
622                    stringify!($recognizer) => Some(<$recognizer>::extract_first_package(path)),
623                )*
624                _ => None
625            }
626        }
627
628        // Used by the parser-golden maintenance tool in `xtask` and by
629        // `tests/scanner_integration.rs` to verify parser registration.
630        #[allow(dead_code)]
631        pub fn list_parser_types() -> Vec<&'static str> {
632            vec![
633                $(
634                    stringify!($parser),
635                )*
636                $(
637                    stringify!($recognizer),
638                )*
639            ]
640        }
641    };
642}
643
644register_package_handlers! {
645    parsers: [
646        AboutFileParser,
647        AlpineApkParser,
648        AlpineApkbuildParser,
649        AlpineInstalledParser,
650        ArchPkginfoParser,
651        ArchSrcinfoParser,
652        AutotoolsConfigureParser,
653        BazelBuildParser,
654        BazelModuleParser,
655        BowerJsonParser,
656        BunLockParser,
657        BunLockbParser,
658        BuckBuildParser,
659        BuckMetadataBzlParser,
660        CargoLockParser,
661        CargoParser,
662        ChefMetadataJsonParser,
663        ChefMetadataRbParser,
664        CitationCffParser,
665        ClojureDepsEdnParser,
666        ClojureProjectCljParser,
667        ComposerJsonParser,
668        ComposerLockParser,
669        ConanDataParser,
670        ConanFilePyParser,
671        ConanfileTxtParser,
672        ConanLockParser,
673        CondaEnvironmentYmlParser,
674        CondaMetaJsonParser,
675        CondaMetaYamlParser,
676        CpanDistIniParser,
677        CpanMakefilePlParser,
678        CpanManifestParser,
679        CpanMetaJsonParser,
680        CpanMetaYmlParser,
681        CranParser,
682        DebianControlInExtractedDebParser,
683        DebianControlParser,
684        DebianCopyrightParser,
685        DebianDebianTarParser,
686        DebianDebParser,
687        DebianDistrolessInstalledParser,
688        DebianDscParser,
689        DebianInstalledListParser,
690        DebianInstalledMd5sumsParser,
691        DebianInstalledParser,
692        DebianMd5sumInPackageParser,
693        DebianOrigTarParser,
694        DenoParser,
695        DenoLockParser,
696        DockerfileParser,
697        FreebsdCompactManifestParser,
698        GemArchiveParser,
699        GemfileLockParser,
700        GemfileParser,
701        GemMetadataExtractedParser,
702        GemspecParser,
703        GitmodulesParser,
704        GodepsParser,
705        GoModParser,
706        GoModGraphParser,
707        GoSumParser,
708        GoWorkParser,
709        GradleLockfileParser,
710        GradleParser,
711        GradleModuleParser,
712        HackageCabalParser,
713        HackageCabalProjectParser,
714        HackageStackYamlParser,
715        HelmChartYamlParser,
716        HelmChartLockParser,
717        HaxeParser,
718        HexLockParser,
719        MavenParser,
720        MesonParser,
721        MicrosoftUpdateManifestParser,
722        NixDefaultParser,
723        NixFlakeLockParser,
724        NixFlakeParser,
725        NpmLockParser,
726        NpmParser,
727        NpmWorkspaceParser,
728        DotNetDepsJsonParser,
729        CentralPackageManagementPropsParser,
730        DirectoryBuildPropsParser,
731        NupkgParser,
732        NuspecParser,
733        PackageReferenceProjectParser,
734        OpamParser,
735        OsReleaseParser,
736        PackagesConfigParser,
737        PackagesLockParser,
738        ProjectJsonParser,
739        ProjectLockJsonParser,
740        PipfileLockParser,
741        PipInspectDeplockParser,
742        PixiTomlParser,
743        PixiLockParser,
744        PnpmLockParser,
745        PodfileLockParser,
746        PodfileParser,
747        PodspecJsonParser,
748        PodspecParser,
749        PoetryLockParser,
750        PubliccodeParser,
751        PylockTomlParser,
752        PubspecLockParser,
753        PubspecYamlParser,
754        PythonParser,
755        UvLockParser,
756        VcpkgManifestParser,
757        ReadmeParser,
758        RequirementsTxtParser,
759        RpmBdbDatabaseParser,
760        RpmLicenseFilesParser,
761        RpmMarinerManifestParser,
762        RpmNdbDatabaseParser,
763        RpmParser,
764        RpmSpecfileParser,
765        RpmSqliteDatabaseParser,
766        RpmYumdbParser,
767        SbtParser,
768        SwiftManifestJsonParser,
769        SwiftPackageResolvedParser,
770        SwiftShowDependenciesParser,
771        YarnLockParser,
772        YarnPnpParser,
773    ],
774    recognizers: [
775        AndroidApkRecognizer,
776        AndroidLibraryRecognizer,
777        AppleDmgRecognizer,
778        Axis2MarRecognizer,
779        Axis2ModuleXmlRecognizer,
780        CabArchiveRecognizer,
781        ChromeCrxRecognizer,
782        InstallShieldRecognizer,
783        IosIpaRecognizer,
784        IsoImageRecognizer,
785        IvyXmlRecognizer,
786        JavaEarAppXmlRecognizer,
787        JavaEarRecognizer,
788        JavaJarRecognizer,
789        JavaWarRecognizer,
790        JavaWarWebXmlRecognizer,
791        JBossSarRecognizer,
792        JBossServiceXmlRecognizer,
793        MeteorPackageRecognizer,
794        MozillaXpiRecognizer,
795        NsisRecognizer,
796        SharArchiveRecognizer,
797        SquashfsRecognizer,
798    ],
799}
800
801#[cfg(test)]
802mod panic_isolation_tests {
803    use super::*;
804
805    #[test]
806    fn capture_parser_diagnostics_turns_panics_into_scan_errors() {
807        let path = Path::new("fixtures/panic-package.json");
808        let result = capture_parser_diagnostics(
809            || -> Vec<PackageData> { panic!("panic boom") },
810            "PanicParser",
811            path,
812        );
813
814        assert!(result.packages.is_empty());
815        assert_eq!(result.scan_errors.len(), 1);
816        assert!(result.scan_errors[0].contains("PanicParser"));
817        assert!(result.scan_errors[0].contains("fixtures/panic-package.json"));
818        assert!(result.scan_errors[0].contains("panic boom"));
819    }
820
821    #[test]
822    fn capture_parser_diagnostics_recovers_after_panic() {
823        let panic_path = Path::new("fixtures/panic-package.json");
824        let _ = capture_parser_diagnostics(
825            || -> Vec<PackageData> { panic!("panic boom") },
826            "PanicParser",
827            panic_path,
828        );
829
830        let ok_path = Path::new("fixtures/recovered-package.json");
831        let result = capture_parser_diagnostics(
832            || {
833                crate::parser_warn!("recoverable parser warning");
834                vec![PackageData {
835                    package_type: Some(PackageType::Npm),
836                    ..Default::default()
837                }]
838            },
839            "RecoveringParser",
840            ok_path,
841        );
842
843        assert_eq!(result.packages.len(), 1);
844        assert_eq!(result.scan_errors, vec!["recoverable parser warning"]);
845    }
846}