From 61e20af23bd9ed6d76eb63f3aee04994c0904351 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 23 Dec 2023 05:05:49 +0100 Subject: [PATCH 01/25] Squiz/EmbeddedPhp: remove some redundant code The removed condition in the `validateInlineEmbeddedPhp()` method could never be `true` as this is already checked in the `process()` method before this method is ever called. As that means we will always already have determined the valid close tag, we may as well pass it to both underlying methods instead of determining it again. This should also yield a small performance improvement, especially when dealing with long files with only an open tag and no a closing tag, as it saves doing the token walking over the complete file for a second time. Includes moving a comment to a more appropriate place. --- .../Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index 816c5a07bc..ce741987c4 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -46,9 +46,9 @@ public function process(File $phpcsFile, $stackPtr) // then we have an inline embedded PHP block. $closeTag = $phpcsFile->findNext(T_CLOSE_TAG, $stackPtr); if ($closeTag === false || $tokens[$stackPtr]['line'] !== $tokens[$closeTag]['line']) { - $this->validateMultilineEmbeddedPhp($phpcsFile, $stackPtr); + $this->validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closeTag); } else { - $this->validateInlineEmbeddedPhp($phpcsFile, $stackPtr); + $this->validateInlineEmbeddedPhp($phpcsFile, $stackPtr, $closeTag); } }//end process() @@ -57,13 +57,15 @@ public function process(File $phpcsFile, $stackPtr) /** * Validates embedded PHP that exists on multiple lines. * - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token in the - * stack passed in $tokens. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * @param int|false $closingTag The position of the PHP close tag in the + * stack passed in $tokens. * * @return void */ - private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr) + private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag) { $tokens = $phpcsFile->getTokens(); @@ -74,7 +76,7 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr) } $firstContent = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); - $closingTag = $phpcsFile->findNext(T_CLOSE_TAG, $stackPtr); + if ($closingTag !== false) { $nextContent = $phpcsFile->findNext(T_WHITESPACE, ($closingTag + 1), $phpcsFile->numTokens, true); if ($nextContent === false) { @@ -293,21 +295,15 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr) * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position of the current token in the * stack passed in $tokens. + * @param int $closeTag The position of the PHP close tag in the + * stack passed in $tokens. * * @return void */ - private function validateInlineEmbeddedPhp($phpcsFile, $stackPtr) + private function validateInlineEmbeddedPhp($phpcsFile, $stackPtr, $closeTag) { $tokens = $phpcsFile->getTokens(); - // We only want one line PHP sections, so return if the closing tag is - // on the next line. - $closeTag = $phpcsFile->findNext(T_CLOSE_TAG, $stackPtr, null, false); - if ($tokens[$stackPtr]['line'] !== $tokens[$closeTag]['line']) { - return; - } - - // Check that there is one, and only one space at the start of the statement. $firstContent = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), $closeTag, true); if ($firstContent === false) { @@ -325,6 +321,7 @@ private function validateInlineEmbeddedPhp($phpcsFile, $stackPtr) return; } + // Check that there is one, and only one space at the start of the statement. // The open tag token always contains a single space after it. $leadingSpace = 1; if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE) { From 6875b9e05d9470f7c6ef995bae5f6d49372686b1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 27 Dec 2023 10:25:41 +0100 Subject: [PATCH 02/25] Squiz/EmbeddedPhp: rename two confusingly named variables --- .../Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index ce741987c4..b4d15f7e4f 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -78,8 +78,8 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag $firstContent = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); if ($closingTag !== false) { - $nextContent = $phpcsFile->findNext(T_WHITESPACE, ($closingTag + 1), $phpcsFile->numTokens, true); - if ($nextContent === false) { + $firstContentAfterBlock = $phpcsFile->findNext(T_WHITESPACE, ($closingTag + 1), $phpcsFile->numTokens, true); + if ($firstContentAfterBlock === false) { // Final closing tag. It will be handled elsewhere. return; } @@ -171,9 +171,9 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag }//end if }//end if - $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); - if ($tokens[$lastContent]['line'] === $tokens[$stackPtr]['line'] - && trim($tokens[$lastContent]['content']) !== '' + $lastContentBeforeBlock = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if ($tokens[$lastContentBeforeBlock]['line'] === $tokens[$stackPtr]['line'] + && trim($tokens[$lastContentBeforeBlock]['content']) !== '' ) { $error = 'Opening PHP tag must be on a line by itself'; $fix = $phpcsFile->addFixableError($error, $stackPtr, 'ContentBeforeOpen'); @@ -227,8 +227,8 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag return; } - $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($closingTag - 1), ($stackPtr + 1), true); - $nextContent = $phpcsFile->findNext(T_WHITESPACE, ($closingTag + 1), null, true); + $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($closingTag - 1), ($stackPtr + 1), true); + $firstContentAfterBlock = $phpcsFile->findNext(T_WHITESPACE, ($closingTag + 1), null, true); if ($tokens[$lastContent]['line'] === $tokens[$closingTag]['line']) { $error = 'Closing PHP tag must be on a line by itself'; @@ -240,7 +240,7 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag $phpcsFile->fixer->addNewlineBefore($closingTag); $phpcsFile->fixer->endChangeset(); } - } else if ($tokens[$nextContent]['line'] === $tokens[$closingTag]['line']) { + } else if ($tokens[$firstContentAfterBlock]['line'] === $tokens[$closingTag]['line']) { $error = 'Closing PHP tag must be on a line by itself'; $fix = $phpcsFile->addFixableError($error, $closingTag, 'ContentAfterEnd'); if ($fix === true) { From 2fc1ca99e716c96caa8f550a153f07b9ebde5913 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 23 Dec 2023 23:44:49 +0100 Subject: [PATCH 03/25] Squiz/EmbeddedPhp: make test cases more descriptive ... to allow for easier debugging. --- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc | 32 +++++++++---------- .../Tests/PHP/EmbeddedPhpUnitTest.inc.fixed | 32 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc index c61d5a391b..eaad0b0dfc 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc @@ -15,11 +15,11 @@ - - - - - + + + + + @@ -27,18 +27,18 @@ - + - - - + + + - - - + + + - + diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed index 5b43d8493d..fa4d934dc7 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed @@ -15,25 +15,25 @@ - - - - - + + + + + - - - + + + - - - + + + - + From 16f4a3a061e64ffc82a6c6f6334cb40561645722 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 23 Dec 2023 03:30:17 +0100 Subject: [PATCH 04/25] Squiz/EmbeddedPhp: add some extra tests with ignore annotations ... just as an extra safeguard. --- src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc | 2 ++ src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed | 2 ++ src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc index eaad0b0dfc..3a0359ff42 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc @@ -117,3 +117,5 @@ function foo() + + diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed index fa4d934dc7..a345161686 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed @@ -117,3 +117,5 @@ function foo() + + diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php index 8944fc2aee..b437828440 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php @@ -60,6 +60,8 @@ public function getErrorList() 113 => 1, 116 => 1, 117 => 1, + 120 => 1, + 121 => 1, ]; }//end getErrorList() From 38a90c1376f2be85dcc4668e766b3cb04d9bbeef Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 24 Dec 2023 06:37:57 +0100 Subject: [PATCH 05/25] Squiz/EmbeddedPhp: prevent tests being skipped The sniff contains a condition which prevents checks from being run on the last tag set when it is a non-single line tag set. Adding a final open tag will prevent the _last_ test in the file accidentally being skipped, independently of what type of tag set this is, making the test case file more stable. --- src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc | 5 +++++ src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc index 3a0359ff42..711d910ef6 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc @@ -119,3 +119,8 @@ function foo() + + + + Date: Sun, 24 Dec 2023 04:33:43 +0100 Subject: [PATCH 06/25] Squiz/EmbeddedPhp: empty tag set fixer should remove trailing spaces As things were, the "empty embed" fixers would in certain circumstances leave trailing spaces behind. For most codebases, this means the sniff would always need to be run in conjunction with a sniff which removes trailing spaces and that there is always a second fixer round needed: the first to fix the embedded PHP formatting, the second to allow the other sniff to remove the trailing spaces introduced by the fixes made by this sniff. Aside from that, it also makes updating the test case file fiddly. This commit fixes the "empty embed" fixers to remove trailing spaces, when applicable, and consolidates the code for this in a separate method. Note: _surrounding_ HTML whitespace, which is not trailing, will not be affected by this fix as the sniff should have no opinion on that. Covered by pre-existing tests + some additional newly added tests. --- .../Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 63 ++++++++++++------- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc | 25 ++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.inc.fixed | 21 ++++++- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 8 +++ 4 files changed, 94 insertions(+), 23 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index b4d15f7e4f..203be9d567 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -86,17 +86,7 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag // We have an opening and a closing tag, that lie within other content. if ($firstContent === $closingTag) { - $error = 'Empty embedded PHP tag found'; - $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Empty'); - if ($fix === true) { - $phpcsFile->fixer->beginChangeset(); - for ($i = $stackPtr; $i <= $closingTag; $i++) { - $phpcsFile->fixer->replaceToken($i, ''); - } - - $phpcsFile->fixer->endChangeset(); - } - + $this->reportEmptyTagSet($phpcsFile, $stackPtr, $closingTag); return; } }//end if @@ -307,17 +297,7 @@ private function validateInlineEmbeddedPhp($phpcsFile, $stackPtr, $closeTag) $firstContent = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), $closeTag, true); if ($firstContent === false) { - $error = 'Empty embedded PHP tag found'; - $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Empty'); - if ($fix === true) { - $phpcsFile->fixer->beginChangeset(); - for ($i = $stackPtr; $i <= $closeTag; $i++) { - $phpcsFile->fixer->replaceToken($i, ''); - } - - $phpcsFile->fixer->endChangeset(); - } - + $this->reportEmptyTagSet($phpcsFile, $stackPtr, $closeTag); return; } @@ -396,4 +376,43 @@ private function validateInlineEmbeddedPhp($phpcsFile, $stackPtr, $closeTag) }//end validateInlineEmbeddedPhp() + /** + * Report and fix an set of empty PHP tags. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * @param int $closeTag The position of the PHP close tag in the + * stack passed in $tokens. + * + * @return void + */ + private function reportEmptyTagSet(File $phpcsFile, $stackPtr, $closeTag) + { + $tokens = $phpcsFile->getTokens(); + $error = 'Empty embedded PHP tag found'; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Empty'); + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + for ($i = $stackPtr; $i <= $closeTag; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + // Prevent leaving indentation whitespace behind when the empty tag set is the only thing on the affected lines. + if (isset($tokens[($closeTag + 1)]) === true + && $tokens[($closeTag + 1)]['line'] !== $tokens[$closeTag]['line'] + && $tokens[($stackPtr - 1)]['code'] === T_INLINE_HTML + && $tokens[($stackPtr - 1)]['line'] === $tokens[$stackPtr]['line'] + && $tokens[($stackPtr - 1)]['column'] === 1 + && trim($tokens[($stackPtr - 1)]['content']) === '' + ) { + $phpcsFile->fixer->replaceToken(($stackPtr - 1), ''); + } + + $phpcsFile->fixer->endChangeset(); + } + + }//end reportEmptyTagSet() + + }//end class diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc index 711d910ef6..5ca71147fd 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc @@ -120,6 +120,31 @@ function foo() + +
+ + + + + + + +
+ + + + + + + - + @@ -120,6 +120,25 @@ function foo() + +
+ + + + + + +
+ + + + + + 1, 120 => 1, 121 => 1, + 128 => 1, + 129 => 1, + 132 => 1, + 134 => 1, + 136 => 1, + 138 => 1, + 142 => 1, + 145 => 1, ]; }//end getErrorList() From 77ebd281f308bff3217360d9061b826a3584d9f5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 24 Dec 2023 06:30:42 +0100 Subject: [PATCH 07/25] Squiz/EmbeddedPhp: opener on own line fixer should remove trailing spaces As things were, the "opener on own line / content after" fixer would always leave at least one trailing space behind. For most codebases, this means the sniff would always need to be run in conjunction with a sniff which removes trailing spaces and that there is always a second fixer round needed: the first to fix the embedded PHP formatting, the second to allow the other sniff to remove the trailing spaces introduced by the fixes made by this sniff. It also makes updating the test case file fiddly. Secondly, if the space between the open tag and the first content was larger than exactly one space, the sniff would also need an extra fixer round to fix the indent of the "first content", which was moved to the next line and given indent, but with the extra space having being moved as well, the indent would now be too large. This commit fixes the "opener on own line / content after" fixer to remove trailing spaces and prevent the potential extra fixer loop to fix the indent. Covered by pre-existing tests + an additional newly added test. --- src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 7 +++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc | 6 ++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed | 13 ++++++++++--- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 1 + 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index 203be9d567..80a31d0ea6 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -97,9 +97,16 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag if ($fix === true) { $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr, true); $padding = (strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content']))); + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($stackPtr, rtrim($tokens[$stackPtr]['content'])); $phpcsFile->fixer->addNewline($stackPtr); $phpcsFile->fixer->addContent($stackPtr, str_repeat(' ', $padding)); + + if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE) { + $phpcsFile->fixer->replaceToken(($stackPtr + 1), ''); + } + $phpcsFile->fixer->endChangeset(); } } else { diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc index 5ca71147fd..04cc1a3e3b 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc @@ -145,6 +145,12 @@ or indentation if there is code _after_ the removed empty tag. + + + - @@ -51,12 +51,12 @@ function test() foreach ($root->section as $section) { ?> - - + + + 1, 142 => 1, 145 => 1, + 151 => 1, ]; }//end getErrorList() From 9fbbe04f4166496965347e6f184fedcc4d326f20 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 24 Dec 2023 07:24:08 +0100 Subject: [PATCH 08/25] Squiz/EmbeddedPhp: closer on own line fixers should remove trailing spaces As things were, the "closer on own line" fixers would, in most cases, leave trailing whitespace behind. For most codebases, this means the sniff would always need to be run in conjunction with a sniff which removes trailing spaces and that there is always a second fixer round needed: the first to fix the embedded PHP formatting, the second to allow the other sniff to remove the trailing spaces introduced by the fixes made by this sniff. It also makes updating the test case file fiddly. Additionally, when the next token is inline HTML, it could lead to weird/incorrect indents, which would not be fixable via a sniff (as determining intended indent for inline HTML is non-trivial). This commit fixes the "closer on own line" fixers to remove trailing spaces and prevents them from affecting the indent of inline HTML unnecessarily. Covered by pre-existing tests + additional newly added tests. --- .../Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 19 +++++++++++++++- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc | 17 ++++++++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.inc.fixed | 22 ++++++++++++++++++- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 3 +++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index 80a31d0ea6..493895fe3f 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -233,6 +233,11 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag if ($fix === true) { $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $closingTag, true); $phpcsFile->fixer->beginChangeset(); + + if ($tokens[($closingTag - 1)]['code'] === T_WHITESPACE) { + $phpcsFile->fixer->replaceToken(($closingTag - 1), ''); + } + $phpcsFile->fixer->addContentBefore($closingTag, str_repeat(' ', ($tokens[$first]['column'] - 1))); $phpcsFile->fixer->addNewlineBefore($closingTag); $phpcsFile->fixer->endChangeset(); @@ -245,8 +250,20 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag $phpcsFile->fixer->beginChangeset(); $phpcsFile->fixer->addNewline($closingTag); $phpcsFile->fixer->addContent($closingTag, str_repeat(' ', ($tokens[$first]['column'] - 1))); + + if ($tokens[$firstContentAfterBlock]['code'] === T_INLINE_HTML) { + $trimmedHtmlContent = ltrim($tokens[$firstContentAfterBlock]['content']); + if ($trimmedHtmlContent === '') { + // HTML token contains only whitespace and the next token after is PHP, not HTML, so remove the whitespace. + $phpcsFile->fixer->replaceToken($firstContentAfterBlock, ''); + } else { + // The HTML token has content, so remove leading whitespace in favour of the indent. + $phpcsFile->fixer->replaceToken($firstContentAfterBlock, $trimmedHtmlContent); + } + } + $phpcsFile->fixer->endChangeset(); - } + }//end if }//end if $next = $phpcsFile->findNext(T_OPEN_TAG, ($closingTag + 1)); diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc index 04cc1a3e3b..aad1d839af 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc @@ -151,6 +151,23 @@ Make sure the "content after opener" fixer does not leave trailing space behind. + + + + + + + + + + + + + + + + + + 1, 145 => 1, 151 => 1, + 158 => 1, + 165 => 1, + 169 => 1, ]; }//end getErrorList() From bac3be55f13f274ee752b278f70671fb2c3515fc Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 24 Dec 2023 22:21:12 +0100 Subject: [PATCH 09/25] Squiz/EmbeddedPhp: bug fix - fixer adds stray new line When the close tag of a previous multi-line embedded PHP snippet and the open tag of the next multi-line embedded PHP snippet are on the same line, the sniff would add a stray blank line with trailing whitespace between the two tokens due to the fixers both running in the same loop. This commit adds a small tweak preventing this stray blank line from being added. Includes unit tests proving the issue and safeguarding the fix. --- .../Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 7 +++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc | 13 +++++++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.inc.fixed | 16 ++++++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 6 ++++++ 4 files changed, 42 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index 493895fe3f..2baa7f7652 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -262,6 +262,13 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag } } + if ($tokens[$firstContentAfterBlock]['code'] === T_OPEN_TAG) { + // Next token is a PHP open tag which will also have thrown an error. + // Prevent both fixers running in the same loop by making sure the token is "touched" during this loop. + // This prevents a stray new line being added between the close and open tags. + $phpcsFile->fixer->replaceToken($firstContentAfterBlock, $tokens[$firstContentAfterBlock]['content']); + } + $phpcsFile->fixer->endChangeset(); }//end if }//end if diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc index aad1d839af..ddd25d477b 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc @@ -168,6 +168,19 @@ Make sure the "content after closer" fixer does not leave trailing space behind. echo $closerNeedsOwnLine; ?> + + + + + + + + + 1, 165 => 1, 169 => 1, + 175 => 1, + 176 => 2, + 178 => 1, + 179 => 1, + 180 => 2, + 181 => 1, ]; }//end getErrorList() From b166d65aa63a94a58c64995298692f1e6dd0bb09 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 23 Dec 2023 03:26:53 +0100 Subject: [PATCH 10/25] Squiz/EmbeddedPhp: rename test case file ... to allow for additional test case files to be added. --- ...UnitTest.inc => EmbeddedPhpUnitTest.1.inc} | 0 ....fixed => EmbeddedPhpUnitTest.1.inc.fixed} | 0 .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 112 ++++++++++-------- 3 files changed, 60 insertions(+), 52 deletions(-) rename src/Standards/Squiz/Tests/PHP/{EmbeddedPhpUnitTest.inc => EmbeddedPhpUnitTest.1.inc} (100%) rename src/Standards/Squiz/Tests/PHP/{EmbeddedPhpUnitTest.inc.fixed => EmbeddedPhpUnitTest.1.inc.fixed} (100%) diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc similarity index 100% rename from src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc rename to src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed similarity index 100% rename from src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.inc.fixed rename to src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php index edeccd66b2..bd439188d1 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php @@ -26,61 +26,69 @@ final class EmbeddedPhpUnitTest 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 [ - 7 => 1, - 12 => 1, - 18 => 1, - 19 => 2, - 20 => 1, - 21 => 1, - 22 => 3, - 24 => 1, - 26 => 1, - 29 => 1, - 30 => 1, - 31 => 1, - 34 => 1, - 36 => 1, - 40 => 1, - 41 => 1, - 44 => 1, - 45 => 1, - 49 => 1, - 59 => 1, - 63 => 1, - 93 => 1, - 94 => 2, - 100 => 1, - 102 => 1, - 112 => 1, - 113 => 1, - 116 => 1, - 117 => 1, - 120 => 1, - 121 => 1, - 128 => 1, - 129 => 1, - 132 => 1, - 134 => 1, - 136 => 1, - 138 => 1, - 142 => 1, - 145 => 1, - 151 => 1, - 158 => 1, - 165 => 1, - 169 => 1, - 175 => 1, - 176 => 2, - 178 => 1, - 179 => 1, - 180 => 2, - 181 => 1, - ]; + switch ($testFile) { + case 'EmbeddedPhpUnitTest.1.inc': + return [ + 7 => 1, + 12 => 1, + 18 => 1, + 19 => 2, + 20 => 1, + 21 => 1, + 22 => 3, + 24 => 1, + 26 => 1, + 29 => 1, + 30 => 1, + 31 => 1, + 34 => 1, + 36 => 1, + 40 => 1, + 41 => 1, + 44 => 1, + 45 => 1, + 49 => 1, + 59 => 1, + 63 => 1, + 93 => 1, + 94 => 2, + 100 => 1, + 102 => 1, + 112 => 1, + 113 => 1, + 116 => 1, + 117 => 1, + 120 => 1, + 121 => 1, + 128 => 1, + 129 => 1, + 132 => 1, + 134 => 1, + 136 => 1, + 138 => 1, + 142 => 1, + 145 => 1, + 151 => 1, + 158 => 1, + 165 => 1, + 169 => 1, + 175 => 1, + 176 => 2, + 178 => 1, + 179 => 1, + 180 => 2, + 181 => 1, + ]; + + default: + return []; + }//end switch }//end getErrorList() From 1e64373a9243810ee360eb2559eb4f1b9c5978a5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 26 Dec 2023 07:52:36 +0100 Subject: [PATCH 11/25] Squiz/EmbeddedPhp: add tests safeguarding that single line embedded PHP blocks are never ignored The exception conditions for the first/last tag set only apply to multi-line PHP blocks and PHP blocks without a closing tag. Single-line embedded PHP with a complete set of tags should not be affected. This is already handled correctly. This commit just adds tests to safeguard this. --- src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc | 7 +++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc.fixed | 7 +++++++ src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 7 +++++++ 3 files changed, 21 insertions(+) create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc.fixed diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc new file mode 100644 index 0000000000..444ad04389 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc @@ -0,0 +1,7 @@ + + + + diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc.fixed b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc.fixed new file mode 100644 index 0000000000..4e064fa2e6 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc.fixed @@ -0,0 +1,7 @@ + + + + diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php index bd439188d1..7fee9e5b81 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php @@ -86,6 +86,13 @@ public function getErrorList($testFile='') 181 => 1, ]; + case 'EmbeddedPhpUnitTest.2.inc': + return [ + 5 => 2, + 6 => 2, + 7 => 2, + ]; + default: return []; }//end switch From 3172a21337e2fc09fb701bb73b032152d27ba25c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 24 Dec 2023 17:51:31 +0100 Subject: [PATCH 12/25] Squiz/EmbeddedPhp: sniff for short open tags As things were, the `Squiz.PHP.EmbeddedPhp` sniff would only check the formatting for code embedded using long PHP open tags, while short PHP open tags are pretty common for embedded PHP. This commit adds support for examining short PHP open tags to the sniff. Notes: * The error codes used are the same for both type of open tags, with one exception: for single line embedded statements, it is checked that there is a semi-colon at the end of the statement. The "statement" in an embedded snippet using short open tags may just be a single variable and in _some_ lines of thinking it is acceptable to leave out the semi-colon in that case. To accommodate this, the error code for the missing semi-colon differentiates between whether the statement uses long open tags `NoSemicolon` (same as before) or short open tags `ShortOpenEchoNoSemicolon`. This will allows for selectively excluding the `ShortOpenEchoNoSemicolon` error code. * I've considered enforcing that short open tags always are always used as a single-line statement, or adding an option to enforce this, but have decided against this at this time. After all, this sniff is about _consistent_ formatting, single line stays single line and multi-line is made consistently/properly multi-line. It could be considered future scope to add a separate sniff to enforce short open echo tag sets to always be single line when they only contain a single statement. Fixes 27 --- .../Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 42 ++++-- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc | 2 +- .../Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed | 2 +- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc | 2 +- .../Tests/PHP/EmbeddedPhpUnitTest.2.inc.fixed | 2 +- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.3.inc | 123 ++++++++++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.3.inc.fixed | 132 ++++++++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.4.inc | 7 + .../Tests/PHP/EmbeddedPhpUnitTest.4.inc.fixed | 7 + .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.5.inc | 48 +++++++ .../Tests/PHP/EmbeddedPhpUnitTest.5.inc.fixed | 39 ++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 58 ++++++++ 12 files changed, 450 insertions(+), 14 deletions(-) create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.3.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.3.inc.fixed create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.4.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.4.inc.fixed create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.5.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.5.inc.fixed diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index 2baa7f7652..bd33821de0 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -24,7 +24,10 @@ class EmbeddedPhpSniff implements Sniff */ public function register() { - return [T_OPEN_TAG]; + return [ + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ]; }//end register() @@ -69,7 +72,7 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag { $tokens = $phpcsFile->getTokens(); - $prevTag = $phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1)); + $prevTag = $phpcsFile->findPrevious($this->register(), ($stackPtr - 1)); if ($prevTag === false) { // This is the first open tag. return; @@ -262,7 +265,9 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag } } - if ($tokens[$firstContentAfterBlock]['code'] === T_OPEN_TAG) { + if ($tokens[$firstContentAfterBlock]['code'] === T_OPEN_TAG + || $tokens[$firstContentAfterBlock]['code'] === T_OPEN_TAG_WITH_ECHO + ) { // Next token is a PHP open tag which will also have thrown an error. // Prevent both fixers running in the same loop by making sure the token is "touched" during this loop. // This prevents a stray new line being added between the close and open tags. @@ -273,7 +278,7 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag }//end if }//end if - $next = $phpcsFile->findNext(T_OPEN_TAG, ($closingTag + 1)); + $next = $phpcsFile->findNext($this->register(), ($closingTag + 1)); if ($next === false) { return; } @@ -333,10 +338,14 @@ private function validateInlineEmbeddedPhp($phpcsFile, $stackPtr, $closeTag) } // Check that there is one, and only one space at the start of the statement. - // The open tag token always contains a single space after it. - $leadingSpace = 1; + $leadingSpace = 0; + if ($tokens[$stackPtr]['code'] === T_OPEN_TAG) { + // The long open tag token in a single line tag set always contains a single space after it. + $leadingSpace = 1; + } + if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE) { - $leadingSpace = ($tokens[($stackPtr + 1)]['length'] + 1); + $leadingSpace += $tokens[($stackPtr + 1)]['length']; } if ($leadingSpace !== 1) { @@ -344,7 +353,15 @@ private function validateInlineEmbeddedPhp($phpcsFile, $stackPtr, $closeTag) $data = [$leadingSpace]; $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingAfterOpen', $data); if ($fix === true) { - $phpcsFile->fixer->replaceToken(($stackPtr + 1), ''); + if ($tokens[$stackPtr]['code'] === T_OPEN_TAG) { + $phpcsFile->fixer->replaceToken(($stackPtr + 1), ''); + } else if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE) { + // Short open tag with too much whitespace. + $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' '); + } else { + // Short open tag without whitespace. + $phpcsFile->fixer->addContent($stackPtr, ' '); + } } } @@ -357,7 +374,12 @@ private function validateInlineEmbeddedPhp($phpcsFile, $stackPtr, $closeTag) && $tokens[$prev]['code'] !== T_SEMICOLON ) { $error = 'Inline PHP statement must end with a semicolon'; - $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSemicolon'); + $code = 'NoSemicolon'; + if ($tokens[$stackPtr]['code'] === T_OPEN_TAG_WITH_ECHO) { + $code = 'ShortOpenEchoNoSemicolon'; + } + + $fix = $phpcsFile->addFixableError($error, $stackPtr, $code); if ($fix === true) { $phpcsFile->fixer->addContent($prev, ';'); } @@ -374,7 +396,7 @@ private function validateInlineEmbeddedPhp($phpcsFile, $stackPtr, $closeTag) $data = [$statementCount]; $phpcsFile->addError($error, $stackPtr, 'MultipleStatements', $data); } - } + }//end if }//end if $trailingSpace = 0; diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc index ddd25d477b..c28a650b1e 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc @@ -1,6 +1,6 @@ diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed index e6dabf5ccd..4c0a5cd0e4 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed @@ -1,6 +1,6 @@ diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc index 444ad04389..755fd355b5 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc @@ -1,5 +1,5 @@ diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc.fixed b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc.fixed index 4e064fa2e6..e9073da915 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc.fixed +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.2.inc.fixed @@ -1,5 +1,5 @@ diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.3.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.3.inc new file mode 100644 index 0000000000..2d608490a8 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.3.inc @@ -0,0 +1,123 @@ + + + +<?= $title ?> + + + + + hello + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// Safeguard fixing when there is no whitespace between the close tag and the contents. + + + + + + + + + +<?= $title; ?> + + + + + hello + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// Safeguard fixing when there is no whitespace between the close tag and the contents. + + + + + + + + + + + + + diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.4.inc.fixed b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.4.inc.fixed new file mode 100644 index 0000000000..b7985cc508 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.4.inc.fixed @@ -0,0 +1,7 @@ + + + + diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.5.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.5.inc new file mode 100644 index 0000000000..f4d8d8a0c0 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.5.inc @@ -0,0 +1,48 @@ + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + 2, 6 => 2, 7 => 2, ]; + case 'EmbeddedPhpUnitTest.3.inc': + return [ + 10 => 1, + 15 => 1, + 21 => 1, + 22 => 2, + 23 => 1, + 24 => 1, + 25 => 3, + 28 => 1, + 29 => 1, + 30 => 1, + 33 => 1, + 35 => 1, + 39 => 1, + 40 => 1, + 43 => 1, + 44 => 1, + 48 => 1, + 53 => 1, + 55 => 1, + 61 => 1, + 62 => 1, + 65 => 2, + 66 => 2, + 69 => 1, + 70 => 1, + 75 => 1, + 82 => 1, + 89 => 1, + 93 => 1, + 98 => 2, + 99 => 1, + 103 => 2, + 105 => 1, + 111 => 1, + 112 => 2, + 114 => 1, + 115 => 1, + 116 => 2, + 117 => 1, + ]; + + case 'EmbeddedPhpUnitTest.5.inc': + return [ + 16 => 1, + 18 => 1, + 25 => 1, + 26 => 1, + 29 => 1, + 31 => 1, + 33 => 1, + 35 => 1, + 39 => 1, + 42 => 1, + ]; + default: return []; }//end switch From 36e1a8cb795ad98436a6a273d3d423f9649d1e2c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 27 Dec 2023 00:25:30 +0100 Subject: [PATCH 13/25] Squiz/EmbeddedPhp: add dedicated tests related to ignoring of first open tag --- src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.6.inc | 8 ++++++++ src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.7.inc | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.6.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.7.inc diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.6.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.6.inc new file mode 100644 index 0000000000..72862b2433 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.6.inc @@ -0,0 +1,8 @@ + + + + + Date: Wed, 27 Dec 2023 00:48:28 +0100 Subject: [PATCH 14/25] Squiz/EmbeddedPhp: efficiency fix - bow out early if there is nothing to do No need to go through the rest of the logic for the multi-line PHP block handling if there is nothing to do for the sniff. --- .../Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 4 ++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.10.inc | 16 ++++++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.11.inc | 14 ++++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.8.inc | 10 ++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.9.inc | 10 ++++++++++ 5 files changed, 54 insertions(+) create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.10.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.11.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.8.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.9.inc diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index bd33821de0..9d9eb598ea 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -79,6 +79,10 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag } $firstContent = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if ($firstContent === false) { + // Unclosed PHP open tag at the end of a file. Nothing to do. + return; + } if ($closingTag !== false) { $firstContentAfterBlock = $phpcsFile->findNext(T_WHITESPACE, ($closingTag + 1), $phpcsFile->numTokens, true); diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.10.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.10.inc new file mode 100644 index 0000000000..4a00156fae --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.10.inc @@ -0,0 +1,16 @@ + + + + + + + + + Date: Wed, 27 Dec 2023 01:24:23 +0100 Subject: [PATCH 15/25] Squiz/EmbeddedPhp: document behaviour for unclosed tag block with content at end of file --- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.12.inc | 12 ++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.12.inc.fixed | 10 ++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.13.inc | 12 ++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.13.inc.fixed | 10 ++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 7 +++++++ 5 files changed, 51 insertions(+) create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.12.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.12.inc.fixed create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.13.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.13.inc.fixed diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.12.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.12.inc new file mode 100644 index 0000000000..7631141347 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.12.inc @@ -0,0 +1,12 @@ + + + + + + + + + 1, ]; + case 'EmbeddedPhpUnitTest.12.inc': + case 'EmbeddedPhpUnitTest.13.inc': + return [ + 10 => 1, + 12 => 1, + ]; + default: return []; }//end switch From f2c3f47bec75bd239e67b956ff418e774a930252 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 27 Dec 2023 01:16:20 +0100 Subject: [PATCH 16/25] Squiz/EmbeddedPhp: document the behaviour for closed tag block at end of file [1] As things are, the last tag block in a file, at the end of a file, will always be ignored if it contains a PHP close tag, even when it would otherwise trigger errors. These tests document this behaviour. --- src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.14.inc | 8 ++++++++ src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.15.inc | 9 +++++++++ src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.16.inc | 8 ++++++++ src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.17.inc | 8 ++++++++ 4 files changed, 33 insertions(+) create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.14.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.15.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.16.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.17.inc diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.14.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.14.inc new file mode 100644 index 0000000000..aa17e34950 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.14.inc @@ -0,0 +1,8 @@ + + + diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.15.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.15.inc new file mode 100644 index 0000000000..d8f0512773 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.15.inc @@ -0,0 +1,9 @@ + + + + diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.16.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.16.inc new file mode 100644 index 0000000000..754c241b8e --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.16.inc @@ -0,0 +1,8 @@ + + + diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.17.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.17.inc new file mode 100644 index 0000000000..71de17bfb4 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.17.inc @@ -0,0 +1,8 @@ + + + From 1016d402eb2e2dc69401831529b5c57dada75ba6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 27 Dec 2023 01:55:32 +0100 Subject: [PATCH 17/25] Squiz/EmbeddedPhp: document the behaviour for closed tag block at end of file [2] The last tag block in a file will **not** be ignored if it is followed by non-empty inline HTML. These tests document this behaviour. --- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.18.inc | 15 +++++++++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.18.inc.fixed | 13 +++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.19.inc | 17 +++++++++++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.19.inc.fixed | 15 +++++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.20.inc | 15 +++++++++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.20.inc.fixed | 16 ++++++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc | 15 +++++++++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.21.inc.fixed | 16 ++++++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 10 ++++++++++ 9 files changed, 132 insertions(+) create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.18.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.18.inc.fixed create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.19.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.19.inc.fixed create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.20.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.20.inc.fixed create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc.fixed diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.18.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.18.inc new file mode 100644 index 0000000000..3d61d4859d --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.18.inc @@ -0,0 +1,15 @@ + + +
+ + +
+

Some more content after the last PHP tag block.

diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.18.inc.fixed b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.18.inc.fixed new file mode 100644 index 0000000000..8c906d40c7 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.18.inc.fixed @@ -0,0 +1,13 @@ + + +
+ +
+

Some more content after the last PHP tag block.

diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.19.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.19.inc new file mode 100644 index 0000000000..34db64d605 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.19.inc @@ -0,0 +1,17 @@ + + +
+ + +
+

Some more content after the last PHP tag block.

diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.19.inc.fixed b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.19.inc.fixed new file mode 100644 index 0000000000..b1738db57f --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.19.inc.fixed @@ -0,0 +1,15 @@ + + +
+ +
+

Some more content after the last PHP tag block.

diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.20.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.20.inc new file mode 100644 index 0000000000..28cdbdaf7a --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.20.inc @@ -0,0 +1,15 @@ + + +
+ + +
+

Some more content after the last PHP tag block.

diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.20.inc.fixed b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.20.inc.fixed new file mode 100644 index 0000000000..44942e31fc --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.20.inc.fixed @@ -0,0 +1,16 @@ + + +
+ + +
+

Some more content after the last PHP tag block.

diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc new file mode 100644 index 0000000000..1da67a2ea7 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc @@ -0,0 +1,15 @@ + + +
+ + +
+

