Skip to content

Commit 0e060e0

Browse files
committed
Merge branch 'php81-enum-case' of https://github.com/kukulich/PHP_CodeSniffer
2 parents 154a688 + 9fd9b01 commit 0e060e0

File tree

5 files changed

+323
-0
lines changed

5 files changed

+323
-0
lines changed

package.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
170170
<file baseinstalldir="" name="DefaultKeywordTest.php" role="test" />
171171
<file baseinstalldir="" name="DoubleArrowTest.inc" role="test" />
172172
<file baseinstalldir="" name="DoubleArrowTest.php" role="test" />
173+
<file baseinstalldir="" name="EnumCaseTest.inc" role="test" />
174+
<file baseinstalldir="" name="EnumCaseTest.php" role="test" />
173175
<file baseinstalldir="" name="FinallyTest.inc" role="test" />
174176
<file baseinstalldir="" name="FinallyTest.php" role="test" />
175177
<file baseinstalldir="" name="GotoLabelTest.inc" role="test" />
@@ -2133,6 +2135,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
21332135
<install as="CodeSniffer/Core/Tokenizer/DefaultKeywordTest.inc" name="tests/Core/Tokenizer/DefaultKeywordTest.inc" />
21342136
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.php" name="tests/Core/Tokenizer/DoubleArrowTest.php" />
21352137
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.inc" name="tests/Core/Tokenizer/DoubleArrowTest.inc" />
2138+
<install as="CodeSniffer/Core/Tokenizer/EnumCaseTest.php" name="tests/Core/Tokenizer/EnumCaseTest.php" />
2139+
<install as="CodeSniffer/Core/Tokenizer/EnumCaseTest.inc" name="tests/Core/Tokenizer/EnumCaseTest.inc" />
21362140
<install as="CodeSniffer/Core/Tokenizer/FinallyTest.php" name="tests/Core/Tokenizer/FinallyTest.php" />
21372141
<install as="CodeSniffer/Core/Tokenizer/FinallyTest.inc" name="tests/Core/Tokenizer/FinallyTest.inc" />
21382142
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.php" name="tests/Core/Tokenizer/GotoLabelTest.php" />
@@ -2231,6 +2235,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
22312235
<install as="CodeSniffer/Core/Tokenizer/DefaultKeywordTest.inc" name="tests/Core/Tokenizer/DefaultKeywordTest.inc" />
22322236
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.php" name="tests/Core/Tokenizer/DoubleArrowTest.php" />
22332237
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.inc" name="tests/Core/Tokenizer/DoubleArrowTest.inc" />
2238+
<install as="CodeSniffer/Core/Tokenizer/EnumCaseTest.php" name="tests/Core/Tokenizer/EnumCaseTest.php" />
2239+
<install as="CodeSniffer/Core/Tokenizer/EnumCaseTest.inc" name="tests/Core/Tokenizer/EnumCaseTest.inc" />
22342240
<install as="CodeSniffer/Core/Tokenizer/FinallyTest.php" name="tests/Core/Tokenizer/FinallyTest.php" />
22352241
<install as="CodeSniffer/Core/Tokenizer/FinallyTest.inc" name="tests/Core/Tokenizer/FinallyTest.inc" />
22362242
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.php" name="tests/Core/Tokenizer/GotoLabelTest.php" />

src/Tokenizers/PHP.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ class PHP extends Tokenizer
347347
T_ENDSWITCH => 9,
348348
T_ENDWHILE => 8,
349349
T_ENUM => 4,
350+
T_ENUM_CASE => 4,
350351
T_EVAL => 4,
351352
T_EXTENDS => 7,
352353
T_FILE => 8,
@@ -476,6 +477,7 @@ class PHP extends Tokenizer
476477
T_INTERFACE => true,
477478
T_TRAIT => true,
478479
T_ENUM => true,
480+
T_ENUM_CASE => true,
479481
T_EXTENDS => true,
480482
T_IMPLEMENTS => true,
481483
T_ATTRIBUTE => true,
@@ -985,6 +987,9 @@ protected function tokenize($string)
985987
&& is_array($tokens[$i]) === true
986988
&& $tokens[$i][0] === T_STRING
987989
) {
990+
// Modify $tokens directly so we can use it later when converting enum "case".
991+
$tokens[$stackPtr][0] = T_ENUM;
992+
988993
$newToken = [];
989994
$newToken['code'] = T_ENUM;
990995
$newToken['type'] = 'T_ENUM';
@@ -1000,6 +1005,65 @@ protected function tokenize($string)
10001005
}
10011006
}//end if
10021007

