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 20, 2021
1 parent 5fb9b64 commit eb27ac3
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 0 deletions.
6 changes: 6 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<file baseinstalldir="" name="DefaultKeywordTest.php" role="test" />
<file baseinstalldir="" name="DoubleArrowTest.inc" role="test" />
<file baseinstalldir="" name="DoubleArrowTest.php" role="test" />
<file baseinstalldir="" name="EnumTest.inc" role="test" />
<file baseinstalldir="" name="EnumTest.php" role="test" />
<file baseinstalldir="" name="FinallyTest.inc" role="test" />
<file baseinstalldir="" name="FinallyTest.php" role="test" />
<file baseinstalldir="" name="GotoLabelTest.inc" role="test" />
Expand Down Expand Up @@ -2084,6 +2086,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/DefaultKeywordTest.inc" name="tests/Core/Tokenizer/DefaultKeywordTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.php" name="tests/Core/Tokenizer/DoubleArrowTest.php" />
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.inc" name="tests/Core/Tokenizer/DoubleArrowTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/EnumTest.php" name="tests/Core/Tokenizer/EnumTest.php" />
<install as="CodeSniffer/Core/Tokenizer/EnumTest.inc" name="tests/Core/Tokenizer/EnumTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/FinallyTest.php" name="tests/Core/Tokenizer/FinallyTest.php" />
<install as="CodeSniffer/Core/Tokenizer/FinallyTest.inc" name="tests/Core/Tokenizer/FinallyTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.php" name="tests/Core/Tokenizer/GotoLabelTest.php" />
Expand Down Expand Up @@ -2174,6 +2178,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/DefaultKeywordTest.inc" name="tests/Core/Tokenizer/DefaultKeywordTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.php" name="tests/Core/Tokenizer/DoubleArrowTest.php" />
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.inc" name="tests/Core/Tokenizer/DoubleArrowTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/EnumTest.php" name="tests/Core/Tokenizer/EnumTest.php" />
<install as="CodeSniffer/Core/Tokenizer/EnumTest.inc" name="tests/Core/Tokenizer/EnumTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/FinallyTest.php" name="tests/Core/Tokenizer/FinallyTest.php" />
<install as="CodeSniffer/Core/Tokenizer/FinallyTest.inc" name="tests/Core/Tokenizer/FinallyTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.php" name="tests/Core/Tokenizer/GotoLabelTest.php" />
Expand Down
45 changes: 45 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,42 @@ 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
) {
$newToken = [];
$newToken['code'] = T_ENUM;
$newToken['type'] = 'T_ENUM';
$newToken['content'] = $token[1];
$finalTokens[$newStackPtr] = $newToken;

if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "\t\t* token $stackPtr changed from T_STRING to T_ENUM".PHP_EOL;
}

$newStackPtr++;
continue;
}
}//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
88 changes: 88 additions & 0 deletions tests/Core/Tokenizer/EnumTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?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
{
}
}

/* testEnumWithEnumAsClassName */
enum /* testEnumAsClassNameAfterEnumKeyword */ Enum {}

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

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

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

/* testEnumUsedAsFunctionName */
function enum()
{
}

/* testDeclarationContainingComment */
enum /* comment */ Name
{
case SOME_CASE;
}

/* testEnumUsedAsNamespaceName */
namespace Enum;
/* testEnumUsedAsPartOfNamespaceName */
namespace My\Enum\Collection;
/* testEnumUsedInObjectInitialization */
$obj = new Enum;
/* testEnumAsFunctionCall */
$var = enum($a, $b);
/* testEnumAsFunctionCallWithNamespace */
var = namespace\enum();
/* testClassConstantFetchWithEnumAsClassName */
echo Enum::CONSTANT;
/* testClassConstantFetchWithEnumAsConstantName */
echo ClassName::ENUM;

/* testParseErrorMissingName */
enum {
case SOME_CASE;
}

/* testParseErrorLiveCoding */
// This must be the last test in the file.
enum
163 changes: 163 additions & 0 deletions tests/Core/Tokenizer/EnumTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php
/**
* Tests the support of PHP 8.1 "enum" keyword.
*
* @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;

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,
],
[
'/* testEnumWithEnumAsClassName */',
39,
39,
],
[
'/* testDeclarationContainingComment */',
62,
64,
],
];

}//end dataEnums()


/**
* Test that "enum" when not used as the 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 [
['/* testEnumAsClassNameAfterEnumKeyword */'],
['/* testEnumUsedAsClassName */'],
['/* testEnumUsedAsClassConstantName */'],
['/* testEnumUsedAsMethodName */'],
['/* testEnumUsedAsPropertyName */'],
['/* testEnumUsedAsFunctionName */'],
['/* testEnumUsedAsNamespaceName */'],
['/* testEnumUsedAsPartOfNamespaceName */'],
['/* testEnumUsedInObjectInitialization */'],
['/* testEnumAsFunctionCall */'],
['/* testEnumAsFunctionCallWithNamespace */'],
['/* testClassConstantFetchWithEnumAsClassName */'],
['/* testClassConstantFetchWithEnumAsConstantName */'],
['/* testParseErrorMissingName */'],
['/* testParseErrorLiveCoding */'],
];

}//end dataNotEnums()


}//end class

0 comments on commit eb27ac3

Please sign in to comment.