From 3e93f2ef80948a9319dcec6e935bd1cfbfb985ca Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 31 Jul 2024 04:19:53 +0200 Subject: [PATCH 01/13] Tokenizer/PHP: add tests for heredoc/nowdoc tokenization The PHP tokenizer contains logic to: * Retokenize the start/end tokens for nowdocs from `T_(START|END)_HEREDOC` to `T_(START|END)_NOWDOC`; * Retokenize the _contents_ of a heredoc/nowdoc to `T_HEREDOC`/`T_NOWDOC` tokens. * Retokenize the start token from `T_START_(HERE|NOW)DOC` to `T_STRING` if the heredoc/nowdoc is unclosed; * Ensure that each line in the contents has its own token. This commit adds tests safeguarding and documenting this part of the tokenizer. --- .../Core/Tokenizer/PHP/HeredocNowdocTest.inc | 39 ++++ .../Core/Tokenizer/PHP/HeredocNowdocTest.php | 213 ++++++++++++++++++ .../Tokenizer/PHP/HeredocParseErrorTest.inc | 11 + .../Tokenizer/PHP/HeredocParseErrorTest.php | 41 ++++ 4 files changed, 304 insertions(+) create mode 100644 tests/Core/Tokenizer/PHP/HeredocNowdocTest.inc create mode 100644 tests/Core/Tokenizer/PHP/HeredocNowdocTest.php create mode 100644 tests/Core/Tokenizer/PHP/HeredocParseErrorTest.inc create mode 100644 tests/Core/Tokenizer/PHP/HeredocParseErrorTest.php diff --git a/tests/Core/Tokenizer/PHP/HeredocNowdocTest.inc b/tests/Core/Tokenizer/PHP/HeredocNowdocTest.inc new file mode 100644 index 0000000000..5041dda157 --- /dev/null +++ b/tests/Core/Tokenizer/PHP/HeredocNowdocTest.inc @@ -0,0 +1,39 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Tokenizer\PHP; + +use PHP_CodeSniffer\Tests\Core\Tokenizer\AbstractTokenizerTestCase; +use PHP_CodeSniffer\Util\Tokens; + +/** + * Tests the tokenization for heredoc/nowdoc constructs. + * + * Verifies that: + * - Nowdoc opener/closers are retokenized from `T_[START_|END_]HEREDOC` to `T_[START_|END_]NOWDOC`. + * - The contents of the heredoc/nowdoc is tokenized as `T_HEREDOC`/`T_NOWDOC`. + * - Each line of the contents has its own token, which includes the new line char. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + */ +final class HeredocNowdocTest extends AbstractTokenizerTestCase +{ + + + /** + * Verify tokenization a heredoc construct. + * + * @phpcs:disable Squiz.Arrays.ArrayDeclaration.SpaceBeforeDoubleArrow -- Readability is better with alignment. + * + * @return void + */ + public function testHeredocSingleLine() + { + $expectedSequence = [ + [T_START_HEREDOC => '<< 'Some $var text'."\n"], + [T_END_HEREDOC => 'EOD'], + ]; + + $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_START_HEREDOC); + + $this->checkTokenSequence($target, $expectedSequence); + + }//end testHeredocSingleLine() + + + /** + * Verify tokenization a nowdoc construct. + * + * @phpcs:disable Squiz.Arrays.ArrayDeclaration.SpaceBeforeDoubleArrow -- Readability is better with alignment. + * + * @return void + */ + public function testNowdocSingleLine() + { + $expectedSequence = [ + [T_START_NOWDOC => "<<<'MARKER'\n"], + [T_NOWDOC => 'Some text'."\n"], + [T_END_NOWDOC => 'MARKER'], + ]; + + $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_START_NOWDOC); + + $this->checkTokenSequence($target, $expectedSequence); + + }//end testNowdocSingleLine() + + + /** + * Verify tokenization a multiline heredoc construct. + * + * @phpcs:disable Squiz.Arrays.ArrayDeclaration.SpaceBeforeDoubleArrow -- Readability is better with alignment. + * + * @return void + */ + public function testHeredocMultiLine() + { + $expectedSequence = [ + [T_START_HEREDOC => '<<<"😬"'."\n"], + [T_HEREDOC => 'Lorum ipsum'."\n"], + [T_HEREDOC => 'Some $var text'."\n"], + [T_HEREDOC => 'dolor sit amet'."\n"], + [T_END_HEREDOC => '😬'], + ]; + + $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_START_HEREDOC); + + $this->checkTokenSequence($target, $expectedSequence); + + }//end testHeredocMultiLine() + + + /** + * Verify tokenization a multiline testNowdocSingleLine construct. + * + * @phpcs:disable Squiz.Arrays.ArrayDeclaration.SpaceBeforeDoubleArrow -- Readability is better with alignment. + * + * @return void + */ + public function testNowdocMultiLine() + { + $expectedSequence = [ + [T_START_NOWDOC => "<<<'multi_line'\n"], + [T_NOWDOC => 'Lorum ipsum'."\n"], + [T_NOWDOC => 'Some text'."\n"], + [T_NOWDOC => 'dolor sit amet'."\n"], + [T_END_NOWDOC => 'multi_line'], + ]; + + $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_START_NOWDOC); + + $this->checkTokenSequence($target, $expectedSequence); + + }//end testNowdocMultiLine() + + + /** + * Verify tokenization a multiline heredoc construct. + * + * @phpcs:disable Squiz.Arrays.ArrayDeclaration.SpaceBeforeDoubleArrow -- Readability is better with alignment. + * + * @return void + */ + public function testHeredocEndsOnBlankLine() + { + $expectedSequence = [ + [T_START_HEREDOC => '<< 'Lorum ipsum'."\n"], + [T_HEREDOC => 'dolor sit amet'."\n"], + [T_HEREDOC => "\n"], + [T_END_HEREDOC => 'EOD'], + ]; + + $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_START_HEREDOC); + + $this->checkTokenSequence($target, $expectedSequence); + + }//end testHeredocEndsOnBlankLine() + + + /** + * Verify tokenization a multiline testNowdocSingleLine construct. + * + * @phpcs:disable Squiz.Arrays.ArrayDeclaration.SpaceBeforeDoubleArrow -- Readability is better with alignment. + * + * @return void + */ + public function testNowdocEndsOnBlankLine() + { + $expectedSequence = [ + [T_START_NOWDOC => "<<<'EOD'\n"], + [T_NOWDOC => 'Lorum ipsum'."\n"], + [T_NOWDOC => 'dolor sit amet'."\n"], + [T_NOWDOC => "\n"], + [T_END_NOWDOC => 'EOD'], + ]; + + $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_START_NOWDOC); + + $this->checkTokenSequence($target, $expectedSequence); + + }//end testNowdocEndsOnBlankLine() + + + /** + * Test helper. Check a token sequence complies with an expected token sequence. + * + * @param int $startPtr The position in the file to start checking from. + * @param array> $expectedSequence The consecutive token constants and their contents to expect. + * + * @return void + */ + private function checkTokenSequence($startPtr, array $expectedSequence) + { + $tokens = $this->phpcsFile->getTokens(); + + $sequenceKey = 0; + $sequenceCount = count($expectedSequence); + + for ($i = $startPtr; $sequenceKey < $sequenceCount; $i++, $sequenceKey++) { + $currentItem = $expectedSequence[$sequenceKey]; + $expectedCode = key($currentItem); + $expectedType = Tokens::tokenName($expectedCode); + $expectedContent = current($currentItem); + $errorMsgSuffix = PHP_EOL.'(StackPtr: '.$i.' | Position in sequence: '.$sequenceKey.' | Expected: '.$expectedType.')'; + + $this->assertSame( + $expectedCode, + $tokens[$i]['code'], + 'Token tokenized as '.Tokens::tokenName($tokens[$i]['code']).', not '.$expectedType.' (code)'.$errorMsgSuffix + ); + + $this->assertSame( + $expectedType, + $tokens[$i]['type'], + 'Token tokenized as '.$tokens[$i]['type'].', not '.$expectedType.' (type)'.$errorMsgSuffix + ); + + $this->assertSame( + $expectedContent, + $tokens[$i]['content'], + 'Token content did not match expectations'.$errorMsgSuffix + ); + }//end for + + }//end checkTokenSequence() + + +}//end class diff --git a/tests/Core/Tokenizer/PHP/HeredocParseErrorTest.inc b/tests/Core/Tokenizer/PHP/HeredocParseErrorTest.inc new file mode 100644 index 0000000000..d552b12832 --- /dev/null +++ b/tests/Core/Tokenizer/PHP/HeredocParseErrorTest.inc @@ -0,0 +1,11 @@ +>>>>>> master diff --git a/tests/Core/Tokenizer/PHP/HeredocParseErrorTest.php b/tests/Core/Tokenizer/PHP/HeredocParseErrorTest.php new file mode 100644 index 0000000000..7cca49aac3 --- /dev/null +++ b/tests/Core/Tokenizer/PHP/HeredocParseErrorTest.php @@ -0,0 +1,41 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Tokenizer\PHP; + +use PHP_CodeSniffer\Tests\Core\Tokenizer\AbstractTokenizerTestCase; + +/** + * Tests the tokenization for an unclosed heredoc construct. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + */ +final class HeredocParseErrorTest extends AbstractTokenizerTestCase +{ + + + /** + * Verify that a heredoc (and nowdoc) start token is retokenized to T_STRING if no closer is found. + * + * @return void + */ + public function testMergeConflict() + { + $tokens = $this->phpcsFile->getTokens(); + + $token = $this->getTargetToken('/* testUnclosedHeredoc */', [T_START_HEREDOC, T_STRING], '<<< HEAD'."\n"); + $tokenArray = $tokens[$token]; + + $this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_START_HEREDOC (code)'); + $this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_START_HEREDOC (type)'); + + }//end testMergeConflict() + + +}//end class From 51e78284ca80c58589fe5ad9bbddb6a8db2aab94 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 23 Jul 2024 13:04:46 +0200 Subject: [PATCH 02/13] GH Actions: provide attestations for release PHAR files GitHub has released a new feature called Artifact Attestations, which allows for verifying the integrity of artifacts build via GitHub Actions. This is an additional security and compliance feature, which allows for checking which workflow build the artifact and ensuring the artifact has not been tampered with after the generation via the workflow. To me, this sounds like a good addition to the build process for release phars, so this commit implements attesting PHARS for releases. To verify the phar files (after the next release, which will be the first to use this feature): * Download the PHAR file(s), either from "Releases", from the tag "Test" workflow run or via PHIVE. * If downloaded as a zipped artifact, unzip to get to the actual PHAR files. * Using the GitHub CLI tool, run the below command to verify: ```bash gh attestation verify phpcs.phar -o PHPCSStandards gh attestation verify phpcbf.phar -o PHPCSStandards ``` References: * https://github.blog/changelog/2024-06-25-artifact-attestations-is-generally-available/ * https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds * https://github.com/actions/attest-build-provenance * https://cli.github.com/ * https://github.com/cli/cli --- .github/workflows/test.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 59df883a74..d402c1ade0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,6 +25,11 @@ jobs: runs-on: ubuntu-latest name: "Build Phar on PHP: 8.0" + permissions: + id-token: write + contents: read + attestations: write + steps: - name: Checkout code uses: actions/checkout@v4 @@ -39,6 +44,17 @@ jobs: - name: Build the phar run: php scripts/build-phar.php + # Provide provenance for generated binaries. + # Only attests the build artifacts which will be used in the published releases as per the guidelines in "what to attest". + # https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds + - name: Generate artifact attestations + if: ${{ github.ref_type == 'tag' }} + uses: actions/attest-build-provenance@v1 + with: + subject-path: | + ${{ github.workspace }}/phpcs.phar + ${{ github.workspace }}/phpcbf.phar + - name: Upload the PHPCS phar uses: actions/upload-artifact@v4 with: From 60a5557569342104672b7838530c3d30a1b30da9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 29 Jul 2024 21:27:14 +0200 Subject: [PATCH 03/13] Squiz/FunctionDeclaration: rename test case file .... to allow for adding additional test case files. --- ...t.inc => FunctionDeclarationUnitTest.1.inc} | 0 .../Functions/FunctionDeclarationUnitTest.php | 18 +++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) rename src/Standards/Squiz/Tests/Functions/{FunctionDeclarationUnitTest.inc => FunctionDeclarationUnitTest.1.inc} (100%) diff --git a/src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.inc b/src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.1.inc similarity index 100% rename from src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.inc rename to src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.1.inc diff --git a/src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.php b/src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.php index 021ebe71dc..eb9713d8fb 100644 --- a/src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.php +++ b/src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.php @@ -26,14 +26,22 @@ final class FunctionDeclarationUnitTest extends AbstractSniffUnitTest * The key of the array should represent the line number and the value * should represent the number of errors that should occur on that line. * + * @param string $testFile The name of the file being tested. + * * @return array */ - public function getErrorList() + public function getErrorList($testFile='') { - return [ - 55 => 1, - 68 => 1, - ]; + switch ($testFile) { + case 'FunctionDeclarationUnitTest.1.inc': + return [ + 55 => 1, + 68 => 1, + ]; + + default: + return []; + }//end switch }//end getErrorList() From 49b84946bcef41d982e3e6a05b25e24cfad20f19 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 29 Jul 2024 21:27:21 +0200 Subject: [PATCH 04/13] AbstractPatternSniff: prevent PHP notice In a live coding situation, the token triggering the pattern being looked for could be at or near the end of the file. This could lead to a situation where the pattern could never match anyhow as there are not enough tokens left in the file to match against. In this situation, the sniff could trigger the following PHP error: ``` Increment on type bool has no effect, this will change in the next major version of PHP in path/to/phpcs/src/Sniffs/AbstractPatternSniff.php on line 627 ``` This commit prevents this error by bowing out early if there are not enough tokens in the file under scan to match the pattern. Tested via the `Squiz.Functions.FunctionDeclaration` sniff via which this issue was discovered. --- src/Sniffs/AbstractPatternSniff.php | 5 +++++ .../Squiz/Tests/Functions/FunctionDeclarationUnitTest.2.inc | 5 +++++ .../Squiz/Tests/Functions/FunctionDeclarationUnitTest.3.inc | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.2.inc create mode 100644 src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.3.inc diff --git a/src/Sniffs/AbstractPatternSniff.php b/src/Sniffs/AbstractPatternSniff.php index e895365688..d9528dccf8 100644 --- a/src/Sniffs/AbstractPatternSniff.php +++ b/src/Sniffs/AbstractPatternSniff.php @@ -416,6 +416,11 @@ protected function processPattern($patternInfo, File $phpcsFile, $stackPtr) $lastAddedStackPtr = null; $patternLen = count($pattern); + if (($stackPtr + $patternLen - $patternInfo['listen_pos']) > $phpcsFile->numTokens) { + // Pattern can never match as there are not enough tokens left in the file. + return false; + } + for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) { if (isset($tokens[$stackPtr]) === false) { break; diff --git a/src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.2.inc b/src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.2.inc new file mode 100644 index 0000000000..51a55e92ad --- /dev/null +++ b/src/Standards/Squiz/Tests/Functions/FunctionDeclarationUnitTest.2.inc @@ -0,0 +1,5 @@ + Date: Mon, 29 Jul 2024 22:41:23 +0200 Subject: [PATCH 05/13] Squiz/OperatorBracket: rename test case file .... to allow for adding additional test case files. --- ...peratorBracketUnitTest.inc => OperatorBracketUnitTest.1.inc} | 0 ...etUnitTest.inc.fixed => OperatorBracketUnitTest.1.inc.fixed} | 0 .../Squiz/Tests/Formatting/OperatorBracketUnitTest.php | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename src/Standards/Squiz/Tests/Formatting/{OperatorBracketUnitTest.inc => OperatorBracketUnitTest.1.inc} (100%) rename src/Standards/Squiz/Tests/Formatting/{OperatorBracketUnitTest.inc.fixed => OperatorBracketUnitTest.1.inc.fixed} (100%) diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.1.inc similarity index 100% rename from src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc rename to src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.1.inc diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.1.inc.fixed similarity index 100% rename from src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed rename to src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.1.inc.fixed diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php index 0da136242b..aac4327571 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php @@ -33,7 +33,7 @@ final class OperatorBracketUnitTest extends AbstractSniffUnitTest public function getErrorList($testFile='') { switch ($testFile) { - case 'OperatorBracketUnitTest.inc': + case 'OperatorBracketUnitTest.1.inc': return [ 3 => 1, 6 => 1, From 7a4c7871528b3ad6e1d9a23937d887c5b4c3b7c2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 29 Jul 2024 22:57:06 +0200 Subject: [PATCH 06/13] Squiz/OperatorBracket: prevent PHP notices during live coding During live coding (or in the case of parse errors), the "end of the expression" cannot always be correctly determined. In such a case, the sniff should stay silent. This wasn't always handled correctly so far and could lead to the following PHP notices: ``` Undefined array key "parenthesis_closer" in path/to/phpcs/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php on line 357 Undefined array key "bracket_closer" in path/to/phpcs/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php on line 362 ``` This commit fixes these by adding some extra defensive coding and bowing out when an unclosed bracket set is encountered. Includes tests. --- .../Sniffs/Formatting/OperatorBracketSniff.php | 17 ++++++++++++----- .../Formatting/OperatorBracketUnitTest.2.inc | 7 +++++++ .../Formatting/OperatorBracketUnitTest.3.inc | 5 +++++ 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.2.inc create mode 100644 src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.3.inc diff --git a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php index b79e6b3ec6..44429012a1 100644 --- a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php +++ b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php @@ -354,16 +354,23 @@ public function addMissingBracketsError($phpcsFile, $stackPtr) } if ($tokens[$after]['code'] === T_OPEN_PARENTHESIS) { + if (isset($tokens[$after]['parenthesis_closer']) === false) { + // Live coding/parse error. Ignore. + return; + } + $after = $tokens[$after]['parenthesis_closer']; continue; } - if ($tokens[$after]['code'] === T_OPEN_SQUARE_BRACKET) { - $after = $tokens[$after]['bracket_closer']; - continue; - } + if (($tokens[$after]['code'] === T_OPEN_SQUARE_BRACKET + || $tokens[$after]['code'] === T_OPEN_SHORT_ARRAY) + ) { + if (isset($tokens[$after]['bracket_closer']) === false) { + // Live coding/parse error. Ignore. + return; + } - if ($tokens[$after]['code'] === T_OPEN_SHORT_ARRAY) { $after = $tokens[$after]['bracket_closer']; continue; } diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.2.inc b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.2.inc new file mode 100644 index 0000000000..1284f121fc --- /dev/null +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.2.inc @@ -0,0 +1,7 @@ + Date: Wed, 31 Jul 2024 03:20:37 +0200 Subject: [PATCH 07/13] Tokenizer: apply tab replacement to heredoc/nowdoc opener _You learn something new every day ;-)_ While probably exceedingly rare to be found in actual codebases, the PHP tokenizer apparently allows for whitespace between the `<<<` and the heredoc/nowdoc identifier. See: https://3v4l.org/NUHZd Both spaces as well as tabs are allowed. New lines are not allowed. Comments are also not allowed. See: https://3v4l.org/7PIEK The PHPCS `Tokenizer` did not execute tab replacement on these tokens leading to unexpected `'content'` and incorrect `'length'` values in the `File::$tokens` array, which in turn could lead to incorrect sniff results and incorrect fixes. This commit adds the `T_START_HEREDOC`/`T_START_NOWDOC` tokens to the array of tokens for which to do tab replacement to make them more consistent with the rest of PHPCS. Includes unit tests safeguarding this change. Ref: https://externals.io/message/124462#124518 --- src/Tokenizers/Tokenizer.php | 2 + .../Tokenizer/HeredocNowdocOpenerTest.inc | 31 +++++ .../Tokenizer/HeredocNowdocOpenerTest.php | 121 ++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 tests/Core/Tokenizer/Tokenizer/HeredocNowdocOpenerTest.inc create mode 100644 tests/Core/Tokenizer/Tokenizer/HeredocNowdocOpenerTest.php diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index ba41f8da88..68238d3023 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -193,6 +193,8 @@ private function createPositionMap() T_DOC_COMMENT_STRING => true, T_CONSTANT_ENCAPSED_STRING => true, T_DOUBLE_QUOTED_STRING => true, + T_START_HEREDOC => true, + T_START_NOWDOC => true, T_HEREDOC => true, T_NOWDOC => true, T_END_HEREDOC => true, diff --git a/tests/Core/Tokenizer/Tokenizer/HeredocNowdocOpenerTest.inc b/tests/Core/Tokenizer/Tokenizer/HeredocNowdocOpenerTest.inc new file mode 100644 index 0000000000..dc2b2f2dc2 --- /dev/null +++ b/tests/Core/Tokenizer/Tokenizer/HeredocNowdocOpenerTest.inc @@ -0,0 +1,31 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Tokenizer\Tokenizer; + +use PHP_CodeSniffer\Tests\Core\Tokenizer\AbstractTokenizerTestCase; + +/** + * Heredoc/nowdoc opener token test. + */ +final class HeredocNowdocOpenerTest extends AbstractTokenizerTestCase +{ + + + /** + * Verify that spaces/tabs in a heredoc/nowdoc opener token get the tab replacement treatment. + * + * @param string $testMarker The comment prefacing the target token. + * @param array $expected Expectations for the token array. + * + * @dataProvider dataHeredocNowdocOpenerTabReplacement + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * + * @return void + */ + public function testHeredocNowdocOpenerTabReplacement($testMarker, $expected) + { + $tokens = $this->phpcsFile->getTokens(); + $opener = $this->getTargetToken($testMarker, [T_START_HEREDOC, T_START_NOWDOC]); + + foreach ($expected as $key => $value) { + if ($key === 'orig_content' && $value === null) { + $this->assertArrayNotHasKey($key, $tokens[$opener], "Unexpected 'orig_content' key found in the token array."); + continue; + } + + $this->assertArrayHasKey($key, $tokens[$opener], "Key $key not found in the token array."); + $this->assertSame($value, $tokens[$opener][$key], "Value for key $key does not match expectation."); + } + + }//end testHeredocNowdocOpenerTabReplacement() + + + /** + * Data provider. + * + * @see testHeredocNowdocOpenerTabReplacement() + * + * @return array>> + */ + public static function dataHeredocNowdocOpenerTabReplacement() + { + return [ + 'Heredoc opener without space' => [ + 'testMarker' => '/* testHeredocOpenerNoSpace */', + 'expected' => [ + 'length' => 6, + 'content' => '<< null, + ], + ], + 'Nowdoc opener without space' => [ + 'testMarker' => '/* testNowdocOpenerNoSpace */', + 'expected' => [ + 'length' => 8, + 'content' => "<<<'EOD' +", + 'orig_content' => null, + ], + ], + 'Heredoc opener with space(s)' => [ + 'testMarker' => '/* testHeredocOpenerHasSpace */', + 'expected' => [ + 'length' => 7, + 'content' => '<<< END +', + 'orig_content' => null, + ], + ], + 'Nowdoc opener with space(s)' => [ + 'testMarker' => '/* testNowdocOpenerHasSpace */', + 'expected' => [ + 'length' => 21, + 'content' => "<<< 'END' +", + 'orig_content' => null, + ], + ], + 'Heredoc opener with tab(s)' => [ + 'testMarker' => '/* testHeredocOpenerHasTab */', + 'expected' => [ + 'length' => 18, + 'content' => '<<< "END" +', + 'orig_content' => '<<< "END" +', + ], + ], + 'Nowdoc opener with tab(s)' => [ + 'testMarker' => '/* testNowdocOpenerHasTab */', + 'expected' => [ + 'length' => 11, + 'content' => "<<< 'END' +", + 'orig_content' => "<<< 'END' +", + ], + ], + ]; + + }//end dataHeredocNowdocOpenerTabReplacement() + + +}//end class From d6a6cc9e54389897c749a7fa143632c3975f6c76 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 5 Aug 2024 03:13:40 +0200 Subject: [PATCH 08/13] Squiz/SelfMemberReference: bug fix - false negative with namespace keyword as operator > The `namespace` keyword can both be used for namespace declarations as well as as an operator - "magic keyword" - to resolve to the current namespace. > See: https://www.php.net/manual/en/language.namespaces.nsconstants.php#example-298 > This last case is not correctly taken into account when determining the current namespace, which leads to false negatives. Fixed now. Includes test. Fixes 553 --- .../Sniffs/Classes/SelfMemberReferenceSniff.php | 14 +++++++++++--- .../Tests/Classes/SelfMemberReferenceUnitTest.inc | 14 ++++++++++++++ .../Classes/SelfMemberReferenceUnitTest.inc.fixed | 14 ++++++++++++++ .../Tests/Classes/SelfMemberReferenceUnitTest.php | 1 + 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php b/src/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php index 4c54aa4618..8ee4de45a8 100644 --- a/src/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php +++ b/src/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php @@ -221,15 +221,23 @@ protected function getDeclarationNameWithNamespace(array $tokens, $stackPtr) */ protected function getNamespaceOfScope(File $phpcsFile, $stackPtr) { - $namespace = '\\'; - $namespaceDeclaration = $phpcsFile->findPrevious(T_NAMESPACE, $stackPtr); + $namespace = '\\'; + $tokens = $phpcsFile->getTokens(); + + while (($namespaceDeclaration = $phpcsFile->findPrevious(T_NAMESPACE, $stackPtr)) !== false) { + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($namespaceDeclaration + 1), null, true); + if ($tokens[$nextNonEmpty]['code'] === T_NS_SEPARATOR) { + // Namespace operator. Ignore. + $stackPtr = ($namespaceDeclaration - 1); + continue; + } - if ($namespaceDeclaration !== false) { $endOfNamespaceDeclaration = $phpcsFile->findNext([T_SEMICOLON, T_OPEN_CURLY_BRACKET, T_CLOSE_TAG], $namespaceDeclaration); $namespace = $this->getDeclarationNameWithNamespace( $phpcsFile->getTokens(), ($endOfNamespaceDeclaration - 1) ); + break; } return $namespace; diff --git a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc index f2c4713a66..4f17813866 100644 --- a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc +++ b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc @@ -183,3 +183,17 @@ class Baz { \EndsIn\CloseTag\Baz::something(); } } + +// Issue PHPCSStandards/PHP_CodeSniffer#553. +namespace TestMe; + +namespace\functionCall(); +namespace\anotherFunctionCall(); + +class SelfMemberReference +{ + public function falseNegative() + { + $testResults[] = \TestMe\SelfMemberReference::test(); + } +} diff --git a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc.fixed index 95689d4a9f..770429dc82 100644 --- a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc.fixed @@ -171,3 +171,17 @@ class Baz { self::something(); } } + +// Issue PHPCSStandards/PHP_CodeSniffer#553. +namespace TestMe; + +namespace\functionCall(); +namespace\anotherFunctionCall(); + +class SelfMemberReference +{ + public function falseNegative() + { + $testResults[] = self::test(); + } +} diff --git a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.php b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.php index 180a010631..84b9c15e55 100644 --- a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.php +++ b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.php @@ -47,6 +47,7 @@ public function getErrorList() 162 => 1, 171 => 1, 183 => 1, + 197 => 1, ]; }//end getErrorList() From b06179a57d42aad69e8c94c2f0437a4185da5287 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 13 Aug 2024 13:07:04 -0300 Subject: [PATCH 09/13] Fix small typo in the changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4172e1cdab..ae6e056d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -396,7 +396,7 @@ _Nothing yet._ - **_In contrast to earlier information, the `squizlabs/php_codesniffer` package now points to the new repository and everything will continue to work as before._** - PHIVE users may need to clear the PHIVE URL cache. - PHIVE users who don't use the package alias, but refer to the package URL, will need to update the URL from `https://squizlabs.github.io/PHP_CodeSniffer/phars/` to `https://phars.phpcodesniffer.com/phars/`. - - Users who download the PHAR files using curl or wget, will need to update the download URL from `https://squizlabs.github.io/PHP_CodeSniffer/[phpcs|phpcbf].phar` or `https://github.com/squizlabs/PHP_CodeSnifffer/releases/latest/download/[phpcs|phpcbf].phar` to `https://phars.phpcodesniffer.com/[phpcs|phpcbf].phar`. + - Users who download the PHAR files using curl or wget, will need to update the download URL from `https://squizlabs.github.io/PHP_CodeSniffer/[phpcs|phpcbf].phar` or `https://github.com/squizlabs/PHP_CodeSniffer/releases/latest/download/[phpcs|phpcbf].phar` to `https://phars.phpcodesniffer.com/[phpcs|phpcbf].phar`. - For users who install PHP_CodeSniffer via the [Setup-PHP](https://github.com/shivammathur/setup-php/) action runner for GitHub Actions, nothing changes. - Users using a git clone will need to update the clone address from `git@github.com:squizlabs/PHP_CodeSniffer.git` to `git@github.com:PHPCSStandards/PHP_CodeSniffer.git`. - Contributors will need to fork the new repo and add both the new fork as well as the new repo as remotes to their local git copy of PHP_CodeSniffer. From 0ed5577b4e3d70ce357d19a36ab5e64587f4327c Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Fri, 9 Aug 2024 15:05:40 -0300 Subject: [PATCH 10/13] Squiz/EmbeddedPhp: fix false positive when handling short open tags The content of a PHP long open tag token is ` ``` Resulted in the error (there is just one space after the opening tag and not two, as stated in the error): ``` Expected 1 space after opening PHP tag; 2 found (Squiz.PHP.EmbeddedPhp.SpacingAfterOpen) ``` And the incorrect fix: ``` ``` This commit fixes this problem by changing the sniff code to consider that only long open tags contain a space after the tag in the `content` key of the token array. --- .../Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 12 ++++++--- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc | 4 +++ .../Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed | 4 +++ .../Tests/PHP/EmbeddedPhpUnitTest.24.inc | 25 +++++++++++++++++++ .../PHP/EmbeddedPhpUnitTest.24.inc.fixed | 25 +++++++++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 12 +++++++++ 6 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.24.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.24.inc.fixed diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index 0be6118e43..63a1cdd09a 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -379,10 +379,14 @@ private function validateInlineEmbeddedPhp($phpcsFile, $stackPtr, $closeTag) } // Check that there is one, and only one space at the start of the statement. - $leadingSpace = 0; - if ($tokens[$stackPtr]['code'] === T_OPEN_TAG) { + $leadingSpace = 0; + $isLongOpenTag = false; + if ($tokens[$stackPtr]['code'] === T_OPEN_TAG + && stripos($tokens[$stackPtr]['content'], 'addFixableError($error, $stackPtr, 'SpacingAfterOpen', $data); if ($fix === true) { - if ($tokens[$stackPtr]['code'] === T_OPEN_TAG) { + if ($isLongOpenTag === true) { $phpcsFile->fixer->replaceToken(($stackPtr + 1), ''); } else if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE) { // Short open tag with too much whitespace. diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc index f28c559894..015468357d 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc @@ -265,6 +265,10 @@ echo 'the PHP tag is correctly indented as an indent less than the previous code echo 'the PHP tag is incorrectly indented as the indent is more than 4 different from the indent of the previous code'; ?> + + + + + + + + + + + + + + + + + + + + + + + + + + 1, 263 => 1, 264 => 1, + 270 => 1, ]; case 'EmbeddedPhpUnitTest.2.inc': @@ -190,6 +191,17 @@ public function getErrorList($testFile='') 22 => 2, ]; + case 'EmbeddedPhpUnitTest.24.inc': + $shortOpenTagDirective = (bool) ini_get('short_open_tag'); + if ($shortOpenTagDirective === true) { + return [ + 18 => 1, + 20 => 1, + ]; + } + + return []; + default: return []; }//end switch From 6fbbc1078094d905f0773421f13830744a144d1e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 13 Aug 2024 20:32:44 +0200 Subject: [PATCH 11/13] CS fix, my bad --- src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php index cb2a8fa926..410f938918 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php @@ -199,7 +199,6 @@ public function getErrorList($testFile='') 20 => 1, ]; } - return []; default: From 5759956f721c913b350df05436cd5562c60cb8e2 Mon Sep 17 00:00:00 2001 From: Marek Date: Fri, 16 Aug 2024 14:28:59 +0200 Subject: [PATCH 12/13] Generic.PHP.LowerCaseKeyword: require lowercase anonymous class keyword --- src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php | 1 + src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc | 4 ++++ .../Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed | 4 ++++ src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php | 2 ++ 4 files changed, 11 insertions(+) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php index 1a36917171..4edcc5d92b 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php @@ -27,6 +27,7 @@ public function register() { $targets = Tokens::$contextSensitiveKeywords; $targets += [ + T_ANON_CLASS => T_ANON_CLASS, T_CLOSURE => T_CLOSURE, T_EMPTY => T_EMPTY, T_ENUM_CASE => T_ENUM_CASE, diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc index 37579d3217..429ddebddf 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc @@ -44,5 +44,9 @@ EnuM ENUM: string Case HEARTS; } +new Class {}; +new clasS extends stdClass {}; +new class {}; + __HALT_COMPILER(); // An exception due to phar support. function diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed index 7063327ae8..2d68e55a2c 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed @@ -44,5 +44,9 @@ enum ENUM: string case HEARTS; } +new class {}; +new class extends stdClass {}; +new class {}; + __HALT_COMPILER(); // An exception due to phar support. function diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php index 17f0e25d3e..c2f8831e47 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php @@ -48,6 +48,8 @@ public function getErrorList() 39 => 2, 42 => 1, 44 => 1, + 47 => 1, + 48 => 1, ]; }//end getErrorList() From 59905dbc335b330ea874650588f598a65d20df9c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 17 Aug 2024 05:36:50 +0200 Subject: [PATCH 13/13] Generic/LowerCaseKeyword: remove some redundant code The `Tokens::$contextSensitiveKeywords` token array was introduced in PHPCS 3.7.0 via PR squizlabs/PHP_CodeSniffer 3484. PR squizlabs/PHP_CodeSniffer 3574 build onto that by removing the bulk of the target tokens from the `register()` method for the Generic/LowerCaseKeyword sniff in favour of using the predefined `Tokens::$contextSensitiveKeywords` token array. The `T_EMPTY`, `T_EVAL`, `T_ISSET` and `T_UNSET` tokens were initially missing from the `Tokens::$contextSensitiveKeywords` array. This was fixed in PHPCS 3.7.1 via PRs squizlabs/PHP_CodeSniffer 3608 and squizlabs/PHP_CodeSniffer 3610. This means those tokens no longer need to be explicitly added as targets for the Generic/LowerCaseKeyword sniff as they are now (and have been since PHPCS 3.7.1) inherited via the `Tokens::$contextSensitiveKeywords` token array. This commit removes the redundancy, but also adds tests to safeguard that those keywords will still be checked by the sniff. --- src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php | 4 ---- src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc | 5 +++++ .../Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed | 5 +++++ src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php | 2 ++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php index 4edcc5d92b..76886471d7 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php @@ -29,14 +29,10 @@ public function register() $targets += [ T_ANON_CLASS => T_ANON_CLASS, T_CLOSURE => T_CLOSURE, - T_EMPTY => T_EMPTY, T_ENUM_CASE => T_ENUM_CASE, - T_EVAL => T_EVAL, - T_ISSET => T_ISSET, T_MATCH_DEFAULT => T_MATCH_DEFAULT, T_PARENT => T_PARENT, T_SELF => T_SELF, - T_UNSET => T_UNSET, ]; return $targets; diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc index 429ddebddf..a50ac22853 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc @@ -48,5 +48,10 @@ new Class {}; new clasS extends stdClass {}; new class {}; +if (isset($a) && !empty($a)) { unset($a); } +if (ISSET($a) && !Empty($a)) { UnSeT($a); } +eval('foo'); +eVaL('foo'); + __HALT_COMPILER(); // An exception due to phar support. function diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed index 2d68e55a2c..5e15765146 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed @@ -48,5 +48,10 @@ new class {}; new class extends stdClass {}; new class {}; +if (isset($a) && !empty($a)) { unset($a); } +if (isset($a) && !empty($a)) { unset($a); } +eval('foo'); +eval('foo'); + __HALT_COMPILER(); // An exception due to phar support. function diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php index c2f8831e47..6d337504f4 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php @@ -50,6 +50,8 @@ public function getErrorList() 44 => 1, 47 => 1, 48 => 1, + 52 => 3, + 54 => 1, ]; }//end getErrorList()