Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 241ddec

Browse files
Add support for enum methods (#289)
* Add tests for enums * Add enum support
1 parent 6270149 commit 241ddec

File tree

5 files changed

+213
-14
lines changed

5 files changed

+213
-14
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace VariableAnalysis\Tests\VariableAnalysisSniff;
4+
5+
use VariableAnalysis\Tests\BaseTestCase;
6+
7+
class EnumTest extends BaseTestCase
8+
{
9+
public function testEnum()
10+
{
11+
$fixtureFile = $this->getFixture('EnumFixture.php');
12+
$phpcsFile = $this->prepareLocalFileForSniffs($fixtureFile);
13+
$phpcsFile->process();
14+
$lines = $this->getWarningLineNumbersFromFile($phpcsFile);
15+
$expectedWarnings = [
16+
33,
17+
];
18+
$this->assertEquals($expectedWarnings, $lines);
19+
}
20+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
enum Suit
4+
{
5+
case Hearts;
6+
case Diamonds;
7+
case Clubs;
8+
case Spades;
9+
}
10+
11+
enum BackedSuit: string
12+
{
13+
case Hearts = 'H';
14+
case Diamonds = 'D';
15+
case Clubs = 'C';
16+
case Spades = 'S';
17+
}
18+
19+
enum Numbers: string {
20+
case ONE = '1';
21+
case TWO = '2';
22+
case THREE = '3';
23+
case FOUR = '4';
24+
25+
public function divisibility(): string {
26+
return match ($this) {
27+
self::ONE, self::THREE => 'odd',
28+
self::TWO, self::FOUR => 'even',
29+
};
30+
}
31+
32+
public function foobar(): string {
33+
return match ($foo) { // undefined variable $foo
34+
'x' => 'first',
35+
'y' => 'second',
36+
default => 'unknown',
37+
};
38+
}
39+
}

‎VariableAnalysis/Lib/EnumInfo.php‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace VariableAnalysis\Lib;
4+
5+
/**
6+
* Holds details of an enum.
7+
*/
8+
class EnumInfo
9+
{
10+
/**
11+
* The position of the `enum` token.
12+
*
13+
* @var int
14+
*/
15+
public $enumIndex;
16+
17+
/**
18+
* The position of the block opener (curly brace) for the enum.
19+
*
20+
* @var int
21+
*/
22+
public $blockStart;
23+
24+
/**
25+
* The position of the block closer (curly brace) for the enum.
26+
*
27+
* @var int
28+
*/
29+
public $blockEnd;
30+
31+
/**
32+
* @param int $enumIndex
33+
* @param int $blockStart
34+
* @param int $blockEnd
35+
*/
36+
public function __construct(
37+
$enumIndex,
38+
$blockStart,
39+
$blockEnd
40+
) {
41+
$this->enumIndex = $enumIndex;
42+
$this->blockStart = $blockStart;
43+
$this->blockEnd = $blockEnd;
44+
}
45+
}

‎VariableAnalysis/Lib/Helpers.php‎

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PHP_CodeSniffer\Files\File;
66
use VariableAnalysis\Lib\ScopeInfo;
77
use VariableAnalysis\Lib\ForLoopInfo;
8+
use VariableAnalysis\Lib\EnumInfo;
89
use VariableAnalysis\Lib\ScopeType;
910
use VariableAnalysis\Lib\VariableInfo;
1011
use PHP_CodeSniffer\Util\Tokens;
@@ -79,14 +80,20 @@ public static function findContainingOpeningBracket(File $phpcsFile, $stackPtr)
7980
}
8081

8182
/**
82-
* @param (int|string)[] $conditions
83+
* @param array{conditions: (int|string)[], content: string} $token
8384
*
8485
* @return bool
8586
*/
86-
public static function areAnyConditionsAClass(array $conditions)
87+
public static function areAnyConditionsAClass(array $token)
8788
{
89+
$conditions = $token['conditions'];
90+
$classlikeCodes = [T_CLASS, T_ANON_CLASS, T_TRAIT];
91+
if (defined('T_ENUM')) {
92+
$classlikeCodes[] = T_ENUM;
93+
}
94+
$classlikeCodes[] = 'PHPCS_T_ENUM';
8895
foreach (array_reverse($conditions, true) as $scopeCode) {
89-
if ($scopeCode === T_CLASS || $scopeCode === T_ANON_CLASS || $scopeCode === T_TRAIT) {
96+
if (in_array($scopeCode, $classlikeCodes, true)) {
9097
return true;
9198
}
9299
}
@@ -97,15 +104,20 @@ public static function areAnyConditionsAClass(array $conditions)
97104
* Return true if the token conditions are within a function before they are
98105
* within a class.
99106
*
100-
* @param (int|string)[] $conditions
107+
* @param array{conditions: (int|string)[], content: string} $token
101108
*
102109
* @return bool
103110
*/
104-
public static function areConditionsWithinFunctionBeforeClass(array $conditions)
111+
public static function areConditionsWithinFunctionBeforeClass(array $token)
105112
{
106-
$classTypes = [T_CLASS, T_ANON_CLASS, T_TRAIT];
113+
$conditions = $token['conditions'];
114+
$classlikeCodes = [T_CLASS, T_ANON_CLASS, T_TRAIT];
115+
if (defined('T_ENUM')) {
116+
$classlikeCodes[] = T_ENUM;
117+
}
118+
$classlikeCodes[] = 'PHPCS_T_ENUM';
107119
foreach (array_reverse($conditions, true) as $scopeCode) {
108-
if (in_array($scopeCode, $classTypes)) {
120+
if (in_array($scopeCode, $classlikeCodes)) {
109121
return false;
110122
}
111123
if ($scopeCode === T_FUNCTION) {
@@ -1282,6 +1294,38 @@ public static function isTokenVariableVariable(File $phpcsFile, $stackPtr)
12821294
return false;
12831295
}
12841296

1297+
/**
1298+
* @param File $phpcsFile
1299+
* @param int $stackPtr
1300+
*
1301+
* @return EnumInfo
1302+
*/
1303+
public static function makeEnumInfo(File $phpcsFile, $stackPtr)
1304+
{
1305+
$tokens = $phpcsFile->getTokens();
1306+
$token = $tokens[$stackPtr];
1307+
1308+
if (isset($token['scope_opener'])) {
1309+
$blockStart = $token['scope_opener'];
1310+
$blockEnd = $token['scope_closer'];
1311+
} else {
1312+
// Enums before phpcs could detect them do not have scopes so we have to
1313+
// find them ourselves.
1314+
1315+
$blockStart = $phpcsFile->findNext([T_OPEN_CURLY_BRACKET], $stackPtr + 1);
1316+
if (! is_int($blockStart)) {
1317+
throw new \Exception("Cannot find enum start at position {$stackPtr}");
1318+
}
1319+
$blockEnd = $tokens[$blockStart]['bracket_closer'];
1320+
}
1321+
1322+
return new EnumInfo(
1323+
$stackPtr,
1324+
$blockStart,
1325+
$blockEnd
1326+
);
1327+
}
1328+
12851329
/**
12861330
* @param File $phpcsFile
12871331
* @param int $stackPtr

‎VariableAnalysis/Sniffs/CodeAnalysis/VariableAnalysisSniff.php‎

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class VariableAnalysisSniff implements Sniff
1717
/**
1818
* The current phpcsFile being checked.
1919
*
20-
* @var File|null phpcsFile
20+
* @var File|null
2121
*/
2222
protected $currentFile = null;
2323

@@ -33,6 +33,13 @@ class VariableAnalysisSniff implements Sniff
3333
*/
3434
private $forLoops = [];
3535

36+
/**
37+
* A list of enum blocks, keyed by the index of their first token in this file.
38+
*
39+
* @var array<int, \VariableAnalysis\Lib\EnumInfo>
40+
*/
41+
private $enums = [];
42+
3643
/**
3744
* A list of custom functions which pass in variables to be initialized by
3845
* reference (eg `preg_match()`) and therefore should not require those
@@ -175,6 +182,9 @@ public function register()
175182
if (defined('T_FN')) {
176183
$types[] = T_FN;
177184
}
185+
if (defined('T_ENUM')) {
186+
$types[] = T_ENUM;
187+
}
178188
return $types;
179189
}
180190

@@ -226,6 +236,7 @@ public function process(File $phpcsFile, $stackPtr)
226236
if ($this->currentFile !== $phpcsFile) {
227237
$this->currentFile = $phpcsFile;
228238
$this->forLoops = [];
239+
$this->enums = [];
229240
}
230241

231242
// Add the global scope for the current file to our scope indexes.
@@ -265,6 +276,12 @@ public function process(File $phpcsFile, $stackPtr)
265276
return;
266277
}
267278

279+
// Record enums so we can detect them even before phpcs was able to.
280+
if ($token['content'] === 'enum') {
281+
$this->recordEnum($phpcsFile, $stackPtr);
282+
return;
283+
}
284+
268285
// If the current token is a call to `get_defined_vars()`, consider that a
269286
// usage of all variables in the current scope.
270287
if ($this->isGetDefinedVars($phpcsFile, $stackPtr)) {
@@ -286,6 +303,19 @@ public function process(File $phpcsFile, $stackPtr)
286303
}
287304
}
288305

306+
/**
307+
* Record the boundaries of an enum.
308+
*
309+
* @param File $phpcsFile
310+
* @param int $stackPtr
311+
*
312+
* @return void
313+
*/
314+
private function recordEnum($phpcsFile, $stackPtr)
315+
{
316+
$this->enums[$stackPtr] = Helpers::makeEnumInfo($phpcsFile, $stackPtr);
317+
}
318+
289319
/**
290320
* Record the boundaries of a for loop.
291321
*
@@ -857,9 +887,11 @@ protected function processVariableAsClassProperty(File $phpcsFile, $stackPtr)
857887
// define variables, so make sure we are not in a function before
858888
// assuming it's a property.
859889
$tokens = $phpcsFile->getTokens();
860-
$token = $tokens[$stackPtr];
861-
if ($token && !empty($token['conditions']) && !Helpers::areConditionsWithinFunctionBeforeClass($token['conditions'])) {
862-
return Helpers::areAnyConditionsAClass($token['conditions']);
890+
891+
/** @var array{conditions?: (int|string)[], content?: string}|null */
892+
$token = $tokens[$stackPtr];
893+
if ($token && !empty($token['conditions']) && !empty($token['content']) && !Helpers::areConditionsWithinFunctionBeforeClass($token)) {
894+
return Helpers::areAnyConditionsAClass($token);
863895
}
864896
return false;
865897
}
@@ -925,13 +957,30 @@ protected function processVariableAsThisWithinClass(File $phpcsFile, $stackPtr,
925957
return false;
926958
}
927959

960+
// Handle enums specially since their condition may not exist in old phpcs.
961+
$inEnum = false;
962+
foreach ($this->enums as $enum) {
963+
if ($stackPtr > $enum->blockStart && $stackPtr < $enum->blockEnd) {
964+
$inEnum = true;
965+
}
966+
}
967+
928968
$inFunction = false;
929969
foreach (array_reverse($token['conditions'], true) as $scopeCode) {
930970
// $this within a closure is valid
931971
if ($scopeCode === T_CLOSURE && $inFunction === false) {
932972
return true;
933973
}
934-
if ($scopeCode === T_CLASS || $scopeCode === T_ANON_CLASS || $scopeCode === T_TRAIT) {
974+
975+
$classlikeCodes = [T_CLASS, T_ANON_CLASS, T_TRAIT];
976+
if (defined('T_ENUM')) {
977+
$classlikeCodes[] = T_ENUM;
978+
}
979+
if (in_array($scopeCode, $classlikeCodes, true)) {
980+
return true;
981+
}
982+
983+
if ($scopeCode === T_FUNCTION && $inEnum) {
935984
return true;
936985
}
937986

@@ -1033,7 +1082,9 @@ protected function processVariableAsStaticOutsideClass(File $phpcsFile, $stackPt
10331082
// Are we refering to self:: outside a class?
10341083
10351084
$tokens = $phpcsFile->getTokens();
1036-
$token = $tokens[$stackPtr];
1085+
1086+
/** @var array{conditions?: (int|string)[], content?: string}|null */
1087+
$token = $tokens[$stackPtr];
10371088

10381089
$doubleColonPtr = $phpcsFile->findPrevious(Tokens::$emptyTokens, $stackPtr - 1, null, true);
10391090
if ($doubleColonPtr === false || $tokens[$doubleColonPtr]['code'] !== T_DOUBLE_COLON) {
@@ -1053,7 +1104,7 @@ protected function processVariableAsStaticOutsideClass(File $phpcsFile, $stackPt
10531104
}
10541105
$errorClass = $code === T_SELF ? 'SelfOutsideClass' : 'StaticOutsideClass';
10551106
$staticRefType = $code === T_SELF ? 'self::' : 'static::';
1056-
if (!empty($token['conditions']) && Helpers::areAnyConditionsAClass($token['conditions'])) {
1107+
if (!empty($token['conditions']) && !empty($token['content']) && Helpers::areAnyConditionsAClass($token)) {
10571108
return false;
10581109
}
10591110
$phpcsFile->addError(

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /