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 committed Oct 29, 2021
1 parent 560406b commit 9bb112a
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/tryceratops/analyzers/__init__.py
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
61 changes: 61 additions & 0 deletions src/tryceratops/analyzers/conditional.py
@@ -0,0 +1,61 @@
import ast

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) -> bool:
if isinstance(node, ast.If):
return self._is_checking_type(node.test)
if isinstance(node, ast.UnaryOp):
return self._is_checking_type(node.operand)
if isinstance(node, ast.BoolOp):
for value in node.values:
if self._is_checking_type(value):
return True
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):
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 visit_If(self, node: ast.If) -> None:
if self._is_checking_type(node):
for stm in ast.iter_child_nodes(node):
if isinstance(stm, ast.Raise):
self._check_is_raise_other_than_typeerror(stm)
else:
for stm in node.orelse:
if isinstance(stm, ast.If):
self.visit_If(stm)
1 change: 1 addition & 0 deletions src/tryceratops/violations/codes.py
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 9bb112a

Please sign in to comment.