Skip to content

Commit 6f4a598

Browse files
committed
Add new configuration option abort_on_unrecognised_options
This option was proposed in issue 5022 and allows rustfmt to exit early in the event that any unstable options are set in the projects rustfmt.toml
1 parent 35f4c55 commit 6f4a598

File tree

9 files changed

+416
-41
lines changed

9 files changed

+416
-41
lines changed

Configurations.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ To enable unstable options, set `unstable_features = true` in `rustfmt.toml` or
1717

1818
Below you find a detailed visual guide on all the supported configuration options of rustfmt:
1919

20+
## `abort_on_unrecognised_options`
21+
22+
Exit early when using nightly only options on the stable channel
23+
24+
- **Default value**: `false`
25+
- **Possible values**: `true`, `false`
26+
- **Stable**: No (tracking issue: [#5022](https://github.com/rust-lang/rustfmt/issues/5022))
27+
28+
2029
## `array_width`
2130

2231
Maximum width of an array literal before falling back to vertical formatting.

src/config/config_type.rs

Lines changed: 120 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::HashMap;
2+
13
use crate::config::file_lines::FileLines;
24
use crate::config::options::{IgnoreList, WidthHeuristics};
35

@@ -58,6 +60,82 @@ impl ConfigType for IgnoreList {
5860
}
5961
}
6062

63+
/// Store a map of all Unstable options used in in the configuration.
64+
#[derive(Clone, Debug)]
65+
pub struct UnstableOptions {
66+
pub(crate) options: HashMap<&'static str, String>,
67+
}
68+
69+
impl std::fmt::Display for UnstableOptions {
70+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71+
if let Some(message) = self.abort_message() {
72+
write!(f, "{}", message)
73+
} else {
74+
write!(f, "No unstable options were used.")
75+
}
76+
}
77+
}
78+
79+
impl UnstableOptions {
80+
/// Create a new UnstableOptions struct
81+
pub(crate) fn new() -> Self {
82+
Self {
83+
options: HashMap::new(),
84+
}
85+
}
86+
87+
/// Insert an unstable option and a user supplied value for that unstable option
88+
pub(crate) fn insert(&mut self, option: &'static str, user_supplied_value: String) {
89+
self.options.insert(option, user_supplied_value);
90+
}
91+
92+
/// Check if any unstable options have been set
93+
pub(crate) fn has_unstable_options(&self) -> bool {
94+
!self.options.is_empty()
95+
}
96+
97+
/// Generate the Warning message
98+
pub(crate) fn warning_message(&self) -> Option<String> {
99+
if self.options.is_empty() {
100+
return None;
101+
}
102+
let mut result = String::new();
103+
104+
for (k, v) in self.options.iter() {
105+
result.push_str(&format!(
106+
"Warning: can't set `{} = {}`, unstable features are only \
107+
available in nightly channel.\n",
108+
k, v,
109+
));
110+
}
111+
112+
let upgrade_to_abort_message = "\nSet `abort_on_unrecognised_options = true` \
113+
to convert this warning into an error\n\n";
114+
115+
result.push_str(upgrade_to_abort_message);
116+
117+
Some(result)
118+
}
119+
120+
/// Generate the Abort message
121+
pub(crate) fn abort_message(&self) -> Option<String> {
122+
if self.options.is_empty() {
123+
return None;
124+
}
125+
126+
let mut result = String::new();
127+
result.push_str("Can't set nightly options when using stable rustfmt\n");
128+
129+
for (k, v) in self.options.iter() {
130+
result.push_str(&format!(" - `{} = {}`\n", k, v));
131+
}
132+
let to_warning_message = "\nSet `abort_on_unrecognised_options = false` \
133+
to convert this error into a warning\n\n";
134+
result.push_str(to_warning_message);
135+
Some(result)
136+
}
137+
}
138+
61139
macro_rules! create_config {
62140
// Options passed in to the macro.
63141
//
@@ -76,6 +154,8 @@ macro_rules! create_config {
76154
#[derive(Clone)]
77155
#[allow(unreachable_pub)]
78156
pub struct Config {
157+
// Unstable Options specified on the stable channel
158+
configured_unstable_options: UnstableOptions,
79159
// For each config item, we store:
80160
//
81161
// - 0: true if the value has been access
@@ -159,12 +239,41 @@ macro_rules! create_config {
159239
ConfigWasSet(self)
160240
}
161241

242+
/// Insert all unstable options and their values into the UnstableOptions struct.
243+
/// The only exception is the "abort_on_unrecognised_options", which helps
244+
/// determine if we should abort or warn when using unstable options on stable rustfmt
245+
#[allow(unreachable_pub)]
246+
pub fn insert_unstable_options(&mut self, option: &'static str, value: String) {
247+
if option == "abort_on_unrecognised_options" {
248+
return
249+
}
250+
251+
match option {
252+
$(
253+
stringify!($i) => {
254+
// If its an unstable option then add it to the unstable list
255+
if !self.$i.3 {
256+
self.configured_unstable_options.insert(option, value);
257+
}
258+
}
259+
)+
260+
_ => panic!("Unknown config key in override: {}", option)
261+
}
262+
263+
}
264+
162265
fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config {
163266
$(
164267
if let Some(option_value) = parsed.$i {
165268
let option_stable = self.$i.3;
269+
if !option_stable || !option_value.stable_variant() {
270+
self.insert_unstable_options(
271+
stringify!($i), format!("{:?}", &option_value)
272+
);
273+
}
274+
166275
if $crate::config::config_type::is_stable_option_and_value(
167-
stringify!($i), option_stable, &option_value
276+
option_stable, &option_value
168277
) {
169278
self.$i.1 = true;
170279
self.$i.2 = option_value;
@@ -228,6 +337,12 @@ macro_rules! create_config {
228337
}
229338
}
230339

340+
/// Get a reference to the UnstableOptions set on the configuration.
341+
#[allow(unreachable_pub)]
342+
pub fn unstable_options(&self) -> &UnstableOptions {
343+
&self.configured_unstable_options
344+
}
345+
231346
#[allow(unreachable_pub)]
232347
pub fn override_value(&mut self, key: &str, val: &str)
233348
{
@@ -439,6 +554,7 @@ macro_rules! create_config {
439554
impl Default for Config {
440555
fn default() -> Config {
441556
Config {
557+
configured_unstable_options: UnstableOptions::new(),
442558
$(
443559
$i: (Cell::new(false), false, $def, $stb),
444560
)+
@@ -448,35 +564,17 @@ macro_rules! create_config {
448564
)
449565
}
450566

451-
pub(crate) fn is_stable_option_and_value<T>(
452-
option_name: &str,
453-
option_stable: bool,
454-
option_value: &T,
455-
) -> bool
567+
pub(crate) fn is_stable_option_and_value<T>(option_stable: bool, option_value: &T) -> bool
456568
where
457569
T: PartialEq + std::fmt::Debug + ConfigType,
458570
{
459571
let nightly = crate::is_nightly_channel!();
460572
let variant_stable = option_value.stable_variant();
461573
match (nightly, option_stable, variant_stable) {
462574
// Stable with an unstable option
463-
(false, false, _) => {
464-
eprintln!(
465-
"Warning: can't set `{} = {:?}`, unstable features are only \
466-
available in nightly channel.",
467-
option_name, option_value
468-
);
469-
false
470-
}
575+
(false, false, _) => false,
471576
// Stable with a stable option, but an unstable variant
472-
(false, true, false) => {
473-
eprintln!(
474-
"Warning: can't set `{} = {:?}`, unstable variants are only \
475-
available in nightly channel.",
476-
option_name, option_value
477-
);
478-
false
479-
}
577+
(false, true, false) => false,
480578
// Nightly: everything allowed
481579
// Stable with stable option and variant: allowed
482580
(true, _, _) | (false, true, true) => true,

src/config/mod.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use thiserror::Error;
99

1010
use crate::config::config_type::ConfigType;
1111
#[allow(unreachable_pub)]
12+
pub use crate::config::config_type::UnstableOptions;
13+
#[allow(unreachable_pub)]
1214
pub use crate::config::file_lines::{FileLines, FileName, Range};
1315
#[allow(unreachable_pub)]
1416
pub use crate::config::lists::*;
@@ -164,6 +166,8 @@ create_config! {
164166
or they are left with trailing whitespaces";
165167
ignore: IgnoreList, IgnoreList::default(), false,
166168
"Skip formatting the specified files and directories";
169+
abort_on_unrecognised_options: bool, false, false,
170+
"Exit early when using nightly only options on the stable channel";
167171

168172
// Not user-facing
169173
verbose: Verbosity, Verbosity::Normal, false, "How much to information to emit to the user";
@@ -586,6 +590,59 @@ mod test {
586590
assert_eq!(s.contains(PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION), true);
587591
}
588592

593+
#[stable_only_test]
594+
#[test]
595+
fn test_get_all_unstable_options_set_in_toml() {
596+
use std::collections::HashMap;
597+
let toml = r#"
598+
reorder_impl_items = true
599+
"#;
600+
let config = Config::from_toml(toml, Path::new("")).unwrap();
601+
let mut expected = HashMap::new();
602+
expected.insert("reorder_impl_items", String::from("true"));
603+
assert_eq!(config.unstable_options().options, expected);
604+
}
605+
606+
#[stable_only_test]
607+
#[test]
608+
fn test_warning_message_when_using_unstable_options() {
609+
let toml = r#"
610+
reorder_impl_items = true
611+
"#;
612+
let config = Config::from_toml(toml, Path::new("")).unwrap();
613+
let warning = "\
614+
Warning: can't set `reorder_impl_items = true`, unstable features are only available in \
615+
nightly channel.
616+
617+
Set `abort_on_unrecognised_options = true` to convert this warning into an error
618+
619+
";
620+
assert_eq!(
621+
warning,
622+
config.unstable_options().warning_message().unwrap()
623+
)
624+
}
625+
626+
#[stable_only_test]
627+
#[test]
628+
fn test_abort_message_when_using_unstable_options() {
629+
let toml = r#"
630+
reorder_impl_items = true
631+
"#;
632+
let config = Config::from_toml(toml, Path::new("")).unwrap();
633+
let abort_message = "\
634+
Can't set nightly options when using stable rustfmt
635+
- `reorder_impl_items = true`
636+
637+
Set `abort_on_unrecognised_options = false` to convert this error into a warning
638+
639+
";
640+
assert_eq!(
641+
abort_message,
642+
config.unstable_options().abort_message().unwrap()
643+
)
644+
}
645+
589646
#[test]
590647
fn test_dump_default_config() {
591648
let default_config = format!(
@@ -663,6 +720,7 @@ hide_parse_errors = false
663720
error_on_line_overflow = false
664721
error_on_unformatted = false
665722
ignore = []
723+
abort_on_unrecognised_options = false
666724
emit_mode = "Files"
667725
make_backup = false
668726
"#,

src/format_report_formatter.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ fn error_kind_to_snippet_annotation_type(error_kind: &ErrorKind) -> AnnotationTy
144144
| ErrorKind::LostComment
145145
| ErrorKind::BadAttr
146146
| ErrorKind::InvalidGlobPattern(_)
147+
| ErrorKind::NightlyOnlyOptions(_)
147148
| ErrorKind::VersionMismatch => AnnotationType::Error,
148149
ErrorKind::DeprecatedAttr => AnnotationType::Warning,
149150
}

src/formatting.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@ impl<'b, T: Write + 'b> Session<'b, T> {
3535
return Err(ErrorKind::VersionMismatch);
3636
}
3737

38+
if !crate::is_nightly_channel!() {
39+
let using_unstalbe_options = self.config.unstable_options().has_unstable_options();
40+
let abort_on_unstable_options = self.config.abort_on_unrecognised_options();
41+
if using_unstalbe_options && abort_on_unstable_options {
42+
return Err(ErrorKind::NightlyOnlyOptions(
43+
self.config.unstable_options().clone(),
44+
));
45+
} else if using_unstalbe_options && !abort_on_unstable_options {
46+
if let Some(warning) = self.config.unstable_options().warning_message() {
47+
eprintln!("{}", warning);
48+
}
49+
}
50+
}
51+
3852
rustc_span::create_session_if_not_set_then(self.config.edition().into(), |_| {
3953
if self.config.disable_all_formatting() {
4054
// When the input is from stdin, echo back the input.

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use crate::utils::indent_next_line;
4646

4747
pub use crate::config::{
4848
load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, NewlineStyle,
49-
Range, Verbosity,
49+
Range, UnstableOptions, Verbosity,
5050
};
5151

5252
pub use crate::format_report_formatter::{FormatReportFormatter, FormatReportFormatterBuilder};
@@ -133,6 +133,9 @@ pub enum ErrorKind {
133133
/// Invalid glob pattern in `ignore` configuration option.
134134
#[error("Invalid glob pattern found in ignore list: {0}")]
135135
InvalidGlobPattern(ignore::Error),
136+
/// Using unstable, nightly only options on stable rustfmt.
137+
#[error("{0}")]
138+
NightlyOnlyOptions(UnstableOptions),
136139
}
137140

138141
impl ErrorKind {

tests/config/no_unstable_options.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
max_width = 100
2+
hard_tabs = false
3+
tab_spaces = 4
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
wrap_comments = true
2+
unstable_features = true

0 commit comments

Comments
 (0)