ruff/src/settings/mod.rs
2022-11-17 11:44:06 -05:00

199 lines
6.7 KiB
Rust

//! Effective program settings, taking into account pyproject.toml and
//! command-line options. Structure is optimized for internal usage, as opposed
//! to external visibility or parsing.
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use fnv::FnvHashSet;
use path_absolutize::path_dedot;
use regex::Regex;
use crate::checks::CheckCode;
use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity};
use crate::settings::configuration::Configuration;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, pep8_naming,
};
pub mod configuration;
pub mod options;
pub mod pyproject;
pub mod types;
pub mod user;
#[derive(Debug)]
pub struct Settings {
pub dummy_variable_rgx: Regex,
pub enabled: FnvHashSet<CheckCode>,
pub exclude: Vec<FilePattern>,
pub extend_exclude: Vec<FilePattern>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub src: Vec<PathBuf>,
pub target_version: PythonVersion,
// Plugins
pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_bugbear: flake8_bugbear::settings::Settings,
pub flake8_quotes: flake8_quotes::settings::Settings,
pub flake8_tidy_imports: flake8_tidy_imports::settings::Settings,
pub isort: isort::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings,
}
impl Settings {
pub fn from_configuration(config: Configuration) -> Self {
Self {
dummy_variable_rgx: config.dummy_variable_rgx,
enabled: resolve_codes(
&config.select,
&config.extend_select,
&config.ignore,
&config.extend_ignore,
),
exclude: config.exclude,
extend_exclude: config.extend_exclude,
flake8_annotations: config.flake8_annotations,
flake8_bugbear: config.flake8_bugbear,
flake8_quotes: config.flake8_quotes,
flake8_tidy_imports: config.flake8_tidy_imports,
isort: config.isort,
line_length: config.line_length,
pep8_naming: config.pep8_naming,
per_file_ignores: config.per_file_ignores,
src: config.src,
target_version: config.target_version,
}
}
pub fn for_rule(check_code: CheckCode) -> Self {
Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: FnvHashSet::from_iter([check_code]),
exclude: Default::default(),
extend_exclude: Default::default(),
line_length: 88,
per_file_ignores: Default::default(),
src: vec![path_dedot::CWD.clone()],
target_version: PythonVersion::Py310,
flake8_annotations: Default::default(),
flake8_bugbear: Default::default(),
flake8_quotes: Default::default(),
flake8_tidy_imports: Default::default(),
isort: Default::default(),
pep8_naming: Default::default(),
}
}
pub fn for_rules(check_codes: Vec<CheckCode>) -> Self {
Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: FnvHashSet::from_iter(check_codes),
exclude: Default::default(),
extend_exclude: Default::default(),
line_length: 88,
per_file_ignores: Default::default(),
src: vec![path_dedot::CWD.clone()],
target_version: PythonVersion::Py310,
flake8_annotations: Default::default(),
flake8_bugbear: Default::default(),
flake8_quotes: Default::default(),
flake8_tidy_imports: Default::default(),
isort: Default::default(),
pep8_naming: Default::default(),
}
}
}
impl Hash for Settings {
fn hash<H: Hasher>(&self, state: &mut H) {
// Add base properties in alphabetical order.
self.dummy_variable_rgx.as_str().hash(state);
for value in self.enabled.iter() {
value.hash(state);
}
self.line_length.hash(state);
for value in self.per_file_ignores.iter() {
value.hash(state);
}
self.target_version.hash(state);
// Add plugin properties in alphabetical order.
self.flake8_annotations.hash(state);
self.flake8_bugbear.hash(state);
self.flake8_quotes.hash(state);
self.isort.hash(state);
self.pep8_naming.hash(state);
}
}
/// Given a set of selected and ignored prefixes, resolve the set of enabled
/// error codes.
fn resolve_codes(
select: &[CheckCodePrefix],
extend_select: &[CheckCodePrefix],
ignore: &[CheckCodePrefix],
extend_ignore: &[CheckCodePrefix],
) -> FnvHashSet<CheckCode> {
let mut codes: FnvHashSet<CheckCode> = FnvHashSet::default();
for specificity in [
PrefixSpecificity::Category,
PrefixSpecificity::Hundreds,
PrefixSpecificity::Tens,
PrefixSpecificity::Explicit,
] {
for prefix in select {
if prefix.specificity() == specificity {
codes.extend(prefix.codes());
}
}
for prefix in extend_select {
if prefix.specificity() == specificity {
codes.extend(prefix.codes());
}
}
for prefix in ignore {
if prefix.specificity() == specificity {
for code in prefix.codes() {
codes.remove(&code);
}
}
}
for prefix in extend_ignore {
if prefix.specificity() == specificity {
for code in prefix.codes() {
codes.remove(&code);
}
}
}
}
codes
}
#[cfg(test)]
mod tests {
use fnv::FnvHashSet;
use crate::checks::CheckCode;
use crate::checks_gen::CheckCodePrefix;
use crate::settings::resolve_codes;
#[test]
fn resolver() {
let actual = resolve_codes(&[CheckCodePrefix::W], &[], &[], &[]);
let expected = FnvHashSet::from_iter([CheckCode::W292, CheckCode::W605]);
assert_eq!(actual, expected);
let actual = resolve_codes(&[CheckCodePrefix::W6], &[], &[], &[]);
let expected = FnvHashSet::from_iter([CheckCode::W605]);
assert_eq!(actual, expected);
let actual = resolve_codes(&[CheckCodePrefix::W], &[], &[CheckCodePrefix::W292], &[]);
let expected = FnvHashSet::from_iter([CheckCode::W605]);
assert_eq!(actual, expected);
let actual = resolve_codes(&[CheckCodePrefix::W605], &[], &[CheckCodePrefix::W605], &[]);
let expected = FnvHashSet::from_iter([]);
assert_eq!(actual, expected);
}
}