mirror of https://github.com/astral-sh/ruff
Merge e35fa57c85 into b0bc990cbf
This commit is contained in:
commit
9b075c2973
|
|
@ -76,7 +76,7 @@ linter.rules.should_fix = [
|
|||
linter.per_file_ignores = {}
|
||||
linter.safety_table.forced_safe = []
|
||||
linter.safety_table.forced_unsafe = []
|
||||
linter.unresolved_target_version = 3.10
|
||||
linter.unresolved_target_version = 3.11
|
||||
linter.per_file_target_version = {}
|
||||
linter.preview = disabled
|
||||
linter.explicit_preview_rules = false
|
||||
|
|
@ -264,7 +264,7 @@ linter.ruff.parenthesize_tuple_in_subscript = false
|
|||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
formatter.unresolved_target_version = 3.10
|
||||
formatter.unresolved_target_version = 3.11
|
||||
formatter.per_file_target_version = {}
|
||||
formatter.preview = disabled
|
||||
formatter.line_width = 88
|
||||
|
|
@ -279,7 +279,7 @@ formatter.docstring_code_line_width = dynamic
|
|||
# Analyze Settings
|
||||
analyze.exclude = []
|
||||
analyze.preview = disabled
|
||||
analyze.target_version = 3.10
|
||||
analyze.target_version = 3.11
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
|
|
|
|||
|
|
@ -497,7 +497,7 @@ impl Default for LinterSettings {
|
|||
/// unset. In contrast, we want to default to `PythonVersion::default()` for lint rules. These
|
||||
/// correspond to the [`TargetVersion::parser_version`] and [`TargetVersion::linter_version`]
|
||||
/// methods, respectively.
|
||||
#[derive(Debug, Clone, Copy, CacheKey)]
|
||||
#[derive(Debug, Clone, Copy, CacheKey, PartialEq, Eq)]
|
||||
pub struct TargetVersion(pub Option<PythonVersion>);
|
||||
|
||||
impl TargetVersion {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use std::str::FromStr;
|
|||
use anyhow::{Context, Result, anyhow};
|
||||
use glob::{GlobError, Paths, PatternError, glob};
|
||||
use itertools::Itertools;
|
||||
use log::debug;
|
||||
use regex::Regex;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use shellexpand;
|
||||
|
|
@ -54,6 +55,8 @@ use crate::options::{
|
|||
McCabeOptions, Options, Pep8NamingOptions, PyUpgradeOptions, PycodestyleOptions,
|
||||
PydoclintOptions, PydocstyleOptions, PyflakesOptions, PylintOptions, RuffOptions,
|
||||
};
|
||||
use crate::pyproject;
|
||||
use crate::resolver::ConfigurationOrigin;
|
||||
use crate::settings::{
|
||||
EXCLUDE, FileResolverSettings, FormatterSettings, INCLUDE, INCLUDE_PREVIEW, LineEnding,
|
||||
Settings,
|
||||
|
|
@ -626,6 +629,23 @@ impl Configuration {
|
|||
analyze: self.analyze.combine(config.analyze),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn apply_fallbacks(
|
||||
mut self,
|
||||
origin: ConfigurationOrigin,
|
||||
initial_config_path: &Path,
|
||||
) -> Self {
|
||||
if matches!(origin, ConfigurationOrigin::Ancestor) {
|
||||
self.target_version = self.target_version.or_else(|| {
|
||||
let dir = initial_config_path.parent()?;
|
||||
let fallback = pyproject::find_fallback_target_version(dir)?;
|
||||
debug!("Derived `target-version` from `requires-python`: {fallback:?}");
|
||||
Some(fallback.into())
|
||||
});
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
|
|
|||
|
|
@ -135,10 +135,7 @@ pub fn find_user_settings_toml() -> Option<PathBuf> {
|
|||
}
|
||||
|
||||
/// Load `Options` from a `pyproject.toml` or `ruff.toml` file.
|
||||
pub(super) fn load_options<P: AsRef<Path>>(
|
||||
path: P,
|
||||
version_strategy: &TargetVersionStrategy,
|
||||
) -> Result<Options> {
|
||||
pub(super) fn load_options<P: AsRef<Path>>(path: P) -> Result<Options> {
|
||||
let path = path.as_ref();
|
||||
if path.ends_with("pyproject.toml") {
|
||||
let pyproject = parse_pyproject_toml(path)?;
|
||||
|
|
@ -155,32 +152,16 @@ pub(super) fn load_options<P: AsRef<Path>>(
|
|||
}
|
||||
Ok(ruff)
|
||||
} else {
|
||||
let mut ruff = parse_ruff_toml(path);
|
||||
if let Ok(ref mut ruff) = ruff {
|
||||
let ruff = parse_ruff_toml(path);
|
||||
if let Ok(ruff) = ruff {
|
||||
if ruff.target_version.is_none() {
|
||||
debug!("No `target-version` found in `ruff.toml`");
|
||||
match version_strategy {
|
||||
TargetVersionStrategy::UseDefault => {}
|
||||
TargetVersionStrategy::RequiresPythonFallback => {
|
||||
if let Some(dir) = path.parent() {
|
||||
let fallback = get_fallback_target_version(dir);
|
||||
if let Some(version) = fallback {
|
||||
debug!(
|
||||
"Derived `target-version` from `requires-python` in `pyproject.toml`: {version:?}"
|
||||
);
|
||||
debug!("No `target-version` found in `{}`", path.display());
|
||||
}
|
||||
Ok(ruff)
|
||||
} else {
|
||||
debug!(
|
||||
"No `pyproject.toml` with `requires-python` in same directory; `target-version` unspecified"
|
||||
);
|
||||
}
|
||||
ruff.target_version = fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ruff
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract `target-version` from `pyproject.toml` in the given directory
|
||||
|
|
@ -240,15 +221,6 @@ fn get_minimum_supported_version(requires_version: &VersionSpecifiers) -> Option
|
|||
PythonVersion::iter().find(|version| Version::from(*version) == minimum_version)
|
||||
}
|
||||
|
||||
/// Strategy for handling missing `target-version` in configuration.
|
||||
#[derive(Debug)]
|
||||
pub(super) enum TargetVersionStrategy {
|
||||
/// Use default `target-version`
|
||||
UseDefault,
|
||||
/// Derive from `requires-python` if available
|
||||
RequiresPythonFallback,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use ruff_linter::package::PackageRoot;
|
|||
use ruff_linter::packaging::is_package;
|
||||
|
||||
use crate::configuration::Configuration;
|
||||
use crate::pyproject::{TargetVersionStrategy, settings_toml};
|
||||
use crate::pyproject::settings_toml;
|
||||
use crate::settings::Settings;
|
||||
use crate::{FileResolverSettings, pyproject};
|
||||
|
||||
|
|
@ -300,13 +300,13 @@ pub trait ConfigurationTransformer {
|
|||
// file at least twice (possibly more than twice, since we'll also parse it when
|
||||
// resolving the "default" configuration).
|
||||
pub fn resolve_configuration(
|
||||
pyproject: &Path,
|
||||
initial_config_path: &Path,
|
||||
transformer: &dyn ConfigurationTransformer,
|
||||
origin: ConfigurationOrigin,
|
||||
) -> Result<Configuration> {
|
||||
let relativity = Relativity::from(origin);
|
||||
let mut configurations = indexmap::IndexMap::new();
|
||||
let mut next = Some(fs::normalize_path(pyproject));
|
||||
let mut next = Some(fs::normalize_path(initial_config_path));
|
||||
while let Some(path) = next {
|
||||
if configurations.contains_key(&path) {
|
||||
bail!(format!(
|
||||
|
|
@ -319,20 +319,7 @@ pub fn resolve_configuration(
|
|||
));
|
||||
}
|
||||
|
||||
// Resolve the current path.
|
||||
let version_strategy =
|
||||
if configurations.is_empty() && matches!(origin, ConfigurationOrigin::Ancestor) {
|
||||
// For configurations that are discovered by
|
||||
// walking back from a file, we will attempt to
|
||||
// infer the `target-version` if it is missing
|
||||
TargetVersionStrategy::RequiresPythonFallback
|
||||
} else {
|
||||
// In all other cases (e.g. for configurations
|
||||
// inherited via `extend`, or user-level settings)
|
||||
// we do not attempt to infer a missing `target-version`
|
||||
TargetVersionStrategy::UseDefault
|
||||
};
|
||||
let options = pyproject::load_options(&path, &version_strategy).with_context(|| {
|
||||
let options = pyproject::load_options(&path).with_context(|| {
|
||||
if configurations.is_empty() {
|
||||
format!(
|
||||
"Failed to load configuration `{path}`",
|
||||
|
|
@ -374,6 +361,9 @@ pub fn resolve_configuration(
|
|||
for extend in configurations {
|
||||
configuration = configuration.combine(extend);
|
||||
}
|
||||
|
||||
let configuration = configuration.apply_fallbacks(origin, initial_config_path);
|
||||
|
||||
Ok(transformer.transform(configuration))
|
||||
}
|
||||
|
||||
|
|
@ -944,7 +934,10 @@ mod tests {
|
|||
use path_absolutize::Absolutize;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use ruff_linter::settings::types::{FilePattern, GlobPath};
|
||||
use ruff_linter::settings::{
|
||||
TargetVersion,
|
||||
types::{FilePattern, GlobPath, PythonVersion},
|
||||
};
|
||||
|
||||
use crate::configuration::Configuration;
|
||||
use crate::pyproject::find_settings_toml;
|
||||
|
|
@ -1131,4 +1124,47 @@ mod tests {
|
|||
&make_exclusion(exclude),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extend_respects_target_version() -> Result<()> {
|
||||
let tmp_dir = TempDir::new()?;
|
||||
let root = tmp_dir.path();
|
||||
|
||||
let ruff_toml = root.join("ruff.toml");
|
||||
std::fs::write(&ruff_toml, "target-version = \"py310\"")?;
|
||||
|
||||
let dot_ruff_toml = root.join(".ruff.toml");
|
||||
std::fs::write(&dot_ruff_toml, "extend = \"ruff.toml\"")?;
|
||||
|
||||
let pyproject_toml = root.join("pyproject.toml");
|
||||
std::fs::write(
|
||||
&pyproject_toml,
|
||||
r#"[project]
|
||||
name = "repro-ruff"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let main_py = root.join("main.py");
|
||||
std::fs::write(
|
||||
&main_py,
|
||||
r#"from typing import TypeAlias
|
||||
|
||||
A: TypeAlias = str | int
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let settings = resolve_root_settings(
|
||||
&dot_ruff_toml,
|
||||
&NoOpTransformer,
|
||||
ConfigurationOrigin::Ancestor,
|
||||
)?;
|
||||
assert_eq!(
|
||||
settings.linter.unresolved_target_version,
|
||||
TargetVersion(Some(PythonVersion::Py310.into()))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue