Skip to content

Commit

Permalink
Tokenizer/PHP: support PHP8 magic constant dereferencing
Browse files Browse the repository at this point in the history
As of PHP 8, magic constants can be dereferenced, however, the square brackets are incorrectly tokenized as _short array_ brackets instead of as "normal" square brackets.

Fixed by:
* Adding a new `$magicConstants` array to the `Util\Tokens` calls.
* Adding the `$magicConstants` to the allowed tokens for leaving the brackets alone in the PHP Tokenizer class.

Includes adding unit tests for this section of the `PHP::processAdditional()` method.

I've added as many relevant syntaxes as I could come up with, but won't claim that the unit test case file covers every possible scenario. Should be a good start though.

Ref: https://wiki.php.net/rfc/variable_syntax_tweaks#constants_and_magic_constants
  • Loading branch information
jrfnl committed Jul 10, 2020
1 parent a957a73 commit a1a7e70
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 0 deletions.
6 changes: 6 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<file baseinstalldir="" name="BackfillFnTokenTest.php" role="test" />
<file baseinstalldir="" name="BackfillNumericSeparatorTest.inc" role="test" />
<file baseinstalldir="" name="BackfillNumericSeparatorTest.php" role="test" />
<file baseinstalldir="" name="ShortArrayTest.inc" role="test" />
<file baseinstalldir="" name="ShortArrayTest.php" role="test" />
</dir>
<file baseinstalldir="" name="AbstractMethodUnitTest.php" role="test" />
<file baseinstalldir="" name="AllTests.php" role="test" />
Expand Down Expand Up @@ -1977,6 +1979,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.inc" name="tests/Core/Tokenizer/BackfillFnTokenTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.php" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.inc" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.php" name="tests/Core/Tokenizer/ShortArrayTest.php" />
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.inc" name="tests/Core/Tokenizer/ShortArrayTest.inc" />
<install as="CodeSniffer/Standards/AllSniffs.php" name="tests/Standards/AllSniffs.php" />
<install as="CodeSniffer/Standards/AbstractSniffUnitTest.php" name="tests/Standards/AbstractSniffUnitTest.php" />
</filelist>
Expand Down Expand Up @@ -2030,6 +2034,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.inc" name="tests/Core/Tokenizer/BackfillFnTokenTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.php" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.inc" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.php" name="tests/Core/Tokenizer/ShortArrayTest.php" />
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.inc" name="tests/Core/Tokenizer/ShortArrayTest.inc" />
<install as="CodeSniffer/Standards/AllSniffs.php" name="tests/Standards/AllSniffs.php" />
<install as="CodeSniffer/Standards/AbstractSniffUnitTest.php" name="tests/Standards/AbstractSniffUnitTest.php" />
<ignore name="bin/phpcs.bat" />
Expand Down
1 change: 1 addition & 0 deletions src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -1973,6 +1973,7 @@ protected function processAdditional()
T_STRING => T_STRING,
T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
];
$allowed += Util\Tokens::$magicConstants;

for ($x = ($i - 1); $x >= 0; $x--) {
// If we hit a scope opener, the statement has ended
Expand Down
18 changes: 18 additions & 0 deletions src/Util/Tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,24 @@ final class Tokens
T_TRAIT => T_TRAIT,
];

/**
* Tokens representing PHP magic constants.
*
* @var array <int|string> => <int|string>
*
* @link https://www.php.net/language.constants.predefined PHP Manual on magic constants
*/
public static $magicConstants = [
T_CLASS_C => T_CLASS_C,
T_DIR => T_DIR,
T_FILE => T_FILE,
T_FUNC_C => T_FUNC_C,
T_LINE => T_LINE,
T_METHOD_C => T_METHOD_C,
T_NS_C => T_NS_C,
T_TRAIT_C => T_TRAIT_C,
];


/**
* Given a token, returns the name of the token.
Expand Down
92 changes: 92 additions & 0 deletions tests/Core/Tokenizer/ShortArrayTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

/*
* Square brackets.
*/

/* testArrayAccess1 */
$var = $array[10];

$var = $array[++$y]/* testArrayAccess2 */[$x];

/* testArrayAssignment */
$array[] = $var;

/* testFunctionCallDereferencing */
$var = function_call()[$x];

/* testMethodCallDereferencing */
$var = $obj->function_call()[$x];

/* testStaticMethodCallDereferencing */
$var = ClassName::function_call()[$x];

/* testPropertyDereferencing */
$var = $obj->property[2];

/* testPropertyDereferencingWithInaccessibleName */
$var = $ref->{'ref-type'}[1];

/* testStaticPropertyDereferencing */
$var ClassName::$property[2];

/* testStringDereferencing */
$var = 'PHP'[1];

/* testStringDereferencingDoubleQuoted */
$var = "PHP"[$y];

/* testConstantDereferencing */
$var = MY_CONSTANT[1];

/* testClassConstantDereferencing */
$var ClassName::CONSTANT_NAME[2];

/* testMagicConstantDereferencing */
$var = __FILE__[0];

