Skip to content

Commit 5a43f86

Browse files
authored
Merge pull request #64 from magento-ecg/ecgm2-added-extdn-rules
Ecgm2 added extdn rules
2 parents 86cd118 + 9069159 commit 5a43f86

File tree

7 files changed

+240
-12
lines changed

7 files changed

+240
-12
lines changed

Ecg/Sniffs/Performance/LoopSniff.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public function process(File $phpcsFile, $stackPtr)
7070
return;
7171
}
7272

73-
for ($ptr = $tokens[$stackPtr]['scope_opener'] + 1; $ptr < $tokens[$stackPtr]['scope_closer']; $ptr++) {
73+
for ($ptr = $tokens[$stackPtr]['parenthesis_opener'] + 1; $ptr < $tokens[$stackPtr]['scope_closer']; $ptr++) {
7474
$content = $tokens[$ptr]['content'];
7575
if ($tokens[$ptr]['code'] !== T_STRING || in_array($ptr, $this->processedStackPointers)) {
7676
continue;
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace EcgM2\Sniffs;
6+
7+
use PHP_CodeSniffer\Files\File;
8+
use PHP_CodeSniffer\Sniffs\Sniff;
9+
10+
class MissingParentCallSniff implements Sniff
11+
{
12+
private const CONSTRUCTOR_METHOD_NAME = '__construct';
13+
private const PARENT_KEYWORD = 'parent';
14+
15+
private const TYPE_STRING_T_WHITESPACE = 'T_WHITESPACE';
16+
private const TYPE_STRING_T_STRING = 'T_STRING';
17+
private const TYPE_STRING_T_DOUBLE_COLON = 'T_DOUBLE_COLON';
18+
19+
private array $tokens = [];
20+
21+
private File $file;
22+
23+
/**
24+
* @return array|int[]|mixed[]
25+
*/
26+
public function register()
27+
{
28+
return [
29+
T_EXTENDS
30+
];
31+
}
32+
33+
/**
34+
* @param File $phpcsFile
35+
* @param $stackPtr
36+
* @return int|void
37+
*/
38+
public function process(File $phpcsFile, $stackPtr)
39+
{
40+
$this->file = $phpcsFile;
41+
$this->tokens = $phpcsFile->getTokens();
42+
if (!$this->isClass()) {
43+
return;
44+
}
45+
$constructorIndex = $this->getConstructorIndex();
46+
if (false === $constructorIndex) {
47+
return;
48+
}
49+
50+
if (!$this->hasConstructorParentCall($constructorIndex)) {
51+
$phpcsFile->addWarning(
52+
'No parent method call! Possible violation',
53+
$constructorIndex,
54+
'NoParentMethodCall'
55+
);
56+
}
57+
}
58+
59+
/**
60+
* @return bool
61+
*/
62+
private function isClass(): bool
63+
{
64+
return false !== $this->file->findNext(T_CLASS, 0);
65+
}
66+
67+
/**
68+
* @return bool|int
69+
*/
70+
private function getConstructorIndex()
71+
{
72+
$publicIndex = 0;
73+
while ($publicIndex = $this->findNextPublic($publicIndex)) {
74+
$endOfStatement = $this->file->findEndOfStatement($publicIndex);
75+
$functionIndex = $this->file->findNext(T_FUNCTION, $publicIndex, $endOfStatement);
76+
if (false === $functionIndex) {
77+
$publicIndex = $endOfStatement;
78+
continue;
79+
}
80+
81+
$methodNameIndex = $this->file->findNext(T_STRING, $functionIndex, $endOfStatement);
82+
if ($methodNameIndex
83+
&& $this->tokens[$methodNameIndex]['content'] === self::CONSTRUCTOR_METHOD_NAME
84+
) {
85+
return $methodNameIndex;
86+
}
87+
}
88+
89+
return false;
90+
}
91+
92+
/**
93+
* @param int $startPos
94+
* @return bool|int
95+
*/
96+
private function findNextPublic(int $startPos)
97+
{
98+
return $this->file->findNext(T_PUBLIC, $startPos);
99+
}
100+
101+
/**
102+
* @param int $constructorIndex
103+
* @return bool
104+
*/
105+
private function hasConstructorParentCall(int $constructorIndex): bool
106+
{
107+
return false !== $this->findParentCallInStatement($constructorIndex);
108+
}
109+
110+
/**
111+
* @param int $constructorIndex
112+
* @return bool|int
113+
*/
114+
private function findParentCallInStatement(int $constructorIndex)
115+
{
116+
$endOfConstructorMethodIndex = $this->file->findEndOfStatement($constructorIndex);
117+
$curlyOpeningIndex = $this->file->findNext(
118+
T_OPEN_CURLY_BRACKET,
119+
$constructorIndex,
120+
$endOfConstructorMethodIndex
121+
);
122+
$stringIndex = $curlyOpeningIndex + 1;
123+
while ($stringIndex = $this->getNextTString($stringIndex, $endOfConstructorMethodIndex)) {
124+
if ($this->tokens[$stringIndex]['content'] !== self::PARENT_KEYWORD) {
125+
$stringIndex++;
126+
continue;
127+
}
128+
129+
if ($this->tokens[$stringIndex + 1]['type'] === self::TYPE_STRING_T_DOUBLE_COLON) {
130+
return $stringIndex;
131+
}
132+
}
133+
134+
return false;
135+
}
136+
137+
/**
138+
* @param $startIndex
139+
* @param int $endIndex
140+
* @return bool|int
141+
*/
142+
private function getNextTString($startIndex, int $endIndex)
143+
{
144+
return $this->file->findNext(T_STRING, $startIndex, $endIndex);
145+
}
146+
}

EcgM2/Sniffs/Templates/EscapedOutputSniff.php

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ class EscapedOutputSniff implements Sniff
1919

2020
private const IGNORE_OPEN_TAG_TOKEN_TYPE = 'T_ECHO';
2121

22+
private const ERROR_MESSAGE_BLOCK_ESCAPE_METHODS_DEPRECATED =
23+
'Use $escaper rather than $block as the use of $block->escape{method} has been deprecated. ' .
24+
'See https://devdocs.magento.com/guides/v2.4/release-notes/release-notes-2-4-0-open-source.html';
25+
26+
private const ERROR_MESSAGE_INVALID_VARIABLE_NAME = 'Use $escaper variable only for escaping output. ' .
27+
'See https://devdocs.magento.com/guides/v2.4/release-notes/release-notes-2-4-0-open-source.html';
28+
29+
private const VARIABLE_NAME_BLOCK = '$block';
30+
31+
private const VARIABLE_NAME_ESCAPER = '$escaper';
32+
2233
private array $tokens = [];
2334

2435
private array $ignoreTokenType = [
@@ -40,6 +51,8 @@ class EscapedOutputSniff implements Sniff
4051
'escapeQuote'
4152
];
4253

54+
private File $file;
55+
4356
/**
4457
* @return int[]|mixed[]|void
4558
*/
@@ -59,6 +72,7 @@ public function register()
5972
public function process(File $phpcsFile, $stackPtr)
6073
{
6174
$this->tokens = $phpcsFile->getTokens();
75+
$this->file = $phpcsFile;
6276
$stackClosingPtr = $this->getClosingTagPtr($stackPtr);
6377
if (null === $stackClosingPtr) {
6478
return;
@@ -182,10 +196,34 @@ private function hasNoEscapeProvided(int $i): bool
182196
*/
183197
private function isEscapeMethodCall(int $index): bool
184198
{
185-
return $this->tokens[$index]['type'] === self::TOKEN_NAME_T_VARIABLE
199+
$result = $this->tokens[$index]['type'] === self::TOKEN_NAME_T_VARIABLE
186200
&& $this->tokens[$index + 1]['type'] === self::TOKEN_NAME_T_OBJECT_OPERATOR
187201
&& $this->tokens[$index + 2]['type'] === self::TOKEN_NAME_T_STRING
188202
&& in_array($this->tokens[$index + 2]['content'], $this->escapingMethodName, true);
203+
204+
if ($result) {
205+
switch ($this->tokens[$index]['content']) {
206+
case self::VARIABLE_NAME_BLOCK:
207+
$this->file->addError(
208+
self::ERROR_MESSAGE_BLOCK_ESCAPE_METHODS_DEPRECATED,
209+
$index,
210+
'DeprecatedEscapeUsage'
211+
);
212+
break;
213+
case self::VARIABLE_NAME_ESCAPER:
214+
//exact this is expected, nothing to do
215+
break;
216+
default:
217+
$this->file->addError(
218+
self::ERROR_MESSAGE_INVALID_VARIABLE_NAME,
219+
$index,
220+
'InvalidVariableEscapeUsage'
221+
);
222+
break;
223+
}
224+
}
225+
226+
return $result;
189227
}
190228

191229
/**
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace EcgM2\Tests;
6+
7+
use Magento\Framework\DataObject;
8+
use Php_Codesniffer\Files\File;
9+
10+
class MissingParentCallUnitTest1 extends DataObject
11+
{
12+
public const CONSTRUCTOR_METHOD_NAMES = '__construct';
13+
14+
private File $file;
15+
16+
private array $data = [];
17+
18+
/**
19+
* @param File $file
20+
* @param array $data
21+
*/
22+
public function __construct(File $file, array $data = [])
23+
{
24+
$this->file = $file;
25+
$this->data = $data;
26+
}
27+
}

EcgM2/Tests/Performance/LoopUnitTest1.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66

77
class LoopUnitTest1
88
{
9-
private array $productSkus = [];
9+
private array $productSkus = ['123', '234'];
1010

1111
private ProductRepositoryInterface $repository;
1212

1313
private SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory;
1414

1515
public function go()
1616
{
17+
for ($i = 0; $i < count($this->productSkus); $i++) {
18+
$a = $this->repository->get($this->getSkuById($i));
19+
}
20+
1721
$i = 0;
1822
$searchCriteriaBuilder = $this->searchCriteriaBuilderFactory->create();
1923
do {
@@ -25,8 +29,7 @@ public function go()
2529
} while (isset($this->productSkus[$i]));
2630

2731
$size = count($this->productSkus);
28-
for ($i = 0 ; $i < $size; $i++) {
29-
32+
for ($i = 0; $i < $size; $i++) {
3033
$a = $this->repository->get($this->getSkuById($i));
3134
$b = $this->repository->getById($i);
3235
$c = $this->repository->getList($searchCriteriaBuilder->create());

EcgM2/Tests/Templates/EscapedOutputUnitTest1.phtml

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php echo count($var); ?>
22
<?php echo /* @noEscape */ count($var); ?>
33
<?php /** @var $block \Magento\Catalog\Block\Product\View */ ?>
4-
<?php /** @var $escaper \Magento\Catalog\Block\Product\View */ ?>
4+
<?php /** @var $escaper \Magento\Framework\Escaper */ ?>
55
<?php /** @var $var array */ ?>
66
<?php echo $block->getTitleHtml() ?>
77
<?php echo /* @noEscape */ $block->getHtmlTitle() ?>
@@ -18,7 +18,10 @@
1818
</div>
1919

2020
<div class="field-tooltip-content">
21-
<?= $block->escapeHtml(__('Escaped Output')) ?>
21+
<?= $block->escapeHtml(__('Deprecated Escaped Output')) ?>
22+
</div>
23+
<div class="field-tooltip-content">
24+
<?= $escaper->escapeHtml(__('Correct Escaped Output')) ?>
2225
</div>
2326

2427

@@ -31,11 +34,19 @@
3134
<?= __('Unescaped Text') ?>
3235
</a>
3336

34-
<a href="<?= $block->escapeUrl($block->getUrl('Escaped')) ?>"
35-
title="<?= $block->escapeHtml(__('Escaped Title')) ?>"
37+
<a href="<?= $block->escapeUrl($block->getUrl('Deprecated Escaped')) ?>"
38+
title="<?= $block->escapeHtml(__('Deprecated Escaped Title')) ?>"
39+
id="noEscape-present-<?= /* @noEscape */ $block->getJsId() ?>"
40+
>
41+
<?= $block->escapeHtml(__('Deprecated Escaped Link Label')) ?>
42+
<span class="_strong"><?= $block->escapeHtml(__('Deprecated Escaped Label')) ?></span>
43+
<?= $block->escapeHtml(__('Deprecated Escaped Text')) ?>
44+
</a>
45+
<a href="<?= $escaper->escapeUrl($block->getUrl('Escaped')) ?>"
46+
title="<?= $escaper->escapeHtml(__('Escaped Title')) ?>"
3647
id="noEscape-present-<?= /* @noEscape */ $block->getJsId() ?>"
3748
>
38-
<?= $block->escapeHtml(__('Escaped Link Label')) ?>
39-
<span class="_strong"><?= $block->escapeHtml(__('Escaped Label')) ?></span>
40-
<?= $block->escapeHtml(__('Escaped Text')) ?>
49+
<?= $escaper->escapeHtml(__('Escaped Link Label')) ?>
50+
<span class="_strong"><?= $escaper->escapeHtml(__('Escaped Label')) ?></span>
51+
<?= $escaper->escapeHtml(__('Escaped Text')) ?>
4152
</a>

EcgM2/ruleset.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,7 @@
8686
<rule ref="EcgM2.Plugins.Plugin">
8787
<exclude-pattern>*\.php(?!$)</exclude-pattern>
8888
</rule>
89+
<rule ref="EcgM2.CodeStyle.MissingParentCall">
90+
<exclude-pattern>*\.php(?!$)</exclude-pattern>
91+
</rule>
8992
</ruleset>

0 commit comments

Comments
 (0)