146 lines
5.7 KiB
Rust
146 lines
5.7 KiB
Rust
use std::path::Path;
|
|
|
|
use anyhow::{Result, bail};
|
|
use log::debug;
|
|
use path_absolutize::path_dedot;
|
|
|
|
use ruff_workspace::configuration::Configuration;
|
|
use ruff_workspace::pyproject::{self, find_fallback_target_version};
|
|
use ruff_workspace::resolver::{
|
|
ConfigurationOrigin, ConfigurationTransformer, PyprojectConfig, PyprojectDiscoveryStrategy,
|
|
resolve_root_settings,
|
|
};
|
|
|
|
use ruff_python_ast as ast;
|
|
|
|
use crate::args::ConfigArguments;
|
|
|
|
/// Resolve the relevant settings strategy and defaults for the current
|
|
/// invocation.
|
|
pub fn resolve(
|
|
config_arguments: &ConfigArguments,
|
|
stdin_filename: Option<&Path>,
|
|
) -> Result<PyprojectConfig> {
|
|
let Ok(cwd) = std::env::current_dir() else {
|
|
bail!("Working directory does not exist")
|
|
};
|
|
|
|
// First priority: if we're running in isolated mode, use the default settings.
|
|
if config_arguments.isolated {
|
|
let config = config_arguments.transform(Configuration::default());
|
|
let settings = config.into_settings(&cwd)?;
|
|
debug!("Isolated mode, not reading any pyproject.toml");
|
|
return Ok(PyprojectConfig::new(
|
|
PyprojectDiscoveryStrategy::Fixed,
|
|
settings,
|
|
None,
|
|
));
|
|
}
|
|
|
|
// Second priority: the user specified a `pyproject.toml` file. Use that
|
|
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
|
|
// current working directory. (This matches ESLint's behavior.)
|
|
if let Some(pyproject) = config_arguments.config_file() {
|
|
let settings = resolve_root_settings(
|
|
pyproject,
|
|
config_arguments,
|
|
ConfigurationOrigin::UserSpecified,
|
|
)?;
|
|
debug!(
|
|
"Using user-specified configuration file at: {}",
|
|
pyproject.display()
|
|
);
|
|
return Ok(PyprojectConfig::new(
|
|
PyprojectDiscoveryStrategy::Fixed,
|
|
settings,
|
|
Some(pyproject.to_path_buf()),
|
|
));
|
|
}
|
|
|
|
// Third priority: find a `pyproject.toml` file in either an ancestor of
|
|
// `stdin_filename` (if set) or the current working path all paths relative to
|
|
// that directory. (With `Strategy::Hierarchical`, we'll end up finding
|
|
// the "closest" `pyproject.toml` file for every Python file later on,
|
|
// so these act as the "default" settings.)
|
|
if let Some(pyproject) = pyproject::find_settings_toml(stdin_filename.unwrap_or(&cwd))? {
|
|
debug!(
|
|
"Using configuration file (via parent) at: {}",
|
|
pyproject.display()
|
|
);
|
|
let settings =
|
|
resolve_root_settings(&pyproject, config_arguments, ConfigurationOrigin::Ancestor)?;
|
|
return Ok(PyprojectConfig::new(
|
|
PyprojectDiscoveryStrategy::Hierarchical,
|
|
settings,
|
|
Some(pyproject),
|
|
));
|
|
}
|
|
|
|
// Fourth priority: find a user-specific `pyproject.toml`, but resolve all paths
|
|
// relative the current working directory. (With `Strategy::Hierarchical`, we'll
|
|
// end up the "closest" `pyproject.toml` file for every Python file later on, so
|
|
// these act as the "default" settings.)
|
|
if let Some(pyproject) = pyproject::find_user_settings_toml() {
|
|
struct FallbackTransformer<'a> {
|
|
arguments: &'a ConfigArguments,
|
|
}
|
|
|
|
impl ConfigurationTransformer for FallbackTransformer<'_> {
|
|
fn transform(&self, mut configuration: Configuration) -> Configuration {
|
|
// The `requires-python` constraint from the `pyproject.toml` takes precedence
|
|
// over the `target-version` from the user configuration.
|
|
let fallback = find_fallback_target_version(&*path_dedot::CWD);
|
|
if let Some(fallback) = fallback {
|
|
debug!("Derived `target-version` from found `requires-python`: {fallback:?}");
|
|
configuration.target_version = Some(fallback.into());
|
|
}
|
|
|
|
self.arguments.transform(configuration)
|
|
}
|
|
}
|
|
|
|
debug!(
|
|
"Using configuration file (via cwd) at: {}",
|
|
pyproject.display()
|
|
);
|
|
let settings = resolve_root_settings(
|
|
&pyproject,
|
|
&FallbackTransformer {
|
|
arguments: config_arguments,
|
|
},
|
|
ConfigurationOrigin::UserSettings,
|
|
)?;
|
|
return Ok(PyprojectConfig::new(
|
|
PyprojectDiscoveryStrategy::Hierarchical,
|
|
settings,
|
|
Some(pyproject),
|
|
));
|
|
}
|
|
|
|
// Fallback: load Ruff's default settings, and resolve all paths relative to the
|
|
// current working directory. (With `Strategy::Hierarchical`, we'll end up the
|
|
// "closest" `pyproject.toml` file for every Python file later on, so these act
|
|
// as the "default" settings.)
|
|
debug!("Using Ruff default settings");
|
|
let mut config = config_arguments.transform(Configuration::default());
|
|
if config.target_version.is_none() {
|
|
// If we have arrived here we know that there was no `pyproject.toml`
|
|
// containing a `[tool.ruff]` section found in an ancestral directory.
|
|
// (This is an implicit requirement in the function
|
|
// `pyproject::find_settings_toml`.)
|
|
// However, there may be a `pyproject.toml` with a `requires-python`
|
|
// specified, and that is what we look for in this step.
|
|
let fallback = find_fallback_target_version(stdin_filename.unwrap_or(&cwd));
|
|
if let Some(version) = fallback {
|
|
debug!("Derived `target-version` from found `requires-python`: {version:?}");
|
|
}
|
|
config.target_version = fallback.map(ast::PythonVersion::from);
|
|
}
|
|
let settings = config.into_settings(&cwd)?;
|
|
Ok(PyprojectConfig::new(
|
|
PyprojectDiscoveryStrategy::Hierarchical,
|
|
settings,
|
|
None,
|
|
))
|
|
}
|