Skip to content

Commit e13d332

Browse files
authored
Merge pull request #17 from cnescatlab/update_support_pylint3
Update support pylint3
2 parents 85924d3 + 0ac5269 commit e13d332

File tree

4 files changed

+64
-58
lines changed

4 files changed

+64
-58
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ These are mostly guidelines, not rules. Use your best judgment, and feel free to
1414
[Git Commit Messages](#git-commit-messages)
1515

1616
## Code of Conduct
17-
This project and everyone participating in it is governed by the [Lequal Code of Conduct](CODE_OF_CONDUCT.md).
18-
By participating, you are expected to uphold this code. Please report unacceptable behavior to [[email protected]](mailto:[email protected]).
17+
This project and everyone participating in it is governed by the [Lequal Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.
1918

2019
## How Can I Contribute?
2120

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ cnes-pylint-extension checks the following metrics :
2828
- Version 4.0 - compatible pylint 2.1.1
2929
- Version 5.0 - compatible pylint >=2.5.0,<2.12.0
3030
- Version 6.0 - compatible pylint >=2.12,<3.0.0
31-
- **warning**: At 6.0.0 release, latest pylint was 2.13.5. If you encounter issue with pylint>2.13.5 and <3.0.0 please open an issue.
31+
- Version 7.0 - compatible pylint >=3.0.0,<4.0.0
32+
- **warning**: At 7.0.0 release, latest pylint was 3.0.3. If you encounter issue with pylint>3.0.3 and <4.0.0 please open an issue.
3233

3334
# To use these checkers:
3435

@@ -39,7 +40,7 @@ cnes-pylint-extension checks the following metrics :
3940

4041
### Install Pylint
4142

42-
`pip install pylint==2.13.5`
43+
`pip install "pylint>=3.0.0,<4.0.0"`
4344

4445
### Install CNES Pylint extension checkers
4546

checkers/cnes_checker/cnes_checker.py

Lines changed: 57 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@
2222
import astroid
2323
from astroid.exceptions import InferenceError
2424
from pylint.extensions import docparams
25-
from pylint.interfaces import IAstroidChecker, ITokenChecker
2625
from pylint.checkers import BaseChecker, BaseTokenChecker
27-
from pylint.checkers.utils import check_messages
26+
from pylint.checkers import utils
2827
from pylint.checkers.raw_metrics import get_type
2928
from pylint.constants import WarningScope
3029
import tokenize
@@ -33,8 +32,6 @@
3332
class DesignChecker(BaseChecker):
3433
"""Checks for multiple exit statements in loops"""
3534

36-
__implements__ = (IAstroidChecker,)
37-
3835
name = 'design'
3936
msgs = {'R5101': ('More than one exit statement for this loop',
4037
'multiple-exit-statements',
@@ -73,12 +70,13 @@ class DesignChecker(BaseChecker):
7370

7471
def __init__(self, linter=None):
7572
BaseChecker.__init__(self, linter)
73+
self.config = linter.config
7674
self._exit_statements = []
7775

7876
def visit_for(self, node):
7977
self._exit_statements.append(0)
8078

81-
@check_messages('use-context-manager')
79+
@utils.only_required_for_messages('use-context-manager')
8280
def visit_attribute(self, node):
8381
try:
8482
for infer in node.infer():
@@ -94,7 +92,7 @@ def visit_attribute(self, node):
9492
except InferenceError:
9593
pass
9694

97-
@check_messages('use-context-manager')
95+
@utils.only_required_for_messages('use-context-manager')
9896
def visit_call(self, node):
9997
try:
10098
for funcdef in node.func.infer():
@@ -108,7 +106,7 @@ def visit_call(self, node):
108106
except InferenceError:
109107
pass
110108

111-
@check_messages('bad-exit-condition')
109+
@utils.only_required_for_messages('bad-exit-condition')
112110
def visit_while(self, node):
113111
self._exit_statements.append(0)
114112
comparisons = None
@@ -124,7 +122,7 @@ def visit_while(self, node):
124122
if ops[0] in ('!=', '=='):
125123
self.add_message('bad-exit-condition', node=node)
126124

127-
@check_messages('multiple-exit-statements')
125+
@utils.only_required_for_messages('multiple-exit-statements')
128126
def leave_for(self, node):
129127
if self._exit_statements[-1] > 1:
130128
self.add_message('multiple-exit-statements', node=node)
@@ -137,13 +135,14 @@ def visit_return(self, node):
137135
visit_break = visit_return
138136
leave_while = leave_for
139137

140-
@check_messages('too-many-decorators')
138+
@utils.only_required_for_messages('too-many-decorators')
141139
def visit_functiondef(self, node):
140+
max_decorators = getattr(self.config, 'max_decorators', self.options[0][1]['default'])
142141
if node.decorators:
143-
if len(node.decorators.nodes) > self.config.max_decorators:
142+
if len(node.decorators.nodes) > max_decorators:
144143
self.add_message('too-many-decorators', node=node,
145144
args=(len(node.decorators.nodes),
146-
self.config.max_decorators))
145+
max_decorators))
147146
for child in node.nodes_of_class(astroid.Call):
148147
try:
149148
for funcdef in child.func.infer():
@@ -153,7 +152,7 @@ def visit_functiondef(self, node):
153152
except:
154153
continue
155154

156-
@check_messages('builtin-name-used')
155+
@utils.only_required_for_messages('builtin-name-used')
157156
def visit_classdef(self, node):
158157
for name, item in node.instance_attrs.items():
159158
self._check_node_name(node, item[0], name)
@@ -180,8 +179,6 @@ def _check_node_name(self, class_node, item, name):
180179
class CommentMetricsChecker(BaseTokenChecker):
181180
"""Checks the ratio comments+docstrings/code lines by module and by function
182181
"""
183-
184-
__implements__ = (ITokenChecker, IAstroidChecker)
185182

186183
# Theses values are hardcoded in pylint (and have changed in pylint 2.12)
187184
# We can't get them directly from the pylint lib :(
@@ -199,6 +196,7 @@ class CommentMetricsChecker(BaseTokenChecker):
199196
{'scope': WarningScope.NODE}
200197
),
201198
}
199+
202200
options = (('min-func-comments-ratio',
203201
{'default': 30, 'type': 'int', 'metavar': '<int>',
204202
'help': 'Minimum ratio (comments+docstrings)/code_lines for a '
@@ -215,6 +213,7 @@ class CommentMetricsChecker(BaseTokenChecker):
215213

216214
def __init__(self, linter):
217215
BaseTokenChecker.__init__(self, linter)
216+
self.config = linter.config
218217
self._reset()
219218

220219
def _reset(self):
@@ -238,13 +237,14 @@ def process_tokens(self, tokens):
238237
self._stats[start_line] = [line_type, lines_number]
239238
tail = start_line
240239

241-
@check_messages('too-few-comments')
240+
@utils.only_required_for_messages('too-few-comments')
242241
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'])
243244
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:
245246
return
246-
func_stats = dict.fromkeys(self.LINE_TYPES,
247-
0)
247+
func_stats = dict.fromkeys(self.LINE_TYPES, 0)
248248
for line in sorted(self._stats):
249249
if line > node.tolineno:
250250
break
@@ -258,22 +258,22 @@ def visit_functiondef(self, node):
258258
return
259259
ratio = ((func_stats[self.LINE_TYPE_COMMENT] + func_stats[self.LINE_TYPE_DOCSTRING])
260260
/ float(func_stats[self.LINE_TYPE_CODE]) * 100)
261-
if ratio < self.config.min_func_comments_ratio:
261+
if ratio < min_func_comments_ratio:
262262
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))
265264

266-
@check_messages('too-few-comments')
265+
@utils.only_required_for_messages('too-few-comments')
267266
def visit_module(self, node):
267+
min_module_comments_ratio = getattr(self.config, 'min_module_comments_ratio', self.options[1][1]['default'])
268268
if self._global_stats[self.LINE_TYPE_CODE] <= 0:
269269
return
270270
ratio = ((self._global_stats[self.LINE_TYPE_COMMENT] +
271271
self._global_stats[self.LINE_TYPE_DOCSTRING]) /
272272
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:
274274
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+
277277

278278
def leave_module(self, node):
279279
self._reset()
@@ -346,7 +346,7 @@ def visitFunctionDef(self, node):
346346
pathnode = self._append_node(node)
347347
self.tail = pathnode
348348
self.dispatch_list(node.body)
349-
bottom = "%s" % self._bottom_counter
349+
bottom = f"{self._bottom_counter}"
350350
self._bottom_counter += 1
351351
self.graph.connect(self.tail, bottom)
352352
self.graph.connect(node, bottom)
@@ -355,7 +355,7 @@ def visitFunctionDef(self, node):
355355
self.graph = PathGraph(node)
356356
self.tail = node
357357
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
359359
self.reset()
360360

361361
def visitClassDef(self, node):
@@ -373,17 +373,17 @@ def visitSimpleStatement(self, node):
373373
visitExpr = visitSimpleStatement
374374

375375
def visitIf(self, node):
376-
name = "If %d" % node.lineno
376+
name = f"If {node.lineno}"
377377
self._subgraph(node, name)
378378

379379
def visitLoop(self, node):
380-
name = "Loop %d" % node.lineno
380+
name = f"Loop {node.lineno}"
381381
self._subgraph(node, name)
382382

383383
visitFor = visitWhile = visitLoop
384384

385385
def visitTryExcept(self, node):
386-
name = "TryExcept %d" % node.lineno
386+
name = f"TryExcept {node.lineno}"
387387
self._subgraph(node, name, extra_blocks=node.handlers)
388388

389389
visitTry = visitTryExcept
@@ -405,7 +405,7 @@ def _subgraph(self, node, name, extra_blocks=()):
405405
# global loop
406406
self.graph = PathGraph(node)
407407
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
409409
self.reset()
410410
else:
411411
self._append_node(node)
@@ -428,7 +428,7 @@ def _subgraph_parse(self, node, extra_blocks):
428428
else:
429429
loose_ends.append(node)
430430
if node:
431-
bottom = "%s" % self._bottom_counter
431+
bottom = f"{self._bottom_counter}"
432432
self._bottom_counter += 1
433433
for le in loose_ends:
434434
self.graph.connect(le, bottom)
@@ -438,8 +438,6 @@ def _subgraph_parse(self, node, extra_blocks):
438438
class McCabeChecker(BaseChecker):
439439
"""Checks for functions or methods having a high McCabe number"""
440440

441-
__implements__ = (IAstroidChecker,)
442-
443441
name = 'mccabe'
444442
msgs = {'R5301': ('Too high cyclomatic complexity (mccabe %d/%d)',
445443
'too-high-complexity',
@@ -462,30 +460,33 @@ class McCabeChecker(BaseChecker):
462460

463461
def __init__(self, linter=None):
464462
BaseChecker.__init__(self, linter)
463+
self.config = linter.config
465464
self.simplified_mccabe_number = []
466465

467-
@check_messages('too-high-complexity')
466+
@utils.only_required_for_messages('too-high-complexity')
468467
def visit_module(self, node):
468+
max_mccabe_number = getattr(self.config, 'max_mccabe_number', self.options[0][1]['default'])
469469
visitor = McCabeASTVisitor()
470470
for child in node.body:
471471
visitor.preorder(child, visitor)
472472
for graph in visitor.graphs.values():
473473
complexity = graph.complexity()
474-
if complexity > self.config.max_mccabe_number:
474+
if complexity > max_mccabe_number:
475475
self.add_message('too-high-complexity', node=graph.root,
476476
args=(complexity,
477-
self.config.max_mccabe_number))
477+
max_mccabe_number))
478478

479479
def visit_functiondef(self, node):
480480
self.simplified_mccabe_number.append(0)
481481

482-
@check_messages('max-simplified-mccabe-number')
482+
@utils.only_required_for_messages('max-simplified-mccabe-number')
483483
def leave_functiondef(self, node):
484+
max_simplified_mccabe_number = getattr(self.config, 'max_simplified_mccabe_number', self.options[1][1]['default'])
484485
complexity = self.simplified_mccabe_number.pop()
485-
if complexity > self.config.max_simplified_mccabe_number:
486+
if complexity > max_simplified_mccabe_number:
486487
self.add_message('too-high-complexity-simplified', node=node,
487488
args=(complexity,
488-
self.config.max_simplified_mccabe_number))
489+
max_simplified_mccabe_number))
489490

