Skip to content

Commit

Permalink
PHP 8.1: Added support for "enum" keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
kukulich committed Nov 19, 2021
1 parent 5fb9b64 commit 7f1bd6b
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 0 deletions.
38 changes: 38 additions & 0 deletions src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ class PHP extends Tokenizer
'shared' => false,
'with' => [],
],
T_ENUM => [
'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
'strict' => true,
'shared' => false,
'with' => [],
],
T_USE => [
'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
Expand Down Expand Up @@ -339,6 +346,7 @@ class PHP extends Tokenizer
T_ENDIF => 5,
T_ENDSWITCH => 9,
T_ENDWHILE => 8,
T_ENUM => 4,
T_EVAL => 4,
T_EXTENDS => 7,
T_FILE => 8,
Expand Down Expand Up @@ -466,6 +474,7 @@ class PHP extends Tokenizer
T_CLASS => true,
T_INTERFACE => true,
T_TRAIT => true,
T_ENUM => true,
T_EXTENDS => true,
T_IMPLEMENTS => true,
T_ATTRIBUTE => true,
Expand Down Expand Up @@ -870,6 +879,35 @@ protected function tokenize($string)
continue;
}//end if

/*
Enum keyword for PHP < 8.1
*/

if ($tokenIsArray === true
&& $token[0] === T_STRING
&& strtolower($token[1]) === 'enum'
) {
// Get the next non-empty token.
for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
if (is_array($tokens[$i]) === false
|| isset(Util\Tokens::$emptyTokens[$tokens[$i][0]]) === false
) {
break;
}
}

if (isset($tokens[$i]) === true
&& is_array($tokens[$i]) === true
&& $tokens[$i][0] === T_STRING
) {
$tokens[$stackPtr][0] = T_ENUM;
$token[0] = T_ENUM;
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "\t\t* token $stackPtr changed from T_STRING to T_ENUM".PHP_EOL;
}
}
}//end if

/*
As of PHP 8.0 fully qualified, partially qualified and namespace relative
identifier names are tokenized differently.
Expand Down
7 changes: 7 additions & 0 deletions src/Util/Tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@
define('T_ATTRIBUTE', 'PHPCS_T_ATTRIBUTE');
}

if (defined('T_ENUM') === false) {
define('T_ENUM', 'PHPCS_T_ENUM');
}

// Some PHP 8.1 tokens, replicated for lower versions.
if (defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG') === false) {
define('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', 'PHPCS_T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG');
Expand Down Expand Up @@ -190,6 +194,7 @@ final class Tokens
T_CLASS => 1000,
T_INTERFACE => 1000,
T_TRAIT => 1000,
T_ENUM => 1000,
T_NAMESPACE => 1000,
T_FUNCTION => 100,
T_CLOSURE => 100,
Expand Down Expand Up @@ -415,6 +420,7 @@ final class Tokens
T_ANON_CLASS => T_ANON_CLASS,
T_INTERFACE => T_INTERFACE,
T_TRAIT => T_TRAIT,
T_ENUM => T_ENUM,
T_NAMESPACE => T_NAMESPACE,
T_FUNCTION => T_FUNCTION,
T_CLOSURE => T_CLOSURE,
Expand Down Expand Up @@ -629,6 +635,7 @@ final class Tokens
T_ANON_CLASS => T_ANON_CLASS,
T_INTERFACE => T_INTERFACE,
T_TRAIT => T_TRAIT,
T_ENUM => T_ENUM,
];

/**
Expand Down
55 changes: 55 additions & 0 deletions tests/Core/Tokenizer/EnumTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

/* testPureEnum */
enum Foo
{
case SOME_CASE;
}

/* testBackedIntEnum */
enum Boo: int {
case ONE = 1;
case TWO = 1;
}

/* testBackedStringEnum */
enum Hoo: string
{
case ONE = 'one';
case TWO = 'two';
}

/* testComplexEnum */
enum ComplexEnum: int implements SomeInterface
{
use SomeTrait {
traitMethod as enumMethod;
}

const SOME_CONSTANT = true;

case ONE = 1;

public function someMethod(): bool
{
}
}

/* testEnumUsedAsClassName */
class Enum {
/* testEnumUsedAsClassConstantName */
const ENUM = 'enum';

/* testEnumUsedAsMethodName */
public function enum() {
// Do something.

/* testEnumUsedAsPropertyName */
$this->enum = 'foo';
}
}

