cargo_deny/bans/
diags.rs

1use std::fmt;
2
3use crate::{
4    Krate, Spanned,
5    bans::{SpecAndReason, cfg},
6    diag::{
7        CfgCoord, Check, Diag, Diagnostic, FileId, GraphNode, KrateCoord, Label, Pack, Severity,
8    },
9};
10
11#[derive(
12    strum::Display,
13    strum::EnumString,
14    strum::EnumIter,
15    strum::IntoStaticStr,
16    Copy,
17    Clone,
18    Debug,
19    PartialEq,
20    Eq,
21    PartialOrd,
22    Ord,
23)]
24#[strum(serialize_all = "kebab-case")]
25pub enum Code {
26    Banned,
27    Allowed,
28    NotAllowed,
29    Duplicate,
30    Skipped,
31    Wildcard,
32    UnmatchedSkip,
33    UnnecessarySkip,
34    AllowedByWrapper,
35    UnmatchedWrapper,
36    SkippedByRoot,
37    UnmatchedSkipRoot,
38    BuildScriptNotAllowed,
39    ExactFeaturesMismatch,
40    FeatureNotExplicitlyAllowed,
41    FeatureBanned,
42    UnknownFeature,
43    DefaultFeatureEnabled,
44    PathBypassed,
45    PathBypassedByGlob,
46    ChecksumMatch,
47    ChecksumMismatch,
48    DeniedByExtension,
49    DetectedExecutable,
50    DetectedExecutableScript,
51    UnableToCheckPath,
52    FeaturesEnabled,
53    UnmatchedBypass,
54    UnmatchedPathBypass,
55    UnmatchedGlob,
56    UnusedWrapper,
57    WorkspaceDuplicate,
58    UnresolvedWorkspaceDependency,
59    UnusedWorkspaceDependency,
60    NonUtf8Path,
61    NonRootPath,
62}
63
64impl Code {
65    #[inline]
66    pub fn description(self) -> &'static str {
67        match self {
68            Self::Banned => "Detected an explicitly banned crate",
69            Self::Allowed => "Detected an explicitly allowed crate",
70            Self::NotAllowed => "Detected a crate not explicitly allowed",
71            Self::Duplicate => "Detected two or more versions of the same crate",
72            Self::Skipped => "A crate version was skipped when checking for multiple versions",
73            Self::Wildcard => "A dependency was declared with a wildcard version",
74            Self::UnmatchedSkip => "A skip entry didn't match any crates in the graph",
75            Self::UnnecessarySkip => "A skip entry applied to a crate that only had one version",
76            Self::AllowedByWrapper => "A banned crate was allowed by a wrapper crate",
77            Self::UnmatchedWrapper => {
78                "A wrapper was declared for a crate that was not a direct parent"
79            }
80            Self::SkippedByRoot => {
81                "A crate version was skipped by a tree skip when checking for multiple versions"
82            }
83            Self::UnmatchedSkipRoot => "A tree skip didn't match any crates in the graph",
84            Self::BuildScriptNotAllowed => {
85                "A crate has code that executes at build time but was not allowed to"
86            }
87            Self::ExactFeaturesMismatch => {
88                "The feature set of a crate did not exactly match the features configured for it"
89            }
90            Self::FeatureNotExplicitlyAllowed => "A feature for a crate was not explicitly allowed",
91            Self::FeatureBanned => "An explicitly banned feature for a crate was set",
92            Self::UnknownFeature => {
93                "Attempted to allow or ban a feature for a crate which doesn't exist"
94            }
95            Self::DefaultFeatureEnabled => "Default features were enabled for a crate",
96            Self::PathBypassed => {
97                "A path was explicitly allowed when checking build time execution"
98            }
99            Self::PathBypassedByGlob => {
100                "A path was explicitly allowed by a glob when checking build time execution"
101            }
102            Self::ChecksumMatch => "A path bypass checksum matched",
103            Self::ChecksumMismatch => "A path bypass checksum did not match the file on disk",
104            Self::DeniedByExtension => {
105                "A file in a crate executed at build time was denied due to its extension"
106            }
107            Self::DetectedExecutable => {
108                "An executable was detected in a crate which executes at build time"
109            }
110            Self::DetectedExecutableScript => {
111                "An executable script was detected in a crate which executes at build time"
112            }
113            Self::UnableToCheckPath => "A failure occurred trying to inspect a file on disk",
114            Self::FeaturesEnabled => {
115                "Features which enable code during build time execution were enabled"
116            }
117            Self::UnmatchedBypass => {
118                "A bypass was declared for a crate which does not execute at build time"
119            }
120            Self::UnmatchedPathBypass => "A path bypass did not match a path in a crate's source",
121            Self::UnmatchedGlob => "A glob bypass did not match any paths in a crate's source",
122            Self::UnusedWrapper => "A wrapper was declared for a crate not in the graph",
123            Self::WorkspaceDuplicate => {
124                "A workspace directly depended on more than one version of the same crate"
125            }
126            Self::UnresolvedWorkspaceDependency => "Failed to resolve a workspace dependency",
127            Self::UnusedWorkspaceDependency => "A workspace dependency was declared but never used",
128            Self::NonUtf8Path => {
129                "A non-utf8 path was detected in a crate that executes at build time"
130            }
131            Self::NonRootPath => "A path was not rooted in the crate source",
132        }
133    }
134}
135
136impl From<Code> for String {
137    fn from(c: Code) -> Self {
138        c.to_string()
139    }
140}
141
142#[inline]
143fn dcode(code: Code) -> Option<crate::diag::DiagnosticCode> {
144    Some(crate::diag::DiagnosticCode::Bans(code))
145}
146
147#[inline]
148fn diag(diag: Diagnostic, code: Code) -> Diag {
149    Diag::new(diag, dcode(code))
150}
151
152impl SpecAndReason {
153    pub(crate) fn to_labels(&self, spec_msg: Option<&str>) -> Vec<Label> {
154        let mut v = Vec::new();
155
156        {
157            let l = Label::primary(self.file_id, self.spec.name.span);
158            if let Some(sm) = spec_msg {
159                v.push(l.with_message(sm));
160            } else {
161                v.push(l);
162            }
163        }
164
165        if let Some(reason) = &self.reason {
166            v.push(Label::secondary(self.file_id, reason.0.span).with_message("reason"));
167        }
168
169        if let Some(ui) = &self.use_instead {
170            v.push(Label::secondary(self.file_id, ui.span).with_message("use instead"));
171        }
172
173        v
174    }
175}
176
177pub(crate) struct ExplicitlyBanned<'a> {
178    pub(crate) krate: &'a Krate,
179    pub(crate) ban_cfg: &'a SpecAndReason,
180}
181
182impl<'a> From<ExplicitlyBanned<'a>> for Diag {
183    fn from(eb: ExplicitlyBanned<'a>) -> Self {
184        diag(
185            Diagnostic::new(Severity::Error)
186                .with_message(format_args!("crate '{}' is explicitly banned", eb.krate))
187                .with_labels(eb.ban_cfg.to_labels(Some("banned here"))),
188            Code::Banned,
189        )
190    }
191}
192
193pub(crate) struct ExplicitlyAllowed<'a> {
194    pub(crate) krate: &'a Krate,
195    pub(crate) allow_cfg: &'a SpecAndReason,
196}
197
198impl<'a> From<ExplicitlyAllowed<'a>> for Diag {
199    fn from(ea: ExplicitlyAllowed<'a>) -> Self {
200        diag(
201            Diagnostic::new(Severity::Note)
202                .with_message(format_args!("crate '{}' is explicitly allowed", ea.krate))
203                .with_labels(ea.allow_cfg.to_labels(Some("allowed here"))),
204            Code::Allowed,
205        )
206    }
207}
208
209pub(crate) struct NotAllowed<'a> {
210    pub(crate) krate: &'a Krate,
211}
212
213impl<'a> From<NotAllowed<'a>> for Diag {
214    fn from(ib: NotAllowed<'a>) -> Self {
215        diag(
216            Diagnostic::new(Severity::Error).with_message(format_args!(
217                "crate '{}' is not explicitly allowed",
218                ib.krate
219            )),
220            Code::NotAllowed,
221        )
222    }
223}
224
225pub(crate) struct Duplicates<'a> {
226    pub(crate) krate_name: &'a str,
227    pub(crate) num_dupes: usize,
228    pub(crate) krates_coord: KrateCoord,
229    pub(crate) severity: Severity,
230}
231
232impl<'a> From<Duplicates<'a>> for Diag {
233    fn from(dup: Duplicates<'a>) -> Self {
234        diag(
235            Diagnostic::new(dup.severity)
236                .with_message(format_args!(
237                    "found {} duplicate entries for crate '{}'",
238                    dup.num_dupes, dup.krate_name,
239                ))
240                .with_labels(vec![
241                    dup.krates_coord.into_label().with_message("lock entries"),
242                ]),
243            Code::Duplicate,
244        )
245    }
246}
247
248pub(crate) struct Skipped<'a> {
249    pub(crate) krate: &'a Krate,
250    pub(crate) skip_cfg: &'a SpecAndReason,
251}
252
253impl<'a> From<Skipped<'a>> for Diag {
254    fn from(sk: Skipped<'a>) -> Self {
255        diag(
256            Diagnostic::new(Severity::Note)
257                .with_message(format_args!(
258                    "crate '{}' skipped when checking for duplicates",
259                    sk.krate
260                ))
261                .with_labels(sk.skip_cfg.to_labels(Some("skipped here"))),
262            Code::Skipped,
263        )
264    }
265}
266
267pub(crate) struct Wildcards<'a> {
268    pub(crate) krate: &'a Krate,
269    pub(crate) severity: Severity,
270    pub(crate) labels: Vec<Label>,
271    pub(crate) allow_wildcard_paths: bool,
272}
273
274impl<'a> From<Wildcards<'a>> for Pack {
275    fn from(wc: Wildcards<'a>) -> Self {
276        let labels = wc.labels;
277        let diag = diag(
278            Diagnostic::new(wc.severity)
279                .with_message(format_args!(
280                    "found {} wildcard dependenc{} for crate '{}'{}",
281                    labels.len(),
282                    if labels.len() == 1 { "y" } else { "ies" },
283                    wc.krate.name,
284                    if wc.allow_wildcard_paths {
285                        ". allow-wildcard-paths is enabled, but does not apply to public crates as crates.io disallows path dependencies."
286                    } else {
287                        ""
288                    },
289                ))
290                .with_labels(labels),
291                Code::Wildcard
292        );
293
294        let mut pack = Pack::with_kid(Check::Bans, wc.krate.id.clone());
295        pack.push(diag);
296
297        pack
298    }
299}
300
301pub(crate) struct UnmatchedSkip<'a> {
302    pub(crate) skip_cfg: &'a SpecAndReason,
303}
304
305impl<'a> From<UnmatchedSkip<'a>> for Diag {
306    fn from(us: UnmatchedSkip<'a>) -> Self {
307        diag(
308            Diagnostic::new(Severity::Warning)
309                .with_message(format_args!(
310                    "skipped crate '{}' was not encountered",
311                    us.skip_cfg.spec,
312                ))
313                .with_labels(us.skip_cfg.to_labels(Some("unmatched skip configuration"))),
314            Code::UnmatchedSkip,
315        )
316    }
317}
318
319pub(crate) struct UnnecessarySkip<'a> {
320    pub(crate) skip_cfg: &'a SpecAndReason,
321}
322
323impl<'a> From<UnnecessarySkip<'a>> for Diag {
324    fn from(us: UnnecessarySkip<'a>) -> Self {
325        diag(
326            Diagnostic::new(Severity::Warning)
327                .with_message(format_args!(
328                    "skip '{}' applied to a crate with only one version",
329                    us.skip_cfg.spec,
330                ))
331                .with_labels(
332                    us.skip_cfg
333                        .to_labels(Some("unnecessary skip configuration")),
334                ),
335            Code::UnnecessarySkip,
336        )
337    }
338}
339
340pub(crate) struct UnusedWrapper {
341    pub(crate) wrapper_cfg: CfgCoord,
342}
343
344impl From<UnusedWrapper> for Diag {
345    fn from(us: UnusedWrapper) -> Self {
346        diag(
347            Diagnostic::new(Severity::Warning)
348                .with_message("wrapper for banned crate was not encountered")
349                .with_labels(vec![
350                    us.wrapper_cfg
351                        .into_label()
352                        .with_message("unmatched wrapper"),
353                ]),
354            Code::UnusedWrapper,
355        )
356    }
357}
358
359pub(crate) struct BannedAllowedByWrapper<'a> {
360    pub(crate) ban_cfg: CfgCoord,
361    pub(crate) banned_krate: &'a Krate,
362    pub(crate) ban_exception_cfg: CfgCoord,
363    pub(crate) wrapper_krate: &'a Krate,
364}
365
366impl<'a> From<BannedAllowedByWrapper<'a>> for Diag {
367    fn from(baw: BannedAllowedByWrapper<'a>) -> Self {
368        diag(
369            Diagnostic::new(Severity::Note)
370                .with_message(format_args!(
371                    "banned crate '{}' allowed by wrapper '{}'",
372                    baw.banned_krate, baw.wrapper_krate
373                ))
374                .with_labels(vec![
375                    baw.ban_cfg.into_label().with_message("banned here"),
376                    baw.ban_exception_cfg
377                        .into_label()
378                        .with_message("allowed wrapper"),
379                ]),
380            Code::AllowedByWrapper,
381        )
382    }
383}
384
385pub(crate) struct BannedUnmatchedWrapper<'a> {
386    pub(crate) ban_cfg: &'a SpecAndReason,
387    pub(crate) banned_krate: &'a Krate,
388    pub(crate) parent_krate: &'a Krate,
389}
390
391impl<'a> From<BannedUnmatchedWrapper<'a>> for Diag {
392    fn from(buw: BannedUnmatchedWrapper<'a>) -> Self {
393        diag(
394            Diagnostic::new(Severity::Warning)
395                .with_message(format_args!(
396                    "direct parent '{}' of banned crate '{}' was not marked as a wrapper",
397                    buw.parent_krate, buw.banned_krate
398                ))
399                .with_labels(buw.ban_cfg.to_labels(Some("banned here"))),
400            Code::UnmatchedWrapper,
401        )
402    }
403}
404
405pub(crate) struct SkippedByRoot<'a> {
406    pub(crate) krate: &'a Krate,
407    pub(crate) skip_root_cfg: &'a SpecAndReason,
408}
409
410impl<'a> From<SkippedByRoot<'a>> for Diag {
411    fn from(sbr: SkippedByRoot<'a>) -> Self {
412        diag(
413            Diagnostic::new(Severity::Note)
414                .with_message(format_args!(
415                    "skipping crate '{}' due to root skip",
416                    sbr.krate
417                ))
418                .with_labels(sbr.skip_root_cfg.to_labels(Some("matched skip root"))),
419            Code::SkippedByRoot,
420        )
421    }
422}
423
424pub(crate) struct UnmatchedSkipRoot {
425    pub(crate) skip_root_cfg: CfgCoord,
426}
427
428impl From<UnmatchedSkipRoot> for Diag {
429    fn from(usr: UnmatchedSkipRoot) -> Self {
430        diag(
431            Diagnostic::new(Severity::Warning)
432                .with_message("skip tree root was not found in the dependency graph")
433                .with_labels(vec![
434                    usr.skip_root_cfg
435                        .into_label()
436                        .with_message("no crate matched these criteria"),
437                ]),
438            Code::UnmatchedSkipRoot,
439        )
440    }
441}
442
443pub(crate) struct BuildScriptNotAllowed<'a> {
444    pub(crate) krate: &'a Krate,
445}
446
447impl<'a> From<BuildScriptNotAllowed<'a>> for Diag {
448    fn from(bs: BuildScriptNotAllowed<'a>) -> Self {
449        diag(
450            Diagnostic::new(Severity::Error).with_message(format_args!(
451                "crate '{}' has a build script but is not allowed to have one",
452                bs.krate
453            )),
454            Code::BuildScriptNotAllowed,
455        )
456    }
457}
458
459pub(crate) struct ExactFeaturesMismatch<'a> {
460    pub(crate) missing_allowed: Vec<CfgCoord>,
461    pub(crate) not_allowed: &'a [&'a str],
462    pub(crate) exact_coord: CfgCoord,
463    pub(crate) krate: &'a Krate,
464}
465
466impl From<ExactFeaturesMismatch<'_>> for Diag {
467    fn from(efm: ExactFeaturesMismatch<'_>) -> Self {
468        let mut labels = vec![
469            efm.exact_coord
470                .into_label()
471                .with_message("exact enabled here"),
472        ];
473
474        labels.extend(
475            efm.missing_allowed
476                .into_iter()
477                .map(|ma| ma.into_label().with_message("allowed feature not present")),
478        );
479
480        let diag = Diagnostic::new(Severity::Error)
481            .with_message(format_args!(
482                "feature set for crate '{}' did not match exactly",
483                efm.krate
484            ))
485            .with_code(Code::ExactFeaturesMismatch)
486            .with_labels(labels)
487            .with_notes(
488                efm.not_allowed
489                    .iter()
490                    .map(|na| format!("'{na}' feature was enabled but not explicitly allowed"))
491                    .collect(),
492            );
493
494        let graph_nodes = if efm.not_allowed.is_empty() {
495            vec![GraphNode {
496                kid: efm.krate.id.clone(),
497                feature: None,
498            }]
499        } else {
500            efm.not_allowed
501                .iter()
502                .map(|feat| GraphNode {
503                    kid: efm.krate.id.clone(),
504                    feature: Some((*feat).to_owned()),
505                })
506                .collect()
507        };
508
509        Diag {
510            diag,
511            code: dcode(Code::ExactFeaturesMismatch),
512            graph_nodes: graph_nodes.into(),
513            extra: None,
514            with_features: true,
515        }
516    }
517}
518
519pub(crate) struct FeatureNotExplicitlyAllowed<'a> {
520    pub(crate) krate: &'a Krate,
521    pub(crate) feature: &'a str,
522    pub(crate) allowed: CfgCoord,
523}
524
525impl From<FeatureNotExplicitlyAllowed<'_>> for Diag {
526    fn from(fna: FeatureNotExplicitlyAllowed<'_>) -> Diag {
527        let diag = Diagnostic::new(Severity::Error)
528            .with_message(format_args!(
529                "feature '{}' for crate '{}' was not explicitly allowed",
530                fna.feature, fna.krate,
531            ))
532            .with_code(Code::FeatureNotExplicitlyAllowed)
533            .with_labels(vec![
534                fna.allowed.into_label().with_message("allowed features"),
535            ]);
536
537        Diag {
538            diag,
539            code: dcode(Code::FeatureNotExplicitlyAllowed),
540            graph_nodes: std::iter::once(GraphNode {
541                kid: fna.krate.id.clone(),
542                feature: None,
543            })
544            .collect(),
545            extra: None,
546            with_features: true,
547        }
548    }
549}
550
551pub(crate) struct FeatureBanned<'a> {
552    pub(crate) krate: &'a Krate,
553    pub(crate) feature: &'a Spanned<String>,
554    pub(crate) file_id: FileId,
555}
556
557impl From<FeatureBanned<'_>> for Diag {
558    fn from(fed: FeatureBanned<'_>) -> Diag {
559        let diag = Diagnostic::new(Severity::Error)
560            .with_message(format_args!(
561                "feature '{}' for crate '{}' is explicitly denied",
562                fed.feature.value, fed.krate,
563            ))
564            .with_code(Code::FeatureBanned)
565            .with_labels(vec![
566                Label::primary(fed.file_id, fed.feature.span).with_message("feature denied here"),
567            ]);
568
569        Diag {
570            diag,
571            code: dcode(Code::FeatureBanned),
572            graph_nodes: std::iter::once(GraphNode {
573                kid: fed.krate.id.clone(),
574                feature: Some(fed.feature.value.clone()),
575            })
576            .collect(),
577            extra: None,
578            with_features: true,
579        }
580    }
581}
582
583pub(crate) struct UnknownFeature<'a> {
584    pub(crate) krate: &'a Krate,
585    pub(crate) feature: &'a Spanned<String>,
586    pub(crate) file_id: FileId,
587}
588
589impl From<UnknownFeature<'_>> for Diag {
590    fn from(uf: UnknownFeature<'_>) -> Diag {
591        let diag = Diagnostic::new(Severity::Warning)
592            .with_message(format_args!(
593                "found unknown feature '{}' for crate '{}'",
594                uf.feature.value, uf.krate,
595            ))
596            .with_code(Code::UnknownFeature)
597            .with_labels(vec![
598                Label::primary(uf.file_id, uf.feature.span).with_message("unknown feature"),
599            ]);
600
601        Diag {
602            diag,
603            code: dcode(Code::UnknownFeature),
604            graph_nodes: std::iter::once(GraphNode {
605                kid: uf.krate.id.clone(),
606                feature: None,
607            })
608            .collect(),
609            extra: None,
610            with_features: false,
611        }
612    }
613}
614
615pub(crate) struct DefaultFeatureEnabled<'a> {
616    pub(crate) krate: &'a Krate,
617    pub(crate) level: &'a Spanned<crate::LintLevel>,
618    pub(crate) file_id: FileId,
619}
620
621impl From<DefaultFeatureEnabled<'_>> for Diag {
622    fn from(dfe: DefaultFeatureEnabled<'_>) -> Diag {
623        let diag = Diagnostic::new(dfe.level.value.into())
624            .with_message(format_args!(
625                "'default' feature enabled for crate '{}'",
626                dfe.krate,
627            ))
628            .with_code(Code::DefaultFeatureEnabled)
629            .with_labels(vec![
630                Label::primary(dfe.file_id, dfe.level.span).with_message("lint level"),
631            ]);
632
633        Diag {
634            diag,
635            code: dcode(Code::DefaultFeatureEnabled),
636            graph_nodes: std::iter::once(GraphNode {
637                kid: dfe.krate.id.clone(),
638                feature: Some("default".to_owned()),
639            })
640            .collect(),
641            extra: None,
642            with_features: true,
643        }
644    }
645}
646
647pub(crate) struct HomePath<'a> {
648    pub(crate) path: &'a crate::Path,
649    pub(crate) root: &'a crate::Path,
650    pub(crate) home: Option<&'a crate::Path>,
651}
652
653impl fmt::Display for HomePath<'_> {
654    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
655        if let Some(rel_path) = self.home.and_then(|home| self.path.strip_prefix(home).ok()) {
656            f.write_str("$CARGO_HOME/")?;
657            f.write_str(rel_path.as_str())
658        } else if let Ok(rel_path) = self.path.strip_prefix(self.root) {
659            f.write_str("$crate/")?;
660            f.write_str(rel_path.as_str())
661        } else {
662            f.write_str(self.path.as_str())
663        }
664    }
665}
666
667pub(crate) struct ExplicitPathAllowance<'a> {
668    pub(crate) allowed: &'a cfg::BypassPath,
669    pub(crate) file_id: FileId,
670}
671
672impl From<ExplicitPathAllowance<'_>> for Diag {
673    fn from(pa: ExplicitPathAllowance<'_>) -> Diag {
674        let mut labels =
675            vec![Label::primary(pa.file_id, pa.allowed.path.span).with_message("allowed path")];
676
677        labels.extend(
678            pa.allowed
679                .checksum
680                .as_ref()
681                .map(|chk| Label::secondary(pa.file_id, chk.span).with_message("matched checksum")),
682        );
683        let diag = Diagnostic::new(Severity::Help)
684            .with_message("file explicitly allowed")
685            .with_code(Code::PathBypassed)
686            .with_labels(labels);
687
688        Diag {
689            diag,
690            code: dcode(Code::PathBypassed),
691            // Not really helpful to show graphs for these
692            graph_nodes: Default::default(),
693            extra: None,
694            with_features: false,
695        }
696    }
697}
698
699#[inline]
700fn globs_to_labels(file_id: FileId, globs: Vec<&cfg::GlobPattern>) -> Vec<Label> {
701    globs
702        .into_iter()
703        .map(|gp| match gp {
704            cfg::GlobPattern::Builtin((glob, id)) => {
705                Label::secondary(*id, glob.span).with_message("builtin")
706            }
707            cfg::GlobPattern::User(glob) => Label::secondary(file_id, glob.span),
708        })
709        .collect()
710}
711
712pub(crate) struct GlobAllowance<'a> {
713    pub(crate) path: HomePath<'a>,
714    pub(crate) globs: Vec<&'a cfg::GlobPattern>,
715    pub(crate) file_id: FileId,
716}
717
718impl From<GlobAllowance<'_>> for Diag {
719    fn from(pa: GlobAllowance<'_>) -> Diag {
720        let diag = Diagnostic::new(Severity::Help)
721            .with_message("file allowed by glob")
722            .with_notes(vec![format!("path = '{}'", pa.path)])
723            .with_code(Code::PathBypassedByGlob)
724            .with_labels(globs_to_labels(pa.file_id, pa.globs));
725
726        Diag {
727            diag,
728            code: dcode(Code::PathBypassedByGlob),
729            // Not really helpful to show graphs for these
730            graph_nodes: Default::default(),
731            extra: None,
732            with_features: false,
733        }
734    }
735}
736
737pub(crate) struct ChecksumMatch<'a> {
738    pub(crate) path: HomePath<'a>,
739    pub(crate) checksum: &'a Spanned<super::cfg::Checksum>,
740    pub(crate) severity: Option<Severity>,
741    pub(crate) file_id: FileId,
742}
743
744impl From<ChecksumMatch<'_>> for Diag {
745    fn from(cm: ChecksumMatch<'_>) -> Diag {
746        let diag = Diagnostic::new(cm.severity.unwrap_or(Severity::Help))
747            .with_message("file checksum matched")
748            .with_notes(vec![format!("path = '{}'", cm.path)])
749            .with_code(Code::ChecksumMatch)
750            .with_labels(vec![
751                Label::primary(cm.file_id, cm.checksum.span).with_message("checksum"),
752            ]);
753
754        Diag {
755            diag,
756            code: dcode(Code::ChecksumMatch),
757            // Not really helpful to show graphs for these
758            graph_nodes: Default::default(),
759            extra: None,
760            with_features: false,
761        }
762    }
763}
764
765pub(crate) struct ChecksumMismatch<'a> {
766    pub(crate) path: HomePath<'a>,
767    pub(crate) checksum: &'a Spanned<super::cfg::Checksum>,
768    pub(crate) severity: Option<Severity>,
769    pub(crate) error: String,
770    pub(crate) file_id: FileId,
771}
772
773impl From<ChecksumMismatch<'_>> for Diag {
774    fn from(cm: ChecksumMismatch<'_>) -> Diag {
775        let mut notes = vec![format!("path = '{}'", cm.path)];
776        notes.extend(
777            format!("error = {:#}", cm.error)
778                .lines()
779                .map(|l| l.to_owned()),
780        );
781
782        let diag = Diagnostic::new(cm.severity.unwrap_or(Severity::Error))
783            .with_message("file did not match the expected checksum")
784            .with_notes(notes)
785            .with_code(Code::ChecksumMismatch)
786            .with_labels(vec![
787                Label::primary(cm.file_id, cm.checksum.span).with_message("expected checksum"),
788            ]);
789
790        Diag {
791            diag,
792            code: dcode(Code::ChecksumMismatch),
793            // Not really helpful to show graphs for these
794            graph_nodes: Default::default(),
795            extra: None,
796            with_features: false,
797        }
798    }
799}
800
801pub(crate) struct DeniedByExtension<'a> {
802    pub(crate) path: HomePath<'a>,
803    pub(crate) globs: Vec<&'a cfg::GlobPattern>,
804    pub(crate) file_id: FileId,
805}
806
807impl From<DeniedByExtension<'_>> for Diag {
808    fn from(de: DeniedByExtension<'_>) -> Diag {
809        let diag = Diagnostic::new(Severity::Error)
810            .with_message("path disallowed by extension")
811            .with_notes(vec![format!("path = '{}'", de.path)])
812            .with_code(Code::DeniedByExtension)
813            .with_labels(globs_to_labels(de.file_id, de.globs));
814
815        Diag {
816            diag,
817            code: dcode(Code::DeniedByExtension),
818            // Not really helpful to show graphs for these
819            graph_nodes: Default::default(),
820            extra: None,
821            with_features: false,
822        }
823    }
824}
825
826pub(crate) struct DetectedExecutable<'a> {
827    pub(crate) path: HomePath<'a>,
828    pub(crate) interpreted: crate::LintLevel,
829    pub(crate) exe_kind: super::ExecutableKind,
830}
831
832impl From<DetectedExecutable<'_>> for Diag {
833    fn from(de: DetectedExecutable<'_>) -> Diag {
834        let (code, exe_note, severity) = match de.exe_kind {
835            super::ExecutableKind::Native(hint) => {
836                let native_kind = match hint {
837                    goblin::Hint::Elf(_) => "elf",
838                    goblin::Hint::PE => "pe",
839                    goblin::Hint::Mach(_) | goblin::Hint::MachFat(_) => "mach",
840                    goblin::Hint::Archive => "archive",
841                    _ => unreachable!("unhandled format {hint:#?} for {}", de.path),
842                };
843
844                (
845                    Code::DetectedExecutable,
846                    format!("executable-kind = '{native_kind}'"),
847                    Severity::Error,
848                )
849            }
850            super::ExecutableKind::Interpreted(interpreter) => (
851                Code::DetectedExecutableScript,
852                format!("interpreter = '{interpreter}'"),
853                de.interpreted.into(),
854            ),
855        };
856
857        let diag = Diagnostic::new(severity)
858            .with_message("detected executable")
859            .with_notes(vec![format!("path = '{}'", de.path), exe_note])
860            .with_code(code);
861
862        Diag {
863            diag,
864            code: dcode(code),
865            // Not really helpful to show graphs for these
866            graph_nodes: Default::default(),
867            extra: None,
868            with_features: false,
869        }
870    }
871}
872
873pub(crate) struct UnableToCheckPath<'a> {
874    pub(crate) path: HomePath<'a>,
875    pub(crate) error: anyhow::Error,
876}
877
878impl From<UnableToCheckPath<'_>> for Diag {
879    fn from(ucp: UnableToCheckPath<'_>) -> Diag {
880        let mut notes = vec![format!("path = {}", ucp.path)];
881
882        notes.extend(
883            format!("error = {:#}", ucp.error)
884                .lines()
885                .map(|l| l.to_owned()),
886        );
887        let diag = Diagnostic::new(Severity::Error)
888            .with_message("unable to check if path is an executable")
889            .with_notes(notes)
890            .with_code(Code::UnableToCheckPath);
891
892        Diag {
893            diag,
894            code: dcode(Code::UnableToCheckPath),
895            // Not really helpful to show graphs for these
896            graph_nodes: Default::default(),
897            extra: None,
898            with_features: false,
899        }
900    }
901}
902
903pub(crate) struct FeaturesEnabled<'a> {
904    pub(crate) enabled_features: Vec<&'a Spanned<String>>,
905    pub(crate) file_id: FileId,
906}
907
908impl From<FeaturesEnabled<'_>> for Diag {
909    fn from(fe: FeaturesEnabled<'_>) -> Diag {
910        let diag = Diagnostic::new(Severity::Note)
911            .with_message(format_args!(
912                "{} features enabled for crate with build script, checking sources",
913                fe.enabled_features.len()
914            ))
915            .with_code(Code::FeaturesEnabled)
916            .with_labels(
917                fe.enabled_features
918                    .into_iter()
919                    .map(|ef| Label::secondary(fe.file_id, ef.span))
920                    .collect(),
921            );
922
923        Diag {
924            diag,
925            code: dcode(Code::FeaturesEnabled),
926            // Not really helpful to show graphs for these
927            graph_nodes: Default::default(),
928            extra: None,
929            with_features: false,
930        }
931    }
932}
933
934pub(crate) struct UnmatchedBypass<'a> {
935    pub(crate) unmatched: &'a super::cfg::ValidBypass,
936    pub(crate) file_id: FileId,
937}
938
939impl<'a> From<UnmatchedBypass<'a>> for Diag {
940    fn from(ubc: UnmatchedBypass<'a>) -> Self {
941        diag(
942            Diagnostic::new(Severity::Warning)
943                .with_message("crate build bypass was not encountered")
944                .with_labels(vec![
945                    Label::primary(ubc.file_id, ubc.unmatched.spec.name.span)
946                        .with_message("unmatched bypass"),
947                ]),
948            Code::UnmatchedBypass,
949        )
950    }
951}
952
953pub(crate) struct UnmatchedPathBypass<'a> {
954    pub(crate) unmatched: &'a super::cfg::BypassPath,
955    pub(crate) file_id: FileId,
956}
957
958impl<'a> From<UnmatchedPathBypass<'a>> for Diag {
959    fn from(ua: UnmatchedPathBypass<'a>) -> Self {
960        diag(
961            Diagnostic::new(Severity::Warning)
962                .with_message("allowed path was not encountered")
963                .with_labels(vec![Label::primary(ua.file_id, ua.unmatched.path.span)]),
964            Code::UnmatchedPathBypass,
965        )
966    }
967}
968
969pub(crate) struct UnmatchedGlob<'a> {
970    pub(crate) unmatched: &'a Spanned<String>,
971    pub(crate) file_id: FileId,
972}
973
974impl<'a> From<UnmatchedGlob<'a>> for Diag {
975    fn from(ug: UnmatchedGlob<'a>) -> Self {
976        diag(
977            Diagnostic::new(Severity::Warning)
978                .with_message("glob was not encountered")
979                .with_labels(vec![Label::primary(ug.file_id, ug.unmatched.span)]),
980            Code::UnmatchedGlob,
981        )
982    }
983}
984
985pub(crate) struct WorkspaceDuplicate<'k> {
986    pub(crate) duplicate: &'k Krate,
987    pub(crate) labels: Vec<Label>,
988    pub(crate) severity: crate::LintLevel,
989    pub(crate) has_workspace_declaration: bool,
990    pub(crate) total_uses: usize,
991}
992
993impl<'k> From<WorkspaceDuplicate<'k>> for Diag {
994    fn from(wd: WorkspaceDuplicate<'k>) -> Self {
995        diag(
996            Diagnostic::new(wd.severity.into())
997                .with_message(format_args!(
998                    "crate {} is used {} times in the workspace, {}",
999                    wd.duplicate,
1000                    wd.total_uses,
1001                    if wd.has_workspace_declaration {
1002                        "but not all declarations use the shared workspace dependency"
1003                    } else {
1004                        "and there is no shared workspace dependency for it"
1005                    }
1006                ))
1007                .with_labels(wd.labels),
1008            Code::WorkspaceDuplicate,
1009        )
1010    }
1011}
1012
1013pub(crate) struct UnresolveWorkspaceDependency<'m, 'k> {
1014    pub(crate) manifest: &'m crate::diag::Manifest<'k>,
1015    pub(crate) dep: &'m crate::diag::ManifestDep<'k>,
1016}
1017
1018#[allow(clippy::fallible_impl_from)]
1019impl<'m, 'k> From<UnresolveWorkspaceDependency<'m, 'k>> for Diag {
1020    fn from(uwd: UnresolveWorkspaceDependency<'m, 'k>) -> Self {
1021        diag(
1022            Diagnostic::bug()
1023                .with_message("failed to resolve a workspace dependency")
1024                .with_labels(vec![
1025                    Label::primary(
1026                        uwd.manifest.id,
1027                        uwd.dep.workspace.as_ref().map(|ws| ws.span).unwrap(),
1028                    )
1029                    .with_message("usage of workspace dependency"),
1030                    Label::secondary(uwd.manifest.id, uwd.dep.value_span),
1031                ]),
1032            Code::UnresolvedWorkspaceDependency,
1033        )
1034    }
1035}
1036
1037pub(crate) struct UnusedWorkspaceDependencies<'u> {
1038    pub(crate) unused: &'u [crate::diag::UnusedWorkspaceDep],
1039    pub(crate) level: crate::LintLevel,
1040    pub(crate) id: FileId,
1041}
1042
1043impl<'u> From<UnusedWorkspaceDependencies<'u>> for Pack {
1044    fn from(uwd: UnusedWorkspaceDependencies<'u>) -> Self {
1045        let mut pack = Pack::new(Check::Bans);
1046
1047        for unused in uwd.unused {
1048            let mut labels = vec![
1049                Label::primary(uwd.id, unused.key).with_message(format_args!(
1050                    "unused {}workspace dependency",
1051                    if unused.patched.is_some() {
1052                        "and patched "
1053                    } else {
1054                        ""
1055                    }
1056                )),
1057            ];
1058
1059            if let Some(patched) = unused.patched {
1060                labels.push(
1061                    Label::secondary(uwd.id, patched)
1062                        .with_message("note this is the original dependency that is patched"),
1063                );
1064            }
1065
1066            if let Some(rename) = &unused.rename {
1067                labels.push(
1068                    Label::secondary(uwd.id, rename.span)
1069                        .with_message("note the dependency is renamed"),
1070                );
1071            }
1072
1073            pack.push(diag(
1074                Diagnostic::new(uwd.level.into())
1075                    .with_message("workspace dependency is declared, but unused")
1076                    .with_labels(labels),
1077                Code::UnusedWorkspaceDependency,
1078            ));
1079        }
1080
1081        pack
1082    }
1083}
1084
1085pub(crate) struct NonUtf8Path<'p> {
1086    #[allow(clippy::disallowed_types)]
1087    pub(crate) path: &'p std::path::Path,
1088}
1089
1090impl<'p> From<NonUtf8Path<'p>> for Diag {
1091    fn from(value: NonUtf8Path<'p>) -> Self {
1092        diag(
1093            Diagnostic::warning()
1094                .with_message(format_args!("path {:?} is not utf-8, skipping", value.path)),
1095            Code::NonUtf8Path,
1096        )
1097    }
1098}
1099
1100pub(crate) struct NonRootPath<'p> {
1101    pub(crate) path: &'p crate::Path,
1102    pub(crate) root: &'p crate::Path,
1103}
1104
1105impl<'p> From<NonRootPath<'p>> for Diag {
1106    fn from(value: NonRootPath<'p>) -> Self {
1107        diag(
1108            Diagnostic::error().with_message(format_args!(
1109                "path '{}' is not relative to crate root '{}'",
1110                value.path, value.root
1111            )),
1112            Code::NonRootPath,
1113        )
1114    }
1115}