Skip to content

Commit

Permalink
feat!: add prefer TypeError analyzer
Browse files Browse the repository at this point in the history
  • Loading branch information
swellander authored and guilatrova committed Nov 22, 2021
1 parent 0ca7c28 commit ac75e37
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/tryceratops/analyzers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Set

from . import call, exception_block, try_block
from . import call, conditional, exception_block, try_block
from .base import BaseAnalyzer

if TYPE_CHECKING:
Expand All @@ -14,6 +14,7 @@
call.CallRaiseVanillaAnalyzer, # type: ignore
call.CallRaiseLongArgsAnalyzer, # type: ignore
call.CallAvoidCheckingToContinueAnalyzer, # type: ignore
conditional.PreferTypeErrorAnalyzer,
exception_block.ExceptReraiseWithoutCauseAnalyzer,
exception_block.ExceptVerboseReraiseAnalyzer,
exception_block.ExceptBroadPassAnalyzer,
Expand Down
64 changes: 64 additions & 0 deletions src/tryceratops/analyzers/conditional.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import ast
from typing import Union

from tryceratops.violations import codes

from .base import BaseAnalyzer

STANDARD_NON_TYPE_ERROR_IDS = (
"ArithmeticError",
"AssertionError",
"AttributeError",
"BufferError",
"EOFError",
"Exception",
"ImportError",
"LookupError",
"MemoryError",
"NameError",
"ReferenceError",
"RuntimeError",
"SyntaxError",
"SystemError",
"ValueError",
)


class PreferTypeErrorAnalyzer(BaseAnalyzer):
violation_code = codes.PREFER_TYPE_ERROR

def _is_checking_type(self, node: Union[ast.stmt, ast.expr]) -> bool:
if isinstance(node, ast.If):
return self._is_checking_type(node.test) and all(
[self._is_checking_type(stm) for stm in node.orelse if isinstance(stm, ast.If)]
)
if isinstance(node, ast.UnaryOp):
return self._is_checking_type(node.operand)
if isinstance(node, ast.BoolOp):
return all([self._is_checking_type(value) for value in node.values])
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name):
if node.func.id in ("isinstance", "issubclass", "callable"):
return True
return False

def _check_is_raise_other_than_typeerror(self, node: ast.Raise) -> None:
if isinstance(node.exc, ast.Call):
if isinstance(node.exc.func, ast.Name):
if node.exc.func.id in STANDARD_NON_TYPE_ERROR_IDS:
self._mark_violation(node.exc.func)
elif isinstance(node.exc, ast.Name):
if node.exc.id in STANDARD_NON_TYPE_ERROR_IDS:
self._mark_violation(node.exc)

def _check_for_raises(self, node):
for stm in ast.iter_child_nodes(node):
if isinstance(stm, ast.Raise):
self._check_is_raise_other_than_typeerror(stm)
if isinstance(node, ast.If):
for stm in node.orelse:
self._check_for_raises(stm)

def visit_If(self, node: ast.If) -> None:
if self._is_checking_type(node):
self._check_for_raises(node)
1 change: 1 addition & 0 deletions src/tryceratops/violations/codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"TC003",
"Avoid specifying long messages outside the exception class",
)
PREFER_TYPE_ERROR = ("TC004", "Prefer TypeError exception for invalid type")

# TC1xx - General
CHECK_TO_CONTINUE = (
Expand Down

0 comments on commit ac75e37

Please sign in to comment.