From 8fedf8c449ba95a8dcd82a51cbe377bf2a03aea2 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 5 Nov 2019 08:21:25 +1100 Subject: [PATCH] The PHP 7.4 T_FN token has been made available for older versions (ref #2523) --- package.xml | 9 +++ src/Tokenizers/PHP.php | 30 ++++++++- src/Util/Tokens.php | 4 ++ tests/Core/Tokenizer/BackfillFnTokenTest.inc | 16 +++++ tests/Core/Tokenizer/BackfillFnTokenTest.php | 69 ++++++++++++++++++++ 5 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 tests/Core/Tokenizer/BackfillFnTokenTest.inc create mode 100644 tests/Core/Tokenizer/BackfillFnTokenTest.php diff --git a/package.xml b/package.xml index 89ef9b3657..f999f4d169 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - The PHP 7.4 T_FN token has been made available for older versions + -- T_FN represents the fn string used for arrow functions + -- The token is associated with the opening and closing parenthesis of the statement - Generic.CodeAnalysis.EmptyPhpStatement now reports unnecessary semicolons after control structure closing braces -- Thanks to Vincent Langlet for the patch - Fixed bug #2638 : Squiz.CSS.DuplicateClassDefinitionSniff sees comments as part of the class name @@ -89,6 +92,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -1917,6 +1922,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -1956,6 +1963,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 8ccb212e90..7024cae254 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1045,7 +1045,7 @@ protected function tokenize($string) }//end if /* - Tokens after a double colon may be look like scope openers, + Tokens after a double colon may look like scope openers, such as when writing code like Foo::NAMESPACE, but they are only ever variables or strings. */ @@ -1606,6 +1606,34 @@ protected function processAdditional() }//end if continue; + } else if ($this->tokens[$i]['code'] === T_STRING + && strtolower($this->tokens[$i]['content']) === 'fn' + ) { + // Possible arrow function. + for ($x = ($i + 1); $i < $numTokens; $x++) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) { + // Non-whitespace content. + break; + } + } + + if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$i]['line']; + echo "\t* token $i on line $line changed from T_STRING to T_FN".PHP_EOL; + } + + $this->tokens[$i]['code'] = T_FN; + $this->tokens[$i]['type'] = 'T_FN'; + $this->tokens[$i]['parenthesis_owner'] = $i; + $this->tokens[$i]['parenthesis_opener'] = $x; + $this->tokens[$i]['parenthesis_closer'] = $this->tokens[$x]['parenthesis_closer']; + + $opener = $this->tokens[$i]['parenthesis_opener']; + $closer = $this->tokens[$i]['parenthesis_closer']; + $this->tokens[$opener]['parenthesis_owner'] = $i; + $this->tokens[$closer]['parenthesis_owner'] = $i; + } } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) { if (isset($this->tokens[$i]['bracket_closer']) === false) { continue; diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index c7ae3de81a..2f7f656f5f 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -119,6 +119,10 @@ define('T_BAD_CHARACTER', 'PHPCS_T_BAD_CHARACTER'); } +if (defined('T_FN') === false) { + define('T_FN', 'PHPCS_T_FN'); +} + // Tokens used for parsing doc blocks. define('T_DOC_COMMENT_STAR', 'PHPCS_T_DOC_COMMENT_STAR'); define('T_DOC_COMMENT_WHITESPACE', 'PHPCS_T_DOC_COMMENT_WHITESPACE'); diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.inc b/tests/Core/Tokenizer/BackfillFnTokenTest.inc new file mode 100644 index 0000000000..f9f002a059 --- /dev/null +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.inc @@ -0,0 +1,16 @@ + $x + $y; + +/* testMixedCase */ +$fn1 = Fn($x) => $x + $y; + +/* testWhitespace */ +$fn1 = fn ($x) => $x + $y; + +/* testComment */ +$fn1 = fn /* comment here */ ($x) => $x + $y; + +/* testFunctionName */ +function fn() {} diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php new file mode 100644 index 0000000000..fcca65b45b --- /dev/null +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -0,0 +1,69 @@ + + * @copyright 2019 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 BackfillFnTokenTest extends AbstractMethodUnitTest +{ + + + /** + * Data provider. + * + * @see testBackill() + * + * @return array + */ + public function dataBackfill() + { + return [ + ['/* testStandard */'], + ['/* testMixedCase */'], + ['/* testWhitespace */'], + ['/* testComment */'], + ['/* testFunctionName */'], + ]; + + }//end dataAnonClassNoParentheses() + + + /** + * Test that anonymous class tokens without parenthesis do not get assigned a parenthesis owner. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * + * @dataProvider dataBackfill + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testBackfill($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, T_FN); + $this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$token]), 'Parenthesis owner is not set'); + $this->assertTrue(array_key_exists('parenthesis_opener', $tokens[$token]), 'Parenthesis opener is not set'); + $this->assertTrue(array_key_exists('parenthesis_closer', $tokens[$token]), 'Parenthesis closer is not set'); + $this->assertSame($tokens[$token]['parenthesis_owner'], $token, 'Parenthesis owner is not the T_FN token'); + + $opener = $tokens[$token]['parenthesis_opener']; + $this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$opener]), 'Opening parenthesis owner is not set'); + $this->assertSame($tokens[$opener]['parenthesis_owner'], $token, 'Opening parenthesis owner is not the T_FN token'); + + $closer = $tokens[$token]['parenthesis_closer']; + $this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$closer]), 'Closing parenthesis owner is not set'); + $this->assertSame($tokens[$closer]['parenthesis_owner'], $token, 'Closing parenthesis owner is not the T_FN token'); + + }//end testAnonClassNoParentheses() + + +}//end class