diff --git a/src/safeds_stubgen/docstring_parsing/_docstring_parser.py b/src/safeds_stubgen/docstring_parsing/_docstring_parser.py index f7c711b7..85940bf1 100644 --- a/src/safeds_stubgen/docstring_parsing/_docstring_parser.py +++ b/src/safeds_stubgen/docstring_parsing/_docstring_parser.py @@ -120,8 +120,14 @@ def get_parameter_documentation( if griffe_docstring is None: # pragma: no cover griffe_docstring = Docstring("") + annotation = last_parameter.annotation + if annotation is None: + type_ = None + else: + type_ = self._griffe_annotation_to_api_type(annotation, griffe_docstring) + return ParameterDocstring( - type=self._griffe_annotation_to_api_type(last_parameter.annotation, griffe_docstring), + type=type_, default_value=last_parameter.default or "", description=remove_newline_from_text(last_parameter.description) or "", ) @@ -156,8 +162,15 @@ def get_attribute_documentation( return AttributeDocstring() last_attribute = matching_attributes[-1] + + annotation = last_attribute.annotation + if annotation is None: + type_ = None + else: + type_ = self._griffe_annotation_to_api_type(annotation, griffe_docstring) + return AttributeDocstring( - type=self._griffe_annotation_to_api_type(last_attribute.annotation, griffe_docstring), + type=type_, description=remove_newline_from_text(last_attribute.description), ) @@ -204,14 +217,8 @@ def _get_matching_docstrings( return [] - def _griffe_annotation_to_api_type( - self, - annotation: Expr | str | None, - docstring: Docstring, - ) -> AbstractType | None: - if annotation is None: - return None - elif isinstance(annotation, ExprName): + def _griffe_annotation_to_api_type(self, annotation: Expr | str, docstring: Docstring) -> AbstractType: + if isinstance(annotation, ExprName): if annotation.canonical_path == "typing.Any": return sds_types.NamedType(name="Any", qname="typing.Any") elif annotation.canonical_path == "int": @@ -236,8 +243,6 @@ def _griffe_annotation_to_api_type( types = [] for slice_ in slices.elements: new_type = self._griffe_annotation_to_api_type(slice_, docstring) - if new_type is None: - continue types.append(new_type) else: types = [] @@ -254,10 +259,11 @@ def _griffe_annotation_to_api_type( elif annotation.canonical_path == "typing.Optional": types.append(sds_types.NamedType(name="None", qname="builtins.None")) return sds_types.UnionType(types=types) - else: + else: # pragma: no cover raise TypeError(f"Can't parse unexpected type from docstring {annotation.canonical_path}.") elif isinstance(annotation, ExprTuple): elements = [] + # Todo Remove the "optional" related part of the code once issue #99 is solved. has_optional = False for element in annotation.elements: if not isinstance(element, str) and element.canonical_path == "optional": @@ -267,12 +273,9 @@ def _griffe_annotation_to_api_type( if new_element is None: # pragma: no cover continue elements.append(new_element) - if len(elements) == 1: - if has_optional: - elements.append(sds_types.NamedType(name="None", qname="builtins.None")) - return sds_types.UnionType(elements) - else: - return elements[0] + if has_optional: + elements.append(sds_types.NamedType(name="None", qname="builtins.None")) + return sds_types.UnionType(elements) else: return sds_types.UnionType(elements) elif isinstance(annotation, str): diff --git a/tests/data/docstring_parser_package/numpydoc.py b/tests/data/docstring_parser_package/numpydoc.py index 5044e414..31ba4aa7 100644 --- a/tests/data/docstring_parser_package/numpydoc.py +++ b/tests/data/docstring_parser_package/numpydoc.py @@ -113,6 +113,36 @@ def __init__(self, x, y, z) -> None: """ +class ClassAndConstructorWithAttributes: + """ + ClassAndConstructorWithParameters + + Dolor sit amet. + + Attributes + ---------- + x : str + Lorem ipsum 1. + z : int, default=5 + Lorem ipsum 3. + """ + x: str + z: int + y: str + + def __init__(self) -> None: + """ + Attributes + ---------- + y : str + Lorem ipsum 2. + z : str + Lorem ipsum 4. + """ + self.y: str + self.z: str + + class ClassWithParametersAndAttributes: """ ClassWithParametersAndAttributes. diff --git a/tests/safeds_stubgen/docstring_parsing/test_numpydoc_parser.py b/tests/safeds_stubgen/docstring_parsing/test_numpydoc_parser.py index 433c0948..0205bf1d 100644 --- a/tests/safeds_stubgen/docstring_parsing/test_numpydoc_parser.py +++ b/tests/safeds_stubgen/docstring_parsing/test_numpydoc_parser.py @@ -835,6 +835,30 @@ def test_get_parameter_documentation( ), ), ), + ( + "ClassAndConstructorWithAttributes", + "x", + AttributeDocstring( + type=NamedType(name="str", qname="builtins.str"), + description="Lorem ipsum 1.", + ), + ), + ( + "ClassAndConstructorWithAttributes", + "y", + AttributeDocstring( + type=NamedType(name="str", qname="builtins.str"), + description="Lorem ipsum 2.", + ), + ), + ( + "ClassAndConstructorWithAttributes", + "z", + AttributeDocstring( + type=NamedType(name="int", qname="builtins.int"), + description="Lorem ipsum 3.", + ), + ), ], ids=[ "class attribute with no type and no default", @@ -872,6 +896,9 @@ def test_get_parameter_documentation( "Various types: imported_type : AnotherClass", "Various types: class_type : ClassWithAttributes", "Various types: imported_type : AnotherClass", + "Class with attributes in constructor - x", + "Class with attributes in constructor - y", + "Class with attributes in constructor - z", ], ) def test_get_attribute_documentation( diff --git a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[numpydoc-NUMPYDOC].sdsstub b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[numpydoc-NUMPYDOC].sdsstub index c2042f0d..bae98940 100644 --- a/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[numpydoc-NUMPYDOC].sdsstub +++ b/tests/safeds_stubgen/stubs_generator/__snapshots__/test_generate_stubs/test_stub_docstring_creation[numpydoc-NUMPYDOC].sdsstub @@ -137,6 +137,26 @@ class ClassAndConstructorWithParameters( z ) +/** + * ClassAndConstructorWithParameters + * + * Dolor sit amet. + */ +class ClassAndConstructorWithAttributes() { + /** + * Lorem ipsum 1. + */ + static attr x: String + /** + * Lorem ipsum 3. + */ + static attr z: Int + /** + * Lorem ipsum 2. + */ + static attr y: String +} + /** * ClassWithParametersAndAttributes. *