From f785f67ca89a1a10258268f7e85440a0235f4d39 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Sat, 15 Mar 2025 08:49:58 +0000 Subject: [PATCH 01/10] Add VariableParser and VariableContext Fixes N1ebieski/vs-code-php-parser-cli#5 --- app/Contexts/Variable.php | 22 +++++++++++++++++ app/Parsers/VariableParser.php | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 app/Contexts/Variable.php create mode 100644 app/Parsers/VariableParser.php diff --git a/app/Contexts/Variable.php b/app/Contexts/Variable.php new file mode 100644 index 0000000..4b691fc --- /dev/null +++ b/app/Contexts/Variable.php @@ -0,0 +1,22 @@ + $this->name, + ]; + } +} diff --git a/app/Parsers/VariableParser.php b/app/Parsers/VariableParser.php new file mode 100644 index 0000000..e0fd7bc --- /dev/null +++ b/app/Parsers/VariableParser.php @@ -0,0 +1,43 @@ +context->name = $node->getName(); + + if (Settings::$capturePosition) { + $range = PositionUtilities::getRangeFromPosition( + $node->getStartPosition(), + mb_strlen($node->getText()), + $node->getRoot()->getFullText(), + ); + + if (Settings::$calculatePosition !== null) { + $range = Settings::adjustPosition($range); + } + + $this->context->setPosition($range); + } + + return $this->context; + } + + public function initNewContext(): ?AbstractContext + { + return new VariableContext; + } +} From 2815bd661b30a8adbcb0db5a7067f5e8676dc94c Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Tue, 9 Sep 2025 08:42:38 +0000 Subject: [PATCH 02/10] Support for searchForVar method --- app/Contexts/Variable.php | 7 +++++-- app/Parsers/VariableParser.php | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/Contexts/Variable.php b/app/Contexts/Variable.php index 4b691fc..f62d0af 100644 --- a/app/Contexts/Variable.php +++ b/app/Contexts/Variable.php @@ -4,7 +4,9 @@ class Variable extends AbstractContext { - public ?string $name = null; + public ?string $varName = null; + + public ?string $className = null; protected bool $hasChildren = false; @@ -16,7 +18,8 @@ public function type(): string public function castToArray(): array { return [ - 'name' => $this->name, + 'name' => $this->varName, + 'className' => $this->className, ]; } } diff --git a/app/Parsers/VariableParser.php b/app/Parsers/VariableParser.php index e0fd7bc..e95b43c 100644 --- a/app/Parsers/VariableParser.php +++ b/app/Parsers/VariableParser.php @@ -6,6 +6,7 @@ use App\Contexts\Variable as VariableContext; use App\Parser\Settings; use Microsoft\PhpParser\Node\Expression\Variable; +use Microsoft\PhpParser\Node\Statement\ExpressionStatement; use Microsoft\PhpParser\PositionUtilities; class VariableParser extends AbstractParser @@ -19,6 +20,12 @@ public function parse(Variable $node) { $this->context->name = $node->getName(); + $result = $this->context->searchForVar($this->context->name); + + if (is_string($result)) { + $this->context->className = $result; + } + if (Settings::$capturePosition) { $range = PositionUtilities::getRangeFromPosition( $node->getStartPosition(), From 71b2acb3d3bb5d2418d106d7a6fce200cebf3b6c Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Tue, 9 Sep 2025 09:07:43 +0000 Subject: [PATCH 03/10] fix name to VarName --- app/Parsers/VariableParser.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Parsers/VariableParser.php b/app/Parsers/VariableParser.php index e95b43c..ffc45d5 100644 --- a/app/Parsers/VariableParser.php +++ b/app/Parsers/VariableParser.php @@ -18,9 +18,9 @@ class VariableParser extends AbstractParser public function parse(Variable $node) { - $this->context->name = $node->getName(); + $this->context->varName = $node->getName(); - $result = $this->context->searchForVar($this->context->name); + $result = $this->context->searchForVar($this->context->varName); if (is_string($result)) { $this->context->className = $result; From 9255cc2a8ab95c87108240c69a1c48f6f4dc364c Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Tue, 9 Sep 2025 09:11:27 +0000 Subject: [PATCH 04/10] fix name to varName --- app/Contexts/Variable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Contexts/Variable.php b/app/Contexts/Variable.php index f62d0af..b24912e 100644 --- a/app/Contexts/Variable.php +++ b/app/Contexts/Variable.php @@ -18,7 +18,7 @@ public function type(): string public function castToArray(): array { return [ - 'name' => $this->varName, + 'varName' => $this->varName, 'className' => $this->className, ]; } From 5135976f816a401fc8f4471bacfdbf671be4d19f Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Tue, 9 Sep 2025 09:15:46 +0000 Subject: [PATCH 05/10] revert to name --- app/Contexts/Variable.php | 4 ++-- app/Parsers/VariableParser.php | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/Contexts/Variable.php b/app/Contexts/Variable.php index b24912e..81450b0 100644 --- a/app/Contexts/Variable.php +++ b/app/Contexts/Variable.php @@ -4,7 +4,7 @@ class Variable extends AbstractContext { - public ?string $varName = null; + public ?string $name = null; public ?string $className = null; @@ -18,7 +18,7 @@ public function type(): string public function castToArray(): array { return [ - 'varName' => $this->varName, + 'name' => $this->name, 'className' => $this->className, ]; } diff --git a/app/Parsers/VariableParser.php b/app/Parsers/VariableParser.php index ffc45d5..afc4c65 100644 --- a/app/Parsers/VariableParser.php +++ b/app/Parsers/VariableParser.php @@ -6,7 +6,6 @@ use App\Contexts\Variable as VariableContext; use App\Parser\Settings; use Microsoft\PhpParser\Node\Expression\Variable; -use Microsoft\PhpParser\Node\Statement\ExpressionStatement; use Microsoft\PhpParser\PositionUtilities; class VariableParser extends AbstractParser @@ -18,9 +17,9 @@ class VariableParser extends AbstractParser public function parse(Variable $node) { - $this->context->varName = $node->getName(); + $this->context->name = $node->getName(); - $result = $this->context->searchForVar($this->context->varName); + $result = $this->context->searchForVar($this->context->name); if (is_string($result)) { $this->context->className = $result; From 9458ada7082a96c913ba471dbcda1f74b7abbcc2 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Tue, 9 Sep 2025 11:49:32 +0000 Subject: [PATCH 06/10] Support for variable doc comments --- app/Parsers/MethodDeclarationParser.php | 5 ++ app/Parsers/VariableParser.php | 108 ++++++++++++++++++++++++ composer.json | 1 + composer.lock | 96 ++++++++++----------- 4 files changed, 162 insertions(+), 48 deletions(-) diff --git a/app/Parsers/MethodDeclarationParser.php b/app/Parsers/MethodDeclarationParser.php index 7dfca17..18741df 100644 --- a/app/Parsers/MethodDeclarationParser.php +++ b/app/Parsers/MethodDeclarationParser.php @@ -17,6 +17,11 @@ public function parse(MethodDeclaration $node) { $this->context->methodName = $node->getName(); + // Every method is a new context, so we need to clear + // the previous variable contexts + // @see https://github.com/laravel/vs-code-php-parser-cli/pull/14 + VariableParser::$previousContexts = []; + return $this->context; } diff --git a/app/Parsers/VariableParser.php b/app/Parsers/VariableParser.php index afc4c65..1d29633 100644 --- a/app/Parsers/VariableParser.php +++ b/app/Parsers/VariableParser.php @@ -5,8 +5,19 @@ use App\Contexts\AbstractContext; use App\Contexts\Variable as VariableContext; use App\Parser\Settings; +use Microsoft\PhpParser\Node; use Microsoft\PhpParser\Node\Expression\Variable; +use Microsoft\PhpParser\Node\NamespaceUseClause; +use Microsoft\PhpParser\Node\Statement\NamespaceUseDeclaration; use Microsoft\PhpParser\PositionUtilities; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; +use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; +use PHPStan\PhpDocParser\Lexer\Lexer; +use PHPStan\PhpDocParser\Parser\ConstExprParser; +use PHPStan\PhpDocParser\Parser\PhpDocParser; +use PHPStan\PhpDocParser\Parser\TokenIterator; +use PHPStan\PhpDocParser\Parser\TypeParser; +use PHPStan\PhpDocParser\ParserConfig; class VariableParser extends AbstractParser { @@ -15,16 +26,111 @@ class VariableParser extends AbstractParser */ protected AbstractContext $context; + public static array $previousContexts = []; + + private function getLatestDocComment(Node $node): ?string + { + $docComment = $node->getDocCommentText(); + + if ($docComment === null && $node->getParent() !== null) { + return $this->getLatestDocComment($node->getParent()); + } + + return $docComment; + } + public function parse(Variable $node) { $this->context->name = $node->getName(); $result = $this->context->searchForVar($this->context->name); + // Firstly, we try to find the className from the method parameter if (is_string($result)) { $this->context->className = $result; } + // If the className is still not found, we try to find the className + // from the doc comment, for example: + // + // /** @var User $user */ + // Gate::allows('edit', $user); + if ($this->context->className === null) { + $docComment = $this->getLatestDocComment($node); + + if ($docComment !== null) { + $config = new ParserConfig([]); + $lexer = new Lexer($config); + $constExprParser = new ConstExprParser($config); + $typeParser = new TypeParser($config, $constExprParser); + $phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser); + + $tokens = new TokenIterator($lexer->tokenize($docComment)); + $phpDocNode = $phpDocParser->parse($tokens); + $varTagValues = $phpDocNode->getVarTagValues(); + + /** @var VarTagValueNode|null $tagValue */ + $tagValue = collect($varTagValues) + // We need to remove first character because it's always $ + ->first(fn(VarTagValueNode $valueNode) => substr($valueNode->variableName, 1) === $this->context->name); + + if ($tagValue?->type instanceof IdentifierTypeNode) { + // If the class name starts with a backslash, it's a fully qualified name + if (str_starts_with($tagValue->type->name, '\\')) { + $this->context->className = substr($tagValue->type->name, 1); + // Otherwise, it's a short name and we need to find the fully qualified name from + // the imported namespaces + } else { + $uses = []; + + foreach ($node->getRoot()->getDescendantNodes() as $node) { + if (! $node instanceof NamespaceUseDeclaration) { + continue; + } + + foreach ($node->useClauses->children ?? [] as $clause) { + if (! $clause instanceof NamespaceUseClause) { + continue; + } + + $fqcn = $clause->namespaceName->getText(); + + // If the namespace has an alias, we need to use the alias as the short name + $alias = $clause->namespaceAliasingClause + ? str($clause->namespaceAliasingClause->getText()) + ->after('as') + ->trim() + ->toString() + : str($fqcn)->explode('\\')->last(); + + // Finally, we add the short and fully qualified name to the uses array + $uses[$alias] = $fqcn; + } + } + + $this->context->className = $uses[$tagValue->type->name] ?? null; + } + } + } + } + + // If the className is still not found, we try to find the className + // from the previous variable contexts, for example: + // + // /** @var \App\Models\User $user */ + // $user = $request->user; + // + // Gate::allows('edit', $user); + if ($this->context->className === null) { + /** @var VariableContext|null $previousVariableContext */ + $previousVariableContext = collect(self::$previousContexts) + ->first(fn(VariableContext $context) => $context->name === $this->context->name); + + if ($previousVariableContext !== null) { + $this->context->className = $previousVariableContext->className; + } + } + if (Settings::$capturePosition) { $range = PositionUtilities::getRangeFromPosition( $node->getStartPosition(), @@ -39,6 +145,8 @@ public function parse(Variable $node) $this->context->setPosition($range); } + array_push(self::$previousContexts, $this->context); + return $this->context; } diff --git a/composer.json b/composer.json index 17dc4dc..142b14e 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "illuminate/log": "^11.5", "laravel-zero/framework": "^11.0.0", "microsoft/tolerant-php-parser": "^0.1.2", + "phpstan/phpdoc-parser": "^2.3", "stillat/blade-parser": "^1.10" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 2118fe1..4d1c54d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f17bbd5e400471f42d8477b9c18eb8d3", + "content-hash": "26e8b6111294cc63899dee62d8623a9d", "packages": [ { "name": "brick/math", @@ -3154,6 +3154,53 @@ ], "time": "2024-07-20T21:41:07+00:00" }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0" + }, + "time": "2025-08-30T15:50:23+00:00" + }, { "name": "psr/clock", "version": "1.0.0", @@ -7465,53 +7512,6 @@ }, "time": "2024-11-09T15:12:26+00:00" }, - { - "name": "phpstan/phpdoc-parser", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299", - "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "doctrine/annotations": "^2.0", - "nikic/php-parser": "^5.3.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.6", - "symfony/process": "^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0" - }, - "time": "2024-10-13T11:29:49+00:00" - }, { "name": "phpunit/php-code-coverage", "version": "10.1.16", From 6f9df2c1ced3dea4b9c36f21ca7fa2c5bb4b3d14 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Tue, 9 Sep 2025 16:16:53 +0000 Subject: [PATCH 07/10] refactoring --- app/Parsers/MethodDeclarationParser.php | 2 +- app/Parsers/VariableParser.php | 187 +++++++++++++----------- 2 files changed, 106 insertions(+), 83 deletions(-) diff --git a/app/Parsers/MethodDeclarationParser.php b/app/Parsers/MethodDeclarationParser.php index 18741df..5beaeb3 100644 --- a/app/Parsers/MethodDeclarationParser.php +++ b/app/Parsers/MethodDeclarationParser.php @@ -20,7 +20,7 @@ public function parse(MethodDeclaration $node) // Every method is a new context, so we need to clear // the previous variable contexts // @see https://github.com/laravel/vs-code-php-parser-cli/pull/14 - VariableParser::$previousContexts = []; + VariableParser::$previousContexts = collect(); return $this->context; } diff --git a/app/Parsers/VariableParser.php b/app/Parsers/VariableParser.php index 1d29633..aceb290 100644 --- a/app/Parsers/VariableParser.php +++ b/app/Parsers/VariableParser.php @@ -5,6 +5,7 @@ use App\Contexts\AbstractContext; use App\Contexts\Variable as VariableContext; use App\Parser\Settings; +use Illuminate\Support\Collection; use Microsoft\PhpParser\Node; use Microsoft\PhpParser\Node\Expression\Variable; use Microsoft\PhpParser\Node\NamespaceUseClause; @@ -26,7 +27,16 @@ class VariableParser extends AbstractParser */ protected AbstractContext $context; - public static array $previousContexts = []; + public static Collection $previousContexts; + + private function createPhpDocParser(ParserConfig $config): PhpDocParser + { + $constExprParser = new ConstExprParser($config); + $typeParser = new TypeParser($config, $constExprParser); + + return new PhpDocParser($config, $typeParser, $constExprParser); + + } private function getLatestDocComment(Node $node): ?string { @@ -39,96 +49,109 @@ private function getLatestDocComment(Node $node): ?string return $docComment; } - public function parse(Variable $node) + private function searchDocComment(Node $node): ?string { - $this->context->name = $node->getName(); + $docComment = $this->getLatestDocComment($node); - $result = $this->context->searchForVar($this->context->name); + if ($docComment === null) { + return null; + } - // Firstly, we try to find the className from the method parameter - if (is_string($result)) { - $this->context->className = $result; + $config = new ParserConfig([]); + $lexer = new Lexer($config); + $phpDocParser = $this->createPhpDocParser($config); + + $tokens = new TokenIterator($lexer->tokenize($docComment)); + $phpDocNode = $phpDocParser->parse($tokens); + + $varTagValues = $phpDocNode->getVarTagValues(); + + /** @var VarTagValueNode|null $tagValue */ + $tagValue = collect($varTagValues) + // We need to remove first character because it's always $ + ->first(fn (VarTagValueNode $valueNode) => substr($valueNode->variableName, 1) === $this->context->name); + + if (! $tagValue?->type instanceof IdentifierTypeNode) { + return null; } - // If the className is still not found, we try to find the className - // from the doc comment, for example: - // - // /** @var User $user */ - // Gate::allows('edit', $user); - if ($this->context->className === null) { - $docComment = $this->getLatestDocComment($node); - - if ($docComment !== null) { - $config = new ParserConfig([]); - $lexer = new Lexer($config); - $constExprParser = new ConstExprParser($config); - $typeParser = new TypeParser($config, $constExprParser); - $phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser); - - $tokens = new TokenIterator($lexer->tokenize($docComment)); - $phpDocNode = $phpDocParser->parse($tokens); - $varTagValues = $phpDocNode->getVarTagValues(); - - /** @var VarTagValueNode|null $tagValue */ - $tagValue = collect($varTagValues) - // We need to remove first character because it's always $ - ->first(fn(VarTagValueNode $valueNode) => substr($valueNode->variableName, 1) === $this->context->name); - - if ($tagValue?->type instanceof IdentifierTypeNode) { - // If the class name starts with a backslash, it's a fully qualified name - if (str_starts_with($tagValue->type->name, '\\')) { - $this->context->className = substr($tagValue->type->name, 1); - // Otherwise, it's a short name and we need to find the fully qualified name from - // the imported namespaces - } else { - $uses = []; - - foreach ($node->getRoot()->getDescendantNodes() as $node) { - if (! $node instanceof NamespaceUseDeclaration) { - continue; - } - - foreach ($node->useClauses->children ?? [] as $clause) { - if (! $clause instanceof NamespaceUseClause) { - continue; - } - - $fqcn = $clause->namespaceName->getText(); - - // If the namespace has an alias, we need to use the alias as the short name - $alias = $clause->namespaceAliasingClause - ? str($clause->namespaceAliasingClause->getText()) - ->after('as') - ->trim() - ->toString() - : str($fqcn)->explode('\\')->last(); - - // Finally, we add the short and fully qualified name to the uses array - $uses[$alias] = $fqcn; - } - } - - $this->context->className = $uses[$tagValue->type->name] ?? null; - } + // If the class name starts with a backslash, it's a fully qualified name + if (str_starts_with($tagValue->type->name, '\\')) { + return substr($tagValue->type->name, 1); + } + + // Otherwise, it's a short name and we need to find the fully qualified name from + // the imported namespaces + $uses = []; + + foreach ($node->getRoot()->getDescendantNodes() as $node) { + if (! $node instanceof NamespaceUseDeclaration) { + continue; + } + + foreach ($node->useClauses->children ?? [] as $clause) { + if (! $clause instanceof NamespaceUseClause) { + continue; } + + $fqcn = $clause->namespaceName->getText(); + + // If the namespace has an alias, we need to use the alias as the short name + $alias = $clause->namespaceAliasingClause + ? str($clause->namespaceAliasingClause->getText()) + ->after('as') + ->trim() + ->toString() + : str($fqcn)->explode('\\')->last(); + + // Finally, we add the short and fully qualified name to the uses array + $uses[$alias] = $fqcn; } } - // If the className is still not found, we try to find the className - // from the previous variable contexts, for example: - // - // /** @var \App\Models\User $user */ - // $user = $request->user; - // - // Gate::allows('edit', $user); - if ($this->context->className === null) { - /** @var VariableContext|null $previousVariableContext */ - $previousVariableContext = collect(self::$previousContexts) - ->first(fn(VariableContext $context) => $context->name === $this->context->name); - - if ($previousVariableContext !== null) { - $this->context->className = $previousVariableContext->className; + return $uses[$tagValue->type->name] ?? null; + } + + private function searchPreviousContexts(): ?string + { + if (! self::$previousContexts instanceof Collection) { + self::$previousContexts = collect(); + } + + /** @var VariableContext|null $previousVariableContext */ + $previousVariableContext = self::$previousContexts + ->last(fn (VariableContext $context) => $context->name === $this->context->name); + + return $previousVariableContext?->className; + } + + public function parse(Variable $node) + { + $this->context->name = $node->getName(); + + foreach ([ + // Firstly, we try to find the className from the method parameter + $this->context->searchForVar($this->context->name), + // If the className is still not found, we try to find the className + // from the doc comment, for example: + // + // /** @var \App\Models\User $user */ + // Gate::allows('edit', $user); + $this->searchDocComment($node), + // If the className is still not found, we try to find the className + // from the previous variable contexts, for example: + // + // /** @var \App\Models\User $user */ + // $user = $request->user; + // + // Gate::allows('edit', $user); + $this->searchPreviousContexts(), + ] as $result) { + if (! is_string($result)) { + continue; } + + $this->context->className = $result; } if (Settings::$capturePosition) { @@ -145,7 +168,7 @@ public function parse(Variable $node) $this->context->setPosition($range); } - array_push(self::$previousContexts, $this->context); + self::$previousContexts->push($this->context); return $this->context; } From 57cba9948c4eb678391ef299e9488c16fca1fcc4 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Tue, 9 Sep 2025 18:23:08 +0000 Subject: [PATCH 08/10] refactoring --- app/Parsers/MethodDeclarationParser.php | 2 +- app/Parsers/VariableParser.php | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/Parsers/MethodDeclarationParser.php b/app/Parsers/MethodDeclarationParser.php index 5beaeb3..18741df 100644 --- a/app/Parsers/MethodDeclarationParser.php +++ b/app/Parsers/MethodDeclarationParser.php @@ -20,7 +20,7 @@ public function parse(MethodDeclaration $node) // Every method is a new context, so we need to clear // the previous variable contexts // @see https://github.com/laravel/vs-code-php-parser-cli/pull/14 - VariableParser::$previousContexts = collect(); + VariableParser::$previousContexts = []; return $this->context; } diff --git a/app/Parsers/VariableParser.php b/app/Parsers/VariableParser.php index aceb290..7086c80 100644 --- a/app/Parsers/VariableParser.php +++ b/app/Parsers/VariableParser.php @@ -5,7 +5,6 @@ use App\Contexts\AbstractContext; use App\Contexts\Variable as VariableContext; use App\Parser\Settings; -use Illuminate\Support\Collection; use Microsoft\PhpParser\Node; use Microsoft\PhpParser\Node\Expression\Variable; use Microsoft\PhpParser\Node\NamespaceUseClause; @@ -27,7 +26,7 @@ class VariableParser extends AbstractParser */ protected AbstractContext $context; - public static Collection $previousContexts; + public static array $previousContexts = []; private function createPhpDocParser(ParserConfig $config): PhpDocParser { @@ -114,12 +113,8 @@ private function searchDocComment(Node $node): ?string private function searchPreviousContexts(): ?string { - if (! self::$previousContexts instanceof Collection) { - self::$previousContexts = collect(); - } - /** @var VariableContext|null $previousVariableContext */ - $previousVariableContext = self::$previousContexts + $previousVariableContext = collect(self::$previousContexts) ->last(fn (VariableContext $context) => $context->name === $this->context->name); return $previousVariableContext?->className; @@ -168,7 +163,7 @@ public function parse(Variable $node) $this->context->setPosition($range); } - self::$previousContexts->push($this->context); + array_push(self::$previousContexts, $this->context); return $this->context; } From 014b45e98a36e72e7a133e218baa8e07d68b8b5a Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Tue, 9 Sep 2025 18:29:38 +0000 Subject: [PATCH 09/10] refactoring --- app/Parsers/VariableParser.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Parsers/VariableParser.php b/app/Parsers/VariableParser.php index 7086c80..f3c9e54 100644 --- a/app/Parsers/VariableParser.php +++ b/app/Parsers/VariableParser.php @@ -65,18 +65,18 @@ private function searchDocComment(Node $node): ?string $varTagValues = $phpDocNode->getVarTagValues(); - /** @var VarTagValueNode|null $tagValue */ - $tagValue = collect($varTagValues) + /** @var VarTagValueNode|null $varTagValue */ + $varTagValue = collect($varTagValues) // We need to remove first character because it's always $ ->first(fn (VarTagValueNode $valueNode) => substr($valueNode->variableName, 1) === $this->context->name); - if (! $tagValue?->type instanceof IdentifierTypeNode) { + if (! $varTagValue?->type instanceof IdentifierTypeNode) { return null; } // If the class name starts with a backslash, it's a fully qualified name - if (str_starts_with($tagValue->type->name, '\\')) { - return substr($tagValue->type->name, 1); + if (str_starts_with($varTagValue->type->name, '\\')) { + return substr($varTagValue->type->name, 1); } // Otherwise, it's a short name and we need to find the fully qualified name from @@ -108,7 +108,7 @@ private function searchDocComment(Node $node): ?string } } - return $uses[$tagValue->type->name] ?? null; + return $uses[$varTagValue->type->name] ?? null; } private function searchPreviousContexts(): ?string From 40dff9476c423d3275c9e0eee25d2eca6b4ad62e Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Wed, 10 Sep 2025 07:53:09 +0000 Subject: [PATCH 10/10] refactoring --- app/Parsers/VariableParser.php | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/app/Parsers/VariableParser.php b/app/Parsers/VariableParser.php index f3c9e54..0cd5681 100644 --- a/app/Parsers/VariableParser.php +++ b/app/Parsers/VariableParser.php @@ -48,7 +48,14 @@ private function getLatestDocComment(Node $node): ?string return $docComment; } - private function searchDocComment(Node $node): ?string + private function searchClassNameInParameter(): ?string + { + $className = $this->context->searchForVar($this->context->name); + + return is_string($className) ? $className : null; + } + + private function searchClassNameInDocComment(Node $node): ?string { $docComment = $this->getLatestDocComment($node); @@ -111,7 +118,7 @@ private function searchDocComment(Node $node): ?string return $uses[$varTagValue->type->name] ?? null; } - private function searchPreviousContexts(): ?string + private function searchClassNameInPreviousContexts(): ?string { /** @var VariableContext|null $previousVariableContext */ $previousVariableContext = collect(self::$previousContexts) @@ -124,15 +131,14 @@ public function parse(Variable $node) { $this->context->name = $node->getName(); - foreach ([ - // Firstly, we try to find the className from the method parameter - $this->context->searchForVar($this->context->name), + // Firstly, we try to find the className from the method parameter + $this->context->className = $this->searchClassNameInParameter() // If the className is still not found, we try to find the className // from the doc comment, for example: // // /** @var \App\Models\User $user */ // Gate::allows('edit', $user); - $this->searchDocComment($node), + ?? $this->searchClassNameInDocComment($node) // If the className is still not found, we try to find the className // from the previous variable contexts, for example: // @@ -140,14 +146,7 @@ public function parse(Variable $node) // $user = $request->user; // // Gate::allows('edit', $user); - $this->searchPreviousContexts(), - ] as $result) { - if (! is_string($result)) { - continue; - } - - $this->context->className = $result; - } + ?? $this->searchClassNameInPreviousContexts(); if (Settings::$capturePosition) { $range = PositionUtilities::getRangeFromPosition(