22
22
import astroid
23
23
from astroid .exceptions import InferenceError
24
24
from pylint .extensions import docparams
25
- from pylint .interfaces import IAstroidChecker , ITokenChecker
26
25
from pylint .checkers import BaseChecker , BaseTokenChecker
27
- from pylint .checkers . utils import check_messages
26
+ from pylint .checkers import utils
28
27
from pylint .checkers .raw_metrics import get_type
29
28
from pylint .constants import WarningScope
30
29
import tokenize
33
32
class DesignChecker (BaseChecker ):
34
33
"""Checks for multiple exit statements in loops"""
35
34
36
- __implements__ = (IAstroidChecker ,)
37
-
38
35
name = 'design'
39
36
msgs = {'R5101' : ('More than one exit statement for this loop' ,
40
37
'multiple-exit-statements' ,
@@ -73,12 +70,13 @@ class DesignChecker(BaseChecker):
73
70
74
71
def __init__ (self , linter = None ):
75
72
BaseChecker .__init__ (self , linter )
73
+ self .config = linter .config
76
74
self ._exit_statements = []
77
75
78
76
def visit_for (self , node ):
79
77
self ._exit_statements .append (0 )
80
78
81
- @check_messages ('use-context-manager' )
79
+ @utils . only_required_for_messages ('use-context-manager' )
82
80
def visit_attribute (self , node ):
83
81
try :
84
82
for infer in node .infer ():
@@ -94,7 +92,7 @@ def visit_attribute(self, node):
94
92
except InferenceError :
95
93
pass
96
94
97
- @check_messages ('use-context-manager' )
95
+ @utils . only_required_for_messages ('use-context-manager' )
98
96
def visit_call (self , node ):
99
97
try :
100
98
for funcdef in node .func .infer ():
@@ -108,7 +106,7 @@ def visit_call(self, node):
108
106
except InferenceError :
109
107
pass
110
108
111
- @check_messages ('bad-exit-condition' )
109
+ @utils . only_required_for_messages ('bad-exit-condition' )
112
110
def visit_while (self , node ):
113
111
self ._exit_statements .append (0 )
114
112
comparisons = None
@@ -124,7 +122,7 @@ def visit_while(self, node):
124
122
if ops [0 ] in ('!=' , '==' ):
125
123
self .add_message ('bad-exit-condition' , node = node )
126
124
127
- @check_messages ('multiple-exit-statements' )
125
+ @utils . only_required_for_messages ('multiple-exit-statements' )
128
126
def leave_for (self , node ):
129
127
if self ._exit_statements [- 1 ] > 1 :
130
128
self .add_message ('multiple-exit-statements' , node = node )
@@ -137,13 +135,14 @@ def visit_return(self, node):
137
135
visit_break = visit_return
138
136
leave_while = leave_for
139
137
140
- @check_messages ('too-many-decorators' )
138
+ @utils . only_required_for_messages ('too-many-decorators' )
141
139
def visit_functiondef (self , node ):
140
+ max_decorators = getattr (self .config , 'max_decorators' , self .options [0 ][1 ]['default' ])
142
141
if node .decorators :
143
- if len (node .decorators .nodes ) > self . config . max_decorators :
142
+ if len (node .decorators .nodes ) > max_decorators :
144
143
self .add_message ('too-many-decorators' , node = node ,
145
144
args = (len (node .decorators .nodes ),
146
- self . config . max_decorators ))
145
+ max_decorators ))
147
146
for child in node .nodes_of_class (astroid .Call ):
148
147
try :
149
148
for funcdef in child .func .infer ():
@@ -153,7 +152,7 @@ def visit_functiondef(self, node):
153
152
except :
154
153
continue
155
154
156
- @check_messages ('builtin-name-used' )
155
+ @utils . only_required_for_messages ('builtin-name-used' )
157
156
def visit_classdef (self , node ):
158
157
for name , item in node .instance_attrs .items ():
159
158
self ._check_node_name (node , item [0 ], name )
@@ -180,8 +179,6 @@ def _check_node_name(self, class_node, item, name):
180
179
class CommentMetricsChecker (BaseTokenChecker ):
181
180
"""Checks the ratio comments+docstrings/code lines by module and by function
182
181
"""
183
-
184
- __implements__ = (ITokenChecker , IAstroidChecker )
185
182
186
183
# Theses values are hardcoded in pylint (and have changed in pylint 2.12)
187
184
# We can't get them directly from the pylint lib :(
@@ -199,6 +196,7 @@ class CommentMetricsChecker(BaseTokenChecker):
199
196
{'scope' : WarningScope .NODE }
200
197
),
201
198
}
199
+
202
200
options = (('min-func-comments-ratio' ,
203
201
{'default' : 30 , 'type' : 'int' , 'metavar' : '<int>' ,
204
202
'help' : 'Minimum ratio (comments+docstrings)/code_lines for a '
@@ -215,6 +213,7 @@ class CommentMetricsChecker(BaseTokenChecker):
215
213
216
214
def __init__ (self , linter ):
217
215
BaseTokenChecker .__init__ (self , linter )
216
+ self .config = linter .config
218
217
self ._reset ()
219
218
220
219
def _reset (self ):
@@ -238,13 +237,14 @@ def process_tokens(self, tokens):
238
237
self ._stats [start_line ] = [line_type , lines_number ]
239
238
tail = start_line
240
239
241
- @check_messages ('too-few-comments' )
240
+ @utils . only_required_for_messages ('too-few-comments' )
242
241
def visit_functiondef (self , node ):
242
+ min_func_comments_ratio = getattr (self .config , 'min_func_comments_ratio' , self .options [0 ][1 ]['default' ])
243
+ min_func_size_to_check_comments = getattr (self .config , 'min_func_size_to_check_comments' , self .options [2 ][1 ]['default' ])
243
244
nb_lines = node .tolineno - node .fromlineno
244
- if nb_lines <= self . config . min_func_size_to_check_comments :
245
+ if nb_lines <= min_func_size_to_check_comments :
245
246
return
246
- func_stats = dict .fromkeys (self .LINE_TYPES ,
247
- 0 )
247
+ func_stats = dict .fromkeys (self .LINE_TYPES , 0 )
248
248
for line in sorted (self ._stats ):
249
249
if line > node .tolineno :
250
250
break
@@ -258,22 +258,22 @@ def visit_functiondef(self, node):
258
258
return
259
259
ratio = ((func_stats [self .LINE_TYPE_COMMENT ] + func_stats [self .LINE_TYPE_DOCSTRING ])
260
260
/ float (func_stats [self .LINE_TYPE_CODE ]) * 100 )
261
- if ratio < self . config . min_func_comments_ratio :
261
+ if ratio < min_func_comments_ratio :
262
262
self .add_message ('too-few-comments' , node = node ,
263
- args = ('%.2f' % ratio ,
264
- self .config .min_func_comments_ratio ))
263
+ args = (f'{ ratio :.2f} ' , min_func_comments_ratio ))
265
264
266
- @check_messages ('too-few-comments' )
265
+ @utils . only_required_for_messages ('too-few-comments' )
267
266
def visit_module (self , node ):
267
+ min_module_comments_ratio = getattr (self .config , 'min_module_comments_ratio' , self .options [1 ][1 ]['default' ])
268
268
if self ._global_stats [self .LINE_TYPE_CODE ] <= 0 :
269
269
return
270
270
ratio = ((self ._global_stats [self .LINE_TYPE_COMMENT ] +
271
271
self ._global_stats [self .LINE_TYPE_DOCSTRING ]) /
272
272
float (self ._global_stats [self .LINE_TYPE_CODE ]) * 100 )
273
- if ratio < self . config . min_module_comments_ratio :
273
+ if ratio < min_module_comments_ratio :
274
274
self .add_message ('too-few-comments' , node = node ,
275
- args = ('% .2f' % ratio ,
276
- self . config . min_module_comments_ratio ))
275
+ args = (f' { ratio : .2f} ' , min_module_comments_ratio ))
276
+
277
277
278
278
def leave_module (self , node ):
279
279
self ._reset ()
@@ -346,7 +346,7 @@ def visitFunctionDef(self, node):
346
346
pathnode = self ._append_node (node )
347
347
self .tail = pathnode
348
348
self .dispatch_list (node .body )
349
- bottom = "%s" % self ._bottom_counter
349
+ bottom = f" { self ._bottom_counter } "
350
350
self ._bottom_counter += 1
351
351
self .graph .connect (self .tail , bottom )
352
352
self .graph .connect (node , bottom )
@@ -355,7 +355,7 @@ def visitFunctionDef(self, node):
355
355
self .graph = PathGraph (node )
356
356
self .tail = node
357
357
self .dispatch_list (node .body )
358
- self .graphs ["%s%s" % ( self .classname , node .name ) ] = self .graph
358
+ self .graphs [f" { self .classname } { node .name } " ] = self .graph
359
359
self .reset ()
360
360
361
361
def visitClassDef (self , node ):
@@ -373,17 +373,17 @@ def visitSimpleStatement(self, node):
373
373
visitExpr = visitSimpleStatement
374
374
375
375
def visitIf (self , node ):
376
- name = "If %d" % node .lineno
376
+ name = f "If { node .lineno } "
377
377
self ._subgraph (node , name )
378
378
379
379
def visitLoop (self , node ):
380
- name = "Loop %d" % node .lineno
380
+ name = f "Loop { node .lineno } "
381
381
self ._subgraph (node , name )
382
382
383
383
visitFor = visitWhile = visitLoop
384
384
385
385
def visitTryExcept (self , node ):
386
- name = "TryExcept %d" % node .lineno
386
+ name = f "TryExcept { node .lineno } "
387
387
self ._subgraph (node , name , extra_blocks = node .handlers )
388
388
389
389
visitTry = visitTryExcept
@@ -405,7 +405,7 @@ def _subgraph(self, node, name, extra_blocks=()):
405
405
# global loop
406
406
self .graph = PathGraph (node )
407
407
self ._subgraph_parse (node , extra_blocks )
408
- self .graphs ["%s%s" % ( self .classname , name ) ] = self .graph
408
+ self .graphs [f" { self .classname } { name } " ] = self .graph
409
409
self .reset ()
410
410
else :
411
411
self ._append_node (node )
@@ -428,7 +428,7 @@ def _subgraph_parse(self, node, extra_blocks):
428
428
else :
429
429
loose_ends .append (node )
430
430
if node :
431
- bottom = "%s" % self ._bottom_counter
431
+ bottom = f" { self ._bottom_counter } "
432
432
self ._bottom_counter += 1
433
433
for le in loose_ends :
434
434
self .graph .connect (le , bottom )
@@ -438,8 +438,6 @@ def _subgraph_parse(self, node, extra_blocks):
438
438
class McCabeChecker (BaseChecker ):
439
439
"""Checks for functions or methods having a high McCabe number"""
440
440
441
- __implements__ = (IAstroidChecker ,)
442
-
443
441
name = 'mccabe'
444
442
msgs = {'R5301' : ('Too high cyclomatic complexity (mccabe %d/%d)' ,
445
443
'too-high-complexity' ,
@@ -462,30 +460,33 @@ class McCabeChecker(BaseChecker):
462
460
463
461
def __init__ (self , linter = None ):
464
462
BaseChecker .__init__ (self , linter )
463
+ self .config = linter .config
465
464
self .simplified_mccabe_number = []
466
465
467
- @check_messages ('too-high-complexity' )
466
+ @utils . only_required_for_messages ('too-high-complexity' )
468
467
def visit_module (self , node ):
468
+ max_mccabe_number = getattr (self .config , 'max_mccabe_number' , self .options [0 ][1 ]['default' ])
469
469
visitor = McCabeASTVisitor ()
470
470
for child in node .body :
471
471
visitor .preorder (child , visitor )
472
472
for graph in visitor .graphs .values ():
473
473
complexity = graph .complexity ()
474
- if complexity > self . config . max_mccabe_number :
474
+ if complexity > max_mccabe_number :
475
475
self .add_message ('too-high-complexity' , node = graph .root ,
476
476
args = (complexity ,
477
- self . config . max_mccabe_number ))
477
+ max_mccabe_number ))
478
478
479
479
def visit_functiondef (self , node ):
480
480
self .simplified_mccabe_number .append (0 )
481
481
482
- @check_messages ('max-simplified-mccabe-number' )
482
+ @utils . only_required_for_messages ('max-simplified-mccabe-number' )
483
483
def leave_functiondef (self , node ):
484
+ max_simplified_mccabe_number = getattr (self .config , 'max_simplified_mccabe_number' , self .options [1 ][1 ]['default' ])
484
485
complexity = self .simplified_mccabe_number .pop ()
485
- if complexity > self . config . max_simplified_mccabe_number :
486
+ if complexity > max_simplified_mccabe_number :
486
487
self .add_message ('too-high-complexity-simplified' , node = node ,
487
488
args = (complexity ,
488
- self . config . max_simplified_mccabe_number ))
489
+ max_simplified_mccabe_number ))
489
490
490
491
def visit_while (self , node ):
491
492
if self .simplified_mccabe_number :
@@ -514,25 +515,31 @@ class SphinxDocChecker(docparams.DocstringParameterChecker):
514
515
515
516
regexp = {}
516
517
for field in ('author' , 'version' , 'date' ):
517
- regexp [field ] = (re .compile (r':%s:' % field ),
518
- re .compile (r':%s : \S+' % field ))
518
+ regexp [field ] = (re .compile (fr': { field } :' ),
519
+ re .compile (fr': { field } : \S+' ))
519
520
520
- @check_messages ('malformed-docstring-field' , 'missing-docstring-field' )
521
+ @utils . only_required_for_messages ('malformed-docstring-field' , 'missing-docstring-field' )
521
522
def visit_module (self , node ):
523
+ if not hasattr (node , 'doc' ) :
524
+ return
522
525
if not node .doc :
523
526
return
524
527
for field , expr in self .regexp .values ():
525
528
self ._check_docstring_field (node , field , expr )
526
529
527
- @check_messages ('malformed-docstring-field' , 'missing-docstring-field' )
530
+ @utils . only_required_for_messages ('malformed-docstring-field' , 'missing-docstring-field' )
528
531
def visit_classdef (self , node ):
532
+ if not hasattr (node , 'doc' ) :
533
+ return
529
534
if not node .doc :
530
535
return
531
536
self ._check_description_exists (node )
532
537
533
- @check_messages ('malformed-docstring-field' , 'missing-docstring-field' )
538
+ @utils . only_required_for_messages ('malformed-docstring-field' , 'missing-docstring-field' )
534
539
def visit_functiondef (self , node ):
535
540
super (SphinxDocChecker , self ).visit_functiondef (node )
541
+ if not hasattr (node , 'doc' ) :
542
+ return
536
543
if not node .doc :
537
544
return
538
545
self ._check_description_exists (node )
@@ -542,7 +549,7 @@ def _check_description_exists(self, node):
542
549
543
550
To do so, check the first line contains something
544
551
"""
545
- if not node . doc :
552
+ if not hasattr ( node , ' doc' ) :
546
553
return
547
554
doc_lines = [line .strip () for line in node .doc .splitlines () if line ]
548
555
if not doc_lines or doc_lines [0 ].startswith (':' ):
@@ -565,7 +572,6 @@ def _check_docstring_field(self, node, field, expr):
565
572
class ForbiddenUsageChecker (BaseChecker ):
566
573
"""Checks for use of forbidden functions or variables"""
567
574
568
- __implements__ = (IAstroidChecker ,)
569
575
name = 'forbiddenusage'
570
576
msgs = {'R5401' : ('Consider dropping use of sys.exit()' ,
571
577
'sys-exit-used' ,
@@ -619,7 +625,7 @@ def visit_module(self, node):
619
625
if self ._is_sys_exit_call (call ):
620
626
self ._authorized_exits .append (call )
621
627
622
- @check_messages ('sys-exit-used' )
628
+ @utils . only_required_for_messages ('sys-exit-used' )
623
629
def visit_call (self , node ):
624
630
self ._check_os_environ_call (node )
625
631
if not self ._is_sys_exit_call (node ):
@@ -630,14 +636,14 @@ def visit_call(self, node):
630
636
return
631
637
self .add_message ('sys-exit-used' , node = node )
632
638
633
- @check_messages ('os-environ-used' , 'sys-argv-used' )
639
+ @utils . only_required_for_messages ('os-environ-used' , 'sys-argv-used' )
634
640
def visit_attribute (self , node ):
635
641
if self ._check_access (node , ('os' , os .name ), 'environ' , astroid .Dict ):
636
642
self .add_message ('os-environ-used' , node = node , args = 'environ' )
637
643
if self ._check_access (node , ('sys' ,), 'argv' , astroid .List ):
638
644
self .add_message ('sys-argv-used' , node = node )
639
645
640
- @check_messages ('os-environ-used' , 'sys-argv-used' )
646
+ @utils . only_required_for_messages ('os-environ-used' , 'sys-argv-used' )
641
647
def visit_name (self , node ):
642
648
if self ._check_access (node , ('os' , os .name ), 'environ' , astroid .Dict ,
643
649
False ):
@@ -689,7 +695,7 @@ def _check_os_environ_call(self, node):
689
695
if (funcdef .name in ('putenv' , 'getenv' , 'unsetenv' )
690
696
and funcdef .root ().name in ('os' , os .name )):
691
697
self .add_message ('os-environ-used' , node = node ,
692
- args = '%s()' % funcdef .name )
698
+ args = f" { funcdef .name } ()" )
693
699
return
694
700
except InferenceError :
695
701
pass
0 commit comments