Skip to content

Commit b91143b

Browse files
Merge mccabe to what is in pylint, remove unused code
1 parent 8710bbe commit b91143b

File tree

1 file changed

+65
-217
lines changed

1 file changed

+65
-217
lines changed

pylint/extensions/mccabe.py

Lines changed: 65 additions & 217 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
44

55
# mypy: ignore-errors
6-
# pylint: disable=consider-using-f-string,inconsistent-return-statements,consider-using-generator,redefined-builtin
7-
# pylint: disable=super-with-arguments,too-many-function-args,bad-super-call
6+
# pylint: disable=unused-argument,consider-using-generator
87

98
"""Module to add McCabe checker class for pylint.
109
@@ -16,9 +15,6 @@
1615

1716
from __future__ import annotations
1817

19-
import ast
20-
from ast import iter_child_nodes
21-
from collections import defaultdict
2218
from collections.abc import Sequence
2319
from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar
2420

@@ -32,212 +28,6 @@
3228
from pylint.lint import PyLinter
3329

3430

35-
class ASTVisitor:
36-
"""Performs a depth-first walk of the AST."""
37-
38-
def __init__(self):
39-
self.node = None
40-
self._cache = {}
41-
42-
def default(self, node, *args):
43-
for child in iter_child_nodes(node):
44-
self.dispatch(child, *args)
45-
46-
def dispatch(self, node, *args):
47-
self.node = node
48-
klass = node.__class__
49-
meth = self._cache.get(klass)
50-
if meth is None:
51-
className = klass.__name__
52-
meth = getattr(self.visitor, "visit" + className, self.default)
53-
self._cache[klass] = meth
54-
return meth(node, *args)
55-
56-
def preorder(self, tree, visitor, *args):
57-
"""Do preorder walk of tree using visitor."""
58-
self.visitor = visitor
59-
visitor.visit = self.dispatch
60-
self.dispatch(tree, *args) # XXX *args make sense?
61-
62-
63-
class PathNode:
64-
def __init__(self, name, look="circle"):
65-
self.name = name
66-
self.look = look
67-
68-
def to_dot(self):
69-
print('node [shape=%s,label="%s"] %d;' % (self.look, self.name, self.dot_id()))
70-
71-
def dot_id(self):
72-
return id(self)
73-
74-
75-
class Mccabe_PathGraph:
76-
def __init__(self, name, entity, lineno, column=0):
77-
self.name = name
78-
self.entity = entity
79-
self.lineno = lineno
80-
self.column = column
81-
self.nodes = defaultdict(list)
82-
83-
def connect(self, n1, n2):
84-
self.nodes[n1].append(n2)
85-
# Ensure that the destination node is always counted.
86-
self.nodes[n2] = []
87-
88-
def to_dot(self):
89-
print("subgraph {")
90-
for node in self.nodes:
91-
node.to_dot()
92-
for node, nexts in self.nodes.items():
93-
for next in nexts:
94-
print("%s -- %s;" % (node.dot_id(), next.dot_id()))
95-
print("}")
96-
97-
def complexity(self):
98-
"""Return the McCabe complexity for the graph.
99-
100-
V-E+2
101-
"""
102-
num_edges = sum([len(n) for n in self.nodes.values()])
103-
num_nodes = len(self.nodes)
104-
return num_edges - num_nodes + 2
105-
106-
107-
class Mccabe_PathGraphingAstVisitor(ASTVisitor):
108-
"""A visitor for a parsed Abstract Syntax Tree which finds executable
109-
statements.
110-
"""
111-
112-
def __init__(self):
113-
super(Mccabe_PathGraphingAstVisitor, self).__init__()
114-
self.classname = ""
115-
self.graphs = {}
116-
self.reset()
117-
118-
def reset(self):
119-
self.graph = None
120-
self.tail = None
121-
122-
def dispatch_list(self, node_list):
123-
for node in node_list:
124-
self.dispatch(node)
125-
126-
def visitFunctionDef(self, node):
127-
128-
if self.classname:
129-
entity = "%s%s" % (self.classname, node.name)
130-
else:
131-
entity = node.name
132-
133-
name = "%d:%d: %r" % (node.lineno, node.col_offset, entity)
134-
135-
if self.graph is not None:
136-
# closure
137-
pathnode = self.appendPathNode(name)
138-
self.tail = pathnode
139-
self.dispatch_list(node.body)
140-
bottom = PathNode("", look="point")
141-
self.graph.connect(self.tail, bottom)
142-
self.graph.connect(pathnode, bottom)
143-
self.tail = bottom
144-
else:
145-
self.graph = PathGraph(name, entity, node.lineno, node.col_offset)
146-
pathnode = PathNode(name)
147-
self.tail = pathnode
148-
self.dispatch_list(node.body)
149-
self.graphs["%s%s" % (self.classname, node.name)] = self.graph
150-
self.reset()
151-
152-
visitAsyncFunctionDef = visitFunctionDef
153-
154-
def visitClassDef(self, node):
155-
old_classname = self.classname
156-
self.classname += node.name + "."
157-
self.dispatch_list(node.body)
158-
self.classname = old_classname
159-
160-
def appendPathNode(self, name):
161-
if not self.tail:
162-
return
163-
pathnode = PathNode(name)
164-
self.graph.connect(self.tail, pathnode)
165-
self.tail = pathnode
166-
return pathnode
167-
168-
def visitSimpleStatement(self, node):
169-
if node.lineno is None:
170-
lineno = 0
171-
else:
172-
lineno = node.lineno
173-
name = "Stmt %d" % lineno
174-
self.appendPathNode(name)
175-
176-
def default(self, node, *args):
177-
if isinstance(node, ast.stmt):
178-
self.visitSimpleStatement(node)
179-
else:
180-
super(PathGraphingAstVisitor, self).default(node, *args)
181-
182-
def visitLoop(self, node):
183-
name = "Loop %d" % node.lineno
184-
self._subgraph(node, name)
185-
186-
visitAsyncFor = visitFor = visitWhile = visitLoop
187-
188-
def visitIf(self, node):
189-
name = "If %d" % node.lineno
190-
self._subgraph(node, name)
191-
192-
def _subgraph(self, node, name, extra_blocks=()):
193-
"""Create the subgraphs representing any `if` and `for` statements."""
194-
if self.graph is None:
195-
# global loop
196-
self.graph = PathGraph(name, name, node.lineno, node.col_offset)
197-
pathnode = PathNode(name)
198-
self._subgraph_parse(node, pathnode, extra_blocks)
199-
self.graphs["%s%s" % (self.classname, name)] = self.graph
200-
self.reset()
201-
else:
202-
pathnode = self.appendPathNode(name)
203-
self._subgraph_parse(node, pathnode, extra_blocks)
204-
205-
def _subgraph_parse(self, node, pathnode, extra_blocks):
206-
"""Parse the body and any `else` block of `if` and `for` statements."""
207-
loose_ends = []
208-
self.tail = pathnode
209-
self.dispatch_list(node.body)
210-
loose_ends.append(self.tail)
211-
for extra in extra_blocks:
212-
self.tail = pathnode
213-
self.dispatch_list(extra.body)
214-
loose_ends.append(self.tail)
215-
if node.orelse:
216-
self.tail = pathnode
217-
self.dispatch_list(node.orelse)
218-
loose_ends.append(self.tail)
219-
else:
220-
loose_ends.append(pathnode)
221-
if pathnode:
222-
bottom = PathNode("", look="point")
223-
for le in loose_ends:
224-
self.graph.connect(le, bottom)
225-
self.tail = bottom
226-
227-
def visitTryExcept(self, node):
228-
name = "TryExcept %d" % node.lineno
229-
self._subgraph(node, name, extra_blocks=node.handlers)
230-
231-
visitTry = visitTryExcept
232-
233-
def visitWith(self, node):
234-
name = "With %d" % node.lineno
235-
self.appendPathNode(name)
236-
self.dispatch_list(node.body)
237-
238-
visitAsyncWith = visitWith
239-
240-
24131
_StatementNodes: TypeAlias = (
24232
nodes.Assert
24333
| nodes.Assign
@@ -263,32 +53,68 @@ def visitWith(self, node):
26353
)
26454

26555

266-
class PathGraph(Mccabe_PathGraph): # type: ignore[misc]
56+
class PathGraph:
26757
def __init__(self, node: _SubGraphNodes | nodes.FunctionDef):
268-
super().__init__(name="", entity="", lineno=1)
58+
self.name = ""
26959
self.root = node
60+
self.nodes = {}
27061

62+
def connect(self, n1, n2):
63+
if n1 not in self.nodes:
64+
self.nodes[n1] = []
65+
self.nodes[n1].append(n2)
66+
# Ensure that the destination node is always counted.
67+
if n2 not in self.nodes:
68+
self.nodes[n2] = []
69+
70+
def complexity(self):
71+
"""Return the McCabe complexity for the graph.
72+
73+
V-E+2
74+
"""
75+
num_edges = sum([len(n) for n in self.nodes.values()])
76+
num_nodes = len(self.nodes)
77+
return num_edges - num_nodes + 2
78+
79+
80+
class PathGraphingAstVisitor:
81+
"""A visitor for a parsed Abstract Syntax Tree which finds executable
82+
statements.
83+
"""
27184

272-
class PathGraphingAstVisitor(Mccabe_PathGraphingAstVisitor): # type: ignore[misc]
27385
def __init__(self) -> None:
274-
super().__init__()
86+
self.classname = ""
87+
self.graphs = {}
88+
self._cache = {}
27589
self._bottom_counter = 0
27690
self.graph: PathGraph | None = None
91+
self.tail = None
92+
93+
def reset(self):
94+
self.graph = None
95+
self.tail = None
27796

27897
def default(self, node: nodes.NodeNG, *args: Any) -> None:
27998
for child in node.get_children():
28099
self.dispatch(child, *args)
281100

282101
def dispatch(self, node: nodes.NodeNG, *args: Any) -> Any:
283-
self.node = node
284102
klass = node.__class__
285103
meth = self._cache.get(klass)
286104
if meth is None:
287105
class_name = klass.__name__
288-
meth = getattr(self.visitor, "visit" + class_name, self.default)
106+
meth = getattr(self, "visit" + class_name, self.default)
289107
self._cache[klass] = meth
290108
return meth(node, *args)
291109

110+
def preorder(self, tree, visitor):
111+
"""Do preorder walk of tree using visitor."""
112+
self.dispatch(tree)
113+
114+
def dispatch_list(self, node_list):
115+
for node in node_list:
116+
self.dispatch(node)
117+
292118
def visitFunctionDef(self, node: nodes.FunctionDef) -> None:
293119
if self.graph is not None:
294120
# closure
@@ -309,6 +135,12 @@ def visitFunctionDef(self, node: nodes.FunctionDef) -> None:
309135

310136
visitAsyncFunctionDef = visitFunctionDef
311137

138+
def visitClassDef(self, node: nodes.ClassDef) -> None:
139+
old_classname = self.classname
140+
self.classname += node.name + "."
141+
self.dispatch_list(node.body)
142+
self.classname = old_classname
143+
312144
def visitSimpleStatement(self, node: _StatementNodes) -> None:
313145
self._append_node(node)
314146

@@ -324,6 +156,22 @@ def visitWith(self, node: nodes.With) -> None:
324156

325157
visitAsyncWith = visitWith
326158

159+
def visitLoop(self, node: nodes.For | nodes.While) -> None:
160+
name = f"loop_{id(node)}"
161+
self._subgraph(node, name)
162+
163+
visitAsyncFor = visitFor = visitWhile = visitLoop
164+
165+
def visitIf(self, node: nodes.If) -> None:
166+
name = f"if_{id(node)}"
167+
self._subgraph(node, name)
168+
169+
def visitTryExcept(self, node: nodes.Try) -> None:
170+
name = f"try_{id(node)}"
171+
self._subgraph(node, name, extra_blocks=node.handlers)
172+
173+
visitTry = visitTryExcept
174+
327175
def visitMatch(self, node: nodes.Match) -> None:
328176
self._subgraph(node, f"match_{id(node)}", node.cases)
329177

0 commit comments

Comments
 (0)