Unify Settings and AllSettings (#7532)

This commit is contained in:
Micha Reiser 2023-09-20 15:56:07 +02:00 committed by GitHub
parent ca3c15858d
commit b19eec9b2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 204 additions and 147 deletions

View file

@ -3,27 +3,11 @@ use std::hash::{Hash, Hasher};
use ruff_cache::{CacheKey, CacheKeyHasher}; use ruff_cache::{CacheKey, CacheKeyHasher};
use ruff_macros::CacheKey; use ruff_macros::CacheKey;
#[derive(CacheKey, Hash)]
struct UnitStruct;
#[derive(CacheKey, Hash)]
struct NamedFieldsStruct {
a: String,
b: String,
}
#[derive(CacheKey, Hash)]
struct UnnamedFieldsStruct(String, String);
#[derive(CacheKey, Hash)]
enum Enum {
Unit,
UnnamedFields(String, String),
NamedFields { a: String, b: String },
}
#[test] #[test]
fn unit_struct_cache_key() { fn unit_struct_cache_key() {
#[derive(CacheKey, Hash)]
struct UnitStruct;
let mut key = CacheKeyHasher::new(); let mut key = CacheKeyHasher::new();
UnitStruct.cache_key(&mut key); UnitStruct.cache_key(&mut key);
@ -36,6 +20,43 @@ fn unit_struct_cache_key() {
#[test] #[test]
fn named_field_struct() { fn named_field_struct() {
#[derive(CacheKey, Hash)]
struct NamedFieldsStruct {
a: String,
b: String,
}
let mut key = CacheKeyHasher::new();
let named_fields = NamedFieldsStruct {
a: "Hello".into(),
b: "World".into(),
};
named_fields.cache_key(&mut key);
let mut hash = CacheKeyHasher::new();
named_fields.hash(&mut hash);
assert_eq!(hash.finish(), key.finish());
}
#[test]
fn struct_ignored_fields() {
#[derive(CacheKey)]
struct NamedFieldsStruct {
a: String,
#[cache_key(ignore)]
#[allow(unused)]
b: String,
}
impl Hash for NamedFieldsStruct {
fn hash<H: Hasher>(&self, state: &mut H) {
self.a.hash(state);
}
}
let mut key = CacheKeyHasher::new(); let mut key = CacheKeyHasher::new();
let named_fields = NamedFieldsStruct { let named_fields = NamedFieldsStruct {
@ -53,6 +74,9 @@ fn named_field_struct() {
#[test] #[test]
fn unnamed_field_struct() { fn unnamed_field_struct() {
#[derive(CacheKey, Hash)]
struct UnnamedFieldsStruct(String, String);
let mut key = CacheKeyHasher::new(); let mut key = CacheKeyHasher::new();
let unnamed_fields = UnnamedFieldsStruct("Hello".into(), "World".into()); let unnamed_fields = UnnamedFieldsStruct("Hello".into(), "World".into());
@ -65,6 +89,13 @@ fn unnamed_field_struct() {
assert_eq!(hash.finish(), key.finish()); assert_eq!(hash.finish(), key.finish());
} }
#[derive(CacheKey, Hash)]
enum Enum {
Unit,
UnnamedFields(String, String),
NamedFields { a: String, b: String },
}
#[test] #[test]
fn enum_unit_variant() { fn enum_unit_variant() {
let mut key = CacheKeyHasher::new(); let mut key = CacheKeyHasher::new();

View file

@ -59,21 +59,18 @@ pub(crate) struct Cache {
impl Cache { impl Cache {
/// Open or create a new cache. /// Open or create a new cache.
/// ///
/// `cache_dir` is considered the root directory of the cache, which can be
/// local to the project, global or otherwise set by the user.
///
/// `package_root` is the path to root of the package that is contained /// `package_root` is the path to root of the package that is contained
/// within this cache and must be canonicalized (to avoid considering `./` /// within this cache and must be canonicalized (to avoid considering `./`
/// and `../project` being different). /// and `../project` being different).
/// ///
/// Finally `settings` is used to ensure we don't open a cache for different /// Finally `settings` is used to ensure we don't open a cache for different
/// settings. /// settings. It also defines the directory where to store the cache.
pub(crate) fn open(cache_dir: &Path, package_root: PathBuf, settings: &Settings) -> Cache { pub(crate) fn open(package_root: PathBuf, settings: &Settings) -> Cache {
debug_assert!(package_root.is_absolute(), "package root not canonicalized"); debug_assert!(package_root.is_absolute(), "package root not canonicalized");
let mut buf = itoa::Buffer::new(); let mut buf = itoa::Buffer::new();
let key = Path::new(buf.format(cache_key(&package_root, settings))); let key = Path::new(buf.format(cache_key(&package_root, settings)));
let path = PathBuf::from_iter([cache_dir, Path::new("content"), key]); let path = PathBuf::from_iter([&settings.cache_dir, Path::new("content"), key]);
let file = match File::open(&path) { let file = match File::open(&path) {
Ok(file) => file, Ok(file) => file,
@ -350,7 +347,7 @@ mod tests {
use itertools::Itertools; use itertools::Itertools;
use ruff_cache::CACHE_DIR_NAME; use ruff_cache::CACHE_DIR_NAME;
use ruff_linter::settings::{flags, AllSettings, Settings}; use ruff_linter::settings::{flags, Settings};
use crate::cache::RelativePathBuf; use crate::cache::RelativePathBuf;
use crate::cache::{self, Cache, FileCache}; use crate::cache::{self, Cache, FileCache};
@ -371,10 +368,13 @@ mod tests {
let _ = fs::remove_dir_all(&cache_dir); let _ = fs::remove_dir_all(&cache_dir);
cache::init(&cache_dir).unwrap(); cache::init(&cache_dir).unwrap();
let settings = Settings::default(); let settings = Settings {
cache_dir,
..Settings::default()
};
let package_root = fs::canonicalize(package_root).unwrap(); let package_root = fs::canonicalize(package_root).unwrap();
let cache = Cache::open(&cache_dir, package_root.clone(), &settings); let cache = Cache::open(package_root.clone(), &settings);
assert_eq!(cache.new_files.lock().unwrap().len(), 0); assert_eq!(cache.new_files.lock().unwrap().len(), 0);
let mut paths = Vec::new(); let mut paths = Vec::new();
@ -426,7 +426,7 @@ mod tests {
cache.store().unwrap(); cache.store().unwrap();
let cache = Cache::open(&cache_dir, package_root.clone(), &settings); let cache = Cache::open(package_root.clone(), &settings);
assert_ne!(cache.package.files.len(), 0); assert_ne!(cache.package.files.len(), 0);
parse_errors.sort(); parse_errors.sort();
@ -651,9 +651,8 @@ mod tests {
} }
struct TestCache { struct TestCache {
cache_dir: PathBuf,
package_root: PathBuf, package_root: PathBuf,
settings: AllSettings, settings: Settings,
} }
impl TestCache { impl TestCache {
@ -672,10 +671,12 @@ mod tests {
cache::init(&cache_dir).unwrap(); cache::init(&cache_dir).unwrap();
fs::create_dir(package_root.clone()).unwrap(); fs::create_dir(package_root.clone()).unwrap();
let settings = AllSettings::default(); let settings = Settings {
cache_dir,
..Settings::default()
};
Self { Self {
cache_dir,
package_root, package_root,
settings, settings,
} }
@ -695,11 +696,7 @@ mod tests {
} }
fn open(&self) -> Cache { fn open(&self) -> Cache {
Cache::open( Cache::open(self.package_root.clone(), &self.settings)
&self.cache_dir,
self.package_root.clone(),
&self.settings.lib,
)
} }
fn lint_file_with_cache( fn lint_file_with_cache(
@ -710,7 +707,7 @@ mod tests {
lint_path( lint_path(
&self.package_root.join(path), &self.package_root.join(path),
Some(&self.package_root), Some(&self.package_root),
&self.settings.lib, &self.settings,
Some(cache), Some(cache),
flags::Noqa::Enabled, flags::Noqa::Enabled,
flags::FixMode::Generate, flags::FixMode::Generate,
@ -720,7 +717,7 @@ mod tests {
impl Drop for TestCache { impl Drop for TestCache {
fn drop(&mut self) { fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.cache_dir); let _ = fs::remove_dir_all(&self.settings.cache_dir);
} }
} }
} }

View file

@ -58,13 +58,13 @@ pub(crate) fn check(
match pyproject_config.strategy { match pyproject_config.strategy {
PyprojectDiscoveryStrategy::Fixed => { PyprojectDiscoveryStrategy::Fixed => {
init_cache(&pyproject_config.settings.cli.cache_dir); init_cache(&pyproject_config.settings.cache_dir);
} }
PyprojectDiscoveryStrategy::Hierarchical => { PyprojectDiscoveryStrategy::Hierarchical => {
for settings in for settings in
std::iter::once(&pyproject_config.settings).chain(resolver.settings()) std::iter::once(&pyproject_config.settings).chain(resolver.settings())
{ {
init_cache(&settings.cli.cache_dir); init_cache(&settings.cache_dir);
} }
} }
} }
@ -88,12 +88,8 @@ pub(crate) fn check(
.unique() .unique()
.par_bridge() .par_bridge()
.map(|cache_root| { .map(|cache_root| {
let settings = resolver.resolve_all(cache_root, pyproject_config); let settings = resolver.resolve(cache_root, pyproject_config);
let cache = Cache::open( let cache = Cache::open(cache_root.to_path_buf(), settings);
&settings.cli.cache_dir,
cache_root.to_path_buf(),
&settings.lib,
);
(cache_root, cache) (cache_root, cache)
}) })
.collect::<HashMap<&Path, Cache>>() .collect::<HashMap<&Path, Cache>>()
@ -242,7 +238,7 @@ mod test {
use ruff_linter::message::{Emitter, EmitterContext, TextEmitter}; use ruff_linter::message::{Emitter, EmitterContext, TextEmitter};
use ruff_linter::registry::Rule; use ruff_linter::registry::Rule;
use ruff_linter::settings::{flags, AllSettings, CliSettings, Settings}; use ruff_linter::settings::{flags, Settings};
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy}; use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy};
use crate::args::Overrides; use crate::args::Overrides;
@ -271,11 +267,8 @@ mod test {
// Configure // Configure
let snapshot = format!("{}_{}", rule_code.noqa_code(), path); let snapshot = format!("{}_{}", rule_code.noqa_code(), path);
let settings = AllSettings { // invalid pyproject.toml is not active by default
cli: CliSettings::default(), let settings = Settings::for_rules(vec![rule_code, Rule::InvalidPyprojectToml]);
// invalid pyproject.toml is not active by default
lib: Settings::for_rules(vec![rule_code, Rule::InvalidPyprojectToml]),
};
let pyproject_config = let pyproject_config =
PyprojectConfig::new(PyprojectDiscoveryStrategy::Fixed, settings, None); PyprojectConfig::new(PyprojectDiscoveryStrategy::Fixed, settings, None);

View file

@ -24,14 +24,14 @@ pub(crate) fn check_stdin(
} }
} }
let package_root = filename.and_then(Path::parent).and_then(|path| { let package_root = filename.and_then(Path::parent).and_then(|path| {
packaging::detect_package_root(path, &pyproject_config.settings.lib.namespace_packages) packaging::detect_package_root(path, &pyproject_config.settings.namespace_packages)
}); });
let stdin = read_from_stdin()?; let stdin = read_from_stdin()?;
let mut diagnostics = lint_stdin( let mut diagnostics = lint_stdin(
filename, filename,
package_root, package_root,
stdin, stdin,
&pyproject_config.settings.lib, &pyproject_config.settings,
noqa, noqa,
autofix, autofix,
)?; )?;

View file

@ -39,11 +39,11 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &Overrides) -> Resu
// Format the file. // Format the file.
let path = cli.stdin_filename.as_deref(); let path = cli.stdin_filename.as_deref();
let preview = match pyproject_config.settings.lib.preview { let preview = match pyproject_config.settings.preview {
PreviewMode::Enabled => ruff_python_formatter::PreviewMode::Enabled, PreviewMode::Enabled => ruff_python_formatter::PreviewMode::Enabled,
PreviewMode::Disabled => ruff_python_formatter::PreviewMode::Disabled, PreviewMode::Disabled => ruff_python_formatter::PreviewMode::Disabled,
}; };
let line_length = pyproject_config.settings.lib.line_length; let line_length = pyproject_config.settings.line_length;
let options = path let options = path
.map(PyFormatOptions::from_extension) .map(PyFormatOptions::from_extension)

View file

@ -11,7 +11,7 @@ use notify::{recommended_watcher, RecursiveMode, Watcher};
use ruff_linter::logging::{set_up_logging, LogLevel}; use ruff_linter::logging::{set_up_logging, LogLevel};
use ruff_linter::settings::types::SerializationFormat; use ruff_linter::settings::types::SerializationFormat;
use ruff_linter::settings::{flags, CliSettings}; use ruff_linter::settings::{flags, Settings};
use ruff_linter::{fs, warn_user, warn_user_once}; use ruff_linter::{fs, warn_user, warn_user_once};
use crate::args::{Args, CheckCommand, Command, FormatCommand}; use crate::args::{Args, CheckCommand, Command, FormatCommand};
@ -224,14 +224,14 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
// Extract options that are included in `Settings`, but only apply at the top // Extract options that are included in `Settings`, but only apply at the top
// level. // level.
let CliSettings { let Settings {
fix, fix,
fix_only, fix_only,
output_format, output_format,
show_fixes, show_fixes,
show_source, show_source,
.. ..
} = pyproject_config.settings.cli; } = pyproject_config.settings;
// Autofix rules are as follows: // Autofix rules are as follows:
// - By default, generate all fixes, but don't apply them to the filesystem. // - By default, generate all fixes, but don't apply them to the filesystem.

View file

@ -7,8 +7,7 @@ use path_absolutize::path_dedot;
use ruff_workspace::configuration::Configuration; use ruff_workspace::configuration::Configuration;
use ruff_workspace::pyproject; use ruff_workspace::pyproject;
use ruff_workspace::resolver::{ use ruff_workspace::resolver::{
resolve_settings_with_processor, ConfigProcessor, PyprojectConfig, PyprojectDiscoveryStrategy, resolve_root_settings, ConfigProcessor, PyprojectConfig, PyprojectDiscoveryStrategy, Relativity,
Relativity,
}; };
use crate::args::Overrides; use crate::args::Overrides;
@ -25,7 +24,7 @@ pub fn resolve(
if isolated { if isolated {
let mut config = Configuration::default(); let mut config = Configuration::default();
overrides.process_config(&mut config); overrides.process_config(&mut config);
let settings = config.into_all_settings(&path_dedot::CWD)?; let settings = config.into_settings(&path_dedot::CWD)?;
debug!("Isolated mode, not reading any pyproject.toml"); debug!("Isolated mode, not reading any pyproject.toml");
return Ok(PyprojectConfig::new( return Ok(PyprojectConfig::new(
PyprojectDiscoveryStrategy::Fixed, PyprojectDiscoveryStrategy::Fixed,
@ -42,7 +41,7 @@ pub fn resolve(
.map(|config| shellexpand::full(&config).map(|config| PathBuf::from(config.as_ref()))) .map(|config| shellexpand::full(&config).map(|config| PathBuf::from(config.as_ref())))
.transpose()? .transpose()?
{ {
let settings = resolve_settings_with_processor(&pyproject, Relativity::Cwd, overrides)?; let settings = resolve_root_settings(&pyproject, Relativity::Cwd, overrides)?;
debug!( debug!(
"Using user specified pyproject.toml at {}", "Using user specified pyproject.toml at {}",
pyproject.display() pyproject.display()
@ -65,7 +64,7 @@ pub fn resolve(
.unwrap_or(&path_dedot::CWD.as_path()), .unwrap_or(&path_dedot::CWD.as_path()),
)? { )? {
debug!("Using pyproject.toml (parent) at {}", pyproject.display()); debug!("Using pyproject.toml (parent) at {}", pyproject.display());
let settings = resolve_settings_with_processor(&pyproject, Relativity::Parent, overrides)?; let settings = resolve_root_settings(&pyproject, Relativity::Parent, overrides)?;
return Ok(PyprojectConfig::new( return Ok(PyprojectConfig::new(
PyprojectDiscoveryStrategy::Hierarchical, PyprojectDiscoveryStrategy::Hierarchical,
settings, settings,
@ -79,7 +78,7 @@ pub fn resolve(
// these act as the "default" settings.) // these act as the "default" settings.)
if let Some(pyproject) = pyproject::find_user_settings_toml() { if let Some(pyproject) = pyproject::find_user_settings_toml() {
debug!("Using pyproject.toml (cwd) at {}", pyproject.display()); debug!("Using pyproject.toml (cwd) at {}", pyproject.display());
let settings = resolve_settings_with_processor(&pyproject, Relativity::Cwd, overrides)?; let settings = resolve_root_settings(&pyproject, Relativity::Cwd, overrides)?;
return Ok(PyprojectConfig::new( return Ok(PyprojectConfig::new(
PyprojectDiscoveryStrategy::Hierarchical, PyprojectDiscoveryStrategy::Hierarchical,
settings, settings,
@ -94,7 +93,7 @@ pub fn resolve(
debug!("Using Ruff default settings"); debug!("Using Ruff default settings");
let mut config = Configuration::default(); let mut config = Configuration::default();
overrides.process_config(&mut config); overrides.process_config(&mut config);
let settings = config.into_all_settings(&path_dedot::CWD)?; let settings = config.into_settings(&path_dedot::CWD)?;
Ok(PyprojectConfig::new( Ok(PyprojectConfig::new(
PyprojectDiscoveryStrategy::Hierarchical, PyprojectDiscoveryStrategy::Hierarchical,
settings, settings,

View file

@ -60,7 +60,7 @@ fn ruff_check_paths(
cli.stdin_filename.as_deref(), cli.stdin_filename.as_deref(),
)?; )?;
// We don't want to format pyproject.toml // We don't want to format pyproject.toml
pyproject_config.settings.lib.include = FilePatternSet::try_from_vec(vec![ pyproject_config.settings.include = FilePatternSet::try_from_vec(vec![
FilePattern::Builtin("*.py"), FilePattern::Builtin("*.py"),
FilePattern::Builtin("*.pyi"), FilePattern::Builtin("*.pyi"),
]) ])

View file

@ -1,6 +1,7 @@
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use path_absolutize::path_dedot; use path_absolutize::path_dedot;
use regex::Regex; use regex::Regex;
use ruff_cache::cache_dir;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use std::collections::HashSet; use std::collections::HashSet;
@ -17,7 +18,7 @@ use crate::rules::{
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pylint, pyupgrade, pycodestyle, pydocstyle, pyflakes, pylint, pyupgrade,
}; };
use crate::settings::types::FilePatternSet; use crate::settings::types::{FilePatternSet, SerializationFormat};
pub const PREFIXES: &[RuleSelector] = &[ pub const PREFIXES: &[RuleSelector] = &[
RuleSelector::Prefix { RuleSelector::Prefix {
@ -72,7 +73,15 @@ pub static INCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
impl Default for Settings { impl Default for Settings {
fn default() -> Self { fn default() -> Self {
let project_root = path_dedot::CWD.clone();
Self { Self {
cache_dir: cache_dir(&project_root),
fix: false,
fix_only: false,
output_format: SerializationFormat::default(),
show_fixes: false,
show_source: false,
rules: PREFIXES rules: PREFIXES
.iter() .iter()
.flat_map(|selector| selector.rules(PreviewMode::default())) .flat_map(|selector| selector.rules(PreviewMode::default()))
@ -92,7 +101,7 @@ impl Default for Settings {
namespace_packages: vec![], namespace_packages: vec![],
preview: PreviewMode::default(), preview: PreviewMode::default(),
per_file_ignores: vec![], per_file_ignores: vec![],
project_root: path_dedot::CWD.clone(), project_root,
respect_gitignore: true, respect_gitignore: true,
src: vec![path_dedot::CWD.clone()], src: vec![path_dedot::CWD.clone()],
tab_size: TabSize::default(), tab_size: TabSize::default(),

View file

@ -31,27 +31,22 @@ pub mod flags;
pub mod rule_table; pub mod rule_table;
pub mod types; pub mod types;
#[derive(Debug, Default)]
pub struct AllSettings {
pub cli: CliSettings,
pub lib: Settings,
}
#[derive(Debug, Default, Clone)]
#[allow(clippy::struct_excessive_bools)]
/// Settings that are not used by this library and only here so that `ruff_cli` can use them.
pub struct CliSettings {
pub cache_dir: PathBuf,
pub fix: bool,
pub fix_only: bool,
pub output_format: SerializationFormat,
pub show_fixes: bool,
pub show_source: bool,
}
#[derive(Debug, CacheKey)] #[derive(Debug, CacheKey)]
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
pub struct Settings { pub struct Settings {
#[cache_key(ignore)]
pub cache_dir: PathBuf,
#[cache_key(ignore)]
pub fix: bool,
#[cache_key(ignore)]
pub fix_only: bool,
#[cache_key(ignore)]
pub output_format: SerializationFormat,
#[cache_key(ignore)]
pub show_fixes: bool,
#[cache_key(ignore)]
pub show_source: bool,
pub rules: RuleTable, pub rules: RuleTable,
pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, RuleSet)>, pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, RuleSet)>,

View file

@ -1,7 +1,8 @@
use proc_macro2::TokenStream; use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::{Data, DeriveInput, Error, Fields}; use syn::{Data, DeriveInput, Error, Field, Fields, Token};
pub(crate) fn derive_cache_key(item: &DeriveInput) -> syn::Result<TokenStream> { pub(crate) fn derive_cache_key(item: &DeriveInput) -> syn::Result<TokenStream> {
let fields = match &item.data { let fields = match &item.data {
@ -65,7 +66,15 @@ pub(crate) fn derive_cache_key(item: &DeriveInput) -> syn::Result<TokenStream> {
} }
Data::Struct(item_struct) => { Data::Struct(item_struct) => {
let fields = item_struct.fields.iter().enumerate().map(|(i, field)| { let mut fields = Vec::with_capacity(item_struct.fields.len());
for (i, field) in item_struct.fields.iter().enumerate() {
if let Some(cache_field_attribute) = cache_key_field_attribute(field)? {
if cache_field_attribute.ignore {
continue;
}
}
let field_attr = match &field.ident { let field_attr = match &field.ident {
Some(ident) => quote!(self.#ident), Some(ident) => quote!(self.#ident),
None => { None => {
@ -74,8 +83,8 @@ pub(crate) fn derive_cache_key(item: &DeriveInput) -> syn::Result<TokenStream> {
} }
}; };
quote!(#field_attr.cache_key(key);) fields.push(quote!(#field_attr.cache_key(key);));
}); }
quote! {#(#fields)*} quote! {#(#fields)*}
} }
@ -101,3 +110,44 @@ pub(crate) fn derive_cache_key(item: &DeriveInput) -> syn::Result<TokenStream> {
} }
)) ))
} }
fn cache_key_field_attribute(field: &Field) -> syn::Result<Option<CacheKeyFieldAttributes>> {
if let Some(attribute) = field
.attrs
.iter()
.find(|attribute| attribute.path().is_ident("cache_key"))
{
attribute.parse_args::<CacheKeyFieldAttributes>().map(Some)
} else {
Ok(None)
}
}
#[derive(Debug, Default)]
struct CacheKeyFieldAttributes {
ignore: bool,
}
impl Parse for CacheKeyFieldAttributes {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut attributes = CacheKeyFieldAttributes::default();
let args = input.parse_terminated(Ident::parse, Token![,])?;
for arg in args {
match arg.to_string().as_str() {
"ignore" => {
attributes.ignore = true;
}
name => {
return Err(Error::new(
arg.span(),
format!("Unknown `cache_field` argument {name}"),
))
}
}
}
Ok(attributes)
}
}

View file

@ -33,7 +33,11 @@ pub fn derive_combine_options(input: TokenStream) -> TokenStream {
.into() .into()
} }
#[proc_macro_derive(CacheKey)] /// Generates a [`CacheKey`] implementation for the attributed type.
///
/// Struct fields can be attributed with the `cache_key` field-attribute that supports:
/// * `ignore`: Ignore the attributed field in the cache key
#[proc_macro_derive(CacheKey, attributes(cache_key))]
pub fn cache_key(input: TokenStream) -> TokenStream { pub fn cache_key(input: TokenStream) -> TokenStream {
let item = parse_macro_input!(input as DeriveInput); let item = parse_macro_input!(input as DeriveInput);

View file

@ -27,9 +27,7 @@ use ruff_linter::settings::types::{
FilePattern, FilePatternSet, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat, FilePattern, FilePatternSet, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat,
Version, Version,
}; };
use ruff_linter::settings::{ use ruff_linter::settings::{defaults, resolve_per_file_ignores, Settings};
defaults, resolve_per_file_ignores, AllSettings, CliSettings, Settings,
};
use ruff_linter::{ use ruff_linter::{
fs, warn_user, warn_user_once, warn_user_once_by_id, RuleSelector, RUFF_PKG_VERSION, fs, warn_user, warn_user_once, warn_user_once_by_id, RuleSelector, RUFF_PKG_VERSION,
}; };
@ -110,23 +108,6 @@ pub struct Configuration {
} }
impl Configuration { impl Configuration {
pub fn into_all_settings(self, project_root: &Path) -> Result<AllSettings> {
Ok(AllSettings {
cli: CliSettings {
cache_dir: self
.cache_dir
.clone()
.unwrap_or_else(|| cache_dir(project_root)),
fix: self.fix.unwrap_or(false),
fix_only: self.fix_only.unwrap_or(false),
output_format: self.output_format.unwrap_or_default(),
show_fixes: self.show_fixes.unwrap_or(false),
show_source: self.show_source.unwrap_or(false),
},
lib: self.into_settings(project_root)?,
})
}
pub fn into_settings(self, project_root: &Path) -> Result<Settings> { pub fn into_settings(self, project_root: &Path) -> Result<Settings> {
if let Some(required_version) = &self.required_version { if let Some(required_version) = &self.required_version {
if &**required_version != RUFF_PKG_VERSION { if &**required_version != RUFF_PKG_VERSION {
@ -139,6 +120,16 @@ impl Configuration {
} }
Ok(Settings { Ok(Settings {
cache_dir: self
.cache_dir
.clone()
.unwrap_or_else(|| cache_dir(project_root)),
fix: self.fix.unwrap_or(false),
fix_only: self.fix_only.unwrap_or(false),
output_format: self.output_format.unwrap_or_default(),
show_fixes: self.show_fixes.unwrap_or(false),
show_source: self.show_source.unwrap_or(false),
rules: self.as_rule_table(), rules: self.as_rule_table(),
allowed_confusables: self allowed_confusables: self
.allowed_confusables .allowed_confusables

View file

@ -14,7 +14,7 @@ use path_absolutize::path_dedot;
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use ruff_linter::packaging::is_package; use ruff_linter::packaging::is_package;
use ruff_linter::settings::{AllSettings, Settings}; use ruff_linter::settings::Settings;
use ruff_linter::{fs, warn_user_once}; use ruff_linter::{fs, warn_user_once};
use crate::configuration::Configuration; use crate::configuration::Configuration;
@ -27,7 +27,7 @@ pub struct PyprojectConfig {
/// each Python file. /// each Python file.
pub strategy: PyprojectDiscoveryStrategy, pub strategy: PyprojectDiscoveryStrategy,
/// All settings from the `pyproject.toml` file. /// All settings from the `pyproject.toml` file.
pub settings: AllSettings, pub settings: Settings,
/// Absolute path to the `pyproject.toml` file. This would be `None` when /// Absolute path to the `pyproject.toml` file. This would be `None` when
/// either using the default settings or the `--isolated` flag is set. /// either using the default settings or the `--isolated` flag is set.
pub path: Option<PathBuf>, pub path: Option<PathBuf>,
@ -36,7 +36,7 @@ pub struct PyprojectConfig {
impl PyprojectConfig { impl PyprojectConfig {
pub fn new( pub fn new(
strategy: PyprojectDiscoveryStrategy, strategy: PyprojectDiscoveryStrategy,
settings: AllSettings, settings: Settings,
path: Option<PathBuf>, path: Option<PathBuf>,
) -> Self { ) -> Self {
Self { Self {
@ -93,21 +93,21 @@ impl Relativity {
#[derive(Default)] #[derive(Default)]
pub struct Resolver { pub struct Resolver {
settings: BTreeMap<PathBuf, AllSettings>, settings: BTreeMap<PathBuf, Settings>,
} }
impl Resolver { impl Resolver {
/// Add a resolved [`Settings`] under a given [`PathBuf`] scope. /// Add a resolved [`Settings`] under a given [`PathBuf`] scope.
fn add(&mut self, path: PathBuf, settings: AllSettings) { fn add(&mut self, path: PathBuf, settings: Settings) {
self.settings.insert(path, settings); self.settings.insert(path, settings);
} }
/// Return the appropriate [`AllSettings`] for a given [`Path`]. /// Return the appropriate [`Settings`] for a given [`Path`].
pub fn resolve_all<'a>( pub fn resolve<'a>(
&'a self, &'a self,
path: &Path, path: &Path,
pyproject_config: &'a PyprojectConfig, pyproject_config: &'a PyprojectConfig,
) -> &'a AllSettings { ) -> &'a Settings {
match pyproject_config.strategy { match pyproject_config.strategy {
PyprojectDiscoveryStrategy::Fixed => &pyproject_config.settings, PyprojectDiscoveryStrategy::Fixed => &pyproject_config.settings,
PyprojectDiscoveryStrategy::Hierarchical => self PyprojectDiscoveryStrategy::Hierarchical => self
@ -119,14 +119,6 @@ impl Resolver {
} }
} }
pub fn resolve<'a>(
&'a self,
path: &Path,
pyproject_config: &'a PyprojectConfig,
) -> &'a Settings {
&self.resolve_all(path, pyproject_config).lib
}
/// Return a mapping from Python package to its package root. /// Return a mapping from Python package to its package root.
pub fn package_roots<'a>( pub fn package_roots<'a>(
&'a self, &'a self,
@ -163,7 +155,7 @@ impl Resolver {
} }
/// Return an iterator over the resolved [`Settings`] in this [`Resolver`]. /// Return an iterator over the resolved [`Settings`] in this [`Resolver`].
pub fn settings(&self) -> impl Iterator<Item = &AllSettings> { pub fn settings(&self) -> impl Iterator<Item = &Settings> {
self.settings.values() self.settings.values()
} }
} }
@ -260,20 +252,20 @@ fn resolve_scoped_settings(
pyproject: &Path, pyproject: &Path,
relativity: Relativity, relativity: Relativity,
processor: &dyn ConfigProcessor, processor: &dyn ConfigProcessor,
) -> Result<(PathBuf, AllSettings)> { ) -> Result<(PathBuf, Settings)> {
let configuration = resolve_configuration(pyproject, relativity, processor)?; let configuration = resolve_configuration(pyproject, relativity, processor)?;
let project_root = relativity.resolve(pyproject); let project_root = relativity.resolve(pyproject);
let settings = configuration.into_all_settings(&project_root)?; let settings = configuration.into_settings(&project_root)?;
Ok((project_root, settings)) Ok((project_root, settings))
} }
/// Extract the [`Settings`] from a given `pyproject.toml` and process the /// Extract the [`Settings`] from a given `pyproject.toml` and process the
/// configuration with the given [`ConfigProcessor`]. /// configuration with the given [`ConfigProcessor`].
pub fn resolve_settings_with_processor( pub fn resolve_root_settings(
pyproject: &Path, pyproject: &Path,
relativity: Relativity, relativity: Relativity,
processor: &dyn ConfigProcessor, processor: &dyn ConfigProcessor,
) -> Result<AllSettings> { ) -> Result<Settings> {
let (_project_root, settings) = resolve_scoped_settings(pyproject, relativity, processor)?; let (_project_root, settings) = resolve_scoped_settings(pyproject, relativity, processor)?;
Ok(settings) Ok(settings)
} }
@ -305,7 +297,7 @@ pub fn python_files_in_path(
} }
// Check if the paths themselves are excluded. // Check if the paths themselves are excluded.
if pyproject_config.settings.lib.force_exclude { if pyproject_config.settings.force_exclude {
paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_config)); paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_config));
if paths.is_empty() { if paths.is_empty() {
return Ok((vec![], resolver)); return Ok((vec![], resolver));
@ -321,7 +313,7 @@ pub fn python_files_in_path(
for path in &paths[1..] { for path in &paths[1..] {
builder.add(path); builder.add(path);
} }
builder.standard_filters(pyproject_config.settings.lib.respect_gitignore); builder.standard_filters(pyproject_config.settings.respect_gitignore);
builder.hidden(false); builder.hidden(false);
let walker = builder.build_parallel(); let walker = builder.build_parallel();
@ -430,7 +422,7 @@ pub fn python_file_at_path(
pyproject_config: &PyprojectConfig, pyproject_config: &PyprojectConfig,
processor: &dyn ConfigProcessor, processor: &dyn ConfigProcessor,
) -> Result<bool> { ) -> Result<bool> {
if !pyproject_config.settings.lib.force_exclude { if !pyproject_config.settings.force_exclude {
return Ok(true); return Ok(true);
} }
@ -510,12 +502,12 @@ mod tests {
use tempfile::TempDir; use tempfile::TempDir;
use ruff_linter::settings::types::FilePattern; use ruff_linter::settings::types::FilePattern;
use ruff_linter::settings::AllSettings; use ruff_linter::settings::Settings;
use crate::configuration::Configuration; use crate::configuration::Configuration;
use crate::pyproject::find_settings_toml; use crate::pyproject::find_settings_toml;
use crate::resolver::{ use crate::resolver::{
is_file_excluded, match_exclusion, python_files_in_path, resolve_settings_with_processor, is_file_excluded, match_exclusion, python_files_in_path, resolve_root_settings,
ConfigProcessor, PyprojectConfig, PyprojectDiscoveryStrategy, Relativity, Resolver, ConfigProcessor, PyprojectConfig, PyprojectDiscoveryStrategy, Relativity, Resolver,
}; };
use crate::tests::test_resource_path; use crate::tests::test_resource_path;
@ -532,7 +524,7 @@ mod tests {
let resolver = Resolver::default(); let resolver = Resolver::default();
let pyproject_config = PyprojectConfig::new( let pyproject_config = PyprojectConfig::new(
PyprojectDiscoveryStrategy::Hierarchical, PyprojectDiscoveryStrategy::Hierarchical,
resolve_settings_with_processor( resolve_root_settings(
&find_settings_toml(&package_root)?.unwrap(), &find_settings_toml(&package_root)?.unwrap(),
Relativity::Parent, Relativity::Parent,
&NoOpProcessor, &NoOpProcessor,
@ -578,11 +570,7 @@ mod tests {
let (paths, _) = python_files_in_path( let (paths, _) = python_files_in_path(
&[root.to_path_buf()], &[root.to_path_buf()],
&PyprojectConfig::new( &PyprojectConfig::new(PyprojectDiscoveryStrategy::Fixed, Settings::default(), None),
PyprojectDiscoveryStrategy::Fixed,
AllSettings::default(),
None,
),
&NoOpProcessor, &NoOpProcessor,
)?; )?;
let paths = paths let paths = paths