Some more content after the last PHP tag block.

diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc.fixed b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc.fixed new file mode 100644 index 0000000000..aa3951edc0 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc.fixed @@ -0,0 +1,16 @@ + + +
+ + +
+

Some more content after the last PHP tag block.

diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php index 37cf55a255..d732bdc135 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php @@ -158,6 +158,16 @@ public function getErrorList($testFile='') 12 => 1, ]; + case 'EmbeddedPhpUnitTest.18.inc': + return [11 => 1]; + + case 'EmbeddedPhpUnitTest.19.inc': + return [13 => 1]; + + case 'EmbeddedPhpUnitTest.20.inc': + case 'EmbeddedPhpUnitTest.21.inc': + return [12 => 2]; + default: return []; }//end switch From e9e2011cf8a63c8689d728ee7090649f3622e63f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 27 Dec 2023 02:24:13 +0100 Subject: [PATCH 18/25] Squiz/EmbeddedPhp: bug fix - closer on own line indent is not calculated correctly When the PHP close tag is on the same line as the first PHP content in a multi-line embedded PHP block and the indent of the first PHP content in the block is incorrect, the indent for the close tag when it is moved to its own line will be based on the (incorrect) indent of the first PHP content line and not on the corrected indent for the first PHP content, which the indent fixer uses. This commit fixes this. Covered by pre-existing tests and adjusting the "fixed" expectations for those. Additionally, an extra test has been added for the case when `$indent` is not pre-calculated (= when the first content in the block is a scope closer). --- .../Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 18 +++++++++++++++--- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc | 7 +++++++ .../Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed | 8 ++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.20.inc.fixed | 2 +- .../Tests/PHP/EmbeddedPhpUnitTest.21.inc.fixed | 2 +- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 1 + 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index 9d9eb598ea..d51ec9559d 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -238,17 +238,29 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag $error = 'Closing PHP tag must be on a line by itself'; $fix = $phpcsFile->addFixableError($error, $closingTag, 'ContentBeforeEnd'); if ($fix === true) { - $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $closingTag, true); + // Calculate the indent for the close tag. + // If the close tag is on the same line as the first content, re-use the indent + // calculated for the first content line to prevent the indent being based on an + // "old" indent, not the _new_ (fixed) indent. + if ($tokens[$firstContent]['line'] === $tokens[$lastContent]['line'] + && isset($indent) === true + ) { + $closerIndent = $indent; + } else { + $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $closingTag, true); + $closerIndent = ($tokens[$first]['column'] - 1); + } + $phpcsFile->fixer->beginChangeset(); if ($tokens[($closingTag - 1)]['code'] === T_WHITESPACE) { $phpcsFile->fixer->replaceToken(($closingTag - 1), ''); } - $phpcsFile->fixer->addContentBefore($closingTag, str_repeat(' ', ($tokens[$first]['column'] - 1))); + $phpcsFile->fixer->addContentBefore($closingTag, str_repeat(' ', $closerIndent)); $phpcsFile->fixer->addNewlineBefore($closingTag); $phpcsFile->fixer->endChangeset(); - } + }//end if } else if ($tokens[$firstContentAfterBlock]['line'] === $tokens[$closingTag]['line']) { $error = 'Closing PHP tag must be on a line by itself'; $fix = $phpcsFile->addFixableError($error, $closingTag, 'ContentAfterEnd'); diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc index c28a650b1e..9603641908 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc @@ -181,6 +181,13 @@ Make sure the fixer does not add stray new lines when there are consecutive PHP echo 'embedded'; ?> + + + + + + + + +?>

