1
1
"""Extension for flake8 that finds usage of the debugger."""
2
2
import ast
3
+ from itertools import chain
3
4
5
+ import pycodestyle
4
6
5
- __version__ = '2.0.0'
6
-
7
- DEBUGGER_ERROR_CODE = 'T002'
8
-
9
-
10
- def flake8ext (f ):
11
- """Decorate flake8 extension function."""
12
- f .name = 'flake8-debugger'
13
- f .version = __version__
14
- return f
15
7
8
+ __version__ = '3.0.0'
16
9
17
- def format_debugger_message (import_type , item_imported , item_alias , trace_method , trace_alias ):
18
- if import_type == 'import' :
19
- if item_imported == item_alias :
20
- return '{0} import for {1} found' .format (DEBUGGER_ERROR_CODE , item_alias )
21
- else :
22
- return '{0} import for {1} found as {2}' .format (DEBUGGER_ERROR_CODE , item_imported , item_alias )
23
- elif import_type == 'import_trace' :
24
- if trace_method == trace_alias :
25
- return '{0} import for {1}.{2} found' .format (DEBUGGER_ERROR_CODE , item_imported , trace_method )
26
- else :
27
- return '{0} import for {1}.{2} found as {3}' .format (DEBUGGER_ERROR_CODE , item_imported , trace_method , trace_alias )
28
- elif import_type == 'trace_used' :
29
- if trace_method == trace_alias :
30
- return '{0} trace found: {1}.{2} used' .format (DEBUGGER_ERROR_CODE , item_imported , trace_method )
31
- else :
32
- return '{0} trace found: {1}.{2} used as {3}' .format (DEBUGGER_ERROR_CODE , item_imported , trace_method , trace_alias )
33
-
10
+ DEBUGGER_ERROR_CODE = 'T002'
34
11
35
12
debuggers = {
36
13
'pdb' : 'set_trace' ,
@@ -40,60 +17,116 @@ def format_debugger_message(import_type, item_imported, item_alias, trace_method
40
17
}
41
18
42
19
43
- def check_for_debugger_import (logical_line , checker_state ):
44
- for node in ast .walk (ast .parse (logical_line )):
45
- if isinstance (node , ast .Import ) or isinstance (node , ast .ImportFrom ):
46
-
47
- if hasattr (node , 'module' ) and node .module not in debuggers .keys ():
48
- continue
49
-
50
- module_names = (hasattr (node , 'names' ) and [module_name .name for module_name in node .names ]) or []
51
- if isinstance (node , ast .Import ):
52
- for debugger in debuggers .keys ():
53
- if debugger in module_names :
54
- index = module_names .index (debugger )
55
- yield 'import' , debugger , getattr (node .names [index ], 'asname' , None ) or debugger , debuggers [debugger ], debuggers [debugger ]
56
-
57
- elif isinstance (node , ast .ImportFrom ):
58
- trace_methods = debuggers .values ()
59
- traces_found = set ([trace for trace in trace_methods if trace in module_names ])
60
- if not traces_found :
61
- continue
62
- for trace in traces_found :
63
- trace_index = trace in module_names and module_names .index (trace )
64
- yield 'import_trace' , node .module , node .module , debuggers [node .module ], getattr (node .names [trace_index ], 'asname' , None ) or debuggers [node .module ]
65
-
66
-
67
- def check_for_set_trace_usage (logical_line , checker_state ):
68
- for node in ast .walk (ast .parse (logical_line )):
69
- if isinstance (node , ast .Call ):
70
- trace_methods = [checker_state ['debuggers_found' ][debugger ]['trace_alias' ] for debugger in checker_state ['debuggers_found' ].keys ()]
71
- if (getattr (node .func , 'attr' , None ) in trace_methods or getattr (node .func , 'id' , None ) in trace_methods ):
72
- for debugger , debugger_info in checker_state ['debuggers_found' ].items ():
73
- trace_method_name = checker_state ['debuggers_found' ][debugger ]['trace_alias' ]
74
- if (
75
- (hasattr (node .func , 'value' ) and node .func .value .id == debugger_info ['alias' ]) or
76
- (hasattr (node .func , 'id' ) and trace_method_name and node .func .id == trace_method_name )
77
- ):
78
- yield 'trace_used' , debugger , debugger_info ['alias' ], debugger_info ['trace_method' ], debugger_info ['trace_alias' ]
79
- break
80
-
81
-
82
- @flake8ext
83
- def debugger_usage (logical_line , checker_state = None , noqa = None ):
84
- if 'debuggers_found' not in checker_state :
85
- checker_state ['debuggers_found' ] = {}
86
- generator = check_for_debugger_import (logical_line , checker_state .copy ())
87
-
88
- for import_type , item_imported , item_alias , trace_method , trace_alias in generator :
89
- if item_imported is not None :
90
- checker_state ['debuggers_found' ][item_imported ] = {
91
- 'alias' : item_alias , 'trace_method' : trace_method , 'trace_alias' : trace_alias
92
- }
93
- if not noqa :
94
- yield 0 , format_debugger_message (import_type , item_imported , item_alias , trace_method , trace_alias )
95
-
96
- if not noqa :
97
- generator = check_for_set_trace_usage (logical_line , checker_state .copy ())
98
- for usage in generator :
99
- yield 0 , format_debugger_message (* usage )
20
+ VIOLATIONS = {
21
+ 'found' : {
22
+ 'set_trace' : 'T001 set_trace found.' ,
23
+ 'InteractiveShellEmbed' : 'T003 InteractiveShellEmbed found.' ,
24
+ },
25
+ 'declared' : {
26
+ 'print' : 'T101 Python 2.x reserved word print used.' ,
27
+ 'pprint' : 'T103 pprint declared' ,
28
+ },
29
+ }
30
+
31
+
32
+ class DebuggerFinder (ast .NodeVisitor ):
33
+ def __init__ (self , * args , ** kwargs ):
34
+ super (DebuggerFinder , self ).__init__ (* args , ** kwargs )
35
+ self .debuggers_used = {}
36
+ self .debuggers_traces_redefined = {}
37
+ self .debuggers_traces_names = {}
38
+ self .debugger_traces_imported = {}
39
+ self .debuggers_names = {}
40
+ self .debuggers_redefined = {}
41
+ self .debuggers_imported = {}
42
+
43
+ def visit_Call (self , node ):
44
+ debugger_method_names = chain (debuggers .values (), self .debuggers_traces_names .values ())
45
+ is_debugger_function = getattr (node .func , "id" , None ) in list (debugger_method_names )
46
+ if is_debugger_function :
47
+ if node .func .id in self .debuggers_traces_names .values ():
48
+ debugger_method = next (item [0 ] for item in self .debuggers_traces_names .items () if item [1 ] == node .func .id )
49
+ entry = self .debuggers_used .setdefault ((node .lineno , node .col_offset ), [])
50
+ if debugger_method == node .func .id :
51
+ entry .append ('{0} trace found: {1} used' .format (DEBUGGER_ERROR_CODE , node .func .id ))
52
+ else :
53
+ entry .append ('{0} trace found: {1} used as {2}' .format (DEBUGGER_ERROR_CODE , debugger_method , node .func .id ))
54
+
55
+ debugger_method_names = chain (debuggers .values (), self .debuggers_traces_names .values ())
56
+ is_debugger_attribute = getattr (node .func , "attr" , None ) in list (debugger_method_names )
57
+ if is_debugger_attribute :
58
+ caller = node .func .value .id
59
+ entry = self .debuggers_used .setdefault ((node .lineno , node .col_offset ), [])
60
+ if caller in self .debuggers_names .values ():
61
+ entry .append ('{0} trace found: {1}.{2} used' .format (DEBUGGER_ERROR_CODE , caller , node .func .attr ))
62
+ else :
63
+ entry .append ('{0} trace found: {1} used' .format (DEBUGGER_ERROR_CODE , node .func .attr ))
64
+ self .generic_visit (node )
65
+
66
+ def visit_Import (self , node ):
67
+ for name_node in node .names :
68
+ if name_node .name in list (debuggers .keys ()):
69
+ if name_node .asname is not None :
70
+ self .debuggers_names [name_node .name ] = name_node .asname
71
+ entry = self .debuggers_redefined .setdefault ((node .lineno , node .col_offset ), [])
72
+ entry .append ('{0} import for {1} found as {2}' .format (DEBUGGER_ERROR_CODE , name_node .name , name_node .asname ))
73
+ else :
74
+ self .debuggers_names [name_node .name ] = name_node .name
75
+ entry = self .debuggers_imported .setdefault ((node .lineno , node .col_offset ), [])
76
+ entry .append ('{0} import for {1} found' .format (DEBUGGER_ERROR_CODE , name_node .name ))
77
+
78
+ def visit_ImportFrom (self , node ):
79
+ if node .module in list (debuggers .keys ()):
80
+ for name_node in node .names :
81
+ if name_node .name == debuggers [node .module ]:
82
+ if name_node .asname is not None :
83
+ self .debuggers_traces_names [name_node .name ] = name_node .asname
84
+ entry = self .debuggers_traces_redefined .setdefault ((node .lineno , node .col_offset ), [])
85
+ entry .append ('{0} import for {1} found as {2}' .format (DEBUGGER_ERROR_CODE , name_node .name , name_node .asname ))
86
+ else :
87
+ self .debuggers_traces_names [name_node .name ] = name_node .name
88
+ entry = self .debugger_traces_imported .setdefault ((node .lineno , node .col_offset ), [])
89
+ entry .append ('{0} import for {1} found' .format (DEBUGGER_ERROR_CODE , name_node .name ))
90
+
91
+
92
+ class DebuggerChecker (object ):
93
+ options = None
94
+ name = 'flake8-debugger'
95
+ version = __version__
96
+
97
+ def __init__ (self , tree , filename ):
98
+ self .tree = tree
99
+ self .filename = filename
100
+ self .lines = None
101
+
102
+ def load_file (self ):
103
+ if self .filename in ("stdin" , "-" , None ):
104
+ self .filename = "stdin"
105
+ self .lines = pycodestyle .stdin_get_value ().splitlines (True )
106
+ else :
107
+ self .lines = pycodestyle .readlines (self .filename )
108
+
109
+ if not self .tree :
110
+ self .tree = ast .parse ("" .join (self .lines ))
111
+
112
+ def run (self ):
113
+ if not self .tree or not self .lines :
114
+ self .load_file ()
115
+
116
+ parser = DebuggerFinder ()
117
+ parser .visit (self .tree )
118
+ for error , messages in parser .debuggers_used .items ():
119
+ if not pycodestyle .noqa (self .lines [error [0 ] - 1 ]):
120
+ for message in messages :
121
+ yield (error [0 ], error [1 ], message , DebuggerChecker )
122
+
123
+ for error , messages in chain (
124
+ parser .debuggers_traces_redefined .items (),
125
+ parser .debugger_traces_imported .items (),
126
+ parser .debuggers_redefined .items (),
127
+ parser .debuggers_imported .items (),
128
+ ):
129
+ if error not in parser .debuggers_used :
130
+ if not pycodestyle .noqa (self .lines [error [0 ] - 1 ]):
131
+ for message in messages :
132
+ yield (error [0 ], error [1 ], message , DebuggerChecker )
0 commit comments