Skip to content

Commit fc8c626

Browse files
Collect more information using static analysis
1 parent a877448 commit fc8c626

File tree

7 files changed

+243
-2
lines changed

7 files changed

+243
-2
lines changed

phpstan.neon

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,13 @@ parameters:
4444
message: "#^Return type \\(void\\) of method SebastianBergmann\\\\CodeCoverage\\\\StaticAnalysis\\\\IgnoredLinesFindingVisitor\\:\\:enterNode\\(\\) should be compatible with return type \\(array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null\\) of method PhpParser\\\\NodeVisitorAbstract\\:\\:enterNode\\(\\)$#"
4545
count: 1
4646
path: src/StaticAnalysis/IgnoredLinesFindingVisitor.php
47+
48+
-
49+
message: "#^Return type \\(void\\) of method SebastianBergmann\\\\CodeCoverage\\\\StaticAnalysis\\\\CodeUnitFindingVisitor\\:\\:leaveNode\\(\\) should be compatible with return type \\(array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null\\) of method PhpParser\\\\NodeVisitor\\:\\:leaveNode\\(\\)$#"
50+
count: 1
51+
path: src/StaticAnalysis/CodeUnitFindingVisitor.php
52+
53+
-
54+
message: "#^Return type \\(void\\) of method SebastianBergmann\\\\CodeCoverage\\\\StaticAnalysis\\\\CodeUnitFindingVisitor\\:\\:leaveNode\\(\\) should be compatible with return type \\(array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null\\) of method PhpParser\\\\NodeVisitorAbstract\\:\\:leaveNode\\(\\)$#"
55+
count: 1
56+
path: src/StaticAnalysis/CodeUnitFindingVisitor.php

src/StaticAnalysis/CachingFileAnalyser.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*
2525
* @phpstan-import-type CodeUnitFunctionType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
2626
* @phpstan-import-type CodeUnitMethodType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
27+
* @phpstan-import-type CodeUnitInterfaceType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
2728
* @phpstan-import-type CodeUnitClassType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
2829
* @phpstan-import-type CodeUnitTraitType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
2930
* @phpstan-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
@@ -48,6 +49,18 @@ public function __construct(string $directory, FileAnalyser $analyser, bool $use
4849
$this->ignoreDeprecatedCode = $ignoreDeprecatedCode;
4950
}
5051

52+
/**
53+
* @return array<string, CodeUnitInterfaceType>
54+
*/
55+
public function interfacesIn(string $filename): array
56+
{
57+
if (!isset($this->cache[$filename])) {
58+
$this->process($filename);
59+
}
60+
61+
return $this->cache[$filename]['interfacesIn'];
62+
}
63+
5164
/**
5265
* @return array<string, CodeUnitClassType>
5366
*/
@@ -131,6 +144,7 @@ public function process(string $filename): void
131144
}
132145

