Skip to content

Commit

Permalink
Fix a bug with ast.assign (#195)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsh9 authored Dec 26, 2024
1 parent f141b71 commit 46c5cb7
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 26 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Change Log

## [unpublished]
## [0.5.14] - 2024-12-26

- Changed

Expand All @@ -10,6 +10,10 @@
Python's AST can correctly parse the files
- Added end-to-end test (essentially an integration test)

- Fixed

- A bug in ast.assign

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

Expand Down
71 changes: 47 additions & 24 deletions pydoclint/utils/arg.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import ast
from typing import Any

from docstring_parser.common import DocstringAttr, DocstringParam

Expand Down Expand Up @@ -217,34 +218,56 @@ def fromAstAssign(cls, astAssign: ast.Assign) -> 'ArgList':
for i, target in enumerate(astAssign.targets):
if isinstance(target, ast.Tuple): # such as `a, b = c, d = 1, 2`
for j, item in enumerate(target.elts):
if not isinstance(item, ast.Name):
raise EdgeCaseError(
f'astAssign.targets[{i}].elts[{j}] is of'
f' type {type(item)} instead of ast.Name'
)

infoList.append(Arg(name=item.id, typeHint=''))
elif isinstance(target, ast.Name): # such as `a = 1` or `a = b = 2`
infoList.append(Arg(name=target.id, typeHint=''))
else:
try: # we may not know all potential cases, so we use try/catch
unparsedTarget: str | None = unparseName(target)
assert unparsedTarget is not None # to help mypy understand type
infoList.append(Arg(name=unparsedTarget, typeHint=''))
except Exception as ex:
lineRange: str = (
f'in Line {astAssign.lineno}'
if astAssign.lineno == astAssign.end_lineno
else f'in Lines {astAssign.lineno}-{astAssign.end_lineno}'
)
msg: str = (
f'Edge case encountered {lineRange}.'
f' astAssign.targets[{i}] is of type {type(target)}.'
cls._unparseTargetAndAppendToInfoList(
target=item,
infoList=infoList,
lineNum=astAssign.lineno,
endLineNum=astAssign.end_lineno,
i=i,
j=j,
)
raise EdgeCaseError(msg) from ex
else: # a single element
cls._unparseTargetAndAppendToInfoList(
target=target,
infoList=infoList,
lineNum=astAssign.lineno,
endLineNum=astAssign.end_lineno,
i=i,
j=None,
)

return ArgList(infoList=infoList)

@classmethod
def _unparseTargetAndAppendToInfoList(
cls,
*,
target: Any,
infoList: list[Arg],
lineNum: int | None,
endLineNum: int | None,
i: int,
j: int | None = None,
) -> None:
try: # we may not know all potential cases, so we use try/catch
unparsedTarget: str | None = unparseName(target)
assert unparsedTarget is not None # to help mypy understand type
infoList.append(Arg(name=unparsedTarget, typeHint=''))
except Exception as ex:
lineRange: str = (
f'in Line {lineNum}'
if lineNum == endLineNum
else f'in Lines {lineNum}-{endLineNum}'
)
msg1: str = f'Edge case encountered {lineRange}.'
msg2: str = (
f' astAssign.targets[{i}] is of type {type(target)}.'
if j is None
else f' astAssign.targets[{i}].elts[{j}] is of type {type(target)}.'
)
msg: str = msg1 + msg2
raise EdgeCaseError(msg) from ex

def contains(self, arg: Arg) -> bool:
"""Whether a given `Arg` object exists in the list"""
return arg.name in self.lookup
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = pydoclint
version = 0.5.13
version = 0.5.14
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 Down
15 changes: 15 additions & 0 deletions tests/data/edge_cases/16_assign_to_attr/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,18 @@ def large_drawing(self, obj):
# Non-self attribute should not be type hinted, because this could lead to
# potential ambiguities. See more: https://stackoverflow.com/a/77831273
large_drawing.descr_2: str = 'Drawing'

# The following is from:
# https://github.com/matplotlib/matplotlib/blob/c2d502d219c8c0abe8722279b21f817aeae2058a/lib/matplotlib/backends/backend_agg.py#L510-L521
print_jpg.__doc__, print_tif.__doc__, print_webp.__doc__ = map(
"""
Write the figure to a {} file.
Parameters
----------
filename_or_obj : str or path-like or file-like
The file to write to.
pil_kwargs : dict, optional
Additional keyword arguments that are passed to
`PIL.Image.Image.save` when saving the figure.
""".format, ["JPEG", "TIFF", "WebP"])

0 comments on commit 46c5cb7

Please sign in to comment.