diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f2025e..a7090f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.5] - 2026-03-22 + +### Added + +- `SyntaxKind::GOOGLE_YIELDS` — dedicated node kind for entries inside a Google + `Yields:` section. Previously these were emitted as `GOOGLE_RETURNS`. +- `SyntaxKind::NUMPY_YIELDS` — dedicated node kind for entries inside a NumPy + `Yields` section. Previously these were emitted as `NUMPY_RETURNS`. +- `GoogleYields` typed wrapper with `return_type()`, `colon()`, and + `description()` accessors (analogous to `GoogleReturns`). +- `NumPyYields` typed wrapper with `name()`, `colon()`, `return_type()`, and + `description()` accessors (analogous to `NumPyReturns`). +- `GoogleSection::yields()` — accessor returning the `GoogleYields` node for + a Yields section, distinct from `returns()`. +- `NumPySection::yields()` — accessor returning an iterator of `NumPyYields` + nodes for a Yields section, distinct from `returns()`. +- Python bindings: `SyntaxKind.GOOGLE_YIELDS`, `SyntaxKind.NUMPY_YIELDS`, + `GoogleYields` class, `NumPyYields` class, `GoogleSection.yields` property, + and `NumPySection.yields` property. + +### Changed + +- Google parser: `Yields:` sections now produce `GOOGLE_YIELDS` child nodes + instead of `GOOGLE_RETURNS`. +- NumPy parser: `Yields` sections now produce `NUMPY_YIELDS` child nodes + instead of `NUMPY_RETURNS`. +- `to_model` (Google & NumPy): `Yields` sections now use the `yields()` + accessor on the typed section wrapper rather than sharing the `returns()` + code path. + ## [0.1.4] - 2026-03-20 ### Added diff --git a/Cargo.lock b/Cargo.lock index 87d0ea9..9b98f8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,4 +4,4 @@ version = 4 [[package]] name = "pydocstring" -version = "0.1.4" +version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index 8933ef2..4e4d30a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pydocstring" -version = "0.1.4" +version = "0.1.5" edition = "2024" authors = ["Ryuma Asai"] description = "A zero-dependency Rust parser for Python docstrings (Google and NumPy styles) with a unified syntax tree and byte-precise source locations" diff --git a/README.md b/README.md index 00cb04d..1cb3277 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Python bindings are also available as [`pydocstring-rs`](https://pypi.org/projec ```toml [dependencies] -pydocstring = "0.1.4" +pydocstring = "0.1.5" ``` ## Usage @@ -226,7 +226,8 @@ Both styles support the following section categories. Typed accessor methods are | Category | Google | NumPy | |-----------------------------------|------------------------------------------|-----------------------------------------| | Parameters | `args()` → `GoogleArg` | `parameters()` → `NumPyParameter` | -| Returns / Yields | `returns()` → `GoogleReturns` | `returns()` → `NumPyReturns` | +| Returns | `returns()` → `GoogleReturns` | `returns()` → `NumPyReturns` | +| Yields | `yields()` → `GoogleYields` | `yields()` → `NumPyYields` | | Raises | `exceptions()` → `GoogleException` | `exceptions()` → `NumPyException` | | Warns | `warnings()` → `GoogleWarning` | `warnings()` → `NumPyWarning` | | See Also | `see_also_items()` → `GoogleSeeAlsoItem` | `see_also_items()` → `NumPySeeAlsoItem` | diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml index be50569..91963ff 100644 --- a/bindings/python/Cargo.toml +++ b/bindings/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pydocstring-python" -version = "0.1.4" +version = "0.1.5" edition = "2024" authors = ["Ryuma Asai"] description = "Python bindings for pydocstring — a fast docstring parser for Google and NumPy styles" @@ -12,5 +12,5 @@ name = "pydocstring" crate-type = ["cdylib"] [dependencies] -pydocstring_core = { package = "pydocstring", version = "0.1.4", path = "../.." } +pydocstring_core = { package = "pydocstring", version = "0.1.5", path = "../.." } pyo3 = { version = "0.24", features = ["extension-module"] } diff --git a/bindings/python/README.md b/bindings/python/README.md index f02ffb9..afcc333 100644 --- a/bindings/python/README.md +++ b/bindings/python/README.md @@ -301,15 +301,17 @@ print(numpy_text) # Contains "Parameters\n----------" |-------------------|------------------------------------------------------------------------------------------------------------------| | `Style` | `GOOGLE`, `NUMPY`, `PLAIN` (enum) | | `GoogleDocstring` | `style`, `summary`, `extended_summary`, `sections`, `node`, `source`, `pretty_print()`, `to_model()` | -| `GoogleSection` | `kind`, `args`, `returns`, `exceptions`, `body_text`, `node` | +| `GoogleSection` | `kind`, `args`, `returns`, `yields`, `exceptions`, `body_text`, `node` | | `GoogleArg` | `name`, `type`, `description`, `optional` | | `GoogleReturns` | `return_type`, `description` | +| `GoogleYields` | `return_type`, `description` | | `GoogleException` | `type`, `description` | | `PlainDocstring` | `style`, `summary`, `extended_summary`, `node`, `source`, `pretty_print()`, `to_model()` | | `NumPyDocstring` | `style`, `summary`, `extended_summary`, `sections`, `node`, `source`, `pretty_print()`, `to_model()` | -| `NumPySection` | `kind`, `parameters`, `returns`, `exceptions`, `body_text`, `node` | +| `NumPySection` | `kind`, `parameters`, `returns`, `yields`, `exceptions`, `body_text`, `node` | | `NumPyParameter` | `names`, `type`, `description`, `optional`, `default_value` | | `NumPyReturns` | `name`, `return_type`, `description` | +| `NumPyYields` | `name`, `return_type`, `description` | | `NumPyException` | `type`, `description` | | `Token` | `kind`, `text`, `range` | | `Node` | `kind`, `range`, `children` | diff --git a/bindings/python/pydocstring.pyi b/bindings/python/pydocstring.pyi index 9c843e7..920b1b5 100644 --- a/bindings/python/pydocstring.pyi +++ b/bindings/python/pydocstring.pyi @@ -39,6 +39,7 @@ class SyntaxKind(enum.IntEnum): GOOGLE_SECTION_HEADER = ... GOOGLE_ARG = ... GOOGLE_RETURNS = ... + GOOGLE_YIELDS = ... GOOGLE_EXCEPTION = ... GOOGLE_WARNING = ... GOOGLE_SEE_ALSO_ITEM = ... @@ -51,6 +52,7 @@ class SyntaxKind(enum.IntEnum): NUMPY_DEPRECATION = ... NUMPY_PARAMETER = ... NUMPY_RETURNS = ... + NUMPY_YIELDS = ... NUMPY_EXCEPTION = ... NUMPY_WARNING = ... NUMPY_SEE_ALSO_ITEM = ... @@ -111,6 +113,12 @@ class GoogleReturns: @property def description(self) -> Token | None: ... +class GoogleYields: + @property + def return_type(self) -> Token | None: ... + @property + def description(self) -> Token | None: ... + class GoogleException: @property def type(self) -> Token: ... @@ -125,6 +133,8 @@ class GoogleSection: @property def returns(self) -> GoogleReturns | None: ... @property + def yields(self) -> GoogleYields | None: ... + @property def exceptions(self) -> list[GoogleException]: ... @property def body_text(self) -> Token | None: ... @@ -176,6 +186,14 @@ class NumPyReturns: @property def description(self) -> Token | None: ... +class NumPyYields: + @property + def name(self) -> Token | None: ... + @property + def return_type(self) -> Token | None: ... + @property + def description(self) -> Token | None: ... + class NumPyException: @property def type(self) -> Token: ... @@ -190,6 +208,8 @@ class NumPySection: @property def returns(self) -> list[NumPyReturns]: ... @property + def yields(self) -> list[NumPyYields]: ... + @property def exceptions(self) -> list[NumPyException]: ... @property def body_text(self) -> Token | None: ... diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml index 6c043cd..ef1615c 100644 --- a/bindings/python/pyproject.toml +++ b/bindings/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "pydocstring-rs" -version = "0.1.4" +version = "0.1.5" description = "Python bindings for pydocstring — a zero-dependency Rust parser for Python docstrings (Google and NumPy styles) with a unified syntax tree and byte-precise source locations" license = {text = "MIT"} authors = [{name = "Ryuma Asai"}] diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 17c99f2..b919b08 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -132,6 +132,7 @@ enum PySyntaxKind { GOOGLE_SECTION_HEADER, GOOGLE_ARG, GOOGLE_RETURNS, + GOOGLE_YIELDS, GOOGLE_EXCEPTION, GOOGLE_WARNING, GOOGLE_SEE_ALSO_ITEM, @@ -144,6 +145,7 @@ enum PySyntaxKind { NUMPY_DEPRECATION, NUMPY_PARAMETER, NUMPY_RETURNS, + NUMPY_YIELDS, NUMPY_EXCEPTION, NUMPY_WARNING, NUMPY_SEE_ALSO_ITEM, @@ -196,6 +198,7 @@ impl PySyntaxKind { SyntaxKind::GOOGLE_SECTION_HEADER => Self::GOOGLE_SECTION_HEADER, SyntaxKind::GOOGLE_ARG => Self::GOOGLE_ARG, SyntaxKind::GOOGLE_RETURNS => Self::GOOGLE_RETURNS, + SyntaxKind::GOOGLE_YIELDS => Self::GOOGLE_YIELDS, SyntaxKind::GOOGLE_EXCEPTION => Self::GOOGLE_EXCEPTION, SyntaxKind::GOOGLE_WARNING => Self::GOOGLE_WARNING, SyntaxKind::GOOGLE_SEE_ALSO_ITEM => Self::GOOGLE_SEE_ALSO_ITEM, @@ -207,6 +210,7 @@ impl PySyntaxKind { SyntaxKind::NUMPY_DEPRECATION => Self::NUMPY_DEPRECATION, SyntaxKind::NUMPY_PARAMETER => Self::NUMPY_PARAMETER, SyntaxKind::NUMPY_RETURNS => Self::NUMPY_RETURNS, + SyntaxKind::NUMPY_YIELDS => Self::NUMPY_YIELDS, SyntaxKind::NUMPY_EXCEPTION => Self::NUMPY_EXCEPTION, SyntaxKind::NUMPY_WARNING => Self::NUMPY_WARNING, SyntaxKind::NUMPY_SEE_ALSO_ITEM => Self::NUMPY_SEE_ALSO_ITEM, @@ -247,6 +251,7 @@ impl PySyntaxKind { Self::GOOGLE_SECTION_HEADER => SyntaxKind::GOOGLE_SECTION_HEADER, Self::GOOGLE_ARG => SyntaxKind::GOOGLE_ARG, Self::GOOGLE_RETURNS => SyntaxKind::GOOGLE_RETURNS, + Self::GOOGLE_YIELDS => SyntaxKind::GOOGLE_YIELDS, Self::GOOGLE_EXCEPTION => SyntaxKind::GOOGLE_EXCEPTION, Self::GOOGLE_WARNING => SyntaxKind::GOOGLE_WARNING, Self::GOOGLE_SEE_ALSO_ITEM => SyntaxKind::GOOGLE_SEE_ALSO_ITEM, @@ -258,6 +263,7 @@ impl PySyntaxKind { Self::NUMPY_DEPRECATION => SyntaxKind::NUMPY_DEPRECATION, Self::NUMPY_PARAMETER => SyntaxKind::NUMPY_PARAMETER, Self::NUMPY_RETURNS => SyntaxKind::NUMPY_RETURNS, + Self::NUMPY_YIELDS => SyntaxKind::NUMPY_YIELDS, Self::NUMPY_EXCEPTION => SyntaxKind::NUMPY_EXCEPTION, Self::NUMPY_WARNING => SyntaxKind::NUMPY_WARNING, Self::NUMPY_SEE_ALSO_ITEM => SyntaxKind::NUMPY_SEE_ALSO_ITEM, @@ -415,6 +421,24 @@ impl PyGoogleReturns { } } +#[pyclass(name = "GoogleYields", frozen)] +struct PyGoogleYields { + return_type: Option>, + description: Option>, +} + +#[pymethods] +impl PyGoogleYields { + #[getter] + fn return_type(&self, py: Python<'_>) -> Option> { + self.return_type.as_ref().map(|t| t.clone_ref(py)) + } + #[getter] + fn description(&self, py: Python<'_>) -> Option> { + self.description.as_ref().map(|t| t.clone_ref(py)) + } +} + #[pyclass(name = "GoogleException", frozen)] struct PyGoogleException { r#type: Py, @@ -438,6 +462,7 @@ struct PyGoogleSection { kind: String, args: Vec>, returns: Option>, + yields: Option>, exceptions: Vec>, body_text: Option>, node: Py, @@ -458,6 +483,10 @@ impl PyGoogleSection { self.returns.as_ref().map(|r| r.clone_ref(py)) } #[getter] + fn yields(&self, py: Python<'_>) -> Option> { + self.yields.as_ref().map(|r| r.clone_ref(py)) + } + #[getter] fn exceptions(&self, py: Python<'_>) -> Vec> { self.exceptions.iter().map(|e| e.clone_ref(py)).collect() } @@ -595,6 +624,29 @@ impl PyNumPyReturns { } } +#[pyclass(name = "NumPyYields", frozen)] +struct PyNumPyYields { + name: Option>, + return_type: Option>, + description: Option>, +} + +#[pymethods] +impl PyNumPyYields { + #[getter] + fn name(&self, py: Python<'_>) -> Option> { + self.name.as_ref().map(|t| t.clone_ref(py)) + } + #[getter] + fn return_type(&self, py: Python<'_>) -> Option> { + self.return_type.as_ref().map(|t| t.clone_ref(py)) + } + #[getter] + fn description(&self, py: Python<'_>) -> Option> { + self.description.as_ref().map(|t| t.clone_ref(py)) + } +} + #[pyclass(name = "NumPyException", frozen)] struct PyNumPyException { r#type: Py, @@ -618,6 +670,7 @@ struct PyNumPySection { kind: String, parameters: Vec>, returns: Vec>, + yields: Vec>, exceptions: Vec>, body_text: Option>, node: Py, @@ -638,6 +691,10 @@ impl PyNumPySection { self.returns.iter().map(|r| r.clone_ref(py)).collect() } #[getter] + fn yields(&self, py: Python<'_>) -> Vec> { + self.yields.iter().map(|r| r.clone_ref(py)).collect() + } + #[getter] fn exceptions(&self, py: Python<'_>) -> Vec> { self.exceptions.iter().map(|e| e.clone_ref(py)).collect() } @@ -804,6 +861,18 @@ fn build_google_docstring(py: Python<'_>, parsed: &Parsed) -> PyResult> = sec .exceptions() .map(|e| { @@ -824,6 +893,7 @@ fn build_google_docstring(py: Python<'_>, parsed: &Parsed) -> PyResult, parsed: &Parsed) -> PyResult>>()?; + let yields: Vec> = sec + .yields() + .map(|r| { + Py::new( + py, + PyNumPyYields { + name: to_py_token_opt(py, r.name(), source)?, + return_type: to_py_token_opt(py, r.return_type(), source)?, + description: to_py_token_opt(py, r.description(), source)?, + }, + ) + }) + .collect::>>()?; let exceptions: Vec> = sec .exceptions() .map(|e| { @@ -910,6 +993,7 @@ fn build_numpy_docstring(py: Python<'_>, parsed: &Parsed) -> PyResult) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/src/parse/google.rs b/src/parse/google.rs index 69e4f8e..e8bc36f 100644 --- a/src/parse/google.rs +++ b/src/parse/google.rs @@ -10,6 +10,6 @@ pub mod to_model; pub use kind::GoogleSectionKind; pub use nodes::{ GoogleArg, GoogleAttribute, GoogleDocstring, GoogleException, GoogleMethod, GoogleReturns, GoogleSection, - GoogleSectionHeader, GoogleSeeAlsoItem, GoogleWarning, + GoogleSectionHeader, GoogleSeeAlsoItem, GoogleWarning, GoogleYields, }; pub use parser::parse_google; diff --git a/src/parse/google/nodes.rs b/src/parse/google/nodes.rs index b53fc5f..8b29845 100644 --- a/src/parse/google/nodes.rs +++ b/src/parse/google/nodes.rs @@ -94,6 +94,11 @@ impl<'a> GoogleSection<'a> { .and_then(GoogleReturns::cast) } + /// Yields entry node in this section, if present. + pub fn yields(&self) -> Option> { + self.0.find_node(SyntaxKind::GOOGLE_YIELDS).and_then(GoogleYields::cast) + } + /// Iterate over exception entry nodes. pub fn exceptions(&self) -> impl Iterator> { self.0 @@ -215,6 +220,29 @@ impl<'a> GoogleReturns<'a> { } } +// ============================================================================= +// GoogleYields +// ============================================================================= + +define_node!(GoogleYields, GOOGLE_YIELDS); + +impl<'a> GoogleYields<'a> { + /// Yield type annotation token, if present. + pub fn return_type(&self) -> Option<&'a SyntaxToken> { + self.0.find_token(SyntaxKind::RETURN_TYPE) + } + + /// Colon separator token, if present. + pub fn colon(&self) -> Option<&'a SyntaxToken> { + self.0.find_token(SyntaxKind::COLON) + } + + /// Description text token, if present. + pub fn description(&self) -> Option<&'a SyntaxToken> { + self.0.find_token(SyntaxKind::DESCRIPTION) + } +} + // ============================================================================= // GoogleException // ============================================================================= diff --git a/src/parse/google/parser.rs b/src/parse/google/parser.rs index 122ec83..2f1a36b 100644 --- a/src/parse/google/parser.rs +++ b/src/parse/google/parser.rs @@ -629,7 +629,7 @@ impl SectionBody { GoogleSectionKind::Attributes => Self::Args(SyntaxKind::GOOGLE_ATTRIBUTE, Vec::new()), GoogleSectionKind::Methods => Self::Args(SyntaxKind::GOOGLE_METHOD, Vec::new()), GoogleSectionKind::Returns => Self::Returns(SyntaxKind::GOOGLE_RETURNS, ReturnsState::new()), - GoogleSectionKind::Yields => Self::Returns(SyntaxKind::GOOGLE_RETURNS, ReturnsState::new()), + GoogleSectionKind::Yields => Self::Returns(SyntaxKind::GOOGLE_YIELDS, ReturnsState::new()), GoogleSectionKind::Raises => Self::Raises(Vec::new()), GoogleSectionKind::Warns => Self::Warns(Vec::new()), GoogleSectionKind::SeeAlso => Self::SeeAlso(Vec::new()), diff --git a/src/parse/google/to_model.rs b/src/parse/google/to_model.rs index a43c49d..bb714a7 100644 --- a/src/parse/google/to_model.rs +++ b/src/parse/google/to_model.rs @@ -45,7 +45,7 @@ fn convert_section(section: &GoogleSection<'_>, source: &str) -> Section { GoogleSectionKind::OtherParameters => { Section::OtherParameters(section.args().map(|a| convert_arg(&a, source)).collect()) } - GoogleSectionKind::Returns | GoogleSectionKind::Yields => { + GoogleSectionKind::Returns => { let entries: Vec = section .returns() .into_iter() @@ -55,11 +55,19 @@ fn convert_section(section: &GoogleSection<'_>, source: &str) -> Section { description: r.description().map(|t| t.text(source).to_owned()), }) .collect(); - match kind { - GoogleSectionKind::Returns => Section::Returns(entries), - GoogleSectionKind::Yields => Section::Yields(entries), - _ => unreachable!(), - } + Section::Returns(entries) + } + GoogleSectionKind::Yields => { + let entries: Vec = section + .yields() + .into_iter() + .map(|r| Return { + name: None, + type_annotation: r.return_type().map(|t| t.text(source).to_owned()), + description: r.description().map(|t| t.text(source).to_owned()), + }) + .collect(); + Section::Yields(entries) } GoogleSectionKind::Raises => { Section::Raises(section.exceptions().map(|e| convert_exception(&e, source)).collect()) diff --git a/src/parse/numpy.rs b/src/parse/numpy.rs index c619895..21a3a74 100644 --- a/src/parse/numpy.rs +++ b/src/parse/numpy.rs @@ -10,6 +10,6 @@ pub mod to_model; pub use kind::NumPySectionKind; pub use nodes::{ NumPyAttribute, NumPyDeprecation, NumPyDocstring, NumPyException, NumPyMethod, NumPyParameter, NumPyReference, - NumPyReturns, NumPySection, NumPySectionHeader, NumPySeeAlsoItem, NumPyWarning, + NumPyReturns, NumPySection, NumPySectionHeader, NumPySeeAlsoItem, NumPyWarning, NumPyYields, }; pub use parser::parse_numpy; diff --git a/src/parse/numpy/nodes.rs b/src/parse/numpy/nodes.rs index 228d474..219e9fe 100644 --- a/src/parse/numpy/nodes.rs +++ b/src/parse/numpy/nodes.rs @@ -100,6 +100,11 @@ impl<'a> NumPySection<'a> { self.0.nodes(SyntaxKind::NUMPY_RETURNS).filter_map(NumPyReturns::cast) } + /// Iterate over yields entry nodes. + pub fn yields(&self) -> impl Iterator> { + self.0.nodes(SyntaxKind::NUMPY_YIELDS).filter_map(NumPyYields::cast) + } + /// Iterate over exception entry nodes. pub fn exceptions(&self) -> impl Iterator> { self.0 @@ -271,6 +276,34 @@ impl<'a> NumPyReturns<'a> { } } +// ============================================================================= +// NumPyYields +// ============================================================================= + +define_node!(NumPyYields, NUMPY_YIELDS); + +impl<'a> NumPyYields<'a> { + /// Yield name token, if present. + pub fn name(&self) -> Option<&'a SyntaxToken> { + self.0.find_token(SyntaxKind::NAME) + } + + /// Colon separator token, if present. + pub fn colon(&self) -> Option<&'a SyntaxToken> { + self.0.find_token(SyntaxKind::COLON) + } + + /// Yield type annotation token, if present. + pub fn return_type(&self) -> Option<&'a SyntaxToken> { + self.0.find_token(SyntaxKind::RETURN_TYPE) + } + + /// Description text token, if present. + pub fn description(&self) -> Option<&'a SyntaxToken> { + self.0.find_token(SyntaxKind::DESCRIPTION) + } +} + // ============================================================================= // NumPyException // ============================================================================= diff --git a/src/parse/numpy/parser.rs b/src/parse/numpy/parser.rs index 89a3db0..6c37b7a 100644 --- a/src/parse/numpy/parser.rs +++ b/src/parse/numpy/parser.rs @@ -355,6 +355,25 @@ fn build_returns_node( SyntaxNode::new(SyntaxKind::NUMPY_RETURNS, range, children) } +fn build_yields_node( + name: Option, + colon: Option, + return_type: Option, + range: TextRange, +) -> SyntaxNode { + let mut children = Vec::new(); + if let Some(n) = name { + children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::NAME, n))); + } + if let Some(c) = colon { + children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::COLON, c))); + } + if let Some(rt) = return_type { + children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::RETURN_TYPE, rt))); + } + SyntaxNode::new(SyntaxKind::NUMPY_YIELDS, range, children) +} + fn build_exception_node( exc_type: TextRange, colon: Option, @@ -577,6 +596,50 @@ fn process_returns_line(cursor: &LineCursor, nodes: &mut Vec, ent ))); } +fn process_yields_line(cursor: &LineCursor, nodes: &mut Vec, entry_indent: &mut Option) { + let indent_cols = cursor.current_indent_columns(); + if let Some(base) = *entry_indent { + if indent_cols > base { + extend_last_node_description(nodes, cursor.current_trimmed_range()); + return; + } + } + if entry_indent.is_none() { + *entry_indent = Some(indent_cols); + } + + let col = cursor.current_indent(); + let trimmed = cursor.current_trimmed(); + + let (name, colon, return_type) = if let Some(colon_pos) = find_entry_colon(trimmed) { + let n = trimmed[..colon_pos].trim_end(); + let after_colon = &trimmed[colon_pos + 1..]; + let t = after_colon.trim(); + let ws_after = after_colon.len() - after_colon.trim_start().len(); + let type_col = col + colon_pos + 1 + ws_after; + ( + Some(cursor.make_line_range(cursor.line, col, n.len())), + Some(cursor.make_line_range(cursor.line, col + colon_pos, 1)), + if t.is_empty() { + None + } else { + Some(cursor.make_line_range(cursor.line, type_col, t.len())) + }, + ) + } else { + // Unnamed: type only (stored as RETURN_TYPE) + (None, None, Some(cursor.current_trimmed_range())) + }; + + let entry_range = cursor.current_trimmed_range(); + nodes.push(SyntaxElement::Node(build_yields_node( + name, + colon, + return_type, + entry_range, + ))); +} + fn process_raises_line(cursor: &LineCursor, nodes: &mut Vec, entry_indent: &mut Option) { let indent_cols = cursor.current_indent_columns(); if let Some(base) = *entry_indent { @@ -825,6 +888,7 @@ fn process_reference_line(cursor: &LineCursor, nodes: &mut Vec, e enum SectionBody { Parameters(Vec), Returns(Vec), + Yields(Vec), Raises(Vec), Warns(Vec), SeeAlso(Vec), @@ -841,7 +905,7 @@ impl SectionBody { NumPySectionKind::OtherParameters => Self::Parameters(Vec::new()), NumPySectionKind::Receives => Self::Parameters(Vec::new()), NumPySectionKind::Returns => Self::Returns(Vec::new()), - NumPySectionKind::Yields => Self::Returns(Vec::new()), + NumPySectionKind::Yields => Self::Yields(Vec::new()), NumPySectionKind::Raises => Self::Raises(Vec::new()), NumPySectionKind::Warns => Self::Warns(Vec::new()), NumPySectionKind::SeeAlso => Self::SeeAlso(Vec::new()), @@ -856,6 +920,7 @@ impl SectionBody { match self { Self::Parameters(nodes) => process_parameter_line(cursor, nodes, entry_indent), Self::Returns(nodes) => process_returns_line(cursor, nodes, entry_indent), + Self::Yields(nodes) => process_yields_line(cursor, nodes, entry_indent), Self::Raises(nodes) => process_raises_line(cursor, nodes, entry_indent), Self::Warns(nodes) => process_warning_line(cursor, nodes, entry_indent), Self::SeeAlso(nodes) => process_see_also_line(cursor, nodes, entry_indent), @@ -876,6 +941,7 @@ impl SectionBody { match self { Self::Parameters(nodes) => nodes, Self::Returns(nodes) => nodes, + Self::Yields(nodes) => nodes, Self::Raises(nodes) => nodes, Self::Warns(nodes) => nodes, Self::SeeAlso(nodes) => nodes, diff --git a/src/parse/numpy/to_model.rs b/src/parse/numpy/to_model.rs index 5ec7e0f..3b4a612 100644 --- a/src/parse/numpy/to_model.rs +++ b/src/parse/numpy/to_model.rs @@ -48,7 +48,7 @@ fn convert_section(section: &NumPySection<'_>, source: &str) -> Section { NumPySectionKind::OtherParameters => { Section::OtherParameters(section.parameters().map(|p| convert_parameter(&p, source)).collect()) } - NumPySectionKind::Returns | NumPySectionKind::Yields => { + NumPySectionKind::Returns => { let entries: Vec = section .returns() .map(|r| Return { @@ -57,11 +57,18 @@ fn convert_section(section: &NumPySection<'_>, source: &str) -> Section { description: r.description().map(|t| t.text(source).to_owned()), }) .collect(); - match kind { - NumPySectionKind::Returns => Section::Returns(entries), - NumPySectionKind::Yields => Section::Yields(entries), - _ => unreachable!(), - } + Section::Returns(entries) + } + NumPySectionKind::Yields => { + let entries: Vec = section + .yields() + .map(|r| Return { + name: r.name().map(|t| t.text(source).to_owned()), + type_annotation: r.return_type().map(|t| t.text(source).to_owned()), + description: r.description().map(|t| t.text(source).to_owned()), + }) + .collect(); + Section::Yields(entries) } NumPySectionKind::Raises => Section::Raises( section diff --git a/src/syntax.rs b/src/syntax.rs index da7c8c1..0db1d06 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -86,6 +86,8 @@ pub enum SyntaxKind { GOOGLE_ARG, /// A single return value entry. GOOGLE_RETURNS, + /// A single yield value entry. + GOOGLE_YIELDS, /// A single exception entry. GOOGLE_EXCEPTION, /// A single warning entry. @@ -116,6 +118,8 @@ pub enum SyntaxKind { NUMPY_PARAMETER, /// A single return value entry. NUMPY_RETURNS, + /// A single yield value entry. + NUMPY_YIELDS, /// A single exception entry. NUMPY_EXCEPTION, /// A single warning entry. @@ -141,6 +145,7 @@ impl SyntaxKind { | Self::GOOGLE_SECTION_HEADER | Self::GOOGLE_ARG | Self::GOOGLE_RETURNS + | Self::GOOGLE_YIELDS | Self::GOOGLE_EXCEPTION | Self::GOOGLE_WARNING | Self::GOOGLE_SEE_ALSO_ITEM @@ -152,6 +157,7 @@ impl SyntaxKind { | Self::NUMPY_DEPRECATION | Self::NUMPY_PARAMETER | Self::NUMPY_RETURNS + | Self::NUMPY_YIELDS | Self::NUMPY_EXCEPTION | Self::NUMPY_WARNING | Self::NUMPY_SEE_ALSO_ITEM @@ -201,6 +207,7 @@ impl SyntaxKind { Self::GOOGLE_SECTION_HEADER => "GOOGLE_SECTION_HEADER", Self::GOOGLE_ARG => "GOOGLE_ARG", Self::GOOGLE_RETURNS => "GOOGLE_RETURNS", + Self::GOOGLE_YIELDS => "GOOGLE_YIELDS", Self::GOOGLE_EXCEPTION => "GOOGLE_EXCEPTION", Self::GOOGLE_WARNING => "GOOGLE_WARNING", Self::GOOGLE_SEE_ALSO_ITEM => "GOOGLE_SEE_ALSO_ITEM", @@ -215,6 +222,7 @@ impl SyntaxKind { Self::NUMPY_DEPRECATION => "NUMPY_DEPRECATION", Self::NUMPY_PARAMETER => "NUMPY_PARAMETER", Self::NUMPY_RETURNS => "NUMPY_RETURNS", + Self::NUMPY_YIELDS => "NUMPY_YIELDS", Self::NUMPY_EXCEPTION => "NUMPY_EXCEPTION", Self::NUMPY_WARNING => "NUMPY_WARNING", Self::NUMPY_SEE_ALSO_ITEM => "NUMPY_SEE_ALSO_ITEM", diff --git a/tests/google/main.rs b/tests/google/main.rs index 9bc82b4..163833c 100644 --- a/tests/google/main.rs +++ b/tests/google/main.rs @@ -2,7 +2,7 @@ pub use pydocstring::parse::google::{ GoogleArg, GoogleAttribute, GoogleDocstring, GoogleException, GoogleMethod, GoogleReturns, GoogleSection, - GoogleSectionKind, GoogleSeeAlsoItem, GoogleWarning, parse_google, + GoogleSectionKind, GoogleSeeAlsoItem, GoogleWarning, GoogleYields, parse_google, }; pub use pydocstring::syntax::{Parsed, SyntaxKind, SyntaxToken}; pub use pydocstring::text::TextSize; @@ -44,11 +44,11 @@ pub fn returns<'a>(result: &'a Parsed) -> Option> { .find_map(|s| s.returns()) } -pub fn yields<'a>(result: &'a Parsed) -> Option> { +pub fn yields<'a>(result: &'a Parsed) -> Option> { doc(result) .sections() .filter(|s| matches!(s.section_kind(result.source()), GoogleSectionKind::Yields)) - .find_map(|s| s.returns()) + .find_map(|s| s.yields()) } pub fn raises<'a>(result: &'a Parsed) -> Vec> { diff --git a/tests/numpy/main.rs b/tests/numpy/main.rs index 510b7d2..3cd615a 100644 --- a/tests/numpy/main.rs +++ b/tests/numpy/main.rs @@ -4,7 +4,7 @@ pub use pydocstring::parse::numpy::{ kind::NumPySectionKind, nodes::{ NumPyAttribute, NumPyDeprecation, NumPyDocstring, NumPyException, NumPyMethod, NumPyParameter, NumPyReference, - NumPyReturns, NumPySection, NumPySeeAlsoItem, NumPyWarning, + NumPyReturns, NumPySection, NumPySeeAlsoItem, NumPyWarning, NumPyYields, }, parse_numpy, }; @@ -50,11 +50,11 @@ pub fn returns<'a>(result: &'a Parsed) -> Vec> { .collect() } -pub fn yields<'a>(result: &'a Parsed) -> Vec> { +pub fn yields<'a>(result: &'a Parsed) -> Vec> { doc(result) .sections() .filter(|s| matches!(s.section_kind(result.source()), NumPySectionKind::Yields)) - .flat_map(|s| s.returns().collect::>()) + .flat_map(|s| s.yields().collect::>()) .collect() } diff --git a/tests/numpy/returns.rs b/tests/numpy/returns.rs index 75702dd..828a295 100644 --- a/tests/numpy/returns.rs +++ b/tests/numpy/returns.rs @@ -132,6 +132,6 @@ fn test_yields_section_body_variant() { let result = parse_numpy(docstring); let s = &all_sections(&result)[0]; assert_eq!(s.section_kind(result.source()), NumPySectionKind::Yields); - let items: Vec<_> = s.returns().collect(); + let items: Vec<_> = s.yields().collect(); assert_eq!(items.len(), 1); }