Some more content after the last PHP tag block.

diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc.fixed b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc.fixed index aa3951edc0..d7487b507d 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc.fixed +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.21.inc.fixed @@ -10,7 +10,7 @@ as long as it is followed by non-empty inline HTML.
+?>

Some more content after the last PHP tag block.

diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php index d732bdc135..f51b6f7e59 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php @@ -84,6 +84,7 @@ public function getErrorList($testFile='') 179 => 1, 180 => 2, 181 => 1, + 189 => 1, ]; case 'EmbeddedPhpUnitTest.2.inc': From efb9e441d726bdf0c7a6981282b877bdd5911fd8 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 27 Dec 2023 03:46:45 +0100 Subject: [PATCH 19/25] Squiz/EmbeddedPhp: add test safeguarding handling of blank lines and scope closers --- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc | 11 +++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc index 9603641908..0938f70242 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc @@ -188,6 +188,17 @@ Safeguard closing tag indent calculation for when the last content on the close + + +
+ + + + + + + Date: Wed, 27 Dec 2023 04:55:59 +0100 Subject: [PATCH 20/25] Squiz/EmbeddedPhp: bug fix - content intended indent calculation The intended content indent calculation contained a pretty nasty bug, though it is unlikely to have been hit much in real code bases. What was happening: * The sniff would try to find the first whitespace token on the line containing the stack pointer. If found, the size of the whitespace was taken as the required indent. This part was working correctly. * If no whitespace token was found, the sniff would try and find the first inline HTML token. If an inline HTML token was found, the leading whitespace in the inline HTML token was taken as the indent. This, again, is correct. * However, now it gets iffy... what if no whitespace or inline HTML token was found on the line containing the open tag ? I.e. the situation where the open tag _is_ the first token on the line. This situation was not accounted for by the sniff. The `File::findFirstOnLine()` method would return `false` in that case, which means that the indent calculation would end up like this: ```php $indent = (strlen($tokens[false]['content']) - strlen(ltrim($tokens[false]['content']))); ``` Now, `false` is not a valid array key, so this would end up being juggled to `0`, meaning the length of the leading whitespace for the first token in the **file** was being used. Fixed now. Includes various additional unit tests safeguarding the fix and one (in a separate file) demonstrating the situation where the bug would have had a real life impact. --- .../Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 9 +++-- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc | 29 ++++++++++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed | 33 +++++++++++++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.22.inc | 20 +++++++++++ .../PHP/EmbeddedPhpUnitTest.22.inc.fixed | 20 +++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 11 +++++++ 6 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.22.inc create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.22.inc.fixed diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index d51ec9559d..c88864f360 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -147,10 +147,13 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag } }//end if - $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr); + $indent = 0; + $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr); if ($first === false) { - $first = $phpcsFile->findFirstOnLine(T_INLINE_HTML, $stackPtr); - $indent = (strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content']))); + $first = $phpcsFile->findFirstOnLine(T_INLINE_HTML, $stackPtr); + if ($first !== false) { + $indent = (strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content']))); + } } else { $indent = ($tokens[($first + 1)]['column'] - 1); } diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc index 0938f70242..ddf0c6c918 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc @@ -199,6 +199,35 @@ Safeguard that blank lines between a PHP tag and a scope closer are not touched } ?> + + + + +
+ + + + +
+ + + + + +
+ + + + + + +
+ + + Inline HTML with indent to demonstrate the bug in the indent calculation.
+ + + + + +Inline HTML with indent to demonstrate the bug in the indent calculation.
+ + + + + + 2, 181 => 1, 189 => 1, + 212 => 1, + 214 => 2, + 219 => 1, + 223 => 1, + 225 => 1, + 226 => 1, + 227 => 2, + 228 => 1, ]; case 'EmbeddedPhpUnitTest.2.inc': @@ -169,6 +177,9 @@ public function getErrorList($testFile='') case 'EmbeddedPhpUnitTest.21.inc': return [12 => 2]; + case 'EmbeddedPhpUnitTest.22.inc': + return [14 => 1]; + default: return []; }//end switch From d1c63af4ef181eb491a282d724c2698e5d270bfb Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 27 Dec 2023 05:29:20 +0100 Subject: [PATCH 21/25] Squiz/EmbeddedPhp: bug fix - open tag on own line intended indent calculation Basically, this is the same bug as fixed in a previous commit for the intended content indent calculation. The indentation of the first token in the file would be used to calculate the intended indentation, instead of the indentation for the first token on the line containing the PHP open tag. Fixed now, includes test. Note: The only reason the test doesn't actually show the bug is because of the earlier "fixer adds stray new line" bugfix in this PR, which means that the fixer for the previous close tag will prevent the buggy fixer from running. Without _that_ fix, this bug would be visible when running with `-vvv` (indent would get set to 32 and then corrected to 4 on the next fixer round via the `OpenTagIndent` fixer). --- src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 9 ++++++--- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.22.inc | 10 ++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.22.inc.fixed | 11 +++++++++++ src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 5 ++++- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index c88864f360..99ff3968db 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -185,10 +185,13 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag $error = 'Opening PHP tag must be on a line by itself'; $fix = $phpcsFile->addFixableError($error, $stackPtr, 'ContentBeforeOpen'); if ($fix === true) { - $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr); + $padding = 0; + $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr); if ($first === false) { - $first = $phpcsFile->findFirstOnLine(T_INLINE_HTML, $stackPtr); - $padding = (strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content']))); + $first = $phpcsFile->findFirstOnLine(T_INLINE_HTML, $stackPtr); + if ($first !== false) { + $padding = (strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content']))); + } } else { $padding = ($tokens[($first + 1)]['column'] - 1); } diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.22.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.22.inc index 8d08da0313..5196a3b45b 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.22.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.22.inc @@ -14,6 +14,16 @@ echo 'indent is correct - first on line for open tag is open tag'; echo 'indent is incorrect - first on line for open tag is inline HTML'; ?> + + + + + + + 2]; case 'EmbeddedPhpUnitTest.22.inc': - return [14 => 1]; + return [ + 14 => 1, + 22 => 2, + ]; default: return []; From b4c16825d2454ed5d832d8de22ce7c9c4b85bc0f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 27 Dec 2023 07:57:56 +0100 Subject: [PATCH 22/25] Squiz/EmbeddedPhp: bug fix - close tag intended indent calculation Yet another indentation calculation bug. If the close tag is not on its own line, but is on a line containing a subsequent line of a star-slash style comment, the indentation would not be calculated correctly as the comment token will include the indentation. Fixed now, includes test. --- .../Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 11 ++++++++++- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc | 12 ++++++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed | 14 ++++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 2 ++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index 99ff3968db..99e9e94e98 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -253,7 +253,16 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag ) { $closerIndent = $indent; } else { - $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $closingTag, true); + $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $closingTag, true); + + while ($tokens[$first]['code'] === T_COMMENT + && $tokens[$first]['content'] !== ltrim($tokens[$first]['content']) + ) { + // This is a subsequent line in a star-slash comment containing leading indent. + // We'll need the first line of the comment to correctly determine the indent. + $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, ($first - 1), true); + } + $closerIndent = ($tokens[$first]['column'] - 1); } diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc index ddf0c6c918..f2be4f7bce 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc @@ -228,6 +228,18 @@ echo 'indent is correct - first on line for open tag is open tag'; echo 'indent is incorrect - first on line for open tag is whitespace'; ?> + + + + + + + + + + 1, 227 => 2, 228 => 1, + 235 => 1, + 241 => 1, ]; case 'EmbeddedPhpUnitTest.2.inc': From 46f710263ddb7cd7e857be79b5216aa71fefb196 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 27 Dec 2023 08:08:46 +0100 Subject: [PATCH 23/25] Squiz/EmbeddedPhp: bug fix - open tag intended indent calculation Basically, this is the same bug as fixed in a previous commit for the intended close tag indent calculation. If the first token on the line before the open tag would be a subsequent line for a star-slash comment, the indentation would not be calculated correctly, but would always be set to `0`, even when there was indentation. Fixed now, includes test. --- .../Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 10 ++++++- .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc | 25 ++++++++++++++++ .../Tests/PHP/EmbeddedPhpUnitTest.1.inc.fixed | 29 +++++++++++++++++++ .../Squiz/Tests/PHP/EmbeddedPhpUnitTest.php | 5 ++++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index 99e9e94e98..be75265917 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -200,11 +200,19 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag } } else { // Find the first token on the first non-empty line we find. - for ($first = ($stackPtr - 1); $first > 0; $first--) { + for ($first = ($lastContentBeforeBlock - 1); $first > 0; $first--) { if ($tokens[$first]['line'] === $tokens[$stackPtr]['line']) { continue; } else if (trim($tokens[$first]['content']) !== '') { $first = $phpcsFile->findFirstOnLine([], $first, true); + if ($tokens[$first]['code'] === T_COMMENT + && $tokens[$first]['content'] !== ltrim($tokens[$first]['content']) + ) { + // This is a subsequent line in a star-slash comment containing leading indent. + // We'll need the first line of the comment to correctly determine the indent. + continue; + } + break; } } diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc index f2be4f7bce..f28c559894 100644 --- a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.1.inc @@ -240,6 +240,31 @@ Safeguard correct close tag indent calculation. This means the calculated indent would be 0 unless we walk to the first line in the comment. */ ?> + + + + + + + + + + + + + + 1, 235 => 1, 241 => 1, + 248 => 1, + 253 => 1, + 258 => 1, + 263 => 1, + 264 => 1, ]; case 'EmbeddedPhpUnitTest.2.inc': From ac5fd07fa2db2335fd739e19c34e39ad923bc758 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 27 Dec 2023 10:54:43 +0100 Subject: [PATCH 24/25] Squiz/EmbeddedPhp: add some extra defensive coding `$firstContentAfterBlock` should not be able to be `false` at this point as it is already checked and handled at the top of the function, but better to be safe than sorry. --- src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php index be75265917..0be6118e43 100644 --- a/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php @@ -284,7 +284,9 @@ private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr, $closingTag $phpcsFile->fixer->addNewlineBefore($closingTag); $phpcsFile->fixer->endChangeset(); }//end if - } else if ($tokens[$firstContentAfterBlock]['line'] === $tokens[$closingTag]['line']) { + } else if ($firstContentAfterBlock !== false + && $tokens[$firstContentAfterBlock]['line'] === $tokens[$closingTag]['line'] + ) { $error = 'Closing PHP tag must be on a line by itself'; $fix = $phpcsFile->addFixableError($error, $closingTag, 'ContentAfterEnd'); if ($fix === true) { From 50ae189f4bf6375e3fc5d67198d55d3b9639b464 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 27 Dec 2023 11:20:21 +0100 Subject: [PATCH 25/25] Squiz/EmbeddedPhp: document the behaviour for closed tag block at end of file [3] While the last tag block in a file will **not** be ignored if it is followed by non-empty inline HTML, the "blank lines before the close tag" check _will_ be ignored if this is the last embedded PHP block. This test documents this behaviour. --- .../Tests/PHP/EmbeddedPhpUnitTest.23.inc | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.23.inc diff --git a/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.23.inc b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.23.inc new file mode 100644 index 0000000000..6642ef6b63 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/EmbeddedPhpUnitTest.23.inc @@ -0,0 +1,23 @@ + + +
+ + +
+

Some more content after the last PHP tag block.