Decouple resolver module from cli::Overrides
This commit is contained in:
committed by
Charlie Marsh
parent
bbc1e7804e
commit
74f14182ea
84
src/cli.rs
84
src/cli.rs
@@ -4,12 +4,13 @@ use clap::{command, Parser};
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::fs;
|
||||
use crate::logging::LogLevel;
|
||||
use crate::registry::{RuleCode, RuleCodePrefix};
|
||||
use crate::resolver::ConfigProcessor;
|
||||
use crate::settings::types::{
|
||||
FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion, SerializationFormat,
|
||||
};
|
||||
use crate::{fs, mccabe};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, about = "Ruff: An extremely fast Python linter.")]
|
||||
@@ -344,6 +345,87 @@ pub struct Overrides {
|
||||
pub update_check: Option<bool>,
|
||||
}
|
||||
|
||||
impl ConfigProcessor for &Overrides {
|
||||
fn process_config(&self, config: &mut crate::settings::configuration::Configuration) {
|
||||
if let Some(cache_dir) = &self.cache_dir {
|
||||
config.cache_dir = Some(cache_dir.clone());
|
||||
}
|
||||
if let Some(dummy_variable_rgx) = &self.dummy_variable_rgx {
|
||||
config.dummy_variable_rgx = Some(dummy_variable_rgx.clone());
|
||||
}
|
||||
if let Some(exclude) = &self.exclude {
|
||||
config.exclude = Some(exclude.clone());
|
||||
}
|
||||
if let Some(extend_exclude) = &self.extend_exclude {
|
||||
config.extend_exclude.extend(extend_exclude.clone());
|
||||
}
|
||||
if let Some(fix) = &self.fix {
|
||||
config.fix = Some(*fix);
|
||||
}
|
||||
if let Some(fix_only) = &self.fix_only {
|
||||
config.fix_only = Some(*fix_only);
|
||||
}
|
||||
if let Some(fixable) = &self.fixable {
|
||||
config.fixable = Some(fixable.clone());
|
||||
}
|
||||
if let Some(format) = &self.format {
|
||||
config.format = Some(*format);
|
||||
}
|
||||
if let Some(force_exclude) = &self.force_exclude {
|
||||
config.force_exclude = Some(*force_exclude);
|
||||
}
|
||||
if let Some(ignore) = &self.ignore {
|
||||
config.ignore = Some(ignore.clone());
|
||||
}
|
||||
if let Some(line_length) = &self.line_length {
|
||||
config.line_length = Some(*line_length);
|
||||
}
|
||||
if let Some(max_complexity) = &self.max_complexity {
|
||||
config.mccabe = Some(mccabe::settings::Options {
|
||||
max_complexity: Some(*max_complexity),
|
||||
});
|
||||
}
|
||||
if let Some(per_file_ignores) = &self.per_file_ignores {
|
||||
config.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone()));
|
||||
}
|
||||
if let Some(respect_gitignore) = &self.respect_gitignore {
|
||||
config.respect_gitignore = Some(*respect_gitignore);
|
||||
}
|
||||
if let Some(select) = &self.select {
|
||||
config.select = Some(select.clone());
|
||||
}
|
||||
if let Some(show_source) = &self.show_source {
|
||||
config.show_source = Some(*show_source);
|
||||
}
|
||||
if let Some(target_version) = &self.target_version {
|
||||
config.target_version = Some(*target_version);
|
||||
}
|
||||
if let Some(unfixable) = &self.unfixable {
|
||||
config.unfixable = Some(unfixable.clone());
|
||||
}
|
||||
if let Some(update_check) = &self.update_check {
|
||||
config.update_check = Some(*update_check);
|
||||
}
|
||||
// Special-case: `extend_ignore` and `extend_select` are parallel arrays, so
|
||||
// push an empty array if only one of the two is provided.
|
||||
match (&self.extend_ignore, &self.extend_select) {
|
||||
(Some(extend_ignore), Some(extend_select)) => {
|
||||
config.extend_ignore.push(extend_ignore.clone());
|
||||
config.extend_select.push(extend_select.clone());
|
||||
}
|
||||
(Some(extend_ignore), None) => {
|
||||
config.extend_ignore.push(extend_ignore.clone());
|
||||
config.extend_select.push(Vec::new());
|
||||
}
|
||||
(None, Some(extend_select)) => {
|
||||
config.extend_ignore.push(Vec::new());
|
||||
config.extend_select.push(extend_select.clone());
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Map the CLI settings to a `LogLevel`.
|
||||
pub fn extract_log_level(cli: &Arguments) -> LogLevel {
|
||||
if cli.silent {
|
||||
|
||||
@@ -17,10 +17,10 @@ use crate::{directives, packaging, resolver};
|
||||
fn resolve(path: &Path) -> Result<Settings> {
|
||||
if let Some(pyproject) = pyproject::find_settings_toml(path)? {
|
||||
// First priority: `pyproject.toml` in the current `Path`.
|
||||
resolver::resolve_settings(&pyproject, &Relativity::Parent, None)
|
||||
resolver::resolve_settings(&pyproject, &Relativity::Parent)
|
||||
} else if let Some(pyproject) = pyproject::find_user_settings_toml() {
|
||||
// Second priority: user-specific `pyproject.toml`.
|
||||
resolver::resolve_settings(&pyproject, &Relativity::Cwd, None)
|
||||
resolver::resolve_settings(&pyproject, &Relativity::Cwd)
|
||||
} else {
|
||||
// Fallback: default settings.
|
||||
Settings::from_configuration(Configuration::default(), &path_dedot::CWD)
|
||||
|
||||
@@ -6,7 +6,9 @@ use std::sync::mpsc::channel;
|
||||
use ::ruff::cli::{extract_log_level, Cli, Overrides};
|
||||
use ::ruff::logging::{set_up_logging, LogLevel};
|
||||
use ::ruff::printer::{Printer, Violations};
|
||||
use ::ruff::resolver::{resolve_settings, FileDiscovery, PyprojectDiscovery, Relativity};
|
||||
use ::ruff::resolver::{
|
||||
resolve_settings_with_processor, ConfigProcessor, FileDiscovery, PyprojectDiscovery, Relativity,
|
||||
};
|
||||
use ::ruff::settings::configuration::Configuration;
|
||||
use ::ruff::settings::types::SerializationFormat;
|
||||
use ::ruff::settings::{pyproject, Settings};
|
||||
@@ -30,14 +32,14 @@ fn resolve(
|
||||
if isolated {
|
||||
// First priority: if we're running in isolated mode, use the default settings.
|
||||
let mut config = Configuration::default();
|
||||
config.apply(overrides.clone());
|
||||
overrides.process_config(&mut config);
|
||||
let settings = Settings::from_configuration(config, &path_dedot::CWD)?;
|
||||
Ok(PyprojectDiscovery::Fixed(settings))
|
||||
} else if let Some(pyproject) = config {
|
||||
// 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.)
|
||||
let settings = resolve_settings(pyproject, &Relativity::Cwd, Some(overrides))?;
|
||||
let settings = resolve_settings_with_processor(pyproject, &Relativity::Cwd, overrides)?;
|
||||
Ok(PyprojectDiscovery::Fixed(settings))
|
||||
} else if let Some(pyproject) = pyproject::find_settings_toml(
|
||||
stdin_filename
|
||||
@@ -49,14 +51,14 @@ fn resolve(
|
||||
// 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.)
|
||||
let settings = resolve_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
||||
let settings = resolve_settings_with_processor(&pyproject, &Relativity::Parent, overrides)?;
|
||||
Ok(PyprojectDiscovery::Hierarchical(settings))
|
||||
} else if let Some(pyproject) = pyproject::find_user_settings_toml() {
|
||||
// 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.)
|
||||
let settings = resolve_settings(&pyproject, &Relativity::Cwd, Some(overrides))?;
|
||||
let settings = resolve_settings_with_processor(&pyproject, &Relativity::Cwd, overrides)?;
|
||||
Ok(PyprojectDiscovery::Hierarchical(settings))
|
||||
} else {
|
||||
// Fallback: load Ruff's default settings, and resolve all paths relative to the
|
||||
@@ -64,7 +66,7 @@ fn resolve(
|
||||
// "closest" `pyproject.toml` file for every Python file later on, so these act
|
||||
// as the "default" settings.)
|
||||
let mut config = Configuration::default();
|
||||
config.apply(overrides.clone());
|
||||
overrides.process_config(&mut config);
|
||||
let settings = Settings::from_configuration(config, &path_dedot::CWD)?;
|
||||
Ok(PyprojectDiscovery::Hierarchical(settings))
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ use log::debug;
|
||||
use path_absolutize::path_dedot;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::cli::Overrides;
|
||||
use crate::fs;
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::pyproject::settings_toml;
|
||||
@@ -113,6 +112,15 @@ impl Resolver {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ConfigProcessor: Copy + Send + Sync {
|
||||
fn process_config(&self, config: &mut Configuration);
|
||||
}
|
||||
|
||||
struct NoOpProcessor;
|
||||
impl ConfigProcessor for &NoOpProcessor {
|
||||
fn process_config(&self, _config: &mut Configuration) {}
|
||||
}
|
||||
|
||||
/// Recursively resolve a `Configuration` from a `pyproject.toml` file at the
|
||||
/// specified `Path`.
|
||||
// TODO(charlie): This whole system could do with some caching. Right now, if a
|
||||
@@ -122,7 +130,7 @@ impl Resolver {
|
||||
pub fn resolve_configuration(
|
||||
pyproject: &Path,
|
||||
relativity: &Relativity,
|
||||
overrides: Option<&Overrides>,
|
||||
processor: impl ConfigProcessor,
|
||||
) -> Result<Configuration> {
|
||||
let mut seen = FxHashSet::default();
|
||||
let mut stack = vec![];
|
||||
@@ -158,9 +166,7 @@ pub fn resolve_configuration(
|
||||
while let Some(extend) = stack.pop() {
|
||||
configuration = configuration.combine(extend);
|
||||
}
|
||||
if let Some(overrides) = overrides {
|
||||
configuration.apply(overrides.clone());
|
||||
}
|
||||
processor.process_config(&mut configuration);
|
||||
Ok(configuration)
|
||||
}
|
||||
|
||||
@@ -169,21 +175,28 @@ pub fn resolve_configuration(
|
||||
pub fn resolve_scoped_settings(
|
||||
pyproject: &Path,
|
||||
relativity: &Relativity,
|
||||
overrides: Option<&Overrides>,
|
||||
processor: impl ConfigProcessor,
|
||||
) -> Result<(PathBuf, Settings)> {
|
||||
let project_root = relativity.resolve(pyproject);
|
||||
let configuration = resolve_configuration(pyproject, relativity, overrides)?;
|
||||
let configuration = resolve_configuration(pyproject, relativity, processor)?;
|
||||
let settings = Settings::from_configuration(configuration, &project_root)?;
|
||||
Ok((project_root, settings))
|
||||
}
|
||||
|
||||
/// Extract the `Settings` from a given `pyproject.toml`.
|
||||
pub fn resolve_settings(
|
||||
pub fn resolve_settings(pyproject: &Path, relativity: &Relativity) -> Result<Settings> {
|
||||
let (_project_root, settings) = resolve_scoped_settings(pyproject, relativity, &NoOpProcessor)?;
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
/// Extract the `Settings` from a given `pyproject.toml` and process the
|
||||
/// configuration with the given [`ConfigProcessor`].
|
||||
pub fn resolve_settings_with_processor(
|
||||
pyproject: &Path,
|
||||
relativity: &Relativity,
|
||||
overrides: Option<&Overrides>,
|
||||
processor: impl ConfigProcessor,
|
||||
) -> Result<Settings> {
|
||||
let (_project_root, settings) = resolve_scoped_settings(pyproject, relativity, overrides)?;
|
||||
let (_project_root, settings) = resolve_scoped_settings(pyproject, relativity, processor)?;
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
@@ -212,7 +225,7 @@ pub fn python_files_in_path(
|
||||
paths: &[PathBuf],
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
processor: impl ConfigProcessor,
|
||||
) -> Result<(Vec<Result<DirEntry, ignore::Error>>, Resolver)> {
|
||||
// Normalize every path (e.g., convert from relative to absolute).
|
||||
let mut paths: Vec<PathBuf> = paths.iter().map(fs::normalize_path).collect();
|
||||
@@ -223,7 +236,7 @@ pub fn python_files_in_path(
|
||||
for ancestor in path.ancestors() {
|
||||
if let Some(pyproject) = settings_toml(ancestor)? {
|
||||
let (root, settings) =
|
||||
resolve_scoped_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
||||
resolve_scoped_settings(&pyproject, &Relativity::Parent, processor)?;
|
||||
resolver.add(root, settings);
|
||||
}
|
||||
}
|
||||
@@ -268,7 +281,7 @@ pub fn python_files_in_path(
|
||||
Ok(Some(pyproject)) => match resolve_scoped_settings(
|
||||
&pyproject,
|
||||
&Relativity::Parent,
|
||||
Some(overrides),
|
||||
processor,
|
||||
) {
|
||||
Ok((root, settings)) => {
|
||||
resolver.write().unwrap().add(root, settings);
|
||||
@@ -342,7 +355,7 @@ pub fn python_file_at_path(
|
||||
path: &Path,
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
processor: impl ConfigProcessor,
|
||||
) -> Result<bool> {
|
||||
if !file_strategy.force_exclude {
|
||||
return Ok(true);
|
||||
@@ -356,7 +369,7 @@ pub fn python_file_at_path(
|
||||
for ancestor in path.ancestors() {
|
||||
if let Some(pyproject) = settings_toml(ancestor)? {
|
||||
let (root, settings) =
|
||||
resolve_scoped_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
||||
resolve_scoped_settings(&pyproject, &Relativity::Parent, processor)?;
|
||||
resolver.add(root, settings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ use regex::Regex;
|
||||
use shellexpand;
|
||||
use shellexpand::LookupError;
|
||||
|
||||
use crate::cli::{collect_per_file_ignores, Overrides};
|
||||
use crate::registry::RuleCodePrefix;
|
||||
use crate::settings::options::Options;
|
||||
use crate::settings::pyproject::load_options;
|
||||
@@ -245,85 +244,6 @@ impl Configuration {
|
||||
pyupgrade: self.pyupgrade.or(config.pyupgrade),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply(&mut self, overrides: Overrides) {
|
||||
if let Some(cache_dir) = overrides.cache_dir {
|
||||
self.cache_dir = Some(cache_dir);
|
||||
}
|
||||
if let Some(dummy_variable_rgx) = overrides.dummy_variable_rgx {
|
||||
self.dummy_variable_rgx = Some(dummy_variable_rgx);
|
||||
}
|
||||
if let Some(exclude) = overrides.exclude {
|
||||
self.exclude = Some(exclude);
|
||||
}
|
||||
if let Some(extend_exclude) = overrides.extend_exclude {
|
||||
self.extend_exclude.extend(extend_exclude);
|
||||
}
|
||||
if let Some(fix) = overrides.fix {
|
||||
self.fix = Some(fix);
|
||||
}
|
||||
if let Some(fix_only) = overrides.fix_only {
|
||||
self.fix_only = Some(fix_only);
|
||||
}
|
||||
if let Some(fixable) = overrides.fixable {
|
||||
self.fixable = Some(fixable);
|
||||
}
|
||||
if let Some(format) = overrides.format {
|
||||
self.format = Some(format);
|
||||
}
|
||||
if let Some(force_exclude) = overrides.force_exclude {
|
||||
self.force_exclude = Some(force_exclude);
|
||||
}
|
||||
if let Some(ignore) = overrides.ignore {
|
||||
self.ignore = Some(ignore);
|
||||
}
|
||||
if let Some(line_length) = overrides.line_length {
|
||||
self.line_length = Some(line_length);
|
||||
}
|
||||
if let Some(max_complexity) = overrides.max_complexity {
|
||||
self.mccabe = Some(mccabe::settings::Options {
|
||||
max_complexity: Some(max_complexity),
|
||||
});
|
||||
}
|
||||
if let Some(per_file_ignores) = overrides.per_file_ignores {
|
||||
self.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores));
|
||||
}
|
||||
if let Some(respect_gitignore) = overrides.respect_gitignore {
|
||||
self.respect_gitignore = Some(respect_gitignore);
|
||||
}
|
||||
if let Some(select) = overrides.select {
|
||||
self.select = Some(select);
|
||||
}
|
||||
if let Some(show_source) = overrides.show_source {
|
||||
self.show_source = Some(show_source);
|
||||
}
|
||||
if let Some(target_version) = overrides.target_version {
|
||||
self.target_version = Some(target_version);
|
||||
}
|
||||
if let Some(unfixable) = overrides.unfixable {
|
||||
self.unfixable = Some(unfixable);
|
||||
}
|
||||
if let Some(update_check) = overrides.update_check {
|
||||
self.update_check = Some(update_check);
|
||||
}
|
||||
// Special-case: `extend_ignore` and `extend_select` are parallel arrays, so
|
||||
// push an empty array if only one of the two is provided.
|
||||
match (overrides.extend_ignore, overrides.extend_select) {
|
||||
(Some(extend_ignore), Some(extend_select)) => {
|
||||
self.extend_ignore.push(extend_ignore);
|
||||
self.extend_select.push(extend_select);
|
||||
}
|
||||
(Some(extend_ignore), None) => {
|
||||
self.extend_ignore.push(extend_ignore);
|
||||
self.extend_select.push(Vec::new());
|
||||
}
|
||||
(None, Some(extend_select)) => {
|
||||
self.extend_ignore.push(Vec::new());
|
||||
self.extend_select.push(extend_select);
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a list of source paths, which could include glob patterns, resolve the
|
||||
|
||||
Reference in New Issue
Block a user