mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:25:17 +00:00
Infer target-version from project metadata (#3470)
* Infer target-version from project metadata * Fix requires-python with ">=3.8.16" * Load requires-python at runtime * Use upstream VersionSpecifiers * Add debug information when parsing ruff.toml * Display debug only if target_version is not set * Bump pep440-rs to add impl Error for Pep440Error
This commit is contained in:
parent
3a5fbd6d74
commit
b540407b74
10 changed files with 151 additions and 7 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -1536,6 +1536,18 @@ version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
|
checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pep440_rs"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "git+https://github.com/konstin/pep440-rs.git?rev=a8fef4ec47f4c25b070b39cdbe6a0b9847e49941#a8fef4ec47f4c25b070b39cdbe6a0b9847e49941"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"tracing",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
@ -1986,6 +1998,8 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"path-absolutize",
|
"path-absolutize",
|
||||||
"pathdiff",
|
"pathdiff",
|
||||||
|
"pep440_rs",
|
||||||
|
"pretty_assertions",
|
||||||
"regex",
|
"regex",
|
||||||
"result-like",
|
"result-like",
|
||||||
"ruff_cache",
|
"ruff_cache",
|
||||||
|
@ -2841,9 +2855,21 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"tracing-attributes",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-attributes"
|
||||||
|
version = "0.1.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.30"
|
version = "0.1.30"
|
||||||
|
|
|
@ -46,8 +46,15 @@ fn main() -> Result<()> {
|
||||||
.map(|tool| ExternalConfig {
|
.map(|tool| ExternalConfig {
|
||||||
black: tool.black.as_ref(),
|
black: tool.black.as_ref(),
|
||||||
isort: tool.isort.as_ref(),
|
isort: tool.isort.as_ref(),
|
||||||
|
..Default::default()
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
let external_config = ExternalConfig {
|
||||||
|
project: pyproject
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|pyproject| pyproject.project.as_ref()),
|
||||||
|
..external_config
|
||||||
|
};
|
||||||
|
|
||||||
// Create Ruff's pyproject.toml section.
|
// Create Ruff's pyproject.toml section.
|
||||||
let pyproject = flake8_to_ruff::convert(&config, &external_config, args.plugin)?;
|
let pyproject = flake8_to_ruff::convert(&config, &external_config, args.plugin)?;
|
||||||
|
|
|
@ -47,6 +47,9 @@ path-absolutize = { workspace = true, features = [
|
||||||
"use_unix_paths_on_wasm",
|
"use_unix_paths_on_wasm",
|
||||||
] }
|
] }
|
||||||
pathdiff = { version = "0.2.1" }
|
pathdiff = { version = "0.2.1" }
|
||||||
|
pep440_rs = { git = "https://github.com/konstin/pep440-rs.git", features = [
|
||||||
|
"serde",
|
||||||
|
], rev = "a8fef4ec47f4c25b070b39cdbe6a0b9847e49941" }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
result-like = { version = "0.4.6" }
|
result-like = { version = "0.4.6" }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
|
@ -64,9 +67,10 @@ thiserror = { version = "1.0.38" }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = { workspace = true, features = ["yaml", "redactions"] }
|
|
||||||
test-case = { workspace = true }
|
|
||||||
criterion = { version = "0.4.0" }
|
criterion = { version = "0.4.0" }
|
||||||
|
insta = { workspace = true, features = ["yaml", "redactions"] }
|
||||||
|
pretty_assertions = "1.3.0"
|
||||||
|
test-case = { workspace = true }
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -20,6 +20,7 @@ use crate::rules::{
|
||||||
};
|
};
|
||||||
use crate::settings::options::Options;
|
use crate::settings::options::Options;
|
||||||
use crate::settings::pyproject::Pyproject;
|
use crate::settings::pyproject::Pyproject;
|
||||||
|
use crate::settings::types::PythonVersion;
|
||||||
use crate::warn_user;
|
use crate::warn_user;
|
||||||
|
|
||||||
const DEFAULT_SELECTORS: &[RuleSelector] = &[
|
const DEFAULT_SELECTORS: &[RuleSelector] = &[
|
||||||
|
@ -424,6 +425,15 @@ pub fn convert(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(project) = &external_config.project {
|
||||||
|
if let Some(requires_python) = &project.requires_python {
|
||||||
|
if options.target_version.is_none() {
|
||||||
|
options.target_version =
|
||||||
|
PythonVersion::get_minimum_supported_version(requires_python);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create the pyproject.toml.
|
// Create the pyproject.toml.
|
||||||
Ok(Pyproject::new(options))
|
Ok(Pyproject::new(options))
|
||||||
}
|
}
|
||||||
|
@ -439,13 +449,17 @@ fn resolve_select(plugins: &[Plugin]) -> HashSet<RuleSelector> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use pep440_rs::VersionSpecifiers;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use super::super::plugin::Plugin;
|
use super::super::plugin::Plugin;
|
||||||
use super::convert;
|
use super::convert;
|
||||||
use crate::flake8_to_ruff::converter::DEFAULT_SELECTORS;
|
use crate::flake8_to_ruff::converter::DEFAULT_SELECTORS;
|
||||||
|
use crate::flake8_to_ruff::pep621::Project;
|
||||||
use crate::flake8_to_ruff::ExternalConfig;
|
use crate::flake8_to_ruff::ExternalConfig;
|
||||||
use crate::registry::Linter;
|
use crate::registry::Linter;
|
||||||
use crate::rule_selector::RuleSelector;
|
use crate::rule_selector::RuleSelector;
|
||||||
|
@ -453,6 +467,7 @@ mod tests {
|
||||||
use crate::rules::{flake8_quotes, pydocstyle};
|
use crate::rules::{flake8_quotes, pydocstyle};
|
||||||
use crate::settings::options::Options;
|
use crate::settings::options::Options;
|
||||||
use crate::settings::pyproject::Pyproject;
|
use crate::settings::pyproject::Pyproject;
|
||||||
|
use crate::settings::types::PythonVersion;
|
||||||
|
|
||||||
fn default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> Options {
|
fn default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> Options {
|
||||||
Options {
|
Options {
|
||||||
|
@ -609,4 +624,25 @@ mod tests {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_converts_project_requires_python() -> Result<()> {
|
||||||
|
let actual = convert(
|
||||||
|
&HashMap::from([("flake8".to_string(), HashMap::default())]),
|
||||||
|
&ExternalConfig {
|
||||||
|
project: Some(&Project {
|
||||||
|
requires_python: Some(VersionSpecifiers::from_str(">=3.8.16, <3.11")?),
|
||||||
|
}),
|
||||||
|
..ExternalConfig::default()
|
||||||
|
},
|
||||||
|
Some(vec![]),
|
||||||
|
)?;
|
||||||
|
let expected = Pyproject::new(Options {
|
||||||
|
target_version: Some(PythonVersion::Py38),
|
||||||
|
..default_options([])
|
||||||
|
});
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use super::black::Black;
|
use super::black::Black;
|
||||||
use super::isort::Isort;
|
use super::isort::Isort;
|
||||||
|
use super::pep621::Project;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ExternalConfig<'a> {
|
pub struct ExternalConfig<'a> {
|
||||||
pub black: Option<&'a Black>,
|
pub black: Option<&'a Black>,
|
||||||
pub isort: Option<&'a Isort>,
|
pub isort: Option<&'a Isort>,
|
||||||
|
pub project: Option<&'a Project>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ mod converter;
|
||||||
mod external_config;
|
mod external_config;
|
||||||
mod isort;
|
mod isort;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
pub mod pep621;
|
||||||
mod plugin;
|
mod plugin;
|
||||||
mod pyproject;
|
mod pyproject;
|
||||||
|
|
||||||
|
|
10
crates/ruff/src/flake8_to_ruff/pep621.rs
Normal file
10
crates/ruff/src/flake8_to_ruff/pep621.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
//! Extract PEP 621 configuration settings from a pyproject.toml.
|
||||||
|
|
||||||
|
use pep440_rs::VersionSpecifiers;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Project {
|
||||||
|
#[serde(alias = "requires-python", alias = "requires_python")]
|
||||||
|
pub requires_python: Option<VersionSpecifiers>,
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::black::Black;
|
use super::black::Black;
|
||||||
use super::isort::Isort;
|
use super::isort::Isort;
|
||||||
|
use super::pep621::Project;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Tools {
|
pub struct Tools {
|
||||||
|
@ -15,6 +16,7 @@ pub struct Tools {
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Pyproject {
|
pub struct Pyproject {
|
||||||
pub tool: Option<Tools>,
|
pub tool: Option<Tools>,
|
||||||
|
pub project: Option<Project>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse<P: AsRef<Path>>(path: P) -> Result<Pyproject> {
|
pub fn parse<P: AsRef<Path>>(path: P) -> Result<Pyproject> {
|
||||||
|
|
|
@ -3,18 +3,22 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use log::debug;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::flake8_to_ruff::pep621::Project;
|
||||||
use crate::settings::options::Options;
|
use crate::settings::options::Options;
|
||||||
|
use crate::settings::types::PythonVersion;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
struct Tools {
|
struct Tools {
|
||||||
ruff: Option<Options>,
|
ruff: Option<Options>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
pub struct Pyproject {
|
pub struct Pyproject {
|
||||||
tool: Option<Tools>,
|
tool: Option<Tools>,
|
||||||
|
project: Option<Project>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pyproject {
|
impl Pyproject {
|
||||||
|
@ -23,6 +27,7 @@ impl Pyproject {
|
||||||
tool: Some(Tools {
|
tool: Some(Tools {
|
||||||
ruff: Some(options),
|
ruff: Some(options),
|
||||||
}),
|
}),
|
||||||
|
project: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,12 +119,27 @@ pub fn find_user_settings_toml() -> Option<PathBuf> {
|
||||||
pub fn load_options<P: AsRef<Path>>(path: P) -> Result<Options> {
|
pub fn load_options<P: AsRef<Path>>(path: P) -> Result<Options> {
|
||||||
if path.as_ref().ends_with("pyproject.toml") {
|
if path.as_ref().ends_with("pyproject.toml") {
|
||||||
let pyproject = parse_pyproject_toml(&path)?;
|
let pyproject = parse_pyproject_toml(&path)?;
|
||||||
Ok(pyproject
|
let mut ruff = pyproject
|
||||||
.tool
|
.tool
|
||||||
.and_then(|tool| tool.ruff)
|
.and_then(|tool| tool.ruff)
|
||||||
.unwrap_or_default())
|
.unwrap_or_default();
|
||||||
|
if ruff.target_version.is_none() {
|
||||||
|
if let Some(project) = pyproject.project {
|
||||||
|
if let Some(requires_python) = project.requires_python {
|
||||||
|
ruff.target_version =
|
||||||
|
PythonVersion::get_minimum_supported_version(&requires_python);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ruff)
|
||||||
} else {
|
} else {
|
||||||
parse_ruff_toml(path)
|
let ruff = parse_ruff_toml(path);
|
||||||
|
if let Ok(ruff) = &ruff {
|
||||||
|
if ruff.target_version.is_none() {
|
||||||
|
debug!("`project.requires_python` in `pyproject.toml` will not be used to set `target_version` when using `ruff.toml`.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ruff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,22 +2,37 @@ use std::hash::{Hash, Hasher};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::string::ToString;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
use pep440_rs::{Version as Pep440Version, VersionSpecifiers};
|
||||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||||
use ruff_macros::CacheKey;
|
use ruff_macros::CacheKey;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::rule_selector::RuleSelector;
|
use crate::rule_selector::RuleSelector;
|
||||||
use crate::{fs, warn_user_once};
|
use crate::{fs, warn_user_once};
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, JsonSchema, CacheKey,
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
JsonSchema,
|
||||||
|
CacheKey,
|
||||||
|
EnumIter,
|
||||||
)]
|
)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum PythonVersion {
|
pub enum PythonVersion {
|
||||||
|
@ -50,6 +65,13 @@ impl FromStr for PythonVersion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<PythonVersion> for Pep440Version {
|
||||||
|
fn from(version: PythonVersion) -> Self {
|
||||||
|
let (major, minor) = version.as_tuple();
|
||||||
|
Self::from_str(&format!("{major}.{minor}.100")).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PythonVersion {
|
impl PythonVersion {
|
||||||
pub const fn as_tuple(&self) -> (u32, u32) {
|
pub const fn as_tuple(&self) -> (u32, u32) {
|
||||||
match self {
|
match self {
|
||||||
|
@ -60,6 +82,20 @@ impl PythonVersion {
|
||||||
Self::Py311 => (3, 11),
|
Self::Py311 => (3, 11),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_minimum_supported_version(requires_version: &VersionSpecifiers) -> Option<Self> {
|
||||||
|
let mut minimum_version = None;
|
||||||
|
for python_version in PythonVersion::iter() {
|
||||||
|
if requires_version
|
||||||
|
.iter()
|
||||||
|
.all(|specifier| specifier.contains(&python_version.into()))
|
||||||
|
{
|
||||||
|
minimum_version = Some(python_version);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
minimum_version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, CacheKey, PartialEq, PartialOrd, Eq, Ord)]
|
#[derive(Debug, Clone, CacheKey, PartialEq, PartialOrd, Eq, Ord)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue