diff options
| author | Philipp Krones <hello@philkrones.com> | 2023-06-02 11:41:57 +0200 |
|---|---|---|
| committer | Philipp Krones <hello@philkrones.com> | 2023-06-02 11:41:57 +0200 |
| commit | e6dc0efc0076690c0dcae05541fec771795ca84c (patch) | |
| tree | 18b4bc307f398c6599d89f70859315a678554b76 /clippy_lints/src/utils | |
| parent | ec2c6155aacbb92e2dba046e1560d9f28a61f077 (diff) | |
| download | rust-e6dc0efc0076690c0dcae05541fec771795ca84c.tar.gz rust-e6dc0efc0076690c0dcae05541fec771795ca84c.zip | |
Merge commit '30448e8cf98d4754350db0c959644564f317bc0f' into clippyup
Diffstat (limited to 'clippy_lints/src/utils')
| -rw-r--r-- | clippy_lints/src/utils/conf.rs | 209 |
1 files changed, 113 insertions, 96 deletions
diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index f6de66bb514..35f40830681 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -2,12 +2,15 @@ #![allow(clippy::module_name_repetitions)] +use rustc_session::Session; +use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext}; use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor}; use serde::Deserialize; -use std::error::Error; +use std::fmt::{Debug, Display, Formatter}; +use std::ops::Range; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::{cmp, env, fmt, fs, io, iter}; +use std::{cmp, env, fmt, fs, io}; #[rustfmt::skip] const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ @@ -67,33 +70,70 @@ impl DisallowedPath { #[derive(Default)] pub struct TryConf { pub conf: Conf, - pub errors: Vec<Box<dyn Error>>, - pub warnings: Vec<Box<dyn Error>>, + pub errors: Vec<ConfError>, + pub warnings: Vec<ConfError>, } impl TryConf { - fn from_error(error: impl Error + 'static) -> Self { + fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self { + ConfError::from_toml(file, error).into() + } +} + +impl From<ConfError> for TryConf { + fn from(value: ConfError) -> Self { Self { conf: Conf::default(), - errors: vec![Box::new(error)], + errors: vec![value], warnings: vec![], } } } +impl From<io::Error> for TryConf { + fn from(value: io::Error) -> Self { + ConfError::from(value).into() + } +} + #[derive(Debug)] -struct ConfError(String); +pub struct ConfError { + pub message: String, + pub span: Option<Span>, +} + +impl ConfError { + fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self { + if let Some(span) = error.span() { + Self::spanned(file, error.message(), span) + } else { + Self { + message: error.message().to_string(), + span: None, + } + } + } -impl fmt::Display for ConfError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - <String as fmt::Display>::fmt(&self.0, f) + fn spanned(file: &SourceFile, message: impl Into<String>, span: Range<usize>) -> Self { + Self { + message: message.into(), + span: Some(Span::new( + file.start_pos + BytePos::from_usize(span.start), + file.start_pos + BytePos::from_usize(span.end), + SyntaxContext::root(), + None, + )), + } } } -impl Error for ConfError {} - -fn conf_error(s: impl Into<String>) -> Box<dyn Error> { - Box::new(ConfError(s.into())) +impl From<io::Error> for ConfError { + fn from(value: io::Error) -> Self { + Self { + message: value.to_string(), + span: None, + } + } } macro_rules! define_Conf { @@ -117,20 +157,14 @@ macro_rules! define_Conf { } } - impl<'de> Deserialize<'de> for TryConf { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> { - deserializer.deserialize_map(ConfVisitor) - } - } - #[derive(Deserialize)] #[serde(field_identifier, rename_all = "kebab-case")] #[allow(non_camel_case_types)] enum Field { $($name,)* third_party, } - struct ConfVisitor; + struct ConfVisitor<'a>(&'a SourceFile); - impl<'de> Visitor<'de> for ConfVisitor { + impl<'de> Visitor<'de> for ConfVisitor<'_> { type Value = TryConf; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -141,32 +175,38 @@ macro_rules! define_Conf { let mut errors = Vec::new(); let mut warnings = Vec::new(); $(let mut $name = None;)* - // could get `Field` here directly, but get `str` first for diagnostics - while let Some(name) = map.next_key::<&str>()? { - match Field::deserialize(name.into_deserializer())? { - $(Field::$name => { - $(warnings.push(conf_error(format!("deprecated field `{}`. {}", name, $dep)));)? - match map.next_value() { - Err(e) => errors.push(conf_error(e.to_string())), + // could get `Field` here directly, but get `String` first for diagnostics + while let Some(name) = map.next_key::<toml::Spanned<String>>()? { + match Field::deserialize(name.get_ref().as_str().into_deserializer()) { + Err(e) => { + let e: FieldError = e; + errors.push(ConfError::spanned(self.0, e.0, name.span())); + } + $(Ok(Field::$name) => { + $(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), name.span()));)? + let raw_value = map.next_value::<toml::Spanned<toml::Value>>()?; + let value_span = raw_value.span(); + match <$ty>::deserialize(raw_value.into_inner()) { + Err(e) => errors.push(ConfError::spanned(self.0, e.to_string().replace('\n', " ").trim(), value_span)), Ok(value) => match $name { - Some(_) => errors.push(conf_error(format!("duplicate field `{}`", name))), + Some(_) => errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), name.span())), None => { $name = Some(value); // $new_conf is the same as one of the defined `$name`s, so // this variable is defined in line 2 of this function. $(match $new_conf { - Some(_) => errors.push(conf_error(concat!( + Some(_) => errors.push(ConfError::spanned(self.0, concat!( "duplicate field `", stringify!($new_conf), "` (provided as `", stringify!($name), "`)" - ))), + ), name.span())), None => $new_conf = $name.clone(), })? }, } } })* - // white-listed; ignore - Field::third_party => drop(map.next_value::<IgnoredAny>()) + // ignore contents of the third_party key + Ok(Field::third_party) => drop(map.next_value::<IgnoredAny>()) } } let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* }; @@ -486,7 +526,7 @@ pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> { const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"]; // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR. - // If neither of those exist, use ".". + // If neither of those exist, use ".". (Update documentation if this priority changes) let mut current = env::var_os("CLIPPY_CONF_DIR") .or_else(|| env::var_os("CARGO_MANIFEST_DIR")) .map_or_else(|| PathBuf::from("."), PathBuf::from) @@ -532,19 +572,19 @@ pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> { /// Read the `toml` configuration file. /// /// In case of error, the function tries to continue as much as possible. -pub fn read(path: &Path) -> TryConf { - let content = match fs::read_to_string(path) { - Err(e) => return TryConf::from_error(e), - Ok(content) => content, +pub fn read(sess: &Session, path: &Path) -> TryConf { + let file = match sess.source_map().load_file(path) { + Err(e) => return e.into(), + Ok(file) => file, }; - match toml::from_str::<TryConf>(&content) { + match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(&file)) { Ok(mut conf) => { extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS); extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES); conf }, - Err(e) => TryConf::from_error(e), + Err(e) => TryConf::from_toml_error(&file, &e), } } @@ -556,65 +596,42 @@ fn extend_vec_if_indicator_present(vec: &mut Vec<String>, default: &[&str]) { const SEPARATOR_WIDTH: usize = 4; -// Check whether the error is "unknown field" and, if so, list the available fields sorted and at -// least one per line, more if `CLIPPY_TERMINAL_WIDTH` is set and allows it. -pub fn format_error(error: Box<dyn Error>) -> String { - let s = error.to_string(); - - if_chain! { - if error.downcast::<toml::de::Error>().is_ok(); - if let Some((prefix, mut fields, suffix)) = parse_unknown_field_message(&s); - then { - use fmt::Write; - - fields.sort_unstable(); - - let (rows, column_widths) = calculate_dimensions(&fields); - - let mut msg = String::from(prefix); - for row in 0..rows { - writeln!(msg).unwrap(); - for (column, column_width) in column_widths.iter().copied().enumerate() { - let index = column * rows + row; - let field = fields.get(index).copied().unwrap_or_default(); - write!( - msg, - "{:SEPARATOR_WIDTH$}{field:column_width$}", - " " - ) - .unwrap(); - } - } - write!(msg, "\n{suffix}").unwrap(); - msg - } else { - s - } +#[derive(Debug)] +struct FieldError(String); + +impl std::error::Error for FieldError {} + +impl Display for FieldError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.pad(&self.0) } } -// `parse_unknown_field_message` will become unnecessary if -// https://github.com/alexcrichton/toml-rs/pull/364 is merged. -fn parse_unknown_field_message(s: &str) -> Option<(&str, Vec<&str>, &str)> { - // An "unknown field" message has the following form: - // unknown field `UNKNOWN`, expected one of `FIELD0`, `FIELD1`, ..., `FIELDN` at line X column Y - // ^^ ^^^^ ^^ - if_chain! { - if s.starts_with("unknown field"); - let slices = s.split("`, `").collect::<Vec<_>>(); - let n = slices.len(); - if n >= 2; - if let Some((prefix, first_field)) = slices[0].rsplit_once(" `"); - if let Some((last_field, suffix)) = slices[n - 1].split_once("` "); - then { - let fields = iter::once(first_field) - .chain(slices[1..n - 1].iter().copied()) - .chain(iter::once(last_field)) - .collect::<Vec<_>>(); - Some((prefix, fields, suffix)) - } else { - None +impl serde::de::Error for FieldError { + fn custom<T: Display>(msg: T) -> Self { + Self(msg.to_string()) + } + + fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self { + // List the available fields sorted and at least one per line, more if `CLIPPY_TERMINAL_WIDTH` is + // set and allows it. + use fmt::Write; + + let mut expected = expected.to_vec(); + expected.sort_unstable(); + + let (rows, column_widths) = calculate_dimensions(&expected); + + let mut msg = format!("unknown field `{field}`, expected one of"); + for row in 0..rows { + writeln!(msg).unwrap(); + for (column, column_width) in column_widths.iter().copied().enumerate() { + let index = column * rows + row; + let field = expected.get(index).copied().unwrap_or_default(); + write!(msg, "{:SEPARATOR_WIDTH$}{field:column_width$}", " ").unwrap(); + } } + Self(msg) } } |
