mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 04:45:01 +00:00
Use ast::PythonVersion
internally in the formatter and linter (#16170)
## Summary This PR updates the formatter and linter to use the `PythonVersion` struct from the `ruff_python_ast` crate internally. While this doesn't remove the need for the `linter::PythonVersion` enum, it does remove the `formatter::PythonVersion` enum and limits the use in the linter to deserializing from CLI arguments and config files and moves most of the remaining methods to the `ast::PythonVersion` struct. ## Test Plan Existing tests, with some inputs and outputs updated to reflect the new (de)serialization format. I think these are test-specific and shouldn't affect any external (de)serialization. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
0868e73d2c
commit
a9efdea113
153 changed files with 456 additions and 539 deletions
|
@ -11,6 +11,7 @@ use std::sync::LazyLock;
|
|||
|
||||
use crate::codes::RuleCodePrefix;
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use crate::line_width::LineLength;
|
||||
use crate::registry::{Linter, Rule};
|
||||
|
@ -21,9 +22,7 @@ use crate::rules::{
|
|||
flake8_self, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe,
|
||||
pep8_naming, pycodestyle, pydoclint, pydocstyle, pyflakes, pylint, pyupgrade, ruff,
|
||||
};
|
||||
use crate::settings::types::{
|
||||
CompiledPerFileIgnoreList, ExtensionMapping, FilePatternSet, PythonVersion,
|
||||
};
|
||||
use crate::settings::types::{CompiledPerFileIgnoreList, ExtensionMapping, FilePatternSet};
|
||||
use crate::{codes, RuleSelector};
|
||||
|
||||
use super::line_width::IndentWidth;
|
||||
|
@ -282,7 +281,7 @@ impl Display for LinterSettings {
|
|||
self.per_file_ignores,
|
||||
self.fix_safety | nested,
|
||||
|
||||
self.target_version | debug,
|
||||
self.target_version,
|
||||
self.preview,
|
||||
self.explicit_preview_rules,
|
||||
self.extension | debug,
|
||||
|
|
|
@ -7,55 +7,66 @@ use std::string::ToString;
|
|||
|
||||
use anyhow::{bail, Result};
|
||||
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
|
||||
use log::debug;
|
||||
use pep440_rs::{Operator, Version as Pep440Version, Version, VersionSpecifier, VersionSpecifiers};
|
||||
use pep440_rs::{VersionSpecifier, VersionSpecifiers};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_ast::{self as ast, PySourceType};
|
||||
|
||||
use crate::registry::RuleSet;
|
||||
use crate::rule_selector::RuleSelector;
|
||||
use crate::{display_settings, fs};
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
CacheKey,
|
||||
EnumIter,
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, EnumIter)]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum PythonVersion {
|
||||
Py37,
|
||||
Py38,
|
||||
// Make sure to also change the default for `ruff_python_formatter::PythonVersion`
|
||||
// when changing the default here.
|
||||
#[default]
|
||||
Py39,
|
||||
Py310,
|
||||
Py311,
|
||||
Py312,
|
||||
Py313,
|
||||
// Remember to update the `latest()` function
|
||||
// when adding new versions here!
|
||||
}
|
||||
|
||||
impl From<PythonVersion> for Pep440Version {
|
||||
impl Default for PythonVersion {
|
||||
fn default() -> Self {
|
||||
// SAFETY: the unit test `default_python_version_works()` checks that this doesn't panic
|
||||
Self::try_from(ast::PythonVersion::default()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ast::PythonVersion> for PythonVersion {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: ast::PythonVersion) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
ast::PythonVersion::PY37 => Ok(Self::Py37),
|
||||
ast::PythonVersion::PY38 => Ok(Self::Py38),
|
||||
ast::PythonVersion::PY39 => Ok(Self::Py39),
|
||||
ast::PythonVersion::PY310 => Ok(Self::Py310),
|
||||
ast::PythonVersion::PY311 => Ok(Self::Py311),
|
||||
ast::PythonVersion::PY312 => Ok(Self::Py312),
|
||||
ast::PythonVersion::PY313 => Ok(Self::Py313),
|
||||
_ => Err(format!("unrecognized python version {value}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PythonVersion> for ast::PythonVersion {
|
||||
fn from(value: PythonVersion) -> Self {
|
||||
let (major, minor) = value.as_tuple();
|
||||
Self { major, minor }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PythonVersion> for pep440_rs::Version {
|
||||
fn from(version: PythonVersion) -> Self {
|
||||
let (major, minor) = version.as_tuple();
|
||||
Self::new([u64::from(major), u64::from(minor)])
|
||||
|
@ -63,15 +74,6 @@ impl From<PythonVersion> for Pep440Version {
|
|||
}
|
||||
|
||||
impl PythonVersion {
|
||||
/// Return the latest supported Python version.
|
||||
pub const fn latest() -> Self {
|
||||
Self::Py313
|
||||
}
|
||||
|
||||
pub const fn minimal_supported() -> Self {
|
||||
Self::Py37
|
||||
}
|
||||
|
||||
pub const fn as_tuple(&self) -> (u8, u8) {
|
||||
match self {
|
||||
Self::Py37 => (3, 7),
|
||||
|
@ -83,53 +85,6 @@ impl PythonVersion {
|
|||
Self::Py313 => (3, 13),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn major(&self) -> u8 {
|
||||
self.as_tuple().0
|
||||
}
|
||||
|
||||
pub const fn minor(&self) -> u8 {
|
||||
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> {
|
||||
/// Truncate a version to its major and minor components.
|
||||
fn major_minor(version: &Version) -> Option<Version> {
|
||||
let major = version.release().first()?;
|
||||
let minor = version.release().get(1)?;
|
||||
Some(Version::new([major, minor]))
|
||||
}
|
||||
|
||||
// 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].
|
||||
///
|
||||
/// [PEP 701]: https://peps.python.org/pep-0701/
|
||||
pub fn supports_pep701(self) -> bool {
|
||||
self >= Self::Py312
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, CacheKey, is_macro::Is)]
|
||||
|
@ -679,3 +634,11 @@ impl Deref for CompiledPerFileIgnoreList {
|
|||
&self.ignores
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn default_python_version_works() {
|
||||
super::PythonVersion::default();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue