Skip to content

Commit 2ee95fb

Browse files
committed
added includedFunctions/includedConstants whitelist, renamed ignored* to excluded*
1 parent 042ce9a commit 2ee95fb

File tree

9 files changed

+162
-61
lines changed

9 files changed

+162
-61
lines changed

preset-sniffer/optimize-fn.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222
<rule ref="../src/NetteCodingStandard/Sniffs/Namespaces/OptimizeGlobalCallsSniff.php">
2323
<properties>
2424
<property name="optimizedFunctionsOnly" value="true"/>
25-
<property name="ignoredFunctions" type="array">
25+
<property name="excludedFunctions" type="array">
2626
<element value="dump"/>
2727
<element value="var_dump"/>
2828
<element value="print_r"/>
2929
<element value="error_get_last"/>
3030
<element value="trigger_error"/>
3131
<element value="debug_backtrace"/>
3232
</property>
33-
<property name="ignoredConstants" type="array">
33+
<property name="excludedConstants" type="array">
3434
<element value="E_*"/>
3535
<element value="INFO_*"/>
3636
<element value="CURL*"/>

src/NetteCodingStandard/Sniffs/Namespaces/OptimizeGlobalCallsSniff.php

Lines changed: 97 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@
3131
* <rule ref="NetteCodingStandard.Namespaces.OptimizeGlobalCalls">
3232
* <properties>
3333
* <property name="optimizedFunctionsOnly" value="false"/>
34-
* <property name="ignoredFunctions" type="array">
34+
* <property name="includedConstants" type="array">
35+
* <element value="PHP_*"/>
36+
* <element value="DIRECTORY_SEPARATOR"/>
37+
* </property>
38+
* <property name="excludedFunctions" type="array">
3539
* <element value="dump"/>
3640
* <element value="dd"/>
3741
* </property>
38-
* <property name="ignoredConstants" type="array">
39-
* <element value="SOME_CONSTANT"/>
40-
* </property>
4142
* </properties>
4243
* </rule>
4344
* ```
@@ -53,8 +54,10 @@
5354
class OptimizeGlobalCallsSniff implements Sniff
5455
{
5556
public $optimizedFunctionsOnly = true;
56-
public $ignoredFunctions = [];
57-
public $ignoredConstants = [];
57+
public $includedFunctions = [];
58+
public $excludedFunctions = [];
59+
public $includedConstants = [];
60+
public $excludedConstants = [];
5861
private static $processedFiles = [];
5962

6063
private $compilerOptimizedFunctions = [
@@ -102,19 +105,26 @@ public function process(File $phpcsFile, $stackPtr)
102105
$usedConstants = $this->findUsedGlobalConstants($phpcsFile, $existingUseStatements);
103106

104107
$finalFunctions = $usedFunctions;
105-
if ($this->optimizedFunctionsOnly) {
106-
$nonOptimizedToKeep = [];
108+
if (!empty($this->includedFunctions) || $this->optimizedFunctionsOnly) {
109+
$nonIncludedToKeep = [];
107110
foreach ($existingUseStatements['all_functions'] as $name) {
108-
if (!in_array(strtolower($name), $this->compilerOptimizedFunctions, true)) {
109-
if ($this->isFunctionUsedInCode($phpcsFile, $name)) {
110-
$nonOptimizedToKeep[] = $name;
111-
}
111+
if (!$this->isFunctionIncluded($name) && $this->isFunctionUsedInCode($phpcsFile, $name)) {
112+
$nonIncludedToKeep[] = $name;
112113
}
113114
}
114-
$finalFunctions = array_values(array_unique(array_merge($finalFunctions, $nonOptimizedToKeep)));
115+
$finalFunctions = array_values(array_unique(array_merge($finalFunctions, $nonIncludedToKeep)));
115116
}
116117

117118
$finalConstants = $usedConstants;
119+
if (!empty($this->includedConstants)) {
120+
$nonIncludedToKeep = [];
121+
foreach ($existingUseStatements['all_constants'] as $name) {
122+
if (!$this->isConstantIncluded($name) && $this->isConstantUsedInCode($phpcsFile, $name)) {
123+
$nonIncludedToKeep[] = $name;
124+
}
125+
}
126+
$finalConstants = array_values(array_unique(array_merge($finalConstants, $nonIncludedToKeep)));
127+
}
118128

119129
$isCorrect = $this->isStateCorrect($phpcsFile, $finalFunctions, $finalConstants, $existingUseStatements);
120130
$hasBackslashesToRemove = $this->hasBackslashesToRemove($phpcsFile, $finalFunctions, $finalConstants);
@@ -293,6 +303,29 @@ private function isFunctionUsedInCode(File $phpcsFile, string $functionName): bo
293303
}
294304

295305

306+
private function isConstantUsedInCode(File $phpcsFile, string $constantName): bool
307+
{
308+
$tokens = $phpcsFile->getTokens();
309+
for ($i = 0; $i < $phpcsFile->numTokens; $i++) {
310+
if ($tokens[$i]['code'] === T_STRING && $tokens[$i]['content'] === $constantName) {
311+
$nextToken = $phpcsFile->findNext(T_WHITESPACE, $i + 1, null, true);
312+
if ($nextToken !== false && $tokens[$nextToken]['code'] === T_OPEN_PARENTHESIS) {
313+
continue; // function call, not constant
314+
}
315+
316+
$prevToken = $phpcsFile->findPrevious(T_WHITESPACE, $i - 1, null, true);
317+
if (
318+
$prevToken === false
319+
|| !in_array($tokens[$prevToken]['code'], [T_OBJECT_OPERATOR, T_DOUBLE_COLON, T_NULLSAFE_OBJECT_OPERATOR, T_COLON], true)
320+
) {
321+
return true;
322+
}
323+
}
324+
}
325+
return false;
326+
}
327+
328+
296329
private function isFunctionDeclaration(File $phpcsFile, int $stackPtr): bool
297330
{
298331
$prevSemicolon = $phpcsFile->findPrevious(T_SEMICOLON, $stackPtr - 1);
@@ -322,7 +355,6 @@ private function findUsedGlobalFunctions(File $phpcsFile, array $existingUseStat
322355
{
323356
$tokens = $phpcsFile->getTokens();
324357
$usedFunctions = [];
325-
$ignoredFunctions = array_map('strtolower', $this->ignoredFunctions);
326358

327359
for ($i = 0; $i < $phpcsFile->numTokens; $i++) {
328360
if ($this->isWithinUseStatement($i, $existingUseStatements)) {
@@ -342,20 +374,9 @@ private function findUsedGlobalFunctions(File $phpcsFile, array $existingUseStat
342374
continue;
343375
}
344376

345-
if (in_array(strtolower($functionName), $ignoredFunctions, true)) {
346-
continue;
347-
}
348-
349-
if ($this->optimizedFunctionsOnly) {
350-
if (!in_array(strtolower($functionName), $this->compilerOptimizedFunctions, true)) {
351-
continue;
352-
}
353-
} else {
354-
if (!function_exists($functionName)) {
355-
continue;
356-
}
377+
if ($this->isFunctionIncluded($functionName)) {
378+
$usedFunctions[] = $functionName;
357379
}
358-
$usedFunctions[] = $functionName;
359380
continue;
360381
}
361382

@@ -369,9 +390,6 @@ private function findUsedGlobalFunctions(File $phpcsFile, array $existingUseStat
369390
}
370391

371392
$functionName = $tokens[$i]['content'];
372-
if (in_array(strtolower($functionName), $ignoredFunctions, true)) {
373-
continue;
374-
}
375393

376394
$nextToken = $phpcsFile->findNext(T_WHITESPACE, $i + 1, null, true);
377395
if ($nextToken === false || $tokens[$nextToken]['code'] !== T_OPEN_PARENTHESIS) {
@@ -393,16 +411,9 @@ private function findUsedGlobalFunctions(File $phpcsFile, array $existingUseStat
393411
}
394412
}
395413

396-
if ($this->optimizedFunctionsOnly) {
397-
if (!in_array(strtolower($functionName), $this->compilerOptimizedFunctions, true)) {
398-
continue;
399-
}
400-
} else {
401-
if (!function_exists($functionName)) {
402-
continue;
403-
}
414+
if ($this->isFunctionIncluded($functionName)) {
415+
$usedFunctions[] = $functionName;
404416
}
405-
$usedFunctions[] = $functionName;
406417
}
407418
return array_values(array_unique($usedFunctions));
408419
}
@@ -431,14 +442,9 @@ private function findUsedGlobalConstants(File $phpcsFile, array $existingUseStat
431442
continue; // This is a function call, not a constant
432443
}
433444

434-
if ($this->isIgnoredConstant($constantName)) {
435-
continue;
445+
if ($this->isConstantIncluded($constantName)) {
446+
$usedConstants[] = $constantName;
436447
}
437-
438-
if (!defined($constantName)) {
439-
continue;
440-
}
441-
$usedConstants[] = $constantName;
442448
continue;
443449
}
444450

@@ -447,9 +453,6 @@ private function findUsedGlobalConstants(File $phpcsFile, array $existingUseStat
447453
}
448454

449455
$constantName = $tokens[$i]['content'];
450-
if ($this->isIgnoredConstant($constantName)) {
451-
continue;
452-
}
453456

454457
$prevTokenPtr = $phpcsFile->findPrevious(T_WHITESPACE, $i - 1, null, true);
455458
$nextTokenPtr = $phpcsFile->findNext(T_WHITESPACE, $i + 1, null, true);
@@ -482,10 +485,9 @@ private function findUsedGlobalConstants(File $phpcsFile, array $existingUseStat
482485
continue;
483486
}
484487

485-
if (!defined($constantName)) {
486-
continue;
488+
if ($this->isConstantIncluded($constantName)) {
489+
$usedConstants[] = $constantName;
487490
}
488-
$usedConstants[] = $constantName;
489491
}
490492
return array_values(array_unique($usedConstants));
491493
}
@@ -681,12 +683,12 @@ private function hasNamespace(File $phpcsFile): bool
681683
}
682684

683685

684-
private function isIgnoredConstant(string $constantName): bool
686+
private function matchesPatternList(string $name, array $patterns, bool $caseSensitive = false): bool
685687
{
686-
$ignored = array_merge($this->builtInIgnoredConstants, $this->ignoredConstants);
687-
foreach ($ignored as $pattern) {
688+
foreach ($patterns as $pattern) {
688689
$regex = str_replace('\*', '.*', preg_quote($pattern, '/'));
689-
if (preg_match('/^' . $regex . '$/i', $constantName)) {
690+
$flags = $caseSensitive ? '' : 'i';
691+
if (preg_match('/^' . $regex . '$/' . $flags, $name)) {
690692
return true;
691693
}
692694
}
@@ -695,6 +697,45 @@ private function isIgnoredConstant(string $constantName): bool
695697
}
696698

697699

700+
private function isExcludedConstant(string $constantName): bool
701+
{
702+
$excluded = array_merge($this->builtInIgnoredConstants, $this->excludedConstants);
703+
return $this->matchesPatternList($constantName, $excluded);
704+
}
705+
706+
707+
private function isFunctionIncluded(string $functionName): bool
708+
{
709+
if ($this->matchesPatternList($functionName, $this->excludedFunctions)) {
710+
return false;
711+
}
712+
713+
if (!empty($this->includedFunctions)) {
714+
return $this->matchesPatternList($functionName, $this->includedFunctions);
715+
}
716+
717+
if ($this->optimizedFunctionsOnly) {
718+
return in_array(strtolower($functionName), $this->compilerOptimizedFunctions, true);
719+
}
720+
721+
return function_exists($functionName);
722+
}
723+
724+
725+
private function isConstantIncluded(string $constantName): bool
726+
{
727+
if ($this->isExcludedConstant($constantName)) {
728+
return false;
729+
}
730+
731+
if (!empty($this->includedConstants)) {
732+
return $this->matchesPatternList($constantName, $this->includedConstants);
733+
}
734+
735+
return defined($constantName);
736+
}
737+
738+
698739
private function createLookupArrays(array $usedFunctions, array $usedConstants): array
699740
{
700741
$functionLookup = [];

tests/SniffTestRunner.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ class SniffTestRunner
157157

158158
/**
159159
* Parses a special JSON comment on the first line of the code to set sniff properties.
160-
* E.g., <?php // {"optimizedFunctionsOnly": true, "ignoredFunctions": ["dd"]}
160+
* E.g., <?php // {"optimizedFunctionsOnly": true, "excludedFunctions": ["dd"]}
161161
*/
162162
private function parsePropertiesFromJsonComment(string $code): array
163163
{

tests/fixtures/IgnoredConfigTest.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?php // {"optimizedFunctionsOnly": false, "ignoredFunctions": ["dd", "dump"]}
1+
<?php // {"optimizedFunctionsOnly": false, "excludedFunctions": ["dd", "dump"]}
22

33
namespace App;
44

tests/fixtures/IgnoredConfigTest.inc.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?php // {"optimizedFunctionsOnly": false, "ignoredFunctions": ["dd", "dump"]}
1+
<?php // {"optimizedFunctionsOnly": false, "excludedFunctions": ["dd", "dump"]}
22

33
namespace App;
44

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php // {"optimizedFunctionsOnly": false, "includedConstants": ["PHP_*", "DIRECTORY_SEPARATOR"]}
2+
3+
namespace App;
4+
5+
class MyClass
6+
{
7+
public function run()
8+
{
9+
$v = \PHP_VERSION;
10+
$s = \DIRECTORY_SEPARATOR;
11+
$e = \PHP_EOL;
12+
$sort = \SORT_ASC;
13+
}
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php // {"optimizedFunctionsOnly": false, "includedConstants": ["PHP_*", "DIRECTORY_SEPARATOR"]}
2+
3+
namespace App;
4+
5+
use const DIRECTORY_SEPARATOR, PHP_EOL, PHP_VERSION;
6+
7+
class MyClass
8+
{
9+
public function run()
10+
{
11+
$v = PHP_VERSION;
12+
$s = DIRECTORY_SEPARATOR;
13+
$e = PHP_EOL;
14+
$sort = \SORT_ASC;
15+
}
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php // {"includedFunctions": ["str*", "count"]}
2+
3+
namespace App;
4+
5+
class MyClass
6+
{
7+
public function run()
8+
{
9+
$a = strlen('hello');
10+
$b = strpos('hello', 'e');
11+
$c = count([]);
12+
$d = trim(' x ');
13+
}
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php // {"includedFunctions": ["str*", "count"]}
2+
3+
namespace App;
4+
5+
use function count, strlen, strpos;
6+
7+
class MyClass
8+
{
9+
public function run()
10+
{
11+
$a = strlen('hello');
12+
$b = strpos('hello', 'e');
13+
$c = count([]);
14+
$d = trim(' x ');
15+
}
16+
}

0 commit comments

Comments
 (0)