mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-04 09:31:25 +00:00
Add support for ruff.toml
(#1378)
This commit is contained in:
parent
28c45eb2a3
commit
b0f30bef8f
8 changed files with 121 additions and 71 deletions
33
README.md
33
README.md
|
@ -214,13 +214,6 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
# Assume Python 3.10.
|
# Assume Python 3.10.
|
||||||
target-version = "py310"
|
target-version = "py310"
|
||||||
|
|
||||||
[tool.ruff.flake8-import-conventions.aliases]
|
|
||||||
altair = "alt"
|
|
||||||
"matplotlib.pyplot" = "plt"
|
|
||||||
numpy = "np"
|
|
||||||
pandas = "pd"
|
|
||||||
seaborn = "sns"
|
|
||||||
|
|
||||||
[tool.ruff.mccabe]
|
[tool.ruff.mccabe]
|
||||||
# Unlike Flake8, default to a complexity level of 10.
|
# Unlike Flake8, default to a complexity level of 10.
|
||||||
max-complexity = 10
|
max-complexity = 10
|
||||||
|
@ -259,6 +252,27 @@ select = ["E", "F", "Q"]
|
||||||
docstring-quotes = "double"
|
docstring-quotes = "double"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
As an alternative to `pyproject.toml`, Ruff will also respect a `ruff.toml` file, which implements
|
||||||
|
an equivalent schema (though the `[tool.ruff]` hierarchy can be omitted). For example, the above
|
||||||
|
`pyproject.toml` described above would be represented via the following `ruff.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Enable Pyflakes and pycodestyle rules.
|
||||||
|
select = ["E", "F"]
|
||||||
|
|
||||||
|
# Never enforce `E501` (line length violations).
|
||||||
|
ignore = ["E501"]
|
||||||
|
|
||||||
|
# Always autofix, but never try to fix `F401` (unused imports).
|
||||||
|
fix = true
|
||||||
|
unfixable = ["F401"]
|
||||||
|
|
||||||
|
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
|
||||||
|
[per-file-ignores]
|
||||||
|
"__init__.py" = ["E402"]
|
||||||
|
"path/to/file.py" = ["E402"]
|
||||||
|
```
|
||||||
|
|
||||||
For a full list of configurable options, see the [API reference](#reference).
|
For a full list of configurable options, see the [API reference](#reference).
|
||||||
|
|
||||||
Some common configuration settings can be provided via the command-line:
|
Some common configuration settings can be provided via the command-line:
|
||||||
|
@ -279,7 +293,7 @@ Arguments:
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--config <CONFIG>
|
--config <CONFIG>
|
||||||
Path to the `pyproject.toml` file to use for configuration
|
Path to the `pyproject.toml` or `ruff.toml` file to use for configuration
|
||||||
-v, --verbose
|
-v, --verbose
|
||||||
Enable verbose logging
|
Enable verbose logging
|
||||||
-q, --quiet
|
-q, --quiet
|
||||||
|
@ -385,6 +399,9 @@ extend = "../pyproject.toml"
|
||||||
line-length = 100
|
line-length = 100
|
||||||
```
|
```
|
||||||
|
|
||||||
|
All of the above rules apply equivalently to `ruff.toml` files. If Ruff detects both a `ruff.toml`
|
||||||
|
and `pyproject.toml` file, it will defer to the `ruff.toml`.
|
||||||
|
|
||||||
### Python file discovery
|
### Python file discovery
|
||||||
|
|
||||||
When passed a path on the command-line, Ruff will automatically discover all Python files in that
|
When passed a path on the command-line, Ruff will automatically discover all Python files in that
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
[tool.ruff]
|
|
||||||
extend = "../../pyproject.toml"
|
extend = "../../pyproject.toml"
|
||||||
src = ["."]
|
src = ["."]
|
||||||
# Enable I001, and re-enable F841, to test extension priority.
|
# Enable I001, and re-enable F841, to test extension priority.
|
|
@ -19,7 +19,8 @@ use crate::settings::types::{
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
#[arg(required_unless_present_any = ["explain", "generate_shell_completion"])]
|
#[arg(required_unless_present_any = ["explain", "generate_shell_completion"])]
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
/// Path to the `pyproject.toml` file to use for configuration.
|
/// Path to the `pyproject.toml` or `ruff.toml` file to use for
|
||||||
|
/// configuration.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub config: Option<PathBuf>,
|
pub config: Option<PathBuf>,
|
||||||
/// Enable verbose logging.
|
/// Enable verbose logging.
|
||||||
|
|
|
@ -89,10 +89,10 @@ pub mod visibility;
|
||||||
|
|
||||||
/// Load the relevant `Settings` for a given `Path`.
|
/// Load the relevant `Settings` for a given `Path`.
|
||||||
fn resolve(path: &Path) -> Result<Settings> {
|
fn resolve(path: &Path) -> Result<Settings> {
|
||||||
if let Some(pyproject) = pyproject::find_pyproject_toml(path)? {
|
if let Some(pyproject) = pyproject::find_settings_toml(path)? {
|
||||||
// First priority: `pyproject.toml` in the current `Path`.
|
// First priority: `pyproject.toml` in the current `Path`.
|
||||||
resolver::resolve_settings(&pyproject, &Relativity::Parent, None)
|
resolver::resolve_settings(&pyproject, &Relativity::Parent, None)
|
||||||
} else if let Some(pyproject) = pyproject::find_user_pyproject_toml() {
|
} else if let Some(pyproject) = pyproject::find_user_settings_toml() {
|
||||||
// Second priority: user-specific `pyproject.toml`.
|
// Second priority: user-specific `pyproject.toml`.
|
||||||
resolver::resolve_settings(&pyproject, &Relativity::Cwd, None)
|
resolver::resolve_settings(&pyproject, &Relativity::Cwd, None)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -46,7 +46,7 @@ fn resolve(
|
||||||
// current working directory. (This matches ESLint's behavior.)
|
// current working directory. (This matches ESLint's behavior.)
|
||||||
let settings = resolve_settings(pyproject, &Relativity::Cwd, Some(overrides))?;
|
let settings = resolve_settings(pyproject, &Relativity::Cwd, Some(overrides))?;
|
||||||
Ok(PyprojectDiscovery::Fixed(settings))
|
Ok(PyprojectDiscovery::Fixed(settings))
|
||||||
} else if let Some(pyproject) = pyproject::find_pyproject_toml(
|
} else if let Some(pyproject) = pyproject::find_settings_toml(
|
||||||
stdin_filename
|
stdin_filename
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or(&path_dedot::CWD.as_path()),
|
.unwrap_or(&path_dedot::CWD.as_path()),
|
||||||
|
@ -58,7 +58,7 @@ fn resolve(
|
||||||
// so these act as the "default" settings.)
|
// so these act as the "default" settings.)
|
||||||
let settings = resolve_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
let settings = resolve_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
||||||
Ok(PyprojectDiscovery::Hierarchical(settings))
|
Ok(PyprojectDiscovery::Hierarchical(settings))
|
||||||
} else if let Some(pyproject) = pyproject::find_user_pyproject_toml() {
|
} else if let Some(pyproject) = pyproject::find_user_settings_toml() {
|
||||||
// Third priority: find a user-specific `pyproject.toml`, but resolve all paths
|
// Third priority: find a user-specific `pyproject.toml`, but resolve all paths
|
||||||
// relative the current working directory. (With `Strategy::Hierarchical`, we'll
|
// relative the current working directory. (With `Strategy::Hierarchical`, we'll
|
||||||
// end up the "closest" `pyproject.toml` file for every Python file later on, so
|
// end up the "closest" `pyproject.toml` file for every Python file later on, so
|
||||||
|
|
|
@ -14,7 +14,7 @@ use rustc_hash::FxHashSet;
|
||||||
use crate::cli::Overrides;
|
use crate::cli::Overrides;
|
||||||
use crate::fs;
|
use crate::fs;
|
||||||
use crate::settings::configuration::Configuration;
|
use crate::settings::configuration::Configuration;
|
||||||
use crate::settings::pyproject::has_ruff_section;
|
use crate::settings::pyproject::settings_toml;
|
||||||
use crate::settings::{pyproject, Settings};
|
use crate::settings::{pyproject, Settings};
|
||||||
|
|
||||||
/// The strategy used to discover Python files in the filesystem..
|
/// The strategy used to discover Python files in the filesystem..
|
||||||
|
@ -221,16 +221,13 @@ pub fn python_files_in_path(
|
||||||
let mut resolver = Resolver::default();
|
let mut resolver = Resolver::default();
|
||||||
for path in &paths {
|
for path in &paths {
|
||||||
for ancestor in path.ancestors() {
|
for ancestor in path.ancestors() {
|
||||||
let pyproject = ancestor.join("pyproject.toml");
|
if let Some(pyproject) = settings_toml(ancestor)? {
|
||||||
if pyproject.is_file() {
|
|
||||||
if has_ruff_section(&pyproject)? {
|
|
||||||
let (root, settings) =
|
let (root, settings) =
|
||||||
resolve_scoped_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
resolve_scoped_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
||||||
resolver.add(root, settings);
|
resolver.add(root, settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the paths themselves are excluded.
|
// Check if the paths themselves are excluded.
|
||||||
if file_strategy.force_exclude {
|
if file_strategy.force_exclude {
|
||||||
|
@ -267,12 +264,8 @@ pub fn python_files_in_path(
|
||||||
.file_type()
|
.file_type()
|
||||||
.map_or(false, |file_type| file_type.is_dir())
|
.map_or(false, |file_type| file_type.is_dir())
|
||||||
{
|
{
|
||||||
let pyproject = entry.path().join("pyproject.toml");
|
match settings_toml(entry.path()) {
|
||||||
if pyproject.is_file() {
|
Ok(Some(pyproject)) => match resolve_scoped_settings(
|
||||||
match has_ruff_section(&pyproject) {
|
|
||||||
Ok(false) => {}
|
|
||||||
Ok(true) => {
|
|
||||||
match resolve_scoped_settings(
|
|
||||||
&pyproject,
|
&pyproject,
|
||||||
&Relativity::Parent,
|
&Relativity::Parent,
|
||||||
Some(overrides),
|
Some(overrides),
|
||||||
|
@ -284,8 +277,8 @@ pub fn python_files_in_path(
|
||||||
*error.lock().unwrap() = Err(err);
|
*error.lock().unwrap() = Err(err);
|
||||||
return WalkState::Quit;
|
return WalkState::Quit;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
Ok(None) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
*error.lock().unwrap() = Err(err);
|
*error.lock().unwrap() = Err(err);
|
||||||
return WalkState::Quit;
|
return WalkState::Quit;
|
||||||
|
@ -293,7 +286,6 @@ pub fn python_files_in_path(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Respect our own exclusion behavior.
|
// Respect our own exclusion behavior.
|
||||||
if let Ok(entry) = &result {
|
if let Ok(entry) = &result {
|
||||||
|
@ -357,15 +349,12 @@ pub fn python_file_at_path(
|
||||||
// Search for `pyproject.toml` files in all parent directories.
|
// Search for `pyproject.toml` files in all parent directories.
|
||||||
let mut resolver = Resolver::default();
|
let mut resolver = Resolver::default();
|
||||||
for ancestor in path.ancestors() {
|
for ancestor in path.ancestors() {
|
||||||
let pyproject = ancestor.join("pyproject.toml");
|
if let Some(pyproject) = settings_toml(ancestor)? {
|
||||||
if pyproject.is_file() {
|
|
||||||
if has_ruff_section(&pyproject)? {
|
|
||||||
let (root, settings) =
|
let (root, settings) =
|
||||||
resolve_scoped_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
resolve_scoped_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
||||||
resolver.add(root, settings);
|
resolver.add(root, settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Check exclusions.
|
// Check exclusions.
|
||||||
Ok(!is_file_excluded(&path, &resolver, pyproject_strategy))
|
Ok(!is_file_excluded(&path, &resolver, pyproject_strategy))
|
||||||
|
|
|
@ -66,8 +66,8 @@ pub struct Configuration {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Configuration {
|
impl Configuration {
|
||||||
pub fn from_pyproject(pyproject: &Path, project_root: &Path) -> Result<Self> {
|
pub fn from_toml(path: &Path, project_root: &Path) -> Result<Self> {
|
||||||
Self::from_options(load_options(pyproject)?, project_root)
|
Self::from_options(load_options(path)?, project_root)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_options(options: Options, project_root: &Path) -> Result<Self> {
|
pub fn from_options(options: Options, project_root: &Path) -> Result<Self> {
|
||||||
|
|
|
@ -28,53 +28,97 @@ impl Pyproject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a `ruff.toml` file.
|
||||||
|
fn parse_ruff_toml<P: AsRef<Path>>(path: P) -> Result<Options> {
|
||||||
|
let contents = fs::read_file(path)?;
|
||||||
|
toml::from_str(&contents).map_err(std::convert::Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a `pyproject.toml` file.
|
||||||
fn parse_pyproject_toml<P: AsRef<Path>>(path: P) -> Result<Pyproject> {
|
fn parse_pyproject_toml<P: AsRef<Path>>(path: P) -> Result<Pyproject> {
|
||||||
let contents = fs::read_file(path)?;
|
let contents = fs::read_file(path)?;
|
||||||
toml::from_str(&contents).map_err(std::convert::Into::into)
|
toml::from_str(&contents).map_err(std::convert::Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if a `pyproject.toml` contains a `[tool.ruff]` section.
|
/// Return `true` if a `pyproject.toml` contains a `[tool.ruff]` section.
|
||||||
pub fn has_ruff_section<P: AsRef<Path>>(path: P) -> Result<bool> {
|
pub fn ruff_enabled<P: AsRef<Path>>(path: P) -> Result<bool> {
|
||||||
let pyproject = parse_pyproject_toml(path)?;
|
let pyproject = parse_pyproject_toml(path)?;
|
||||||
Ok(pyproject.tool.and_then(|tool| tool.ruff).is_some())
|
Ok(pyproject.tool.and_then(|tool| tool.ruff).is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the path to the `pyproject.toml` file, if such a file exists.
|
/// Return the path to the `pyproject.toml` or `ruff.toml` file in a given
|
||||||
pub fn find_pyproject_toml<P: AsRef<Path>>(path: P) -> Result<Option<PathBuf>> {
|
/// directory.
|
||||||
|
pub fn settings_toml<P: AsRef<Path>>(path: P) -> Result<Option<PathBuf>> {
|
||||||
|
// Check for `ruff.toml`.
|
||||||
|
let ruff_toml = path.as_ref().join("ruff.toml");
|
||||||
|
if ruff_toml.is_file() {
|
||||||
|
return Ok(Some(ruff_toml));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for `pyproject.toml`.
|
||||||
|
let pyproject_toml = path.as_ref().join("pyproject.toml");
|
||||||
|
if pyproject_toml.is_file() && ruff_enabled(&pyproject_toml)? {
|
||||||
|
return Ok(Some(pyproject_toml));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the path to the `pyproject.toml` or `ruff.toml` file, if such a file
|
||||||
|
/// exists.
|
||||||
|
pub fn find_settings_toml<P: AsRef<Path>>(path: P) -> Result<Option<PathBuf>> {
|
||||||
for directory in path.as_ref().ancestors() {
|
for directory in path.as_ref().ancestors() {
|
||||||
let pyproject = directory.join("pyproject.toml");
|
if let Some(pyproject) = settings_toml(directory)? {
|
||||||
if pyproject.is_file() && has_ruff_section(&pyproject)? {
|
|
||||||
return Ok(Some(pyproject));
|
return Ok(Some(pyproject));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the path to the user-specific `pyproject.toml`, if it exists.
|
/// Find the path to the user-specific `pyproject.toml` or `ruff.toml`, if it
|
||||||
pub fn find_user_pyproject_toml() -> Option<PathBuf> {
|
/// exists.
|
||||||
|
pub fn find_user_settings_toml() -> Option<PathBuf> {
|
||||||
|
// Search for a user-specific `ruff.toml`.
|
||||||
|
let mut path = dirs::config_dir()?;
|
||||||
|
path.push("ruff");
|
||||||
|
path.push("ruff.toml");
|
||||||
|
if path.is_file() {
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for a user-specific `pyproject.toml`.
|
||||||
let mut path = dirs::config_dir()?;
|
let mut path = dirs::config_dir()?;
|
||||||
path.push("ruff");
|
path.push("ruff");
|
||||||
path.push("pyproject.toml");
|
path.push("pyproject.toml");
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
Some(path)
|
return Some(path);
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load `Options` from a `pyproject.toml`.
|
/// Load `Options` from a `pyproject.toml` or `ruff.toml` file.
|
||||||
pub fn load_options<P: AsRef<Path>>(pyproject: P) -> Result<Options> {
|
pub fn load_options<P: AsRef<Path>>(path: P) -> Result<Options> {
|
||||||
Ok(parse_pyproject_toml(&pyproject)
|
if path.as_ref().ends_with("ruff.toml") {
|
||||||
.map_err(|err| {
|
parse_ruff_toml(path)
|
||||||
|
} else if path.as_ref().ends_with("pyproject.toml") {
|
||||||
|
let pyproject = parse_pyproject_toml(&path).map_err(|err| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"Failed to parse `{}`: {}",
|
"Failed to parse `{}`: {}",
|
||||||
pyproject.as_ref().to_string_lossy(),
|
path.as_ref().to_string_lossy(),
|
||||||
err
|
err
|
||||||
)
|
)
|
||||||
})?
|
})?;
|
||||||
|
Ok(pyproject
|
||||||
.tool
|
.tool
|
||||||
.and_then(|tool| tool.ruff)
|
.and_then(|tool| tool.ruff)
|
||||||
.unwrap_or_default())
|
.unwrap_or_default())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(
|
||||||
|
"Unrecognized settings file: `{}`",
|
||||||
|
path.as_ref().to_string_lossy()
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -89,7 +133,7 @@ mod tests {
|
||||||
use crate::flake8_quotes::settings::Quote;
|
use crate::flake8_quotes::settings::Quote;
|
||||||
use crate::flake8_tidy_imports::settings::Strictness;
|
use crate::flake8_tidy_imports::settings::Strictness;
|
||||||
use crate::settings::pyproject::{
|
use crate::settings::pyproject::{
|
||||||
find_pyproject_toml, parse_pyproject_toml, Options, Pyproject, Tools,
|
find_settings_toml, parse_pyproject_toml, Options, Pyproject, Tools,
|
||||||
};
|
};
|
||||||
use crate::settings::types::PatternPrefixPair;
|
use crate::settings::types::PatternPrefixPair;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -399,14 +443,14 @@ other-attribute = 1
|
||||||
fn find_and_parse_pyproject_toml() -> Result<()> {
|
fn find_and_parse_pyproject_toml() -> Result<()> {
|
||||||
let cwd = current_dir()?;
|
let cwd = current_dir()?;
|
||||||
let pyproject =
|
let pyproject =
|
||||||
find_pyproject_toml(cwd.join("resources/test/fixtures/__init__.py"))?.unwrap();
|
find_settings_toml(cwd.join("resources/test/fixtures/__init__.py"))?.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pyproject,
|
pyproject,
|
||||||
cwd.join("resources/test/fixtures/pyproject.toml")
|
cwd.join("resources/test/fixtures/pyproject.toml")
|
||||||
);
|
);
|
||||||
|
|
||||||
let pyproject = parse_pyproject_toml(&pyproject)?;
|
let pyproject = parse_pyproject_toml(&pyproject)?;
|
||||||
let config = pyproject.tool.and_then(|tool| tool.ruff).unwrap();
|
let config = pyproject.tool.unwrap().ruff.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config,
|
config,
|
||||||
Options {
|
Options {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue