|
1 |
| -use crate::core::{Edition, Feature, Features, Manifest, Package}; |
| 1 | +use crate::core::compiler::{CompileKind, TargetInfo}; |
| 2 | +use crate::core::{Edition, Feature, Features, Manifest, Package, Workspace}; |
2 | 3 | use crate::{CargoResult, GlobalContext};
|
3 | 4 | use annotate_snippets::{Level, Snippet};
|
| 5 | +use cargo_platform::{Cfg, ExpectedValues, Platform}; |
4 | 6 | use cargo_util_schemas::manifest::{TomlLintLevel, TomlToolLints};
|
5 | 7 | use pathdiff::diff_paths;
|
| 8 | +use std::borrow::Cow; |
6 | 9 | use std::fmt::Display;
|
7 | 10 | use std::ops::Range;
|
8 | 11 | use std::path::Path;
|
9 | 12 | use toml_edit::ImDocument;
|
10 | 13 |
|
11 | 14 | const LINT_GROUPS: &[LintGroup] = &[TEST_DUMMY_UNSTABLE];
|
12 |
| -pub const LINTS: &[Lint] = &[IM_A_TEAPOT, UNKNOWN_LINTS]; |
| 15 | +pub const LINTS: &[Lint] = &[IM_A_TEAPOT, UNEXPECTED_CFGS, UNKNOWN_LINTS]; |
13 | 16 |
|
14 | 17 | pub fn analyze_cargo_lints_table(
|
15 | 18 | pkg: &Package,
|
@@ -605,6 +608,164 @@ fn output_unknown_lints(
|
605 | 608 | Ok(())
|
606 | 609 | }
|
607 | 610 |
|
| 611 | +const UNEXPECTED_CFGS: Lint = Lint { |
| 612 | + name: "unexpected_cfgs", |
| 613 | + desc: "lint on unexpected target cfgs", |
| 614 | + groups: &[], |
| 615 | + default_level: LintLevel::Warn, |
| 616 | + edition_lint_opts: None, |
| 617 | + feature_gate: None, |
| 618 | + docs: Some( |
| 619 | + r#" |
| 620 | +### What it does |
| 621 | +Checks for unexpected cfgs in `[target.'cfg(...)']` |
| 622 | +
|
| 623 | +### Why it is bad |
| 624 | +The lint helps with verifying that the crate is correctly handling conditional |
| 625 | +compilation for different target platforms. It ensures that the cfg settings are |
| 626 | +consistent between what is intended and what is used, helping to |
| 627 | +catch potential bugs or errors early in the development process. |
| 628 | +
|
| 629 | +### Example |
| 630 | +```toml |
| 631 | +[lints.cargo] |
| 632 | +unexpected_cfgs = "warn" |
| 633 | +``` |
| 634 | +"#, |
| 635 | + ), |
| 636 | +}; |
| 637 | + |
| 638 | +pub fn unexpected_target_cfgs( |
| 639 | + ws: &Workspace<'_>, |
| 640 | + pkg: &Package, |
| 641 | + path: &Path, |
| 642 | + pkg_lints: &TomlToolLints, |
| 643 | + error_count: &mut usize, |
| 644 | + gctx: &GlobalContext, |
| 645 | +) -> CargoResult<()> { |
| 646 | + let manifest = pkg.manifest(); |
| 647 | + |
| 648 | + let (lint_level, _lint_reason) = |
| 649 | + UNEXPECTED_CFGS.level(pkg_lints, manifest.edition(), manifest.unstable_features()); |
| 650 | + |
| 651 | + if lint_level == LintLevel::Allow { |
| 652 | + return Ok(()); |
| 653 | + } |
| 654 | + |
| 655 | + let rustc = gctx.load_global_rustc(Some(ws))?; |
| 656 | + // FIXME: While it doesn't doesn't really matter for `--print=check-cfg`, we should |
| 657 | + // still pass the actual requested targets instead of an empty array so that the |
| 658 | + // target info can always be de-duplicated. |
| 659 | + // FIXME: Move the target info creation before any linting is done and re-use it for |
| 660 | + // all the packages. |
| 661 | + let compile_kinds = CompileKind::from_requested_targets(gctx, &[])?; |
| 662 | + let target_info = TargetInfo::new(gctx, &compile_kinds, &rustc, CompileKind::Host)?; |
| 663 | + |
| 664 | + let Some(global_check_cfg) = target_info.check_cfg() else { |
| 665 | + return Ok(()); |
| 666 | + }; |
| 667 | + |
| 668 | + if !global_check_cfg.exhaustive { |
| 669 | + return Ok(()); |
| 670 | + } |
| 671 | + |
| 672 | + // FIXME: If the `[lints.rust.unexpected_cfgs.check-cfg]` config is set we should |
| 673 | + // re-fetch the check-cfg informations with those extra args |
| 674 | + |
| 675 | + for dep in pkg.summary().dependencies() { |
| 676 | + let Some(platform) = dep.platform() else { |
| 677 | + continue; |
| 678 | + }; |
| 679 | + let Platform::Cfg(cfg_expr) = platform else { |
| 680 | + continue; |
| 681 | + }; |
| 682 | + |
| 683 | + cfg_expr.walk(|cfg| -> CargoResult<()> { |
| 684 | + let (name, value) = match cfg { |
| 685 | + Cfg::Name(name) => (name, None), |
| 686 | + Cfg::KeyPair(name, value) => (name, Some(value.to_string())), |
| 687 | + }; |
| 688 | + |
| 689 | + match global_check_cfg.expecteds.get(name.as_str()) { |
| 690 | + Some(ExpectedValues::Some(values)) if !values.contains(&value) => { |
| 691 | + let level = lint_level.to_diagnostic_level(); |
| 692 | + if lint_level == LintLevel::Forbid || lint_level == LintLevel::Deny { |
| 693 | + *error_count += 1; |
| 694 | + } |
| 695 | + |
| 696 | + let value = match value { |
| 697 | + Some(value) => Cow::from(format!("`{value}`")), |
| 698 | + None => Cow::from("(none)"), |
| 699 | + }; |
| 700 | + |
| 701 | + // Retrieving the span can fail if our pretty-priting doesn't match what the |
| 702 | + // user wrote: `unix="foo"` and `unix = "foo"`, for that reason we don't fail |
| 703 | + // and just produce a slightly worth diagnostic. |
| 704 | + if let Some(span) = get_span(manifest.document(), &["target", &*format!("cfg({cfg_expr})")], false) { |
| 705 | + let manifest_path = rel_cwd_manifest_path(path, gctx); |
| 706 | + let title = format!( |
| 707 | + "unexpected `cfg` condition value: {value} for `{cfg}`" |
| 708 | + ); |
| 709 | + let diag = level.title(&*title).snippet( |
| 710 | + Snippet::source(manifest.contents()) |
| 711 | + .origin(&manifest_path) |
| 712 | + .annotation(level.span(span)) |
| 713 | + .fold(true) |
| 714 | + ); |
| 715 | + gctx.shell().print_message(diag)?; |
| 716 | + } else { |
| 717 | + let title = format!( |
| 718 | + "unexpected `cfg` condition value: {value} for `{cfg}` in `[target.'cfg({cfg_expr})']`" |
| 719 | + ); |
| 720 | + let help_where = format!("occurred in `{path}`", path = path.display()); |
| 721 | + let diag = level.title(&*title).footer(Level::Help.title(&*help_where)); |
| 722 | + gctx.shell().print_message(diag)?; |
| 723 | + } |
| 724 | + } |
| 725 | + None => { |
| 726 | + let level = lint_level.to_diagnostic_level(); |
| 727 | + if lint_level == LintLevel::Forbid || lint_level == LintLevel::Deny { |
| 728 | + *error_count += 1; |
| 729 | + } |
| 730 | + |
| 731 | + let for_cfg = match value { |
| 732 | + Some(_value) => Cow::from(format!(" for `{cfg}`")), |
| 733 | + None => Cow::from(""), |
| 734 | + }; |
| 735 | + |
| 736 | + // Retrieving the span can fail if our pretty-priting doesn't match what the |
| 737 | + // user wrote: `unix="foo"` and `unix = "foo"`, for that reason we don't fail |
| 738 | + // and just produce a slightly worth diagnostic. |
| 739 | + if let Some(span) = get_span(manifest.document(), &["target", &*format!("cfg({cfg_expr})")], false) { |
| 740 | + let manifest_path = rel_cwd_manifest_path(path, gctx); |
| 741 | + let title = format!( |
| 742 | + "unexpected `cfg` condition name: {name}{for_cfg}" |
| 743 | + ); |
| 744 | + let diag = level.title(&*title).snippet( |
| 745 | + Snippet::source(manifest.contents()) |
| 746 | + .origin(&manifest_path) |
| 747 | + .annotation(level.span(span)) |
| 748 | + .fold(true) |
| 749 | + ); |
| 750 | + gctx.shell().print_message(diag)?; |
| 751 | + } else { |
| 752 | + let title = format!( |
| 753 | + "unexpected `cfg` condition name: {name}{for_cfg} in `[target.'cfg({cfg_expr})']`" |
| 754 | + ); |
| 755 | + let help_where = format!("occurred in `{path}`", path = path.display()); |
| 756 | + let diag = level.title(&*title).footer(Level::Help.title(&*help_where)); |
| 757 | + gctx.shell().print_message(diag)?; |
| 758 | + } |
| 759 | + } |
| 760 | + _ => { /* not unexpected */ } |
| 761 | + } |
| 762 | + Ok(()) |
| 763 | + })?; |
| 764 | + } |
| 765 | + |
| 766 | + Ok(()) |
| 767 | +} |
| 768 | + |
608 | 769 | #[cfg(test)]
|
609 | 770 | mod tests {
|
610 | 771 | use itertools::Itertools;
|
|
0 commit comments