diff --git a/.python-version b/.python-version deleted file mode 100644 index e4fba21..0000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.12 diff --git a/Cargo.lock b/Cargo.lock index a41062f..cfa0c7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -276,12 +276,6 @@ dependencies = [ "hashbrown 0.15.2", ] -[[package]] -name = "indoc" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" - [[package]] name = "is-macro" version = "0.3.7" @@ -403,15 +397,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "num-integer" version = "0.1.46" @@ -492,12 +477,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "portable-atomic" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -525,69 +504,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "pyo3" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" -dependencies = [ - "cfg-if", - "indoc", - "libc", - "memoffset", - "once_cell", - "portable-atomic", - "pyo3-build-config", - "pyo3-ffi", - "pyo3-macros", - "unindent", -] - -[[package]] -name = "pyo3-build-config" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" -dependencies = [ - "once_cell", - "target-lexicon", -] - -[[package]] -name = "pyo3-ffi" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" -dependencies = [ - "libc", - "pyo3-build-config", -] - -[[package]] -name = "pyo3-macros" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" -dependencies = [ - "proc-macro2", - "pyo3-macros-backend", - "quote", - "syn", -] - -[[package]] -name = "pyo3-macros-backend" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" -dependencies = [ - "heck", - "proc-macro2", - "pyo3-build-config", - "quote", - "syn", -] - [[package]] name = "quote" version = "1.0.40" @@ -814,12 +730,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "tiny-keccak" version = "2.0.2" @@ -938,12 +848,6 @@ dependencies = [ "rand", ] -[[package]] -name = "unindent" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" - [[package]] name = "utf8parse" version = "0.2.2" @@ -962,7 +866,6 @@ version = "0.1.0" dependencies = [ "clap", "lazy_static", - "pyo3", "regex", "rstest", "rustpython-ast", diff --git a/Cargo.toml b/Cargo.toml index 4546f19..333a131 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,19 +3,12 @@ name = "vipyrdocs" version = "0.1.0" edition = "2021" -[lib] -name = "_core" -# "cdylib" is necessary to produce a shared library for Python to import from. -crate-type = ["cdylib"] [[bin]] name = "vipyrdocs" path = "src/main.rs" # Adjust if your binary source file is elsewhere [dependencies] lazy_static = "1.5.0" -# "extension-module" tells pyo3 we want to build an extension module (skips linking against libpython.so) -# "abi3-py39" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.9 -pyo3 = { version = "0.22.4", features = ["extension-module", "abi3-py39"] } regex = "1.11.1" rustpython-ast = { version = "0.4.0", features = ["visitor"] } rustpython-parser = "0.4.0" diff --git a/README.md b/README.md index 962a075..ec3d961 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,6 @@ Build from source with [cargo](https://www.rust-lang.org/tools/install): cargo install --path . ``` -Or use maturin to build a Python-compatible wheel: - -``` -maturin develop -``` - ## ๐Ÿงช Usage ``` diff --git a/examples/multiline_test_docstrings.py b/examples/multiline_test_docstrings.py new file mode 100644 index 0000000..65e0504 --- /dev/null +++ b/examples/multiline_test_docstrings.py @@ -0,0 +1,64 @@ +""" +Examples of multi-line test docstrings. + +This file demonstrates the new feature that allows test docstrings to have +multi-line descriptions for arrange/act/assert or given/when/then patterns +using indentation-based continuation. +""" + + +def test_arrange_act_assert_pattern(): + """ + arrange: This is a very important part of a very important test so + the arrange sentence has to be loooong and span multiple lines + to properly describe the complex setup. + act: Do the test. + assert: It better not fail ๐Ÿ˜ . + """ + # Test implementation here + pass + + +def test_given_when_then_pattern(): + """ + given: A complex setup scenario that requires multiple lines + to properly explain all the preconditions and + initial state of the system under test. + when: The user performs an action. + then: The system should respond appropriately with + the expected behavior and state changes. + """ + # Test implementation here + pass + + +def test_multiline_continuation(): + """ + arrange: First we need to set up the database with + initial data that includes users and + their associated permissions and + various configuration settings that + are necessary for this particular test scenario. + act: Execute the migration script. + assert: All tables should be updated correctly and + all constraints should be in place. + """ + # Test implementation here + pass + + +def function_with_multiline_args(param1, param2, param3): + """Function demonstrating multiline Args section. + + Args: + param1: This is a very long parameter description that needs to + span multiple lines because it describes something complex + and important about the parameter usage. + param2: Short description. + param3: Another long description that also + needs multiple lines to explain properly. + + Returns: + A result value. + """ + return param1 + param2 + param3 diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 85afd67..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,30 +0,0 @@ -[project] -name = "ruff-docstrings-complete" -version = "0.1.0" -description = "Add your description here" -readme = "README.md" -authors = [ - { name = "Ali Ugur", email = "ali.ugur@canonical.com" } -] -requires-python = ">=3.12" -dependencies = [ - "maturin>=1.8.3", - "pytest>=8.3.5", -] - -[project.scripts] -ruff-docstrings-complete = "ruff_docstrings_complete:main" - -[tool.maturin] -module-name = "ruff_docstrings_complete._core" -python-packages = ["ruff_docstrings_complete"] -python-source = "src" - -[build-system] -requires = ["maturin>=1.0,<2.0"] -build-backend = "maturin" - -[dependency-groups] -dev = [ - "pytest>=8.3.5", -] diff --git a/src/constants.rs b/src/constants.rs index bad72e2..61188c2 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,5 +1,5 @@ pub const ERROR_CODE_PREFIX: &str = "D"; -pub const MORE_INFO_BASE: &str = " (more info: https://example.com/"; +pub const MORE_INFO_BASE: &str = ""; //" (more info: https://example.com/"; pub fn docstr_missing_code() -> String { format!("{}010", ERROR_CODE_PREFIX) @@ -315,3 +315,17 @@ pub fn attr_in_docstr_msg(_attribute: &str) -> String { attr_in_docstr_code().to_lowercase() ) } + +pub fn duplicate_attr_docstr_code() -> String { + format!("{}065", ERROR_CODE_PREFIX) +} + +pub fn duplicate_attr_docstr_msg(_attribute: &str) -> String { + format!( + "{} {} attribute documented multiple times {}{}", + duplicate_attr_docstr_code(), + _attribute, + MORE_INFO_BASE, + attr_in_docstr_code().to_lowercase() + ) +} diff --git a/src/debug_test.rs b/src/debug_test.rs new file mode 100644 index 0000000..bfb56f8 --- /dev/null +++ b/src/debug_test.rs @@ -0,0 +1,23 @@ +#[cfg(test)] +mod test_debug_attr { + use crate::rule_engine::lint_file; + + #[test] + fn test_simple_property() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + """ + @property + def attr_1(): + """Docstring 2.""" + return "value 1" +"#; + let output = lint_file(code, None); + println!("Test output: {:?}", output); + // Let test fail to see output + assert!(false, "Debug test - output: {:?}", output); + } +} \ No newline at end of file diff --git a/src/docstring.rs b/src/docstring.rs index fe3f07b..921334d 100644 --- a/src/docstring.rs +++ b/src/docstring.rs @@ -1,11 +1,11 @@ -use pyo3::prelude::*; - use regex::Regex; use rustpython_ast::text_size::TextRange; +#[cfg(test)] +use rustpython_ast::text_size::TextSize; use rustpython_ast::ExprConstant; -use rustpython_ast::TextSize; use std::collections::HashMap; use std::collections::HashSet; +use std::fmt; lazy_static::lazy_static! { static ref _SECTION_NAMES: HashMap<&'static str, HashSet<&'static str>> = { @@ -25,48 +25,16 @@ lazy_static::lazy_static! { } lazy_static::lazy_static! { - static ref _SUB_SECTION_PATTERN : regex::Regex = regex::Regex::new(r"\s*(\w+)( \(.*\))?:").unwrap(); + static ref _SUB_SECTION_PATTERN: Regex = Regex::new(r"\s*(\w+)( \(.*\))?:").unwrap(); } -#[pyclass] -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct _Section { name: Option, subs: Vec, } -#[pymethods] -impl _Section { - #[new] - #[pyo3(signature = (name=None, subs=None))] - fn new(name: Option, subs: Option>) -> Self { - let subsections = subs.unwrap_or_else(|| vec![]); - _Section { - name, - subs: subsections, - } - } - fn __eq__(&self, other: &Self) -> PyResult { - let mut self_subs = self.subs.clone(); - let mut other_subs = other.subs.clone(); - self_subs.sort(); - other_subs.sort(); - - Ok(self.name == other.name && self_subs == other_subs) - } - - fn __repr__(&self) -> String { - let name_str = match &self.name { - Some(name) => format!("\"{}\"", name), - None => "None".to_string(), - }; - let subs_str = format!("{:?}", self.subs); - format!("_Section(name={}, subs={})", name_str, subs_str) - } -} - -#[pyclass] -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub struct Docstring { args: Option>, args_sections: Option>, @@ -104,182 +72,125 @@ impl Docstring { } } - fn __eq__(&self, other: &Docstring) -> PyResult { - fn sorted(opt: &Option>) -> Vec { - let mut v = opt.clone().unwrap_or_default(); - v.sort(); - v - } - - Ok(sorted(&self.args) == sorted(&other.args) - && sorted(&self.args_sections) == sorted(&other.args_sections) - && sorted(&self.attrs) == sorted(&other.attrs) - && sorted(&self.attrs_sections) == sorted(&other.attrs_sections) - && sorted(&self.returns_sections) == sorted(&other.returns_sections) - && sorted(&self.yields_sections) == sorted(&other.yields_sections) - && sorted(&self.raises) == sorted(&other.raises) - && sorted(&self.raises_sections) == sorted(&other.raises_sections)) - } - pub fn __repr__(&self) -> String { - format!( - "Docstring(\n args={:?},\n args_sections={:?},\n attrs={:?},\n attrs_sections={:?},\n returns_sections={:?},\n yields_sections={:?},\n raises={:?},\n raises_sections={:?},\n range={:?})", - self.args, - self.args_sections, - self.attrs, - self.attrs_sections, - self.returns_sections, - self.yields_sections, - self.raises, - self.raises_sections, - self.range, - ) - } - - pub fn is_empty(&self) -> bool { - if self.args.is_some() - || self.args_sections.is_some() - || self.attrs.is_some() - || self.attrs_sections.is_some() - || self.returns_sections.is_some() - || self.yields_sections.is_some() - || self.raises.is_some() - || self.raises_sections.is_some() - { - return false; - } - true - } - pub fn has_returns(&self) -> bool { - if self.returns_sections.is_none() { - return false; - } - if self.returns_sections.clone().unwrap().is_empty() { - return false; - } - true + self.returns_sections + .as_ref() + .map(|sections| !sections.is_empty()) + .unwrap_or(false) } + pub fn get_returns(&self) -> Vec { - if self.returns_sections.is_none() { - return Vec::::new(); - } - self.returns_sections.clone().unwrap() + self.returns_sections.clone().unwrap_or_default() } - pub fn has_raises(&self) -> bool { - if self.raises.is_none() { - return false; - } - if self.raises.clone().unwrap().is_empty() { - return false; - } - true - } pub fn has_raises_sections(&self) -> bool { - if self.raises_sections.is_none() { - return false; - } - if self.raises_sections.clone().unwrap().is_empty() { - return false; - } - true + self.raises_sections + .as_ref() + .map(|sections| !sections.is_empty()) + .unwrap_or(false) } + pub fn get_raises(&self) -> Vec { - if self.raises.is_none() { - return Vec::::new(); - } - self.raises.clone().unwrap() + self.raises.clone().unwrap_or_default() } + pub fn get_raises_sections(&self) -> Vec { - if self.raises_sections.is_none() { - return Vec::::new(); - } - self.raises_sections.clone().unwrap() + self.raises_sections.clone().unwrap_or_default() } pub fn has_yields(&self) -> bool { - if self.yields_sections.is_none() { - return false; - } - if self.yields_sections.clone().unwrap().is_empty() { - return false; - } - true + self.yields_sections + .as_ref() + .map(|sections| !sections.is_empty()) + .unwrap_or(false) } + pub fn get_yields(&self) -> Vec { - if self.yields_sections.is_none() { - return Vec::::new(); - } - self.yields_sections.clone().unwrap() + self.yields_sections.clone().unwrap_or_default() } + pub fn has_args_sections(&self) -> bool { - if self.args_sections.is_none() { - return false; - } - if self.args_sections.clone().unwrap().is_empty() { - return false; - } - true + self.args_sections + .as_ref() + .map(|sections| !sections.is_empty()) + .unwrap_or(false) } + pub fn get_args_sections(&self) -> Vec { - if self.args_sections.is_none() { - return Vec::::new(); - } - self.args_sections.clone().unwrap() + self.args_sections.clone().unwrap_or_default() } + pub fn has_args(&self) -> bool { - if self.args.is_none() { - return false; - } - if self.args.clone().unwrap().is_empty() { - return false; - } - true + self.args + .as_ref() + .map(|args| !args.is_empty()) + .unwrap_or(false) } + pub fn get_args(&self) -> Vec { - if self.args.is_none() { - return Vec::::new(); - } - self.args.clone().unwrap() + self.args.clone().unwrap_or_default() } + pub fn get_range(&self) -> TextRange { self.range } - pub fn has_attrs(&self) -> bool { - if self.attrs.is_none() { - return false; - } - if self.attrs.clone().unwrap().is_empty() { - return false; - } - true - } pub fn has_attrs_sections(&self) -> bool { - if self.attrs_sections.is_none() { - return false; - } - - if self.attrs_sections.clone().unwrap().is_empty() { - return false; - } - true + self.attrs_sections + .as_ref() + .map(|sections| !sections.is_empty()) + .unwrap_or(false) } + pub fn get_attrs(&self) -> Vec { - if self.attrs.is_none() { - return Vec::::new(); - } - self.attrs.clone().unwrap() + self.attrs.clone().unwrap_or_default() } + pub fn get_attrs_sections(&self) -> Vec { - if self.attrs_sections.is_none() { - return Vec::::new(); + self.attrs_sections.clone().unwrap_or_default() + } +} + +impl PartialEq for Docstring { + fn eq(&self, other: &Self) -> bool { + fn sorted(opt: &Option>) -> Vec { + let mut v = opt.clone().unwrap_or_default(); + v.sort(); + v } - self.attrs_sections.clone().unwrap() + + sorted(&self.args) == sorted(&other.args) + && sorted(&self.args_sections) == sorted(&other.args_sections) + && sorted(&self.attrs) == sorted(&other.attrs) + && sorted(&self.attrs_sections) == sorted(&other.attrs_sections) + && sorted(&self.returns_sections) == sorted(&other.returns_sections) + && sorted(&self.yields_sections) == sorted(&other.yields_sections) + && sorted(&self.raises) == sorted(&other.raises) + && sorted(&self.raises_sections) == sorted(&other.raises_sections) + && self.range == other.range + } +} + +impl Eq for Docstring {} + +impl fmt::Display for Docstring { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Docstring(\n args={:?},\n args_sections={:?},\n attrs={:?},\n attrs_sections={:?},\n returns_sections={:?},\n yields_sections={:?},\n raises={:?},\n raises_sections={:?},\n range={:?})", + self.args, + self.args_sections, + self.attrs, + self.attrs_sections, + self.returns_sections, + self.yields_sections, + self.raises, + self.raises_sections, + self.range, + ) } } -#[pyfunction] pub fn _get_sections(lines: Vec) -> Vec<_Section> { let cleaned_lines: Vec = lines .into_iter() @@ -313,14 +224,18 @@ pub fn _get_sections(lines: Vec) -> Vec<_Section> { section_lines.push(lines.next().unwrap()); } - let subs = section_lines - .iter() - .filter_map(|line| { - _SUB_SECTION_PATTERN - .captures(line) - .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())) - }) - .collect(); + // Extract subsections, handling multi-line descriptions + let mut subs: Vec = Vec::new(); + for line in section_lines.iter() { + // Check if this line starts a new subsection + if let Some(caps) = _SUB_SECTION_PATTERN.captures(line) { + if let Some(sub_name) = caps.get(1).map(|m| m.as_str().to_string()) { + subs.push(sub_name); + } + } + // Otherwise, it's a continuation line for the previous subsection - we just skip it + // (the description is not stored, only the subsection names) + } sections.push(_Section { name: section_name, @@ -367,7 +282,10 @@ fn _get_all_section_names_by_name<'a>(name: &str, sections: &'a [_Section]) -> O pub fn parse(constant_expr: &ExprConstant) -> Docstring { let value = constant_expr.clone().value.expect_str(); let sections = _get_sections(value.lines().map(|line| line.to_string()).collect()); + build_docstring_from_sections(sections, constant_expr.range) +} +fn build_docstring_from_sections(sections: Vec<_Section>, range: TextRange) -> Docstring { let args_section = _get_section_by_name("args", §ions); let attrs_section = _get_section_by_name("attrs", §ions); let raises_section = _get_section_by_name("raises", §ions); @@ -381,147 +299,15 @@ pub fn parse(constant_expr: &ExprConstant) -> Docstring { _get_all_section_names_by_name("yields", §ions), raises_section.map(|s| s.subs.clone()), _get_all_section_names_by_name("raises", §ions), - constant_expr.range, + range, ) } -////////// Tests - -struct TestInput { - input: Vec, - expected: Option>, -} +#[cfg(test)] +mod tests; -struct TestParseInput { - input: String, - expected: Docstring, -} - -#[test] -pub fn test_get_sections() { - let test_inputs = [ - TestInput { - input: vec!["".to_string()], - expected: None, - }, - TestInput { - input: vec![" ".to_string()], - expected: None, - }, - TestInput { - input: vec!["\t".to_string()], - expected: None, - }, - TestInput { - input: vec!["line 1".to_string()], - expected: Some(vec![_Section { - name: None, - subs: vec![], - }]), - }, - TestInput { - input: vec!["line 1".to_string(), "line 2".to_string()], - expected: Some(vec![_Section { - name: None, - subs: vec![], - }]), - }, - TestInput { - input: vec!["line 1".to_string(), "name_1:".to_string()], - expected: Some(vec![_Section { - name: None, - subs: vec!["name_1".to_string()], - }]), - }, - TestInput { - input: vec!["line 1:".to_string()], - expected: Some(vec![_Section { - name: None, - subs: vec![], - }]), - }, - TestInput { - input: vec!["name_1:".to_string()], - expected: Some(vec![_Section { - name: Some("name_1".to_string()), - subs: vec![], - }]), - }, - TestInput { - input: vec![" name_1:".to_string()], - expected: Some(vec![_Section { - name: Some("name_1".to_string()), - subs: vec![], - }]), - }, - TestInput { - input: vec!["\tname_1:".to_string()], - expected: Some(vec![_Section { - name: Some("name_1".to_string()), - subs: vec![], - }]), - }, - TestInput { - input: vec![" name_1:".to_string()], - expected: Some(vec![_Section { - name: Some("name_1".to_string()), - subs: vec![], - }]), - }, - TestInput { - input: vec!["name_1: ".to_string()], - expected: Some(vec![_Section { - name: Some("name_1".to_string()), - subs: vec![], - }]), - }, - TestInput { - input: vec!["name_1: description".to_string()], - expected: Some(vec![_Section { - name: Some("name_1".to_string()), - subs: vec![], - }]), - }, - ]; - - for input in test_inputs.iter() { - let returned_sections = _get_sections(input.input.clone()); - - println!("Line: {:?}", input.input); - println!( - "||| Comparing: {:?} with {:?}", - returned_sections, input.expected - ); - - match (&returned_sections.is_empty(), &input.expected) { - (true, None) => { - // Both are empty, test passes - println!("Test passed for input: {:?}\n\n\n", input.input); - } - (false, Some(expected_sections)) => { - // compare the element size - assert_eq!( - returned_sections.len(), - expected_sections.len(), - "Length mismatch for input: {:?}. Returned: {:?}, Expected: {:?}\n\n\n", - input.input, - returned_sections, - expected_sections - ); - - // Compare the vectors element by element - for (returned, expected) in returned_sections.iter().zip(expected_sections.iter()) { - println!("Returned: {:?}\nExpected: {:?}\n\n\n", returned, expected); - assert_eq!(returned, expected); - } - } - _ => { - // Mismatch between returned and expected - panic!( - "Test failed for input: {:?}. Returned: {:?}, Expected: {:?}", - input.input, returned_sections, input.expected - ); - } - } - } +#[cfg(test)] +pub(crate) fn parse_from_str_for_tests(value: &str) -> Docstring { + let sections = _get_sections(value.lines().map(|line| line.to_string()).collect()); + build_docstring_from_sections(sections, TextRange::new(TextSize::new(0), TextSize::new(0))) } diff --git a/src/docstring/tests.rs b/src/docstring/tests.rs new file mode 100644 index 0000000..6b625b4 --- /dev/null +++ b/src/docstring/tests.rs @@ -0,0 +1,740 @@ +use super::{parse_from_str_for_tests, Docstring, _Section, _get_sections}; +use rustpython_ast::text_size::{TextRange, TextSize}; + +#[derive(Clone, Copy)] +struct SectionExpectation { + name: Option<&'static str>, + subs: &'static [&'static str], +} + +struct SectionCase { + input: &'static [&'static str], + expected: &'static [SectionExpectation], +} + +fn build_section(expect: &SectionExpectation) -> _Section { + _Section { + name: expect.name.map(|name| name.to_string()), + subs: expect.subs.iter().map(|sub| sub.to_string()).collect(), + } +} + +#[test] +fn get_sections_handles_variety_of_inputs() { + let cases = [ + SectionCase { + input: &[""], + expected: &[], + }, + SectionCase { + input: &[" "], + expected: &[], + }, + SectionCase { + input: &["\t"], + expected: &[], + }, + SectionCase { + input: &["line 1"], + expected: &[SectionExpectation { + name: None, + subs: &[], + }], + }, + SectionCase { + input: &["line 1", "line 2"], + expected: &[SectionExpectation { + name: None, + subs: &[], + }], + }, + SectionCase { + input: &["line 1", "name_1:"], + expected: &[SectionExpectation { + name: None, + subs: &["name_1"], + }], + }, + SectionCase { + input: &["line 1:"], + expected: &[SectionExpectation { + name: None, + subs: &[], + }], + }, + SectionCase { + input: &["name_1:"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &[], + }], + }, + SectionCase { + input: &[" name_1:"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &[], + }], + }, + SectionCase { + input: &["\tname_1:"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &[], + }], + }, + SectionCase { + input: &[" name_1:"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &[], + }], + }, + SectionCase { + input: &["name_1: "], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &[], + }], + }, + SectionCase { + input: &["name_1: description"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &[], + }], + }, + SectionCase { + input: &["name_1:", "description 1"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &[], + }], + }, + SectionCase { + input: &["name_1:", "sub_name_1:"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &["sub_name_1"], + }], + }, + SectionCase { + input: &["name_1:", "sub_name_1 (text 1):"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &["sub_name_1"], + }], + }, + SectionCase { + input: &["name_1:", " sub_name_1:"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &["sub_name_1"], + }], + }, + SectionCase { + input: &["name_1:", "sub_name_1: "], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &["sub_name_1"], + }], + }, + SectionCase { + input: &["name_1:", "sub_name_1: description"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &["sub_name_1"], + }], + }, + SectionCase { + input: &["name_1:", "sub_name_1:", "description 1"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &["sub_name_1"], + }], + }, + SectionCase { + input: &["name_1:", "description 1", "sub_name_1:"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &["sub_name_1"], + }], + }, + SectionCase { + input: &["name_1:", "sub_name_1:", "sub_name_2:"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &["sub_name_1", "sub_name_2"], + }], + }, + SectionCase { + input: &["name_1:", "sub_name_1:", "sub_name_2:", "sub_name_3:"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &["sub_name_1", "sub_name_2", "sub_name_3"], + }], + }, + SectionCase { + input: &["name_1:", "description 1", "description 2"], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &[], + }], + }, + SectionCase { + input: &["name_1:", ""], + expected: &[SectionExpectation { + name: Some("name_1"), + subs: &[], + }], + }, + SectionCase { + input: &["name_1:", "", "name_2:"], + expected: &[ + SectionExpectation { + name: Some("name_1"), + subs: &[], + }, + SectionExpectation { + name: Some("name_2"), + subs: &[], + }, + ], + }, + SectionCase { + input: &["# name_1:", "# ", "# name_2:"], + expected: &[ + SectionExpectation { + name: Some("name_1"), + subs: &[], + }, + SectionExpectation { + name: Some("name_2"), + subs: &[], + }, + ], + }, + SectionCase { + input: &["# name_1:", " ", "# name_2:"], + expected: &[ + SectionExpectation { + name: Some("name_1"), + subs: &[], + }, + SectionExpectation { + name: Some("name_2"), + subs: &[], + }, + ], + }, + SectionCase { + input: &["name_1:", "\t", "name_2:"], + expected: &[ + SectionExpectation { + name: Some("name_1"), + subs: &[], + }, + SectionExpectation { + name: Some("name_2"), + subs: &[], + }, + ], + }, + SectionCase { + input: &["name_1:", " ", "name_2:"], + expected: &[ + SectionExpectation { + name: Some("name_1"), + subs: &[], + }, + SectionExpectation { + name: Some("name_2"), + subs: &[], + }, + ], + }, + SectionCase { + input: &["name_1:", "sub_name_1:", "", "name_2:"], + expected: &[ + SectionExpectation { + name: Some("name_1"), + subs: &["sub_name_1"], + }, + SectionExpectation { + name: Some("name_2"), + subs: &[], + }, + ], + }, + SectionCase { + input: &["name_1:", "", "name_2:", "sub_name_1:"], + expected: &[ + SectionExpectation { + name: Some("name_1"), + subs: &[], + }, + SectionExpectation { + name: Some("name_2"), + subs: &["sub_name_1"], + }, + ], + }, + SectionCase { + input: &["name_1:", "", "name_2:", "", "name_3:"], + expected: &[ + SectionExpectation { + name: Some("name_1"), + subs: &[], + }, + SectionExpectation { + name: Some("name_2"), + subs: &[], + }, + SectionExpectation { + name: Some("name_3"), + subs: &[], + }, + ], + }, + ]; + + for case in cases { + let rendered_input = case + .input + .iter() + .map(|line| line.to_string()) + .collect::>(); + let result = _get_sections(rendered_input); + + let expected = case + .expected + .iter() + .map(build_section) + .collect::>(); + + assert_eq!( + result, expected, + "Unexpected sections for input {:?}", + case.input + ); + } +} + +#[derive(Default)] +struct DocstringExpectation { + args: Option<&'static [&'static str]>, + args_sections: Option<&'static [&'static str]>, + attrs: Option<&'static [&'static str]>, + attrs_sections: Option<&'static [&'static str]>, + returns_sections: Option<&'static [&'static str]>, + yields_sections: Option<&'static [&'static str]>, + raises: Option<&'static [&'static str]>, + raises_sections: Option<&'static [&'static str]>, +} + +struct ParseCase { + value: &'static str, + expected: DocstringExpectation, +} + +fn to_string_vec(slice_opt: Option<&[&str]>) -> Option> { + slice_opt.map(|items| items.iter().map(|item| item.to_string()).collect()) +} + +fn build_expected_docstring(expect: &DocstringExpectation) -> Docstring { + Docstring::new( + to_string_vec(expect.args), + to_string_vec(expect.args_sections), + to_string_vec(expect.attrs), + to_string_vec(expect.attrs_sections), + to_string_vec(expect.returns_sections), + to_string_vec(expect.yields_sections), + to_string_vec(expect.raises), + to_string_vec(expect.raises_sections), + TextRange::new(TextSize::new(0), TextSize::new(0)), + ) +} + +#[test] +fn parse_extracts_expected_sections() { + let cases = [ + ParseCase { + value: "", + expected: DocstringExpectation::default(), + }, + ParseCase { + value: "short description", + expected: DocstringExpectation::default(), + }, + ParseCase { + value: "short description\n\nlong description", + expected: DocstringExpectation::default(), + }, + ParseCase { + value: "short description\n\nArgs:\n ", + expected: DocstringExpectation { + args: Some(&[]), + args_sections: Some(&["Args"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nArgs:\n arg_1:\n ", + expected: DocstringExpectation { + args: Some(&["arg_1"]), + args_sections: Some(&["Args"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\n# Args:\n# arg_1:\n# arg_2:\n# ", + expected: DocstringExpectation { + args: Some(&["arg_1", "arg_2"]), + args_sections: Some(&["Args"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nargs:\n arg_1:\n ", + expected: DocstringExpectation { + args: Some(&["arg_1"]), + args_sections: Some(&["args"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nArguments:\n arg_1:\n ", + expected: DocstringExpectation { + args: Some(&["arg_1"]), + args_sections: Some(&["Arguments"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nParameters:\n arg_1:\n ", + expected: DocstringExpectation { + args: Some(&["arg_1"]), + args_sections: Some(&["Parameters"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nArgs:\n arg_1:\n\nParameters:\n arg_2:\n ", + expected: DocstringExpectation { + args: Some(&["arg_1"]), + args_sections: Some(&["Args", "Parameters"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nAttrs:\n ", + expected: DocstringExpectation { + attrs: Some(&[]), + attrs_sections: Some(&["Attrs"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nAttrs:\n\nAttributes:\n ", + expected: DocstringExpectation { + attrs: Some(&[]), + attrs_sections: Some(&["Attrs", "Attributes"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nAttrs:\n\nAttrs:\n ", + expected: DocstringExpectation { + attrs: Some(&[]), + attrs_sections: Some(&["Attrs", "Attrs"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nAttrs:\n attr_1:\n ", + expected: DocstringExpectation { + attrs: Some(&["attr_1"]), + attrs_sections: Some(&["Attrs"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nAttrs:\n attr_1:\n attr_2:\n ", + expected: DocstringExpectation { + attrs: Some(&["attr_1", "attr_2"]), + attrs_sections: Some(&["Attrs"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nattrs:\n attr_1:\n ", + expected: DocstringExpectation { + attrs: Some(&["attr_1"]), + attrs_sections: Some(&["attrs"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nAttributes:\n attr_1:\n ", + expected: DocstringExpectation { + attrs: Some(&["attr_1"]), + attrs_sections: Some(&["Attributes"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nReturns:\n ", + expected: DocstringExpectation { + returns_sections: Some(&["Returns"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nReturns:\n The return value.\n ", + expected: DocstringExpectation { + returns_sections: Some(&["Returns"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nReturn:\n ", + expected: DocstringExpectation { + returns_sections: Some(&["Return"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nReturns:\n\nReturns:\n ", + expected: DocstringExpectation { + returns_sections: Some(&["Returns", "Returns"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nReturns:\n\nReturn:\n ", + expected: DocstringExpectation { + returns_sections: Some(&["Returns", "Return"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nYields:\n ", + expected: DocstringExpectation { + yields_sections: Some(&["Yields"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nYields:\n The return value.\n ", + expected: DocstringExpectation { + yields_sections: Some(&["Yields"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nYield:\n ", + expected: DocstringExpectation { + yields_sections: Some(&["Yield"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nYields:\n\nYields:\n ", + expected: DocstringExpectation { + yields_sections: Some(&["Yields", "Yields"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nYields:\n\nYield:\n ", + expected: DocstringExpectation { + yields_sections: Some(&["Yields", "Yield"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nRaises:\n ", + expected: DocstringExpectation { + raises: Some(&[]), + raises_sections: Some(&["Raises"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nRaises:\n\nRaises:\n ", + expected: DocstringExpectation { + raises: Some(&[]), + raises_sections: Some(&["Raises", "Raises"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nRaises:\n\nRaise:\n ", + expected: DocstringExpectation { + raises: Some(&[]), + raises_sections: Some(&["Raises", "Raise"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nRaises:\n exc_1:\n ", + expected: DocstringExpectation { + raises: Some(&["exc_1"]), + raises_sections: Some(&["Raises"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nRaises:\n exc_1:\n exc_2:\n ", + expected: DocstringExpectation { + raises: Some(&["exc_1", "exc_2"]), + raises_sections: Some(&["Raises"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nraises:\n exc_1:\n ", + expected: DocstringExpectation { + raises: Some(&["exc_1"]), + raises_sections: Some(&["raises"]), + ..Default::default() + }, + }, + ParseCase { + value: "short description\n\nAttrs:\n attr_1:\n\nArgs:\n arg_1:\n\nReturns:\n The return value.\n\nYields:\n The yield value.\n\nRaises:\n exc_1:\n ", + expected: DocstringExpectation { + args: Some(&["arg_1"]), + args_sections: Some(&["Args"]), + attrs: Some(&["attr_1"]), + attrs_sections: Some(&["Attrs"]), + returns_sections: Some(&["Returns"]), + yields_sections: Some(&["Yields"]), + raises: Some(&["exc_1"]), + raises_sections: Some(&["Raises"]), + ..Default::default() + }, + }, + ]; + + for case in cases { + let parsed = parse_from_str_for_tests(case.value); + let expected = build_expected_docstring(&case.expected); + + assert_eq!( + parsed, expected, + "Docstring mismatch for input: {:?}", + case.value + ); + } +} + +#[test] +fn test_multiline_subsections() { + let lines = vec![ + "arrange: This is a very important part so".to_string(), + " the arrange sentence has to be loooong.".to_string(), + "act: Do the test.".to_string(), + "assert: It better not fail.".to_string(), + ]; + + let sections = _get_sections(lines); + + // Print for debugging + for section in §ions { + eprintln!("Section: {:?}", section); + } + + // The first line "arrange: ..." is treated as a section (because it's first line and matches pattern) + // The continuation line should not create a new subsection + // "act:" and "assert:" should be subsections under "arrange:" + assert_eq!(sections.len(), 1, "Expected 1 section"); + assert_eq!( + sections[0].name, + Some("arrange".to_string()), + "Expected section name to be 'arrange'" + ); + assert_eq!( + sections[0].subs.len(), + 2, + "Expected 2 subsections, got: {:?}", + sections[0].subs + ); + assert!(sections[0].subs.contains(&"act".to_string())); + assert!(sections[0].subs.contains(&"assert".to_string())); +} + +#[test] +fn test_multiline_subsections_in_named_section() { + // Test when subsections with multi-line descriptions are within a named section + let lines = vec![ + "Args:".to_string(), + " arg1: This is a very long description that needs to".to_string(), + " span multiple lines because it's important.".to_string(), + " arg2: Short description.".to_string(), + " arg3: Another long description that also".to_string(), + " needs multiple lines.".to_string(), + ]; + + let sections = _get_sections(lines); + + eprintln!("Sections: {:?}", sections); + + assert_eq!(sections.len(), 1); + assert_eq!(sections[0].name, Some("Args".to_string())); + assert_eq!( + sections[0].subs.len(), + 3, + "Expected 3 args, got: {:?}", + sections[0].subs + ); + assert!(sections[0].subs.contains(&"arg1".to_string())); + assert!(sections[0].subs.contains(&"arg2".to_string())); + assert!(sections[0].subs.contains(&"arg3".to_string())); +} + +#[test] +fn test_multiline_subsections_given_when_then() { + // Test the given/when/then pattern common in tests + let lines = vec![ + "given: A test setup with multiple components that require".to_string(), + " detailed explanation across lines.".to_string(), + "when: The action is performed.".to_string(), + "then: The expected outcome should be this specific thing that".to_string(), + " also needs a detailed explanation.".to_string(), + ]; + + let sections = _get_sections(lines); + + eprintln!("Sections: {:?}", sections); + + assert_eq!(sections.len(), 1); + assert_eq!(sections[0].name, Some("given".to_string())); + assert_eq!( + sections[0].subs.len(), + 2, + "Expected 2 subsections, got: {:?}", + sections[0].subs + ); + assert!(sections[0].subs.contains(&"when".to_string())); + assert!(sections[0].subs.contains(&"then".to_string())); +} + +#[test] +fn test_multiline_with_multiple_continuation_lines() { + // Test with multiple continuation lines for a single subsection + let lines = vec![ + "Args:".to_string(), + " param1: This is line 1".to_string(), + " This is line 2 of the same parameter".to_string(), + " And this is line 3".to_string(), + " param2: Another parameter.".to_string(), + ]; + + let sections = _get_sections(lines); + + assert_eq!(sections.len(), 1); + assert_eq!(sections[0].name, Some("Args".to_string())); + assert_eq!(sections[0].subs.len(), 2); + assert!(sections[0].subs.contains(&"param1".to_string())); + assert!(sections[0].subs.contains(&"param2".to_string())); +} diff --git a/src/lib.rs b/src/lib.rs index 5d4ec9f..c0721e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,42 +1,7 @@ -use pyo3::prelude::*; - -mod docstring; -//::{parse, Docstring, _get_sections}; - pub mod constants; -mod plugin; +pub mod docstring; +pub mod plugin; pub mod rule_engine; -mod test_rule_engine; - -#[pyfunction] -fn hello_from_bin() -> String { - "Hello from ruff-docstrings-complete!".to_string() -} - -#[pyfunction] -fn my_hello() { - println!("Hello from Ali!"); -} -/// A Python module implemented in Rust. The name of this function must match -/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to -/// import the module. -#[pymodule] -fn _core(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(hello_from_bin, m)?)?; - m.add_function(wrap_pyfunction!(my_hello, m)?)?; - m.add_function(wrap_pyfunction!(rule_engine::apply_rules, m)?)?; - - let submodule = PyModule::new_bound(py, "docstring")?; - submodule.add_class::()?; - - m.add_submodule(&submodule)?; - let constants = PyModule::new_bound(py, "constants")?; - let _ = constants.add("ERROR_CODE_PREFIX", constants::ERROR_CODE_PREFIX); - let _ = constants.add("MORE_INFO_BASE", constants::MORE_INFO_BASE); - let _ = constants.add("DOCSTR_MISSING_CODE", constants::docstr_missing_code()); - let _ = constants.add("DOCSTR_MISSING_MSG", constants::docstr_missing_msg()); - m.add_submodule(&constants)?; - - Ok(()) -} +#[cfg(test)] +mod test_rule_engine; diff --git a/src/main.rs b/src/main.rs index ee612d7..30b22bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,63 +32,111 @@ Examples: "# )] struct Cli { - /// Path to a Python file or directory to check - path: PathBuf, + /// Paths to Python files or directories to check + #[arg(value_name = "PATH", num_args = 1..)] + paths: Vec, } -fn get_files_recursively(path: PathBuf) -> Vec { +fn collect_python_files(path: &Path) -> Vec { + if path.is_file() { + if is_python_file(path) { + return vec![path.to_path_buf()]; + } + return Vec::new(); + } + let mut py_files = Vec::new(); - visit_dirs(&path, &mut py_files); + if path.is_dir() { + visit_dirs(path, &mut py_files); + } py_files } -fn visit_dirs(dir: &Path, py_files: &mut Vec) { +fn visit_dirs(dir: &Path, py_files: &mut Vec) { if let Ok(entries) = fs::read_dir(dir) { for entry in entries.flatten() { let path = entry.path(); if path.is_dir() { visit_dirs(&path, py_files); - } else if let Some(ext) = path.extension() { - if ext == "py" { - if let Some(path_str) = path.to_str() { - py_files.push(path_str.to_string()); - } - } + } else if is_python_file(&path) { + py_files.push(path); } } } } +fn is_python_file(path: &Path) -> bool { + path.extension() + .and_then(|ext| ext.to_str()) + .map(|ext| ext.eq_ignore_ascii_case("py")) + .unwrap_or(false) +} + fn main() { let cli = Cli::parse(); - if !cli.path.exists() { - eprintln!("โŒ Error: Path '{}' does not exist.", cli.path.display()); - std::process::exit(1); - } + let mut files_scanned = 0usize; + let mut any_missing_paths = false; + + for path in cli.paths { + if !path.exists() { + eprintln!("โŒ Error: Path '{}' does not exist.", path.display()); + any_missing_paths = true; + continue; + } + + println!("๐Ÿ Scanning path: {}", path.display()); + let files = collect_python_files(&path); - println!("๐Ÿ Scanning path: {}", cli.path.display()); + if files.is_empty() { + if path.is_file() { + println!(" โš ๏ธ Skipping: not a Python file."); + } else { + println!(" โš ๏ธ No Python files found in directory."); + } + continue; + } - // TODO: Call your core logic here - // _core::check_docstrings(cli.path); - if cli.path.is_dir() { - let files = get_files_recursively(cli.path); + println!("๐Ÿ Scan result:"); + let mut issues_found = false; - println!("๐Ÿ Scan result: "); for file in files { - let output = rule_engine::lint_file("", Some(file.as_str())); - println!("{}: ", file); + let file_str = match file.to_str() { + Some(value) => value.to_string(), + None => { + eprintln!( + " โš ๏ธ Skipping '{}': path is not valid UTF-8.", + file.display() + ); + continue; + } + }; + + let output = rule_engine::lint_file("", Some(file_str.as_str())); + + if output.is_empty() { + files_scanned += 1; + continue; + } + if !issues_found { + issues_found = true; + } + + println!(" ๐Ÿšจ {}:", file_str); for line in output { println!(" - {}", line); } + + files_scanned += 1; } - } else if cli.path.is_file() { - let output = rule_engine::lint_file("", cli.path.to_str()); - println!("๐Ÿ Scan result: "); - for line in output { - println!(" - {}", line); + if !issues_found { + println!(" โœ… No issues found."); } } + + if files_scanned == 0 && !any_missing_paths { + println!("โš ๏ธ No Python files scanned."); + } } diff --git a/src/plugin.rs b/src/plugin.rs index 7bb03d6..45c1b61 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,10 +1,14 @@ use crate::docstring; use crate::docstring::Docstring; use rustpython_ast::text_size::TextRange; -use rustpython_ast::{Arguments, ExprAttribute, ExprCall, ExprYield, ExprYieldFrom, Stmt, StmtAssign, StmtAsyncFunctionDef, StmtClassDef, StmtFunctionDef, StmtRaise, StmtReturn, Visitor}; +use rustpython_ast::{ + Arguments, Expr, ExprAttribute, ExprCall, ExprYield, ExprYieldFrom, Stmt, StmtAnnAssign, + StmtAssign, StmtAsyncFunctionDef, StmtAugAssign, StmtClassDef, StmtFunctionDef, StmtRaise, + StmtReturn, Visitor, +}; use rustpython_parser::{parse, Mode}; -use rustpython_ast::Expr; +use std::collections::HashSet; pub fn get_result(code: &str, filename: Option<&str>) -> DocstringCollector { let filename = filename.unwrap_or(""); @@ -94,6 +98,7 @@ pub struct ClassInfo { pub funcs: Vec, pub docstring: Option, pub attributes: Vec, + pub instance_attributes: Vec, } fn get_docs(expr: &Expr) -> Option { if expr.is_constant_expr() { @@ -282,90 +287,221 @@ impl Visitor for ReturnCollector { } } struct AttributeCollector { - pub attributes: Vec, - class_depth: usize, + pub class_attributes: Vec, + pub instance_attributes: Vec, + receiver_stack: Vec>, + fn_depth: usize, } impl AttributeCollector { pub fn new() -> Self { Self { - attributes: Vec::new(), - class_depth: 0, + class_attributes: Vec::new(), + instance_attributes: Vec::new(), + receiver_stack: vec![HashSet::new()], + fn_depth: 0, } } + + fn push_receivers(&mut self, receivers: HashSet) { + self.receiver_stack.push(receivers); + } + + fn pop_receivers(&mut self) { + self.receiver_stack.pop(); + } + + fn is_tracked_receiver(&self, name: &str) -> bool { + self.receiver_stack + .iter() + .rev() + .any(|receivers| receivers.contains(name)) + } + + fn record_class_attribute(&mut self, name: String) { + self.class_attributes.push(name); + } + + fn record_instance_attribute(&mut self, name: String) { + self.instance_attributes.push(name); + } + + fn extract_tracked_attribute_name(&self, expr: &ExprAttribute) -> Option { + if let Some(name_expr) = expr.value.as_name_expr() { + if self.is_tracked_receiver(name_expr.id.as_str()) { + return Some(expr.attr.to_string()); + } + } else if let Some(inner_attr) = expr.value.as_attribute_expr() { + if let Some(attr) = self.extract_tracked_attribute_name(inner_attr) { + return Some(attr); + } + } + None + } } impl Visitor for AttributeCollector { - fn visit_stmt_class_def(&mut self, node: StmtClassDef) { - self.class_depth += 1; - for stmt in &node.body { - self.visit_stmt(stmt.clone()); + fn visit_stmt_assign(&mut self, node: StmtAssign) { + for target in &node.targets { + if self.fn_depth == 0 { + if let Some(name_expr) = target.as_name_expr() { + self.record_class_attribute(name_expr.id.to_string()); + continue; + } + } + + if let Some(attr_expr) = target.as_attribute_expr() { + if let Some(attr_name) = self.extract_tracked_attribute_name(attr_expr) { + self.record_instance_attribute(attr_name); + } + } } - self.class_depth -= 1; } - fn visit_expr_attribute(&mut self, node: ExprAttribute) { - if self.class_depth == 0 { - self.attributes.push(node.attr.to_string()); + fn visit_stmt_ann_assign(&mut self, node: StmtAnnAssign) { + if self.fn_depth == 0 { + if let Some(name_expr) = node.target.as_name_expr() { + self.record_class_attribute(name_expr.id.to_string()); + } + } + + if let Some(attr_expr) = node.target.as_attribute_expr() { + if let Some(attr_name) = self.extract_tracked_attribute_name(attr_expr) { + self.record_instance_attribute(attr_name); + } } } - fn visit_stmt_assign(&mut self, node: StmtAssign) { - let targets = &node.targets; - println!("{:?}", targets); - for target in targets { - if target.as_name_expr().is_some() { - let _target = target.as_name_expr().unwrap().id.clone(); - self.attributes.push(_target.to_string()); + + fn visit_stmt_aug_assign(&mut self, node: StmtAugAssign) { + if self.fn_depth == 0 { + if let Some(name_expr) = node.target.as_name_expr() { + self.record_class_attribute(name_expr.id.to_string()); + } + } + + if let Some(attr_expr) = node.target.as_attribute_expr() { + if let Some(attr_name) = self.extract_tracked_attribute_name(attr_expr) { + self.record_instance_attribute(attr_name); } } } + fn visit_stmt_function_def(&mut self, node: StmtFunctionDef) { - for dec in &node.decorator_list { - if is_property(dec){ - self.attributes.push(node.name.to_string()); + let was_top_level_method = self.fn_depth == 0; + self.fn_depth += 1; + + if was_top_level_method { + if node.decorator_list.iter().any(|dec| is_property(dec)) { + self.record_class_attribute(node.name.to_string()); } + + let mut receivers = HashSet::new(); + if !has_decorator_named(&node.decorator_list, "staticmethod") { + if let Some(first_param) = first_parameter_name(&node.args) { + receivers.insert(first_param); + } + } + self.push_receivers(receivers); + } else { + self.push_receivers(HashSet::new()); + } + + for stmt in &node.body { + self.visit_stmt(stmt.clone()); } - // self.generic_visit_stmt_function_def(node); + + self.pop_receivers(); + self.fn_depth -= 1; } + fn visit_stmt_async_function_def(&mut self, node: StmtAsyncFunctionDef) { - for dec in &node.decorator_list { - if is_property(dec){ - self.attributes.push(node.name.to_string()); + let was_top_level_method = self.fn_depth == 0; + self.fn_depth += 1; + + if was_top_level_method { + if node.decorator_list.iter().any(|dec| is_property(dec)) { + self.record_class_attribute(node.name.to_string()); } + + let mut receivers = HashSet::new(); + if !has_decorator_named(&node.decorator_list, "staticmethod") { + if let Some(first_param) = first_parameter_name(&node.args) { + receivers.insert(first_param); + } + } + self.push_receivers(receivers); + } else { + self.push_receivers(HashSet::new()); + } + + for stmt in &node.body { + self.visit_stmt(stmt.clone()); } - // self.generic_visit_stmt_async_function_def(node); + + self.pop_receivers(); + self.fn_depth -= 1; } + + fn visit_stmt_class_def(&mut self, _node: StmtClassDef) {} } fn is_property(decorator: &Expr) -> bool { - let property_tag_list = ["property", "cached_property"]; - if decorator.is_name_expr() { - let id = &decorator.as_name_expr().unwrap().id; - for property_tag in property_tag_list{ - if id.eq_ignore_ascii_case(property_tag) { - return true; - } + decorator_matches_any(decorator, &["property", "cached_property"]) +} + +fn decorator_matches_any(decorator: &Expr, names: &[&str]) -> bool { + names + .iter() + .any(|name| decorator_matches_name(decorator, name)) +} + +fn decorator_matches_name(decorator: &Expr, name: &str) -> bool { + if decorator.is_name_expr() { + return decorator + .as_name_expr() + .map(|expr| expr.id.eq_ignore_ascii_case(name)) + .unwrap_or(false); + } + + if decorator.is_call_expr() { + let call: &ExprCall = decorator.as_call_expr().unwrap(); + if let Some(name_expr) = call.func.as_name_expr() { + if name_expr.id.eq_ignore_ascii_case(name) { + return true; } } - if decorator.is_call_expr() { - let call: &ExprCall = decorator.as_call_expr().unwrap(); - if let Some(name_expr) = call.func.as_name_expr() { - let id = &name_expr.id; - for property_tag in property_tag_list{ - if id.eq_ignore_ascii_case(property_tag) { - return true; - } - } - }} - if decorator.is_attribute_expr() { - let id = &decorator.as_attribute_expr().unwrap().attr; - for property_tag in property_tag_list{ - if id.eq_ignore_ascii_case(property_tag) { - return true; - } - } + + if let Some(attr_expr) = call.func.as_attribute_expr() { + if attr_expr.attr.eq_ignore_ascii_case(name) { + return true; } + } + } + + if decorator.is_attribute_expr() { + return decorator + .as_attribute_expr() + .map(|expr| expr.attr.eq_ignore_ascii_case(name)) + .unwrap_or(false); + } + false } + +fn has_decorator_named(decorators: &[Expr], name: &str) -> bool { + decorators + .iter() + .any(|decorator| decorator_matches_name(decorator, name)) +} + +fn first_parameter_name(args: &Arguments) -> Option { + if let Some(arg) = args.posonlyargs.first() { + return Some(arg.def.arg.to_string()); + } + if let Some(arg) = args.args.first() { + return Some(arg.def.arg.to_string()); + } + None +} impl Visitor for DocstringCollector { fn visit_stmt_async_function_def(&mut self, node: StmtAsyncFunctionDef) { let function_info = get_func(&FunctionDefKind::Async(node.clone())); @@ -409,12 +545,12 @@ impl Visitor for DocstringCollector { class_funcs.push(get_func(&FunctionDefKind::Sync(func_def.clone()))); } } - let class_info = ClassInfo { def: node.clone(), funcs: class_funcs, docstring: class_docs, - attributes: attribute_collector.attributes, + attributes: attribute_collector.class_attributes, + instance_attributes: attribute_collector.instance_attributes, }; self.class_infos.push(class_info); diff --git a/src/ruff_docstrings_complete/__init__.py b/src/ruff_docstrings_complete/__init__.py deleted file mode 100644 index 62b27a2..0000000 --- a/src/ruff_docstrings_complete/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from ruff_docstrings_complete._core import hello_from_bin -from ruff_docstrings_complete._core import my_hello - - -def main() -> None: - print(hello_from_bin()) - my_hello() diff --git a/src/ruff_docstrings_complete/_core.abi3.so b/src/ruff_docstrings_complete/_core.abi3.so deleted file mode 100755 index 1b6ebe7..0000000 Binary files a/src/ruff_docstrings_complete/_core.abi3.so and /dev/null differ diff --git a/src/ruff_docstrings_complete/_core.pyi b/src/ruff_docstrings_complete/_core.pyi deleted file mode 100644 index d52129e..0000000 --- a/src/ruff_docstrings_complete/_core.pyi +++ /dev/null @@ -1 +0,0 @@ -def hello_from_bin() -> str: ... diff --git a/src/rule_engine.rs b/src/rule_engine.rs index 7f8496a..b27ba58 100644 --- a/src/rule_engine.rs +++ b/src/rule_engine.rs @@ -1,18 +1,18 @@ use crate::constants::{ arg_in_docstr_msg, arg_not_in_docstr_msg, args_section_in_docstr_msg, - args_section_not_in_docstr_msg, attrs_section_not_in_docstr_msg, docstr_missing_msg, - duplicate_arg_msg, duplicate_exc_msg, exc_in_docstr_msg, exc_not_in_docstr_msg, - mult_args_sections_in_docstr_msg, mult_raises_sections_in_docstr_msg, - mult_returns_sections_in_docstr_msg, mult_yields_sections_in_docstr_msg, - raises_section_in_docstr_msg, raises_section_not_in_docstr_msg, re_raise_no_exc_in_docstr_msg, - returns_section_in_docstr_msg, returns_section_not_in_docstr_msg, yields_section_in_docstr_msg, - yields_section_not_in_docstr_msg,attrs_section_in_docstr_msg,mult_attrs_section_in_docstr_msg, - attr_not_in_docstr_msg,attr_in_docstr_msg, + args_section_not_in_docstr_msg, attr_in_docstr_msg, attr_not_in_docstr_msg, + attrs_section_in_docstr_msg, attrs_section_not_in_docstr_msg, docstr_missing_msg, + duplicate_arg_msg, duplicate_attr_docstr_msg, duplicate_exc_msg, exc_in_docstr_msg, + exc_not_in_docstr_msg, mult_args_sections_in_docstr_msg, mult_attrs_section_in_docstr_msg, + mult_raises_sections_in_docstr_msg, mult_returns_sections_in_docstr_msg, + mult_yields_sections_in_docstr_msg, raises_section_in_docstr_msg, + raises_section_not_in_docstr_msg, re_raise_no_exc_in_docstr_msg, returns_section_in_docstr_msg, + returns_section_not_in_docstr_msg, yields_section_in_docstr_msg, + yields_section_not_in_docstr_msg, }; use crate::plugin::{ get_result, ClassInfo, DocstringCollector, FunctionDefKind, FunctionInfo, YieldKind, }; -use pyo3::prelude::*; use rustpython_ast::text_size::TextRange; use rustpython_ast::{Arguments, Expr, ExprAttribute, ExprCall, StmtRaise, StmtReturn}; use rustpython_parser::text_size::TextSize; @@ -47,8 +47,6 @@ pub fn lint_file(code: &str, file_name: Option<&str>) -> Vec { apply_rules(code.as_str(), file_name) } -#[pyfunction] -#[pyo3(signature = (code, file_name=None))] pub fn apply_rules(code: &str, file_name: Option<&str>) -> Vec { let mut output: Vec = Vec::new(); @@ -171,11 +169,9 @@ fn check_functions_for_duplicate_arg_in_args_section( let docstring_args_sections = function.docstring.clone().unwrap().get_args_sections(); let docstring_args = function.docstring.clone().unwrap().get_args(); - println!("{}", function.docstring.clone().unwrap().__repr__()); if docstring_args_sections.is_empty() { continue; } - println!("docstring_args: {:?}", docstring_args); let mut counts = HashMap::new(); let mut _range = function @@ -232,12 +228,9 @@ fn check_functions_for_extra_arg_in_args_section( let docstring_args_sections = function.docstring.clone().unwrap().get_args_sections(); let docstring_args = function.docstring.clone().unwrap().get_args(); - println!("{}", function.docstring.clone().unwrap().__repr__()); if docstring_args_sections.is_empty() { continue; } - println!("docstring_args: {:?}", docstring_args); - println!("clean_args: {:?}", clean_args); let mut _range = function.def.range(); // if DC022 is here we don't need to check for DC023 if function @@ -304,16 +297,27 @@ fn check_classes_for_attrs_section_not_in_docstr( return errors; } - if class_info.attributes.is_empty() { + let public_class_attributes: Vec = class_info + .attributes + .iter() + .filter(|attr| !attr.starts_with('_')) + .cloned() + .collect(); + + if public_class_attributes.is_empty() { return errors; } if let Some(docstring) = &class_info.docstring { // Check if docstring has attrs sections if !docstring.has_attrs_sections() { + let attr_name = public_class_attributes + .first() + .cloned() + .unwrap_or_else(|| class_info.attributes.first().cloned().unwrap_or_default()); let exc_lines = find_string_in_text_range( file_contents, &TextRange::new(TextSize::new(0), class_info.def.range.end()), - vec![class_info.attributes[0].as_str()], + vec![attr_name.as_str()], ); let (line, line_location, _) = exc_lines.first().unwrap().to_owned(); errors.push(format_problem( @@ -341,36 +345,66 @@ fn check_classes_for_extra_attrs_section_in_docstr( if class_info.docstring.is_none() { return errors; } - let public_attributes: Vec = class_info.attributes.clone() - .into_iter() - .filter(|attr| !attr.starts_with('_')) - .collect(); - - if !public_attributes.is_empty() { - return errors; - } if let Some(docstring) = &class_info.docstring { // Check if docstring has attrs sections if !docstring.has_attrs_sections() { return errors; } - let exc_lines = find_string_in_text_range( - file_contents, - &TextRange::new(TextSize::new(0), class_info.def.range.end()), - vec!["attrs", "attributes"], - ); - let (line, line_location, _) = exc_lines.first().unwrap().to_owned(); - errors.push(format_problem( - line, - line_location, - attrs_section_in_docstr_msg(), - )); + + let public_class_attributes: Vec = class_info + .attributes + .clone() + .into_iter() + .filter(|attr| !attr.starts_with('_')) + .collect(); + let public_instance_attributes: Vec = class_info + .instance_attributes + .clone() + .into_iter() + .filter(|attr| !attr.starts_with('_')) + .collect(); + + // Rule 61: If there's an attrs section but no public attributes, it's extra + // However, if private attributes are documented in the attrs section, + // then the attrs section is justified + if public_class_attributes.is_empty() && public_instance_attributes.is_empty() { + let docstr_attrs = docstring.get_attrs(); + + // If the docstring documents a private attribute that's actually present, + // treat it as a valid attribute definition so the attrs section is justified. + if docstr_attrs.iter().any(|attr| attr.starts_with('_')) + && (class_info + .attributes + .iter() + .any(|attr| attr.starts_with('_')) + || class_info + .instance_attributes + .iter() + .any(|attr| attr.starts_with('_'))) + { + return errors; + } + + if !docstr_attrs.iter().any(|attr| attr.starts_with('_')) { + let exc_lines = find_string_in_text_range( + file_contents, + &TextRange::new(TextSize::new(0), class_info.def.range.end()), + vec!["attrs", "attributes"], + ); + if let Some((line, line_location, _)) = exc_lines.first() { + errors.push(format_problem( + *line, + *line_location, + attrs_section_in_docstr_msg(), + )); + } + } + } } errors } - fn check_classes_for_multiple_attrs_section_in_docstr( class_info: &ClassInfo, file_contents: &str, @@ -392,7 +426,7 @@ fn check_classes_for_multiple_attrs_section_in_docstr( return errors; } if docstring.get_attrs_sections().len() == 1 { - return errors; + return errors; } let exc_lines = find_string_in_text_range( file_contents, @@ -417,6 +451,50 @@ fn check_classes_for_multiple_attrs_section_in_docstr( } errors } + +fn check_classes_for_multiple_attrs_in_docstr( + class_info: &ClassInfo, + file_contents: &str, + is_test_file: bool, +) -> Vec { + let mut errors = Vec::new(); + + // Skip if this is a test file (similar pattern to other rules) + if is_test_file { + return errors; + } + + if class_info.docstring.is_none() { + return errors; + } + if let Some(docstring) = &class_info.docstring { + // Check if docstring has attrs sections + if !docstring.has_attrs_sections() { + return errors; + } + + let duplicates = find_duplicates(&docstring.get_attrs()); + + for duplicate in duplicates { + let exc_lines = find_string_in_text_range( + file_contents, + &TextRange::new(TextSize::new(0), class_info.def.range.end()), + vec![&duplicate], + ); + // TODO: attribute section can be attrs instead of Attrs. Make sure the + // find_string_in_text_range function returns the actual found string + let (line, line_location, _) = exc_lines.first().unwrap().to_owned(); + + errors.push(format_problem( + line, + line_location, + duplicate_attr_docstr_msg(duplicate.as_str()), + )); + } + } + errors +} + fn check_classes_for_missing_attrs_in_docstr( class_info: &ClassInfo, file_contents: &str, @@ -432,7 +510,9 @@ fn check_classes_for_missing_attrs_in_docstr( if class_info.docstring.is_none() { return errors; } - let public_attributes: Vec = class_info.attributes.clone() + let public_attributes: Vec = class_info + .attributes + .clone() .into_iter() .filter(|attr| !attr.starts_with('_')) .collect(); @@ -449,13 +529,17 @@ fn check_classes_for_missing_attrs_in_docstr( let docstr_attrs = docstring.get_attrs(); - for attr in public_attributes { - if !docstr_attrs.contains(&attr) { + for attr in &public_attributes { + if !docstr_attrs.contains(attr) { let exc_lines = find_string_in_text_range( file_contents, &TextRange::new(TextSize::new(0), class_info.def.range.end()), vec![attr.as_str()], ); + if exc_lines.is_empty() { + eprintln!("Warning: Could not find attribute '{}' in source", attr); + continue; + } let (line, line_location, _) = exc_lines.first().unwrap().to_owned(); errors.push(format_problem( line, @@ -483,11 +567,21 @@ fn check_classes_for_extra_attrs_in_docstr( if class_info.docstring.is_none() { return errors; } - let public_attributes: Vec = class_info.attributes.clone() - .into_iter() + let mut public_attributes: HashSet = class_info + .attributes + .iter() .filter(|attr| !attr.starts_with('_')) + .cloned() .collect(); + public_attributes.extend( + class_info + .instance_attributes + .iter() + .filter(|attr| !attr.starts_with('_')) + .cloned(), + ); + if public_attributes.is_empty() { return errors; } @@ -500,13 +594,20 @@ fn check_classes_for_extra_attrs_in_docstr( let docstr_attrs = docstring.get_attrs(); - for attr in docstr_attrs { - if !public_attributes.contains(&attr) { + for attr in &docstr_attrs { + if !public_attributes.contains(attr) { let exc_lines = find_string_in_text_range( file_contents, &TextRange::new(TextSize::new(0), class_info.def.range.end()), vec![attr.as_str()], ); + if exc_lines.is_empty() { + eprintln!( + "Warning: Could not find docstring attribute '{}' in source", + attr + ); + continue; + } let (line, line_location, _) = exc_lines.first().unwrap().to_owned(); errors.push(format_problem( line, @@ -853,13 +954,9 @@ fn check_functions_for_missing_arg_in_args_section( let docstring_args_sections = function.docstring.clone().unwrap().get_args_sections(); let docstring_args = function.docstring.clone().unwrap().get_args(); - - println!("{}", function.docstring.clone().unwrap().__repr__()); if docstring_args_sections.is_empty() { continue; } - println!("docstring_args: {:?}", docstring_args); - println!("clean_args: {:?}", clean_args); let mut _range = function.def.range(); // if DC022 is here we don't need to check for DC023 if function @@ -924,10 +1021,6 @@ fn is_arg_in_docstring( _range: &TextRange, file_contents: &str, ) -> Option { - println!( - "arg: {}, docstring_args: {:?}, contains", - arg_name, docstring_args - ); if !docstring_args.contains(&arg_name) { let args_lines = find_string_in_text_range(file_contents, _range, vec![arg_name.as_str()]); let (line, line_location, _) = args_lines.first().unwrap().to_owned(); @@ -976,7 +1069,6 @@ fn check_functions_for_multiple_args_section( _range, vec!["Args:", "Arguments:", "Parameters:"], ); - println!("args_lines: {:?}", args_lines); if args_lines.len() < 2 { continue; } @@ -1169,55 +1261,53 @@ fn check_functions_for_extra_args_section( fn cleanse_args(args: &Arguments, del_private_args: bool) -> Arguments { let mut clean_args: Arguments = args.clone(); - if args.vararg.is_some() { - let arg_name = args.vararg.clone().unwrap().arg.trim().to_owned(); - if arg_name == "self" { - clean_args.vararg = None; - } - if arg_name == "cls" { - clean_args.vararg = None; - } - if del_private_args && arg_name.starts_with("_") { + + if let Some(vararg) = clean_args.vararg.clone() { + let arg_name = vararg.arg.trim(); + let should_drop = + matches!(arg_name, "self" | "cls") || (del_private_args && arg_name.starts_with('_')); + if should_drop { clean_args.vararg = None; } } - if args.kwarg.is_some() { - let arg_name = args.kwarg.clone().unwrap().arg.trim().to_owned(); - if del_private_args && arg_name.starts_with("_") { + + if let Some(kwarg) = clean_args.kwarg.clone() { + let arg_name = kwarg.arg.trim(); + if del_private_args && arg_name.starts_with('_') { clean_args.kwarg = None; } } - for (index, arg) in args.args.iter().enumerate() { + + clean_args.args.retain(|arg| { let arg_name = arg.def.arg.trim(); - if arg_name == "self" { - clean_args.args.remove(index); - } - if arg_name == "cls" { - clean_args.args.remove(index); + if matches!(arg_name, "self" | "cls") { + return false; } - if del_private_args && arg_name.starts_with("_") { - clean_args.args.remove(index); + if del_private_args && arg_name.starts_with('_') { + return false; } - } + true + }); - for (index, arg) in args.kwonlyargs.iter().enumerate() { + clean_args.kwonlyargs.retain(|arg| { let arg_name = arg.def.arg.trim(); - if del_private_args && arg_name.starts_with("_") { - clean_args.kwonlyargs.remove(index); + if del_private_args && arg_name.starts_with('_') { + return false; } - } - for (index, arg) in args.posonlyargs.iter().enumerate() { + true + }); + + clean_args.posonlyargs.retain(|arg| { let arg_name = arg.def.arg.trim(); - if arg_name == "self" { - clean_args.posonlyargs.remove(index); + if matches!(arg_name, "self" | "cls") { + return false; } - if arg_name == "cls" { - clean_args.posonlyargs.remove(index); + if del_private_args && arg_name.starts_with('_') { + return false; } - if del_private_args && arg_name.starts_with("_") { - clean_args.posonlyargs.remove(index); - } - } + true + }); + clean_args } @@ -1796,10 +1886,12 @@ fn generate_rules_output( is_test_file, )); - - - - + // DC065: Attribute documented multiple times + problem_functions.extend(check_classes_for_multiple_attrs_in_docstr( + class_info, + file_contents, + is_test_file, + )); } problem_functions } diff --git a/src/test_rule_engine.rs b/src/test_rule_engine.rs index 24fe61d..5e6fbef 100644 --- a/src/test_rule_engine.rs +++ b/src/test_rule_engine.rs @@ -22,6 +22,8 @@ mod test_rule_61; mod test_rule_62; mod test_rule_63; mod test_rule_64; +mod test_rule_65; +mod test_rule_6x; use crate::constants::{returns_section_in_docstr_msg, returns_section_not_in_docstr_msg}; use crate::rule_engine::lint_file; diff --git a/src/test_rule_engine/test_rule_23.rs b/src/test_rule_engine/test_rule_23.rs index b60394d..39e8494 100644 --- a/src/test_rule_engine/test_rule_23.rs +++ b/src/test_rule_engine/test_rule_23.rs @@ -255,3 +255,50 @@ def function_1(arg_1, arg_2): let expected = vec![format!("2:15 {}", arg_not_in_docstr_msg("arg_1"))]; general_test(code, expected); } + +#[test] +fn test_rule_23_method_has_single_arg_docstring_no_arg() { + let code = r#" +class Class1: + """Docstring.""" + def function_1(self, arg_1): + """Docstring 1. + + Args: + """ +"#; + let expected = vec![format!("4:25 {}", arg_not_in_docstr_msg("arg_1"))]; + general_test(code, expected); +} + +#[test] +fn test_rule_23_method_has_single_arg_docstring_no_arg_staticmethod() { + let code = r#" +class Class1: + """Docstring.""" + @staticmethod + def function_1(arg_1): + """Docstring 1. + + Args: + """ +"#; + let expected = vec![format!("5:19 {}", arg_not_in_docstr_msg("arg_1"))]; + general_test(code, expected); +} + +#[test] +fn test_rule_23_method_has_single_arg_docstring_no_arg_classmethod() { + let code = r#" +class Class1: + """Docstring.""" + @classmethod + def function_1(cls, arg_1): + """Docstring 1. + + Args: + """ +"#; + let expected = vec![format!("5:24 {}", arg_not_in_docstr_msg("arg_1"))]; + general_test(code, expected); +} diff --git a/src/test_rule_engine/test_rule_62.rs b/src/test_rule_engine/test_rule_62.rs index 16dc08e..c7f7187 100644 --- a/src/test_rule_engine/test_rule_62.rs +++ b/src/test_rule_engine/test_rule_62.rs @@ -29,7 +29,10 @@ class Class1: """ attr_1 = "value 1" "#; - let expected = vec![format!("3:4 {}", mult_attrs_section_in_docstr_msg("Attrs,Attributes"))]; + let expected = vec![format!( + "3:4 {}", + mult_attrs_section_in_docstr_msg("Attrs,Attributes") + )]; general_test(code, expected); } @@ -47,6 +50,9 @@ class Class1: """ attr_1 = "value 1" "#; - let expected = vec![format!("3:4 {}", mult_attrs_section_in_docstr_msg("Attrs,Attrs"))]; + let expected = vec![format!( + "3:4 {}", + mult_attrs_section_in_docstr_msg("Attrs,Attrs") + )]; general_test(code, expected); } diff --git a/src/test_rule_engine/test_rule_63.rs b/src/test_rule_engine/test_rule_63.rs index e4d1c67..c521ffa 100644 --- a/src/test_rule_engine/test_rule_63.rs +++ b/src/test_rule_engine/test_rule_63.rs @@ -187,9 +187,7 @@ class Class1: _attr_1 = "value 1" attr_2 = "value 2" "#; - let expected = vec![ - format!("6:4 {}", attr_not_in_docstr_msg("attr_2")), - ]; + let expected = vec![format!("6:4 {}", attr_not_in_docstr_msg("attr_2"))]; general_test(code, expected); } @@ -204,8 +202,6 @@ class Class1: attr_1 = "value 1" _attr_2 = "value 2" "#; - let expected = vec![ - format!("5:4 {}", attr_not_in_docstr_msg("attr_1")), - ]; + let expected = vec![format!("5:4 {}", attr_not_in_docstr_msg("attr_1"))]; general_test(code, expected); } diff --git a/src/test_rule_engine/test_rule_64.rs b/src/test_rule_engine/test_rule_64.rs index f183dcb..59ac623 100644 --- a/src/test_rule_engine/test_rule_64.rs +++ b/src/test_rule_engine/test_rule_64.rs @@ -1,5 +1,5 @@ #[cfg(test)] -use crate::constants::{attr_not_in_docstr_msg, attr_in_docstr_msg}; +use crate::constants::{attr_in_docstr_msg, attr_not_in_docstr_msg}; use crate::rule_engine::lint_file; fn general_test(code: &str, expected: Vec) { @@ -26,8 +26,10 @@ class Class1: """ attr_1 = "value 1" "#; - let expected = vec![format!("6:4 {}", attr_not_in_docstr_msg("attr_1")), - format!("4:8 {}", attr_in_docstr_msg("attr_2"))]; + let expected = vec![ + format!("6:4 {}", attr_not_in_docstr_msg("attr_1")), + format!("4:8 {}", attr_in_docstr_msg("attr_2")), + ]; general_test(code, expected); } #[test] diff --git a/src/test_rule_engine/test_rule_65.rs b/src/test_rule_engine/test_rule_65.rs new file mode 100644 index 0000000..4f4c64d --- /dev/null +++ b/src/test_rule_engine/test_rule_65.rs @@ -0,0 +1,141 @@ +#[cfg(test)] +use crate::constants::duplicate_attr_docstr_msg; +use crate::rule_engine::lint_file; + +fn general_test(code: &str, expected: Vec) { + let output = lint_file(code, None); + println!("{:#?}", output); + assert_eq!(output.len(), expected.len()); + for (index, exp) in expected.iter().enumerate() { + assert_eq!( + &output[index], exp, + "Mismatch at output index {}: got `{}`, expected `{}`", + index, output[index], exp + ); + } +} + +#[test] +fn test_rule_65_class_single_attr_docstring_single_attr_duplicate() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + attr_1: + """ + attr_1 = "value 1" +"#; + let expected = vec![format!("4:8 {}", duplicate_attr_docstr_msg("attr_1"))]; + general_test(code, expected); +} + +#[test] +fn test_rule_65_class_single_private_attr_docstring_single_attr_duplicate() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + _attr_1: + _attr_1: + """ + _attr_1 = "value 1" +"#; + let expected = vec![format!("4:8 {}", duplicate_attr_docstr_msg("_attr_1"))]; + general_test(code, expected); +} + +#[test] +fn test_rule_65_class_single_attr_docstring_single_attr_duplicate_many() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + attr_1: + attr_1: + """ + attr_1 = "value 1" +"#; + let expected = vec![format!("4:8 {}", duplicate_attr_docstr_msg("attr_1"))]; + general_test(code, expected); +} + +#[test] +fn test_rule_65_class_multiple_attr_docstring_duplicate_attr_first() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + attr_1: + attr_2: + """ + attr_1 = "value 1" + attr_2 = "value 2" +"#; + let expected = vec![format!("4:8 {}", duplicate_attr_docstr_msg("attr_1"))]; + general_test(code, expected); +} + +#[test] +fn test_rule_65_class_multiple_attr_docstring_duplicate_attr_second() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + attr_2: + attr_2: + """ + attr_1 = "value 1" + attr_2 = "value 2" +"#; + let expected = vec![format!("5:8 {}", duplicate_attr_docstr_msg("attr_2"))]; + general_test(code, expected); +} + +#[test] +fn test_rule_65_class_multiple_attr_docstring_duplicate_attr_all() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + attr_1: + attr_2: + attr_2: + """ + attr_1 = "value 1" + attr_2 = "value 2" +"#; + let expected = vec![ + format!("4:8 {}", duplicate_attr_docstr_msg("attr_1")), + format!("6:8 {}", duplicate_attr_docstr_msg("attr_2")), + ]; + general_test(code, expected); +} + +#[test] +fn test_rule_65_class_single_attr_init_docstring_single_attr_duplicate() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + attr_1: + """ + def __init__(self): + """Docstring 2.""" + self.attr_1 = "value 1" +"#; + let expected = vec![format!("4:8 {}", duplicate_attr_docstr_msg("attr_1"))]; + general_test(code, expected); +} diff --git a/src/test_rule_engine/test_rule_6x.rs b/src/test_rule_engine/test_rule_6x.rs new file mode 100644 index 0000000..bd762cb --- /dev/null +++ b/src/test_rule_engine/test_rule_6x.rs @@ -0,0 +1,721 @@ +#[cfg(test)] +use crate::rule_engine::lint_file; + +fn general_test(code: &str, expected: Vec) { + let output = lint_file(code, None); + println!("{:#?}", output); + assert_eq!(output.len(), expected.len()); + for (index, exp) in expected.iter().enumerate() { + assert_eq!( + &output[index], exp, + "Mismatch at output index {}: got `{}`, expected `{}`", + index, output[index], exp + ); + } +} + +// Positive test cases (no errors expected) + +#[test] +fn test_rule_6x_class_single_attr_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_property_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + @property + def attr_1(): + """Docstring 2.""" + return "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_cached_property_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + @cached_property + def attr_1(): + """Docstring 2.""" + return "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_functools_cached_property_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + @functools.cached_property + def attr_1(): + """Docstring 2.""" + return "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_attr_typed_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + attr_1: str = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_attr_augmented_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + attr_1 += "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_attr_init_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + def __init__(self): + """Docstring 2.""" + self.attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_attr_method_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + def method_1(self): + """Docstring 2.""" + self.attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_multiple_attr_method_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + attr_2: + """ + def method_1(self): + """Docstring 2.""" + self.attr_1 = "value 1" + def method_2(self): + """Docstring 3.""" + self.attr_2 = "value 2" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_attr_classmethod_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + @classmethod + def method_1(cls): + """Docstring 2.""" + cls.attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_private_attr_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + _attr_1: + """ + _attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_private_attr_no_docstring() { + let code = r#" +class Class1: + """Docstring 1.""" + _attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_var_init_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + def __init__(self): + """Docstring 2.""" + var_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_property_with_assignment_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + @property + def attr_1(self): + """Docstring 2.""" + self.attr_2 = "value 2" + return "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_property_with_assignment_docstring_both_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + attr_2: + """ + @property + def attr_1(self): + """Docstring 2.""" + self.attr_2 = "value 2" + return "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_in_init_docstring_no_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + def __init__(self): + """Docstring 2.""" + self.attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_in_method_docstring_no_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + def method_1(self): + """Docstring 2.""" + self.attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_typed_in_method_docstring_no_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + def method_1(self): + """Docstring 2.""" + self.attr_1: str = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_typed_in_method_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + def method_1(self): + """Docstring 2.""" + self.attr_1: str = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_augmented_in_method_docstring_no_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + def method_1(self): + """Docstring 2.""" + self.attr_1 += "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_augmented_in_method_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + def method_1(self): + """Docstring 2.""" + self.attr_1 += "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_multiple_attr_in_method_docstring_no_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + def method_1(self): + """Docstring 2.""" + self.attr_1 = self.attr_2 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_multiple_attr_in_method_docstring_multiple_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + attr_2: + """ + def method_1(self): + """Docstring 2.""" + self.attr_1 = self.attr_2 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_nested_in_method_docstring_no_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + def method_1(self): + """Docstring 2.""" + self.attr_1.nested_attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_nested_in_method_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + def method_1(self): + """Docstring 2.""" + self.attr_1.nested_attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_deep_nested_in_method_docstring_no_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + def method_1(self): + """Docstring 2.""" + self.attr_1.nested_attr_1.nested_attr_2 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_deep_nested_in_method_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + def method_1(self): + """Docstring 2.""" + self.attr_1.nested_attr_1.nested_attr_2 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_multiple_attr_in_multiple_method_docstring_no_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + def method_1(self): + """Docstring 2.""" + self.attr_1 = "value 1" + def method_2(self): + """Docstring 3.""" + self.attr_2 = "value 2" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_multiple_attr_in_multiple_method_docstring_single_attr_first() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + def method_1(self): + """Docstring 2.""" + self.attr_1 = "value 1" + def method_2(self): + """Docstring 3.""" + self.attr_2 = "value 2" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_multiple_attr_in_multiple_method_docstring_single_attr_second() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_2: + """ + def method_1(self): + """Docstring 2.""" + self.attr_1 = "value 1" + def method_2(self): + """Docstring 3.""" + self.attr_2 = "value 2" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_multiple_attr_in_multiple_method_docstring_multiple_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + attr_2: + """ + def method_1(self): + """Docstring 2.""" + self.attr_1 = "value 1" + def method_2(self): + """Docstring 3.""" + self.attr_2 = "value 2" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_in_async_method_docstring_no_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + async def method_1(self): + """Docstring 2.""" + self.attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_in_async_method_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + async def method_1(self): + """Docstring 2.""" + self.attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_in_classmethod_method_docstring_no_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + @classmethod + def method_1(cls): + """Docstring 2.""" + cls.attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_has_single_attr_in_classmethod_method_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + @classmethod + def method_1(cls): + """Docstring 2.""" + cls.attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_var_method_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + def method_1(self): + """Docstring 2.""" + var_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_var_classmethod_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1.""" + @classmethod + def method_1(cls): + """Docstring 2.""" + var_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_multiple_attr_docstring_multiple_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + attr_2: + """ + attr_1 = "value 1" + attr_2 = "value 2" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_multiple_attr_first_private_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_2: + """ + _attr_1 = "value 1" + attr_2 = "value 2" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_multiple_attr_second_private_docstring_single_attr() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + attr_1: + """ + attr_1 = "value 1" + _attr_2 = "value 2" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_nested_class_single_attr_docstring_no_attrs() { + let code = r#" +class Class1: + """Docstring 1.""" + class Class2: + """Docstring 2. + + Attrs: + attr_1: + """ + attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_attr_method_nested_method_docstring_no_attrs() { + let code = r#" +class Class1: + """Docstring 1.""" + def method_1(self): + """Docstring 2.""" + def nested_function_1(self): + """Docstring 3.""" + self.attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_attr_method_nested_async_method_docstring_no_attrs() { + let code = r#" +class Class1: + """Docstring 1.""" + def method_1(self): + """Docstring 2.""" + async def nested_function_1(self): + """Docstring 3.""" + self.attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} + +#[test] +fn test_rule_6x_class_single_attr_method_nested_classmethod_docstring_no_attrs() { + let code = r#" +class Class1: + """Docstring 1.""" + def method_1(self): + """Docstring 2.""" + def nested_function_1(cls): + """Docstring 3.""" + cls.attr_1 = "value 1" +"#; + let expected = vec![]; + general_test(code, expected); +} diff --git a/test_debug.rs b/test_debug.rs new file mode 100644 index 0000000..36fb7b4 --- /dev/null +++ b/test_debug.rs @@ -0,0 +1,17 @@ +use crate::rule_engine::lint_file; + +fn main() { + let code = r#" +class Class1: + """Docstring 1. + + Attrs: + """ + @property + def attr_1(): + """Docstring 2.""" + return "value 1" +"#; + let output = lint_file(code, None); + println!("Output: {:?}", output); +} \ No newline at end of file diff --git a/tests/python_tests/__init__.py b/tests/python_tests/__init__.py deleted file mode 100644 index 52a40dd..0000000 --- a/tests/python_tests/__init__.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Tests.""" - -from ruff_docstrings_complete._core import constants - -ERROR_CODE_PREFIX = constants.ERROR_CODE_PREFIX -MORE_INFO_BASE = constants.MORE_INFO_BASE - - -DOCSTR_MISSING_CODE = f"{ERROR_CODE_PREFIX}010" -DOCSTR_MISSING_MSG = ( - f"{DOCSTR_MISSING_CODE} docstring should be defined for a function/ method/ class" - f"{MORE_INFO_BASE}{DOCSTR_MISSING_CODE.lower()}" -) -RETURNS_SECTION_NOT_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}030" -RETURNS_SECTION_NOT_IN_DOCSTR_MSG = ( - f"{RETURNS_SECTION_NOT_IN_DOCSTR_CODE} function/ method that returns a value should have the " - f"returns section in the docstring{MORE_INFO_BASE}{RETURNS_SECTION_NOT_IN_DOCSTR_CODE.lower()}" -) -RETURNS_SECTION_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}031" -RETURNS_SECTION_IN_DOCSTR_MSG = ( - f"{RETURNS_SECTION_IN_DOCSTR_CODE} function/ method that does not return a value should not " - f"have the returns section in the docstring" - f"{MORE_INFO_BASE}{RETURNS_SECTION_IN_DOCSTR_CODE.lower()}" -) -MULT_RETURNS_SECTIONS_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}032" -MULT_RETURNS_SECTIONS_IN_DOCSTR_MSG = ( - f"{MULT_RETURNS_SECTIONS_IN_DOCSTR_CODE} a docstring should only contain a single returns " - "section, found %s" - f"{MORE_INFO_BASE}{MULT_RETURNS_SECTIONS_IN_DOCSTR_CODE.lower()}" -) -YIELDS_SECTION_NOT_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}040" -YIELDS_SECTION_NOT_IN_DOCSTR_MSG = ( - f"{YIELDS_SECTION_NOT_IN_DOCSTR_CODE} function/ method that yields a value should have the " - f"yields section in the docstring{MORE_INFO_BASE}{YIELDS_SECTION_NOT_IN_DOCSTR_CODE.lower()}" -) -YIELDS_SECTION_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}041" -YIELDS_SECTION_IN_DOCSTR_MSG = ( - f"{YIELDS_SECTION_IN_DOCSTR_CODE} function/ method that does not yield a value should not " - f"have the yields section in the docstring" - f"{MORE_INFO_BASE}{YIELDS_SECTION_IN_DOCSTR_CODE.lower()}" -) -MULT_YIELDS_SECTIONS_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}042" -MULT_YIELDS_SECTIONS_IN_DOCSTR_MSG = ( - f"{MULT_YIELDS_SECTIONS_IN_DOCSTR_CODE} a docstring should only contain a single yields " - "section, found %s" - f"{MORE_INFO_BASE}{MULT_YIELDS_SECTIONS_IN_DOCSTR_CODE.lower()}" -) \ No newline at end of file diff --git a/tests/python_tests/integration/__init__.py b/tests/python_tests/integration/__init__.py deleted file mode 100644 index c210fac..0000000 --- a/tests/python_tests/integration/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Integration tests.""" diff --git a/tests/python_tests/integration/test___init__.py b/tests/python_tests/integration/test___init__.py deleted file mode 100644 index 1cd9b6d..0000000 --- a/tests/python_tests/integration/test___init__.py +++ /dev/null @@ -1,611 +0,0 @@ -# """Integration tests for plugin.""" - -# from __future__ import annotations - -# import subprocess -# import sys -# from pathlib import Path - -# import pytest - -# from ruff_docstrings_complete import ( -# docstr_missing_code, -# FIXTURE_DECORATOR_PATTERN_ARG_NAME, -# FIXTURE_DECORATOR_PATTERN_DEFAULT, -# FIXTURE_FILENAME_PATTERN_ARG_NAME, -# FIXTURE_FILENAME_PATTERN_DEFAULT, -# mult_returns_sections_in_docstr_code, -# mult_yields_sections_in_docstr_code, -# returns_section_in_docstr_code, -# returns_section_not_in_docstr_code, -# TEST_FILENAME_PATTERN_ARG_NAME, -# TEST_FILENAME_PATTERN_DEFAULT, -# TEST_FUNCTION_PATTERN_ARG_NAME, -# TEST_FUNCTION_PATTERN_DEFAULT, -# yields_section_in_docstr_code, -# yields_section_not_in_docstr_code, -# ) -# from ruff_docstrings_complete.args import ( -# ARG_IN_DOCSTR_CODE, -# ARG_NOT_IN_DOCSTR_CODE, -# ARGS_SECTION_IN_DOCSTR_CODE, -# ARGS_SECTION_NOT_IN_DOCSTR_CODE, -# ARGS_SECTION_NOT_IN_DOCSTR_MSG, -# DUPLICATE_ARG_CODE, -# MULT_ARGS_SECTIONS_IN_DOCSTR_CODE, -# ) -# from ruff_docstrings_complete.attrs import ( -# ATTR_IN_DOCSTR_CODE, -# ATTR_NOT_IN_DOCSTR_CODE, -# ATTRS_SECTION_IN_DOCSTR_CODE, -# ATTRS_SECTION_NOT_IN_DOCSTR_CODE, -# DUPLICATE_ATTR_CODE, -# MULT_ATTRS_SECTIONS_IN_DOCSTR_CODE, -# ) -# from ruff_docstrings_complete.raises import ( -# DUPLICATE_EXC_CODE, -# EXC_IN_DOCSTR_CODE, -# EXC_NOT_IN_DOCSTR_CODE, -# MULT_RAISES_SECTIONS_IN_DOCSTR_CODE, -# RAISES_SECTION_IN_DOCSTR_CODE, -# RAISES_SECTION_NOT_IN_DOCSTR_CODE, -# RE_RAISE_NO_EXC_IN_DOCSTR_CODE, -# ) - - -# def test_help(): -# """ -# given: linter -# when: the flake8 help message is generated -# then: plugin is registered with flake8 -# """ -# with subprocess.Popen( -# f"{sys.executable} -m flake8 --help", -# stdout=subprocess.PIPE, -# shell=True, -# ) as proc: -# stdout = proc.communicate()[0].decode(encoding="utf-8") - -# assert "flake8-docstrings-complete" in stdout -# assert TEST_FILENAME_PATTERN_ARG_NAME in stdout -# assert TEST_FILENAME_PATTERN_DEFAULT in stdout -# assert TEST_FUNCTION_PATTERN_ARG_NAME in stdout -# assert TEST_FUNCTION_PATTERN_DEFAULT in stdout -# assert FIXTURE_FILENAME_PATTERN_ARG_NAME in stdout -# assert FIXTURE_FILENAME_PATTERN_DEFAULT in stdout -# assert FIXTURE_DECORATOR_PATTERN_ARG_NAME in stdout -# assert FIXTURE_DECORATOR_PATTERN_DEFAULT in stdout - - -# def create_code_file(code: str, filename: str, base_path: Path) -> Path: -# """Create the code file with the given code. - -# Args: -# code: The code to write to the file. -# filename: The name of the file to create. -# base_path: The path to create the file within - -# Returns: -# The path to the code file. -# """ -# (code_file := base_path / filename).write_text(f'"""Docstring."""\n\n{code}') -# return code_file - - -# def test_fail(tmp_path: Path): -# """ -# given: file with Python code that fails the linting -# when: flake8 is run against the code -# then: the process exits with non-zero code and includes the error message -# """ -# code_file = create_code_file( -# '\ndef foo(arg_1):\n """Docstring."""\n', "source.py", tmp_path -# ) - -# with subprocess.Popen( -# f"{sys.executable} -m flake8 {code_file}", -# stdout=subprocess.PIPE, -# shell=True, -# ) as proc: -# stdout = proc.communicate()[0].decode(encoding="utf-8") - -# assert ARGS_SECTION_NOT_IN_DOCSTR_MSG in stdout -# assert proc.returncode - - -# @pytest.mark.parametrize( -# "code, filename, extra_args", -# [ -# pytest.param( -# ''' -# def foo(): -# """Docstring.""" -# ''', -# "source.py", -# "", -# id="default", -# ), -# pytest.param( -# ''' -# def _test(arg_1): -# """ -# arrange: line 1 -# act: line 2 -# assert: line 3 -# """ -# ''', -# "_test.py", -# ( -# f"{TEST_FILENAME_PATTERN_ARG_NAME} .*_test\\.py " -# f"{TEST_FUNCTION_PATTERN_ARG_NAME} _test" -# ), -# id="custom test filename and function pattern", -# ), -# pytest.param( -# ''' -# def custom(): -# """Docstring.""" - - -# @custom -# def fixture(arg_1): -# """Docstring.""" -# ''', -# "fixture.py", -# ( -# f"{FIXTURE_FILENAME_PATTERN_ARG_NAME} fixture\\.py " -# f"{FIXTURE_DECORATOR_PATTERN_ARG_NAME} custom" -# ), -# id="custom fixture filename and function pattern", -# ), -# pytest.param( -# f""" -# def foo(): # noqa: {docstr_missing_code} -# pass -# """, -# "source.py", -# "", -# id=f"{docstr_missing_code} disabled", -# ), -# pytest.param( -# f''' -# def foo(arg_1): -# """Docstring.""" # noqa: {ARGS_SECTION_NOT_IN_DOCSTR_CODE} -# ''', -# "source.py", -# "", -# id=f"{ARGS_SECTION_NOT_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# def foo(): -# """Docstring. - -# Args: -# Arguments. -# """ # noqa: {ARGS_SECTION_IN_DOCSTR_CODE} -# ''', -# "source.py", -# "", -# id=f"{ARGS_SECTION_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# def foo(arg_1): -# """Docstring. - -# Args: -# arg_1: - -# Parameters: -# arg_1: -# """ # noqa: {MULT_ARGS_SECTIONS_IN_DOCSTR_CODE} -# ''', -# "source.py", -# "", -# id=f"{MULT_ARGS_SECTIONS_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# def foo(arg_1): # noqa: {ARG_NOT_IN_DOCSTR_CODE} -# """Docstring. - -# Args: -# Arguments. -# """ -# ''', -# "source.py", -# "", -# id=f"{ARG_NOT_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# def foo( -# arg_1, -# arg2, # noqa: {ARG_NOT_IN_DOCSTR_CODE} -# ): -# """Docstring. - -# Args: -# arg_1: -# """ -# ''', -# "source.py", -# "", -# id=f"{ARG_NOT_IN_DOCSTR_CODE} disabled specific arg", -# ), -# pytest.param( -# f''' -# def foo(arg_1): -# """Docstring. - -# Args: -# arg_1: -# arg_2: -# """ # noqa: {ARG_IN_DOCSTR_CODE} -# ''', -# "source.py", -# "", -# id=f"{ARG_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# def foo(arg_1): -# """Docstring. - -# Args: -# arg_1: -# arg_1: -# """ # noqa: {DUPLICATE_ARG_CODE} -# ''', -# "source.py", -# "", -# id=f"{DUPLICATE_ARG_CODE} disabled", -# ), -# pytest.param( -# f''' -# def foo(): -# """Docstring.""" -# return 1 # noqa: {returns_section_not_in_docstr_code} -# ''', -# "source.py", -# "", -# id=f"{returns_section_not_in_docstr_code} disabled", -# ), -# pytest.param( -# f''' -# def foo(): -# """Docstring. - -# Returns: -# A value. -# """ # noqa: {returns_section_in_docstr_code} -# ''', -# "source.py", -# "", -# id=f"{returns_section_in_docstr_code} disabled", -# ), -# pytest.param( -# f''' -# def foo(): -# """Docstring. -# -# Returns: -# A value. -# -# Return: -# A value. -# """ # noqa: {mult_returns_sections_in_docstr_code} -# return 1 -# ''', -# "source.py", -# "", -# id=f"{mult_returns_sections_in_docstr_code} disabled", -# ), -# pytest.param( -# f''' -# def foo(): -# """Docstring.""" -# yield 1 # noqa: {yields_section_not_in_docstr_code} -# ''', -# "source.py", -# "", -# id=f"{yields_section_not_in_docstr_code} disabled", -# ), -# pytest.param( -# f''' -# def foo(): -# """Docstring. - -# Yields: -# A value. -# """ # noqa: {yields_section_in_docstr_code} -# ''', -# "source.py", -# "", -# id=f"{yields_section_in_docstr_code} disabled", -# ), -# pytest.param( -# f''' -# def foo(): -# """Docstring. - -# Yields: -# A value. - -# Yield: -# A value. -# """ # noqa: {mult_yields_sections_in_docstr_code} -# yield 1 -# ''', -# "source.py", -# "", -# id=f"{mult_yields_sections_in_docstr_code} disabled", -# ), -# pytest.param( -# f''' -# class Exc1Error(Exception): -# """Docstring.""" - - -# def foo(): -# """Docstring.""" # noqa: {RAISES_SECTION_NOT_IN_DOCSTR_CODE} -# raise Exc1Error -# ''', -# "source.py", -# "", -# id=f"{RAISES_SECTION_NOT_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# def foo(): -# """Docstring. - -# Raises: -# Exc1:. -# """ # noqa: {RAISES_SECTION_IN_DOCSTR_CODE} -# ''', -# "source.py", -# "", -# id=f"{RAISES_SECTION_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# class Exc1Error(Exception): -# """Docstring.""" - - -# def foo(): -# """Docstring. - -# Raises: -# Exc1Error: - -# Raise: -# Exc1Error: -# """ # noqa: {MULT_RAISES_SECTIONS_IN_DOCSTR_CODE} -# raise Exc1Error -# ''', -# "source.py", -# "", -# id=f"{MULT_RAISES_SECTIONS_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# class Exc1Error(Exception): -# """Docstring.""" - - -# class Exc2Error(Exception): -# """Docstring.""" - - -# def foo(): -# """Docstring. - -# Raises: -# Exc1Error:. -# """ -# raise Exc1Error -# raise Exc2Error # noqa: {EXC_NOT_IN_DOCSTR_CODE} -# ''', -# "source.py", -# "", -# id=f"{EXC_NOT_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# class Exc1Error(Exception): -# """Docstring.""" - - -# def foo(): -# """Docstring. - -# Raises: -# Exc1Error: -# Exc2Error: -# """ # noqa: {EXC_IN_DOCSTR_CODE} -# raise Exc1Error -# ''', -# "source.py", -# "", -# id=f"{EXC_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# def foo(): -# """Docstring. - -# Raises: -# """ # noqa: {RE_RAISE_NO_EXC_IN_DOCSTR_CODE},D414 -# raise -# ''', -# "source.py", -# "", -# id=f"{RE_RAISE_NO_EXC_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# class Exc1Error(Exception): -# """Docstring.""" - - -# def foo(): -# """Docstring. - -# Raises: -# Exc1Error: -# Exc1Error: -# """ # noqa: {DUPLICATE_EXC_CODE} -# raise Exc1Error -# ''', -# "source.py", -# "", -# id=f"{DUPLICATE_EXC_CODE} disabled", -# ), -# pytest.param( -# f''' -# class Class1: -# """Docstring.""" # noqa: {ATTRS_SECTION_NOT_IN_DOCSTR_CODE} - -# attr_1 = "value 1" -# ''', -# "source.py", -# "", -# id=f"{ATTRS_SECTION_NOT_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# class Class1: -# """Docstring. - -# Attrs: -# Attributes. -# """ # noqa: {ATTRS_SECTION_IN_DOCSTR_CODE} -# ''', -# "source.py", -# "", -# id=f"{ATTRS_SECTION_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# class Class1: -# """Docstring. - -# Attrs: -# attr_1: - -# Attributes: -# attr_1: -# """ # noqa: {MULT_ATTRS_SECTIONS_IN_DOCSTR_CODE} - -# attr_1 = "value 1" -# ''', -# "source.py", -# "", -# id=f"{MULT_ATTRS_SECTIONS_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# class Class1: -# """Docstring. - -# Attrs: -# Attributes. -# """ - -# attr_1 = "value 1" # noqa: {ATTR_NOT_IN_DOCSTR_CODE} -# ''', -# "source.py", -# "", -# id=f"{ATTR_NOT_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# class Class1: -# """Docstring. - -# Attrs: -# attr_1: -# """ - -# attr_1 = "value 1" -# attr_2 = "value 2" # noqa: {ATTR_NOT_IN_DOCSTR_CODE} -# ''', -# "source.py", -# "", -# id=f"{ATTR_NOT_IN_DOCSTR_CODE} disabled specific arg", -# ), -# pytest.param( -# f''' -# class Class1: -# """Docstring. - -# Attrs: -# attr_1: -# attr_2: -# """ # noqa: {ATTR_IN_DOCSTR_CODE} - -# attr_1 = "value 1" -# ''', -# "source.py", -# "", -# id=f"{ATTR_IN_DOCSTR_CODE} disabled", -# ), -# pytest.param( -# f''' -# class Class1: -# """Docstring. - -# Attrs: -# attr_1: -# attr_1: -# """ # noqa: {DUPLICATE_ATTR_CODE} - -# attr_1 = "value 1" -# ''', -# "source.py", -# "", -# id=f"{DUPLICATE_ATTR_CODE} disabled", -# ), -# ], -# ) -# def test_pass(code: str, filename: str, extra_args: str, tmp_path: Path): -# """ -# given: file with Python code that passes the linting -# when: flake8 is run against the code -# then: the process exits with zero code and empty stdout -# """ -# code_file = create_code_file(code, filename, tmp_path) -# (config_file := tmp_path / ".flake8").touch() - -# with subprocess.Popen( -# ( -# f"{sys.executable} -m flake8 {code_file} {extra_args} --ignore D205,D400,D103 " -# f"--config {config_file}" -# ), -# stdout=subprocess.PIPE, -# shell=True, -# ) as proc: -# stdout = proc.communicate()[0].decode(encoding="utf-8") - -# assert not stdout, stdout -# assert not proc.returncode - - -# def test_self(): -# """ -# given: working linter -# when: flake8 is run against the source and tests of the linter -# then: the process exits with zero code and empty stdout -# """ -# with subprocess.Popen( -# f"{sys.executable} -m flake8 ruff_docstrings_complete/ tests/ --ignore D205,D400,D103", -# stdout=subprocess.PIPE, -# shell=True, -# ) as proc: -# stdout = proc.communicate()[0].decode(encoding="utf-8") - -# assert not stdout, stdout -# assert not proc.returncode diff --git a/tests/python_tests/test_docstring.py b/tests/python_tests/test_docstring.py deleted file mode 100644 index 6a6de4b..0000000 --- a/tests/python_tests/test_docstring.py +++ /dev/null @@ -1,523 +0,0 @@ -"""Tests for docstring module.""" - -from __future__ import annotations - -import pytest - -from ruff_docstrings_complete._core import docstring - -# Need access to protected functions for testing -# pylint: disable=protected-access - - - - - -@pytest.mark.parametrize( - "lines, expected_sections", - [ - pytest.param((), (), id="empty"), - pytest.param(("",), (), id="single not a section"), - pytest.param((" ",), (), id="single not a section whitespace"), - pytest.param(("\t",), (), id="single not a section alternate whitespace"), - pytest.param( - ("line 1",), (docstring._Section(None, ()),), id="single line section no name" - ), - pytest.param( - ("line 1", "line 2"), (docstring._Section(None, ()),), id="multi line section no name" - ), - pytest.param( - ("line 1", "name_1:"), - (docstring._Section(None, ("name_1",)),), - id="multi line section no name second like name", - ), - pytest.param( - ("line 1:",), - (docstring._Section(None, ()),), - id="single section no name colon after first word", - ), - pytest.param(("name_1:",), (docstring._Section("name_1", ()),), id="single section"), - pytest.param( - (" name_1:",), - (docstring._Section("name_1", ()),), - id="single section leading whitespace single space", - ), - pytest.param( - ("\tname_1:",), - (docstring._Section("name_1", ()),), - id="single section leading whitespace single tab", - ), - pytest.param( - (" name_1:",), - (docstring._Section("name_1", ()),), - id="single section leading whitespace multiple", - ), - pytest.param( - ("name_1: ",), - (docstring._Section("name_1", ()),), - id="single section trailing whitespace", - ), - pytest.param( - ("name_1: description",), - (docstring._Section("name_1", ()),), - id="single section trailing characters", - ), - pytest.param( - ("name_1:", "description 1"), - (docstring._Section("name_1", ()),), - id="single section multiple lines", - ), - pytest.param( - ("name_1:", "sub_name_1:"), - (docstring._Section("name_1", ("sub_name_1",)),), - id="single section single sub-section", - ), - pytest.param( - ("name_1:", "sub_name_1 (text 1):"), - (docstring._Section("name_1", ("sub_name_1",)),), - id="single section single sub-section brackets", - ), - pytest.param( - ("name_1:", " sub_name_1:"), - (docstring._Section("name_1", ("sub_name_1",)),), - id="single section single sub-section leading whitespace", - ), - pytest.param( - ("name_1:", "sub_name_1: "), - (docstring._Section("name_1", ("sub_name_1",)),), - id="single section single sub-section trailing whitespace", - ), - pytest.param( - ("name_1:", "sub_name_1: description 1"), - (docstring._Section("name_1", ("sub_name_1",)),), - id="single section single sub-section trailing characters", - ), - pytest.param( - ("name_1:", "sub_name_1:", "description 1"), - (docstring._Section("name_1", ("sub_name_1",)),), - id="single section single sub-section other sub first", - ), - pytest.param( - ("name_1:", "description 1", "sub_name_1:"), - (docstring._Section("name_1", ("sub_name_1",)),), - id="single section single sub-section other sub last", - ), - pytest.param( - ("name_1:", "sub_name_1:", "sub_name_2:"), - (docstring._Section("name_1", ("sub_name_1", "sub_name_2")),), - id="single section multiple sub-sections", - ), - pytest.param( - ("name_1:", "sub_name_1:", "sub_name_2:", "sub_name_3:"), - (docstring._Section("name_1", ("sub_name_1", "sub_name_2", "sub_name_3")),), - id="single section many sub-sections", - ), - pytest.param( - ("name_1:", "description 1", "description 2"), - (docstring._Section("name_1", ()),), - id="single section many lines", - ), - pytest.param( - ("name_1:", ""), (docstring._Section("name_1", ()),), id="single section separator" - ), - pytest.param( - ("name_1:", "", "name_2:"), - (docstring._Section("name_1", ()), docstring._Section("name_2", ())), - id="multiple sections separator empty", - ), - pytest.param( - ("# name_1:", "# ", "# name_2:"), - (docstring._Section("name_1", ()), docstring._Section("name_2", ())), - id="commented multiple sections separator single space", - ), - pytest.param( - ("# name_1:", " ", "# name_2:"), - (docstring._Section("name_1", ()), docstring._Section("name_2", ())), - id="commented multiple sections separator not commented single space ", - ), - pytest.param( - ("name_1:", "\t", "name_2:"), - (docstring._Section("name_1", ()), docstring._Section("name_2", ())), - id="multiple sections separator single tab", - ), - pytest.param( - ("name_1:", " ", "name_2:"), - (docstring._Section("name_1", ()), docstring._Section("name_2", ())), - id="multiple sections separator multiple whitespace", - ), - pytest.param( - ("name_1:", "sub_name_1:", "", "name_2:"), - (docstring._Section("name_1", ("sub_name_1",)), docstring._Section("name_2", ())), - id="multiple sections first has sub-section", - ), - pytest.param( - ("name_1:", "", "name_2:", "sub_name_1:"), - (docstring._Section("name_1", ()), docstring._Section("name_2", ("sub_name_1",))), - id="multiple sections last has sub-section", - ), - pytest.param( - ("name_1:", "", "name_2:", "", "name_3:"), - ( - docstring._Section("name_1", ()), - docstring._Section("name_2", ()), - docstring._Section("name_3", ()), - ), - id="many sections", - ), - ], -) -def test__get_sections( - lines: tuple[()] | tuple[str, ...], - expected_sections: tuple[()] | tuple[docstring._Section, ...], -): - """ - given: lines of a docstring - when: _get_sections is called with the lines - then: the expected sections are returned. - """ - assert isinstance(lines, tuple) - assert isinstance(expected_sections, tuple) - - returned_sections = tuple(docstring._get_sections(lines=lines)) - - assert returned_sections == expected_sections - - -@pytest.mark.parametrize( - "value, expected_docstring", - [ - pytest.param("", docstring.Docstring(), id="empty"), - pytest.param("short description", docstring.Docstring(), id="short description"), - pytest.param( - """short description - -long description""", - docstring.Docstring(), - id="short and long description", - ), - pytest.param( - """short description - -Args: - """, - docstring.Docstring(args=(), args_sections=("Args",)), - id="args empty", - ), - pytest.param( - """short description - -Args: - arg_1: - """, - docstring.Docstring(args=("arg_1",), args_sections=("Args",)), - id="args single", - ), - pytest.param( - """short description - -# Args: -# arg_1: -# arg_2: -# """, - docstring.Docstring(args=("arg_1", "arg_2"), args_sections=("Args",)), - id="args multiple", - ), - pytest.param( - """short description - -args: - arg_1: - """, - docstring.Docstring(args=("arg_1",), args_sections=("args",)), - id="args lower case", - ), - pytest.param( - """short description - -Arguments: - arg_1: - """, - docstring.Docstring(args=("arg_1",), args_sections=("Arguments",)), - id="args alternate Arguments", - ), - pytest.param( - """short description - -Parameters: - arg_1: - """, - docstring.Docstring(args=("arg_1",), args_sections=("Parameters",)), - id="args alternate Parameters", - ), - pytest.param( - """short description - -Args: - arg_1: - -Parameters: - arg_2: - """, - docstring.Docstring(args=("arg_1",), args_sections=("Args", "Parameters")), - id="args multiple sections", - ), - pytest.param( - """short description - -Attrs: - """, - docstring.Docstring(attrs=(), attrs_sections=("Attrs",)), - id="attrs empty", - ), - pytest.param( - """short description - -Attrs: - -Attributes: - """, - docstring.Docstring(attrs=(), attrs_sections=("Attrs", "Attributes")), - id="multiple attrs empty", - ), - pytest.param( - """short description - -Attrs: - -Attrs: - """, - docstring.Docstring(attrs=(), attrs_sections=("Attrs", "Attrs")), - id="multiple attrs alternate empty", - ), - pytest.param( - """short description - -Attrs: - attr_1: - """, - docstring.Docstring(attrs=("attr_1",), attrs_sections=("Attrs",)), - id="attrs single", - ), - pytest.param( - """short description - -Attrs: - attr_1: - attr_2: - """, - docstring.Docstring(attrs=("attr_1", "attr_2"), attrs_sections=("Attrs",)), - id="attrs multiple", - ), - pytest.param( - """short description - -attrs: - attr_1: - """, - docstring.Docstring(attrs=("attr_1",), attrs_sections=("attrs",)), - id="attrs lower case", - ), - pytest.param( - """short description - -Attributes: - attr_1: - """, - docstring.Docstring(attrs=("attr_1",), attrs_sections=("Attributes",)), - id="attrs alternate Attributes", - ), - pytest.param( - """short description - -Returns: - """, - docstring.Docstring(returns_sections=("Returns",)), - id="returns empty", - ), - pytest.param( - """short description - -Returns: - The return value. - """, - docstring.Docstring(returns_sections=("Returns",)), - id="returns single line", - ), - pytest.param( - """short description - -Return: - """, - docstring.Docstring(returns_sections=("Return",)), - id="returns alternate", - ), - pytest.param( - """short description - -Returns: - -Returns: - """, - docstring.Docstring(returns_sections=("Returns", "Returns")), - id="multiple returns", - ), - pytest.param( - """short description - -Returns: - -Return: - """, - docstring.Docstring(returns_sections=("Returns", "Return")), - id="multiple returns alternate", - ), - pytest.param( - """short description - -Yields: - """, - docstring.Docstring(yields_sections=("Yields",)), - id="yields empty", - ), - pytest.param( - """short description - -Yields: - The return value. - """, - docstring.Docstring(yields_sections=("Yields",)), - id="yields single line", - ), - pytest.param( - """short description - -Yield: - """, - docstring.Docstring(yields_sections=("Yield",)), - id="yields alternate", - ), - pytest.param( - """short description - -Yields: - -Yields: - """, - docstring.Docstring(yields_sections=("Yields", "Yields")), - id="multiple yields", - ), - pytest.param( - """short description - -Yields: - -Yield: - """, - docstring.Docstring(yields_sections=("Yields", "Yield")), - id="multiple yields alternate", - ), - pytest.param( - """short description - -Raises: - """, - docstring.Docstring(raises=(), raises_sections=("Raises",)), - id="raises empty", - ), - pytest.param( - """short description - -Raises: - -Raises: - """, - docstring.Docstring(raises=(), raises_sections=("Raises", "Raises")), - id="raises empty multiple", - ), - pytest.param( - """short description - -Raises: - -Raise: - """, - docstring.Docstring(raises=(), raises_sections=("Raises", "Raise")), - id="raises empty multiple alternate", - ), - pytest.param( - """short description - -Raises: - """, - docstring.Docstring(raises=(), raises_sections=("Raises",)), - id="raises empty multiple", - ), - pytest.param( - """short description - -Raises: - exc_1: - """, - docstring.Docstring(raises=("exc_1",), raises_sections=("Raises",)), - id="raises single", - ), - pytest.param( - """short description - -Raises: - exc_1: - exc_2: - """, - docstring.Docstring(raises=("exc_1", "exc_2"), raises_sections=("Raises",)), - id="raises multiple", - ), - pytest.param( - """short description - -raises: - exc_1: - """, - docstring.Docstring(raises=("exc_1",), raises_sections=("raises",)), - id="raises lower case", - ), - pytest.param( - """short description - -Attrs: - attr_1: - -Args: - arg_1: - -Returns: - The return value. - -Yields: - The yield value. - -Raises: - exc_1: - """, - docstring.Docstring( - args=("arg_1",), - args_sections=("Args",), - attrs=("attr_1",), - attrs_sections=("Attrs",), - returns_sections=("Returns",), - yields_sections=("Yields",), - raises=("exc_1",), - raises_sections=("Raises",), - ), - id="all defined", - ), - ], -) -def test_parse(value: str, expected_docstring: docstring.Docstring): - """ - given: docstring value - when: parse is called with the docstring - then: the expected docstring information is returned. - """ - returned_docstring = docstring.parse(value=value) - - assert returned_docstring == expected_docstring diff --git a/tests/python_tests/unit/__init__.py b/tests/python_tests/unit/__init__.py deleted file mode 100644 index e0310a0..0000000 --- a/tests/python_tests/unit/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Unit tests.""" diff --git a/tests/python_tests/unit/result.py b/tests/python_tests/unit/result.py deleted file mode 100644 index 6fb6410..0000000 --- a/tests/python_tests/unit/result.py +++ /dev/null @@ -1,22 +0,0 @@ -# """Get the linting result.""" - -# from __future__ import annotations - -# import ast - -# from ruff_docstrings_complete import Plugin - - -# def get(code: str, filename: str = "source.py") -> tuple[str, ...]: -# """Generate linting results. - -# Args: -# code: The code to check. -# filename: The name of the file the code is in. - -# Returns: -# The linting result. -# """ -# tree = ast.parse(code) -# plugin = Plugin(tree, filename) -# return tuple(f"{line}:{col} {msg}" for line, col, msg, _ in plugin.run()) diff --git a/tests/python_tests/unit/test___init__.py b/tests/python_tests/unit/test___init__.py deleted file mode 100644 index 2985db0..0000000 --- a/tests/python_tests/unit/test___init__.py +++ /dev/null @@ -1,1063 +0,0 @@ -"""Unit tests for plugin except for args rules.""" - -# The lines represent the number of test cases -# pylint: disable=too-many-lines - -from __future__ import annotations - -import pytest - - -from . import result -from ruff_docstrings_complete._core import apply_rules - -from ruff_docstrings_complete._core import constants - -ERROR_CODE_PREFIX = constants.ERROR_CODE_PREFIX -MORE_INFO_BASE = constants.MORE_INFO_BASE - -DOCSTR_MISSING_CODE = constants.DOCSTR_MISSING_CODE -DOCSTR_MISSING_MSG = constants.DOCSTR_MISSING_MSG - - -RETURNS_SECTION_NOT_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}030" -RETURNS_SECTION_NOT_IN_DOCSTR_MSG = ( - f"{RETURNS_SECTION_NOT_IN_DOCSTR_CODE} function/ method that returns a value should have the " - f"returns section in the docstring{MORE_INFO_BASE}{RETURNS_SECTION_NOT_IN_DOCSTR_CODE.lower()}" -) -RETURNS_SECTION_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}031" -RETURNS_SECTION_IN_DOCSTR_MSG = ( - f"{RETURNS_SECTION_IN_DOCSTR_CODE} function/ method that does not return a value should not " - f"have the returns section in the docstring" - f"{MORE_INFO_BASE}{RETURNS_SECTION_IN_DOCSTR_CODE.lower()}" -) -MULT_RETURNS_SECTIONS_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}032" -MULT_RETURNS_SECTIONS_IN_DOCSTR_MSG = ( - f"{MULT_RETURNS_SECTIONS_IN_DOCSTR_CODE} a docstring should only contain a single returns " - "section, found %s" - f"{MORE_INFO_BASE}{MULT_RETURNS_SECTIONS_IN_DOCSTR_CODE.lower()}" -) -YIELDS_SECTION_NOT_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}040" -YIELDS_SECTION_NOT_IN_DOCSTR_MSG = ( - f"{YIELDS_SECTION_NOT_IN_DOCSTR_CODE} function/ method that yields a value should have the " - f"yields section in the docstring{MORE_INFO_BASE}{YIELDS_SECTION_NOT_IN_DOCSTR_CODE.lower()}" -) -YIELDS_SECTION_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}041" -YIELDS_SECTION_IN_DOCSTR_MSG = ( - f"{YIELDS_SECTION_IN_DOCSTR_CODE} function/ method that does not yield a value should not " - f"have the yields section in the docstring" - f"{MORE_INFO_BASE}{YIELDS_SECTION_IN_DOCSTR_CODE.lower()}" -) -MULT_YIELDS_SECTIONS_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}042" -MULT_YIELDS_SECTIONS_IN_DOCSTR_MSG = ( - f"{MULT_YIELDS_SECTIONS_IN_DOCSTR_CODE} a docstring should only contain a single yields " - "section, found %s" - f"{MORE_INFO_BASE}{MULT_YIELDS_SECTIONS_IN_DOCSTR_CODE.lower()}" -) - -@pytest.mark.parametrize( - "code, expected_result", - [ - pytest.param("", (), id="trivial"), - pytest.param( - """ -def function_1(): - return -""", - (f"2:0 {DOCSTR_MISSING_MSG}",), - id="function docstring missing return", - ), - pytest.param( - """ -def _function_1(): - return -""", - (f"2:0 {DOCSTR_MISSING_MSG}",), - id="private function docstring missing return", - ), - pytest.param( - """ -@overload -def function_1(): - ... -""", - (), - id="function docstring missing overload", - ), - pytest.param( - """ -@overload() -def function_1(): - ... -""", - (), - id="function docstring missing overload call", - ), - pytest.param( - """ -@typing.overload -def function_1(): - ... -""", - (), - id="function docstring missing overload attr", - ), - pytest.param( - """ -def function_1(): - return - -def function_2(): - return -""", - (f"2:0 {DOCSTR_MISSING_MSG}", f"5:0 {DOCSTR_MISSING_MSG}"), - id="multiple functions docstring missing return", - ), - pytest.param( - """ -def function_1(): - pass -""", - (f"2:0 {DOCSTR_MISSING_MSG}",), - id="function docstring missing expression not constant", - ), - pytest.param( - """ -def function_1(): - 1 -""", - (f"2:0 {DOCSTR_MISSING_MSG}",), - id="function docstring missing expression constnant not string", - ), - pytest.param( - ''' -def function_1(): - """Docstring. - - Returns: - """ -''', - (f"3:4 {RETURNS_SECTION_IN_DOCSTR_MSG}",), - id="function no return returns in docstring", - ), - pytest.param( - ''' -def _function_1(): - """Docstring. - - Returns: - """ -''', - (f"3:4 {RETURNS_SECTION_IN_DOCSTR_MSG}",), - id="private function no return returns in docstring", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(): - """Docstring. - - Returns: - """ -''', - (f"5:8 {RETURNS_SECTION_IN_DOCSTR_MSG}",), - id="method no return returns in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring. - - Returns: - """ - return -''', - (f"3:4 {RETURNS_SECTION_IN_DOCSTR_MSG}",), - id="function return no value returns in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring. - - Returns: - - Returns: - """ - return 1 -''', - (f"3:4 {MULT_RETURNS_SECTIONS_IN_DOCSTR_MSG % 'Returns,Returns'}",), - id="function return multiple returns in docstring", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(): - """Docstring. - - Returns: - - Returns: - """ - return 1 -''', - (f"5:8 {MULT_RETURNS_SECTIONS_IN_DOCSTR_MSG % 'Returns,Returns'}",), - id="method return multiple returns in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - return 1 -''', - (f"4:4 {RETURNS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function single return value returns not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - return 0 -''', - (f"4:4 {RETURNS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function single falsely return value returns not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - return None -''', - (f"4:4 {RETURNS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function single None return value returns not in docstring", - ), - pytest.param( - ''' -async def function_1(): - """Docstring.""" - return 1 -''', - (f"4:4 {RETURNS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="async function single return value returns not in docstring", - ), - pytest.param( - ''' -class FooClass: - """Docstring.""" - def function_1(self): - """Docstring.""" - return 1 -''', - (f"6:8 {RETURNS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="method single return value returns not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - if True: - return 1 -''', - (f"5:8 {RETURNS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function single nested return value returns not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - return 11 - return 12 -''', - ( - f"4:4 {RETURNS_SECTION_NOT_IN_DOCSTR_MSG}", - f"5:4 {RETURNS_SECTION_NOT_IN_DOCSTR_MSG}", - ), - id="function multiple return value returns not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - return 11 - return -''', - (f"4:4 {RETURNS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function multiple return first value returns not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - return - return 12 -''', - (f"5:4 {RETURNS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function multiple return second value returns not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - yield 1 -''', - (f"4:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function single yield value yields not in docstring", - ), - pytest.param( - ''' -def _function_1(): - """Docstring.""" - yield 1 -''', - (), - id="private function single yield value yields not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - yield from tuple() -''', - (f"4:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function single yield from value yields not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - yield 0 -''', - (f"4:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function single falsely yield value yields not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - yield None -''', - (f"4:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function single None yield value yields not in docstring", - ), - pytest.param( - ''' -async def function_1(): - """Docstring.""" - yield 1 -''', - (f"4:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="async function single yield value yields not in docstring", - ), - pytest.param( - ''' -class FooClass: - """Docstring.""" - def function_1(self): - """Docstring.""" - yield 1 -''', - (f"6:8 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="method single yield value yields not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - if True: - yield 1 -''', - (f"5:8 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function single nested yield value yields not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - yield 11 - yield 12 -''', - ( - f"4:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}", - f"5:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}", - ), - id="function multiple yield value yields not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - yield 11 - yield -''', - (f"4:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function multiple yield first value yields not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - yield - yield 12 -''', - (f"5:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function multiple yield second value yields not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - yield from tuple() - yield from list() -''', - ( - f"4:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}", - f"5:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}", - ), - id="function multiple yield from value yields not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - yield from tuple() - yield -''', - (f"4:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function multiple yield from first value yields not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring.""" - yield - yield from list() -''', - (f"5:4 {YIELDS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function multiple yield from second value yields not in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring. - - Yields: - """ -''', - (f"3:4 {YIELDS_SECTION_IN_DOCSTR_MSG}",), - id="function no yield yields in docstring", - ), - pytest.param( - ''' -def _function_1(): - """Docstring. - - Yields: - """ -''', - (f"3:4 {YIELDS_SECTION_IN_DOCSTR_MSG}",), - id="private function no yield yields in docstring", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(): - """Docstring. - - Yields: - """ -''', - (f"5:8 {YIELDS_SECTION_IN_DOCSTR_MSG}",), - id="method no yield yields in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring. - - Yields: - """ - yield -''', - (f"3:4 {YIELDS_SECTION_IN_DOCSTR_MSG}",), - id="function yield no value yields in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring. - - Yields: - - Yields: - """ - yield 1 -''', - (f"3:4 {MULT_YIELDS_SECTIONS_IN_DOCSTR_MSG % 'Yields,Yields'}",), - id="function yield multiple yields in docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring. - - Yields: - - Yields: - """ - yield from tuple() -''', - (f"3:4 {MULT_YIELDS_SECTIONS_IN_DOCSTR_MSG % 'Yields,Yields'}",), - id="function yield from multiple yields in docstring", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(): - """Docstring. - - Yields: - - Yields: - """ - yield 1 -''', - (f"5:8 {MULT_YIELDS_SECTIONS_IN_DOCSTR_MSG % 'Yields,Yields'}",), - id="method yield multiple yields in docstring", - ), - pytest.param( - ''' -async def function_1(): - """Docstring 1.""" -''', - (), - id="function docstring", - ), - pytest.param( - ''' -async def function_1(): - """Docstring 1.""" - -async def function_2(): - """Docstring 2.""" -''', - (), - id="multiple functions docstring", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1.""" -''', - (), - id="async function docstring", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(self): - return -''', - (f"4:4 {DOCSTR_MISSING_MSG}",), - id="method docstring missing return", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1.""" - return -''', - (), - id="function return no value docstring no returns section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1. - - Returns: - """ - return 1 -''', - (), - id="function return value docstring returns section", - ), - pytest.param( - ''' -def _function_1(): - """Docstring 1. - - Returns: - """ - return 1 -''', - (), - id="private function return value docstring returns section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1.""" - def function_2(): - """Docstring 2. - - Returns: - """ - return 1 -''', - (), - id="function return value in nested function docstring no returns section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1.""" - async def function_2(): - """Docstring 2. - - Returns: - """ - return 1 -''', - (), - id="function return value in nested async function docstring no returns section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1.""" - class Class1: - """Docstring.""" - return 1 -''', - (), - id="function return value in class docstring no returns section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1. - - Returns: - """ - return 1 - return 2 -''', - (), - id="function multiple return values docstring returns section", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(self): - """Docstring 1. - - Returns: - """ - return 1 -''', - (), - id="method return value docstring returns section", - ), - pytest.param( - ''' -class Class1: - """Docstring. - - Attrs: - function_1: - """ - @property - def function_1(self): - """Docstring 1.""" - return 1 -''', - (), - id="property return value docstring no returns section", - ), - pytest.param( - ''' -class Class1: - """Docstring. - - Attrs: - function_1: - """ - @cached_property - def function_1(self): - """Docstring 1.""" - return 1 -''', - (), - id="cached_property return value docstring no returns section", - ), - pytest.param( - ''' -class Class1: - """Docstring. - - Attrs: - function_1: - """ - @functools.cached_property - def function_1(self): - """Docstring 1.""" - return 1 -''', - (), - id="functools.cached_property return value docstring no returns section", - ), - pytest.param( - ''' -class Class1: - """Docstring. - - Attrs: - function_1: - """ - @property - async def function_1(self): - """Docstring 1.""" - return 1 -''', - (), - id="async property return value docstring no returns section", - ), - pytest.param( - ''' -class Class1: - """Docstring. - - Attrs: - function_1: - """ - @property() - def function_1(self): - """Docstring 1.""" - return 1 -''', - (), - id="property call return value docstring no returns section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1.""" - yield -''', - (), - id="function yield no value docstring no yields section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1. - - Yields: - """ - yield 1 -''', - (), - id="function yield value docstring yields section", - ), - pytest.param( - ''' -def _function_1(): - """Docstring 1. - - Yields: - """ - yield 1 -''', - (), - id="private function yield value docstring yields section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1. - - Yields: - """ - yield from tuple() -''', - (), - id="function yield from docstring yields section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1.""" - def function_2(): - """Docstring 2. - - Yields: - """ - yield 1 -''', - (), - id="function yield value in nested function docstring no yields section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1.""" - async def function_2(): - """Docstring 2. - - Yields: - """ - yield 1 -''', - (), - id="function yield value in nested async function docstring no yields section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1.""" - class Class1: - """Docstring.""" - yield 1 -''', - (), - id="function yield value in class docstring no yields section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1. - - Yields: - """ - yield 1 - yield 2 -''', - (), - id="function multiple yield values docstring yields section", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(self): - """Docstring 1. - - Yields: - """ - yield 1 -''', - (), - id="method yield value docstring yields section", - ), - pytest.param( - ''' -class Class1: - """Docstring. - - Attrs: - function_1: - """ - @property - def function_1(self): - """Docstring 1.""" - yield 1 -''', - (), - id="property yield value docstring no yields section", - ), - ], -) -def test_plugin(code: str, expected_result: tuple[str, ...]): - """ - given: code - when: linting is run on the code - then: the expected result is returned - """ - assert tuple(apply_rules(code)) == expected_result - - -@pytest.mark.parametrize( - "code, filename, expected_result", - [ - pytest.param( - """ -def test_(): - pass -""", - "source.py", - (f"2:0 {DOCSTR_MISSING_MSG}",), - id="not test file", - ), - pytest.param( - """ -def foo(): - pass -""", - "test_.py", - (f"2:0 {DOCSTR_MISSING_MSG}",), - id="test file not test function", - ), - pytest.param( - """ -def test_(): - pass -""", - "test_.py", - (), - id="test file test function", - ), - pytest.param( - """ -def test_(): - pass -""", - "tests/test_.py", - (), - id="test file test function in directory", - ), - pytest.param( - """ -def foo(): - pass -""", - "conftest.py", - (f"2:0 {DOCSTR_MISSING_MSG}",), - id="normal file not fixture function", - ), - pytest.param( - """ -@fixture -def foo(): - pass -""", - "source.py", - (f"3:0 {DOCSTR_MISSING_MSG}",), - id="source file fixture function", - ), - pytest.param( - """ -@fixture -def foo(): - pass -""", - "conftest.py", - (), - id="fixture file fixture function", - ), - pytest.param( - """ -@fixture -def foo(): - pass -""", - "test_.py", - (), - id="test file fixture function", - ), - pytest.param( - """ -@FIXTURE -def foo(): - pass -""", - "conftest.py", - (), - id="fixture file fixture function capitalised", - ), - pytest.param( - """ -@fixture -@decorator -def foo(): - pass -""", - "conftest.py", - (), - id="fixture file fixture function multiple decorators first", - ), - pytest.param( - """ -@decorator -@fixture -def foo(): - pass -""", - "conftest.py", - (), - id="fixture file fixture function multiple decorators second", - ), - pytest.param( - """ -@pytest.fixture -def foo(): - pass -""", - "conftest.py", - (), - id="fixture file fixture function prefix", - ), - pytest.param( - """ -@pytest.fixture(scope="module") -def foo(): - pass -""", - "conftest.py", - (), - id="fixture file fixture function prefix call", - ), - pytest.param( - """ -@additional.pytest.fixture -def foo(): - pass -""", - "conftest.py", - (), - id="fixture file fixture function nested prefix", - ), - pytest.param( - """ -@additional.something.pytest.fixture -def foo(): - pass -""", - "conftest.py", - (), - id="fixture file fixture function double nested prefix", - ), - pytest.param( - """ -@fixture(scope="module") -def foo(): - pass -""", - "conftest.py", - (), - id="fixture file fixture function arguments", - ), - pytest.param( - """ -@fixture -def foo(): - pass -""", - "tests/conftest.py", - (), - id="fixture file fixture function in directory", - ), - ], -) -def test_plugin_filename(code: str, filename: str, expected_result: tuple[str, ...]): - """ - given: code and filename - when: linting is run on the code - then: the expected result is returned - """ - assert tuple(apply_rules(code, filename)) == expected_result diff --git a/tests/python_tests/unit/test___init__args.py b/tests/python_tests/unit/test___init__args.py deleted file mode 100644 index db3f849..0000000 --- a/tests/python_tests/unit/test___init__args.py +++ /dev/null @@ -1,792 +0,0 @@ -"""Unit tests for args checks in the plugin.""" - -from __future__ import annotations - -import pytest - -from ruff_docstrings_complete._core import apply_rules -# from . import result -from ruff_docstrings_complete._core import constants - -ERROR_CODE_PREFIX = constants.ERROR_CODE_PREFIX -MORE_INFO_BASE = constants.MORE_INFO_BASE - -ARGS_SECTION_NOT_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}020" -ARGS_SECTION_NOT_IN_DOCSTR_MSG = ( - f"{ARGS_SECTION_NOT_IN_DOCSTR_CODE} a function/ method with arguments should have the " - "arguments section in the docstring" - f"{MORE_INFO_BASE}{ARGS_SECTION_NOT_IN_DOCSTR_CODE.lower()}" -) -ARGS_SECTION_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}021" -ARGS_SECTION_IN_DOCSTR_MSG = ( - f"{ARGS_SECTION_IN_DOCSTR_CODE} a function/ method without arguments should not have the " - "arguments section in the docstring" - f"{MORE_INFO_BASE}{ARGS_SECTION_IN_DOCSTR_CODE.lower()}" -) -MULT_ARGS_SECTIONS_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}022" -MULT_ARGS_SECTIONS_IN_DOCSTR_MSG = ( - f"{MULT_ARGS_SECTIONS_IN_DOCSTR_CODE} a docstring should only contain a single arguments " - f"section, found %s{MORE_INFO_BASE}{MULT_ARGS_SECTIONS_IN_DOCSTR_CODE.lower()}" -) -ARG_NOT_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}023" -ARG_NOT_IN_DOCSTR_MSG = ( - f'{ARG_NOT_IN_DOCSTR_CODE} "%s" argument should be described in the docstring{MORE_INFO_BASE}' - f"{ARG_NOT_IN_DOCSTR_CODE.lower()}" -) -ARG_IN_DOCSTR_CODE = f"{ERROR_CODE_PREFIX}024" -ARG_IN_DOCSTR_MSG = ( - f'{ARG_IN_DOCSTR_CODE} "%s" argument should not be described in the docstring{MORE_INFO_BASE}' - f"{ARG_IN_DOCSTR_CODE.lower()}" -) -DUPLICATE_ARG_CODE = f"{ERROR_CODE_PREFIX}025" -DUPLICATE_ARG_MSG = ( - f'{DUPLICATE_ARG_CODE} "%s" argument documented multiple times{MORE_INFO_BASE}' - f"{DUPLICATE_ARG_CODE.lower()}" -) - -SKIP_ARGS = {"self", "cls"} -UNUSED_ARGS_PREFIX = "_" - - -@pytest.mark.parametrize( - "code, expected_result", - [ - pytest.param( - ''' -def function_1(arg_1): - """Docstring 1.""" -''', - (f"3:4 {ARGS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="function has single arg docstring no args section", - ), - pytest.param( - ''' -def function_1(arg_1): - """Docstring 1.""" - -def function_2(arg_2): - """Docstring 2.""" -''', - ( - f"3:4 {ARGS_SECTION_NOT_IN_DOCSTR_MSG}", - f"6:4 {ARGS_SECTION_NOT_IN_DOCSTR_MSG}", - ), - id="multiple function has single arg docstring no args section", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(self, arg_1): - """Docstring 1.""" -''', - (f"5:8 {ARGS_SECTION_NOT_IN_DOCSTR_MSG}",), - id="method has single arg docstring no args section", - ), - pytest.param( - ''' -def function_1(): - """Docstring 1. - - Args: - """ -''', - (f"3:4 {ARGS_SECTION_IN_DOCSTR_MSG}",), - id="function has no args docstring args section", - ), - pytest.param( - ''' -def _function_1(): - """Docstring 1. - - Args: - """ -''', - (f"3:4 {ARGS_SECTION_IN_DOCSTR_MSG}",), - id="private function has no args docstring args section", - ), - pytest.param( - ''' -def function_1(_arg_1): - """Docstring 1. - - Args: - """ -''', - (f"3:4 {ARGS_SECTION_IN_DOCSTR_MSG}",), - id="function has single unused arg docstring args", - ), -pytest.param( - ''' - def function_1(arg_1): - """Docstring 1. - - Args: -arg_1: - - Args: -arg_1: - """ - ''', - (f"3:4 {MULT_ARGS_SECTIONS_IN_DOCSTR_MSG % 'Args,Args'}",), - id="function has single args docstring multiple args sections same name", -), -pytest.param( - ''' - def function_1(arg_1): - """Docstring 1. - - Args: -arg_1: - - Arguments: -arg_1: - """ - ''', - (f"3:4 {MULT_ARGS_SECTIONS_IN_DOCSTR_MSG % 'Args,Arguments'}",), - id="function has single args docstring multiple args sections different name", -), - pytest.param( - ''' -def function_1(arg_1): - """Docstring 1. - - Args: - """ -''', - (f"2:15 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}",), - id="function has single arg docstring no arg", - ), - pytest.param( - ''' -async def function_1(arg_1): - """Docstring 1. - - Args: - """ -''', - (f"2:21 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}",), - id="async function has single arg docstring no arg", - ), - pytest.param( - ''' -def function_1(arg_1, /): - """Docstring 1. - - Args: - """ -''', - (f"2:15 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}",), - id="function has single positional only arg docstring no arg", - ), - pytest.param( - ''' -def function_1(arg_1, arg_2, /): - """Docstring 1. - - Args: - """ -''', - (f"2:15 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}", f"2:22 {ARG_NOT_IN_DOCSTR_MSG % 'arg_2'}"), - id="function has multiple positional only arg docstring no arg", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(self, arg_1, /): - """Docstring 1. - - Args: - """ -''', - (f"4:25 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}",), - id="method has single positional only arg docstring no arg", - ), - pytest.param( - ''' -def function_1(*, arg_1): - """Docstring 1. - - Args: - """ -''', - (f"2:18 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}",), - id="function has single keyword only arg docstring no arg", - ), - pytest.param( - ''' -def function_1(*, arg_1, arg_2): - """Docstring 1. - - Args: - """ -''', - (f"2:18 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}", f"2:25 {ARG_NOT_IN_DOCSTR_MSG % 'arg_2'}"), - id="function has multiple keyword only arg docstring no arg", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(self, *, arg_1): - """Docstring 1. - - Args: - """ -''', - (f"4:28 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}",), - id="method has single keyword only arg docstring no arg", - ), - pytest.param( - ''' -def function_1(*args): - """Docstring 1. - - Args: - """ -''', - (f"2:16 {ARG_NOT_IN_DOCSTR_MSG % 'args'}",), - id="function has *args docstring no arg", - ), - pytest.param( - ''' -def function_1(**kwargs): - """Docstring 1. - - Args: - """ -''', - (f"2:17 {ARG_NOT_IN_DOCSTR_MSG % 'kwargs'}",), - id="function has **kwargs docstring no arg", - ), - pytest.param( - ''' -def function_1(*args, **kwargs): - """Docstring 1. - - Args: - """ -''', - ( - f"2:16 {ARG_NOT_IN_DOCSTR_MSG % 'args'}", - f"2:24 {ARG_NOT_IN_DOCSTR_MSG % 'kwargs'}", - ), - id="function has *args and **kwargs docstring no arg", - ), - pytest.param( - ''' -def function_1(*args, arg_1): - """Docstring 1. - - Args: - """ -''', - (f"2:22 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}", f"2:16 {ARG_NOT_IN_DOCSTR_MSG % 'args'}"), - id="function has *args docstring no arg", - ), - pytest.param( - ''' -def function_1(arg_1, arg_2): - """Docstring 1. - - Args: - """ - ''', - (f"2:15 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}", f"2:22 {ARG_NOT_IN_DOCSTR_MSG % 'arg_2'}"), - id="function multiple args docstring no arg", - ), - pytest.param( - ''' -def function_1(_arg_1, arg_2): - """Docstring 1. - - Args: - """ - ''', - (f"2:23 {ARG_NOT_IN_DOCSTR_MSG % 'arg_2'}",), - id="function multiple args first unused docstring no arg", - ), - pytest.param( - ''' -def function_1(arg_1, _arg_2): - """Docstring 1. - - Args: - """ - ''', - (f"2:15 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}",), - id="function multiple args second unused docstring no arg", - ), - pytest.param( - ''' -def function_1(arg_1, arg_2): - """Docstring 1. - - Args: - arg_1: - """ -''', - (f"2:22 {ARG_NOT_IN_DOCSTR_MSG % 'arg_2'}",), - id="function multiple args docstring single arg first", - ), - pytest.param( - ''' -def function_1(arg_1, arg_2): - """Docstring 1. - - Args: - arg_2: - """ -''', - (f"2:15 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}",), - id="function multiple args docstring single arg second", - ), - pytest.param( - ''' -def function_1(arg_1): - """Docstring 1. - - Args: - arg_2: - """ -''', - ( - f"2:15 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}", - f"3:4 {ARG_IN_DOCSTR_MSG % 'arg_2'}", - ), - id="function has single arg docstring arg different", - ), - pytest.param( - ''' -def function_1(arg_1): - """Docstring 1. - - Args: - arg_2: - arg_3: - """ - ''', - ( - f"2:15 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}", - f"3:4 {ARG_IN_DOCSTR_MSG % 'arg_2'}", - f"3:4 {ARG_IN_DOCSTR_MSG % 'arg_3'}", - ), - id="function single arg docstring multiple args different", - ), - pytest.param( - ''' -def function_1(arg_1, arg_2): - """Docstring 1. - - Args: - arg_3: - arg_4: - """ - ''', - ( - f"2:15 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}", - f"2:22 {ARG_NOT_IN_DOCSTR_MSG % 'arg_2'}", - f"3:4 {ARG_IN_DOCSTR_MSG % 'arg_3'}", - f"3:4 {ARG_IN_DOCSTR_MSG % 'arg_4'}", - ), - id="function multiple arg docstring multiple args different", - ), - pytest.param( - ''' -def function_1(arg_1, arg_2): - """Docstring 1. - - Args: - arg_3: - arg_2: - """ - ''', - (f"2:15 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}", f"3:4 {ARG_IN_DOCSTR_MSG % 'arg_3'}"), - id="function multiple arg docstring multiple args first different", - ), - pytest.param( - ''' -def function_1(arg_1, arg_2): - """Docstring 1. - - Args: - arg_1: - arg_3: - """ - ''', - (f"2:22 {ARG_NOT_IN_DOCSTR_MSG % 'arg_2'}", f"3:4 {ARG_IN_DOCSTR_MSG % 'arg_3'}"), - id="function multiple arg docstring multiple args last different", - ), - pytest.param( - ''' -def function_1(arg_1): - """Docstring 1. - - Args: - arg_1: - arg_1: - """ -''', - (f"3:4 {DUPLICATE_ARG_MSG % 'arg_1'}",), - id="function single arg docstring duplicate arg", - ), - pytest.param( - ''' -def function_1(_arg_1): - """Docstring 1. - - Args: - _arg_1: - _arg_1: - """ -''', - (f"3:4 {DUPLICATE_ARG_MSG % '_arg_1'}",), - id="function single unused arg docstring duplicate arg", - ), - pytest.param( - ''' -def function_1(arg_1): - """Docstring 1. - - Args: - arg_1: - arg_1: - arg_1: - """ -''', - (f"3:4 {DUPLICATE_ARG_MSG % 'arg_1'}",), - id="function single arg docstring duplicate arg many", - ), - pytest.param( - ''' -def function_1(arg_1, arg_2): - """Docstring 1. - - Args: - arg_1: - arg_1: - arg_2: - """ -''', - (f"3:4 {DUPLICATE_ARG_MSG % 'arg_1'}",), - id="function multiple arg docstring duplicate arg first", - ), - pytest.param( - ''' -def function_1(arg_1, arg_2): - """Docstring 1. - - Args: - arg_1: - arg_2: - arg_2: - """ -''', - (f"3:4 {DUPLICATE_ARG_MSG % 'arg_2'}",), - id="function multiple arg docstring duplicate arg second", - ), - pytest.param( - ''' -def function_1(arg_1, arg_2): - """Docstring 1. - - Args: - arg_1: - arg_1: - arg_2: - arg_2: - """ -''', - (f"3:4 {DUPLICATE_ARG_MSG % 'arg_1'}", f"3:4 {DUPLICATE_ARG_MSG % 'arg_2'}"), - id="function multiple arg docstring duplicate arg all", - ), - pytest.param( - ''' -def function_1(arg_1): - """Docstring 1. - - Args: - arg_1: - """ -''', - (), - id="function single arg docstring single arg", - ), - pytest.param( - ''' -def _function_1(arg_1): - """Docstring 1. - - Args: - arg_1: - """ -''', - (), - id="private function single arg docstring single arg", - ), - pytest.param( - ''' -def function_1(_arg_1): - """Docstring 1. - - Args: - _arg_1: - """ -''', - (), - id="function single unused arg docstring single arg", - ), - pytest.param( - ''' -def _function_1(arg_1): - """Docstring 1.""" -''', - (), - id="private function single arg docstring no arg", - ), - pytest.param( - ''' -def function_1(_arg_1): - """Docstring 1.""" -''', - (), - id="function single unused arg docstring no args", - ), - pytest.param( - ''' -def function_1(*_args): - """Docstring 1. - - Args: - _args: - """ -''', - (), - id="function single unused *args docstring single arg", - ), - pytest.param( - ''' -def function_1(*_args): - """Docstring 1.""" -''', - (), - id="function single unused *args docstring no args", - ), - pytest.param( - ''' -def function_1(**_kwargs): - """Docstring 1. - - Args: - _kwargs: - """ -''', - (), - id="function single unused **kwargs docstring single arg", - ), - pytest.param( - ''' -def function_1(**_kwargs): - """Docstring 1.""" -''', - (), - id="function single unused **kwargs docstring no args", - ), - pytest.param( - ''' -def function_1(*args): - """Docstring 1. - - Args: - args: - """ -''', - (), - id="function single arg docstring *args", - ), - pytest.param( - ''' -def function_1(**kwargs): - """Docstring 1. - - Args: - kwargs: - """ -''', - (), - id="function single arg docstring **kwargs", - ), - pytest.param( - ''' -def function_1(*args, **kwargs): - """Docstring 1. - - Args: - args: - kwargs: - """ -''', - (), - id="function single arg docstring *args and **kwargs", - ), - pytest.param( - ''' -def function_1(arg_1, arg_2): - """Docstring 1. - - Args: - arg_1: - arg_2: - """ -''', - (), - id="function multiple arg docstring multiple arg", - ), - pytest.param( - ''' -def function_1(_arg_1, arg_2): - """Docstring 1. - - Args: - arg_2: - """ -''', - (), - id="function multiple arg first unused docstring single arg", - ), - pytest.param( - ''' -def function_1(arg_1, _arg_2): - """Docstring 1. - - Args: - arg_1: - """ -''', - (), - id="function multiple arg first unused docstring single arg", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(self): - """Docstring 1. - - Args: - """ -''', - (f"5:8 {ARGS_SECTION_IN_DOCSTR_MSG}",), - id="method has no args docstring args section", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(self, arg_1): - """Docstring 1. - - Args: - """ -''', - (f"4:25 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}",), - id="method has single arg docstring no arg", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - @staticmethod - def function_1(arg_1): - """Docstring 1. - - Args: - """ -''', - (f"5:19 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}",), - id="method has single arg docstring no arg staticmethod", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - @classmethod - def function_1(cls, arg_1): - """Docstring 1. - - Args: - """ -''', - (f"5:24 {ARG_NOT_IN_DOCSTR_MSG % 'arg_1'}",), - id="method has single arg docstring no arg classmethod", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(self, arg_1): - """Docstring 1. - - Args: - arg_1: - arg_1: - """ -''', - (f"5:8 {DUPLICATE_ARG_MSG % 'arg_1'}",), - id="method single arg docstring single arg duplicate", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - def function_1(self, arg_1): - """Docstring 1. - - Args: - arg_1: - """ -''', - (), - id="method single arg docstring single arg", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - @staticmethod - def function_1(arg_1): - """Docstring 1. - - Args: - arg_1: - """ -''', - (), - id="method single arg docstring single arg staticmethod", - ), - pytest.param( - ''' -class Class1: - """Docstring.""" - @classmethod - def function_1(cls, arg_1): - """Docstring 1. - - Args: - arg_1: - """ -''', - (), - id="method single arg docstring single arg classmethod", - ), - ], -) -def test_plugin(code: str, expected_result: tuple[str, ...]): - """ - given: code - when: linting is run on the code - then: the expected result is returned - """ - assert tuple(apply_rules(code)) == expected_result -1 diff --git a/tests/python_tests/unit/test___init__attrs.py b/tests/python_tests/unit/test___init__attrs.py deleted file mode 100644 index f22430c..0000000 --- a/tests/python_tests/unit/test___init__attrs.py +++ /dev/null @@ -1,1206 +0,0 @@ -# """Unit tests for attrs checks in the plugin.""" - -# # The lines represent the number of test cases -# # pylint: disable=too-many-lines - -# from __future__ import annotations - -# import pytest - -# from ruff_docstrings_complete import docstr_missing_msg -# from ruff_docstrings_complete.attrs import ( -# ATTR_IN_DOCSTR_MSG, -# ATTR_NOT_IN_DOCSTR_MSG, -# ATTRS_SECTION_IN_DOCSTR_MSG, -# ATTRS_SECTION_NOT_IN_DOCSTR_MSG, -# DUPLICATE_ATTR_MSG, -# MULT_ATTRS_SECTIONS_IN_DOCSTR_MSG, -# ) - -# from . import result - - -# @pytest.mark.parametrize( -# "code, expected_result", -# [ -# pytest.param( -# """ -# class Class1: -# pass -# """, -# (f"2:0 {docstr_missing_msg}",), -# id="class no docstring", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# attr_1 = "value 1" -# ''', -# (f"3:4 {ATTRS_SECTION_NOT_IN_DOCSTR_MSG}",), -# id="class has single class attr docstring no attrs section", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# attr_1 = "value 1" - -# class Class2: -# """Docstring 2.""" -# attr_2 = "value 2" -# ''', -# ( -# f"3:4 {ATTRS_SECTION_NOT_IN_DOCSTR_MSG}", -# f"7:4 {ATTRS_SECTION_NOT_IN_DOCSTR_MSG}", -# ), -# id="multiple class has single class attr docstring no attrs section", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# ''', -# (f"3:4 {ATTRS_SECTION_IN_DOCSTR_MSG}",), -# id="class has no attrs docstring attrs section", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: - -# Attrs: -# attr_1: -# """ -# attr_1 = "value 1" -# ''', -# (f"3:4 {MULT_ATTRS_SECTIONS_IN_DOCSTR_MSG % 'Attrs,Attrs'}",), -# id="class has single attrs docstring multiple attrs sections same name", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: - -# Attributes: -# attr_1: -# """ -# attr_1 = "value 1" -# ''', -# (f"3:4 {MULT_ATTRS_SECTIONS_IN_DOCSTR_MSG % 'Attrs,Attributes'}",), -# id="class has single attrs docstring multiple attrs sections alternate name", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# attr_1 = "value 1" -# ''', -# (f"7:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class has single attr docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# attr_1 = attr_2 = "value 1" -# ''', -# ( -# f"7:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}", -# f"7:13 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_2'}", -# ), -# id="class has multiple assign attr docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# attr_1.nested_attr_1 = "value 1" -# ''', -# (f"7:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class has single nested attr docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# attr_1.nested_attr_1.nested_attr_2 = "value 1" -# ''', -# (f"7:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class has single double nested attr docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# _attr_1 = "value 1" -# ''', -# (f"3:4 {ATTRS_SECTION_IN_DOCSTR_MSG}",), -# id="class has single unused attr docstring attrs", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# @property -# def attr_1(): -# """Docstring 2.""" -# return "value 1" -# ''', -# (f"8:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class has single property docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# @cached_property -# def attr_1(): -# """Docstring 2.""" -# return "value 1" -# ''', -# (f"8:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class has single cached_property docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# @functools.cached_property -# def attr_1(): -# """Docstring 2.""" -# return "value 1" -# ''', -# (f"8:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class has single functools.cached_property docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# @property -# def attr_1(self): -# """Docstring 2.""" -# self.attr_2 = "value 2" -# return "value 1" -# ''', -# (f"8:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class has single property with assignment docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# @property -# async def attr_1(): -# """Docstring 2.""" -# return "value 1" -# ''', -# (f"8:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class has single async property docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# @property() -# def attr_1(): -# """Docstring 2.""" -# return "value 1" -# ''', -# (f"8:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class has single property call docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# def __init__(self): -# """Docstring 2.""" -# attr_1 = "value 1" -# ''', -# (f"9:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class has single attr after init docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# @property -# def attr_1(): -# """Docstring 2.""" -# return "value 1" -# @property -# def attr_2(): -# """Docstring 3.""" -# return "value 3" -# ''', -# ( -# f"8:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}", -# f"12:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_2'}", -# ), -# id="class has multiple property docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# attr_1 = "value 1" -# attr_2 = "value 2" -# ''', -# ( -# f"7:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}", -# f"8:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_2'}", -# ), -# id="class multiple attrs docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# _attr_1 = "value 1" -# attr_2 = "value 2" -# ''', -# (f"8:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_2'}",), -# id="class multiple attrs first private docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# attr_1 = "value 1" -# _attr_2 = "value 2" -# ''', -# (f"7:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class multiple attrs second private docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# attr_1 = "value 1" -# attr_2 = "value 2" -# ''', -# (f"9:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_2'}",), -# id="class multiple attrs docstring single attr first", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_2: -# """ -# attr_1 = "value 1" -# attr_2 = "value 2" -# ''', -# (f"8:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class multiple attrs docstring single attr second", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_2: -# """ -# attr_1 = "value 1" -# ''', -# ( -# f"8:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}", -# f"3:4 {ATTR_IN_DOCSTR_MSG % 'attr_2'}", -# ), -# id="class has single attr docstring attr different", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# attr_1: str = "value 1" -# ''', -# (f"7:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class has single typed attr docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# """ -# attr_1 += "value 1" -# ''', -# (f"7:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}",), -# id="class has single augmented attr docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_2: -# attr_3: -# """ -# attr_1 = "value 1" -# ''', -# ( -# f"9:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}", -# f"3:4 {ATTR_IN_DOCSTR_MSG % 'attr_2'}", -# f"3:4 {ATTR_IN_DOCSTR_MSG % 'attr_3'}", -# ), -# id="class single attr docstring multiple attrs different", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_3: -# attr_4: -# """ -# attr_1 = "value 1" -# attr_2 = "value 2" -# ''', -# ( -# f"9:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}", -# f"10:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_2'}", -# f"3:4 {ATTR_IN_DOCSTR_MSG % 'attr_3'}", -# f"3:4 {ATTR_IN_DOCSTR_MSG % 'attr_4'}", -# ), -# id="class multiple attr docstring multiple attrs different", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_2: -# attr_3: -# """ -# attr_1 = "value 1" -# attr_2 = "value 2" -# ''', -# (f"9:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_1'}", f"3:4 {ATTR_IN_DOCSTR_MSG % 'attr_3'}"), -# id="class multiple attr docstring multiple attrs first different", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# attr_3: -# """ -# attr_1 = "value 1" -# attr_2 = "value 2" -# ''', -# (f"10:4 {ATTR_NOT_IN_DOCSTR_MSG % 'attr_2'}", f"3:4 {ATTR_IN_DOCSTR_MSG % 'attr_3'}"), -# id="class multiple attr docstring multiple attrs second different", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# attr_1: -# """ -# attr_1 = "value 1" -# ''', -# (f"3:4 {DUPLICATE_ATTR_MSG % 'attr_1'}",), -# id="class single attr docstring single attr duplicate", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# _attr_1: -# _attr_1: -# """ -# _attr_1 = "value 1" -# ''', -# (f"3:4 {DUPLICATE_ATTR_MSG % '_attr_1'}",), -# id="class single private attr docstring single attr duplicate", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# attr_1: -# attr_1: -# """ -# attr_1 = "value 1" -# ''', -# (f"3:4 {DUPLICATE_ATTR_MSG % 'attr_1'}",), -# id="class single attr docstring single attr duplicate many", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# attr_1: -# attr_2: -# """ -# attr_1 = "value 1" -# attr_2 = "value 2" -# ''', -# (f"3:4 {DUPLICATE_ATTR_MSG % 'attr_1'}",), -# id="class multiple attr docstring duplicate attr first", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# attr_2: -# attr_2: -# """ -# attr_1 = "value 1" -# attr_2 = "value 2" -# ''', -# (f"3:4 {DUPLICATE_ATTR_MSG % 'attr_2'}",), -# id="class multiple attr docstring duplicate attr second", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# attr_1: -# attr_2: -# attr_2: -# """ -# attr_1 = "value 1" -# attr_2 = "value 2" -# ''', -# ( -# f"3:4 {DUPLICATE_ATTR_MSG % 'attr_1'}", -# f"3:4 {DUPLICATE_ATTR_MSG % 'attr_2'}", -# ), -# id="class multiple attr docstring duplicate attr all", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# attr_1: -# """ -# def __init__(self): -# """Docstring 2.""" -# self.attr_1 = "value 1" -# ''', -# (f"3:4 {DUPLICATE_ATTR_MSG % 'attr_1'}",), -# id="class single attr init docstring single attr duplicate", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# attr_1 = "value 1" -# ''', -# (), -# id="class single attr docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# @property -# def attr_1(): -# """Docstring 2.""" -# return "value 1" -# ''', -# (), -# id="class single property docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# @cached_property -# def attr_1(): -# """Docstring 2.""" -# return "value 1" -# ''', -# (), -# id="class single cached_property docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# @functools.cached_property -# def attr_1(): -# """Docstring 2.""" -# return "value 1" -# ''', -# (), -# id="class single functools.cached_property docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# attr_1: str = "value 1" -# ''', -# (), -# id="class single attr typed docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# attr_1 += "value 1" -# ''', -# (), -# id="class single attr augmented docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# def __init__(self): -# """Docstring 2.""" -# self.attr_1 = "value 1" -# ''', -# (), -# id="class single attr init docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# def method_1(self): -# """Docstring 2.""" -# self.attr_1 = "value 1" -# ''', -# (), -# id="class single attr method docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# attr_2: -# """ -# def method_1(self): -# """Docstring 2.""" -# self.attr_1 = "value 1" -# def method_2(self): -# """Docstring 3.""" -# self.attr_2 = "value 2" -# ''', -# (), -# id="class multiple attr method docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# @classmethod -# def method_1(cls): -# """Docstring 2.""" -# cls.attr_1 = "value 1" -# ''', -# (), -# id="class single attr classmethod docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# _attr_1: -# """ -# _attr_1 = "value 1" -# ''', -# (), -# id="class single private attr docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# _attr_1 = "value 1" -# ''', -# (), -# id="class single private attr docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def __init__(self): -# """Docstring 2.""" -# var_1 = "value 1" -# ''', -# (), -# id="class single var init docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# @property -# def attr_1(self): -# """Docstring 2.""" -# self.attr_2 = "value 2" -# return "value 1" -# ''', -# (), -# id="class has single property with assignment docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# attr_2: -# """ -# @property -# def attr_1(self): -# """Docstring 2.""" -# self.attr_2 = "value 2" -# return "value 1" -# ''', -# (), -# id="class has single property with assignment docstring both attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def __init__(self): -# """Docstring 2.""" -# self.attr_1 = "value 1" -# ''', -# (), -# id="class has single attr in init docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def method_1(self): -# """Docstring 2.""" -# self.attr_1 = "value 1" -# ''', -# (), -# id="class has single attr in method docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def method_1(self): -# """Docstring 2.""" -# self.attr_1: str = "value 1" -# ''', -# (), -# id="class has single attr typed in method docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# def method_1(self): -# """Docstring 2.""" -# self.attr_1: str = "value 1" -# ''', -# (), -# id="class has single attr typed in method docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def method_1(self): -# """Docstring 2.""" -# self.attr_1 += "value 1" -# ''', -# (), -# id="class has single attr augmented in method docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# def method_1(self): -# """Docstring 2.""" -# self.attr_1 += "value 1" -# ''', -# (), -# id="class has single attr augmented in method docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def method_1(self): -# """Docstring 2.""" -# self.attr_1 = self.attr_2 = "value 1" -# ''', -# (), -# id="class has multiple attr in method docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# attr_2: -# """ -# def method_1(self): -# """Docstring 2.""" -# self.attr_1 = self.attr_2 = "value 1" -# ''', -# (), -# id="class has multiple attr in method docstring multiple attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def method_1(self): -# """Docstring 2.""" -# self.attr_1.nested_attr_1 = "value 1" -# ''', -# (), -# id="class has single attr nested in method docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# def method_1(self): -# """Docstring 2.""" -# self.attr_1.nested_attr_1 = "value 1" -# ''', -# (), -# id="class has single attr nested in method docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def method_1(self): -# """Docstring 2.""" -# self.attr_1.nested_attr_1.nested_attr_2 = "value 1" -# ''', -# (), -# id="class has single attr deep nested in method docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# def method_1(self): -# """Docstring 2.""" -# self.attr_1.nested_attr_1.nested_attr_2 = "value 1" -# ''', -# (), -# id="class has single attr deep nested in method docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def method_1(self): -# """Docstring 2.""" -# self.attr_1 = "value 1" -# def method_2(self): -# """Docstring 3.""" -# self.attr_2 = "value 2" -# ''', -# (), -# id="class has multiple attr in multiple method docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# def method_1(self): -# """Docstring 2.""" -# self.attr_1 = "value 1" -# def method_2(self): -# """Docstring 3.""" -# self.attr_2 = "value 2" -# ''', -# (), -# id="class has multiple attr in multiple method docstring single attr first", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_2: -# """ -# def method_1(self): -# """Docstring 2.""" -# self.attr_1 = "value 1" -# def method_2(self): -# """Docstring 3.""" -# self.attr_2 = "value 2" -# ''', -# (), -# id="class has multiple attr in multiple method docstring single attr second", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# attr_2: -# """ -# def method_1(self): -# """Docstring 2.""" -# self.attr_1 = "value 1" -# def method_2(self): -# """Docstring 3.""" -# self.attr_2 = "value 2" -# ''', -# (), -# id="class has multiple attr in multiple method docstring multiple attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# async def method_1(self): -# """Docstring 2.""" -# self.attr_1 = "value 1" -# ''', -# (), -# id="class has single attr in async method docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# async def method_1(self): -# """Docstring 2.""" -# self.attr_1 = "value 1" -# ''', -# (), -# id="class has single attr in async method docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# @classmethod -# def method_1(cls): -# """Docstring 2.""" -# cls.attr_1 = "value 1" -# ''', -# (), -# id="class has single attr in classmethod method docstring no attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# @classmethod -# def method_1(cls): -# """Docstring 2.""" -# cls.attr_1 = "value 1" -# ''', -# (), -# id="class has single attr in classmethod method docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def method_1(self): -# """Docstring 2.""" -# var_1 = "value 1" -# ''', -# (), -# id="class single var method docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# @classmethod -# def method_1(cls): -# """Docstring 2.""" -# var_1 = "value 1" -# ''', -# (), -# id="class single var classmethod docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# attr_2: -# """ -# attr_1 = "value 1" -# attr_2 = "value 2" -# ''', -# (), -# id="class multiple attr docstring multiple attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_2: -# """ -# _attr_1 = "value 1" -# attr_2 = "value 2" -# ''', -# (), -# id="class multiple attr first private docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1. - -# Attrs: -# attr_1: -# """ -# attr_1 = "value 1" -# _attr_2 = "value 2" -# ''', -# (), -# id="class multiple attr second private docstring single attr", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# class Class2: -# """Docstring 2. - -# Attrs: -# attr_1: -# """ -# attr_1 = "value 1" -# ''', -# (), -# id="nested class single attr docstring no attrs", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def method_1(self): -# """Docstring 2.""" -# def nested_funciont_1(self): -# """Docstring 3.""" -# self.attr_1 = "value 1" -# ''', -# (), -# id="class single attr method nested method docstring no attrs", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def method_1(self): -# """Docstring 2.""" -# async def nested_funciont_1(self): -# """Docstring 3.""" -# self.attr_1 = "value 1" -# ''', -# (), -# id="class single attr method nested async method docstring no attrs", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring 1.""" -# def method_1(self): -# """Docstring 2.""" -# def nested_funciont_1(cls): -# """Docstring 3.""" -# cls.attr_1 = "value 1" -# ''', -# (), -# id="class single attr method nested classmethod docstring no attrs", -# ), -# ], -# ) -# def test_plugin(code: str, expected_result: tuple[str, ...]): -# """ -# given: code -# when: linting is run on the code -# then: the expected result is returned -# """ -# assert result.get(code) == expected_result diff --git a/tests/python_tests/unit/test___init__raises.py b/tests/python_tests/unit/test___init__raises.py deleted file mode 100644 index 0bcc1ae..0000000 --- a/tests/python_tests/unit/test___init__raises.py +++ /dev/null @@ -1,758 +0,0 @@ -"""Unit tests for raises checks in the plugin.""" - -# from __future__ import annotations - -# import pytest - -# from ruff_docstrings_complete.raises import ( -# DUPLICATE_EXC_MSG, -# EXC_IN_DOCSTR_MSG, -# EXC_NOT_IN_DOCSTR_MSG, -# MULT_RAISES_SECTIONS_IN_DOCSTR_MSG, -# RAISES_SECTION_IN_DOCSTR_MSG, -# RAISES_SECTION_NOT_IN_DOCSTR_MSG, -# RE_RAISE_NO_EXC_IN_DOCSTR_MSG, -# ) - -# from . import result - - -# @pytest.mark.parametrize( -# "code, expected_result", -# [ -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1.""" -# raise Exc1 -# ''', -# (f"3:4 {RAISES_SECTION_NOT_IN_DOCSTR_MSG}",), -# id="function raises single exc docstring no raises section", -# ), -# pytest.param( -# ''' -# def _function_1(): -# """Docstring 1.""" -# raise Exc1 -# ''', -# (), -# id="private function raises single exc docstring no raises section", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1.""" -# raise Exc1 - -# def function_2(): -# """Docstring 2.""" -# raise Exc2 -# ''', -# ( -# f"3:4 {RAISES_SECTION_NOT_IN_DOCSTR_MSG}", -# f"7:4 {RAISES_SECTION_NOT_IN_DOCSTR_MSG}", -# ), -# id="multiple function raises single exc docstring no raises section", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# """ -# ''', -# (f"3:4 {RAISES_SECTION_IN_DOCSTR_MSG}",), -# id="function raises no exc docstring raises section", -# ), -# pytest.param( -# ''' -# def _function_1(): -# """Docstring 1. - -# Raises: -# """ -# ''', -# (f"3:4 {RAISES_SECTION_IN_DOCSTR_MSG}",), -# id="private function raises no exc docstring raises section", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: - -# Raises: -# Exc1: -# """ -# raise Exc1 -# ''', -# (f"3:4 {MULT_RAISES_SECTIONS_IN_DOCSTR_MSG % 'Raises,Raises'}",), -# id="function raises single excs docstring multiple raises sections same name", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: - -# Raise: -# Exc1: -# """ -# raise Exc1 -# ''', -# (f"3:4 {MULT_RAISES_SECTIONS_IN_DOCSTR_MSG % 'Raises,Raise'}",), -# id="function raises single excs docstring multiple raises sections different name", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# """ -# raise Exc1 -# ''', -# (f"7:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}",), -# id="function raises single exc docstring no exc", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# """ -# raise Exc1 -# raise -# ''', -# (f"7:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}",), -# id="function raises single exc and single no exc docstring no exc", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# """ -# raise Exc1() -# ''', -# (f"7:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}",), -# id="function raises single exc call docstring no exc", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# """ -# raise module.Exc1 -# ''', -# (f"7:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}",), -# id="function raises single nested exc docstring no exc", -# ), -# pytest.param( -# ''' -# async def function_1(): -# """Docstring 1. - -# Raises: -# """ -# raise Exc1 -# ''', -# (f"7:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}",), -# id="async function raises single exc docstring no exc", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# """ -# raise Exc1 -# raise Exc2 -# ''', -# (f"7:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}", f"8:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc2'}"), -# id="function multiple excs docstring no exc", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# """ -# def function_2(): -# """Docstring 2. - -# Raises: -# Exc1: -# """ -# raise Exc1 -# raise Exc2 -# ''', -# (f"14:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc2'}",), -# id="function multiple excs first nested function", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# """ -# async def function_2(): -# """Docstring 2. - -# Raises: -# Exc1: -# """ -# raise Exc1 -# raise Exc2 -# ''', -# (f"14:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc2'}",), -# id="function multiple excs first nested async function", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# """ -# class Class1: -# """Docstring 2.""" -# raise Exc1 -# raise Exc2 -# ''', -# (f"10:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc2'}",), -# id="function multiple excs first nested class", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# """ -# raise Exc1 -# def function_2(): -# """Docstring 2. - -# Raises: -# Exc2: -# """ -# raise Exc2 -# ''', -# (f"7:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}",), -# id="function multiple excs second nested function", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# """ -# raise Exc1 -# raise Exc2 -# ''', -# (f"9:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc2'}",), -# id="function multiple excs docstring single exc first", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc2: -# """ -# raise Exc1 -# raise Exc2 -# ''', -# (f"8:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}",), -# id="function multiple excs docstring single exc second", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc2: -# """ -# raise Exc1 -# ''', -# ( -# f"8:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}", -# f"3:4 {EXC_IN_DOCSTR_MSG % 'Exc2'}", -# ), -# id="function raises single exc docstring exc different", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc2: -# Exc3: -# """ -# raise Exc1 -# ''', -# ( -# f"9:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}", -# f"3:4 {EXC_IN_DOCSTR_MSG % 'Exc2'}", -# f"3:4 {EXC_IN_DOCSTR_MSG % 'Exc3'}", -# ), -# id="function single exc docstring multiple exc different", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc3: -# Exc4: -# """ -# raise Exc1 -# raise Exc2 -# ''', -# ( -# f"9:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}", -# f"10:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc2'}", -# f"3:4 {EXC_IN_DOCSTR_MSG % 'Exc3'}", -# f"3:4 {EXC_IN_DOCSTR_MSG % 'Exc4'}", -# ), -# id="function multiple exc docstring multiple exc different", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc3: -# Exc2: -# """ -# raise Exc1 -# raise Exc2 -# ''', -# (f"9:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}", f"3:4 {EXC_IN_DOCSTR_MSG % 'Exc3'}"), -# id="function multiple exc docstring multiple exc first different", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# Exc3: -# """ -# raise Exc1 -# raise Exc2 -# ''', -# (f"10:10 {EXC_NOT_IN_DOCSTR_MSG % 'Exc2'}", f"3:4 {EXC_IN_DOCSTR_MSG % 'Exc3'}"), -# id="function multiple exc docstring multiple exc last different", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1.""" -# raise -# ''', -# ( -# f"3:4 {RAISES_SECTION_NOT_IN_DOCSTR_MSG}", -# f"3:4 {RE_RAISE_NO_EXC_IN_DOCSTR_MSG}", -# ), -# id="function single raise no exc docstring no raises exc", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring.""" -# def function_1(self): -# """Docstring 1.""" -# raise -# ''', -# ( -# f"5:8 {RAISES_SECTION_NOT_IN_DOCSTR_MSG}", -# f"5:8 {RE_RAISE_NO_EXC_IN_DOCSTR_MSG}", -# ), -# id="method raise no exc docstring no raises", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# """ -# raise -# ''', -# (f"3:4 {RE_RAISE_NO_EXC_IN_DOCSTR_MSG}",), -# id="function raise no exc docstring raises empty", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# Exc1: -# """ -# raise Exc1 -# ''', -# (f"3:4 {DUPLICATE_EXC_MSG}" % "Exc1",), -# id="function single raise docstring raises duplicate", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# Exc1: -# Exc1: -# """ -# raise Exc1 -# ''', -# (f"3:4 {DUPLICATE_EXC_MSG}" % "Exc1",), -# id="function single raise docstring raises duplicate many", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# Exc1: -# Exc2: -# """ -# raise Exc1 -# raise Exc2 -# ''', -# (f"3:4 {DUPLICATE_EXC_MSG}" % "Exc1",), -# id="function multiple raise docstring raises duplicate first", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# Exc2: -# Exc2: -# """ -# raise Exc1 -# raise Exc2 -# ''', -# (f"3:4 {DUPLICATE_EXC_MSG}" % "Exc2",), -# id="function multiple raise docstring raises duplicate second", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# Exc1: -# Exc2: -# Exc2: -# """ -# raise Exc1 -# raise Exc2 -# ''', -# ( -# f"3:4 {DUPLICATE_EXC_MSG}" % "Exc1", -# f"3:4 {DUPLICATE_EXC_MSG}" % "Exc2", -# ), -# id="function multiple raise docstring raises duplicate all", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# """ -# raise -# ''', -# (), -# id="function single raise no exc docstring raises exc", -# ), -# pytest.param( -# ''' -# def _function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# """ -# raise -# ''', -# (), -# id="private function single raise no exc docstring raises exc", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# """ -# raise Exc1 -# ''', -# (), -# id="function single raise exc docstring raises", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1.""" -# def function_2(): -# """Docstring 2. - -# Raises: -# Exc1: -# """ -# raise Exc1 -# ''', -# (), -# id="function single nested function exc docstring no raises", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1.""" -# async def function_2(): -# """Docstring 2. - -# Raises: -# Exc1: -# """ -# raise Exc1 -# ''', -# (), -# id="function single nested async function exc docstring no raises", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1.""" -# class Class1: -# """Docstring 2.""" -# raise Exc1 -# ''', -# (), -# id="function single nested class exc docstring no raises", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# """ -# raise Exc1() -# ''', -# (), -# id="function single exc call docstring single exc", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# """ -# raise (lambda: True)() -# ''', -# (), -# id="function single exc lambda docstring single exc", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# """ -# raise module.Exc1 -# ''', -# (), -# id="function single exc attribute docstring single exc", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# """ -# raise module.Exc1() -# ''', -# (), -# id="function single exc attribute call docstring single exc", -# ), -# pytest.param( -# ''' -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# Exc2: -# """ -# raise Exc1 -# raise Exc2 -# ''', -# (), -# id="function multiple exc docstring multiple exc", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring.""" -# def function_1(self): -# """Docstring 1.""" -# raise Exc1 -# ''', -# (f"5:8 {RAISES_SECTION_NOT_IN_DOCSTR_MSG}",), -# id="method raises single exc docstring no raises section", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring.""" -# def function_1(self): -# """Docstring 1. - -# Raises: -# """ -# ''', -# (f"5:8 {RAISES_SECTION_IN_DOCSTR_MSG}",), -# id="method raises no exc docstring raises section", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring.""" -# def function_1(self): -# """Docstring 1. - -# Raises: -# """ -# raise Exc1 -# ''', -# (f"9:14 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}",), -# id="method raises single exc docstring no exc", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring.""" -# @staticmethod -# def function_1(): -# """Docstring 1. - -# Raises: -# """ -# raise Exc1 -# ''', -# (f"10:14 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}",), -# id="method raises single exc docstring no exc staticmethod", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring.""" -# @classmethod -# def function_1(cls): -# """Docstring 1. - -# Raises: -# """ -# raise Exc1 -# ''', -# (f"10:14 {EXC_NOT_IN_DOCSTR_MSG % 'Exc1'}",), -# id="method raises single exc docstring no exc classmethod", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring.""" -# def function_1(self): -# """Docstring 1. - -# Raises: -# Exc1: -# """ -# raise Exc1 -# ''', -# (), -# id="method single exc docstring single exc", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring.""" -# @staticmethod -# def function_1(): -# """Docstring 1. - -# Raises: -# Exc1: -# """ -# raise Exc1 -# ''', -# (), -# id="method single exc docstring single exc staticmethod", -# ), -# pytest.param( -# ''' -# class Class1: -# """Docstring.""" -# @classmethod -# def function_1(cls): -# """Docstring 1. - -# Raises: -# Exc1: -# """ -# raise Exc1 -# ''', -# (), -# id="method single exc docstring single exc classmethod", -# ), -# ], -# ) -# def test_plugin(code: str, expected_result: tuple[str, ...]): -# """ -# given: code -# when: linting is run on the code -# then: the expected result is returned -# """ -# assert result.get(code) == expected_result diff --git a/uv.lock b/uv.lock deleted file mode 100644 index 2dadfda..0000000 --- a/uv.lock +++ /dev/null @@ -1,97 +0,0 @@ -version = 1 -revision = 1 -requires-python = ">=3.12" - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, -] - -[[package]] -name = "maturin" -version = "1.8.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/0b/3fd746cf5cfa3c8d7e20ea08c0dbc2c2c765ae051d0fc43d808a38bc9548/maturin-1.8.3.tar.gz", hash = "sha256:304762f86fd53a8031b1bf006d12572a2aa0a5235485031113195cc0152e1e12", size = 199656 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/80/08579d0184ba743345bbd350a3b6419510ff8a4f6e9e671f713160d41fbb/maturin-1.8.3-py3-none-linux_armv6l.whl", hash = "sha256:fa27466b627150123729b2e611f9f9cfade84d24385d72c6877f78c30de30e89", size = 7758366 }, - { url = "https://files.pythonhosted.org/packages/87/1c/00755d28ae277daa828e183c3d118e2923e8b8f0cba4ff708b15d274ac0e/maturin-1.8.3-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:583404d20d7f1d9c8f3c18dcab9014faacabbed6be02da80062c06cd0e279554", size = 15201378 }, - { url = "https://files.pythonhosted.org/packages/58/9f/b8738dc55ba3eb149ad2686d3f9b3c24e44b7baff46cc6baa85e5dc8cf5e/maturin-1.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f9ffdac53dfe0089cf19b597410bc552eb34c856ddb41482b243a695e8b549d3", size = 7934215 }, - { url = "https://files.pythonhosted.org/packages/18/68/300f1a279486d6f63b624a76b81f0fba82545d027127c1ca4d5ded0d1b43/maturin-1.8.3-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:7949a4a17637341f84e88f4cbf0c155998780bbb7a145ed735725b907881c0ae", size = 7791270 }, - { url = "https://files.pythonhosted.org/packages/2e/6d/bf1b8bb9a8b1d9adad242b4089794be318446142975762d04f04ffabae40/maturin-1.8.3-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:11564fac7486313b7baf3aa4e82c20e1b20364aad3fde2ccbc4c07693c0b7e16", size = 8282824 }, - { url = "https://files.pythonhosted.org/packages/18/e9/c601de8699e546774d3f9e761eab374988f88e4ed7006afbb5ff61bb41c0/maturin-1.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:8555d8701cdba6c19c4705a22ce4d2a1814efd792f55dc5873262ff903317540", size = 7595855 }, - { url = "https://files.pythonhosted.org/packages/34/b8/82ae650e6b589289f4219a480f2b220bcf3d9391ae9ea02cc7a58ef59cfc/maturin-1.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:5b2a513468c1c9b4d1728d4b6d3d044b7c183985ef819c9ef15e373b70d99c7d", size = 7634888 }, - { url = "https://files.pythonhosted.org/packages/ac/4a/4f898a66cf4697267c447a6f240b6cbcc2dcacd3cab89735bfbd44810be1/maturin-1.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:2fe8fdff420cfccde127bbc3d8e40835163dcebb6d28c49fffd9aaf1e6ec1090", size = 9810616 }, - { url = "https://files.pythonhosted.org/packages/31/bd/21b0d471e6ade0801f27c33672bca4d563eb1d1e624e534f3bef8a01b1ac/maturin-1.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22cd8b6dc490fee99a62590f914f0c04ddad1dd6dbd5c7e933b3f882b1bd78c2", size = 10923701 }, - { url = "https://files.pythonhosted.org/packages/6c/07/85c32f03ed757c0626e2fc5a9d6454988228be96a756991702a2a757691b/maturin-1.8.3-py3-none-win32.whl", hash = "sha256:2427924546c9d1079b1c0c6c5c1d380e1b8187baba4e6342cca81597a0f3d697", size = 7016502 }, - { url = "https://files.pythonhosted.org/packages/d5/62/f92a130a370dd7aca13c316844b82853647f048cfe1594a81f628ab7101f/maturin-1.8.3-py3-none-win_amd64.whl", hash = "sha256:85f2b882d8235c1c1cb0a38d382ccd5b3ba0674d99cb548d49df9342cc688e36", size = 7953286 }, - { url = "https://files.pythonhosted.org/packages/04/95/8379140838cd95472de843e982d0bf674e8dbf25a899c44e2f76b15704d9/maturin-1.8.3-py3-none-win_arm64.whl", hash = "sha256:33939aabf9a06a8a14ca6c399d32616c7e574fcca8d4ff6dcd984441051f32fb", size = 6687772 }, -] - -[[package]] -name = "packaging" -version = "24.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, -] - -[[package]] -name = "pytest" -version = "8.3.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, -] - -[[package]] -name = "ruff-docstrings-complete" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "maturin" }, - { name = "pytest" }, -] - -[package.dev-dependencies] -dev = [ - { name = "pytest" }, -] - -[package.metadata] -requires-dist = [ - { name = "maturin", specifier = ">=1.8.3" }, - { name = "pytest", specifier = ">=8.3.5" }, -] - -[package.metadata.requires-dev] -dev = [{ name = "pytest", specifier = ">=8.3.5" }]