Compare commits
9 Commits
charlie/pa
...
charlie/ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e23ee0e1d | ||
|
|
7a349660e4 | ||
|
|
a9d13e6bc9 | ||
|
|
8ec56277e9 | ||
|
|
b21ba71ef4 | ||
|
|
d387d0ba82 | ||
|
|
6f0e4ad332 | ||
|
|
7ca515c0aa | ||
|
|
1ce07d65bd |
659
Cargo.lock
generated
659
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,7 @@ argfile = { version = "0.1.6" }
|
||||
assert_cmd = { version = "2.0.13" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "2.4.1" }
|
||||
bstr = { version = "1.9.0" }
|
||||
cachedir = { version = "0.3.1" }
|
||||
chrono = { version = "0.4.33", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.18", features = ["derive"] }
|
||||
|
||||
@@ -49,6 +49,7 @@ serde_json = { workspace = true }
|
||||
shellexpand = { workspace = true }
|
||||
strum = { workspace = true, features = [] }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
walkdir = { workspace = true }
|
||||
wild = { workspace = true }
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Formatter;
|
||||
use std::path::PathBuf;
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::bail;
|
||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
||||
use clap::{command, Parser};
|
||||
use colored::Colorize;
|
||||
use path_absolutize::path_dedot;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use toml;
|
||||
|
||||
use ruff_linter::line_width::LineLength;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
@@ -19,7 +25,7 @@ use ruff_linter::{warn_user, RuleParser, RuleSelector, RuleSelectorParser};
|
||||
use ruff_source_file::{LineIndex, OneIndexed};
|
||||
use ruff_text_size::TextRange;
|
||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||
use ruff_workspace::options::PycodestyleOptions;
|
||||
use ruff_workspace::options::{Options, PycodestyleOptions};
|
||||
use ruff_workspace::resolver::ConfigurationTransformer;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -155,10 +161,20 @@ pub struct CheckCommand {
|
||||
preview: bool,
|
||||
#[clap(long, overrides_with("preview"), hide = true)]
|
||||
no_preview: bool,
|
||||
/// Path to the `pyproject.toml` or `ruff.toml` file to use for
|
||||
/// configuration.
|
||||
#[arg(long, conflicts_with = "isolated")]
|
||||
pub config: Option<PathBuf>,
|
||||
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
|
||||
/// or a TOML `<KEY> = <VALUE>` pair
|
||||
/// (such as you might find in a `ruff.toml` configuration file)
|
||||
/// overriding a specific configuration option.
|
||||
/// Overrides of individual settings using this option always take precedence
|
||||
/// over all configuration files, including configuration files that were also
|
||||
/// specified using `--config`.
|
||||
#[arg(
|
||||
long,
|
||||
action = clap::ArgAction::Append,
|
||||
value_name = "CONFIG_OPTION",
|
||||
value_parser = ConfigArgumentParser,
|
||||
)]
|
||||
pub config: Vec<SingleConfigArgument>,
|
||||
/// Comma-separated list of rule codes to enable (or ALL, to enable all rules).
|
||||
#[arg(
|
||||
long,
|
||||
@@ -291,7 +307,15 @@ pub struct CheckCommand {
|
||||
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||
pub no_cache: bool,
|
||||
/// Ignore all configuration files.
|
||||
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
|
||||
//
|
||||
// Note: We can't mark this as conflicting with `--config` here
|
||||
// as `--config` can be used for specifying configuration overrides
|
||||
// as well as configuration files.
|
||||
// Specifying a configuration file conflicts with `--isolated`;
|
||||
// specifying a configuration override does not.
|
||||
// If a user specifies `ruff check --isolated --config=ruff.toml`,
|
||||
// we emit an error later on, after the initial parsing by clap.
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub isolated: bool,
|
||||
/// Path to the cache directory.
|
||||
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
|
||||
@@ -384,9 +408,20 @@ pub struct FormatCommand {
|
||||
/// difference between the current file and how the formatted file would look like.
|
||||
#[arg(long)]
|
||||
pub diff: bool,
|
||||
/// Path to the `pyproject.toml` or `ruff.toml` file to use for configuration.
|
||||
#[arg(long, conflicts_with = "isolated")]
|
||||
pub config: Option<PathBuf>,
|
||||
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
|
||||
/// or a TOML `<KEY> = <VALUE>` pair
|
||||
/// (such as you might find in a `ruff.toml` configuration file)
|
||||
/// overriding a specific configuration option.
|
||||
/// Overrides of individual settings using this option always take precedence
|
||||
/// over all configuration files, including configuration files that were also
|
||||
/// specified using `--config`.
|
||||
#[arg(
|
||||
long,
|
||||
action = clap::ArgAction::Append,
|
||||
value_name = "CONFIG_OPTION",
|
||||
value_parser = ConfigArgumentParser,
|
||||
)]
|
||||
pub config: Vec<SingleConfigArgument>,
|
||||
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||
@@ -428,7 +463,15 @@ pub struct FormatCommand {
|
||||
#[arg(long, help_heading = "Format configuration")]
|
||||
pub line_length: Option<LineLength>,
|
||||
/// Ignore all configuration files.
|
||||
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
|
||||
//
|
||||
// Note: We can't mark this as conflicting with `--config` here
|
||||
// as `--config` can be used for specifying configuration overrides
|
||||
// as well as configuration files.
|
||||
// Specifying a configuration file conflicts with `--isolated`;
|
||||
// specifying a configuration override does not.
|
||||
// If a user specifies `ruff check --isolated --config=ruff.toml`,
|
||||
// we emit an error later on, after the initial parsing by clap.
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub isolated: bool,
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
@@ -515,101 +558,181 @@ impl From<&LogLevelArgs> for LogLevel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration-related arguments passed via the CLI.
|
||||
#[derive(Default)]
|
||||
pub struct ConfigArguments {
|
||||
/// Path to a pyproject.toml or ruff.toml configuration file (etc.).
|
||||
/// Either 0 or 1 configuration file paths may be provided on the command line.
|
||||
config_file: Option<PathBuf>,
|
||||
/// Overrides provided via the `--config "KEY=VALUE"` option.
|
||||
/// An arbitrary number of these overrides may be provided on the command line.
|
||||
/// These overrides take precedence over all configuration files,
|
||||
/// even configuration files that were also specified using `--config`.
|
||||
overrides: Configuration,
|
||||
/// Overrides provided via dedicated flags such as `--line-length` etc.
|
||||
/// These overrides take precedence over all configuration files,
|
||||
/// and also over all overrides specified using any `--config "KEY=VALUE"` flags.
|
||||
per_flag_overrides: ExplicitConfigOverrides,
|
||||
}
|
||||
|
||||
impl ConfigArguments {
|
||||
pub fn config_file(&self) -> Option<&Path> {
|
||||
self.config_file.as_deref()
|
||||
}
|
||||
|
||||
fn from_cli_arguments(
|
||||
config_options: Vec<SingleConfigArgument>,
|
||||
per_flag_overrides: ExplicitConfigOverrides,
|
||||
isolated: bool,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut new = Self {
|
||||
per_flag_overrides,
|
||||
..Self::default()
|
||||
};
|
||||
|
||||
for option in config_options {
|
||||
match option {
|
||||
SingleConfigArgument::SettingsOverride(overridden_option) => {
|
||||
let overridden_option = Arc::try_unwrap(overridden_option)
|
||||
.unwrap_or_else(|option| option.deref().clone());
|
||||
new.overrides = new.overrides.combine(Configuration::from_options(
|
||||
overridden_option,
|
||||
None,
|
||||
&path_dedot::CWD,
|
||||
)?);
|
||||
}
|
||||
SingleConfigArgument::FilePath(path) => {
|
||||
if isolated {
|
||||
bail!(
|
||||
"\
|
||||
The argument `--config={}` cannot be used with `--isolated`
|
||||
|
||||
tip: You cannot specify a configuration file and also specify `--isolated`,
|
||||
as `--isolated` causes ruff to ignore all configuration files.
|
||||
For more information, try `--help`.
|
||||
",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
if let Some(ref config_file) = new.config_file {
|
||||
let (first, second) = (config_file.display(), path.display());
|
||||
bail!(
|
||||
"\
|
||||
You cannot specify more than one configuration file on the command line.
|
||||
|
||||
tip: remove either `--config={first}` or `--config={second}`.
|
||||
For more information, try `--help`.
|
||||
"
|
||||
);
|
||||
}
|
||||
new.config_file = Some(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(new)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigurationTransformer for ConfigArguments {
|
||||
fn transform(&self, config: Configuration) -> Configuration {
|
||||
let with_config_overrides = self.overrides.clone().combine(config);
|
||||
self.per_flag_overrides.transform(with_config_overrides)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckCommand {
|
||||
/// Partition the CLI into command-line arguments and configuration
|
||||
/// overrides.
|
||||
pub fn partition(self) -> (CheckArguments, CliOverrides) {
|
||||
(
|
||||
CheckArguments {
|
||||
add_noqa: self.add_noqa,
|
||||
config: self.config,
|
||||
diff: self.diff,
|
||||
ecosystem_ci: self.ecosystem_ci,
|
||||
exit_non_zero_on_fix: self.exit_non_zero_on_fix,
|
||||
exit_zero: self.exit_zero,
|
||||
files: self.files,
|
||||
ignore_noqa: self.ignore_noqa,
|
||||
isolated: self.isolated,
|
||||
no_cache: self.no_cache,
|
||||
output_file: self.output_file,
|
||||
show_files: self.show_files,
|
||||
show_settings: self.show_settings,
|
||||
statistics: self.statistics,
|
||||
stdin_filename: self.stdin_filename,
|
||||
watch: self.watch,
|
||||
},
|
||||
CliOverrides {
|
||||
dummy_variable_rgx: self.dummy_variable_rgx,
|
||||
exclude: self.exclude,
|
||||
extend_exclude: self.extend_exclude,
|
||||
extend_fixable: self.extend_fixable,
|
||||
extend_ignore: self.extend_ignore,
|
||||
extend_per_file_ignores: self.extend_per_file_ignores,
|
||||
extend_select: self.extend_select,
|
||||
extend_unfixable: self.extend_unfixable,
|
||||
fixable: self.fixable,
|
||||
ignore: self.ignore,
|
||||
line_length: self.line_length,
|
||||
per_file_ignores: self.per_file_ignores,
|
||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||
respect_gitignore: resolve_bool_arg(
|
||||
self.respect_gitignore,
|
||||
self.no_respect_gitignore,
|
||||
),
|
||||
select: self.select,
|
||||
target_version: self.target_version,
|
||||
unfixable: self.unfixable,
|
||||
// TODO(charlie): Included in `pyproject.toml`, but not inherited.
|
||||
cache_dir: self.cache_dir,
|
||||
fix: resolve_bool_arg(self.fix, self.no_fix),
|
||||
fix_only: resolve_bool_arg(self.fix_only, self.no_fix_only),
|
||||
unsafe_fixes: resolve_bool_arg(self.unsafe_fixes, self.no_unsafe_fixes)
|
||||
.map(UnsafeFixes::from),
|
||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||
output_format: resolve_output_format(
|
||||
self.output_format,
|
||||
resolve_bool_arg(self.show_source, self.no_show_source),
|
||||
resolve_bool_arg(self.preview, self.no_preview).unwrap_or_default(),
|
||||
),
|
||||
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
||||
extension: self.extension,
|
||||
},
|
||||
)
|
||||
pub fn partition(self) -> anyhow::Result<(CheckArguments, ConfigArguments)> {
|
||||
let check_arguments = CheckArguments {
|
||||
add_noqa: self.add_noqa,
|
||||
diff: self.diff,
|
||||
ecosystem_ci: self.ecosystem_ci,
|
||||
exit_non_zero_on_fix: self.exit_non_zero_on_fix,
|
||||
exit_zero: self.exit_zero,
|
||||
files: self.files,
|
||||
ignore_noqa: self.ignore_noqa,
|
||||
isolated: self.isolated,
|
||||
no_cache: self.no_cache,
|
||||
output_file: self.output_file,
|
||||
show_files: self.show_files,
|
||||
show_settings: self.show_settings,
|
||||
statistics: self.statistics,
|
||||
stdin_filename: self.stdin_filename,
|
||||
watch: self.watch,
|
||||
};
|
||||
|
||||
let cli_overrides = ExplicitConfigOverrides {
|
||||
dummy_variable_rgx: self.dummy_variable_rgx,
|
||||
exclude: self.exclude,
|
||||
extend_exclude: self.extend_exclude,
|
||||
extend_fixable: self.extend_fixable,
|
||||
extend_ignore: self.extend_ignore,
|
||||
extend_per_file_ignores: self.extend_per_file_ignores,
|
||||
extend_select: self.extend_select,
|
||||
extend_unfixable: self.extend_unfixable,
|
||||
fixable: self.fixable,
|
||||
ignore: self.ignore,
|
||||
line_length: self.line_length,
|
||||
per_file_ignores: self.per_file_ignores,
|
||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||
respect_gitignore: resolve_bool_arg(self.respect_gitignore, self.no_respect_gitignore),
|
||||
select: self.select,
|
||||
target_version: self.target_version,
|
||||
unfixable: self.unfixable,
|
||||
// TODO(charlie): Included in `pyproject.toml`, but not inherited.
|
||||
cache_dir: self.cache_dir,
|
||||
fix: resolve_bool_arg(self.fix, self.no_fix),
|
||||
fix_only: resolve_bool_arg(self.fix_only, self.no_fix_only),
|
||||
unsafe_fixes: resolve_bool_arg(self.unsafe_fixes, self.no_unsafe_fixes)
|
||||
.map(UnsafeFixes::from),
|
||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||
output_format: resolve_output_format(
|
||||
self.output_format,
|
||||
resolve_bool_arg(self.show_source, self.no_show_source),
|
||||
resolve_bool_arg(self.preview, self.no_preview).unwrap_or_default(),
|
||||
),
|
||||
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
||||
extension: self.extension,
|
||||
};
|
||||
|
||||
let config_args =
|
||||
ConfigArguments::from_cli_arguments(self.config, cli_overrides, self.isolated)?;
|
||||
Ok((check_arguments, config_args))
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatCommand {
|
||||
/// Partition the CLI into command-line arguments and configuration
|
||||
/// overrides.
|
||||
pub fn partition(self) -> (FormatArguments, CliOverrides) {
|
||||
(
|
||||
FormatArguments {
|
||||
check: self.check,
|
||||
diff: self.diff,
|
||||
config: self.config,
|
||||
files: self.files,
|
||||
isolated: self.isolated,
|
||||
no_cache: self.no_cache,
|
||||
stdin_filename: self.stdin_filename,
|
||||
range: self.range,
|
||||
},
|
||||
CliOverrides {
|
||||
line_length: self.line_length,
|
||||
respect_gitignore: resolve_bool_arg(
|
||||
self.respect_gitignore,
|
||||
self.no_respect_gitignore,
|
||||
),
|
||||
exclude: self.exclude,
|
||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||
target_version: self.target_version,
|
||||
cache_dir: self.cache_dir,
|
||||
extension: self.extension,
|
||||
pub fn partition(self) -> anyhow::Result<(FormatArguments, ConfigArguments)> {
|
||||
let format_arguments = FormatArguments {
|
||||
check: self.check,
|
||||
diff: self.diff,
|
||||
files: self.files,
|
||||
isolated: self.isolated,
|
||||
no_cache: self.no_cache,
|
||||
stdin_filename: self.stdin_filename,
|
||||
range: self.range,
|
||||
};
|
||||
|
||||
// Unsupported on the formatter CLI, but required on `Overrides`.
|
||||
..CliOverrides::default()
|
||||
},
|
||||
)
|
||||
let cli_overrides = ExplicitConfigOverrides {
|
||||
line_length: self.line_length,
|
||||
respect_gitignore: resolve_bool_arg(self.respect_gitignore, self.no_respect_gitignore),
|
||||
exclude: self.exclude,
|
||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||
target_version: self.target_version,
|
||||
cache_dir: self.cache_dir,
|
||||
extension: self.extension,
|
||||
|
||||
// Unsupported on the formatter CLI, but required on `Overrides`.
|
||||
..ExplicitConfigOverrides::default()
|
||||
};
|
||||
|
||||
let config_args =
|
||||
ConfigArguments::from_cli_arguments(self.config, cli_overrides, self.isolated)?;
|
||||
Ok((format_arguments, config_args))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,6 +745,154 @@ fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TomlParseFailureKind {
|
||||
SyntaxError,
|
||||
UnknownOption,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TomlParseFailureKind {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let display = match self {
|
||||
Self::SyntaxError => "The supplied argument is not valid TOML",
|
||||
Self::UnknownOption => {
|
||||
"Could not parse the supplied argument as a `ruff.toml` configuration option"
|
||||
}
|
||||
};
|
||||
write!(f, "{display}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TomlParseFailure {
|
||||
kind: TomlParseFailureKind,
|
||||
underlying_error: toml::de::Error,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TomlParseFailure {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let TomlParseFailure {
|
||||
kind,
|
||||
underlying_error,
|
||||
} = self;
|
||||
let display = format!("{kind}:\n\n{underlying_error}");
|
||||
write!(f, "{}", display.trim_end())
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumeration to represent a single `--config` argument
|
||||
/// passed via the CLI.
|
||||
///
|
||||
/// Using the `--config` flag, users may pass 0 or 1 paths
|
||||
/// to configuration files and an arbitrary number of
|
||||
/// "inline TOML" overrides for specific settings.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```sh
|
||||
/// ruff check --config "path/to/ruff.toml" --config "extend-select=['E501', 'F841']" --config "lint.per-file-ignores = {'some_file.py' = ['F841']}"
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SingleConfigArgument {
|
||||
FilePath(PathBuf),
|
||||
SettingsOverride(Arc<Options>),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigArgumentParser;
|
||||
|
||||
impl ValueParserFactory for SingleConfigArgument {
|
||||
type Parser = ConfigArgumentParser;
|
||||
|
||||
fn value_parser() -> Self::Parser {
|
||||
ConfigArgumentParser
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedValueParser for ConfigArgumentParser {
|
||||
type Value = SingleConfigArgument;
|
||||
|
||||
fn parse_ref(
|
||||
&self,
|
||||
cmd: &clap::Command,
|
||||
arg: Option<&clap::Arg>,
|
||||
value: &std::ffi::OsStr,
|
||||
) -> Result<Self::Value, clap::Error> {
|
||||
let path_to_config_file = PathBuf::from(value);
|
||||
if path_to_config_file.exists() {
|
||||
return Ok(SingleConfigArgument::FilePath(path_to_config_file));
|
||||
}
|
||||
|
||||
let value = value
|
||||
.to_str()
|
||||
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
|
||||
|
||||
let toml_parse_error = match toml::Table::from_str(value) {
|
||||
Ok(table) => match table.try_into() {
|
||||
Ok(option) => return Ok(SingleConfigArgument::SettingsOverride(Arc::new(option))),
|
||||
Err(underlying_error) => TomlParseFailure {
|
||||
kind: TomlParseFailureKind::UnknownOption,
|
||||
underlying_error,
|
||||
},
|
||||
},
|
||||
Err(underlying_error) => TomlParseFailure {
|
||||
kind: TomlParseFailureKind::SyntaxError,
|
||||
underlying_error,
|
||||
},
|
||||
};
|
||||
|
||||
let mut new_error = clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
|
||||
if let Some(arg) = arg {
|
||||
new_error.insert(
|
||||
clap::error::ContextKind::InvalidArg,
|
||||
clap::error::ContextValue::String(arg.to_string()),
|
||||
);
|
||||
}
|
||||
new_error.insert(
|
||||
clap::error::ContextKind::InvalidValue,
|
||||
clap::error::ContextValue::String(value.to_string()),
|
||||
);
|
||||
|
||||
// small hack so that multiline tips
|
||||
// have the same indent on the left-hand side:
|
||||
let tip_indent = " ".repeat(" tip: ".len());
|
||||
|
||||
let mut tip = format!(
|
||||
"\
|
||||
A `--config` flag must either be a path to a `.toml` configuration file
|
||||
{tip_indent}or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
{tip_indent}option"
|
||||
);
|
||||
|
||||
// Here we do some heuristics to try to figure out whether
|
||||
// the user was trying to pass in a path to a configuration file
|
||||
// or some inline TOML.
|
||||
// We want to display the most helpful error to the user as possible.
|
||||
if std::path::Path::new(value)
|
||||
.extension()
|
||||
.map_or(false, |ext| ext.eq_ignore_ascii_case("toml"))
|
||||
{
|
||||
if !value.contains('=') {
|
||||
tip.push_str(&format!(
|
||||
"
|
||||
|
||||
It looks like you were trying to pass a path to a configuration file.
|
||||
The path `{value}` does not exist"
|
||||
));
|
||||
}
|
||||
} else if value.contains('=') {
|
||||
tip.push_str(&format!("\n\n{toml_parse_error}"));
|
||||
}
|
||||
|
||||
new_error.insert(
|
||||
clap::error::ContextKind::Suggested,
|
||||
clap::error::ContextValue::StyledStrs(vec![tip.into()]),
|
||||
);
|
||||
|
||||
Err(new_error)
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_output_format(
|
||||
output_format: Option<SerializationFormat>,
|
||||
show_sources: Option<bool>,
|
||||
@@ -664,7 +935,6 @@ fn resolve_output_format(
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct CheckArguments {
|
||||
pub add_noqa: bool,
|
||||
pub config: Option<PathBuf>,
|
||||
pub diff: bool,
|
||||
pub ecosystem_ci: bool,
|
||||
pub exit_non_zero_on_fix: bool,
|
||||
@@ -688,7 +958,6 @@ pub struct FormatArguments {
|
||||
pub check: bool,
|
||||
pub no_cache: bool,
|
||||
pub diff: bool,
|
||||
pub config: Option<PathBuf>,
|
||||
pub files: Vec<PathBuf>,
|
||||
pub isolated: bool,
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
@@ -884,39 +1153,40 @@ impl LineColumnParseError {
|
||||
}
|
||||
}
|
||||
|
||||
/// CLI settings that function as configuration overrides.
|
||||
/// Configuration overrides provided via dedicated CLI flags:
|
||||
/// `--line-length`, `--respect-gitignore`, etc.
|
||||
#[derive(Clone, Default)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct CliOverrides {
|
||||
pub dummy_variable_rgx: Option<Regex>,
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
pub extend_exclude: Option<Vec<FilePattern>>,
|
||||
pub extend_fixable: Option<Vec<RuleSelector>>,
|
||||
pub extend_ignore: Option<Vec<RuleSelector>>,
|
||||
pub extend_select: Option<Vec<RuleSelector>>,
|
||||
pub extend_unfixable: Option<Vec<RuleSelector>>,
|
||||
pub fixable: Option<Vec<RuleSelector>>,
|
||||
pub ignore: Option<Vec<RuleSelector>>,
|
||||
pub line_length: Option<LineLength>,
|
||||
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
pub extend_per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
pub preview: Option<PreviewMode>,
|
||||
pub respect_gitignore: Option<bool>,
|
||||
pub select: Option<Vec<RuleSelector>>,
|
||||
pub target_version: Option<PythonVersion>,
|
||||
pub unfixable: Option<Vec<RuleSelector>>,
|
||||
struct ExplicitConfigOverrides {
|
||||
dummy_variable_rgx: Option<Regex>,
|
||||
exclude: Option<Vec<FilePattern>>,
|
||||
extend_exclude: Option<Vec<FilePattern>>,
|
||||
extend_fixable: Option<Vec<RuleSelector>>,
|
||||
extend_ignore: Option<Vec<RuleSelector>>,
|
||||
extend_select: Option<Vec<RuleSelector>>,
|
||||
extend_unfixable: Option<Vec<RuleSelector>>,
|
||||
fixable: Option<Vec<RuleSelector>>,
|
||||
ignore: Option<Vec<RuleSelector>>,
|
||||
line_length: Option<LineLength>,
|
||||
per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
extend_per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
preview: Option<PreviewMode>,
|
||||
respect_gitignore: Option<bool>,
|
||||
select: Option<Vec<RuleSelector>>,
|
||||
target_version: Option<PythonVersion>,
|
||||
unfixable: Option<Vec<RuleSelector>>,
|
||||
// TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`.
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
pub fix: Option<bool>,
|
||||
pub fix_only: Option<bool>,
|
||||
pub unsafe_fixes: Option<UnsafeFixes>,
|
||||
pub force_exclude: Option<bool>,
|
||||
pub output_format: Option<SerializationFormat>,
|
||||
pub show_fixes: Option<bool>,
|
||||
pub extension: Option<Vec<ExtensionPair>>,
|
||||
cache_dir: Option<PathBuf>,
|
||||
fix: Option<bool>,
|
||||
fix_only: Option<bool>,
|
||||
unsafe_fixes: Option<UnsafeFixes>,
|
||||
force_exclude: Option<bool>,
|
||||
output_format: Option<SerializationFormat>,
|
||||
show_fixes: Option<bool>,
|
||||
extension: Option<Vec<ExtensionPair>>,
|
||||
}
|
||||
|
||||
impl ConfigurationTransformer for CliOverrides {
|
||||
impl ConfigurationTransformer for ExplicitConfigOverrides {
|
||||
fn transform(&self, mut config: Configuration) -> Configuration {
|
||||
if let Some(cache_dir) = &self.cache_dir {
|
||||
config.cache_dir = Some(cache_dir.clone());
|
||||
|
||||
@@ -12,17 +12,17 @@ use ruff_linter::warn_user_once;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
|
||||
/// Add `noqa` directives to a collection of files.
|
||||
pub(crate) fn add_noqa(
|
||||
files: &[PathBuf],
|
||||
pyproject_config: &PyprojectConfig,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
) -> Result<usize> {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ use ruff_workspace::resolver::{
|
||||
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile,
|
||||
};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
use crate::cache::{Cache, PackageCacheMap, PackageCaches};
|
||||
use crate::diagnostics::Diagnostics;
|
||||
use crate::panic::catch_unwind;
|
||||
@@ -34,7 +34,7 @@ use crate::panic::catch_unwind;
|
||||
pub(crate) fn check(
|
||||
files: &[PathBuf],
|
||||
pyproject_config: &PyprojectConfig,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
cache: flags::Cache,
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
@@ -42,7 +42,7 @@ pub(crate) fn check(
|
||||
) -> Result<Diagnostics> {
|
||||
// Collect all the Python files to check.
|
||||
let start = Instant::now();
|
||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
|
||||
debug!("Identified files to lint in: {:?}", start.elapsed());
|
||||
|
||||
if paths.is_empty() {
|
||||
@@ -233,7 +233,7 @@ mod test {
|
||||
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy};
|
||||
use ruff_workspace::Settings;
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
|
||||
use super::check;
|
||||
|
||||
@@ -272,7 +272,7 @@ mod test {
|
||||
// Notebooks are not included by default
|
||||
&[tempdir.path().to_path_buf(), notebook],
|
||||
&pyproject_config,
|
||||
&CliOverrides::default(),
|
||||
&ConfigArguments::default(),
|
||||
flags::Cache::Disabled,
|
||||
flags::Noqa::Disabled,
|
||||
flags::FixMode::Generate,
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_linter::packaging;
|
||||
use ruff_linter::settings::flags;
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig, Resolver};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
use crate::diagnostics::{lint_stdin, Diagnostics};
|
||||
use crate::stdin::{parrot_stdin, read_from_stdin};
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::stdin::{parrot_stdin, read_from_stdin};
|
||||
pub(crate) fn check_stdin(
|
||||
filename: Option<&Path>,
|
||||
pyproject_config: &PyprojectConfig,
|
||||
overrides: &CliOverrides,
|
||||
overrides: &ConfigArguments,
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
|
||||
@@ -29,7 +29,7 @@ use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile, Resolver};
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments, FormatRange};
|
||||
use crate::args::{ConfigArguments, FormatArguments, FormatRange};
|
||||
use crate::cache::{Cache, FileCacheKey, PackageCacheMap, PackageCaches};
|
||||
use crate::panic::{catch_unwind, PanicError};
|
||||
use crate::resolve::resolve;
|
||||
@@ -60,18 +60,17 @@ impl FormatMode {
|
||||
/// Format a set of files, and return the exit status.
|
||||
pub(crate) fn format(
|
||||
cli: FormatArguments,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
log_level: LogLevel,
|
||||
) -> Result<ExitStatus> {
|
||||
let pyproject_config = resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
overrides,
|
||||
config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
let mode = FormatMode::from_cli(&cli);
|
||||
let files = resolve_default_files(cli.files, false);
|
||||
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, overrides)?;
|
||||
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, config_arguments)?;
|
||||
|
||||
if paths.is_empty() {
|
||||
warn_user_once!("No Python files found under the given path(s)");
|
||||
|
||||
@@ -9,7 +9,7 @@ use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver};
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments, FormatRange};
|
||||
use crate::args::{ConfigArguments, FormatArguments, FormatRange};
|
||||
use crate::commands::format::{
|
||||
format_source, warn_incompatible_formatter_settings, FormatCommandError, FormatMode,
|
||||
FormatResult, FormattedSource,
|
||||
@@ -19,11 +19,13 @@ use crate::stdin::{parrot_stdin, read_from_stdin};
|
||||
use crate::ExitStatus;
|
||||
|
||||
/// Run the formatter over a single file, read from `stdin`.
|
||||
pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> Result<ExitStatus> {
|
||||
pub(crate) fn format_stdin(
|
||||
cli: &FormatArguments,
|
||||
config_arguments: &ConfigArguments,
|
||||
) -> Result<ExitStatus> {
|
||||
let pyproject_config = resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
overrides,
|
||||
config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
|
||||
@@ -34,7 +36,7 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
|
||||
if resolver.force_exclude() {
|
||||
if let Some(filename) = cli.stdin_filename.as_deref() {
|
||||
if !python_file_at_path(filename, &mut resolver, overrides)? {
|
||||
if !python_file_at_path(filename, &mut resolver, config_arguments)? {
|
||||
if mode.is_write() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
|
||||
@@ -7,17 +7,17 @@ use itertools::Itertools;
|
||||
use ruff_linter::warn_user_once;
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
|
||||
/// Show the list of files to be checked based on current settings.
|
||||
pub(crate) fn show_files(
|
||||
files: &[PathBuf],
|
||||
pyproject_config: &PyprojectConfig,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
writer: &mut impl Write,
|
||||
) -> Result<()> {
|
||||
// Collect all files in the hierarchy.
|
||||
let (paths, _resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||
let (paths, _resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
|
||||
|
||||
if paths.is_empty() {
|
||||
warn_user_once!("No Python files found under the given path(s)");
|
||||
|
||||
@@ -6,17 +6,17 @@ use itertools::Itertools;
|
||||
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
|
||||
/// Print the user-facing configuration settings.
|
||||
pub(crate) fn show_settings(
|
||||
files: &[PathBuf],
|
||||
pyproject_config: &PyprojectConfig,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
writer: &mut impl Write,
|
||||
) -> Result<()> {
|
||||
// Collect all files in the hierarchy.
|
||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
|
||||
|
||||
// Print the list of files.
|
||||
let Some(path) = paths
|
||||
|
||||
@@ -204,24 +204,23 @@ pub fn run(
|
||||
}
|
||||
|
||||
fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let (cli, overrides) = args.partition();
|
||||
let (cli, config_arguments) = args.partition()?;
|
||||
|
||||
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
|
||||
commands::format_stdin::format_stdin(&cli, &overrides)
|
||||
commands::format_stdin::format_stdin(&cli, &config_arguments)
|
||||
} else {
|
||||
commands::format::format(cli, &overrides, log_level)
|
||||
commands::format::format(cli, &config_arguments, log_level)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let (cli, overrides) = args.partition();
|
||||
let (cli, config_arguments) = args.partition()?;
|
||||
|
||||
// Construct the "default" settings. These are used when no `pyproject.toml`
|
||||
// files are present, or files are injected from outside of the hierarchy.
|
||||
let pyproject_config = resolve::resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
&overrides,
|
||||
&config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
|
||||
@@ -239,11 +238,21 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let files = resolve_default_files(cli.files, is_stdin);
|
||||
|
||||
if cli.show_settings {
|
||||
commands::show_settings::show_settings(&files, &pyproject_config, &overrides, &mut writer)?;
|
||||
commands::show_settings::show_settings(
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&config_arguments,
|
||||
&mut writer,
|
||||
)?;
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
if cli.show_files {
|
||||
commands::show_files::show_files(&files, &pyproject_config, &overrides, &mut writer)?;
|
||||
commands::show_files::show_files(
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&config_arguments,
|
||||
&mut writer,
|
||||
)?;
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
@@ -302,7 +311,8 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
if !fix_mode.is_generate() {
|
||||
warn_user!("--fix is incompatible with --add-noqa.");
|
||||
}
|
||||
let modifications = commands::add_noqa::add_noqa(&files, &pyproject_config, &overrides)?;
|
||||
let modifications =
|
||||
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
|
||||
if modifications > 0 && log_level >= LogLevel::Default {
|
||||
let s = if modifications == 1 { "" } else { "s" };
|
||||
#[allow(clippy::print_stderr)]
|
||||
@@ -352,7 +362,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let messages = commands::check::check(
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
&config_arguments,
|
||||
cache.into(),
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
@@ -374,8 +384,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
if matches!(change_kind, ChangeKind::Configuration) {
|
||||
pyproject_config = resolve::resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
&overrides,
|
||||
&config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
}
|
||||
@@ -385,7 +394,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let messages = commands::check::check(
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
&config_arguments,
|
||||
cache.into(),
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
@@ -402,7 +411,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
commands::check_stdin::check_stdin(
|
||||
cli.stdin_filename.map(fs::normalize_path).as_deref(),
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
&config_arguments,
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
)?
|
||||
@@ -410,7 +419,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
commands::check::check(
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
&config_arguments,
|
||||
cache.into(),
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
|
||||
@@ -11,19 +11,18 @@ use ruff_workspace::resolver::{
|
||||
Relativity,
|
||||
};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
|
||||
/// Resolve the relevant settings strategy and defaults for the current
|
||||
/// invocation.
|
||||
pub fn resolve(
|
||||
isolated: bool,
|
||||
config: Option<&Path>,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
stdin_filename: Option<&Path>,
|
||||
) -> Result<PyprojectConfig> {
|
||||
// First priority: if we're running in isolated mode, use the default settings.
|
||||
if isolated {
|
||||
let config = overrides.transform(Configuration::default());
|
||||
let config = config_arguments.transform(Configuration::default());
|
||||
let settings = config.into_settings(&path_dedot::CWD)?;
|
||||
debug!("Isolated mode, not reading any pyproject.toml");
|
||||
return Ok(PyprojectConfig::new(
|
||||
@@ -36,12 +35,13 @@ pub fn resolve(
|
||||
// Second priority: the user specified a `pyproject.toml` file. Use that
|
||||
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
|
||||
// current working directory. (This matches ESLint's behavior.)
|
||||
if let Some(pyproject) = config
|
||||
if let Some(pyproject) = config_arguments
|
||||
.config_file()
|
||||
.map(|config| config.display().to_string())
|
||||
.map(|config| shellexpand::full(&config).map(|config| PathBuf::from(config.as_ref())))
|
||||
.transpose()?
|
||||
{
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, overrides)?;
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, config_arguments)?;
|
||||
debug!(
|
||||
"Using user-specified configuration file at: {}",
|
||||
pyproject.display()
|
||||
@@ -67,7 +67,7 @@ pub fn resolve(
|
||||
"Using configuration file (via parent) at: {}",
|
||||
pyproject.display()
|
||||
);
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Parent, overrides)?;
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Parent, config_arguments)?;
|
||||
return Ok(PyprojectConfig::new(
|
||||
PyprojectDiscoveryStrategy::Hierarchical,
|
||||
settings,
|
||||
@@ -84,7 +84,7 @@ pub fn resolve(
|
||||
"Using configuration file (via cwd) at: {}",
|
||||
pyproject.display()
|
||||
);
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, overrides)?;
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, config_arguments)?;
|
||||
return Ok(PyprojectConfig::new(
|
||||
PyprojectDiscoveryStrategy::Hierarchical,
|
||||
settings,
|
||||
@@ -97,7 +97,7 @@ pub fn resolve(
|
||||
// "closest" `pyproject.toml` file for every Python file later on, so these act
|
||||
// as the "default" settings.)
|
||||
debug!("Using Ruff default settings");
|
||||
let config = overrides.transform(Configuration::default());
|
||||
let config = config_arguments.transform(Configuration::default());
|
||||
let settings = config.into_settings(&path_dedot::CWD)?;
|
||||
Ok(PyprojectConfig::new(
|
||||
PyprojectDiscoveryStrategy::Hierarchical,
|
||||
|
||||
@@ -90,6 +90,179 @@ fn format_warn_stdin_filename_with_files() {
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonexistent_config_file() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--config", "foo.toml", "."]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'foo.toml' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
It looks like you were trying to pass a path to a configuration file.
|
||||
The path `foo.toml` does not exist
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_override_rejected_if_invalid_toml() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--config", "foo = bar", "."]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'foo = bar' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
The supplied argument is not valid TOML:
|
||||
|
||||
TOML parse error at line 1, column 7
|
||||
|
|
||||
1 | foo = bar
|
||||
| ^
|
||||
invalid string
|
||||
expected `"`, `'`
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_many_config_files() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
||||
let ruff2_dot_toml = tempdir.path().join("ruff2.toml");
|
||||
fs::File::create(&ruff_dot_toml)?;
|
||||
fs::File::create(&ruff2_dot_toml)?;
|
||||
let expected_stderr = format!(
|
||||
"\
|
||||
ruff failed
|
||||
Cause: You cannot specify more than one configuration file on the command line.
|
||||
|
||||
tip: remove either `--config={}` or `--config={}`.
|
||||
For more information, try `--help`.
|
||||
|
||||
",
|
||||
ruff_dot_toml.display(),
|
||||
ruff2_dot_toml.display(),
|
||||
);
|
||||
let cmd = Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("format")
|
||||
.arg("--config")
|
||||
.arg(&ruff_dot_toml)
|
||||
.arg("--config")
|
||||
.arg(&ruff2_dot_toml)
|
||||
.arg(".")
|
||||
.output()?;
|
||||
let stderr = std::str::from_utf8(&cmd.stderr)?;
|
||||
assert_eq!(stderr, expected_stderr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_file_and_isolated() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
||||
fs::File::create(&ruff_dot_toml)?;
|
||||
let expected_stderr = format!(
|
||||
"\
|
||||
ruff failed
|
||||
Cause: The argument `--config={}` cannot be used with `--isolated`
|
||||
|
||||
tip: You cannot specify a configuration file and also specify `--isolated`,
|
||||
as `--isolated` causes ruff to ignore all configuration files.
|
||||
For more information, try `--help`.
|
||||
|
||||
",
|
||||
ruff_dot_toml.display(),
|
||||
);
|
||||
let cmd = Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("format")
|
||||
.arg("--config")
|
||||
.arg(&ruff_dot_toml)
|
||||
.arg("--isolated")
|
||||
.arg(".")
|
||||
.output()?;
|
||||
let stderr = std::str::from_utf8(&cmd.stderr)?;
|
||||
assert_eq!(stderr, expected_stderr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_override_via_cli() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(&ruff_toml, "line-length = 100")?;
|
||||
let fixture = r#"
|
||||
def foo():
|
||||
print("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string")
|
||||
|
||||
"#;
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("format")
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
// This overrides the long line length set in the config file
|
||||
.args(["--config", "line-length=80"])
|
||||
.arg("-")
|
||||
.pass_stdin(fixture), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def foo():
|
||||
print(
|
||||
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string"
|
||||
)
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_doubly_overridden_via_cli() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(&ruff_toml, "line-length = 70")?;
|
||||
let fixture = r#"
|
||||
def foo():
|
||||
print("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string")
|
||||
|
||||
"#;
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("format")
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
// This overrides the long line length set in the config file...
|
||||
.args(["--config", "line-length=80"])
|
||||
// ...but this overrides them both:
|
||||
.args(["--line-length", "100"])
|
||||
.arg("-")
|
||||
.pass_stdin(fixture), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def foo():
|
||||
print("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string")
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
@@ -510,6 +510,341 @@ ignore = ["D203", "D212"]
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonexistent_config_file() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", "foo.toml", "."]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'foo.toml' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
It looks like you were trying to pass a path to a configuration file.
|
||||
The path `foo.toml` does not exist
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_override_rejected_if_invalid_toml() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", "foo = bar", "."]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'foo = bar' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
The supplied argument is not valid TOML:
|
||||
|
||||
TOML parse error at line 1, column 7
|
||||
|
|
||||
1 | foo = bar
|
||||
| ^
|
||||
invalid string
|
||||
expected `"`, `'`
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_many_config_files() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
||||
let ruff2_dot_toml = tempdir.path().join("ruff2.toml");
|
||||
fs::File::create(&ruff_dot_toml)?;
|
||||
fs::File::create(&ruff2_dot_toml)?;
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_dot_toml)
|
||||
.arg("--config")
|
||||
.arg(&ruff2_dot_toml)
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: You cannot specify more than one configuration file on the command line.
|
||||
|
||||
tip: remove either `--config=[TMP]/ruff.toml` or `--config=[TMP]/ruff2.toml`.
|
||||
For more information, try `--help`.
|
||||
|
||||
"###);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_file_and_isolated() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
||||
fs::File::create(&ruff_dot_toml)?;
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_dot_toml)
|
||||
.arg("--isolated")
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: The argument `--config=[TMP]/ruff.toml` cannot be used with `--isolated`
|
||||
|
||||
tip: You cannot specify a configuration file and also specify `--isolated`,
|
||||
as `--isolated` causes ruff to ignore all configuration files.
|
||||
For more information, try `--help`.
|
||||
|
||||
"###);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_override_via_cli() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
line-length = 100
|
||||
|
||||
[lint]
|
||||
select = ["I"]
|
||||
|
||||
[lint.isort]
|
||||
combine-as-imports = true
|
||||
"#,
|
||||
)?;
|
||||
let fixture = r#"
|
||||
from foo import (
|
||||
aaaaaaaaaaaaaaaaaaa,
|
||||
bbbbbbbbbbb as bbbbbbbbbbbbbbbb,
|
||||
cccccccccccccccc,
|
||||
ddddddddddd as ddddddddddddd,
|
||||
eeeeeeeeeeeeeee,
|
||||
ffffffffffff as ffffffffffffff,
|
||||
ggggggggggggg,
|
||||
hhhhhhh as hhhhhhhhhhh,
|
||||
iiiiiiiiiiiiii,
|
||||
jjjjjjjjjjjjj as jjjjjj,
|
||||
)
|
||||
|
||||
x = "longer_than_90_charactersssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss"
|
||||
"#;
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.args(["--config", "line-length=90"])
|
||||
.args(["--config", "lint.extend-select=['E501', 'F841']"])
|
||||
.args(["--config", "lint.isort.combine-as-imports = false"])
|
||||
.arg("-")
|
||||
.pass_stdin(fixture), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:2:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
-:15:91: E501 Line too long (97 > 90)
|
||||
Found 2 errors.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_toml_but_nonexistent_option_provided_via_config_argument() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args([".", "--config", "extend-select=['F481']"]), // No such code as F481!
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'extend-select=['F481']' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
Could not parse the supplied argument as a `ruff.toml` configuration option:
|
||||
|
||||
Unknown rule selector: `F481`
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_toml_option_requires_a_new_flag_1() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
// commas can't be used to delimit different config overrides;
|
||||
// you need a new --config flag for each override
|
||||
.args([".", "--config", "extend-select=['F841'], line-length=90"]),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'extend-select=['F841'], line-length=90' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
The supplied argument is not valid TOML:
|
||||
|
||||
TOML parse error at line 1, column 23
|
||||
|
|
||||
1 | extend-select=['F841'], line-length=90
|
||||
| ^
|
||||
expected newline, `#`
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_toml_option_requires_a_new_flag_2() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
// spaces *also* can't be used to delimit different config overrides;
|
||||
// you need a new --config flag for each override
|
||||
.args([".", "--config", "extend-select=['F841'] line-length=90"]),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'extend-select=['F841'] line-length=90' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
The supplied argument is not valid TOML:
|
||||
|
||||
TOML parse error at line 1, column 24
|
||||
|
|
||||
1 | extend-select=['F841'] line-length=90
|
||||
| ^
|
||||
expected newline, `#`
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_doubly_overridden_via_cli() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
line-length = 100
|
||||
|
||||
[lint]
|
||||
select=["E501"]
|
||||
"#,
|
||||
)?;
|
||||
let fixture = "x = 'longer_than_90_charactersssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss'";
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
// The --line-length flag takes priority over both the config file
|
||||
// and the `--config="line-length=110"` flag,
|
||||
// despite them both being specified after this flag on the command line:
|
||||
.args(["--line-length", "90"])
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.args(["--config", "line-length=110"])
|
||||
.arg("-")
|
||||
.pass_stdin(fixture), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:91: E501 Line too long (97 > 90)
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex_config_setting_overridden_via_cli() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(&ruff_toml, "lint.select = ['N801']")?;
|
||||
let fixture = "class violates_n801: pass";
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.args(["--config", "lint.per-file-ignores = {'generated.py' = ['N801']}"])
|
||||
.args(["--stdin-filename", "generated.py"])
|
||||
.arg("-")
|
||||
.pass_stdin(fixture), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deprecated_config_option_overridden_via_cli() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", "select=['N801']", "-"])
|
||||
.pass_stdin("class lowercase: ..."),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:7: N801 Class name `lowercase` should use CapWords convention
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your `--config` CLI arguments:
|
||||
- 'select' -> 'lint.select'
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
@@ -27,7 +27,7 @@ use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use ruff::args::{CliOverrides, FormatArguments, FormatCommand, LogLevelArgs};
|
||||
use ruff::args::{ConfigArguments, FormatArguments, FormatCommand, LogLevelArgs};
|
||||
use ruff::resolve::resolve;
|
||||
use ruff_formatter::{FormatError, LineWidth, PrintError};
|
||||
use ruff_linter::logging::LogLevel;
|
||||
@@ -38,24 +38,23 @@ use ruff_python_formatter::{
|
||||
use ruff_python_parser::ParseError;
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
|
||||
|
||||
fn parse_cli(dirs: &[PathBuf]) -> anyhow::Result<(FormatArguments, CliOverrides)> {
|
||||
fn parse_cli(dirs: &[PathBuf]) -> anyhow::Result<(FormatArguments, ConfigArguments)> {
|
||||
let args_matches = FormatCommand::command()
|
||||
.no_binary_name(true)
|
||||
.get_matches_from(dirs);
|
||||
let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?;
|
||||
let (cli, overrides) = arguments.partition();
|
||||
Ok((cli, overrides))
|
||||
let (cli, config_arguments) = arguments.partition()?;
|
||||
Ok((cli, config_arguments))
|
||||
}
|
||||
|
||||
/// Find the [`PyprojectConfig`] to use for formatting.
|
||||
fn find_pyproject_config(
|
||||
cli: &FormatArguments,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
) -> anyhow::Result<PyprojectConfig> {
|
||||
let mut pyproject_config = resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
overrides,
|
||||
config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
// We don't want to format pyproject.toml
|
||||
@@ -72,9 +71,9 @@ fn find_pyproject_config(
|
||||
fn ruff_check_paths<'a>(
|
||||
pyproject_config: &'a PyprojectConfig,
|
||||
cli: &FormatArguments,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
) -> anyhow::Result<(Vec<Result<ResolvedFile, ignore::Error>>, Resolver<'a>)> {
|
||||
let (paths, resolver) = python_files_in_path(&cli.files, pyproject_config, overrides)?;
|
||||
let (paths, resolver) = python_files_in_path(&cli.files, pyproject_config, config_arguments)?;
|
||||
Ok((paths, resolver))
|
||||
}
|
||||
|
||||
|
||||
@@ -250,6 +250,23 @@ __all__ = (
|
||||
,
|
||||
)
|
||||
|
||||
__all__ = ( # comment about the opening paren
|
||||
# multiline strange comment 0a
|
||||
# multiline strange comment 0b
|
||||
"foo" # inline comment about foo
|
||||
# multiline strange comment 1a
|
||||
# multiline strange comment 1b
|
||||
, # comment about the comma??
|
||||
# comment about bar part a
|
||||
# comment about bar part b
|
||||
"bar" # inline comment about bar
|
||||
# strange multiline comment comment 2a
|
||||
# strange multiline comment 2b
|
||||
,
|
||||
# strange multiline comment 3a
|
||||
# strange multiline comment 3b
|
||||
) # comment about the closing paren
|
||||
|
||||
###################################
|
||||
# These should all not get flagged:
|
||||
###################################
|
||||
|
||||
@@ -188,6 +188,10 @@ class BezierBuilder4:
|
||||
,
|
||||
)
|
||||
|
||||
__slots__ = {"foo", "bar",
|
||||
"baz", "bingo"
|
||||
}
|
||||
|
||||
###################################
|
||||
# These should all not get flagged:
|
||||
###################################
|
||||
|
||||
@@ -343,15 +343,16 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
Expr::Call(
|
||||
call @ ast::ExprCall {
|
||||
func,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
},
|
||||
arguments,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
let Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
} = &**arguments;
|
||||
|
||||
if checker.any_enabled(&[
|
||||
// pylint
|
||||
Rule::BadStringFormatCharacter,
|
||||
|
||||
@@ -904,7 +904,7 @@ where
|
||||
range: _,
|
||||
}) => {
|
||||
if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() {
|
||||
if id == "locals" && ctx.is_load() {
|
||||
if &**id == "locals" && ctx.is_load() {
|
||||
let scope = self.semantic.current_scope_mut();
|
||||
scope.set_uses_locals();
|
||||
}
|
||||
@@ -1073,7 +1073,7 @@ where
|
||||
range: _,
|
||||
} = keyword;
|
||||
if let Some(id) = arg {
|
||||
if id.as_str() == "bound" {
|
||||
if &**id == "bound" {
|
||||
self.visit_type_definition(value);
|
||||
} else {
|
||||
self.visit_non_type_definition(value);
|
||||
@@ -1117,7 +1117,7 @@ where
|
||||
match (arg.as_ref(), value) {
|
||||
// Ex) NamedTuple("a", **{"a": int})
|
||||
(None, Expr::Dict(ast::ExprDict { keys, values, .. })) => {
|
||||
for (key, value) in keys.iter().zip(values) {
|
||||
for (key, value) in keys.iter().zip(values.iter()) {
|
||||
if let Some(key) = key.as_ref() {
|
||||
self.visit_non_type_definition(key);
|
||||
self.visit_type_definition(value);
|
||||
@@ -1153,7 +1153,7 @@ where
|
||||
for key in keys.iter().flatten() {
|
||||
self.visit_non_type_definition(key);
|
||||
}
|
||||
for value in values {
|
||||
for value in values.iter() {
|
||||
self.visit_type_definition(value);
|
||||
}
|
||||
} else {
|
||||
@@ -1755,21 +1755,21 @@ impl<'a> Checker<'a> {
|
||||
&& match parent {
|
||||
Stmt::Assign(ast::StmtAssign { targets, .. }) => {
|
||||
if let Some(Expr::Name(ast::ExprName { id, .. })) = targets.first() {
|
||||
id == "__all__"
|
||||
&**id == "__all__"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() {
|
||||
id == "__all__"
|
||||
&**id == "__all__"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() {
|
||||
id == "__all__"
|
||||
&**id == "__all__"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ fn extract_import_map(path: &Path, package: Option<&Path>, blocks: &[&Block]) ->
|
||||
}) => {
|
||||
let level = level.unwrap_or_default() as usize;
|
||||
let module = if let Some(module) = module {
|
||||
let module: &String = module.as_ref();
|
||||
let module: &str = module.as_str();
|
||||
if level == 0 {
|
||||
Cow::Borrowed(module)
|
||||
} else {
|
||||
|
||||
@@ -81,7 +81,7 @@ pub(crate) fn variable_name_task_id(
|
||||
let ast::ExprStringLiteral { value: task_id, .. } = keyword.value.as_string_literal_expr()?;
|
||||
|
||||
// If the target name is the same as the task_id, no violation.
|
||||
if task_id == id {
|
||||
if task_id == &**id {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ impl AutoPythonType {
|
||||
)
|
||||
.ok()?;
|
||||
let expr = Expr::Name(ast::ExprName {
|
||||
id: binding,
|
||||
id: binding.into_boxed_str(),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
});
|
||||
|
||||
@@ -40,7 +40,9 @@ impl Violation for HardcodedBindAllInterfaces {
|
||||
pub(crate) fn hardcoded_bind_all_interfaces(checker: &mut Checker, string: StringLike) {
|
||||
let is_bind_all_interface = match string {
|
||||
StringLike::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "0.0.0.0",
|
||||
StringLike::FStringLiteral(ast::FStringLiteralElement { value, .. }) => value == "0.0.0.0",
|
||||
StringLike::FStringLiteral(ast::FStringLiteralElement { value, .. }) => {
|
||||
&**value == "0.0.0.0"
|
||||
}
|
||||
StringLike::BytesLiteral(_) => return,
|
||||
};
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ impl Violation for HardcodedPasswordString {
|
||||
fn password_target(target: &Expr) -> Option<&str> {
|
||||
let target_name = match target {
|
||||
// variable = "s3cr3t"
|
||||
Expr::Name(ast::ExprName { id, .. }) => id.as_str(),
|
||||
Expr::Name(ast::ExprName { id, .. }) => &**id,
|
||||
// d["password"] = "s3cr3t"
|
||||
Expr::Subscript(ast::ExprSubscript { slice, .. }) => match slice.as_ref() {
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.to_str(),
|
||||
|
||||
@@ -69,7 +69,7 @@ pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, call: &ast::ExprCal
|
||||
Expr::BooleanLiteral(ast::ExprBooleanLiteral { value: true, .. }) => (),
|
||||
Expr::Call(ast::ExprCall { func, .. }) => {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
|
||||
if id != "select_autoescape" {
|
||||
if &**id != "select_autoescape" {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
Jinja2AutoescapeFalse { value: true },
|
||||
keyword.range(),
|
||||
|
||||
@@ -64,7 +64,7 @@ pub(crate) fn ssl_with_bad_defaults(checker: &mut Checker, function_def: &StmtFu
|
||||
if let Some(default) = ¶m.default {
|
||||
match default.as_ref() {
|
||||
Expr::Name(ast::ExprName { id, range, .. }) => {
|
||||
if is_insecure_protocol(id.as_str()) {
|
||||
if is_insecure_protocol(id) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
SslWithBadDefaults {
|
||||
protocol: id.to_string(),
|
||||
|
||||
@@ -83,7 +83,7 @@ pub(crate) fn blind_except(
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(id.as_str(), "BaseException" | "Exception") {
|
||||
if !matches!(&**id, "BaseException" | "Exception") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ pub(crate) fn blind_except(
|
||||
if let Stmt::Raise(ast::StmtRaise { exc, .. }) = stmt {
|
||||
if let Some(exc) = exc {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = exc.as_ref() {
|
||||
name.is_some_and(|name| id == name)
|
||||
name.is_some_and(|name| &**id == name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(super) fn is_allowed_func_def(name: &str) -> bool {
|
||||
pub(super) fn allow_boolean_trap(call: &ast::ExprCall) -> bool {
|
||||
let func_name = match call.func.as_ref() {
|
||||
Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr.as_str(),
|
||||
Expr::Name(ast::ExprName { id, .. }) => id.as_str(),
|
||||
Expr::Name(ast::ExprName { id, .. }) => &**id,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ pub(crate) fn boolean_type_hint_positional_argument(
|
||||
fn match_annotation_to_literal_bool(annotation: &Expr) -> bool {
|
||||
match annotation {
|
||||
// Ex) `True`
|
||||
Expr::Name(name) => &name.id == "bool",
|
||||
Expr::Name(name) => &*name.id == "bool",
|
||||
// Ex) `"True"`
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "bool",
|
||||
_ => false,
|
||||
@@ -176,7 +176,7 @@ fn match_annotation_to_literal_bool(annotation: &Expr) -> bool {
|
||||
fn match_annotation_to_complex_bool(annotation: &Expr, semantic: &SemanticModel) -> bool {
|
||||
match annotation {
|
||||
// Ex) `bool`
|
||||
Expr::Name(name) => &name.id == "bool",
|
||||
Expr::Name(name) => &*name.id == "bool",
|
||||
// Ex) `"bool"`
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "bool",
|
||||
// Ex) `bool | int`
|
||||
|
||||
@@ -57,7 +57,7 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
})),
|
||||
arguments: Arguments {
|
||||
arguments: Box::new(Arguments {
|
||||
args: if let Some(msg) = msg {
|
||||
Box::from([msg.clone()])
|
||||
} else {
|
||||
@@ -65,7 +65,7 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
},
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
}))),
|
||||
cause: None,
|
||||
|
||||
@@ -63,7 +63,7 @@ pub(crate) fn assignment_to_os_environ(checker: &mut Checker, targets: &[Expr])
|
||||
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if id != "os" {
|
||||
if &**id != "os" {
|
||||
return;
|
||||
}
|
||||
checker
|
||||
|
||||
@@ -85,7 +85,7 @@ pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[De
|
||||
// TODO(charlie): This should take into account `classmethod-decorators` and
|
||||
// `staticmethod-decorators`.
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = &decorator.expression {
|
||||
if id == "classmethod" || id == "staticmethod" {
|
||||
if &**id == "classmethod" || &**id == "staticmethod" {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
}) => {
|
||||
match func.as_ref() {
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
if matches!(id.as_str(), "filter" | "reduce" | "map") {
|
||||
if matches!(&**id, "filter" | "reduce" | "map") {
|
||||
for arg in arguments.args.iter() {
|
||||
if arg.is_lambda_expr() {
|
||||
self.safe_functions.push(arg);
|
||||
@@ -142,7 +142,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
|
||||
if attr == "reduce" {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() {
|
||||
if id == "functools" {
|
||||
if &**id == "functools" {
|
||||
for arg in arguments.args.iter() {
|
||||
if arg.is_lambda_expr() {
|
||||
self.safe_functions.push(arg);
|
||||
@@ -209,7 +209,7 @@ impl<'a> Visitor<'a> for NamesFromAssignmentsVisitor<'a> {
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
match expr {
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
self.names.push(id.as_str());
|
||||
self.names.push(id);
|
||||
}
|
||||
Expr::Starred(ast::ExprStarred { value, .. }) => {
|
||||
self.visit_expr(value);
|
||||
@@ -303,7 +303,7 @@ pub(crate) fn function_uses_loop_variable(checker: &mut Checker, node: &Node) {
|
||||
// If a variable was used in a function or lambda body, and assigned in the
|
||||
// loop, flag it.
|
||||
for name in suspicious_variables {
|
||||
if reassigned_in_loop.contains(&name.id.as_str()) {
|
||||
if reassigned_in_loop.contains(&&*name.id) {
|
||||
if !checker.flake8_bugbear_seen.contains(&name.range()) {
|
||||
checker.flake8_bugbear_seen.push(name.range());
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
|
||||
@@ -57,7 +57,7 @@ pub(crate) fn getattr_with_constant(
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
||||
return;
|
||||
};
|
||||
if id != "getattr" {
|
||||
if &**id != "getattr" {
|
||||
return;
|
||||
}
|
||||
let [obj, arg] = args else {
|
||||
|
||||
@@ -87,7 +87,7 @@ pub(crate) fn raise_without_from_inside_except(
|
||||
if let Some(name) = name {
|
||||
if exc
|
||||
.as_name_expr()
|
||||
.is_some_and(|ast::ExprName { id, .. }| name == id)
|
||||
.is_some_and(|ast::ExprName { id, .. }| &**id == name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ impl<'a> GroupNameFinder<'a> {
|
||||
|
||||
fn name_matches(&self, expr: &Expr) -> bool {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = expr {
|
||||
id == self.group_name
|
||||
&**id == self.group_name
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ pub(crate) fn setattr_with_constant(
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
||||
return;
|
||||
};
|
||||
if id != "setattr" {
|
||||
if &**id != "setattr" {
|
||||
return;
|
||||
}
|
||||
let [obj, name, value] = args else {
|
||||
|
||||
@@ -73,7 +73,7 @@ pub(crate) fn static_key_dict_comprehension(checker: &mut Checker, dict_comp: &a
|
||||
fn is_constant(key: &Expr, names: &FxHashMap<&str, &ast::ExprName>) -> bool {
|
||||
match key {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.iter().all(|elt| is_constant(elt, names)),
|
||||
Expr::Name(ast::ExprName { id, .. }) => !names.contains_key(id.as_str()),
|
||||
Expr::Name(ast::ExprName { id, .. }) => !names.contains_key(&**id),
|
||||
Expr::Attribute(ast::ExprAttribute { value, .. }) => is_constant(value, names),
|
||||
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
||||
is_constant(value, names) && is_constant(slice, names)
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(crate) fn unintentional_type_annotation(
|
||||
}
|
||||
Expr::Attribute(ast::ExprAttribute { value, .. }) => {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() {
|
||||
if id != "self" {
|
||||
if &**id != "self" {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(UnintentionalTypeAnnotation, stmt.range()));
|
||||
|
||||
@@ -61,7 +61,7 @@ pub(crate) fn unreliable_callable_check(
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
||||
return;
|
||||
};
|
||||
if !matches!(id.as_str(), "hasattr" | "getattr") {
|
||||
if !matches!(&**id, "hasattr" | "getattr") {
|
||||
return;
|
||||
}
|
||||
let [obj, attr, ..] = args else {
|
||||
@@ -75,7 +75,7 @@ pub(crate) fn unreliable_callable_check(
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(UnreliableCallableCheck, expr.range());
|
||||
if id == "hasattr" {
|
||||
if &**id == "hasattr" {
|
||||
if checker.semantic().is_builtin("callable") {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("callable({})", checker.locator().slice(obj)),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -53,7 +53,7 @@ impl AlwaysFixableViolation for ZipWithoutExplicitStrict {
|
||||
/// B905
|
||||
pub(crate) fn zip_without_explicit_strict(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = call.func.as_ref() {
|
||||
if id == "zip"
|
||||
if &**id == "zip"
|
||||
&& checker.semantic().is_builtin("zip")
|
||||
&& call.arguments.find_keyword("strict").is_none()
|
||||
&& !call
|
||||
@@ -91,9 +91,7 @@ pub(crate) fn zip_without_explicit_strict(checker: &mut Checker, call: &ast::Exp
|
||||
/// `itertools.cycle` or similar).
|
||||
fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
..
|
||||
func, arguments, ..
|
||||
}) = &arg
|
||||
else {
|
||||
return false;
|
||||
@@ -104,17 +102,17 @@ fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
|
||||
["itertools", "cycle" | "count"] => true,
|
||||
["itertools", "repeat"] => {
|
||||
// Ex) `itertools.repeat(1)`
|
||||
if keywords.is_empty() && args.len() == 1 {
|
||||
if arguments.keywords.is_empty() && arguments.args.len() == 1 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ex) `itertools.repeat(1, None)`
|
||||
if args.len() == 2 && args[1].is_none_literal_expr() {
|
||||
if arguments.args.len() == 2 && arguments.args[1].is_none_literal_expr() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ex) `iterools.repeat(1, times=None)`
|
||||
for keyword in keywords.iter() {
|
||||
for keyword in arguments.keywords.iter() {
|
||||
if keyword.arg.as_ref().is_some_and(|name| name == "times") {
|
||||
if keyword.value.is_none_literal_expr() {
|
||||
return true;
|
||||
|
||||
@@ -13,7 +13,7 @@ pub(super) fn exactly_one_argument_with_matching_function<'a>(
|
||||
return None;
|
||||
}
|
||||
let func = func.as_name_expr()?;
|
||||
if func.id != name {
|
||||
if &*func.id != name {
|
||||
return None;
|
||||
}
|
||||
Some(arg)
|
||||
@@ -24,7 +24,7 @@ pub(super) fn first_argument_with_matching_function<'a>(
|
||||
func: &Expr,
|
||||
args: &'a [Expr],
|
||||
) -> Option<&'a Expr> {
|
||||
if func.as_name_expr().is_some_and(|func| func.id == name) {
|
||||
if func.as_name_expr().is_some_and(|func| &*func.id == name) {
|
||||
args.first()
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -66,7 +66,7 @@ pub(crate) fn unnecessary_call_around_sorted(
|
||||
let Some(outer) = func.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if !matches!(outer.id.as_str(), "list" | "reversed") {
|
||||
if !matches!(&*outer.id, "list" | "reversed") {
|
||||
return;
|
||||
}
|
||||
let Some(arg) = args.first() else {
|
||||
@@ -78,7 +78,7 @@ pub(crate) fn unnecessary_call_around_sorted(
|
||||
let Some(inner) = func.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if inner.id != "sorted" {
|
||||
if &*inner.id != "sorted" {
|
||||
return;
|
||||
}
|
||||
if !checker.semantic().is_builtin(&inner.id) || !checker.semantic().is_builtin(&outer.id) {
|
||||
@@ -93,7 +93,7 @@ pub(crate) fn unnecessary_call_around_sorted(
|
||||
diagnostic.try_set_fix(|| {
|
||||
Ok(Fix::applicable_edit(
|
||||
fixes::fix_unnecessary_call_around_sorted(expr, checker.locator(), checker.stylist())?,
|
||||
if outer.id == "reversed" {
|
||||
if &*outer.id == "reversed" {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
|
||||
@@ -68,7 +68,7 @@ pub(crate) fn unnecessary_collection_call(
|
||||
let Some(func) = call.func.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let collection = match func.id.as_str() {
|
||||
let collection = match &*func.id {
|
||||
"dict"
|
||||
if call.arguments.keywords.is_empty()
|
||||
|| (!settings.allow_dict_calls_with_keyword_arguments
|
||||
@@ -87,7 +87,7 @@ pub(crate) fn unnecessary_collection_call(
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
if !checker.semantic().is_builtin(func.id.as_str()) {
|
||||
if !checker.semantic().is_builtin(&func.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ pub(crate) fn unnecessary_comprehension_any_all(
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
||||
return;
|
||||
};
|
||||
if !matches!(id.as_str(), "all" | "any") {
|
||||
if !matches!(&**id, "all" | "any") {
|
||||
return;
|
||||
}
|
||||
let [arg] = args else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableKeyword;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword};
|
||||
use ruff_python_ast::{self as ast, Expr, Keyword};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -77,21 +77,14 @@ pub(crate) fn unnecessary_double_cast_or_process(
|
||||
let Some(outer) = func.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if !matches!(
|
||||
outer.id.as_str(),
|
||||
"list" | "tuple" | "set" | "reversed" | "sorted"
|
||||
) {
|
||||
if !matches!(&*outer.id, "list" | "tuple" | "set" | "reversed" | "sorted") {
|
||||
return;
|
||||
}
|
||||
let Some(arg) = args.first() else {
|
||||
return;
|
||||
};
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments {
|
||||
keywords: inner_kw, ..
|
||||
},
|
||||
..
|
||||
func, arguments, ..
|
||||
}) = arg
|
||||
else {
|
||||
return;
|
||||
@@ -105,11 +98,11 @@ pub(crate) fn unnecessary_double_cast_or_process(
|
||||
|
||||
// Avoid collapsing nested `sorted` calls with non-identical keyword arguments
|
||||
// (i.e., `key`, `reverse`).
|
||||
if inner.id == "sorted" && outer.id == "sorted" {
|
||||
if inner_kw.len() != outer_kw.len() {
|
||||
if &*inner.id == "sorted" && &*outer.id == "sorted" {
|
||||
if arguments.keywords.len() != outer_kw.len() {
|
||||
return;
|
||||
}
|
||||
if !inner_kw.iter().all(|inner| {
|
||||
if !arguments.keywords.iter().all(|inner| {
|
||||
outer_kw
|
||||
.iter()
|
||||
.any(|outer| ComparableKeyword::from(inner) == ComparableKeyword::from(outer))
|
||||
@@ -122,7 +115,7 @@ pub(crate) fn unnecessary_double_cast_or_process(
|
||||
// Ex) `list(tuple(...))`
|
||||
// Ex) `set(set(...))`
|
||||
if matches!(
|
||||
(outer.id.as_str(), inner.id.as_str()),
|
||||
(&*outer.id, &*inner.id),
|
||||
("set" | "sorted", "list" | "tuple" | "reversed" | "sorted")
|
||||
| ("set", "set")
|
||||
| ("list" | "tuple", "list" | "tuple")
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_diagnostics::{FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::visitor;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Parameters, Stmt};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprContext, Parameters, Stmt};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -77,7 +77,7 @@ pub(crate) fn unnecessary_map(
|
||||
return;
|
||||
};
|
||||
|
||||
let object_type = match func.id.as_str() {
|
||||
let object_type = match &*func.id {
|
||||
"map" => ObjectType::Generator,
|
||||
"list" => ObjectType::List,
|
||||
"set" => ObjectType::Set,
|
||||
@@ -95,7 +95,7 @@ pub(crate) fn unnecessary_map(
|
||||
if parent
|
||||
.and_then(Expr::as_call_expr)
|
||||
.and_then(|call| call.func.as_name_expr())
|
||||
.is_some_and(|name| matches!(name.id.as_str(), "list" | "set" | "dict"))
|
||||
.is_some_and(|name| matches!(&*name.id, "list" | "set" | "dict"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -125,23 +125,22 @@ pub(crate) fn unnecessary_map(
|
||||
ObjectType::List | ObjectType::Set => {
|
||||
// Only flag, e.g., `list(map(lambda x: x + 1, iterable))`.
|
||||
let [Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
..
|
||||
func, arguments, ..
|
||||
})] = args
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if args.len() != 2 {
|
||||
if arguments.args.len() != 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
if !keywords.is_empty() {
|
||||
if !arguments.keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(argument) = helpers::first_argument_with_matching_function("map", func, args)
|
||||
let Some(argument) =
|
||||
helpers::first_argument_with_matching_function("map", func, &arguments.args)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -170,23 +169,22 @@ pub(crate) fn unnecessary_map(
|
||||
ObjectType::Dict => {
|
||||
// Only flag, e.g., `dict(map(lambda v: (v, v ** 2), values))`.
|
||||
let [Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
..
|
||||
func, arguments, ..
|
||||
})] = args
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if args.len() != 2 {
|
||||
if arguments.args.len() != 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
if !keywords.is_empty() {
|
||||
if !arguments.keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(argument) = helpers::first_argument_with_matching_function("map", func, args)
|
||||
let Some(argument) =
|
||||
helpers::first_argument_with_matching_function("map", func, &arguments.args)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ pub(crate) fn unnecessary_subscript_reversal(checker: &mut Checker, call: &ast::
|
||||
let Some(func) = call.func.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if !matches!(func.id.as_str(), "reversed" | "set" | "sorted") {
|
||||
if !matches!(&*func.id, "reversed" | "set" | "sorted") {
|
||||
return;
|
||||
}
|
||||
if !checker.semantic().is_builtin(&func.id) {
|
||||
|
||||
@@ -72,7 +72,7 @@ pub(crate) fn all_with_model_form(checker: &mut Checker, class_def: &ast::StmtCl
|
||||
let Expr::Name(ast::ExprName { id, .. }) = target else {
|
||||
continue;
|
||||
};
|
||||
if id != "fields" {
|
||||
if &**id != "fields" {
|
||||
continue;
|
||||
}
|
||||
match value.as_ref() {
|
||||
|
||||
@@ -70,7 +70,7 @@ pub(crate) fn exclude_with_model_form(checker: &mut Checker, class_def: &ast::St
|
||||
let Expr::Name(ast::ExprName { id, .. }) = target else {
|
||||
continue;
|
||||
};
|
||||
if id == "exclude" {
|
||||
if &**id == "exclude" {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(DjangoExcludeWithModelForm, target.range()));
|
||||
|
||||
@@ -106,7 +106,7 @@ fn is_model_abstract(class_def: &ast::StmtClassDef) -> bool {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = target else {
|
||||
continue;
|
||||
};
|
||||
if id != "abstract" {
|
||||
if &**id != "abstract" {
|
||||
continue;
|
||||
}
|
||||
if !is_const_true(value) {
|
||||
|
||||
@@ -165,7 +165,7 @@ fn get_element_type(element: &Stmt, semantic: &SemanticModel) -> Option<ContentT
|
||||
let Expr::Name(ast::ExprName { id, .. }) = expr else {
|
||||
return None;
|
||||
};
|
||||
if id == "objects" {
|
||||
if &**id == "objects" {
|
||||
Some(ContentType::ManagerDeclaration)
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -175,12 +175,8 @@ impl Violation for DotFormatInException {
|
||||
|
||||
/// EM101, EM102, EM103
|
||||
pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr) {
|
||||
if let Expr::Call(ast::ExprCall {
|
||||
arguments: Arguments { args, .. },
|
||||
..
|
||||
}) = exc
|
||||
{
|
||||
if let Some(first) = args.first() {
|
||||
if let Expr::Call(ast::ExprCall { arguments, .. }) = exc {
|
||||
if let Some(first) = arguments.args.first() {
|
||||
match first {
|
||||
// Check for string literals.
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value: string, .. }) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ pub mod settings;
|
||||
/// Returns true if the [`Expr`] is an internationalization function call.
|
||||
pub(crate) fn is_gettext_func_call(func: &Expr, functions_names: &[String]) -> bool {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
||||
functions_names.contains(id)
|
||||
functions_names.iter().any(|name| name == &**id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator};
|
||||
use ruff_python_ast::{self as ast, Expr, Keyword, Operator};
|
||||
use ruff_python_semantic::analyze::logging;
|
||||
use ruff_python_stdlib::logging::LoggingLevel;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -90,7 +90,7 @@ fn check_msg(checker: &mut Checker, msg: &Expr) {
|
||||
fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) {
|
||||
match &extra.value {
|
||||
Expr::Dict(ast::ExprDict { keys, .. }) => {
|
||||
for key in keys {
|
||||
for key in keys.iter() {
|
||||
if let Some(key) = &key {
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value: attr, .. }) = key {
|
||||
if is_reserved_attr(attr.to_str()) {
|
||||
@@ -104,16 +104,14 @@ fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) {
|
||||
}
|
||||
}
|
||||
Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { keywords, .. },
|
||||
..
|
||||
func, arguments, ..
|
||||
}) => {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["", "dict"]))
|
||||
{
|
||||
for keyword in keywords.iter() {
|
||||
for keyword in arguments.keywords.iter() {
|
||||
if let Some(attr) = &keyword.arg {
|
||||
if is_reserved_attr(attr) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
|
||||
@@ -93,7 +93,7 @@ pub(crate) fn duplicate_class_field_definition(checker: &mut Checker, body: &[St
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
if !seen_targets.insert(target.id.as_str()) {
|
||||
if !seen_targets.insert(&*target.id) {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
DuplicateClassFieldDefinition {
|
||||
name: target.id.to_string(),
|
||||
|
||||
@@ -81,23 +81,18 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||
for (index, call) in values.iter().enumerate() {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
},
|
||||
arguments,
|
||||
range: _,
|
||||
}) = &call
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !keywords.is_empty() {
|
||||
if !arguments.keywords.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let [arg] = &**args else {
|
||||
let [arg] = &*arguments.args else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -120,7 +115,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||
}
|
||||
|
||||
duplicates
|
||||
.entry((attr.as_str(), arg_name.as_str()))
|
||||
.entry((&**attr, &**arg_name))
|
||||
.or_insert_with(Vec::new)
|
||||
.push(index);
|
||||
}
|
||||
@@ -140,12 +135,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||
.map(|expr| {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func: _,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords: _,
|
||||
range: _,
|
||||
},
|
||||
arguments,
|
||||
range: _,
|
||||
}) = expr
|
||||
else {
|
||||
@@ -154,7 +144,9 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||
format!("Indices should only contain `{attr_name}` calls")
|
||||
)
|
||||
};
|
||||
args.first()
|
||||
arguments
|
||||
.args
|
||||
.first()
|
||||
.unwrap_or_else(|| panic!("`{attr_name}` should have one argument"))
|
||||
})
|
||||
.collect();
|
||||
@@ -187,11 +179,11 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||
});
|
||||
let node3 = Expr::Call(ast::ExprCall {
|
||||
func: Box::new(node2),
|
||||
arguments: Arguments {
|
||||
arguments: Box::new(Arguments {
|
||||
args: Box::from([node]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
});
|
||||
let call = node3;
|
||||
@@ -229,7 +221,7 @@ fn is_bound_to_tuple(arg: &Expr, semantic: &SemanticModel) -> bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(binding_id) = semantic.lookup_symbol(id.as_str()) else {
|
||||
let Some(binding_id) = semantic.lookup_symbol(id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCal
|
||||
};
|
||||
|
||||
// Ex) `foo(**{**bar})`
|
||||
if matches!(keys.as_slice(), [None]) {
|
||||
if matches!(&**keys, [None]) {
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, keyword.range());
|
||||
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
@@ -151,7 +151,7 @@ fn duplicates(call: &ast::ExprCall) -> FxHashSet<&str> {
|
||||
duplicates.insert(name.as_str());
|
||||
}
|
||||
} else if let Expr::Dict(ast::ExprDict { keys, .. }) = &keyword.value {
|
||||
for key in keys {
|
||||
for key in keys.iter() {
|
||||
if let Some(name) = key.as_ref().and_then(as_kwarg) {
|
||||
if !seen.insert(name) {
|
||||
duplicates.insert(name);
|
||||
|
||||
@@ -47,7 +47,7 @@ pub(crate) fn unnecessary_range_start(checker: &mut Checker, call: &ast::ExprCal
|
||||
let Expr::Name(ast::ExprName { id, .. }) = call.func.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if id != "range" {
|
||||
if &**id != "range" {
|
||||
return;
|
||||
};
|
||||
if !checker.semantic().is_builtin("range") {
|
||||
|
||||
@@ -142,7 +142,7 @@ fn class_method(
|
||||
|
||||
// Don't error if the first argument is annotated with typing.Type[T].
|
||||
// These are edge cases, and it's hard to give good error messages for them.
|
||||
if value.id != "type" {
|
||||
if &*value.id != "type" {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ fn is_name(expr: &Expr, name: &str) -> bool {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = expr else {
|
||||
return false;
|
||||
};
|
||||
id.as_str() == name
|
||||
&**id == name
|
||||
}
|
||||
|
||||
/// Return `true` if the given expression resolves to `typing.Self`.
|
||||
|
||||
@@ -132,7 +132,7 @@ impl fmt::Display for ExprType {
|
||||
/// `str`, `bytes`, or `complex`).
|
||||
fn match_builtin_type(expr: &Expr, semantic: &SemanticModel) -> Option<ExprType> {
|
||||
let name = expr.as_name_expr()?;
|
||||
let result = match name.id.as_str() {
|
||||
let result = match &*name.id {
|
||||
"int" => ExprType::Int,
|
||||
"bool" => ExprType::Bool,
|
||||
"str" => ExprType::Str,
|
||||
@@ -141,7 +141,7 @@ fn match_builtin_type(expr: &Expr, semantic: &SemanticModel) -> Option<ExprType>
|
||||
"complex" => ExprType::Complex,
|
||||
_ => return None,
|
||||
};
|
||||
if !semantic.is_builtin(name.id.as_str()) {
|
||||
if !semantic.is_builtin(&name.id) {
|
||||
return None;
|
||||
}
|
||||
Some(result)
|
||||
|
||||
@@ -307,7 +307,7 @@ fn is_valid_default_value_with_annotation(
|
||||
}) => {
|
||||
return allow_container
|
||||
&& keys.len() <= 10
|
||||
&& keys.iter().zip(values).all(|(k, v)| {
|
||||
&& keys.iter().zip(values.iter()).all(|(k, v)| {
|
||||
k.as_ref().is_some_and(|k| {
|
||||
is_valid_default_value_with_annotation(k, false, locator, semantic)
|
||||
}) && is_valid_default_value_with_annotation(v, false, locator, semantic)
|
||||
@@ -450,7 +450,7 @@ fn is_type_var_like_call(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
/// `__all__`).
|
||||
fn is_special_assignment(target: &Expr, semantic: &SemanticModel) -> bool {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = target {
|
||||
match id.as_str() {
|
||||
match &**id {
|
||||
"__all__" => semantic.current_scope().kind.is_module(),
|
||||
"__match_args__" | "__slots__" => semantic.current_scope().kind.is_class(),
|
||||
_ => false,
|
||||
|
||||
@@ -122,7 +122,7 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr)
|
||||
.into_iter()
|
||||
.map(|type_member| {
|
||||
Expr::Name(ast::ExprName {
|
||||
id: type_member,
|
||||
id: type_member.into_boxed_str(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
})
|
||||
|
||||
@@ -47,7 +47,7 @@ pub(crate) fn unsupported_method_call_on_all(checker: &mut Checker, func: &Expr)
|
||||
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if id.as_str() != "__all__" {
|
||||
if &**id != "__all__" {
|
||||
return;
|
||||
}
|
||||
if !is_unsupported_method(attr.as_str()) {
|
||||
|
||||
@@ -242,7 +242,7 @@ where
|
||||
match expr {
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
if let Some(current_assert) = self.current_assert {
|
||||
if id.as_str() == self.exception_name {
|
||||
if &**id == self.exception_name {
|
||||
self.errors.push(Diagnostic::new(
|
||||
PytestAssertInExcept {
|
||||
name: id.to_string(),
|
||||
@@ -419,7 +419,7 @@ fn to_pytest_raises_args<'a>(
|
||||
if kwarg
|
||||
.arg
|
||||
.as_ref()
|
||||
.is_some_and(|id| id.as_str() == "expected_exception") =>
|
||||
.is_some_and(|id| &**id == "expected_exception") =>
|
||||
{
|
||||
Cow::Borrowed(checker.locator().slice(kwarg.value.range()))
|
||||
}
|
||||
@@ -452,11 +452,11 @@ fn to_pytest_raises_args<'a>(
|
||||
if kwarg1
|
||||
.arg
|
||||
.as_ref()
|
||||
.is_some_and(|id| id.as_str() == "expected_exception")
|
||||
.is_some_and(|id| &**id == "expected_exception")
|
||||
&& kwarg2
|
||||
.arg
|
||||
.as_ref()
|
||||
.is_some_and(|id| id.as_str() == "expected_regex") =>
|
||||
.is_some_and(|id| &**id == "expected_regex") =>
|
||||
{
|
||||
Cow::Owned(format!(
|
||||
"{}, match={}",
|
||||
@@ -469,11 +469,11 @@ fn to_pytest_raises_args<'a>(
|
||||
if kwarg1
|
||||
.arg
|
||||
.as_ref()
|
||||
.is_some_and(|id| id.as_str() == "expected_regex")
|
||||
.is_some_and(|id| &**id == "expected_regex")
|
||||
&& kwarg2
|
||||
.arg
|
||||
.as_ref()
|
||||
.is_some_and(|id| id.as_str() == "expected_exception") =>
|
||||
.is_some_and(|id| &**id == "expected_exception") =>
|
||||
{
|
||||
Cow::Owned(format!(
|
||||
"{}, match={}",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Decorator, Expr};
|
||||
use ruff_python_ast::{self as ast, Decorator, Expr};
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -137,18 +137,10 @@ fn check_mark_parentheses(checker: &mut Checker, decorator: &Decorator, marker:
|
||||
match &decorator.expression {
|
||||
Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
},
|
||||
arguments,
|
||||
range: _,
|
||||
}) => {
|
||||
if !checker.settings.flake8_pytest_style.mark_parentheses
|
||||
&& args.is_empty()
|
||||
&& keywords.is_empty()
|
||||
{
|
||||
if !checker.settings.flake8_pytest_style.mark_parentheses && arguments.is_empty() {
|
||||
let fix = Fix::safe_edit(Edit::deletion(func.end(), decorator.end()));
|
||||
pytest_mark_parentheses(checker, decorator, marker, fix, "", "()");
|
||||
}
|
||||
@@ -171,11 +163,8 @@ fn check_useless_usefixtures(checker: &mut Checker, decorator: &Decorator, marke
|
||||
// @pytest.mark.usefixtures
|
||||
Expr::Attribute(..) => {}
|
||||
// @pytest.mark.usefixtures(...)
|
||||
Expr::Call(ast::ExprCall {
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
..
|
||||
}) => {
|
||||
if !args.is_empty() || !keywords.is_empty() {
|
||||
Expr::Call(ast::ExprCall { arguments, .. }) => {
|
||||
if !arguments.is_empty() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::AstNode;
|
||||
use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, ExprContext};
|
||||
use ruff_python_ast::{self as ast, Decorator, Expr, ExprContext};
|
||||
use ruff_python_codegen::Generator;
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
@@ -632,23 +632,19 @@ fn handle_value_rows(
|
||||
pub(crate) fn parametrize(checker: &mut Checker, decorators: &[Decorator]) {
|
||||
for decorator in decorators {
|
||||
if is_pytest_parametrize(decorator, checker.semantic()) {
|
||||
if let Expr::Call(ast::ExprCall {
|
||||
arguments: Arguments { args, .. },
|
||||
..
|
||||
}) = &decorator.expression
|
||||
{
|
||||
if let Expr::Call(ast::ExprCall { arguments, .. }) = &decorator.expression {
|
||||
if checker.enabled(Rule::PytestParametrizeNamesWrongType) {
|
||||
if let [names, ..] = &**args {
|
||||
if let [names, ..] = &*arguments.args {
|
||||
check_names(checker, decorator, names);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::PytestParametrizeValuesWrongType) {
|
||||
if let [names, values, ..] = &**args {
|
||||
if let [names, values, ..] = &*arguments.args {
|
||||
check_values(checker, names, values);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::PytestDuplicateParametrizeTestCases) {
|
||||
if let [_, values, ..] = &**args {
|
||||
if let [_, values, ..] = &*arguments.args {
|
||||
check_duplicates(checker, values);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,11 +389,11 @@ impl UnittestAssert {
|
||||
};
|
||||
let node1 = ast::ExprCall {
|
||||
func: Box::new(node.into()),
|
||||
arguments: Arguments {
|
||||
arguments: Box::new(Arguments {
|
||||
args: Box::from([(**obj).clone(), (**cls).clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
let isinstance = node1.into();
|
||||
@@ -433,11 +433,11 @@ impl UnittestAssert {
|
||||
};
|
||||
let node2 = ast::ExprCall {
|
||||
func: Box::new(node1.into()),
|
||||
arguments: Arguments {
|
||||
arguments: Box::new(Arguments {
|
||||
args: Box::from([(**regex).clone(), (**text).clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
let re_search = node2.into();
|
||||
|
||||
@@ -564,7 +564,7 @@ fn unnecessary_assign(checker: &mut Checker, stack: &Stack) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if stack.non_locals.contains(assigned_id.as_str()) {
|
||||
if stack.non_locals.contains(&**assigned_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,8 @@ pub(crate) fn private_member_access(checker: &mut Checker, expr: &Expr) {
|
||||
.settings
|
||||
.flake8_self
|
||||
.ignore_names
|
||||
.contains(attr.as_ref())
|
||||
.iter()
|
||||
.any(|name| name == attr.as_str())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -303,27 +303,22 @@ fn isinstance_target<'a>(call: &'a Expr, semantic: &'a SemanticModel) -> Option<
|
||||
// Verify that this is an `isinstance` call.
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
},
|
||||
arguments,
|
||||
range: _,
|
||||
}) = &call
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
if args.len() != 2 {
|
||||
if !arguments.keywords.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if !keywords.is_empty() {
|
||||
if arguments.args.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
let Expr::Name(ast::ExprName { id: func_name, .. }) = func.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
if func_name != "isinstance" {
|
||||
if &**func_name != "isinstance" {
|
||||
return None;
|
||||
}
|
||||
if !semantic.is_builtin("isinstance") {
|
||||
@@ -331,7 +326,7 @@ fn isinstance_target<'a>(call: &'a Expr, semantic: &'a SemanticModel) -> Option<
|
||||
}
|
||||
|
||||
// Collect the target (e.g., `obj` in `isinstance(obj, int)`).
|
||||
Some(&args[0])
|
||||
Some(&arguments.args[0])
|
||||
}
|
||||
|
||||
/// SIM101
|
||||
@@ -374,12 +369,10 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
|
||||
if indices.len() > 1 {
|
||||
// Grab the target used in each duplicate `isinstance` call (e.g., `obj` in
|
||||
// `isinstance(obj, int)`).
|
||||
let target = if let Expr::Call(ast::ExprCall {
|
||||
arguments: Arguments { args, .. },
|
||||
..
|
||||
}) = &values[indices[0]]
|
||||
{
|
||||
args.first()
|
||||
let target = if let Expr::Call(ast::ExprCall { arguments, .. }) = &values[indices[0]] {
|
||||
arguments
|
||||
.args
|
||||
.first()
|
||||
.expect("`isinstance` should have two arguments")
|
||||
} else {
|
||||
unreachable!("Indices should only contain `isinstance` calls")
|
||||
@@ -401,14 +394,13 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
|
||||
.iter()
|
||||
.map(|index| &values[*index])
|
||||
.map(|expr| {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
arguments: Arguments { args, .. },
|
||||
..
|
||||
}) = expr
|
||||
else {
|
||||
let Expr::Call(ast::ExprCall { arguments, .. }) = expr else {
|
||||
unreachable!("Indices should only contain `isinstance` calls")
|
||||
};
|
||||
args.get(1).expect("`isinstance` should have two arguments")
|
||||
arguments
|
||||
.args
|
||||
.get(1)
|
||||
.expect("`isinstance` should have two arguments")
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -436,11 +428,11 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
|
||||
};
|
||||
let node2 = ast::ExprCall {
|
||||
func: Box::new(node1.into()),
|
||||
arguments: Arguments {
|
||||
arguments: Box::new(Arguments {
|
||||
args: Box::from([target.clone(), node.into()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
let call = node2.into();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::fix::snippet::SourceCodeSnippet;
|
||||
@@ -134,14 +134,12 @@ pub(crate) fn use_capital_environment_variables(checker: &mut Checker, expr: &Ex
|
||||
|
||||
// Ex) `os.environ.get('foo')`, `os.getenv('foo')`
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, .. },
|
||||
..
|
||||
func, arguments, ..
|
||||
}) = expr
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(arg) = args.first() else {
|
||||
let Some(arg) = arguments.args.first() else {
|
||||
return;
|
||||
};
|
||||
let Expr::StringLiteral(ast::ExprStringLiteral { value: env_var, .. }) = arg else {
|
||||
@@ -193,7 +191,7 @@ fn check_os_environ_subscript(checker: &mut Checker, expr: &Expr) {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = attr_value.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if id != "os" || attr != "environ" {
|
||||
if &**id != "os" || attr != "environ" {
|
||||
return;
|
||||
}
|
||||
let Expr::StringLiteral(ast::ExprStringLiteral { value: env_var, .. }) = slice.as_ref() else {
|
||||
@@ -233,13 +231,13 @@ fn check_os_environ_subscript(checker: &mut Checker, expr: &Expr) {
|
||||
pub(crate) fn dict_get_with_none_default(checker: &mut Checker, expr: &Expr) {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
arguments,
|
||||
range: _,
|
||||
}) = expr
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !keywords.is_empty() {
|
||||
if !arguments.keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() else {
|
||||
@@ -248,13 +246,13 @@ pub(crate) fn dict_get_with_none_default(checker: &mut Checker, expr: &Expr) {
|
||||
if attr != "get" {
|
||||
return;
|
||||
}
|
||||
let Some(key) = args.first() else {
|
||||
let Some(key) = arguments.args.first() else {
|
||||
return;
|
||||
};
|
||||
if !(key.is_literal_expr() || key.is_name_expr()) {
|
||||
return;
|
||||
}
|
||||
let Some(default) = args.get(1) else {
|
||||
let Some(default) = arguments.args.get(1) else {
|
||||
return;
|
||||
};
|
||||
if !default.is_none_literal_expr() {
|
||||
|
||||
@@ -184,11 +184,11 @@ pub(crate) fn if_expr_with_true_false(
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
arguments: Arguments {
|
||||
arguments: Box::new(Arguments {
|
||||
args: Box::from([test.clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
}
|
||||
.into(),
|
||||
|
||||
@@ -278,11 +278,11 @@ pub(crate) fn double_negation(checker: &mut Checker, expr: &Expr, op: UnaryOp, o
|
||||
};
|
||||
let node1 = ast::ExprCall {
|
||||
func: Box::new(node.into()),
|
||||
arguments: Arguments {
|
||||
arguments: Box::new(Arguments {
|
||||
args: Box::from([*operand.clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
|
||||
@@ -252,7 +252,7 @@ fn is_main_check(expr: &Expr) -> bool {
|
||||
}) = expr
|
||||
{
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = left.as_ref() {
|
||||
if id == "__name__" {
|
||||
if &**id == "__name__" {
|
||||
if let [Expr::StringLiteral(ast::ExprStringLiteral { value, .. })] = &**comparators
|
||||
{
|
||||
if value == "__main__" {
|
||||
|
||||
@@ -175,11 +175,11 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &mut Checker, stmt_if:
|
||||
};
|
||||
let node3 = ast::ExprCall {
|
||||
func: Box::new(node2.into()),
|
||||
arguments: Arguments {
|
||||
arguments: Box::new(Arguments {
|
||||
args: Box::from([node1, node]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
let node4 = expected_var.clone();
|
||||
@@ -275,11 +275,11 @@ pub(crate) fn if_exp_instead_of_dict_get(
|
||||
};
|
||||
let fixed_node = ast::ExprCall {
|
||||
func: Box::new(dict_get_node.into()),
|
||||
arguments: Arguments {
|
||||
arguments: Box::new(Arguments {
|
||||
args: Box::from([dict_key_node, default_value_node]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_diagnostics::{Applicability, Edit};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{self as ast, Arguments, CmpOp, Comprehension, Expr};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Comprehension, Expr};
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
@@ -67,20 +67,21 @@ fn key_in_dict(
|
||||
) {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
arguments,
|
||||
range: _,
|
||||
}) = &right
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !(args.is_empty() && keywords.is_empty()) {
|
||||
|
||||
if !arguments.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if attr != "keys" {
|
||||
if &**attr != "keys" {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -89,10 +90,7 @@ fn key_in_dict(
|
||||
// def __contains__(self, key: object) -> bool:
|
||||
// return key in self.keys()
|
||||
// ```
|
||||
if value
|
||||
.as_name_expr()
|
||||
.is_some_and(|name| matches!(name.id.as_str(), "self"))
|
||||
{
|
||||
if value.as_name_expr().is_some_and(|name| &*name.id == "self") {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -160,11 +160,11 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt_if: &ast::StmtIf) {
|
||||
};
|
||||
let value_node = ast::ExprCall {
|
||||
func: Box::new(func_node.into()),
|
||||
arguments: Arguments {
|
||||
arguments: Box::new(Arguments {
|
||||
args: Box::from([if_test.clone()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
let return_node = ast::StmtReturn {
|
||||
|
||||
@@ -121,7 +121,7 @@ fn is_open(checker: &mut Checker, func: &Expr) -> bool {
|
||||
}
|
||||
// open(...)
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
id.as_str() == "open" && checker.semantic().is_builtin("open")
|
||||
&**id == "open" && checker.semantic().is_builtin("open")
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
|
||||
@@ -390,11 +390,11 @@ fn return_stmt(id: &str, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
|
||||
};
|
||||
let node2 = ast::ExprCall {
|
||||
func: Box::new(node1.into()),
|
||||
arguments: Arguments {
|
||||
arguments: Box::new(Arguments {
|
||||
args: Box::from([node.into()]),
|
||||
keywords: Box::from([]),
|
||||
range: TextRange::default(),
|
||||
},
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
let node3 = ast::StmtReturn {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ast::{ExprAttribute, ExprName, Identifier};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, ExprCall};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::{checkers::ast::Checker, fix::snippet::SourceCodeSnippet};
|
||||
@@ -61,21 +61,19 @@ impl AlwaysFixableViolation for ZipDictKeysAndValues {
|
||||
/// SIM911
|
||||
pub(crate) fn zip_dict_keys_and_values(checker: &mut Checker, expr: &ExprCall) {
|
||||
let ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
..
|
||||
func, arguments, ..
|
||||
} = expr;
|
||||
match &keywords[..] {
|
||||
match &*arguments.keywords {
|
||||
[] => {}
|
||||
[ast::Keyword {
|
||||
arg: Some(name), ..
|
||||
}] if name.as_str() == "strict" => {}
|
||||
_ => return,
|
||||
};
|
||||
if matches!(func.as_ref(), Expr::Name(ExprName { id, .. }) if id != "zip") {
|
||||
if matches!(func.as_ref(), Expr::Name(ExprName { id, .. }) if &**id != "zip") {
|
||||
return;
|
||||
}
|
||||
let [arg1, arg2] = &args[..] else {
|
||||
let [arg1, arg2] = &*arguments.args else {
|
||||
return;
|
||||
};
|
||||
let Some((var1, attr1)) = get_var_attr(arg1) else {
|
||||
|
||||
@@ -7,7 +7,7 @@ pub(super) fn has_slots(body: &[Stmt]) -> bool {
|
||||
Stmt::Assign(ast::StmtAssign { targets, .. }) => {
|
||||
for target in targets {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = target {
|
||||
if id.as_str() == "__slots__" {
|
||||
if &**id == "__slots__" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ pub(super) fn has_slots(body: &[Stmt]) -> bool {
|
||||
}
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() {
|
||||
if id.as_str() == "__slots__" {
|
||||
if &**id == "__slots__" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ fn is_empty_stmt(stmt: &Stmt) -> bool {
|
||||
if let Some(exc) = exc {
|
||||
match exc.as_ref() {
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
return id == "NotImplementedError" || id == "NotImplemented";
|
||||
return &**id == "NotImplementedError" || &**id == "NotImplemented";
|
||||
}
|
||||
Expr::Call(ast::ExprCall { func, .. }) => {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
|
||||
return id == "NotImplementedError" || id == "NotImplemented";
|
||||
return &**id == "NotImplementedError" || &**id == "NotImplemented";
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -10,7 +10,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
///
|
||||
/// When possible, using `Path` object methods such as `Path.stat()` can
|
||||
/// improve readability over the `os` module's counterparts (e.g.,
|
||||
/// `os.path.getsize()`).
|
||||
/// `os.path.getatime()`).
|
||||
///
|
||||
/// Note that `os` functions may be preferable if performance is a concern,
|
||||
/// e.g., in hot loops.
|
||||
@@ -19,19 +19,19 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.getsize(__file__)
|
||||
/// os.path.getatime(__file__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(__file__).stat().st_size
|
||||
/// Path(__file__).stat().st_atime
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getsize`](https://docs.python.org/3/library/os.path.html#os.path.getsize)
|
||||
/// - [Python documentation: `os.path.getatime`](https://docs.python.org/3/library/os.path.html#os.path.getatime)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.getatime`.
|
||||
/// Checks for uses of `os.path.getctime`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
@@ -10,7 +10,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
///
|
||||
/// When possible, using `Path` object methods such as `Path.stat()` can
|
||||
/// improve readability over the `os` module's counterparts (e.g.,
|
||||
/// `os.path.getsize()`).
|
||||
/// `os.path.getctime()`).
|
||||
///
|
||||
/// Note that `os` functions may be preferable if performance is a concern,
|
||||
/// e.g., in hot loops.
|
||||
@@ -19,19 +19,19 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.getsize(__file__)
|
||||
/// os.path.getctime(__file__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(__file__).stat().st_size
|
||||
/// Path(__file__).stat().st_ctime
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getsize`](https://docs.python.org/3/library/os.path.html#os.path.getsize)
|
||||
/// - [Python documentation: `os.path.getctime`](https://docs.python.org/3/library/os.path.html#os.path.getctime)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.getatime`.
|
||||
/// Checks for uses of `os.path.getmtime`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
@@ -10,7 +10,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
///
|
||||
/// When possible, using `Path` object methods such as `Path.stat()` can
|
||||
/// improve readability over the `os` module's counterparts (e.g.,
|
||||
/// `os.path.getsize()`).
|
||||
/// `os.path.getmtime()`).
|
||||
///
|
||||
/// Note that `os` functions may be preferable if performance is a concern,
|
||||
/// e.g., in hot loops.
|
||||
@@ -19,19 +19,19 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.getsize(__file__)
|
||||
/// os.path.getmtime(__file__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(__file__).stat().st_size
|
||||
/// Path(__file__).stat().st_mtime
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getsize`](https://docs.python.org/3/library/os.path.html#os.path.getsize)
|
||||
/// - [Python documentation: `os.path.getmtime`](https://docs.python.org/3/library/os.path.html#os.path.getmtime)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, ConversionFlag, Expr};
|
||||
use ruff_python_ast::{self as ast, ConversionFlag, Expr};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
/// Wrap an expression in a [`ast::FStringElement::Expression`] with no special formatting.
|
||||
@@ -15,7 +15,7 @@ fn to_f_string_expression_element(inner: &Expr) -> ast::FStringElement {
|
||||
/// Convert a string to a [`ast::FStringElement::Literal`].
|
||||
pub(super) fn to_f_string_literal_element(s: &str) -> ast::FStringElement {
|
||||
ast::FStringElement::Literal(ast::FStringLiteralElement {
|
||||
value: s.to_owned(),
|
||||
value: s.to_string().into_boxed_str(),
|
||||
range: TextRange::default(),
|
||||
})
|
||||
}
|
||||
@@ -26,14 +26,9 @@ fn is_simple_call(expr: &Expr) -> bool {
|
||||
match expr {
|
||||
Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
},
|
||||
arguments,
|
||||
range: _,
|
||||
}) => args.is_empty() && keywords.is_empty() && is_simple_callee(func),
|
||||
}) => arguments.is_empty() && is_simple_callee(func),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -53,7 +48,7 @@ pub(super) fn to_f_string_element(expr: &Expr) -> Option<ast::FStringElement> {
|
||||
match expr {
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, range }) => {
|
||||
Some(ast::FStringElement::Literal(ast::FStringLiteralElement {
|
||||
value: value.to_string(),
|
||||
value: value.to_string().into_boxed_str(),
|
||||
range: *range,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use itertools::Itertools;
|
||||
use crate::fix::edits::pad;
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -103,20 +103,16 @@ fn build_fstring(joiner: &str, joinees: &[Expr]) -> Option<Expr> {
|
||||
|
||||
/// FLY002
|
||||
pub(crate) fn static_join_to_fstring(checker: &mut Checker, expr: &Expr, joiner: &str) {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
..
|
||||
}) = expr
|
||||
else {
|
||||
let Expr::Call(ast::ExprCall { arguments, .. }) = expr else {
|
||||
return;
|
||||
};
|
||||
|
||||
// If there are kwargs or more than one argument, this is some non-standard
|
||||
// string join call.
|
||||
if !keywords.is_empty() {
|
||||
if !arguments.keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
let [arg] = &**args else {
|
||||
let [arg] = &*arguments.args else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ pub(super) fn test_expression(expr: &Expr, semantic: &SemanticModel) -> Resoluti
|
||||
match &semantic.binding(id).kind {
|
||||
BindingKind::Argument => {
|
||||
// Avoid, e.g., `self.values`.
|
||||
if matches!(name.id.as_str(), "self" | "cls") {
|
||||
if matches!(&*name.id, "self" | "cls") {
|
||||
Resolution::IrrelevantBinding
|
||||
} else {
|
||||
Resolution::RelevantLocal
|
||||
|
||||
@@ -45,7 +45,7 @@ pub(crate) fn assignment_to_df(targets: &[Expr]) -> Option<Diagnostic> {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = target else {
|
||||
return None;
|
||||
};
|
||||
if id != "df" {
|
||||
if &**id != "df" {
|
||||
return None;
|
||||
}
|
||||
Some(Diagnostic::new(PandasDfVariableName, target.range()))
|
||||
|
||||
@@ -58,7 +58,7 @@ impl Violation for PandasUseOfPdMerge {
|
||||
pub(crate) fn use_of_pd_merge(checker: &mut Checker, func: &Expr) {
|
||||
if let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() {
|
||||
if id == "pd" && attr == "merge" {
|
||||
if &**id == "pd" && attr == "merge" {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(PandasUseOfPdMerge, func.range()));
|
||||
|
||||
@@ -56,7 +56,7 @@ pub(crate) fn error_suffix_on_exception_name(
|
||||
if !arguments.is_some_and(|arguments| {
|
||||
arguments.args.iter().any(|base| {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = &base {
|
||||
id == "Exception" || id.ends_with("Error")
|
||||
&**id == "Exception" || id.ends_with("Error")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::fmt;
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Arguments, Expr};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -70,14 +70,12 @@ pub(crate) fn incorrect_dict_iterator(checker: &mut Checker, stmt_for: &ast::Stm
|
||||
return;
|
||||
};
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, .. },
|
||||
..
|
||||
func, arguments, ..
|
||||
}) = stmt_for.iter.as_ref()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !args.is_empty() {
|
||||
if !arguments.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func.as_ref() else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -93,23 +93,18 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
|
||||
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
},
|
||||
arguments,
|
||||
range,
|
||||
}) = value.as_ref()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !keywords.is_empty() {
|
||||
if !arguments.keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let [arg] = &**args else {
|
||||
let [arg] = &*arguments.args else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::any_over_expr;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_python_semantic::analyze::typing::is_list;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -60,23 +60,18 @@ pub(crate) fn manual_list_copy(checker: &mut Checker, target: &Expr, body: &[Stm
|
||||
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
},
|
||||
arguments,
|
||||
range,
|
||||
}) = value.as_ref()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !keywords.is_empty() {
|
||||
if !arguments.keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let [arg] = &**args else {
|
||||
let [arg] = &*arguments.args else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_python_semantic::analyze::typing::find_assigned_value;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
@@ -52,19 +52,14 @@ impl AlwaysFixableViolation for UnnecessaryListCast {
|
||||
pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr, body: &[Stmt]) {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords: _,
|
||||
range: _,
|
||||
},
|
||||
arguments,
|
||||
range: list_range,
|
||||
}) = iter
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let [arg] = &**args else {
|
||||
let [arg] = &*arguments.args else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -72,7 +67,7 @@ pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr, body: &[
|
||||
return;
|
||||
};
|
||||
|
||||
if !(id == "list" && checker.semantic().is_builtin("list")) {
|
||||
if &**id != "list" || !checker.semantic().is_builtin("list") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,7 +137,7 @@ fn match_append(stmt: &Stmt, id: &str) -> bool {
|
||||
let Some(ast::ExprName { id: target_id, .. }) = value.as_name_expr() else {
|
||||
return false;
|
||||
};
|
||||
target_id == id
|
||||
&**target_id == id
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to remove a `list` cast from an expression.
|
||||
|
||||
@@ -80,7 +80,7 @@ fn deprecated_type_comparison(checker: &mut Checker, compare: &ast::ExprCompare)
|
||||
continue;
|
||||
};
|
||||
|
||||
if !(id == "type" && checker.semantic().is_builtin("type")) {
|
||||
if &**id != "type" || !checker.semantic().is_builtin("type") {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ fn deprecated_type_comparison(checker: &mut Checker, compare: &ast::ExprCompare)
|
||||
continue;
|
||||
};
|
||||
|
||||
if id == "type" && checker.semantic().is_builtin("type") {
|
||||
if &**id == "type" && checker.semantic().is_builtin("type") {
|
||||
// Allow comparison for types which are not obvious.
|
||||
if arguments
|
||||
.args
|
||||
@@ -128,7 +128,7 @@ fn deprecated_type_comparison(checker: &mut Checker, compare: &ast::ExprCompare)
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
// Ex) `type(obj) is int`
|
||||
if matches!(
|
||||
id.as_str(),
|
||||
&**id,
|
||||
"int"
|
||||
| "str"
|
||||
| "float"
|
||||
@@ -191,7 +191,7 @@ fn is_type(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
if !(id == "type" && semantic.is_builtin("type")) {
|
||||
if &**id != "type" || !semantic.is_builtin("type") {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -204,7 +204,7 @@ fn is_type(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
// Ex) `type(obj) == int`
|
||||
matches!(
|
||||
id.as_str(),
|
||||
&**id,
|
||||
"bool"
|
||||
| "bytearray"
|
||||
| "bytes"
|
||||
|
||||
@@ -61,7 +61,7 @@ pub(crate) fn invalid_print_syntax(checker: &mut Checker, left: &Expr) {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = &left else {
|
||||
return;
|
||||
};
|
||||
if id != "print" {
|
||||
if &**id != "print" {
|
||||
return;
|
||||
}
|
||||
if !checker.semantic().is_builtin("print") {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user