Skip to content

Commit 4217c33

Browse files
[Magento Community Engineering] Community Contributions - 2.3-develop
- merged latest code from mainline branch
2 parents 8263717 + 3f24171 commit 4217c33

File tree

10 files changed

+328
-0
lines changed

10 files changed

+328
-0
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Sniffs\Html;
8+
9+
use PHP_CodeSniffer\Sniffs\Sniff;
10+
use PHP_CodeSniffer\Files\File;
11+
12+
/**
13+
* Sniffing improper HTML bindings.
14+
*/
15+
class HtmlBindingSniff implements Sniff
16+
{
17+
/**
18+
* @inheritDoc
19+
*/
20+
public function register()
21+
{
22+
return [T_INLINE_HTML];
23+
}
24+
25+
/**
26+
* Load HTML document to validate.
27+
*
28+
* @param int $stackPointer
29+
* @param File $file
30+
* @return \DOMDocument|null
31+
*/
32+
private function loadHtmlDocument(int $stackPointer, File $file): ?\DOMDocument
33+
{
34+
if ($stackPointer === 0) {
35+
$html = $file->getTokensAsString($stackPointer, count($file->getTokens()));
36+
$dom = new \DOMDocument();
37+
try {
38+
// phpcs:disable Generic.PHP.NoSilencedErrors
39+
@$dom->loadHTML($html);
40+
return $dom;
41+
} catch (\Throwable $exception) {
42+
return null;
43+
}
44+
}
45+
46+
return null;
47+
}
48+
49+
/**
50+
* @inheritDoc
51+
*
52+
* Find HTML data bindings and check variables used.
53+
*/
54+
public function process(File $phpcsFile, $stackPtr)
55+
{
56+
if (!$dom = $this->loadHtmlDocument($stackPtr, $phpcsFile)) {
57+
return;
58+
}
59+
60+
/** @var string[] $htmlBindings */
61+
$htmlBindings = [];
62+
$domXpath = new \DOMXPath($dom);
63+
$dataBindAttributes = $domXpath->query('//@*[name() = "data-bind"]');
64+
foreach ($dataBindAttributes as $dataBindAttribute) {
65+
$knockoutBinding = $dataBindAttribute->nodeValue;
66+
preg_match('/^(.+\s*?)?html\s*?\:(.+)/ims', $knockoutBinding, $htmlBindingStart);
67+
if ($htmlBindingStart) {
68+
$htmlBinding = trim(preg_replace('/\,[a-z0-9\_\s]+\:.+/ims', '', $htmlBindingStart[2]));
69+
$htmlBindings[] = $htmlBinding;
70+
}
71+
}
72+
$htmlAttributes = $domXpath->query('//@*[name() = "html"]');
73+
foreach ($htmlAttributes as $htmlAttribute) {
74+
$magentoBinding = $htmlAttribute->nodeValue;
75+
$htmlBindings[] = trim($magentoBinding);
76+
}
77+
foreach ($htmlBindings as $htmlBinding) {
78+
if (!preg_match('/^[0-9\\\'\"]/ims', $htmlBinding)
79+
&& !preg_match('/UnsanitizedHtml(\(.*?\))*?$/', $htmlBinding)
80+
) {
81+
$phpcsFile->addError(
82+
'Variables/functions used for HTML binding must have UnsanitizedHtml suffix'
83+
. ' - "' . $htmlBinding . '" doesn\'t,' . PHP_EOL
84+
. 'consider using text binding if the value is supposed to be text',
85+
null,
86+
'UIComponentTemplate.KnockoutBinding.HtmlSuffix'
87+
);
88+
}
89+
}
90+
}
91+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\TestFramework\CodingStandard\Tool\CodeSniffer;
8+
9+
/**
10+
* Add HTML files extension to config.
11+
*/
12+
class HtmlWrapper extends Wrapper
13+
{
14+
const FILE_EXTENSION = 'html';
15+
16+
private const TOKENIZER = 'PHP';
17+
18+
/**
19+
* @inheritDoc
20+
*/
21+
public function init()
22+
{
23+
parent::init();
24+
25+
$this->config->extensions += [self::FILE_EXTENSION => self::TOKENIZER];
26+
}
27+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Sniffs\Html;
10+
11+
use Magento\TestFramework\CodingStandard\Tool\CodeSniffer\HtmlWrapper;
12+
use PHPUnit\Framework\TestCase;
13+
use Magento\TestFramework\CodingStandard\Tool\CodeSniffer;
14+
15+
/**
16+
* Test the html binding sniff on real files.
17+
*/
18+
class HtmlBindingSniffTest extends TestCase
19+
{
20+
/**
21+
* Files to sniff and expected reports.
22+
*
23+
* @return array
24+
*/
25+
public function processDataProvider(): array
26+
{
27+
return [
28+
[
29+
'test-html-binding.html',
30+
'test-html-binding-errors.txt'
31+
]
32+
];
33+
}
34+
35+
/**
36+
* Run CS on provided files.
37+
*
38+
* @param string $fileUnderTest
39+
* @param string $expectedReportFile
40+
* @return void
41+
* @dataProvider processDataProvider
42+
*/
43+
public function testProcess(string $fileUnderTest, string $expectedReportFile): void
44+
{
45+
$reportFile = __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'phpcs_report.txt';
46+
$ruleSetDir = __DIR__ . DIRECTORY_SEPARATOR . '_files';
47+
$wrapper = new HtmlWrapper();
48+
$codeSniffer = new CodeSniffer($ruleSetDir, $reportFile, $wrapper);
49+
$codeSniffer->setExtensions([HtmlWrapper::FILE_EXTENSION]);
50+
$result = $codeSniffer->run(
51+
[__DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . $fileUnderTest]
52+
);
53+
// Remove the absolute path to the file from the output
54+
//phpcs:ignore
55+
$actual = preg_replace('/^.+\n/', '', ltrim(file_get_contents($reportFile)));
56+
//phpcs:ignore
57+
$expected = file_get_contents(
58+
__DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . $expectedReportFile
59+
);
60+
//phpcs:ignore
61+
unlink($reportFile);
62+
$this->assertEquals(1, $result);
63+
$this->assertEquals($expected, $actual);
64+
}
65+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<ruleset name="Magento">
9+
<description>UI Component Coding Standard</description>
10+
<rule ref="Internal.NoCodeFound">
11+
<severity>0</severity>
12+
</rule>
13+
<rule ref="../../../../../../../Magento/Sniffs/Html"/>
14+
</ruleset>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2+
FOUND 6 ERRORS AFFECTING 1 LINE
3+
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4+
1 | ERROR | Variables/functions used for HTML binding must have UnsanitizedHtml suffix - "testError()" doesn't,
5+
| | consider using text binding if the value is supposed to be text
6+
1 | ERROR | Variables/functions used for HTML binding must have UnsanitizedHtml suffix - "test.getSomething().value.error()" doesn't,
7+
| | consider using text binding if the value is supposed to be text
8+
1 | ERROR | Variables/functions used for HTML binding must have UnsanitizedHtml suffix - "bind_stuff(1, 2)" doesn't,
9+
| | consider using text binding if the value is supposed to be text
10+
1 | ERROR | Variables/functions used for HTML binding must have UnsanitizedHtml suffix - "testError()" doesn't,
11+
| | consider using text binding if the value is supposed to be text
12+
1 | ERROR | Variables/functions used for HTML binding must have UnsanitizedHtml suffix - "test.getSomething().value.error(1)" doesn't,
13+
| | consider using text binding if the value is supposed to be text
14+
1 | ERROR | Variables/functions used for HTML binding must have UnsanitizedHtml suffix - "bind_stuff()" doesn't,
15+
| | consider using text binding if the value is supposed to be text
16+
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
17+
18+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!--
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
-->
7+
8+
<div data-bind="attr: test.value"></div>
9+
<p>Test</p>
10+
<span data-bind="html: testError()"></span>
11+
<div data-bind="
12+
attr : tst,
13+
html: test.getSomething().value.error()
14+
"></div>
15+
<p data-bind="html: '<b>Some html</b>', attr: test"></p>
16+
<div data-bind="html: valueUnsanitizedHtml"></div>
17+
<div data-bind="attr: testhtml, html: valueUnsanitizedHtml()"></div>
18+
<p data-bind="other_html: bind, html: bind_stuff(1, 2)"></p>
19+
20+
<div style="tst()"></div>
21+
<span html="testError()"></span>
22+
<div html="
23+
test.getSomething().value.error(1)
24+
"></div>
25+
<p html="'<b>Some html</b>'"></p>
26+
<div html="valueUnsanitizedHtml"></div>
27+
<div html="
28+
valueUnsanitizedHtml('test')
29+
"></div>
30+
<p html="bind_stuff()"></p>

dev/tests/static/phpunit.xml.dist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
<testsuite name="Less Static Code Analysis">
1919
<file>testsuite/Magento/Test/Less/LiveCodeTest.php</file>
2020
</testsuite>
21+
<testsuite name="HTML Static Code Analysis">
22+
<file>testsuite/Magento/Test/Html/LiveCodeTest.php</file>
23+
</testsuite>
2124
<testsuite name="PHP Coding Standard Verification">
2225
<file>testsuite/Magento/Test/Php/LiveCodeTest.php</file>
2326
</testsuite>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Test\Html;
8+
9+
use Magento\Framework\App\Utility;
10+
use Magento\TestFramework\CodingStandard\Tool\CodeSniffer;
11+
use Magento\Framework\App\Utility\Files;
12+
use Magento\Test\Php\LiveCodeTest as PHPCodeTest;
13+
use PHPUnit\Framework\TestCase;
14+
15+
/**
16+
* Set of tests for static code style
17+
*/
18+
class LiveCodeTest extends TestCase
19+
{
20+
/**
21+
* @var string
22+
*/
23+
private static $reportDir = '';
24+
25+
/**
26+
* Setup basics for all tests
27+
*
28+
* @return void
29+
*/
30+
public static function setUpBeforeClass(): void
31+
{
32+
self::$reportDir = BP . '/dev/tests/static/report';
33+
if (!is_dir(self::$reportDir)) {
34+
mkdir(self::$reportDir, 0770);
35+
}
36+
}
37+
38+
/**
39+
* Run the magento specific coding standards on the code
40+
*
41+
* @return void
42+
*/
43+
public function testCodeStyle(): void
44+
{
45+
$reportFile = self::$reportDir . '/html_report.txt';
46+
$wrapper = new CodeSniffer\HtmlWrapper();
47+
$codeSniffer = new CodeSniffer(realpath(__DIR__ . '/_files/html'), $reportFile, $wrapper);
48+
if (!$codeSniffer->canRun()) {
49+
$this->markTestSkipped('PHP Code Sniffer is not installed.');
50+
}
51+
$codeSniffer->setExtensions([CodeSniffer\HtmlWrapper::FILE_EXTENSION]);
52+
//Looking for changed .html files
53+
$fileList = PHPCodeTest::getWhitelist([CodeSniffer\HtmlWrapper::FILE_EXTENSION], __DIR__, __DIR__);
54+
55+
$result = $codeSniffer->run($fileList);
56+
57+
$report = file_exists($reportFile) ? file_get_contents($reportFile) : "";
58+
$this->assertEquals(
59+
0,
60+
$result,
61+
"PHP Code Sniffer has found {$result} error(s): " . PHP_EOL . $report
62+
);
63+
}
64+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<ruleset name="Magento">
9+
<description>UI Component Coding Standard</description>
10+
<rule ref="Internal.NoCodeFound">
11+
<severity>0</severity>
12+
</rule>
13+
<rule ref="../../../../../../framework/Magento/Sniffs/Html"/>
14+
</ruleset>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Format: <componentType=module|library|theme|language|*> <componentName> <globPattern> or simply <globPattern>
2+
* * /

0 commit comments

Comments
 (0)