From 72cb4f923d15a8dd4e4bc7230c7b72ebc43a67b1 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Fri, 18 Jun 2021 15:49:58 -0400 Subject: [PATCH] Allow parsing mixed HTML and PHP (#234) * Add test for mixed html and php * Consider html and closing tags to be empty tokens * Remove explicit return type to support PHP 5.6 * Add additional test for sniff codes for closing tags fixture --- .../ClosingPhpTagsTest.php | 31 +++++++++++++ .../fixtures/ClosingPhpTagsFixture.php | 18 ++++++++ VariableAnalysis/Lib/Helpers.php | 45 ++++++++++++------- 3 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 Tests/VariableAnalysisSniff/ClosingPhpTagsTest.php create mode 100644 Tests/VariableAnalysisSniff/fixtures/ClosingPhpTagsFixture.php diff --git a/Tests/VariableAnalysisSniff/ClosingPhpTagsTest.php b/Tests/VariableAnalysisSniff/ClosingPhpTagsTest.php new file mode 100644 index 00000000..e7856fbd --- /dev/null +++ b/Tests/VariableAnalysisSniff/ClosingPhpTagsTest.php @@ -0,0 +1,31 @@ +getFixture('ClosingPhpTagsFixture.php'); + $phpcsFile = $this->prepareLocalFileForSniffs($fixtureFile); + $phpcsFile->process(); + $lines = $this->getWarningLineNumbersFromFile($phpcsFile); + $expectedWarnings = [ + 6, + 8, + 13, + 16, + ]; + $this->assertEquals($expectedWarnings, $lines); + } + + public function testVariableWarningsHaveCorrectSniffCodesWhenClosingTagsAreUsed() { + $fixtureFile = $this->getFixture('ClosingPhpTagsFixture.php'); + $phpcsFile = $this->prepareLocalFileForSniffs($fixtureFile); + $phpcsFile->process(); + $warnings = $phpcsFile->getWarnings(); + $this->assertEquals('VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable', $warnings[6][1][0]['source']); + $this->assertEquals('VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable', $warnings[8][6][0]['source']); + $this->assertEquals('VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable', $warnings[13][1][0]['source']); + $this->assertEquals('VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable', $warnings[16][6][0]['source']); + } +} diff --git a/Tests/VariableAnalysisSniff/fixtures/ClosingPhpTagsFixture.php b/Tests/VariableAnalysisSniff/fixtures/ClosingPhpTagsFixture.php new file mode 100644 index 00000000..487964bd --- /dev/null +++ b/Tests/VariableAnalysisSniff/fixtures/ClosingPhpTagsFixture.php @@ -0,0 +1,18 @@ + +

Page Title

+ +

More stuff

+ + diff --git a/VariableAnalysis/Lib/Helpers.php b/VariableAnalysis/Lib/Helpers.php index b9ffb1b6..d9dab91e 100644 --- a/VariableAnalysis/Lib/Helpers.php +++ b/VariableAnalysis/Lib/Helpers.php @@ -9,6 +9,19 @@ use PHP_CodeSniffer\Util\Tokens; class Helpers { + /** + * return int[] + */ + public static function getEmptyTokens() { + return array_merge( + array_values(Tokens::$emptyTokens), + [ + T_INLINE_HTML, + T_CLOSE_TAG, + ] + ); + } + /** * @param int|bool $value * @@ -146,7 +159,7 @@ public static function getFunctionIndexForFunctionArgument(File $phpcsFile, $sta return null; } - $nonFunctionTokenTypes = array_values(Tokens::$emptyTokens); + $nonFunctionTokenTypes = self::getEmptyTokens(); $nonFunctionTokenTypes[] = T_STRING; $nonFunctionTokenTypes[] = T_BITWISE_AND; $functionPtr = self::getIntOrNull($phpcsFile->findPrevious($nonFunctionTokenTypes, $startOfArguments - 1, null, true, null, true)); @@ -186,7 +199,7 @@ public static function isTokenInsideFunctionUseImport(File $phpcsFile, $stackPtr public static function getUseIndexForUseImport(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $nonUseTokenTypes = array_values(Tokens::$emptyTokens); + $nonUseTokenTypes = self::getEmptyTokens(); $nonUseTokenTypes[] = T_VARIABLE; $nonUseTokenTypes[] = T_ELLIPSIS; $nonUseTokenTypes[] = T_COMMA; @@ -215,7 +228,7 @@ public static function findFunctionCall(File $phpcsFile, $stackPtr) { $openPtr = Helpers::findContainingOpeningBracket($phpcsFile, $stackPtr); if (is_int($openPtr)) { // First non-whitespace thing and see if it's a T_STRING function name - $functionPtr = $phpcsFile->findPrevious(Tokens::$emptyTokens, $openPtr - 1, null, true, null, true); + $functionPtr = $phpcsFile->findPrevious(self::getEmptyTokens(), $openPtr - 1, null, true, null, true); if (is_int($functionPtr) && $tokens[$functionPtr]['code'] === T_STRING) { return $functionPtr; } @@ -242,7 +255,7 @@ public static function findFunctionCallArguments(File $phpcsFile, $stackPtr) { } // $stackPtr is the function name, find our brackets after it - $openPtr = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true); + $openPtr = $phpcsFile->findNext(self::getEmptyTokens(), $stackPtr + 1, null, true, null, true); if (($openPtr === false) || ($tokens[$openPtr]['code'] !== T_OPEN_PARENTHESIS)) { return []; } @@ -280,7 +293,7 @@ public static function getNextAssignPointer(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); // Is the next non-whitespace an assignment? - $nextPtr = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true); + $nextPtr = $phpcsFile->findNext(self::getEmptyTokens(), $stackPtr + 1, null, true, null, true); if (is_int($nextPtr) && isset(Tokens::$assignmentTokens[$tokens[$nextPtr]['code']]) // Ignore double arrow to prevent triggering on `foreach ( $array as $k => $v )`. @@ -508,14 +521,14 @@ public static function isArrowFunction(File $phpcsFile, $stackPtr) { return false; } // Make sure next non-space token is an open parenthesis - $openParenIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true); + $openParenIndex = $phpcsFile->findNext(self::getEmptyTokens(), $stackPtr + 1, null, true); if (! is_int($openParenIndex) || $tokens[$openParenIndex]['code'] !== T_OPEN_PARENTHESIS) { return false; } // Find the associated close parenthesis $closeParenIndex = $tokens[$openParenIndex]['parenthesis_closer']; // Make sure the next token is a fat arrow - $fatArrowIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $closeParenIndex + 1, null, true); + $fatArrowIndex = $phpcsFile->findNext(self::getEmptyTokens(), $closeParenIndex + 1, null, true); if (! is_int($fatArrowIndex)) { return false; } @@ -543,14 +556,14 @@ public static function getArrowFunctionOpenClose(File $phpcsFile, $stackPtr) { return null; } // Make sure next non-space token is an open parenthesis - $openParenIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true); + $openParenIndex = $phpcsFile->findNext(self::getEmptyTokens(), $stackPtr + 1, null, true); if (! is_int($openParenIndex) || $tokens[$openParenIndex]['code'] !== T_OPEN_PARENTHESIS) { return null; } // Find the associated close parenthesis $closeParenIndex = $tokens[$openParenIndex]['parenthesis_closer']; // Make sure the next token is a fat arrow - $fatArrowIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $closeParenIndex + 1, null, true); + $fatArrowIndex = $phpcsFile->findNext(self::getEmptyTokens(), $closeParenIndex + 1, null, true); if (! is_int($fatArrowIndex)) { return null; } @@ -600,7 +613,7 @@ public static function getListAssignments(File $phpcsFile, $listOpenerIndex) { } // Find the assignment (equals sign) which, if this is a list assignment, should be the next non-space token - $assignPtr = $phpcsFile->findNext(Tokens::$emptyTokens, $closePtr + 1, null, true); + $assignPtr = $phpcsFile->findNext(self::getEmptyTokens(), $closePtr + 1, null, true); // If the next token isn't an assignment, check for nested brackets because we might be a nested assignment if (! is_int($assignPtr) || $tokens[$assignPtr]['code'] !== T_EQUAL) { @@ -715,7 +728,7 @@ public static function isVariableANumericVariable($varName) { */ public static function isVariableInsideElseCondition(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $nonFunctionTokenTypes = array_values(Tokens::$emptyTokens); + $nonFunctionTokenTypes = self::getEmptyTokens(); $nonFunctionTokenTypes[] = T_OPEN_PARENTHESIS; $nonFunctionTokenTypes[] = T_VARIABLE; $nonFunctionTokenTypes[] = T_ELLIPSIS; @@ -837,7 +850,7 @@ public static function getScopeCloseForScopeOpen(File $phpcsFile, $scopeStartInd public static function getLastNonEmptyTokenIndexInFile(File $phpcsFile) { $tokens = $phpcsFile->getTokens(); foreach (array_reverse($tokens, true) as $index => $token) { - if (! in_array($token['code'], Tokens::$emptyTokens, true)) { + if (! in_array($token['code'], self::getEmptyTokens(), true)) { return $index; } } @@ -921,7 +934,7 @@ public static function getFunctionIndexForFunctionCallArgument(File $phpcsFile, return null; } - $nonFunctionTokenTypes = array_values(Tokens::$emptyTokens); + $nonFunctionTokenTypes = self::getEmptyTokens(); $functionPtr = self::getIntOrNull($phpcsFile->findPrevious($nonFunctionTokenTypes, $startOfArguments - 1, null, true, null, true)); if (! is_int($functionPtr) || ! isset($tokens[$functionPtr]['code'])) { return null; @@ -965,7 +978,7 @@ public static function isVariableInsideIssetOrEmpty(File $phpcsFile, $stackPtr) */ public static function isVariableArrayPushShortcut(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $nonFunctionTokenTypes = array_values(Tokens::$emptyTokens); + $nonFunctionTokenTypes = self::getEmptyTokens(); $arrayPushOperatorIndex1 = self::getIntOrNull($phpcsFile->findNext($nonFunctionTokenTypes, $stackPtr + 1, null, true, null, true)); if (! is_int($arrayPushOperatorIndex1)) { @@ -1063,7 +1076,7 @@ public static function isTokenInsideAssignmentLHS(File $phpcsFile, $stackPtr) { public static function isTokenVariableVariable(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + $prev = $phpcsFile->findPrevious(self::getEmptyTokens(), ($stackPtr - 1), null, true); if ($prev === false) { return false; } @@ -1074,7 +1087,7 @@ public static function isTokenVariableVariable(File $phpcsFile, $stackPtr) { return false; } - $prevPrev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true); + $prevPrev = $phpcsFile->findPrevious(self::getEmptyTokens(), ($prev - 1), null, true); if ($prevPrev !== false && $tokens[$prevPrev]['code'] === T_DOLLAR) { return true; }