Skip to content

Commit

Permalink
feat: Add option to warn about unknown parameters in Sphinx docstrings
Browse files Browse the repository at this point in the history
Issue #64: #64
PR #210: #210
Co-authored-by: Timothée Mazzucotelli <pawamoy@pm.me>
  • Loading branch information
ashwinvin committed Nov 11, 2023
1 parent 6598582 commit 8b11d77
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 9 deletions.
67 changes: 58 additions & 9 deletions src/griffe/docstrings/sphinx.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,24 @@ class ParsedValues:
return_type: str | None = None


def parse(docstring: Docstring, **options: Any) -> list[DocstringSection]: # noqa: ARG001
def parse(docstring: Docstring, *, warn_unknown_params: bool = True, **options: Any) -> list[DocstringSection]:
"""Parse a Sphinx-style docstring.
Parameters:
docstring: The docstring to parse.
warn_unknown_params: Warn about documented parameters not appearing in the signature.
**options: Additional parsing options.
Returns:
A list of docstring sections.
"""
parsed_values = ParsedValues()

options = {
"warn_unknown_params": warn_unknown_params,
**options,
}

lines = docstring.lines
curr_line_index = 0

Expand All @@ -105,7 +111,7 @@ def parse(docstring: Docstring, **options: Any) -> list[DocstringSection]: # no
for field_type in field_types:
if field_type.matches(line):
# https://github.com/python/mypy/issues/5485
curr_line_index = field_type.reader(docstring, curr_line_index, parsed_values)
curr_line_index = field_type.reader(docstring, curr_line_index, parsed_values, **options)
break
else:
parsed_values.description.append(line)
Expand All @@ -115,7 +121,14 @@ def parse(docstring: Docstring, **options: Any) -> list[DocstringSection]: # no
return _parsed_values_to_sections(parsed_values)


def _read_parameter(docstring: Docstring, offset: int, parsed_values: ParsedValues) -> int:
def _read_parameter(
docstring: Docstring,
offset: int,
parsed_values: ParsedValues,
*,
warn_unknown_params: bool = True,
**options: Any, # noqa: ARG001
) -> int:
parsed_directive = _parse_directive(docstring, offset)
if parsed_directive.invalid:
return parsed_directive.next_index
Expand All @@ -135,6 +148,17 @@ def _read_parameter(docstring: Docstring, offset: int, parsed_values: ParsedValu
_warn(docstring, 0, f"Duplicate parameter entry for '{name}'")
return parsed_directive.next_index

if warn_unknown_params:
with suppress(AttributeError): # for parameters sections in objects without parameters
params = docstring.parent.parameters # type: ignore[union-attr]
if name not in params:
message = f"Parameter '{name}' does not appear in the function signature"
for starred_name in (f"*{name}", f"**{name}"):
if starred_name in params:
message += f". Did you mean '{starred_name}'?"
break
_warn(docstring, 0, message)

annotation = _determine_param_annotation(docstring, name, directive_type, parsed_values)
default = _determine_param_default(docstring, name)

Expand Down Expand Up @@ -187,7 +211,12 @@ def _determine_param_annotation(
return annotation


def _read_parameter_type(docstring: Docstring, offset: int, parsed_values: ParsedValues) -> int:
def _read_parameter_type(
docstring: Docstring,
offset: int,
parsed_values: ParsedValues,
**options: Any, # noqa: ARG001
) -> int:
parsed_directive = _parse_directive(docstring, offset)
if parsed_directive.invalid:
return parsed_directive.next_index
Expand All @@ -209,7 +238,12 @@ def _read_parameter_type(docstring: Docstring, offset: int, parsed_values: Parse
return parsed_directive.next_index


def _read_attribute(docstring: Docstring, offset: int, parsed_values: ParsedValues) -> int:
def _read_attribute(
docstring: Docstring,
offset: int,
parsed_values: ParsedValues,
**options: Any, # noqa: ARG001
) -> int:
parsed_directive = _parse_directive(docstring, offset)
if parsed_directive.invalid:
return parsed_directive.next_index
Expand Down Expand Up @@ -246,7 +280,12 @@ def _read_attribute(docstring: Docstring, offset: int, parsed_values: ParsedValu
return parsed_directive.next_index


def _read_attribute_type(docstring: Docstring, offset: int, parsed_values: ParsedValues) -> int:
def _read_attribute_type(
docstring: Docstring,
offset: int,
parsed_values: ParsedValues,
**options: Any, # noqa: ARG001
) -> int:
parsed_directive = _parse_directive(docstring, offset)
if parsed_directive.invalid:
return parsed_directive.next_index
Expand All @@ -268,7 +307,12 @@ def _read_attribute_type(docstring: Docstring, offset: int, parsed_values: Parse
return parsed_directive.next_index


def _read_exception(docstring: Docstring, offset: int, parsed_values: ParsedValues) -> int:
def _read_exception(
docstring: Docstring,
offset: int,
parsed_values: ParsedValues,
**options: Any, # noqa: ARG001
) -> int:
parsed_directive = _parse_directive(docstring, offset)
if parsed_directive.invalid:
return parsed_directive.next_index
Expand All @@ -282,7 +326,7 @@ def _read_exception(docstring: Docstring, offset: int, parsed_values: ParsedValu
return parsed_directive.next_index


def _read_return(docstring: Docstring, offset: int, parsed_values: ParsedValues) -> int:
def _read_return(docstring: Docstring, offset: int, parsed_values: ParsedValues, **options: Any) -> int: # noqa: ARG001
parsed_directive = _parse_directive(docstring, offset)
if parsed_directive.invalid:
return parsed_directive.next_index
Expand All @@ -307,7 +351,12 @@ def _read_return(docstring: Docstring, offset: int, parsed_values: ParsedValues)
return parsed_directive.next_index


def _read_return_type(docstring: Docstring, offset: int, parsed_values: ParsedValues) -> int:
def _read_return_type(
docstring: Docstring,
offset: int,
parsed_values: ParsedValues,
**options: Any, # noqa: ARG001
) -> int:
parsed_directive = _parse_directive(docstring, offset)
if parsed_directive.invalid:
return parsed_directive.next_index
Expand Down
24 changes: 24 additions & 0 deletions tests/test_docstrings/test_sphinx.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,30 @@ def test_parse__param_type_twice_annotated__error_message(parse_sphinx: ParserTy
assert "Duplicate parameter information for 'foo'" in warnings[0]


def test_warn_about_unknown_parameters(parse_sphinx: ParserType) -> None:
"""Warn about unknown parameters in "Parameters" sections.
Parameters:
parse_sphinx: Fixture parser.
"""
docstring = """
:param str a: {SOME_TEXT}
"""

_, warnings = parse_sphinx(
docstring,
parent=Function(
"func",
parameters=Parameters(
Parameter("b"),
),
),
)
assert len(warnings) == 1
assert "Parameter 'a' does not appear in the function signature" in warnings[0]


def test_parse__param_type_no_type__error_message(parse_sphinx: ParserType) -> None:
"""Parse a simple docstring.
Expand Down

0 comments on commit 8b11d77

Please # to comment.