From 8d18437db014e6ba931bcb6fcdbde75e4905beed Mon Sep 17 00:00:00 2001 From: blmage Date: Fri, 14 May 2021 12:31:05 +0200 Subject: [PATCH 01/12] Fix PHP code in template minifier test The template minifier should not fix PHP code and hide errors from the user --- .../Framework/View/Test/Unit/Template/Html/MinifierTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Template/Html/MinifierTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Template/Html/MinifierTest.php index 01b274eb529b5..87ac7b484c182 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Template/Html/MinifierTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Template/Html/MinifierTest.php @@ -150,8 +150,8 @@ public function testMinify(): void var i = 1;// comment var j = 1;// // ')){ -// if ( -// if () { +// if ()) { // comment // Date: Fri, 14 May 2021 12:41:43 +0200 Subject: [PATCH 02/12] Improve detection of heredocs in minified templates --- lib/internal/Magento/Framework/View/Template/Html/Minifier.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/View/Template/Html/Minifier.php b/lib/internal/Magento/Framework/View/Template/Html/Minifier.php index 3eb8baec7f569..ac849804dbfe6 100644 --- a/lib/internal/Magento/Framework/View/Template/Html/Minifier.php +++ b/lib/internal/Magento/Framework/View/Template/Html/Minifier.php @@ -119,7 +119,7 @@ public function minify($file) //Storing Heredocs $heredocs = []; $content = preg_replace_callback( - '/<<<([A-z]+).*?\1;/ims', + '/<<<([A-z]+).*?\1\s*;/ims', function ($match) use (&$heredocs) { $heredocs[] = $match[0]; From e3a6ef780744120251abeb89d94b04b8a7c9e743 Mon Sep 17 00:00:00 2001 From: blmage Date: Fri, 14 May 2021 17:41:38 +0200 Subject: [PATCH 03/12] Clean up PHP code using nikic/php-parser before minifying template HTML --- composer.json | 1 + .../Framework/View/Template/Html/Minifier.php | 37 +++-- .../Html/Minifier/Php/NodeVisitor.php | 89 ++++++++++++ .../Html/Minifier/Php/PrettyPrinter.php | 132 ++++++++++++++++++ 4 files changed, 251 insertions(+), 8 deletions(-) create mode 100644 lib/internal/Magento/Framework/View/Template/Html/Minifier/Php/NodeVisitor.php create mode 100644 lib/internal/Magento/Framework/View/Template/Html/Minifier/Php/PrettyPrinter.php diff --git a/composer.json b/composer.json index 07b4e1349b958..b32efd410e686 100644 --- a/composer.json +++ b/composer.json @@ -65,6 +65,7 @@ "magento/magento-composer-installer": ">=0.1.11", "magento/zendframework1": "dev-master as 1.14.6", "monolog/monolog": "^2.3", + "nikic/php-parser": "~4.4.0", "pelago/emogrifier": "^5.0.0", "php-amqplib/php-amqplib": "~3.0.0", "phpseclib/mcrypt_compat": "2.0.0", diff --git a/lib/internal/Magento/Framework/View/Template/Html/Minifier.php b/lib/internal/Magento/Framework/View/Template/Html/Minifier.php index ac849804dbfe6..c9f7b5a8350dd 100644 --- a/lib/internal/Magento/Framework/View/Template/Html/Minifier.php +++ b/lib/internal/Magento/Framework/View/Template/Html/Minifier.php @@ -9,6 +9,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem; +use Magento\Framework\View\Template\Html\Minifier\Php; class Minifier implements MinifierInterface { @@ -116,17 +117,37 @@ public function minify($file) $dir = dirname($file); $fileName = basename($file); $content = $this->readFactory->create($dir)->readFile($fileName); + + $parser = (new \PhpParser\ParserFactory())->create(\PhpParser\ParserFactory::PREFER_PHP7); + $heredocs = null; + + try { + $ast = $parser->parse($content); + + $traverser = new \PhpParser\NodeTraverser(); + $traverser->addVisitor(new Php\NodeVisitor()); + $ast = $traverser->traverse($ast); + + $prettyPrinter = new Php\PrettyPrinter(); + $content = $prettyPrinter->prettyPrintFile($ast); + $heredocs = $prettyPrinter->getDelayedHeredocs(); + } catch (\PhpParser\Error $error) { + // Some PHP code is seemingly invalid. + } + //Storing Heredocs - $heredocs = []; - $content = preg_replace_callback( - '/<<<([A-z]+).*?\1\s*;/ims', - function ($match) use (&$heredocs) { - $heredocs[] = $match[0]; + if (null === $heredocs) { + $content = preg_replace_callback( + '/<<<([A-z]+).*?\1\s*;/ims', + function ($match) use (&$heredocs) { + $heredocs[] = $match[0]; - return '__MINIFIED_HEREDOC__' .(count($heredocs) - 1); - }, + return '__MINIFIED_HEREDOC__' .(count($heredocs) - 1); + }, ($content ?? '') - ); + ); + } + $content = preg_replace( '#(?)\s+stack = []; + $this->previous = null; + } + + public function enterNode(Node $node) + { + if ($node instanceof Node\Stmt) { + // Mark isolated echo statements, to replace them later with short echo tags. + $parent = empty($this->stack) ?: $this->stack[count($this->stack) - 1]; + + if ($node instanceof Node\Stmt\InlineHTML) { + $node->setAttribute('parent', $parent); + + if ( + ($this->previous instanceof Node\Stmt\Echo_) + && ($previousHtmlStatement = $this->previous->getAttribute('previousHtmlStatement')) + && ($previousHtmlStatement->getAttribute('parent') === $parent) + ) { + $this->previous->setAttribute('isSingleEchoStatement', true); + $previousHtmlStatement->setAttribute('hasSingleEchoStatementNext', true); + } + } elseif ( + ($this->previous instanceof Node\Stmt\InlineHTML) + && ($this->previous->getAttribute('parent') === $parent) + ) { + $node->setAttribute('previousHtmlStatement', $this->previous); + } + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node) + { + $this->previous = $node; + + array_pop($this->stack); + + // Remove nodes that only contain non-doc comments. + if ($node instanceof Node\Stmt\Nop) { + $comments = $node->getComments(); + $isSuperfluousNode = true; + + foreach ($comments as $key => $comment) { + if ($comment instanceof Comment\Doc) { + $isSuperfluousNode = false; + break; + } + } + + if ($isSuperfluousNode) { + return NodeTraverser::REMOVE_NODE; + } + } + } +} diff --git a/lib/internal/Magento/Framework/View/Template/Html/Minifier/Php/PrettyPrinter.php b/lib/internal/Magento/Framework/View/Template/Html/Minifier/Php/PrettyPrinter.php new file mode 100644 index 0000000000000..fd15d78cea7c8 --- /dev/null +++ b/lib/internal/Magento/Framework/View/Template/Html/Minifier/Php/PrettyPrinter.php @@ -0,0 +1,132 @@ +delayedHeredocs = []; + $this->indentLevel = 0; + $this->nl = ''; + $this->origTokens = null; + } + + protected function setIndentLevel(int $level) + { + // Ignore indentation. + } + + protected function indent() + { + // Ignore indentation. + } + + protected function outdent() + { + // Ignore indentation. + } + + /** + * @param string $heredoc + * @return string + */ + private function getHeredocPlaceholder(string $heredoc): string + { + $index = count($this->delayedHeredocs) + 1; + + $this->delayedHeredocs[$index] = $this->handleMagicTokens($heredoc); + + return '__MINIFIED_HEREDOC__' . $index; + } + + protected function pScalar_String(Node\Scalar\String_ $node): string + { + $result = parent::pScalar_String($node); + + return $node->getAttribute('kind') !== Node\Scalar\String_::KIND_HEREDOC + ? $result + : $this->getHeredocPlaceholder($result); + } + + protected function pScalar_Encapsed(Node\Scalar\Encapsed $node): string + { + $result = parent::pScalar_Encapsed($node); + + return $node->getAttribute('kind') !== Node\Scalar\String_::KIND_HEREDOC + ? $result + : $this->getHeredocPlaceholder($result); + } + + protected function pCommaSeparated(array $nodes): string + { + return $this->pImplode($nodes, ','); + } + + protected function pComments(array $comments): string + { + // Only preserve doc comments. + foreach ($comments as $key => $comment) { + if (!$comment instanceof Comment\Doc) { + unset($comments[$key]); + } + } + + $formattedComments = []; + + foreach ($comments as $comment) { + $formattedComments[] = str_replace("\n", '', $comment->getReformattedText()); + } + + // Add a space between doc comments to avoid occurrences of "//" that could later be misinterpreted. + return implode(' ', $formattedComments) . ' '; + } + + protected function pExpr_Array(Node\Expr\Array_ $node): string + { + $node->setAttribute('kind', Node\Expr\Array_::KIND_SHORT); + + return parent::pExpr_Array($node); + } + + protected function pStmt_Echo(Node\Stmt\Echo_ $node): string + { + $output = $this->pCommaSeparated($node->exprs); + + return $node->getAttribute('isSingleEchoStatement') + ? $output . ' ' + : 'echo ' . $output . ';'; + } + + protected function pStmt_InlineHTML(Node\Stmt\InlineHTML $node): string + { + $newline = $node->getAttribute('hasLeadingNewline', true) ? "\n" : ''; + + return '?>' + . $newline + . $node->value + . ($node->getAttribute('hasSingleEchoStatementNext') ? 'delayedHeredocs; + } +} From e0e579199479c018cd1eedc4110de7f55a491687 Mon Sep 17 00:00:00 2001 From: blmage Date: Sat, 15 May 2021 09:28:42 +0200 Subject: [PATCH 04/12] Remove redundant cleaning of PHP comments --- .../Magento/Framework/View/Template/Html/Minifier.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/internal/Magento/Framework/View/Template/Html/Minifier.php b/lib/internal/Magento/Framework/View/Template/Html/Minifier.php index c9f7b5a8350dd..52725921dd353 100644 --- a/lib/internal/Magento/Framework/View/Template/Html/Minifier.php +++ b/lib/internal/Magento/Framework/View/Template/Html/Minifier.php @@ -165,13 +165,9 @@ function ($match) use (&$heredocs) { '#(?)[^\n\r]*#', '', preg_replace( - '#(?)#', - ' $1', - preg_replace( - '#(?)[^\n\r]*#', - '', - ($content ?? '') - ) + '#(?)[^\n\r]*#', + '', + ($content ?? '') ) ) ) @@ -205,3 +201,4 @@ private function getRelativeGeneratedPath($sourcePath) return $this->filesystem->getDirectoryRead(DirectoryList::ROOT)->getRelativePath($sourcePath); } } + From 59dfff2c7c003e378273a535f94a758562e864b9 Mon Sep 17 00:00:00 2001 From: blmage Date: Sat, 15 May 2021 10:03:30 +0200 Subject: [PATCH 05/12] Remove remaining single-line comments in inline text getChildHtml('someChildBlock'); ?> inline text getChildHtml('someChildBlock') ?> + +SOMETEXT +;?>
// This is not a comment and should be preserved. // This is not a comment
This is HTML : getChildHtml('someChildBlock')} + +SOMEOTHERTEXT +;?> TEXT; $this->appDirectoryMock->expects($this->once()) From 4e50effeae3a00e1e87047b3c272096f578b4ece Mon Sep 17 00:00:00 2001 From: blmage Date: Sat, 15 May 2021 15:34:52 +0200 Subject: [PATCH 12/12] Remove unused variable --- .../View/Template/Html/Minifier/Php/NodeVisitor.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/internal/Magento/Framework/View/Template/Html/Minifier/Php/NodeVisitor.php b/lib/internal/Magento/Framework/View/Template/Html/Minifier/Php/NodeVisitor.php index ebb2db00f37c0..e127e323b49a5 100644 --- a/lib/internal/Magento/Framework/View/Template/Html/Minifier/Php/NodeVisitor.php +++ b/lib/internal/Magento/Framework/View/Template/Html/Minifier/Php/NodeVisitor.php @@ -24,11 +24,6 @@ class NodeVisitor extends NodeVisitorAbstract */ private $previous; - /** - * @var int - */ - private $heredocCount = 0; - public function beforeTraverse(array $nodes) { $this->stack = [];