99use Phauthentic \CognitiveCodeAnalysis \PhpParser \CognitiveMetricsVisitor ;
1010use Phauthentic \CognitiveCodeAnalysis \PhpParser \CyclomaticComplexityVisitor ;
1111use Phauthentic \CognitiveCodeAnalysis \PhpParser \HalsteadMetricsVisitor ;
12+ use Phauthentic \CognitiveCodeAnalysis \PhpParser \CombinedMetricsVisitor ;
1213use PhpParser \NodeTraverserInterface ;
1314use PhpParser \Parser as PhpParser ;
1415use PhpParser \NodeTraverser ;
1516use PhpParser \Error ;
1617use PhpParser \ParserFactory ;
18+ use ReflectionClass ;
1719
1820/**
1921 *
@@ -25,6 +27,7 @@ class Parser
2527 protected CognitiveMetricsVisitor $ cognitiveMetricsVisitor ;
2628 protected CyclomaticComplexityVisitor $ cyclomaticComplexityVisitor ;
2729 protected HalsteadMetricsVisitor $ halsteadMetricsVisitor ;
30+ protected CombinedMetricsVisitor $ combinedVisitor ;
2831
2932 public function __construct (
3033 ParserFactory $ parserFactory ,
@@ -47,6 +50,10 @@ public function __construct(
4750 $ this ->halsteadMetricsVisitor = new HalsteadMetricsVisitor ();
4851 $ this ->halsteadMetricsVisitor ->setAnnotationVisitor ($ this ->annotationVisitor );
4952 $ this ->traverser ->addVisitor ($ this ->halsteadMetricsVisitor );
53+
54+ // Create the combined visitor for performance optimization
55+ $ this ->combinedVisitor = new CombinedMetricsVisitor ();
56+ $ this ->combinedVisitor ->setAnnotationVisitor ();
5057 }
5158
5259 /**
@@ -58,14 +65,35 @@ public function parse(string $code): array
5865 // First, scan for annotations to collect ignored items
5966 $ this ->scanForAnnotations ($ code );
6067
61- // Then parse for metrics
62- $ this ->traverseAbstractSyntaxTree ($ code );
68+ // Then parse for metrics using the combined visitor for better performance
69+ $ this ->traverseAbstractSyntaxTreeWithCombinedVisitor ($ code );
70+
71+ // Get all metrics before resetting
72+ $ methodMetrics = $ this ->combinedVisitor ->getMethodMetrics ();
73+ $ cyclomaticMetrics = $ this ->combinedVisitor ->getMethodComplexity ();
74+ $ halsteadMetrics = $ this ->combinedVisitor ->getHalsteadMethodMetrics ();
6375
64- $ methodMetrics = $ this -> cognitiveMetricsVisitor -> getMethodMetrics ();
65- $ this ->cognitiveMetricsVisitor -> resetValues ();
76+ // Now reset the combined visitor
77+ $ this ->combinedVisitor -> resetAll ();
6678
67- $ methodMetrics = $ this ->getCyclomaticComplexityVisitor ($ methodMetrics );
68- $ methodMetrics = $ this ->getHalsteadMetricsVisitor ($ methodMetrics );
79+ // Add cyclomatic complexity to method metrics
80+ foreach ($ cyclomaticMetrics as $ method => $ complexityData ) {
81+ if (isset ($ methodMetrics [$ method ])) {
82+ $ complexity = $ complexityData ['complexity ' ] ?? $ complexityData ;
83+ $ riskLevel = $ complexityData ['risk_level ' ] ?? $ this ->getRiskLevel ($ complexity );
84+ $ methodMetrics [$ method ]['cyclomatic_complexity ' ] = [
85+ 'complexity ' => $ complexity ,
86+ 'risk_level ' => $ riskLevel
87+ ];
88+ }
89+ }
90+
91+ // Add Halstead metrics to method metrics
92+ foreach ($ halsteadMetrics as $ method => $ metrics ) {
93+ if (isset ($ methodMetrics [$ method ])) {
94+ $ methodMetrics [$ method ]['halstead ' ] = $ metrics ;
95+ }
96+ }
6997
7098 return $ methodMetrics ;
7199 }
@@ -95,9 +123,10 @@ private function scanForAnnotations(string $code): void
95123 }
96124
97125 /**
126+ * Traverse the AST using the combined visitor for better performance.
98127 * @throws CognitiveAnalysisException
99128 */
100- private function traverseAbstractSyntaxTree (string $ code ): void
129+ private function traverseAbstractSyntaxTreeWithCombinedVisitor (string $ code ): void
101130 {
102131 try {
103132 $ ast = $ this ->parser ->parse ($ code );
@@ -109,58 +138,12 @@ private function traverseAbstractSyntaxTree(string $code): void
109138 throw new CognitiveAnalysisException ("Could not parse the code. " );
110139 }
111140
112- $ this ->traverser ->traverse ($ ast );
141+ // Create a new traverser for the combined visitor
142+ $ combinedTraverser = new NodeTraverser ();
143+ $ combinedTraverser ->addVisitor ($ this ->combinedVisitor );
144+ $ combinedTraverser ->traverse ($ ast );
113145 }
114146
115- /**
116- * @param array<string, array<string, int>> $methodMetrics
117- * @return array<string, array<string, int>>
118- */
119- private function getHalsteadMetricsVisitor (array $ methodMetrics ): array
120- {
121- $ halstead = $ this ->halsteadMetricsVisitor ->getMetrics ();
122- foreach ($ halstead ['methods ' ] as $ method => $ metrics ) {
123- // Skip ignored methods
124- if ($ this ->annotationVisitor ->isMethodIgnored ($ method )) {
125- continue ;
126- }
127- // Skip malformed method keys (ClassName::)
128- if (str_ends_with ($ method , ':: ' )) {
129- continue ;
130- }
131- // Only add Halstead metrics to methods that were processed by CognitiveMetricsVisitor
132- if (isset ($ methodMetrics [$ method ])) {
133- $ methodMetrics [$ method ]['halstead ' ] = $ metrics ;
134- }
135- }
136-
137- return $ methodMetrics ;
138- }
139-
140- /**
141- * @param array<string, array<string, int>> $methodMetrics
142- * @return array<string, array<string, int>>
143- */
144- private function getCyclomaticComplexityVisitor (array $ methodMetrics ): array
145- {
146- $ cyclomatic = $ this ->cyclomaticComplexityVisitor ->getComplexitySummary ();
147- foreach ($ cyclomatic ['methods ' ] as $ method => $ complexity ) {
148- // Skip ignored methods
149- if ($ this ->annotationVisitor ->isMethodIgnored ($ method )) {
150- continue ;
151- }
152- // Skip malformed method keys (ClassName::)
153- if (str_ends_with ($ method , ':: ' )) {
154- continue ;
155- }
156- // Only add cyclomatic complexity to methods that were processed by CognitiveMetricsVisitor
157- if (isset ($ methodMetrics [$ method ])) {
158- $ methodMetrics [$ method ]['cyclomatic_complexity ' ] = $ complexity ;
159- }
160- }
161-
162- return $ methodMetrics ;
163- }
164147
165148 /**
166149 * Get all ignored classes and methods.
@@ -191,4 +174,54 @@ public function getIgnoredMethods(): array
191174 {
192175 return $ this ->annotationVisitor ->getIgnoredMethods ();
193176 }
177+
178+ /**
179+ * Clear static caches to prevent memory leaks during long-running processes.
180+ */
181+ public function clearStaticCaches (): void
182+ {
183+ // Clear FQCN caches from all visitors
184+ $ this ->clearStaticProperty ('Phauthentic\CognitiveCodeAnalysis\PhpParser\CognitiveMetricsVisitor ' , 'fqcnCache ' );
185+ $ this ->clearStaticProperty ('Phauthentic\CognitiveCodeAnalysis\PhpParser\CyclomaticComplexityVisitor ' , 'fqcnCache ' );
186+ $ this ->clearStaticProperty ('Phauthentic\CognitiveCodeAnalysis\PhpParser\HalsteadMetricsVisitor ' , 'fqcnCache ' );
187+ $ this ->clearStaticProperty ('Phauthentic\CognitiveCodeAnalysis\PhpParser\AnnotationVisitor ' , 'fqcnCache ' );
188+
189+ // Clear regex pattern caches
190+ $ this ->clearStaticProperty ('Phauthentic\CognitiveCodeAnalysis\Business\DirectoryScanner ' , 'compiledPatterns ' );
191+ $ this ->clearStaticProperty ('Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollector ' , 'compiledPatterns ' );
192+
193+ // Clear accumulated data in visitors
194+ $ this ->combinedVisitor ->resetAllBetweenFiles ();
195+ }
196+
197+ /**
198+ * Clear a static property using reflection.
199+ */
200+ private function clearStaticProperty (string $ className , string $ propertyName ): void
201+ {
202+ try {
203+ /** @var class-string $className */
204+ $ reflection = new ReflectionClass ($ className );
205+ if ($ reflection ->hasProperty ($ propertyName )) {
206+ $ property = $ reflection ->getProperty ($ propertyName );
207+ $ property ->setAccessible (true );
208+ $ property ->setValue (null , []);
209+ }
210+ } catch (\ReflectionException $ e ) {
211+ // Ignore reflection errors
212+ }
213+ }
214+
215+ /**
216+ * Calculate risk level based on cyclomatic complexity.
217+ */
218+ private function getRiskLevel (int $ complexity ): string
219+ {
220+ return match (true ) {
221+ $ complexity <= 5 => 'low ' ,
222+ $ complexity <= 10 => 'medium ' ,
223+ $ complexity <= 15 => 'high ' ,
224+ default => 'very_high ' ,
225+ };
226+ }
194227}
0 commit comments