1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Util;
6:
7: use InvalidArgumentException;
8: use PHPStan\BetterReflection\Identifier\IdentifierType;
9: use PHPStan\BetterReflection\Reflection\Reflection;
10: use PHPStan\BetterReflection\Reflection\ReflectionClass;
11: use PHPStan\BetterReflection\Reflection\ReflectionConstant;
12: use PHPStan\BetterReflection\Reflection\ReflectionFunction;
13: use PHPStan\BetterReflection\Reflection\ReflectionMethod;
14: use PHPStan\BetterReflection\Reflector\DefaultReflector;
15: use PHPStan\BetterReflection\SourceLocator\Ast\Exception\ParseToAstFailure;
16: use PHPStan\BetterReflection\SourceLocator\Ast\Locator;
17: use PHPStan\BetterReflection\SourceLocator\Exception\InvalidFileLocation;
18: use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
19: use PHPStan\BetterReflection\SourceLocator\Type\SingleFileSourceLocator;
20: use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
21:
22: use function array_merge;
23:
24: final class FindReflectionOnLine
25: {
26: private SourceLocator $sourceLocator;
27: private Locator $astLocator;
28: public function __construct(SourceLocator $sourceLocator, Locator $astLocator)
29: {
30: $this->sourceLocator = $sourceLocator;
31: $this->astLocator = $astLocator;
32: }
33:
34: /**
35: * Find a reflection on the specified line number.
36: *
37: * Returns null if no reflections found on the line.
38: *
39: * @param non-empty-string $filename
40: *
41: * @throws InvalidFileLocation
42: * @throws ParseToAstFailure
43: * @throws InvalidArgumentException
44: * @return \PHPStan\BetterReflection\Reflection\ReflectionMethod|\PHPStan\BetterReflection\Reflection\ReflectionClass|\PHPStan\BetterReflection\Reflection\ReflectionFunction|\PHPStan\BetterReflection\Reflection\ReflectionConstant|\PHPStan\BetterReflection\Reflection\Reflection|null
45: */
46: public function __invoke(string $filename, int $lineNumber)
47: {
48: $reflections = $this->computeReflections($filename);
49:
50: foreach ($reflections as $reflection) {
51: if ($reflection instanceof ReflectionClass && $this->containsLine($reflection, $lineNumber)) {
52: foreach ($reflection->getMethods() as $method) {
53: if ($this->containsLine($method, $lineNumber)) {
54: return $method;
55: }
56: }
57:
58: return $reflection;
59: }
60:
61: if ($reflection instanceof ReflectionFunction && $this->containsLine($reflection, $lineNumber)) {
62: return $reflection;
63: }
64:
65: if ($reflection instanceof ReflectionConstant && $this->containsLine($reflection, $lineNumber)) {
66: return $reflection;
67: }
68: }
69:
70: return null;
71: }
72:
73: /**
74: * Find all class and function reflections in the specified file
75: *
76: * @param non-empty-string $filename
77: *
78: * @return list<Reflection>
79: *
80: * @throws ParseToAstFailure
81: * @throws InvalidFileLocation
82: */
83: private function computeReflections(string $filename): array
84: {
85: $singleFileSourceLocator = new SingleFileSourceLocator($filename, $this->astLocator);
86: $reflector = new DefaultReflector(new AggregateSourceLocator([$singleFileSourceLocator, $this->sourceLocator]));
87:
88: return array_merge(
89: $singleFileSourceLocator->locateIdentifiersByType($reflector, new IdentifierType(IdentifierType::IDENTIFIER_CLASS)),
90: $singleFileSourceLocator->locateIdentifiersByType($reflector, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION)),
91: $singleFileSourceLocator->locateIdentifiersByType($reflector, new IdentifierType(IdentifierType::IDENTIFIER_CONSTANT)),
92: );
93: }
94:
95: /**
96: * Check to see if the line is within the boundaries of the reflection specified.
97: * @param \PHPStan\BetterReflection\Reflection\ReflectionClass|\PHPStan\BetterReflection\Reflection\ReflectionMethod|\PHPStan\BetterReflection\Reflection\ReflectionFunction|\PHPStan\BetterReflection\Reflection\ReflectionConstant $reflection
98: */
99: private function containsLine($reflection, int $lineNumber): bool
100: {
101: return $lineNumber >= $reflection->getStartLine() && $lineNumber <= $reflection->getEndLine();
102: }
103: }
104: