Skip to content

Commit 1863240

Browse files
[mccabe] Remove unused code that was used to draw graphics originally
1 parent b91143b commit 1863240

File tree

2 files changed

+75
-139
lines changed

2 files changed

+75
-139
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The dependency to mccabe was removed and its content is now vendored in pylint. Optimization were done as a result
2+
because mccabe was a code to dot graph generator and pylint only need to calculate the mccabe score not draw graphics.
3+
4+
Refs #10551

pylint/extensions/mccabe.py

Lines changed: 71 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
33
# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
44

5-
# mypy: ignore-errors
6-
# pylint: disable=unused-argument,consider-using-generator
7-
85
"""Module to add McCabe checker class for pylint.
96
107
Based on:
@@ -15,8 +12,7 @@
1512

1613
from __future__ import annotations
1714

18-
from collections.abc import Sequence
19-
from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar
15+
from typing import TYPE_CHECKING, Any
2016

2117
from astroid import nodes
2218

@@ -28,51 +24,24 @@
2824
from pylint.lint import PyLinter
2925

3026

31-
_StatementNodes: TypeAlias = (
32-
nodes.Assert
33-
| nodes.Assign
34-
| nodes.AugAssign
35-
| nodes.Delete
36-
| nodes.Raise
37-
| nodes.Yield
38-
| nodes.Import
39-
| nodes.Call
40-
| nodes.Subscript
41-
| nodes.Pass
42-
| nodes.Continue
43-
| nodes.Break
44-
| nodes.Global
45-
| nodes.Return
46-
| nodes.Expr
47-
| nodes.Await
48-
)
49-
50-
_SubGraphNodes: TypeAlias = nodes.If | nodes.Try | nodes.For | nodes.While | nodes.Match
51-
_AppendableNodeT = TypeVar(
52-
"_AppendableNodeT", bound=_StatementNodes | nodes.While | nodes.FunctionDef
53-
)
54-
55-
5627
class PathGraph:
57-
def __init__(self, node: _SubGraphNodes | nodes.FunctionDef):
58-
self.name = ""
59-
self.root = node
60-
self.nodes = {}
28+
def __init__(self) -> None:
29+
self.nodes: dict[Any, list[Any]] = {}
6130

62-
def connect(self, n1, n2):
31+
def connect(self, n1: Any, n2: Any) -> None:
6332
if n1 not in self.nodes:
6433
self.nodes[n1] = []
6534
self.nodes[n1].append(n2)
6635
# Ensure that the destination node is always counted.
6736
if n2 not in self.nodes:
6837
self.nodes[n2] = []
6938

70-
def complexity(self):
39+
def complexity(self) -> int:
7140
"""Return the McCabe complexity for the graph.
7241
7342
V-E+2
7443
"""
75-
num_edges = sum([len(n) for n in self.nodes.values()])
44+
num_edges = sum(len(n) for n in self.nodes.values())
7645
num_nodes = len(self.nodes)
7746
return num_edges - num_nodes + 2
7847

@@ -83,155 +52,119 @@ class PathGraphingAstVisitor:
8352
"""
8453

8554
def __init__(self) -> None:
86-
self.classname = ""
87-
self.graphs = {}
88-
self._cache = {}
55+
self.graphs: dict[str, tuple[PathGraph, nodes.NodeNG]] = {}
8956
self._bottom_counter = 0
9057
self.graph: PathGraph | None = None
91-
self.tail = None
58+
self.tail: Any = None
9259

93-
def reset(self):
94-
self.graph = None
95-
self.tail = None
60+
def dispatch(self, node: nodes.NodeNG) -> None:
61+
meth = getattr(self, "visit" + node.__class__.__name__, self.default)
62+
meth(node)
9663