/* testArrayAccessCurlyBraces */
$var = $array{'key'}['key'];

/* testArrayLiteralDereferencing */
echo array(1, 2, 3)[0];

echo [1, 2, 3]/* testShortArrayLiteralDereferencing */[0];

/* testClassMemberDereferencingOnInstantiation1 */
(new foo)[0];

/* testClassMemberDereferencingOnInstantiation2 */
$a = (new Foo( array(1, array(4, 5), 3) ))[1][0];

/* testClassMemberDereferencingOnClone */
echo (clone $iterable)[20];


/*
* Short array brackets.
*/

/* testShortArrayDeclarationEmpty */
$array = [];

/* testShortArrayDeclarationWithOneValue */
$array = [1];

/* testShortArrayDeclarationWithMultipleValues */
$array = [1, 2, 3];

/* testShortArrayDeclarationWithDereferencing */
echo [1, 2, 3][0];

/* testShortListDeclaration */
[ $a, $b ] = $array;

[ $a, $b, /* testNestedListDeclaration */, [$c, $d]] = $array;

/* testArrayWithinFunctionCall */
$var = functionCall([$x, $y]);

/* testLiveCoding */
// Intentional parse error. This has to be the last test in the file.
$array = [
130 changes: 130 additions & 0 deletions tests/Core/Tokenizer/ShortArrayTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php
/**
* Tests the conversion of square bracket tokens to short array tokens.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2020 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 ShortArrayTest extends AbstractMethodUnitTest
{


/**
* Test that real square brackets are still tokenized as square brackets.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataSquareBrackets
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testSquareBrackets($testMarker)
{
$tokens = self::$phpcsFile->getTokens();

$opener = $this->getTargetToken($testMarker, [T_OPEN_SQUARE_BRACKET, T_OPEN_SHORT_ARRAY]);
$this->assertSame(T_OPEN_SQUARE_BRACKET, $tokens[$opener]['code']);
$this->assertSame('T_OPEN_SQUARE_BRACKET', $tokens[$opener]['type']);

if (isset($tokens[$opener]['bracket_closer']) === true) {
$closer = $tokens[$opener]['bracket_closer'];
$this->assertSame(T_CLOSE_SQUARE_BRACKET, $tokens[$closer]['code']);
$this->assertSame('T_CLOSE_SQUARE_BRACKET', $tokens[$closer]['type']);
}

}//end testSquareBrackets()


/**
* Data provider.
*
* @see testSquareBrackets()
*
* @return array
*/
public function dataSquareBrackets()
{
return [
['/* testArrayAccess1 */'],
['/* testArrayAccess2 */'],
['/* testArrayAssignment */'],
['/* testFunctionCallDereferencing */'],
['/* testMethodCallDereferencing */'],
['/* testStaticMethodCallDereferencing */'],
['/* testPropertyDereferencing */'],
['/* testPropertyDereferencingWithInaccessibleName */'],
['/* testStaticPropertyDereferencing */'],
['/* testStringDereferencing */'],
['/* testStringDereferencingDoubleQuoted */'],
['/* testConstantDereferencing */'],
['/* testClassConstantDereferencing */'],
['/* testMagicConstantDereferencing */'],
['/* testArrayAccessCurlyBraces */'],
['/* testArrayLiteralDereferencing */'],
['/* testShortArrayLiteralDereferencing */'],
['/* testClassMemberDereferencingOnInstantiation1 */'],
['/* testClassMemberDereferencingOnInstantiation2 */'],
['/* testClassMemberDereferencingOnClone */'],
['/* testLiveCoding */'],
];

}//end dataSquareBrackets()


/**
* Test that short arrays and short lists are still tokenized as short arrays.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataShortArrays
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testShortArrays($testMarker)
{
$tokens = self::$phpcsFile->getTokens();

$opener = $this->getTargetToken($testMarker, [T_OPEN_SQUARE_BRACKET, T_OPEN_SHORT_ARRAY]);
$this->assertSame(T_OPEN_SHORT_ARRAY, $tokens[$opener]['code']);
$this->assertSame('T_OPEN_SHORT_ARRAY', $tokens[$opener]['type']);

if (isset($tokens[$opener]['bracket_closer']) === true) {
$closer = $tokens[$opener]['bracket_closer'];
$this->assertSame(T_CLOSE_SHORT_ARRAY, $tokens[$closer]['code']);
$this->assertSame('T_CLOSE_SHORT_ARRAY', $tokens[$closer]['type']);
}

}//end testShortArrays()


/**
* Data provider.
*
* @see testShortArrays()
*
* @return array
*/
public function dataShortArrays()
{
return [
['/* testShortArrayDeclarationEmpty */'],
['/* testShortArrayDeclarationWithOneValue */'],
['/* testShortArrayDeclarationWithMultipleValues */'],
['/* testShortArrayDeclarationWithDereferencing */'],
['/* testShortListDeclaration */'],
['/* testNestedListDeclaration */'],
['/* testArrayWithinFunctionCall */'],
];

}//end dataShortArrays()


}//end class

0 comments on commit a1a7e70

Please sign in to comment.