diff --git a/src/Utils/ObjectHelpers.php b/src/Utils/ObjectHelpers.php index 148189c2..2bc68feb 100644 --- a/src/Utils/ObjectHelpers.php +++ b/src/Utils/ObjectHelpers.php @@ -30,7 +30,7 @@ public static function strictGet(string $class, string $name): void $rc = new \ReflectionClass($class); $hint = self::getSuggestion(array_merge( array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), - self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:(?!@)[^\s$*]+[\s*]+)*+\$(\w+)~m'), ), $name); throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); } @@ -45,7 +45,7 @@ public static function strictSet(string $class, string $name): void $rc = new \ReflectionClass($class); $hint = self::getSuggestion(array_merge( array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), - self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:(?!@)[^\s$*]+[\s*]+)*+\$(\w+)~m'), ), $name); throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); } @@ -131,7 +131,7 @@ public static function getMagicProperties(string $class): array $rc = new \ReflectionClass($class); preg_match_all( - '~^ [ \t*]* @property(|-read|-write|-deprecated) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx', + '~^ [ \t*]* @property(|-read|-write|-deprecated) [ \t]+ (?:(?!@)[^\s$*]+[\s*]+)++ \$ (\w+) ()~mx', (string) $rc->getDocComment(), $matches, PREG_SET_ORDER, diff --git a/tests/Utils/ObjectHelpers.getMagicProperites().phpt b/tests/Utils/ObjectHelpers.getMagicProperites().phpt index 3e606c7f..e09d2a75 100644 --- a/tests/Utils/ObjectHelpers.getMagicProperites().phpt +++ b/tests/Utils/ObjectHelpers.getMagicProperites().phpt @@ -16,6 +16,15 @@ require __DIR__ . '/../bootstrap.php'; * @property int $getter2 by ref * @property int $setter * @property A\B $both with typehint + * @property array $generic with space in type + * @property array> $nested + * @property array{a: int, b?: string} $shape + * @property array{ + * multi: string, + * line: int, + * } $multiline + * @property array $multilineGeneric * @property int $missing * @property int $Upper * @property int $cAse @@ -27,6 +36,9 @@ require __DIR__ . '/../bootstrap.php'; * @property-x int $invalid1 * @property $invalid2 * abc @property int $invalid3 + * @property array{ + * unterminated: string, + * @property-read int $bridged */ class TestClass { @@ -55,6 +67,41 @@ class TestClass } + public function getGeneric() + { + } + + + public function getNested() + { + } + + + public function getShape() + { + } + + + public function getMultiline() + { + } + + + public function getMultilineGeneric() + { + } + + + public function getBridged() + { + } + + + public function setBridged() + { + } + + public function getUpper() { } @@ -118,10 +165,16 @@ Assert::same([ 'getter2' => 0b0101, 'setter' => 0b1000, 'both' => 0b1011, + 'generic' => 0b0011, + 'nested' => 0b0011, + 'shape' => 0b0011, + 'multiline' => 0b0011, + 'multilineGeneric' => 0b0011, 'Upper' => 0b0011, 'protected' => 0b0011, 'read' => 0b0011, 'write' => 0b1000, + 'bridged' => 0b0011, ], ObjectHelpers::getMagicProperties('TestClass')); diff --git a/tests/Utils/ObjectHelpers.strictness.phpt b/tests/Utils/ObjectHelpers.strictness.phpt index d3f6a1a2..0503e35f 100644 --- a/tests/Utils/ObjectHelpers.strictness.phpt +++ b/tests/Utils/ObjectHelpers.strictness.phpt @@ -143,3 +143,59 @@ Assert::exception( MemberAccessException::class, 'Cannot read an undeclared property TestClass::$protectedX.', ); + + +// hints from @property annotations with whitespace inside the type +/** + * @property array $generic + * @property array{ + * foo: string, + * } $multiline + * @property-read array $readOnly + * @property-write array $writeOnly + */ +class AnnotatedClass +{ +} + +Assert::exception( + fn() => ObjectHelpers::strictGet('AnnotatedClass', 'gneric'), + MemberAccessException::class, + 'Cannot read an undeclared property AnnotatedClass::$gneric, did you mean $generic?', +); + +Assert::exception( + fn() => ObjectHelpers::strictSet('AnnotatedClass', 'gneric'), + MemberAccessException::class, + 'Cannot write to an undeclared property AnnotatedClass::$gneric, did you mean $generic?', +); + +Assert::exception( + fn() => ObjectHelpers::strictGet('AnnotatedClass', 'multilin'), + MemberAccessException::class, + 'Cannot read an undeclared property AnnotatedClass::$multilin, did you mean $multiline?', +); + +Assert::exception( + fn() => ObjectHelpers::strictGet('AnnotatedClass', 'readOnli'), + MemberAccessException::class, + 'Cannot read an undeclared property AnnotatedClass::$readOnli, did you mean $readOnly?', +); + +Assert::exception( + fn() => ObjectHelpers::strictSet('AnnotatedClass', 'writeOnli'), + MemberAccessException::class, + 'Cannot write to an undeclared property AnnotatedClass::$writeOnli, did you mean $writeOnly?', +); + +Assert::exception( + fn() => ObjectHelpers::strictSet('AnnotatedClass', 'readOnli'), + MemberAccessException::class, + 'Cannot write to an undeclared property AnnotatedClass::$readOnli.', +); + +Assert::exception( + fn() => ObjectHelpers::strictGet('AnnotatedClass', 'writeOnli'), + MemberAccessException::class, + 'Cannot read an undeclared property AnnotatedClass::$writeOnli.', +);