97-
def default(self, node: nodes.NodeNG, *args: Any) -> None:
64+
def default(self, node: nodes.NodeNG) -> None:
9865
for child in node.get_children():
99-
self.dispatch(child, *args)
100-
101-
def dispatch(self, node: nodes.NodeNG, *args: Any) -> Any:
102-
klass = node.__class__
103-
meth = self._cache.get(klass)
104-
if meth is None:
105-
class_name = klass.__name__
106-
meth = getattr(self, "visit" + class_name, self.default)
107-
self._cache[klass] = meth
108-
return meth(node, *args)
109-
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)
66+
self.dispatch(child)
11767

11868
def visitFunctionDef(self, node: nodes.FunctionDef) -> None:
11969
if self.graph is not None:
12070
# closure
121-
pathnode = self._append_node(node)
122-
self.tail = pathnode
123-
self.dispatch_list(node.body)
71+
self.graph.connect(self.tail, node)
72+
self.tail = node
73+
for child in node.body:
74+
self.dispatch(child)
12475
bottom = f"{self._bottom_counter}"
12576
self._bottom_counter += 1
12677
self.graph.connect(self.tail, bottom)
12778
self.graph.connect(node, bottom)
12879
self.tail = bottom
12980
else:
130-
self.graph = PathGraph(node)
81+
self.graph = PathGraph()
13182
self.tail = node
132-
self.dispatch_list(node.body)
133-
self.graphs[f"{self.classname}{node.name}"] = self.graph
134-
self.reset()
83+
for child in node.body:
84+
self.dispatch(child)
85+
self.graphs[node.name] = (self.graph, node)
86+
self.graph = None
87+
self.tail = None
13588

13689
visitAsyncFunctionDef = visitFunctionDef
13790

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-
144-
def visitSimpleStatement(self, node: _StatementNodes) -> None:
145-
self._append_node(node)
91+
def visitAssert(self, node: nodes.NodeNG) -> None:
92+
if self.tail and self.graph:
93+
self.graph.connect(self.tail, node)
94+
self.tail = node
14695

147-
visitAssert = visitAssign = visitAugAssign = visitDelete = visitRaise = (
148-
visitYield
149-
) = visitImport = visitCall = visitSubscript = visitPass = visitContinue = (
150-
visitBreak
151-
) = visitGlobal = visitReturn = visitExpr = visitAwait = visitSimpleStatement
96+
visitAssign = visitAugAssign = visitDelete = visitRaise = visitYield = (
97+
visitImport
98+
) = visitCall = visitSubscript = visitPass = visitContinue = visitBreak = (
99+
visitGlobal
100+
) = visitReturn = visitExpr = visitAwait = visitAssert
152101

153102
def visitWith(self, node: nodes.With) -> None:
154-
self._append_node(node)
155-
self.dispatch_list(node.body)
103+
if self.tail and self.graph:
104+
self.graph.connect(self.tail, node)
105+
self.tail = node
106+
for child in node.body:
107+
self.dispatch(child)
156108

157109
visitAsyncWith = visitWith
158110

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
111+
def visitFor(self, node: nodes.For | nodes.While) -> None:
112+
self._subgraph(node, node.handlers if isinstance(node, nodes.Try) else [])
164113

165-
def visitIf(self, node: nodes.If) -> None:
166-
name = f"if_{id(node)}"
167-
self._subgraph(node, name)
114+
visitAsyncFor = visitWhile = visitIf = visitFor
168115

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
116+
def visitTry(self, node: nodes.Try) -> None:
117+
self._subgraph(node, node.handlers)
174118

175119
def visitMatch(self, node: nodes.Match) -> None:
176-
self._subgraph(node, f"match_{id(node)}", node.cases)
177-
178-
def _append_node(self, node: _AppendableNodeT) -> _AppendableNodeT | None:
179-
if not self.tail or not self.graph:
180-
return None
181-
self.graph.connect(self.tail, node)
182-
self.tail = node
183-
return node
120+
self._subgraph(node, node.cases)
184121

