diff --git a/Magento2/Sniffs/Html/HtmlClosingVoidTagsSniff.php b/Magento2/Sniffs/Html/HtmlClosingVoidTagsSniff.php index 5ce7c20d..b70c5a35 100644 --- a/Magento2/Sniffs/Html/HtmlClosingVoidTagsSniff.php +++ b/Magento2/Sniffs/Html/HtmlClosingVoidTagsSniff.php @@ -13,7 +13,7 @@ /** * Sniff for void closing tags. */ -class HtmlClosingVoidTagsSniff implements Sniff +class HtmlClosingVoidTagsSniff extends HtmlSelfClosingTagsSniff implements Sniff { /** * String representation of warning. @@ -30,39 +30,6 @@ class HtmlClosingVoidTagsSniff implements Sniff */ private const WARNING_CODE = 'HtmlClosingVoidElements'; - /** - * List of void elements. - * - * https://html.spec.whatwg.org/multipage/syntax.html#void-elements - * - * @var string[] - */ - private const HTML_VOID_ELEMENTS = [ - 'area', - 'base', - 'br', - 'col', - 'embed', - 'hr', - 'input', - 'keygen', - 'link', - 'menuitem', - 'meta', - 'param', - 'source', - 'track', - 'wbr' - ]; - - /** - * @inheritdoc - */ - public function register(): array - { - return [T_INLINE_HTML]; - } - /** * Detect use of self-closing tag with void html element. * @@ -84,11 +51,25 @@ public function process(File $phpcsFile, $stackPtr): void if (preg_match_all('$<(\w{2,})\s?[^<]*\/>$', $html, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { if (in_array($match[1], self::HTML_VOID_ELEMENTS)) { - $phpcsFile->addWarning( + $ptr = $this->findPointer($phpcsFile, $match[0]); + $fix = $phpcsFile->addFixableWarning( sprintf(self::WARNING_MESSAGE, $match[0]), - null, + $ptr, self::WARNING_CODE ); + + if ($fix) { + $token = $phpcsFile->getTokens()[$ptr]; + $original = $token['content']; + $replacement = str_replace(' />', '>', $original); + $replacement = str_replace('/>', '>', $replacement); + + if (preg_match('{^\s* />}', $original)) { + $replacement = ' ' . $replacement; + } + + $phpcsFile->fixer->replaceToken($ptr, $replacement); + } } } } diff --git a/Magento2/Sniffs/Html/HtmlSelfClosingTagsSniff.php b/Magento2/Sniffs/Html/HtmlSelfClosingTagsSniff.php index ecbdca44..627b7152 100644 --- a/Magento2/Sniffs/Html/HtmlSelfClosingTagsSniff.php +++ b/Magento2/Sniffs/Html/HtmlSelfClosingTagsSniff.php @@ -17,13 +17,13 @@ class HtmlSelfClosingTagsSniff implements Sniff { /** - * List of void elements + * List of void elements. * - * https://www.w3.org/TR/html51/syntax.html#writing-html-documents-elements + * https://html.spec.whatwg.org/multipage/syntax.html#void-elements * * @var string[] */ - private $voidElements = [ + protected const HTML_VOID_ELEMENTS = [ 'area', 'base', 'br', @@ -32,16 +32,16 @@ class HtmlSelfClosingTagsSniff implements Sniff 'hr', 'img', 'input', - 'keygen', 'link', - 'menuitem', 'meta', - 'param', 'source', 'track', 'wbr', ]; + /** @var int */ + private int $lastPointer = 0; + /** * @inheritDoc */ @@ -55,7 +55,7 @@ public function register() * * @param File $phpcsFile * @param int $stackPtr - * @return int|void + * @return void */ public function process(File $phpcsFile, $stackPtr) { @@ -70,15 +70,63 @@ public function process(File $phpcsFile, $stackPtr) if (preg_match_all('$<(\w{2,})\s?[^<]*\/>$', $html, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { - if (!in_array($match[1], $this->voidElements)) { - $phpcsFile->addError( + if (!in_array($match[1], self::HTML_VOID_ELEMENTS)) { + $ptr = $this->findPointer($phpcsFile, $match[0]); + $fix = $phpcsFile->addFixableError( 'Avoid using self-closing tag with non-void html element' - . ' - "' . $match[0] . PHP_EOL, - null, + . ' - "' . $match[0] . PHP_EOL, + $ptr, 'HtmlSelfClosingNonVoidTag' ); + + if ($fix) { + $token = $phpcsFile->getTokens()[$ptr]; + $original = $token['content']; + $replacement = str_replace(' />', '>' . $match[1] . '>', $original); + $replacement = str_replace('/>', '>' . $match[1] . '>', $replacement); + + if (preg_match('{^\s* />}', $original)) { + $replacement = ' ' . $replacement; + } + + $phpcsFile->fixer->replaceToken($ptr, $replacement); + } } } } } + + /** + * Apply a fix for the detected issue + * + * @param File $phpcsFile + * @param string $needle + * @return int|null + */ + protected function findPointer(File $phpcsFile, string $needle): ?int + { + if (str_contains($needle, "\n")) { + foreach (explode("\n", $needle) as $line) { + $result = $this->findPointer($phpcsFile, $line); + } + return $result; + } + + foreach ($phpcsFile->getTokens() as $ptr => $token) { + if ($ptr < $this->lastPointer) { + continue; + } + + if ($token['code'] !== T_INLINE_HTML) { + continue; + } + + if (str_contains($token['content'], $needle)) { + $this->lastPointer = $ptr; + return $ptr; + } + } + + return null; + } } diff --git a/Magento2/Tests/Html/HtmlClosingVoidTagsUnitTest.inc b/Magento2/Tests/Html/HtmlClosingVoidTagsUnitTest.inc index dd701d03..68f40388 100644 --- a/Magento2/Tests/Html/HtmlClosingVoidTagsUnitTest.inc +++ b/Magento2/Tests/Html/HtmlClosingVoidTagsUnitTest.inc @@ -22,14 +22,21 @@