Skip to content

Commit 9aa864d

Browse files
authored
Merge branch 'develop' into patch-1
2 parents b76ae4e + a5bf741 commit 9aa864d

8 files changed

+595
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
<?php
2+
/**
3+
* Copyright © Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento2\Sniffs\Classes;
7+
8+
use PHP_CodeSniffer\Files\File;
9+
use PHP_CodeSniffer\Sniffs\Sniff;
10+
11+
/**
12+
* Detects explicit request of proxies and interceptors in constructors
13+
*
14+
* Search is in variable names and namespaces, including indirect namespaces from use statements
15+
*/
16+
class DiscouragedDependenciesSniff implements Sniff
17+
{
18+
const CONSTRUCT_METHOD_NAME = '__construct';
19+
20+
/**
21+
* String representation of warning.
22+
*
23+
* @var string
24+
*/
25+
protected $warningMessage = 'Proxies and interceptors MUST never be explicitly requested in constructors.';
26+
27+
/**
28+
* Warning violation code.
29+
*
30+
* @var string
31+
*/
32+
protected $warningCode = 'ConstructorProxyInterceptor';
33+
34+
/**
35+
* Aliases of proxies or plugins from use statements
36+
*
37+
* @var string[]
38+
*/
39+
private $aliases = [];
40+
41+
/**
42+
* The current file - used for clearing USE aliases when file changes
43+
*
44+
* @var null|string
45+
*/
46+
private $currentFile = null;
47+
48+
/**
49+
* Terms to search for in variables and namespaces
50+
*
51+
* @var string[]
52+
*/
53+
public $incorrectClassNames = ['proxy','interceptor'];
54+
55+
/**
56+
* @inheritDoc
57+
*/
58+
public function register()
59+
{
60+
return [
61+
T_USE,
62+
T_FUNCTION
63+
];
64+
}
65+
66+
/**
67+
* @inheritDoc
68+
*/
69+
public function process(File $phpcsFile, $stackPtr)
70+
{
71+
// Clear down aliases when file under test changes
72+
$currentFileName = $phpcsFile->getFilename();
73+
if ($this->currentFile != $currentFileName) {
74+
// Clear aliases
75+
$this->aliases = [];
76+
$this->currentFile = $currentFileName;
77+
}
78+
79+
// Match use statements and constructor (latter matches T_FUNCTION to find constructors)
80+
$tokens = $phpcsFile->getTokens();
81+
82+
if ($tokens[$stackPtr]['code'] == T_USE) {
83+
$this->processUse($phpcsFile, $stackPtr, $tokens);
84+
} elseif ($tokens[$stackPtr]['code'] == T_FUNCTION) {
85+
$this->processFunction($phpcsFile, $stackPtr, $tokens);
86+
}
87+
}
88+
89+
/**
90+
* Store plugin/proxy class names for use in matching constructor
91+
*
92+
* @param File $phpcsFile
93+
* @param int $stackPtr
94+
* @param array $tokens
95+
*/
96+
private function processUse(
97+
File $phpcsFile,
98+
$stackPtr,
99+
$tokens
100+
) {
101+
// Find end of use statement and position of AS alias if exists
102+
$endPos = $phpcsFile->findNext(T_SEMICOLON, $stackPtr);
103+
$asPos = $phpcsFile->findNext(T_AS, $stackPtr, $endPos);
104+
// Find whether this use statement includes any of the warning words
105+
$includesWarnWord =
106+
$this->includesWarnWordsInSTRINGs(
107+
$phpcsFile,
108+
$stackPtr,
109+
min($asPos, $endPos),
110+
$tokens,
111+
$lastWord
112+
);
113+
if (! $includesWarnWord) {
114+
return;
115+
}
116+
// If there is an alias then store this explicit alias for matching in constructor
117+
if ($asPos) {
118+
$aliasNamePos = $asPos + 2;
119+
$this->aliases[] = strtolower($tokens[$aliasNamePos]['content']);
120+
}
121+
// Always store last word as alias for checking in constructor
122+
$this->aliases[] = $lastWord;
123+
}
124+
125+
/**
126+
* If constructor, check for proxy/plugin names and warn
127+
*
128+
* @param File $phpcsFile
129+
* @param int $stackPtr
130+
* @param array $tokens
131+
*/
132+
private function processFunction(
133+
File $phpcsFile,
134+
$stackPtr,
135+
$tokens
136+
) {
137+
// Find start and end of constructor signature based on brackets
138+
if (! $this->getConstructorPosition($phpcsFile, $stackPtr, $tokens, $openParenth, $closeParenth)) {
139+
return;
140+
}
141+
$positionInConstrSig = $openParenth;
142+
$lastName = null;
143+
do {
144+
// Find next part of namespace (string) or variable name
145+
$positionInConstrSig = $phpcsFile->findNext(
146+
[T_STRING, T_VARIABLE],
147+
$positionInConstrSig + 1,
148+
$closeParenth
149+
);
150+
151+
$currentTokenIsString = $tokens[$positionInConstrSig]['code'] == T_STRING;
152+
153+
if ($currentTokenIsString) {
154+
// Remember string in case this is last before variable
155+
$lastName = strtolower($tokens[$positionInConstrSig]['content']);
156+
} else {
157+
// If this is a variable, check last word for matches as was end of classname/alias
158+
if ($lastName !== null) {
159+
$namesToWarn = $this->mergedNamesToWarn(true);
160+
if ($this->containsWord($namesToWarn, $lastName)) {
161+
$phpcsFile->addError(
162+
$this->warningMessage,
163+
$positionInConstrSig,
164+
$this->warningCode,
165+
[$lastName]
166+
);
167+
}
168+
$lastName = null;
169+
}
170+
}
171+
172+
} while ($positionInConstrSig !== false && $positionInConstrSig < $closeParenth);
173+
}
174+
175+
/**
176+
* Sets start and end of constructor signature or returns false
177+
*
178+
* @param File $phpcsFile
179+
* @param int $stackPtr
180+
* @param array $tokens
181+
* @param int $openParenth
182+
* @param int $closeParenth
183+
*
184+
* @return bool Whether a constructor
185+
*/
186+
private function getConstructorPosition(
187+
File $phpcsFile,
188+
$stackPtr,
189+
array $tokens,
190+
&$openParenth,
191+
&$closeParenth
192+
) {
193+
$methodNamePos = $phpcsFile->findNext(T_STRING, $stackPtr - 1);
194+
if ($methodNamePos === false) {
195+
return false;
196+
}
197+
// There is a method name
198+
if ($tokens[$methodNamePos]['content'] != self::CONSTRUCT_METHOD_NAME) {
199+
return false;
200+
}
201+
202+
// KNOWN: There is a constructor, so get position
203+
204+
$openParenth = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $methodNamePos);
205+
$closeParenth = $phpcsFile->findNext(T_CLOSE_PARENTHESIS, $openParenth);
206+
if ($openParenth === false || $closeParenth === false) {
207+
return false;
208+
}
209+
210+
return true;
211+
}
212+
213+
/**
214+
* Whether $name exactly matches any of $haystacks
215+
*
216+
* @param array $haystacks
217+
* @param string $name
218+
*
219+
* @return bool
220+
*/
221+
private function containsWord($haystacks, $name)
222+
{
223+
return in_array($name, $haystacks);
224+
}
225+
226+
/**
227+
* Whether warn words are included in STRING tokens in the given range
228+
*
229+
* Populates $lastWord in set to get usable name from namespace
230+
*
231+
* @param File $phpcsFile
232+
* @param int $startPosition
233+
* @param int $endPosition
234+
* @param array $tokens
235+
* @param string|null $lastWord
236+
*
237+
* @return bool
238+
*/
239+
private function includesWarnWordsInSTRINGs(
240+
File $phpcsFile,
241+
$startPosition,
242+
$endPosition,
243+
$tokens,
244+
&$lastWord
245+
) {
246+
$includesWarnWord = false;
247+
$currentPosition = $startPosition;
248+
do {
249+
$currentPosition = $phpcsFile->findNext(T_STRING, $currentPosition + 1, $endPosition);
250+
if ($currentPosition !== false) {
251+
$word = strtolower($tokens[$currentPosition]['content']);
252+
if ($this->containsWord($this->mergedNamesToWarn(false), $word)) {
253+
$includesWarnWord = true;
254+
}
255+
$lastWord = $word;
256+
}
257+
} while ($currentPosition !== false && $currentPosition < $endPosition);
258+
259+
return $includesWarnWord;
260+
}
261+
262+
/**
263+
* Get array of names that if matched should raise warning.
264+
*
265+
* Includes aliases if required
266+
*
267+
* @param bool $includeAliases
268+
*
269+
* @return array
270+
*/
271+
private function mergedNamesToWarn($includeAliases = false)
272+
{
273+
$namesToWarn = $this->incorrectClassNames;
274+
if ($includeAliases) {
275+
$namesToWarn = array_merge($namesToWarn, $this->aliases);
276+
}
277+
278+
return $namesToWarn;
279+
}
280+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
/**
3+
* Copyright © Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento2\Sniffs\Performance;
7+
8+
use PHP_CodeSniffer\Files\File;
9+
use PHP_CodeSniffer\Sniffs\Sniff;
10+
11+
/**
12+
* Detects array_merge(...) is used in a loop and is a resources greedy construction.
13+
*/
14+
class ForeachArrayMergeSniff implements Sniff
15+
{
16+
/**
17+
* String representation of warning.
18+
*
19+
* @var string
20+
*/
21+
protected $warningMessage = 'array_merge(...) is used in a loop and is a resources greedy construction.';
22+
23+
/**
24+
* Warning violation code.
25+
*
26+
* @var string
27+
*/
28+
protected $warningCode = 'ForeachArrayMerge';
29+
30+
/**
31+
* @var array
32+
*/
33+
protected $foreachCache = [];
34+
35+
/**
36+
* @inheritdoc
37+
*/
38+
public function register()
39+
{
40+
return [T_FOREACH, T_FOR];
41+
}
42+
43+
/**
44+
* @inheritdoc
45+
*/
46+
public function process(File $phpcsFile, $stackPtr)
47+
{
48+
$tokens = $phpcsFile->getTokens();
49+
50+
$scopeOpener = $tokens[$stackPtr]['scope_opener'];
51+
$scopeCloser = $tokens[$stackPtr]['scope_closer'];
52+
53+
for ($i = $scopeOpener; $i < $scopeCloser; $i++) {
54+
$tag = $tokens[$i];
55+
if ($tag['code'] !== T_STRING) {
56+
continue;
57+
}
58+
if ($tag['content'] !== 'array_merge') {
59+
continue;
60+
}
61+
62+
$cacheKey = $phpcsFile->getFilename() . $i;
63+
if (isset($this->foreachCache[$cacheKey])) {
64+
continue;
65+
}
66+
67+
$this->foreachCache[$cacheKey] = '';
68+
$phpcsFile->addWarning($this->warningMessage, $i, $this->warningCode);
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)