133146
$this->cache[$filename] = [
147+
'interfacesIn' => $this->analyser->interfacesIn($filename),
134148
'classesIn' => $this->analyser->classesIn($filename),
135149
'traitsIn' => $this->analyser->traitsIn($filename),
136150
'functionsIn' => $this->analyser->functionsIn($filename),

src/StaticAnalysis/CodeUnitFindingVisitor.php

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,24 @@
5050
* endLine: int,
5151
* ccn: int
5252
* }
53+
* @phpstan-type CodeUnitInterfaceType = array{
54+
* name: string,
55+
* namespacedName: string,
56+
* namespace: string,
57+
* startLine: int,
58+
* endLine: int,
59+
* parentInterfaces: list<non-empty-string>,
60+
* }
5361
* @phpstan-type CodeUnitClassType = array{
5462
* name: string,
5563
* namespacedName: string,
5664
* namespace: string,
5765
* startLine: int,
5866
* endLine: int,
59-
* methods: array<string, CodeUnitMethodType>
67+
* parentClass: ?non-empty-string,
68+
* interfaces: list<non-empty-string>,
69+
* traits: list<non-empty-string>,
70+
* methods: array<string, CodeUnitMethodType>,
6071
* }
6172
* @phpstan-type CodeUnitTraitType = array{
6273
* name: string,
@@ -69,6 +80,11 @@
6980
*/
7081
final class CodeUnitFindingVisitor extends NodeVisitorAbstract
7182
{
83+
/**
84+
* @var array<string, CodeUnitInterfaceType>
85+
*/
86+
private array $interfaces = [];
87+
7288
/**
7389
* @var array<string, CodeUnitClassType>
7490
*/
@@ -86,6 +102,10 @@ final class CodeUnitFindingVisitor extends NodeVisitorAbstract
86102

87103
public function enterNode(Node $node): void
88104
{
105+
if ($node instanceof Interface_) {
106+
$this->processInterface($node);
107+
}
108+
89109
if ($node instanceof Class_) {
90110
if ($node->isAnonymous()) {
91111
return;
@@ -117,6 +137,37 @@ public function enterNode(Node $node): void
117137
$this->processFunction($node);
118138
}
119139

140+
public function leaveNode(Node $node): void
141+
{
142+
if (!$node instanceof Class_) {
143+
return;
144+
}
145+
146+
if ($node->isAnonymous()) {
147+
return;
148+
}
149+
150+
$traits = [];
151+
152+
foreach ($node->getTraitUses() as $traitUse) {
153+
foreach ($traitUse->traits as $trait) {
154+
$traits[] = $trait->toString();
155+
}
156+
}
157+
158+
assert(isset($this->classes[$node->namespacedName->toString()]));
159+
160+
$this->classes[$node->namespacedName->toString()]['traits'] = $traits;
161+
}
162+
163+
/**
164+
* @return array<string, CodeUnitInterfaceType>
165+
*/
166+
public function interfaces(): array
167+
{
168+
return $this->interfaces;
169+
}
170+
120171
/**
121172
* @return array<string, CodeUnitClassType>
122173
*/
@@ -223,17 +274,50 @@ private function visibility(ClassMethod $node): string
223274
return 'public';
224275
}
225276

277+
private function processInterface(Interface_ $node): void
278+
{
279+
$name = $node->name->toString();
280+
$namespacedName = $node->namespacedName->toString();
281+
$parentInterfaces = [];
282+
283+
foreach ($node->extends as $parentInterface) {
284+
$parentInterfaces[] = $parentInterface->toString();
285+
}
286+
287+
$this->interfaces[$namespacedName] = [
288+
'name' => $name,
289+
'namespacedName' => $namespacedName,
290+
'namespace' => $this->namespace($namespacedName, $name),
291+
'startLine' => $node->getStartLine(),
292+
'endLine' => $node->getEndLine(),
293+
'parentInterfaces' => $parentInterfaces,
294+
];
295+
}
296+
226297
private function processClass(Class_ $node): void
227298
{
228299
$name = $node->name->toString();
229300
$namespacedName = $node->namespacedName->toString();
301+
$parentClass = null;
302+
$interfaces = [];
303+
304+
if ($node->extends instanceof Name) {
305+
$parentClass = $node->extends->toString();
306+
}
307+
308+
foreach ($node->implements as $interface) {
309+
$interfaces[] = $interface->toString();
310+
}
230311

231312
$this->classes[$namespacedName] = [
232313
'name' => $name,
233314
'namespacedName' => $namespacedName,
234315
'namespace' => $this->namespace($namespacedName, $name),
235316
'startLine' => $node->getStartLine(),
236317
'endLine' => $node->getEndLine(),
318+
'parentClass' => $parentClass,
319+
'interfaces' => $interfaces,
320+
'traits' => [],
237321
'methods' => [],
238322
];
239323
}

src/StaticAnalysis/FileAnalyser.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*
1515
* @phpstan-import-type CodeUnitFunctionType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
1616
* @phpstan-import-type CodeUnitMethodType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
17+
* @phpstan-import-type CodeUnitInterfaceType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
1718
* @phpstan-import-type CodeUnitClassType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
1819
* @phpstan-import-type CodeUnitTraitType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
1920
*
@@ -26,6 +27,11 @@
2627
*/
2728
interface FileAnalyser
2829
{
30+
/**
31+
* @return array<string, CodeUnitInterfaceType>
32+
*/
33+
public function interfacesIn(string $filename): array;
34+
2935
/**
3036
* @return array<string, CodeUnitClassType>
3137
*/

src/StaticAnalysis/ParsingFileAnalyser.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,19 @@
3434
*
3535
* @phpstan-import-type CodeUnitFunctionType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
3636
* @phpstan-import-type CodeUnitMethodType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
37+
* @phpstan-import-type CodeUnitInterfaceType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
3738
* @phpstan-import-type CodeUnitClassType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
3839
* @phpstan-import-type CodeUnitTraitType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
3940
* @phpstan-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
4041
* @phpstan-import-type LinesType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
4142
*/
4243
final class ParsingFileAnalyser implements FileAnalyser
4344
{
45+
/**
46+
* @var array<string, array<string, CodeUnitInterfaceType>>
47+
*/
48+
private array $interfaces = [];
49+
4450
/**
4551
* @var array<string, array<string, CodeUnitClassType>>
4652
*/
@@ -79,6 +85,16 @@ public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDep
7985
$this->ignoreDeprecatedCode = $ignoreDeprecatedCode;
8086
}
8187

88+
/**
89+
* @return array<string, CodeUnitInterfaceType>
90+
*/
91+
public function interfacesIn(string $filename): array
92+
{
93+
$this->analyse($filename);
94+
95+
return $this->interfaces[$filename];
96+
}
97+
8298
/**
8399
* @return array<string, CodeUnitClassType>
84100
*/
@@ -144,7 +160,7 @@ public function ignoredLinesFor(string $filename): array
144160
*/
145161
private function analyse(string $filename): void
146162
{
147-
if (isset($this->classes[$filename])) {
163+
if (isset($this->interfaces[$filename])) {
148164
return;
149165
}
150166

@@ -193,6 +209,7 @@ private function analyse(string $filename): void
193209
}
194210
// @codeCoverageIgnoreEnd
195211

212+
$this->interfaces[$filename] = $codeUnitFindingVisitor->interfaces();
196213
$this->classes[$filename] = $codeUnitFindingVisitor->classes();
197214
$this->traits[$filename] = $codeUnitFindingVisitor->traits();
198215
$this->functions[$filename] = $codeUnitFindingVisitor->functions();
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php declare(strict_types=1);
2+
namespace SebastianBergmann\CodeCoverage\StaticAnalysis;
3+
4+
interface A
5+
{
6+
}
7+
8+
interface B
9+
{
10+
}
11+
12+
interface C extends A, B
13+
{
14+
}
15+
16+
trait T
17+
{
18+
}
19+
20+
abstract class ParentClass implements C
21+
{
22+
}
23+
24+
final class ChildClass extends ParentClass implements A, B
25+
{
26+
use T;
27+
}

tests/tests/StaticAnalysis/CodeUnitFindingVisitorTest.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,89 @@ public function testHandlesFunctionOrMethodWithDisjunctiveNormalFormTypes(): voi
139139
);
140140
}
141141

142+
public function testDetailsAboutExtendedClassesImplementedInterfacesAndUsedTraitsAreAvailable(): void
143+
{
144+
$codeUnitFindingVisitor = $this->findCodeUnits(__DIR__ . '/../../_files/source_with_interfaces_classes_and_traits.php');
145+
146+
$interfaces = $codeUnitFindingVisitor->interfaces();
147+
148+
$this->assertCount(3, $interfaces);
149+
150+
$a = 'SebastianBergmann\CodeCoverage\StaticAnalysis\A';
151+
$b = 'SebastianBergmann\CodeCoverage\StaticAnalysis\B';
152+
$c = 'SebastianBergmann\CodeCoverage\StaticAnalysis\C';
153+
154+
$this->assertArrayHasKey($a, $interfaces);
155+
$this->assertArrayHasKey($b, $interfaces);
156+
$this->assertArrayHasKey($c, $interfaces);
157+
158+
$this->assertSame('A', $interfaces[$a]['name']);
159+
$this->assertSame($a, $interfaces[$a]['namespacedName']);
160+
$this->assertSame('SebastianBergmann\CodeCoverage\StaticAnalysis', $interfaces[$a]['namespace']);
161+
$this->assertSame(4, $interfaces[$a]['startLine']);
162+
$this->assertSame(6, $interfaces[$a]['endLine']);
163+
$this->assertSame([], $interfaces[$a]['parentInterfaces']);
164+
165+
$this->assertSame('B', $interfaces[$b]['name']);
166+
$this->assertSame($b, $interfaces[$b]['namespacedName']);
167+
$this->assertSame('SebastianBergmann\CodeCoverage\StaticAnalysis', $interfaces[$b]['namespace']);
168+
$this->assertSame(8, $interfaces[$b]['startLine']);
169+
$this->assertSame(10, $interfaces[$b]['endLine']);
170+
$this->assertSame([], $interfaces[$b]['parentInterfaces']);
171+
172+
$this->assertSame('C', $interfaces[$c]['name']);
173+
$this->assertSame($c, $interfaces[$c]['namespacedName']);
174+
$this->assertSame('SebastianBergmann\CodeCoverage\StaticAnalysis', $interfaces[$c]['namespace']);
175+
$this->assertSame(12, $interfaces[$c]['startLine']);
176+
$this->assertSame(14, $interfaces[$c]['endLine']);
177+
$this->assertSame([$a, $b], $interfaces[$c]['parentInterfaces']);
178+
179+
$traits = $codeUnitFindingVisitor->traits();
180+
181+
$this->assertCount(1, $traits);
182+
183+
$t = 'SebastianBergmann\CodeCoverage\StaticAnalysis\T';
184+
185+
$this->assertArrayHasKey($t, $traits);
186+
187+
$this->assertSame('T', $traits[$t]['name']);
188+
$this->assertSame($t, $traits[$t]['namespacedName']);
189+
$this->assertSame('SebastianBergmann\CodeCoverage\StaticAnalysis', $traits[$t]['namespace']);
190+
$this->assertSame(16, $traits[$t]['startLine']);
191+
$this->assertSame(18, $traits[$t]['endLine']);
192+
$this->assertSame([], $traits[$t]['methods']);
193+
194+
$classes = $codeUnitFindingVisitor->classes();
195+
196+
$this->assertCount(2, $classes);
197+
198+
$parentClass = 'SebastianBergmann\CodeCoverage\StaticAnalysis\ParentClass';
199+
$childClass = 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass';
200+
201+
$this->assertArrayHasKey($parentClass, $classes);
202+
$this->assertArrayHasKey($childClass, $classes);
203+
204+
$this->assertSame('ParentClass', $classes[$parentClass]['name']);
205+
$this->assertSame($parentClass, $classes[$parentClass]['namespacedName']);
206+
$this->assertSame('SebastianBergmann\CodeCoverage\StaticAnalysis', $classes[$parentClass]['namespace']);
207+
$this->assertSame(20, $classes[$parentClass]['startLine']);
208+
$this->assertSame(22, $classes[$parentClass]['endLine']);
209+
$this->assertNull($classes[$parentClass]['parentClass']);
210+
$this->assertSame([$c], $classes[$parentClass]['interfaces']);
211+
$this->assertSame([], $classes[$parentClass]['traits']);
212+
$this->assertSame([], $classes[$parentClass]['methods']);
213+
214+
$this->assertSame('ChildClass', $classes[$childClass]['name']);
215+
$this->assertSame($childClass, $classes[$childClass]['namespacedName']);
216+
$this->assertSame('SebastianBergmann\CodeCoverage\StaticAnalysis', $classes[$childClass]['namespace']);
217+
$this->assertSame(24, $classes[$childClass]['startLine']);
218+
$this->assertSame(27, $classes[$childClass]['endLine']);
219+
$this->assertSame($parentClass, $classes[$childClass]['parentClass']);
220+
$this->assertSame([$a, $b], $classes[$childClass]['interfaces']);
221+
$this->assertSame([$t], $classes[$childClass]['traits']);
222+
$this->assertSame([], $classes[$childClass]['methods']);
223+
}
224+
142225
private function findCodeUnits(string $filename): CodeUnitFindingVisitor
143226
{
144227
$nodes = (new ParserFactory)->createForHostVersion()->parse(

0 commit comments

Comments
 (0)