Skip to content

Commit

Permalink
Properly deal with quotes in return annotations (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsh9 authored Jul 5, 2023
1 parent 1a3596f commit d14c159
Show file tree
Hide file tree
Showing 17 changed files with 485 additions and 13 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ repos:
- id: isort

- repo: https://github.com/jsh9/cercis
rev: 0.1.6
rev: 0.1.7
hooks:
- id: cercis

Expand All @@ -21,6 +21,6 @@ repos:
- id: trailing-whitespace

- repo: https://github.com/pre-commit/pre-commit
rev: v3.2.2
rev: v3.3.3
hooks:
- id: validate_manifest
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Change Log

## [0.0.14] - 2023-07-05

- Fixed
- Fixed an issue where quotes in return annotations are not properly handled
- Full diff
- https://github.com/jsh9/pydoclint/compare/0.0.13...0.0.14

## [0.0.13] - 2023-06-26

- Fixed
Expand Down
9 changes: 3 additions & 6 deletions pydoclint/utils/arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from numpydoc.docscrape import Parameter

from pydoclint.utils.annotation import unparseAnnotation
from pydoclint.utils.generic import stripQuotes


class Arg:
Expand Down Expand Up @@ -50,7 +51,7 @@ def __le__(self, other: 'Arg') -> bool:
return self < other or self == other

def __hash__(self) -> int:
return hash((self.name, self._stripQuotes(self.typeHint)))
return hash((self.name, stripQuotes(self.typeHint)))

def nameEquals(self, other: 'Arg') -> bool:
"""More lenient equality: only compare names"""
Expand Down Expand Up @@ -91,11 +92,7 @@ def _str(cls, typeName: Optional[str]) -> str:

@classmethod
def _eq(cls, str1: str, str2: str) -> bool:
return cls._stripQuotes(str1) == cls._stripQuotes(str2)

@classmethod
def _stripQuotes(cls, string: str) -> str:
return string.replace('"', '').replace("'", '')
return stripQuotes(str1) == stripQuotes(str2)


class ArgList:
Expand Down
5 changes: 5 additions & 0 deletions pydoclint/utils/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,8 @@ def stringStartsWith(string: str, substrings: Tuple[str, ...]) -> bool:
return True

return False


def stripQuotes(string: Optional[str]) -> Optional[str]:
"""Strip quote (both double and single quote) from the given string"""
return None if string is None else string.replace('"', '').replace("'", '')
3 changes: 2 additions & 1 deletion pydoclint/utils/return_anno.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
from typing import List, Optional

from pydoclint.utils.annotation import unparseAnnotation
from pydoclint.utils.generic import stripQuotes
from pydoclint.utils.internal_error import InternalError


class ReturnAnnotation:
"""A class to hold the return annotation in a function's signature"""

def __init__(self, annotation: Optional[str]) -> None:
self.annotation = annotation
self.annotation = stripQuotes(annotation)

def decompose(self) -> List[str]:
"""
Expand Down
10 changes: 7 additions & 3 deletions pydoclint/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
generateMsgPrefix,
getDocstring,
isPropertyMethod,
stripQuotes,
)
from pydoclint.utils.internal_error import InternalError
from pydoclint.utils.method_type import MethodType
Expand Down Expand Up @@ -506,7 +507,9 @@ def checkReturns( # noqa: C901
returnAnnoItems: List[str] = returnAnno.decompose()
returnAnnoInList: List[str] = returnAnno.putAnnotationInList()

returnSecTypes: List[str] = [_.argType for _ in returnSec]
returnSecTypes: List[str] = [
stripQuotes(_.argType) for _ in returnSec
]

if returnAnnoInList != returnSecTypes:
if len(returnAnnoItems) != len(returnSec):
Expand All @@ -531,15 +534,16 @@ def checkReturns( # noqa: C901
# use one compound style for tuples.

if len(returnSec) > 0:
retArgType = stripQuotes(returnSec[0].argType)
if returnAnno.annotation is None:
msg = 'Return annotation has 0 type(s); docstring'
msg += ' return section has 1 type(s).'
violations.append(v203.appendMoreMsg(moreMsg=msg))
elif returnSec[0].argType != returnAnno.annotation:
elif retArgType != returnAnno.annotation:
msg = 'Return annotation types: '
msg += str([returnAnno.annotation]) + '; '
msg += 'docstring return section types: '
msg += str([returnSec[0].argType])
msg += str([retArgType])
violations.append(v203.appendMoreMsg(moreMsg=msg))
else:
if returnAnno.annotation != '':
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.0.13
version = 0.0.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
50 changes: 50 additions & 0 deletions tests/data/google/returns/classmethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,53 @@ def func82(cls) -> Tuple[int, bool]:
bool: Boolean to return
"""
return (1, 1.1)

@classmethod
def func91(cls) -> Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]:
"""
No violation should be reported here.
Returns:
Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]: Something
"""
print(1)

