Skip to content

Commit 832c0fa

Browse files
InSyncWithFooMichaReiserAlexWaygood
authored
Better error message when --config is given a table key and a non-inline-table value (#15266)
Co-authored-by: Micha Reiser <[email protected]> Co-authored-by: Alex Waygood <[email protected]>
1 parent f29c9e4 commit 832c0fa

File tree

3 files changed

+132
-5
lines changed

3 files changed

+132
-5
lines changed

crates/ruff/src/args.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use anyhow::bail;
99
use clap::builder::{TypedValueParser, ValueParserFactory};
1010
use clap::{command, Parser, Subcommand};
1111
use colored::Colorize;
12+
use itertools::Itertools;
1213
use path_absolutize::path_dedot;
1314
use regex::Regex;
1415
use ruff_graph::Direction;
@@ -24,6 +25,7 @@ use ruff_source_file::{LineIndex, OneIndexed};
2425
use ruff_text_size::TextRange;
2526
use ruff_workspace::configuration::{Configuration, RuleSelection};
2627
use ruff_workspace::options::{Options, PycodestyleOptions};
28+
use ruff_workspace::options_base::{OptionEntry, OptionsMetadata};
2729
use ruff_workspace::resolver::ConfigurationTransformer;
2830
use rustc_hash::FxHashMap;
2931
use toml;
@@ -967,11 +969,36 @@ It looks like you were trying to pass a path to a configuration file.
967969
The path `{value}` does not point to a configuration file"
968970
));
969971
}
970-
} else if value.contains('=') {
971-
tip.push_str(&format!(
972-
"\n\n{}:\n\n{underlying_error}",
973-
config_parse_error.description()
974-
));
972+
} else if let Some((key, value)) = value.split_once('=') {
973+
let key = key.trim_ascii();
974+
let value = value.trim_ascii_start();
975+
976+
match Options::metadata().find(key) {
977+
Some(OptionEntry::Set(set)) if !value.starts_with('{') => {
978+
let prefixed_subfields = set
979+
.collect_fields()
980+
.iter()
981+
.map(|(name, _)| format!("- `{key}.{name}`"))
982+
.join("\n");
983+
984+
tip.push_str(&format!(
985+
"
986+
987+
`{key}` is a table of configuration options.
988+
Did you want to override one of the table's subkeys?
989+
990+
Possible choices:
991+
992+
{prefixed_subfields}"
993+
));
994+
}
995+
_ => {
996+
tip.push_str(&format!(
997+
"\n\n{}:\n\n{underlying_error}",
998+
config_parse_error.description()
999+
));
1000+
}
1001+
}
9751002
}
9761003
let tip = tip.trim_end().to_owned().into();
9771004

crates/ruff/tests/lint.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,90 @@ fn each_toml_option_requires_a_new_flag_2() {
780780
");
781781
}
782782

783+
#[test]
784+
fn value_given_to_table_key_is_not_inline_table_1() {
785+
// https://github.com/astral-sh/ruff/issues/13995
786+
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
787+
.args(STDIN_BASE_OPTIONS)
788+
.args([".", "--config", r#"lint.flake8-pytest-style="csv""#]),
789+
@r#"
790+
success: false
791+
exit_code: 2
792+
----- stdout -----
793+
794+
----- stderr -----
795+
error: invalid value 'lint.flake8-pytest-style="csv"' for '--config <CONFIG_OPTION>'
796+
797+
tip: A `--config` flag must either be a path to a `.toml` configuration file
798+
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
799+
option
800+
801+
`lint.flake8-pytest-style` is a table of configuration options.
802+
Did you want to override one of the table's subkeys?
803+
804+
Possible choices:
805+
806+
- `lint.flake8-pytest-style.fixture-parentheses`
807+
- `lint.flake8-pytest-style.parametrize-names-type`
808+
- `lint.flake8-pytest-style.parametrize-values-type`
809+
- `lint.flake8-pytest-style.parametrize-values-row-type`
810+
- `lint.flake8-pytest-style.raises-require-match-for`
811+
- `lint.flake8-pytest-style.raises-extend-require-match-for`
812+
- `lint.flake8-pytest-style.mark-parentheses`
813+
814+
For more information, try '--help'.
815+
"#);
816+
}
817+
818+
#[test]
819+
fn value_given_to_table_key_is_not_inline_table_2() {
820+
// https://github.com/astral-sh/ruff/issues/13995
821+
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
822+
.args(STDIN_BASE_OPTIONS)
823+
.args([".", "--config", r#"lint=123"#]),
824+
@r"
825+
success: false
826+
exit_code: 2
827+
----- stdout -----
828+
829+
----- stderr -----
830+
error: invalid value 'lint=123' for '--config <CONFIG_OPTION>'
831+
832+
tip: A `--config` flag must either be a path to a `.toml` configuration file
833+
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
834+
option
835+
836+
`lint` is a table of configuration options.
837+
Did you want to override one of the table's subkeys?
838+
839+
Possible choices:
840+
841+
- `lint.allowed-confusables`
842+
- `lint.dummy-variable-rgx`
843+
- `lint.extend-ignore`
844+
- `lint.extend-select`
845+
- `lint.extend-fixable`
846+
- `lint.external`
847+
- `lint.fixable`
848+
- `lint.ignore`
849+
- `lint.extend-safe-fixes`
850+
- `lint.extend-unsafe-fixes`
851+
- `lint.ignore-init-module-imports`
852+
- `lint.logger-objects`
853+
- `lint.select`
854+
- `lint.explicit-preview-rules`
855+
- `lint.task-tags`
856+
- `lint.typing-modules`
857+
- `lint.unfixable`
858+
- `lint.per-file-ignores`
859+
- `lint.extend-per-file-ignores`
860+
- `lint.exclude`
861+
- `lint.preview`
862+
863+
For more information, try '--help'.
864+
");
865+
}
866+
783867
#[test]
784868
fn config_doubly_overridden_via_cli() -> Result<()> {
785869
let tempdir = TempDir::new()?;

crates/ruff_workspace/src/options_base.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,22 @@ impl OptionSet {
285285
None
286286
}
287287
}
288+
289+
pub fn collect_fields(&self) -> Vec<(String, OptionField)> {
290+
struct FieldsCollector(Vec<(String, OptionField)>);
291+
292+
impl Visit for FieldsCollector {
293+
fn record_field(&mut self, name: &str, field: OptionField) {
294+
self.0.push((name.to_string(), field));
295+
}
296+
297+
fn record_set(&mut self, _name: &str, _group: OptionSet) {}
298+
}
299+
300+
let mut visitor = FieldsCollector(vec![]);
301+
self.record(&mut visitor);
302+
visitor.0
303+
}
288304
}
289305

290306
/// Visitor that writes out the names of all fields and sets.

0 commit comments

Comments
 (0)