Implement configuration options for pep8-naming (#505)

This commit is contained in:
Charlie Marsh 2022-10-29 17:00:30 -04:00 committed by GitHub
parent 80b00cc89f
commit c0c8dff6ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 287 additions and 150 deletions

View file

@ -14,3 +14,25 @@ inline-quotes = "single"
multiline-quotes = "double"
docstring-quotes = "double"
avoid-escape = true
[tool.ruff.pep8-naming]
ignore-names = [
"setUp",
"tearDown",
"setUpClass",
"tearDownClass",
"setUpModule",
"tearDownModule",
"asyncSetUp",
"asyncTearDown",
"setUpTestData",
"failureException",
"longMessage",
"maxDiff",
]
classmethod-decorators = [
"classmethod",
]
staticmethod-decorators = [
"staticmethod",
]

View file

@ -236,7 +236,11 @@ where
}
if self.settings.enabled.contains(&CheckCode::N802) {
if let Some(check) = pep8_naming::checks::invalid_function_name(stmt, name) {
if let Some(check) = pep8_naming::checks::invalid_function_name(
stmt,
name,
&self.settings.pep8_naming,
) {
self.checks.push(check);
}
}
@ -247,6 +251,7 @@ where
self.current_scope(),
decorator_list,
args,
&self.settings.pep8_naming,
)
{
self.checks.push(check);
@ -258,6 +263,7 @@ where
self.current_scope(),
decorator_list,
args,
&self.settings.pep8_naming,
) {
self.checks.push(check);
}

View file

@ -1,4 +1,4 @@
//! Settings for the `flake_quotes` plugin.
//! Settings for the `flake-quotes` plugin.
use serde::{Deserialize, Serialize};
@ -27,12 +27,12 @@ pub struct Settings {
}
impl Settings {
pub fn from_config(config: Options) -> Self {
pub fn from_options(options: Options) -> Self {
Self {
inline_quotes: config.inline_quotes.unwrap_or(Quote::Single),
multiline_quotes: config.multiline_quotes.unwrap_or(Quote::Double),
docstring_quotes: config.docstring_quotes.unwrap_or(Quote::Double),
avoid_escape: config.avoid_escape.unwrap_or(true),
inline_quotes: options.inline_quotes.unwrap_or(Quote::Single),
multiline_quotes: options.multiline_quotes.unwrap_or(Quote::Double),
docstring_quotes: options.docstring_quotes.unwrap_or(Quote::Double),
avoid_escape: options.avoid_escape.unwrap_or(true),
}
}
}

View file

@ -3,7 +3,9 @@ use rustpython_ast::{Arguments, Expr, ExprKind, Stmt};
use crate::ast::types::{FunctionScope, Range, Scope, ScopeKind};
use crate::checks::{Check, CheckKind};
use crate::pep8_naming::settings::Settings;
/// N801
pub fn invalid_class_name(class_def: &Stmt, name: &str) -> Option<Check> {
let stripped = name.strip_prefix('_').unwrap_or(name);
if !stripped
@ -21,23 +23,14 @@ pub fn invalid_class_name(class_def: &Stmt, name: &str) -> Option<Check> {
None
}
const IGNORE_NAMES: &[&str] = &[
"setUp",
"tearDown",
"setUpClass",
"tearDownClass",
"setUpModule",
"tearDownModule",
"asyncSetUp",
"asyncTearDown",
"setUpTestData",
"failureException",
"longMessage",
"maxDiff",
];
pub fn invalid_function_name(func_def: &Stmt, name: &str) -> Option<Check> {
if !is_lower(name) && !IGNORE_NAMES.contains(&name) {
/// N802
pub fn invalid_function_name(func_def: &Stmt, name: &str, settings: &Settings) -> Option<Check> {
if !is_lower(name)
&& !settings
.ignore_names
.iter()
.any(|ignore_name| ignore_name == name)
{
return Some(Check::new(
CheckKind::InvalidFunctionName(name.to_string()),
Range::from_located(func_def),
@ -46,6 +39,7 @@ pub fn invalid_function_name(func_def: &Stmt, name: &str) -> Option<Check> {
None
}
/// N803
pub fn invalid_argument_name(location: Range, name: &str) -> Option<Check> {
if !is_lower(name) {
return Some(Check::new(
@ -56,10 +50,12 @@ pub fn invalid_argument_name(location: Range, name: &str) -> Option<Check> {
None
}
/// N804
pub fn invalid_first_argument_name_for_class_method(
scope: &Scope,
decorator_list: &[Expr],
args: &Arguments,
settings: &Settings,
) -> Option<Check> {
if !matches!(scope.kind, ScopeKind::Class) {
return None;
@ -67,7 +63,7 @@ pub fn invalid_first_argument_name_for_class_method(
if decorator_list.iter().any(|decorator| {
if let ExprKind::Name { id, .. } = &decorator.node {
id == "classmethod"
settings.classmethod_decorators.contains(id)
} else {
false
}
@ -84,10 +80,12 @@ pub fn invalid_first_argument_name_for_class_method(
None
}
/// N805
pub fn invalid_first_argument_name_for_method(
scope: &Scope,
decorator_list: &[Expr],
args: &Arguments,
settings: &Settings,
) -> Option<Check> {
if !matches!(scope.kind, ScopeKind::Class) {
return None;
@ -95,7 +93,8 @@ pub fn invalid_first_argument_name_for_method(
if decorator_list.iter().any(|decorator| {
if let ExprKind::Name { id, .. } = &decorator.node {
id == "classmethod" || id == "staticmethod"
settings.classmethod_decorators.contains(id)
|| settings.staticmethod_decorators.contains(id)
} else {
false
}
@ -114,6 +113,7 @@ pub fn invalid_first_argument_name_for_method(
None
}
/// N806
pub fn non_lowercase_variable_in_function(scope: &Scope, expr: &Expr, name: &str) -> Option<Check> {
if !matches!(scope.kind, ScopeKind::Function(FunctionScope { .. })) {
return None;
@ -127,6 +127,83 @@ pub fn non_lowercase_variable_in_function(scope: &Scope, expr: &Expr, name: &str
None
}
/// N807
pub fn dunder_function_name(func_def: &Stmt, scope: &Scope, name: &str) -> Option<Check> {
if matches!(scope.kind, ScopeKind::Class) {
return None;
}
if name.starts_with("__") && name.ends_with("__") {
return Some(Check::new(
CheckKind::DunderFunctionName,
Range::from_located(func_def),
));
}
None
}
/// N811
pub fn constant_imported_as_non_constant(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_upper(name) && !is_upper(asname) {
return Some(Check::new(
CheckKind::ConstantImportedAsNonConstant(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
/// N812
pub fn lowercase_imported_as_non_lowercase(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_lower(name) && asname.to_lowercase() != asname {
return Some(Check::new(
CheckKind::LowercaseImportedAsNonLowercase(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
/// N813
pub fn camelcase_imported_as_lowercase(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_camelcase(name) && is_lower(asname) {
return Some(Check::new(
CheckKind::CamelcaseImportedAsLowercase(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
/// N814
pub fn camelcase_imported_as_constant(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_camelcase(name) && is_upper(asname) && !is_acronym(name, asname) {
return Some(Check::new(
CheckKind::CamelcaseImportedAsConstant(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
/// N815
pub fn mixed_case_variable_in_class_scope(scope: &Scope, expr: &Expr, name: &str) -> Option<Check> {
if !matches!(scope.kind, ScopeKind::Class) {
return None;
@ -140,6 +217,7 @@ pub fn mixed_case_variable_in_class_scope(scope: &Scope, expr: &Expr, name: &str
None
}
/// N816
pub fn mixed_case_variable_in_global_scope(
scope: &Scope,
expr: &Expr,
@ -157,18 +235,41 @@ pub fn mixed_case_variable_in_global_scope(
None
}
pub fn dunder_function_name(func_def: &Stmt, scope: &Scope, name: &str) -> Option<Check> {
if matches!(scope.kind, ScopeKind::Class) {
return None;
}
if name.starts_with("__") && name.ends_with("__") {
/// N817
pub fn camelcase_imported_as_acronym(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_camelcase(name) && is_upper(asname) && is_acronym(name, asname) {
return Some(Check::new(
CheckKind::DunderFunctionName,
Range::from_located(func_def),
CheckKind::CamelcaseImportedAsAcronym(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
/// N818
pub fn error_suffix_on_exception_name(
class_def: &Stmt,
bases: &[Expr],
name: &str,
) -> Option<Check> {
if bases.iter().any(|base| {
if let ExprKind::Name { id, .. } = &base.node {
id == "Exception"
} else {
false
}
}) {
if !name.ends_with("Error") {
return Some(Check::new(
CheckKind::ErrorSuffixOnExceptionName(name.to_string()),
Range::from_located(class_def),
));
}
}
None
}
@ -196,34 +297,6 @@ fn is_upper(s: &str) -> bool {
cased
}
pub fn constant_imported_as_non_constant(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_upper(name) && !is_upper(asname) {
return Some(Check::new(
CheckKind::ConstantImportedAsNonConstant(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
pub fn lowercase_imported_as_non_lowercase(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_lower(name) && asname.to_lowercase() != asname {
return Some(Check::new(
CheckKind::LowercaseImportedAsNonLowercase(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
fn is_camelcase(name: &str) -> bool {
!is_lower(name) && !is_upper(name) && !name.contains('_')
}
@ -242,70 +315,6 @@ fn is_acronym(name: &str, asname: &str) -> bool {
name.chars().filter(|c| c.is_uppercase()).join("") == asname
}
pub fn camelcase_imported_as_lowercase(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_camelcase(name) && is_lower(asname) {
return Some(Check::new(
CheckKind::CamelcaseImportedAsLowercase(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
pub fn camelcase_imported_as_constant(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_camelcase(name) && is_upper(asname) && !is_acronym(name, asname) {
return Some(Check::new(
CheckKind::CamelcaseImportedAsConstant(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
pub fn camelcase_imported_as_acronym(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_camelcase(name) && is_upper(asname) && is_acronym(name, asname) {
return Some(Check::new(
CheckKind::CamelcaseImportedAsAcronym(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
pub fn error_suffix_on_exception_name(
class_def: &Stmt,
bases: &[Expr],
name: &str,
) -> Option<Check> {
if bases.iter().any(|base| {
if let ExprKind::Name { id, .. } = &base.node {
id == "Exception"
} else {
false
}
}) {
if !name.ends_with("Error") {
return Some(Check::new(
CheckKind::ErrorSuffixOnExceptionName(name.to_string()),
Range::from_located(class_def),
));
}
}
None
}
#[cfg(test)]
mod tests {
use super::{is_acronym, is_camelcase, is_lower, is_mixed_case, is_upper};

View file

@ -1 +1,2 @@
pub mod checks;
pub mod settings;

View file

@ -0,0 +1,63 @@
//! Settings for the `pep8-naming` plugin.
use serde::Deserialize;
const IGNORE_NAMES: [&str; 12] = [
"setUp",
"tearDown",
"setUpClass",
"tearDownClass",
"setUpModule",
"tearDownModule",
"asyncSetUp",
"asyncTearDown",
"setUpTestData",
"failureException",
"longMessage",
"maxDiff",
];
const CLASSMETHOD_DECORATORS: [&str; 1] = ["classmethod"];
const STATICMETHOD_DECORATORS: [&str; 1] = ["staticmethod"];
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
pub ignore_names: Option<Vec<String>>,
pub classmethod_decorators: Option<Vec<String>>,
pub staticmethod_decorators: Option<Vec<String>>,
}
#[derive(Debug)]
pub struct Settings {
pub ignore_names: Vec<String>,
pub classmethod_decorators: Vec<String>,
pub staticmethod_decorators: Vec<String>,
}
impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
ignore_names: options
.ignore_names
.unwrap_or_else(|| IGNORE_NAMES.map(String::from).to_vec()),
classmethod_decorators: options
.classmethod_decorators
.unwrap_or_else(|| CLASSMETHOD_DECORATORS.map(String::from).to_vec()),
staticmethod_decorators: options
.staticmethod_decorators
.unwrap_or_else(|| STATICMETHOD_DECORATORS.map(String::from).to_vec()),
}
}
}
impl Default for Settings {
fn default() -> Self {
Self {
ignore_names: IGNORE_NAMES.map(String::from).to_vec(),
classmethod_decorators: CLASSMETHOD_DECORATORS.map(String::from).to_vec(),
staticmethod_decorators: STATICMETHOD_DECORATORS.map(String::from).to_vec(),
}
}
}

View file

@ -8,9 +8,9 @@ use once_cell::sync::Lazy;
use regex::Regex;
use crate::checks_gen::CheckCodePrefix;
use crate::flake8_quotes;
use crate::settings::pyproject::load_options;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::{flake8_quotes, pep8_naming};
#[derive(Debug)]
pub struct Configuration {
@ -26,6 +26,7 @@ pub struct Configuration {
pub target_version: PythonVersion,
// Plugins
pub flake8_quotes: flake8_quotes::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings,
}
static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
@ -98,7 +99,11 @@ impl Configuration {
// Plugins
flake8_quotes: options
.flake8_quotes
.map(flake8_quotes::settings::Settings::from_config)
.map(flake8_quotes::settings::Settings::from_options)
.unwrap_or_default(),
pep8_naming: options
.pep8_naming
.map(pep8_naming::settings::Settings::from_options)
.unwrap_or_default(),
})
}

View file

@ -8,9 +8,9 @@ use regex::Regex;
use crate::checks::CheckCode;
use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity};
use crate::flake8_quotes;
use crate::settings::configuration::Configuration;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::{flake8_quotes, pep8_naming};
pub mod configuration;
pub mod options;
@ -29,6 +29,7 @@ pub struct Settings {
pub target_version: PythonVersion,
// Plugins
pub flake8_quotes: flake8_quotes::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings,
}
impl Settings {
@ -45,6 +46,7 @@ impl Settings {
extend_exclude: config.extend_exclude,
flake8_quotes: config.flake8_quotes,
line_length: config.line_length,
pep8_naming: config.pep8_naming,
per_file_ignores: config.per_file_ignores,
target_version: config.target_version,
}
@ -60,6 +62,7 @@ impl Settings {
per_file_ignores: vec![],
target_version: PythonVersion::Py310,
flake8_quotes: Default::default(),
pep8_naming: Default::default(),
}
}
@ -73,6 +76,7 @@ impl Settings {
per_file_ignores: vec![],
target_version: PythonVersion::Py310,
flake8_quotes: Default::default(),
pep8_naming: Default::default(),
}
}
}

View file

@ -3,8 +3,8 @@
use serde::Deserialize;
use crate::checks_gen::CheckCodePrefix;
use crate::flake8_quotes;
use crate::settings::types::{PythonVersion, StrCheckCodePair};
use crate::{flake8_quotes, pep8_naming};
#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
@ -24,5 +24,7 @@ pub struct Options {
pub per_file_ignores: Vec<StrCheckCodePair>,
pub dummy_variable_rgx: Option<String>,
pub target_version: Option<PythonVersion>,
// Plugins
pub flake8_quotes: Option<flake8_quotes::settings::Options>,
pub pep8_naming: Option<pep8_naming::settings::Options>,
}

View file

@ -94,12 +94,12 @@ mod tests {
use anyhow::Result;
use crate::checks_gen::CheckCodePrefix;
use crate::flake8_quotes;
use crate::flake8_quotes::settings::Quote;
use crate::settings::pyproject::{
find_project_root, find_pyproject_toml, parse_pyproject_toml, Options, Pyproject, Tools,
};
use crate::settings::types::StrCheckCodePair;
use crate::{flake8_quotes, pep8_naming};
#[test]
fn deserialize() -> Result<()> {
@ -133,7 +133,8 @@ mod tests {
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
flake8_quotes: None
flake8_quotes: None,
pep8_naming: None,
})
})
);
@ -159,7 +160,8 @@ line-length = 79
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
flake8_quotes: None
flake8_quotes: None,
pep8_naming: None,
})
})
);
@ -185,7 +187,8 @@ exclude = ["foo.py"]
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
flake8_quotes: None
flake8_quotes: None,
pep8_naming: None,
})
})
);
@ -211,7 +214,8 @@ select = ["E501"]
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
flake8_quotes: None
flake8_quotes: None,
pep8_naming: None,
})
})
);
@ -238,7 +242,8 @@ ignore = ["E501"]
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
flake8_quotes: None
flake8_quotes: None,
pep8_naming: None,
})
})
);
@ -316,7 +321,25 @@ other-attribute = 1
multiline_quotes: Some(Quote::Double),
docstring_quotes: Some(Quote::Double),
avoid_escape: Some(true),
})
}),
pep8_naming: Some(pep8_naming::settings::Options {
ignore_names: Some(vec![
"setUp".to_string(),
"tearDown".to_string(),
"setUpClass".to_string(),
"tearDownClass".to_string(),
"setUpModule".to_string(),
"tearDownModule".to_string(),
"asyncSetUp".to_string(),
"asyncTearDown".to_string(),
"setUpTestData".to_string(),
"failureException".to_string(),
"longMessage".to_string(),
"maxDiff".to_string(),
]),
classmethod_decorators: Some(vec!["classmethod".to_string()]),
staticmethod_decorators: Some(vec!["staticmethod".to_string()]),
}),
}
);

View file

@ -6,7 +6,7 @@ use regex::Regex;
use crate::checks_gen::CheckCodePrefix;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::{flake8_quotes, Configuration};
use crate::{flake8_quotes, pep8_naming, Configuration};
/// Struct to render user-facing exclusion patterns.
#[derive(Debug)]
@ -46,6 +46,7 @@ pub struct UserConfiguration {
pub target_version: PythonVersion,
// Plugins
pub flake8_quotes: flake8_quotes::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings,
// Non-settings exposed to the user
pub project_root: Option<PathBuf>,
pub pyproject: Option<PathBuf>,
@ -53,30 +54,31 @@ pub struct UserConfiguration {
impl UserConfiguration {
pub fn from_configuration(
settings: Configuration,
configuration: Configuration,
project_root: Option<PathBuf>,
pyproject: Option<PathBuf>,
) -> Self {
Self {
dummy_variable_rgx: settings.dummy_variable_rgx,
exclude: settings
dummy_variable_rgx: configuration.dummy_variable_rgx,
exclude: configuration
.exclude
.into_iter()
.map(Exclusion::from_file_pattern)
.collect(),
extend_exclude: settings
extend_exclude: configuration
.extend_exclude
.into_iter()
.map(Exclusion::from_file_pattern)
.collect(),
extend_ignore: settings.extend_ignore,
extend_select: settings.extend_select,
ignore: settings.ignore,
line_length: settings.line_length,
per_file_ignores: settings.per_file_ignores,
select: settings.select,
target_version: settings.target_version,
flake8_quotes: settings.flake8_quotes,
extend_ignore: configuration.extend_ignore,
extend_select: configuration.extend_select,
ignore: configuration.ignore,
line_length: configuration.line_length,
per_file_ignores: configuration.per_file_ignores,
select: configuration.select,
target_version: configuration.target_version,
flake8_quotes: configuration.flake8_quotes,
pep8_naming: configuration.pep8_naming,
project_root,
pyproject,
}