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
107Based on:
1512
1613from __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
2117from astroid import nodes
2218
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-
5627class 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