@@ -6,7 +6,8 @@ use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor};
6
6
use serde:: Deserialize ;
7
7
use std:: error:: Error ;
8
8
use std:: path:: { Path , PathBuf } ;
9
- use std:: { env, fmt, fs, io} ;
9
+ use std:: str:: FromStr ;
10
+ use std:: { cmp, env, fmt, fs, io, iter} ;
10
11
11
12
/// Holds information used by `MISSING_ENFORCED_IMPORT_RENAMES` lint.
12
13
#[ derive( Clone , Debug , Deserialize ) ]
@@ -43,18 +44,33 @@ pub enum DisallowedType {
43
44
#[ derive( Default ) ]
44
45
pub struct TryConf {
45
46
pub conf : Conf ,
46
- pub errors : Vec < String > ,
47
+ pub errors : Vec < Box < dyn Error > > ,
47
48
}
48
49
49
50
impl TryConf {
50
- fn from_error ( error : impl Error ) -> Self {
51
+ fn from_error ( error : impl Error + ' static ) -> Self {
51
52
Self {
52
53
conf : Conf :: default ( ) ,
53
- errors : vec ! [ error . to_string ( ) ] ,
54
+ errors : vec ! [ Box :: new ( error ) ] ,
54
55
}
55
56
}
56
57
}
57
58
59
+ #[ derive( Debug ) ]
60
+ struct ConfError ( String ) ;
61
+
62
+ impl fmt:: Display for ConfError {
63
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
64
+ <String as fmt:: Display >:: fmt ( & self . 0 , f)
65
+ }
66
+ }
67
+
68
+ impl Error for ConfError { }
69
+
70
+ fn conf_error ( s : String ) -> Box < dyn Error > {
71
+ Box :: new ( ConfError ( s) )
72
+ }
73
+
58
74
macro_rules! define_Conf {
59
75
( $(
60
76
$( #[ doc = $doc: literal] ) +
@@ -103,11 +119,11 @@ macro_rules! define_Conf {
103
119
while let Some ( name) = map. next_key:: <& str >( ) ? {
104
120
match Field :: deserialize( name. into_deserializer( ) ) ? {
105
121
$( Field :: $name => {
106
- $( errors. push( format!( "deprecated field `{}`. {}" , name, $dep) ) ; ) ?
122
+ $( errors. push( conf_error ( format!( "deprecated field `{}`. {}" , name, $dep) ) ) ; ) ?
107
123
match map. next_value( ) {
108
- Err ( e) => errors. push( e. to_string( ) ) ,
124
+ Err ( e) => errors. push( conf_error ( e. to_string( ) ) ) ,
109
125
Ok ( value) => match $name {
110
- Some ( _) => errors. push( format!( "duplicate field `{}`" , name) ) ,
126
+ Some ( _) => errors. push( conf_error ( format!( "duplicate field `{}`" , name) ) ) ,
111
127
None => $name = Some ( value) ,
112
128
}
113
129
}
@@ -383,3 +399,102 @@ pub fn read(path: &Path) -> TryConf {
383
399
} ;
384
400
toml:: from_str ( & content) . unwrap_or_else ( TryConf :: from_error)
385
401
}
402
+
403
+ const SEPARATOR_WIDTH : usize = 4 ;
404
+
405
+ // Check whether the error is "unknown field" and, if so, list the available fields sorted and at
406
+ // least one per line, more if `CLIPPY_TERMINAL_WIDTH` is set and allows it.
407
+ pub fn format_error ( error : Box < dyn Error > ) -> String {
408
+ let s = error. to_string ( ) ;
409
+
410
+ if_chain ! {
411
+ if error. downcast:: <toml:: de:: Error >( ) . is_ok( ) ;
412
+ if let Some ( ( prefix, mut fields, suffix) ) = parse_unknown_field_message( & s) ;
413
+ then {
414
+ use fmt:: Write ;
415
+
416
+ fields. sort_unstable( ) ;
417
+
418
+ let ( rows, column_widths) = calculate_dimensions( & fields) ;
419
+
420
+ let mut msg = String :: from( prefix) ;
421
+ for row in 0 ..rows {
422
+ write!( msg, "\n " ) . unwrap( ) ;
423
+ for ( column, column_width) in column_widths. iter( ) . copied( ) . enumerate( ) {
424
+ let index = column * rows + row;
425
+ let field = fields. get( index) . copied( ) . unwrap_or_default( ) ;
426
+ write!(
427
+ msg,
428
+ "{:separator_width$}{:field_width$}" ,
429
+ " " ,
430
+ field,
431
+ separator_width = SEPARATOR_WIDTH ,
432
+ field_width = column_width
433
+ )
434
+ . unwrap( ) ;
435
+ }
436
+ }
437
+ write!( msg, "\n {}" , suffix) . unwrap( ) ;
438
+ msg
439
+ } else {
440
+ s
441
+ }
442
+ }
443
+ }
444
+
445
+ // `parse_unknown_field_message` will become unnecessary if
446
+ // https://github.com/alexcrichton/toml-rs/pull/364 is merged.
447
+ fn parse_unknown_field_message ( s : & str ) -> Option < ( & str , Vec < & str > , & str ) > {
448
+ // An "unknown field" message has the following form:
449
+ // unknown field `UNKNOWN`, expected one of `FIELD0`, `FIELD1`, ..., `FIELDN` at line X column Y
450
+ // ^^ ^^^^ ^^
451
+ if_chain ! {
452
+ if s. starts_with( "unknown field" ) ;
453
+ let slices = s. split( "`, `" ) . collect:: <Vec <_>>( ) ;
454
+ let n = slices. len( ) ;
455
+ if n >= 2 ;
456
+ if let Some ( ( prefix, first_field) ) = slices[ 0 ] . rsplit_once( " `" ) ;
457
+ if let Some ( ( last_field, suffix) ) = slices[ n - 1 ] . split_once( "` " ) ;
458
+ then {
459
+ let fields = iter:: once( first_field)
460
+ . chain( slices[ 1 ..n - 1 ] . iter( ) . copied( ) )
461
+ . chain( iter:: once( last_field) )
462
+ . collect:: <Vec <_>>( ) ;
463
+ Some ( ( prefix, fields, suffix) )
464
+ } else {
465
+ None
466
+ }
467
+ }
468
+ }
469
+
470
+ fn calculate_dimensions ( fields : & [ & str ] ) -> ( usize , Vec < usize > ) {
471
+ let columns = env:: var ( "CLIPPY_TERMINAL_WIDTH" )
472
+ . ok ( )
473
+ . and_then ( |s| <usize as FromStr >:: from_str ( & s) . ok ( ) )
474
+ . map_or ( 1 , |terminal_width| {
475
+ let max_field_width = fields. iter ( ) . map ( |field| field. len ( ) ) . max ( ) . unwrap ( ) ;
476
+ cmp:: max ( 1 , terminal_width / ( SEPARATOR_WIDTH + max_field_width) )
477
+ } ) ;
478
+
479
+ let rows = ( fields. len ( ) + ( columns - 1 ) ) / columns;
480
+
481
+ let column_widths = ( 0 ..columns)
482
+ . map ( |column| {
483
+ if column < columns - 1 {
484
+ ( 0 ..rows)
485
+ . map ( |row| {
486
+ let index = column * rows + row;
487
+ let field = fields. get ( index) . copied ( ) . unwrap_or_default ( ) ;
488
+ field. len ( )
489
+ } )
490
+ . max ( )
491
+ . unwrap ( )
492
+ } else {
493
+ // Avoid adding extra space to the last column.
494
+ 0
495
+ }
496
+ } )
497
+ . collect :: < Vec < _ > > ( ) ;
498
+
499
+ ( rows, column_widths)
500
+ }
0 commit comments