Skip to content

Commit

Permalink
Throw DOC001 when docstring sections can't be parsed (numpy style) (#199
Browse files Browse the repository at this point in the history
)
  • Loading branch information
jsh9 authored Jan 10, 2025
1 parent 46c5cb7 commit 4021d8d
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 8 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Change Log

## [0.5.15] - 2025-01-10

- Changed

- Changed to using v0.0.10 of docstring_parser_fork, which now throws a
`ParseError` when a non-empty docstring section cannot be parsed (in Numpy
style). This `ParseError` would lead to DOC001.

- Full diff
- https://github.com/jsh9/pydoclint/compare/0.5.14...0.5.15

## [0.5.14] - 2024-12-26

- Changed
Expand Down
10 changes: 7 additions & 3 deletions pydoclint/utils/visitor_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,13 +507,17 @@ def extractYieldTypeFromGeneratorOrIteratorAnnotation(

try:
if hasGeneratorAsReturnAnnotation:
if sys.version_info >= (3, 9):
if isinstance(
ast.parse(returnAnnoText).body[0].value.slice, # type:ignore[attr-defined,arg-type]
ast.Constant,
):
# This means returnAnnoText is something like "Generator[None]"
yieldType = unparseName(
ast.parse(returnAnnoText).body[0].value.slice.elts[0] # type:ignore[attr-defined,arg-type]
ast.parse(returnAnnoText).body[0].value.slice # type:ignore[attr-defined,arg-type]
)
else:
yieldType = unparseName(
ast.parse(returnAnnoText).body[0].value.slice.value.elts[0]
ast.parse(returnAnnoText).body[0].value.slice.elts[0] # type:ignore[attr-defined,arg-type]
)
elif hasIteratorOrIterableAsReturnAnnotation:
yieldType = unparseName(
Expand Down
8 changes: 7 additions & 1 deletion pydoclint/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,18 @@ def visit_FunctionDef(self, node: FuncOrAsyncFuncDef) -> None: # noqa: D102
doc: Doc = Doc(docstring=docstring, style=self.style)
except Exception as excp:
doc = Doc(docstring='', style=self.style)
msgPostfix: str = (
str(excp).replace('\n', ' ')
+ ' (Note: DOC001 could trigger other unrelated'
+ ' violations under this function/method too. Please'
+ ' fix the docstring formatting first.)'
)
self.violations.append(
Violation(
code=1,
line=node.lineno,
msgPrefix=f'Function/method `{node.name}`:',
msgPostfix=str(excp).replace('\n', ' '),
msgPostfix=msgPostfix,
)
)

Expand Down
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = pydoclint
version = 0.5.14
version = 0.5.15
description = A Python docstring linter that checks arguments, returns, yields, and raises sections
long_description = file: README.md
long_description_content_type = text/markdown
Expand All @@ -16,7 +16,7 @@ classifiers =
packages = find:
install_requires =
click>=8.1.0
docstring_parser_fork>=0.0.9
docstring_parser_fork>=0.0.10
tomli>=2.0.1; python_version<'3.11'
python_requires = >=3.9

Expand Down
57 changes: 57 additions & 0 deletions tests/data/edge_cases/22_PEP696_generator/case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# From: https://github.com/jsh9/pydoclint/issues/198

@pytest.fixture
def setup_custom_logger(caplog: pytest.LogCaptureFixture) -> Generator[None]:
"""
Set up a custom logger.
Parameters
----------
caplog : pytest.LogCaptureFixture
Pytest logging capture fixture.
Yields
------
None
Yield to run test before cleanup.
"""
# Set class
logging.setLoggerClass(CustomLogger)
# Set new format
format_copy = caplog.handler.formatter._fmt # type: ignore[union-attr]
caplog.handler.setFormatter(logging.Formatter('%(id_string)s : %(message)s'))

yield

caplog.handler.setFormatter(logging.Formatter(format_copy))
# Reset logger class
logging.setLoggerClass(logging.Logger)



@pytest.fixture
def setup_custom_logger2(caplog: pytest.LogCaptureFixture) -> Generator[None, None, None]:
"""
Set up a custom logger.
Parameters
----------
caplog : pytest.LogCaptureFixture
Pytest logging capture fixture.
Yields
------
None
Yield to run test before cleanup.
"""
# Set class
logging.setLoggerClass(CustomLogger)
# Set new format
format_copy = caplog.handler.formatter._fmt # type: ignore[union-attr]
caplog.handler.setFormatter(logging.Formatter('%(id_string)s : %(message)s'))

yield

caplog.handler.setFormatter(logging.Formatter(format_copy))
# Reset logger class
logging.setLoggerClass(logging.Logger)
10 changes: 10 additions & 0 deletions tests/data/numpy/parsing_errors/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,13 @@ def method1(self, arg3):
arg 3
"""
pass

def method2(self, arg4):
"""
Yields
------
Something to yield. This is not the correct docstring
format and will lead to DOC001, because the yielded
type is needed.
"""
pass
28 changes: 26 additions & 2 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,9 @@ def testParsingErrors_google() -> None:
'DOC001: Class `A`: Potential formatting errors in docstring. Error message: '
"Expected a colon in 'arg1'.",
'DOC001: Function/method `__init__`: Potential formatting errors in '
"docstring. Error message: Expected a colon in 'arg1'.",
"docstring. Error message: Expected a colon in 'arg1'. (Note: DOC001 could "
'trigger other unrelated violations under this function/method too. Please '
'fix the docstring formatting first.)',
]
assert list(map(str, violations)) == expected

Expand All @@ -959,7 +961,24 @@ def testParsingErrors_numpy() -> None:
argTypeHintsInSignature=False,
style='numpy',
)
expected = [] # not sure how to craft docstrings with parsing errors yet
expected = [
'DOC001: Class `A`: Potential formatting errors in docstring. Error message: '
"Section 'Parameters' is not empty but nothing was parsed.",
'DOC001: Function/method `__init__`: Potential formatting errors in '
"docstring. Error message: Section 'Parameters' is not empty but nothing was "
'parsed. (Note: DOC001 could trigger other unrelated violations under this '
'function/method too. Please fix the docstring formatting first.)',
'DOC001: Function/method `method2`: Potential formatting errors in docstring. '
"Error message: Section 'Yields' is not empty but nothing was parsed. (Note: "
'DOC001 could trigger other unrelated violations under this function/method '
'too. Please fix the docstring formatting first.)',
'DOC101: Method `A.method2`: Docstring contains fewer arguments than in '
'function signature.',
'DOC103: Method `A.method2`: Docstring arguments are different from function '
'arguments. (Or could be other formatting issues: '
'https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). '
'Arguments in the function signature but not in the docstring: [arg4: ].',
]
assert list(map(str, violations)) == expected


Expand Down Expand Up @@ -1576,6 +1595,11 @@ def testNonAscii() -> None:
+ ' (<unknown>, line 2)'
],
),
(
'22_PEP696_generator/case.py',
{'style': 'numpy'},
[],
),
],
)
def testEdgeCases(
Expand Down

0 comments on commit 4021d8d

Please sign in to comment.