490491
def visit_while(self, node):
491492
if self.simplified_mccabe_number:
@@ -514,25 +515,31 @@ class SphinxDocChecker(docparams.DocstringParameterChecker):
514515

515516
regexp = {}
516517
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+'))
519520

520-
@check_messages('malformed-docstring-field', 'missing-docstring-field')
521+
@utils.only_required_for_messages('malformed-docstring-field', 'missing-docstring-field')
521522
def visit_module(self, node):
523+
if not hasattr(node, 'doc') :
524+
return
522525
if not node.doc:
523526
return
524527
for field, expr in self.regexp.values():
525528
self._check_docstring_field(node, field, expr)
526529

527-
@check_messages('malformed-docstring-field', 'missing-docstring-field')
530+
@utils.only_required_for_messages('malformed-docstring-field', 'missing-docstring-field')
528531
def visit_classdef(self, node):
532+
if not hasattr(node, 'doc') :
533+
return
529534
if not node.doc:
530535
return
531536
self._check_description_exists(node)
532537

533-
@check_messages('malformed-docstring-field', 'missing-docstring-field')
538+
@utils.only_required_for_messages('malformed-docstring-field', 'missing-docstring-field')
534539
def visit_functiondef(self, node):
535540
super(SphinxDocChecker, self).visit_functiondef(node)
541+
if not hasattr(node, 'doc') :
542+
return
536543
if not node.doc:
537544
return
538545
self._check_description_exists(node)
@@ -542,7 +549,7 @@ def _check_description_exists(self, node):
542549
543550
To do so, check the first line contains something
544551
"""
545-
if not node.doc:
552+
if not hasattr(node, 'doc') :
546553
return
547554
doc_lines = [line.strip() for line in node.doc.splitlines() if line]
548555
if not doc_lines or doc_lines[0].startswith(':'):
@@ -565,7 +572,6 @@ def _check_docstring_field(self, node, field, expr):
565572
class ForbiddenUsageChecker(BaseChecker):
566573
"""Checks for use of forbidden functions or variables"""
567574

568-
__implements__ = (IAstroidChecker,)
569575
name = 'forbiddenusage'
570576
msgs = {'R5401': ('Consider dropping use of sys.exit()',
571577
'sys-exit-used',
@@ -619,7 +625,7 @@ def visit_module(self, node):
619625
if self._is_sys_exit_call(call):
620626
self._authorized_exits.append(call)
621627

622-
@check_messages('sys-exit-used')
628+
@utils.only_required_for_messages('sys-exit-used')
623629
def visit_call(self, node):
624630
self._check_os_environ_call(node)
625631
if not self._is_sys_exit_call(node):
@@ -630,14 +636,14 @@ def visit_call(self, node):
630636
return
631637
self.add_message('sys-exit-used', node=node)
632638

633-
@check_messages('os-environ-used', 'sys-argv-used')
639+
@utils.only_required_for_messages('os-environ-used', 'sys-argv-used')
634640
def visit_attribute(self, node):
635641
if self._check_access(node, ('os', os.name), 'environ', astroid.Dict):
636642
self.add_message('os-environ-used', node=node, args='environ')
637643
if self._check_access(node, ('sys',), 'argv', astroid.List):
638644
self.add_message('sys-argv-used', node=node)
639645

640-
@check_messages('os-environ-used', 'sys-argv-used')
646+
@utils.only_required_for_messages('os-environ-used', 'sys-argv-used')
641647
def visit_name(self, node):
642648
if self._check_access(node, ('os', os.name), 'environ', astroid.Dict,
643649
False):
@@ -689,7 +695,7 @@ def _check_os_environ_call(self, node):
689695
if (funcdef.name in ('putenv', 'getenv', 'unsetenv')
690696
and funcdef.root().name in('os', os.name)):
691697
self.add_message('os-environ-used', node=node,
692-
args='%s()' % funcdef.name)
698+
args=f"{funcdef.name}()")
693699
return
694700
except InferenceError:
695701
pass

0 commit comments

Comments
 (0)