1008+
/*
1009+
Convert enum "case" to T_ENUM_CASE
1010+
*/
1011+
1012+
if ($tokenIsArray === true
1013+
&& $token[0] === T_CASE
1014+
&& isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
1015+
) {
1016+
$isEnumCase = false;
1017+
$scope = 1;
1018+
1019+
for ($i = ($stackPtr - 1); $i > 0; $i--) {
1020+
if ($tokens[$i] === '}') {
1021+
$scope++;
1022+
continue;
1023+
}
1024+
1025+
if ($tokens[$i] === '{') {
1026+
$scope--;
1027+
continue;
1028+
}
1029+
1030+
if (is_array($tokens[$i]) === false) {
1031+
continue;
1032+
}
1033+
1034+
if ($scope !== 0) {
1035+
continue;
1036+
}
1037+
1038+
if ($tokens[$i][0] === T_SWITCH) {
1039+
break;
1040+
}
1041+
1042+
if ($tokens[$i][0] === T_ENUM || $tokens[$i][0] === T_ENUM_CASE) {
1043+
$isEnumCase = true;
1044+
break;
1045+
}
1046+
}//end for
1047+
1048+
if ($isEnumCase === true) {
1049+
// Modify $tokens directly so we can use it as optimisation for other enum "case".
1050+
$tokens[$stackPtr][0] = T_ENUM_CASE;
1051+
1052+
$newToken = [];
1053+
$newToken['code'] = T_ENUM_CASE;
1054+
$newToken['type'] = 'T_ENUM_CASE';
1055+
$newToken['content'] = $token[1];
1056+
$finalTokens[$newStackPtr] = $newToken;
1057+
1058+
if (PHP_CODESNIFFER_VERBOSITY > 1) {
1059+
echo "\t\t* token $stackPtr changed from T_CASE to T_ENUM_CASE".PHP_EOL;
1060+
}
1061+
1062+
$newStackPtr++;
1063+
continue;
1064+
}
1065+
}//end if
1066+
10031067
/*
10041068
As of PHP 8.0 fully qualified, partially qualified and namespace relative
10051069
identifier names are tokenized differently.

src/Util/Tokens.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
define('T_MATCH_ARROW', 'PHPCS_T_MATCH_ARROW');
8181
define('T_MATCH_DEFAULT', 'PHPCS_T_MATCH_DEFAULT');
8282
define('T_ATTRIBUTE_END', 'PHPCS_T_ATTRIBUTE_END');
83+
define('T_ENUM_CASE', 'PHPCS_T_ENUM_CASE');
8384

8485
// Some PHP 5.5 tokens, replicated for lower versions.
8586
if (defined('T_FINALLY') === false) {

tests/Core/Tokenizer/EnumCaseTest.inc

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
enum Foo
4+
{
5+
/* testPureEnumCase */
6+
case SOME_CASE;
7+
}
8+
9+
enum Boo: int {
10+
/* testBackingIntegerEnumCase */
11+
case ONE = 1;
12+
}
13+
14+
enum Hoo: string
15+
{
16+
/* testBackingStringEnumCase */
17+
case ONE = 'one';
18+
}
19+
20+
enum ComplexEnum: int implements SomeInterface
21+
{
22+
use SomeTrait {
23+
traitMethod as enumMethod;
24+
}
25+
26+
const SOME_CONSTANT = true;
27+
28+
/* testEnumCaseInComplexEnum */
29+
case ONE = 1;
30+
31+
/* testEnumCaseIsCaseInsensitive */
32+
CaSe TWO = 2;
33+
34+
public function someMethod(): bool
35+
{
36+
switch (true) {
37+
/* testCaseWithSemicolonIsNotEnumCase */
38+
case CONSTANT;
39+
}
40+
}
41+
42+
/* testEnumCaseAfterSwitch */
43+
case THREE = 3;
44+
45+
public function someOtherMethod(): bool
46+
{
47+
switch (true):
48+
case false:
49+
endswitch;
50+
}
51+
52+
/* testEnumCaseAfterSwitchWithEndSwitch */
53+
case FOUR = 4;
54+
}
55+
56+
switch (true) {
57+
/* testCaseWithConstantIsNotEnumCase */
58+
case CONSTANT:
59+
/* testCaseWithConstantAndIdenticalIsNotEnumCase */
60+
case CONSTANT === 1:
61+
/* testCaseWithAssigmentToConstantIsNotEnumCase */
62+
case CONSTANT = 1:
63+
/* testIsNotEnumCaseIsCaseInsensitive */
64+
cAsE CONSTANT:
65+
}
66+
67+
switch ($x) {
68+
/* testCaseInSwitchWhenCreatingEnumInSwitch1 */
69+
case 'a': {
70+
enum Foo {}
71+
break;
72+
}
73+
74+
/* testCaseInSwitchWhenCreatingEnumInSwitch2 */
75+
case 'b';
76+
enum Bar {}
77+
break;
78+
}
79+
80+
enum Foo: string {
81+
/* testKeywordAsEnumCaseNameShouldBeString1 */
82+
case INTERFACE = 'interface';
83+
/* testKeywordAsEnumCaseNameShouldBeString2 */
84+
case TRAIT = 'trait';
85+
/* testKeywordAsEnumCaseNameShouldBeString3 */
86+
case ENUM = 'enum';
87+
/* testKeywordAsEnumCaseNameShouldBeString4 */
88+
case FUNCTION = 'function';
89+
/* testKeywordAsEnumCaseNameShouldBeString5 */
90+
case FALSE = 'false';
91+
/* testKeywordAsEnumCaseNameShouldBeString6 */
92+
case DEFAULT = 'default';
93+
/* testKeywordAsEnumCaseNameShouldBeString7 */
94+
case ARRAY = 'array';
95+
}

