From 6511b3e849b36ff776ae3012185534769c06c18c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C5=BDoljom?= Date: Sat, 27 Aug 2022 18:48:28 +0200 Subject: [PATCH] Add new sniff that checks the declare statements --- .../DeclareStatementsStyleStandard.xml | 50 ++ .../DeclareStatementsStyleSniff.php | 241 +++++++++ .../DeclareStatementsStyleUnitTest.inc | 481 ++++++++++++++++++ .../DeclareStatementsStyleUnitTest.php | 78 +++ 4 files changed, 850 insertions(+) create mode 100644 Universal/Docs/DeclareStatements/DeclareStatementsStyleStandard.xml create mode 100644 Universal/Sniffs/DeclareStatements/DeclareStatementsStyleSniff.php create mode 100644 Universal/Tests/DeclareStatements/DeclareStatementsStyleUnitTest.inc create mode 100644 Universal/Tests/DeclareStatements/DeclareStatementsStyleUnitTest.php diff --git a/Universal/Docs/DeclareStatements/DeclareStatementsStyleStandard.xml b/Universal/Docs/DeclareStatements/DeclareStatementsStyleStandard.xml new file mode 100644 index 0000000..9adfd58 --- /dev/null +++ b/Universal/Docs/DeclareStatements/DeclareStatementsStyleStandard.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Universal/Sniffs/DeclareStatements/DeclareStatementsStyleSniff.php b/Universal/Sniffs/DeclareStatements/DeclareStatementsStyleSniff.php new file mode 100644 index 0000000..6448820 --- /dev/null +++ b/Universal/Sniffs/DeclareStatements/DeclareStatementsStyleSniff.php @@ -0,0 +1,241 @@ +getTokens(); + + $openParenPtr = $tokens[$stackPtr]['parenthesis_opener']; + $closeParenPtr = $tokens[$stackPtr]['parenthesis_closer']; + + if (isset($openParenPtr, $closeParenPtr) === false) { + // Parse error or live coding, bow out. + return; + } + + $directiveStrings = []; + // Get the next string and check if it's an allowed directive. + // Find all the directive strings inside the declare statement. + for ($i = $openParenPtr; $i <= $closeParenPtr; $i++) { + if ($tokens[$i]['code'] === \T_STRING) { + $directiveStrings[] = $tokens[$i]['content']; + } + } + + foreach ($directiveStrings as $directiveString) { + if (!in_array($directiveString, $this->allowedDirectives, true)) { + $phpcsFile->addError( + sprintf( + 'Declare directives can be one of: %1$s. "%2$s" found.', + implode(', ', $this->allowedDirectives), + $directiveString + ), + $stackPtr, + 'WrongDeclareDirective' + ); + return; + } + unset($directiveString); + } + + // Curly braces. + $hasScopeOpenerCloser = isset($tokens[$stackPtr]['scope_opener']); + + // If strict types is defined using curly brace, throw error. + if ($hasScopeOpenerCloser !== false && in_array('strict_types', $directiveStrings, true)) { + $phpcsFile->addError( + sprintf( + 'strict_types declaration must not use block mode. Opening brace found on line %d', + $tokens[$stackPtr]['line'] + ), + $stackPtr, + 'Forbidden' + ); + return; + } + + // Fix for the case when the code is between the curly braces for the strict_types. + $codePtr = $phpcsFile->findNext(Tokens::$emptyTokens, ($closeParenPtr + 1), null, true); + + /* If the code pointer is not one of: + * \T_SEMICOLON, \T_CLOSE_TAG or \T_OPEN_CURLY_BRACKET, \T_COLON + * throw an error. + */ + if (!in_array($tokens[$codePtr]['code'], [\T_SEMICOLON, \T_CLOSE_TAG, \T_OPEN_CURLY_BRACKET, \T_COLON], true)) { + $phpcsFile->addError( + 'Unexpected code found after opening the declare statement without closing it.', + $stackPtr, + 'UnexpectedCodeFound' + ); + return; + } + + if ($this->declareStyle === 'requireBraces' && + $hasScopeOpenerCloser === false && + in_array('strict_types', $directiveStrings, true) + ) { + $phpcsFile->addError( + 'strict_types declaration is not compatible with requireBraces option.', + $stackPtr, + 'IncompatibleCurlyBracesRequirement' + ); + return; + } + + if ($this->declareStyle === 'disallowBraces' && $hasScopeOpenerCloser !== false) { + $phpcsFile->addError('Declare statement found using curly braces', $stackPtr, 'DisallowedCurlyBraces'); + return; + } + + if (in_array('ticks', $this->directiveType, true)) { + if ($this->declareStyle === 'requireBraces' && + $hasScopeOpenerCloser === false && + in_array('ticks', $directiveStrings, true) + ) { + $phpcsFile->addError( + 'Declare statement for found without curly braces', + $stackPtr, + 'MissingCurlyBracesTicks' + ); + return; + } + + if ($this->declareStyle === 'disallowBraces' && $hasScopeOpenerCloser !== false) { + $phpcsFile->addError( + 'Declare statement found using curly braces', + $stackPtr, + 'DisallowedCurlyBracesTicks' + ); + return; + } + } + + if (in_array('encoding', $this->directiveType, true)) { + if ($this->declareStyle === 'requireBraces' && + $hasScopeOpenerCloser === false && + in_array('encoding', $directiveStrings, true) + ) { + $phpcsFile->addError( + 'Declare statement found without curly braces', + $stackPtr, + 'MissingCurlyBracesEncoding' + ); + return; + } + + if ($this->declareStyle === 'disallowBraces' && $hasScopeOpenerCloser !== false) { + $phpcsFile->addError( + 'Declare statement found using curly braces', + $stackPtr, + 'DisallowedCurlyBracesEncoding' + ); + return; + } + } + + if ($this->declareStyle === 'requireBraces' && + $hasScopeOpenerCloser === false && + empty($this->directiveType) && + !in_array('strict_types', $directiveStrings, true) + ) { + $phpcsFile->addError('Declare statement found without curly braces', $stackPtr, 'MissingCurlyBraces'); + return; + } + } +} diff --git a/Universal/Tests/DeclareStatements/DeclareStatementsStyleUnitTest.inc b/Universal/Tests/DeclareStatements/DeclareStatementsStyleUnitTest.inc new file mode 100644 index 0000000..6d9b84d --- /dev/null +++ b/Universal/Tests/DeclareStatements/DeclareStatementsStyleUnitTest.inc @@ -0,0 +1,481 @@ + => + */ + public function getErrorList() + { + return [ + 15 => 1, + 19 => 1, + 23 => 1, + 32 => 1, + 41 => 1, + 45 => 1, + 49 => 1, + 50 => 1, + 55 => 1, + 56 => 1, + 63 => 1, + 67 => 1, + 76 => 1, + 85 => 1, + 89 => 1, + 95 => 1, + 104 => 1, + 185 => 1, + 187 => 1, + 189 => 1, + 191 => 1, + 217 => 1, + 226 => 1, + 261 => 1, + 271 => 1, + 331 => 1, + 340 => 1, + 375 => 1, + 385 => 1, + 445 => 1, + 454 => 1, + 463 => 1, + 472 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +}