From b5255669a556ac1708817981cace8fbe7167ec5b Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 16 Jan 2025 14:46:52 -0500 Subject: [PATCH 1/2] better echo detection for blade --- app/Parsers/InlineHtmlParser.php | 91 ++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/app/Parsers/InlineHtmlParser.php b/app/Parsers/InlineHtmlParser.php index 88b4447..ff5b0bb 100644 --- a/app/Parsers/InlineHtmlParser.php +++ b/app/Parsers/InlineHtmlParser.php @@ -10,12 +10,20 @@ use Microsoft\PhpParser\Parser; use Microsoft\PhpParser\Range; use Stillat\BladeParser\Document\Document; +use Stillat\BladeParser\Nodes\BaseNode; use Stillat\BladeParser\Nodes\DirectiveNode; use Stillat\BladeParser\Nodes\EchoNode; use Stillat\BladeParser\Nodes\EchoType; +use Stillat\BladeParser\Nodes\LiteralNode; class InlineHtmlParser extends AbstractParser { + protected $echoStrings = [ + '{!!' => '!!}', + '{{{' => '}}}', + '{{' => '}}', + ]; + /** * @var Blade */ @@ -42,6 +50,11 @@ public function parse(InlineHtml $node) protected function parseBladeContent($node) { foreach ($node->getNodes() as $child) { + // TODO: Add other echo types as well + if ($child instanceof LiteralNode) { + $this->parseLiteralNode($child); + } + if ($child instanceof DirectiveNode) { $this->parseBladeDirective($child); } @@ -54,22 +67,18 @@ protected function parseBladeContent($node) } } - protected function parseBladeDirective(DirectiveNode $node) + protected function doEchoParse(BaseNode $node, $prefix, $content) { - if ($node->isClosingDirective || !$node->hasArguments()) { - return; - } - - $methodUsed = '@' . $node->content; - $safetyPrefix = 'directive'; - $snippet = "getStartIndentationLevel()) . str_replace($methodUsed, $safetyPrefix . $node->content, $node->toString() . ';'); + $snippet = "getStartIndentationLevel()) . str_replace($prefix, '', $content) . ';'; $sourceFile = (new Parser)->parseSourceFile($snippet); - Settings::$calculatePosition = function (Range $range) use ($node, $safetyPrefix) { + $suffix = $this->echoStrings[$prefix]; + + Settings::$calculatePosition = function (Range $range) use ($node, $prefix, $suffix) { if ($range->start->line === 1) { - $range->start->character -= strlen($safetyPrefix) - 1; - $range->end->character -= strlen($safetyPrefix) - 1; + $range->start->character += mb_strlen($prefix); + $range->end->character += mb_strlen($suffix); } $range->start->line += $node->position->startLine - 2; @@ -80,35 +89,42 @@ protected function parseBladeDirective(DirectiveNode $node) $result = Parse::parse($sourceFile); - $child = $result->children[0]; + if (count($result->children) === 0) { + return; + } - $child->methodName = '@' . substr($child->methodName, strlen($safetyPrefix)); + $child = $result->children[0]; $this->items[] = $child; } - protected function parseEchoNode(EchoNode $node) + protected function parseLiteralNode(LiteralNode $node) { - $snippet = "getStartIndentationLevel()) . $node->innerContent . ';'; + foreach ($this->echoStrings as $prefix => $suffix) { + if (!str_starts_with($node->content, $prefix)) { + continue; + } - $sourceFile = (new Parser)->parseSourceFile($snippet); + $this->doEchoParse($node, $prefix, $node->content); + } + } - Settings::$calculatePosition = function (Range $range) use ($node) { - $prefix = match ($node->type) { - EchoType::RawEcho => '{!!', - EchoType::TripleEcho => '{{{', - default => '{{', - }; + protected function parseBladeDirective(DirectiveNode $node) + { + if ($node->isClosingDirective || !$node->hasArguments()) { + return; + } - $suffix = match ($node->type) { - EchoType::RawEcho => '!!}', - EchoType::TripleEcho => '}}}', - default => '}}', - }; + $methodUsed = '@' . $node->content; + $safetyPrefix = 'directive'; + $snippet = "getStartIndentationLevel()) . str_replace($methodUsed, $safetyPrefix . $node->content, $node->toString() . ';'); + $sourceFile = (new Parser)->parseSourceFile($snippet); + + Settings::$calculatePosition = function (Range $range) use ($node, $safetyPrefix) { if ($range->start->line === 1) { - $range->start->character += strlen($prefix); - $range->end->character += strlen($suffix); + $range->start->character -= mb_strlen($safetyPrefix) - 1; + $range->end->character -= mb_strlen($safetyPrefix) - 1; } $range->start->line += $node->position->startLine - 2; @@ -119,12 +135,21 @@ protected function parseEchoNode(EchoNode $node) $result = Parse::parse($sourceFile); - if (count($result->children) === 0) { - return; - } - $child = $result->children[0]; + $child->methodName = '@' . substr($child->methodName, mb_strlen($safetyPrefix)); + $this->items[] = $child; } + + protected function parseEchoNode(EchoNode $node) + { + $prefix = match ($node->type) { + EchoType::RawEcho => '{!!', + EchoType::TripleEcho => '{{{', + default => '{{', + }; + + $this->doEchoParse($node, $prefix, $node->innerContent); + } } From acc138d6de5e8acb8e745d127765ce6f20697a09 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 16 Jan 2025 14:51:49 -0500 Subject: [PATCH 2/2] make sure the blade file closes with a quote --- app/Parser/Walker.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/Parser/Walker.php b/app/Parser/Walker.php index 6532559..d2c2f5f 100644 --- a/app/Parser/Walker.php +++ b/app/Parser/Walker.php @@ -5,6 +5,7 @@ use App\Contexts\Base; use App\Support\Debugs; use Microsoft\PhpParser\Node\SourceFileNode; +use Microsoft\PhpParser\Node\Statement\InlineHtml; use Microsoft\PhpParser\Parser; use Microsoft\PhpParser\SkippedToken; @@ -31,6 +32,14 @@ public function __construct(protected string $document, $debug = false) protected function documentSkipsClosingQuote() { + if (count($this->sourceFile->statementList) === 1 && $this->sourceFile->statementList[0] instanceof InlineHtml) { + // Probably Blade... + $lastChar = substr($this->sourceFile->getFullText(), -1); + $closesWithQuote = in_array($lastChar, ['"', "'"]); + + return $closesWithQuote; + } + foreach ($this->sourceFile->getDescendantNodesAndTokens() as $child) { if ($child instanceof SkippedToken && $child->getText($this->sourceFile->getFullText()) === "'") { return true;