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 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 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 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 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 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 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 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 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}