Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
"""Extension for flake8 that finds usage of the debugger."""
import ast
from itertools import chain
import pycodestyle
try:
from flake8.engine import pep8 as stdin_utils
except ImportError:
from flake8 import utils as stdin_utils
__version__ = "4.1.2"
DEBUGGER_ERROR_CODE = "T100"
debuggers = {
"pdb": ["set_trace"],
"pudb": ["set_trace"],
"ipdb": ["set_trace", "sset_trace"],
"IPython.terminal.embed": ["InteractiveShellEmbed"],
"IPython.frontend.terminal.embed": ["InteractiveShellEmbed"],
"celery.contrib.rdb": ["set_trace"],
"builtins": ["breakpoint"]
}
class DebuggerFinder(ast.NodeVisitor):
def __init__(self, *args, **kwargs):
super(DebuggerFinder, self).__init__(*args, **kwargs)
self.debuggers_used = {}
self.debuggers_traces_redefined = {}
self.debuggers_traces_names = {}
self.debugger_traces_imported = {}
self.debuggers_names = {}
self.debuggers_redefined = {}
self.debuggers_imported = {}
def visit_Call(self, node):
if getattr(node.func, "id", None) == "breakpoint":
entry = self.debuggers_used.setdefault((node.lineno, node.col_offset), [])
entry.append("{0} trace found: breakpoint used".format(DEBUGGER_ERROR_CODE))
debugger_method_names = list(chain(self.debuggers_traces_names.values(), *debuggers.values()))
is_debugger_function = getattr(node.func, "id", None) in debugger_method_names
if is_debugger_function:
if node.func.id in self.debuggers_traces_names.values():
debugger_method = next(
item[0] for item in self.debuggers_traces_names.items() if item[1] == node.func.id
)
entry = self.debuggers_used.setdefault((node.lineno, node.col_offset), [])
if debugger_method == node.func.id:
entry.append("{0} trace found: {1} used".format(DEBUGGER_ERROR_CODE, node.func.id))
else:
entry.append(
"{0} trace found: {1} used as {2}".format(DEBUGGER_ERROR_CODE, debugger_method, node.func.id)
)
is_debugger_attribute = getattr(node.func, "attr", None) in debugger_method_names
if is_debugger_attribute:
caller = getattr(node.func.value, "id", None)
entry = self.debuggers_used.setdefault((node.lineno, node.col_offset), [])
if caller in self.debuggers_names.values():
entry.append("{0} trace found: {1}.{2} used".format(DEBUGGER_ERROR_CODE, caller, node.func.attr))
else:
entry.append("{0} trace found: {1} used".format(DEBUGGER_ERROR_CODE, node.func.attr))
self.generic_visit(node)
def visit_Import(self, node):
for name_node in node.names:
if name_node.name in list(debuggers.keys()):
if name_node.asname is not None:
self.debuggers_names[name_node.name] = name_node.asname
entry = self.debuggers_redefined.setdefault((node.lineno, node.col_offset), [])
entry.append(
"{0} import for {1} found as {2}".format(DEBUGGER_ERROR_CODE, name_node.name, name_node.asname)
)
# Unlike the other imports, we don't want to consider all builtin imports as worthy of flagging.
elif name_node.name != "builtins":
self.debuggers_names[name_node.name] = name_node.name
entry = self.debuggers_imported.setdefault((node.lineno, node.col_offset), [])
entry.append("{0} import for {1} found".format(DEBUGGER_ERROR_CODE, name_node.name))
def visit_ImportFrom(self, node):
if node.module in list(debuggers.keys()):
for name_node in node.names:
if name_node.name in debuggers[node.module]:
if name_node.asname is not None:
self.debuggers_traces_names[name_node.name] = name_node.asname
entry = self.debuggers_traces_redefined.setdefault((node.lineno, node.col_offset), [])
entry.append(
"{0} import for {1} found as {2}".format(
DEBUGGER_ERROR_CODE, name_node.name, name_node.asname
)
)
else:
self.debuggers_traces_names[name_node.name] = name_node.name
entry = self.debugger_traces_imported.setdefault((node.lineno, node.col_offset), [])
entry.append("{0} import for {1} found".format(DEBUGGER_ERROR_CODE, name_node.name))
class DebuggerChecker(object):
options = None
name = "flake8-debugger"
version = __version__
def __init__(self, tree, filename):
self.tree = tree
self.filename = filename
self.lines = None
def load_file(self):
if self.filename in ("stdin", "-", None):
self.filename = "stdin"
self.lines = stdin_utils.stdin_get_value().splitlines(True)
else:
self.lines = pycodestyle.readlines(self.filename)
if not self.tree:
self.tree = ast.parse("".join(self.lines))
def run(self):
if not self.tree or not self.lines:
self.load_file()
parser = DebuggerFinder()
parser.visit(self.tree)
for error, messages in parser.debuggers_used.items():
if not pycodestyle.noqa(self.lines[error[0] - 1]):
for message in messages:
yield (error[0], error[1], message, DebuggerChecker)
for error, messages in chain(
parser.debuggers_traces_redefined.items(),
parser.debugger_traces_imported.items(),
parser.debuggers_redefined.items(),
parser.debuggers_imported.items(),
):
if error not in parser.debuggers_used:
if not pycodestyle.noqa(self.lines[error[0] - 1]):
for message in messages:
yield (error[0], error[1], message, DebuggerChecker)