diff --git a/package.xml b/package.xml index 4d2105b94e..be71b05995 100644 --- a/package.xml +++ b/package.xml @@ -113,6 +113,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -1977,6 +1979,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2030,6 +2034,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 2fc86a86d6..84c06a072c 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -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 diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index d11b193629..bc3a32414a 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -589,6 +589,24 @@ final class Tokens T_TRAIT => T_TRAIT, ]; + /** + * Tokens representing PHP magic constants. + * + * @var array => + * + * @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. diff --git a/tests/Core/Tokenizer/ShortArrayTest.inc b/tests/Core/Tokenizer/ShortArrayTest.inc new file mode 100644 index 0000000000..a864869af9 --- /dev/null +++ b/tests/Core/Tokenizer/ShortArrayTest.inc @@ -0,0 +1,92 @@ +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 = [ diff --git a/tests/Core/Tokenizer/ShortArrayTest.php b/tests/Core/Tokenizer/ShortArrayTest.php new file mode 100644 index 0000000000..04aaf3db26 --- /dev/null +++ b/tests/Core/Tokenizer/ShortArrayTest.php @@ -0,0 +1,130 @@ + + * @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