From 72c3c22bde837178bad76480204b3a9a8bd69df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mantas=20Jonu=C5=A1as?= Date: Fri, 17 Apr 2015 15:36:03 +0300 Subject: [PATCH] Added space after not operator checker --- Sniffs/WhiteSpace/OperatorSpacingSniff.php | 324 ++++++++++++++++++ .../WhiteSpace/OperatorSpacingSniffTest.php | 35 ++ .../OperatorSpacingSniffTest.phptest | 14 + ruleset.xml | 1 + 4 files changed, 374 insertions(+) create mode 100644 Sniffs/WhiteSpace/OperatorSpacingSniff.php create mode 100644 Tests/Unit/WhiteSpace/OperatorSpacingSniffTest.php create mode 100644 Tests/Unit/WhiteSpace/OperatorSpacingSniffTest.phptest diff --git a/Sniffs/WhiteSpace/OperatorSpacingSniff.php b/Sniffs/WhiteSpace/OperatorSpacingSniff.php new file mode 100644 index 0000000..ff7de32 --- /dev/null +++ b/Sniffs/WhiteSpace/OperatorSpacingSniff.php @@ -0,0 +1,324 @@ + + * @author Marc McIntyre + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Sniffs_Squiz_WhiteSpace_OperatorSpacingSniff. + * + * Verifies that operators have valid spacing surrounding them. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Ongr_Sniffs_WhiteSpace_OperatorSpacingSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * A list of tokenizers this sniff supports. + * + * @var array + */ + public $supportedTokenizers = array( + 'PHP', + 'JS', + ); + + /** + * Allow newlines instead of spaces. + * + * @var boolean + */ + public $ignoreNewlines = false; + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + $comparison = PHP_CodeSniffer_Tokens::$comparisonTokens; + $operators = PHP_CodeSniffer_Tokens::$operators; + $assignment = PHP_CodeSniffer_Tokens::$assignmentTokens; + $inlineIf = array( + T_INLINE_THEN, + T_INLINE_ELSE, + ); + + return array_unique( + array_merge($comparison, $operators, $assignment, $inlineIf, [T_BOOLEAN_NOT]) + ); + + }//end register() + + + /** + * Processes this sniff, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The current file being checked. + * @param int $stackPtr The position of the current token in + * the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // Ongr check space after not operator. + if ($tokens[$stackPtr]['code'] === T_BOOLEAN_NOT && $tokens[$stackPtr + 1]['code'] === T_WHITESPACE) { + $error = 'Whitespace found after not operator'; + $phpcsFile->addError($error, $stackPtr, 'SpacingAfterNot'); + return; + } + + // Skip default values in function declarations. + if ($tokens[$stackPtr]['code'] === T_EQUAL + || $tokens[$stackPtr]['code'] === T_MINUS + ) { + if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { + $parenthesis = array_keys($tokens[$stackPtr]['nested_parenthesis']); + $bracket = array_pop($parenthesis); + if (isset($tokens[$bracket]['parenthesis_owner']) === true) { + $function = $tokens[$bracket]['parenthesis_owner']; + if ($tokens[$function]['code'] === T_FUNCTION + || $tokens[$function]['code'] === T_CLOSURE + ) { + return; + } + } + } + } + + if ($tokens[$stackPtr]['code'] === T_EQUAL) { + // Skip for '=&' case. + if (isset($tokens[($stackPtr + 1)]) === true + && $tokens[($stackPtr + 1)]['code'] === T_BITWISE_AND + ) { + return; + } + } + + // Skip short ternary such as: "$foo = $bar ?: true;". + if (($tokens[$stackPtr]['code'] === T_INLINE_THEN + && $tokens[($stackPtr + 1)]['code'] === T_INLINE_ELSE) + || ($tokens[($stackPtr - 1)]['code'] === T_INLINE_THEN + && $tokens[$stackPtr]['code'] === T_INLINE_ELSE) + ) { + return; + } + + if ($tokens[$stackPtr]['code'] === T_BITWISE_AND) { + // If it's not a reference, then we expect one space either side of the + // bitwise operator. + if ($phpcsFile->isReference($stackPtr) === true) { + return; + } + + // Check there is one space before the & operator. + if ($tokens[($stackPtr - 1)]['code'] !== T_WHITESPACE) { + $error = 'Expected 1 space before "&" operator; 0 found'; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceBeforeAmp'); + if ($fix === true) { + $phpcsFile->fixer->addContentBefore($stackPtr, ' '); + } + + $phpcsFile->recordMetric($stackPtr, 'Space before operator', 0); + } else { + if ($tokens[($stackPtr - 2)]['line'] !== $tokens[$stackPtr]['line']) { + $found = 'newline'; + } else { + $found = $tokens[($stackPtr - 1)]['length']; + } + + $phpcsFile->recordMetric($stackPtr, 'Space before operator', $found); + if ($found !== 1 + && ($found !== 'newline' || $this->ignoreNewlines === false) + ) { + $error = 'Expected 1 space before "&" operator; %s found'; + $data = array($found); + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBeforeAmp', $data); + if ($fix === true) { + $phpcsFile->fixer->replaceToken(($stackPtr - 1), ' '); + } + } + }//end if + + // Check there is one space after the & operator. + if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { + $error = 'Expected 1 space after "&" operator; 0 found'; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceAfterAmp'); + if ($fix === true) { + $phpcsFile->fixer->addContent($stackPtr, ' '); + } + + $phpcsFile->recordMetric($stackPtr, 'Space after operator', 0); + } else { + if ($tokens[($stackPtr + 2)]['line'] !== $tokens[$stackPtr]['line']) { + $found = 'newline'; + } else { + $found = $tokens[($stackPtr + 1)]['length']; + } + + $phpcsFile->recordMetric($stackPtr, 'Space after operator', $found); + if ($found !== 1 + && ($found !== 'newline' || $this->ignoreNewlines === false) + ) { + $error = 'Expected 1 space after "&" operator; %s found'; + $data = array($found); + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingAfterAmp', $data); + if ($fix === true) { + $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' '); + } + } + }//end if + + return; + }//end if + + if ($tokens[$stackPtr]['code'] === T_MINUS) { + // Check minus spacing, but make sure we aren't just assigning + // a minus value or returning one. + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if ($tokens[$prev]['code'] === T_RETURN) { + // Just returning a negative value; eg. (return -1). + return; + } + + if (isset(PHP_CodeSniffer_Tokens::$operators[$tokens[$prev]['code']]) === true) { + // Just trying to operate on a negative value; eg. ($var * -1). + return; + } + + if (isset(PHP_CodeSniffer_Tokens::$comparisonTokens[$tokens[$prev]['code']]) === true) { + // Just trying to compare a negative value; eg. ($var === -1). + return; + } + + if (isset(PHP_CodeSniffer_Tokens::$booleanOperators[$tokens[$prev]['code']]) === true) { + // Just trying to compare a negative value; eg. ($var || -1 === $b). + return; + } + + if (isset(PHP_CodeSniffer_Tokens::$assignmentTokens[$tokens[$prev]['code']]) === true) { + // Just trying to assign a negative value; eg. ($var = -1). + return; + } + + // A list of tokens that indicate that the token is not + // part of an arithmetic operation. + $invalidTokens = array( + T_COMMA => true, + T_OPEN_PARENTHESIS => true, + T_OPEN_SQUARE_BRACKET => true, + T_DOUBLE_ARROW => true, + T_COLON => true, + T_INLINE_THEN => true, + T_INLINE_ELSE => true, + T_CASE => true, + ); + + if (isset($invalidTokens[$tokens[$prev]['code']]) === true) { + // Just trying to use a negative value; eg. myFunction($var, -2). + return; + } + }//end if + + $operator = $tokens[$stackPtr]['content']; + + if ($tokens[($stackPtr - 1)]['code'] !== T_WHITESPACE) { + $error = "Expected 1 space before \"$operator\"; 0 found"; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceBefore'); + if ($fix === true) { + $phpcsFile->fixer->addContentBefore($stackPtr, ' '); + } + + $phpcsFile->recordMetric($stackPtr, 'Space before operator', 0); + } else if (isset(PHP_CodeSniffer_Tokens::$assignmentTokens[$tokens[$stackPtr]['code']]) === false) { + // Don't throw an error for assignments, because other standards allow + // multiple spaces there to align multiple assignments. + if ($tokens[($stackPtr - 2)]['line'] !== $tokens[$stackPtr]['line']) { + $found = 'newline'; + } else { + $found = $tokens[($stackPtr - 1)]['length']; + } + + $phpcsFile->recordMetric($stackPtr, 'Space before operator', $found); + if ($found !== 1 + && ($found !== 'newline' || $this->ignoreNewlines === false) + ) { + $error = 'Expected 1 space before "%s"; %s found'; + $data = array( + $operator, + $found, + ); + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBefore', $data); + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + if ($found === 'newline') { + $i = ($stackPtr - 2); + while ($tokens[$i]['code'] === T_WHITESPACE) { + $phpcsFile->fixer->replaceToken($i, ''); + $i--; + } + } + + $phpcsFile->fixer->replaceToken(($stackPtr - 1), ' '); + $phpcsFile->fixer->endChangeset(); + } + }//end if + }//end if + + if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { + $error = "Expected 1 space after \"$operator\"; 0 found"; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceAfter'); + if ($fix === true) { + $phpcsFile->fixer->addContent($stackPtr, ' '); + } + + $phpcsFile->recordMetric($stackPtr, 'Space after operator', 0); + } else { + if ($tokens[($stackPtr + 2)]['line'] !== $tokens[$stackPtr]['line']) { + $found = 'newline'; + } else { + $found = $tokens[($stackPtr + 1)]['length']; + } + + $phpcsFile->recordMetric($stackPtr, 'Space after operator', $found); + if ($found !== 1 + && ($found !== 'newline' || $this->ignoreNewlines === false) + ) { + $error = 'Expected 1 space after "%s"; %s found'; + $data = array( + $operator, + $found, + ); + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingAfter', $data); + if ($fix === true) { + $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' '); + } + } + }//end if + + }//end process() + + +}//end class diff --git a/Tests/Unit/WhiteSpace/OperatorSpacingSniffTest.php b/Tests/Unit/WhiteSpace/OperatorSpacingSniffTest.php new file mode 100644 index 0000000..dbe0ed9 --- /dev/null +++ b/Tests/Unit/WhiteSpace/OperatorSpacingSniffTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ongr\Tests\Unit\WhiteSpace; + +use Ongr\Tests\AbstractSniffUnitTest; + +class OperatorSpacingSniffTest extends AbstractSniffUnitTest +{ + /** + * {@inheritdoc} + */ + protected function getErrorList() + { + return [ + 12 => 1, + ]; + } + + /** + * {@inheritdoc} + */ + protected function getWarningList() + { + return []; + } +} diff --git a/Tests/Unit/WhiteSpace/OperatorSpacingSniffTest.phptest b/Tests/Unit/WhiteSpace/OperatorSpacingSniffTest.phptest new file mode 100644 index 0000000..f71b15b --- /dev/null +++ b/Tests/Unit/WhiteSpace/OperatorSpacingSniffTest.phptest @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (! $foo) { + true; +} diff --git a/ruleset.xml b/ruleset.xml index 03a3de1..70e5749 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -68,6 +68,7 @@ +