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