Skip to content

Commit

Permalink
Change how type hints are checked (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsh9 authored Jun 12, 2023
1 parent c26221a commit b6c280e
Show file tree
Hide file tree
Showing 10 changed files with 567 additions and 154 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## Unreleased

- Changed
- Replaced the `--check-type-hint` option with two new options:
`--type-hints-in-docstring` and `--type-hints-in-signature`

## [0.0.8] - 2023-06-06

- Added
Expand Down
36 changes: 20 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,22 +248,26 @@ or
flake8 --style=google <FILE_OR_FOLDER>
```
### 4.4. `--check-type-hint` (shortform: `-th`, default: `True`)
If `True`, the type hints in the docstring and in the Python code need to
exactly match.
To turn this option on/off, do this:
```
pydoclint --check-type-hint=False <FILE_OR_FOLDER>
```
or
```
flake8 --check-type-hint=False <FILE_OR_FOLDER>
```
### 4.4. `--type-hints-in-docstring` and `--type-hints-in-signature`
- `--type-hints-in-docstring`
- Shortform: `-thd`
- Default: `True`
- Meaning:
- If `True`, there need to be type hints in the argument list of a
docstring
- If `False`, there cannot be any type hints in the argument list of a
docstring
- `--type-hints-in-signature`
- Shortform: `-ths`
- Default: `True`
- Meaning:
- If `True`, there need to be type hints in the function/method signature
- If `False`, there cannot be any type hints in the function/method
signature
Note: if users choose `True` for both options, the type hints in the signature
and in the docstring need to match, otherwise there will be a style violation.
### 4.5. `--check-arg-order` (shortform: `-ao`, default: `True`)
Expand Down
29 changes: 23 additions & 6 deletions pydoclint/flake8_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,20 @@ def add_options(cls, parser): # noqa: D102
help='Which style of docstring is your code base using',
)
parser.add_option(
'-th',
'--check-type-hint',
'-ths',
'--type-hints-in-signature',
action='store',
default='True',
parse_from_config=True,
help='Whether to check type hints in the docstring',
help='Whether to require type hints in function signatures',
)
parser.add_option(
'-thd',
'--type-hints-in-docstring',
action='store',
default='True',
parse_from_config=True,
help='Whether to require type hints in the argument list in docstrings',
)
parser.add_option(
'-ao',
Expand Down Expand Up @@ -81,7 +89,8 @@ def add_options(cls, parser): # noqa: D102

@classmethod
def parse_options(cls, options): # noqa: D102
cls.check_type_hint = options.check_type_hint
cls.type_hints_in_signature = options.type_hints_in_signature
cls.type_hints_in_docstring = options.type_hints_in_docstring
cls.check_arg_order = options.check_arg_order
cls.skip_checking_short_docstrings = (
options.skip_checking_short_docstrings
Expand All @@ -95,7 +104,14 @@ def parse_options(cls, options): # noqa: D102

def run(self) -> Generator[Tuple[int, int, str, Any], None, None]:
"""Run the linter and yield the violation information"""
checkTypeHint = self._bool('--check-type-hint', self.check_type_hint)
typeHintsInSignature = self._bool(
'--type-hints-in-signature',
self.type_hints_in_signature,
)
typeHintsInDocstring = self._bool(
'--type-hints-in-docstring',
self.type_hints_in_docstring,
)
checkArgOrder = self._bool('--check-arg-order', self.check_arg_order)
skipCheckingShortDocstrings = self._bool(
'--skip-checking-short-docstrings',
Expand All @@ -120,7 +136,8 @@ def run(self) -> Generator[Tuple[int, int, str, Any], None, None]:
)

v = Visitor(
checkTypeHint=checkTypeHint,
typeHintsInSignature=typeHintsInSignature,
typeHintsInDocstring=typeHintsInDocstring,
checkArgOrder=checkArgOrder,
skipCheckingShortDocstrings=skipCheckingShortDocstrings,
skipCheckingRaises=skipCheckingRaises,
Expand Down
34 changes: 24 additions & 10 deletions pydoclint/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,20 @@ def validateStyleValue(
help='',
)
@click.option(
'-th',
'--check-type-hint',
'-ths',
'--type-hints-in-signature',
type=bool,
show_default=True,
default=True,
help='Whether to check type hints in docstrings',
help='Whether to require type hints in function signatures',
)
@click.option(
'-thd',
'--type-hints-in-docstring',
type=bool,
show_default=True,
default=True,
help='Whether to require type hints in the argument list in docstrings',
)
@click.option(
'-ao',
Expand Down Expand Up @@ -158,13 +166,14 @@ def main( # noqa: C901
style: str,
src: Optional[str],
paths: Tuple[str, ...],
check_type_hint: bool,
type_hints_in_signature: bool,
type_hints_in_docstring: bool,
check_arg_order: bool,
skip_checking_short_docstrings: bool,
skip_checking_raises: bool,
allow_init_docstring: bool,
require_return_section_when_returning_none: bool,
config: Optional[str],
config: Optional[str], # don't remove it b/c it's required by `click`
) -> None:
"""Command-line entry point of pydoclint"""
ctx.ensure_object(dict)
Expand All @@ -189,7 +198,8 @@ def main( # noqa: C901
exclude=exclude,
style=style,
paths=paths,
checkTypeHint=check_type_hint,
typeHintsInSignature=type_hints_in_signature,
typeHintsInDocstring=type_hints_in_docstring,
checkArgOrder=check_arg_order,
skipCheckingShortDocstrings=skip_checking_short_docstrings,
skipCheckingRaises=skip_checking_raises,
Expand Down Expand Up @@ -243,7 +253,8 @@ def main( # noqa: C901
def _checkPaths(
paths: Tuple[str, ...],
style: str = 'numpy',
checkTypeHint: bool = True,
typeHintsInSignature: bool = True,
typeHintsInDocstring: bool = True,
checkArgOrder: bool = True,
skipCheckingShortDocstrings: bool = True,
skipCheckingRaises: bool = False,
Expand Down Expand Up @@ -283,7 +294,8 @@ def _checkPaths(
violationsInThisFile: List[Violation] = _checkFile(
filename,
style=style,
checkTypeHint=checkTypeHint,
typeHintsInSignature=typeHintsInSignature,
typeHintsInDocstring=typeHintsInDocstring,
checkArgOrder=checkArgOrder,
skipCheckingShortDocstrings=skipCheckingShortDocstrings,
skipCheckingRaises=skipCheckingRaises,
Expand All @@ -298,7 +310,8 @@ def _checkPaths(
def _checkFile(
filename: Path,
style: str = 'numpy',
checkTypeHint: bool = True,
typeHintsInSignature: bool = True,
typeHintsInDocstring: bool = True,
checkArgOrder: bool = True,
skipCheckingShortDocstrings: bool = True,
skipCheckingRaises: bool = False,
Expand All @@ -311,7 +324,8 @@ def _checkFile(
tree: ast.Module = ast.parse(src)
visitor = Visitor(
style=style,
checkTypeHint=checkTypeHint,
typeHintsInSignature=typeHintsInSignature,
typeHintsInDocstring=typeHintsInDocstring,
checkArgOrder=checkArgOrder,
skipCheckingShortDocstrings=skipCheckingShortDocstrings,
skipCheckingRaises=skipCheckingRaises,
Expand Down
34 changes: 34 additions & 0 deletions pydoclint/utils/arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ def nameEquals(self, other: 'Arg') -> bool:
"""More lenient equality: only compare names"""
return self.name == other.name

def hasTypeHint(self) -> bool:
"""Check whether this arg has type hint"""
return self.typeHint != ''

def isStarArg(self) -> bool:
"""Check whether this arg is a star arg (such as *args, **kwargs)"""
return self.name.startswith('*')

def notStarArg(self) -> bool:
"""Check whether this arg is not a star arg (*args, **kwargs)"""
return not self.isStarArg()

@classmethod
def fromNumpydocParam(cls, param: Parameter) -> 'Arg':
"""Construct an Arg object from a Numpydoc Parameter object"""
Expand Down Expand Up @@ -205,3 +217,25 @@ def equals(
def subtract(self, other: 'ArgList') -> Set[Arg]:
"""Find the args that are in this object but not in `other`."""
return set(self.infoList) - set(other.infoList)

def noTypeHints(self) -> bool:
"""Check whether none of the args have type hints"""
return not self.hasTypeHintInAnyArg()

def hasTypeHintInAnyArg(self) -> bool:
"""
Check whether any arg has a type hint.
Start arguments (such as `*args` or `**kwargs`) are excluded because
they don't need to have type hints.
"""
return any(_.hasTypeHint() for _ in self.infoList if _.notStarArg())

def hasTypeHintInAllArgs(self) -> bool:
"""
Check whether all args have a type hint.
Start arguments (such as `*args` or `**kwargs`) are excluded because
they don't need to have type hints.
"""
return all(_.hasTypeHint() for _ in self.infoList if _.notStarArg())
6 changes: 6 additions & 0 deletions pydoclint/utils/violation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
),
104: 'Arguments are the same in the docstring and the function signature, but are in a different order.',
105: 'Argument names match, but type hints do not match',
106: 'The option `--type-hints-in-signature` is `True` but there are no type hints in the signature',
107: 'The option `--type-hints-in-signature` is `True` but not all args in the signature have type hints',
108: 'The option `--type-hints-in-signature` is `False` but there are type hints in the signature',
109: 'The option `--type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list',
110: 'The option `--type-hints-in-docstring` is `True` but not all args in the docstring arg list have type hints',
111: 'The option `--type-hints-in-docstring` is `False` but there are type hints in the docstring arg list',

201: 'does not have a return section in docstring',
202: 'has a return section in docstring, but there are no return statements or annotations',
Expand Down
41 changes: 34 additions & 7 deletions pydoclint/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ class Visitor(ast.NodeVisitor):
def __init__(
self,
style: str = 'numpy',
checkTypeHint: bool = True,
typeHintsInSignature: bool = True,
typeHintsInDocstring: bool = True,
checkArgOrder: bool = True,
skipCheckingShortDocstrings: bool = True,
skipCheckingRaises: bool = False,
allowInitDocstring: bool = False,
requireReturnSectionWhenReturningNone: bool = False,
) -> None:
self.style: str = style
self.checkTypeHint: bool = checkTypeHint
self.typeHintsInSignature: bool = typeHintsInSignature
self.typeHintsInDocstring: bool = typeHintsInDocstring
self.checkArgOrder: bool = checkArgOrder
self.skipCheckingShortDocstrings: bool = skipCheckingShortDocstrings
self.skipCheckingRaises: bool = skipCheckingRaises
Expand Down Expand Up @@ -315,9 +317,15 @@ def checkArguments( # noqa: C901
v102 = Violation(code=102, line=lineNum, msgPrefix=msgPrefix)
v104 = Violation(code=104, line=lineNum, msgPrefix=msgPrefix)
v105 = Violation(code=105, line=lineNum, msgPrefix=msgPrefix)
v106 = Violation(code=106, line=lineNum, msgPrefix=msgPrefix)
v107 = Violation(code=107, line=lineNum, msgPrefix=msgPrefix)
v108 = Violation(code=108, line=lineNum, msgPrefix=msgPrefix)
v109 = Violation(code=109, line=lineNum, msgPrefix=msgPrefix)
v110 = Violation(code=110, line=lineNum, msgPrefix=msgPrefix)
v111 = Violation(code=111, line=lineNum, msgPrefix=msgPrefix)

docArgs = doc.argList
funcArgs = ArgList([Arg.fromAstArg(_) for _ in astArgList])
docArgs: ArgList = doc.argList
funcArgs: ArgList = ArgList([Arg.fromAstArg(_) for _ in astArgList])

if docArgs.length == 0 and funcArgs.length == 0:
return []
Expand All @@ -329,14 +337,32 @@ def checkArguments( # noqa: C901
if docArgs.length > funcArgs.length:
violations.append(v102)

if self.typeHintsInSignature and funcArgs.noTypeHints():
violations.append(v106)

if self.typeHintsInSignature and not funcArgs.hasTypeHintInAllArgs():
violations.append(v107)

if not self.typeHintsInSignature and funcArgs.hasTypeHintInAnyArg():
violations.append(v108)

if self.typeHintsInDocstring and docArgs.noTypeHints():
violations.append(v109)

if self.typeHintsInDocstring and not docArgs.hasTypeHintInAllArgs():
violations.append(v110)

if not self.typeHintsInDocstring and docArgs.hasTypeHintInAnyArg():
violations.append(v111)

if not docArgs.equals(
funcArgs,
checkTypeHint=self.checkTypeHint,
checkTypeHint=True,
orderMatters=self.checkArgOrder,
):
if docArgs.equals(
funcArgs,
checkTypeHint=self.checkTypeHint,
checkTypeHint=True,
orderMatters=False,
):
violations.append(v104)
Expand All @@ -345,7 +371,8 @@ def checkArguments( # noqa: C901
checkTypeHint=False,
orderMatters=self.checkArgOrder,
):
violations.append(v105)
if self.typeHintsInSignature and self.typeHintsInDocstring:
violations.append(v105)
elif docArgs.equals(
funcArgs,
checkTypeHint=False,
Expand Down
Loading

0 comments on commit b6c280e

Please sign in to comment.