2
2
3
3
#![allow(clippy::module_name_repetitions)]
4
4
5
+ use rustc_session::Session;
6
+ use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
5
7
use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor};
6
8
use serde::Deserialize;
7
- use std::error::Error;
9
+ use std::fmt::{Debug, Display, Formatter};
10
+ use std::ops::Range;
8
11
use std::path::{Path, PathBuf};
9
12
use std::str::FromStr;
10
- use std::{cmp, env, fmt, fs, io, iter };
13
+ use std::{cmp, env, fmt, fs, io};
11
14
12
15
#[rustfmt::skip]
13
16
const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
@@ -67,33 +70,70 @@ impl DisallowedPath {
67
70
#[derive(Default)]
68
71
pub struct TryConf {
69
72
pub conf: Conf,
70
- pub errors: Vec<Box<dyn Error> >,
71
- pub warnings: Vec<Box<dyn Error> >,
73
+ pub errors: Vec<ConfError >,
74
+ pub warnings: Vec<ConfError >,
72
75
}
73
76
74
77
impl TryConf {
75
- fn from_error(error: impl Error + 'static) -> Self {
78
+ fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self {
79
+ ConfError::from_toml(file, error).into()
80
+ }
81
+ }
82
+
83
+ impl From<ConfError> for TryConf {
84
+ fn from(value: ConfError) -> Self {
76
85
Self {
77
86
conf: Conf::default(),
78
- errors: vec![Box::new(error) ],
87
+ errors: vec![value ],
79
88
warnings: vec![],
80
89
}
81
90
}
82
91
}
83
92
93
+ impl From<io::Error> for TryConf {
94
+ fn from(value: io::Error) -> Self {
95
+ ConfError::from(value).into()
96
+ }
97
+ }
98
+
84
99
#[derive(Debug)]
85
- struct ConfError(String);
100
+ pub struct ConfError {
101
+ pub message: String,
102
+ pub span: Option<Span>,
103
+ }
104
+
105
+ impl ConfError {
106
+ fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
107
+ if let Some(span) = error.span() {
108
+ Self::spanned(file, error.message(), span)
109
+ } else {
110
+ Self {
111
+ message: error.message().to_string(),
112
+ span: None,
113
+ }
114
+ }
115
+ }
86
116
87
- impl fmt::Display for ConfError {
88
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89
- <String as fmt::Display>::fmt(&self.0, f)
117
+ fn spanned(file: &SourceFile, message: impl Into<String>, span: Range<usize>) -> Self {
118
+ Self {
119
+ message: message.into(),
120
+ span: Some(Span::new(
121
+ file.start_pos + BytePos::from_usize(span.start),
122
+ file.start_pos + BytePos::from_usize(span.end),
123
+ SyntaxContext::root(),
124
+ None,
125
+ )),
126
+ }
90
127
}
91
128
}
92
129
93
- impl Error for ConfError {}
94
-
95
- fn conf_error(s: impl Into<String>) -> Box<dyn Error> {
96
- Box::new(ConfError(s.into()))
130
+ impl From<io::Error> for ConfError {
131
+ fn from(value: io::Error) -> Self {
132
+ Self {
133
+ message: value.to_string(),
134
+ span: None,
135
+ }
136
+ }
97
137
}
98
138
99
139
macro_rules! define_Conf {
@@ -117,20 +157,14 @@ macro_rules! define_Conf {
117
157
}
118
158
}
119
159
120
- impl<'de> Deserialize<'de> for TryConf {
121
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
122
- deserializer.deserialize_map(ConfVisitor)
123
- }
124
- }
125
-
126
160
#[derive(Deserialize)]
127
161
#[serde(field_identifier, rename_all = "kebab-case")]
128
162
#[allow(non_camel_case_types)]
129
163
enum Field { $($name,)* third_party, }
130
164
131
- struct ConfVisitor;
165
+ struct ConfVisitor<'a>(&'a SourceFile) ;
132
166
133
- impl<'de> Visitor<'de> for ConfVisitor {
167
+ impl<'de> Visitor<'de> for ConfVisitor<'_> {
134
168
type Value = TryConf;
135
169
136
170
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -141,32 +175,38 @@ macro_rules! define_Conf {
141
175
let mut errors = Vec::new();
142
176
let mut warnings = Vec::new();
143
177
$(let mut $name = None;)*
144
- // could get `Field` here directly, but get `str` first for diagnostics
145
- while let Some(name) = map.next_key::<&str>()? {
146
- match Field::deserialize(name.into_deserializer())? {
147
- $(Field::$name => {
148
- $(warnings.push(conf_error(format!("deprecated field `{}`. {}", name, $dep)));)?
149
- match map.next_value() {
150
- Err(e) => errors.push(conf_error(e.to_string())),
178
+ // could get `Field` here directly, but get `String` first for diagnostics
179
+ while let Some(name) = map.next_key::<toml::Spanned<String>>()? {
180
+ match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
181
+ Err(e) => {
182
+ let e: FieldError = e;
183
+ errors.push(ConfError::spanned(self.0, e.0, name.span()));
184
+ }
185
+ $(Ok(Field::$name) => {
186
+ $(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), name.span()));)?
187
+ let raw_value = map.next_value::<toml::Spanned<toml::Value>>()?;
188
+ let value_span = raw_value.span();
189
+ match <$ty>::deserialize(raw_value.into_inner()) {
190
+ Err(e) => errors.push(ConfError::spanned(self.0, e.to_string().replace('\n', " ").trim(), value_span)),
151
191
Ok(value) => match $name {
152
- Some(_) => errors.push(conf_error( format!("duplicate field `{}`", name))),
192
+ Some(_) => errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), name.span( ))),
153
193
None => {
154
194
$name = Some(value);
155
195
// $new_conf is the same as one of the defined `$name`s, so
156
196
// this variable is defined in line 2 of this function.
157
197
$(match $new_conf {
158
- Some(_) => errors.push(conf_error( concat!(
198
+ Some(_) => errors.push(ConfError::spanned(self.0, concat!(
159
199
"duplicate field `", stringify!($new_conf),
160
200
"` (provided as `", stringify!($name), "`)"
161
- ))),
201
+ ), name.span() )),
162
202
None => $new_conf = $name.clone(),
163
203
})?
164
204
},
165
205
}
166
206
}
167
207
})*
168
- // white-listed; ignore
169
- Field::third_party => drop(map.next_value::<IgnoredAny>())
208
+ // ignore contents of the third_party key
209
+ Ok( Field::third_party) => drop(map.next_value::<IgnoredAny>())
170
210
}
171
211
}
172
212
let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
@@ -532,19 +572,19 @@ pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
532
572
/// Read the `toml` configuration file.
533
573
///
534
574
/// In case of error, the function tries to continue as much as possible.
535
- pub fn read(path: &Path) -> TryConf {
536
- let content = match fs::read_to_string (path) {
537
- Err(e) => return TryConf::from_error(e ),
538
- Ok(content ) => content ,
575
+ pub fn read(sess: &Session, path: &Path) -> TryConf {
576
+ let file = match sess.source_map().load_file (path) {
577
+ Err(e) => return e.into( ),
578
+ Ok(file ) => file ,
539
579
};
540
- match toml::from_str::<TryConf>(&content ) {
580
+ match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(&file) ) {
541
581
Ok(mut conf) => {
542
582
extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS);
543
583
extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
544
584
545
585
conf
546
586
},
547
- Err(e) => TryConf::from_error( e),
587
+ Err(e) => TryConf::from_toml_error(&file, & e),
548
588
}
549
589
}
550
590
@@ -556,65 +596,42 @@ fn extend_vec_if_indicator_present(vec: &mut Vec<String>, default: &[&str]) {
556
596
557
597
const SEPARATOR_WIDTH: usize = 4;
558
598
559
- // Check whether the error is "unknown field" and, if so, list the available fields sorted and at
560
- // least one per line, more if `CLIPPY_TERMINAL_WIDTH` is set and allows it.
561
- pub fn format_error(error: Box<dyn Error>) -> String {
562
- let s = error.to_string();
563
-
564
- if_chain! {
565
- if error.downcast::<toml::de::Error>().is_ok();
566
- if let Some((prefix, mut fields, suffix)) = parse_unknown_field_message(&s);
567
- then {
568
- use fmt::Write;
569
-
570
- fields.sort_unstable();
571
-
572
- let (rows, column_widths) = calculate_dimensions(&fields);
573
-
574
- let mut msg = String::from(prefix);
575
- for row in 0..rows {
576
- writeln!(msg).unwrap();
577
- for (column, column_width) in column_widths.iter().copied().enumerate() {
578
- let index = column * rows + row;
579
- let field = fields.get(index).copied().unwrap_or_default();
580
- write!(
581
- msg,
582
- "{:SEPARATOR_WIDTH$}{field:column_width$}",
583
- " "
584
- )
585
- .unwrap();
586
- }
587
- }
588
- write!(msg, "\n{suffix}").unwrap();
589
- msg
590
- } else {
591
- s
592
- }
599
+ #[derive(Debug)]
600
+ struct FieldError(String);
601
+
602
+ impl std::error::Error for FieldError {}
603
+
604
+ impl Display for FieldError {
605
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
606
+ f.pad(&self.0)
593
607
}
594
608
}
595
609
596
- // `parse_unknown_field_message` will become unnecessary if
597
- // https://github.com/alexcrichton/toml-rs/pull/364 is merged.
598
- fn parse_unknown_field_message(s: &str) -> Option<(&str, Vec<&str>, &str)> {
599
- // An "unknown field" message has the following form:
600
- // unknown field `UNKNOWN`, expected one of `FIELD0`, `FIELD1`, ..., `FIELDN` at line X column Y
601
- // ^^ ^^^^ ^^
602
- if_chain! {
603
- if s.starts_with("unknown field");
604
- let slices = s.split("`, `").collect::<Vec<_>>();
605
- let n = slices.len();
606
- if n >= 2;
607
- if let Some((prefix, first_field)) = slices[0].rsplit_once(" `");
608
- if let Some((last_field, suffix)) = slices[n - 1].split_once("` ");
609
- then {
610
- let fields = iter::once(first_field)
611
- .chain(slices[1..n - 1].iter().copied())
612
- .chain(iter::once(last_field))
613
- .collect::<Vec<_>>();
614
- Some((prefix, fields, suffix))
615
- } else {
616
- None
610
+ impl serde::de::Error for FieldError {
611
+ fn custom<T: Display>(msg: T) -> Self {
612
+ Self(msg.to_string())
613
+ }
614
+
615
+ fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
616
+ // List the available fields sorted and at least one per line, more if `CLIPPY_TERMINAL_WIDTH` is
617
+ // set and allows it.
618
+ use fmt::Write;
619
+
620
+ let mut expected = expected.to_vec();
621
+ expected.sort_unstable();
622
+
623
+ let (rows, column_widths) = calculate_dimensions(&expected);
624
+
625
+ let mut msg = format!("unknown field `{field}`, expected one of");
626
+ for row in 0..rows {
627
+ writeln!(msg).unwrap();
628
+ for (column, column_width) in column_widths.iter().copied().enumerate() {
629
+ let index = column * rows + row;
630
+ let field = expected.get(index).copied().unwrap_or_default();
631
+ write!(msg, "{:SEPARATOR_WIDTH$}{field:column_width$}", " ").unwrap();
632
+ }
617
633
}
634
+ Self(msg)
618
635
}
619
636
}
620
637
0 commit comments