Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
php-version: ${{ matrix.php }}
coverage: xdebug
extensions: zip
tools: composer
tools: composer, infection

- name: Determine composer cache directory
id: composer-cache
Expand All @@ -57,8 +57,8 @@ jobs:

- name: Install dependencies
run: |
if [[ "${{ matrix.php }}" == "8.1" ]]; then
composer require phpstan/phpstan --no-update
if [[ "${{ matrix.php }}" == "8.5" ]]; then
composer require --dev phpstan/phpstan:^2.1 --no-update
fi;

if [[ "${{ matrix.composer }}" == "lowest" ]]; then
Expand All @@ -77,11 +77,17 @@ jobs:
php vendor/bin/phpunit -c phpunit.xml --coverage-clover=build/logs/clover.xml

- name: Run phpstan
continue-on-error: true
if: ${{ matrix.php == '8.1' }}
if: ${{ matrix.php == '8.5' }}
run: |
php vendor/bin/phpstan analyse

- name: Run infection
if: ${{ matrix.php == '8.5' }}
env:
XDEBUG_MODE: coverage
run: |
infection --threads=max --min-msi=0 --min-covered-msi=0 --only-covering-test-cases --no-progress

- name: Upload coverage results to Coveralls
continue-on-error: true
env:
Expand Down
12 changes: 12 additions & 0 deletions infection.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "https://infection.github.io/schema.json",
"staticAnalysisTool": "phpstan",
"source": {
"directories": [
"src"
]
},
"logs": {
"summary": "build/logs/infection-summary.log"
}
}
1 change: 0 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
parameters:
level: 8
checkMissingIterableValueType: false
paths:
- %currentWorkingDirectory%/src/
66 changes: 36 additions & 30 deletions src/voku/helper/HtmlMin.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,6 @@ class HtmlMin implements HtmlMinInterface
*/
private static $regExSpace = "/[[:space:]]{2,}|[\r\n]/u";

/**
* @var string[]
*
* @psalm-var list<string>
*/
private static $optional_end_tags = [
'html',
'head',
'body',
];

/**
* @var string[]
*
Expand Down Expand Up @@ -91,7 +80,7 @@ class HtmlMin implements HtmlMinInterface
];

/**
* @var array
* @var array<string, string>
*/
private static $booleanAttributes = [
'allowfullscreen' => '',
Expand Down Expand Up @@ -138,7 +127,7 @@ class HtmlMin implements HtmlMinInterface
];

/**
* @var array
* @var list<string>
*/
private static $skipTagsForRemoveWhitespace = [
'code',
Expand All @@ -149,7 +138,7 @@ class HtmlMin implements HtmlMinInterface
];

/**
* @var array
* @var array<int, string>
*/
private $protectedChildNodes = [];

Expand Down Expand Up @@ -375,7 +364,7 @@ public function __construct()
*/
public function attachObserverToTheDomLoop(HtmlMinDomObserverInterface $observer)
{
$this->domLoopObservers[$observer] = $observer;
$this->domLoopObservers->offsetSet($observer, $observer);
}

/**
Expand Down Expand Up @@ -1230,7 +1219,7 @@ private function domNodeOpeningTagOptional(\DOMNode $node): bool
}

return !\in_array(
$firstChild->tagName,
$firstChild->nodeName,
[
'meta',
'link',
Expand Down Expand Up @@ -1586,7 +1575,7 @@ private function minifyJsonString(string $json): string
}

/**
* @return array
* @return string[]
*/
public function getDomainsToRemoveHttpPrefixFromAttributes(): array
{
Expand Down Expand Up @@ -2266,9 +2255,9 @@ private function protectTagHelper(HtmlDomParser $dom, string $selector): HtmlDom
}

$parentNode = $element->parentNode();
if ($parentNode !== null && $parentNode->nodeValue !== null) {
if ($parentNode !== null && $parentNode->getNode()->nodeValue !== null) {
$this->protectedChildNodes[$this->protected_tags_counter] = $parentNode->innerHtml();
$parentNode->nodeValue = '<' . $this->protectedChildNodesHelper . ' data-' . $this->protectedChildNodesHelper . '="' . $this->protected_tags_counter . '"></' . $this->protectedChildNodesHelper . '>';
$parentNode->getNode()->nodeValue = '<' . $this->protectedChildNodesHelper . ' data-' . $this->protectedChildNodesHelper . '="' . $this->protected_tags_counter . '"></' . $this->protectedChildNodesHelper . '>';
}

++$this->protected_tags_counter;
Expand Down Expand Up @@ -2301,7 +2290,7 @@ private function protectTags(HtmlDomParser $dom): HtmlDomParser
}
}

$innerHtml = $element->innerhtml;
$innerHtml = $element->innerHtml();

// On PHP < 8.0 the simplevokubroken-hash mechanism restores content
// (including surrounding newlines/spaces) AFTER fixHtmlOutput's trim
Expand Down Expand Up @@ -2338,7 +2327,8 @@ private function protectTags(HtmlDomParser $dom): HtmlDomParser
) {
$originalInnerHtml = $innerHtml;
try {
$innerHtml = \JShrink\Minifier::minify($innerHtml);
$minifiedInnerHtml = \JShrink\Minifier::minify($innerHtml);
$innerHtml = \is_string($minifiedInnerHtml) ? $minifiedInnerHtml : $originalInnerHtml;
} catch (\Exception $e) {
$innerHtml = $originalInnerHtml;
}
Expand Down Expand Up @@ -2411,6 +2401,9 @@ private function removeComments(HtmlDomParser $dom): HtmlDomParser
foreach ($dom->findMulti('//comment()') as $commentWrapper) {
$comment = $commentWrapper->getNode();
$val = $comment->nodeValue;
if ($val === null) {
continue;
}
if (\strpos($val, '[') === false) {
$parentNode = $comment->parentNode;
if ($parentNode !== null) {
Expand Down Expand Up @@ -2449,6 +2442,9 @@ private function removeCommentsOnlyFromHtmlString(string $html): string
foreach ($dom->findMulti('//comment()') as $commentWrapper) {
$comment = $commentWrapper->getNode();
$commentValue = $comment->nodeValue;
if ($commentValue === null) {
continue;
}
if (
$this->isConditionalComment($commentValue)
||
Expand Down Expand Up @@ -2508,15 +2504,15 @@ private function removeWhitespaceAroundTags(SimpleHtmlDomInterface $element)
/**
* Callback function for preg_replace_callback use.
*
* @param array $matches PREG matches
* @param array<string, string> $matches PREG matches
*
* @return string
*/
private function restoreProtectedHtml($matches): string
{
\preg_match('/=["\']*(?<id>\d+)/', $matches['attributes'], $matchesInner);

return $this->protectedChildNodes[$matchesInner['id']] ?? '';
return isset($matchesInner['id']) ? ($this->protectedChildNodes[(int) $matchesInner['id']] ?? '') : '';
}

/**
Expand Down Expand Up @@ -2574,6 +2570,10 @@ private function sumUpWhitespace(HtmlDomParser $dom): HtmlDomParser
continue;
}

if ($text_node->nodeValue === null) {
continue;
}

$nodeValueTmp = \preg_replace(self::$regExSpace, ' ', $text_node->nodeValue);
if ($nodeValueTmp !== null) {
$text_node->nodeValue = $nodeValueTmp;
Expand All @@ -2600,38 +2600,44 @@ public function useKeepBrokenHtml(bool $keepBrokenHtml): self
}

/**
* @param string[] $templateLogicSyntaxInSpecialScriptTags
* @param array<int, mixed> $templateLogicSyntaxInSpecialScriptTags
*
* @return HtmlMin
*/
public function overwriteTemplateLogicSyntaxInSpecialScriptTags(array $templateLogicSyntaxInSpecialScriptTags): self
{
$validatedTemplateLogicSyntaxInSpecialScriptTags = [];
foreach ($templateLogicSyntaxInSpecialScriptTags as $tmp) {
if (!\is_string($tmp)) {
throw new \InvalidArgumentException('setTemplateLogicSyntaxInSpecialScriptTags only allows string[]');
throw new \InvalidArgumentException('overwriteTemplateLogicSyntaxInSpecialScriptTags only allows string[]');
}

$validatedTemplateLogicSyntaxInSpecialScriptTags[] = $tmp;
}

$this->templateLogicSyntaxInSpecialScriptTags = $templateLogicSyntaxInSpecialScriptTags;
$this->templateLogicSyntaxInSpecialScriptTags = $validatedTemplateLogicSyntaxInSpecialScriptTags;

return $this;
}


/**
* @param string[] $specialScriptTags
* @param array<int, mixed> $specialScriptTags
*
* @return HtmlDomParser
* @return HtmlMin
*/
public function overwriteSpecialScriptTags(array $specialScriptTags): self
{
$validatedSpecialScriptTags = [];
foreach ($specialScriptTags as $tag) {
if (!\is_string($tag)) {
throw new \InvalidArgumentException('SpecialScriptTags only allows string[]');
throw new \InvalidArgumentException('overwriteSpecialScriptTags only allows string[]');
}

$validatedSpecialScriptTags[] = $tag;
}

$this->specialScriptTags = $specialScriptTags;
$this->specialScriptTags = $validatedSpecialScriptTags;

return $this;
}
Expand Down
2 changes: 1 addition & 1 deletion src/voku/helper/HtmlMinDomObserverOptimizeAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public function domElementAfterMinification(SimpleHtmlDomInterface $element, Htm
* @param string $tag
* @param string $attrName
* @param string $attrValue
* @param array $allAttr
* @param array<string, string> $allAttr
* @param HtmlMinInterface $htmlMin
*
* @return bool
Expand Down
2 changes: 1 addition & 1 deletion src/voku/helper/HtmlMinInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public function isXHTML(): bool;
public function getLocalDomains(): array;

/**
* @return array
* @return string[]
*/
public function getDomainsToRemoveHttpPrefixFromAttributes(): array;
}
57 changes: 57 additions & 0 deletions tests/HtmlMinTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,63 @@
static::assertSame('', (new HtmlMin())->minify(''));
}

public function testAttachObserverToTheDomLoopDoesNotTriggerDeprecation()
{
$observer = new class implements \voku\helper\HtmlMinDomObserverInterface {
/** @var int */
public $beforeCalls = 0;

/** @var int */
public $afterCalls = 0;

public function domElementBeforeMinification(\voku\helper\SimpleHtmlDomInterface $element, \voku\helper\HtmlMinInterface $htmlMin)
{
++$this->beforeCalls;
}

public function domElementAfterMinification(\voku\helper\SimpleHtmlDomInterface $element, \voku\helper\HtmlMinInterface $htmlMin)
{
++$this->afterCalls;
}
};

\set_error_handler(static function (int $severity, string $message, string $file, int $line): bool {
if (($severity & \E_DEPRECATED) !== 0) {
throw new \ErrorException($message, 0, $severity, $file, $line);

Check warning on line 46 in tests/HtmlMinTest.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define and throw a dedicated exception instead of using a generic one.

See more on https://sonarcloud.io/project/issues?id=voku_HtmlMin&issues=AZ476t_5kxCsbE4nVRUu&open=AZ476t_5kxCsbE4nVRUu&pullRequest=138
}

return false;
});

try {
$minifier = new HtmlMin();
$minifier->attachObserverToTheDomLoop($observer);

static::assertSame('<div class="a b">test</div>', $minifier->minify('<div class="b a">test</div>'));
} finally {
\restore_error_handler();
}

static::assertGreaterThan(0, $observer->beforeCalls);
static::assertGreaterThan(0, $observer->afterCalls);
}

public function testOverwriteTemplateLogicSyntaxInSpecialScriptTagsRejectsNonStringValues()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('overwriteTemplateLogicSyntaxInSpecialScriptTags only allows string[]');

(new HtmlMin())->overwriteTemplateLogicSyntaxInSpecialScriptTags(['{%', 123]);
}

public function testOverwriteSpecialScriptTagsRejectsNonStringValues()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('overwriteSpecialScriptTags only allows string[]');

(new HtmlMin())->overwriteSpecialScriptTags(['text/html', 123]);
}

/**
* @return array
*/
Expand Down
Loading