diff --git a/app/Contexts/AbstractContext.php b/app/Contexts/AbstractContext.php index b241b77..5500282 100644 --- a/app/Contexts/AbstractContext.php +++ b/app/Contexts/AbstractContext.php @@ -9,6 +9,8 @@ abstract class AbstractContext { public array $children = []; + public bool $isAbleToAutocomplete = false; + public bool $autocompleting = false; protected array $freshObject; @@ -48,20 +50,20 @@ public function flip() public function findAutocompleting(?AbstractContext $context = null) { $context = $context ?? $this; - $result = $this->seachForAutocompleting($context, true); + $result = $this->searchForAutocompleting($context, true); $lastResult = null; while ($result !== null) { $lastResult = $result; - $result = $this->seachForAutocompleting($result); + $result = $this->searchForAutocompleting($result); } return $lastResult; } - protected function seachForAutocompleting(AbstractContext $context, $checkCurrent = false) + protected function searchForAutocompleting(AbstractContext $context, $checkCurrent = false) { - if ($checkCurrent && $context->autocompleting && ($context instanceof MethodCall || $context instanceof ObjectValue)) { + if ($checkCurrent && $context->autocompleting && $context->isAbleToAutocomplete) { return $context; } diff --git a/app/Contexts/MethodCall.php b/app/Contexts/MethodCall.php index 4e82220..26b77ff 100644 --- a/app/Contexts/MethodCall.php +++ b/app/Contexts/MethodCall.php @@ -4,6 +4,8 @@ class MethodCall extends AbstractContext { + public bool $isAbleToAutocomplete = true; + public ?string $methodName = null; public ?string $className = null; diff --git a/app/Contexts/ObjectValue.php b/app/Contexts/ObjectValue.php index 6d3ea44..77c66cf 100644 --- a/app/Contexts/ObjectValue.php +++ b/app/Contexts/ObjectValue.php @@ -4,6 +4,8 @@ class ObjectValue extends AbstractContext { + public bool $isAbleToAutocomplete = true; + public ?string $className = null; public Arguments $arguments; diff --git a/app/Parsers/AbstractParser.php b/app/Parsers/AbstractParser.php index ad3ee12..17ec868 100644 --- a/app/Parsers/AbstractParser.php +++ b/app/Parsers/AbstractParser.php @@ -55,4 +55,14 @@ public function debug(...$messages) echo $this->indent($message) . PHP_EOL; } } + + protected function parentNodeIs(Node $node, array $nodeClasses): bool + { + if ($node->getParent() === null) { + return false; + } + + return in_array(get_class($node->getParent()), $nodeClasses) + || $this->parentNodeIs($node->getParent(), $nodeClasses); + } } diff --git a/app/Parsers/ArrayCreationExpressionParser.php b/app/Parsers/ArrayCreationExpressionParser.php index 550eeab..21cf78e 100644 --- a/app/Parsers/ArrayCreationExpressionParser.php +++ b/app/Parsers/ArrayCreationExpressionParser.php @@ -6,6 +6,8 @@ use App\Contexts\ArrayValue; use Microsoft\PhpParser\MissingToken; use Microsoft\PhpParser\Node\Expression\ArrayCreationExpression; +use Microsoft\PhpParser\Node\Expression\CallExpression; +use Microsoft\PhpParser\Node\Expression\ObjectCreationExpression; class ArrayCreationExpressionParser extends AbstractParser { @@ -16,6 +18,13 @@ class ArrayCreationExpressionParser extends AbstractParser public function parse(ArrayCreationExpression $node) { + // If array is inside a method, for example Validator::validate([' + // then we need to ignore isAbleToAutocomplete for ArrayValue because + // priority is given to App\Contexts\MethodCall or App\Contexts\ObjectValue + if (!$this->parentNodeIs($node, [CallExpression::class, ObjectCreationExpression::class])) { + $this->context->isAbleToAutocomplete = true; + } + $this->context->autocompleting = $node->closeParenOrBracket instanceof MissingToken; return $this->context; diff --git a/app/Parsers/InlineHtmlParser.php b/app/Parsers/InlineHtmlParser.php index 8ce758d..6ecf6a4 100644 --- a/app/Parsers/InlineHtmlParser.php +++ b/app/Parsers/InlineHtmlParser.php @@ -34,6 +34,17 @@ class InlineHtmlParser extends AbstractParser protected array $items = []; + /** + * Stillat\BladeParser\Document\Document::fromText treats multibyte characters + * as indentations and spaces resulting in a miscalculated Node position. + * + * This function replaces the multibyte characters with a single, placeholder character + */ + private function replaceMultibyteChars(string $text, string $placeholder = '*'): string + { + return preg_replace('/[^\x00-\x7F]/u', $placeholder, $text); + } + public function parse(InlineHtml $node) { if ($node->getStartPosition() > 0) { @@ -46,7 +57,9 @@ public function parse(InlineHtml $node) $this->startLine = $range->start->line; } - $this->parseBladeContent(Document::fromText($node->getText())); + $this->parseBladeContent(Document::fromText( + $this->replaceMultibyteChars($node->getText()) + )); if (count($this->items)) { $blade = new Blade; @@ -95,7 +108,7 @@ protected function doEchoParse(BaseNode $node, $prefix, $content) } $range->start->line += $this->startLine + $node->position->startLine - 2; - $range->end->line += $this->startLine + $node->position->startLine - 2; + $range->end->line += $this->startLine + $node->position->startLine - 2; return $range; }; diff --git a/app/Parsers/MemberAccessExpressionParser.php b/app/Parsers/MemberAccessExpressionParser.php index e087dc4..7fb14d9 100644 --- a/app/Parsers/MemberAccessExpressionParser.php +++ b/app/Parsers/MemberAccessExpressionParser.php @@ -30,7 +30,9 @@ public function parse(MemberAccessExpression $node) if ($child instanceof Variable) { if ($child->getName() === 'this') { - if ($child->getParent()->getParent() instanceof CallExpression) { + $parent = $child->getParent(); + + if ($parent?->getParent() instanceof CallExpression) { // They are calling a method on the current class $result = $this->context->nearestClassDefinition(); @@ -41,12 +43,14 @@ public function parse(MemberAccessExpression $node) continue; } - $propName = $child->getParent()->memberName->getFullText($node->getRoot()->getFullText()); + if ($parent instanceof MemberAccessExpression) { + $propName = $parent->memberName->getFullText($node->getRoot()->getFullText()); - $result = $this->context->searchForProperty($propName); + $result = $this->context->searchForProperty($propName); - if ($result) { - $this->context->className = $result['types'][0] ?? null; + if ($result) { + $this->context->className = $result['types'][0] ?? null; + } } continue;