1use std::cmp::{Reverse, max_by_key};
2use std::fmt::Display;
3
4use cargo_util_schemas::manifest;
5use cargo_util_terminal::report::Level;
6
7use crate::core::{Feature, Features};
8
9#[derive(Clone, Debug)]
10pub struct Lint {
11 pub name: &'static str,
12 pub desc: &'static str,
13 pub primary_group: &'static LintGroup,
14 pub msrv: Option<manifest::RustVersion>,
20 pub feature_gate: Option<&'static Feature>,
21 pub docs: Option<&'static str>,
25}
26
27impl Lint {
28 pub fn level(
29 &self,
30 pkg_lints: &manifest::TomlToolLints,
31 pkg_rust_version: Option<&manifest::RustVersion>,
32 unstable_features: &Features,
33 ) -> LintLevelProduct {
34 if self
37 .feature_gate
38 .is_some_and(|f| !unstable_features.is_enabled(f))
39 {
40 let level = LintLevel::Allow;
41 let source = LintLevelSource::Default;
42 return LintLevelProduct { level, source };
43 }
44
45 if let (Some(msrv), Some(pkg_rust_version)) = (&self.msrv, pkg_rust_version) {
46 let pkg_rust_version = pkg_rust_version.to_partial();
47 if !msrv.is_compatible_with(&pkg_rust_version) {
48 let level = LintLevel::Allow;
49 let source = LintLevelSource::Default;
50 return LintLevelProduct { level, source };
51 }
52 }
53
54 let lint_level_priority =
55 level_priority(self.name, self.primary_group.default_level, pkg_lints);
56
57 let group_level_priority = level_priority(
58 self.primary_group.name,
59 self.primary_group.default_level,
60 pkg_lints,
61 );
62
63 let (_, (level, source, _)) = max_by_key(
64 (self.name, lint_level_priority),
65 (self.primary_group.name, group_level_priority),
66 |(n, (l, s, p))| {
67 (
68 l == &LintLevel::Forbid,
69 *s != LintLevelSource::Default,
70 *p,
71 Reverse(*n),
72 )
73 },
74 );
75 LintLevelProduct { level, source }
76 }
77
78 pub fn emitted_source(&self, lint_level: LintLevel, source: LintLevelSource) -> String {
79 format!("`cargo::{}` is set to `{lint_level}` {source}", self.name,)
80 }
81}
82
83pub struct LintLevelProduct {
84 pub level: LintLevel,
85 pub source: LintLevelSource,
86}
87
88#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
89pub enum LintLevel {
90 Allow,
91 Warn,
92 Deny,
93 Forbid,
94}
95
96impl Display for LintLevel {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 match self {
99 LintLevel::Allow => write!(f, "allow"),
100 LintLevel::Warn => write!(f, "warn"),
101 LintLevel::Deny => write!(f, "deny"),
102 LintLevel::Forbid => write!(f, "forbid"),
103 }
104 }
105}
106
107impl LintLevel {
108 pub fn is_warn(&self) -> bool {
109 self == &LintLevel::Warn
110 }
111
112 pub fn is_error(&self) -> bool {
113 self == &LintLevel::Forbid || self == &LintLevel::Deny
114 }
115
116 pub fn to_diagnostic_level(self) -> Level<'static> {
117 match self {
118 LintLevel::Allow => unreachable!("allow does not map to a diagnostic level"),
119 LintLevel::Warn => Level::WARNING,
120 LintLevel::Deny => Level::ERROR,
121 LintLevel::Forbid => Level::ERROR,
122 }
123 }
124
125 pub fn force(self) -> bool {
126 match self {
127 Self::Allow => false,
128 Self::Warn => true,
129 Self::Deny => true,
130 Self::Forbid => true,
131 }
132 }
133}
134
135impl From<manifest::TomlLintLevel> for LintLevel {
136 fn from(toml_lint_level: manifest::TomlLintLevel) -> LintLevel {
137 match toml_lint_level {
138 manifest::TomlLintLevel::Allow => LintLevel::Allow,
139 manifest::TomlLintLevel::Warn => LintLevel::Warn,
140 manifest::TomlLintLevel::Deny => LintLevel::Deny,
141 manifest::TomlLintLevel::Forbid => LintLevel::Forbid,
142 }
143 }
144}
145
146#[derive(Copy, Clone, Debug, PartialEq, Eq)]
147pub enum LintLevelSource {
148 Default,
149 Package,
150}
151
152impl Display for LintLevelSource {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 match self {
155 LintLevelSource::Default => write!(f, "by default"),
156 LintLevelSource::Package => write!(f, "in `[lints]`"),
157 }
158 }
159}
160
161impl LintLevelSource {
162 pub(crate) fn is_user_specified(&self) -> bool {
163 match self {
164 LintLevelSource::Default => false,
165 LintLevelSource::Package => true,
166 }
167 }
168}
169
170pub(crate) fn level_priority(
171 name: &str,
172 default_level: LintLevel,
173 pkg_lints: &manifest::TomlToolLints,
174) -> (LintLevel, LintLevelSource, i8) {
175 if let Some(defined_level) = pkg_lints.get(name) {
176 (
177 defined_level.level().into(),
178 LintLevelSource::Package,
179 defined_level.priority(),
180 )
181 } else {
182 (default_level, LintLevelSource::Default, 0)
183 }
184}
185
186#[derive(Clone, Debug)]
187pub struct LintGroup {
188 pub name: &'static str,
189 pub default_level: LintLevel,
190 pub desc: &'static str,
191 pub feature_gate: Option<&'static Feature>,
192 pub hidden: bool,
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 const STYLE: LintGroup = LintGroup {
200 name: "style",
201 desc: "code that should be written in a more idiomatic way",
202 default_level: LintLevel::Warn,
203 feature_gate: None,
204 hidden: false,
205 };
206
207 fn test_lint(name: &'static str, group: &'static LintGroup) -> Lint {
208 Lint {
209 name,
210 desc: "test lint",
211 primary_group: group,
212 msrv: None,
213 feature_gate: None,
214 docs: None,
215 }
216 }
217
218 #[test]
219 fn lint_level_prefers_user_specified_over_default() {
220 let lint = test_lint("unused_dependencies", &STYLE);
221
222 let mut pkg_lints = manifest::TomlToolLints::new();
223 pkg_lints.insert(
224 "unused_dependencies".to_string(),
225 manifest::TomlLint::Level(manifest::TomlLintLevel::Deny),
226 );
227 let features = Features::default();
228
229 let LintLevelProduct { level, source } = lint.level(&pkg_lints, None, &features);
230 assert_eq!(level, LintLevel::Deny);
231 assert_eq!(source, LintLevelSource::Package);
232 }
233
234 #[test]
235 fn lint_level_group_overrides_default() {
236 let lint = test_lint("non_kebab_case_bins", &STYLE);
237
238 let mut pkg_lints = manifest::TomlToolLints::new();
239 pkg_lints.insert(
240 "style".to_string(),
241 manifest::TomlLint::Level(manifest::TomlLintLevel::Deny),
242 );
243 let features = Features::default();
244
245 let LintLevelProduct { level, source } = lint.level(&pkg_lints, None, &features);
246 assert_eq!(level, LintLevel::Deny);
247 assert_eq!(source, LintLevelSource::Package);
248 }
249}