From 5a989395a4318475590c6515512f17e2b8e4df8f Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 19 Apr 2018 11:59:29 +1000 Subject: [PATCH] Added new Generic.PHP.LowerCaseType sniff to ensure PHP types used for type hints, return types, and type casting are lowercase --- package.xml | 7 + .../Docs/PHP/LowerCaseTypeStandard.xml | 38 +++++ .../Generic/Sniffs/PHP/LowerCaseTypeSniff.php | 148 ++++++++++++++++++ .../Tests/PHP/LowerCaseTypeUnitTest.inc | 29 ++++ .../Tests/PHP/LowerCaseTypeUnitTest.inc.fixed | 29 ++++ .../Tests/PHP/LowerCaseTypeUnitTest.php | 60 +++++++ 6 files changed, 311 insertions(+) create mode 100644 src/Standards/Generic/Docs/PHP/LowerCaseTypeStandard.xml create mode 100644 src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php create mode 100644 src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc create mode 100644 src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed create mode 100644 src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php diff --git a/package.xml b/package.xml index 730f946467..10472737f7 100644 --- a/package.xml +++ b/package.xml @@ -99,6 +99,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Functionality of the Squiz standard remains the same, but the error codes are now different -- Previously, Squiz.PHP.ForbiddenFunctions.Found and Squiz.PHP.ForbiddenFunctions.FoundWithAlternative -- Now, Generic.PHP.ForbiddenFunctions.Found and Generic.PHP.ForbiddenFunctions.FoundWithAlternative + - Added new Generic.PHP.LowerCaseType sniff + -- Ensures PHP types used for type hints, return types, and type casting are lowercase - Added new Generic.WhiteSpace.ArbitraryParenthesesSpacing sniff -- Generates an error for whitespace inside parenthesis that don't belong to a function call/declaration or control structure -- Generates a warning for any empty parenthesis found @@ -391,6 +393,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + @@ -489,6 +492,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + @@ -705,6 +709,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + diff --git a/src/Standards/Generic/Docs/PHP/LowerCaseTypeStandard.xml b/src/Standards/Generic/Docs/PHP/LowerCaseTypeStandard.xml new file mode 100644 index 0000000000..61c8d5396f --- /dev/null +++ b/src/Standards/Generic/Docs/PHP/LowerCaseTypeStandard.xml @@ -0,0 +1,38 @@ + + + + + + + + + + Int $foo) : STRING { +} + ]]> + + + + + + + + + + + (BOOL) $isValid; + ]]> + + + diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php new file mode 100644 index 0000000000..feaa6dd183 --- /dev/null +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php @@ -0,0 +1,148 @@ + + * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Sniffs\PHP; + +use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; + +class LowerCaseTypeSniff implements Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + $tokens = Tokens::$castTokens; + $tokens[] = T_FUNCTION; + $tokens[] = T_CLOSURE; + return $tokens; + + }//end register() + + + /** + * Processes this sniff, when one of its tokens is encountered. + * + * @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. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (isset(Tokens::$castTokens[$tokens[$stackPtr]['code']]) === true) { + // A cast token. + if (strtolower($tokens[$stackPtr]['content']) !== $tokens[$stackPtr]['content']) { + if ($tokens[$stackPtr]['content'] === strtoupper($tokens[$stackPtr]['content'])) { + $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'upper'); + } else { + $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'mixed'); + } + + $error = 'PHP types must be lowercase; expected "%s" but found "%s"'; + $data = [ + strtolower($tokens[$stackPtr]['content']), + $tokens[$stackPtr]['content'], + ]; + + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Found', $data); + if ($fix === true) { + $phpcsFile->fixer->replaceToken($stackPtr, strtolower($tokens[$stackPtr]['content'])); + } + } else { + $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'lower'); + }//end if + + return; + } + + $phpTypes = [ + 'self' => true, + 'array' => true, + 'callable' => true, + 'bool' => true, + 'float' => true, + 'int' => true, + 'string' => true, + 'iterable' => true, + ]; + + $props = $phpcsFile->getMethodProperties($stackPtr); + $returnType = $props['return_type']; + if ($returnType !== '' + && isset($phpTypes[strtolower($returnType)]) === true + ) { + // A function return type. + if (strtolower($returnType) !== $returnType) { + if ($returnType === strtoupper($returnType)) { + $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'upper'); + } else { + $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'mixed'); + } + + $error = 'PHP types must be lowercase; expected "%s" but found "%s"'; + $data = [ + strtolower($returnType), + $returnType, + ]; + + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Found', $data); + if ($fix === true) { + $token = $props['return_type_token']; + $phpcsFile->fixer->replaceToken($token, strtolower($tokens[$token]['content'])); + } + } else { + $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'lower'); + }//end if + } + + $params = $phpcsFile->getMethodParameters($stackPtr); + foreach ($params as $param) { + $typeHint = $param['type_hint']; + if ($typeHint !== '' + && isset($phpTypes[strtolower($typeHint)]) === true + ) { + // A function return type. + if (strtolower($typeHint) !== $typeHint) { + if ($typeHint === strtoupper($typeHint)) { + $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'upper'); + } else { + $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'mixed'); + } + + $error = 'PHP types must be lowercase; expected "%s" but found "%s"'; + $data = [ + strtolower($typeHint), + $typeHint, + ]; + + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Found', $data); + if ($fix === true) { + $token = $param['type_hint_token']; + $phpcsFile->fixer->replaceToken($token, strtolower($tokens[$token]['content'])); + } + } else { + $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'lower'); + }//end if + } + } + + }//end process() + + +}//end class diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc new file mode 100644 index 0000000000..3ed2e99c46 --- /dev/null +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc @@ -0,0 +1,29 @@ + + * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Tests\PHP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +class LowerCaseTypeUnitTest extends AbstractSniffUnitTest +{ + + + /** + * Returns the lines where errors should occur. + * + * 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. + * + * @return array + */ + public function getErrorList() + { + return [ + 14 => 1, + 15 => 1, + 16 => 1, + 17 => 1, + 18 => 1, + 21 => 4, + 22 => 3, + 23 => 3, + 25 => 1, + 26 => 2, + 27 => 2, + ]; + + }//end getErrorList() + + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @return array + */ + public function getWarningList() + { + return []; + + }//end getWarningList() + + +}//end class