tests/Core/Tokenizer/EnumCaseTest.php

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
<?php
2+
/**
3+
* Tests converting enum "case" to T_ENUM_CASE.
4+
*
5+
* @author Jaroslav Hanslík <[email protected]>
6+
* @copyright 2021 Squiz Pty Ltd (ABN 77 084 670 600)
7+
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8+
*/
9+
10+
namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
11+
12+
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
13+
14+
class EnumCaseTest extends AbstractMethodUnitTest
15+
{
16+
17+
18+
/**
19+
* Test that the enum "case" is converted to T_ENUM_CASE.
20+
*
21+
* @param string $testMarker The comment which prefaces the target token in the test file.
22+
*
23+
* @dataProvider dataEnumCases
24+
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
25+
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap
26+
*
27+
* @return void
28+
*/
29+
public function testEnumCases($testMarker)
30+
{
31+
$tokens = self::$phpcsFile->getTokens();
32+
33+
$enumCase = $this->getTargetToken($testMarker, [T_ENUM_CASE, T_CASE]);
34+
35+
$this->assertSame(T_ENUM_CASE, $tokens[$enumCase]['code']);
36+
$this->assertSame('T_ENUM_CASE', $tokens[$enumCase]['type']);
37+
38+
$this->assertArrayNotHasKey('scope_condition', $tokens[$enumCase], 'Scope condition is set');
39+
$this->assertArrayNotHasKey('scope_opener', $tokens[$enumCase], 'Scope opener is set');
40+
$this->assertArrayNotHasKey('scope_closer', $tokens[$enumCase], 'Scope closer is set');
41+
42+
}//end testEnumCases()
43+
44+
45+
/**
46+
* Data provider.
47+
*
48+
* @see testEnumCases()
49+
*
50+
* @return array
51+
*/
52+
public function dataEnumCases()
53+
{
54+
return [
55+
['/* testPureEnumCase */'],
56+
['/* testBackingIntegerEnumCase */'],
57+
['/* testBackingStringEnumCase */'],
58+
['/* testEnumCaseInComplexEnum */'],
59+
['/* testEnumCaseIsCaseInsensitive */'],
60+
['/* testEnumCaseAfterSwitch */'],
61+
['/* testEnumCaseAfterSwitchWithEndSwitch */'],
62+
];
63+
64+
}//end dataEnumCases()
65+
66+
67+
/**
68+
* Test that "case" that is not enum case is still tokenized as `T_CASE`.
69+
*
70+
* @param string $testMarker The comment which prefaces the target token in the test file.
71+
*
72+
* @dataProvider dataNotEnumCases
73+
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
74+
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap
75+
*
76+
* @return void
77+
*/
78+
public function testNotEnumCases($testMarker)
79+
{
80+
$tokens = self::$phpcsFile->getTokens();
81+
82+
$case = $this->getTargetToken($testMarker, [T_ENUM_CASE, T_CASE]);
83+
84+
$this->assertSame(T_CASE, $tokens[$case]['code']);
85+
$this->assertSame('T_CASE', $tokens[$case]['type']);
86+
87+
$this->assertArrayHasKey('scope_condition', $tokens[$case], 'Scope condition is not set');
88+
$this->assertArrayHasKey('scope_opener', $tokens[$case], 'Scope opener is not set');
89+
$this->assertArrayHasKey('scope_closer', $tokens[$case], 'Scope closer is not set');
90+
91+
}//end testNotEnumCases()
92+
93+
94+
/**
95+
* Data provider.
96+
*
97+
* @see testNotEnumCases()
98+
*
99+
* @return array
100+
*/
101+
public function dataNotEnumCases()
102+
{
103+
return [
104+
['/* testCaseWithSemicolonIsNotEnumCase */'],
105+
['/* testCaseWithConstantIsNotEnumCase */'],
106+
['/* testCaseWithConstantAndIdenticalIsNotEnumCase */'],
107+
['/* testCaseWithAssigmentToConstantIsNotEnumCase */'],
108+
['/* testIsNotEnumCaseIsCaseInsensitive */'],
109+
['/* testCaseInSwitchWhenCreatingEnumInSwitch1 */'],
110+
['/* testCaseInSwitchWhenCreatingEnumInSwitch2 */'],
111+
];
112+
113+
}//end dataNotEnumCases()
114+
115+
116+
/**
117+
* Test that "case" that is not enum case is still tokenized as `T_CASE`.
118+
*
119+
* @param string $testMarker The comment which prefaces the target token in the test file.
120+
*
121+
* @dataProvider dataKeywordAsEnumCaseNameShouldBeString
122+
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
123+
*
124+
* @return void
125+
*/
126+
public function testKeywordAsEnumCaseNameShouldBeString($testMarker)
127+
{
128+
$tokens = self::$phpcsFile->getTokens();
129+
130+
$enumCaseName = $this->getTargetToken($testMarker, [T_STRING, T_INTERFACE, T_TRAIT, T_ENUM, T_FUNCTION, T_FALSE, T_DEFAULT, T_ARRAY]);
131+
132+
$this->assertSame(T_STRING, $tokens[$enumCaseName]['code']);
133+
$this->assertSame('T_STRING', $tokens[$enumCaseName]['type']);
134+
135+
}//end testKeywordAsEnumCaseNameShouldBeString()
136+
137+
138+
/**
139+
* Data provider.
140+
*
141+
* @see testKeywordAsEnumCaseNameShouldBeString()
142+
*
143+
* @return array
144+
*/
145+
public function dataKeywordAsEnumCaseNameShouldBeString()
146+
{
147+
return [
148+
['/* testKeywordAsEnumCaseNameShouldBeString1 */'],
149+
['/* testKeywordAsEnumCaseNameShouldBeString2 */'],
150+
['/* testKeywordAsEnumCaseNameShouldBeString3 */'],
151+
['/* testKeywordAsEnumCaseNameShouldBeString4 */'],
152+
];
153+
154+
}//end dataKeywordAsEnumCaseNameShouldBeString()
155+
156+
157+
}//end class

0 commit comments

Comments
 (0)