mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +00:00
[ty] Add --config CLI arg (#17697)
This commit is contained in:
parent
6c177e2bbe
commit
f46ed8d410
3 changed files with 186 additions and 4 deletions
|
@ -1,9 +1,11 @@
|
||||||
use crate::logging::Verbosity;
|
use crate::logging::Verbosity;
|
||||||
use crate::python_version::PythonVersion;
|
use crate::python_version::PythonVersion;
|
||||||
|
use clap::error::ErrorKind;
|
||||||
use clap::{ArgAction, ArgMatches, Error, Parser};
|
use clap::{ArgAction, ArgMatches, Error, Parser};
|
||||||
use ruff_db::system::SystemPathBuf;
|
use ruff_db::system::SystemPathBuf;
|
||||||
|
use ty_project::combine::Combine;
|
||||||
use ty_project::metadata::options::{EnvironmentOptions, Options, TerminalOptions};
|
use ty_project::metadata::options::{EnvironmentOptions, Options, TerminalOptions};
|
||||||
use ty_project::metadata::value::{RangedValue, RelativePathBuf};
|
use ty_project::metadata::value::{RangedValue, RelativePathBuf, ValueSource};
|
||||||
use ty_python_semantic::lint;
|
use ty_python_semantic::lint;
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
|
@ -14,6 +16,7 @@ pub(crate) struct Args {
|
||||||
pub(crate) command: Command,
|
pub(crate) command: Command,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Debug, clap::Subcommand)]
|
#[derive(Debug, clap::Subcommand)]
|
||||||
pub(crate) enum Command {
|
pub(crate) enum Command {
|
||||||
/// Check a project for type errors.
|
/// Check a project for type errors.
|
||||||
|
@ -86,6 +89,9 @@ pub(crate) struct CheckCommand {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) rules: RulesArg,
|
pub(crate) rules: RulesArg,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub(crate) config: ConfigsArg,
|
||||||
|
|
||||||
/// The format to use for printing diagnostic messages.
|
/// The format to use for printing diagnostic messages.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub(crate) output_format: Option<OutputFormat>,
|
pub(crate) output_format: Option<OutputFormat>,
|
||||||
|
@ -140,7 +146,7 @@ impl CheckCommand {
|
||||||
.no_respect_ignore_files
|
.no_respect_ignore_files
|
||||||
.then_some(false)
|
.then_some(false)
|
||||||
.or(self.respect_ignore_files);
|
.or(self.respect_ignore_files);
|
||||||
Options {
|
let options = Options {
|
||||||
environment: Some(EnvironmentOptions {
|
environment: Some(EnvironmentOptions {
|
||||||
python_version: self
|
python_version: self
|
||||||
.python_version
|
.python_version
|
||||||
|
@ -166,7 +172,9 @@ impl CheckCommand {
|
||||||
rules,
|
rules,
|
||||||
respect_ignore_files,
|
respect_ignore_files,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
};
|
||||||
|
// Merge with options passed in via --config
|
||||||
|
options.combine(self.config.into_options().unwrap_or_default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,3 +307,55 @@ pub(crate) enum TerminalColor {
|
||||||
/// Never display colors.
|
/// Never display colors.
|
||||||
Never,
|
Never,
|
||||||
}
|
}
|
||||||
|
/// A TOML `<KEY> = <VALUE>` pair
|
||||||
|
/// (such as you might find in a `ty.toml` configuration file)
|
||||||
|
/// overriding a specific configuration option.
|
||||||
|
/// Overrides of individual settings using this option always take precedence
|
||||||
|
/// over all configuration files.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct ConfigsArg(Option<Options>);
|
||||||
|
|
||||||
|
impl clap::FromArgMatches for ConfigsArg {
|
||||||
|
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, Error> {
|
||||||
|
let combined = matches
|
||||||
|
.get_many::<String>("config")
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|s| {
|
||||||
|
Options::from_toml_str(s, ValueSource::Cli)
|
||||||
|
.map_err(|err| Error::raw(ErrorKind::InvalidValue, err.to_string()))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
|
.into_iter()
|
||||||
|
.reduce(|acc, item| item.combine(acc));
|
||||||
|
Ok(Self(combined))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), Error> {
|
||||||
|
self.0 = Self::from_arg_matches(matches)?.0;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl clap::Args for ConfigsArg {
|
||||||
|
fn augment_args(cmd: clap::Command) -> clap::Command {
|
||||||
|
cmd.arg(
|
||||||
|
clap::Arg::new("config")
|
||||||
|
.short('c')
|
||||||
|
.long("config")
|
||||||
|
.value_name("CONFIG_OPTION")
|
||||||
|
.help("A TOML `<KEY> = <VALUE>` pair")
|
||||||
|
.action(ArgAction::Append),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
|
||||||
|
Self::augment_args(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigsArg {
|
||||||
|
pub(crate) fn into_options(self) -> Option<Options> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1338,6 +1338,128 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cli_config_args_toml_string_basic() -> anyhow::Result<()> {
|
||||||
|
let case = TestCase::with_file("test.py", r"print(x) # [unresolved-reference]")?;
|
||||||
|
|
||||||
|
// Long flag
|
||||||
|
assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config").arg("terminal.error-on-warning=true"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
warning: lint:unresolved-reference: Name `x` used when not defined
|
||||||
|
--> test.py:1:7
|
||||||
|
|
|
||||||
|
1 | print(x) # [unresolved-reference]
|
||||||
|
| ^
|
||||||
|
|
|
||||||
|
info: `lint:unresolved-reference` was selected on the command line
|
||||||
|
|
||||||
|
Found 1 diagnostic
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
// Short flag
|
||||||
|
assert_cmd_snapshot!(case.command().arg("-c").arg("terminal.error-on-warning=true"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
error: lint:unresolved-reference: Name `x` used when not defined
|
||||||
|
--> test.py:1:7
|
||||||
|
|
|
||||||
|
1 | print(x) # [unresolved-reference]
|
||||||
|
| ^
|
||||||
|
|
|
||||||
|
info: `lint:unresolved-reference` is enabled by default
|
||||||
|
|
||||||
|
Found 1 diagnostic
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cli_config_args_overrides_knot_toml() -> anyhow::Result<()> {
|
||||||
|
let case = TestCase::with_files(vec![
|
||||||
|
(
|
||||||
|
"knot.toml",
|
||||||
|
r#"
|
||||||
|
[terminal]
|
||||||
|
error-on-warning = true
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
("test.py", r"print(x) # [unresolved-reference]"),
|
||||||
|
])?;
|
||||||
|
assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config").arg("terminal.error-on-warning=false"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
warning: lint:unresolved-reference: Name `x` used when not defined
|
||||||
|
--> test.py:1:7
|
||||||
|
|
|
||||||
|
1 | print(x) # [unresolved-reference]
|
||||||
|
| ^
|
||||||
|
|
|
||||||
|
info: `lint:unresolved-reference` was selected on the command line
|
||||||
|
|
||||||
|
Found 1 diagnostic
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cli_config_args_later_overrides_earlier() -> anyhow::Result<()> {
|
||||||
|
let case = TestCase::with_file("test.py", r"print(x) # [unresolved-reference]")?;
|
||||||
|
assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config").arg("terminal.error-on-warning=true").arg("--config").arg("terminal.error-on-warning=false"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
warning: lint:unresolved-reference: Name `x` used when not defined
|
||||||
|
--> test.py:1:7
|
||||||
|
|
|
||||||
|
1 | print(x) # [unresolved-reference]
|
||||||
|
| ^
|
||||||
|
|
|
||||||
|
info: `lint:unresolved-reference` was selected on the command line
|
||||||
|
|
||||||
|
Found 1 diagnostic
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cli_config_args_invalid_option() -> anyhow::Result<()> {
|
||||||
|
let case = TestCase::with_file("test.py", r"print(1)")?;
|
||||||
|
assert_cmd_snapshot!(case.command().arg("--config").arg("bad-option=true"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: TOML parse error at line 1, column 1
|
||||||
|
|
|
||||||
|
1 | bad-option=true
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal`, `respect-ignore-files`
|
||||||
|
|
||||||
|
|
||||||
|
Usage: ty <COMMAND>
|
||||||
|
|
||||||
|
For more information, try '--help'.
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
struct TestCase {
|
struct TestCase {
|
||||||
_temp_dir: TempDir,
|
_temp_dir: TempDir,
|
||||||
_settings_scope: SettingsBindDropGuard,
|
_settings_scope: SettingsBindDropGuard,
|
||||||
|
|
|
@ -38,7 +38,7 @@ pub struct Options {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Options {
|
impl Options {
|
||||||
pub(crate) fn from_toml_str(content: &str, source: ValueSource) -> Result<Self, TyTomlError> {
|
pub fn from_toml_str(content: &str, source: ValueSource) -> Result<Self, TyTomlError> {
|
||||||
let _guard = ValueSourceGuard::new(source, true);
|
let _guard = ValueSourceGuard::new(source, true);
|
||||||
let options = toml::from_str(content)?;
|
let options = toml::from_str(content)?;
|
||||||
Ok(options)
|
Ok(options)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue