Make setting and retrieving pydocstyle settings less tedious (#12582)

This commit is contained in:
Alex Waygood 2024-07-31 10:39:33 +01:00 committed by GitHub
parent 138e70bd5c
commit 83b1c48a93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 146 additions and 134 deletions

View file

@ -1,14 +1,11 @@
use std::collections::BTreeSet;
use ruff_python_ast::helpers::map_callable;
use ruff_python_ast::name::QualifiedName;
use ruff_python_semantic::{Definition, SemanticModel};
use ruff_source_file::UniversalNewlines;
use crate::docstrings::sections::{SectionContexts, SectionKind};
use crate::docstrings::styles::SectionStyle;
use crate::docstrings::Docstring;
use crate::rules::pydocstyle::settings::Convention;
use crate::rules::pydocstyle::settings::{Convention, Settings};
/// Return the index of the first logical line in a string.
pub(super) fn logical_line(content: &str) -> Option<usize> {
@ -45,10 +42,12 @@ pub(super) fn ends_with_backslash(line: &str) -> bool {
/// Check decorator list to see if function should be ignored.
pub(crate) fn should_ignore_definition(
definition: &Definition,
ignore_decorators: &BTreeSet<String>,
settings: &Settings,
semantic: &SemanticModel,
) -> bool {
if ignore_decorators.is_empty() {
let ignore_decorators = settings.ignore_decorators();
if ignore_decorators.len() == 0 {
return false;
}
@ -61,15 +60,15 @@ pub(crate) fn should_ignore_definition(
.resolve_qualified_name(map_callable(&decorator.expression))
.is_some_and(|qualified_name| {
ignore_decorators
.iter()
.any(|decorator| QualifiedName::from_dotted_name(decorator) == qualified_name)
.clone()
.any(|decorator| decorator == qualified_name)
})
})
}
pub(crate) fn get_section_contexts<'a>(
docstring: &'a Docstring<'a>,
convention: Option<&'a Convention>,
convention: Option<Convention>,
) -> SectionContexts<'a> {
match convention {
Some(Convention::Google) => {

View file

@ -5,7 +5,6 @@ pub mod settings;
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use std::path::Path;
use anyhow::Result;
@ -98,13 +97,11 @@ mod tests {
let diagnostics = test_path(
Path::new("pydocstyle").join(path).as_path(),
&settings::LinterSettings {
pydocstyle: Settings {
convention: None,
ignore_decorators: BTreeSet::from_iter(["functools.wraps".to_string()]),
property_decorators: BTreeSet::from_iter([
"gi.repository.GObject.Property".to_string()
]),
},
pydocstyle: Settings::new(
None,
["functools.wraps".to_string()],
["gi.repository.GObject.Property".to_string()],
),
..settings::LinterSettings::for_rule(rule_code)
},
)?;
@ -129,11 +126,7 @@ mod tests {
&settings::LinterSettings {
// When inferring the convention, we'll see a few false negatives.
// See: https://github.com/PyCQA/pydocstyle/issues/459.
pydocstyle: Settings {
convention: None,
ignore_decorators: BTreeSet::new(),
property_decorators: BTreeSet::new(),
},
pydocstyle: Settings::default(),
..settings::LinterSettings::for_rule(Rule::UndocumentedParam)
},
)?;
@ -147,11 +140,7 @@ mod tests {
Path::new("pydocstyle/D417.py"),
&settings::LinterSettings {
// With explicit Google convention, we should flag every function.
pydocstyle: Settings {
convention: Some(Convention::Google),
ignore_decorators: BTreeSet::new(),
property_decorators: BTreeSet::new(),
},
pydocstyle: Settings::new(Some(Convention::Google), [], []),
..settings::LinterSettings::for_rule(Rule::UndocumentedParam)
},
)?;
@ -164,12 +153,8 @@ mod tests {
let diagnostics = test_path(
Path::new("pydocstyle/D417.py"),
&settings::LinterSettings {
// With explicit Google convention, we shouldn't flag anything.
pydocstyle: Settings {
convention: Some(Convention::Numpy),
ignore_decorators: BTreeSet::new(),
property_decorators: BTreeSet::new(),
},
// With explicit numpy convention, we shouldn't flag anything.
pydocstyle: Settings::new(Some(Convention::Numpy), [], []),
..settings::LinterSettings::for_rule(Rule::UndocumentedParam)
},
)?;

View file

@ -1,11 +1,8 @@
use std::collections::BTreeSet;
use imperative::Mood;
use once_cell::sync::Lazy;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::name::QualifiedName;
use ruff_python_semantic::analyze::visibility::{is_property, is_test};
use ruff_source_file::UniversalNewlines;
use ruff_text_size::Ranged;
@ -13,6 +10,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::rules::pydocstyle::helpers::normalize_word;
use crate::rules::pydocstyle::settings::Settings;
static MOOD: Lazy<Mood> = Lazy::new(Mood::new);
@ -66,24 +64,21 @@ impl Violation for NonImperativeMood {
pub(crate) fn non_imperative_mood(
checker: &mut Checker,
docstring: &Docstring,
property_decorators: &BTreeSet<String>,
settings: &Settings,
) {
let Some(function) = docstring.definition.as_function_def() else {
return;
};
let property_decorators = property_decorators
.iter()
.map(|decorator| QualifiedName::from_dotted_name(decorator))
.collect::<Vec<QualifiedName>>();
if is_test(&function.name) {
return;
}
if is_test(&function.name)
|| is_property(
&function.decorator_list,
&property_decorators,
checker.semantic(),
)
{
if is_property(
&function.decorator_list,
settings.property_decorators(),
checker.semantic(),
) {
return;
}

View file

@ -1325,7 +1325,7 @@ pub(crate) fn sections(
checker: &mut Checker,
docstring: &Docstring,
section_contexts: &SectionContexts,
convention: Option<&Convention>,
convention: Option<Convention>,
) {
match convention {
Some(Convention::Google) => parse_google_sections(checker, docstring, section_contexts),

View file

@ -2,12 +2,14 @@
use std::collections::BTreeSet;
use std::fmt;
use std::iter::FusedIterator;
use serde::{Deserialize, Serialize};
use crate::display_settings;
use ruff_macros::CacheKey;
use ruff_python_ast::name::QualifiedName;
use crate::display_settings;
use crate::registry::Rule;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, CacheKey)]
@ -85,9 +87,36 @@ impl fmt::Display for Convention {
#[derive(Debug, Clone, Default, CacheKey)]
pub struct Settings {
pub convention: Option<Convention>,
pub ignore_decorators: BTreeSet<String>,
pub property_decorators: BTreeSet<String>,
convention: Option<Convention>,
ignore_decorators: BTreeSet<String>,
property_decorators: BTreeSet<String>,
}
impl Settings {
#[must_use]
pub fn new(
convention: Option<Convention>,
ignore_decorators: impl IntoIterator<Item = String>,
property_decorators: impl IntoIterator<Item = String>,
) -> Self {
Self {
convention,
ignore_decorators: ignore_decorators.into_iter().collect(),
property_decorators: property_decorators.into_iter().collect(),
}
}
pub fn convention(&self) -> Option<Convention> {
self.convention
}
pub fn ignore_decorators(&self) -> DecoratorIterator {
DecoratorIterator::new(&self.ignore_decorators)
}
pub fn property_decorators(&self) -> DecoratorIterator {
DecoratorIterator::new(&self.property_decorators)
}
}
impl fmt::Display for Settings {
@ -104,3 +133,34 @@ impl fmt::Display for Settings {
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct DecoratorIterator<'a> {
decorators: std::collections::btree_set::Iter<'a, String>,
}
impl<'a> DecoratorIterator<'a> {
fn new(decorators: &'a BTreeSet<String>) -> Self {
Self {
decorators: decorators.iter(),
}
}
}
impl<'a> Iterator for DecoratorIterator<'a> {
type Item = QualifiedName<'a>;
fn next(&mut self) -> Option<QualifiedName<'a>> {
self.decorators
.next()
.map(|deco| QualifiedName::from_dotted_name(deco))
}
}
impl FusedIterator for DecoratorIterator<'_> {}
impl ExactSizeIterator for DecoratorIterator<'_> {
fn len(&self) -> usize {
self.decorators.len()
}
}