Skip to content

Commit 53c2838

Browse files
authored
Merge pull request #119 from PHPCSStandards/feature/generic-lowercaseconstants-performance-fix
Generic/LowerCaseConstant: improve performance
2 parents ec2709a + aae1797 commit 53c2838

File tree

5 files changed

+175
-17
lines changed

5 files changed

+175
-17
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ The file documents changes to the PHP_CodeSniffer project.
101101
- Runtime performance improvement for PHPCS CLI users. The improvement should be most noticeable for users on Windows.
102102
- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
103103
- The following sniffs have received performance related improvements:
104+
- Generic.PHP.LowerCaseConstant
104105
- Generic.PHP.LowerCaseType
105106
- PSR12.Files.OpenTag
106107
- Thanks to Juliette Reinders Folmer (@jrfnl) for the patches

src/Standards/Generic/Sniffs/PHP/LowerCaseConstantSniff.php

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,29 @@ class LowerCaseConstantSniff implements Sniff
3737
T_NULL => T_NULL,
3838
];
3939

40+
/**
41+
* Token types which can be encountered in a property type declaration.
42+
*
43+
* @var array<int|string, int|string>
44+
*/
45+
private $propertyTypeTokens = [
46+
T_CALLABLE => T_CALLABLE,
47+
T_SELF => T_SELF,
48+
T_PARENT => T_PARENT,
49+
T_FALSE => T_FALSE,
50+
T_TRUE => T_TRUE,
51+
T_NULL => T_NULL,
52+
T_STRING => T_STRING,
53+
T_NAME_QUALIFIED => T_NAME_QUALIFIED,
54+
T_NAME_FULLY_QUALIFIED => T_NAME_FULLY_QUALIFIED,
55+
T_NAME_RELATIVE => T_NAME_RELATIVE,
56+
T_NS_SEPARATOR => T_NS_SEPARATOR,
57+
T_NAMESPACE => T_NAMESPACE,
58+
T_TYPE_UNION => T_TYPE_UNION,
59+
T_TYPE_INTERSECTION => T_TYPE_INTERSECTION,
60+
T_NULLABLE => T_NULLABLE,
61+
];
62+
4063

4164
/**
4265
* Returns an array of tokens this test wants to listen for.
@@ -47,7 +70,13 @@ public function register()
4770
{
4871
$targets = $this->targets;
4972

50-
// Register function keywords to filter out type declarations.
73+
// Register scope modifiers to filter out property type declarations.
74+
$targets += Tokens::$scopeModifiers;
75+
$targets[] = T_VAR;
76+
$targets[] = T_STATIC;
77+
$targets[] = T_READONLY;
78+
79+
// Register function keywords to filter out param/return type declarations.
5180
$targets[] = T_FUNCTION;
5281
$targets[] = T_CLOSURE;
5382
$targets[] = T_FN;
@@ -64,12 +93,43 @@ public function register()
6493
* @param int $stackPtr The position of the current token in the
6594
* stack passed in $tokens.
6695
*
67-
* @return void|int
96+
* @return void|int Optionally returns a stack pointer. The sniff will not be
97+
* called again on the current file until the returned stack
98+
* pointer is reached.
6899
*/
69100
public function process(File $phpcsFile, $stackPtr)
70101
{
71102
$tokens = $phpcsFile->getTokens();
72103

104+
/*
105+
* Skip over type declarations for properties.
106+
*
107+
* Note: for other uses of the visibility modifiers (functions, constants, trait use),
108+
* nothing relevant will be skipped as the next non-empty token will be an "non-skippable"
109+
* one.
110+
* Functions are handled separately below (and then skip to their scope opener), so
111+
* this should also not cause any confusion for constructor property promotion.
112+
*
113+
* For other uses of the "static" keyword, it also shouldn't be problematic as the only
114+
* time the next non-empty token will be a "skippable" token will be in return type
115+
* declarations, in which case, it is correct to skip over them.
116+
*/
117+
118+
if (isset(Tokens::$scopeModifiers[$tokens[$stackPtr]['code']]) === true
119+
|| $tokens[$stackPtr]['code'] === T_VAR
120+
|| $tokens[$stackPtr]['code'] === T_STATIC
121+
|| $tokens[$stackPtr]['code'] === T_READONLY
122+
) {
123+
$skipOver = (Tokens::$emptyTokens + $this->propertyTypeTokens);
124+
$skipTo = $phpcsFile->findNext($skipOver, ($stackPtr + 1), null, true);
125+
if ($skipTo !== false) {
126+
return $skipTo;
127+
}
128+
129+
// If we're at the end of the file, just return.
130+
return;
131+
}
132+
73133
// Handle function declarations separately as they may contain the keywords in type declarations.
74134
if ($tokens[$stackPtr]['code'] === T_FUNCTION
75135
|| $tokens[$stackPtr]['code'] === T_CLOSURE
@@ -79,9 +139,15 @@ public function process(File $phpcsFile, $stackPtr)
79139
return;
80140
}
81141

142+
// Make sure to skip over return type declarations.
82143
$end = $tokens[$stackPtr]['parenthesis_closer'];
83144
if (isset($tokens[$stackPtr]['scope_opener']) === true) {
84145
$end = $tokens[$stackPtr]['scope_opener'];
146+
} else {
147+
$skipTo = $phpcsFile->findNext([T_SEMICOLON, T_OPEN_CURLY_BRACKET], ($end + 1), null, false, null, true);
148+
if ($skipTo !== false) {
149+
$end = $skipTo;
150+
}
85151
}
86152

87153
// Do a quick check if any of the targets exist in the declaration.
@@ -114,21 +180,6 @@ public function process(File $phpcsFile, $stackPtr)
114180
return $end;
115181
}//end if
116182

