| use super::LINT_GROUPS_PRIORITY; |
| use clippy_utils::diagnostics::span_lint_and_then; |
| use rustc_data_structures::fx::FxHashSet; |
| use rustc_errors::Applicability; |
| use rustc_lint::{LateContext, unerased_lint_store}; |
| use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext}; |
| use std::ops::Range; |
| use std::path::Path; |
| use toml::Spanned; |
| use toml::de::{DeTable, DeValue}; |
| |
| fn toml_span(range: Range<usize>, file: &SourceFile) -> Span { |
| Span::new( |
| file.start_pos + BytePos::from_usize(range.start), |
| file.start_pos + BytePos::from_usize(range.end), |
| SyntaxContext::root(), |
| None, |
| ) |
| } |
| |
| struct LintConfig<'a> { |
| sp: Range<usize>, |
| level: &'a str, |
| priority: Option<i64>, |
| } |
| impl<'a> LintConfig<'a> { |
| fn priority(&self) -> i64 { |
| self.priority.unwrap_or(0) |
| } |
| |
| fn is_implicit(&self) -> bool { |
| self.priority.is_none() |
| } |
| |
| fn parse(value: &'a Spanned<DeValue<'a>>) -> Option<Self> { |
| let sp = value.span(); |
| let (level, priority) = match value.get_ref() { |
| DeValue::String(level) => (&**level, None), |
| DeValue::Table(tbl) => { |
| let level = tbl.get("level")?.get_ref().as_str()?; |
| let priority = if let Some(priority) = tbl.get("priority") { |
| let priority = priority.get_ref().as_integer()?; |
| Some(i64::from_str_radix(priority.as_str(), priority.radix()).ok()?) |
| } else { |
| None |
| }; |
| (level, priority) |
| }, |
| _ => return None, |
| }; |
| Some(Self { sp, level, priority }) |
| } |
| } |
| |
| fn check_table(cx: &LateContext<'_>, table: &DeTable<'_>, known_groups: &FxHashSet<&str>, file: &SourceFile) { |
| let mut lints = Vec::new(); |
| let mut groups = Vec::new(); |
| for (name, config) in table { |
| if name.get_ref() != "warnings" |
| && let Some(config) = LintConfig::parse(config) |
| { |
| if known_groups.contains(&**name.get_ref()) { |
| groups.push((name, config)); |
| } else { |
| lints.push((name, config)); |
| } |
| } |
| } |
| |
| for (group, group_config) in groups { |
| if let Some((conflict, _)) = lints.iter().rfind(|(_, lint_config)| { |
| lint_config.priority() == group_config.priority() && lint_config.level != group_config.level |
| }) { |
| span_lint_and_then( |
| cx, |
| LINT_GROUPS_PRIORITY, |
| toml_span(group.span(), file), |
| format!( |
| "lint group `{}` has the same priority ({}) as a lint", |
| group.as_ref(), |
| group_config.priority(), |
| ), |
| |diag| { |
| let config_span = toml_span(group_config.sp.clone(), file); |
| |
| if group_config.is_implicit() { |
| diag.span_label(config_span, "has an implicit priority of 0"); |
| } |
| diag.span_label(toml_span(conflict.span(), file), "has the same priority as this lint"); |
| diag.note("the order of the lints in the table is ignored by Cargo"); |
| |
| let low_priority = lints |
| .iter() |
| .map(|(_, lint_config)| lint_config.priority().saturating_sub(1)) |
| .min() |
| .unwrap_or(-1); |
| diag.span_suggestion_verbose( |
| config_span, |
| format!( |
| "to have lints override the group set `{}` to a lower priority", |
| group.as_ref() |
| ), |
| format!("{{ level = {:?}, priority = {low_priority} }}", group_config.level,), |
| Applicability::MaybeIncorrect, |
| ); |
| }, |
| ); |
| } |
| } |
| } |
| |
| struct LintTbls<'a> { |
| rust: Option<&'a DeTable<'a>>, |
| clippy: Option<&'a DeTable<'a>>, |
| } |
| fn get_lint_tbls<'a>(tbl: &'a DeTable<'a>) -> LintTbls<'a> { |
| if let Some(lints) = tbl.get("lints") |
| && let Some(lints) = lints.get_ref().as_table() |
| { |
| let rust = lints.get("rust").and_then(|x| x.get_ref().as_table()); |
| let clippy = lints.get("clippy").and_then(|x| x.get_ref().as_table()); |
| LintTbls { rust, clippy } |
| } else { |
| LintTbls { |
| rust: None, |
| clippy: None, |
| } |
| } |
| } |
| |
| pub fn check(cx: &LateContext<'_>) { |
| if let Ok(file) = cx.tcx.sess.source_map().load_file(Path::new("Cargo.toml")) |
| && let Some(src) = file.src.as_deref() |
| && let Ok(cargo_toml) = DeTable::parse(src) |
| { |
| let mut rustc_groups = FxHashSet::default(); |
| let mut clippy_groups = FxHashSet::default(); |
| for (group, ..) in unerased_lint_store(cx.tcx.sess).get_lint_groups() { |
| match group.split_once("::") { |
| None => { |
| rustc_groups.insert(group); |
| }, |
| Some(("clippy", group)) => { |
| clippy_groups.insert(group); |
| }, |
| _ => {}, |
| } |
| } |
| |
| let lints = get_lint_tbls(cargo_toml.get_ref()); |
| if let Some(lints) = lints.rust { |
| check_table(cx, lints, &rustc_groups, &file); |
| } |
| if let Some(lints) = lints.clippy { |
| check_table(cx, lints, &clippy_groups, &file); |
| } |
| if let Some(tbl) = cargo_toml.get_ref().get("workspace") |
| && let Some(tbl) = tbl.get_ref().as_table() |
| { |
| let lints = get_lint_tbls(tbl); |
| if let Some(lints) = lints.rust { |
| check_table(cx, lints, &rustc_groups, &file); |
| } |
| if let Some(lints) = lints.clippy { |
| check_table(cx, lints, &clippy_groups, &file); |
| } |
| } |
| } |
| } |