]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | //! lint on missing cargo common metadata |
2 | ||
3 | use std::path::PathBuf; | |
4 | ||
cdc7bbd5 XL |
5 | use clippy_utils::diagnostics::span_lint; |
6 | use clippy_utils::run_lints; | |
f20569fa XL |
7 | use rustc_hir::{hir_id::CRATE_HIR_ID, Crate}; |
8 | use rustc_lint::{LateContext, LateLintPass}; | |
9 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
10 | use rustc_span::source_map::DUMMY_SP; | |
11 | ||
12 | declare_clippy_lint! { | |
13 | /// **What it does:** Checks to see if all common metadata is defined in | |
14 | /// `Cargo.toml`. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata | |
15 | /// | |
16 | /// **Why is this bad?** It will be more difficult for users to discover the | |
17 | /// purpose of the crate, and key information related to it. | |
18 | /// | |
19 | /// **Known problems:** None. | |
20 | /// | |
21 | /// **Example:** | |
22 | /// ```toml | |
cdc7bbd5 | 23 | /// # This `Cargo.toml` is missing a description field: |
f20569fa XL |
24 | /// [package] |
25 | /// name = "clippy" | |
26 | /// version = "0.0.212" | |
f20569fa XL |
27 | /// repository = "https://github.com/rust-lang/rust-clippy" |
28 | /// readme = "README.md" | |
29 | /// license = "MIT OR Apache-2.0" | |
30 | /// keywords = ["clippy", "lint", "plugin"] | |
31 | /// categories = ["development-tools", "development-tools::cargo-plugins"] | |
32 | /// ``` | |
33 | /// | |
cdc7bbd5 | 34 | /// Should include a description field like: |
f20569fa XL |
35 | /// |
36 | /// ```toml | |
37 | /// # This `Cargo.toml` includes all common metadata | |
38 | /// [package] | |
39 | /// name = "clippy" | |
40 | /// version = "0.0.212" | |
f20569fa XL |
41 | /// description = "A bunch of helpful lints to avoid common pitfalls in Rust" |
42 | /// repository = "https://github.com/rust-lang/rust-clippy" | |
43 | /// readme = "README.md" | |
44 | /// license = "MIT OR Apache-2.0" | |
45 | /// keywords = ["clippy", "lint", "plugin"] | |
46 | /// categories = ["development-tools", "development-tools::cargo-plugins"] | |
47 | /// ``` | |
48 | pub CARGO_COMMON_METADATA, | |
49 | cargo, | |
50 | "common metadata is defined in `Cargo.toml`" | |
51 | } | |
52 | ||
53 | #[derive(Copy, Clone, Debug)] | |
54 | pub struct CargoCommonMetadata { | |
55 | ignore_publish: bool, | |
56 | } | |
57 | ||
58 | impl CargoCommonMetadata { | |
59 | pub fn new(ignore_publish: bool) -> Self { | |
60 | Self { ignore_publish } | |
61 | } | |
62 | } | |
63 | ||
64 | impl_lint_pass!(CargoCommonMetadata => [ | |
65 | CARGO_COMMON_METADATA | |
66 | ]); | |
67 | ||
68 | fn missing_warning(cx: &LateContext<'_>, package: &cargo_metadata::Package, field: &str) { | |
69 | let message = format!("package `{}` is missing `{}` metadata", package.name, field); | |
70 | span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, &message); | |
71 | } | |
72 | ||
73 | fn is_empty_str(value: &Option<String>) -> bool { | |
74 | value.as_ref().map_or(true, String::is_empty) | |
75 | } | |
76 | ||
77 | fn is_empty_path(value: &Option<PathBuf>) -> bool { | |
78 | value.as_ref().and_then(|x| x.to_str()).map_or(true, str::is_empty) | |
79 | } | |
80 | ||
81 | fn is_empty_vec(value: &[String]) -> bool { | |
82 | // This works because empty iterators return true | |
83 | value.iter().all(String::is_empty) | |
84 | } | |
85 | ||
86 | impl LateLintPass<'_> for CargoCommonMetadata { | |
87 | fn check_crate(&mut self, cx: &LateContext<'_>, _: &Crate<'_>) { | |
88 | if !run_lints(cx, &[CARGO_COMMON_METADATA], CRATE_HIR_ID) { | |
89 | return; | |
90 | } | |
91 | ||
92 | let metadata = unwrap_cargo_metadata!(cx, CARGO_COMMON_METADATA, false); | |
93 | ||
94 | for package in metadata.packages { | |
95 | // only run the lint if publish is `None` (`publish = true` or skipped entirely) | |
96 | // or if the vector isn't empty (`publish = ["something"]`) | |
97 | if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || self.ignore_publish { | |
f20569fa XL |
98 | if is_empty_str(&package.description) { |
99 | missing_warning(cx, &package, "package.description"); | |
100 | } | |
101 | ||
102 | if is_empty_str(&package.license) && is_empty_path(&package.license_file) { | |
103 | missing_warning(cx, &package, "either package.license or package.license_file"); | |
104 | } | |
105 | ||
106 | if is_empty_str(&package.repository) { | |
107 | missing_warning(cx, &package, "package.repository"); | |
108 | } | |
109 | ||
110 | if is_empty_path(&package.readme) { | |
111 | missing_warning(cx, &package, "package.readme"); | |
112 | } | |
113 | ||
114 | if is_empty_vec(&package.keywords) { | |
115 | missing_warning(cx, &package, "package.keywords"); | |
116 | } | |
117 | ||
118 | if is_empty_vec(&package.categories) { | |
119 | missing_warning(cx, &package, "package.categories"); | |
120 | } | |
121 | } | |
122 | } | |
123 | } | |
124 | } |