77from contextlib import ExitStack
88from typing import Optional
99
10- import astroid
11-
12- try :
13- from astroid import NodeNG
14- except ImportError :
15- from astroid .node_classes import NodeNG
10+ from astroid import nodes
1611
1712import pylint .interfaces
1813from pylint .checkers import BaseChecker , utils
4035
4136function_definitions = collections .defaultdict (list )
4237callsites_for_queries = collections .defaultdict (list )
43- root_call : contextvars .ContextVar [Optional [astroid .Call ]] = \
38+ root_call : contextvars .ContextVar [Optional [nodes .Call ]] = \
4439 contextvars .ContextVar ('root_call' , default = None )
4540@contextlib .contextmanager
46- def push_call (node : astroid .Call ):
41+ def push_call (node : nodes .Call ):
4742 with ExitStack () as s :
4843 if root_call .get () is None :
4944 t = root_call .set (node )
@@ -77,39 +72,39 @@ def _infer_filename(self, node):
7772 return node .file
7873 def _get_return_node (self , node ):
7974 ret = []
80- nodes = deque ([node ])
81- while nodes :
82- node = nodes .popleft ()
83- if isinstance (node , astroid .Return ):
75+ q = deque ([node ])
76+ while q :
77+ node = q .popleft ()
78+ if isinstance (node , nodes .Return ):
8479 ret .append (node )
8580 else :
86- nodes .extend (node .get_children ())
81+ q .extend (node .get_children ())
8782 return ret
8883
8984 def _is_asserted (self , node ): # If there is an assert on the value of the node, it's very likely to be safe
90- asserted = deque (( assert_ .test for assert_ in node .scope ().nodes_of_class (astroid .Assert ) ))
85+ asserted = deque (assert_ .test for assert_ in node .scope ().nodes_of_class (nodes .Assert ))
9186 while asserted :
9287 n = asserted .popleft ()
93- if isinstance (n , astroid .Name ) and n .name == node .name :
88+ if isinstance (n , nodes .Name ) and n .name == node .name :
9489 return True
9590 else :
9691 asserted .extend (n .get_children ())
9792 return False
9893
9994 def _get_attribute_chain (self , node ):
100- if isinstance (node , astroid .Attribute ):
95+ if isinstance (node , nodes .Attribute ):
10196 return self ._get_attribute_chain (node .expr ) + '.' + node .attrname
102- elif isinstance (node , astroid .Name ):
97+ elif isinstance (node , nodes .Name ):
10398 return node .name
104- elif isinstance (node , astroid .Call ):
99+ elif isinstance (node , nodes .Call ):
105100 return self ._get_attribute_chain (node .func )
106101 return '' #FIXME
107102
108103 def _evaluate_function_call (self , node , args_allowed , position ):
109- name = node .func .attrname if isinstance (node .func , astroid .Attribute ) else node .func .name
104+ name = node .func .attrname if isinstance (node .func , nodes .Attribute ) else node .func .name
110105 if name == 'SQL' :
111106 return True
112- if isinstance (node .scope (), astroid .GeneratorExp ):
107+ if isinstance (node .scope (), nodes .GeneratorExp ):
113108 return True
114109 if name == node .scope ().name :
115110 return True
@@ -125,13 +120,13 @@ def _evaluate_function_call(self, node, args_allowed, position):
125120 )
126121 return True
127122
128- def _is_fstring_cst (self , node : astroid .JoinedStr , args_allowed = False , position = None ):
123+ def _is_fstring_cst (self , node : nodes .JoinedStr , args_allowed = False , position = None ):
129124 # an fstring is constant if all its FormattedValue are constant, or
130125 # are access to private attributes (nb: whitelist?)
131126 return self .all_const ((
132127 node .value for node in node .values
133- if isinstance (node , astroid .FormattedValue )
134- if not (isinstance (node .value , astroid .Attribute ) and node .value .attrname .startswith ('_' ))
128+ if isinstance (node , nodes .FormattedValue )
129+ if not (isinstance (node .value , nodes .Attribute ) and node .value .attrname .startswith ('_' ))
135130 ),
136131 args_allowed = args_allowed ,
137132 position = position
@@ -143,72 +138,70 @@ def all_const(self, nodes, args_allowed=False, position=None):
143138 for node in nodes
144139 )
145140
146- def _is_constexpr (self , node : NodeNG , * , args_allowed = False , position = None ):
147- if isinstance (node , astroid .Const ): # astroid.const is always safe
141+ def _is_constexpr (self , node : nodes . NodeNG , * , args_allowed = False , position = None ):
142+ if isinstance (node , nodes .Const ): # astroid.const is always safe
148143 return True
149- elif isinstance (node , (astroid .List , astroid .Set )):
144+ elif isinstance (node , (nodes .List , nodes .Set )):
150145 return self .all_const (node .elts , args_allowed = args_allowed )
151- elif isinstance (node , astroid .Tuple ):
146+ elif isinstance (node , nodes .Tuple ):
152147 if position is None :
153148 return self .all_const (node .elts , args_allowed = args_allowed )
154149 else :
155150 return self ._is_constexpr (node .elts [position ], args_allowed = args_allowed )
156- elif isinstance (node , astroid .Dict ):
151+ elif isinstance (node , nodes .Dict ):
157152 return all (
158153 self ._is_constexpr (k , args_allowed = args_allowed ) and self ._is_constexpr (v , args_allowed = args_allowed )
159154 for k , v in node .items
160155 )
161- elif isinstance (node , astroid .Starred ):
156+ elif isinstance (node , nodes .Starred ):
162157 return self ._is_constexpr (node .value , args_allowed = args_allowed , position = position )
163- elif isinstance (node , astroid .BinOp ): # recusively infer both side of the operation. Failing if either side is not inferable
158+ elif isinstance (node , nodes .BinOp ): # recusively infer both side of the operation. Failing if either side is not inferable
164159 left_operand = self ._is_constexpr (node .left , args_allowed = args_allowed )
165160 # This case allows to always consider a string formatted with %d to be safe
166161 if node .op == '%' and \
167- isinstance (node .left , astroid .Const ) and \
162+ isinstance (node .left , nodes .Const ) and \
168163 node .left .pytype () == 'builtins.str' and \
169164 '%d' in node .left .value and \
170165 not '%s' in node .left .value :
171166 return True
172167 right_operand = self ._is_constexpr (node .right , args_allowed = args_allowed )
173168 return left_operand and right_operand
174- elif isinstance (node , astroid .Name ) or isinstance ( node , astroid .AssignName ): # Variable: find the assignement instruction in the AST and infer its value.
175- assignements = node .lookup (node .name )
169+ elif isinstance (node , ( nodes .Name , nodes .AssignName )): # Variable: find the assignement instruction in the AST and infer its value.
170+ assignment = node .lookup (node .name )
176171 assigned_node = []
177- for n in assignements [1 ]: #assignement [0] contains the scope, so assignment[1] contains the assignement nodes
172+ for n in assignment [1 ]: # assignment [0] contains the scope, so assignment[1] contains the assignement nodes
178173 # FIXME: makes no sense, assuming this gets
179174 # `visit_functiondef`'d we should just ignore it
180- if isinstance (n .parent , astroid .FunctionDef ):
175+ if isinstance (n .parent , ( nodes .FunctionDef , nodes . Arguments ) ):
181176 assigned_node += [args_allowed ]
182- elif isinstance (n .parent , astroid .Arguments ):
183- assigned_node += [args_allowed ]
184- elif isinstance (n .parent , astroid .Tuple ): # multi assign a,b = (a,b)
177+ elif isinstance (n .parent , nodes .Tuple ): # multi assign a,b = (a,b)
185178 statement = n .statement ()
186- if isinstance (statement , astroid .For ):
179+ if isinstance (statement , nodes .For ):
187180 assigned_node += [self ._is_constexpr (statement .iter , args_allowed = args_allowed )]
188- elif isinstance (statement , astroid .Assign ):
181+ elif isinstance (statement , nodes .Assign ):
189182 assigned_node += [self ._is_constexpr (statement .value , args_allowed = args_allowed , position = n .parent .elts .index (n ))]
190183 else :
191184 raise TypeError (f"Expected statement Assign or For, got { statement } " )
192- elif isinstance (n .parent , astroid .For ):
185+ elif isinstance (n .parent , nodes .For ):
193186 assigned_node .append (self ._is_constexpr (n .parent .iter , args_allowed = args_allowed ))
194- elif isinstance (n .parent , astroid .AugAssign ):
187+ elif isinstance (n .parent , nodes .AugAssign ):
195188 left = self ._is_constexpr (n .parent .target , args_allowed = args_allowed )
196189 right = self ._is_constexpr (n .parent .value , args_allowed = args_allowed )
197190 assigned_node .append (left and right )
198- elif isinstance (n .parent , astroid .Module ):
191+ elif isinstance (n .parent , nodes .Module ):
199192 return True
200193 else :
201- if isinstance (n .parent , astroid .Comprehension ):
194+ if isinstance (n .parent , nodes .Comprehension ):
202195 assigned_node += [self ._is_constexpr (n .parent .iter , args_allowed = args_allowed )]
203196 else :
204197 assigned_node += [self ._is_constexpr (n .parent .value , args_allowed = args_allowed )]
205198 if assigned_node and all (assigned_node ):
206199 return True
207200 return self ._is_asserted (node )
208- elif isinstance (node , astroid .JoinedStr ):
201+ elif isinstance (node , nodes .JoinedStr ):
209202 return self ._is_fstring_cst (node , args_allowed )
210- elif isinstance (node , astroid .Call ):
211- if isinstance (node .func , astroid .Attribute ):
203+ elif isinstance (node , nodes .Call ):
204+ if isinstance (node .func , nodes .Attribute ):
212205 if node .func .attrname == 'append' :
213206 return self ._is_constexpr (node .args [0 ])
214207 elif node .func .attrname == 'format' :
@@ -219,16 +212,16 @@ def _is_constexpr(self, node: NodeNG, *, args_allowed=False, position=None):
219212 )
220213 with push_call (node ):
221214 return self ._evaluate_function_call (node , args_allowed = args_allowed , position = position )
222- elif isinstance (node , astroid .IfExp ):
215+ elif isinstance (node , nodes .IfExp ):
223216 body = self ._is_constexpr (node .body , args_allowed = args_allowed )
224217 orelse = self ._is_constexpr (node .orelse , args_allowed = args_allowed )
225218 return body and orelse
226- elif isinstance (node , astroid .Subscript ):
219+ elif isinstance (node , nodes .Subscript ):
227220 return self ._is_constexpr (node .value , args_allowed = args_allowed )
228- elif isinstance (node , astroid .BoolOp ):
221+ elif isinstance (node , nodes .BoolOp ):
229222 return self .all_const (node .values , args_allowed = args_allowed )
230223
231- elif isinstance (node , astroid .Attribute ):
224+ elif isinstance (node , nodes .Attribute ):
232225 attr_chain = self ._get_attribute_chain (node )
233226 while attr_chain :
234227 if attr_chain in ATTRIBUTE_WHITELIST or attr_chain .startswith ('_' ):
@@ -243,20 +236,17 @@ def _is_constexpr(self, node: NodeNG, *, args_allowed=False, position=None):
243236 def _get_cursor_name (self , node ):
244237 expr_list = []
245238 node_expr = node .expr
246- while isinstance (node_expr , astroid .Attribute ):
239+ while isinstance (node_expr , nodes .Attribute ):
247240 expr_list .insert (0 , node_expr .attrname )
248241 node_expr = node_expr .expr
249- if isinstance (node_expr , astroid .Name ):
242+ if isinstance (node_expr , nodes .Name ):
250243 expr_list .insert (0 , node_expr .name )
251244 cursor_name = '.' .join (expr_list )
252245 return cursor_name
253246
254- def _allowable (self , node ):
255- """
256- :type node: NodeNG
257- """
247+ def _allowable (self , node : nodes .NodeNG ) -> bool :
258248 scope = node .scope ()
259- if isinstance (scope , astroid .FunctionDef ) and (scope .name .startswith ("_" ) or scope .name == 'init' ):
249+ if isinstance (scope , nodes .FunctionDef ) and (scope .name .startswith ("_" ) or scope .name == 'init' ):
260250 return True
261251
262252 infered = utils .safe_infer (node )
@@ -269,23 +259,23 @@ def _allowable(self, node):
269259
270260 # self._thing is OK (mostly self._table), self._thing() also because
271261 # it's a common pattern of reports (self._select, self._group_by, ...)
272- return (isinstance (node , astroid .Attribute )
273- and isinstance (node .expr , astroid .Name )
262+ return (isinstance (node , nodes .Attribute )
263+ and isinstance (node .expr , nodes .Name )
274264 and node .attrname .startswith ('_' )
275265 )
276266
277- def _check_concatenation (self , node ) :
267+ def _check_concatenation (self , node : nodes . NodeNG ) -> bool | None :
278268 node = self .resolve (node )
279269
280270 if self ._allowable (node ):
281271 return False
282272
283- if isinstance (node , astroid .BinOp ) and node .op in ('%' , '+' ):
284- if isinstance (node .right , astroid .Tuple ):
273+ if isinstance (node , nodes .BinOp ) and node .op in ('%' , '+' ):
274+ if isinstance (node .right , nodes .Tuple ):
285275 # execute("..." % (self._table, thing))
286276 if not all (map (self ._allowable , node .right .elts )):
287277 return True
288- elif isinstance (node .right , astroid .Dict ):
278+ elif isinstance (node .right , nodes .Dict ):
289279 # execute("..." % {'table': self._table}
290280 if not all (self ._allowable (v ) for _ , v in node .right .items ):
291281 return True
@@ -308,8 +298,8 @@ def _check_concatenation(self, node):
308298 return self ._check_concatenation (node .left )
309299
310300 # check execute("...".format(self._table, table=self._table))
311- if isinstance (node , astroid .Call ) \
312- and isinstance (node .func , astroid .Attribute ) \
301+ if isinstance (node , nodes .Call ) \
302+ and isinstance (node .func , nodes .Attribute ) \
313303 and node .func .attrname == 'format' :
314304
315305 return not (
@@ -318,18 +308,20 @@ def _check_concatenation(self, node):
318308 )
319309
320310 # check execute(f'foo {...}')
321- if isinstance (node , astroid .JoinedStr ):
311+ if isinstance (node , nodes .JoinedStr ):
322312 return not all (
323313 self ._allowable (formatted .value )
324- for formatted in node .nodes_of_class (astroid .FormattedValue )
314+ for formatted in node .nodes_of_class (nodes .FormattedValue )
325315 )
326316
317+ return None
318+
327319 def resolve (self , node ):
328320 # if node is a variable, find how it was built
329- if isinstance (node , astroid .Name ):
321+ if isinstance (node , nodes .Name ):
330322 for target in node .lookup (node .name )[1 ]:
331323 # could also be e.g. arguments (if the source is a function parameter)
332- if isinstance (target .parent , astroid .Assign ):
324+ if isinstance (target .parent , nodes .Assign ):
333325 # FIXME: handle multiple results (e.g. conditional assignment)
334326 return target .parent .value
335327 # otherwise just return the original node for checking
@@ -341,9 +333,9 @@ def _check_sql_injection_risky(self, node):
341333 current_file_bname = os .path .basename (self .linter .current_file )
342334 if not (
343335 # .execute() or .executemany()
344- isinstance (node , astroid .Call ) and node .args and
345- ((isinstance (node .func , astroid .Attribute ) and node .func .attrname in ('execute' , 'executemany' , 'SQL' ) and self ._get_cursor_name (node .func ) in DFTL_CURSOR_EXPR ) or
346- (isinstance (node .func , astroid .Name ) and node .func .name == 'SQL' )) and
336+ isinstance (node , nodes .Call ) and node .args and
337+ ((isinstance (node .func , nodes .Attribute ) and node .func .attrname in ('execute' , 'executemany' , 'SQL' ) and self ._get_cursor_name (node .func ) in DFTL_CURSOR_EXPR ) or
338+ (isinstance (node .func , nodes .Name ) and node .func .name == 'SQL' )) and
347339 # ignore in test files, probably not accessible
348340 not current_file_bname .startswith ('test_' )
349341 ):
@@ -387,7 +379,7 @@ def visit_functiondef(self, node):
387379 )
388380
389381 @functools .lru_cache (None )
390- def _is_const_def (self , node : astroid .FunctionDef , / , * , position : Optional [int ], const_args : bool = False ) -> bool :
382+ def _is_const_def (self , node : nodes .FunctionDef , / , * , position : Optional [int ], const_args : bool = False ) -> bool :
391383 if node .name .startswith ('__' ) or node .name in FUNCTION_WHITELIST :
392384 return True
393385
0 commit comments