Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw DOC001 when docstring sections can't be parsed (numpy style) #199

Merged
merged 4 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading