From b5fb64a6bf6896e75cf2c77dcfc8ccfe7986851b Mon Sep 17 00:00:00 2001 From: John Koster Date: Sat, 8 Feb 2025 15:04:08 -0600 Subject: [PATCH] Improves compilation of custom functions declared within a component's template --- CHANGELOG.md | 4 ++ src/Compiler/ComponentCompiler.php | 2 + .../ComponentStages/RewriteFunctions.php | 27 +++++++ src/Parser/Visitors/FindFunctionsVisitor.php | 25 +++++++ src/Parser/Visitors/RenameFunctionVisitor.php | 70 +++++++++++++++++++ tests/Compiler/CustomFunctionsTest.php | 22 ++++++ .../components/functions/declared.blade.php | 9 +++ 7 files changed, 159 insertions(+) create mode 100644 src/Compiler/ComponentStages/RewriteFunctions.php create mode 100644 src/Parser/Visitors/FindFunctionsVisitor.php create mode 100644 src/Parser/Visitors/RenameFunctionVisitor.php create mode 100644 tests/Compiler/CustomFunctionsTest.php create mode 100644 tests/resources/components/functions/declared.blade.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b2e4a..6116e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Improves compilation of custom functions declared within a component's template + ## [v1.0.6](https://github.com/Stillat/dagger/compare/v1.0.5...v1.0.6) - 2025-01-31 - Corrects an issue where Blade stack compilation results in array index errors diff --git a/src/Compiler/ComponentCompiler.php b/src/Compiler/ComponentCompiler.php index 5417ff8..e19e984 100644 --- a/src/Compiler/ComponentCompiler.php +++ b/src/Compiler/ComponentCompiler.php @@ -8,6 +8,7 @@ use Stillat\Dagger\Compiler\ComponentStages\ExtractsRenderCalls; use Stillat\Dagger\Compiler\ComponentStages\RemoveUseStatements; use Stillat\Dagger\Compiler\ComponentStages\ResolveNamespaces; +use Stillat\Dagger\Compiler\ComponentStages\RewriteFunctions; use Stillat\Dagger\Compiler\Concerns\CompilesPhp; use Stillat\Dagger\Parser\PhpParser; @@ -42,6 +43,7 @@ public function compile(string $component): string ResolveNamespaces::class, RemoveUseStatements::class, new ExtractsRenderCalls($this), + RewriteFunctions::class, ]) ->thenReturn(); diff --git a/src/Compiler/ComponentStages/RewriteFunctions.php b/src/Compiler/ComponentStages/RewriteFunctions.php new file mode 100644 index 0000000..a168e38 --- /dev/null +++ b/src/Compiler/ComponentStages/RewriteFunctions.php @@ -0,0 +1,27 @@ +addVisitor($finder); + $traverser->traverse($ast); + + $traverser->removeVisitor($finder); + + $modifyFunctionsVisitor = new RenameFunctionVisitor($finder->getFunctionNames()); + $traverser->addVisitor($modifyFunctionsVisitor); + + return $next($traverser->traverse($ast)); + } +} diff --git a/src/Parser/Visitors/FindFunctionsVisitor.php b/src/Parser/Visitors/FindFunctionsVisitor.php new file mode 100644 index 0000000..65ac01b --- /dev/null +++ b/src/Parser/Visitors/FindFunctionsVisitor.php @@ -0,0 +1,25 @@ +functionNames[] = $node->name->toString(); + } + + public function getFunctionNames(): array + { + return $this->functionNames; + } +} diff --git a/src/Parser/Visitors/RenameFunctionVisitor.php b/src/Parser/Visitors/RenameFunctionVisitor.php new file mode 100644 index 0000000..de494af --- /dev/null +++ b/src/Parser/Visitors/RenameFunctionVisitor.php @@ -0,0 +1,70 @@ +functionNames = $this->buildFunctionNameMap($functionNames); + } + + protected function buildFunctionNameMap(array $functionNames): array + { + return collect($functionNames) + ->mapWithKeys(function ($name) { + return [$name => $name.'_'.Utils::makeRandomString()]; + }) + ->all(); + } + + public function enterNode(Node $node) + { + if (! $node instanceof Node\Expr\FuncCall || ! $node->name instanceof Node\Name) { + return; + } + + $functionName = $node->name->toString(); + + if (! isset($this->functionNames[$functionName])) { + return; + } + + $node->name = new Node\Name($this->functionNames[$functionName]); + } + + public function leaveNode(Node $node) + { + if (! $node instanceof Node\Stmt\Function_) { + return null; + } + + $functionName = $node->name->toString(); + + if (! isset($this->functionNames[$functionName])) { + return null; + } + + $newFunctionName = $this->functionNames[$functionName]; + + $node->name = new Node\Identifier($newFunctionName); + + return new Node\Stmt\If_( + new Node\Expr\BooleanNot( + new Node\Expr\FuncCall( + new Node\Name('function_exists'), + [new Node\Arg(new Node\Scalar\String_($newFunctionName))] + ) + ), + [ + 'stmts' => [$node], + ] + ); + } +} diff --git a/tests/Compiler/CustomFunctionsTest.php b/tests/Compiler/CustomFunctionsTest.php new file mode 100644 index 0000000..6bbf743 --- /dev/null +++ b/tests/Compiler/CustomFunctionsTest.php @@ -0,0 +1,22 @@ + +@endfor +BLADE; + + $expected = <<<'EXPECTED' +THE TITLE: 0THE TITLE: 1THE TITLE: 2THE TITLE: 3THE TITLE: 4 +EXPECTED; + + $this->assertSame( + $expected, + $this->render($template) + ); +}); diff --git a/tests/resources/components/functions/declared.blade.php b/tests/resources/components/functions/declared.blade.php new file mode 100644 index 0000000..1935017 --- /dev/null +++ b/tests/resources/components/functions/declared.blade.php @@ -0,0 +1,9 @@ +props(['title'])->trimOutput(); + +function toUpper($value) { + return mb_strtoupper($value); +} +?> + +{{ toUpper($title) }}