diff --git a/src/Console/ModelsCommand.php b/src/Console/ModelsCommand.php index 8e539d9..f3fb773 100644 --- a/src/Console/ModelsCommand.php +++ b/src/Console/ModelsCommand.php @@ -132,6 +132,10 @@ class ModelsCommand extends Command * @var array */ protected $contextCache = []; + /** + * @var array> + */ + protected $localTypeAliasCache = []; /** * @var array */ @@ -1485,12 +1489,55 @@ class ModelsCommand extends Command return $typeAlias; } + $localTypeAlias = strtok(trim($returnTag->getContent()), " \t\n\r"); + + if ($localTypeAlias !== false + && in_array($localTypeAlias, $this->getLocalTypeAliases($reflection->getDeclaringClass()), true) + ) { + return $localTypeAlias; + } + $type = $phpdoc->getTagsByName('return')[0]->getType(); } return $type; } + /** + * Get the type aliases declared or imported on the given class and its parents. + * + * These are local names rather than classes, so they must be left as-is + * instead of being resolved against the class namespace. + * + * @return array + */ + protected function getLocalTypeAliases(ReflectionClass $class): array + { + $key = $class->getName(); + + if (isset($this->localTypeAliasCache[$key])) { + return $this->localTypeAliasCache[$key]; + } + + $aliases = []; + + for ($current = $class; $current !== false; $current = $current->getParentClass()) { + if (($docComment = $current->getDocComment()) === false) { + continue; + } + + preg_match_all( + '/@(?:phpstan|psalm)-(?:import-)?type\s+([A-Za-z_\x80-\xff][A-Za-z0-9_\x80-\xff]*)/', + $docComment, + $matches + ); + + $aliases = array_merge($aliases, $matches[1]); + } + + return $this->localTypeAliasCache[$key] = array_values(array_unique($aliases)); + } + protected function getDocBlockContext(\Reflector $reflector): Context { if ($reflector instanceof \ReflectionMethod) { diff --git a/tests/Console/ModelsCommand/PhpstanImportType/Models/Shapes.php b/tests/Console/ModelsCommand/PhpstanImportType/Models/Shapes.php new file mode 100644 index 0000000..351ec5f --- /dev/null +++ b/tests/Console/ModelsCommand/PhpstanImportType/Models/Shapes.php @@ -0,0 +1,12 @@ + 'Taylor', 'age' => 40]; + } + + /** + * @phpstan-return LocalShape + */ + public function getLocalAttribute(): array + { + return []; + } +} diff --git a/tests/Console/ModelsCommand/PhpstanImportType/Test.php b/tests/Console/ModelsCommand/PhpstanImportType/Test.php new file mode 100644 index 0000000..342cebf --- /dev/null +++ b/tests/Console/ModelsCommand/PhpstanImportType/Test.php @@ -0,0 +1,27 @@ +app->make(ModelsCommand::class); + + $tester = $this->runCommand($command, ['--write' => true]); + + $this->assertSame(0, $tester->getStatusCode()); + $this->assertMatchesMockedSnapshot(); + } +} diff --git a/tests/Console/ModelsCommand/PhpstanImportType/__snapshots__/Test__testImportedTypeIsNotQualifiedWithTheModelNamespace__1.php b/tests/Console/ModelsCommand/PhpstanImportType/__snapshots__/Test__testImportedTypeIsNotQualifiedWithTheModelNamespace__1.php new file mode 100644 index 0000000..9e7ab40 --- /dev/null +++ b/tests/Console/ModelsCommand/PhpstanImportType/__snapshots__/Test__testImportedTypeIsNotQualifiedWithTheModelNamespace__1.php @@ -0,0 +1,37 @@ +|Simple newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Simple query() + * @method static \Illuminate\Database\Eloquent\Builder|Simple whereId($value) + * @mixin \Eloquent + */ +class Simple extends Model +{ + /** + * @return ArrayShape + */ + public function getSomeArrayAttribute(): array + { + return ['name' => 'Taylor', 'age' => 40]; + } + + /** + * @phpstan-return LocalShape + */ + public function getLocalAttribute(): array + { + return []; + } +}