diff --git a/lib/private/AppFramework/Utility/ControllerMethodReflector.php b/lib/private/AppFramework/Utility/ControllerMethodReflector.php index 2031327dfaedc..b63be92dc3bee 100644 --- a/lib/private/AppFramework/Utility/ControllerMethodReflector.php +++ b/lib/private/AppFramework/Utility/ControllerMethodReflector.php @@ -18,6 +18,8 @@ class ControllerMethodReflector implements IControllerMethodReflector { private $types = []; private $parameters = []; private array $ranges = []; + private int $startLine = 0; + private string $file = ''; /** * @param object $object an object or classname @@ -25,6 +27,9 @@ class ControllerMethodReflector implements IControllerMethodReflector { */ public function reflect($object, string $method) { $reflection = new \ReflectionMethod($object, $method); + $this->startLine = $reflection->getStartLine(); + $this->file = $reflection->getFileName(); + $docs = $reflection->getDocComment(); if ($docs !== false) { @@ -50,7 +55,7 @@ public function reflect($object, string $method) { // extract type parameter information preg_match_all('/@param\h+(?P\w+)\h+\$(?P\w+)/', $docs, $matches); $this->types = array_combine($matches['var'], $matches['type']); - preg_match_all('/@psalm-param\h+(?P\w+)<(?P(-?\d+|min)),\h*(?P(-?\d+|max))>\h+\$(?P\w+)/', $docs, $matches); + preg_match_all('/@(?:psalm-)?param\h+(\?)?(?P\w+)<(?P(-?\d+|min)),\h*(?P(-?\d+|max))>(\|null)?\h+\$(?P\w+)/', $docs, $matches); foreach ($matches['var'] as $index => $varName) { if ($matches['type'][$index] !== 'int') { // only int ranges are possible at the moment @@ -134,4 +139,12 @@ public function getAnnotationParameter(string $name, string $key): string { return ''; } + + public function getStartLine(): int { + return $this->startLine; + } + + public function getFile(): string { + return $this->file; + } } diff --git a/tests/lib/AppFramework/Utility/ControllerMethodReflectorTest.php b/tests/lib/AppFramework/Utility/ControllerMethodReflectorTest.php index e0748d89f7f18..0d95dcac45fc6 100644 --- a/tests/lib/AppFramework/Utility/ControllerMethodReflectorTest.php +++ b/tests/lib/AppFramework/Utility/ControllerMethodReflectorTest.php @@ -43,9 +43,21 @@ public function test3() { /** * @psalm-param int<-4, 42> $rangedOne * @psalm-param int $rangedTwo + * @psalm-param int<1, 6>|null $rangedThree + * @psalm-param ?int<-70, -30> $rangedFour * @return void */ - public function test4(int $rangedOne, int $rangedTwo) { + public function test4(int $rangedOne, int $rangedTwo, ?int $rangedThree, ?int $rangedFour) { + } + + /** + * @param int<-4, 42> $rangedOne + * @param int $rangedTwo + * @param int<1, 6>|null $rangedThree + * @param ?int<-70, -30> $rangedFour + * @return void + */ + public function test5(int $rangedOne, int $rangedTwo, ?int $rangedThree, ?int $rangedFour) { } } @@ -132,9 +144,6 @@ public function testReadTypeIntAnnotations(): void { public function arguments3($a, float $b, int $c, $d) { } - /** - * @requires PHP 7 - */ public function testReadTypeIntAnnotationsScalarTypes(): void { $reader = new ControllerMethodReflector(); $reader->reflect( @@ -228,7 +237,7 @@ public function testInheritanceOverrideNoDocblock(): void { $this->assertFalse($reader->hasAnnotation('Annotation')); } - public function testRangeDetection(): void { + public function testRangeDetectionPsalm(): void { $reader = new ControllerMethodReflector(); $reader->reflect('Test\AppFramework\Utility\EndController', 'test4'); @@ -239,5 +248,34 @@ public function testRangeDetection(): void { $rangeInfo2 = $reader->getRange('rangedTwo'); $this->assertSame(PHP_INT_MIN, $rangeInfo2['min']); $this->assertSame(PHP_INT_MAX, $rangeInfo2['max']); + + $rangeInfo3 = $reader->getRange('rangedThree'); + $this->assertSame(1, $rangeInfo3['min']); + $this->assertSame(6, $rangeInfo3['max']); + + $rangeInfo3 = $reader->getRange('rangedFour'); + $this->assertSame(-70, $rangeInfo3['min']); + $this->assertSame(-30, $rangeInfo3['max']); + } + + public function testRangeDetectionNative(): void { + $reader = new ControllerMethodReflector(); + $reader->reflect('Test\AppFramework\Utility\EndController', 'test5'); + + $rangeInfo1 = $reader->getRange('rangedOne'); + $this->assertSame(-4, $rangeInfo1['min']); + $this->assertSame(42, $rangeInfo1['max']); + + $rangeInfo2 = $reader->getRange('rangedTwo'); + $this->assertSame(PHP_INT_MIN, $rangeInfo2['min']); + $this->assertSame(PHP_INT_MAX, $rangeInfo2['max']); + + $rangeInfo3 = $reader->getRange('rangedThree'); + $this->assertSame(1, $rangeInfo3['min']); + $this->assertSame(6, $rangeInfo3['max']); + + $rangeInfo3 = $reader->getRange('rangedFour'); + $this->assertSame(-70, $rangeInfo3['min']); + $this->assertSame(-30, $rangeInfo3['max']); } }