117-
// Handle property declarations separately as they may contain the keywords in type declarations.
118-
if (isset($tokens[$stackPtr]['conditions']) === true) {
119-
$conditions = $tokens[$stackPtr]['conditions'];
120-
$lastCondition = end($conditions);
121-
if (isset(Tokens::$ooScopeTokens[$lastCondition]) === true) {
122-
// This can only be an OO constant or property declaration as methods are handled above.
123-
$equals = $phpcsFile->findPrevious(T_EQUAL, ($stackPtr - 1), null, false, null, true);
124-
if ($equals !== false) {
125-
$this->processConstant($phpcsFile, $stackPtr);
126-
}
127-
128-
return;
129-
}
130-
}
131-
132183
// Handle everything else.
133184
$this->processConstant($phpcsFile, $stackPtr);
134185

src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.inc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,52 @@ class TypedThings {
9898
}
9999

100100
$cl = function (int|FALSE $param = NULL, Type|NULL $obj = new MyObj(FALSE)) : string|FALSE|NULL {};
101+
102+
// Adding some extra tests to safeguard that function declarations which don't create scope are handled correctly.
103+
interface InterfaceMethodsWithReturnTypeNoScopeOpener {
104+
private function typed($param = TRUE) : string|FALSE|NULL;
105+
}
106+
107+
abstract class ClassMethodsWithReturnTypeNoScopeOpener {
108+
abstract public function typed($param = FALSE) : TRUE;
109+
}
110+
111+
// Additional tests to safeguard improved property type skip logic.
112+
readonly class Properties {
113+
use SomeTrait {
114+
sayHello as private myPrivateHello;
115+
}
116+
117+
public Type|FALSE|NULL $propertyA = array(
118+
'itemA' => TRUE,
119+
'itemB' => FALSE,
120+
'itemC' => NULL,
121+
), $propertyB = FALSE;
122+
123+
protected \FullyQualified&Partially\Qualified&namespace\Relative $propertyC;
124+
var ?TRUE $propertyD;
125+
static array|callable|FALSE|self|parent $propertyE = TRUE;
126+
private
127+
// phpcs:ignore Stnd.Cat.Sniff -- for reasons.
128+
TRUE /*comment*/
129+
$propertyF = TRUE;
130+
131+
public function __construct(
132+
public FALSE|NULL $promotedPropA,
133+
readonly callable|TRUE $promotedPropB,
134+
) {
135+
static $var;
136+
echo static::class;
137+
static::foo();
138+
$var = $var instanceof static;
139+
$obj = new static();
140+
}
141+
142+
public static function foo(): static|self|FALSE {
143+
$callable = static function() {};
144+
}
145+
}
146+
147+
// Last coding/parse error.
148+
// This has to be the last test in the file.
149+
function UnclosedCurly (): FALSE {

src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.inc.fixed

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,52 @@ class TypedThings {
9898
}
9999

100100
$cl = function (int|FALSE $param = null, Type|NULL $obj = new MyObj(false)) : string|FALSE|NULL {};
101+
102+
// Adding some extra tests to safeguard that function declarations which don't create scope are handled correctly.
103+
interface InterfaceMethodsWithReturnTypeNoScopeOpener {
104+
private function typed($param = true) : string|FALSE|NULL;
105+
}
106+
107+
abstract class ClassMethodsWithReturnTypeNoScopeOpener {
108+
abstract public function typed($param = false) : TRUE;
109+
}
110+
111+
// Additional tests to safeguard improved property type skip logic.
112+
readonly class Properties {
113+
use SomeTrait {
114+
sayHello as private myPrivateHello;
115+
}
116+
117+
public Type|FALSE|NULL $propertyA = array(
118+
'itemA' => true,
119+
'itemB' => false,
120+
'itemC' => null,
121+
), $propertyB = false;
122+
123+
protected \FullyQualified&Partially\Qualified&namespace\Relative $propertyC;
124+
var ?TRUE $propertyD;
125+
static array|callable|FALSE|self|parent $propertyE = true;
126+
private
127+
// phpcs:ignore Stnd.Cat.Sniff -- for reasons.
128+
TRUE /*comment*/
129+
$propertyF = true;
130+
131+
public function __construct(
132+
public FALSE|NULL $promotedPropA,
133+
readonly callable|TRUE $promotedPropB,
134+
) {
135+
static $var;
136+
echo static::class;
137+
static::foo();
138+
$var = $var instanceof static;
139+
$obj = new static();
140+
}
141+
142+
public static function foo(): static|self|FALSE {
143+
$callable = static function() {};
144+
}
145+
}
146+
147+
// Last coding/parse error.
148+
// This has to be the last test in the file.
149+
function UnclosedCurly (): FALSE {

src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ public function getErrorList($testFile='LowerCaseConstantUnitTest.inc')
5151
94 => 2,
5252
95 => 1,
5353
100 => 2,
54+
104 => 1,
55+
108 => 1,
56+
118 => 1,
57+
119 => 1,
58+
120 => 1,
59+
121 => 1,
60+
125 => 1,
61+
129 => 1,
5462
];
5563

5664
case 'LowerCaseConstantUnitTest.js':

0 commit comments

Comments
 (0)