185122
def _subgraph(
186-
self,
187-
node: _SubGraphNodes,
188-
name: str,
189-
extra_blocks: Sequence[nodes.ExceptHandler | nodes.MatchCase] = (),
123+
self, node: nodes.NodeNG, extra_blocks: list[nodes.NodeNG] | None = None
190124
) -> None:
191-
"""Create the subgraphs representing any `if`, `for` or `match` statements."""
125+
if extra_blocks is None:
126+
extra_blocks = []
192127
if self.graph is None:
193-
# global loop
194-
self.graph = PathGraph(node)
195-
self._subgraph_parse(node, node, extra_blocks)
196-
self.graphs[f"{self.classname}{name}"] = self.graph
197-
self.reset()
128+
self.graph = PathGraph()
129+
self._parse(node, extra_blocks)
130+
self.graphs[f"loop_{id(node)}"] = (self.graph, node)
131+
self.graph = None
132+
self.tail = None
198133
else:
199-
self._append_node(node)
200-
self._subgraph_parse(node, node, extra_blocks)
201-
202-
def _subgraph_parse(
203-
self,
204-
node: _SubGraphNodes,
205-
pathnode: _SubGraphNodes,
206-
extra_blocks: Sequence[nodes.ExceptHandler | nodes.MatchCase],
207-
) -> None:
208-
"""Parse `match`/`case` blocks, or the body and `else` block of `if`/`for`
209-
statements.
210-
"""
134+
if self.tail:
135+
self.graph.connect(self.tail, node)
136+
self.tail = node
137+
self._parse(node, extra_blocks)
138+
139+
def _parse(self, node: nodes.NodeNG, extra_blocks: list[nodes.NodeNG]) -> None:
211140
loose_ends = []
212141
if isinstance(node, nodes.Match):
213142
for case in extra_blocks:
214143
if isinstance(case, nodes.MatchCase):
215144
self.tail = node
216-
self.dispatch_list(case.body)
145+
for child in case.body:
146+
self.dispatch(child)
217147
loose_ends.append(self.tail)
218148
loose_ends.append(node)
219149
else:
220150
self.tail = node
221-
self.dispatch_list(node.body)
151+
for child in node.body:
152+
self.dispatch(child)
222153
loose_ends.append(self.tail)
223154
for extra in extra_blocks:
224155
self.tail = node
225-
self.dispatch_list(extra.body)
156+
for child in extra.body:
157+
self.dispatch(child)
226158
loose_ends.append(self.tail)
227-
if node.orelse:
159+
if hasattr(node, "orelse") and node.orelse:
228160
self.tail = node
229-
self.dispatch_list(node.orelse)
161+
for child in node.orelse:
162+
self.dispatch(child)
230163
loose_ends.append(self.tail)
231164
else:
232165
loose_ends.append(node)
233166

234-
if node and self.graph:
167+
if self.graph:
235168
bottom = f"{self._bottom_counter}"
236169
self._bottom_counter += 1
237170
for end in loose_ends:
@@ -267,16 +200,15 @@ class McCabeMethodChecker(checkers.BaseChecker):
267200
)
268201

269202
@only_required_for_messages("too-complex")
270-
def visit_module(self, node: nodes.Module) -> None:
203+
def visit_module(self, module: nodes.Module) -> None:
271204
"""Visit an astroid.Module node to check too complex rating and
272205
add message if is greater than max_complexity stored from options.
273206
"""
274207
visitor = PathGraphingAstVisitor()
275-
for child in node.body:
276-
visitor.preorder(child, visitor)
277-
for graph in visitor.graphs.values():
208+
for child in module.body:
209+
visitor.dispatch(child)
210+
for graph, node in visitor.graphs.values():
278211
complexity = graph.complexity()
279-
node = graph.root
280212
if hasattr(node, "name"):
281213
node_name = f"'{node.name}'"
282214
else:

0 commit comments

Comments
 (0)