/* testEnumUsedAsFunctionName */
function enum()
{
}
147 changes: 147 additions & 0 deletions tests/Core/Tokenizer/EnumTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php
/**
* Tests the support of PHP 8.1 enums
*
* @author Jaroslav Hanslík <kukulich@kukulich.cz>
* @copyright 2021 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Tests\Core\Tokenizer;

use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
use function array_key_exists;
use const T_CLOSE_PARENTHESIS;
use const T_OPEN_PARENTHESIS;
use const T_STRING;

class EnumTest extends AbstractMethodUnitTest
{


/**
* Test that the enum keyword is tokenized as such.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int $expectedScopeOpenerLine Expected line of scope opener.
* @param int $expectedScopeCloserLine Expected line of scope closer.
*
* @dataProvider dataEnums
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testEnums($testMarker, $expectedScopeOpenerLine, $expectedScopeCloserLine)
{
$tokens = self::$phpcsFile->getTokens();

$enum = $this->getTargetToken($testMarker, [T_ENUM, T_STRING]);

$this->assertSame(T_ENUM, $tokens[$enum]['code']);
$this->assertSame('T_ENUM', $tokens[$enum]['type']);

$this->assertArrayHasKey('scope_condition', $tokens[$enum]);
$this->assertArrayHasKey('scope_opener', $tokens[$enum]);
$this->assertArrayHasKey('scope_closer', $tokens[$enum]);

$this->assertSame($enum, $tokens[$enum]['scope_condition']);

$scopeOpener = $tokens[$enum]['scope_opener'];
$scopeCloser = $tokens[$enum]['scope_closer'];

$this->assertSame($expectedScopeOpenerLine, $tokens[$scopeOpener]['line']);
$this->assertArrayHasKey('scope_condition', $tokens[$scopeOpener]);
$this->assertArrayHasKey('scope_opener', $tokens[$scopeOpener]);
$this->assertArrayHasKey('scope_closer', $tokens[$scopeOpener]);
$this->assertSame($enum, $tokens[$scopeOpener]['scope_condition']);
$this->assertSame($scopeOpener, $tokens[$scopeOpener]['scope_opener']);
$this->assertSame($scopeCloser, $tokens[$scopeOpener]['scope_closer']);

$this->assertSame($expectedScopeCloserLine, $tokens[$scopeCloser]['line']);
$this->assertArrayHasKey('scope_condition', $tokens[$scopeCloser]);
$this->assertArrayHasKey('scope_opener', $tokens[$scopeCloser]);
$this->assertArrayHasKey('scope_closer', $tokens[$scopeCloser]);
$this->assertSame($enum, $tokens[$scopeCloser]['scope_condition']);
$this->assertSame($scopeOpener, $tokens[$scopeCloser]['scope_opener']);
$this->assertSame($scopeCloser, $tokens[$scopeCloser]['scope_closer']);

}//end testEnums()


/**
* Data provider.
*
* @see testEnums()
*
* @return array
*/
public function dataEnums()
{
return [
[
'/* testPureEnum */',
5,
7,
],
[
'/* testBackedIntEnum */',
10,
13,
],
[
'/* testBackedStringEnum */',
17,
20,
],
[
'/* testComplexEnum */',
24,
36,
],
];

}//end dataEnums()


/**
* Test that 'enum' when not used as the enum keyword is still tokenized as `T_STRING`.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataNotEnums
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testNotEnums($testMarker)
{
$tokens = self::$phpcsFile->getTokens();

$target = $this->getTargetToken($testMarker, [T_ENUM, T_STRING]);
$this->assertSame(T_STRING, $tokens[$target]['code']);
$this->assertSame('T_STRING', $tokens[$target]['type']);

}//end testNotEnums()


/**
* Data provider.
*
* @see testNotEnums()
*
* @return array
*/
public function dataNotEnums()
{
return [
['/* testEnumUsedAsClassName */'],
['/* testEnumUsedAsClassConstantName */'],
['/* testEnumUsedAsMethodName */'],
['/* testEnumUsedAsPropertyName */'],
['/* testEnumUsedAsFunctionName */'],
];

}//end dataNotEnums()


}//end class

0 comments on commit 7f1bd6b

Please sign in to comment.