@classmethod
def func92(cls) -> Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]:
"""
No violation should be reported here.
Returns:
Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]: Something
"""
print(1)

@classmethod
def func93(cls) -> Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]:
"""
No violation should be reported here.
Returns:
Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]: Something
"""
print(1)

@classmethod
def func94(cls) -> Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]:
"""
No violation should be reported here.
Returns:
Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]: Something
"""
print(1)

@classmethod
def func95(cls) -> Tuple[Dict[MyClass1, 'MyClass2'], List['MyClass3']]:
"""
No violation should be reported here.
Returns:
Tuple[Dict["MyClass1", MyClass2], List['MyClass3']]: Something
"""
print(1)
50 changes: 50 additions & 0 deletions tests/data/google/returns/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,53 @@ def func82() -> Tuple[int, bool]:
bool: Boolean to return
"""
return (1, 1.1)


def func91() -> Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]:
"""
No violation should be reported here.
Returns:
Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]: Something
"""
print(1)


def func92() -> Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]:
"""
No violation should be reported here.
Returns:
Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]: Something
"""
print(1)


def func93() -> Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]:
"""
No violation should be reported here.
Returns:
Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]: Something
"""
print(1)


def func94() -> Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]:
"""
No violation should be reported here.
Returns:
Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]: Something
"""
print(1)


def func95() -> Tuple[Dict[MyClass1, 'MyClass2'], List['MyClass3']]:
"""
No violation should be reported here.
Returns:
Tuple[Dict["MyClass1", MyClass2], List['MyClass3']]: Something
"""
print(1)
45 changes: 45 additions & 0 deletions tests/data/google/returns/method.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,48 @@ def func82(self) -> Tuple[int, bool]:
bool: Boolean to return
"""
return (1, 1.1)

def func91(self) -> Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]:
"""
No violation should be reported here.
Returns:
Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]: Something
"""
print(1)

def func92(self) -> Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]:
"""
No violation should be reported here.
Returns:
Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]: Something
"""
print(1)

def func93(self) -> Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]:
"""
No violation should be reported here.
Returns:
Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]: Something
"""
print(1)

def func94(self) -> Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]:
"""
No violation should be reported here.
Returns:
Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]: Something
"""
print(1)

def func95(self) -> Tuple[Dict[MyClass1, 'MyClass2'], List['MyClass3']]:
"""
No violation should be reported here.
Returns:
Tuple[Dict["MyClass1", MyClass2], List['MyClass3']]: Something
"""
print(1)
50 changes: 50 additions & 0 deletions tests/data/google/returns/staticmethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,53 @@ def func82() -> Tuple[int, bool]:
bool: Boolean to return
"""
return (1, 1.1)

@staticmethod
def func91() -> Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]:
"""
No violation should be reported here.
Returns:
Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]: Something
"""
print(1)

@staticmethod
def func92() -> Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]:
"""
No violation should be reported here.
Returns:
Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]: Something
"""
print(1)

@staticmethod
def func93() -> Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]:
"""
No violation should be reported here.
Returns:
Tuple[Dict['MyClass1', 'MyClass2'], List['MyClass3']]: Something
"""
print(1)

@staticmethod
def func94() -> Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]:
"""
No violation should be reported here.
Returns:
Tuple[Dict[MyClass1, MyClass2], List[MyClass3]]: Something
"""
print(1)

@staticmethod
def func95() -> Tuple[Dict[MyClass1, 'MyClass2'], List['MyClass3']]:
"""
No violation should be reported here.
Returns:
Tuple[Dict["MyClass1", MyClass2], List['MyClass3']]: Something
"""
print(1)
Loading

0 comments on commit d14c159

Please sign in to comment.