mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 12:55:05 +00:00
Make requires-python
inference robust to ==
(#12091)
## Summary Instead of using a high patch version, attempt to detect the minimum-supported minor. Closes #12088.
This commit is contained in:
parent
434ce307a7
commit
c326778652
2 changed files with 217 additions and 12 deletions
|
@ -1618,3 +1618,189 @@ print(
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Infer `3.11` from `requires-python` in `pyproject.toml`.
|
||||||
|
#[test]
|
||||||
|
fn requires_python() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("pyproject.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"[project]
|
||||||
|
requires-python = ">= 3.11"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["UP006"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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_toml)
|
||||||
|
.args(["--stdin-filename", "test.py"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"from typing import List; foo: List[int]"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
test.py:1:31: UP006 [*] Use `list` instead of `List` for type annotation
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
let pyproject_toml = tempdir.path().join("pyproject.toml");
|
||||||
|
fs::write(
|
||||||
|
&pyproject_toml,
|
||||||
|
r#"[project]
|
||||||
|
requires-python = ">= 3.8"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["UP006"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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(&pyproject_toml)
|
||||||
|
.args(["--stdin-filename", "test.py"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"from typing import List; foo: List[int]"#), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Infer `3.11` from `requires-python` in `pyproject.toml`.
|
||||||
|
#[test]
|
||||||
|
fn requires_python_patch() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let pyproject_toml = tempdir.path().join("pyproject.toml");
|
||||||
|
fs::write(
|
||||||
|
&pyproject_toml,
|
||||||
|
r#"[project]
|
||||||
|
requires-python = ">= 3.11.4"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["UP006"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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(&pyproject_toml)
|
||||||
|
.args(["--stdin-filename", "test.py"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"from typing import List; foo: List[int]"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
test.py:1:31: UP006 [*] Use `list` instead of `List` for type annotation
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Infer `3.11` from `requires-python` in `pyproject.toml`.
|
||||||
|
#[test]
|
||||||
|
fn requires_python_equals() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let pyproject_toml = tempdir.path().join("pyproject.toml");
|
||||||
|
fs::write(
|
||||||
|
&pyproject_toml,
|
||||||
|
r#"[project]
|
||||||
|
requires-python = "== 3.11"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["UP006"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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(&pyproject_toml)
|
||||||
|
.args(["--stdin-filename", "test.py"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"from typing import List; foo: List[int]"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
test.py:1:31: UP006 [*] Use `list` instead of `List` for type annotation
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Infer `3.11` from `requires-python` in `pyproject.toml`.
|
||||||
|
#[test]
|
||||||
|
fn requires_python_equals_patch() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let pyproject_toml = tempdir.path().join("pyproject.toml");
|
||||||
|
fs::write(
|
||||||
|
&pyproject_toml,
|
||||||
|
r#"[project]
|
||||||
|
requires-python = "== 3.11.4"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["UP006"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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(&pyproject_toml)
|
||||||
|
.args(["--stdin-filename", "test.py"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"from typing import List; foo: List[int]"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
test.py:1:31: UP006 [*] Use `list` instead of `List` for type annotation
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,8 @@ use std::string::ToString;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
|
||||||
use pep440_rs::{Version as Pep440Version, VersionSpecifier, VersionSpecifiers};
|
use log::debug;
|
||||||
|
use pep440_rs::{Operator, Version as Pep440Version, Version, VersionSpecifier, VersionSpecifiers};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
@ -59,7 +60,7 @@ pub enum PythonVersion {
|
||||||
impl From<PythonVersion> for Pep440Version {
|
impl From<PythonVersion> for Pep440Version {
|
||||||
fn from(version: PythonVersion) -> Self {
|
fn from(version: PythonVersion) -> Self {
|
||||||
let (major, minor) = version.as_tuple();
|
let (major, minor) = version.as_tuple();
|
||||||
Self::from_str(&format!("{major}.{minor}.100")).unwrap()
|
Self::new([u64::from(major), u64::from(minor)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,18 +90,36 @@ impl PythonVersion {
|
||||||
self.as_tuple().1
|
self.as_tuple().1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Infer the minimum supported [`PythonVersion`] from a `requires-python` specifier.
|
||||||
pub fn get_minimum_supported_version(requires_version: &VersionSpecifiers) -> Option<Self> {
|
pub fn get_minimum_supported_version(requires_version: &VersionSpecifiers) -> Option<Self> {
|
||||||
let mut minimum_version = None;
|
/// Truncate a version to its major and minor components.
|
||||||
for python_version in PythonVersion::iter() {
|
fn major_minor(version: &Version) -> Option<Version> {
|
||||||
if requires_version
|
let major = version.release().first()?;
|
||||||
.iter()
|
let minor = version.release().get(1)?;
|
||||||
.all(|specifier| specifier.contains(&python_version.into()))
|
Some(Version::new([major, minor]))
|
||||||
{
|
|
||||||
minimum_version = Some(python_version);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
minimum_version
|
|
||||||
|
// Extract the minimum supported version from the specifiers.
|
||||||
|
let minimum_version = requires_version
|
||||||
|
.iter()
|
||||||
|
.filter(|specifier| {
|
||||||
|
matches!(
|
||||||
|
specifier.operator(),
|
||||||
|
Operator::Equal
|
||||||
|
| Operator::EqualStar
|
||||||
|
| Operator::ExactEqual
|
||||||
|
| Operator::TildeEqual
|
||||||
|
| Operator::GreaterThan
|
||||||
|
| Operator::GreaterThanEqual
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.filter_map(|specifier| major_minor(specifier.version()))
|
||||||
|
.min()?;
|
||||||
|
|
||||||
|
debug!("Detected minimum supported `requires-python` version: {minimum_version}");
|
||||||
|
|
||||||
|
// Find the Python version that matches the minimum supported version.
|
||||||
|
PythonVersion::iter().find(|version| Version::from(*version) == minimum_version)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the current version supports [PEP 701].
|
/// Return `true` if the current version supports [PEP 701].
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue