@@ -221,38 +221,9 @@ def visit_For(self, node):
221221 self .generic_visit (node )
222222
223223 def visit_FunctionDef (self , node ):
224- xs = list (node .body )
225- has_yield = False
226- return_node = None
227- while xs :
228- x = xs .pop ()
229- if isinstance (x , (ast .AsyncFunctionDef , ast .FunctionDef )):
230- continue
231- elif isinstance (x , (ast .Yield , ast .YieldFrom )):
232- has_yield = True
233- elif isinstance (x , ast .Return ) and x .value is not None :
234- return_node = x
235-
236- if has_yield and return_node is not None :
237- self .errors .append (
238- B901 (return_node .lineno , return_node .col_offset )
239- )
240- break
241-
242- xs .extend (ast .iter_child_nodes (x ))
243-
244- for default in node .args .defaults :
245- if isinstance (default , B006 .mutable_literals ):
246- self .errors .append (
247- B006 (default .lineno , default .col_offset )
248- )
249- elif isinstance (default , ast .Call ):
250- call_path = '.' .join (self .compose_call_path (default .func ))
251- if call_path in B006 .mutable_calls :
252- self .errors .append (
253- B006 (default .lineno , default .col_offset )
254- )
255-
224+ self .check_for_b901 (node )
225+ self .check_for_b902 (node )
226+ self .check_for_b006 (node )
256227 self .generic_visit (node )
257228
258229 def compose_call_path (self , node ):
@@ -284,6 +255,19 @@ def check_for_b005(self, node):
284255 B005 (node .lineno , node .col_offset )
285256 )
286257
258+ def check_for_b006 (self , node ):
259+ for default in node .args .defaults :
260+ if isinstance (default , B006 .mutable_literals ):
261+ self .errors .append (
262+ B006 (default .lineno , default .col_offset )
263+ )
264+ elif isinstance (default , ast .Call ):
265+ call_path = '.' .join (self .compose_call_path (default .func ))
266+ if call_path in B006 .mutable_calls :
267+ self .errors .append (
268+ B006 (default .lineno , default .col_offset )
269+ )
270+
287271 def check_for_b007 (self , node ):
288272 targets = NameFinder ()
289273 targets .visit (node .target )
@@ -296,6 +280,86 @@ def check_for_b007(self, node):
296280 n = targets .names [name ][0 ]
297281 self .errors .append (B007 (n .lineno , n .col_offset , vars = (name ,)))
298282
283+ def check_for_b901 (self , node ):
284+ xs = list (node .body )
285+ has_yield = False
286+ return_node = None
287+ while xs :
288+ x = xs .pop ()
289+ if isinstance (x , (ast .AsyncFunctionDef , ast .FunctionDef )):
290+ continue
291+ elif isinstance (x , (ast .Yield , ast .YieldFrom )):
292+ has_yield = True
293+ elif isinstance (x , ast .Return ) and x .value is not None :
294+ return_node = x
295+
296+ if has_yield and return_node is not None :
297+ self .errors .append (
298+ B901 (return_node .lineno , return_node .col_offset )
299+ )
300+ break
301+
302+ xs .extend (ast .iter_child_nodes (x ))
303+
304+ def check_for_b902 (self , node ):
305+ if not isinstance (self .node_stack [- 2 ], ast .ClassDef ):
306+ return
307+
308+ decorators = NameFinder ()
309+ decorators .visit (node .decorator_list )
310+
311+ if 'staticmethod' in decorators .names :
312+ # TODO: maybe warn if the first argument is surprisingly `self` or
313+ # `cls`?
314+ return
315+
316+ if (
317+ 'classmethod' in decorators .names or
318+ node .name in B902 .implicit_classmethods
319+ ):
320+ expected_first_args = B902 .cls
321+ kind = 'class'
322+ else :
323+ expected_first_args = B902 .self
324+ kind = 'instance'
325+
326+ args = node .args .args
327+ vararg = node .args .vararg
328+ kwarg = node .args .kwarg
329+ kwonlyargs = node .args .kwonlyargs
330+
331+ if args :
332+ actual_first_arg = args [0 ].arg
333+ lineno = args [0 ].lineno
334+ col = args [0 ].col_offset
335+ elif vararg :
336+ actual_first_arg = '*' + vararg .arg
337+ lineno = vararg .lineno
338+ col = vararg .col_offset
339+ elif kwarg :
340+ actual_first_arg = '**' + kwarg .arg
341+ lineno = kwarg .lineno
342+ col = kwarg .col_offset
343+ elif kwonlyargs :
344+ actual_first_arg = '*, ' + kwonlyargs [0 ].arg
345+ lineno = kwonlyargs [0 ].lineno
346+ col = kwonlyargs [0 ].col_offset
347+ else :
348+ actual_first_arg = '(none)'
349+ lineno = node .lineno
350+ col = node .col_offset
351+
352+ if actual_first_arg not in expected_first_args :
353+ if not actual_first_arg .startswith (('(' , '*' )):
354+ actual_first_arg = repr (actual_first_arg )
355+ self .errors .append (
356+ B902 (
357+ lineno ,
358+ col ,
359+ vars = (actual_first_arg , kind , expected_first_args [0 ])
360+ )
361+ )
362+
299363
300364@attr .s
301365class NameFinder (ast .NodeVisitor ):
@@ -309,6 +373,15 @@ class NameFinder(ast.NodeVisitor):
309373 def visit_Name (self , node ):
310374 self .names .setdefault (node .id , []).append (node )
311375
376+ def visit (self , node ):
377+ """Like super-visit but supports iteration over lists."""
378+ if not isinstance (node , list ):
379+ return super ().visit (node )
380+
381+ for elem in node :
382+ super ().visit (elem )
383+ return node
384+
312385
313386error = namedtuple ('error' , 'lineno col message type vars' )
314387Error = partial (partial , error , type = BugBearChecker , vars = ())
@@ -425,8 +498,16 @@ def visit_Name(self, node):
425498 "`async def` coroutines or put a `# noqa` comment on this "
426499 "line if this was intentional." ,
427500)
501+ B902 = Error (
502+ message = "B902 Invalid first argument {} used for {} method. Use the "
503+ "canonical first argument name in methods, i.e. {}."
504+ )
505+ B902 .implicit_classmethods = {'__new__' , '__init_subclass__' }
506+ B902 .self = ['self' ] # it's a list because the first is preferred
507+ B902 .cls = ['cls' , 'klass' ] # ditto.
508+
428509B950 = Error (
429510 message = 'B950 line too long ({} > {} characters)' ,
430511)
431512
432- disabled_by_default = ["B901" , "B950" ]
513+ disabled_by_default = ["B901" , "B902" , " B950" ]
0 commit comments