diff --git a/package.xml b/package.xml index 73aa918436..f040c25063 100644 --- a/package.xml +++ b/package.xml @@ -65,22 +65,126 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -211,6 +315,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + @@ -312,6 +420,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + @@ -375,6 +487,12 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + @@ -546,6 +664,14 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + + + @@ -922,6 +1048,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + @@ -937,6 +1067,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + @@ -1130,7 +1263,6 @@ http://pear.php.net/dtd/package-2.0.xsd"> - @@ -1147,6 +1279,12 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + @@ -1746,6 +1884,21 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + + + + + + + + + + @@ -1793,23 +1946,97 @@ http://pear.php.net/dtd/package-2.0.xsd"> + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1826,23 +2053,97 @@ http://pear.php.net/dtd/package-2.0.xsd"> + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Config.php b/src/Config.php index cba3f67ec3..c31273db59 100644 --- a/src/Config.php +++ b/src/Config.php @@ -156,7 +156,7 @@ class Config /** * Command line values that the user has supplied directly. * - * @var array + * @var array */ private static $overriddenDefaults = []; @@ -584,6 +584,7 @@ public function restoreDefaults() * @param int $pos The position of the argument on the command line. * * @return void + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException */ public function processShortArgument($arg, $pos) { @@ -688,6 +689,7 @@ public function processShortArgument($arg, $pos) * @param int $pos The position of the argument on the command line. * * @return void + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException */ public function processLongArgument($arg, $pos) { @@ -1249,6 +1251,7 @@ public function processLongArgument($arg, $pos) * @param int $pos The position of the argument on the command line. * * @return void + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException */ public function processUnknownArgument($arg, $pos) { @@ -1274,6 +1277,7 @@ public function processUnknownArgument($arg, $pos) * @param string $path The path to the file to add. * * @return void + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException */ public function processFilePath($path) { @@ -1558,7 +1562,7 @@ public static function getExecutablePath($name) * * @return bool * @see getConfigData() - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the config file can not be written. + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the config file can not be written. */ public static function setConfigData($key, $value, $temp=false) { @@ -1639,6 +1643,7 @@ public static function setConfigData($key, $value, $temp=false) * * @return array * @see getConfigData() + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the config file could not be read. */ public static function getAllConfigData() { diff --git a/src/Files/File.php b/src/Files/File.php index 05fa3455c9..0f3ae5befc 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1211,44 +1211,16 @@ public function getFilename() * declared the class, interface, trait, or function. * * @return string|null The name of the class, interface, trait, or function; - * or NULL if the function or class is anonymous. + * NULL if the function or class is anonymous; or + * an empty string in case of a parse error/live coding. * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token is not of type - * T_FUNCTION, T_CLASS, T_ANON_CLASS, - * T_CLOSURE, T_TRAIT, or T_INTERFACE. + * T_FUNCTION, T_CLASS, T_TRAIT, or T_INTERFACE. + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\ConstructNames::getDeclarationName() instead. */ public function getDeclarationName($stackPtr) { - $tokenCode = $this->tokens[$stackPtr]['code']; - - if ($tokenCode === T_ANON_CLASS || $tokenCode === T_CLOSURE) { - return null; - } - - if ($tokenCode !== T_FUNCTION - && $tokenCode !== T_CLASS - && $tokenCode !== T_INTERFACE - && $tokenCode !== T_TRAIT - ) { - throw new RuntimeException('Token type "'.$this->tokens[$stackPtr]['type'].'" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT'); - } - - if ($tokenCode === T_FUNCTION - && strtolower($this->tokens[$stackPtr]['content']) !== 'function' - ) { - // This is a function declared without the "function" keyword. - // So this token is the function name. - return $this->tokens[$stackPtr]['content']; - } - - $content = null; - for ($i = $stackPtr; $i < $this->numTokens; $i++) { - if ($this->tokens[$i]['code'] === T_STRING) { - $content = $this->tokens[$i]['content']; - break; - } - } - - return $content; + return Util\Sniffs\ConstructNames::getDeclarationName($this, $stackPtr); }//end getDeclarationName() @@ -1279,171 +1251,14 @@ public function getDeclarationName($stackPtr) * to acquire the parameters for. * * @return array - * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified $stackPtr is not of - * type T_FUNCTION or T_CLOSURE. + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified $stackPtr is not of + * type T_FUNCTION or T_CLOSURE. + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters() instead. */ public function getMethodParameters($stackPtr) { - if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION - && $this->tokens[$stackPtr]['code'] !== T_CLOSURE - ) { - throw new TokenizerException('$stackPtr must be of type T_FUNCTION or T_CLOSURE'); - } - - $opener = $this->tokens[$stackPtr]['parenthesis_opener']; - $closer = $this->tokens[$stackPtr]['parenthesis_closer']; - - $vars = []; - $currVar = null; - $paramStart = ($opener + 1); - $defaultStart = null; - $paramCount = 0; - $passByReference = false; - $variableLength = false; - $typeHint = ''; - $typeHintToken = false; - $nullableType = false; - - for ($i = $paramStart; $i <= $closer; $i++) { - // Check to see if this token has a parenthesis or bracket opener. If it does - // it's likely to be an array which might have arguments in it. This - // could cause problems in our parsing below, so lets just skip to the - // end of it. - if (isset($this->tokens[$i]['parenthesis_opener']) === true) { - // Don't do this if it's the close parenthesis for the method. - if ($i !== $this->tokens[$i]['parenthesis_closer']) { - $i = ($this->tokens[$i]['parenthesis_closer'] + 1); - } - } - - if (isset($this->tokens[$i]['bracket_opener']) === true) { - // Don't do this if it's the close parenthesis for the method. - if ($i !== $this->tokens[$i]['bracket_closer']) { - $i = ($this->tokens[$i]['bracket_closer'] + 1); - } - } - - switch ($this->tokens[$i]['code']) { - case T_BITWISE_AND: - if ($defaultStart === null) { - $passByReference = true; - } - break; - case T_VARIABLE: - $currVar = $i; - break; - case T_ELLIPSIS: - $variableLength = true; - break; - case T_CALLABLE: - if ($typeHintToken === false) { - $typeHintToken = $i; - } - - $typeHint .= $this->tokens[$i]['content']; - break; - case T_SELF: - case T_PARENT: - case T_STATIC: - // Self and parent are valid, static invalid, but was probably intended as type hint. - if (isset($defaultStart) === false) { - if ($typeHintToken === false) { - $typeHintToken = $i; - } - - $typeHint .= $this->tokens[$i]['content']; - } - break; - case T_STRING: - // This is a string, so it may be a type hint, but it could - // also be a constant used as a default value. - $prevComma = false; - for ($t = $i; $t >= $opener; $t--) { - if ($this->tokens[$t]['code'] === T_COMMA) { - $prevComma = $t; - break; - } - } - - if ($prevComma !== false) { - $nextEquals = false; - for ($t = $prevComma; $t < $i; $t++) { - if ($this->tokens[$t]['code'] === T_EQUAL) { - $nextEquals = $t; - break; - } - } - - if ($nextEquals !== false) { - break; - } - } - - if ($defaultStart === null) { - if ($typeHintToken === false) { - $typeHintToken = $i; - } - - $typeHint .= $this->tokens[$i]['content']; - } - break; - case T_NS_SEPARATOR: - // Part of a type hint or default value. - if ($defaultStart === null) { - if ($typeHintToken === false) { - $typeHintToken = $i; - } - - $typeHint .= $this->tokens[$i]['content']; - } - break; - case T_NULLABLE: - if ($defaultStart === null) { - $nullableType = true; - $typeHint .= $this->tokens[$i]['content']; - } - break; - case T_CLOSE_PARENTHESIS: - case T_COMMA: - // If it's null, then there must be no parameters for this - // method. - if ($currVar === null) { - continue 2; - } - - $vars[$paramCount] = []; - $vars[$paramCount]['token'] = $currVar; - $vars[$paramCount]['name'] = $this->tokens[$currVar]['content']; - $vars[$paramCount]['content'] = trim($this->getTokensAsString($paramStart, ($i - $paramStart))); - - if ($defaultStart !== null) { - $vars[$paramCount]['default'] = trim($this->getTokensAsString($defaultStart, ($i - $defaultStart))); - } - - $vars[$paramCount]['pass_by_reference'] = $passByReference; - $vars[$paramCount]['variable_length'] = $variableLength; - $vars[$paramCount]['type_hint'] = $typeHint; - $vars[$paramCount]['type_hint_token'] = $typeHintToken; - $vars[$paramCount]['nullable_type'] = $nullableType; - - // Reset the vars, as we are about to process the next parameter. - $defaultStart = null; - $paramStart = ($i + 1); - $passByReference = false; - $variableLength = false; - $typeHint = ''; - $typeHintToken = false; - $nullableType = false; - - $paramCount++; - break; - case T_EQUAL: - $defaultStart = ($i + 1); - break; - }//end switch - }//end for - - return $vars; + return Util\Sniffs\FunctionDeclarations::getParameters($this, $stackPtr); }//end getMethodParameters() @@ -1471,133 +1286,14 @@ public function getMethodParameters($stackPtr) * acquire the properties for. * * @return array - * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a - * T_FUNCTION token. + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * T_FUNCTION token. + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties() instead. */ public function getMethodProperties($stackPtr) { - if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION - && $this->tokens[$stackPtr]['code'] !== T_CLOSURE - ) { - throw new TokenizerException('$stackPtr must be of type T_FUNCTION or T_CLOSURE'); - } - - if ($this->tokens[$stackPtr]['code'] === T_FUNCTION) { - $valid = [ - T_PUBLIC => T_PUBLIC, - T_PRIVATE => T_PRIVATE, - T_PROTECTED => T_PROTECTED, - T_STATIC => T_STATIC, - T_FINAL => T_FINAL, - T_ABSTRACT => T_ABSTRACT, - T_WHITESPACE => T_WHITESPACE, - T_COMMENT => T_COMMENT, - T_DOC_COMMENT => T_DOC_COMMENT, - ]; - } else { - $valid = [ - T_STATIC => T_STATIC, - T_WHITESPACE => T_WHITESPACE, - T_COMMENT => T_COMMENT, - T_DOC_COMMENT => T_DOC_COMMENT, - ]; - } - - $scope = 'public'; - $scopeSpecified = false; - $isAbstract = false; - $isFinal = false; - $isStatic = false; - - for ($i = ($stackPtr - 1); $i > 0; $i--) { - if (isset($valid[$this->tokens[$i]['code']]) === false) { - break; - } - - switch ($this->tokens[$i]['code']) { - case T_PUBLIC: - $scope = 'public'; - $scopeSpecified = true; - break; - case T_PRIVATE: - $scope = 'private'; - $scopeSpecified = true; - break; - case T_PROTECTED: - $scope = 'protected'; - $scopeSpecified = true; - break; - case T_ABSTRACT: - $isAbstract = true; - break; - case T_FINAL: - $isFinal = true; - break; - case T_STATIC: - $isStatic = true; - break; - }//end switch - }//end for - - $returnType = ''; - $returnTypeToken = false; - $nullableReturnType = false; - $hasBody = true; - - if (isset($this->tokens[$stackPtr]['parenthesis_closer']) === true) { - $scopeOpener = null; - if (isset($this->tokens[$stackPtr]['scope_opener']) === true) { - $scopeOpener = $this->tokens[$stackPtr]['scope_opener']; - } - - $valid = [ - T_STRING => T_STRING, - T_CALLABLE => T_CALLABLE, - T_SELF => T_SELF, - T_PARENT => T_PARENT, - T_NS_SEPARATOR => T_NS_SEPARATOR, - ]; - - for ($i = $this->tokens[$stackPtr]['parenthesis_closer']; $i < $this->numTokens; $i++) { - if (($scopeOpener === null && $this->tokens[$i]['code'] === T_SEMICOLON) - || ($scopeOpener !== null && $i === $scopeOpener) - ) { - // End of function definition. - break; - } - - if ($this->tokens[$i]['code'] === T_NULLABLE) { - $nullableReturnType = true; - } - - if (isset($valid[$this->tokens[$i]['code']]) === true) { - if ($returnTypeToken === false) { - $returnTypeToken = $i; - } - - $returnType .= $this->tokens[$i]['content']; - } - } - - $end = $this->findNext([T_OPEN_CURLY_BRACKET, T_SEMICOLON], $this->tokens[$stackPtr]['parenthesis_closer']); - $hasBody = $this->tokens[$end]['code'] === T_OPEN_CURLY_BRACKET; - }//end if - - if ($returnType !== '' && $nullableReturnType === true) { - $returnType = '?'.$returnType; - } - - return [ - 'scope' => $scope, - 'scope_specified' => $scopeSpecified, - 'return_type' => $returnType, - 'return_type_token' => $returnTypeToken, - 'nullable_return_type' => $nullableReturnType, - 'is_abstract' => $isAbstract, - 'is_final' => $isFinal, - 'is_static' => $isStatic, - 'has_body' => $hasBody, - ]; + return Util\Sniffs\FunctionDeclarations::getProperties($this, $stackPtr); }//end getMethodProperties() @@ -1620,106 +1316,15 @@ public function getMethodProperties($stackPtr) * acquire the properties for. * * @return array - * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a - * T_VARIABLE token, or if the position is not - * a class member variable. + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * T_VARIABLE token, or if the position is not + * a class member variable. + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\Variables::getMemberProperties() instead. */ public function getMemberProperties($stackPtr) { - if ($this->tokens[$stackPtr]['code'] !== T_VARIABLE) { - throw new TokenizerException('$stackPtr must be of type T_VARIABLE'); - } - - $conditions = array_keys($this->tokens[$stackPtr]['conditions']); - $ptr = array_pop($conditions); - if (isset($this->tokens[$ptr]) === false - || ($this->tokens[$ptr]['code'] !== T_CLASS - && $this->tokens[$ptr]['code'] !== T_ANON_CLASS - && $this->tokens[$ptr]['code'] !== T_TRAIT) - ) { - if (isset($this->tokens[$ptr]) === true - && $this->tokens[$ptr]['code'] === T_INTERFACE - ) { - // T_VARIABLEs in interfaces can actually be method arguments - // but they wont be seen as being inside the method because there - // are no scope openers and closers for abstract methods. If it is in - // parentheses, we can be pretty sure it is a method argument. - if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === false - || empty($this->tokens[$stackPtr]['nested_parenthesis']) === true - ) { - $error = 'Possible parse error: interfaces may not include member vars'; - $this->addWarning($error, $stackPtr, 'Internal.ParseError.InterfaceHasMemberVar'); - return []; - } - } else { - throw new TokenizerException('$stackPtr is not a class member var'); - } - } - - // Make sure it's not a method parameter. - if (empty($this->tokens[$stackPtr]['nested_parenthesis']) === false) { - $parenthesis = array_keys($this->tokens[$stackPtr]['nested_parenthesis']); - $deepestOpen = array_pop($parenthesis); - if ($deepestOpen > $ptr - && isset($this->tokens[$deepestOpen]['parenthesis_owner']) === true - && $this->tokens[$this->tokens[$deepestOpen]['parenthesis_owner']]['code'] === T_FUNCTION - ) { - throw new TokenizerException('$stackPtr is not a class member var'); - } - } - - $valid = [ - T_PUBLIC => T_PUBLIC, - T_PRIVATE => T_PRIVATE, - T_PROTECTED => T_PROTECTED, - T_STATIC => T_STATIC, - T_VAR => T_VAR, - ]; - - $valid += Util\Tokens::$emptyTokens; - - $scope = 'public'; - $scopeSpecified = false; - $isStatic = false; - - $startOfStatement = $this->findPrevious( - [ - T_SEMICOLON, - T_OPEN_CURLY_BRACKET, - T_CLOSE_CURLY_BRACKET, - ], - ($stackPtr - 1) - ); - - for ($i = ($startOfStatement + 1); $i < $stackPtr; $i++) { - if (isset($valid[$this->tokens[$i]['code']]) === false) { - break; - } - - switch ($this->tokens[$i]['code']) { - case T_PUBLIC: - $scope = 'public'; - $scopeSpecified = true; - break; - case T_PRIVATE: - $scope = 'private'; - $scopeSpecified = true; - break; - case T_PROTECTED: - $scope = 'protected'; - $scopeSpecified = true; - break; - case T_STATIC: - $isStatic = true; - break; - } - }//end for - - return [ - 'scope' => $scope, - 'scope_specified' => $scopeSpecified, - 'is_static' => $isStatic, - ]; + return Util\Sniffs\Variables::getMemberProperties($this, $stackPtr); }//end getMemberProperties() @@ -1739,46 +1344,14 @@ public function getMemberProperties($stackPtr) * acquire the properties for. * * @return array - * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a - * T_CLASS token. + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * T_CLASS token. + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations::getClassProperties() instead. */ public function getClassProperties($stackPtr) { - if ($this->tokens[$stackPtr]['code'] !== T_CLASS) { - throw new TokenizerException('$stackPtr must be of type T_CLASS'); - } - - $valid = [ - T_FINAL => T_FINAL, - T_ABSTRACT => T_ABSTRACT, - T_WHITESPACE => T_WHITESPACE, - T_COMMENT => T_COMMENT, - T_DOC_COMMENT => T_DOC_COMMENT, - ]; - - $isAbstract = false; - $isFinal = false; - - for ($i = ($stackPtr - 1); $i > 0; $i--) { - if (isset($valid[$this->tokens[$i]['code']]) === false) { - break; - } - - switch ($this->tokens[$i]['code']) { - case T_ABSTRACT: - $isAbstract = true; - break; - - case T_FINAL: - $isFinal = true; - break; - } - }//end for - - return [ - 'is_abstract' => $isAbstract, - 'is_final' => $isFinal, - ]; + return Util\Sniffs\ObjectDeclarations::getClassProperties($this, $stackPtr); }//end getClassProperties() @@ -1792,126 +1365,12 @@ public function getClassProperties($stackPtr) * @param int $stackPtr The position of the T_BITWISE_AND token. * * @return boolean + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\TokenIs::isReference() instead. */ public function isReference($stackPtr) { - if ($this->tokens[$stackPtr]['code'] !== T_BITWISE_AND) { - return false; - } - - $tokenBefore = $this->findPrevious( - Util\Tokens::$emptyTokens, - ($stackPtr - 1), - null, - true - ); - - if ($this->tokens[$tokenBefore]['code'] === T_FUNCTION) { - // Function returns a reference. - return true; - } - - if ($this->tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) { - // Inside a foreach loop or array assignment, this is a reference. - return true; - } - - if ($this->tokens[$tokenBefore]['code'] === T_AS) { - // Inside a foreach loop, this is a reference. - return true; - } - - if (isset(Util\Tokens::$assignmentTokens[$this->tokens[$tokenBefore]['code']]) === true) { - // This is directly after an assignment. It's a reference. Even if - // it is part of an operation, the other tests will handle it. - return true; - } - - $tokenAfter = $this->findNext( - Util\Tokens::$emptyTokens, - ($stackPtr + 1), - null, - true - ); - - if ($this->tokens[$tokenAfter]['code'] === T_NEW) { - return true; - } - - if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === true) { - $brackets = $this->tokens[$stackPtr]['nested_parenthesis']; - $lastBracket = array_pop($brackets); - if (isset($this->tokens[$lastBracket]['parenthesis_owner']) === true) { - $owner = $this->tokens[$this->tokens[$lastBracket]['parenthesis_owner']]; - if ($owner['code'] === T_FUNCTION - || $owner['code'] === T_CLOSURE - ) { - $params = $this->getMethodParameters($this->tokens[$lastBracket]['parenthesis_owner']); - foreach ($params as $param) { - $varToken = $tokenAfter; - if ($param['variable_length'] === true) { - $varToken = $this->findNext( - (Util\Tokens::$emptyTokens + [T_ELLIPSIS]), - ($stackPtr + 1), - null, - true - ); - } - - if ($param['token'] === $varToken - && $param['pass_by_reference'] === true - ) { - // Function parameter declared to be passed by reference. - return true; - } - } - }//end if - } else { - $prev = false; - for ($t = ($this->tokens[$lastBracket]['parenthesis_opener'] - 1); $t >= 0; $t--) { - if ($this->tokens[$t]['code'] !== T_WHITESPACE) { - $prev = $t; - break; - } - } - - if ($prev !== false && $this->tokens[$prev]['code'] === T_USE) { - // Closure use by reference. - return true; - } - }//end if - }//end if - - // Pass by reference in function calls and assign by reference in arrays. - if ($this->tokens[$tokenBefore]['code'] === T_OPEN_PARENTHESIS - || $this->tokens[$tokenBefore]['code'] === T_COMMA - || $this->tokens[$tokenBefore]['code'] === T_OPEN_SHORT_ARRAY - ) { - if ($this->tokens[$tokenAfter]['code'] === T_VARIABLE) { - return true; - } else { - $skip = Util\Tokens::$emptyTokens; - $skip[] = T_NS_SEPARATOR; - $skip[] = T_SELF; - $skip[] = T_PARENT; - $skip[] = T_STATIC; - $skip[] = T_STRING; - $skip[] = T_NAMESPACE; - $skip[] = T_DOUBLE_COLON; - - $nextSignificantAfter = $this->findNext( - $skip, - ($stackPtr + 1), - null, - true - ); - if ($this->tokens[$nextSignificantAfter]['code'] === T_VARIABLE) { - return true; - } - }//end if - }//end if - - return false; + return Util\Sniffs\TokenIs::isReference($this, $stackPtr); }//end isReference() @@ -1926,6 +1385,7 @@ public function isReference($stackPtr) * content should be used. * * @return string The token contents. + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position does not exist. */ public function getTokensAsString($start, $length, $origContent=false) { @@ -2313,30 +1773,12 @@ public function findFirstOnLine($types, $start, $exclude=false, $value=null) * @param int|string|array $types The type(s) of tokens to search for. * * @return boolean + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\Conditions::hasCondition() instead. */ public function hasCondition($stackPtr, $types) { - // Check for the existence of the token. - if (isset($this->tokens[$stackPtr]) === false) { - return false; - } - - // Make sure the token has conditions. - if (isset($this->tokens[$stackPtr]['conditions']) === false) { - return false; - } - - $types = (array) $types; - $conditions = $this->tokens[$stackPtr]['conditions']; - - foreach ($types as $type) { - if (in_array($type, $conditions, true) === true) { - // We found a token with the required type. - return true; - } - } - - return false; + return Util\Sniffs\Conditions::hasCondition($this, $stackPtr, $types); }//end hasCondition() @@ -2350,27 +1792,12 @@ public function hasCondition($stackPtr, $types) * @param int|string $type The type of token to search for. * * @return int + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\Conditions::getCondition() instead. */ public function getCondition($stackPtr, $type) { - // Check for the existence of the token. - if (isset($this->tokens[$stackPtr]) === false) { - return false; - } - - // Make sure the token has conditions. - if (isset($this->tokens[$stackPtr]['conditions']) === false) { - return false; - } - - $conditions = $this->tokens[$stackPtr]['conditions']; - foreach ($conditions as $token => $condition) { - if ($condition === $type) { - return $token; - } - } - - return false; + return Util\Sniffs\Conditions::getCondition($this, $stackPtr, $type); }//end getCondition() @@ -2384,46 +1811,12 @@ public function getCondition($stackPtr, $type) * @param int $stackPtr The stack position of the class. * * @return string|false + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations::findExtendedClassName() instead. */ public function findExtendedClassName($stackPtr) { - // Check for the existence of the token. - if (isset($this->tokens[$stackPtr]) === false) { - return false; - } - - if ($this->tokens[$stackPtr]['code'] !== T_CLASS - && $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS - && $this->tokens[$stackPtr]['code'] !== T_INTERFACE - ) { - return false; - } - - if (isset($this->tokens[$stackPtr]['scope_opener']) === false) { - return false; - } - - $classOpenerIndex = $this->tokens[$stackPtr]['scope_opener']; - $extendsIndex = $this->findNext(T_EXTENDS, $stackPtr, $classOpenerIndex); - if (false === $extendsIndex) { - return false; - } - - $find = [ - T_NS_SEPARATOR, - T_STRING, - T_WHITESPACE, - ]; - - $end = $this->findNext($find, ($extendsIndex + 1), ($classOpenerIndex + 1), true); - $name = $this->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1)); - $name = trim($name); - - if ($name === '') { - return false; - } - - return $name; + return Util\Sniffs\ObjectDeclarations::findExtendedClassName($this, $stackPtr); }//end findExtendedClassName() @@ -2436,48 +1829,12 @@ public function findExtendedClassName($stackPtr) * @param int $stackPtr The stack position of the class. * * @return array|false + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations::findImplementedInterfaceNames() instead. */ public function findImplementedInterfaceNames($stackPtr) { - // Check for the existence of the token. - if (isset($this->tokens[$stackPtr]) === false) { - return false; - } - - if ($this->tokens[$stackPtr]['code'] !== T_CLASS - && $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS - ) { - return false; - } - - if (isset($this->tokens[$stackPtr]['scope_closer']) === false) { - return false; - } - - $classOpenerIndex = $this->tokens[$stackPtr]['scope_opener']; - $implementsIndex = $this->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex); - if ($implementsIndex === false) { - return false; - } - - $find = [ - T_NS_SEPARATOR, - T_STRING, - T_WHITESPACE, - T_COMMA, - ]; - - $end = $this->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true); - $name = $this->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1)); - $name = trim($name); - - if ($name === '') { - return false; - } else { - $names = explode(',', $name); - $names = array_map('trim', $names); - return $names; - } + return Util\Sniffs\ObjectDeclarations::findImplementedInterfaceNames($this, $stackPtr); }//end findImplementedInterfaceNames() diff --git a/src/Files/FileList.php b/src/Files/FileList.php index ee65ccb163..877b1c003e 100644 --- a/src/Files/FileList.php +++ b/src/Files/FileList.php @@ -136,6 +136,7 @@ public function addFile($path, $file=null) * Get the class name of the filter being used for the run. * * @return string + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the specified filter could not be found. */ private function getFilterClass() { diff --git a/src/Fixer.php b/src/Fixer.php index 943af2ed35..6b7139bb8a 100644 --- a/src/Fixer.php +++ b/src/Fixer.php @@ -50,7 +50,7 @@ class Fixer * else. This is the array that is updated as fixes are made, not the file's * token array. Imploding this array will give you the file content back. * - * @var array + * @var array */ private $tokens = []; @@ -60,7 +60,7 @@ class Fixer * We don't allow the same token to be fixed more than once each time * through a file as this can easily cause conflicts between sniffs. * - * @var int[] + * @var integer[] */ private $fixedTokens = []; @@ -70,7 +70,7 @@ class Fixer * If a token is being "fixed" back to its last value, the fix is * probably conflicting with another. * - * @var array + * @var array */ private $oldTokenValues = []; diff --git a/src/Reporter.php b/src/Reporter.php index 4d38c870fd..b81888637c 100644 --- a/src/Reporter.php +++ b/src/Reporter.php @@ -92,7 +92,8 @@ class Reporter * @param \PHP_CodeSniffer\Config $config The config data for the run. * * @return void - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If a report is not available. + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If a custom report class could not be found. + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If a report class is incorrectly set up. */ public function __construct(Config $config) { diff --git a/src/Reports/Cbf.php b/src/Reports/Cbf.php index 4d20ba10e5..25249e858d 100644 --- a/src/Reports/Cbf.php +++ b/src/Reports/Cbf.php @@ -34,6 +34,7 @@ class Cbf implements Report * @param int $width Maximum allowed line width. * * @return bool + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException */ public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80) { diff --git a/src/Reports/Gitblame.php b/src/Reports/Gitblame.php index e2b76af073..f83c5257cc 100644 --- a/src/Reports/Gitblame.php +++ b/src/Reports/Gitblame.php @@ -63,6 +63,7 @@ protected function getAuthor($line) * @param string $filename File to blame. * * @return array + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException */ protected function getBlameContent($filename) { diff --git a/src/Reports/Hgblame.php b/src/Reports/Hgblame.php index 223e498587..1e229f4834 100644 --- a/src/Reports/Hgblame.php +++ b/src/Reports/Hgblame.php @@ -64,6 +64,7 @@ protected function getAuthor($line) * @param string $filename File to blame. * * @return array + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException */ protected function getBlameContent($filename) { diff --git a/src/Reports/Svnblame.php b/src/Reports/Svnblame.php index e4f07dd262..f4719fe5dc 100644 --- a/src/Reports/Svnblame.php +++ b/src/Reports/Svnblame.php @@ -49,6 +49,7 @@ protected function getAuthor($line) * @param string $filename File to blame. * * @return array + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException */ protected function getBlameContent($filename) { diff --git a/src/Ruleset.php b/src/Ruleset.php index 1e95756ef0..cff9f66d1b 100644 --- a/src/Ruleset.php +++ b/src/Ruleset.php @@ -87,7 +87,7 @@ class Ruleset * The key is the token name being listened for and the value * is the sniff object. * - * @var array + * @var array */ public $tokenListeners = []; @@ -122,6 +122,7 @@ class Ruleset * @param \PHP_CodeSniffer\Config $config The config data for the run. * * @return void + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If no sniffs were registered. */ public function __construct(Config $config) { @@ -304,7 +305,8 @@ public function explain() * is only used for debug output. * * @return string[] - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the ruleset path is invalid. + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException - If the ruleset path is invalid. + * - If a specified autoload file could not be found. */ public function processRuleset($rulesetPath, $depth=0) { diff --git a/src/Runner.php b/src/Runner.php index 32e9a245e6..996a508208 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -233,7 +233,7 @@ public function runPHPCBF() * Exits if the minimum requirements of PHP_CodeSniffer are not met. * * @return array - * @throws \PHP_CodeSniffer\Exceptions\DeepExitException + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the requirements are not met. */ public function checkRequirements() { @@ -281,7 +281,7 @@ public function checkRequirements() * Init the rulesets and other high-level settings. * * @return void - * @throws \PHP_CodeSniffer\Exceptions\DeepExitException + * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If a referenced standard is not installed. */ public function init() { diff --git a/src/Sniffs/AbstractArraySniff.php b/src/Sniffs/AbstractArraySniff.php index d7bdd8184c..c922b847f2 100644 --- a/src/Sniffs/AbstractArraySniff.php +++ b/src/Sniffs/AbstractArraySniff.php @@ -10,6 +10,8 @@ namespace PHP_CodeSniffer\Sniffs; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\PassedParameters; +use PHP_CodeSniffer\Util\Sniffs\TokenIs; use PHP_CodeSniffer\Util\Tokens; abstract class AbstractArraySniff implements Sniff @@ -44,6 +46,13 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); + if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY + && TokenIs::isShortList($phpcsFile, $stackPtr) === true + ) { + // No need to examine nested subs of this short list. + return $tokens[$stackPtr]['bracket_closer']; + } + if ($tokens[$stackPtr]['code'] === T_ARRAY) { $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'no'); @@ -76,124 +85,39 @@ public function process(File $phpcsFile, $stackPtr) $lastToken = $stackPtr; } - $keyUsed = false; - $indices = []; - - for ($checkToken = ($stackPtr + 1); $checkToken <= $lastArrayToken; $checkToken++) { - // Skip bracketed statements, like function calls. - if ($tokens[$checkToken]['code'] === T_OPEN_PARENTHESIS - && (isset($tokens[$checkToken]['parenthesis_owner']) === false - || $tokens[$checkToken]['parenthesis_owner'] !== $stackPtr) - ) { - $checkToken = $tokens[$checkToken]['parenthesis_closer']; - continue; - } - - if ($tokens[$checkToken]['code'] === T_ARRAY - || $tokens[$checkToken]['code'] === T_OPEN_SHORT_ARRAY - || $tokens[$checkToken]['code'] === T_CLOSURE - ) { - // Let subsequent calls of this test handle nested arrays. - if ($tokens[$lastToken]['code'] !== T_DOUBLE_ARROW) { - $indices[] = ['value_start' => $checkToken]; - $lastToken = $checkToken; - } - - if ($tokens[$checkToken]['code'] === T_ARRAY) { - $checkToken = $tokens[$tokens[$checkToken]['parenthesis_opener']]['parenthesis_closer']; - } else if ($tokens[$checkToken]['code'] === T_OPEN_SHORT_ARRAY) { - $checkToken = $tokens[$checkToken]['bracket_closer']; - } else { - // T_CLOSURE. - $checkToken = $tokens[$checkToken]['scope_closer']; - } - - $checkToken = $phpcsFile->findNext(T_WHITESPACE, ($checkToken + 1), null, true); - $lastToken = $checkToken; - if ($tokens[$checkToken]['code'] !== T_COMMA) { - $checkToken--; - } - - continue; - }//end if - - if ($tokens[$checkToken]['code'] !== T_DOUBLE_ARROW - && $tokens[$checkToken]['code'] !== T_COMMA - && $checkToken !== $arrayEnd - ) { - continue; - } - - if ($tokens[$checkToken]['code'] === T_COMMA - || $checkToken === $arrayEnd - ) { - $stackPtrCount = 0; - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - $stackPtrCount = count($tokens[$stackPtr]['nested_parenthesis']); - } - - $commaCount = 0; - if (isset($tokens[$checkToken]['nested_parenthesis']) === true) { - $commaCount = count($tokens[$checkToken]['nested_parenthesis']); - if ($tokens[$stackPtr]['code'] === T_ARRAY) { - // Remove parenthesis that are used to define the array. - $commaCount--; - } - } - - if ($commaCount > $stackPtrCount) { - // This comma is inside more parenthesis than the ARRAY keyword, - // so it is actually a comma used to do things like - // separate arguments in a function call. - continue; - } - - if ($keyUsed === false) { - $valueContent = $phpcsFile->findNext( - Tokens::$emptyTokens, - ($lastToken + 1), - $checkToken, - true - ); - - $indices[] = ['value_start' => $valueContent]; - } - - $lastToken = $checkToken; - $keyUsed = false; - continue; - }//end if - - if ($tokens[$checkToken]['code'] === T_DOUBLE_ARROW) { - $keyUsed = true; - - // Find the start of index that uses this double arrow. - $indexEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($checkToken - 1), $arrayStart, true); - $indexStart = $phpcsFile->findStartOfStatement($indexEnd); - - // Find the value of this index. - $nextContent = $phpcsFile->findNext( + $arrayItems = PassedParameters::getParameters($phpcsFile, $stackPtr); + foreach ($arrayItems as $key => $item) { + $firstNonEmpty = $phpcsFile->findNext( + Tokens::$emptyTokens, + ($item['start'] + 1), + ($item['end'] + 1), + true + ); + $doubleArrow = PassedParameters::getDoubleArrowPosition($phpcsFile, $item['start'], $item['end']); + + if ($doubleArrow === false) { + // This array item does not have an index. + $arrayItems[$key]['value_start'] = $firstNonEmpty; + } else { + $indexEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($doubleArrow - 1), $item['start'], true); + $valueStart = $phpcsFile->findNext( Tokens::$emptyTokens, - ($checkToken + 1), - $arrayEnd, + ($doubleArrow + 1), + ($item['end'] + 1), true ); - $indices[] = [ - 'index_start' => $indexStart, - 'index_end' => $indexEnd, - 'arrow' => $checkToken, - 'value_start' => $nextContent, - ]; - - $lastToken = $checkToken; - }//end if - }//end for + $arrayItems[$key]['index_start'] = $firstNonEmpty; + $arrayItems[$key]['index_end'] = $indexEnd; + $arrayItems[$key]['arrow'] = $doubleArrow; + $arrayItems[$key]['value_start'] = $valueStart; + } + }//end foreach if ($tokens[$arrayStart]['line'] === $tokens[$arrayEnd]['line']) { - $this->processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices); + $this->processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $arrayItems); } else { - $this->processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices); + $this->processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $arrayItems); } }//end process() diff --git a/src/Sniffs/AbstractPatternSniff.php b/src/Sniffs/AbstractPatternSniff.php index 66bc2f5247..acf52d5a1c 100644 --- a/src/Sniffs/AbstractPatternSniff.php +++ b/src/Sniffs/AbstractPatternSniff.php @@ -41,7 +41,7 @@ abstract class AbstractPatternSniff implements Sniff /** * Tokens that this sniff wishes to process outside of the patterns. * - * @var int[] + * @var (integer|string)[] * @see registerSupplementary() * @see processSupplementary() */ @@ -50,7 +50,7 @@ abstract class AbstractPatternSniff implements Sniff /** * Positions in the stack where errors have occurred. * - * @var array + * @var array */ private $errorPos = []; diff --git a/src/Sniffs/AbstractScopeSniff.php b/src/Sniffs/AbstractScopeSniff.php index 579e5ddaeb..5add47b845 100644 --- a/src/Sniffs/AbstractScopeSniff.php +++ b/src/Sniffs/AbstractScopeSniff.php @@ -4,16 +4,25 @@ * * Below is a test that listens to methods that exist only within classes: * - * class ClassScopeTest extends PHP_CodeSniffer_Standards_AbstractScopeSniff + * use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; + * use PHP_CodeSniffer\Files\File; + * use PHP_CodeSniffer\Utils\Sniffs\ConstructNames; + * + * class ClassScopeTest extends AbstractScopeSniff * { * public function __construct() * { * parent::__construct(array(T_CLASS), array(T_FUNCTION)); * } * - * protected function processTokenWithinScope(\PHP_CodeSniffer\Files\File $phpcsFile, $stackPtr, $currScope) + * protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope) * { - * $className = $phpcsFile->getDeclarationName($currScope); + * $className = ConstructNames::getDeclarationName($phpcsFile, $currScope); + * if (empty($className) === true) { + * // Live coding or parse error. + * return; + * } + * * echo 'encountered a method within class '.$className; * } * } @@ -66,7 +75,8 @@ abstract class AbstractScopeSniff implements Sniff * processTokenOutsideScope method. * * @see PHP_CodeSniffer.getValidScopeTokeners() - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified tokens array is empty. + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified tokens arrays are empty + * or invalid. */ public function __construct( array $scopeTokens, diff --git a/src/Sniffs/AbstractVariableSniff.php b/src/Sniffs/AbstractVariableSniff.php index e03f412d29..3dc6bd40ab 100644 --- a/src/Sniffs/AbstractVariableSniff.php +++ b/src/Sniffs/AbstractVariableSniff.php @@ -16,6 +16,8 @@ namespace PHP_CodeSniffer\Sniffs; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\Variables; use PHP_CodeSniffer\Util\Tokens; abstract class AbstractVariableSniff extends AbstractScopeSniff @@ -27,22 +29,13 @@ abstract class AbstractVariableSniff extends AbstractScopeSniff * * Used by various naming convention sniffs. * + * Set from within the constructor. + * * @var array + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\Variables::$phpReservedVars instead. */ - protected $phpReservedVars = [ - '_SERVER' => true, - '_GET' => true, - '_POST' => true, - '_REQUEST' => true, - '_SESSION' => true, - '_ENV' => true, - '_COOKIE' => true, - '_FILES' => true, - 'GLOBALS' => true, - 'http_response_header' => true, - 'HTTP_RAW_POST_DATA' => true, - 'php_errormsg' => true, - ]; + protected $phpReservedVars = []; /** @@ -50,6 +43,12 @@ abstract class AbstractVariableSniff extends AbstractScopeSniff */ public function __construct() { + // Preserve BC without code duplication. + $this->phpReservedVars = array_combine( + array_keys(Variables::$phpReservedVars), + array_fill(0, count(Variables::$phpReservedVars), true) + ); + $scopes = Tokens::$ooScopeTokens; $listen = [ @@ -92,54 +91,12 @@ final protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $cu return; } - // If this token is nested inside a function at a deeper - // level than the current OO scope that was found, it's a normal - // variable and not a member var. - $conditions = array_reverse($tokens[$stackPtr]['conditions'], true); - $inFunction = false; - foreach ($conditions as $scope => $code) { - if (isset(Tokens::$ooScopeTokens[$code]) === true) { - break; - } - - if ($code === T_FUNCTION || $code === T_CLOSURE) { - $inFunction = true; - } - } - - if ($scope !== $currScope) { - // We found a closer scope to this token, so ignore - // this particular time through the sniff. We will process - // this token when this closer scope is found to avoid - // duplicate checks. + $deepestScope = Conditions::getLastCondition($phpcsFile, $stackPtr, Tokens::$ooScopeTokens); + if ($deepestScope !== $currScope) { return; } - // Just make sure this isn't a variable in a function declaration. - if ($inFunction === false && isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - foreach ($tokens[$stackPtr]['nested_parenthesis'] as $opener => $closer) { - if (isset($tokens[$opener]['parenthesis_owner']) === false) { - // Check if this is a USE statement for a closure. - $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), null, true); - if ($tokens[$prev]['code'] === T_USE) { - $inFunction = true; - break; - } - - continue; - } - - $owner = $tokens[$opener]['parenthesis_owner']; - if ($tokens[$owner]['code'] === T_FUNCTION - || $tokens[$owner]['code'] === T_CLOSURE - ) { - $inFunction = true; - break; - } - } - }//end if - - if ($inFunction === true) { + if (Conditions::isOOProperty($phpcsFile, $stackPtr) === false) { return $this->processVariable($phpcsFile, $stackPtr); } else { return $this->processMemberVar($phpcsFile, $stackPtr); diff --git a/src/Standards/Generic/Docs/Lists/DisallowLongListSyntaxStandard.xml b/src/Standards/Generic/Docs/Lists/DisallowLongListSyntaxStandard.xml new file mode 100644 index 0000000000..d1b0ea6109 --- /dev/null +++ b/src/Standards/Generic/Docs/Lists/DisallowLongListSyntaxStandard.xml @@ -0,0 +1,19 @@ + + + + + + + [$a, $b] = $array; + ]]> + + + list($a, $b) = $array; + ]]> + + + diff --git a/src/Standards/Generic/Docs/Lists/DisallowShortListSyntaxStandard.xml b/src/Standards/Generic/Docs/Lists/DisallowShortListSyntaxStandard.xml new file mode 100644 index 0000000000..db6e66e630 --- /dev/null +++ b/src/Standards/Generic/Docs/Lists/DisallowShortListSyntaxStandard.xml @@ -0,0 +1,19 @@ + + + + + + + list($a, $b) = $array; + ]]> + + + [$a, $b] = $array; + ]]> + + + diff --git a/src/Standards/Generic/Sniffs/Arrays/DisallowShortArraySyntaxSniff.php b/src/Standards/Generic/Sniffs/Arrays/DisallowShortArraySyntaxSniff.php index 8bd01ac9d9..98b1cccebe 100644 --- a/src/Standards/Generic/Sniffs/Arrays/DisallowShortArraySyntaxSniff.php +++ b/src/Standards/Generic/Sniffs/Arrays/DisallowShortArraySyntaxSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\TokenIs; class DisallowShortArraySyntaxSniff implements Sniff { @@ -39,13 +40,19 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { + $tokens = $phpcsFile->getTokens(); + + if (TokenIs::isShortList($phpcsFile, $stackPtr) === true) { + // No need to examine nested subs of this short list. + return $tokens[$stackPtr]['bracket_closer']; + } + $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'yes'); $error = 'Short array syntax is not allowed'; $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Found'); if ($fix === true) { - $tokens = $phpcsFile->getTokens(); $opener = $tokens[$stackPtr]['bracket_opener']; $closer = $tokens[$stackPtr]['bracket_closer']; diff --git a/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php b/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php index 0288063af1..c4292107b2 100644 --- a/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php +++ b/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Namespaces; class DuplicateClassNameSniff implements Sniff { @@ -22,6 +23,20 @@ class DuplicateClassNameSniff implements Sniff */ protected $foundClasses = []; + /** + * The name of the last file seen. + * + * @var string + */ + private $currentFile = ''; + + /** + * The name of the current namespace. + * + * @var string + */ + private $currentNamespace = ''; + /** * Registers the tokens that this sniff wants to listen for. @@ -46,9 +61,14 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); + $tokens = $phpcsFile->getTokens(); + $fileName = $phpcsFile->getFilename(); + + if ($fileName !== $this->currentFile) { + $this->currentNamespace = ''; + $this->currentFile = $fileName; + } - $namespace = ''; $findTokens = [ T_CLASS, T_INTERFACE, @@ -67,24 +87,21 @@ public function process(File $phpcsFile, $stackPtr) // Keep track of what namespace we are in. if ($tokens[$stackPtr]['code'] === T_NAMESPACE) { - $nsEnd = $phpcsFile->findNext( - [ - T_NS_SEPARATOR, - T_STRING, - T_WHITESPACE, - ], - ($stackPtr + 1), - null, - true - ); - - $namespace = trim($phpcsFile->getTokensAsString(($stackPtr + 1), ($nsEnd - $stackPtr - 1))); - $stackPtr = $nsEnd; + $newNamespace = Namespaces::getDeclaredName($phpcsFile, $stackPtr); + if ($newNamespace !== false) { + $this->currentNamespace = $newNamespace; + $stackPtr = $phpcsFile->findNext(Namespaces::$statementClosers, ($stackPtr + 1)); + + if ($tokens[$stackPtr]['code'] === T_CLOSE_TAG) { + // Namespace declaration ended on a close tag. + return; + } + } } else { $nameToken = $phpcsFile->findNext(T_STRING, $stackPtr); $name = $tokens[$nameToken]['content']; - if ($namespace !== '') { - $name = $namespace.'\\'.$name; + if ($this->currentNamespace !== '') { + $name = $this->currentNamespace.'\\'.$name; } $compareName = strtolower($name); diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/EmptyPHPStatementSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/EmptyPHPStatementSniff.php index 03a6f09985..c9707ee9a1 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/EmptyPHPStatementSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/EmptyPHPStatementSniff.php @@ -14,6 +14,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; use PHP_CodeSniffer\Util\Tokens; class EmptyPHPStatementSniff implements Sniff @@ -61,15 +62,9 @@ public function process(File $phpcsFile, $stackPtr) return; } - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - $nested = $tokens[$stackPtr]['nested_parenthesis']; - $lastCloser = array_pop($nested); - if (isset($tokens[$lastCloser]['parenthesis_owner']) === true - && $tokens[$tokens[$lastCloser]['parenthesis_owner']]['code'] === T_FOR - ) { - // Empty for() condition. - return; - } + if (Parentheses::lastOwnerIn($phpcsFile, $stackPtr, T_FOR) !== false) { + // Empty for() condition. + return; } $fix = $phpcsFile->addFixableWarning( diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php index 07a9b03ef0..e0ac052eef 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php @@ -18,6 +18,9 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; +use PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations; use PHP_CodeSniffer\Util\Tokens; class UnusedFunctionParameterSniff implements Sniff @@ -61,10 +64,10 @@ public function process(File $phpcsFile, $stackPtr) $errorCode = 'Found'; $implements = false; $extends = false; - $classPtr = $phpcsFile->getCondition($stackPtr, T_CLASS); + $classPtr = Conditions::getLastCondition($phpcsFile, $stackPtr, [T_CLASS, T_ANON_CLASS]); if ($classPtr !== false) { - $implements = $phpcsFile->findImplementedInterfaceNames($classPtr); - $extends = $phpcsFile->findExtendedClassName($classPtr); + $implements = ObjectDeclarations::findImplementedInterfaceNames($phpcsFile, $classPtr); + $extends = ObjectDeclarations::findExtendedClassName($phpcsFile, $classPtr); if ($extends !== false) { $errorCode .= 'InExtendedClass'; } else if ($implements !== false) { @@ -73,7 +76,7 @@ public function process(File $phpcsFile, $stackPtr) } $params = []; - $methodParams = $phpcsFile->getMethodParameters($stackPtr); + $methodParams = FunctionDeclarations::getParameters($phpcsFile, $stackPtr); // Skip when no parameters found. $methodParamsCount = count($methodParams); diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/UselessOverridingMethodSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/UselessOverridingMethodSniff.php index 39dcc1152a..9eb0b6ffe2 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/UselessOverridingMethodSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/UselessOverridingMethodSniff.php @@ -23,6 +23,8 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; use PHP_CodeSniffer\Util\Tokens; class UselessOverridingMethodSniff implements Sniff @@ -61,11 +63,16 @@ public function process(File $phpcsFile, $stackPtr) } // Get function name. - $methodName = $phpcsFile->getDeclarationName($stackPtr); + $methodName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); + if (empty($methodName) === true) { + // Live coding or parse error. + return; + } // Get all parameters from method signature. - $signature = []; - foreach ($phpcsFile->getMethodParameters($stackPtr) as $param) { + $signature = []; + $parameters = FunctionDeclarations::getParameters($phpcsFile, $stackPtr); + foreach ($parameters as $param) { $signature[] = $param['name']; } diff --git a/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php b/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php index 14cc192301..c2a60a5219 100644 --- a/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php +++ b/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Orthography; class DocCommentSniff implements Sniff { @@ -155,7 +156,7 @@ public function process(File $phpcsFile, $stackPtr) } } - if (preg_match('/^\p{Ll}/u', $shortContent) === 1) { + if (Orthography::isFirstCharLowercase($shortContent) === true) { $error = 'Doc comment short description must start with a capital letter'; $phpcsFile->addError($error, $short, 'ShortNotCapital'); } @@ -181,7 +182,9 @@ public function process(File $phpcsFile, $stackPtr) } } - if (preg_match('/^\p{Ll}/u', $tokens[$long]['content']) === 1) { + // Allow for long comments in list format. + $longComment = ltrim($tokens[$long]['content'], '-'); + if (Orthography::isFirstCharLowercase($longComment) === true) { $error = 'Doc comment long description must start with a capital letter'; $phpcsFile->addError($error, $long, 'LongNotCapital'); } diff --git a/src/Standards/Generic/Sniffs/Formatting/DisallowMultipleStatementsSniff.php b/src/Standards/Generic/Sniffs/Formatting/DisallowMultipleStatementsSniff.php index a91af474fc..262f70ab7c 100644 --- a/src/Standards/Generic/Sniffs/Formatting/DisallowMultipleStatementsSniff.php +++ b/src/Standards/Generic/Sniffs/Formatting/DisallowMultipleStatementsSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; class DisallowMultipleStatementsSniff implements Sniff { @@ -59,18 +60,8 @@ public function process(File $phpcsFile, $stackPtr) } while ($tokens[$prev]['code'] === T_PHPCS_IGNORE); // Ignore multiple statements in a FOR condition. - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - foreach ($tokens[$stackPtr]['nested_parenthesis'] as $bracket) { - if (isset($tokens[$bracket]['parenthesis_owner']) === false) { - // Probably a closure sitting inside a function call. - continue; - } - - $owner = $tokens[$bracket]['parenthesis_owner']; - if ($tokens[$owner]['code'] === T_FOR) { - return; - } - } + if (Parentheses::hasOwner($phpcsFile, $stackPtr, T_FOR) === true) { + return; } if ($tokens[$prev]['line'] === $tokens[$stackPtr]['line']) { diff --git a/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php b/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php index 4a68683235..4d9edf49c6 100644 --- a/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php +++ b/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php @@ -14,6 +14,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; use PHP_CodeSniffer\Util\Tokens; class MultipleStatementAlignmentSniff implements Sniff @@ -76,12 +77,8 @@ public function process(File $phpcsFile, $stackPtr) $tokens = $phpcsFile->getTokens(); // Ignore assignments used in a condition, like an IF or FOR. - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - foreach ($tokens[$stackPtr]['nested_parenthesis'] as $start => $end) { - if (isset($tokens[$start]['parenthesis_owner']) === true) { - return; - } - } + if (Parentheses::hasOwner($phpcsFile, $stackPtr, Tokens::$parenthesisOpeners) === true) { + return; } $lastAssign = $this->checkAlignment($phpcsFile, $stackPtr); @@ -216,12 +213,8 @@ public function checkAlignment($phpcsFile, $stackPtr, $end=null) } // Make sure it is not assigned inside a condition (eg. IF, FOR). - if (isset($tokens[$assign]['nested_parenthesis']) === true) { - foreach ($tokens[$assign]['nested_parenthesis'] as $start => $end) { - if (isset($tokens[$start]['parenthesis_owner']) === true) { - break(2); - } - } + if (Parentheses::hasOwner($phpcsFile, $assign, Tokens::$parenthesisOpeners) === true) { + break; } }//end if diff --git a/src/Standards/Generic/Sniffs/Functions/CallTimePassByReferenceSniff.php b/src/Standards/Generic/Sniffs/Functions/CallTimePassByReferenceSniff.php index 294346271c..b4c25b2756 100644 --- a/src/Standards/Generic/Sniffs/Functions/CallTimePassByReferenceSniff.php +++ b/src/Standards/Generic/Sniffs/Functions/CallTimePassByReferenceSniff.php @@ -11,6 +11,8 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; +use PHP_CodeSniffer\Util\Sniffs\TokenIs; use PHP_CodeSniffer\Util\Tokens; class CallTimePassByReferenceSniff implements Sniff @@ -97,8 +99,7 @@ public function process(File $phpcsFile, $stackPtr) // Make sure the variable belongs directly to this function call // and is not inside a nested function call or array. - $brackets = $tokens[$nextSeparator]['nested_parenthesis']; - $lastBracket = array_pop($brackets); + $lastBracket = Parentheses::getLastCloser($phpcsFile, $nextSeparator); if ($lastBracket !== $closeBracket) { continue; } @@ -111,7 +112,7 @@ public function process(File $phpcsFile, $stackPtr) ); if ($tokens[$tokenBefore]['code'] === T_BITWISE_AND) { - if ($phpcsFile->isReference($tokenBefore) === false) { + if (TokenIs::isReference($phpcsFile, $tokenBefore) === false) { continue; } diff --git a/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php b/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php index 495151ea38..47dbdfedb1 100644 --- a/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php +++ b/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; use PHP_CodeSniffer\Util\Tokens; class FunctionCallArgumentSpacingSniff implements Sniff @@ -108,8 +109,7 @@ public function process(File $phpcsFile, $stackPtr) // Make sure the comma or variable belongs directly to this function call, // and is not inside a nested function call or array. - $brackets = $tokens[$nextSeparator]['nested_parenthesis']; - $lastBracket = array_pop($brackets); + $lastBracket = Parentheses::getLastCloser($phpcsFile, $nextSeparator); if ($lastBracket !== $closeBracket) { continue; } diff --git a/src/Standards/Generic/Sniffs/Lists/DisallowLongListSyntaxSniff.php b/src/Standards/Generic/Sniffs/Lists/DisallowLongListSyntaxSniff.php new file mode 100644 index 0000000000..f05a52f4e3 --- /dev/null +++ b/src/Standards/Generic/Sniffs/Lists/DisallowLongListSyntaxSniff.php @@ -0,0 +1,78 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Sniffs\Lists; + +use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; + +class DisallowLongListSyntaxSniff implements Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return int[] + */ + public function register() + { + return [T_LIST]; + + }//end register() + + + /** + * Processes this test, 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(); + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($next === false || $tokens[$next]['code'] !== T_OPEN_PARENTHESIS) { + // Live coding or parse error. + return; + } + + $phpcsFile->recordMetric($stackPtr, 'Short list syntax used', 'no'); + + $error = 'Long list syntax is not allowed'; + if (isset($tokens[$next]['parenthesis_closer']) === false) { + // Live coding/parse error, just show the error, don't try and fix it. + $phpcsFile->addError($error, $stackPtr, 'Found'); + return; + } + + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Found'); + + if ($fix === true) { + $opener = $next; + $closer = $tokens[$next]['parenthesis_closer']; + + $phpcsFile->fixer->beginChangeset(); + + $phpcsFile->fixer->replaceToken($stackPtr, ''); + $phpcsFile->fixer->replaceToken($opener, '['); + $phpcsFile->fixer->replaceToken($closer, ']'); + + $phpcsFile->fixer->endChangeset(); + } + + }//end process() + + +}//end class diff --git a/src/Standards/Generic/Sniffs/Lists/DisallowShortListSyntaxSniff.php b/src/Standards/Generic/Sniffs/Lists/DisallowShortListSyntaxSniff.php new file mode 100644 index 0000000000..a9050d8a1c --- /dev/null +++ b/src/Standards/Generic/Sniffs/Lists/DisallowShortListSyntaxSniff.php @@ -0,0 +1,68 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Sniffs\Lists; + +use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\TokenIs; + +class DisallowShortListSyntaxSniff implements Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return int[] + */ + public function register() + { + return [T_OPEN_SHORT_ARRAY]; + + }//end register() + + + /** + * Processes this test, 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 (TokenIs::isShortList($phpcsFile, $stackPtr) === false) { + // No need to examine nested subs of this short array. + return $tokens[$stackPtr]['bracket_closer']; + } + + $phpcsFile->recordMetric($stackPtr, 'Short list syntax used', 'yes'); + + $error = 'Short list syntax is not allowed'; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Found'); + + if ($fix === true) { + $opener = $tokens[$stackPtr]['bracket_opener']; + $closer = $tokens[$stackPtr]['bracket_closer']; + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($opener, 'list('); + $phpcsFile->fixer->replaceToken($closer, ')'); + $phpcsFile->fixer->endChangeset(); + } + + }//end process() + + +}//end class diff --git a/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php index 6b4331be71..dceb1523e1 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php @@ -10,9 +10,11 @@ namespace PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions; use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; -use PHP_CodeSniffer\Util\Common; -use PHP_CodeSniffer\Util\Tokens; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; +use PHP_CodeSniffer\Util\Tokens; class CamelCapsFunctionNameSniff extends AbstractScopeSniff { @@ -20,54 +22,37 @@ class CamelCapsFunctionNameSniff extends AbstractScopeSniff /** * A list of all PHP magic methods. * + * Set from within the constructor. + * * @var array + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::$magicMethods instead. */ - protected $magicMethods = [ - 'construct' => true, - 'destruct' => true, - 'call' => true, - 'callstatic' => true, - 'get' => true, - 'set' => true, - 'isset' => true, - 'unset' => true, - 'sleep' => true, - 'wakeup' => true, - 'tostring' => true, - 'set_state' => true, - 'clone' => true, - 'invoke' => true, - 'debuginfo' => true, - ]; + protected $magicMethods = []; /** * A list of all PHP non-magic methods starting with a double underscore. * * These come from PHP modules such as SOAPClient. * + * Set from within the constructor. + * * @var array + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::$methodsDoubleUnderscore instead. */ - protected $methodsDoubleUnderscore = [ - 'dorequest' => true, - 'getcookies' => true, - 'getfunctions' => true, - 'getlastrequest' => true, - 'getlastrequestheaders' => true, - 'getlastresponse' => true, - 'getlastresponseheaders' => true, - 'gettypes' => true, - 'setcookie' => true, - 'setlocation' => true, - 'setsoapheaders' => true, - 'soapcall' => true, - ]; + protected $methodsDoubleUnderscore = []; /** * A list of all PHP magic functions. * + * Set from within the constructor. + * * @var array + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::$magicFunctions instead. */ - protected $magicFunctions = ['autoload' => true]; + protected $magicFunctions = []; /** * If TRUE, the string must not have two capital letters next to each other. @@ -82,6 +67,22 @@ class CamelCapsFunctionNameSniff extends AbstractScopeSniff */ public function __construct() { + // Preserve BC without code duplication. + $this->magicMethods = array_combine( + FunctionDeclarations::$magicMethods, + array_fill(0, count(FunctionDeclarations::$magicMethods), true) + ); + $this->magicFunctions = array_combine( + FunctionDeclarations::$magicFunctions, + array_fill(0, count(FunctionDeclarations::$magicFunctions), true) + ); + + $methodsDoubleUnderscore = array_keys(FunctionDeclarations::$methodsDoubleUnderscore); + foreach ($methodsDoubleUnderscore as $method) { + $method = ltrim($method, '_'); + $this->methodsDoubleUnderscore[$method] = true; + } + parent::__construct(Tokens::$ooScopeTokens, [T_FUNCTION], true); }//end __construct() @@ -102,35 +103,27 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop $tokens = $phpcsFile->getTokens(); // Determine if this is a function which needs to be examined. - $conditions = $tokens[$stackPtr]['conditions']; - end($conditions); - $deepestScope = key($conditions); + $deepestScope = Conditions::getLastCondition($phpcsFile, $stackPtr); if ($deepestScope !== $currScope) { return; } - $methodName = $phpcsFile->getDeclarationName($stackPtr); - if ($methodName === null) { - // Ignore closures. + $methodName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); + if (empty($methodName) === true) { + // Live coding or parse error. return; } - $className = $phpcsFile->getDeclarationName($currScope); + $className = ConstructNames::getDeclarationName($phpcsFile, $currScope); if (isset($className) === false) { $className = '[Anonymous Class]'; } $errorData = [$className.'::'.$methodName]; - $methodNameLc = strtolower($methodName); - $classNameLc = strtolower($className); - - // Is this a magic method. i.e., is prefixed with "__" ? + // Check is this method is prefixed with "__" and not magic. if (preg_match('|^__[^_]|', $methodName) !== 0) { - $magicPart = substr($methodNameLc, 2); - if (isset($this->magicMethods[$magicPart]) === true - || isset($this->methodsDoubleUnderscore[$magicPart]) === true - ) { + if (FunctionDeclarations::isSpecialMethodName($methodName) === true) { return; } @@ -138,6 +131,9 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop $phpcsFile->addError($error, $stackPtr, 'MethodDoubleUnderscore', $errorData); } + $methodNameLc = strtolower($methodName); + $classNameLc = strtolower($className); + // PHP4 constructors are allowed to break our rules. if ($methodNameLc === $classNameLc) { return; @@ -151,8 +147,8 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop // Ignore first underscore in methods prefixed with "_". $methodName = ltrim($methodName, '_'); - $methodProps = $phpcsFile->getMethodProperties($stackPtr); - if (Common::isCamelCaps($methodName, false, true, $this->strict) === false) { + $methodProps = FunctionDeclarations::getProperties($phpcsFile, $stackPtr); + if (ConstructNames::isCamelCaps($methodName, false, true, $this->strict) === false) { if ($methodProps['scope_specified'] === true) { $error = '%s method name "%s" is not in camel caps format'; $data = [ @@ -185,18 +181,17 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop */ protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) { - $functionName = $phpcsFile->getDeclarationName($stackPtr); - if ($functionName === null) { - // Ignore closures. + $functionName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); + if (empty($functionName) === true) { + // Live coding or parse error. return; } $errorData = [$functionName]; - // Is this a magic function. i.e., it is prefixed with "__". + // Check is this function is prefixed with "__" and not magic. if (preg_match('|^__[^_]|', $functionName) !== 0) { - $magicPart = strtolower(substr($functionName, 2)); - if (isset($this->magicFunctions[$magicPart]) === true) { + if (FunctionDeclarations::isMagicFunctionName($functionName) === true) { return; } @@ -207,7 +202,7 @@ protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) // Ignore first underscore in functions prefixed with "_". $functionName = ltrim($functionName, '_'); - if (Common::isCamelCaps($functionName, false, true, $this->strict) === false) { + if (ConstructNames::isCamelCaps($functionName, false, true, $this->strict) === false) { $error = 'Function name "%s" is not in camel caps format'; $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $errorData); $phpcsFile->recordMetric($stackPtr, 'CamelCase function name', 'no'); diff --git a/src/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php index 2b233c9d5d..628ae466c7 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php @@ -15,6 +15,9 @@ use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations; class ConstructorNameSniff extends AbstractScopeSniff { @@ -59,20 +62,18 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop $tokens = $phpcsFile->getTokens(); // Determine if this is a function which needs to be examined. - $conditions = $tokens[$stackPtr]['conditions']; - end($conditions); - $deepestScope = key($conditions); + $deepestScope = Conditions::getLastCondition($phpcsFile, $stackPtr); if ($deepestScope !== $currScope) { return; } - $className = strtolower($phpcsFile->getDeclarationName($currScope)); + $className = strtolower(ConstructNames::getDeclarationName($phpcsFile, $currScope)); if ($className !== $this->currentClass) { $this->loadFunctionNamesInScope($phpcsFile, $currScope); $this->currentClass = $className; } - $methodName = strtolower($phpcsFile->getDeclarationName($stackPtr)); + $methodName = strtolower(ConstructNames::getDeclarationName($phpcsFile, $stackPtr)); if ($methodName === $className) { if (in_array('__construct', $this->functionList, true) === false) { @@ -89,7 +90,7 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop return; } - $parentClassName = strtolower($phpcsFile->findExtendedClassName($currScope)); + $parentClassName = strtolower(ObjectDeclarations::findExtendedClassName($phpcsFile, $currScope)); if ($parentClassName === false) { return; } @@ -144,7 +145,7 @@ protected function loadFunctionNamesInScope(File $phpcsFile, $currScope) continue; } - $this->functionList[] = trim(strtolower($phpcsFile->getDeclarationName($i))); + $this->functionList[] = trim(strtolower(ConstructNames::getDeclarationName($phpcsFile, $i))); if (isset($tokens[$i]['scope_closer']) !== false) { // Skip past nested functions and such. @@ -152,6 +153,9 @@ protected function loadFunctionNamesInScope(File $phpcsFile, $currScope) } } + // Remove any empties. + $this->functionList = array_filter($this->functionList); + }//end loadFunctionNamesInScope() diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php index a490d1a74d..dedf4668b7 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; use PHP_CodeSniffer\Util\Tokens; class LowerCaseTypeSniff implements Sniff @@ -85,7 +86,7 @@ public function process(File $phpcsFile, $stackPtr) 'object' => true, ]; - $props = $phpcsFile->getMethodProperties($stackPtr); + $props = FunctionDeclarations::getProperties($phpcsFile, $stackPtr); // Strip off potential nullable indication. $returnType = ltrim($props['return_type'], '?'); @@ -118,7 +119,7 @@ public function process(File $phpcsFile, $stackPtr) }//end if }//end if - $params = $phpcsFile->getMethodParameters($stackPtr); + $params = FunctionDeclarations::getParameters($phpcsFile, $stackPtr); if (empty($params) === true) { return; } diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/LanguageConstructSpacingSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/LanguageConstructSpacingSniff.php index 82bf536292..4265b3a0e4 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/LanguageConstructSpacingSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/LanguageConstructSpacingSniff.php @@ -12,7 +12,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Util\Common; -use PHP_CodeSniffer\Util\Tokens; +use PHP_CodeSniffer\Util\Sniffs\Namespaces; class LanguageConstructSpacingSniff implements Sniff { @@ -69,12 +69,11 @@ public function process(File $phpcsFile, $stackPtr) } $content = $tokens[$stackPtr]['content']; - if ($tokens[$stackPtr]['code'] === T_NAMESPACE) { - $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); - if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === T_NS_SEPARATOR) { - // Namespace keyword used as operator, not as the language construct. - return; - } + if ($tokens[$stackPtr]['code'] === T_NAMESPACE + && Namespaces::isDeclaration($phpcsFile, $stackPtr) === false + ) { + // Namespace keyword used as operator, not as the language construct; or live coding. + return; } if ($tokens[$stackPtr]['code'] === T_YIELD_FROM diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php index 14ecb27c37..0b5c9b61aa 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php @@ -11,6 +11,8 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; use PHP_CodeSniffer\Util\Tokens; use PHP_CodeSniffer\Config; @@ -70,7 +72,7 @@ class ScopeIndentSniff implements Sniff * or PHP open/close tags can escape from here and have their own * rules elsewhere. * - * @var int[] + * @var string[] */ public $ignoreIndentationTokens = []; @@ -80,14 +82,14 @@ class ScopeIndentSniff implements Sniff * This is a cached copy of the public version of this var, which * can be set in a ruleset file, and some core ignored tokens. * - * @var int[] + * @var (integer|string)[] */ private $ignoreIndentation = []; /** * Any scope openers that should not cause an indent. * - * @var int[] + * @var (integer|string)[] */ protected $nonIndentingScopes = []; @@ -280,28 +282,25 @@ public function process(File $phpcsFile, $stackPtr) $parenOpener = $tokens[$parenCloser]['parenthesis_opener']; if ($tokens[$parenCloser]['line'] !== $tokens[$parenOpener]['line']) { - $parens = 0; - if (isset($tokens[$parenCloser]['nested_parenthesis']) === true - && empty($tokens[$parenCloser]['nested_parenthesis']) === false - ) { - $parens = $tokens[$parenCloser]['nested_parenthesis']; - end($parens); - $parens = key($parens); + $parens = 0; + $lastParens = Parentheses::getLastOpener($phpcsFile, $parenCloser); + if ($lastParens !== false) { + $parens = $lastParens; if ($this->debug === true) { $line = $tokens[$parens]['line']; echo "\t* token has nested parenthesis $parens on line $line *".PHP_EOL; } } - $condition = 0; - if (isset($tokens[$parenCloser]['conditions']) === true - && empty($tokens[$parenCloser]['conditions']) === false + unset($lastParens); + + $condition = 0; + $endCondition = Conditions::getLastCondition($phpcsFile, $parenCloser); + if ($endCondition !== false && (isset($tokens[$parenCloser]['parenthesis_owner']) === false || $parens > 0) ) { - $condition = $tokens[$parenCloser]['conditions']; - end($condition); - $condition = key($condition); + $condition = $endCondition; if ($this->debug === true) { $line = $tokens[$condition]['line']; $type = $tokens[$condition]['type']; @@ -327,8 +326,7 @@ public function process(File $phpcsFile, $stackPtr) $exact = false; - $lastOpenTagConditions = array_keys($tokens[$lastOpenTag]['conditions']); - $lastOpenTagCondition = array_pop($lastOpenTagConditions); + $lastOpenTagCondition = Conditions::getLastCondition($phpcsFile, $lastOpenTag); if ($condition > 0 && $lastOpenTagCondition === $condition) { if ($this->debug === true) { @@ -566,9 +564,7 @@ public function process(File $phpcsFile, $stackPtr) && $tokens[$checkToken]['scope_opener'] === $checkToken)) ) { if (empty($tokens[$checkToken]['conditions']) === false) { - $condition = $tokens[$checkToken]['conditions']; - end($condition); - $condition = key($condition); + $condition = Conditions::getLastCondition($phpcsFile, $checkToken); } else { $condition = $tokens[$checkToken]['scope_condition']; } @@ -715,26 +711,21 @@ public function process(File $phpcsFile, $stackPtr) } } - $parens = 0; - if (isset($tokens[$scopeCloser]['nested_parenthesis']) === true - && empty($tokens[$scopeCloser]['nested_parenthesis']) === false - ) { - $parens = $tokens[$scopeCloser]['nested_parenthesis']; - end($parens); - $parens = key($parens); + $parens = 0; + $lastParens = Parentheses::getLastOpener($phpcsFile, $scopeCloser); + if ($lastParens !== false) { + $parens = $lastParens; if ($this->debug === true) { $line = $tokens[$parens]['line']; echo "\t* token has nested parenthesis $parens on line $line *".PHP_EOL; } } + unset($lastParens); + $condition = 0; - if (isset($tokens[$scopeCloser]['conditions']) === true - && empty($tokens[$scopeCloser]['conditions']) === false - ) { - $condition = $tokens[$scopeCloser]['conditions']; - end($condition); - $condition = key($condition); + if (empty($tokens[$scopeCloser]['conditions']) === false) { + $condition = Conditions::getLastCondition($phpcsFile, $scopeCloser); if ($this->debug === true) { $line = $tokens[$condition]['line']; $type = $tokens[$condition]['type']; @@ -886,8 +877,7 @@ public function process(File $phpcsFile, $stackPtr) if ($close !== false && $tokens[$checkToken]['conditions'] === $tokens[$close]['conditions'] ) { - $conditions = array_keys($tokens[$checkToken]['conditions']); - $lastCondition = array_pop($conditions); + $lastCondition = Conditions::getLastCondition($phpcsFile, $checkToken); $lastOpener = $tokens[$lastCondition]['scope_opener']; $lastCloser = $tokens[$lastCondition]['scope_closer']; if ($tokens[$lastCloser]['line'] !== $tokens[$checkToken]['line'] @@ -1248,13 +1238,9 @@ public function process(File $phpcsFile, $stackPtr) $object = 0; if ($phpcsFile->tokenizerType === 'JS') { - $conditions = $tokens[$i]['conditions']; - krsort($conditions, SORT_NUMERIC); - foreach ($conditions as $token => $condition) { - if ($condition === T_OBJECT) { - $object = $token; - break; - } + $objectCondition = Conditions::getLastCondition($phpcsFile, $i, T_OBJECT); + if ($objectCondition !== false) { + $object = $objectCondition; } if ($this->debug === true && $object !== 0) { @@ -1263,26 +1249,21 @@ public function process(File $phpcsFile, $stackPtr) } } - $parens = 0; - if (isset($tokens[$i]['nested_parenthesis']) === true - && empty($tokens[$i]['nested_parenthesis']) === false - ) { - $parens = $tokens[$i]['nested_parenthesis']; - end($parens); - $parens = key($parens); + $parens = 0; + $lastParens = Parentheses::getLastOpener($phpcsFile, $i); + if ($lastParens !== false) { + $parens = $lastParens; if ($this->debug === true) { $line = $tokens[$parens]['line']; echo "\t* token has nested parenthesis $parens on line $line *".PHP_EOL; } } + unset($lastParens); + $condition = 0; - if (isset($tokens[$i]['conditions']) === true - && empty($tokens[$i]['conditions']) === false - ) { - $condition = $tokens[$i]['conditions']; - end($condition); - $condition = key($condition); + if (empty($tokens[$i]['conditions']) === false) { + $condition = Conditions::getLastCondition($phpcsFile, $i); if ($this->debug === true) { $line = $tokens[$condition]['line']; $type = $tokens[$condition]['type']; diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc index 2a0ef0425d..35e8e77a36 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc @@ -56,3 +56,15 @@ $var = [ 2 => 'two', /* three */ 3 => 'three', ]; + +// phpcs:set Generic.Arrays.ArrayIndent indent 4 + +// Short lists should not be touched. +[ + $a, + [ + $b, + $c, + $d, + ], +] = $array; diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed index 138565a7c7..652153d8cc 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed @@ -57,3 +57,15 @@ $var = [ 2 => 'two', /* three */ 3 => 'three', ]; + +// phpcs:set Generic.Arrays.ArrayIndent indent 4 + +// Short lists should not be touched. +[ + $a, + [ + $b, + $c, + $d, + ], +] = $array; diff --git a/src/Standards/Generic/Tests/Arrays/DisallowShortArraySyntaxUnitTest.inc b/src/Standards/Generic/Tests/Arrays/DisallowShortArraySyntaxUnitTest.inc index 74f27d8d47..0b86eecf33 100644 --- a/src/Standards/Generic/Tests/Arrays/DisallowShortArraySyntaxUnitTest.inc +++ b/src/Standards/Generic/Tests/Arrays/DisallowShortArraySyntaxUnitTest.inc @@ -10,3 +10,6 @@ $foo = [ 2, 3 ]; + +// Short list, not short array. +[$a, $b] = $array; diff --git a/src/Standards/Generic/Tests/Arrays/DisallowShortArraySyntaxUnitTest.inc.fixed b/src/Standards/Generic/Tests/Arrays/DisallowShortArraySyntaxUnitTest.inc.fixed index 7f68d4dc56..a585cef63c 100644 --- a/src/Standards/Generic/Tests/Arrays/DisallowShortArraySyntaxUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Arrays/DisallowShortArraySyntaxUnitTest.inc.fixed @@ -10,3 +10,6 @@ $foo = array( 2, 3 ); + +// Short list, not short array. +[$a, $b] = $array; diff --git a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.10.inc b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.10.inc new file mode 100644 index 0000000000..d5ab20e3ed --- /dev/null +++ b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.10.inc @@ -0,0 +1,6 @@ + + + + + + 1, 10 => 1, ]; - break; + case 'DuplicateClassNameUnitTest.2.inc': return [ 2 => 1, 3 => 1, 4 => 1, ]; - break; + case 'DuplicateClassNameUnitTest.5.inc': return [ 3 => 1, 7 => 1, ]; - break; + case 'DuplicateClassNameUnitTest.6.inc': return [10 => 1]; - break; + + case 'DuplicateClassNameUnitTest.8.inc': + return [ + 7 => 1, + 8 => 1, + ]; + + case 'DuplicateClassNameUnitTest.9.inc': + return [ + 3 => 1, + 4 => 1, + ]; + + case 'DuplicateClassNameUnitTest.11.inc': + return [13 => 1]; + default: return []; - break; }//end switch }//end getWarningList() diff --git a/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc b/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc index 3073e6fcdb..b7b6147a95 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc +++ b/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc @@ -121,3 +121,29 @@ function something($a) { function myCallback($a, $b, $c, $d) { return $a * $c; } + +$anonClass = new class() extends SomeClass { + public function something($a, $b) { + return $a * 2; + } +}; + +class ClassWithNestedAnonClass extends SomeClass { + public function getAnonClass() { + return new class() { + public function something($a, $b) { + return $a * 2; + } + }; + } +} + +class ClassWithNestedExtendedAnonClass { + public function getAnonClass() { + return new class() extends SomeClass { + public function something($a, $b) { + return $a * 2; + } + }; + } +} diff --git a/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.php b/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.php index 583e049864..a2f4743140 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.php +++ b/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.php @@ -49,6 +49,9 @@ public function getWarningList() 106 => 1, 117 => 1, 121 => 2, + 126 => 1, + 134 => 1, + 144 => 1, ]; }//end getWarningList() diff --git a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc index 81366272c9..e978b28a14 100644 --- a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc +++ b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc @@ -249,4 +249,18 @@ * @link http://pear.php.net/package/PHP_CodeSniffer */ +/** + * Short description. + * + * - Long description in list format, correctly capitalized. + * - Second item long description in list format. + */ + +/** + * Short description. + * + * - long description in list format, not capitalized + * - second item long description in list format + */ + /** No docblock close tag. Must be last test without new line. \ No newline at end of file diff --git a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc.fixed index 43ce064a5d..723ac73221 100644 --- a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc.fixed @@ -254,4 +254,18 @@ * @link http://pear.php.net/package/PHP_CodeSniffer */ +/** + * Short description. + * + * - Long description in list format, correctly capitalized. + * - Second item long description in list format. + */ + +/** + * Short description. + * + * - long description in list format, not capitalized + * - second item long description in list format + */ + /** No docblock close tag. Must be last test without new line. \ No newline at end of file diff --git a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js index b3283f7853..e59bdebbe0 100644 --- a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js +++ b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js @@ -248,3 +248,17 @@ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence * @link http://pear.php.net/package/PHP_CodeSniffer */ + +/** + * Short description. + * + * - Long description in list format, correctly capitalized. + * - Second item long description in list format. + */ + +/** + * Short description. + * + * - long description in list format, not capitalized + * - second item long description in list format + */ diff --git a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js.fixed b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js.fixed index 0df0687a2f..d32ab6cb75 100644 --- a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js.fixed +++ b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js.fixed @@ -253,3 +253,17 @@ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence * @link http://pear.php.net/package/PHP_CodeSniffer */ + +/** + * Short description. + * + * - Long description in list format, correctly capitalized. + * - Second item long description in list format. + */ + +/** + * Short description. + * + * - long description in list format, not capitalized + * - second item long description in list format + */ diff --git a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.php b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.php index 57937581ba..ddc774e6e7 100644 --- a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.php +++ b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.php @@ -88,6 +88,7 @@ public function getErrorList() 246 => 1, 248 => 1, 249 => 1, + 262 => 1, ]; }//end getErrorList() diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc index 7d0f9fb2de..4bb6eb5c7b 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc @@ -403,3 +403,11 @@ $foofoo = new Foo([ $b = new Bar(), $c = new Bar(), ]); + +// Handling of short list syntax. +foreach ($files as $file) { + $saves[$file] = array(); + $contents = stripslashes(file_get_contents($file)); + [$assetid, $time, $content] = explode("\n", $contents); + $saves[$file]['assetid'] = $assetid; +} \ No newline at end of file diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed index 191c83e3f4..9e22d489d0 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed @@ -403,3 +403,11 @@ $foofoo = new Foo([ $b = new Bar(), $c = new Bar(), ]); + +// Handling of short list syntax. +foreach ($files as $file) { + $saves[$file] = array(); + $contents = stripslashes(file_get_contents($file)); + [$assetid, $time, $content] = explode("\n", $contents); + $saves[$file]['assetid'] = $assetid; +} \ No newline at end of file diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.php b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.php index 4076515161..fed8b6b896 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.php +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.php @@ -111,6 +111,9 @@ public function getWarningList($testFile='MultipleStatementAlignmentUnitTest.inc 398 => 1, 399 => 1, 401 => 1, + 409 => 1, + 410 => 1, + 412 => 1, ]; break; case 'MultipleStatementAlignmentUnitTest.js': diff --git a/src/Standards/Generic/Tests/Lists/DisallowLongListSyntaxUnitTest.inc b/src/Standards/Generic/Tests/Lists/DisallowLongListSyntaxUnitTest.inc new file mode 100644 index 0000000000..9c450caf07 --- /dev/null +++ b/src/Standards/Generic/Tests/Lists/DisallowLongListSyntaxUnitTest.inc @@ -0,0 +1,21 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Tests\Lists; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +class DisallowLongListSyntaxUnitTest 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 [ + 2 => 1, + 4 => 2, + 6 => 2, + 7 => 1, + 9 => 1, + 16 => 1, + 21 => 1, + ]; + + }//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 diff --git a/src/Standards/Generic/Tests/Lists/DisallowShortListSyntaxUnitTest.inc b/src/Standards/Generic/Tests/Lists/DisallowShortListSyntaxUnitTest.inc new file mode 100644 index 0000000000..bec29ffa98 --- /dev/null +++ b/src/Standards/Generic/Tests/Lists/DisallowShortListSyntaxUnitTest.inc @@ -0,0 +1,17 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Tests\Lists; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +class DisallowShortListSyntaxUnitTest 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 [ + 6 => 1, + 8 => 2, + 9 => 2, + 10 => 1, + 12 => 1, + ]; + + }//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 diff --git a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc index 9bda11472e..76939c267f 100644 --- a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc +++ b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc @@ -33,42 +33,20 @@ function get_some_value() {} /* Test for magic functions */ -class Magic_Test { +class Magic_Test extends \SoapClient { function __construct() {} - function __destruct() {} - function __call($name, $args) {} - static function __callStatic($name, $args) {} - function __get($name) {} function __set($name, $value) {} - function __isset($name) {} - function __unset($name) {} - function __sleep() {} - function __wakeup() {} - function __toString() {} - function __set_state() {} - function __clone() {} + function __getCookies() {} function __autoload() {} - function __invoke() {} function __myFunction() {} function __my_function() {} } -function __construct() {} function __destruct() {} -function __call() {} function __callStatic() {} -function __get() {} -function __set() {} -function __isset() {} -function __unset() {} -function __sleep() {} -function __wakeup() {} -function __toString() {} function __set_state() {} -function __clone() {} function __autoload($class) {} -function __invoke() {} function __myFunction() {} function __my_function() {} @@ -90,68 +68,12 @@ class MyClass public function __construct() {} } -trait Foo -{ - function __call($name, $args) {} -} - -class Magic_Case_Test { - function __Construct() {} - function __isSet($name) {} - function __tostring() {} -} -function __autoLoad($class) {} - -class Foo extends \SoapClient -{ - public function __soapCall( - $functionName, - $arguments, - $options = array(), - $inputHeaders = null, - &$outputHeaders = array() - ) { - // body - } -} - -function __debugInfo() {} -class Foo { - function __debugInfo() {} -} - function ___tripleUnderscore() {} // Ok. class triple { public function ___tripleUnderscore() {} // Ok. } -/* Magic methods in anonymous classes. */ -$a = new class { - function __construct() {} - function __destruct() {} - function __call($name, $args) {} - static function __callStatic($name, $args) {} - function __get($name) {} - function __set($name, $value) {} - function __isset($name) {} - function __unset($name) {} - function __sleep() {} - function __wakeup() {} - function __toString() {} - function __set_state() {} - function __clone() {} - function __autoload() {} - function __invoke() {} - function __myFunction() {} - function __my_function() {} - -}; - -class FooBar extends \SoapClient { - public function __getCookies() {} -} - class Nested { public function getAnonymousClass() { return new class() { diff --git a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php index 502498d8bc..2ac640441c 100644 --- a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php +++ b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php @@ -26,43 +26,28 @@ class CamelCapsFunctionNameUnitTest extends AbstractSniffUnitTest public function getErrorList() { $errors = [ - 10 => 1, - 11 => 1, - 12 => 1, - 13 => 1, - 16 => 1, - 17 => 1, - 20 => 1, - 21 => 1, - 24 => 1, - 25 => 1, - 30 => 1, - 31 => 1, - 50 => 1, - 52 => 1, - 53 => 2, - 57 => 1, - 58 => 1, - 59 => 1, - 60 => 1, - 61 => 1, - 62 => 1, - 63 => 1, - 64 => 1, - 65 => 1, - 66 => 1, - 67 => 1, - 68 => 2, - 69 => 1, - 71 => 1, - 72 => 1, - 73 => 2, - 118 => 1, - 144 => 1, - 146 => 1, - 147 => 2, - 158 => 1, - 159 => 1, + 10 => 1, + 11 => 1, + 12 => 1, + 13 => 1, + 16 => 1, + 17 => 1, + 20 => 1, + 21 => 1, + 24 => 1, + 25 => 1, + 30 => 1, + 31 => 1, + 40 => 1, + 41 => 1, + 42 => 2, + 46 => 1, + 47 => 1, + 48 => 2, + 50 => 1, + 51 => 2, + 80 => 1, + 81 => 1, ]; return $errors; diff --git a/src/Standards/MySource/Sniffs/Channels/IncludeSystemSniff.php b/src/Standards/MySource/Sniffs/Channels/IncludeSystemSniff.php index 599be1b9c4..8ea4554bcc 100644 --- a/src/Standards/MySource/Sniffs/Channels/IncludeSystemSniff.php +++ b/src/Standards/MySource/Sniffs/Channels/IncludeSystemSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; use PHP_CodeSniffer\Util\Tokens; class IncludeSystemSniff extends AbstractScopeSniff @@ -94,7 +95,7 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop $includedClasses[$matches[2]] = true; // Or a system it implements. - $class = $phpcsFile->getCondition($stackPtr, T_CLASS); + $class = Conditions::getCondition($phpcsFile, $stackPtr, T_CLASS); $implements = $phpcsFile->findNext(T_IMPLEMENTS, $class, ($class + 10)); if ($implements !== false) { $implementsClass = $phpcsFile->findNext(T_STRING, $implements); @@ -123,13 +124,10 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop // Now go searching for includeSystem, includeAsset or require/include // calls outside our scope. If we are in a class, look outside the // class. If we are not, look outside the function. - $condPtr = $currScope; - if ($phpcsFile->hasCondition($stackPtr, T_CLASS) === true) { - foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condType) { - if ($condType === T_CLASS) { - break; - } - } + $condPtr = $currScope; + $classCondPtr = Conditions::getCondition($phpcsFile, $stackPtr, T_CLASS); + if ($classCondPtr !== false) { + $condPtr = $classCondPtr; } for ($i = 0; $i < $condPtr; $i++) { @@ -148,7 +146,7 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop // If we are in a testing class, we might have also included // some systems and classes in our setUp() method. $setupFunction = null; - if ($phpcsFile->hasCondition($stackPtr, T_CLASS) === true) { + if (Conditions::hasCondition($phpcsFile, $stackPtr, T_CLASS) === true) { foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condType) { if ($condType === T_CLASS) { // Is this is a testing class? diff --git a/src/Standards/MySource/Sniffs/Channels/UnusedSystemSniff.php b/src/Standards/MySource/Sniffs/Channels/UnusedSystemSniff.php index 6b10cd3ede..7782158172 100644 --- a/src/Standards/MySource/Sniffs/Channels/UnusedSystemSniff.php +++ b/src/Standards/MySource/Sniffs/Channels/UnusedSystemSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; class UnusedSystemSniff implements Sniff { @@ -78,14 +79,11 @@ public function process(File $phpcsFile, $stackPtr) if ($tokens[$stackPtr]['level'] === $level) { // We are still in the base level, so this is the first // time we have got here. - $conditions = array_keys($tokens[$stackPtr]['conditions']); - if (empty($conditions) === false) { - $cond = array_pop($conditions); - if ($tokens[$cond]['code'] === T_IF) { - $i = $tokens[$cond]['scope_closer']; - $level--; - continue; - } + $directScope = Conditions::validDirectScope($phpcsFile, $stackPtr, T_IF); + if ($directScope !== false) { + $i = $tokens[$directScope]['scope_closer']; + $level--; + continue; } } diff --git a/src/Standards/MySource/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/MySource/Sniffs/Commenting/FunctionCommentSniff.php index fd75bcb22e..c5f44d0d6a 100644 --- a/src/Standards/MySource/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/MySource/Sniffs/Commenting/FunctionCommentSniff.php @@ -12,8 +12,8 @@ namespace PHP_CodeSniffer\Standards\MySource\Sniffs\Commenting; use PHP_CodeSniffer\Standards\Squiz\Sniffs\Commenting\FunctionCommentSniff as SquizFunctionCommentSniff; -use PHP_CodeSniffer\Util\Tokens; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Comments; class FunctionCommentSniff extends SquizFunctionCommentSniff { @@ -32,12 +32,9 @@ public function process(File $phpcsFile, $stackPtr) { parent::process($phpcsFile, $stackPtr); - $tokens = $phpcsFile->getTokens(); - $find = Tokens::$methodPrefixes; - $find[] = T_WHITESPACE; - - $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true); - if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) { + $tokens = $phpcsFile->getTokens(); + $commentEnd = Comments::findFunctionComment($phpcsFile, $stackPtr); + if ($commentEnd === false || $tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) { return; } diff --git a/src/Standards/MySource/Sniffs/Objects/CreateWidgetTypeCallbackSniff.php b/src/Standards/MySource/Sniffs/Objects/CreateWidgetTypeCallbackSniff.php index 6224956b56..2fda8882cd 100644 --- a/src/Standards/MySource/Sniffs/Objects/CreateWidgetTypeCallbackSniff.php +++ b/src/Standards/MySource/Sniffs/Objects/CreateWidgetTypeCallbackSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; use PHP_CodeSniffer\Util\Tokens; class CreateWidgetTypeCallbackSniff implements Sniff @@ -133,17 +134,14 @@ public function process(File $phpcsFile, $stackPtr) // Just make sure those brackets dont belong to anyone, // like an IF or FOR statement. - foreach ($tokens[$i]['nested_parenthesis'] as $bracket) { - if (isset($tokens[$bracket]['parenthesis_owner']) === true) { - continue(2); - } + if (Parentheses::hasOwner($phpcsFile, $i, Tokens::$parenthesisOpeners) === true) { + continue; } // Note that we use this endBracket down further when checking // for a RETURN statement. - $nestedParens = $tokens[$i]['nested_parenthesis']; - $endBracket = end($nestedParens); - $bracket = key($nestedParens); + $endBracket = Parentheses::getLastOpener($phpcsFile, $i); + $bracket = Parentheses::getLastCloser($phpcsFile, $i); $prev = $phpcsFile->findPrevious( Tokens::$emptyTokens, diff --git a/src/Standards/MySource/Sniffs/PHP/AjaxNullComparisonSniff.php b/src/Standards/MySource/Sniffs/PHP/AjaxNullComparisonSniff.php index f86fa9aef2..f3ceb083e3 100644 --- a/src/Standards/MySource/Sniffs/PHP/AjaxNullComparisonSniff.php +++ b/src/Standards/MySource/Sniffs/PHP/AjaxNullComparisonSniff.php @@ -14,6 +14,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Comments; class AjaxNullComparisonSniff implements Sniff { @@ -42,11 +43,15 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); + $tokens = $phpcsFile->getTokens(); + $commentEnd = Comments::findFunctionComment($phpcsFile, $stackPtr); + if ($commentEnd === false || $tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) { + // Function doesn't have a doc comment or is using the wrong type of comment. + return; + } // Make sure it is an API function. We know this by the doc comment. - $commentEnd = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $stackPtr); - $commentStart = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, ($commentEnd - 1)); + $commentStart = $tokens[$commentEnd]['comment_opener']; $comment = $phpcsFile->getTokensAsString($commentStart, ($commentEnd - $commentStart)); if (strpos($comment, '* @api') === false) { return; diff --git a/src/Standards/MySource/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/MySource/Tests/Commenting/FunctionCommentUnitTest.inc index 19d5c5d4cc..2128a70b49 100644 --- a/src/Standards/MySource/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/MySource/Tests/Commenting/FunctionCommentUnitTest.inc @@ -98,4 +98,12 @@ private function functionCall() {} * @api read */ private function functionCall() {} -?> + +/** + * Short content. + * + * @api read + * + * @return void + */ +private /* comment */ function CorrectlyIdentifyDocblock() {} diff --git a/src/Standards/MySource/Tests/Commenting/FunctionCommentUnitTest.php b/src/Standards/MySource/Tests/Commenting/FunctionCommentUnitTest.php index 5cc43b6447..e4ffcb1c5b 100644 --- a/src/Standards/MySource/Tests/Commenting/FunctionCommentUnitTest.php +++ b/src/Standards/MySource/Tests/Commenting/FunctionCommentUnitTest.php @@ -26,11 +26,12 @@ class FunctionCommentUnitTest extends AbstractSniffUnitTest public function getErrorList() { return [ - 28 => 1, - 36 => 1, - 37 => 2, - 49 => 1, - 58 => 1, + 28 => 1, + 36 => 1, + 37 => 2, + 49 => 1, + 58 => 1, + 108 => 1, ]; }//end getErrorList() diff --git a/src/Standards/MySource/Tests/PHP/AjaxNullComparisonUnitTest.inc b/src/Standards/MySource/Tests/PHP/AjaxNullComparisonUnitTest.inc index aa95b03c70..faa9c74328 100644 --- a/src/Standards/MySource/Tests/PHP/AjaxNullComparisonUnitTest.inc +++ b/src/Standards/MySource/Tests/PHP/AjaxNullComparisonUnitTest.inc @@ -176,4 +176,21 @@ public static function addIssue( } }//end addIssue() -?> + + +/** + * Docblock. + * + * @param string $title Title of the new issue. + * + * @api write + * @api-permission public + */ +function apiFunction( $title = NULL ) { + if (empty($title)) {} +}//end apiFunction() + +function functionWithoutDocblock( $title ) { + if ($title === NULL) {} + +}//end functionWithoutDocblock() diff --git a/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php index 90e9eb8cab..483123ca2e 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php @@ -10,7 +10,8 @@ namespace PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting; use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Util\Tokens; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\Comments; class ClassCommentSniff extends FileCommentSniff { @@ -43,18 +44,13 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - $type = strtolower($tokens[$stackPtr]['content']); - $errorData = [$type]; - - $find = Tokens::$methodPrefixes; - $find[] = T_WHITESPACE; - - $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true); - if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG - && $tokens[$commentEnd]['code'] !== T_COMMENT - ) { - $errorData[] = $phpcsFile->getDeclarationName($stackPtr); + $tokens = $phpcsFile->getTokens(); + $type = strtolower($tokens[$stackPtr]['content']); + $errorData = [$type]; + $commentEnd = Comments::findOOStructureComment($phpcsFile, $stackPtr); + + if ($commentEnd === false) { + $errorData[] = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); $phpcsFile->addError('Missing doc comment for %s %s', $stackPtr, 'Missing', $errorData); $phpcsFile->recordMetric($stackPtr, 'Class has doc comment', 'no'); return; diff --git a/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php index 25896153d2..06d8495be5 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php @@ -11,7 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Util\Common; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; class FileCommentSniff implements Sniff { @@ -313,7 +313,7 @@ protected function processCategory($phpcsFile, array $tags) } $content = $tokens[($tag + 2)]['content']; - if (Common::isUnderscoreName($content) !== true) { + if (ConstructNames::isUnderscoreName($content) !== true) { $newContent = str_replace(' ', '_', $content); $nameBits = explode('_', $newContent); $firstBit = array_shift($nameBits); @@ -355,7 +355,7 @@ protected function processPackage($phpcsFile, array $tags) } $content = $tokens[($tag + 2)]['content']; - if (Common::isUnderscoreName($content) === true) { + if (ConstructNames::isUnderscoreName($content) === true) { continue; } @@ -408,7 +408,7 @@ protected function processSubpackage($phpcsFile, array $tags) } $content = $tokens[($tag + 2)]['content']; - if (Common::isUnderscoreName($content) === true) { + if (ConstructNames::isUnderscoreName($content) === true) { continue; } diff --git a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php index 2f93218847..9d73135c40 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php @@ -11,7 +11,9 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Util\Tokens; +use PHP_CodeSniffer\Util\Sniffs\Comments; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; class FunctionCommentSniff implements Sniff { @@ -40,26 +42,9 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - $find = Tokens::$methodPrefixes; - $find[] = T_WHITESPACE; - - $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true); - if ($tokens[$commentEnd]['code'] === T_COMMENT) { - // Inline comments might just be closing comments for - // control structures or functions instead of function comments - // using the wrong comment type. If there is other code on the line, - // assume they relate to that code. - $prev = $phpcsFile->findPrevious($find, ($commentEnd - 1), null, true); - if ($prev !== false && $tokens[$prev]['line'] === $tokens[$commentEnd]['line']) { - $commentEnd = $prev; - } - } - - if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG - && $tokens[$commentEnd]['code'] !== T_COMMENT - ) { - $function = $phpcsFile->getDeclarationName($stackPtr); + $commentEnd = Comments::findFunctionComment($phpcsFile, $stackPtr); + if ($commentEnd === false) { + $function = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); $phpcsFile->addError( 'Missing doc comment for function %s()', $stackPtr, @@ -72,6 +57,7 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->recordMetric($stackPtr, 'Function has doc comment', 'yes'); } + $tokens = $phpcsFile->getTokens(); if ($tokens[$commentEnd]['code'] === T_COMMENT) { $phpcsFile->addError('You must use "/**" style comments for a function comment', $stackPtr, 'WrongStyle'); return; @@ -116,7 +102,7 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) $tokens = $phpcsFile->getTokens(); // Skip constructor and destructor. - $methodName = $phpcsFile->getDeclarationName($stackPtr); + $methodName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); $isSpecialMethod = ($methodName === '__construct' || $methodName === '__destruct'); $return = null; @@ -279,7 +265,7 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) ]; }//end foreach - $realParams = $phpcsFile->getMethodParameters($stackPtr); + $realParams = FunctionDeclarations::getParameters($phpcsFile, $stackPtr); $foundParams = []; // We want to use ... for all variable length arguments, so add diff --git a/src/Standards/PEAR/Sniffs/Files/IncludingFileSniff.php b/src/Standards/PEAR/Sniffs/Files/IncludingFileSniff.php index cda11e4ae6..f503b11e7d 100644 --- a/src/Standards/PEAR/Sniffs/Files/IncludingFileSniff.php +++ b/src/Standards/PEAR/Sniffs/Files/IncludingFileSniff.php @@ -13,6 +13,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; use PHP_CodeSniffer\Util\Tokens; class IncludingFileSniff implements Sniff @@ -76,12 +77,8 @@ public function process(File $phpcsFile, $stackPtr) // Check to see if this including statement is within the parenthesis // of a condition. If that's the case then we need to process it as being // within a condition, as they are checking the return value. - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - foreach ($tokens[$stackPtr]['nested_parenthesis'] as $left => $right) { - if (isset($tokens[$left]['parenthesis_owner']) === true) { - $inCondition = true; - } - } + if (Parentheses::hasOwner($phpcsFile, $stackPtr, Tokens::$parenthesisOpeners) === true) { + $inCondition = true; } // Check to see if they are assigning the return value of this diff --git a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php index 0fa9cf4c30..9f2fad862b 100644 --- a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php +++ b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; use PHP_CodeSniffer\Util\Tokens; use PHP_CodeSniffer\Standards\Generic\Sniffs\Functions\OpeningFunctionBraceKernighanRitchieSniff; use PHP_CodeSniffer\Standards\Generic\Sniffs\Functions\OpeningFunctionBraceBsdAllmanSniff; @@ -105,7 +106,8 @@ public function process(File $phpcsFile, $stackPtr) // Unfinished closures are tokenized as T_FUNCTION however, and can be excluded // by checking for the scope_opener. if ($tokens[$stackPtr]['code'] === T_FUNCTION - && (isset($tokens[$stackPtr]['scope_opener']) === true || $phpcsFile->getMethodProperties($stackPtr)['has_body'] === false) + && (isset($tokens[$stackPtr]['scope_opener']) === true + || FunctionDeclarations::getProperties($phpcsFile, $stackPtr)['has_body'] === false) ) { if ($tokens[($openBracket - 1)]['content'] === $phpcsFile->eolChar) { $spaces = 'newline'; @@ -125,7 +127,7 @@ public function process(File $phpcsFile, $stackPtr) } // Must be no space before semicolon in abstract/interface methods. - if ($phpcsFile->getMethodProperties($stackPtr)['has_body'] === false) { + if (FunctionDeclarations::getProperties($phpcsFile, $stackPtr)['has_body'] === false) { $end = $phpcsFile->findNext(T_SEMICOLON, $closeBracket); if ($tokens[($end - 1)]['content'] === $phpcsFile->eolChar) { $spaces = 'newline'; diff --git a/src/Standards/PEAR/Sniffs/Functions/ValidDefaultValueSniff.php b/src/Standards/PEAR/Sniffs/Functions/ValidDefaultValueSniff.php index 20bd848878..c09dd9cf0e 100644 --- a/src/Standards/PEAR/Sniffs/Functions/ValidDefaultValueSniff.php +++ b/src/Standards/PEAR/Sniffs/Functions/ValidDefaultValueSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; class ValidDefaultValueSniff implements Sniff { @@ -46,7 +47,7 @@ public function process(File $phpcsFile, $stackPtr) // If there is a value without a default after this, it is an error. $defaultFound = false; - $params = $phpcsFile->getMethodParameters($stackPtr); + $params = FunctionDeclarations::getParameters($phpcsFile, $stackPtr); foreach ($params as $param) { if ($param['variable_length'] === true) { continue; diff --git a/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php b/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php index d2629f57e2..0169a43729 100644 --- a/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php +++ b/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -10,9 +10,11 @@ namespace PHP_CodeSniffer\Standards\PEAR\Sniffs\NamingConventions; use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; -use PHP_CodeSniffer\Util\Common; -use PHP_CodeSniffer\Util\Tokens; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; +use PHP_CodeSniffer\Util\Tokens; class ValidFunctionNameSniff extends AbstractScopeSniff { @@ -20,32 +22,24 @@ class ValidFunctionNameSniff extends AbstractScopeSniff /** * A list of all PHP magic methods. * + * Set from within the constructor. + * * @var array + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::$magicMethods instead. */ - protected $magicMethods = [ - 'construct' => true, - 'destruct' => true, - 'call' => true, - 'callstatic' => true, - 'get' => true, - 'set' => true, - 'isset' => true, - 'unset' => true, - 'sleep' => true, - 'wakeup' => true, - 'tostring' => true, - 'set_state' => true, - 'clone' => true, - 'invoke' => true, - 'debuginfo' => true, - ]; + protected $magicMethods = []; /** * A list of all PHP magic functions. * + * Set from within the constructor. + * * @var array + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::$magicFunctions instead. */ - protected $magicFunctions = ['autoload' => true]; + protected $magicFunctions = []; /** @@ -53,6 +47,16 @@ class ValidFunctionNameSniff extends AbstractScopeSniff */ public function __construct() { + // Preserve BC without code duplication. + $this->magicMethods = array_combine( + FunctionDeclarations::$magicMethods, + array_fill(0, count(FunctionDeclarations::$magicMethods), true) + ); + $this->magicFunctions = array_combine( + FunctionDeclarations::$magicFunctions, + array_fill(0, count(FunctionDeclarations::$magicFunctions), true) + ); + parent::__construct(Tokens::$ooScopeTokens, [T_FUNCTION], true); }//end __construct() @@ -73,33 +77,27 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop $tokens = $phpcsFile->getTokens(); // Determine if this is a function which needs to be examined. - $conditions = $tokens[$stackPtr]['conditions']; - end($conditions); - $deepestScope = key($conditions); + $deepestScope = Conditions::getLastCondition($phpcsFile, $stackPtr); if ($deepestScope !== $currScope) { return; } - $methodName = $phpcsFile->getDeclarationName($stackPtr); - if ($methodName === null) { - // Ignore closures. + $methodName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); + if (empty($methodName) === true) { + // Live coding or parse error. return; } - $className = $phpcsFile->getDeclarationName($currScope); + $className = ConstructNames::getDeclarationName($phpcsFile, $currScope); if (isset($className) === false) { $className = '[Anonymous Class]'; } $errorData = [$className.'::'.$methodName]; - $methodNameLc = strtolower($methodName); - $classNameLc = strtolower($className); - - // Is this a magic method. i.e., is prefixed with "__" ? + // Check is this method is prefixed with "__" and not magic. if (preg_match('|^__[^_]|', $methodName) !== 0) { - $magicPart = substr($methodNameLc, 2); - if (isset($this->magicMethods[$magicPart]) === true) { + if (FunctionDeclarations::isSpecialMethodName($methodName) === true) { return; } @@ -107,6 +105,9 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop $phpcsFile->addError($error, $stackPtr, 'MethodDoubleUnderscore', $errorData); } + $methodNameLc = strtolower($methodName); + $classNameLc = strtolower($className); + // PHP4 constructors are allowed to break our rules. if ($methodNameLc === $classNameLc) { return; @@ -117,7 +118,7 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop return; } - $methodProps = $phpcsFile->getMethodProperties($stackPtr); + $methodProps = FunctionDeclarations::getProperties($phpcsFile, $stackPtr); $scope = $methodProps['scope']; $scopeSpecified = $methodProps['scope_specified']; @@ -150,7 +151,7 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop $testMethodName = ltrim($methodName, '_'); - if (Common::isCamelCaps($testMethodName, false, true, false) === false) { + if (ConstructNames::isCamelCaps($testMethodName, false, true, false) === false) { if ($scopeSpecified === true) { $error = '%s method name "%s" is not in camel caps format'; $data = [ @@ -178,9 +179,9 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop */ protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) { - $functionName = $phpcsFile->getDeclarationName($stackPtr); - if ($functionName === null) { - // Ignore closures. + $functionName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); + if (empty($functionName) === true) { + // Live coding or parse error. return; } @@ -191,10 +192,9 @@ protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) $errorData = [$functionName]; - // Is this a magic function. i.e., it is prefixed with "__". + // Check is this function is prefixed with "__" and not magic. if (preg_match('|^__[^_]|', $functionName) !== 0) { - $magicPart = strtolower(substr($functionName, 2)); - if (isset($this->magicFunctions[$magicPart]) === true) { + if (FunctionDeclarations::isMagicFunctionName($functionName) === true) { return; } @@ -241,7 +241,7 @@ protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) $newCamelCapsPart = $camelCapsPart; // Every function must have a camel caps part, so check that first. - if (Common::isCamelCaps($camelCapsPart, false, true, false) === false) { + if (ConstructNames::isCamelCaps($camelCapsPart, false, true, false) === false) { $validName = false; $newCamelCapsPart = strtolower($camelCapsPart{0}).substr($camelCapsPart, 1); } diff --git a/src/Standards/PEAR/Sniffs/NamingConventions/ValidVariableNameSniff.php b/src/Standards/PEAR/Sniffs/NamingConventions/ValidVariableNameSniff.php index 26d25ae9ce..d7eaf91e31 100644 --- a/src/Standards/PEAR/Sniffs/NamingConventions/ValidVariableNameSniff.php +++ b/src/Standards/PEAR/Sniffs/NamingConventions/ValidVariableNameSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\AbstractVariableSniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Variables; class ValidVariableNameSniff extends AbstractVariableSniff { @@ -27,13 +28,12 @@ class ValidVariableNameSniff extends AbstractVariableSniff */ protected function processMemberVar(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - - $memberProps = $phpcsFile->getMemberProperties($stackPtr); + $memberProps = Variables::getMemberProperties($phpcsFile, $stackPtr); if (empty($memberProps) === true) { return; } + $tokens = $phpcsFile->getTokens(); $memberName = ltrim($tokens[$stackPtr]['content'], '$'); $scope = $memberProps['scope']; $scopeSpecified = $memberProps['scope_specified']; diff --git a/src/Standards/PEAR/Sniffs/WhiteSpace/ScopeIndentSniff.php b/src/Standards/PEAR/Sniffs/WhiteSpace/ScopeIndentSniff.php index 2620d20f2e..e846f3b6ab 100644 --- a/src/Standards/PEAR/Sniffs/WhiteSpace/ScopeIndentSniff.php +++ b/src/Standards/PEAR/Sniffs/WhiteSpace/ScopeIndentSniff.php @@ -17,7 +17,7 @@ class ScopeIndentSniff extends GenericScopeIndentSniff /** * Any scope openers that should not cause an indent. * - * @var int[] + * @var (integer|string)[] */ protected $nonIndentingScopes = [T_SWITCH]; diff --git a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc index 0ca87512f8..5fa8017097 100644 --- a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc +++ b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc @@ -117,4 +117,6 @@ interface Empty_Interface_Doc trait Empty_Trait_Doc { -}//end trait +}/** Unrelated end comment for previous structure using wrong comment style. */ + +class NoCommentConfusedWithTrailingEndComment {} diff --git a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.php b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.php index 9a4bcf7dd6..664ba65755 100644 --- a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.php +++ b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.php @@ -44,6 +44,7 @@ public function getErrorList() 96 => 5, 106 => 5, 116 => 5, + 122 => 1, ]; }//end getErrorList() diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc index 977004ebed..ebddf44fe5 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc @@ -370,3 +370,10 @@ function process(File $phpcsFile, $stackPtr) { }//end process() + +class FooBar { + /** + * Docblock + */ + public /* comment */ static function PickUpDocblockNotComment() {} +} diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index ded6799a7a..2b6c156e8e 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -370,3 +370,10 @@ function process(File $phpcsFile, $stackPtr) { }//end process() + +class FooBar { + /** + * Docblock + */ + public /* comment */ static function PickUpDocblockNotComment() {} +} diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php index cf755c8196..e8f0939584 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php @@ -69,6 +69,7 @@ public function getErrorList() 361 => 1, 363 => 1, 364 => 1, + 377 => 1, ]; }//end getErrorList() diff --git a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc index 78280cdd90..27922723a1 100644 --- a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc +++ b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc @@ -106,41 +106,19 @@ function _My_Package() {} /* Test for magic functions */ -class Magic_Test { - function __construct() {} - function __destruct() {} - function __call($name, $args) {} +class Magic_Test extends \SoapClient { static function __callStatic($name, $args) {} - function __get($name) {} - function __set($name, $value) {} - function __isset($name) {} - function __unset($name) {} - function __sleep() {} function __wakeup() {} - function __toString() {} - function __set_state() {} - function __clone() {} + function __getTypes() {} function __autoload() {} - function __invoke() {} function __myFunction() {} function __my_function() {} } -function __construct() {} -function __destruct() {} function __call() {} -function __callStatic() {} -function __get() {} function __set() {} -function __isset() {} -function __unset() {} -function __sleep() {} -function __wakeup() {} -function __toString() {} function __set_state() {} -function __clone() {} function __autoload($class) {} -function __invoke() {} function __myFunction() {} function __my_function() {} @@ -165,45 +143,8 @@ class MyClass public function __construct() {} } -trait Foo -{ - function __call($name, $args) {} -} - -class Magic_Case_Test { - function __Construct() {} - function __isSet($name) {} - function __tostring() {} -} -function __autoLoad($class) {} function _() {} -function __debugInfo() {} -class Foo { - function __debugInfo() {} -} - -/* Magic methods in anonymous classes. */ -$a = new class { - function __construct() {} - function __destruct() {} - function __call($name, $args) {} - static function __callStatic($name, $args) {} - function __get($name) {} - function __set($name, $value) {} - function __isset($name) {} - function __unset($name) {} - function __sleep() {} - function __wakeup() {} - function __toString() {} - function __set_state() {} - function __clone() {} - function __autoload() {} - function __invoke() {} - function __myFunction() {} - function __my_function() {} -}; - function send_system_email__to_user($body, $recipient){} class Nested { diff --git a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php index 9bb6de0d84..28ff6011a8 100644 --- a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php +++ b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php @@ -92,36 +92,21 @@ public function getErrorList() 102 => 2, 103 => 2, 104 => 2, - 123 => 1, - 125 => 1, - 126 => 2, - 129 => 1, - 130 => 1, - 131 => 1, - 132 => 1, - 133 => 1, - 134 => 1, - 135 => 1, - 136 => 1, - 137 => 1, - 138 => 1, - 139 => 1, - 140 => 3, - 141 => 1, - 143 => 1, - 144 => 1, - 145 => 3, - 147 => 2, - 148 => 1, - 149 => 1, - 181 => 1, - 201 => 1, - 203 => 1, - 204 => 2, - 207 => 2, - 212 => 1, - 213 => 1, - 214 => 1, + 113 => 1, + 114 => 1, + 115 => 2, + 118 => 1, + 119 => 1, + 120 => 3, + 122 => 1, + 123 => 3, + 125 => 2, + 126 => 1, + 127 => 1, + 148 => 2, + 153 => 1, + 154 => 1, + 155 => 1, ]; }//end getErrorList() diff --git a/src/Standards/PSR1/Sniffs/Classes/ClassDeclarationSniff.php b/src/Standards/PSR1/Sniffs/Classes/ClassDeclarationSniff.php index 858a626853..8fb0cea482 100644 --- a/src/Standards/PSR1/Sniffs/Classes/ClassDeclarationSniff.php +++ b/src/Standards/PSR1/Sniffs/Classes/ClassDeclarationSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Namespaces; class ClassDeclarationSniff implements Sniff { @@ -59,8 +60,8 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->recordMetric($stackPtr, 'One class per file', 'yes'); } - $namespace = $phpcsFile->findNext([T_NAMESPACE, T_CLASS, T_INTERFACE, T_TRAIT], 0); - if ($tokens[$namespace]['code'] !== T_NAMESPACE) { + $namespace = Namespaces::determineNamespace($phpcsFile, $stackPtr); + if ($namespace === '') { $error = 'Each %s must be in a namespace of at least one level (a top-level vendor name)'; $phpcsFile->addError($error, $stackPtr, 'MissingNamespace', $errorData); $phpcsFile->recordMetric($stackPtr, 'Class defined in namespace', 'no'); diff --git a/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php b/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php index 66dcbb4b24..3e7392c2f4 100644 --- a/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php +++ b/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Namespaces; use PHP_CodeSniffer\Util\Tokens; class SideEffectsSniff implements Sniff @@ -143,9 +144,8 @@ private function searchForConflict($phpcsFile, $start, $end, $tokens) continue; } - // Ignore entire namespace, declare, const and use statements. - if ($tokens[$i]['code'] === T_NAMESPACE - || $tokens[$i]['code'] === T_USE + // Ignore entire declare, const and use statements. + if ($tokens[$i]['code'] === T_USE || $tokens[$i]['code'] === T_DECLARE || $tokens[$i]['code'] === T_CONST ) { @@ -161,11 +161,41 @@ private function searchForConflict($phpcsFile, $start, $end, $tokens) continue; } + // Ignore namespace declarations. + if ($tokens[$i]['code'] === T_NAMESPACE + && Namespaces::isDeclaration($phpcsFile, $i) === true + ) { + if (isset($tokens[$i]['scope_opener']) === true) { + $i = $tokens[$i]['scope_opener']; + } else { + $declarationCloser = $phpcsFile->findNext(Namespaces::$statementClosers, ($i + 1)); + if ($declarationCloser !== false) { + $i = $declarationCloser; + } + } + + continue; + } + + // Ignore the close brace of a scoped namespace declaration. + if ($tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET + && isset($tokens[$i]['scope_condition']) === true + && $tokens[$tokens[$i]['scope_condition']]['code'] === T_NAMESPACE + ) { + continue; + } + // Ignore function/class prefixes. if (isset(Tokens::$methodPrefixes[$tokens[$i]['code']]) === true) { continue; } + // Ignore closures. + if ($tokens[$i]['code'] === T_CLOSURE) { + $i = $tokens[$i]['scope_closer']; + continue; + } + // Ignore anon classes. if ($tokens[$i]['code'] === T_ANON_CLASS) { $i = $tokens[$i]['scope_closer']; diff --git a/src/Standards/PSR1/Sniffs/Methods/CamelCapsMethodNameSniff.php b/src/Standards/PSR1/Sniffs/Methods/CamelCapsMethodNameSniff.php index 1670fd5762..0e4a3eeda1 100644 --- a/src/Standards/PSR1/Sniffs/Methods/CamelCapsMethodNameSniff.php +++ b/src/Standards/PSR1/Sniffs/Methods/CamelCapsMethodNameSniff.php @@ -10,8 +10,10 @@ namespace PHP_CodeSniffer\Standards\PSR1\Sniffs\Methods; use PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\CamelCapsFunctionNameSniff as GenericCamelCapsFunctionNameSniff; -use PHP_CodeSniffer\Util\Common; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; class CamelCapsMethodNameSniff extends GenericCamelCapsFunctionNameSniff { @@ -32,33 +34,26 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop $tokens = $phpcsFile->getTokens(); // Determine if this is a function which needs to be examined. - $conditions = $tokens[$stackPtr]['conditions']; - end($conditions); - $deepestScope = key($conditions); + $deepestScope = Conditions::getLastCondition($phpcsFile, $stackPtr); if ($deepestScope !== $currScope) { return; } - $methodName = $phpcsFile->getDeclarationName($stackPtr); - if ($methodName === null) { - // Ignore closures. + $methodName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); + if (empty($methodName) === true) { + // Live coding or parse error. return; } // Ignore magic methods. - if (preg_match('|^__[^_]|', $methodName) !== 0) { - $magicPart = strtolower(substr($methodName, 2)); - if (isset($this->magicMethods[$magicPart]) === true - || isset($this->methodsDoubleUnderscore[$magicPart]) === true - ) { - return; - } + if (FunctionDeclarations::isSpecialMethodName($methodName) === true) { + return; } $testName = ltrim($methodName, '_'); - if ($testName !== '' && Common::isCamelCaps($testName, false, true, false) === false) { + if ($testName !== '' && ConstructNames::isCamelCaps($testName, false, true, false) === false) { $error = 'Method name "%s" is not in camel caps format'; - $className = $phpcsFile->getDeclarationName($currScope); + $className = ConstructNames::getDeclarationName($phpcsFile, $currScope); if (isset($className) === false) { $className = '[Anonymous Class]'; } diff --git a/src/Standards/PSR1/Tests/Classes/ClassDeclarationUnitTest.3.inc b/src/Standards/PSR1/Tests/Classes/ClassDeclarationUnitTest.3.inc new file mode 100644 index 0000000000..b83445a338 --- /dev/null +++ b/src/Standards/PSR1/Tests/Classes/ClassDeclarationUnitTest.3.inc @@ -0,0 +1,4 @@ + 1, + 3 => 2, + ]; + + case 'ClassDeclarationUnitTest.2.inc': return []; - } - return [ - 2 => 1, - 3 => 2, - ]; + case 'ClassDeclarationUnitTest.3.inc': + return [4 => 1]; + + case 'ClassDeclarationUnitTest.4.inc': + return [3 => 1]; + + case 'ClassDeclarationUnitTest.5.inc': + return [4 => 1]; + + case 'ClassDeclarationUnitTest.6.inc': + return [8 => 2]; + }//end switch }//end getErrorList() diff --git a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.13.inc b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.13.inc new file mode 100644 index 0000000000..4bfe476d9a --- /dev/null +++ b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.13.inc @@ -0,0 +1,5 @@ + 1]; default: return []; diff --git a/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php index 0b9f43058f..e0ed1f4d75 100644 --- a/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php @@ -10,8 +10,9 @@ namespace PHP_CodeSniffer\Standards\PSR2\Sniffs\Classes; use PHP_CodeSniffer\Sniffs\AbstractVariableSniff; -use PHP_CodeSniffer\Util\Tokens; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Variables; +use PHP_CodeSniffer\Util\Tokens; class PropertyDeclarationSniff extends AbstractVariableSniff { @@ -60,13 +61,8 @@ protected function processMemberVar(File $phpcsFile, $stackPtr) $phpcsFile->addError($error, $stackPtr, 'Multiple'); } - try { - $propertyInfo = $phpcsFile->getMemberProperties($stackPtr); - if (empty($propertyInfo) === true) { - return; - } - } catch (\Exception $e) { - // Turns out not to be a property after all. + $propertyInfo = Variables::getMemberProperties($phpcsFile, $stackPtr); + if (empty($propertyInfo) === true) { return; } diff --git a/src/Standards/PSR2/Sniffs/Methods/MethodDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Methods/MethodDeclarationSniff.php index abd1cd3550..c91432c981 100644 --- a/src/Standards/PSR2/Sniffs/Methods/MethodDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/Methods/MethodDeclarationSniff.php @@ -10,8 +10,10 @@ namespace PHP_CodeSniffer\Standards\PSR2\Sniffs\Methods; use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; -use PHP_CodeSniffer\Util\Tokens; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Tokens; class MethodDeclarationSniff extends AbstractScopeSniff { @@ -41,16 +43,14 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop $tokens = $phpcsFile->getTokens(); // Determine if this is a function which needs to be examined. - $conditions = $tokens[$stackPtr]['conditions']; - end($conditions); - $deepestScope = key($conditions); + $deepestScope = Conditions::getLastCondition($phpcsFile, $stackPtr); if ($deepestScope !== $currScope) { return; } - $methodName = $phpcsFile->getDeclarationName($stackPtr); - if ($methodName === null) { - // Ignore closures. + $methodName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); + if (empty($methodName) === true) { + // Live coding or parse error. return; } diff --git a/src/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php index 42c55d1d4a..3fb494df27 100644 --- a/src/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Namespaces; class NamespaceDeclarationSniff implements Sniff { @@ -39,9 +40,18 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { + if (Namespaces::isDeclaration($phpcsFile, $stackPtr) === false) { + // Namespace operator or live coding/parse error. + return; + } + $tokens = $phpcsFile->getTokens(); + $end = $phpcsFile->findNext(Namespaces::$statementClosers, ($stackPtr + 1)); + if ($tokens[$end]['code'] === T_CLOSE_TAG) { + // PHP close tags have their own rules. + return; + } - $end = $phpcsFile->findEndOfStatement($stackPtr); for ($i = ($end + 1); $i < ($phpcsFile->numTokens - 1); $i++) { if ($tokens[$i]['line'] === $tokens[$end]['line']) { continue; diff --git a/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php index b0f0b747c5..acd7c764e9 100644 --- a/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php @@ -11,6 +11,8 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Namespaces; +use PHP_CodeSniffer\Util\Sniffs\UseStatements; use PHP_CodeSniffer\Util\Tokens; class UseDeclarationSniff implements Sniff @@ -40,7 +42,7 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { - if ($this->shouldIgnoreUse($phpcsFile, $stackPtr) === true) { + if (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) { return; } @@ -166,28 +168,33 @@ public function process(File $phpcsFile, $stackPtr) }//end if // Make sure this USE comes after the first namespace declaration. - $prev = $phpcsFile->findPrevious(T_NAMESPACE, ($stackPtr - 1)); - if ($prev !== false) { + $namespacePtr = Namespaces::findNamespacePtr($phpcsFile, $stackPtr); + if ($namespacePtr !== false) { $first = $phpcsFile->findNext(T_NAMESPACE, 1); - if ($prev !== $first) { + while ($first !== false && Namespaces::isDeclaration($phpcsFile, $first) === false) { + $first = $phpcsFile->findNext(T_NAMESPACE, ($first + 1)); + } + + if ($namespacePtr !== $first) { $error = 'USE declarations must go after the first namespace declaration'; $phpcsFile->addError($error, $stackPtr, 'UseAfterNamespace'); } } else { $next = $phpcsFile->findNext(T_NAMESPACE, ($stackPtr + 1)); + while ($next !== false && Namespaces::isDeclaration($phpcsFile, $next) === false) { + $next = $phpcsFile->findNext(T_NAMESPACE, ($next + 1)); + } + if ($next !== false) { $error = 'USE declarations must go after the namespace declaration'; $phpcsFile->addError($error, $stackPtr, 'UseBeforeNamespace'); } - } + }//end if // Only interested in the last USE statement from here onwards. $nextUse = $phpcsFile->findNext(T_USE, ($stackPtr + 1)); - while ($this->shouldIgnoreUse($phpcsFile, $nextUse) === true) { + while ($nextUse !== false && UseStatements::isImportUse($phpcsFile, $nextUse) === false) { $nextUse = $phpcsFile->findNext(T_USE, ($nextUse + 1)); - if ($nextUse === false) { - break; - } } if ($nextUse !== false) { @@ -267,33 +274,4 @@ public function process(File $phpcsFile, $stackPtr) }//end process() - /** - * Check if this use statement is part of the namespace block. - * - * @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 bool - */ - private function shouldIgnoreUse($phpcsFile, $stackPtr) - { - $tokens = $phpcsFile->getTokens(); - - // Ignore USE keywords inside closures and during live coding. - $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); - if ($next === false || $tokens[$next]['code'] === T_OPEN_PARENTHESIS) { - return true; - } - - // Ignore USE keywords for traits. - if ($phpcsFile->hasCondition($stackPtr, [T_CLASS, T_TRAIT]) === true) { - return true; - } - - return false; - - }//end shouldIgnoreUse() - - }//end class diff --git a/src/Standards/PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc index 2500b6448e..5c0f707053 100644 --- a/src/Standards/PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc @@ -20,3 +20,14 @@ namespace Vendor\ Package; namespace Vendor\Package; + +$a = namespace\functionCall(); +$b = 'test'; + +namespace; /*comment*/ +namespace Vendor\Package { + class TestIt {} +} + +namespace ?> + + 1, 17 => 1, 19 => 1, + 27 => 1, + 28 => 1, ]; }//end getErrorList() diff --git a/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.1.inc b/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.1.inc index c4e83da440..4890794a8f 100644 --- a/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.1.inc +++ b/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.1.inc @@ -34,5 +34,9 @@ $x = $foo ? function ($foo) use /* comment */ ($bar): int { return 1; } : $bar; +$anonClass = new class() { + use TContainer; +}; + // Testcase must be on last line in the file. use \ No newline at end of file diff --git a/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.18.inc b/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.18.inc new file mode 100644 index 0000000000..aa6468aded --- /dev/null +++ b/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.18.inc @@ -0,0 +1,7 @@ + 1, 12 => 2, ]; + case 'UseDeclarationUnitTest.3.inc': return [ 4 => 1, 6 => 1, ]; + case 'UseDeclarationUnitTest.5.inc': return [ 5 => 1, @@ -55,6 +57,7 @@ public function getErrorList($testFile='') 30 => 1, 35 => 1, ]; + case 'UseDeclarationUnitTest.10.inc': case 'UseDeclarationUnitTest.11.inc': case 'UseDeclarationUnitTest.12.inc': @@ -63,12 +66,17 @@ public function getErrorList($testFile='') case 'UseDeclarationUnitTest.16.inc': case 'UseDeclarationUnitTest.17.inc': return [2 => 1]; + case 'UseDeclarationUnitTest.15.inc': return [ 3 => 1, 4 => 1, 5 => 1, ]; + + case 'UseDeclarationUnitTest.19.inc': + return [4 => 1]; + default: return []; }//end switch diff --git a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php index 56521702c1..767d2a4217 100644 --- a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php +++ b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php @@ -11,6 +11,8 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; +use PHP_CodeSniffer\Util\Sniffs\TokenIs; use PHP_CodeSniffer\Util\Tokens; class ArrayDeclarationSniff implements Sniff @@ -45,6 +47,13 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); + if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY + && TokenIs::isShortList($phpcsFile, $stackPtr) === true + ) { + // No need to examine nested subs of this short list. + return $tokens[$stackPtr]['bracket_closer']; + } + if ($tokens[$stackPtr]['code'] === T_ARRAY) { $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'no'); @@ -229,12 +238,7 @@ public function processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arra }//end while if ($valueCount > 0) { - $nestedParenthesis = false; - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - $nested = $tokens[$stackPtr]['nested_parenthesis']; - $nestedParenthesis = array_pop($nested); - } - + $nestedParenthesis = Parentheses::getLastCloser($phpcsFile, $stackPtr); if ($nestedParenthesis === false || $tokens[$nestedParenthesis]['line'] !== $tokens[$stackPtr]['line'] ) { diff --git a/src/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php b/src/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php index 3faca91db6..706809a0bf 100644 --- a/src/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php +++ b/src/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php @@ -16,6 +16,9 @@ use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\Namespaces; use PHP_CodeSniffer\Util\Tokens; class SelfMemberReferenceSniff extends AbstractScopeSniff @@ -46,14 +49,7 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop $tokens = $phpcsFile->getTokens(); // Determine if this is a double colon which needs to be examined. - $conditions = $tokens[$stackPtr]['conditions']; - $conditions = array_reverse($conditions, true); - foreach ($conditions as $conditionToken => $tokenCode) { - if ($tokenCode === T_CLASS || $tokenCode === T_ANON_CLASS || $tokenCode === T_CLOSURE) { - break; - } - } - + $conditionToken = Conditions::getLastCondition($phpcsFile, $stackPtr, [T_CLASS, T_ANON_CLASS, T_CLOSURE]); if ($conditionToken !== $currScope) { return; } @@ -81,18 +77,20 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($calledClassName - 1), null, true); if ($prevNonEmpty !== false && $tokens[$prevNonEmpty]['code'] === T_NS_SEPARATOR) { $declarationName = $this->getDeclarationNameWithNamespace($tokens, $calledClassName); - $declarationName = ltrim($declarationName, '\\'); - $fullQualifiedClassName = $this->getNamespaceOfScope($phpcsFile, $currScope); - if ($fullQualifiedClassName === '\\') { - $fullQualifiedClassName = ''; - } else { - $fullQualifiedClassName .= '\\'; - } + $fullQualifiedClassName = ''; - $fullQualifiedClassName .= $phpcsFile->getDeclarationName($currScope); + if (strpos($declarationName, '\\') === 0) { + // Only bother with building the FQCN when this is an absolute reference. + $fullQualifiedClassName = $this->getNamespaceOfScope($phpcsFile, $currScope); + if ($fullQualifiedClassName !== '\\') { + $fullQualifiedClassName .= '\\'; + } + + $fullQualifiedClassName .= ConstructNames::getDeclarationName($phpcsFile, $currScope); + } } else { - $declarationName = $phpcsFile->getDeclarationName($currScope); - $fullQualifiedClassName = $tokens[$calledClassName]['content']; + $declarationName = $tokens[$calledClassName]['content']; + $fullQualifiedClassName = ConstructNames::getDeclarationName($phpcsFile, $currScope); } if ($declarationName === $fullQualifiedClassName) { @@ -221,18 +219,7 @@ protected function getDeclarationNameWithNamespace(array $tokens, $stackPtr) */ protected function getNamespaceOfScope(File $phpcsFile, $stackPtr) { - $namespace = '\\'; - $namespaceDeclaration = $phpcsFile->findPrevious(T_NAMESPACE, $stackPtr); - - if ($namespaceDeclaration !== false) { - $endOfNamespaceDeclaration = $phpcsFile->findNext([T_SEMICOLON, T_OPEN_CURLY_BRACKET], $namespaceDeclaration); - $namespace = $this->getDeclarationNameWithNamespace( - $phpcsFile->getTokens(), - ($endOfNamespaceDeclaration - 1) - ); - } - - return $namespace; + return '\\'.Namespaces::determineNamespace($phpcsFile, $stackPtr); }//end getNamespaceOfScope() diff --git a/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php b/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php index 5e4515e876..fc8b849e72 100644 --- a/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php +++ b/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php @@ -11,7 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Util\Common; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; class ValidClassNameSniff implements Sniff { @@ -53,20 +53,14 @@ public function process(File $phpcsFile, $stackPtr) return; } - // Determine the name of the class or interface. Note that we cannot - // simply look for the first T_STRING because a class name - // starting with the number will be multiple tokens. - $opener = $tokens[$stackPtr]['scope_opener']; - $nameStart = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), $opener, true); - $nameEnd = $phpcsFile->findNext(T_WHITESPACE, $nameStart, $opener); - if ($nameEnd === false) { - $name = $tokens[$nameStart]['content']; - } else { - $name = trim($phpcsFile->getTokensAsString($nameStart, ($nameEnd - $nameStart))); + $name = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); + if (empty($name) === true) { + // Live coding or parse error. + return; } // Check for PascalCase format. - $valid = Common::isCamelCaps($name, true, true, false); + $valid = ConstructNames::isCamelCaps($name, true, true, false); if ($valid === false) { $type = ucfirst($tokens[$stackPtr]['content']); $error = '%s name "%s" is not in PascalCase format'; diff --git a/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php index dd735f6992..9464fc79df 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php @@ -11,6 +11,8 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Comments; +use PHP_CodeSniffer\Util\Sniffs\Orthography; use PHP_CodeSniffer\Util\Tokens; class BlockCommentSniff implements Sniff @@ -111,34 +113,8 @@ public function process(File $phpcsFile, $stackPtr) return; }//end if - $commentLines = [$stackPtr]; - $nextComment = $stackPtr; - $lastLine = $tokens[$stackPtr]['line']; - $commentString = $tokens[$stackPtr]['content']; - - // Construct the comment into an array. - while (($nextComment = $phpcsFile->findNext(T_WHITESPACE, ($nextComment + 1), null, true)) !== false) { - if ($tokens[$nextComment]['code'] !== $tokens[$stackPtr]['code'] - && isset(Tokens::$phpcsCommentTokens[$tokens[$nextComment]['code']]) === false - ) { - // Found the next bit of code. - break; - } - - if (($tokens[$nextComment]['line'] - 1) !== $lastLine) { - // Not part of the block. - break; - } - - $lastLine = $tokens[$nextComment]['line']; - $commentLines[] = $nextComment; - $commentString .= $tokens[$nextComment]['content']; - if ($tokens[$nextComment]['code'] === T_DOC_COMMENT_CLOSE_TAG - || substr($tokens[$nextComment]['content'], -2) === '*/' - ) { - break; - } - }//end while + $lastCommentToken = Comments::findEndOfComment($phpcsFile, $stackPtr); + $commentString = $phpcsFile->getTokensAsString($stackPtr, ($lastCommentToken - $stackPtr + 1)); $commentText = str_replace($phpcsFile->eolChar, '', $commentString); $commentText = trim($commentText, "/* \t"); @@ -147,9 +123,7 @@ public function process(File $phpcsFile, $stackPtr) $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Empty'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); - $phpcsFile->fixer->replaceToken($stackPtr, ''); - $lastToken = array_pop($commentLines); - for ($i = ($stackPtr + 1); $i <= $lastToken; $i++) { + for ($i = $stackPtr; $i <= $lastCommentToken; $i++) { $phpcsFile->fixer->replaceToken($i, ''); } @@ -159,7 +133,7 @@ public function process(File $phpcsFile, $stackPtr) return; } - if (count($commentLines) === 1) { + if ($stackPtr === $lastCommentToken) { $error = 'Single line block comment not allowed; use inline ("// text") comment instead'; // Only fix comments when they are the last token on a line. @@ -207,15 +181,16 @@ public function process(File $phpcsFile, $stackPtr) $hasStars = false; // Make sure first line isn't blank. - if (trim($tokens[$commentLines[1]]['content']) === '') { + $firstLine = ($stackPtr + 1); + if (trim($tokens[$firstLine]['content']) === '') { $error = 'Empty line not allowed at start of comment'; - $fix = $phpcsFile->addFixableError($error, $commentLines[1], 'HasEmptyLine'); + $fix = $phpcsFile->addFixableError($error, $firstLine, 'HasEmptyLine'); if ($fix === true) { - $phpcsFile->fixer->replaceToken($commentLines[1], ''); + $phpcsFile->fixer->replaceToken($firstLine, ''); } } else { // Check indentation of first line. - $content = $tokens[$commentLines[1]]['content']; + $content = $tokens[$firstLine]['content']; $commentText = ltrim($content); $leadingSpace = (strlen($content) - strlen($commentText)); @@ -237,10 +212,10 @@ public function process(File $phpcsFile, $stackPtr) ]; $error = 'First line of comment not aligned correctly; expected %s but found %s'; - $fix = $phpcsFile->addFixableError($error, $commentLines[1], 'FirstLineIndent', $data); + $fix = $phpcsFile->addFixableError($error, $firstLine, 'FirstLineIndent', $data); if ($fix === true) { - if (isset($tokens[$commentLines[1]]['orig_content']) === true - && $tokens[$commentLines[1]]['orig_content'][0] === "\t" + if (isset($tokens[$firstLine]['orig_content']) === true + && $tokens[$firstLine]['orig_content'][0] === "\t" ) { // Line is indented using tabs. $padding = str_repeat("\t", floor($expected / $this->tabWidth)); @@ -249,28 +224,22 @@ public function process(File $phpcsFile, $stackPtr) $padding = str_repeat(' ', $expected); } - $phpcsFile->fixer->replaceToken($commentLines[1], $padding.$commentText); + $phpcsFile->fixer->replaceToken($firstLine, $padding.$commentText); } }//end if - if (preg_match('/^\p{Ll}/u', $commentText) === 1) { + // Make sure this check also works with leading asterixes and list comments. + $commentTextNoLeadingMarks = ltrim($commentText, '*- '); + if (Orthography::isFirstCharLowercase($commentTextNoLeadingMarks) === true) { $error = 'Block comments must start with a capital letter'; - $phpcsFile->addError($error, $commentLines[1], 'NoCapital'); + $phpcsFile->addError($error, $firstLine, 'NoCapital'); } }//end if // Check that each line of the comment is indented past the star. - foreach ($commentLines as $line) { - // First and last lines (comment opener and closer) are handled separately. - if ($line === $commentLines[(count($commentLines) - 1)] || $line === $commentLines[0]) { - continue; - } - - // First comment line was handled above. - if ($line === $commentLines[1]) { - continue; - } - + // First and last lines (comment opener and closer) are handled separately. + // And the first actual comment line was handled above. + for ($line = ($stackPtr + 2); $line < $lastCommentToken; $line++) { // If it's empty, continue. if (trim($tokens[$line]['content']) === '') { continue; @@ -312,15 +281,14 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->fixer->replaceToken($line, $padding.$commentText); } }//end if - }//end foreach + }//end for // Finally, test the last line is correct. - $lastIndex = (count($commentLines) - 1); - $content = $tokens[$commentLines[$lastIndex]]['content']; + $content = $tokens[$lastCommentToken]['content']; $commentText = ltrim($content); if ($commentText !== '*/' && $commentText !== '**/') { $error = 'Comment closer must be on a new line'; - $phpcsFile->addError($error, $commentLines[$lastIndex], 'CloserSameLine'); + $phpcsFile->addError($error, $lastCommentToken, 'CloserSameLine'); } else { $leadingSpace = (strlen($content) - strlen($commentText)); @@ -341,7 +309,7 @@ public function process(File $phpcsFile, $stackPtr) ]; $error = 'Last line of comment aligned incorrectly; expected %s but found %s'; - $fix = $phpcsFile->addFixableError($error, $commentLines[$lastIndex], 'LastLineIndent', $data); + $fix = $phpcsFile->addFixableError($error, $lastCommentToken, 'LastLineIndent', $data); if ($fix === true) { if (isset($tokens[$line]['orig_content']) === true && $tokens[$line]['orig_content'][0] === "\t" @@ -353,7 +321,7 @@ public function process(File $phpcsFile, $stackPtr) $padding = str_repeat(' ', $expected); } - $phpcsFile->fixer->replaceToken($commentLines[$lastIndex], $padding.$commentText); + $phpcsFile->fixer->replaceToken($lastCommentToken, $padding.$commentText); } }//end if }//end if @@ -374,11 +342,10 @@ public function process(File $phpcsFile, $stackPtr) } } - $commentCloser = $commentLines[$lastIndex]; - $contentAfter = $phpcsFile->findNext(T_WHITESPACE, ($commentCloser + 1), null, true); - if ($contentAfter !== false && ($tokens[$contentAfter]['line'] - $tokens[$commentCloser]['line']) < 2) { + $contentAfter = $phpcsFile->findNext(T_WHITESPACE, ($lastCommentToken + 1), null, true); + if ($contentAfter !== false && ($tokens[$contentAfter]['line'] - $tokens[$lastCommentToken]['line']) < 2) { $error = 'Empty line required after block comment'; - $phpcsFile->addError($error, $commentCloser, 'NoEmptyLineAfter'); + $phpcsFile->addError($error, $lastCommentToken, 'NoEmptyLineAfter'); } }//end process() diff --git a/src/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php index 818c6d772d..d19bf9c6bc 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php @@ -19,7 +19,8 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Util\Tokens; +use PHP_CodeSniffer\Util\Sniffs\Comments; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; class ClassCommentSniff implements Sniff { @@ -48,15 +49,10 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - $find = Tokens::$methodPrefixes; - $find[] = T_WHITESPACE; - - $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true); - if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG - && $tokens[$commentEnd]['code'] !== T_COMMENT - ) { - $class = $phpcsFile->getDeclarationName($stackPtr); + $commentEnd = Comments::findOOStructureComment($phpcsFile, $stackPtr); + + if ($commentEnd === false) { + $class = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); $phpcsFile->addError('Missing doc comment for class %s', $stackPtr, 'Missing', [$class]); $phpcsFile->recordMetric($stackPtr, 'Class has doc comment', 'no'); return; @@ -64,6 +60,7 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->recordMetric($stackPtr, 'Class has doc comment', 'yes'); + $tokens = $phpcsFile->getTokens(); if ($tokens[$commentEnd]['code'] === T_COMMENT) { $phpcsFile->addError('You must use "/**" style comments for a class comment', $stackPtr, 'WrongStyle'); return; diff --git a/src/Standards/Squiz/Sniffs/Commenting/ClosingDeclarationCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/ClosingDeclarationCommentSniff.php index 63975b6077..f54b584485 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/ClosingDeclarationCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/ClosingDeclarationCommentSniff.php @@ -11,6 +11,9 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; class ClosingDeclarationCommentSniff implements Sniff { @@ -46,7 +49,7 @@ public function process(File $phpcsFile, $stackPtr) $tokens = $phpcsFile->getTokens(); if ($tokens[$stackPtr]['code'] === T_FUNCTION) { - $methodProps = $phpcsFile->getMethodProperties($stackPtr); + $methodProps = FunctionDeclarations::getProperties($phpcsFile, $stackPtr); // Abstract methods do not require a closing comment. if ($methodProps['is_abstract'] === true) { @@ -55,7 +58,7 @@ public function process(File $phpcsFile, $stackPtr) // If this function is in an interface then we don't require // a closing comment. - if ($phpcsFile->hasCondition($stackPtr, T_INTERFACE) === true) { + if (Conditions::hasCondition($phpcsFile, $stackPtr, T_INTERFACE) === true) { return; } @@ -65,7 +68,7 @@ public function process(File $phpcsFile, $stackPtr) return; } - $decName = $phpcsFile->getDeclarationName($stackPtr); + $decName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); $comment = '//end '.$decName.'()'; } else if ($tokens[$stackPtr]['code'] === T_CLASS) { $comment = '//end class'; diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index c2f90f4a13..13d46d765c 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -12,11 +12,25 @@ use PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting\FunctionCommentSniff as PEARFunctionCommentSniff; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Config; -use PHP_CodeSniffer\Util\Common; +use PHP_CodeSniffer\Util\Sniffs\Comments; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; +use PHP_CodeSniffer\Util\Sniffs\Orthography; class FunctionCommentSniff extends PEARFunctionCommentSniff { + /** + * Whether short or long form parameter/return types are preferred. + * + * Valid values: 'short' or 'long'. Defaults to 'long'. + * + * This applies to just two types: `int` vs `integer` and `bool` vs `boolean`. + * + * @var string + */ + public $typeFormat = 'long'; + /** * The current PHP version. * @@ -53,7 +67,7 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) } // Skip constructor and destructor. - $methodName = $phpcsFile->getDeclarationName($stackPtr); + $methodName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); $isSpecialMethod = ($methodName === '__construct' || $methodName === '__destruct'); if ($isSpecialMethod === true) { return; @@ -66,7 +80,7 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) $phpcsFile->addError($error, $return, 'MissingReturnType'); } else { // Support both a return type and a description. - preg_match('`^((?:\|?(?:array\([^\)]*\)|[\\\\a-z0-9\[\]]+))*)( .*)?`i', $content, $returnParts); + preg_match('`^((?:\|?(?:array(?:\([^\)]*\)|<[^>]*>+)|\(?[\\\\a-z0-9\[\]]+\)?))*)( .*)?`i', $content, $returnParts); if (isset($returnParts[1]) === false) { return; } @@ -74,16 +88,7 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) $returnType = $returnParts[1]; // Check return type (can be multiple, separated by '|'). - $typeNames = explode('|', $returnType); - $suggestedNames = []; - foreach ($typeNames as $i => $typeName) { - $suggestedName = Common::suggestType($typeName); - if (in_array($suggestedName, $suggestedNames, true) === false) { - $suggestedNames[] = $suggestedName; - } - } - - $suggestedType = implode('|', $suggestedNames); + $suggestedType = Comments::suggestTypeString($returnType, $this->typeFormat); if ($returnType !== $suggestedType) { $error = 'Expected "%s" but found "%s" for function return type'; $data = [ @@ -133,7 +138,7 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) } } }//end if - } else if ($returnType !== 'mixed' && in_array('void', $typeNames, true) === false) { + } else if ($returnType !== 'mixed' && strpos($suggestedType, 'void') === false) { // If return type is not void, there needs to be a return statement // somewhere in the function that returns something. if (isset($tokens[$stackPtr]['scope_closer']) === true) { @@ -226,14 +231,12 @@ protected function processThrows(File $phpcsFile, $stackPtr, $commentStart) } // Starts with a capital letter and ends with a fullstop. - $firstChar = $comment{0}; - if (strtoupper($firstChar) !== $firstChar) { + if (Orthography::isFirstCharCapitalized($comment) === false) { $error = '@throws tag comment must start with a capital letter'; $phpcsFile->addError($error, ($tag + 2), 'ThrowsNotCapital'); } - $lastChar = substr($comment, -1); - if ($lastChar !== '.') { + if (Orthography::isLastCharPunctuation($comment, '.') === false) { $error = '@throws tag comment must end with a full stop'; $phpcsFile->addError($error, ($tag + 2), 'ThrowsNoFullStop'); } @@ -355,7 +358,7 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) ]; }//end foreach - $realParams = $phpcsFile->getMethodParameters($stackPtr); + $realParams = FunctionDeclarations::getParameters($phpcsFile, $stackPtr); $foundParams = []; // We want to use ... for all variable length arguments, so added @@ -382,7 +385,7 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) $typeName = substr($typeName, 1); } - $suggestedName = Common::suggestType($typeName); + $suggestedName = Comments::suggestType($typeName, $this->typeFormat); $suggestedTypeNames[] = $suggestedName; if (count($typeNames) > 1) { @@ -397,7 +400,9 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) $suggestedTypeHint = 'callable'; } else if (strpos($suggestedName, 'callback') !== false) { $suggestedTypeHint = 'callable'; - } else if (in_array($suggestedName, Common::$allowedTypes, true) === false) { + } else if (isset(Comments::$allowedTypes[$suggestedName]) === false + && $suggestedName !== 'boolean' && $suggestedName !== 'integer' + ) { $suggestedTypeHint = $suggestedName; } @@ -408,7 +413,9 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) $suggestedTypeHint = 'int'; } else if ($suggestedName === 'float') { $suggestedTypeHint = 'float'; - } else if ($suggestedName === 'bool' || $suggestedName === 'boolean') { + } else if ($suggestedName === 'bool' || $suggestedName === 'boolean' + || $suggestedName === 'true' || $suggestedName === 'false' + ) { $suggestedTypeHint = 'bool'; } } @@ -464,7 +471,9 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) }//end if }//end foreach - $suggestedType = implode($suggestedTypeNames, '|'); + $suggestedType = Comments::suggestTypeString($param['type'], $this->typeFormat); + // Don't allow prefixing with nullable indicator. + $suggestedType = str_replace('?', '', $suggestedType); if ($param['type'] !== $suggestedType) { $error = 'Expected "%s" but found "%s" for parameter type'; $data = [ @@ -549,13 +558,12 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) $this->checkSpacingAfterParamName($phpcsFile, $param, $maxVar); // Param comments must start with a capital letter and end with a full stop. - if (preg_match('/^(\p{Ll}|\P{L})/u', $param['comment']) === 1) { + if (Orthography::isFirstCharCapitalized($param['comment']) === false) { $error = 'Parameter comment must start with a capital letter'; $phpcsFile->addError($error, $param['tag'], 'ParamCommentNotCapital'); } - $lastChar = substr($param['comment'], -1); - if ($lastChar !== '.') { + if (Orthography::isLastCharPunctuation($param['comment'], '.') === false) { $error = 'Parameter comment must end with a full stop'; $phpcsFile->addError($error, $param['tag'], 'ParamCommentFullStop'); } diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentThrowTagSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentThrowTagSniff.php index cc68634d59..3e45a27d6b 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentThrowTagSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentThrowTagSniff.php @@ -11,7 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Util\Tokens; +use PHP_CodeSniffer\Util\Sniffs\Comments; class FunctionCommentThrowTagSniff implements Sniff { @@ -47,11 +47,8 @@ public function process(File $phpcsFile, $stackPtr) return; } - $find = Tokens::$methodPrefixes; - $find[] = T_WHITESPACE; - - $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true); - if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) { + $commentEnd = Comments::findFunctionComment($phpcsFile, $stackPtr); + if ($commentEnd === false || $tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) { // Function doesn't have a doc comment or is using the wrong type of comment. return; } diff --git a/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php index 6863a08021..e8a9947c9f 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php @@ -11,6 +11,9 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Comments; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\Orthography; use PHP_CodeSniffer\Util\Tokens; class InlineCommentSniff implements Sniff @@ -159,33 +162,15 @@ public function process(File $phpcsFile, $stackPtr) $commentTokens = [$stackPtr]; - $nextComment = $stackPtr; - $lastComment = $stackPtr; - while (($nextComment = $phpcsFile->findNext(T_COMMENT, ($nextComment + 1), null, false)) !== false) { - if ($tokens[$nextComment]['line'] !== ($tokens[$lastComment]['line'] + 1)) { - break; - } - - // Only want inline comments. - if (substr($tokens[$nextComment]['content'], 0, 2) !== '//') { - break; - } + $lastCommentToken = Comments::findEndOfComment($phpcsFile, $stackPtr); - // There is a comment on the very next line. If there is - // no code between the comments, they are part of the same - // comment block. - $prevNonWhitespace = $phpcsFile->findPrevious(T_WHITESPACE, ($nextComment - 1), $lastComment, true); - if ($prevNonWhitespace !== $lastComment) { - break; + $commentText = ''; + for ($current = $stackPtr; $current <= $lastCommentToken; $current++) { + if ($tokens[$current]['code'] === T_WHITESPACE) { + continue; } - $commentTokens[] = $nextComment; - $lastComment = $nextComment; - }//end while - - $commentText = ''; - foreach ($commentTokens as $lastCommentToken) { - $comment = rtrim($tokens[$lastCommentToken]['content']); + $comment = rtrim($tokens[$current]['content']); if (trim(substr($comment, 2)) === '') { continue; @@ -215,14 +200,14 @@ public function process(File $phpcsFile, $stackPtr) ltrim(substr($comment, 2)), $comment, ]; - $fix = $phpcsFile->addFixableError($error, $lastCommentToken, 'TabBefore', $data); + $fix = $phpcsFile->addFixableError($error, $current, 'TabBefore', $data); } else if ($spaceCount === 0) { $error = 'No space found before comment text; expected "// %s" but found "%s"'; $data = [ substr($comment, 2), $comment, ]; - $fix = $phpcsFile->addFixableError($error, $lastCommentToken, 'NoSpaceBefore', $data); + $fix = $phpcsFile->addFixableError($error, $current, 'NoSpaceBefore', $data); } else if ($spaceCount > 1) { $error = 'Expected 1 space before comment text but found %s; use block comment if you need indentation'; $data = [ @@ -230,16 +215,16 @@ public function process(File $phpcsFile, $stackPtr) substr($comment, (2 + $spaceCount)), $comment, ]; - $fix = $phpcsFile->addFixableError($error, $lastCommentToken, 'SpacingBefore', $data); + $fix = $phpcsFile->addFixableError($error, $current, 'SpacingBefore', $data); }//end if if ($fix === true) { - $newComment = '// '.ltrim($tokens[$lastCommentToken]['content'], "/\t "); - $phpcsFile->fixer->replaceToken($lastCommentToken, $newComment); + $newComment = '// '.ltrim($tokens[$current]['content'], "/\t "); + $phpcsFile->fixer->replaceToken($current, $newComment); } - $commentText .= trim(substr($tokens[$lastCommentToken]['content'], 2)); - }//end foreach + $commentText .= trim(substr($tokens[$current]['content'], 2)); + }//end for if ($commentText === '') { $error = 'Blank comments are not allowed'; @@ -251,7 +236,7 @@ public function process(File $phpcsFile, $stackPtr) return ($lastCommentToken + 1); } - if (preg_match('/^\p{Ll}/u', $commentText) === 1) { + if (Orthography::isFirstCharLowercase($commentText) === true) { $error = 'Inline comments must start with a capital letter'; $phpcsFile->addError($error, $stackPtr, 'NotCapital'); } @@ -259,14 +244,13 @@ public function process(File $phpcsFile, $stackPtr) // Only check the end of comment character if the start of the comment // is a letter, indicating that the comment is just standard text. if (preg_match('/^\p{L}/u', $commentText) === 1) { - $commentCloser = $commentText[(strlen($commentText) - 1)]; $acceptedClosers = [ 'full-stops' => '.', 'exclamation marks' => '!', 'or question marks' => '?', ]; - if (in_array($commentCloser, $acceptedClosers, true) === false) { + if (Orthography::isLastCharPunctuation($commentText, implode('', $acceptedClosers)) === false) { $error = 'Inline comments must end in %s'; $ender = ''; foreach ($acceptedClosers as $closerName => $symbol) { @@ -298,11 +282,8 @@ public function process(File $phpcsFile, $stackPtr) $errorCode = 'SpacingAfter'; if (isset($tokens[$stackPtr]['conditions']) === true) { - $conditions = $tokens[$stackPtr]['conditions']; - $type = end($conditions); - $conditionPtr = key($conditions); - - if (($type === T_FUNCTION || $type === T_CLOSURE) + $conditionPtr = Conditions::validDirectScope($phpcsFile, $stackPtr, [T_FUNCTION, T_CLOSURE]); + if ($conditionPtr !== false && $tokens[$conditionPtr]['scope_closer'] === $next ) { $errorCode = 'SpacingAfterAtFunctionEnd'; diff --git a/src/Standards/Squiz/Sniffs/Commenting/PostStatementCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/PostStatementCommentSniff.php index 5bfa9ebef3..967cd5449a 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/PostStatementCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/PostStatementCommentSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; class PostStatementCommentSniff implements Sniff { @@ -34,12 +35,12 @@ class PostStatementCommentSniff implements Sniff * @var array */ private $controlStructureExceptions = [ - T_IF => true, - T_ELSEIF => true, - T_SWITCH => true, - T_WHILE => true, - T_FOR => true, - T_FOREACH => true, + T_IF, + T_ELSEIF, + T_SWITCH, + T_WHILE, + T_FOR, + T_FOREACH, ]; @@ -97,15 +98,8 @@ public function process(File $phpcsFile, $stackPtr) } // Special case for (trailing) comments within multi-line control structures. - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - $nestedParens = $tokens[$stackPtr]['nested_parenthesis']; - foreach ($nestedParens as $open => $close) { - if (isset($tokens[$open]['parenthesis_owner']) === true - && isset($this->controlStructureExceptions[$tokens[$tokens[$open]['parenthesis_owner']]['code']]) === true - ) { - return; - } - } + if (Parentheses::hasOwner($phpcsFile, $stackPtr, $this->controlStructureExceptions) === true) { + return; } $error = 'Comments may not appear after statements'; diff --git a/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php index 8c9fdeb3c7..edff62ab98 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php @@ -11,11 +11,22 @@ use PHP_CodeSniffer\Sniffs\AbstractVariableSniff; use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Util\Common; +use PHP_CodeSniffer\Util\Sniffs\Comments; class VariableCommentSniff extends AbstractVariableSniff { + /** + * Whether short or long form parameter/return types are preferred. + * + * Valid values: 'short' or 'long'. Defaults to 'long'. + * + * This applies to just two types: `int` vs `integer` and `bool` vs `boolean`. + * + * @var string + */ + public $typeFormat = 'long'; + /** * Called to process class member vars. @@ -28,25 +39,13 @@ class VariableCommentSniff extends AbstractVariableSniff */ public function processMemberVar(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - $ignore = [ - T_PUBLIC, - T_PRIVATE, - T_PROTECTED, - T_VAR, - T_STATIC, - T_WHITESPACE, - ]; - - $commentEnd = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true); - if ($commentEnd === false - || ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG - && $tokens[$commentEnd]['code'] !== T_COMMENT) - ) { + $commentEnd = Comments::findPropertyComment($phpcsFile, $stackPtr); + if ($commentEnd === false) { $phpcsFile->addError('Missing member variable doc comment', $stackPtr, 'Missing'); return; } + $tokens = $phpcsFile->getTokens(); if ($tokens[$commentEnd]['code'] === T_COMMENT) { $phpcsFile->addError('You must use "/**" style comments for a member variable comment', $stackPtr, 'WrongStyle'); return; @@ -99,7 +98,7 @@ public function processMemberVar(File $phpcsFile, $stackPtr) } // Support both a var type and a description. - preg_match('`^((?:\|?(?:array\([^\)]*\)|[\\\\a-z0-9\[\]]+))*)( .*)?`i', $tokens[($foundVar + 2)]['content'], $varParts); + preg_match('`^((?:\|?(?:array(?:\([^\)]*\)|<[^>]*>+)|\(?[\\\\a-z0-9\[\]]+\)?))*)( .*)?`i', $tokens[($foundVar + 2)]['content'], $varParts); if (isset($varParts[1]) === false) { return; } @@ -107,16 +106,7 @@ public function processMemberVar(File $phpcsFile, $stackPtr) $varType = $varParts[1]; // Check var type (can be multiple, separated by '|'). - $typeNames = explode('|', $varType); - $suggestedNames = []; - foreach ($typeNames as $i => $typeName) { - $suggestedName = Common::suggestType($typeName); - if (in_array($suggestedName, $suggestedNames, true) === false) { - $suggestedNames[] = $suggestedName; - } - } - - $suggestedType = implode('|', $suggestedNames); + $suggestedType = Comments::suggestTypeString($varType, $this->typeFormat); if ($varType !== $suggestedType) { $error = 'Expected "%s" but found "%s" for @var tag in member variable comment'; $data = [ diff --git a/src/Standards/Squiz/Sniffs/ControlStructures/InlineIfDeclarationSniff.php b/src/Standards/Squiz/Sniffs/ControlStructures/InlineIfDeclarationSniff.php index f6513329d7..63243e1b70 100644 --- a/src/Standards/Squiz/Sniffs/ControlStructures/InlineIfDeclarationSniff.php +++ b/src/Standards/Squiz/Sniffs/ControlStructures/InlineIfDeclarationSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; class InlineIfDeclarationSniff implements Sniff { @@ -41,12 +42,10 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $openBracket = null; - $closeBracket = null; - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - $parens = $tokens[$stackPtr]['nested_parenthesis']; - $openBracket = array_pop($parens); - $closeBracket = $tokens[$openBracket]['parenthesis_closer']; + $closeBracket = null; + $lastParenCloser = Parentheses::getLastCloser($phpcsFile, $stackPtr); + if ($lastParenCloser !== false) { + $closeBracket = $lastParenCloser; } // Find the beginning of the statement. If we don't find a diff --git a/src/Standards/Squiz/Sniffs/Debug/JavaScriptLintSniff.php b/src/Standards/Squiz/Sniffs/Debug/JavaScriptLintSniff.php index 033327bdeb..99a4bbd659 100644 --- a/src/Standards/Squiz/Sniffs/Debug/JavaScriptLintSniff.php +++ b/src/Standards/Squiz/Sniffs/Debug/JavaScriptLintSniff.php @@ -45,6 +45,7 @@ public function register() * the token was found. * * @return void + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If Javascript Lint ran into trouble. */ public function process(File $phpcsFile, $stackPtr) { diff --git a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php index 9a5bbb833c..356dd170f5 100644 --- a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php +++ b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php @@ -11,6 +11,8 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; +use PHP_CodeSniffer\Util\Sniffs\TokenIs; use PHP_CodeSniffer\Util\Tokens; class OperatorBracketSniff implements Sniff @@ -52,49 +54,52 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - if ($phpcsFile->tokenizerType === 'JS' && $tokens[$stackPtr]['code'] === T_PLUS) { - // JavaScript uses the plus operator for string concatenation as well - // so we cannot accurately determine if it is a string concat or addition. - // So just ignore it. - return; - } - // If the & is a reference, then we don't want to check for brackets. - if ($tokens[$stackPtr]['code'] === T_BITWISE_AND && $phpcsFile->isReference($stackPtr) === true) { + if ($tokens[$stackPtr]['code'] === T_BITWISE_AND + && TokenIs::isReference($phpcsFile, $stackPtr) === true + ) { return; } // There is one instance where brackets aren't needed, which involves - // the minus sign being used to assign a negative number to a variable. - if ($tokens[$stackPtr]['code'] === T_MINUS) { - // Check to see if we are trying to return -n. - $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); - if ($tokens[$prev]['code'] === T_RETURN) { - return; - } - - $number = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); - if ($tokens[$number]['code'] === T_LNUMBER || $tokens[$number]['code'] === T_DNUMBER) { - $previous = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); - if ($previous !== false) { - $isAssignment = isset(Tokens::$assignmentTokens[$tokens[$previous]['code']]); - $isEquality = isset(Tokens::$equalityTokens[$tokens[$previous]['code']]); - $isComparison = isset(Tokens::$comparisonTokens[$tokens[$previous]['code']]); - if ($isAssignment === true || $isEquality === true || $isComparison === true) { - // This is a negative assignment or comparison. - // We need to check that the minus and the number are - // adjacent. - if (($number - $stackPtr) !== 1) { - $error = 'No space allowed between minus sign and number'; - $phpcsFile->addError($error, $stackPtr, 'SpacingAfterMinus'); + // the plus/minus sign being used to assign a positive/negative number to a variable. + if ($tokens[$stackPtr]['code'] === T_MINUS || $tokens[$stackPtr]['code'] === T_PLUS) { + if (TokenIs::isUnaryPlusMinus($phpcsFile, $stackPtr) === true) { + // This is a positive/negative assignment or comparison. + // We need to check that the sign and the number are adjacent. + $number = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if (($tokens[$number]['code'] === T_LNUMBER || $tokens[$number]['code'] === T_DNUMBER) + && (($number - $stackPtr) !== 1) + ) { + $error = 'No space allowed between the unary sign and the number'; + + $nextNonWhitespace = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if ($nextNonWhitespace === $number) { + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingAfterSign'); + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + for ($i = ($stackPtr + 1); $i < $number; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->endChangeset(); } - - return; + } else { + $phpcsFile->addError($error, $stackPtr, 'SpacingAfterSign'); } } - } + + return; + }//end if }//end if + if ($phpcsFile->tokenizerType === 'JS' && $tokens[$stackPtr]['code'] === T_PLUS) { + // JavaScript uses the plus operator for string concatenation as well + // so we cannot accurately determine if it is a string concat or addition. + // So just ignore it. + return; + } + $previousToken = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true, null, true); if ($previousToken !== false) { // A list of tokens that indicate that the token is not @@ -115,17 +120,11 @@ public function process(File $phpcsFile, $stackPtr) } if ($tokens[$stackPtr]['code'] === T_BITWISE_OR - && isset($tokens[$stackPtr]['nested_parenthesis']) === true + && Parentheses::lastOwnerIn($phpcsFile, $stackPtr, T_CATCH) !== false ) { - $brackets = $tokens[$stackPtr]['nested_parenthesis']; - $lastBracket = array_pop($brackets); - if (isset($tokens[$lastBracket]['parenthesis_owner']) === true - && $tokens[$tokens[$lastBracket]['parenthesis_owner']]['code'] === T_CATCH - ) { - // This is a pipe character inside a catch statement, so it is acting - // as an exception type separator and not an arithmetic operation. - return; - } + // This is a pipe character inside a catch statement, so it is acting + // as an exception type separator and not an arithmetic operation. + return; } // Tokens that are allowed inside a bracketed operation. diff --git a/src/Standards/Squiz/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php b/src/Standards/Squiz/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php index 98f6dfdde2..d4d47e2784 100644 --- a/src/Standards/Squiz/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\TokenIs; use PHP_CodeSniffer\Util\Tokens; class FunctionDeclarationArgumentSpacingSniff implements Sniff @@ -192,7 +193,7 @@ public function processBracket($phpcsFile, $openBracket) // Take references into account when expecting the // location of whitespace. - if ($phpcsFile->isReference($checkToken) === true) { + if (TokenIs::isReference($phpcsFile, $checkToken) === true) { $whitespace = ($checkToken - 1); } else { $whitespace = $checkToken; @@ -205,7 +206,7 @@ public function processBracket($phpcsFile, $openBracket) // Before we throw an error, make sure there is no type hint. $comma = $phpcsFile->findPrevious(T_COMMA, ($nextParam - 1)); $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($comma + 1), null, true); - if ($phpcsFile->isReference($nextToken) === true) { + if (TokenIs::isReference($phpcsFile, $nextToken) === true) { $nextToken++; } @@ -294,7 +295,7 @@ public function processBracket($phpcsFile, $openBracket) // Before we throw an error, make sure there is no type hint. $bracket = $phpcsFile->findPrevious(T_OPEN_PARENTHESIS, ($nextParam - 1)); $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($bracket + 1), null, true); - if ($phpcsFile->isReference($nextToken) === true) { + if (TokenIs::isReference($phpcsFile, $nextToken) === true) { $nextToken++; } diff --git a/src/Standards/Squiz/Sniffs/Functions/GlobalFunctionSniff.php b/src/Standards/Squiz/Sniffs/Functions/GlobalFunctionSniff.php index 25159388f6..8a0c4cfbd7 100644 --- a/src/Standards/Squiz/Sniffs/Functions/GlobalFunctionSniff.php +++ b/src/Standards/Squiz/Sniffs/Functions/GlobalFunctionSniff.php @@ -11,6 +11,10 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; +use PHP_CodeSniffer\Util\Tokens; class GlobalFunctionSniff implements Sniff { @@ -39,22 +43,25 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - - if (empty($tokens[$stackPtr]['conditions']) === true) { - $functionName = $phpcsFile->getDeclarationName($stackPtr); - if ($functionName === null) { - return; - } - - // Special exception for __autoload as it needs to be global. - if ($functionName !== '__autoload') { - $error = 'Consider putting global function "%s" in a static class'; - $data = [$functionName]; - $phpcsFile->addWarning($error, $stackPtr, 'Found', $data); - } + if (Conditions::hasCondition($phpcsFile, $stackPtr, Tokens::$ooScopeTokens) === true) { + return; } + // Special exception for PHP magic functions as they need to be global. + if (FunctionDeclarations::isMagicFunction($phpcsFile, $stackPtr) === true) { + return; + } + + $functionName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); + if (empty($functionName) === true) { + // Live coding or parse error. + return; + } + + $error = 'Consider putting global function "%s" in a static class'; + $data = [$functionName]; + $phpcsFile->addWarning($error, $stackPtr, 'Found', $data); + }//end process() diff --git a/src/Standards/Squiz/Sniffs/NamingConventions/ValidFunctionNameSniff.php b/src/Standards/Squiz/Sniffs/NamingConventions/ValidFunctionNameSniff.php index 537d7d6680..695472005c 100644 --- a/src/Standards/Squiz/Sniffs/NamingConventions/ValidFunctionNameSniff.php +++ b/src/Standards/Squiz/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -10,8 +10,8 @@ namespace PHP_CodeSniffer\Standards\Squiz\Sniffs\NamingConventions; use PHP_CodeSniffer\Standards\PEAR\Sniffs\NamingConventions\ValidFunctionNameSniff as PEARValidFunctionNameSniff; -use PHP_CodeSniffer\Util\Common; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; class ValidFunctionNameSniff extends PEARValidFunctionNameSniff { @@ -28,8 +28,9 @@ class ValidFunctionNameSniff extends PEARValidFunctionNameSniff */ protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) { - $functionName = $phpcsFile->getDeclarationName($stackPtr); - if ($functionName === null) { + $functionName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); + if (empty($functionName) === true) { + // Live coding or parse error. return; } @@ -43,7 +44,7 @@ protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) $functionName = ltrim($functionName, '_'); } - if (Common::isCamelCaps($functionName, false, true, false) === false) { + if (ConstructNames::isCamelCaps($functionName, false, true, false) === false) { $error = 'Function name "%s" is not in camel caps format'; $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $errorData); } diff --git a/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php b/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php index 2a3966fa0c..ab0f15f5a9 100644 --- a/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php +++ b/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php @@ -10,8 +10,10 @@ namespace PHP_CodeSniffer\Standards\Squiz\Sniffs\NamingConventions; use PHP_CodeSniffer\Sniffs\AbstractVariableSniff; -use PHP_CodeSniffer\Util\Common; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\Variables; use PHP_CodeSniffer\Util\Tokens; class ValidVariableNameSniff extends AbstractVariableSniff @@ -29,11 +31,10 @@ class ValidVariableNameSniff extends AbstractVariableSniff */ protected function processVariable(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - $varName = ltrim($tokens[$stackPtr]['content'], '$'); + $tokens = $phpcsFile->getTokens(); // If it's a php reserved var, then its ok. - if (isset($this->phpReservedVars[$varName]) === true) { + if (Variables::isPHPReservedVarName($tokens[$stackPtr]['content']) === true) { return; } @@ -54,7 +55,7 @@ protected function processVariable(File $phpcsFile, $stackPtr) $objVarName = substr($objVarName, 1); } - if (Common::isCamelCaps($objVarName, false, true, false) === false) { + if (ConstructNames::isCamelCaps($objVarName, false, true, false) === false) { $error = 'Variable "%s" is not in valid camel caps format'; $data = [$originalVarName]; $phpcsFile->addError($error, $var, 'NotCamelCaps', $data); @@ -66,6 +67,7 @@ protected function processVariable(File $phpcsFile, $stackPtr) // There is no way for us to know if the var is public or private, // so we have to ignore a leading underscore if there is one and just // check the main part of the variable name. + $varName = ltrim($tokens[$stackPtr]['content'], '$'); $originalVarName = $varName; if (substr($varName, 0, 1) === '_') { $objOperator = $phpcsFile->findPrevious([T_WHITESPACE], ($stackPtr - 1), null, true); @@ -74,7 +76,7 @@ protected function processVariable(File $phpcsFile, $stackPtr) // this: MyClass::$_variable, so we don't know its scope. $inClass = true; } else { - $inClass = $phpcsFile->hasCondition($stackPtr, Tokens::$ooScopeTokens); + $inClass = Conditions::hasCondition($phpcsFile, $stackPtr, Tokens::$ooScopeTokens); } if ($inClass === true) { @@ -82,7 +84,7 @@ protected function processVariable(File $phpcsFile, $stackPtr) } } - if (Common::isCamelCaps($varName, false, true, false) === false) { + if (ConstructNames::isCamelCaps($varName, false, true, false) === false) { $error = 'Variable "%s" is not in valid camel caps format'; $data = [$originalVarName]; $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $data); @@ -102,10 +104,7 @@ protected function processVariable(File $phpcsFile, $stackPtr) */ protected function processMemberVar(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - - $varName = ltrim($tokens[$stackPtr]['content'], '$'); - $memberProps = $phpcsFile->getMemberProperties($stackPtr); + $memberProps = Variables::getMemberProperties($phpcsFile, $stackPtr); if (empty($memberProps) === true) { // Couldn't get any info about this variable, which // generally means it is invalid or possibly has a parse @@ -114,6 +113,8 @@ protected function processMemberVar(File $phpcsFile, $stackPtr) return; } + $tokens = $phpcsFile->getTokens(); + $varName = ltrim($tokens[$stackPtr]['content'], '$'); $public = ($memberProps['scope'] !== 'private'); $errorData = [$varName]; @@ -136,7 +137,7 @@ protected function processMemberVar(File $phpcsFile, $stackPtr) // Remove a potential underscore prefix for testing CamelCaps. $varName = ltrim($varName, '_'); - if (Common::isCamelCaps($varName, false, true, false) === false) { + if (ConstructNames::isCamelCaps($varName, false, true, false) === false) { $error = 'Member variable "%s" is not in valid camel caps format'; $phpcsFile->addError($error, $stackPtr, 'MemberNotCamelCaps', $errorData); } @@ -160,11 +161,11 @@ protected function processVariableInString(File $phpcsFile, $stackPtr) if (preg_match_all('|[^\\\]\${?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)|', $tokens[$stackPtr]['content'], $matches) !== 0) { foreach ($matches[1] as $varName) { // If it's a php reserved var, then its ok. - if (isset($this->phpReservedVars[$varName]) === true) { + if (Variables::isPHPReservedVarName($varName) === true) { continue; } - if (Common::isCamelCaps($varName, false, true, false) === false) { + if (ConstructNames::isCamelCaps($varName, false, true, false) === false) { $error = 'Variable "%s" is not in valid camel caps format'; $data = [$varName]; $phpcsFile->addError($error, $stackPtr, 'StringNotCamelCaps', $data); diff --git a/src/Standards/Squiz/Sniffs/Operators/ComparisonOperatorUsageSniff.php b/src/Standards/Squiz/Sniffs/Operators/ComparisonOperatorUsageSniff.php index 5eed89bff4..dc2b0190c6 100644 --- a/src/Standards/Squiz/Sniffs/Operators/ComparisonOperatorUsageSniff.php +++ b/src/Standards/Squiz/Sniffs/Operators/ComparisonOperatorUsageSniff.php @@ -44,7 +44,7 @@ class ComparisonOperatorUsageSniff implements Sniff /** * A list of invalid operators with their alternatives. * - * @var array + * @var array> */ private static $invalidOps = [ 'PHP' => [ diff --git a/src/Standards/Squiz/Sniffs/PHP/CommentedOutCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/CommentedOutCodeSniff.php index d6619045fa..5ee8f69fcb 100644 --- a/src/Standards/Squiz/Sniffs/PHP/CommentedOutCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/CommentedOutCodeSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Comments; use PHP_CodeSniffer\Util\Tokens; use PHP_CodeSniffer\Exceptions\TokenizerException; @@ -67,40 +68,17 @@ public function process(File $phpcsFile, $stackPtr) } $content = ''; - $lastLineSeen = $tokens[$stackPtr]['line']; $commentStyle = 'line'; if (strpos($tokens[$stackPtr]['content'], '/*') === 0) { $commentStyle = 'block'; } - $lastCommentBlockToken = $stackPtr; - for ($i = $stackPtr; $i < $phpcsFile->numTokens; $i++) { - if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === false) { - break; - } - - if ($tokens[$i]['code'] === T_WHITESPACE) { - continue; - } - - if (isset(Tokens::$phpcsCommentTokens[$tokens[$i]['code']]) === true) { - $lastLineSeen = $tokens[$i]['line']; - continue; - } - - if ($commentStyle === 'line' - && ($lastLineSeen + 1) <= $tokens[$i]['line'] - && strpos($tokens[$i]['content'], '/*') === 0 + $lastCommentBlockToken = Comments::findEndOfComment($phpcsFile, $stackPtr); + for ($i = $stackPtr; $i <= $lastCommentBlockToken; $i++) { + if ($tokens[$i]['code'] === T_WHITESPACE + || isset(Tokens::$phpcsCommentTokens[$tokens[$i]['code']]) === true ) { - // First non-whitespace token on a new line is start of a different style comment. - break; - } - - if ($commentStyle === 'line' - && ($lastLineSeen + 1) < $tokens[$i]['line'] - ) { - // Blank line breaks a '//' style comment block. - break; + continue; } /* @@ -109,7 +87,6 @@ public function process(File $phpcsFile, $stackPtr) */ $tokenContent = trim($tokens[$i]['content']); - $break = false; if ($commentStyle === 'line') { if (substr($tokenContent, 0, 2) === '//') { @@ -130,7 +107,6 @@ public function process(File $phpcsFile, $stackPtr) if (substr($tokenContent, -2) === '*/') { $tokenContent = substr($tokenContent, 0, -2); - $break = true; } if (substr($tokenContent, 0, 1) === '*') { @@ -138,15 +114,7 @@ public function process(File $phpcsFile, $stackPtr) } }//end if - $content .= $tokenContent.$phpcsFile->eolChar; - $lastLineSeen = $tokens[$i]['line']; - - $lastCommentBlockToken = $i; - - if ($break === true) { - // Closer of a block comment found. - break; - } + $content .= $tokenContent.$phpcsFile->eolChar; }//end for // Ignore typical warning suppression annotations from other tools. diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowBooleanStatementSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowBooleanStatementSniff.php index 8c9bd27a4a..2295b63f33 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowBooleanStatementSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowBooleanStatementSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; use PHP_CodeSniffer\Util\Tokens; class DisallowBooleanStatementSniff implements Sniff @@ -40,14 +41,9 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - foreach ($tokens[$stackPtr]['nested_parenthesis'] as $open => $close) { - if (isset($tokens[$open]['parenthesis_owner']) === true) { - // Any owner means we are not just a simple statement. - return; - } - } + if (Parentheses::hasOwner($phpcsFile, $stackPtr, Tokens::$parenthesisOpeners) === true) { + // Any owner means we are not just a simple statement. + return; } $error = 'Boolean operators are not allowed outside of control structure conditions'; diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php index 43e0a8b1eb..8264b6803f 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; use PHP_CodeSniffer\Util\Tokens; class DisallowComparisonAssignmentSniff implements Sniff @@ -43,42 +44,7 @@ public function process(File $phpcsFile, $stackPtr) $tokens = $phpcsFile->getTokens(); // Ignore default value assignments in function definitions. - $function = $phpcsFile->findPrevious(T_FUNCTION, ($stackPtr - 1), null, false, null, true); - if ($function !== false) { - $opener = $tokens[$function]['parenthesis_opener']; - $closer = $tokens[$function]['parenthesis_closer']; - if ($opener < $stackPtr && $closer > $stackPtr) { - return; - } - } - - // Ignore values in array definitions. - $array = $phpcsFile->findNext( - T_ARRAY, - ($stackPtr + 1), - null, - false, - null, - true - ); - - if ($array !== false) { - return; - } - - // Ignore function calls. - $ignore = [ - T_STRING, - T_WHITESPACE, - T_OBJECT_OPERATOR, - ]; - - $next = $phpcsFile->findNext($ignore, ($stackPtr + 1), null, true); - if ($tokens[$next]['code'] === T_OPEN_PARENTHESIS - && $tokens[($next - 1)]['code'] === T_STRING - ) { - // Code will look like: $var = myFunction( - // and will be ignored. + if (Parentheses::lastOwnerIn($phpcsFile, $stackPtr, [T_FUNCTION, T_CLOSURE]) !== false) { return; } @@ -90,6 +56,43 @@ public function process(File $phpcsFile, $stackPtr) } for ($i = ($stackPtr + 1); $i < $endStatement; $i++) { + if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { + continue; + } + + // Skip over arrays. + if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) { + $i = $tokens[$i]['bracket_closer']; + continue; + } + + if ($tokens[$i]['code'] === T_ARRAY && isset($tokens[$i]['parenthesis_closer']) === true) { + $i = $tokens[$i]['parenthesis_closer']; + continue; + } + + // Skip over closures. + if ($tokens[$i]['code'] === T_CLOSURE) { + if (isset($tokens[$i]['scope_closer']) === false) { + // Live coding. + break; + } + + $i = $tokens[$i]['scope_closer']; + continue; + } + + // Skip over named function calls. + if ($tokens[$i]['code'] === T_STRING) { + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true); + if ($tokens[$next]['code'] === T_OPEN_PARENTHESIS + && isset($tokens[$next]['parenthesis_closer']) === true + ) { + $i = $tokens[$next]['parenthesis_closer']; + continue; + } + } + if ((isset(Tokens::$comparisonTokens[$tokens[$i]['code']]) === true && $tokens[$i]['code'] !== T_COALESCE) || $tokens[$i]['code'] === T_INLINE_THEN @@ -106,7 +109,7 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->addError($error, $stackPtr, 'AssignedBool'); break; } - } + }//end for }//end process() diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php index 01c2818ec0..ac145e5374 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; use PHP_CodeSniffer\Util\Tokens; class DisallowMultipleAssignmentsSniff implements Sniff @@ -42,26 +43,14 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - // Ignore default value assignments in function definitions. - $function = $phpcsFile->findPrevious([T_FUNCTION, T_CLOSURE], ($stackPtr - 1), null, false, null, true); - if ($function !== false) { - $opener = $tokens[$function]['parenthesis_opener']; - $closer = $tokens[$function]['parenthesis_closer']; - if ($opener < $stackPtr && $closer > $stackPtr) { - return; - } + // Ignore default value assignments in function definitions and assignments in WHILE loop conditions. + if (Parentheses::lastOwnerIn($phpcsFile, $stackPtr, [T_FUNCTION, T_CLOSURE]) !== false) { + return; } // Ignore assignments in WHILE loop conditions. - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - $nested = $tokens[$stackPtr]['nested_parenthesis']; - foreach ($nested as $opener => $closer) { - if (isset($tokens[$opener]['parenthesis_owner']) === true - && $tokens[$tokens[$opener]['parenthesis_owner']]['code'] === T_WHILE - ) { - return; - } - } + if (Parentheses::hasOwner($phpcsFile, $stackPtr, T_WHILE) === true) { + return; } /* @@ -135,11 +124,10 @@ public function process(File $phpcsFile, $stackPtr) // Ignore the first part of FOR loops as we are allowed to // assign variables there even though the variable is not the // first thing on the line. - if ($tokens[$varToken]['code'] === T_OPEN_PARENTHESIS && isset($tokens[$varToken]['parenthesis_owner']) === true) { - $owner = $tokens[$varToken]['parenthesis_owner']; - if ($tokens[$owner]['code'] === T_FOR) { - return; - } + if ($tokens[$varToken]['code'] === T_OPEN_PARENTHESIS + && Parentheses::isOwnerIn($phpcsFile, $varToken, T_FOR) === true + ) { + return; } if ($tokens[$varToken]['code'] === T_VARIABLE diff --git a/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php b/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php index 5df214db03..d93fb86cae 100644 --- a/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; class InnerFunctionsSniff implements Sniff { @@ -41,13 +42,13 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $function = $phpcsFile->getCondition($stackPtr, T_FUNCTION); + $function = Conditions::getCondition($phpcsFile, $stackPtr, T_FUNCTION); if ($function === false) { // Not a nested function. return; } - $class = $phpcsFile->getCondition($stackPtr, T_ANON_CLASS); + $class = Conditions::getCondition($phpcsFile, $stackPtr, T_ANON_CLASS); if ($class !== false && $class > $function) { // Ignore methods in anon classes. return; diff --git a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php index 64e34df130..ebf7fbb882 100644 --- a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php @@ -11,6 +11,8 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; use PHP_CodeSniffer\Util\Tokens; class NonExecutableCodeSniff implements Sniff @@ -139,24 +141,14 @@ public function process(File $phpcsFile, $stackPtr) // If we find a closing parenthesis that belongs to a condition // we should ignore this token. $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); - if (isset($tokens[$prev]['parenthesis_owner']) === true) { - $owner = $tokens[$prev]['parenthesis_owner']; - $ignore = [ - T_IF => true, - T_ELSE => true, - T_ELSEIF => true, - ]; - if (isset($ignore[$tokens[$owner]['code']]) === true) { - return; - } + if (Parentheses::isOwnerIn($phpcsFile, $prev, [T_IF, T_ELSE, T_ELSEIF]) === true) { + return; } - $ourConditions = array_keys($tokens[$stackPtr]['conditions']); - - if (empty($ourConditions) === false) { - $condition = array_pop($ourConditions); + $lastCondition = Conditions::getLastCondition($phpcsFile, $stackPtr); - if (isset($tokens[$condition]['scope_closer']) === false) { + if ($lastCondition !== false) { + if (isset($tokens[$lastCondition]['scope_closer']) === false) { return; } @@ -165,13 +157,13 @@ public function process(File $phpcsFile, $stackPtr) // used to close a CASE statement, so it is most likely non-executable // code itself (as is the case when you put return; break; at the end of // a case). So we need to ignore this token. - if ($tokens[$condition]['code'] === T_SWITCH + if ($tokens[$lastCondition]['code'] === T_SWITCH && $tokens[$stackPtr]['code'] === T_BREAK ) { return; } - $closer = $tokens[$condition]['scope_closer']; + $closer = $tokens[$lastCondition]['scope_closer']; // If the closer for our condition is shared with other openers, // we will need to throw errors from this token to the next diff --git a/src/Standards/Squiz/Sniffs/Scope/MemberVarScopeSniff.php b/src/Standards/Squiz/Sniffs/Scope/MemberVarScopeSniff.php index 2f3c525373..11292f57a2 100644 --- a/src/Standards/Squiz/Sniffs/Scope/MemberVarScopeSniff.php +++ b/src/Standards/Squiz/Sniffs/Scope/MemberVarScopeSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\AbstractVariableSniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Variables; class MemberVarScopeSniff extends AbstractVariableSniff { @@ -26,15 +27,14 @@ class MemberVarScopeSniff extends AbstractVariableSniff */ protected function processMemberVar(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - $properties = $phpcsFile->getMemberProperties($stackPtr); - + $properties = Variables::getMemberProperties($phpcsFile, $stackPtr); if ($properties === [] || $properties['scope_specified'] !== false) { return; } - $error = 'Scope modifier not specified for member variable "%s"'; - $data = [$tokens[$stackPtr]['content']]; + $tokens = $phpcsFile->getTokens(); + $error = 'Scope modifier not specified for member variable "%s"'; + $data = [$tokens[$stackPtr]['content']]; $phpcsFile->addError($error, $stackPtr, 'Missing', $data); }//end processMemberVar() diff --git a/src/Standards/Squiz/Sniffs/Scope/MethodScopeSniff.php b/src/Standards/Squiz/Sniffs/Scope/MethodScopeSniff.php index 2e9c185eb0..7fbb1e3de5 100644 --- a/src/Standards/Squiz/Sniffs/Scope/MethodScopeSniff.php +++ b/src/Standards/Squiz/Sniffs/Scope/MethodScopeSniff.php @@ -11,6 +11,8 @@ use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; use PHP_CodeSniffer\Util\Tokens; class MethodScopeSniff extends AbstractScopeSniff @@ -41,16 +43,14 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop $tokens = $phpcsFile->getTokens(); // Determine if this is a function which needs to be examined. - $conditions = $tokens[$stackPtr]['conditions']; - end($conditions); - $deepestScope = key($conditions); + $deepestScope = Conditions::getLastCondition($phpcsFile, $stackPtr); if ($deepestScope !== $currScope) { return; } - $methodName = $phpcsFile->getDeclarationName($stackPtr); - if ($methodName === null) { - // Ignore closures. + $methodName = ConstructNames::getDeclarationName($phpcsFile, $stackPtr); + if (empty($methodName) === true) { + // Live coding or parse error. return; } diff --git a/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php b/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php index b5a11b09ba..a02d8e0080 100644 --- a/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php +++ b/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php @@ -11,6 +11,8 @@ use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; use PHP_CodeSniffer\Util\Tokens; class StaticThisUsageSniff extends AbstractScopeSniff @@ -42,9 +44,7 @@ public function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope) $tokens = $phpcsFile->getTokens(); // Determine if this is a function which needs to be examined. - $conditions = $tokens[$stackPtr]['conditions']; - end($conditions); - $deepestScope = key($conditions); + $deepestScope = Conditions::getLastCondition($phpcsFile, $stackPtr); if ($deepestScope !== $currScope) { return; } @@ -60,7 +60,7 @@ public function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope) return; } - $methodProps = $phpcsFile->getMethodProperties($stackPtr); + $methodProps = FunctionDeclarations::getProperties($phpcsFile, $stackPtr); if ($methodProps['is_static'] === false) { return; } diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php index 4afa688400..585d3c50e8 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; use PHP_CodeSniffer\Util\Tokens; class ControlStructureSpacingSniff implements Sniff @@ -292,7 +293,7 @@ public function process(File $phpcsFile, $stackPtr) } if ($tokens[$owner]['code'] === T_CLOSURE - && ($phpcsFile->hasCondition($stackPtr, [T_FUNCTION, T_CLOSURE]) === true + && (Conditions::hasCondition($phpcsFile, $stackPtr, [T_FUNCTION, T_CLOSURE]) === true || isset($tokens[$stackPtr]['nested_parenthesis']) === true) ) { return; diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php index d9ee5954e2..ef88b86b1f 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; class FunctionClosingBraceSpaceSniff implements Sniff { @@ -83,7 +84,7 @@ public function process(File $phpcsFile, $stackPtr) } $nestedFunction = false; - if ($phpcsFile->hasCondition($stackPtr, [T_FUNCTION, T_CLOSURE]) === true + if (Conditions::hasCondition($phpcsFile, $stackPtr, [T_FUNCTION, T_CLOSURE]) === true || isset($tokens[$stackPtr]['nested_parenthesis']) === true ) { $nestedFunction = true; diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php index 1cfc8a6f42..8ec5152831 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php @@ -11,6 +11,8 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; +use PHP_CodeSniffer\Util\Sniffs\TokenIs; use PHP_CodeSniffer\Util\Tokens; class OperatorSpacingSniff implements Sniff @@ -255,18 +257,8 @@ protected function isOperator(File $phpcsFile, $stackPtr) 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 - || $tokens[$function]['code'] === T_DECLARE - ) { - return false; - } - } + if (Parentheses::lastOwnerIn($phpcsFile, $stackPtr, [T_FUNCTION, T_CLOSURE, T_DECLARE]) !== false) { + return false; } } @@ -282,60 +274,17 @@ protected function isOperator(File $phpcsFile, $stackPtr) 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) { + if (TokenIs::isReference($phpcsFile, $stackPtr) === true) { return false; } } if ($tokens[$stackPtr]['code'] === T_MINUS || $tokens[$stackPtr]['code'] === T_PLUS) { - // Check minus spacing, but make sure we aren't just assigning - // a minus value or returning one. - $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); - if ($tokens[$prev]['code'] === T_RETURN) { - // Just returning a negative value; eg. (return -1). - return false; - } - - if (isset(Tokens::$operators[$tokens[$prev]['code']]) === true) { - // Just trying to operate on a negative value; eg. ($var * -1). - return false; - } - - if (isset(Tokens::$comparisonTokens[$tokens[$prev]['code']]) === true) { - // Just trying to compare a negative value; eg. ($var === -1). - return false; - } - - if (isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true) { - // Just trying to compare a negative value; eg. ($var || -1 === $b). - return false; - } - - if (isset(Tokens::$assignmentTokens[$tokens[$prev]['code']]) === true) { - // Just trying to assign a negative value; eg. ($var = -1). + // Check that we aren't just assigning a minus/plus value or returning one. + if (TokenIs::isUnaryPlusMinus($phpcsFile, $stackPtr) === true) { return false; } - - // A list of tokens that indicate that the token is not - // part of an arithmetic operation. - $invalidTokens = [ - T_COMMA => true, - T_OPEN_PARENTHESIS => true, - T_OPEN_SQUARE_BRACKET => true, - T_OPEN_SHORT_ARRAY => true, - T_DOUBLE_ARROW => true, - T_COLON => true, - T_INLINE_THEN => true, - T_INLINE_ELSE => true, - T_CASE => true, - T_OPEN_CURLY_BRACKET => true, - ]; - - if (isset($invalidTokens[$tokens[$prev]['code']]) === true) { - // Just trying to use a negative value; eg. myFunction($var, -2). - return false; - } - }//end if + } return true; diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/SemicolonSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/SemicolonSpacingSniff.php index 72a5e91b52..c820b0288e 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/SemicolonSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/SemicolonSpacingSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; use PHP_CodeSniffer\Util\Tokens; class SemicolonSpacingSniff implements Sniff @@ -61,18 +62,10 @@ public function process(File $phpcsFile, $stackPtr) // Detect whether this is a semi-colons for a conditions in a `for()` control structure. $forCondition = false; - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - $nestedParens = $tokens[$stackPtr]['nested_parenthesis']; - $closeParenthesis = end($nestedParens); - - if (isset($tokens[$closeParenthesis]['parenthesis_owner']) === true) { - $owner = $tokens[$closeParenthesis]['parenthesis_owner']; - - if ($tokens[$owner]['code'] === T_FOR) { - $forCondition = true; - $nonSpace = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 2), null, true); - } - } + $owner = Parentheses::getLastOwner($phpcsFile, $stackPtr); + if ($owner !== false && $tokens[$owner]['code'] === T_FOR) { + $forCondition = true; + $nonSpace = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 2), null, true); } if ($tokens[$nonSpace]['code'] === T_SEMICOLON diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/SuperfluousWhitespaceSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/SuperfluousWhitespaceSniff.php index 376fbd24ba..b1e2d2d9c8 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/SuperfluousWhitespaceSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/SuperfluousWhitespaceSniff.php @@ -15,6 +15,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; class SuperfluousWhitespaceSniff implements Sniff { @@ -224,7 +225,7 @@ public function process(File $phpcsFile, $stackPtr) Check for multiple blank lines in a function. */ - if (($phpcsFile->hasCondition($stackPtr, [T_FUNCTION, T_CLOSURE]) === true) + if ((Conditions::hasCondition($phpcsFile, $stackPtr, [T_FUNCTION, T_CLOSURE]) === true) && $tokens[($stackPtr - 1)]['line'] < $tokens[$stackPtr]['line'] && $tokens[($stackPtr - 2)]['line'] === $tokens[($stackPtr - 1)]['line'] ) { diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc index 8c3101be77..08422605c6 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc @@ -1,5 +1,5 @@ 1, 8 => 2, 10 => 2, 22 => 1, @@ -116,7 +115,6 @@ public function getErrorList($testFile='') ]; case 'ArrayDeclarationUnitTest.2.inc': return [ - 2 => 1, 10 => 1, 23 => 2, 24 => 2, diff --git a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc index 0a0729a0a9..43d4f58598 100644 --- a/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc +++ b/src/Standards/Squiz/Tests/Classes/SelfMemberReferenceUnitTest.inc @@ -70,8 +70,8 @@ class MyClass { MyClass::test($value); }; - $array = array(1,2,3); - array_walk($array, $callback); + $array = MyClass::getValues(); // Non-namespaced relative, should be picked up. + $array = \MyClass::getValues(); // Non-namespaced absolute, should be picked up. } } @@ -96,11 +96,11 @@ class Foo namespace TYPO3\CMS\Reports; class Status { - const NOTICE = -2; - const INFO = -1; - const OK = 0; - const WARNING = 1; - const ERROR = 2; + + function test() { + $array = TYPO3\CMS\Reports\Status::getValues(); // Namespaced relative, should NOT be picked up. + $array = \TYPO3\CMS\Reports\Status::getValues(); // Namespaced absolute, should be picked up. + } } namespace TYPO3\CMS\Reports\Report\Status; @@ -159,7 +159,7 @@ class Nested_Anon_Class { namespace Foo\Baz { class BarFoo { public function foo() { - echo Foo\Baz\BarFoo::$prop; + echo \Foo\Baz\BarFoo::$prop; } } } @@ -168,7 +168,43 @@ namespace Foo\Baz { namespace Foo /*comment*/ \ Bah { class BarFoo { public function foo() { - echo Foo \ /*comment*/ Bah\BarFoo::$prop; + echo \Foo \ /*comment*/ Bah\BarFoo::$prop; } } } + +namespace FoolBah; + +$a = namespace\functionCall(); + +class Bah { + + function myFunction() + { + \FoolBah\Whatever::something(); + \FoolBah\Bah::something(); + FoolBah\Bah::something(); // Calls \FoolBah\FoolBah\Bah::something() ! + Bah::something(); + } +} + +namespace BahHumbug ?> + + + + 1, 32 => 2, 40 => 2, + 73 => 1, + 74 => 1, 92 => 1, + 102 => 1, 121 => 1, 132 => 1, 139 => 3, @@ -41,6 +44,10 @@ public function getErrorList() 143 => 2, 162 => 1, 171 => 1, + 185 => 1, + 187 => 1, + 199 => 1, + 207 => 1, ]; }//end getErrorList() diff --git a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc index 709b5f6ae4..1396d8c78b 100644 --- a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc @@ -242,3 +242,26 @@ $y = 10 + /* test */ -2; * See: {@link https://github.com/squizlabs/PHP_CodeSniffer/issues/1918} * @phpcs:disable Standard.Category.Sniff */ + +/* + * block comment with leading asterixes + * no capital letter. + */ + +/* + - List comment, correctly capitalized. + - Another list item, correctly capitalized. +*/ + +/* + - list comment, no capital letter. +*/ + +/* + * - List comment, correctly capitalized. + * - Another list item, correctly capitalized. + */ + +/* + * - list comment with leading asterixes, no capital letter + */ diff --git a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed index 8038ecb97b..d8ffce07e0 100644 --- a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed @@ -244,3 +244,26 @@ $y = 10 + /* test */ -2; * See: {@link https://github.com/squizlabs/PHP_CodeSniffer/issues/1918} * @phpcs:disable Standard.Category.Sniff */ + +/* + * block comment with leading asterixes + * no capital letter. + */ + +/* + - List comment, correctly capitalized. + - Another list item, correctly capitalized. +*/ + +/* + - list comment, no capital letter. +*/ + +/* + * - List comment, correctly capitalized. + * - Another list item, correctly capitalized. + */ + +/* + * - list comment with leading asterixes, no capital letter + */ diff --git a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.php index 113c987639..c256b04855 100644 --- a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.php @@ -75,6 +75,9 @@ public function getErrorList() 227 => 1, 232 => 1, 233 => 1, + 247 => 1, + 257 => 1, + 266 => 1, ]; return $errors; diff --git a/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.inc index eefa44c9bb..e09f839e97 100644 --- a/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.inc @@ -123,3 +123,5 @@ class Space_At_end { }//end class + +class NoCommentConfusedWithTrailingEndComment {} diff --git a/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.php index 80a404be07..e2c6463631 100644 --- a/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.php @@ -26,10 +26,11 @@ class ClassCommentUnitTest extends AbstractSniffUnitTest public function getErrorList() { return [ - 2 => 1, - 15 => 1, - 31 => 1, - 54 => 1, + 2 => 1, + 15 => 1, + 31 => 1, + 54 => 1, + 127 => 1, ]; }//end getErrorList() diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentThrowTagUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/FunctionCommentThrowTagUnitTest.inc index 02752af523..49649f7b83 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentThrowTagUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentThrowTagUnitTest.inc @@ -482,5 +482,12 @@ namespace { public function ThrowGlobalExceptionToo() { throw new SomeGlobalException(); } + + /** + * Missing throws tag. + */ + public /* comment */ function ThrowGlobalExceptionToo() { + throw new SomeGlobalException(); + } } } diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentThrowTagUnitTest.php b/src/Standards/Squiz/Tests/Commenting/FunctionCommentThrowTagUnitTest.php index 1e2d07fafb..3e24c60101 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentThrowTagUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentThrowTagUnitTest.php @@ -37,6 +37,7 @@ public function getErrorList() 219 => 1, 287 => 1, 397 => 1, + 488 => 1, ]; }//end getErrorList() diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc index 1a7020003f..8255efb1b7 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc @@ -1000,3 +1000,33 @@ public function foo(object $a, ?object $b) {} * @return void */ function foo($foo) {} + +// phpcs:set Squiz.Commenting.FunctionComment typeFormat short + +/** + * Test short form type preference. + * + * @param integer $fooLong Comment. + * @param boolean $barLong Comment. + * @param int $fooShort Comment. + * @param bool $barShort Comment. + * + * @return void + */ +public function shortForm($fooLong, $barLong, $fooShort, $barShort) {} + +// phpcs:set Squiz.Commenting.FunctionComment typeFormat long + +/** + * Description + * + * @return (int|bool)[] + */ +function MultiTypeArrayReturn() { + return []; +} + +class FooBar {/** Unrelated trailing comment using wrong comment style. */ + + public static function NoDocblock() {} +} diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index 4d91ecdcab..c51fe1c804 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -871,7 +871,7 @@ function returnTypeWithDescriptionD() /** * Yield from test * - * @return int[] + * @return integer[] */ function yieldFromTest() { @@ -1000,3 +1000,33 @@ public function foo(object $a, ?object $b) {} * @return void */ function foo($foo) {} + +// phpcs:set Squiz.Commenting.FunctionComment typeFormat short + +/** + * Test short form type preference. + * + * @param int $fooLong Comment. + * @param bool $barLong Comment. + * @param int $fooShort Comment. + * @param bool $barShort Comment. + * + * @return void + */ +public function shortForm($fooLong, $barLong, $fooShort, $barShort) {} + +// phpcs:set Squiz.Commenting.FunctionComment typeFormat long + +/** + * Description + * + * @return (integer|boolean)[] + */ +function MultiTypeArrayReturn() { + return []; +} + +class FooBar {/** Unrelated trailing comment using wrong comment style. */ + + public static function NoDocblock() {} +} diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php index e8e02f8496..5e3329f540 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php @@ -26,93 +26,98 @@ class FunctionCommentUnitTest extends AbstractSniffUnitTest public function getErrorList() { $errors = [ - 5 => 1, - 10 => 3, - 12 => 2, - 13 => 2, - 14 => 1, - 15 => 1, - 28 => 1, - 43 => 1, - 76 => 1, - 87 => 1, - 103 => 1, - 109 => 1, - 112 => 1, - 122 => 1, - 123 => 3, - 124 => 2, - 125 => 1, - 126 => 1, - 137 => 4, - 138 => 4, - 139 => 4, - 143 => 2, - 152 => 1, - 155 => 2, - 159 => 1, - 166 => 1, - 173 => 1, - 183 => 1, - 190 => 2, - 193 => 2, - 196 => 1, - 199 => 2, - 210 => 1, - 211 => 1, - 222 => 1, - 223 => 1, - 224 => 1, - 225 => 1, - 226 => 1, - 227 => 1, - 230 => 2, - 232 => 2, - 246 => 1, - 248 => 4, - 261 => 1, - 263 => 1, - 276 => 1, - 277 => 1, - 278 => 1, - 279 => 1, - 280 => 1, - 281 => 1, - 284 => 1, - 286 => 7, - 294 => 1, - 302 => 1, - 312 => 1, - 358 => 1, - 359 => 2, - 372 => 1, - 373 => 1, - 387 => 1, - 407 => 1, - 441 => 1, - 500 => 1, - 526 => 1, - 548 => 1, - 641 => 1, - 669 => 1, - 688 => 1, - 744 => 1, - 748 => 1, - 767 => 1, - 789 => 1, - 792 => 1, - 794 => 1, - 797 => 1, - 801 => 1, - 828 => 1, - 840 => 1, - 852 => 1, - 864 => 1, - 886 => 1, - 888 => 1, - 890 => 1, - 978 => 1, - 997 => 1, + 5 => 1, + 10 => 3, + 12 => 2, + 13 => 2, + 14 => 1, + 15 => 1, + 28 => 1, + 43 => 1, + 76 => 1, + 87 => 1, + 103 => 1, + 109 => 1, + 112 => 1, + 122 => 1, + 123 => 3, + 124 => 2, + 125 => 1, + 126 => 1, + 137 => 4, + 138 => 4, + 139 => 4, + 143 => 2, + 152 => 1, + 155 => 2, + 159 => 1, + 166 => 1, + 173 => 1, + 183 => 1, + 190 => 2, + 193 => 2, + 196 => 1, + 199 => 2, + 210 => 1, + 211 => 1, + 222 => 1, + 223 => 1, + 224 => 1, + 225 => 1, + 226 => 1, + 227 => 1, + 230 => 2, + 232 => 2, + 246 => 1, + 248 => 4, + 261 => 1, + 263 => 1, + 276 => 1, + 277 => 1, + 278 => 1, + 279 => 1, + 280 => 1, + 281 => 1, + 284 => 1, + 286 => 7, + 294 => 1, + 302 => 1, + 312 => 1, + 358 => 1, + 359 => 2, + 372 => 1, + 373 => 1, + 387 => 1, + 407 => 1, + 441 => 1, + 500 => 1, + 526 => 1, + 548 => 1, + 641 => 1, + 669 => 1, + 688 => 1, + 744 => 1, + 748 => 1, + 767 => 1, + 789 => 1, + 792 => 1, + 794 => 1, + 797 => 1, + 801 => 1, + 828 => 1, + 840 => 1, + 852 => 1, + 864 => 1, + 874 => 1, + 886 => 1, + 888 => 1, + 890 => 1, + 978 => 1, + 997 => 1, + 1009 => 1, + 1010 => 1, + 1023 => 1, + 1031 => 1, ]; // Scalar type hints only work from PHP 7 onwards. @@ -128,6 +133,7 @@ public function getErrorList() $errors[575] = 2; $errors[627] = 1; $errors[1002] = 1; + $errors[1016] = 4; } else { $errors[729] = 4; $errors[740] = 2; diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc index 0eedb686a1..a5e88db052 100644 --- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc @@ -139,7 +139,7 @@ class VariableCommentUnitTest /** - * Var type checking (array(int => string) v.s. array(int => string)). + * Var type checking (array(int => string) v.s. array(integer => string)). * * @var array(int => string) */ @@ -343,3 +343,45 @@ class Foo public static $variableName = array(); } + +// phpcs:set Squiz.Commenting.VariableComment typeFormat short +class ShortForm { + + /** + * @var boolean + */ + public $fooLong = true; + + /** + * @var bool + */ + public $fooShort = true; + + /** + * @var integer + */ + public $barLong = 1; + + /** + * @var int + */ + public $barShort = 1; +} +// phpcs:set Squiz.Commenting.VariableComment typeFormat long + +class PSR5Types { + /** + * @var bool|int + */ + public $foo = true; + + /** + * @var real[] + */ + public $bar = 1.5; +} + +class CommentConfusion { /** This is a trailing end comment, not a property comment. */ + + public $prop; +} diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed index c1af18faed..959f2879c5 100644 --- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed @@ -139,7 +139,7 @@ class VariableCommentUnitTest /** - * Var type checking (array(int => string) v.s. array(int => string)). + * Var type checking (array(int => string) v.s. array(integer => string)). * * @var array(integer => string) */ @@ -343,3 +343,45 @@ class Foo public static $variableName = array(); } + +// phpcs:set Squiz.Commenting.VariableComment typeFormat short +class ShortForm { + + /** + * @var bool + */ + public $fooLong = true; + + /** + * @var bool + */ + public $fooShort = true; + + /** + * @var int + */ + public $barLong = 1; + + /** + * @var int + */ + public $barShort = 1; +} +// phpcs:set Squiz.Commenting.VariableComment typeFormat long + +class PSR5Types { + /** + * @var boolean|integer + */ + public $foo = true; + + /** + * @var float[] + */ + public $bar = 1.5; +} + +class CommentConfusion { /** This is a trailing end comment, not a property comment. */ + + public $prop; +} diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.php index 7e9d1dcd8c..a8c2b7572c 100644 --- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.php @@ -56,6 +56,11 @@ public function getErrorList() 294 => 1, 311 => 1, 336 => 1, + 351 => 1, + 361 => 1, + 374 => 1, + 379 => 1, + 386 => 1, ]; }//end getErrorList() diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc index 30a2b98fbe..5e16b07926 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc @@ -167,3 +167,9 @@ $foo = $bar ?? $baz ?? ''; $foo = $myString{-1}; $value = (binary) $blah + b"binary $foo"; + +$value = + + 1.1; + +$value = + /*comment*/ + 1.1; diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed index 409091db62..0703e98de3 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed @@ -22,7 +22,7 @@ $value = ($one + ($two + $three)); $value++; $value--; $value = -1; -$value = - 1; +$value = -1; $value = (1 + 2); $value = (1 + 2); @@ -106,7 +106,7 @@ class MyClass }//end class if ($index < -1) $index = 0; -if ($index < - 1) $index = 0; +if ($index < -1) $index = 0; $three = ceil($one / $two); $three = ceil(($one / $two) / $four); @@ -127,7 +127,7 @@ $expectedPermission = array( 'denied' => 1, 'cascade' => TRUE, 'blockergranted' => 2, - 'blockerdenied' => - 3, + 'blockerdenied' => -3, 'effective' => TRUE, ); @@ -167,3 +167,8 @@ $foo = ($bar ?? $baz ?? ''); $foo = $myString{-1}; $value = ((binary) $blah + b"binary $foo"); + +$value = +1.1; + +$value = + /*comment*/ + 1.1; diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.js b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.js index 92ed8038a3..f530c5d3c6 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.js +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.js @@ -116,3 +116,6 @@ if (something === true if (true === /^\d*\.?\d*$/.test(input)) return true; if ( ! /^(?:a|select)$/i.test( element.tagName ) ) return true; + +value = + + 1; diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.js.fixed b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.js.fixed index 04e35d977c..e93046696e 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.js.fixed +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.js.fixed @@ -21,7 +21,7 @@ value = one + (two + three); value++; value--; value = -1; -value = - 1; +value = -1; value = (1 + 2); value = 1 + 2; @@ -60,7 +60,7 @@ function myFunction() params['mode'] = id.replace(/WidgetType/, ''); if (index < -1) index = 0; -if (index < - 1) index = 0; +if (index < -1) index = 0; var classN = prvId.replace(/\./g, '-'); @@ -116,3 +116,5 @@ if (something === true if (true === /^\d*\.?\d*$/.test(input)) return true; if ( ! /^(?:a|select)$/i.test( element.tagName ) ) return true; + +value = +1; diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php index 651319b877..a8005e508e 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php @@ -66,6 +66,8 @@ public function getErrorList($testFile='OperatorBracketUnitTest.inc') 163 => 2, 165 => 2, 169 => 1, + 171 => 1, + 174 => 1, ]; break; case 'OperatorBracketUnitTest.js': @@ -83,8 +85,9 @@ public function getErrorList($testFile='OperatorBracketUnitTest.inc') 47 => 1, 63 => 1, 108 => 1, + 120 => 1, ]; - break; + break; default: return []; break; diff --git a/src/Standards/Squiz/Tests/Functions/GlobalFunctionUnitTest.inc b/src/Standards/Squiz/Tests/Functions/GlobalFunctionUnitTest.inc index b7b7f205c7..3c43633be8 100644 --- a/src/Standards/Squiz/Tests/Functions/GlobalFunctionUnitTest.inc +++ b/src/Standards/Squiz/Tests/Functions/GlobalFunctionUnitTest.inc @@ -8,10 +8,13 @@ class MyClass interface MyInterface { - function func1() {} + function func1(); } function __autoload($class) {} echo preg_replace_callback('~-([a-z])~', function ($match) { return strtoupper($match[1]); }, 'hello-world'); -?> + +if ( ! function_exists('conditionalFunction') ) { + function conditionalFunction() {} +} diff --git a/src/Standards/Squiz/Tests/Functions/GlobalFunctionUnitTest.php b/src/Standards/Squiz/Tests/Functions/GlobalFunctionUnitTest.php index 7be76488e6..b425b18fe9 100644 --- a/src/Standards/Squiz/Tests/Functions/GlobalFunctionUnitTest.php +++ b/src/Standards/Squiz/Tests/Functions/GlobalFunctionUnitTest.php @@ -40,7 +40,10 @@ public function getErrorList() */ public function getWarningList() { - return [2 => 1]; + return [ + 2 => 1, + 19 => 1, + ]; }//end getWarningList() diff --git a/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc b/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc index 4d574ef32f..f638ac883c 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc @@ -34,7 +34,7 @@ $var = myFunction( $this, !$directLinks, FALSE, - ); + ), ); for ($node = $fieldsTag->nextSibling; $node; $node = $node->nextSibling) { @@ -54,3 +54,16 @@ $a = $b === true ? $c : $d; $this->_args = $this->_getArgs(($_SERVER['argv'] ?? [])); $args = ($_SERVER['argv'] ?? []); + +function ignoreMe($a = !SOME_CONSTANT){} +$closure = function ($a = !SOME_CONSTANT){}; + +$var = /* comment */ myFunction(!$var); +$var = namespace\myFunction(!$var); +$var = self::myFunction(!$var); +$var = myFunction($var) || $bar; + +$compound = $array + array(!$something); +$compound = $array !== array($something); +$compound = $array + [!$something]; +$compound = $array !== [$something]; diff --git a/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.php b/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.php index c9bb3da60f..1696d93c0b 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.php @@ -34,6 +34,9 @@ public function getErrorList() 10 => 1, 52 => 1, 53 => 1, + 64 => 1, + 67 => 1, + 69 => 1, ]; }//end getErrorList() diff --git a/src/Standards/Squiz/Tests/Scope/MemberVarScopeUnitTest.inc b/src/Standards/Squiz/Tests/Scope/MemberVarScopeUnitTest.inc index d8e3069e88..23a5548efe 100644 --- a/src/Standards/Squiz/Tests/Scope/MemberVarScopeUnitTest.inc +++ b/src/Standards/Squiz/Tests/Scope/MemberVarScopeUnitTest.inc @@ -22,8 +22,8 @@ class Foo { }//end class class MyClass { - $anonFunc = function($foo) use ($bar) {}; - public $anonFunc2 = function($foo2) use ($bar2) {}; + $anonFunc = function($foo) use ($bar) {}; // Intentional parse error. + public $anonFunc2 = function($foo2) use ($bar2) {}; // Intentional parse error. public function method($var1, $var2) { $anon = new class { $p1 = null; diff --git a/src/Standards/Squiz/Tests/Scope/MemberVarScopeUnitTest.php b/src/Standards/Squiz/Tests/Scope/MemberVarScopeUnitTest.php index 59e49910df..782a93c765 100644 --- a/src/Standards/Squiz/Tests/Scope/MemberVarScopeUnitTest.php +++ b/src/Standards/Squiz/Tests/Scope/MemberVarScopeUnitTest.php @@ -27,7 +27,7 @@ public function getErrorList() { return [ 7 => 1, - 25 => 1, + 25 => 3, 29 => 1, 33 => 1, 39 => 1, @@ -49,7 +49,7 @@ public function getErrorList() */ public function getWarningList() { - return [71 => 1]; + return []; }//end getWarningList() diff --git a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc index 4ae1fd0340..575bc2193b 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc @@ -11,7 +11,7 @@ class MyClass }//end class -interface MyInterface +trait MyTrait { public $var1 = 'value'; @@ -45,7 +45,7 @@ class MyClass }//end class -interface MyInterface +trait MyTrait { public $var1 = 'value'; function myFunction(); @@ -202,7 +202,7 @@ class MyClass }//end class -interface MyInterface +trait MyTrait { /* testing */ diff --git a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed index 38324473b4..b07caba8e6 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed @@ -11,7 +11,7 @@ class MyClass }//end class -interface MyInterface +trait MyTrait { public $var1 = 'value'; @@ -43,7 +43,7 @@ class MyClass }//end class -interface MyInterface +trait MyTrait { public $var1 = 'value'; @@ -199,7 +199,7 @@ class MyClass }//end class -interface MyInterface +trait MyTrait { /* testing */ public $var1 = 'value'; diff --git a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc index e50c094948..09010986c7 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc @@ -260,3 +260,5 @@ function bar(): array {} if ($line{-1} === ':') { $line = substr($line, 0, -1); } + +$a = (bool) -1; diff --git a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed index 7d61c83ed9..cc33ec1e28 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed @@ -254,3 +254,5 @@ function bar(): array {} if ($line{-1} === ':') { $line = substr($line, 0, -1); } + +$a = (bool) -1; diff --git a/src/Standards/Zend/Sniffs/Debug/CodeAnalyzerSniff.php b/src/Standards/Zend/Sniffs/Debug/CodeAnalyzerSniff.php index 9071209782..d18c947294 100644 --- a/src/Standards/Zend/Sniffs/Debug/CodeAnalyzerSniff.php +++ b/src/Standards/Zend/Sniffs/Debug/CodeAnalyzerSniff.php @@ -39,6 +39,7 @@ public function register() * the token was found. * * @return int + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If ZendCodeAnalyzer could not be run. */ public function process(File $phpcsFile, $stackPtr) { diff --git a/src/Standards/Zend/Sniffs/NamingConventions/ValidVariableNameSniff.php b/src/Standards/Zend/Sniffs/NamingConventions/ValidVariableNameSniff.php index ea98fd2847..3de8f76a99 100644 --- a/src/Standards/Zend/Sniffs/NamingConventions/ValidVariableNameSniff.php +++ b/src/Standards/Zend/Sniffs/NamingConventions/ValidVariableNameSniff.php @@ -10,8 +10,10 @@ namespace PHP_CodeSniffer\Standards\Zend\Sniffs\NamingConventions; use PHP_CodeSniffer\Sniffs\AbstractVariableSniff; -use PHP_CodeSniffer\Util\Common; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHP_CodeSniffer\Util\Sniffs\Variables; use PHP_CodeSniffer\Util\Tokens; class ValidVariableNameSniff extends AbstractVariableSniff @@ -29,11 +31,10 @@ class ValidVariableNameSniff extends AbstractVariableSniff */ protected function processVariable(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - $varName = ltrim($tokens[$stackPtr]['content'], '$'); + $tokens = $phpcsFile->getTokens(); // If it's a php reserved var, then its ok. - if (isset($this->phpReservedVars[$varName]) === true) { + if (Variables::isPHPReservedVarName($tokens[$stackPtr]['content']) === true) { return; } @@ -56,7 +57,7 @@ protected function processVariable(File $phpcsFile, $stackPtr) $objVarName = substr($objVarName, 1); } - if (Common::isCamelCaps($objVarName, false, true, false) === false) { + if (ConstructNames::isCamelCaps($objVarName, false, true, false) === false) { $error = 'Variable "%s" is not in valid camel caps format'; $data = [$originalVarName]; $phpcsFile->addError($error, $var, 'NotCamelCaps', $data); @@ -72,6 +73,7 @@ protected function processVariable(File $phpcsFile, $stackPtr) // There is no way for us to know if the var is public or private, // so we have to ignore a leading underscore if there is one and just // check the main part of the variable name. + $varName = ltrim($tokens[$stackPtr]['content'], '$'); $originalVarName = $varName; if (substr($varName, 0, 1) === '_') { $objOperator = $phpcsFile->findPrevious([T_WHITESPACE], ($stackPtr - 1), null, true); @@ -80,7 +82,7 @@ protected function processVariable(File $phpcsFile, $stackPtr) // this: MyClass::$_variable, so we don't know its scope. $inClass = true; } else { - $inClass = $phpcsFile->hasCondition($stackPtr, Tokens::$ooScopeTokens); + $inClass = Conditions::hasCondition($phpcsFile, $stackPtr, Tokens::$ooScopeTokens); } if ($inClass === true) { @@ -88,7 +90,7 @@ protected function processVariable(File $phpcsFile, $stackPtr) } } - if (Common::isCamelCaps($varName, false, true, false) === false) { + if (ConstructNames::isCamelCaps($varName, false, true, false) === false) { $error = 'Variable "%s" is not in valid camel caps format'; $data = [$originalVarName]; $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $data); @@ -112,15 +114,14 @@ protected function processVariable(File $phpcsFile, $stackPtr) */ protected function processMemberVar(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - $varName = ltrim($tokens[$stackPtr]['content'], '$'); - $memberProps = $phpcsFile->getMemberProperties($stackPtr); + $memberProps = Variables::getMemberProperties($phpcsFile, $stackPtr); if (empty($memberProps) === true) { - // Exception encountered. return; } - $public = ($memberProps['scope'] === 'public'); + $tokens = $phpcsFile->getTokens(); + $varName = ltrim($tokens[$stackPtr]['content'], '$'); + $public = ($memberProps['scope'] === 'public'); if ($public === true) { if (substr($varName, 0, 1) === '_') { @@ -143,7 +144,7 @@ protected function processMemberVar(File $phpcsFile, $stackPtr) // Remove a potential underscore prefix for testing CamelCaps. $varName = ltrim($varName, '_'); - if (Common::isCamelCaps($varName, false, true, false) === false) { + if (ConstructNames::isCamelCaps($varName, false, true, false) === false) { $error = 'Member variable "%s" is not in valid camel caps format'; $data = [$varName]; $phpcsFile->addError($error, $stackPtr, 'MemberVarNotCamelCaps', $data); @@ -172,11 +173,11 @@ protected function processVariableInString(File $phpcsFile, $stackPtr) if (preg_match_all('|[^\\\]\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)|', $tokens[$stackPtr]['content'], $matches) !== 0) { foreach ($matches[1] as $varName) { // If it's a php reserved var, then its ok. - if (isset($this->phpReservedVars[$varName]) === true) { + if (Variables::isPHPReservedVarName($varName) === true) { continue; } - if (Common::isCamelCaps($varName, false, true, false) === false) { + if (ConstructNames::isCamelCaps($varName, false, true, false) === false) { $error = 'Variable "%s" is not in valid camel caps format'; $data = [$varName]; $phpcsFile->addError($error, $stackPtr, 'StringVarNotCamelCaps', $data); diff --git a/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.inc b/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.inc index 1bf486cab6..afce96a975 100644 --- a/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.inc +++ b/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.inc @@ -102,7 +102,7 @@ var_dump($http_response_header); var_dump($HTTP_RAW_POST_DATA); var_dump($php_errormsg); -interface Base +trait Base { protected $anonymous; diff --git a/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.php b/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.php index d22b24fd3f..b5fcfee57f 100644 --- a/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.php +++ b/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.php @@ -51,6 +51,7 @@ public function getErrorList() 79 => 1, 96 => 1, 99 => 1, + 107 => 1, 113 => 1, 116 => 1, ]; @@ -69,22 +70,21 @@ public function getErrorList() public function getWarningList() { return [ - 6 => 1, - 14 => 1, - 20 => 1, - 26 => 1, - 32 => 1, - 39 => 1, - 45 => 1, - 51 => 1, - 64 => 1, - 70 => 1, - 73 => 1, - 76 => 1, - 79 => 1, - 82 => 1, - 94 => 1, - 107 => 1, + 6 => 1, + 14 => 1, + 20 => 1, + 26 => 1, + 32 => 1, + 39 => 1, + 45 => 1, + 51 => 1, + 64 => 1, + 70 => 1, + 73 => 1, + 76 => 1, + 79 => 1, + 82 => 1, + 94 => 1, ]; }//end getWarningList() diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index e52dc43e27..96b27e930e 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -285,7 +285,7 @@ class PHP extends Tokenizer /** * Known lengths of tokens. * - * @var array + * @var array */ public $knownLengths = [ T_ABSTRACT => 8, diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index bc904586ca..a9d3fd4050 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -9,7 +9,7 @@ namespace PHP_CodeSniffer\Tokenizers; -use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Exceptions\TokenizerException; use PHP_CodeSniffer\Util; abstract class Tokenizer @@ -60,7 +60,7 @@ abstract class Tokenizer /** * Known lengths of tokens. * - * @var array + * @var array */ public $knownLengths = []; @@ -879,6 +879,7 @@ private function createScopeMap() * @param int $ignore How many curly braces we are ignoring. * * @return int The position in the stack that closed the scope. + * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the nesting level gets too deep. */ private function recurseScopeMap($stackPtr, $depth=1, &$ignore=0) { @@ -1215,7 +1216,7 @@ private function recurseScopeMap($stackPtr, $depth=1, &$ignore=0) echo '* reached maximum nesting level; aborting *'.PHP_EOL; } - throw new RuntimeException('Maximum nesting level reached; file could not be processed'); + throw new TokenizerException('Maximum nesting level reached; file could not be processed'); } $oldDepth = $depth; diff --git a/src/Util/Common.php b/src/Util/Common.php index e65c923ae7..eaed5eb519 100644 --- a/src/Util/Common.php +++ b/src/Util/Common.php @@ -16,6 +16,8 @@ class Common * An array of variable types for param/var we will check. * * @var string[] + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\Comments::$allowedTypes instead. */ public static $allowedTypes = [ 'array', @@ -279,6 +281,8 @@ public static function prepareForOutput($content, $exclude=[]) * for acronyms. * * @return boolean + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\ConstructNames::isCamelCaps() instead. */ public static function isCamelCaps( $string, @@ -286,61 +290,7 @@ public static function isCamelCaps( $public=true, $strict=true ) { - // Check the first character first. - if ($classFormat === false) { - $legalFirstChar = ''; - if ($public === false) { - $legalFirstChar = '[_]'; - } - - if ($strict === false) { - // Can either start with a lowercase letter, or multiple uppercase - // in a row, representing an acronym. - $legalFirstChar .= '([A-Z]{2,}|[a-z])'; - } else { - $legalFirstChar .= '[a-z]'; - } - } else { - $legalFirstChar = '[A-Z]'; - } - - if (preg_match("/^$legalFirstChar/", $string) === 0) { - return false; - } - - // Check that the name only contains legal characters. - $legalChars = 'a-zA-Z0-9'; - if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) { - return false; - } - - if ($strict === true) { - // Check that there are not two capital letters next to each other. - $length = strlen($string); - $lastCharWasCaps = $classFormat; - - for ($i = 1; $i < $length; $i++) { - $ascii = ord($string{$i}); - if ($ascii >= 48 && $ascii <= 57) { - // The character is a number, so it cant be a capital. - $isCaps = false; - } else { - if (strtoupper($string{$i}) === $string{$i}) { - $isCaps = true; - } else { - $isCaps = false; - } - } - - if ($isCaps === true && $lastCharWasCaps === true) { - return false; - } - - $lastCharWasCaps = $isCaps; - } - }//end if - - return true; + return Sniffs\ConstructNames::isCamelCaps($string, $classFormat, $public, $strict); }//end isCamelCaps() @@ -351,34 +301,12 @@ public static function isCamelCaps( * @param string $string The string to verify. * * @return boolean + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\ConstructNames::isUnderscoreName() instead. */ public static function isUnderscoreName($string) { - // If there are space in the name, it can't be valid. - if (strpos($string, ' ') !== false) { - return false; - } - - $validName = true; - $nameBits = explode('_', $string); - - if (preg_match('|^[A-Z]|', $string) === 0) { - // Name does not begin with a capital letter. - $validName = false; - } else { - foreach ($nameBits as $bit) { - if ($bit === '') { - continue; - } - - if ($bit{0} !== strtoupper($bit{0})) { - $validName = false; - break; - } - } - } - - return $validName; + return Sniffs\ConstructNames::isUnderscoreName($string); }//end isUnderscoreName() @@ -392,67 +320,16 @@ public static function isUnderscoreName($string) * @param string $varType The variable type to process. * * @return string + * + * @deprecated 3.5.0 Use PHP_CodeSniffer\Util\Sniffs\Comments::suggestType() instead. + * Note: the moved function has undergone significant changes + * compared to the original function. + * The call from this function preserves BC as much as possible. */ public static function suggestType($varType) { - if ($varType === '') { - return ''; - } - - if (in_array($varType, self::$allowedTypes, true) === true) { - return $varType; - } else { - $lowerVarType = strtolower($varType); - switch ($lowerVarType) { - case 'bool': - case 'boolean': - return 'boolean'; - case 'double': - case 'real': - case 'float': - return 'float'; - case 'int': - case 'integer': - return 'integer'; - case 'array()': - case 'array': - return 'array'; - }//end switch - - if (strpos($lowerVarType, 'array(') !== false) { - // Valid array declaration: - // array, array(type), array(type1 => type2). - $matches = []; - $pattern = '/^array\(\s*([^\s^=^>]*)(\s*=>\s*(.*))?\s*\)/i'; - if (preg_match($pattern, $varType, $matches) !== 0) { - $type1 = ''; - if (isset($matches[1]) === true) { - $type1 = $matches[1]; - } - - $type2 = ''; - if (isset($matches[3]) === true) { - $type2 = $matches[3]; - } - - $type1 = self::suggestType($type1); - $type2 = self::suggestType($type2); - if ($type2 !== '') { - $type2 = ' => '.$type2; - } - - return "array($type1$type2)"; - } else { - return 'array'; - }//end if - } else if (in_array($lowerVarType, self::$allowedTypes, true) === true) { - // A valid type, but not lower cased. - return $lowerVarType; - } else { - // Must be a custom type name. - return $varType; - }//end if - }//end if + $types = array_combine(self::$allowedTypes, self::$allowedTypes); + return Sniffs\Comments::suggestType($varType, 'long', $types); }//end suggestType() diff --git a/src/Util/Sniffs/Comments.php b/src/Util/Sniffs/Comments.php new file mode 100644 index 0000000000..26d38ebbfb --- /dev/null +++ b/src/Util/Sniffs/Comments.php @@ -0,0 +1,755 @@ + + * @author Juliette Reinders Folmer + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; + +class Comments +{ + + /** + * Regex to split unioned type strings without splitting multi-type PSR-5 + * array types or "old-style" array types. + * + * @var string + */ + const SPLIT_UNION_TYPES = '`(?:^(?Parray\(\s*([^\s=>]*)(?:\s*=>\s*+(.*))?\s*\)|array<\s*([^\s,]*)(?:\s*,\s*+(.*))?\s*>|\([^)]+\)\[\]|[^|]+)(?=|)|(?<=|)(?P>type)$|(?<=|)(?P>type)(?=|))`i'; + + /** + * Regex to match array(type), array(type1 => type2) types. + * + * @var string + */ + const MATCH_ARRAY = '`^array\(\s*([^\s=>]*)(?:\s*=>\s*+(.*))?\s*\)`i'; + + /** + * Regex to match array types. + * + * @var string + */ + const MATCH_ARRAY_SQUARE = '`^array<\s*([^\s,]*)(?:\s*,\s*+(.*))?\s*>`i'; + + + /** + * Valid variable types for param/var/return tags. + * + * Keys are short-form, values long-form. + * + * @var string[] + */ + public static $allowedTypes = [ + 'array' => 'array', + 'bool' => 'boolean', + 'callable' => 'callable', + 'false' => 'false', + 'float' => 'float', + 'int' => 'integer', + 'iterable' => 'iterable', + 'mixed' => 'mixed', + 'null' => 'null', + 'object' => 'object', + 'resource' => 'resource', + 'self' => 'self', + 'static' => 'static', + 'string' => 'string', + 'true' => 'true', + 'void' => 'void', + '$this' => '$this', + ]; + + + /** + * Examine a complete variable type string for param/var tags. + * + * Examines the individual parts of unioned and intersectioned types. + * - Where relevant, will unduplicate types. + * - Where relevant, will combine multiple single/multi-types array types into one. + * - Where relevant, will remove duplicate union/intersect separators. + * + * @param string $typeString The complete variable type string to process. + * @param string $form Optional. Whether to prefer long-form or short-form + * types. By default, this only affects the integer and + * boolean types. + * Accepted values: 'long', 'short'. Defaults to `short`. + * @param array|null $allowedTypes Optional. Array of allowed variable types. + * Keys are short form types, values long form. + * Both lowercase. + * If for a particular standard, long/short form does + * not apply, keys and values should be the same. + * + * @return string Valid variable type string. + */ + public static function suggestTypeString($typeString, $form='short', $allowedTypes=null) + { + // Check for PSR-5 Union types, like `int|null`. + if (strpos($typeString, '|') !== false && $typeString !== '|') { + $arrayCount = substr_count($typeString, '[]'); + $typeCount = preg_match_all(self::SPLIT_UNION_TYPES, $typeString, $matches); + $types = $matches[0]; + if ($typeCount > 0) { + if ($arrayCount < 2) { + // No or only one array type found, process like normal. + $formArray = array_fill(0, $typeCount, $form); + $allowedArray = array_fill(0, $typeCount, $allowedTypes); + $types = array_map('self::suggestType', $types, $formArray, $allowedArray); + $types = array_unique($types); + } else { + // Ok, so there were two or more array types in this type string. Let's combine them. + $newTypes = []; + $arrayTypes = []; + $firstArrayType = null; + foreach ($types as $order => $type) { + if (substr($type, 0, 1) === '(' && substr($type, -3) === ')[]') { + if ($firstArrayType === null) { + $firstArrayType = $order; + } + + $subTypes = explode('|', substr($type, 1, -3)); + // Remove empty entries. + $subTypes = array_filter($subTypes); + foreach ($subTypes as $subType) { + $arrayTypes[] = self::suggestType($subType, $form, $allowedTypes); + } + } else if (substr($type, -2) === '[]') { + if ($firstArrayType === null) { + $firstArrayType = $order; + } + + $arrayTypes[] = self::suggestType(substr($type, 0, -2), $form, $allowedTypes); + } else { + $newTypes[$order] = self::suggestTypeString($type, $form, $allowedTypes); + } + }//end foreach + + $newTypes = array_unique($newTypes); + $arrayTypes = array_unique($arrayTypes); + $arrayTypeCount = count($arrayTypes); + if ($arrayTypeCount > 1) { + $newTypes[$firstArrayType] = '('.implode('|', $arrayTypes).')[]'; + } else if ($arrayTypeCount === 1) { + $newTypes[$firstArrayType] = implode('', $arrayTypes).'[]'; + } + + $types = $newTypes; + ksort($types); + }//end if + }//end if + + // Check if both null as well as nullable types are used and if so, remove nullable indicator. + if (array_search('null', $types, true) !== false) { + foreach ($types as $key => $type) { + if (strpos($type, '?') === 0) { + $types[$key] = ltrim($type, '?'); + } + } + } + + return implode('|', $types); + }//end if + + // Check for PSR-5 Intersection types, like `\MyClass&\PHPUnit\Framework\MockObject\MockObject`. + if (strpos($typeString, '&') !== false && $typeString !== '&') { + $types = explode('&', $typeString); + // Remove empty entries. + $types = array_filter($types); + $typeCount = count($types); + $formArray = array_fill(0, $typeCount, $form); + $allowedArray = array_fill(0, $typeCount, $allowedTypes); + $types = array_map('self::suggestType', $types, $formArray, $allowedArray); + $types = array_unique($types); + return implode('&', $types); + } + + // Simple type. + return self::suggestType($typeString, $form, $allowedTypes); + + }//end suggestTypeString() + + + /** + * Returns a valid variable type for param/var tags. + * + * If type is not one of the standard types, it must be a custom type. + * Returns the correct type name suggestion if type name is invalid. + * + * @param string $varType The variable type to process. + * @param string $form Optional. Whether to prefer long-form or short-form + * types. By default, this only affects the integer and + * boolean types. + * Accepted values: 'long', 'short'. Defaults to `short`. + * @param array|null $allowedTypes Optional. Array of allowed variable types. + * Keys are short form types, values long form. + * Both lowercase. + * If for a particular standard, long/short form does + * not apply, keys and values should be the same. + * + * @return string + */ + public static function suggestType($varType, $form='short', $allowedTypes=null) + { + if ($allowedTypes === null) { + $allowedTypes = self::$allowedTypes; + } + + if ($varType === '') { + return ''; + } + + $lowerVarType = strtolower(trim($varType)); + + if (($form === 'short' && isset($allowedTypes[$lowerVarType]) === true) + || ($form === 'long' && in_array($lowerVarType, $allowedTypes, true) === true) + ) { + return $lowerVarType; + } + + // Check for short form use when long form is expected and visa versa. + if ($form === 'long' && isset($allowedTypes[$lowerVarType]) === true) { + return $allowedTypes[$lowerVarType]; + } + + if ($form === 'short' && in_array($lowerVarType, $allowedTypes, true) === true) { + return array_search($lowerVarType, $allowedTypes, true); + } + + // Not listed in allowed types, check for a limited set of known variations. + switch ($lowerVarType) { + case 'double': + case 'real': + return 'float'; + + case 'array()': + case 'array<>': + return 'array'; + }//end switch + + // Handle more complex types, like arrays. + if (strpos($lowerVarType, 'array(') !== false || strpos($lowerVarType, 'array<') !== false) { + // Valid array declarations: + // array, array(type), array(type1 => type2), array. + $open = '('; + $close = ')'; + $sep = ' =>'; + $pattern = self::MATCH_ARRAY; + if (strpos($lowerVarType, 'array<') !== false) { + $open = '<'; + $close = '>'; + $sep = ','; + $pattern = self::MATCH_ARRAY_SQUARE; + } + + $matches = []; + if (preg_match($pattern, $varType, $matches) === 1) { + $type1 = ''; + if (isset($matches[1]) === true) { + $type1 = self::suggestTypeString($matches[1], $form, $allowedTypes); + } + + $type2 = ''; + if (isset($matches[2]) === true) { + $type2 = self::suggestTypeString($matches[2], $form, $allowedTypes); + if ($type2 !== '') { + $type2 = $sep.' '.$type2; + } + } + + return 'array'.$open.$type1.$type2.$close; + }//end if + + return 'array'; + }//end if + + // Check for PSR-5 multiple type array format, like `(int|string)[]`. + if (strpos($varType, '(') === 0 && substr($varType, -3) === ')[]' && $varType !== '()[]') { + return '('.self::suggestTypeString(substr($varType, 1, -3), $form, $allowedTypes).')[]'; + } + + // Check for PSR-5 single type array format, like `int[]`. + if (strpos($varType, '|') === false && substr($varType, -2) === '[]' && $varType !== '[]') { + return self::suggestType(substr($varType, 0, -2), $form, $allowedTypes).'[]'; + } + + // Allow for nullable type format, like `?string`. + if (strpos($varType, '?') === 0) { + return '?'.self::suggestType(substr($varType, 1), $form, $allowedTypes); + } + + // Must be a custom type name. + return $varType; + + }//end suggestType() + + + /** + * Find the end of a docblock, inline or block comment sequence. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the + * start of the comment. + * + * @return int Stack pointer to the end of the comment. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified $stackPtr is + * not of type T_COMMENT or + * T_DOC_COMMENT_OPEN_TAG or if it + * is not the start of a comment. + */ + public static function findEndOfComment(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] !== T_COMMENT + && $tokens[$stackPtr]['code'] !== T_DOC_COMMENT_OPEN_TAG + ) { + throw new RuntimeException('$stackPtr must be of type T_COMMENT or T_DOC_COMMENT_OPEN_TAG'); + } + + if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_OPEN_TAG) { + return $tokens[$stackPtr]['comment_closer']; + } + + // Find the end of inline comment blocks. + if (strpos($tokens[$stackPtr]['content'], '//') === 0 + || strpos($tokens[$stackPtr]['content'], '#') === 0 + ) { + $commentPrefix = '//'; + if (strpos($tokens[$stackPtr]['content'], '#') === 0) { + $commentPrefix = '#'; + } + + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if ($prev !== false) { + if ($tokens[$prev]['line'] === $tokens[$stackPtr]['line']) { + // Stand-alone trailing comment. + return $stackPtr; + } else if ($tokens[$prev]['line'] === ($tokens[$stackPtr]['line'] - 1)) { + // Previous token was on the previous line. + // Now make sure it wasn't a stand-alone trailing comment. + if ($tokens[$prev]['code'] === T_COMMENT + && strpos($tokens[$prev]['content'], $commentPrefix) === 0 + ) { + $pprev = $phpcsFile->findPrevious(T_WHITESPACE, ($prev - 1), null, true); + if ($pprev === false + || $tokens[$pprev]['line'] !== $tokens[$prev]['line'] + ) { + throw new RuntimeException('$stackPtr must point to the start of a comment'); + } + } + } + } + + $commentEnd = $stackPtr; + for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) { + if ($tokens[$i]['code'] === T_WHITESPACE) { + continue; + } + + if ($tokens[$i]['code'] !== T_COMMENT + && isset(Tokens::$phpcsCommentTokens[$tokens[$i]['code']]) === false + ) { + break; + } + + if (strpos($tokens[$i]['content'], $commentPrefix) !== 0) { + // Not an inline comment or not same style comment, so not part of this comment sequence. + break; + } + + if ($tokens[$i]['line'] !== ($tokens[$commentEnd]['line'] + 1)) { + // There must have been a blank line between these comments. + break; + } + + $commentEnd = $i; + }//end for + + if (isset(Tokens::$phpcsCommentTokens[$tokens[$commentEnd]['code']]) === true) { + // Inline comment blocks can't end on a PHPCS annotation, so move one back. + // We already know that the previous token must exist and be a comment token, + // so no extra validation needed. + $commentEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($commentEnd - 1), null, true); + } + + return $commentEnd; + }//end if + + // Deal with block comments which start with a PHPCS annotation. + if (strpos($tokens[$stackPtr]['content'], '/*') !== 0) { + do { + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if (isset(Tokens::$phpcsCommentTokens[$tokens[$prev]['code']]) === false) { + throw new RuntimeException('$stackPtr must point to the start of a comment'); + } + + $stackPtr = $prev; + + if (strpos($tokens[$prev]['content'], '/*') === 0) { + break; + } + } while ($stackPtr >= 0); + } + + // Find the end of block comments. + if (strpos($tokens[$stackPtr]['content'], '/*') === 0) { + if (substr($tokens[$stackPtr]['content'], -2) === '*/') { + // Single line block comment. + return $stackPtr; + } + + $valid = Tokens::$phpcsCommentTokens; + $valid[T_COMMENT] = T_COMMENT; + + $commentEnd = $stackPtr; + $i = ($stackPtr + 1); + while ($i < $phpcsFile->numTokens && isset($valid[$tokens[$i]['code']]) === true) { + $commentEnd = $i; + if (substr($tokens[$i]['content'], -2) === '*/') { + // Found end of the comment. + break; + } + + ++$i; + } + + return $commentEnd; + }//end if + + }//end findEndOfComment() + + + /** + * Find the start of a docblock, inline or block comment sequence. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the + * end of the comment. + * + * @return int Stack pointer to the start of the comment. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified $stackPtr is + * not of type T_COMMENT or + * T_DOC_COMMENT_CLOSE_TAG or if it + * is not the end of a comment. + */ + public static function findStartOfComment(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] !== T_COMMENT + && $tokens[$stackPtr]['code'] !== T_DOC_COMMENT_CLOSE_TAG + ) { + throw new RuntimeException('$stackPtr must be of type T_COMMENT or T_DOC_COMMENT_CLOSE_TAG'); + } + + if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_CLOSE_TAG) { + return $tokens[$stackPtr]['comment_opener']; + } + + // Find the start of inline comment blocks. + if (strpos($tokens[$stackPtr]['content'], '//') === 0 + || strpos($tokens[$stackPtr]['content'], '#') === 0 + ) { + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if ($prev !== false && $tokens[$prev]['line'] === $tokens[$stackPtr]['line']) { + // Stand-alone trailing comment. + return $stackPtr; + } + + $commentPrefix = '//'; + if (strpos($tokens[$stackPtr]['content'], '#') === 0) { + $commentPrefix = '#'; + } + + $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if ($next !== false + && $tokens[$next]['code'] === T_COMMENT + && strpos($tokens[$next]['content'], $commentPrefix) === 0 + && $tokens[$next]['line'] === ($tokens[$stackPtr]['line'] + 1) + ) { + throw new RuntimeException('$stackPtr must point to the end of a comment'); + } + + $commentStart = $stackPtr; + for ($i = ($stackPtr - 1); $i >= 0; $i--) { + if ($tokens[$i]['code'] === T_WHITESPACE) { + continue; + } + + if ($tokens[$i]['code'] !== T_COMMENT + && isset(Tokens::$phpcsCommentTokens[$tokens[$i]['code']]) === false + ) { + break; + } + + if (strpos($tokens[$i]['content'], $commentPrefix) !== 0) { + // Not an inline comment or not same style comment, so not part of this comment sequence. + break; + } + + if ($tokens[$i]['line'] !== ($tokens[$commentStart]['line'] - 1)) { + // There must have been a blank line between these comments. + break; + } + + $commentStart = $i; + }//end for + + if (isset(Tokens::$phpcsCommentTokens[$tokens[$commentStart]['code']]) === true) { + // Inline comment blocks can't start on a PHPCS annotation, so move one forward. + // We already know that the next token must exist and be a comment token, + // so no extra validation needed. + $commentStart = $phpcsFile->findNext(T_WHITESPACE, ($commentStart + 1), null, true); + } else { + // Check that the current token we are at isn't a trailing comment. + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($commentStart - 1), null, true); + if ($prev !== false && $tokens[$prev]['line'] === $tokens[$commentStart]['line']) { + // Trailing comment, so move one forward. + $commentStart = $phpcsFile->findNext(T_WHITESPACE, ($commentStart + 1), null, true); + } + } + + return $commentStart; + }//end if + + // Deal with block comments which end with a PHPCS annotation. + if (substr($tokens[$stackPtr]['content'], -2) !== '*/') { + do { + $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if (isset(Tokens::$phpcsCommentTokens[$tokens[$next]['code']]) === false) { + throw new RuntimeException('$stackPtr must point to the end of a comment'); + } + + $stackPtr = $next; + + if (substr($tokens[$next]['content'], -2) === '*/') { + break; + } + } while ($stackPtr >= 0); + } + + // Find the start of block comments. + if (substr($tokens[$stackPtr]['content'], -2) === '*/') { + if (strpos($tokens[$stackPtr]['content'], '/*') === 0) { + // Single line block comment. + return $stackPtr; + } + + $valid = Tokens::$phpcsCommentTokens; + $valid[T_COMMENT] = T_COMMENT; + + $commentStart = $stackPtr; + $i = ($stackPtr - 1); + while ($i >= 0 && isset($valid[$tokens[$i]['code']]) === true) { + $commentStart = $i; + if (strpos($tokens[$i]['content'], '/*') === 0) { + // Found start of the comment. + break; + } + + --$i; + } + + return $commentStart; + }//end if + + }//end findStartOfComment() + + + /** + * Find the related docblock/comment based on a T_CONST token. + * + * Note: As this function is based on the `T_CONST` token, it can not find + * individual docblocks for each constant in a multi-constant declaration. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the + * T_CONST token. + * + * @return int|false Integer stack pointer to the docblock/comment end (close) token; + * or false if no docblock or comment was found. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified $stackPtr is + * not of type T_CONST. + */ + public static function findConstantComment(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + if ($tokens[$stackPtr]['code'] !== T_CONST) { + throw new RuntimeException('$stackPtr must be of type T_CONST'); + } + + $ignore = Tokens::$scopeModifiers; + + return self::findCommentAbove($phpcsFile, $stackPtr, $ignore); + + }//end findConstantComment() + + + /** + * Find the related docblock/comment based on a T_FUNCTION token. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the + * T_FUNCTION token. + * + * @return int|false Integer stack pointer to the docblock/comment end (close) token; + * or false if no docblock or comment was found. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified $stackPtr is + * not of type T_FUNCTION. + */ + public static function findFunctionComment(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + if ($tokens[$stackPtr]['code'] !== T_FUNCTION) { + throw new RuntimeException('$stackPtr must be of type T_FUNCTION'); + } + + $ignore = Tokens::$methodPrefixes; + + return self::findCommentAbove($phpcsFile, $stackPtr, $ignore); + + }//end findFunctionComment() + + + /** + * Find the related docblock/comment based on a class/interface/trait token. + * + * Note: anonymous classes are not supported by this method as what tokens should + * be allowed to precede them is too arbitrary. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the + * OO construct to find the comment for. + * + * @return int|false Integer stack pointer to the docblock/comment end (close) token; + * or false if no docblock or comment was found. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified $stackPtr is + * not a class, interface or trait + * token. + */ + public static function findOOStructureComment(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + if (isset(Tokens::$ooScopeTokens[$tokens[$stackPtr]['code']]) === false + || $tokens[$stackPtr]['code'] === T_ANON_CLASS + ) { + throw new RuntimeException('$stackPtr must be a class, interface or trait token'); + } + + $ignore = []; + if ($tokens[$stackPtr]['code'] === T_CLASS) { + // Only classes can be abstract/final. + $ignore = [ + T_ABSTRACT => T_ABSTRACT, + T_FINAL => T_FINAL, + ]; + } + + return self::findCommentAbove($phpcsFile, $stackPtr, $ignore); + + }//end findOOStructureComment() + + + /** + * Find the related docblock/comment based on the T_VARIABLE token for a class/trait property. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the + * T_VARIABLE token. + * + * @return int|false Integer stack pointer to the docblock/comment end (close) token; + * or false if no docblock or comment was found. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified $stackPtr is + * not an OO property. + */ + public static function findPropertyComment(File $phpcsFile, $stackPtr) + { + if (Conditions::isOOProperty($phpcsFile, $stackPtr) === false) { + throw new RuntimeException('$stackPtr must be an OO property'); + } + + $ignore = Tokens::$scopeModifiers; + $ignore[] = T_STATIC; + $ignore[] = T_VAR; + + return self::findCommentAbove($phpcsFile, $stackPtr, $ignore); + + }//end findPropertyComment() + + + /** + * Find docblock/comment based on construct token. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the + * construct to find the comment for. + * @param array $ignore Array of tokens to ignore if found + * before the construct token while looking + * for the comment/docblock. + * Note: T_WHITESPACE tokens and PHPCS + * native annotations will always be + * ignored. + * + * @return int|false Integer stack pointer to the docblock/comment end (close) token; + * or false if no docblock or comment was found. + */ + public static function findCommentAbove(File $phpcsFile, $stackPtr, $ignore=[]) + { + $tokens = $phpcsFile->getTokens(); + + // Check for the existence of the token. + if (isset($tokens[$stackPtr]) === false) { + return false; + } + + $customIgnore = $ignore; + + $ignore[] = T_WHITESPACE; + $ignore += Tokens::$phpcsCommentTokens; + $commentEnd = $stackPtr; + + // Find the right comment. + do { + $commentEnd = $phpcsFile->findPrevious($ignore, ($commentEnd - 1), null, true); + + if ($commentEnd === false + || ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG + && $tokens[$commentEnd]['code'] !== T_COMMENT) + ) { + return false; + } + + $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($commentEnd - 1), null, true); + + // Handle structures interlaced with inline comments where we need an earlier comment. + if (in_array($tokens[$prevNonEmpty]['code'], $customIgnore, true) === true) { + $commentEnd = $prevNonEmpty; + continue; + } + + // Handle end comments for preceeding structures, such as control structures + // or function declarations. Assume the end comment belongs to the preceeding structure. + if ($tokens[$prevNonEmpty]['line'] === $tokens[$commentEnd]['line']) { + return false; + } + + return $commentEnd; + } while (true); + + }//end findCommentAbove() + + +}//end class diff --git a/src/Util/Sniffs/Conditions.php b/src/Util/Sniffs/Conditions.php new file mode 100644 index 0000000000..1350be780d --- /dev/null +++ b/src/Util/Sniffs/Conditions.php @@ -0,0 +1,266 @@ + + * @author Juliette Reinders Folmer + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; + +class Conditions +{ + + + /** + * Return the position of the condition for the passed token. + * + * If no types are specified, the first condition for the token - or if $reverse=true, + * the last condition - will be returned. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the token we are checking. + * @param int|string|array $types Optional. The type(s) of tokens to search for. + * @param bool $reverse Optional. Whether to search for the highest + * (false) or the deepest condition (true) of + * the specified type(s). + * + * @return int|false StackPtr to the condition or false if the token does not have the condition. + */ + public static function getCondition(File $phpcsFile, $stackPtr, $types=[], $reverse=false) + { + $tokens = $phpcsFile->getTokens(); + + // Check for the existence of the token. + if (isset($tokens[$stackPtr]) === false) { + return false; + } + + // Make sure the token has conditions. + if (empty($tokens[$stackPtr]['conditions']) === true) { + return false; + } + + $types = (array) $types; + $conditions = $tokens[$stackPtr]['conditions']; + + if (empty($types) === true) { + // No types specified, just return the first/last condition pointer. + if ($reverse === true) { + end($conditions); + } else { + reset($conditions); + } + + return key($conditions); + } + + if ($reverse === true) { + $conditions = array_reverse($conditions, true); + } + + foreach ($conditions as $ptr => $type) { + if (isset($tokens[$ptr]) === true + && in_array($type, $types, true) === true + ) { + // We found a token with the required type. + return $ptr; + } + } + + return false; + + }//end getCondition() + + + /** + * Determine if the passed token has a condition of one of the passed types. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the token we are checking. + * @param int|string|array $types The type(s) of tokens to search for. + * + * @return bool + */ + public static function hasCondition(File $phpcsFile, $stackPtr, $types) + { + return (self::getCondition($phpcsFile, $stackPtr, $types) !== false); + + }//end hasCondition() + + + /** + * Return the position of the first condition of a certain type for the passed token. + * + * If no types are specified, the first condition for the token, independently of type, + * will be returned. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the token we are checking. + * @param int|string|array $types Optional. The type(s) of tokens to search for. + * + * @return int|false StackPtr to the condition or false if the token does not have the condition. + */ + public static function getFirstCondition(File $phpcsFile, $stackPtr, $types=[]) + { + return self::getCondition($phpcsFile, $stackPtr, $types, false); + + }//end getFirstCondition() + + + /** + * Return the position of the last condition of a certain type for the passed token. + * + * If no types are specified, the last condition for the token, independently of type, + * will be returned. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the token we are checking. + * @param int|string|array $types Optional. The type(s) of tokens to search for. + * + * @return int|false StackPtr to the condition or false if the token does not have the condition. + */ + public static function getLastCondition(File $phpcsFile, $stackPtr, $types=[]) + { + return self::getCondition($phpcsFile, $stackPtr, $types, true); + + }//end getLastCondition() + + + /** + * Check whether a T_VARIABLE token is a class/trait property declaration. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the + * T_VARIABLE token to verify. + * + * @return bool + */ + public static function isOOProperty(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== T_VARIABLE) { + return false; + } + + // Note: interfaces can not declare properties. + $validScopes = [ + T_CLASS, + T_ANON_CLASS, + T_TRAIT, + ]; + + $scopePtr = self::validDirectScope($phpcsFile, $stackPtr, $validScopes); + if ($scopePtr !== false) { + // Make sure it's not a method parameter. + $deepestOpen = Parentheses::getLastOpener($phpcsFile, $stackPtr); + if ($deepestOpen === false + || $deepestOpen < $scopePtr + || Parentheses::isOwnerIn($phpcsFile, $deepestOpen, T_FUNCTION) === false + ) { + return true; + } + } + + return false; + + }//end isOOProperty() + + + /** + * Check whether a T_CONST token is a class/interface constant declaration. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the + * T_CONST token to verify. + * + * @return bool + */ + public static function isOOConstant(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== T_CONST) { + return false; + } + + // Note: traits can not declare constants. + $validScopes = [ + T_CLASS, + T_ANON_CLASS, + T_INTERFACE, + ]; + + if (self::validDirectScope($phpcsFile, $stackPtr, $validScopes) !== false) { + return true; + } + + return false; + + }//end isOOConstant() + + + /** + * Check whether a T_FUNCTION token is a class/interface/trait method declaration. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the + * T_FUNCTION token to verify. + * + * @return bool + */ + public static function isOOMethod(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== T_FUNCTION) { + return false; + } + + if (self::validDirectScope($phpcsFile, $stackPtr, Tokens::$ooScopeTokens) !== false) { + return true; + } + + return false; + + }//end isOOMethod() + + + /** + * Check whether the direct wrapping scope of a token is within a limited set of + * acceptable tokens. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the + * token to verify. + * @param int|string|array $validScopes Array of token constants. + * + * @return int|false StackPtr to the valid direct scope or false if no valid direct scope was found. + */ + public static function validDirectScope(File $phpcsFile, $stackPtr, $validScopes) + { + $ptr = self::getLastCondition($phpcsFile, $stackPtr); + + if ($ptr !== false) { + $tokens = $phpcsFile->getTokens(); + $validScopes = (array) $validScopes; + + if (isset($tokens[$ptr]) === true + && in_array($tokens[$ptr]['code'], $validScopes, true) === true + ) { + return $ptr; + } + } + + return false; + + }//end validDirectScope() + + +}//end class diff --git a/src/Util/Sniffs/ConstructNames.php b/src/Util/Sniffs/ConstructNames.php new file mode 100644 index 0000000000..c0a8cc370d --- /dev/null +++ b/src/Util/Sniffs/ConstructNames.php @@ -0,0 +1,330 @@ + + * @author Juliette Reinders Folmer + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +use PHP_CodeSniffer\Config; +use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; + +class ConstructNames +{ + + + /** + * Returns the declaration names for classes, interfaces, traits, and functions. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the declaration token which + * declared the class, interface, trait, or function. + * + * @return string|null The name of the class, interface, trait, or function; + * NULL if the function or class is anonymous; or + * an empty string in case of a parse error/live coding. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token is not of type + * T_FUNCTION, T_CLASS, T_TRAIT, or T_INTERFACE. + */ + public static function getDeclarationName(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $tokenCode = $tokens[$stackPtr]['code']; + + if ($tokenCode === T_ANON_CLASS || $tokenCode === T_CLOSURE) { + return null; + } + + if ($tokenCode !== T_FUNCTION + && $tokenCode !== T_CLASS + && $tokenCode !== T_INTERFACE + && $tokenCode !== T_TRAIT + ) { + throw new RuntimeException('Token type "'.$tokens[$stackPtr]['type'].'" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT'); + } + + if ($tokenCode === T_FUNCTION + && strtolower($tokens[$stackPtr]['content']) !== 'function' + ) { + // JS specific: This is a function declared without the "function" keyword. + // So this token is the function name. + return $tokens[$stackPtr]['content']; + } + + /* + * Determine the name. Note that we cannot simply look for the first T_STRING + * because an (invalid) class name starting with the number will be multiple tokens. + * Whitespace or comment are however not allowed within a name. + */ + + if ($tokenCode === T_FUNCTION && isset($tokens[$stackPtr]['parenthesis_opener']) === true) { + $opener = $tokens[$stackPtr]['parenthesis_opener']; + } else if (isset($tokens[$stackPtr]['scope_opener']) === true) { + $opener = $tokens[$stackPtr]['scope_opener']; + } + + if (isset($opener) === false) { + // Live coding or parse error. + return ''; + } + + $nameStart = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), $opener, true); + if ($nameStart === false) { + // Live coding or parse error. + return ''; + } + + $nameEnd = $phpcsFile->findNext(Tokens::$emptyTokens, $nameStart, $opener); + if ($nameEnd === false) { + return $tokens[$nameStart]['content']; + } + + // Name starts with number, so is composed of multiple tokens. + return trim($phpcsFile->getTokensAsString($nameStart, ($nameEnd - $nameStart))); + + }//end getDeclarationName() + + + /** + * Returns true if the specified string is in the camel caps format. + * + * @param string $string The string the verify. + * @param boolean $classFormat If true, check to see if the string is in the + * class format. Class format strings must start + * with a capital letter and contain no + * underscores. + * @param boolean $public If true, the first character in the string + * must be an a-z character. If false, the + * character must be an underscore. This + * argument is only applicable if $classFormat + * is false. + * @param boolean $strict If true, the string must not have two capital + * letters next to each other. If false, a + * relaxed camel caps policy is used to allow + * for acronyms. + * + * @return boolean + */ + public static function isCamelCaps( + $string, + $classFormat=false, + $public=true, + $strict=true + ) { + // Check the first character first. + if ($classFormat === false) { + $legalFirstChar = ''; + if ($public === false) { + $legalFirstChar = '[_]'; + } + + if ($strict === false) { + // Can either start with a lowercase letter, or multiple uppercase + // in a row, representing an acronym. + $legalFirstChar .= '([A-Z]{2,}|[a-z])'; + } else { + $legalFirstChar .= '[a-z]'; + } + } else { + $legalFirstChar = '[A-Z]'; + } + + if (preg_match("/^$legalFirstChar/", $string) === 0) { + return false; + } + + // Check that the name only contains legal characters. + $legalChars = 'a-zA-Z0-9'; + if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) { + return false; + } + + if ($strict === true) { + // Check that there are not two capital letters next to each other. + $length = strlen($string); + $lastCharWasCaps = $classFormat; + + for ($i = 1; $i < $length; $i++) { + $ascii = ord($string{$i}); + if ($ascii >= 48 && $ascii <= 57) { + // The character is a number, so it cant be a capital. + $isCaps = false; + } else { + if (strtoupper($string{$i}) === $string{$i}) { + $isCaps = true; + } else { + $isCaps = false; + } + } + + if ($isCaps === true && $lastCharWasCaps === true) { + return false; + } + + $lastCharWasCaps = $isCaps; + } + }//end if + + return true; + + }//end isCamelCaps() + + + /** + * Returns true if the specified string is in the underscore caps format. + * + * @param string $string The string to verify. + * + * @return boolean + */ + public static function isUnderscoreName($string) + { + // If there is a space in the name, it can't be valid. + if (strpos($string, ' ') !== false) { + return false; + } + + $validName = true; + $nameBits = explode('_', $string); + + if (preg_match('|^[A-Z]|', $string) === 0) { + // Name does not begin with a capital letter. + $validName = false; + } else { + foreach ($nameBits as $bit) { + if ($bit === '') { + continue; + } + + if ($bit{0} !== strtoupper($bit{0})) { + $validName = false; + break; + } + } + } + + return $validName; + + }//end isUnderscoreName() + + + /** + * Verify whether a name contains numeric characters. + * + * @param string $name The string. + * + * @return bool + */ + public static function hasNumbers($name) + { + if ($name === '') { + return false; + } + + return preg_match('`\pN`u', $name) === 1; + + }//end hasNumbers() + + + /** + * Remove numeric characters from the start of a string. + * + * @param string $name The string. + * + * @return string + */ + public static function ltrimNumbers($name) + { + if ($name === '') { + return ''; + } + + return preg_replace('`^[\pN]+(\X*)`u', '$1', $name); + + }//end ltrimNumbers() + + + /** + * Remove all numeric characters from a string. + * + * @param string $name The string. + * + * @return string + */ + public static function removeNumbers($name) + { + if ($name === '') { + return ''; + } + + return preg_replace('`[\pN]+`u', '', $name); + + }//end removeNumbers() + + + /** + * Transform consecutive uppercase characters to lowercase. + * + * Important: this function will only work on non-ascii strings when the MBString + * extension is enabled. + * + * @param string $name The string. + * + * @return string The adjusted name or the original name if no consecutive uppercase + * characters where found or when MBString is not available and the input + * was non-ascii. + */ + public static function lowerConsecutiveCaps($name) + { + static $mbstring = null, $encoding = null; + + if ($name === '') { + return ''; + } + + // Cache the results of MbString check and encoding. These values won't change during a run. + if (isset($mbstring) === false) { + $mbstring = function_exists('mb_strtolower'); + } + + if (isset($encoding) === false) { + $encoding = Config::getConfigData('encoding'); + if ($encoding === null) { + $encoding = 'utf-8'; + } + } + + // MBString can mangle non-ascii text when the encoding is not correctly set and + // strtolower will mangle any non-ascii, so just return the name unchanged in that case. + if (utf8_decode($name) !== $name && $mbstring === false) { + return $name; + } + + $name = preg_replace_callback( + '`([\p{Lt}\p{Lu}])([\p{Lt}\p{Lu}]+?)(\b|$|\PL|[\p{Lt}\p{Lu}](?=[^\p{Lt}\p{Lu}])\pL|(?=[^\p{Lt}\p{Lu}])\pL)`u', + function ($matches) use ($mbstring, $encoding) { + if ($mbstring === true) { + $consecutiveChars = mb_strtolower($matches[2], $encoding); + } else { + $consecutiveChars = strtolower($matches[2]); + } + + return $matches[1].$consecutiveChars.$matches[3]; + }, + $name + ); + + return $name; + + }//end lowerConsecutiveCaps() + + +}//end class diff --git a/src/Util/Sniffs/FunctionDeclarations.php b/src/Util/Sniffs/FunctionDeclarations.php new file mode 100644 index 0000000000..129c176f28 --- /dev/null +++ b/src/Util/Sniffs/FunctionDeclarations.php @@ -0,0 +1,568 @@ + + * @author Juliette Reinders Folmer + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; + +class FunctionDeclarations +{ + + /** + * A list of all PHP magic methods. + * + * @var array => + */ + public static $magicMethods = [ + '__construct' => 'construct', + '__destruct' => 'destruct', + '__call' => 'call', + '__callstatic' => 'callstatic', + '__get' => 'get', + '__set' => 'set', + '__isset' => 'isset', + '__unset' => 'unset', + '__sleep' => 'sleep', + '__wakeup' => 'wakeup', + '__tostring' => 'tostring', + '__set_state' => 'set_state', + '__clone' => 'clone', + '__invoke' => 'invoke', + '__debuginfo' => 'debuginfo', + ]; + + /** + * A list of all PHP non-magic methods starting with a double underscore. + * + * These come from PHP modules such as SOAPClient. + * + * @var array => + */ + public static $methodsDoubleUnderscore = [ + '__dorequest' => 'SOAPClient', + '__getcookies' => 'SOAPClient', + '__getfunctions' => 'SOAPClient', + '__getlastrequest' => 'SOAPClient', + '__getlastrequestheaders' => 'SOAPClient', + '__getlastresponse' => 'SOAPClient', + '__getlastresponseheaders' => 'SOAPClient', + '__gettypes' => 'SOAPClient', + '__setcookie' => 'SOAPClient', + '__setlocation' => 'SOAPClient', + '__setsoapheaders' => 'SOAPClient', + '__soapcall' => 'SOAPClient', + ]; + + /** + * A list of all PHP magic functions. + * + * @var array => + */ + public static $magicFunctions = ['__autoload' => 'autoload']; + + + /** + * Returns the parameters for the specified function token. + * + * Each parameter is in the following format: + * + * + * 0 => array( + * 'name' => '$var', // The variable name. + * 'token' => integer, // The stack pointer to the variable name. + * 'content' => string, // The full content of the variable definition. + * 'pass_by_reference' => boolean, // Is the variable passed by reference? + * 'variable_length' => boolean, // Is the param of variable length through use of `...` ? + * 'type_hint' => string, // The type hint for the variable. + * 'type_hint_token' => integer, // The stack pointer to the type hint + * // or false if there is no type hint. + * 'nullable_type' => boolean, // Is the variable using a nullable type? + * ) + * + * + * Parameters with default values have an additional array index of + * 'default' with the value of the default as a string. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the function token + * to acquire the parameters for. + * + * @return array + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified $stackPtr is not of + * type T_FUNCTION or T_CLOSURE. + */ + public static function getParameters(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] !== T_FUNCTION + && $tokens[$stackPtr]['code'] !== T_CLOSURE + ) { + throw new RuntimeException('$stackPtr must be of type T_FUNCTION or T_CLOSURE'); + } + + $opener = $tokens[$stackPtr]['parenthesis_opener']; + $closer = $tokens[$stackPtr]['parenthesis_closer']; + + $vars = []; + $currVar = null; + $paramStart = ($opener + 1); + $defaultStart = null; + $paramCount = 0; + $passByReference = false; + $variableLength = false; + $typeHint = ''; + $typeHintToken = false; + $nullableType = false; + + for ($i = $paramStart; $i <= $closer; $i++) { + // Check to see if this token has a parenthesis or bracket opener. If it does + // it's likely to be an array which might have arguments in it. This + // could cause problems in our parsing below, so lets just skip to the + // end of it. + if (isset($tokens[$i]['parenthesis_opener']) === true) { + // Don't do this if it's the close parenthesis for the method. + if ($i !== $tokens[$i]['parenthesis_closer']) { + $i = ($tokens[$i]['parenthesis_closer'] + 1); + } + } + + if (isset($tokens[$i]['bracket_opener']) === true) { + // Don't do this if it's the close parenthesis for the method. + if ($i !== $tokens[$i]['bracket_closer']) { + $i = ($tokens[$i]['bracket_closer'] + 1); + } + } + + switch ($tokens[$i]['code']) { + case T_BITWISE_AND: + if ($defaultStart === null) { + $passByReference = true; + } + break; + case T_VARIABLE: + $currVar = $i; + break; + case T_ELLIPSIS: + $variableLength = true; + break; + case T_CALLABLE: + if ($typeHintToken === false) { + $typeHintToken = $i; + } + + $typeHint .= $tokens[$i]['content']; + break; + case T_SELF: + case T_PARENT: + case T_STATIC: + // Self and parent are valid, static invalid, but was probably intended as type hint. + if (isset($defaultStart) === false) { + if ($typeHintToken === false) { + $typeHintToken = $i; + } + + $typeHint .= $tokens[$i]['content']; + } + break; + case T_STRING: + // This is a string, so it may be a type hint, but it could + // also be a constant used as a default value. + $prevComma = false; + for ($t = $i; $t >= $opener; $t--) { + if ($tokens[$t]['code'] === T_COMMA) { + $prevComma = $t; + break; + } + } + + if ($prevComma !== false) { + $nextEquals = false; + for ($t = $prevComma; $t < $i; $t++) { + if ($tokens[$t]['code'] === T_EQUAL) { + $nextEquals = $t; + break; + } + } + + if ($nextEquals !== false) { + break; + } + } + + if ($defaultStart === null) { + if ($typeHintToken === false) { + $typeHintToken = $i; + } + + $typeHint .= $tokens[$i]['content']; + } + break; + case T_NS_SEPARATOR: + // Part of a type hint or default value. + if ($defaultStart === null) { + if ($typeHintToken === false) { + $typeHintToken = $i; + } + + $typeHint .= $tokens[$i]['content']; + } + break; + case T_NULLABLE: + if ($defaultStart === null) { + $nullableType = true; + $typeHint .= $tokens[$i]['content']; + } + break; + case T_CLOSE_PARENTHESIS: + case T_COMMA: + // If it's null, then there must be no parameters for this + // method. + if ($currVar === null) { + continue 2; + } + + $vars[$paramCount] = []; + $vars[$paramCount]['token'] = $currVar; + $vars[$paramCount]['name'] = $tokens[$currVar]['content']; + $vars[$paramCount]['content'] = trim($phpcsFile->getTokensAsString($paramStart, ($i - $paramStart))); + + if ($defaultStart !== null) { + $vars[$paramCount]['default'] = trim($phpcsFile->getTokensAsString($defaultStart, ($i - $defaultStart))); + } + + $vars[$paramCount]['pass_by_reference'] = $passByReference; + $vars[$paramCount]['variable_length'] = $variableLength; + $vars[$paramCount]['type_hint'] = $typeHint; + $vars[$paramCount]['type_hint_token'] = $typeHintToken; + $vars[$paramCount]['nullable_type'] = $nullableType; + + // Reset the vars, as we are about to process the next parameter. + $defaultStart = null; + $paramStart = ($i + 1); + $passByReference = false; + $variableLength = false; + $typeHint = ''; + $typeHintToken = false; + $nullableType = false; + + $paramCount++; + break; + case T_EQUAL: + $defaultStart = ($i + 1); + break; + }//end switch + }//end for + + return $vars; + + }//end getParameters() + + + /** + * Returns the visibility and implementation properties of a function or method. + * + * The format of the array is: + * + * array( + * 'scope' => 'public', // public protected or protected + * 'scope_specified' => true, // true is scope keyword was found. + * 'return_type' => '', // the return type of the method. + * 'return_type_token' => integer, // The stack pointer to the start of the return type + * // or false if there is no return type. + * 'nullable_return_type' => false, // true if the return type is nullable. + * 'is_abstract' => false, // true if the abstract keyword was found. + * 'is_final' => false, // true if the final keyword was found. + * 'is_static' => false, // true if the static keyword was found. + * 'has_body' => false, // true if the method has a body + * ); + * + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the function token to + * acquire the properties for. + * + * @return array + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * T_FUNCTION token. + */ + public static function getProperties(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] !== T_FUNCTION + && $tokens[$stackPtr]['code'] !== T_CLOSURE + ) { + throw new RuntimeException('$stackPtr must be of type T_FUNCTION or T_CLOSURE'); + } + + if ($tokens[$stackPtr]['code'] === T_FUNCTION) { + $valid = [ + T_PUBLIC => T_PUBLIC, + T_PRIVATE => T_PRIVATE, + T_PROTECTED => T_PROTECTED, + T_STATIC => T_STATIC, + T_FINAL => T_FINAL, + T_ABSTRACT => T_ABSTRACT, + T_WHITESPACE => T_WHITESPACE, + T_COMMENT => T_COMMENT, + T_DOC_COMMENT => T_DOC_COMMENT, + ]; + } else { + $valid = [ + T_STATIC => T_STATIC, + T_WHITESPACE => T_WHITESPACE, + T_COMMENT => T_COMMENT, + T_DOC_COMMENT => T_DOC_COMMENT, + ]; + } + + $scope = 'public'; + $scopeSpecified = false; + $isAbstract = false; + $isFinal = false; + $isStatic = false; + + for ($i = ($stackPtr - 1); $i > 0; $i--) { + if (isset($valid[$tokens[$i]['code']]) === false) { + break; + } + + switch ($tokens[$i]['code']) { + case T_PUBLIC: + $scope = 'public'; + $scopeSpecified = true; + break; + case T_PRIVATE: + $scope = 'private'; + $scopeSpecified = true; + break; + case T_PROTECTED: + $scope = 'protected'; + $scopeSpecified = true; + break; + case T_ABSTRACT: + $isAbstract = true; + break; + case T_FINAL: + $isFinal = true; + break; + case T_STATIC: + $isStatic = true; + break; + }//end switch + }//end for + + $returnType = ''; + $returnTypeToken = false; + $nullableReturnType = false; + $hasBody = true; + + if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) { + $scopeOpener = null; + if (isset($tokens[$stackPtr]['scope_opener']) === true) { + $scopeOpener = $tokens[$stackPtr]['scope_opener']; + } + + $valid = [ + T_STRING => T_STRING, + T_CALLABLE => T_CALLABLE, + T_SELF => T_SELF, + T_PARENT => T_PARENT, + T_NS_SEPARATOR => T_NS_SEPARATOR, + ]; + + for ($i = $tokens[$stackPtr]['parenthesis_closer']; $i < $phpcsFile->numTokens; $i++) { + if (($scopeOpener === null && $tokens[$i]['code'] === T_SEMICOLON) + || ($scopeOpener !== null && $i === $scopeOpener) + ) { + // End of function definition. + break; + } + + if ($tokens[$i]['code'] === T_NULLABLE) { + $nullableReturnType = true; + } + + if (isset($valid[$tokens[$i]['code']]) === true) { + if ($returnTypeToken === false) { + $returnTypeToken = $i; + } + + $returnType .= $tokens[$i]['content']; + } + } + + $end = $phpcsFile->findNext([T_OPEN_CURLY_BRACKET, T_SEMICOLON], $tokens[$stackPtr]['parenthesis_closer']); + $hasBody = $tokens[$end]['code'] === T_OPEN_CURLY_BRACKET; + }//end if + + if ($returnType !== '' && $nullableReturnType === true) { + $returnType = '?'.$returnType; + } + + return [ + 'scope' => $scope, + 'scope_specified' => $scopeSpecified, + 'return_type' => $returnType, + 'return_type_token' => $returnTypeToken, + 'nullable_return_type' => $nullableReturnType, + 'is_abstract' => $isAbstract, + 'is_final' => $isFinal, + 'is_static' => $isStatic, + 'has_body' => $hasBody, + ]; + + }//end getProperties() + + + /** + * Checks if a given function is a PHP magic function. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The T_FUNCTION token to check. + * + * @return bool + */ + public static function isMagicFunction(File $phpcsFile, $stackPtr) + { + if (Conditions::hasCondition($phpcsFile, $stackPtr, Tokens::$ooScopeTokens) === true) { + return false; + } + + $name = $phpcsFile->getDeclarationName($stackPtr); + return self::isMagicFunctionName($name); + + }//end isMagicFunction() + + + /** + * Verify if a given function name is the name of a PHP magic function. + * + * @param string $name The full function name. + * + * @return bool + */ + public static function isMagicFunctionName($name) + { + $name = strtolower($name); + return (isset(self::$magicFunctions[$name]) === true); + + }//end isMagicFunctionName() + + + /** + * Checks if a given function is a PHP magic method. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The T_FUNCTION token to check. + * + * @return bool + */ + public static function isMagicMethod(File $phpcsFile, $stackPtr) + { + if (Conditions::isOOMethod($phpcsFile, $stackPtr) === false) { + return false; + } + + $name = $phpcsFile->getDeclarationName($stackPtr); + return self::isMagicMethodName($name); + + }//end isMagicMethod() + + + /** + * Verify if a given function name is the name of a PHP magic method. + * + * @param string $name The full function name. + * + * @return bool + */ + public static function isMagicMethodName($name) + { + $name = strtolower($name); + return (isset(self::$magicMethods[$name]) === true); + + }//end isMagicMethodName() + + + /** + * Checks if a given function is a PHP native double underscore method. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The T_FUNCTION token to check. + * + * @return bool + */ + public static function isPHPDoubleUnderscoreMethod(File $phpcsFile, $stackPtr) + { + if (Conditions::isOOMethod($phpcsFile, $stackPtr) === false) { + return false; + } + + $name = $phpcsFile->getDeclarationName($stackPtr); + return self::isPHPDoubleUnderscoreMethodName($name); + + }//end isPHPDoubleUnderscoreMethod() + + + /** + * Verify if a given function name is the name of a PHP native double underscore method. + * + * @param string $name The full function name. + * + * @return bool + */ + public static function isPHPDoubleUnderscoreMethodName($name) + { + $name = strtolower($name); + return (isset(self::$methodsDoubleUnderscore[$name]) === true); + + }//end isPHPDoubleUnderscoreMethodName() + + + /** + * Checks if a given function is a magic method or a PHP native double underscore method. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The T_FUNCTION token to check. + * + * @return bool + */ + public static function isSpecialMethod(File $phpcsFile, $stackPtr) + { + if (Conditions::isOOMethod($phpcsFile, $stackPtr) === false) { + return false; + } + + $name = $phpcsFile->getDeclarationName($stackPtr); + return self::isSpecialMethodName($name); + + }//end isSpecialMethod() + + + /** + * Verify if a given function name is the name of a magic method or a PHP native double underscore method. + * + * @param string $name The full function name. + * + * @return bool + */ + public static function isSpecialMethodName($name) + { + $name = strtolower($name); + return (isset(self::$magicMethods[$name]) === true || isset(self::$methodsDoubleUnderscore[$name]) === true); + + }//end isSpecialMethodName() + + +}//end class diff --git a/src/Util/Sniffs/Namespaces.php b/src/Util/Sniffs/Namespaces.php new file mode 100644 index 0000000000..4c98840ee7 --- /dev/null +++ b/src/Util/Sniffs/Namespaces.php @@ -0,0 +1,298 @@ + + * @copyright 2017-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; + +class Namespaces +{ + + /** + * List of tokens which can end a namespace declaration statement. + * + * @var array + */ + public static $statementClosers = [ + T_SEMICOLON => T_SEMICOLON, + T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET, + T_CLOSE_TAG => T_CLOSE_TAG, + ]; + + + /** + * Determine what a T_NAMESPACE token is used for. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the T_NAMESPACE token. + * + * @return string Either 'declaration', 'operator'. + * An empty string will be returned if it couldn't be + * reliably determined what the T_NAMESPACE token is used for. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is + * not a T_NAMESPACE token. + */ + public static function getType(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== T_NAMESPACE) { + throw new RuntimeException('$stackPtr must be of type T_NAMESPACE'); + } + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($next === false) { + // Live coding or parse error. + return ''; + } + + if ($tokens[$next]['code'] === T_STRING + || isset(self::$statementClosers[$tokens[$next]['code']]) === true + ) { + return 'declaration'; + } + + if ($tokens[$next]['code'] === T_NS_SEPARATOR) { + return 'operator'; + } + + return ''; + + }//end getType() + + + /** + * Determine whether a T_NAMESPACE token is the keyword for a namespace declaration. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of a T_NAMESPACE token. + * + * @return bool True if the token passed is the keyword for a namespace declaration. + * False if not. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is + * not a T_NAMESPACE token. + */ + public static function isDeclaration(File $phpcsFile, $stackPtr) + { + return (self::getType($phpcsFile, $stackPtr) === 'declaration'); + + }//end isDeclaration() + + + /** + * Determine whether a T_NAMESPACE token is used as an operator. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of a T_NAMESPACE token. + * + * @return bool True if the token passed is used as an operator. False if not. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is + * not a T_NAMESPACE token. + */ + public static function isOperator(File $phpcsFile, $stackPtr) + { + return (self::getType($phpcsFile, $stackPtr) === 'operator'); + + }//end isOperator() + + + /** + * Get the complete namespace name as declared. + * + * For hierarchical namespaces, the name will be composed of several tokens, + * i.e. MyProject\Sub\Level which will be returned together as one string. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of a T_NAMESPACE token. + * @param bool $clean Optional. Whether to get the name stripped + * of potentially interlaced whitespace and/or + * comments. Defaults to true. + * + * @return string|false The namespace name, or false if the specified position is not a + * T_NAMESPACE token, not the keyword for a namespace declaration + * or when parse errors are encountered/during live coding. + * Note: The name can be an empty string for a valid global + * namespace declaration. + */ + public static function getDeclaredName(File $phpcsFile, $stackPtr, $clean=true) + { + try { + if (self::isDeclaration($phpcsFile, $stackPtr) === false) { + // Not a namespace declaration. + return false; + } + } catch (RuntimeException $e) { + // Non-existent token or not a namespace keyword token. + return false; + } + + $endOfStatement = $phpcsFile->findNext(self::$statementClosers, ($stackPtr + 1)); + if ($endOfStatement === false) { + // Live coding or parse error. + return false; + } + + $tokens = $phpcsFile->getTokens(); + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), ($endOfStatement + 1), true); + if ($next === $endOfStatement) { + // Declaration of global namespace. I.e.: namespace {}. + // If not a scoped {} namespace declaration, no name/global declarations are invalid + // and result in parse errors, but that's not our concern. + return ''; + } + + if ($clean === false) { + return trim($phpcsFile->getTokensAsString($next, ($endOfStatement - $next), true)); + } + + $name = ''; + for ($i = $next; $i < $endOfStatement; $i++) { + if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { + continue; + } + + $name .= $tokens[$i]['content']; + } + + return trim($name); + + }//end getDeclaredName() + + + /** + * Determine the namespace an arbitrary token lives in. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The token for which to determine + * the namespace. + * + * @return int|false Token pointer to the applicable namespace keyword or + * false if it couldn't be determined or no namespace applies. + */ + public static function findNamespacePtr(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // Check for the existence of the token. + if (isset($tokens[$stackPtr]) === false) { + return false; + } + + // Check for scoped namespace {}. + $namespacePtr = Conditions::getCondition($phpcsFile, $stackPtr, T_NAMESPACE); + if ($namespacePtr !== false) { + return $namespacePtr; + } + + /* + * Not in a scoped namespace, so let's see if we can find a non-scoped namespace instead. + * Keeping in mind that: + * - there can be multiple non-scoped namespaces in a file (bad practice, but is allowed); + * - the namespace keyword can also be used as an operator; + * - a non-named namespace resolves to the global namespace; + * - and that namespace declarations can't be nested in anything, so we can skip over any + * nesting structures. + */ + + $previousNSToken = $stackPtr; + $find = [ + T_NAMESPACE, + T_CLOSE_CURLY_BRACKET, + T_CLOSE_PARENTHESIS, + T_CLOSE_SHORT_ARRAY, + ]; + + do { + $previousNSToken = $phpcsFile->findPrevious($find, ($previousNSToken - 1)); + if ($previousNSToken === false) { + break; + } + + if ($tokens[$previousNSToken]['code'] === T_CLOSE_CURLY_BRACKET) { + // Stop if we encounter a scoped namespace declaration as we already know we're not in one. + if (isset($tokens[$previousNSToken]['scope_condition']) === true + && $tokens[$tokens[$previousNSToken]['scope_condition']]['code'] === T_NAMESPACE + ) { + break; + } + + // Skip over other scoped structures for efficiency. + if (isset($tokens[$previousNSToken]['scope_condition']) === true) { + $previousNSToken = $tokens[$previousNSToken]['scope_condition']; + } else if (isset($tokens[$previousNSToken]['bracket_opener']) === true) { + $previousNSToken = $tokens[$previousNSToken]['bracket_opener']; + } + + continue; + } + + // Skip over other nesting structures for efficiency. + if ($tokens[$previousNSToken]['code'] === T_CLOSE_SHORT_ARRAY) { + if (isset($tokens[$previousNSToken]['bracket_opener']) === true) { + $previousNSToken = $tokens[$previousNSToken]['bracket_opener']; + } + + continue; + } + + if ($tokens[$previousNSToken]['code'] === T_CLOSE_PARENTHESIS) { + if (isset($tokens[$previousNSToken]['parenthesis_owner']) === true) { + $previousNSToken = $tokens[$previousNSToken]['parenthesis_owner']; + } else if (isset($tokens[$previousNSToken]['parenthesis_opener']) === true) { + $previousNSToken = $tokens[$previousNSToken]['parenthesis_opener']; + } + + continue; + } + + // So this is a namespace keyword, check if it's a declaration. + if (self::isDeclaration($phpcsFile, $previousNSToken) === true) { + return $previousNSToken; + } + } while (true); + + return false; + + }//end findNamespacePtr() + + + /** + * Determine the namespace name an arbitrary token lives in. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The token for which to determine + * the namespace. + * + * @return string Namespace name or empty string if it couldn't be determined + * or no namespace applies. + */ + public static function determineNamespace(File $phpcsFile, $stackPtr) + { + $namespacePtr = self::findNamespacePtr($phpcsFile, $stackPtr); + if ($namespacePtr === false) { + return ''; + } + + $namespace = self::getDeclaredName($phpcsFile, $namespacePtr); + if ($namespace !== false) { + return $namespace; + } + + return ''; + + }//end determineNamespace() + + +}//end class diff --git a/src/Util/Sniffs/ObjectDeclarations.php b/src/Util/Sniffs/ObjectDeclarations.php new file mode 100644 index 0000000000..7e450df68e --- /dev/null +++ b/src/Util/Sniffs/ObjectDeclarations.php @@ -0,0 +1,238 @@ + + * @author Juliette Reinders Folmer + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; + +class ObjectDeclarations +{ + + + /** + * Returns the visibility and implementation properties of a class. + * + * The format of the array is: + * + * array( + * 'is_abstract' => false, // true if the abstract keyword was found. + * 'is_final' => false, // true if the final keyword was found. + * ); + * + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the T_CLASS + * token to acquire the properties for. + * + * @return array + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * T_CLASS token. + */ + public static function getClassProperties(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] !== T_CLASS) { + throw new RuntimeException('$stackPtr must be of type T_CLASS'); + } + + $valid = Tokens::$emptyTokens; + $valid[T_FINAL] = T_FINAL; + $valid[T_ABSTRACT] = T_ABSTRACT; + + $isAbstract = false; + $isFinal = false; + + for ($i = ($stackPtr - 1); $i > 0; $i--) { + if (isset($valid[$tokens[$i]['code']]) === false) { + break; + } + + switch ($tokens[$i]['code']) { + case T_ABSTRACT: + $isAbstract = true; + break; + + case T_FINAL: + $isFinal = true; + break; + } + }//end for + + return [ + 'is_abstract' => $isAbstract, + 'is_final' => $isFinal, + ]; + + }//end getClassProperties() + + + /** + * Returns the name of the class that the specified class extends. + * + * Works for classes, anonymous classes and interfaces, though it is + * strongly recommended to use the findExtendedInterfaceNames() method + * to examine interfaces as they can extend multiple parent interfaces. + * + * Returns FALSE on error or if there is no extended class name. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The stack position of the + * class/interface keyword. + * + * @return string|false + */ + public static function findExtendedClassName(File $phpcsFile, $stackPtr) + { + $validStructures = [ + T_CLASS => true, + T_ANON_CLASS => true, + T_INTERFACE => true, + ]; + + $classes = self::findExtendedImplemented($phpcsFile, $stackPtr, $validStructures, T_EXTENDS); + + if (empty($classes) === true) { + return false; + } + + // Classes can only extend one parent class. + return $classes[0]; + + }//end findExtendedClassName() + + + /** + * Returns the names of the interfaces that the specified interface extends. + * + * Returns FALSE on error or if there is no extended interface name. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The stack position of the interface keyword. + * + * @return array|false + */ + public static function findExtendedInterfaceNames(File $phpcsFile, $stackPtr) + { + $validStructures = [T_INTERFACE => true]; + + $interfaces = self::findExtendedImplemented($phpcsFile, $stackPtr, $validStructures, T_EXTENDS); + + if (empty($interfaces) === true) { + return false; + } + + return $interfaces; + + }//end findExtendedInterfaceNames() + + + /** + * Returns the names of the interfaces that the specified class implements. + * + * Returns FALSE on error or if there are no implemented interface names. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The stack position of the class keyword. + * + * @return array|false + */ + public static function findImplementedInterfaceNames(File $phpcsFile, $stackPtr) + { + $validStructures = [ + T_CLASS => true, + T_ANON_CLASS => true, + ]; + + $interfaces = self::findExtendedImplemented($phpcsFile, $stackPtr, $validStructures, T_IMPLEMENTS); + + if (empty($interfaces) === true) { + return false; + } + + return $interfaces; + + }//end findImplementedInterfaceNames() + + + /** + * Returns the names of the extended classes or interfaces or the implemented + * interfaces that the specific class/interface declaration extends/implements. + * + * Returns FALSE on error or if the object does not extend/implement another object. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The stack position of the + * class/interface keyword. + * @param array $OOTypes Array of accepted token types. + * Array format => true. + * @param int $keyword The token constant for the keyword to examine. + * Either `T_EXTENDS` or `T_IMPLEMENTS`. + * + * @return array|false + */ + private static function findExtendedImplemented(File $phpcsFile, $stackPtr, array $OOTypes, $keyword) + { + $tokens = $phpcsFile->getTokens(); + + // Check for the existence of the token. + if (isset($tokens[$stackPtr]) === false) { + return false; + } + + if (isset($OOTypes[$tokens[$stackPtr]['code']]) === false) { + return false; + } + + if (isset($tokens[$stackPtr]['scope_opener']) === false) { + return false; + } + + $openerIndex = $tokens[$stackPtr]['scope_opener']; + $keywordIndex = $phpcsFile->findNext($keyword, ($stackPtr + 1), $openerIndex); + if ($keywordIndex === false) { + return false; + } + + $find = Tokens::$emptyTokens; + $find[] = T_NS_SEPARATOR; + $find[] = T_STRING; + $find[] = T_COMMA; + + $end = $phpcsFile->findNext($find, ($keywordIndex + 1), ($openerIndex + 1), true); + $names = []; + $name = ''; + for ($i = ($keywordIndex + 1); $i < $end; $i++) { + if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { + continue; + } + + if ($tokens[$i]['code'] === T_COMMA && $name !== '') { + $names[] = $name; + $name = ''; + continue; + } + + $name .= $tokens[$i]['content']; + } + + // Add the last name. + if ($name !== '') { + $names[] = $name; + } + + return $names; + + }//end findExtendedImplemented() + + +}//end class diff --git a/src/Util/Sniffs/Orthography.php b/src/Util/Sniffs/Orthography.php new file mode 100644 index 0000000000..1fd9073b89 --- /dev/null +++ b/src/Util/Sniffs/Orthography.php @@ -0,0 +1,103 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +class Orthography +{ + + /** + * Characters which are considered terminal points for a sentence. + * + * @link https://www.thepunctuationguide.com/terminal-points.html + * + * @var string + */ + const TERMINAL_POINTS = '.?!'; + + + /** + * Check if the first character of an arbitrary text string is a capital letter. + * + * Letter characters which do not have a concept of lower/uppercase will + * be accepted as correctly capitalized. + * + * @param string $string The text string to examine. + * This can be the contents of a text string token, + * but also, for instance, a comment text. + * Potential text delimiter quotes should be stripped + * off a text string before passing it to this method. + * + * @return boolean True when the first character is a capital letter or a letter + * which doesn't have a concept of capitalization. + * False otherwise, including for non-letter characters. + */ + public static function isFirstCharCapitalized($string) + { + $string = ltrim($string); + return (preg_match('`^[\p{Lu}\p{Lt}\p{Lo}]`u', $string) > 0); + + }//end isFirstCharCapitalized() + + + /** + * Check if the first character of an arbitrary text string is a lowercase letter. + * + * @param string $string The text string to examine. + * This can be the contents of a text string token, + * but also, for instance, a comment text. + * Potential text delimiter quotes should be stripped + * off a text string before passing it to this method. + * + * @return boolean True when the first character is a lowercase letter. + * False otherwise, including for letters which don't have a concept of + * capitalization and for non-letter characters. + */ + public static function isFirstCharLowercase($string) + { + $string = ltrim($string); + return (preg_match('`^\p{Ll}`u', $string) > 0); + + }//end isFirstCharLowercase() + + + /** + * Check if the last character of an arbitrary text string is a valid punctuation character. + * + * @param string $string The text string to examine. + * This can be the contents of a text string token, + * but also, for instance, a comment text. + * Potential text delimiter quotes should be stripped + * off a text string before passing it to this method. + * @param string $allowedChars Characters which are considered valid punctuation + * to end the text string. + * Defaults to '.?!', i.e. a full stop, question mark + * or exclamation mark. + * + * @return boolean + */ + public static function isLastCharPunctuation($string, $allowedChars=self::TERMINAL_POINTS) + { + $string = rtrim($string); + if (function_exists('iconv_substr') === true) { + $lastChar = iconv_substr($string, -1); + } else { + $lastChar = substr($string, -1); + } + + if (function_exists('iconv_strpos') === true) { + return (iconv_strpos($allowedChars, $lastChar) !== false); + } else { + return (strpos($allowedChars, $lastChar) !== false); + } + + }//end isLastCharPunctuation() + + +}//end class diff --git a/src/Util/Sniffs/Parentheses.php b/src/Util/Sniffs/Parentheses.php new file mode 100644 index 0000000000..6aea1c271e --- /dev/null +++ b/src/Util/Sniffs/Parentheses.php @@ -0,0 +1,332 @@ + + * @copyright 2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +use PHP_CodeSniffer\Files\File; + +class Parentheses +{ + + + /** + * Get the pointer to the parentheses owner of an open/close parenthesis. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of T_OPEN/CLOSE_PARENTHESIS token. + * + * @return int|false StackPtr to the parentheses owner or false if the parenthesis + * does not have a (direct) owner. + */ + public static function getOwner(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + if (isset($tokens[$stackPtr], $tokens[$stackPtr]['parenthesis_owner']) === false) { + return false; + } + + return $tokens[$stackPtr]['parenthesis_owner']; + + }//end getOwner() + + + /** + * Check whether the parenthesis owner of an open/close parenthesis is within a + * limited set of valid owners. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of T_OPEN/CLOSE_PARENTHESIS token. + * @param int|string|array $validOwners Array of token constants for the owners + * which should be considered valid. + * + * @return boolean True if the owner is within the list of $validOwners, false if not and + * if the parenthesis does not have a (direct) owner. + */ + public static function isOwnerIn(File $phpcsFile, $stackPtr, $validOwners) + { + $owner = self::getOwner($phpcsFile, $stackPtr); + if ($owner === false) { + return false; + } + + $tokens = $phpcsFile->getTokens(); + $validOwners = (array) $validOwners; + return in_array($tokens[$owner]['code'], $validOwners, true); + + }//end isOwnerIn() + + + /** + * Check whether the passed token is nested within parentheses owned by one of the valid owners. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the token we are checking. + * @param int|string|array $validOwners Array of token constants for the owners + * which should be considered valid. + * + * @return boolean + */ + public static function hasOwner(File $phpcsFile, $stackPtr, $validOwners) + { + return (self::nestedParensWalker($phpcsFile, $stackPtr, $validOwners) !== false); + + }//end hasOwner() + + + /** + * Retrieve the position of the opener to the first set of parentheses an arbitrary token + * is wrapped in where the parentheses owner is within the set of valid owners. + * + * If no $validOwners are specified, the opener to the first set of parentheses surrounding + * the token will be returned. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the token we are checking. + * @param int|string|array $validOwners Array of token constants for the owners + * which should be considered valid. + * + * @return int|false StackPtr to the parentheses opener or false if the token does not have + * parentheses owned by any of the valid owners or if the token is not + * nested in parentheses at all. + */ + public static function getFirstOpener(File $phpcsFile, $stackPtr, $validOwners=[]) + { + return self::nestedParensWalker($phpcsFile, $stackPtr, $validOwners, false); + + }//end getFirstOpener() + + + /** + * Retrieve the position of the closer to the first set of parentheses an arbitrary token + * is wrapped in where the parentheses owner is within the set of valid owners. + * + * If no $validOwners are specified, the closer to the first set of parentheses surrounding + * the token will be returned. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the token we are checking. + * @param int|string|array $validOwners Array of token constants for the owners + * which should be considered valid. + * + * @return int|false StackPtr to the parentheses closer or false if the token does not have + * parentheses owned by any of the valid owners or if the token is not + * nested in parentheses at all. + */ + public static function getFirstCloser(File $phpcsFile, $stackPtr, $validOwners=[]) + { + $opener = self::getFirstOpener($phpcsFile, $stackPtr, $validOwners); + $tokens = $phpcsFile->getTokens(); + if ($opener !== false && isset($tokens[$opener]['parenthesis_closer']) === true) { + return $tokens[$opener]['parenthesis_closer']; + } + + return false; + + }//end getFirstCloser() + + + /** + * Retrieve the position of the parentheses owner to the first set of parentheses an + * arbitrary token is wrapped in where the parentheses owner is within the set of valid owners. + * + * If no $validOwners are specified, the owner to the first set of parentheses surrounding + * the token will be returned or false if the first set of parentheses does not have an owner. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the token we are checking. + * @param int|string|array $validOwners Array of token constants for the owners + * which should be considered valid. + * + * @return int|false StackPtr to the parentheses owner or false if the token does not have + * parentheses owned by any of the valid owners or if the token is not + * nested in parentheses at all. + */ + public static function getFirstOwner(File $phpcsFile, $stackPtr, $validOwners=[]) + { + $opener = self::getFirstOpener($phpcsFile, $stackPtr, $validOwners); + if ($opener !== false) { + return self::getOwner($phpcsFile, $opener); + } + + return false; + + }//end getFirstOwner() + + + /** + * Retrieve the position of the opener to the last set of parentheses an arbitrary token + * is wrapped in where the parentheses owner is within the set of valid owners. + * + * If no $validOwners are specified, the opener to the last set of parentheses surrounding + * the token will be returned. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the token we are checking. + * @param int|string|array $validOwners Array of token constants for the owners + * which should be considered valid. + * + * @return int|false StackPtr to the parentheses opener or false if the token does not have + * parentheses owned by any of the valid owners or if the token is not + * nested in parentheses at all. + */ + public static function getLastOpener(File $phpcsFile, $stackPtr, $validOwners=[]) + { + return self::nestedParensWalker($phpcsFile, $stackPtr, $validOwners, true); + + }//end getLastOpener() + + + /** + * Retrieve the position of the closer to the last set of parentheses an arbitrary token + * is wrapped in where the parentheses owner is within the set of valid owners. + * + * If no $validOwners are specified, the closer to the last set of parentheses surrounding + * the token will be returned. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the token we are checking. + * @param int|string|array $validOwners Array of token constants for the owners + * which should be considered valid. + * + * @return int|false StackPtr to the parentheses closer or false if the token does not have + * parentheses owned by any of the valid owners or if the token is not + * nested in parentheses at all. + */ + public static function getLastCloser(File $phpcsFile, $stackPtr, $validOwners=[]) + { + $opener = self::getLastOpener($phpcsFile, $stackPtr, $validOwners); + $tokens = $phpcsFile->getTokens(); + if ($opener !== false && isset($tokens[$opener]['parenthesis_closer']) === true) { + return $tokens[$opener]['parenthesis_closer']; + } + + return false; + + }//end getLastCloser() + + + /** + * Retrieve the position of the parentheses owner to the last set of parentheses an + * arbitrary token is wrapped in where the parentheses owner is within the set of valid owners. + * + * If no $validOwners are specified, the owner to the last set of parentheses surrounding + * the token will be returned or false if the last set of parentheses does not have an owner. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the token we are checking. + * @param int|string|array $validOwners Array of token constants for the owners + * which should be considered valid. + * + * @return int|false StackPtr to the parentheses owner or false if the token does not have + * parentheses owned by any of the valid owners or if the token is not + * nested in parentheses at all. + */ + public static function getLastOwner(File $phpcsFile, $stackPtr, $validOwners=[]) + { + $opener = self::getLastOpener($phpcsFile, $stackPtr, $validOwners); + if ($opener !== false) { + return self::getOwner($phpcsFile, $opener); + } + + return false; + + }//end getLastOwner() + + + /** + * Check whether the owner of a direct wrapping set of parentheses is within a limited set of + * acceptable tokens. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the + * token to verify. + * @param int|string|array $validOwners Array of token constants for the owners + * which should be considered valid. + * + * @return int|false StackPtr to the valid parentheses owner or false if the token was not + * wrapped in parentheses or if the last set of parentheses in which the + * token is wrapped does not have an owner within the set of owners + * considered valid. + */ + public static function lastOwnerIn(File $phpcsFile, $stackPtr, $validOwners) + { + $opener = self::getLastOpener($phpcsFile, $stackPtr); + + if ($opener !== false && self::isOwnerIn($phpcsFile, $opener, $validOwners) === true) { + return self::getOwner($phpcsFile, $opener); + } + + return false; + + }//end lastOwnerIn() + + + /** + * Helper method. Retrieve the position of a parentheses opener for an arbitrary passed token. + * + * If no $validOwners are specified, the opener to the first set of parentheses surrounding + * the token - or if $reverse=true, the last set of parentheses - will be returned. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the token we are checking. + * @param int|string|array $validOwners Optional. Array of token constants for the owners + * which should be considered valid. + * @param bool $reverse Optional. Whether to search for the highest + * (false) or the deepest set of parentheses (true) + * with the specified owner(s). + * + * @return int|false StackPtr to the parentheses opener or false if the token does not have + * parentheses owned by any of the valid owners or if the token is not + * nested in parentheses at all. + */ + private static function nestedParensWalker(File $phpcsFile, $stackPtr, $validOwners=[], $reverse=false) + { + $tokens = $phpcsFile->getTokens(); + + // Check for the existence of the token. + if (isset($tokens[$stackPtr]) === false) { + return false; + } + + // Make sure the token is nested in parenthesis. + if (empty($tokens[$stackPtr]['nested_parenthesis']) === true) { + return false; + } + + $validOwners = (array) $validOwners; + $parentheses = $tokens[$stackPtr]['nested_parenthesis']; + + if (empty($validOwners) === true) { + // No owners specified, just return the first/last parentheses opener. + if ($reverse === true) { + end($parentheses); + } else { + reset($parentheses); + } + + return key($parentheses); + } + + if ($reverse === true) { + $parentheses = array_reverse($parentheses, true); + } + + foreach ($parentheses as $opener => $closer) { + if (self::isOwnerIn($phpcsFile, $opener, $validOwners) === true) { + // We found a token with a valid owner. + return $opener; + } + } + + return false; + + }//end nestedParensWalker() + + +}//end class diff --git a/src/Util/Sniffs/PassedParameters.php b/src/Util/Sniffs/PassedParameters.php new file mode 100644 index 0000000000..cfd01958a6 --- /dev/null +++ b/src/Util/Sniffs/PassedParameters.php @@ -0,0 +1,385 @@ + + * @copyright 2016-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; + +class PassedParameters +{ + + + /** + * The token types these methods can handle. + * + * @var array + */ + private static $allowedConstructs = [ + T_STRING => true, + T_VARIABLE => true, + T_SELF => true, + T_STATIC => true, + T_CLOSE_CURLY_BRACKET => true, + T_CLOSE_PARENTHESIS => true, + T_ARRAY => true, + T_OPEN_SHORT_ARRAY => true, + T_ISSET => true, + T_UNSET => true, + T_LIST => true, + ]; + + /** + * Tokens which are considered stop point, either because they are the end + * of the parameter (comma) or because we need to skip over them. + * + * @var array + */ + private static $callParsingStopPoints = [ + T_COMMA => T_COMMA, + T_ARRAY => T_ARRAY, + T_OPEN_SHORT_ARRAY => T_OPEN_SHORT_ARRAY, + T_CLOSURE => T_CLOSURE, + T_ANON_CLASS => T_ANON_CLASS, + ]; + + + /** + * The tokens to target to find the double arrow in an array item. + * + * @var array + */ + private static $doubleArrowTargets = [ + T_DOUBLE_ARROW, + T_ARRAY, + T_OPEN_SHORT_ARRAY, + ]; + + + /** + * Checks if any parameters have been passed. + * + * Expects to be passed the T_STRING or T_VARIABLE stack pointer for a function call. + * If passed a T_STRING which is *not* a function call, the behaviour is unreliable. + * + * Extra features: + * - If passed a T_SELF or T_STATIC stack pointer, it will accept it as a + * function call when used like `new self()`. + * - A T_CLOSE_CURLY_BRACKET and a T_CLOSE_PARENTHESIS stack pointer will be + * checked as a function call. + * - If passed a T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer, it will detect + * whether the array (or short list) has values or is empty. + * - If passed a T_ISSET, T_UNSET or T_LIST stack pointer, it will detect whether + * those language constructs have "parameters". + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the T_STRING, T_VARIABLE, T_ARRAY, + * T_OPEN_SHORT_ARRAY, T_ISSET, T_UNSET or T_LIST token. + * + * @return bool + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the token passed is not one of the + * accepted types. + */ + public static function hasParameters(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // Is this one of the tokens this function handles ? + if (isset(self::$allowedConstructs[$tokens[$stackPtr]['code']]) === false) { + throw new RuntimeException( + 'The hasParameters() method expects a function call, array, list, isset or unset token to be passed. Received "'.$tokens[$stackPtr]['type'].'" instead' + ); + } + + // Weed out some of the obvious non-function calls. + if ($tokens[$stackPtr]['code'] === T_SELF || $tokens[$stackPtr]['code'] === T_STATIC) { + $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($tokens[$prev]['code'] !== T_NEW) { + throw new RuntimeException( + 'The hasParameters() method expects a function call, array, list, isset or unset token to be passed. Received "'.$tokens[$stackPtr]['type'].'" instead' + ); + } + } else if ($tokens[$stackPtr]['code'] === T_CLOSE_CURLY_BRACKET + && isset($tokens[$stackPtr]['scope_condition']) === true + ) { + throw new RuntimeException( + 'The hasParameters() method expects a function call, array, list, isset or unset token to be passed. Received a "'.$tokens[$stackPtr]['type'].'" which is not function call' + ); + } else if ($tokens[$stackPtr]['code'] === T_CLOSE_PARENTHESIS + && isset($tokens[$stackPtr]['parenthesis_owner']) === true + ) { + throw new RuntimeException( + 'The hasParameters() method expects a function call, array, list, isset or unset token to be passed. Received a "'.$tokens[$stackPtr]['type'].'" which is not function call' + ); + }//end if + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true); + + if ($nextNonEmpty === false) { + return false; + } + + // Deal with short array and short list syntax. + if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY) { + if ($nextNonEmpty === $tokens[$stackPtr]['bracket_closer']) { + // No parameters. + return false; + } else { + return true; + } + } + + // Deal with function calls, long arrays, long lists, isset and unset. + // Next non-empty token should be the open parenthesis. + if ($tokens[$nextNonEmpty]['code'] !== T_OPEN_PARENTHESIS) { + return false; + } + + if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) { + return false; + } + + $closeParenthesis = $tokens[$nextNonEmpty]['parenthesis_closer']; + $nextNextNonEmpty = $phpcsFile->findNext( + Tokens::$emptyTokens, + ($nextNonEmpty + 1), + ($closeParenthesis + 1), + true + ); + + if ($nextNextNonEmpty === $closeParenthesis) { + // No parameters. + return false; + } + + return true; + + }//end hasParameters() + + + /** + * Get information on all parameters passed to a function call. + * + * Expects to be passed the T_STRING or T_VARIABLE stack pointer for the function call. + * If passed a T_STRING which is *not* a function call, the behaviour is unreliable. + * + * Will return an multi-dimentional array with the start token pointer, end token + * pointer and raw parameter value for all parameters. Index will be 1-based. + * If no parameters are found, will return an empty array. + * + * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer, + * it will tokenize the values / key/value pairs contained in the array call. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the T_STRING, T_VARIABLE, T_ARRAY, + * T_OPEN_SHORT_ARRAY, T_ISSET, T_UNSET or T_LIST token. + * + * @return array + */ + public static function getParameters(File $phpcsFile, $stackPtr) + { + if (self::hasParameters($phpcsFile, $stackPtr) === false) { + return []; + } + + // Ok, we know we have a valid token with parameters and valid open & close brackets/parenthesis. + $tokens = $phpcsFile->getTokens(); + + // Mark the beginning and end tokens. + if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY) { + $opener = $stackPtr; + $closer = $tokens[$stackPtr]['bracket_closer']; + + $nestedParenthesisCount = 0; + } else { + $opener = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true); + $closer = $tokens[$opener]['parenthesis_closer']; + + $nestedParenthesisCount = 1; + } + + // Which nesting level is the one we are interested in ? + if (isset($tokens[$opener]['nested_parenthesis']) === true) { + $nestedParenthesisCount += count($tokens[$opener]['nested_parenthesis']); + } + + $parameters = []; + $nextComma = $opener; + $paramStart = ($opener + 1); + $cnt = 1; + $stopPoints = self::$callParsingStopPoints; + $stopPoints[] = $tokens[$closer]['code']; + + while (($nextComma = $phpcsFile->findNext($stopPoints, ($nextComma + 1), ($closer + 1))) !== false) { + // Ignore anything within short array definition brackets. + if ($tokens[$nextComma]['code'] === T_OPEN_SHORT_ARRAY) { + // Skip forward to the end of the short array definition. + $nextComma = $tokens[$nextComma]['bracket_closer']; + continue; + } + + // Skip past nested arrays. + if ($tokens[$nextComma]['code'] === T_ARRAY + && isset($tokens[$nextComma]['parenthesis_opener'], $tokens[$tokens[$nextComma]['parenthesis_opener']]['parenthesis_closer']) === true + ) { + $nextComma = $tokens[$tokens[$nextComma]['parenthesis_opener']]['parenthesis_closer']; + continue; + } + + // Skip past closures and anonymous classes passed as function parameters. + if (($tokens[$nextComma]['code'] === T_CLOSURE + || $tokens[$nextComma]['code'] === T_ANON_CLASS) + && (isset($tokens[$nextComma]['scope_condition']) === true + && $tokens[$nextComma]['scope_condition'] === $nextComma) + && isset($tokens[$nextComma]['scope_closer']) === true + ) { + // Skip forward to the end of the closure/anonymous class declaration. + $nextComma = $tokens[$nextComma]['scope_closer']; + continue; + } + + // Ignore comma's at a lower nesting level. + if ($tokens[$nextComma]['code'] === T_COMMA + && isset($tokens[$nextComma]['nested_parenthesis']) === true + && count($tokens[$nextComma]['nested_parenthesis']) !== $nestedParenthesisCount + ) { + continue; + } + + // Ignore closing parenthesis/bracket if not 'ours'. + if ($tokens[$nextComma]['code'] === $tokens[$closer]['code'] && $nextComma !== $closer) { + continue; + } + + // Ok, we've reached the end of the parameter. + $parameters[$cnt]['start'] = $paramStart; + $parameters[$cnt]['end'] = ($nextComma - 1); + $parameters[$cnt]['raw'] = trim($phpcsFile->getTokensAsString($paramStart, ($nextComma - $paramStart))); + + // Check if there are more tokens before the closing parenthesis. + // Prevents function calls with trailing comma's from setting an extra parameter: + // `functionCall( $param1, $param2, );`. + $hasNextParam = $phpcsFile->findNext( + Tokens::$emptyTokens, + ($nextComma + 1), + $closer, + true, + null, + true + ); + if ($hasNextParam === false) { + break; + } + + // Prepare for the next parameter. + $paramStart = ($nextComma + 1); + $cnt++; + }//end while + + return $parameters; + + }//end getParameters() + + + /** + * Get information on a specific parameter passed. + * + * Expects to be passed the T_STRING or T_VARIABLE stack pointer for the function call. + * If passed a T_STRING which is *not* a function call, the behaviour is unreliable. + * + * Will return a array with the start token pointer, end token pointer and the raw value + * of the parameter at a specific offset. + * If the specified parameter is not found, will return false. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the T_STRING, T_VARIABLE, T_ARRAY, + * T_OPEN_SHORT_ARRAY, T_ISSET, T_UNSET or T_LIST token. + * @param int $paramOffset The 1-based index position of the parameter to retrieve. + * + * @return array|false + */ + public static function getParameter(File $phpcsFile, $stackPtr, $paramOffset) + { + $parameters = self::getParameters($phpcsFile, $stackPtr); + + if (isset($parameters[$paramOffset]) === false) { + return false; + } + + return $parameters[$paramOffset]; + + }//end getParameter() + + + /** + * Count the number of parameters which have been passed. + * + * Expects to be passed the T_STRING or T_VARIABLE stack pointer for the function call. + * If passed a T_STRING which is *not* a function call, the behaviour is unreliable. + * + * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer, + * it will return the number of values in the array. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the T_STRING, T_VARIABLE, T_ARRAY, + * T_OPEN_SHORT_ARRAY, T_ISSET, T_UNSET or T_LIST token. + * + * @return int + */ + public static function getParameterCount(File $phpcsFile, $stackPtr) + { + if (self::hasParameters($phpcsFile, $stackPtr) === false) { + return 0; + } + + return count(self::getParameters($phpcsFile, $stackPtr)); + + }//end getParameterCount() + + + /** + * Get the position of the double arrow within an array item. + * + * Expects to be passed the parameter start/end as retrieved via getParameters(). + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being examined. + * @param int $start Stack pointer to the start of the array item. + * @param int $end Stack pointer to the end of the array item. + * + * @return int|false Pointer to the double arrow if this array item has an index or false otherwise. + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the start or end positions are invalid. + */ + public static function getDoubleArrowPosition(File $phpcsFile, $start, $end) + { + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$start], $tokens[$end]) === false || $start > $end) { + throw new RuntimeException( + 'Invalid start and/or end position passed to getDoubleArrowPosition(). Received: $start '.$start.', $end '.$end + ); + } + + $doubleArrow = $phpcsFile->findNext( + self::$doubleArrowTargets, + $start, + ($end + 1) + ); + + if ($doubleArrow !== false && $tokens[$doubleArrow]['code'] === T_DOUBLE_ARROW) { + return $doubleArrow; + } + + return false; + + }//end getDoubleArrowPosition() + + +}//end class diff --git a/src/Util/Sniffs/TextStrings.php b/src/Util/Sniffs/TextStrings.php new file mode 100644 index 0000000000..dbec9dc6c6 --- /dev/null +++ b/src/Util/Sniffs/TextStrings.php @@ -0,0 +1,111 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Files\File; + +class TextStrings +{ + + + /** + * Get the complete contents of a multi-line text string. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr Pointer to the first text string token + * of a multi-line text string or to a + * Nowdoc/Heredoc opener. + * @param bool $stripQuotes Optional. Whether to strip text delimiter + * quotes off the resulting text string. + * Defaults to true. + * + * @return string + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * valid text string token or if the + * token is not the first text string token. + */ + public static function getCompleteTextString(File $phpcsFile, $stackPtr, $stripQuotes=true) + { + $tokens = $phpcsFile->getTokens(); + + // Must be the start of a text string token. + if ($tokens[$stackPtr]['code'] !== T_START_HEREDOC + && $tokens[$stackPtr]['code'] !== T_START_NOWDOC + && $tokens[$stackPtr]['code'] !== T_CONSTANT_ENCAPSED_STRING + && $tokens[$stackPtr]['code'] !== T_DOUBLE_QUOTED_STRING + ) { + throw new RuntimeException('$stackPtr must be of type T_START_HEREDOC, T_START_NOWDOC, T_CONSTANT_ENCAPSED_STRING or T_DOUBLE_QUOTED_STRING'); + } + + if ($tokens[$stackPtr]['code'] === T_CONSTANT_ENCAPSED_STRING + || $tokens[$stackPtr]['code'] === T_DOUBLE_QUOTED_STRING + ) { + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if ($tokens[$stackPtr]['code'] === $tokens[$prev]['code']) { + throw new RuntimeException('$stackPtr must be the start of the text string'); + } + } + + switch ($tokens[$stackPtr]['code']) { + case T_START_HEREDOC: + $stripQuotes = false; + $targetType = T_HEREDOC; + $current = ($stackPtr + 1); + break; + + case T_START_NOWDOC: + $stripQuotes = false; + $targetType = T_NOWDOC; + $current = ($stackPtr + 1); + break; + + default: + $targetType = $tokens[$stackPtr]['code']; + $current = $stackPtr; + break; + } + + $string = ''; + do { + $string .= $tokens[$current]['content']; + ++$current; + } while ($tokens[$current]['code'] === $targetType); + + if ($stripQuotes === true) { + return self::stripQuotes($string); + } + + return $string; + + }//end getCompleteTextString() + + + /** + * Strip text delimiter quotes from an arbitrary string. + * + * Intended for use with the "contents" of a T_CONSTANT_ENCAPSED_STRING / T_DOUBLE_QUOTED_STRING. + * + * Prevents stripping mis-matched quotes. + * Prevents stripping quotes from the textual content of the string. + * + * @param string $string The raw string. + * + * @return string String without quotes around it. + */ + public static function stripQuotes($string) + { + return preg_replace('`^([\'"])(.*)\1$`Ds', '$2', $string); + + }//end stripQuotes() + + +}//end class diff --git a/src/Util/Sniffs/TokenIs.php b/src/Util/Sniffs/TokenIs.php new file mode 100644 index 0000000000..3eb6b0427a --- /dev/null +++ b/src/Util/Sniffs/TokenIs.php @@ -0,0 +1,296 @@ + + * @author Juliette Reinders Folmer + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; + +class TokenIs +{ + + + /** + * Determine if the passed token is a reference operator. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position of the T_BITWISE_AND token. + * + * @return boolean True if the specified token represents a reference. + * False if the token represents a bitwise operator or is not + * a T_BITWISE_AND token. + */ + public static function isReference(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] !== T_BITWISE_AND) { + return false; + } + + $tokenBefore = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + + if ($tokens[$tokenBefore]['code'] === T_FUNCTION) { + // Function returns a reference. + return true; + } + + if ($tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) { + // Inside a foreach loop or array assignment, this is a reference. + return true; + } + + if ($tokens[$tokenBefore]['code'] === T_AS) { + // Inside a foreach loop, this is a reference. + return true; + } + + if (isset(Tokens::$assignmentTokens[$tokens[$tokenBefore]['code']]) === true) { + // This is directly after an assignment. It's a reference. Even if + // it is part of an operation, the other tests will handle it. + return true; + } + + $tokenAfter = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + + if ($tokens[$tokenAfter]['code'] === T_NEW) { + return true; + } + + $lastOpener = Parentheses::getLastOpener($phpcsFile, $stackPtr); + if ($lastOpener !== false) { + $lastOwner = Parentheses::lastOwnerIn($phpcsFile, $stackPtr, [T_FUNCTION, T_CLOSURE]); + if ($lastOwner !== false) { + $params = FunctionDeclarations::getParameters($phpcsFile, $lastOwner); + foreach ($params as $param) { + $varToken = $tokenAfter; + if ($param['variable_length'] === true) { + $varToken = $phpcsFile->findNext( + (Tokens::$emptyTokens + [T_ELLIPSIS]), + ($stackPtr + 1), + null, + true + ); + } + + if ($param['token'] === $varToken + && $param['pass_by_reference'] === true + ) { + // Function parameter declared to be passed by reference. + return true; + } + } + } else if (isset($tokens[$lastOpener]['parenthesis_owner']) === false) { + $prev = false; + for ($t = ($lastOpener - 1); $t >= 0; $t--) { + if ($tokens[$t]['code'] !== T_WHITESPACE) { + $prev = $t; + break; + } + } + + if ($prev !== false && $tokens[$prev]['code'] === T_USE) { + // Closure use by reference. + return true; + } + }//end if + }//end if + + // Pass by reference in function calls and assign by reference in arrays. + if ($tokens[$tokenBefore]['code'] === T_OPEN_PARENTHESIS + || $tokens[$tokenBefore]['code'] === T_COMMA + || $tokens[$tokenBefore]['code'] === T_OPEN_SHORT_ARRAY + ) { + if ($tokens[$tokenAfter]['code'] === T_VARIABLE) { + return true; + } else { + $skip = Tokens::$emptyTokens; + $skip[] = T_NS_SEPARATOR; + $skip[] = T_SELF; + $skip[] = T_PARENT; + $skip[] = T_STATIC; + $skip[] = T_STRING; + $skip[] = T_NAMESPACE; + $skip[] = T_DOUBLE_COLON; + + $nextSignificantAfter = $phpcsFile->findNext( + $skip, + ($stackPtr + 1), + null, + true + ); + if ($tokens[$nextSignificantAfter]['code'] === T_VARIABLE) { + return true; + } + }//end if + }//end if + + return false; + + }//end isReference() + + + /** + * Determine whether a T_OPEN/CLOSE_SHORT_ARRAY token is a short list() construct. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the array bracket token. + * + * @return bool True if the token passed is the open/close bracket of a short list. + * False if the token is a short array bracket or not + * a T_OPEN/CLOSE_SHORT_ARRAY token. + */ + public static function isShortList(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // Is this one of the tokens this function handles ? + if ($tokens[$stackPtr]['code'] !== T_OPEN_SHORT_ARRAY + && $tokens[$stackPtr]['code'] !== T_CLOSE_SHORT_ARRAY + ) { + return false; + } + + switch ($tokens[$stackPtr]['code']) { + case T_OPEN_SHORT_ARRAY: + $opener = $stackPtr; + $closer = $tokens[$stackPtr]['bracket_closer']; + break; + + case T_CLOSE_SHORT_ARRAY: + $opener = $tokens[$stackPtr]['bracket_opener']; + $closer = $stackPtr; + break; + } + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($closer + 1), null, true, null, true); + if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === T_EQUAL) { + return true; + } + + // Check for short list in foreach, i.e. `foreach($array as [$a, $b])`. + $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), null, true, null, true); + if ($prevNonEmpty !== false + && ($tokens[$prevNonEmpty]['code'] === T_AS + || $tokens[$prevNonEmpty]['code'] === T_DOUBLE_ARROW) + && Parentheses::lastOwnerIn($phpcsFile, $prevNonEmpty, T_FOREACH) !== false + ) { + return true; + } + + // Maybe this is a short list syntax nested inside another short list syntax ? + $parentOpen = $opener; + do { + $parentOpen = $phpcsFile->findPrevious( + T_OPEN_SHORT_ARRAY, + ($parentOpen - 1), + null, + false, + null, + true + ); + + if ($parentOpen === false) { + return false; + } + } while ($tokens[$parentOpen]['bracket_closer'] < $opener); + + return self::isShortList($phpcsFile, $parentOpen); + + }//end isShortList() + + + /** + * Determine whether a T_MINUS/T_PLUS token is a unary operator. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the plus/minus token. + * + * @return bool True if the token passed is a unary operator. + * False otherwise or if the token is not a T_PLUS/T_MINUS token. + */ + public static function isUnaryPlusMinus(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$stackPtr]) === false + || ($tokens[$stackPtr]['code'] !== T_PLUS + && $tokens[$stackPtr]['code'] !== T_MINUS) + ) { + return false; + } + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($next === false) { + // Live coding or parse error. + return false; + } + + if (isset(Tokens::$operators[$tokens[$next]['code']]) === true) { + // Next token is an operator, so this is not a unary. + return false; + } + + $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + + if ($tokens[$prev]['code'] === T_RETURN) { + // Just returning a positive/negative value; eg. (return -1). + return true; + } + + if (isset(Tokens::$operators[$tokens[$prev]['code']]) === true) { + // Just trying to operate on a positive/negative value; eg. ($var * -1). + return true; + } + + if (isset(Tokens::$comparisonTokens[$tokens[$prev]['code']]) === true) { + // Just trying to compare a positive/negative value; eg. ($var === -1). + return true; + } + + if (isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true) { + // Just trying to compare a positive/negative value; eg. ($var || -1 === $b). + return true; + } + + if (isset(Tokens::$assignmentTokens[$tokens[$prev]['code']]) === true) { + // Just trying to assign a positive/negative value; eg. ($var = -1). + return true; + } + + if (isset(Tokens::$castTokens[$tokens[$prev]['code']]) === true) { + // Just casting a positive/negative value; eg. (string) -$var. + return true; + } + + // Other indicators that a plus/minus sign is a unary operator. + $invalidTokens = [ + T_COMMA => true, + T_OPEN_PARENTHESIS => true, + T_OPEN_SQUARE_BRACKET => true, + T_OPEN_SHORT_ARRAY => true, + T_COLON => true, + T_INLINE_THEN => true, + T_INLINE_ELSE => true, + T_CASE => true, + T_OPEN_CURLY_BRACKET => true, + ]; + + if (isset($invalidTokens[$tokens[$prev]['code']]) === true) { + // Just trying to use a positive/negative value; eg. myFunction($var, -2). + return true; + } + + return false; + + }//end isUnaryPlusMinus() + + +}//end class diff --git a/src/Util/Sniffs/UseStatements.php b/src/Util/Sniffs/UseStatements.php new file mode 100644 index 0000000000..9b138b82eb --- /dev/null +++ b/src/Util/Sniffs/UseStatements.php @@ -0,0 +1,284 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; + +class UseStatements +{ + + + /** + * Determine what a T_USE token is used for. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the T_USE token. + * + * @return string Either 'closure', 'import' or 'trait'. + * An empty string will be returned if the token is used in an + * invalid context or if it couldn't be reliably determined + * what the T_USE token is used for. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * T_USE token. + */ + public static function getType(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$stackPtr]) === false + || $tokens[$stackPtr]['code'] !== T_USE + ) { + throw new RuntimeException('$stackPtr must be of type T_USE'); + } + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($next === false) { + // Live coding or parse error. + return ''; + } + + $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($prev !== false && $tokens[$prev]['code'] === T_CLOSE_PARENTHESIS + && Parentheses::isOwnerIn($phpcsFile, $prev, T_CLOSURE) === true + ) { + return 'closure'; + } + + $lastCondition = Conditions::getLastCondition($phpcsFile, $stackPtr); + + if ($lastCondition === false || $tokens[$lastCondition]['code'] === T_NAMESPACE) { + // Global or scoped namespace and not a closure use statement. + return 'import'; + } + + $traitScopes = Tokens::$ooScopeTokens; + // Only classes and traits can import traits. + unset($traitScopes[T_INTERFACE]); + + if (isset($traitScopes[$tokens[$lastCondition]['code']]) === true) { + return 'trait'; + } + + return ''; + + }//end getType() + + + /** + * Determine whether a T_USE token represents a closure use statement. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the T_USE token. + * + * @return bool True if the token passed is a closure use statement. + * False if it's not. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * T_USE token. + */ + public static function isClosureUse(File $phpcsFile, $stackPtr) + { + return (self::getType($phpcsFile, $stackPtr) === 'closure'); + + }//end isClosureUse() + + + /** + * Determine whether a T_USE token represents a class/function/constant import use statement. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the T_USE token. + * + * @return bool True if the token passed is an import use statement. + * False if it's not. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * T_USE token. + */ + public static function isImportUse(File $phpcsFile, $stackPtr) + { + return (self::getType($phpcsFile, $stackPtr) === 'import'); + + }//end isImportUse() + + + /** + * Determine whether a T_USE token represents a trait use statement. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the T_USE token. + * + * @return bool True if the token passed is a trait use statement. + * False if it's not. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * T_USE token. + */ + public static function isTraitUse(File $phpcsFile, $stackPtr) + { + return (self::getType($phpcsFile, $stackPtr) === 'trait'); + + }//end isTraitUse() + + + /** + * Split an import use statement into individual imports. + * + * Handles single import, multi-import and group-import statements. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the T_USE token. + * + * @return array A multi-level array containing information about the use statement. + * The first level is 'name', 'function' and 'const'. These keys will always exist. + * If any statements are found for any of these categories, the second level + * will contain the alias/name as the key and the full original use name as the + * value for each of the found imports or an empty array if no imports were found + * in this use statement for this category. + * + * For example, for this function group use statement: + * `use function Vendor\Package\{LevelA\Name as Alias, LevelB\Another_Name}` + * the return value would look like this: + * `[ + * 'name' => [], + * 'function' => [ + * 'Alias' => 'Vendor\Package\LevelA\Name', + * 'Another_Name' => 'Vendor\Package\LevelB\Another_Name', + * ], + * 'const' => [], + * ]` + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * T_USE token or not an import use statement. + */ + public static function splitImportUseStatement(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] !== T_USE) { + throw new RuntimeException('$stackPtr must be of type T_USE'); + } + + if (self::isImportUse($phpcsFile, $stackPtr) === false) { + throw new RuntimeException('$stackPtr must be an import use statement'); + } + + $statements = [ + 'name' => [], + 'function' => [], + 'const' => [], + ]; + + $endOfStatement = $phpcsFile->findNext([T_SEMICOLON, T_CLOSE_TAG], ($stackPtr + 1)); + if ($endOfStatement === false) { + // Live coding or parse error. + return $statements; + } + + $endOfStatement++; + + $start = true; + $useGroup = false; + $hasAlias = false; + $baseName = ''; + $name = ''; + $type = ''; + $fixedType = false; + $alias = ''; + + for ($i = ($stackPtr + 1); $i < $endOfStatement; $i++) { + if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { + continue; + } + + switch ($tokens[$i]['code']) { + case T_STRING: + // Only when either at the start of the statement or at the start of a new sub within a group. + if ($start === true && $fixedType === false) { + $content = strtolower($tokens[$i]['content']); + if ($content === 'function' + || $content === 'const' + ) { + $type = $content; + $start = false; + if ($useGroup === false) { + $fixedType = true; + } + + break; + } else { + $type = 'name'; + } + } + + $start = false; + + if ($hasAlias === false) { + $name .= $tokens[$i]['content']; + } + + $alias = $tokens[$i]['content']; + break; + + case T_AS: + $hasAlias = true; + break; + + case T_OPEN_USE_GROUP: + $start = true; + $useGroup = true; + $baseName = $name; + $name = ''; + break; + + case T_SEMICOLON: + case T_CLOSE_TAG: + case T_CLOSE_USE_GROUP: + case T_COMMA: + if ($name !== '') { + if ($useGroup === true) { + $statements[$type][$alias] = $baseName.$name; + } else { + $statements[$type][$alias] = $name; + } + } + + if ($tokens[$i]['code'] !== T_COMMA) { + return $statements; + } + + // Reset. + $start = true; + $name = ''; + $hasAlias = false; + if ($fixedType === false) { + $type = ''; + } + break; + + case T_NS_SEPARATOR: + $name .= $tokens[$i]['content']; + break; + + // Fall back in case reserved keyword is (illegally) used in name. + // Parse error, but not our concern. + default: + $name .= $tokens[$i]['content']; + break; + }//end switch + }//end for + + }//end splitImportUseStatement() + + +}//end class diff --git a/src/Util/Sniffs/Variables.php b/src/Util/Sniffs/Variables.php new file mode 100644 index 0000000000..7df142dec8 --- /dev/null +++ b/src/Util/Sniffs/Variables.php @@ -0,0 +1,290 @@ + + * @author Juliette Reinders Folmer + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util\Sniffs; + +use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Sniffs\TextStrings; +use PHP_CodeSniffer\Util\Tokens; + +class Variables +{ + + + /** + * List of PHP Reserved variables. + * + * @var array => + * + * @link http://php.net/manual/en/reserved.variables.php + */ + public static $phpReservedVars = [ + '_SERVER' => true, + '_GET' => true, + '_POST' => true, + '_REQUEST' => true, + '_SESSION' => true, + '_ENV' => true, + '_COOKIE' => true, + '_FILES' => true, + 'GLOBALS' => true, + 'http_response_header' => false, + 'argc' => false, + 'argv' => false, + + // Deprecated. + 'php_errormsg' => false, + + // Removed PHP 5.4.0. + 'HTTP_SERVER_VARS' => false, + 'HTTP_GET_VARS' => false, + 'HTTP_POST_VARS' => false, + 'HTTP_SESSION_VARS' => false, + 'HTTP_ENV_VARS' => false, + 'HTTP_COOKIE_VARS' => false, + 'HTTP_POST_FILES' => false, + + // Removed PHP 5.6.0. + 'HTTP_RAW_POST_DATA' => false, + ]; + + + /** + * Returns the visibility and implementation properties of the class member + * variable found at the specified position in the stack. + * + * The format of the array is: + * + * + * array( + * 'scope' => 'public', // public protected or protected. + * 'scope_specified' => false, // true if the scope was explicitly specified. + * 'is_static' => false, // true if the static keyword was found. + * ); + * + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of the T_VARIABLE token to + * acquire the properties for. + * + * @return array + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * T_VARIABLE token, or if the position is not + * a class member variable. + */ + public static function getMemberProperties(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] !== T_VARIABLE) { + throw new RuntimeException('$stackPtr must be of type T_VARIABLE'); + } + + if (Conditions::isOOProperty($phpcsFile, $stackPtr) === false) { + $lastCondition = Conditions::getlastCondition($phpcsFile, $stackPtr); + if ($lastCondition !== false + && $tokens[$lastCondition]['code'] === T_INTERFACE + ) { + // T_VARIABLEs in interfaces can actually be method arguments + // but they wont be seen as being inside the method because there + // are no scope openers and closers for abstract methods. If it is in + // parentheses, we can be pretty sure it is a method argument. + if (empty($tokens[$stackPtr]['nested_parenthesis']) === true) { + $error = 'Possible parse error: interfaces may not include member vars'; + $phpcsFile->addWarning($error, $stackPtr, 'Internal.ParseError.InterfaceHasMemberVar'); + return []; + } + } else { + throw new RuntimeException('$stackPtr is not a class member var'); + } + } + + $valid = [ + T_PUBLIC => T_PUBLIC, + T_PRIVATE => T_PRIVATE, + T_PROTECTED => T_PROTECTED, + T_STATIC => T_STATIC, + T_VAR => T_VAR, + ]; + + $valid += Tokens::$emptyTokens; + + $scope = 'public'; + $scopeSpecified = false; + $isStatic = false; + + $startOfStatement = $phpcsFile->findPrevious( + [ + T_SEMICOLON, + T_OPEN_CURLY_BRACKET, + T_CLOSE_CURLY_BRACKET, + ], + ($stackPtr - 1) + ); + + for ($i = ($startOfStatement + 1); $i < $stackPtr; $i++) { + if (isset($valid[$tokens[$i]['code']]) === false) { + break; + } + + switch ($tokens[$i]['code']) { + case T_PUBLIC: + $scope = 'public'; + $scopeSpecified = true; + break; + case T_PRIVATE: + $scope = 'private'; + $scopeSpecified = true; + break; + case T_PROTECTED: + $scope = 'protected'; + $scopeSpecified = true; + break; + case T_STATIC: + $isStatic = true; + break; + } + }//end for + + return [ + 'scope' => $scope, + 'scope_specified' => $scopeSpecified, + 'is_static' => $isStatic, + ]; + + }//end getMemberProperties() + + + /** + * Verify if a given variable name is the name of a PHP reserved variable. + * + * @param string $name The full variable name with or without leading dollar sign. + * This allows for passing an array key variable name, such as + * '_GET' retrieved from $GLOBALS['_GET']. + * Note: when passing an array key, string quotes are expected + * to have been stripped already. + * + * @return bool + */ + public static function isPHPReservedVarName($name) + { + if (strpos($name, '$') === 0) { + $name = substr($name, 1); + } + + return (isset(self::$phpReservedVars[$name]) === true); + + }//end isPHPReservedVarName() + + + /** + * Verify if a given variable or array key token points to a PHP superglobal. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack of a T_VARIABLE + * token or the array key to a variable in $GLOBALS. + * + * @return bool + */ + public static function isSuperglobal(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] !== T_VARIABLE + && $tokens[$stackPtr]['code'] !== T_CONSTANT_ENCAPSED_STRING + ) { + return false; + } + + $content = $tokens[$stackPtr]['content']; + + if ($tokens[$stackPtr]['code'] === T_CONSTANT_ENCAPSED_STRING) { + $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($prev === false || $tokens[$prev]['code'] !== T_OPEN_SQUARE_BRACKET + && $next === false || $tokens[$next]['code'] !== T_CLOSE_SQUARE_BRACKET + ) { + return false; + } + + $pprev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true); + if ($pprev === false + || $tokens[$pprev]['code'] !== T_VARIABLE + || $tokens[$pprev]['content'] !== '$GLOBALS' + ) { + return false; + } + + $content = TextStrings::stripQuotes($content); + } + + return self::isSuperglobalName($content); + + }//end isSuperglobal() + + + /** + * Verify if a given variable name is the name of a PHP superglobal. + * + * @param string $name The full variable name with or without leading dollar sign. + * This allows for passing an array key variable name, such as + * '_GET' retrieved from $GLOBALS['_GET']. + * Note: when passing an array key, string quotes are expected + * to have been stripped already. + * + * @return bool + */ + public static function isSuperglobalName($name) + { + if (strpos($name, '$') === 0) { + $name = substr($name, 1); + } + + if (isset(self::$phpReservedVars[$name]) === false) { + return false; + } + + return self::$phpReservedVars[$name]; + + }//end isSuperglobalName() + + + /** + * Determine if a variable is in the `as $key => $value` part of a foreach condition. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. + * @param int $stackPtr Pointer to the variable. + * + * @return bool True if it is. False otherwise. + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a + * T_VARIABLE token. + */ + public static function isForeachAs(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] !== T_VARIABLE) { + throw new RuntimeException('$stackPtr must be of type T_VARIABLE'); + } + + if (Parentheses::lastOwnerIn($phpcsFile, $stackPtr, T_FOREACH) === false) { + return false; + } + + $openParenthesis = Parentheses::getLastOpener($phpcsFile, $stackPtr); + $closeParenthesis = Parentheses::getLastCloser($phpcsFile, $stackPtr); + $asPtr = $phpcsFile->findNext(T_AS, ($openParenthesis + 1), $closeParenthesis); + return ($asPtr !== false && $stackPtr > $asPtr); + + }//end isForeachAs() + + +}//end class diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 19daf1b683..2f0777d677 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -135,7 +135,7 @@ final class Tokens /** * The token weightings. * - * @var array + * @var array */ public static $weightings = [ T_CLASS => 1000, @@ -216,7 +216,7 @@ final class Tokens /** * Tokens that represent assignments. * - * @var array + * @var array */ public static $assignmentTokens = [ T_EQUAL => T_EQUAL, @@ -240,7 +240,7 @@ final class Tokens /** * Tokens that represent equality comparisons. * - * @var array + * @var array */ public static $equalityTokens = [ T_IS_EQUAL => T_IS_EQUAL, @@ -254,7 +254,7 @@ final class Tokens /** * Tokens that represent comparison operator. * - * @var array + * @var array */ public static $comparisonTokens = [ T_IS_EQUAL => T_IS_EQUAL, @@ -272,7 +272,7 @@ final class Tokens /** * Tokens that represent arithmetic operators. * - * @var array + * @var array */ public static $arithmeticTokens = [ T_PLUS => T_PLUS, @@ -286,7 +286,7 @@ final class Tokens /** * Tokens that perform operations. * - * @var array + * @var array */ public static $operators = [ T_MINUS => T_MINUS, @@ -307,7 +307,7 @@ final class Tokens /** * Tokens that perform boolean operations. * - * @var array + * @var array */ public static $booleanOperators = [ T_BOOLEAN_AND => T_BOOLEAN_AND, @@ -320,7 +320,7 @@ final class Tokens /** * Tokens that represent casting. * - * @var array + * @var array */ public static $castTokens = [ T_INT_CAST => T_INT_CAST, @@ -336,7 +336,7 @@ final class Tokens /** * Token types that open parenthesis. * - * @var array + * @var array */ public static $parenthesisOpeners = [ T_ARRAY => T_ARRAY, @@ -355,7 +355,7 @@ final class Tokens /** * Tokens that are allowed to open scopes. * - * @var array + * @var array */ public static $scopeOpeners = [ T_CLASS => T_CLASS, @@ -387,7 +387,7 @@ final class Tokens /** * Tokens that represent scope modifiers. * - * @var array + * @var array */ public static $scopeModifiers = [ T_PRIVATE => T_PRIVATE, @@ -398,7 +398,7 @@ final class Tokens /** * Tokens that can prefix a method name * - * @var array + * @var array */ public static $methodPrefixes = [ T_PRIVATE => T_PRIVATE, @@ -412,7 +412,7 @@ final class Tokens /** * Tokens that open code blocks. * - * @var array + * @var array */ public static $blockOpeners = [ T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET, @@ -424,7 +424,7 @@ final class Tokens /** * Tokens that don't represent code. * - * @var array + * @var array */ public static $emptyTokens = [ T_WHITESPACE => T_WHITESPACE, @@ -446,7 +446,7 @@ final class Tokens /** * Tokens that are comments. * - * @var array + * @var array */ public static $commentTokens = [ T_COMMENT => T_COMMENT, @@ -467,7 +467,7 @@ final class Tokens /** * Tokens that are comments containing PHPCS instructions. * - * @var array + * @var array */ public static $phpcsCommentTokens = [ T_PHPCS_ENABLE => T_PHPCS_ENABLE, @@ -482,7 +482,7 @@ final class Tokens * * Note that T_STRINGS are NOT represented in this list. * - * @var array + * @var array */ public static $stringTokens = [ T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING, @@ -492,7 +492,7 @@ final class Tokens /** * Tokens that represent text strings. * - * @var array + * @var array */ public static $textStringTokens = [ T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING, @@ -505,7 +505,7 @@ final class Tokens /** * Tokens that represent brackets and parenthesis. * - * @var array + * @var array */ public static $bracketTokens = [ T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET, @@ -519,7 +519,7 @@ final class Tokens /** * Tokens that include files. * - * @var array + * @var array */ public static $includeTokens = [ T_REQUIRE_ONCE => T_REQUIRE_ONCE, @@ -531,7 +531,7 @@ final class Tokens /** * Tokens that make up a heredoc string. * - * @var array + * @var array */ public static $heredocTokens = [ T_START_HEREDOC => T_START_HEREDOC, @@ -548,7 +548,7 @@ final class Tokens * Mostly, these are just strings. But PHP tokenizes some language * constructs and functions using their own tokens. * - * @var array + * @var array */ public static $functionNameTokens = [ T_STRING => T_STRING, @@ -568,7 +568,7 @@ final class Tokens /** * Tokens that open class and object scopes. * - * @var array + * @var array */ public static $ooScopeTokens = [ T_CLASS => T_CLASS, @@ -585,7 +585,7 @@ final class Tokens * function. If passed a string, it is assumed to be a PHPCS-supplied token * that begins with PHPCS_T_, so the name is sourced from the token value itself. * - * @param int|string $token The token to get the name for. + * @param integer|string $token The token to get the name for. * * @return string */ @@ -611,8 +611,8 @@ public static function tokenName($token) * * Returns false if there are no weightings for any of the specified tokens. * - * @param array $tokens The token types to get the highest weighted - * type for. + * @param array $tokens The token types to get the highest weighted + * type for. * * @return int The highest weighted token. */ diff --git a/tests/Core/AbstractMethodUnitTest.php b/tests/Core/AbstractMethodUnitTest.php new file mode 100644 index 0000000000..327bb76acb --- /dev/null +++ b/tests/Core/AbstractMethodUnitTest.php @@ -0,0 +1,137 @@ + + * @copyright 2018-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core; + +use PHP_CodeSniffer\Config; +use PHP_CodeSniffer\Ruleset; +use PHP_CodeSniffer\Files\DummyFile; +use PHPUnit\Framework\TestCase; + +abstract class AbstractMethodUnitTest extends TestCase +{ + + /** + * The file extension of the test case file (without leading dot). + * + * This allows child classes to overrule the default `inc` with, for instance, + * `js` or `css` when applicable. + * + * @var string + */ + protected static $fileExtension = 'inc'; + + /** + * The \PHP_CodeSniffer\Files\File object containing the parsed contents of the test case file. + * + * @var \PHP_CodeSniffer\Files\File + */ + protected static $phpcsFile; + + + /** + * Initialize & tokenize \PHP_CodeSniffer\Files\File with code from the test case file. + * + * The test case file for a unit test class has to be in the same directory + * directory and use the same file name as the test class, using the .inc extension. + * + * @return void + */ + public static function setUpBeforeClass() + { + $config = new Config(); + $config->standards = ['PSR1']; + + $ruleset = new Ruleset($config); + + // Default to a file with the same name as the test class. Extension is property based. + $relativeCN = str_replace(__NAMESPACE__, '', get_called_class()); + $relativePath = str_replace('\\', DIRECTORY_SEPARATOR, $relativeCN); + $pathToTestFile = realpath(__DIR__).$relativePath.'.'.static::$fileExtension; + + // Make sure the file gets parsed correctly based on the file type. + $contents = 'phpcs_input_file: '.$pathToTestFile.PHP_EOL; + $contents .= file_get_contents($pathToTestFile); + + self::$phpcsFile = new DummyFile($contents, $ruleset, $config); + self::$phpcsFile->process(); + + }//end setUpBeforeClass() + + + /** + * Clean up after finished test. + * + * @return void + */ + public static function tearDownAfterClass() + { + self::$phpcsFile = null; + + }//end tearDownAfterClass() + + + /** + * Get the token pointer for a target token based on a specific comment found on the line before. + * + * @param string $commentString The comment to look for. + * @param int|string|array $tokenType The type of token(s) to look for. + * @param string $tokenContent Optional. The token content for the target token. + * + * @return int + */ + public function getTargetToken($commentString, $tokenType, $tokenContent=null) + { + $start = (self::$phpcsFile->numTokens - 1); + $comment = self::$phpcsFile->findPrevious( + T_COMMENT, + $start, + null, + false, + $commentString + ); + + $tokens = self::$phpcsFile->getTokens(); + $end = ($start + 1); + + // Limit the token finding to between this and the next case comment. + for ($i = ($comment + 1); $i < $end; $i++) { + if ($tokens[$i]['code'] !== T_COMMENT) { + continue; + } + + if (stripos($tokens[$i]['content'], '/* test') === 0) { + $end = $i; + break; + } + } + + $target = self::$phpcsFile->findNext( + $tokenType, + ($comment + 1), + $end, + false, + $tokenContent + ); + + if ($target === false) { + $msg = 'Failed to find test target token for comment string: '.$commentString; + if ($tokenContent !== null) { + $msg .= ' With token content: '.$tokenContent; + } + + $this->assertFalse(true, $msg); + } + + return $target; + + }//end getTargetToken() + + +}//end class diff --git a/tests/Core/AllTests.php b/tests/Core/AllTests.php index 36a98a9ba7..d6587f076e 100644 --- a/tests/Core/AllTests.php +++ b/tests/Core/AllTests.php @@ -12,15 +12,7 @@ use PHPUnit\TextUI\TestRunner; use PHPUnit\Framework\TestSuite; -require_once 'IsCamelCapsTest.php'; -require_once 'ErrorSuppressionTest.php'; -require_once 'File/FindEndOfStatementTest.php'; -require_once 'File/FindExtendedClassNameTest.php'; -require_once 'File/FindImplementedInterfaceNamesTest.php'; -require_once 'File/GetMemberPropertiesTest.php'; -require_once 'File/GetMethodParametersTest.php'; -require_once 'File/GetMethodPropertiesTest.php'; -require_once 'File/IsReferenceTest.php'; +require_once dirname(dirname(__DIR__)).'/scripts/ValidatePEAR/FileList.php'; class AllTests { @@ -46,15 +38,23 @@ public static function main() public static function suite() { $suite = new TestSuite('PHP CodeSniffer Core'); - $suite->addTestSuite('PHP_CodeSniffer\Tests\Core\IsCamelCapsTest'); - $suite->addTestSuite('PHP_CodeSniffer\Tests\Core\ErrorSuppressionTest'); - $suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\FindEndOfStatementTest'); - $suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\FindExtendedClassNameTest'); - $suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\FindImplementedInterfaceNamesTest'); - $suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\GetMemberPropertiesTest'); - $suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\GetMethodParametersTest'); - $suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\GetMethodPropertiesTest'); - $suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\IsReferenceTest'); + + $testFileIterator = (new \FileList(__DIR__, '', '`Test\.php$`Di'))->getList(); + foreach ($testFileIterator as $file) { + if (strpos($file, 'AbstractMethodUnitTest.php') !== false) { + continue; + } + + include_once $file; + + $class = str_replace(__DIR__, '', $file); + $class = str_replace('.php', '', $class); + $class = str_replace('/', '\\', $class); + $class = 'PHP_CodeSniffer\Tests\Core'.$class; + + $suite->addTestSuite($class); + } + return $suite; }//end suite() diff --git a/tests/Core/File/FindEndOfStatementTest.php b/tests/Core/File/FindEndOfStatementTest.php index d3cd104f56..deacd9a49c 100644 --- a/tests/Core/File/FindEndOfStatementTest.php +++ b/tests/Core/File/FindEndOfStatementTest.php @@ -9,55 +9,11 @@ namespace PHP_CodeSniffer\Tests\Core\File; -use PHP_CodeSniffer\Config; -use PHP_CodeSniffer\Ruleset; -use PHP_CodeSniffer\Files\DummyFile; -use PHPUnit\Framework\TestCase; +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; -class FindEndOfStatementTest extends TestCase +class FindEndOfStatementTest extends AbstractMethodUnitTest { - /** - * The PHP_CodeSniffer_File object containing parsed contents of the test case file. - * - * @var \PHP_CodeSniffer\Files\File - */ - private $phpcsFile; - - - /** - * Initialize & tokenize \PHP_CodeSniffer\Files\File with code from the test case file. - * - * Methods used for these tests can be found in a test case file in the same - * directory and with the same name, using the .inc extension. - * - * @return void - */ - public function setUp() - { - $config = new Config(); - $config->standards = ['Generic']; - - $ruleset = new Ruleset($config); - - $pathToTestFile = dirname(__FILE__).'/'.basename(__FILE__, '.php').'.inc'; - $this->phpcsFile = new DummyFile(file_get_contents($pathToTestFile), $ruleset, $config); - $this->phpcsFile->process(); - - }//end setUp() - - - /** - * Clean up after finished test. - * - * @return void - */ - public function tearDown() - { - unset($this->phpcsFile); - - }//end tearDown() - /** * Test a simple assignment. @@ -66,10 +22,10 @@ public function tearDown() */ public function testSimpleAssignment() { - $start = ($this->phpcsFile->findNext(T_COMMENT, 0, null, false, '/* testSimpleAssignment */') + 2); - $found = $this->phpcsFile->findEndOfStatement($start); + $start = (self::$phpcsFile->findNext(T_COMMENT, 0, null, false, '/* testSimpleAssignment */') + 2); + $found = self::$phpcsFile->findEndOfStatement($start); - $tokens = $this->phpcsFile->getTokens(); + $tokens = self::$phpcsFile->getTokens(); $this->assertSame($tokens[($start + 5)], $tokens[$found]); }//end testSimpleAssignment() @@ -82,10 +38,10 @@ public function testSimpleAssignment() */ public function testControlStructure() { - $start = ($this->phpcsFile->findNext(T_COMMENT, 0, null, false, '/* testControlStructure */') + 2); - $found = $this->phpcsFile->findEndOfStatement($start); + $start = (self::$phpcsFile->findNext(T_COMMENT, 0, null, false, '/* testControlStructure */') + 2); + $found = self::$phpcsFile->findEndOfStatement($start); - $tokens = $this->phpcsFile->getTokens(); + $tokens = self::$phpcsFile->getTokens(); $this->assertSame($tokens[($start + 6)], $tokens[$found]); }//end testControlStructure() @@ -98,10 +54,10 @@ public function testControlStructure() */ public function testClosureAssignment() { - $start = ($this->phpcsFile->findNext(T_COMMENT, 0, null, false, '/* testClosureAssignment */') + 2); - $found = $this->phpcsFile->findEndOfStatement($start); + $start = (self::$phpcsFile->findNext(T_COMMENT, 0, null, false, '/* testClosureAssignment */') + 2); + $found = self::$phpcsFile->findEndOfStatement($start); - $tokens = $this->phpcsFile->getTokens(); + $tokens = self::$phpcsFile->getTokens(); $this->assertSame($tokens[($start + 13)], $tokens[$found]); }//end testClosureAssignment() @@ -115,24 +71,24 @@ public function testClosureAssignment() public function testHeredocFunctionArg() { // Find the end of the function. - $start = ($this->phpcsFile->findNext(T_COMMENT, 0, null, false, '/* testHeredocFunctionArg */') + 2); - $found = $this->phpcsFile->findEndOfStatement($start); + $start = (self::$phpcsFile->findNext(T_COMMENT, 0, null, false, '/* testHeredocFunctionArg */') + 2); + $found = self::$phpcsFile->findEndOfStatement($start); - $tokens = $this->phpcsFile->getTokens(); + $tokens = self::$phpcsFile->getTokens(); $this->assertSame($tokens[($start + 10)], $tokens[$found]); // Find the end of the heredoc. $start += 2; - $found = $this->phpcsFile->findEndOfStatement($start); + $found = self::$phpcsFile->findEndOfStatement($start); - $tokens = $this->phpcsFile->getTokens(); + $tokens = self::$phpcsFile->getTokens(); $this->assertSame($tokens[($start + 4)], $tokens[$found]); // Find the end of the last arg. $start = ($found + 2); - $found = $this->phpcsFile->findEndOfStatement($start); + $found = self::$phpcsFile->findEndOfStatement($start); - $tokens = $this->phpcsFile->getTokens(); + $tokens = self::$phpcsFile->getTokens(); $this->assertSame($tokens[$start], $tokens[$found]); }//end testHeredocFunctionArg() @@ -146,24 +102,24 @@ public function testHeredocFunctionArg() public function testSwitch() { // Find the end of the switch. - $start = ($this->phpcsFile->findNext(T_COMMENT, 0, null, false, '/* testSwitch */') + 2); - $found = $this->phpcsFile->findEndOfStatement($start); + $start = (self::$phpcsFile->findNext(T_COMMENT, 0, null, false, '/* testSwitch */') + 2); + $found = self::$phpcsFile->findEndOfStatement($start); - $tokens = $this->phpcsFile->getTokens(); + $tokens = self::$phpcsFile->getTokens(); $this->assertSame($tokens[($start + 28)], $tokens[$found]); // Find the end of the case. $start += 9; - $found = $this->phpcsFile->findEndOfStatement($start); + $found = self::$phpcsFile->findEndOfStatement($start); - $tokens = $this->phpcsFile->getTokens(); + $tokens = self::$phpcsFile->getTokens(); $this->assertSame($tokens[($start + 8)], $tokens[$found]); // Find the end of default case. $start += 11; - $found = $this->phpcsFile->findEndOfStatement($start); + $found = self::$phpcsFile->findEndOfStatement($start); - $tokens = $this->phpcsFile->getTokens(); + $tokens = self::$phpcsFile->getTokens(); $this->assertSame($tokens[($start + 6)], $tokens[$found]); }//end testSwitch() @@ -177,24 +133,24 @@ public function testSwitch() public function testStatementAsArrayValue() { // Test short array syntax. - $start = ($this->phpcsFile->findNext(T_COMMENT, 0, null, false, '/* testStatementAsArrayValue */') + 7); - $found = $this->phpcsFile->findEndOfStatement($start); + $start = (self::$phpcsFile->findNext(T_COMMENT, 0, null, false, '/* testStatementAsArrayValue */') + 7); + $found = self::$phpcsFile->findEndOfStatement($start); - $tokens = $this->phpcsFile->getTokens(); + $tokens = self::$phpcsFile->getTokens(); $this->assertSame($tokens[($start + 2)], $tokens[$found]); // Test long array syntax. $start += 12; - $found = $this->phpcsFile->findEndOfStatement($start); + $found = self::$phpcsFile->findEndOfStatement($start); - $tokens = $this->phpcsFile->getTokens(); + $tokens = self::$phpcsFile->getTokens(); $this->assertSame($tokens[($start + 2)], $tokens[$found]); // Test same statement outside of array. $start += 10; - $found = $this->phpcsFile->findEndOfStatement($start); + $found = self::$phpcsFile->findEndOfStatement($start); - $tokens = $this->phpcsFile->getTokens(); + $tokens = self::$phpcsFile->getTokens(); $this->assertSame($tokens[($start + 3)], $tokens[$found]); }//end testStatementAsArrayValue() diff --git a/tests/Core/File/FindExtendedClassNameTest.php b/tests/Core/File/FindExtendedClassNameTest.php deleted file mode 100644 index 5ebb4d9f56..0000000000 --- a/tests/Core/File/FindExtendedClassNameTest.php +++ /dev/null @@ -1,146 +0,0 @@ - - * @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\Tests\Core\File; - -use PHP_CodeSniffer\Config; -use PHP_CodeSniffer\Ruleset; -use PHP_CodeSniffer\Files\DummyFile; -use PHPUnit\Framework\TestCase; - -class FindExtendedClassNameTest extends TestCase -{ - - /** - * The PHP_CodeSniffer_File object containing parsed contents of the test case file. - * - * @var \PHP_CodeSniffer\Files\File - */ - private $phpcsFile; - - - /** - * Initialize & tokenize \PHP_CodeSniffer\Files\File with code from the test case file. - * - * Methods used for these tests can be found in a test case file in the same - * directory and with the same name, using the .inc extension. - * - * @return void - */ - public function setUp() - { - $config = new Config(); - $config->standards = ['Generic']; - - $ruleset = new Ruleset($config); - - $pathToTestFile = dirname(__FILE__).'/'.basename(__FILE__, '.php').'.inc'; - $this->phpcsFile = new DummyFile(file_get_contents($pathToTestFile), $ruleset, $config); - $this->phpcsFile->process(); - - }//end setUp() - - - /** - * Clean up after finished test. - * - * @return void - */ - public function tearDown() - { - unset($this->phpcsFile); - - }//end tearDown() - - - /** - * Test retrieving the name of the class being extended by another class - * (or interface). - * - * @param string $identifier Comment which precedes the test case. - * @param bool $expected Expected function output. - * - * @dataProvider dataExtendedClass - * - * @return void - */ - public function testFindExtendedClassName($identifier, $expected) - { - $start = ($this->phpcsFile->numTokens - 1); - $delim = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - $identifier - ); - $OOToken = $this->phpcsFile->findNext([T_CLASS, T_ANON_CLASS, T_INTERFACE], ($delim + 1)); - - $result = $this->phpcsFile->findExtendedClassName($OOToken); - $this->assertSame($expected, $result); - - }//end testFindExtendedClassName() - - - /** - * Data provider for the FindExtendedClassName test. - * - * @see testFindExtendedClassName() - * - * @return array - */ - public function dataExtendedClass() - { - return [ - [ - '/* testExtendedClass */', - 'testFECNClass', - ], - [ - '/* testNamespacedClass */', - '\PHP_CodeSniffer\Tests\Core\File\testFECNClass', - ], - [ - '/* testNonExtendedClass */', - false, - ], - [ - '/* testInterface */', - false, - ], - [ - '/* testInterfaceThatExtendsInterface */', - 'testFECNInterface', - ], - [ - '/* testInterfaceThatExtendsFQCNInterface */', - '\PHP_CodeSniffer\Tests\Core\File\testFECNInterface', - ], - [ - '/* testNestedExtendedClass */', - false, - ], - [ - '/* testNestedExtendedAnonClass */', - 'testFECNAnonClass', - ], - [ - '/* testClassThatExtendsAndImplements */', - 'testFECNClass', - ], - [ - '/* testClassThatImplementsAndExtends */', - 'testFECNClass', - ], - ]; - - }//end dataExtendedClass() - - -}//end class diff --git a/tests/Core/File/FindImplementedInterfaceNamesTest.php b/tests/Core/File/FindImplementedInterfaceNamesTest.php deleted file mode 100644 index 6a8ee623ae..0000000000 --- a/tests/Core/File/FindImplementedInterfaceNamesTest.php +++ /dev/null @@ -1,142 +0,0 @@ - - * @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\Tests\Core\File; - -use PHP_CodeSniffer\Config; -use PHP_CodeSniffer\Ruleset; -use PHP_CodeSniffer\Files\DummyFile; -use PHPUnit\Framework\TestCase; - -class FindImplementedInterfaceNamesTest extends TestCase -{ - - /** - * The \PHP_CodeSniffer\Files\File object containing parsed contents of the test case file. - * - * @var \PHP_CodeSniffer\Files\File - */ - private $phpcsFile; - - - /** - * Initialize & tokenize \PHP_CodeSniffer\Files\File with code from the test case file. - * - * Methods used for these tests can be found in a test case file in the same - * directory and with the same name, using the .inc extension. - * - * @return void - */ - public function setUp() - { - $config = new Config(); - $config->standards = ['Generic']; - - $ruleset = new Ruleset($config); - - $pathToTestFile = dirname(__FILE__).'/'.basename(__FILE__, '.php').'.inc'; - $this->phpcsFile = new DummyFile(file_get_contents($pathToTestFile), $ruleset, $config); - $this->phpcsFile->process(); - - }//end setUp() - - - /** - * Clean up after finished test. - * - * @return void - */ - public function tearDown() - { - unset($this->phpcsFile); - - }//end tearDown() - - - /** - * Test retrieving the name(s) of the interfaces being implemented by a class. - * - * @param string $identifier Comment which precedes the test case. - * @param bool $expected Expected function output. - * - * @dataProvider dataImplementedInterface - * - * @return void - */ - public function testFindImplementedInterfaceNames($identifier, $expected) - { - $start = ($this->phpcsFile->numTokens - 1); - $delim = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - $identifier - ); - $OOToken = $this->phpcsFile->findNext([T_CLASS, T_ANON_CLASS, T_INTERFACE], ($delim + 1)); - - $result = $this->phpcsFile->findImplementedInterfaceNames($OOToken); - $this->assertSame($expected, $result); - - }//end testFindImplementedInterfaceNames() - - - /** - * Data provider for the FindImplementedInterfaceNames test. - * - * @see testFindImplementedInterfaceNames() - * - * @return array - */ - public function dataImplementedInterface() - { - return [ - [ - '/* testImplementedClass */', - ['testFIINInterface'], - ], - [ - '/* testMultiImplementedClass */', - [ - 'testFIINInterface', - 'testFIINInterface2', - ], - ], - [ - '/* testNamespacedClass */', - ['\PHP_CodeSniffer\Tests\Core\File\testFIINInterface'], - ], - [ - '/* testNonImplementedClass */', - false, - ], - [ - '/* testInterface */', - false, - ], - [ - '/* testClassThatExtendsAndImplements */', - [ - 'InterfaceA', - '\NameSpaced\Cat\InterfaceB', - ], - ], - [ - '/* testClassThatImplementsAndExtends */', - [ - '\InterfaceA', - 'InterfaceB', - ], - ], - ]; - - }//end dataImplementedInterface() - - -}//end class diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc deleted file mode 100644 index a0ae183d42..0000000000 --- a/tests/Core/File/GetMethodParametersTest.inc +++ /dev/null @@ -1,29 +0,0 @@ - - * @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\Tests\Core\File; - -use PHP_CodeSniffer\Config; -use PHP_CodeSniffer\Ruleset; -use PHP_CodeSniffer\Files\DummyFile; -use PHPUnit\Framework\TestCase; - -class GetMethodParametersTest extends TestCase -{ - - /** - * The PHP_CodeSniffer_File object containing parsed contents of the test case file. - * - * @var \PHP_CodeSniffer\Files\File - */ - private $phpcsFile; - - - /** - * Initialize & tokenize PHP_CodeSniffer_File with code from the test case file. - * - * Methods used for these tests can be found in a test case file in the same - * directory and with the same name, using the .inc extension. - * - * @return void - */ - public function setUp() - { - $config = new Config(); - $config->standards = ['Generic']; - - $ruleset = new Ruleset($config); - - $pathToTestFile = dirname(__FILE__).'/'.basename(__FILE__, '.php').'.inc'; - $this->phpcsFile = new DummyFile(file_get_contents($pathToTestFile), $ruleset, $config); - $this->phpcsFile->process(); - - }//end setUp() - - - /** - * Clean up after finished test. - * - * @return void - */ - public function tearDown() - { - unset($this->phpcsFile); - - }//end tearDown() - - - /** - * Verify pass-by-reference parsing. - * - * @return void - */ - public function testPassByReference() - { - $expected = []; - $expected[0] = [ - 'name' => '$var', - 'content' => '&$var', - 'pass_by_reference' => true, - 'variable_length' => false, - 'type_hint' => '', - 'nullable_type' => false, - ]; - - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testPassByReference */' - ); - - $found = $this->phpcsFile->getMethodParameters(($function + 2)); - unset($found[0]['token']); - unset($found[0]['type_hint_token']); - $this->assertSame($expected, $found); - - }//end testPassByReference() - - - /** - * Verify array hint parsing. - * - * @return void - */ - public function testArrayHint() - { - $expected = []; - $expected[0] = [ - 'name' => '$var', - 'content' => 'array $var', - 'pass_by_reference' => false, - 'variable_length' => false, - 'type_hint' => 'array', - 'nullable_type' => false, - ]; - - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testArrayHint */' - ); - - $found = $this->phpcsFile->getMethodParameters(($function + 2)); - unset($found[0]['token']); - unset($found[0]['type_hint_token']); - $this->assertSame($expected, $found); - - }//end testArrayHint() - - - /** - * Verify type hint parsing. - * - * @return void - */ - public function testTypeHint() - { - $expected = []; - $expected[0] = [ - 'name' => '$var1', - 'content' => 'foo $var1', - 'pass_by_reference' => false, - 'variable_length' => false, - 'type_hint' => 'foo', - 'nullable_type' => false, - ]; - - $expected[1] = [ - 'name' => '$var2', - 'content' => 'bar $var2', - 'pass_by_reference' => false, - 'variable_length' => false, - 'type_hint' => 'bar', - 'nullable_type' => false, - ]; - - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testTypeHint */' - ); - - $found = $this->phpcsFile->getMethodParameters(($function + 2)); - unset($found[0]['token']); - unset($found[1]['token']); - unset($found[0]['type_hint_token']); - unset($found[1]['type_hint_token']); - $this->assertSame($expected, $found); - - }//end testTypeHint() - - - /** - * Verify self type hint parsing. - * - * @return void - */ - public function testSelfTypeHint() - { - $expected = []; - $expected[0] = [ - 'name' => '$var', - 'content' => 'self $var', - 'pass_by_reference' => false, - 'variable_length' => false, - 'type_hint' => 'self', - 'nullable_type' => false, - ]; - - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testSelfTypeHint */' - ); - - $found = $this->phpcsFile->getMethodParameters(($function + 2)); - unset($found[0]['token']); - unset($found[0]['type_hint_token']); - $this->assertSame($expected, $found); - - }//end testSelfTypeHint() - - - /** - * Verify nullable type hint parsing. - * - * @return void - */ - public function testNullableTypeHint() - { - $expected = []; - $expected[0] = [ - 'name' => '$var1', - 'content' => '?int $var1', - 'pass_by_reference' => false, - 'variable_length' => false, - 'type_hint' => '?int', - 'nullable_type' => true, - ]; - - $expected[1] = [ - 'name' => '$var2', - 'content' => '?\bar $var2', - 'pass_by_reference' => false, - 'variable_length' => false, - 'type_hint' => '?\bar', - 'nullable_type' => true, - ]; - - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testNullableTypeHint */' - ); - - $found = $this->phpcsFile->getMethodParameters(($function + 2)); - unset($found[0]['token']); - unset($found[1]['token']); - unset($found[0]['type_hint_token']); - unset($found[1]['type_hint_token']); - $this->assertSame($expected, $found); - - }//end testNullableTypeHint() - - - /** - * Verify variable. - * - * @return void - */ - public function testVariable() - { - $expected = []; - $expected[0] = [ - 'name' => '$var', - 'content' => '$var', - 'pass_by_reference' => false, - 'variable_length' => false, - 'type_hint' => '', - 'nullable_type' => false, - ]; - - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testVariable */' - ); - - $found = $this->phpcsFile->getMethodParameters(($function + 2)); - unset($found[0]['token']); - unset($found[0]['type_hint_token']); - $this->assertSame($expected, $found); - - }//end testVariable() - - - /** - * Verify default value parsing with a single function param. - * - * @return void - */ - public function testSingleDefaultValue() - { - $expected = []; - $expected[0] = [ - 'name' => '$var1', - 'content' => '$var1=self::CONSTANT', - 'default' => 'self::CONSTANT', - 'pass_by_reference' => false, - 'variable_length' => false, - 'type_hint' => '', - 'nullable_type' => false, - ]; - - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testSingleDefaultValue */' - ); - - $found = $this->phpcsFile->getMethodParameters(($function + 2)); - unset($found[0]['token']); - unset($found[0]['type_hint_token']); - $this->assertSame($expected, $found); - - }//end testSingleDefaultValue() - - - /** - * Verify default value parsing. - * - * @return void - */ - public function testDefaultValues() - { - $expected = []; - $expected[0] = [ - 'name' => '$var1', - 'content' => '$var1=1', - 'default' => '1', - 'pass_by_reference' => false, - 'variable_length' => false, - 'type_hint' => '', - 'nullable_type' => false, - ]; - $expected[1] = [ - 'name' => '$var2', - 'content' => "\$var2='value'", - 'default' => "'value'", - 'pass_by_reference' => false, - 'variable_length' => false, - 'type_hint' => '', - 'nullable_type' => false, - ]; - - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testDefaultValues */' - ); - - $found = $this->phpcsFile->getMethodParameters(($function + 2)); - unset($found[0]['token']); - unset($found[1]['token']); - unset($found[0]['type_hint_token']); - unset($found[1]['type_hint_token']); - $this->assertSame($expected, $found); - - }//end testDefaultValues() - - - /** - * Verify "bitwise and" in default value !== pass-by-reference. - * - * @return void - */ - public function testBitwiseAndConstantExpressionDefaultValue() - { - $expected = []; - $expected[0] = [ - 'name' => '$a', - 'content' => '$a = 10 & 20', - 'default' => '10 & 20', - 'pass_by_reference' => false, - 'variable_length' => false, - 'type_hint' => '', - 'nullable_type' => false, - ]; - - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testBitwiseAndConstantExpressionDefaultValue */' - ); - - $found = $this->phpcsFile->getMethodParameters(($function + 2)); - unset($found[0]['token']); - unset($found[0]['type_hint_token']); - $this->assertSame($expected, $found); - - }//end testBitwiseAndConstantExpressionDefaultValue() - - -}//end class diff --git a/tests/Core/File/IsReferenceTest.php b/tests/Core/File/IsReferenceTest.php deleted file mode 100644 index a72546a31f..0000000000 --- a/tests/Core/File/IsReferenceTest.php +++ /dev/null @@ -1,285 +0,0 @@ - - * @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\Tests\Core\File; - -use PHP_CodeSniffer\Config; -use PHP_CodeSniffer\Ruleset; -use PHP_CodeSniffer\Files\DummyFile; -use PHPUnit\Framework\TestCase; - -class IsReferenceTest extends TestCase -{ - - /** - * The PHP_CodeSniffer_File object containing parsed contents of the test case file. - * - * @var \PHP_CodeSniffer\Files\File - */ - private $phpcsFile; - - - /** - * Initialize & tokenize \PHP_CodeSniffer\Files\File with code from the test case file. - * - * Methods used for these tests can be found in a test case file in the same - * directory and with the same name, using the .inc extension. - * - * @return void - */ - public function setUp() - { - $config = new Config(); - $config->standards = ['Generic']; - - $ruleset = new Ruleset($config); - - $pathToTestFile = dirname(__FILE__).'/'.basename(__FILE__, '.php').'.inc'; - $this->phpcsFile = new DummyFile(file_get_contents($pathToTestFile), $ruleset, $config); - $this->phpcsFile->process(); - - }//end setUp() - - - /** - * Clean up after finished test. - * - * @return void - */ - public function tearDown() - { - unset($this->phpcsFile); - - }//end tearDown() - - - /** - * Test a class that extends another. - * - * @param string $identifier Comment which precedes the test case. - * @param bool $expected Expected function output. - * - * @dataProvider dataIsReference - * - * @return void - */ - public function testIsReference($identifier, $expected) - { - $start = ($this->phpcsFile->numTokens - 1); - $delim = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - $identifier - ); - $bitwiseAnd = $this->phpcsFile->findNext(T_BITWISE_AND, ($delim + 1)); - - $result = $this->phpcsFile->isReference($bitwiseAnd); - $this->assertSame($expected, $result); - - }//end testIsReference() - - - /** - * Data provider for the IsReference test. - * - * @see testIsReference() - * - * @return array - */ - public function dataIsReference() - { - return [ - [ - '/* bitwiseAndA */', - false, - ], - [ - '/* bitwiseAndB */', - false, - ], - [ - '/* bitwiseAndC */', - false, - ], - [ - '/* bitwiseAndD */', - false, - ], - [ - '/* bitwiseAndE */', - false, - ], - [ - '/* bitwiseAndF */', - false, - ], - [ - '/* bitwiseAndG */', - false, - ], - [ - '/* bitwiseAndH */', - false, - ], - [ - '/* bitwiseAndI */', - false, - ], - [ - '/* functionReturnByReference */', - true, - ], - [ - '/* functionPassByReferenceA */', - true, - ], - [ - '/* functionPassByReferenceB */', - true, - ], - [ - '/* functionPassByReferenceC */', - true, - ], - [ - '/* functionPassByReferenceD */', - true, - ], - [ - '/* functionPassByReferenceE */', - true, - ], - [ - '/* functionPassByReferenceF */', - true, - ], - [ - '/* functionPassByReferenceG */', - true, - ], - [ - '/* foreachValueByReference */', - true, - ], - [ - '/* foreachKeyByReference */', - true, - ], - [ - '/* arrayValueByReferenceA */', - true, - ], - [ - '/* arrayValueByReferenceB */', - true, - ], - [ - '/* arrayValueByReferenceC */', - true, - ], - [ - '/* arrayValueByReferenceD */', - true, - ], - [ - '/* arrayValueByReferenceE */', - true, - ], - [ - '/* arrayValueByReferenceF */', - true, - ], - [ - '/* arrayValueByReferenceG */', - true, - ], - [ - '/* arrayValueByReferenceH */', - true, - ], - [ - '/* assignByReferenceA */', - true, - ], - [ - '/* assignByReferenceB */', - true, - ], - [ - '/* assignByReferenceC */', - true, - ], - [ - '/* assignByReferenceD */', - true, - ], - [ - '/* assignByReferenceE */', - true, - ], - [ - '/* passByReferenceA */', - true, - ], - [ - '/* passByReferenceB */', - true, - ], - [ - '/* passByReferenceC */', - true, - ], - [ - '/* passByReferenceD */', - true, - ], - [ - '/* passByReferenceE */', - true, - ], - [ - '/* passByReferenceF */', - true, - ], - [ - '/* passByReferenceG */', - true, - ], - [ - '/* passByReferenceH */', - true, - ], - [ - '/* passByReferenceI */', - true, - ], - [ - '/* passByReferenceJ */', - true, - ], - [ - '/* newByReferenceA */', - true, - ], - [ - '/* newByReferenceB */', - true, - ], - [ - '/* useByReference */', - true, - ], - ]; - - }//end dataIsReference() - - -}//end class diff --git a/tests/Core/IsCamelCapsTest.php b/tests/Core/IsCamelCapsTest.php deleted file mode 100644 index b60d524b03..0000000000 --- a/tests/Core/IsCamelCapsTest.php +++ /dev/null @@ -1,135 +0,0 @@ - - * @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\Tests\Core; - -use PHP_CodeSniffer\Util\Common; -use PHPUnit\Framework\TestCase; - -class IsCamelCapsTest extends TestCase -{ - - - /** - * Test valid public function/method names. - * - * @return void - */ - public function testValidNotClassFormatPublic() - { - $this->assertTrue(Common::isCamelCaps('thisIsCamelCaps', false, true, true)); - $this->assertTrue(Common::isCamelCaps('thisISCamelCaps', false, true, false)); - - }//end testValidNotClassFormatPublic() - - - /** - * Test invalid public function/method names. - * - * @return void - */ - public function testInvalidNotClassFormatPublic() - { - $this->assertFalse(Common::isCamelCaps('_thisIsCamelCaps', false, true, true)); - $this->assertFalse(Common::isCamelCaps('thisISCamelCaps', false, true, true)); - $this->assertFalse(Common::isCamelCaps('ThisIsCamelCaps', false, true, true)); - - $this->assertFalse(Common::isCamelCaps('3thisIsCamelCaps', false, true, true)); - $this->assertFalse(Common::isCamelCaps('*thisIsCamelCaps', false, true, true)); - $this->assertFalse(Common::isCamelCaps('-thisIsCamelCaps', false, true, true)); - - $this->assertFalse(Common::isCamelCaps('this*IsCamelCaps', false, true, true)); - $this->assertFalse(Common::isCamelCaps('this-IsCamelCaps', false, true, true)); - $this->assertFalse(Common::isCamelCaps('this_IsCamelCaps', false, true, true)); - $this->assertFalse(Common::isCamelCaps('this_is_camel_caps', false, true, true)); - - }//end testInvalidNotClassFormatPublic() - - - /** - * Test valid private method names. - * - * @return void - */ - public function testValidNotClassFormatPrivate() - { - $this->assertTrue(Common::isCamelCaps('_thisIsCamelCaps', false, false, true)); - $this->assertTrue(Common::isCamelCaps('_thisISCamelCaps', false, false, false)); - $this->assertTrue(Common::isCamelCaps('_i18N', false, false, true)); - $this->assertTrue(Common::isCamelCaps('_i18n', false, false, true)); - - }//end testValidNotClassFormatPrivate() - - - /** - * Test invalid private method names. - * - * @return void - */ - public function testInvalidNotClassFormatPrivate() - { - $this->assertFalse(Common::isCamelCaps('thisIsCamelCaps', false, false, true)); - $this->assertFalse(Common::isCamelCaps('_thisISCamelCaps', false, false, true)); - $this->assertFalse(Common::isCamelCaps('_ThisIsCamelCaps', false, false, true)); - $this->assertFalse(Common::isCamelCaps('__thisIsCamelCaps', false, false, true)); - $this->assertFalse(Common::isCamelCaps('__thisISCamelCaps', false, false, false)); - - $this->assertFalse(Common::isCamelCaps('3thisIsCamelCaps', false, false, true)); - $this->assertFalse(Common::isCamelCaps('*thisIsCamelCaps', false, false, true)); - $this->assertFalse(Common::isCamelCaps('-thisIsCamelCaps', false, false, true)); - $this->assertFalse(Common::isCamelCaps('_this_is_camel_caps', false, false, true)); - - }//end testInvalidNotClassFormatPrivate() - - - /** - * Test valid class names. - * - * @return void - */ - public function testValidClassFormatPublic() - { - $this->assertTrue(Common::isCamelCaps('ThisIsCamelCaps', true, true, true)); - $this->assertTrue(Common::isCamelCaps('ThisISCamelCaps', true, true, false)); - $this->assertTrue(Common::isCamelCaps('This3IsCamelCaps', true, true, false)); - - }//end testValidClassFormatPublic() - - - /** - * Test invalid class names. - * - * @return void - */ - public function testInvalidClassFormat() - { - $this->assertFalse(Common::isCamelCaps('thisIsCamelCaps', true)); - $this->assertFalse(Common::isCamelCaps('This-IsCamelCaps', true)); - $this->assertFalse(Common::isCamelCaps('This_Is_Camel_Caps', true)); - - }//end testInvalidClassFormat() - - - /** - * Test invalid class names with the private flag set. - * - * Note that the private flag is ignored if the class format - * flag is set, so these names are all invalid. - * - * @return void - */ - public function testInvalidClassFormatPrivate() - { - $this->assertFalse(Common::isCamelCaps('_ThisIsCamelCaps', true, true)); - $this->assertFalse(Common::isCamelCaps('_ThisIsCamelCaps', true, false)); - - }//end testInvalidClassFormatPrivate() - - -}//end class diff --git a/tests/Core/Util/Sniffs/Comments/FindConstantCommentTest.inc b/tests/Core/Util/Sniffs/Comments/FindConstantCommentTest.inc new file mode 100644 index 0000000000..b5d3b3723c --- /dev/null +++ b/tests/Core/Util/Sniffs/Comments/FindConstantCommentTest.inc @@ -0,0 +1,86 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Comments; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Comments; +use PHP_CodeSniffer\Util\Tokens; + +class FindConstantCommentTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a token which is not supported is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_CONST + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findConstantComment + * + * @return void + */ + public function testNotAConstTokenException() + { + $stackPtr = self::$phpcsFile->findNext( + T_STRING, + 0, + null, + false, + 'GLOBAL_CONST_NO_DOCBLOCK' + ); + $result = Comments::findConstantComment(self::$phpcsFile, $stackPtr); + + }//end testNotAConstTokenException() + + + /** + * Test correctly identifying a comment above a T_CONST token. + * + * @param string $constName The name of the constant for which to find the comment. + * @param int|bool $expected The expected function return value. + * + * @dataProvider dataFindConstantComment + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findConstantComment + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findCommentAbove + * + * @return void + */ + public function testFindConstantComment($constName, $expected) + { + $stackPtr = $this->getTargetToken($constName, T_CONST); + + // End token position values are set as offsets in relation to the target token. + // Change these to exact positions based on the retrieved stackPtr. + if ($expected !== false) { + $expected += $stackPtr; + } + + $result = Comments::findConstantComment(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testFindConstantComment() + + + /** + * Data provider. + * + * @see testFindConstantComment() + * + * @return array + */ + public function dataFindConstantComment() + { + return [ + [ + 'GLOBAL_CONST_NO_DOCBLOCK', + false, + ], + [ + 'GLOBAL_CONST_UNRELATED_COMMENT', + false, + ], + [ + 'GLOBAL_CONST_HAS_COMMENT_1', + -1, + ], + [ + 'GLOBAL_CONST_HAS_COMMENT_2', + -1, + ], + [ + 'GLOBAL_CONST_HAS_COMMENT_3', + -2, + ], + [ + 'GLOBAL_CONST_HAS_DOCBLOCK', + -2, + ], + [ + 'CLASS_CONST_NO_DOCBLOCK', + false, + ], + [ + 'CLASS_CONST_UNRELATED_COMMENT', + false, + ], + [ + 'CLASS_CONST_HAS_COMMENT_1', + -2, + ], + [ + 'CLASS_CONST_HAS_COMMENT_2', + -2, + ], + [ + 'CLASS_CONST_HAS_COMMENT_3', + -3, + ], + [ + 'CLASS_CONST_HAS_COMMENT_4', + -3, + ], + [ + 'CLASS_CONST_HAS_DOCBLOCK_1', + -3, + ], + [ + 'CLASS_CONST_HAS_DOCBLOCK_2', + -7, + ], + [ + 'CLASS_CONST_HAS_DOCBLOCK_3', + -7, + ], + [ + 'CLASS_CONST_HAS_DOCBLOCK_4', + -7, + ], + [ + 'CLASS_CONST_HAS_DOCBLOCK_5', + -14, + ], + ]; + + }//end dataFindConstantComment() + + + /** + * Helper method. Get the token pointer for a target token based on a specific constant name. + * + * Overloading the parent method as we can't look for marker comments for these methods as they + * would confuse the tests. + * + * @param string $constName The constant name to look for. + * @param int|string|array $tokenType The type of token(s) to look for. + * @param null $notUsed Parameter not used in this implementation of the method. + * + * @return int + */ + public function getTargetToken($constName, $tokenType, $notUsed=null) + { + $tokens = self::$phpcsFile->getTokens(); + $namePtr = self::$phpcsFile->findPrevious( + T_STRING, + (self::$phpcsFile->numTokens - 1), + null, + false, + $constName + ); + $target = self::$phpcsFile->findPrevious(Tokens::$emptyTokens, ($namePtr - 1), null, true); + + if ($target === false || $tokens[$target]['code'] !== $tokenType) { + $this->assertFalse(true, 'Failed to find test target token for '.$constName); + } + + return $target; + + }//end getTargetToken() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Comments/FindEndOfCommentTest.inc b/tests/Core/Util/Sniffs/Comments/FindEndOfCommentTest.inc new file mode 100644 index 0000000000..63062958a2 --- /dev/null +++ b/tests/Core/Util/Sniffs/Comments/FindEndOfCommentTest.inc @@ -0,0 +1,187 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Comments; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Comments; + +class FindEndOfCommentTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a token which is not supported is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_COMMENT or T_DOC_COMMENT_OPEN_TAG + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findEndOfComment + * + * @return void + */ + public function testNotACommentException() + { + $stackPtr = self::$phpcsFile->findNext(T_ECHO, 0); + $result = Comments::findEndOfComment(self::$phpcsFile, $stackPtr); + + }//end testNotACommentException() + + + /** + * Test receiving an expected exception when an inline comment token which is + * not the *start* of the inline comment is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must point to the start of a comment + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findEndOfComment + * + * @return void + */ + public function testNotStartOfAnInlineCommentException() + { + $stackPtr = self::$phpcsFile->findNext( + T_COMMENT, + 0, + null, + false, + '//line 2 +' + ); + $result = Comments::findEndOfComment(self::$phpcsFile, $stackPtr); + + }//end testNotStartOfAnInlineCommentException() + + + /** + * Test receiving an expected exception when an inline comment token which is + * not the *start* of a block comment is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must point to the start of a comment + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findEndOfComment + * + * @return void + */ + public function testNotStartOfABlockCommentException() + { + $stackPtr = self::$phpcsFile->findPrevious( + T_COMMENT, + (self::$phpcsFile->numTokens - 1), + null, + false, + ' * line 2 +' + ); + $result = Comments::findEndOfComment(self::$phpcsFile, $stackPtr); + + }//end testNotStartOfABlockCommentException() + + + /** + * Test receiving an expected exception when an inline comment token which is + * not the *start* of a block comment is passed and the comment contents starts with //. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must point to the start of a comment + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findEndOfComment + * + * @return void + */ + public function testMixedInlineBlockCommentException() + { + $stackPtr = $this->getTargetToken( + "'testBlockCommentSequence_13'", + null, + ' // line 2 +' + ); + $result = Comments::findEndOfComment(self::$phpcsFile, $stackPtr); + + }//end testMixedInlineBlockCommentException() + + + /** + * Test correctly identifying the end of an inline or block comment. + * + * @param string $delimiter The text string delimiter to use to find the + * start of the comment. + * @param int $expected The expected function return value as an offset + * from the start of the comment. + * @param string|null $tokenContent Optional. Specific content to get the correct + * comment token. + * + * @dataProvider dataFindEndOfComment + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findEndOfComment + * + * @return void + */ + public function testFindEndOfComment($delimiter, $expected, $tokenContent=null) + { + $stackPtr = $this->getTargetToken($delimiter, null, $tokenContent); + + // Expected end token position values are set as offsets in relation to + // the target token. + // Change these to exact positions based on the retrieved stackPtr. + $expected += $stackPtr; + + $result = Comments::findEndOfComment(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testFindEndOfComment() + + + /** + * Data provider. + * + * @see testFindEndOfComment() + * + * @return array + */ + public function dataFindEndOfComment() + { + return [ + // Docblock. + [ + "'testDocblockSequence'", + 8, + ], + + // Inline comments. + [ + "'testInlineCommentSequence_1'", + 0, + ], + [ + "'testInlineCommentSequence_2'", + 6, + ], + [ + "'testInlineCommentSequence_3'", + 4, + ], + [ + "'testInlineCommentSequence_4'", + 4, + ], + [ + "'testInlineCommentSequence_5'", + 4, + ], + [ + "'testInlineCommentSequence_6'", + 4, + ], + [ + "'testInlineCommentSequence_7'", + 0, + ], + [ + "'testInlineCommentSequence_7'", + 0, + '// This starts a new inline comment +', + ], + [ + "'testInlineCommentSequence_8'", + 0, + '/* Stand-alone block comment.*/', + ], + [ + "'testInlineCommentSequence_8'", + 0, + '// Stand-alone inline trailing comment. +', + ], + [ + "'testInlineCommentSequence_8'", + 0, + '// This starts a new inline comment +', + ], + [ + "'testInlineCommentSequence_9'", + 0, + '// Stand alone inline comment. +', + ], + [ + "'testInlineCommentSequence_9'", + 0, + '// Another stand alone inline comment. +', + ], + [ + "'testInlineCommentSequence_10'", + 2, + ], + [ + "'testInlineCommentSequence_10'", + 0, + '# Perl-style comment not belonging to the sequence. +', + ], + [ + "'testInlineCommentSequence_11'", + 2, + ], + [ + "'testInlineCommentSequence_11'", + 0, + '// Slash-style comment not belonging to the sequence. +', + ], + [ + "'testInlineCommentSequence_12'", + 4, + ], + + // Block comments. + [ + "'testBlockCommentSequence_1'", + 0, + ], + [ + "'testBlockCommentSequence_2'", + 0, + ], + [ + "'testBlockCommentSequence_3'", + 2, + ], + [ + "'testBlockCommentSequence_4'", + 4, + ], + [ + "'testBlockCommentSequence_5'", + 4, + ], + [ + "'testBlockCommentSequence_6'", + 5, + ], + [ + "'testBlockCommentSequence_7'", + 4, + ], + [ + "'testBlockCommentSequence_8'", + 3, + ], + [ + "'testBlockCommentSequence_9'", + 3, + ], + [ + "'testBlockCommentSequence_10'", + 2, + ], + [ + "'testBlockCommentSequence_11'", + 2, + ], + [ + "'testBlockCommentSequence_12'", + 0, + ], + [ + "'testBlockCommentSequence_13'", + 4, + ], + [ + "'testBlockCommentSequence_last'", + 3, + ], + ]; + + }//end dataFindEndOfComment() + + + /** + * Helper method. Get the token pointer for a target token based on a specific text string. + * + * Overloading the parent method as we can't look for marker comments for these methods as they + * would confuse the tests. + * + * @param string $delimiter The text string content to look for. + * @param null $notUsed Parameter not used in this implementation of the method. + * @param string|null $tokenContent Optional. Specific content to get the correct + * comment token. + * + * @return int + */ + public function getTargetToken($delimiter, $notUsed, $tokenContent=null) + { + $tokens = self::$phpcsFile->getTokens(); + $delimiterPtr = self::$phpcsFile->findPrevious( + T_CONSTANT_ENCAPSED_STRING, + (self::$phpcsFile->numTokens - 1), + null, + false, + $delimiter + ); + + $target = self::$phpcsFile->findNext( + [ + T_COMMENT, + T_DOC_COMMENT_OPEN_TAG, + ], + ($delimiterPtr + 1), + null, + false, + $tokenContent + ); + + if ($target === false) { + $msg = 'Failed to find test target token for '.$delimiter; + if (isset($tokenContent) === true) { + $msg .= ' with content '.$tokenContent; + } + + $this->assertFalse(true, $msg); + } + + return $target; + + }//end getTargetToken() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Comments/FindFunctionCommentTest.inc b/tests/Core/Util/Sniffs/Comments/FindFunctionCommentTest.inc new file mode 100644 index 0000000000..2c0111052a --- /dev/null +++ b/tests/Core/Util/Sniffs/Comments/FindFunctionCommentTest.inc @@ -0,0 +1,80 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Comments; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Comments; +use PHP_CodeSniffer\Util\Tokens; + +class FindFunctionCommentTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a token which is not supported is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_FUNCTION + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findFunctionComment + * + * @return void + */ + public function testNotAFunctionTokenException() + { + $stackPtr = self::$phpcsFile->findNext( + T_STRING, + 0, + null, + false, + 'functionNoDocblock' + ); + $result = Comments::findFunctionComment(self::$phpcsFile, $stackPtr); + + }//end testNotAFunctionTokenException() + + + /** + * Test correctly identifying a comment above a T_FUNCTION token. + * + * @param string $functionName The name of the function for which to find the comment. + * @param int|bool $expected The expected function return value. + * + * @dataProvider dataFindFunctionComment + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findFunctionComment + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findCommentAbove + * + * @return void + */ + public function testFindFunctionComment($functionName, $expected) + { + $stackPtr = $this->getTargetToken($functionName, T_FUNCTION); + + // End token position values are set as offsets in relation to the target token. + // Change these to exact positions based on the retrieved stackPtr. + if ($expected !== false) { + $expected += $stackPtr; + } + + $result = Comments::findFunctionComment(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testFindFunctionComment() + + + /** + * Data provider. + * + * @see testFindFunctionComment() + * + * @return array + */ + public function dataFindFunctionComment() + { + return [ + [ + 'functionNoDocblock', + false, + ], + [ + 'functionUnrelatedComment', + false, + ], + [ + 'functionHasComment_1', + -1, + ], + [ + 'functionHasComment_2', + -1, + ], + [ + 'functionHasComment_3', + -2, + ], + [ + 'functionHasDocblock', + -2, + ], + [ + 'methodNoDocblock_1', + false, + ], + [ + 'methodUnrelatedComment', + false, + ], + [ + 'methodHasComment_1', + -4, + ], + [ + 'methodHasComment_2', + -6, + ], + [ + 'methodHasComment_3', + -5, + ], + [ + 'methodHasComment_4', + -7, + ], + [ + 'methodHasDocblock_1', + -5, + ], + [ + 'methodHasDocblock_2', + -7, + ], + [ + 'methodHasDocblock_3', + -20, + ], + [ + 'methodHasDocblock_4', + -14, + ], + [ + 'methodNoDocblock_2', + false, + ], + ]; + + }//end dataFindFunctionComment() + + + /** + * Helper method. Get the token pointer for a target token based on a specific function name. + * + * Overloading the parent method as we can't look for marker comments for these methods as they + * would confuse the tests. + * + * @param string $functionName The function name to look for. + * @param int|string|array $tokenType The type of token(s) to look for. + * @param null $notUsed Parameter not used in this implementation of the method. + * + * @return int + */ + public function getTargetToken($functionName, $tokenType, $notUsed=null) + { + $tokens = self::$phpcsFile->getTokens(); + $namePtr = self::$phpcsFile->findPrevious( + T_STRING, + (self::$phpcsFile->numTokens - 1), + null, + false, + $functionName + ); + $target = self::$phpcsFile->findPrevious(Tokens::$emptyTokens, ($namePtr - 1), null, true); + + if ($target === false || $tokens[$target]['code'] !== $tokenType) { + $this->assertFalse(true, 'Failed to find test target token for '.$functionName); + } + + return $target; + + }//end getTargetToken() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Comments/FindOOStructureCommentTest.inc b/tests/Core/Util/Sniffs/Comments/FindOOStructureCommentTest.inc new file mode 100644 index 0000000000..d60d98c11d --- /dev/null +++ b/tests/Core/Util/Sniffs/Comments/FindOOStructureCommentTest.inc @@ -0,0 +1,68 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Comments; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Comments; +use PHP_CodeSniffer\Util\Tokens; + +class FindOOStructureCommentTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a token which is not supported is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be a class, interface or trait token + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findOOStructureComment + * + * @return void + */ + public function testNotAnOOStructureTokenException() + { + $stackPtr = self::$phpcsFile->findNext(T_ANON_CLASS, 0); + $result = Comments::findOOStructureComment(self::$phpcsFile, $stackPtr); + + }//end testNotAnOOStructureTokenException() + + + /** + * Test correctly identifying a comment above an OO structure token. + * + * @param string $OOName The name of the OO structure for which to find the comment. + * @param int|bool $expected The expected function return value. + * @param int|string|array $tokenType Optional. The type of token(s) to look for. + * + * @dataProvider dataFindOOStructureComment + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findOOStructureComment + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findCommentAbove + * + * @return void + */ + public function testFindOOStructureComment($OOName, $expected, $tokenType=T_CLASS) + { + $stackPtr = $this->getTargetToken($OOName, $tokenType); + + // End token position values are set as offsets in relation to the target token. + // Change these to exact positions based on the retrieved stackPtr. + if ($expected !== false) { + $expected += $stackPtr; + } + + $result = Comments::findOOStructureComment(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testFindOOStructureComment() + + + /** + * Data provider. + * + * @see testFindOOStructureComment() + * + * @return array + */ + public function dataFindOOStructureComment() + { + return [ + [ + 'ClassNoDocblock', + false, + ], + [ + 'ClassNoDocblockTrailingCommentPrevious', + false, + ], + [ + 'ClassComment_1', + -1, + ], + [ + 'ClassComment_2', + -2, + ], + [ + 'ClassDocblock_1', + -2, + ], + [ + 'ClassDocblock_2', + -7, + ], + [ + 'ClassDocblock_3', + -11, + ], + [ + 'ClassNoDocblockWithInterlacedComments', + false, + ], + [ + 'InterfaceNoDocblock', + false, + T_INTERFACE, + ], + [ + 'InterfaceComment', + -2, + T_INTERFACE, + ], + [ + 'InterfaceDocblock', + -2, + T_INTERFACE, + ], + [ + 'TraitNoDocblock', + false, + T_TRAIT, + ], + [ + 'TraitComment', + -1, + T_TRAIT, + ], + [ + 'TraitDocblock', + -2, + T_TRAIT, + ], + [ + 'TraitParseError', + false, + T_TRAIT, + ], + ]; + + }//end dataFindOOStructureComment() + + + /** + * Helper method. Get the token pointer for a target token based on a specific function name. + * + * Overloading the parent method as we can't look for marker comments for these methods as they + * would confuse the tests. + * + * @param string $OOName The OO Structure name to look for. + * @param int|string|array $tokenType The type of token(s) to look for. + * @param null $notUsed Parameter not used in this implementation of the method. + * + * @return int + */ + public function getTargetToken($OOName, $tokenType, $notUsed=null) + { + $tokens = self::$phpcsFile->getTokens(); + $namePtr = self::$phpcsFile->findPrevious( + T_STRING, + (self::$phpcsFile->numTokens - 1), + null, + false, + $OOName + ); + $target = self::$phpcsFile->findPrevious(Tokens::$emptyTokens, ($namePtr - 1), null, true); + + if ($target === false || $tokens[$target]['code'] !== $tokenType) { + $this->assertFalse(true, 'Failed to find test target token for '.$OOName); + } + + return $target; + + }//end getTargetToken() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Comments/FindPropertyCommentTest.inc b/tests/Core/Util/Sniffs/Comments/FindPropertyCommentTest.inc new file mode 100644 index 0000000000..918740b294 --- /dev/null +++ b/tests/Core/Util/Sniffs/Comments/FindPropertyCommentTest.inc @@ -0,0 +1,43 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Comments; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Comments; + +class FindPropertyCommentTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a token which is not supported is passed. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findCommentAbove + * + * @return void + */ + public function testNonExistentToken() + { + $this->assertFalse(Comments::findCommentAbove(self::$phpcsFile, 100000)); + + }//end testNonExistentToken() + + + /** + * Test receiving an expected exception when a non-variable token is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be an OO property + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findPropertyComment + * + * @return void + */ + public function testNotAnOOPropertyTokenExceptionNotVar() + { + $stackPtr = self::$phpcsFile->findNext(T_CLASS, 0); + $result = Comments::findPropertyComment(self::$phpcsFile, $stackPtr); + + }//end testNotAnOOPropertyTokenExceptionNotVar() + + + /** + * Test receiving an expected exception when a variable token which is not an OO property is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be an OO property + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findPropertyComment + * + * @return void + */ + public function testNotAnOOPropertyTokenExceptionGlobalVar() + { + $stackPtr = self::$phpcsFile->findNext( + T_VARIABLE, + 0, + null, + false, + '$notAProperty' + ); + $result = Comments::findPropertyComment(self::$phpcsFile, $stackPtr); + + }//end testNotAnOOPropertyTokenExceptionGlobalVar() + + + /** + * Test receiving an expected exception when a variable token which is a method parameter, not + * a property, is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be an OO property + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findPropertyComment + * + * @return void + */ + public function testNotAnOOPropertyTokenExceptionParam() + { + $stackPtr = self::$phpcsFile->findNext( + T_VARIABLE, + 0, + null, + false, + '$paramNotProperty' + ); + $result = Comments::findPropertyComment(self::$phpcsFile, $stackPtr); + + }//end testNotAnOOPropertyTokenExceptionParam() + + + /** + * Test correctly identifying a comment above an OO property T_VARIABLE token. + * + * @param string $propertyName The name of the property for which to find the comment. + * @param int|bool $expected The expected function return value. + * + * @dataProvider dataFindPropertyComment + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findPropertyComment + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findCommentAbove + * + * @return void + */ + public function testFindPropertyComment($propertyName, $expected) + { + $stackPtr = $this->getTargetToken($propertyName); + + // End token position values are set as offsets in relation to the target token. + // Change these to exact positions based on the retrieved stackPtr. + if ($expected !== false) { + $expected += $stackPtr; + } + + $result = Comments::findPropertyComment(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testFindPropertyComment() + + + /** + * Data provider. + * + * @see testFindPropertyComment() + * + * @return array + */ + public function dataFindPropertyComment() + { + return [ + [ + '$propertyNoDocblock', + false, + ], + [ + '$propertyStaticHadDocblock', + -5, + ], + [ + '$propertyNoDocblockTrailingComment', + false, + ], + [ + '$propertyMultiLineComment', + -5, + ], + [ + '$propertyInlineComment', + -4, + ], + [ + '$propertyHadDocblockAndWhitespace', + -9, + ], + [ + '$propertyHasDocblockInterlacedComments', + -14, + ], + ]; + + }//end dataFindPropertyComment() + + + /** + * Helper method. Get the token pointer for a target token based on a specific property name. + * + * Overloading the parent method as we can't look for marker comments for these methods as they + * would confuse the tests. + * + * @param string $propertyName The property name to look for. + * @param null $notUsed1 Parameter not used in this implementation of the method. + * @param null $notUsed2 Parameter not used in this implementation of the method. + * + * @return int + */ + public function getTargetToken($propertyName, $notUsed1=null, $notUsed2=null) + { + $tokens = self::$phpcsFile->getTokens(); + $target = self::$phpcsFile->findPrevious( + T_VARIABLE, + (self::$phpcsFile->numTokens - 1), + null, + false, + $propertyName + ); + + if ($target === false) { + $this->assertFalse(true, 'Failed to find test target token for '.$propertyName); + } + + return $target; + + }//end getTargetToken() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Comments/FindStartOfCommentTest.inc b/tests/Core/Util/Sniffs/Comments/FindStartOfCommentTest.inc new file mode 100644 index 0000000000..6c4dadbd45 --- /dev/null +++ b/tests/Core/Util/Sniffs/Comments/FindStartOfCommentTest.inc @@ -0,0 +1,183 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Comments; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Comments; + +class FindStartOfCommentTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a token which is not supported is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_COMMENT or T_DOC_COMMENT_CLOSE_TAG + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findStartOfComment + * + * @return void + */ + public function testNotACommentException() + { + $stackPtr = self::$phpcsFile->findNext(T_ECHO, 0); + $result = Comments::findStartOfComment(self::$phpcsFile, $stackPtr); + + }//end testNotACommentException() + + + /** + * Test receiving an expected exception when an inline comment token which is + * not the *end* of the inline comment is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must point to the end of a comment + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findStartOfComment + * + * @return void + */ + public function testNotEndOfAnInlineCommentException() + { + $stackPtr = self::$phpcsFile->findNext( + T_COMMENT, + 0, + null, + false, + '//line 2 +' + ); + $result = Comments::findStartOfComment(self::$phpcsFile, $stackPtr); + + }//end testNotEndOfAnInlineCommentException() + + + /** + * Test receiving an expected exception when an inline comment token which is + * not the *end* of a block comment is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must point to the end of a comment + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findStartOfComment + * + * @return void + */ + public function testNotEndOfABlockCommentException() + { + $stackPtr = self::$phpcsFile->findPrevious( + T_COMMENT, + (self::$phpcsFile->numTokens - 1), + null, + false, + ' * line 2 +' + ); + $result = Comments::findStartOfComment(self::$phpcsFile, $stackPtr); + + }//end testNotEndOfABlockCommentException() + + + /** + * Test receiving an expected exception when an inline comment token which is + * not the *end* of a block comment is passed and the comment contents starts with //. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must point to the end of a comment + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findEndOfComment + * + * @return void + */ + public function testMixedInlineBlockCommentException() + { + $stackPtr = $this->getTargetToken( + "'testBlockCommentSequence_13'", + null, + ' // line 2 +' + ); + $result = Comments::findStartOfComment(self::$phpcsFile, $stackPtr); + + }//end testMixedInlineBlockCommentException() + + + /** + * Test correctly identifying the start of an inline or block comment. + * + * @param string $delimiter The text string delimiter to use to find the + * end of the comment. + * @param int $expected The expected function return value as an offset + * from the end of the comment. + * @param string|null $tokenContent Optional. Specific content to get the correct + * comment token. + * + * @dataProvider dataFindStartOfComment + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::findStartOfComment + * + * @return void + */ + public function testFindStartOfComment($delimiter, $expected, $tokenContent=null) + { + $stackPtr = $this->getTargetToken($delimiter, null, $tokenContent); + + // Expected start token position values are set as offsets in relation to + // the target token. + // Change these to exact positions based on the retrieved stackPtr. + $expected += $stackPtr; + + $result = Comments::findStartOfComment(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testFindStartOfComment() + + + /** + * Data provider. + * + * Note: the delimiters for these tests are *below* the "end of comment" target token! + * + * @see testFindStartOfComment() + * + * @return array + */ + public function dataFindStartOfComment() + { + return [ + // Docblock. + [ + "'testDocblockSequence'", + -8, + ], + + // Inline comments. + [ + "'testInlineCommentSequence_1'", + 0, + ], + [ + "'testInlineCommentSequence_2'", + -6, + ], + [ + "'testInlineCommentSequence_3'", + -4, + ], + [ + "'testInlineCommentSequence_4'", + -4, + ], + [ + "'testInlineCommentSequence_5'", + -4, + ], + [ + "'testInlineCommentSequence_6'", + -4, + ], + [ + "'testInlineCommentSequence_7'", + 0, + ], + [ + "'testInlineCommentSequence_7'", + 0, + '// Stand-alone inline trailing comment. +', + ], + [ + "'testInlineCommentSequence_8'", + 0, + '// This starts (and ends) a new inline comment +', + ], + [ + "'testInlineCommentSequence_8'", + 0, + '// Stand-alone inline trailing comment. +', + ], + [ + "'testInlineCommentSequence_8'", + 0, + '/* Stand-alone block comment.*/', + ], + [ + "'testInlineCommentSequence_9'", + 0, + '// Stand alone inline comment. +', + ], + [ + "'testInlineCommentSequence_9'", + 0, + '// Another stand alone inline comment. +', + ], + [ + "'testInlineCommentSequence_10'", + -2, + ], + [ + "'testInlineCommentSequence_10'", + 0, + '# Perl-style comment not belonging to the sequence. +', + ], + [ + "'testInlineCommentSequence_11'", + -2, + ], + [ + "'testInlineCommentSequence_11'", + 0, + '// Slash-style comment not belonging to the sequence. +', + ], + [ + "'testInlineCommentSequence_12'", + -4, + ], + + // Block comments. + [ + "'testBlockCommentSequence_1'", + 0, + ], + [ + "'testBlockCommentSequence_2'", + 0, + ], + [ + "'testBlockCommentSequence_3'", + -2, + ], + [ + "'testBlockCommentSequence_4'", + -4, + ], + [ + "'testBlockCommentSequence_5'", + -4, + ], + [ + "'testBlockCommentSequence_6'", + -5, + ], + [ + "'testBlockCommentSequence_7'", + -4, + ], + [ + "'testBlockCommentSequence_8'", + -1, + ], + [ + "'testBlockCommentSequence_9'", + -1, + ], + [ + "'testBlockCommentSequence_10'", + -4, + ], + [ + "'testBlockCommentSequence_11'", + -4, + ], + [ + "'testBlockCommentSequence_12'", + 0, + ], + [ + "'testBlockCommentSequence_13'", + -4, + ], + ]; + + }//end dataFindStartOfComment() + + + /** + * Helper method. Get the token pointer for a target token based on a specific text string. + * + * Overloading the parent method as we can't look for marker comments for these methods as they + * would confuse the tests. + * + * Note: for these tests, the delimiter is placed *below* the target token. + * + * @param string $delimiter The text string content to look for. + * @param null $notUsed Parameter not used in this implementation of the method. + * @param string|null $tokenContent Optional. Specific content to get the correct + * comment token. + * + * @return int + */ + public function getTargetToken($delimiter, $notUsed=null, $tokenContent=null) + { + $tokens = self::$phpcsFile->getTokens(); + $delimiterPtr = self::$phpcsFile->findPrevious( + T_CONSTANT_ENCAPSED_STRING, + (self::$phpcsFile->numTokens - 1), + null, + false, + $delimiter + ); + + $target = self::$phpcsFile->findPrevious( + [ + T_COMMENT, + T_DOC_COMMENT_CLOSE_TAG, + ], + ($delimiterPtr - 1), + null, + false, + $tokenContent + ); + + if ($target === false) { + $msg = 'Failed to find test target token for '.$delimiter; + if (isset($tokenContent) === true) { + $msg .= ' with content '.$tokenContent; + } + + $this->assertFalse(true, $msg); + } + + return $target; + + }//end getTargetToken() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Comments/SuggestTypeStringTest.php b/tests/Core/Util/Sniffs/Comments/SuggestTypeStringTest.php new file mode 100644 index 0000000000..67ef229722 --- /dev/null +++ b/tests/Core/Util/Sniffs/Comments/SuggestTypeStringTest.php @@ -0,0 +1,264 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Comments; + +use PHPUnit\Framework\TestCase; +use PHP_CodeSniffer\Util\Sniffs\Comments; + +class SuggestTypeStringTest extends TestCase +{ + + + /** + * Test the suggestTypeString() method. + * + * @param string $typeString The complete variable type string to process. + * @param string $expectedLong Expected suggested long-form type. + * @param string $expectedShort Expected suggested short-form type. + * + * @dataProvider dataSuggestTypeString + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::suggestTypeString + * + * @return void + */ + public function testSuggestTypeString($typeString, $expectedLong, $expectedShort) + { + $result = Comments::suggestTypeString($typeString, 'long'); + $this->assertSame($expectedLong, $result); + + $result = Comments::suggestTypeString($typeString, 'short'); + $this->assertSame($expectedShort, $result); + + }//end testSuggestTypeString() + + + /** + * Data provider. + * + * @see testSuggestTypeString() + * + * @return array + */ + public function dataSuggestTypeString() + { + return [ + // Simple, singular type. + [ + 'input' => 'DoUbLe', + 'long' => 'float', + 'short' => 'float', + ], + [ + 'input' => 'BOOLEAN[]', + 'long' => 'boolean[]', + 'short' => 'bool[]', + ], + [ + 'input' => 'array(real)', + 'long' => 'array(float)', + 'short' => 'array(float)', + ], + [ + 'input' => 'array', + 'long' => 'array', + 'short' => 'array', + ], + [ + 'input' => '(Int|False)[]', + 'long' => '(integer|false)[]', + 'short' => '(int|false)[]', + ], + + // Slightly more complex array types. + [ + 'input' => 'array(string => string|null)', + 'long' => 'array(string => string|null)', + 'short' => 'array(string => string|null)', + ], + [ + 'input' => 'array(integer|string => int||null)', + 'long' => 'array(integer|string => integer|null)', + 'short' => 'array(int|string => int|null)', + ], + [ + 'input' => 'array', + 'long' => 'array', + 'short' => 'array', + ], + [ + 'input' => 'array', + 'long' => 'array', + 'short' => 'array', + ], + + // Union types. + [ + 'input' => 'int|real', + 'long' => 'integer|float', + 'short' => 'int|float', + ], + [ + 'input' => 'NULL|MIXED|RESOURCE', + 'long' => 'null|mixed|resource', + 'short' => 'null|mixed|resource', + ], + [ + 'input' => 'NULL|(int|False)[]', + 'long' => 'null|(integer|false)[]', + 'short' => 'null|(int|false)[]', + ], + [ + 'input' => '\ArrayObject|\DateTime[]', + 'long' => '\ArrayObject|\DateTime[]', + 'short' => '\ArrayObject|\DateTime[]', + ], + [ + 'input' => 'NULL|array(int => object)', + 'long' => 'null|array(integer => object)', + 'short' => 'null|array(int => object)', + ], + [ + 'input' => 'array(int => object)|NULL', + 'long' => 'array(integer => object)|null', + 'short' => 'array(int => object)|null', + ], + [ + 'input' => 'NULL|array', + 'long' => 'null|array', + 'short' => 'null|array', + ], + [ + 'input' => 'array|NULL', + 'long' => 'array|null', + 'short' => 'array|null', + ], + + // Intersect types. + [ + 'input' => '\MyClass&\PHPUnit\Framework\MockObject\MockObject', + 'long' => '\MyClass&\PHPUnit\Framework\MockObject\MockObject', + 'short' => '\MyClass&\PHPUnit\Framework\MockObject\MockObject', + ], + + // Mixed union and intersect types. + [ + 'input' => 'NULL|(int|False)[]|\MyClass&\PHPUnit\Framework\MockObject\MockObject', + 'long' => 'null|(integer|false)[]|\MyClass&\PHPUnit\Framework\MockObject\MockObject', + 'short' => 'null|(int|false)[]|\MyClass&\PHPUnit\Framework\MockObject\MockObject', + ], + + // Simple union types with duplicates. + [ + 'input' => 'int|integer', + 'long' => 'integer', + 'short' => 'int', + ], + [ + 'input' => 'bool|null|boolean|null', + 'long' => 'boolean|null', + 'short' => 'bool|null', + ], + + // Simple union type with duplicate `|`. + [ + 'input' => 'int||false', + 'long' => 'integer|false', + 'short' => 'int|false', + ], + + // Combining PSR-5 style single/multi-type arrays. + [ + 'input' => 'int[]|real[]|string[]', + 'long' => '(integer|float|string)[]', + 'short' => '(int|float|string)[]', + ], + [ + 'input' => '\ClassName[]|\Package\AnotherClass[]', + 'long' => '(\ClassName|\Package\AnotherClass)[]', + 'short' => '(\ClassName|\Package\AnotherClass)[]', + ], + [ + 'input' => '(int|\ClassName)[]|float[]|null', + 'long' => '(integer|\ClassName|float)[]|null', + 'short' => '(int|\ClassName|float)[]|null', + ], + [ + 'input' => 'null|(float|int)[]|\ClassName[]', + 'long' => 'null|(float|integer|\ClassName)[]', + 'short' => 'null|(float|int|\ClassName)[]', + ], + [ + 'input' => 'null|\ClassName[]|(real|int)[]', + 'long' => 'null|(\ClassName|float|integer)[]', + 'short' => 'null|(\ClassName|float|int)[]', + ], + + // Combining single/multi-type arrays with duplicates. + [ + 'input' => 'int[]|integer[]|false', + 'long' => 'integer[]|false', + 'short' => 'int[]|false', + ], + [ + 'input' => '(int|\ClassName)[]|integer[]|null', + 'long' => '(integer|\ClassName)[]|null', + 'short' => '(int|\ClassName)[]|null', + ], + [ + 'input' => '(int|\ClassName)[]|(float|int)[]', + 'long' => '(integer|\ClassName|float)[]', + 'short' => '(int|\ClassName|float)[]', + ], + + // Combining single/multi-type arrays with duplicates and duplicate `|`. + [ + 'input' => '(int||\ClassName)[]|(float||int)[]', + 'long' => '(integer|\ClassName|float)[]', + 'short' => '(int|\ClassName|float)[]', + ], + + // Combining single/multi-type arrays with duplicates in the array and the simple types. + [ + 'input' => 'int|(int|\ClassName)[]|integer[]|integer', + 'long' => 'integer|(integer|\ClassName)[]', + 'short' => 'int|(int|\ClassName)[]', + ], + + // Intersect types with duplicates. + [ + 'input' => '\MyClass&\YourClass&\AnotherClass&\MyClass', + 'long' => '\MyClass&\YourClass&\AnotherClass', + 'short' => '\MyClass&\YourClass&\AnotherClass', + ], + + // Intersect type with duplicate `&`. + [ + 'input' => '\MyClass&&\YourClass', + 'long' => '\MyClass&\YourClass', + 'short' => '\MyClass&\YourClass', + ], + + // Nullable types. + [ + 'input' => '?int|?real', + 'long' => '?integer|?float', + 'short' => '?int|?float', + ], + [ + 'input' => '?int|?string|null', + 'long' => 'integer|string|null', + 'short' => 'int|string|null', + ], + ]; + + }//end dataSuggestTypeString() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Comments/SuggestTypeTest.php b/tests/Core/Util/Sniffs/Comments/SuggestTypeTest.php new file mode 100644 index 0000000000..7848fd57ff --- /dev/null +++ b/tests/Core/Util/Sniffs/Comments/SuggestTypeTest.php @@ -0,0 +1,387 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Comments; + +use PHPUnit\Framework\TestCase; +use PHP_CodeSniffer\Util\Sniffs\Comments; + +class SuggestTypeTest extends TestCase +{ + + + /** + * Test passing an empty type to the suggestType() method. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::suggestType + * + * @return void + */ + public function testSuggestTypeEmpty() + { + $this->assertSame('', Comments::suggestType('')); + + }//end testSuggestTypeEmpty() + + + /** + * Test passing one of the allowed types to the suggestType() method. + * + * @param string $varType The type. + * @param string $expectedLong Expected suggested long-form type. + * @param string $expectedShort Expected suggested short-form type. + * + * @dataProvider dataSuggestTypeAllowedType + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::suggestType + * + * @return void + */ + public function testSuggestTypeAllowedType($varType, $expectedLong, $expectedShort) + { + $result = Comments::suggestType($varType, 'long'); + $this->assertSame($expectedLong, $result); + + $result = Comments::suggestType($varType, 'short'); + $this->assertSame($expectedShort, $result); + + }//end testSuggestTypeAllowedType() + + + /** + * Data provider. + * + * @see testSuggestTypeAllowedType() + * + * @return array + */ + public function dataSuggestTypeAllowedType() + { + $types = Comments::$allowedTypes; + $data = []; + foreach ($types as $short => $long) { + $data[$long] = [ + 'input' => $short, + 'long' => $long, + 'short' => $short, + ]; + } + + // Add tests for input being long form. + $data['int'] = [ + 'input' => 'integer', + 'long' => 'integer', + 'short' => 'int', + ]; + $data['bool'] = [ + 'input' => 'boolean', + 'long' => 'boolean', + 'short' => 'bool', + ]; + + return $data; + + }//end dataSuggestTypeAllowedType() + + + /** + * Test passing one of the allowed types in the wrong case to the suggestType() method. + * + * @param string $varType The type. + * @param string $expectedLong Expected suggested long-form type. + * @param string $expectedShort Expected suggested short-form type. + * + * @dataProvider dataSuggestTypeAllowedTypeWrongCase + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::suggestType + * + * @return void + */ + public function testSuggestTypeAllowedTypeWrongCase($varType, $expectedLong, $expectedShort) + { + $this->testSuggestTypeAllowedType($varType, $expectedLong, $expectedShort); + + }//end testSuggestTypeAllowedTypeWrongCase() + + + /** + * Data provider. + * + * @see testSuggestTypeAllowedTypeWrongCase() + * + * @return array + */ + public function dataSuggestTypeAllowedTypeWrongCase() + { + $types = Comments::$allowedTypes; + $data = []; + foreach ($types as $short => $long) { + $data[] = [ + 'input' => ucfirst($short), + 'long' => $long, + 'short' => $short, + ]; + $data[] = [ + 'input' => strtoupper($short), + 'long' => $long, + 'short' => $short, + ]; + } + + // Add tests for input being long form in non-lowercase. + $data[] = [ + 'input' => 'Integer', + 'long' => 'integer', + 'short' => 'int', + ]; + $data[] = [ + 'input' => 'INTEGER', + 'long' => 'integer', + 'short' => 'int', + ]; + $data[] = [ + 'input' => 'Boolean', + 'long' => 'boolean', + 'short' => 'bool', + ]; + $data[] = [ + 'input' => 'BOOLEAN', + 'long' => 'boolean', + 'short' => 'bool', + ]; + + return $data; + + }//end dataSuggestTypeAllowedTypeWrongCase() + + + /** + * Test the suggestType() method for all other cases. + * + * @param string $varType The type found. + * @param string $expectedLong Expected suggested long-form type. + * @param string $expectedShort Expected suggested short-form type. + * + * @dataProvider dataSuggestTypeOther + * @covers \PHP_CodeSniffer\Util\Sniffs\Comments::suggestType + * + * @return void + */ + public function testSuggestTypeOther($varType, $expectedLong, $expectedShort) + { + $result = Comments::suggestType($varType, 'long'); + $this->assertSame($expectedLong, $result); + + $result = Comments::suggestType($varType, 'short'); + $this->assertSame($expectedShort, $result); + + }//end testSuggestTypeOther() + + + /** + * Data provider. + * + * @see testSuggestTypeOther() + * + * @return array + */ + public function dataSuggestTypeOther() + { + return [ + // Wrong form. + [ + 'input' => 'double', + 'long' => 'float', + 'short' => 'float', + ], + [ + 'input' => 'Real', + 'long' => 'float', + 'short' => 'float', + ], + [ + 'input' => 'DoUbLe', + 'long' => 'float', + 'short' => 'float', + ], + + // Array types. + [ + 'input' => 'Array()', + 'long' => 'array', + 'short' => 'array', + ], + [ + 'input' => 'array(real)', + 'long' => 'array(float)', + 'short' => 'array(float)', + ], + [ + 'input' => 'array(int => object)', + 'long' => 'array(integer => object)', + 'short' => 'array(int => object)', + ], + [ + 'input' => 'array(integer => object)', + 'long' => 'array(integer => object)', + 'short' => 'array(int => object)', + ], + [ + 'input' => 'array(integer => array(string => resource))', + 'long' => 'array(integer => array(string => resource))', + 'short' => 'array(int => array(string => resource))', + ], + [ + 'input' => 'ARRAY(BOOL => DOUBLE)', + 'long' => 'array(boolean => float)', + 'short' => 'array(bool => float)', + ], + [ + 'input' => 'array(string=>resource)', + 'long' => 'array(string => resource)', + 'short' => 'array(string => resource)', + ], + [ + 'input' => 'ARRAY( BOOLEAN => Real )', + 'long' => 'array(boolean => float)', + 'short' => 'array(bool => float)', + ], + [ + 'input' => 'array(string => string|null)', + 'long' => 'array(string => string|null)', + 'short' => 'array(string => string|null)', + ], + [ + 'input' => 'array(integer|string => int||null)', + 'long' => 'array(integer|string => integer|null)', + 'short' => 'array(int|string => int|null)', + ], + + // Arrays with <> brackets. + [ + 'input' => 'Array<>', + 'long' => 'array', + 'short' => 'array', + ], + [ + 'input' => 'array', + 'long' => 'array', + 'short' => 'array', + ], + [ + 'input' => 'array', + 'long' => 'array', + 'short' => 'array', + ], + [ + 'input' => 'array', + 'long' => 'array', + 'short' => 'array', + ], + [ + 'input' => 'array>', + 'long' => 'array>', + 'short' => 'array>', + ], + [ + 'input' => 'ARRAY', + 'long' => 'array', + 'short' => 'array', + ], + [ + 'input' => 'ARRAY< BOOLEAN , Real >', + 'long' => 'array', + 'short' => 'array', + ], + [ + 'input' => 'array', + 'long' => 'array', + 'short' => 'array', + ], + [ + 'input' => 'array', + 'long' => 'array', + 'short' => 'array', + ], + + // Incomplete array type. + [ + 'input' => 'array(int =>', + 'long' => 'array', + 'short' => 'array', + ], + + // Custom types are returned unchanged. + [ + 'input' => ' => ', + 'long' => ' => ', + 'short' => ' => ', + ], + [ + 'input' => '[]', + 'long' => '[]', + 'short' => '[]', + ], + + // Single type PSR-5 style arrays. + [ + 'input' => 'string[]', + 'long' => 'string[]', + 'short' => 'string[]', + ], + [ + 'input' => 'BOOLEAN[]', + 'long' => 'boolean[]', + 'short' => 'bool[]', + ], + [ + 'input' => 'int[]', + 'long' => 'integer[]', + 'short' => 'int[]', + ], + [ + 'input' => 'double[]', + 'long' => 'float[]', + 'short' => 'float[]', + ], + + // Multi-type PSR-5 style arrays. + [ + 'input' => '(string|null)[]', + 'long' => '(string|null)[]', + 'short' => '(string|null)[]', + ], + [ + 'input' => '(Int|False)[]', + 'long' => '(integer|false)[]', + 'short' => '(int|false)[]', + ], + [ + 'input' => '(real|TRUE|resource)[]', + 'long' => '(float|true|resource)[]', + 'short' => '(float|true|resource)[]', + ], + + // Multi-type PSR-5 style array with duplicate `|`. + [ + 'input' => '(string||null)[]', + 'long' => '(string|null)[]', + 'short' => '(string|null)[]', + ], + + // Nullable types. + [ + 'input' => '?bool', + 'long' => '?boolean', + 'short' => '?bool', + ], + ]; + + }//end dataSuggestTypeOther() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Conditions/GetConditionTest.inc b/tests/Core/Util/Sniffs/Conditions/GetConditionTest.inc new file mode 100644 index 0000000000..e7684daa97 --- /dev/null +++ b/tests/Core/Util/Sniffs/Conditions/GetConditionTest.inc @@ -0,0 +1,91 @@ + $v) { + + /* condition 11-2: try */ + try { + --$k; + + /* condition 11-3: catch */ + } catch (Exception $e) { + /* testInException */ + echo 'oh darn'; + /* condition 11-4: finally */ + } finally { + return true; + } + } + + $a++; + } + break; + + /* condition 8b: default */ + default: + /* testInDefault */ + $return = 'nada'; + return $return; + } + } + } + } + } + } + } +} diff --git a/tests/Core/Util/Sniffs/Conditions/GetConditionTest.php b/tests/Core/Util/Sniffs/Conditions/GetConditionTest.php new file mode 100644 index 0000000000..fb51fdc6cc --- /dev/null +++ b/tests/Core/Util/Sniffs/Conditions/GetConditionTest.php @@ -0,0 +1,648 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Conditions; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Conditions; +use PHP_CodeSniffer\Util\Tokens; + +class GetConditionTest extends AbstractMethodUnitTest +{ + + /** + * List of all the test markers with their target token in the test case file. + * + * - The startPoint token is left out as it is tested separately. + * - The key is the type of token to look for after the test marker. + * + * @var array => + */ + public static $testTargets = [ + T_VARIABLE => '/* testSeriouslyNestedMethod */', + T_RETURN => '/* testDeepestNested */', + T_ECHO => '/* testInException */', + T_CONSTANT_ENCAPSED_STRING => '/* testInDefault */', + ]; + + /** + * Cache for the test token stack pointers. + * + * @var array => + */ + private $testTokens = []; + + /** + * List of all the condition markers in the test case file. + * + * @var string[] + */ + private $conditionMarkers = [ + '/* condition 0: namespace */', + '/* condition 1: if */', + '/* condition 2: function */', + '/* condition 3-1: if */', + '/* condition 3-2: else */', + '/* condition 4: if */', + '/* condition 5: nested class */', + '/* condition 6: class method */', + '/* condition 7: switch */', + '/* condition 8a: case */', + '/* condition 9: while */', + '/* condition 10-1: if */', + '/* condition 11-1: nested anonymous class */', + '/* condition 12: nested anonymous class method */', + '/* condition 13: closure */', + '/* condition 10-2: elseif */', + '/* condition 10-3: foreach */', + '/* condition 11-2: try */', + '/* condition 11-3: catch */', + '/* condition 11-4: finally */', + '/* condition 8b: default */', + ]; + + /** + * Cache for the marker token stack pointers. + * + * @var array => + */ + private $markerTokens = []; + + /** + * Base array with all the scope opening tokens. + * + * This array is merged with expected result arrays for various unit tests + * to make sure all possible conditions are tested. + * + * This array should be kept in sync with the Tokens::$scopeOpeners array. + * This array isn't auto-generated based on the array in Tokens as for these + * tests we want to have access to the token constant names, not just their values. + * + * @var array => + */ + private $conditionDefaults = [ + 'T_CLASS' => false, + 'T_ANON_CLASS' => false, + 'T_INTERFACE' => false, + 'T_TRAIT' => false, + 'T_NAMESPACE' => false, + 'T_FUNCTION' => false, + 'T_CLOSURE' => false, + 'T_IF' => false, + 'T_SWITCH' => false, + 'T_CASE' => false, + 'T_DECLARE' => false, + 'T_DEFAULT' => false, + 'T_WHILE' => false, + 'T_ELSE' => false, + 'T_ELSEIF' => false, + 'T_FOR' => false, + 'T_FOREACH' => false, + 'T_DO' => false, + 'T_TRY' => false, + 'T_CATCH' => false, + 'T_FINALLY' => false, + 'T_PROPERTY' => false, + 'T_OBJECT' => false, + 'T_USE' => false, + ]; + + + /** + * Set up the token position caches for the tests. + * + * Retrieves the test tokens and marker token stack pointer positions + * only once and caches them as they won't change between the tests anyway. + * + * @return void + */ + protected function setUp() + { + if (empty($this->testTokens) === true) { + foreach (self::$testTargets as $targetToken => $marker) { + $this->testTokens[$marker] = $this->getTargetToken($marker, $targetToken); + } + } + + if (empty($this->markerTokens) === true) { + foreach ($this->conditionMarkers as $marker) { + $this->markerTokens[$marker] = $this->getTargetToken($marker, Tokens::$scopeOpeners); + } + } + + }//end setUp() + + + /** + * Test passing a non-existent token pointer. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::getCondition + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::hasCondition + * + * @return void + */ + public function testNonExistentToken() + { + $result = Conditions::getCondition(self::$phpcsFile, 100000, Tokens::$ooScopeTokens); + $this->assertFalse($result); + + $result = Conditions::hasCondition(self::$phpcsFile, 100000, T_IF); + $this->assertFalse($result); + + }//end testNonExistentToken() + + + /** + * Test passing a non conditional token. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::getCondition + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::hasCondition + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::getFirstCondition + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::getLastCondition + * + * @return void + */ + public function testNonConditionalToken() + { + $stackPtr = $this->getTargetToken('/* testStartPoint */', T_STRING); + + $result = Conditions::getCondition(self::$phpcsFile, $stackPtr, T_IF); + $this->assertFalse($result); + + $result = Conditions::hasCondition(self::$phpcsFile, $stackPtr, Tokens::$ooScopeTokens); + $this->assertFalse($result); + + $result = Conditions::getFirstCondition(self::$phpcsFile, $stackPtr); + $this->assertFalse($result); + + $result = Conditions::getLastCondition(self::$phpcsFile, $stackPtr); + $this->assertFalse($result); + + }//end testNonConditionalToken() + + + /** + * Test retrieving a specific condition from a tokens "conditions" array. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expectedResults Array with the condition token type to search for as key + * and the marker for the expected stack pointer result as a value. + * @param array $expectedResultsReversed Array with the condition token type to search for as key + * and the marker for the expected stack pointer result as a value. + * + * @dataProvider dataGetCondition + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::getCondition + * + * @return void + */ + public function testGetCondition($testMarker, $expectedResults, $expectedResultsReversed) + { + $stackPtr = $this->testTokens[$testMarker]; + + // Add expected results for all test markers not listed in the data provider. + $expectedResults += $this->conditionDefaults; + + foreach ($expectedResults as $conditionType => $expected) { + if ($expected !== false) { + $expected = $this->markerTokens[$expected]; + } + + $result = Conditions::getCondition(self::$phpcsFile, $stackPtr, constant($conditionType)); + $this->assertSame( + $expected, + $result, + "Assertion failed for test marker '{$testMarker}' with condition {$conditionType}" + ); + } + + foreach ($expectedResultsReversed as $conditionType => $expected) { + if ($expected !== false) { + $expected = $this->markerTokens[$expected]; + } + + $result = Conditions::getCondition(self::$phpcsFile, $stackPtr, constant($conditionType), true); + $this->assertSame( + $expected, + $result, + "Assertion failed for test marker '{$testMarker}' with condition {$conditionType} (reversed)" + ); + } + + }//end testGetCondition() + + + /** + * Data provider. + * + * Only the conditions which are expected to be *found* need to be listed here. + * All other potential conditions will automatically also be tested and will expect + * `false` as a result. + * + * @see testGetCondition() + * + * @return array + */ + public function dataGetCondition() + { + $data = [ + 'testSeriouslyNestedMethod' => [ + '/* testSeriouslyNestedMethod */', + [ + 'T_CLASS' => '/* condition 5: nested class */', + 'T_NAMESPACE' => '/* condition 0: namespace */', + 'T_FUNCTION' => '/* condition 2: function */', + 'T_IF' => '/* condition 1: if */', + 'T_ELSE' => '/* condition 3-2: else */', + ], + ], + 'testDeepestNested' => [ + '/* testDeepestNested */', + [ + 'T_CLASS' => '/* condition 5: nested class */', + 'T_ANON_CLASS' => '/* condition 11-1: nested anonymous class */', + 'T_NAMESPACE' => '/* condition 0: namespace */', + 'T_FUNCTION' => '/* condition 2: function */', + 'T_CLOSURE' => '/* condition 13: closure */', + 'T_IF' => '/* condition 1: if */', + 'T_SWITCH' => '/* condition 7: switch */', + 'T_CASE' => '/* condition 8a: case */', + 'T_WHILE' => '/* condition 9: while */', + 'T_ELSE' => '/* condition 3-2: else */', + ], + ], + 'testInException' => [ + '/* testInException */', + [ + 'T_CLASS' => '/* condition 5: nested class */', + 'T_NAMESPACE' => '/* condition 0: namespace */', + 'T_FUNCTION' => '/* condition 2: function */', + 'T_IF' => '/* condition 1: if */', + 'T_SWITCH' => '/* condition 7: switch */', + 'T_CASE' => '/* condition 8a: case */', + 'T_WHILE' => '/* condition 9: while */', + 'T_ELSE' => '/* condition 3-2: else */', + 'T_FOREACH' => '/* condition 10-3: foreach */', + 'T_CATCH' => '/* condition 11-3: catch */', + ], + ], + 'testInDefault' => [ + '/* testInDefault */', + [ + 'T_CLASS' => '/* condition 5: nested class */', + 'T_NAMESPACE' => '/* condition 0: namespace */', + 'T_FUNCTION' => '/* condition 2: function */', + 'T_IF' => '/* condition 1: if */', + 'T_SWITCH' => '/* condition 7: switch */', + 'T_DEFAULT' => '/* condition 8b: default */', + 'T_ELSE' => '/* condition 3-2: else */', + ], + ], + ]; + + // Set up the data for the reversed results. + $reversed = $data['testSeriouslyNestedMethod'][1]; + $reversed['T_IF'] = '/* condition 4: if */'; + $data['testSeriouslyNestedMethod'][] = $reversed; + + $reversed = $data['testDeepestNested'][1]; + $reversed['T_FUNCTION'] = '/* condition 12: nested anonymous class method */'; + $reversed['T_IF'] = '/* condition 10-1: if */'; + $data['testDeepestNested'][] = $reversed; + + $reversed = $data['testInException'][1]; + $reversed['T_FUNCTION'] = '/* condition 6: class method */'; + $reversed['T_IF'] = '/* condition 4: if */'; + $data['testInException'][] = $reversed; + + $reversed = $data['testInDefault'][1]; + $reversed['T_FUNCTION'] = '/* condition 6: class method */'; + $reversed['T_IF'] = '/* condition 4: if */'; + $data['testInDefault'][] = $reversed; + + return $data; + + }//end dataGetCondition() + + + /** + * Test retrieving a specific condition from a token's "conditions" array, + * with multiple allowed possibilities. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::getCondition + * + * @return void + */ + public function testGetConditionMultipleTypes() + { + $stackPtr = $this->testTokens['/* testInException */']; + + $result = Conditions::getCondition(self::$phpcsFile, $stackPtr, [T_DO, T_FOR]); + $this->assertFalse( + $result, + 'Failed asserting that "testInException" does not have a "do" nor a "for" condition' + ); + + $result = Conditions::getCondition(self::$phpcsFile, $stackPtr, [T_DO, T_FOR, T_FOREACH]); + $this->assertSame( + $this->markerTokens['/* condition 10-3: foreach */'], + $result, + 'Failed asserting that "testInException" has a foreach condition based on the types "do", "for" and "foreach"' + ); + + $stackPtr = $this->testTokens['/* testDeepestNested */']; + + $result = Conditions::getCondition(self::$phpcsFile, $stackPtr, [T_INTERFACE, T_TRAIT]); + $this->assertFalse( + $result, + 'Failed asserting that "testDeepestNested" does not have an interface nor a trait condition' + ); + + $result = Conditions::getCondition(self::$phpcsFile, $stackPtr, Tokens::$ooScopeTokens); + $this->assertSame( + $this->markerTokens['/* condition 5: nested class */'], + $result, + 'Failed asserting that "testDeepestNested" has a class condition based on the OO Scope token types' + ); + + }//end testGetConditionMultipleTypes() + + + /** + * Test whether a token has a condition of a certain type. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expectedResults Array with the condition token type to search for as key + * and the expected result as a value. + * + * @dataProvider dataHasCondition + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::hasCondition + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::getCondition + * + * @return void + */ + public function testHasCondition($testMarker, $expectedResults) + { + $stackPtr = $this->testTokens[$testMarker]; + + // Add expected results for all test markers not listed in the data provider. + $expectedResults += $this->conditionDefaults; + + foreach ($expectedResults as $conditionType => $expected) { + $result = Conditions::hasCondition(self::$phpcsFile, $stackPtr, constant($conditionType)); + $this->assertSame( + $expected, + $result, + "Assertion failed for test marker '{$testMarker}' with condition {$conditionType}" + ); + } + + }//end testHasCondition() + + + /** + * Data Provider. + * + * Only list the "true" conditions in the $results array. + * All other potential conditions will automatically also be tested + * and will expect "false" as a result. + * + * @see testHasCondition() + * + * @return array + */ + public function dataHasCondition() + { + return [ + 'testSeriouslyNestedMethod' => [ + '/* testSeriouslyNestedMethod */', + [ + 'T_CLASS' => true, + 'T_NAMESPACE' => true, + 'T_FUNCTION' => true, + 'T_IF' => true, + 'T_ELSE' => true, + ], + ], + 'testDeepestNested' => [ + '/* testDeepestNested */', + [ + 'T_CLASS' => true, + 'T_ANON_CLASS' => true, + 'T_NAMESPACE' => true, + 'T_FUNCTION' => true, + 'T_CLOSURE' => true, + 'T_IF' => true, + 'T_SWITCH' => true, + 'T_CASE' => true, + 'T_WHILE' => true, + 'T_ELSE' => true, + ], + ], + 'testInException' => [ + '/* testInException */', + [ + 'T_CLASS' => true, + 'T_NAMESPACE' => true, + 'T_FUNCTION' => true, + 'T_IF' => true, + 'T_SWITCH' => true, + 'T_CASE' => true, + 'T_WHILE' => true, + 'T_ELSE' => true, + 'T_FOREACH' => true, + 'T_CATCH' => true, + ], + ], + 'testInDefault' => [ + '/* testInDefault */', + [ + 'T_CLASS' => true, + 'T_NAMESPACE' => true, + 'T_FUNCTION' => true, + 'T_IF' => true, + 'T_SWITCH' => true, + 'T_DEFAULT' => true, + 'T_ELSE' => true, + ], + ], + ]; + + }//end dataHasCondition() + + + /** + * Test whether a token has a condition of a certain type, with multiple allowed possibilities. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::hasCondition + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::getCondition + * + * @return void + */ + public function testHasConditionMultipleTypes() + { + $stackPtr = $this->testTokens['/* testInException */']; + + $result = Conditions::hasCondition(self::$phpcsFile, $stackPtr, [T_TRY, T_FINALLY]); + $this->assertFalse( + $result, + 'Failed asserting that "testInException" does not have a "try" nor a "finally" condition' + ); + + $result = Conditions::hasCondition(self::$phpcsFile, $stackPtr, [T_TRY, T_CATCH, T_FINALLY]); + $this->assertTrue( + $result, + 'Failed asserting that "testInException" has a "try", "catch" or "finally" condition' + ); + + $stackPtr = $this->testTokens['/* testSeriouslyNestedMethod */']; + + $result = Conditions::hasCondition(self::$phpcsFile, $stackPtr, [T_ANON_CLASS, T_CLOSURE]); + $this->assertFalse( + $result, + 'Failed asserting that "testSeriouslyNestedMethod" does not have an anonymous class nor a closure condition' + ); + + $result = Conditions::hasCondition(self::$phpcsFile, $stackPtr, Tokens::$ooScopeTokens); + $this->assertTrue( + $result, + 'Failed asserting that "testSeriouslyNestedMethod" has an OO Scope token condition' + ); + + }//end testHasConditionMultipleTypes() + + + /** + * Test retrieving the first condition token pointer, in general and of specific types. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * + * @dataProvider dataGetFirstCondition + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::getFirstCondition + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::getCondition + * + * @return void + */ + public function testGetFirstCondition($testMarker) + { + $stackPtr = $this->testTokens[$testMarker]; + + $result = Conditions::getFirstCondition(self::$phpcsFile, $stackPtr); + $this->assertSame($this->markerTokens['/* condition 0: namespace */'], $result); + + $result = Conditions::getFirstCondition(self::$phpcsFile, $stackPtr, T_IF); + $this->assertSame($this->markerTokens['/* condition 1: if */'], $result); + + $result = Conditions::getFirstCondition(self::$phpcsFile, $stackPtr, Tokens::$ooScopeTokens); + $this->assertSame($this->markerTokens['/* condition 5: nested class */'], $result); + + $result = Conditions::getFirstCondition(self::$phpcsFile, $stackPtr, [T_ELSEIF]); + $this->assertFalse($result); + + }//end testGetFirstCondition() + + + /** + * Data provider. Pass the markers for the test tokens on. + * + * @see testGetFirstCondition() + * + * @return array + */ + public function dataGetFirstCondition() + { + $data = []; + foreach (self::$testTargets as $marker) { + $data[] = [$marker]; + } + + return $data; + + }//end dataGetFirstCondition() + + + /** + * Test retrieving the last condition token pointer, in general and of specific types. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The marker for the pointers to the expected condition + * results for the pre-set tests. + * + * @dataProvider dataGetLastCondition + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::getLastCondition + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::getCondition + * + * @return void + */ + public function testGetLastCondition($testMarker, $expected) + { + $stackPtr = $this->testTokens[$testMarker]; + + $result = Conditions::getLastCondition(self::$phpcsFile, $stackPtr); + $this->assertSame($this->markerTokens[$expected['no type']], $result); + + $result = Conditions::getLastCondition(self::$phpcsFile, $stackPtr, T_IF); + $this->assertSame($this->markerTokens[$expected['T_IF']], $result); + + $result = Conditions::getLastCondition(self::$phpcsFile, $stackPtr, Tokens::$ooScopeTokens); + $this->assertSame($this->markerTokens[$expected['OO tokens']], $result); + + $result = Conditions::getLastCondition(self::$phpcsFile, $stackPtr, [T_FINALLY]); + $this->assertFalse($result); + + }//end testGetLastCondition() + + + /** + * Data provider. + * + * @see testGetLastCondition() + * + * @return array + */ + public function dataGetLastCondition() + { + return [ + 'testSeriouslyNestedMethod' => [ + '/* testSeriouslyNestedMethod */', + [ + 'no type' => '/* condition 5: nested class */', + 'T_IF' => '/* condition 4: if */', + 'OO tokens' => '/* condition 5: nested class */', + ], + ], + 'testDeepestNested' => [ + '/* testDeepestNested */', + [ + 'no type' => '/* condition 13: closure */', + 'T_IF' => '/* condition 10-1: if */', + 'OO tokens' => '/* condition 11-1: nested anonymous class */', + ], + ], + 'testInException' => [ + '/* testInException */', + [ + 'no type' => '/* condition 11-3: catch */', + 'T_IF' => '/* condition 4: if */', + 'OO tokens' => '/* condition 5: nested class */', + ], + ], + 'testInDefault' => [ + '/* testInDefault */', + [ + 'no type' => '/* condition 8b: default */', + 'T_IF' => '/* condition 4: if */', + 'OO tokens' => '/* condition 5: nested class */', + ], + ], + ]; + + }//end dataGetLastCondition() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Conditions/IsOOConstantTest.inc b/tests/Core/Util/Sniffs/Conditions/IsOOConstantTest.inc new file mode 100644 index 0000000000..e9ad92b8f0 --- /dev/null +++ b/tests/Core/Util/Sniffs/Conditions/IsOOConstantTest.inc @@ -0,0 +1,39 @@ + + * @copyright 2017-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Conditions; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Conditions; + +class IsOOConstantTest extends AbstractMethodUnitTest +{ + + + /** + * Test passing a non-existent token pointer. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::isOOConstant + * + * @return void + */ + public function testNonExistentToken() + { + $result = Conditions::isOOConstant(self::$phpcsFile, 10000); + $this->assertFalse($result); + + }//end testNonExistentToken() + + + /** + * Test passing a non const token. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::isOOConstant + * + * @return void + */ + public function testNonConstToken() + { + $result = Conditions::isOOConstant(self::$phpcsFile, 0); + $this->assertFalse($result); + + }//end testNonConstToken() + + + /** + * Test correctly identifying whether a T_CONST token is a class constant. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expected The expected function return value. + * + * @dataProvider dataIsOOConstant + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::isOOConstant + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::validDirectScope + * + * @return void + */ + public function testIsOOConstant($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_CONST); + $result = Conditions::isOOConstant(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testIsOOConstant() + + + /** + * Data provider. + * + * @see testIsOOConstant() + * + * @return array + */ + public function dataIsOOConstant() + { + return [ + [ + '/* testGlobalConst */', + false, + ], + [ + '/* testFunctionConst */', + false, + ], + [ + '/* testClassConst */', + true, + ], + [ + '/* testClassMethodConst */', + false, + ], + [ + '/* testAnonClassConst */', + true, + ], + [ + '/* testInterfaceConst */', + true, + ], + [ + '/* testTraitConst */', + false, + ], + ]; + + }//end dataIsOOConstant() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Conditions/IsOOMethodTest.inc b/tests/Core/Util/Sniffs/Conditions/IsOOMethodTest.inc new file mode 100644 index 0000000000..d4e0c2ec11 --- /dev/null +++ b/tests/Core/Util/Sniffs/Conditions/IsOOMethodTest.inc @@ -0,0 +1,40 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Conditions; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Conditions; + +class IsOOMethodTest extends AbstractMethodUnitTest +{ + + + /** + * Test passing a non-existent token pointer. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::isOOMethod + * + * @return void + */ + public function testNonExistentToken() + { + $result = Conditions::isOOMethod(self::$phpcsFile, 10000); + $this->assertFalse($result); + + }//end testNonExistentToken() + + + /** + * Test passing a non function token. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::isOOMethod + * + * @return void + */ + public function testNonFunctionToken() + { + $result = Conditions::isOOMethod(self::$phpcsFile, 0); + $this->assertFalse($result); + + }//end testNonFunctionToken() + + + /** + * Test correctly identifying whether a T_FUNCTION token is a class method declaration. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expected The expected function return value. + * + * @dataProvider dataIsOOMethod + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::isOOMethod + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::validDirectScope + * + * @return void + */ + public function testIsOOMethod($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, [T_FUNCTION, T_CLOSURE]); + $result = Conditions::isOOMethod(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testIsOOMethod() + + + /** + * Data provider. + * + * @see testIsOOMethod() + * + * @return array + */ + public function dataIsOOMethod() + { + return [ + [ + '/* testGlobalFunction */', + false, + ], + [ + '/* testNestedFunction */', + false, + ], + [ + '/* testNestedClosure */', + false, + ], + [ + '/* testClassMethod */', + true, + ], + [ + '/* testClassNestedFunction */', + false, + ], + [ + '/* testClassNestedClosure */', + false, + ], + [ + '/* testClassAbstractMethod */', + true, + ], + [ + '/* testAnonClassMethod */', + true, + ], + [ + '/* testInterfaceMethod */', + true, + ], + [ + '/* testTraitMethod */', + true, + ], + ]; + + }//end dataIsOOMethod() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Conditions/IsOOPropertyTest.inc b/tests/Core/Util/Sniffs/Conditions/IsOOPropertyTest.inc new file mode 100644 index 0000000000..176395604b --- /dev/null +++ b/tests/Core/Util/Sniffs/Conditions/IsOOPropertyTest.inc @@ -0,0 +1,89 @@ +setLogger( + new class { + /* testNestedAnonClassProp */ + private $varName = 'hello'; +}); + +if ( has_filter( 'comments_open' ) === false ) { + add_filter( 'comments_open', new class { + /* testDoubleNestedAnonClassProp */ + public $year = 2017; // Ok. + + /* testDoubleNestedAnonClassMethodParameter */ + public function __construct( $open, $post_id ) { + /* testDoubleNestedAnonClassMethodLocalVar */ + global $page; + } + /* testFunctionCallParameter */ + }, $priority, 2 ); +} diff --git a/tests/Core/Util/Sniffs/Conditions/IsOOPropertyTest.php b/tests/Core/Util/Sniffs/Conditions/IsOOPropertyTest.php new file mode 100644 index 0000000000..d0ce19e4b3 --- /dev/null +++ b/tests/Core/Util/Sniffs/Conditions/IsOOPropertyTest.php @@ -0,0 +1,181 @@ + + * @copyright 2017-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Conditions; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Conditions; + +class IsOOPropertyTest extends AbstractMethodUnitTest +{ + + + /** + * Test passing a non-existent token pointer. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::isOOProperty + * + * @return void + */ + public function testNonExistentToken() + { + $result = Conditions::isOOProperty(self::$phpcsFile, 10000); + $this->assertFalse($result); + + }//end testNonExistentToken() + + + /** + * Test passing a non variable token. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::isOOProperty + * + * @return void + */ + public function testNonVariableToken() + { + $result = Conditions::isOOProperty(self::$phpcsFile, 0); + $this->assertFalse($result); + + }//end testNonVariableToken() + + + /** + * Test correctly identifying whether a T_VARIABLE token is a class property declaration. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expected The expected function return value. + * + * @dataProvider dataIsOOProperty + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::isOOProperty + * @covers \PHP_CodeSniffer\Util\Sniffs\Conditions::validDirectScope + * + * @return void + */ + public function testIsOOProperty($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_VARIABLE); + $result = Conditions::isOOProperty(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testIsOOProperty() + + + /** + * Data provider. + * + * @see testIsOOProperty() + * + * @return array + */ + public function dataIsOOProperty() + { + return [ + [ + '/* testGlobalVar */', + false, + ], + [ + '/* testFunctionParameter */', + false, + ], + [ + '/* testFunctionLocalVar */', + false, + ], + [ + '/* testClassPropPublic */', + true, + ], + [ + '/* testClassPropVar */', + true, + ], + [ + '/* testClassPropStaticProtected */', + true, + ], + [ + '/* testMethodParameter */', + false, + ], + [ + '/* testMethodLocalVar */', + false, + ], + [ + '/* testAnonClassPropPrivate */', + true, + ], + [ + '/* testAnonMethodParameter */', + false, + ], + [ + '/* testAnonMethodLocalVar */', + false, + ], + [ + '/* testInterfaceProp */', + false, + ], + [ + '/* testInterfaceMethodParameter */', + false, + ], + [ + '/* testTraitProp */', + true, + ], + [ + '/* testTraitMethodParameter */', + false, + ], + [ + '/* testClassMultiProp1 */', + true, + ], + [ + '/* testClassMultiProp2 */', + true, + ], + [ + '/* testClassMultiProp3 */', + true, + ], + [ + '/* testGlobalVarObj */', + false, + ], + [ + '/* testNestedAnonClassProp */', + true, + ], + [ + '/* testDoubleNestedAnonClassProp */', + true, + ], + [ + '/* testDoubleNestedAnonClassMethodParameter */', + false, + ], + [ + '/* testDoubleNestedAnonClassMethodLocalVar */', + false, + ], + [ + '/* testFunctionCallParameter */', + false, + ], + ]; + + }//end dataIsOOProperty() + + +}//end class diff --git a/tests/Core/Util/Sniffs/ConstructNames/GetDeclarationNameJSTest.js b/tests/Core/Util/Sniffs/ConstructNames/GetDeclarationNameJSTest.js new file mode 100644 index 0000000000..ee0a76a44f --- /dev/null +++ b/tests/Core/Util/Sniffs/ConstructNames/GetDeclarationNameJSTest.js @@ -0,0 +1,23 @@ +/* testInvalidTokenPassed */ +print something; + +var object = +{ + /* testClosure */ + propertyName: function () {} +} + +/* testFunction */ +function functionName() {} + +/* testClass */ +class ClassName +{ + /* testMethod */ + methodName() { + return false; + } +} + +/* testFunctionUnicode */ +function π() {} diff --git a/tests/Core/Util/Sniffs/ConstructNames/GetDeclarationNameJSTest.php b/tests/Core/Util/Sniffs/ConstructNames/GetDeclarationNameJSTest.php new file mode 100644 index 0000000000..4aee36d606 --- /dev/null +++ b/tests/Core/Util/Sniffs/ConstructNames/GetDeclarationNameJSTest.php @@ -0,0 +1,93 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\ConstructNames; + +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; + +class GetDeclarationNameJSTest extends GetDeclarationNameTest +{ + + /** + * The file extension of the test case file (without leading dot). + * + * @var string + */ + protected static $fileExtension = 'js'; + + + /** + * Test receiving an expected exception when a non-supported token is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage Token type "T_STRING" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT + * + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::getDeclarationName + * + * @return void + */ + public function testInvalidTokenPassed() + { + $print = $this->getTargetToken('/* testInvalidTokenPassed */', T_STRING); + $result = ConstructNames::getDeclarationName(self::$phpcsFile, $print); + + }//end testInvalidTokenPassed() + + + /** + * Data provider. + * + * @see GetDeclarationNameTest::testGetDeclarationNameNull() + * + * @return array + */ + public function dataGetDeclarationNameNull() + { + return [ + [ + '/* testClosure */', + T_CLOSURE, + ], + ]; + + }//end dataGetDeclarationNameNull() + + + /** + * Data provider. + * + * @see GetDeclarationNameTest::testGetDeclarationName() + * + * @return array + */ + public function dataGetDeclarationName() + { + return [ + [ + '/* testFunction */', + 'functionName', + ], + [ + '/* testClass */', + 'ClassName', + ], + [ + '/* testMethod */', + 'methodName', + ], + [ + '/* testFunctionUnicode */', + 'π', + ], + ]; + + }//end dataGetDeclarationName() + + +}//end class diff --git a/tests/Core/Util/Sniffs/ConstructNames/GetDeclarationNameTest.inc b/tests/Core/Util/Sniffs/ConstructNames/GetDeclarationNameTest.inc new file mode 100644 index 0000000000..764f873a45 --- /dev/null +++ b/tests/Core/Util/Sniffs/ConstructNames/GetDeclarationNameTest.inc @@ -0,0 +1,60 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\ConstructNames; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; + +class GetDeclarationNameTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a non-supported token is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage Token type "T_ECHO" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT + * + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::getDeclarationName + * + * @return void + */ + public function testInvalidTokenPassed() + { + $interface = $this->getTargetToken('/* testInvalidTokenPassed */', T_ECHO); + $result = ConstructNames::getDeclarationName(self::$phpcsFile, $interface); + + }//end testInvalidTokenPassed() + + + /** + * Test receiving "null" when passed an anonymous construct. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param int|string $targetType Token type of the token to get as stackPtr. + * + * @dataProvider dataGetDeclarationNameNull + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::getDeclarationName + * + * @return void + */ + public function testGetDeclarationNameNull($testMarker, $targetType) + { + $target = $this->getTargetToken($testMarker, $targetType); + $result = ConstructNames::getDeclarationName(self::$phpcsFile, $target); + $this->assertNull($result); + + }//end testGetDeclarationNameNull() + + + /** + * Data provider for the GetDeclarationNameNull test. + * + * @see testGetDeclarationNameNull() + * + * @return array + */ + public function dataGetDeclarationNameNull() + { + return [ + [ + '/* testClosure */', + T_CLOSURE, + ], + [ + '/* testAnonClass */', + T_ANON_CLASS, + ], + ]; + + }//end dataGetDeclarationNameNull() + + + /** + * Test retrieving the name of a function or OO structure. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param string $expected Expected function output. + * + * @dataProvider dataGetDeclarationName + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::getDeclarationName + * + * @return void + */ + public function testGetDeclarationName($testMarker, $expected) + { + $target = $this->getTargetToken($testMarker, [T_CLASS, T_INTERFACE, T_TRAIT, T_FUNCTION]); + $result = ConstructNames::getDeclarationName(self::$phpcsFile, $target); + $this->assertSame($expected, $result); + + }//end testGetDeclarationName() + + + /** + * Data provider for the GetDeclarationName test. + * + * @see testGetDeclarationName() + * + * @return array + */ + public function dataGetDeclarationName() + { + return [ + [ + '/* testFunction */', + 'functionName', + ], + [ + '/* testClass */', + 'ClassName', + ], + [ + '/* testMethod */', + 'methodName', + ], + [ + '/* testAbstractMethod */', + 'abstractMethodName', + ], + [ + '/* testExtendedClass */', + 'ExtendedClass', + ], + [ + '/* testInterface */', + 'InterfaceName', + ], + [ + '/* testTrait */', + 'TraitName', + ], + [ + '/* testClassWithCommentsAndNewLines */', + 'ClassWithCommentsAndNewLines', + ], + [ + '/* testClassWithNumber */', + 'ClassWith1Number', + ], + [ + '/* testInterfaceWithNumbers */', + 'InterfaceWith12345Numbers', + ], + [ + '/* testTraitStartingWithNumber */', + '5InvalidNameStartingWithNumber', + ], + [ + '/* testClassEndingWithNumber */', + 'ValidNameEndingWithNumber5', + ], + [ + '/* testInterfaceFullyNumeric */', + '12345', + ], + [ + '/* testMissingInterfaceName */', + '', + ], + [ + '/* testLiveCoding */', + '', + ], + ]; + + }//end dataGetDeclarationName() + + +}//end class diff --git a/tests/Core/Util/Sniffs/ConstructNames/IsCamelCapsTest.php b/tests/Core/Util/Sniffs/ConstructNames/IsCamelCapsTest.php new file mode 100644 index 0000000000..d5399bea3a --- /dev/null +++ b/tests/Core/Util/Sniffs/ConstructNames/IsCamelCapsTest.php @@ -0,0 +1,149 @@ + + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\ConstructNames; + +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHPUnit\Framework\TestCase; + +class IsCamelCapsTest extends TestCase +{ + + + /** + * Test valid public function/method names. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::isCamelCaps + * + * @return void + */ + public function testValidNotClassFormatPublic() + { + $this->assertTrue(ConstructNames::isCamelCaps('thisIsCamelCaps', false, true, true)); + $this->assertTrue(ConstructNames::isCamelCaps('thisISCamelCaps', false, true, false)); + + }//end testValidNotClassFormatPublic() + + + /** + * Test invalid public function/method names. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::isCamelCaps + * + * @return void + */ + public function testInvalidNotClassFormatPublic() + { + $this->assertFalse(ConstructNames::isCamelCaps('_thisIsCamelCaps', false, true, true)); + $this->assertFalse(ConstructNames::isCamelCaps('thisISCamelCaps', false, true, true)); + $this->assertFalse(ConstructNames::isCamelCaps('ThisIsCamelCaps', false, true, true)); + + $this->assertFalse(ConstructNames::isCamelCaps('3thisIsCamelCaps', false, true, true)); + $this->assertFalse(ConstructNames::isCamelCaps('*thisIsCamelCaps', false, true, true)); + $this->assertFalse(ConstructNames::isCamelCaps('-thisIsCamelCaps', false, true, true)); + + $this->assertFalse(ConstructNames::isCamelCaps('this*IsCamelCaps', false, true, true)); + $this->assertFalse(ConstructNames::isCamelCaps('this-IsCamelCaps', false, true, true)); + $this->assertFalse(ConstructNames::isCamelCaps('this_IsCamelCaps', false, true, true)); + $this->assertFalse(ConstructNames::isCamelCaps('this_is_camel_caps', false, true, true)); + + }//end testInvalidNotClassFormatPublic() + + + /** + * Test valid private method names. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::isCamelCaps + * + * @return void + */ + public function testValidNotClassFormatPrivate() + { + $this->assertTrue(ConstructNames::isCamelCaps('_thisIsCamelCaps', false, false, true)); + $this->assertTrue(ConstructNames::isCamelCaps('_thisISCamelCaps', false, false, false)); + $this->assertTrue(ConstructNames::isCamelCaps('_i18N', false, false, true)); + $this->assertTrue(ConstructNames::isCamelCaps('_i18n', false, false, true)); + + }//end testValidNotClassFormatPrivate() + + + /** + * Test invalid private method names. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::isCamelCaps + * + * @return void + */ + public function testInvalidNotClassFormatPrivate() + { + $this->assertFalse(ConstructNames::isCamelCaps('thisIsCamelCaps', false, false, true)); + $this->assertFalse(ConstructNames::isCamelCaps('_thisISCamelCaps', false, false, true)); + $this->assertFalse(ConstructNames::isCamelCaps('_ThisIsCamelCaps', false, false, true)); + $this->assertFalse(ConstructNames::isCamelCaps('__thisIsCamelCaps', false, false, true)); + $this->assertFalse(ConstructNames::isCamelCaps('__thisISCamelCaps', false, false, false)); + + $this->assertFalse(ConstructNames::isCamelCaps('3thisIsCamelCaps', false, false, true)); + $this->assertFalse(ConstructNames::isCamelCaps('*thisIsCamelCaps', false, false, true)); + $this->assertFalse(ConstructNames::isCamelCaps('-thisIsCamelCaps', false, false, true)); + $this->assertFalse(ConstructNames::isCamelCaps('_this_is_camel_caps', false, false, true)); + + }//end testInvalidNotClassFormatPrivate() + + + /** + * Test valid class names. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::isCamelCaps + * + * @return void + */ + public function testValidClassFormatPublic() + { + $this->assertTrue(ConstructNames::isCamelCaps('ThisIsCamelCaps', true, true, true)); + $this->assertTrue(ConstructNames::isCamelCaps('ThisISCamelCaps', true, true, false)); + $this->assertTrue(ConstructNames::isCamelCaps('This3IsCamelCaps', true, true, false)); + + }//end testValidClassFormatPublic() + + + /** + * Test invalid class names. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::isCamelCaps + * + * @return void + */ + public function testInvalidClassFormat() + { + $this->assertFalse(ConstructNames::isCamelCaps('thisIsCamelCaps', true)); + $this->assertFalse(ConstructNames::isCamelCaps('This-IsCamelCaps', true)); + $this->assertFalse(ConstructNames::isCamelCaps('This_Is_Camel_Caps', true)); + + }//end testInvalidClassFormat() + + + /** + * Test invalid class names with the private flag set. + * + * Note that the private flag is ignored if the class format + * flag is set, so these names are all invalid. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::isCamelCaps + * + * @return void + */ + public function testInvalidClassFormatPrivate() + { + $this->assertFalse(ConstructNames::isCamelCaps('_ThisIsCamelCaps', true, true)); + $this->assertFalse(ConstructNames::isCamelCaps('_ThisIsCamelCaps', true, false)); + + }//end testInvalidClassFormatPrivate() + + +}//end class diff --git a/tests/Core/Util/Sniffs/ConstructNames/LowerConsecutiveCapsTest.php b/tests/Core/Util/Sniffs/ConstructNames/LowerConsecutiveCapsTest.php new file mode 100644 index 0000000000..9a72f17a2a --- /dev/null +++ b/tests/Core/Util/Sniffs/ConstructNames/LowerConsecutiveCapsTest.php @@ -0,0 +1,154 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\ConstructNames; + +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHPUnit\Framework\TestCase; + +class LowerConsecutiveCapsTest extends TestCase +{ + + + /** + * Test lowering consecutive caps in a text string. + * + * @param string $string The string. + * @param string $expected The expected function return value. + * + * @dataProvider dataLowerConsecutiveCaps + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::lowerConsecutiveCaps + * + * @return void + */ + public function testLowerConsecutiveCaps($string, $expected) + { + $this->assertSame($expected, ConstructNames::lowerConsecutiveCaps($string)); + + }//end testLowerConsecutiveCaps() + + + /** + * Data provider. + * + * @see testLowerConsecutiveCaps() + * + * @return array + */ + public function dataLowerConsecutiveCaps() + { + $data = [ + // Deliberately empty. + [ + '', + '', + ], + [ + 'nocaps', + 'nocaps', + ], + [ + 'noConsecutiveCaps', + 'noConsecutiveCaps', + ], + [ + 'IsAMethod', + 'IsAmethod', + ], + [ + 'IsThisAI', + 'IsThisAi', + ], + [ + 'IsThisAI20', + 'IsThisAi20', + ], + [ + 'Is_A_Method', + 'Is_A_Method', + ], + [ + 'PHPMethod', + 'PhpMethod', + ], + [ + 'PHP7Method', + 'Php7Method', + ], + [ + 'MyPHPMethod', + 'MyPhpMethod', + ], + [ + 'My_PHP_Method', + 'My_Php_Method', + ], + [ + 'MyMethodInPHP', + 'MyMethodInPhp', + ], + [ + 'MyMethodInPHP7', + 'MyMethodInPhp7', + ], + [ + 'My-CSS-Selector', + 'My-Css-Selector', + ], + [ + 'SomeCAPSAndMoreCAPSAndMORE', + 'SomeCapsAndMoreCapsAndMore', + ], + ]; + + $unicodeData = [ + // ASCII Extended. + [ + 'IÑTËRNÂTÎÔNÀLÍŽÆTIØN', + 'Iñtërnâtîônàlížætiøn', + ], + + // Russian, no consecutive caps. + [ + 'МояРабота', + 'МояРабота', + ], + + // Russian, consecutive caps. + [ + 'МОЯРабота', + 'МояРабота', + ], + + // Russian, consecutive caps with separator. + [ + 'МОЯ_Работа', + 'Моя_Работа', + ], + ]; + + // Add Unicode name testcases. + if (function_exists('mb_strtolower') === true) { + $data = array_merge($data, $unicodeData); + } else { + // If MBString is not available, non-ASCII input should be returned unchanged. + foreach ($unicodeData as $dataset) { + $data[] = [ + $dataset[0], + $dataset[0], + ]; + } + } + + return $data; + + }//end dataLowerConsecutiveCaps() + + +}//end class diff --git a/tests/Core/Util/Sniffs/ConstructNames/NumbersTest.php b/tests/Core/Util/Sniffs/ConstructNames/NumbersTest.php new file mode 100644 index 0000000000..eb7626c630 --- /dev/null +++ b/tests/Core/Util/Sniffs/ConstructNames/NumbersTest.php @@ -0,0 +1,181 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\ConstructNames; + +use PHP_CodeSniffer\Util\Sniffs\ConstructNames; +use PHPUnit\Framework\TestCase; + +class NumbersTest extends TestCase +{ + + + /** + * Test verifying whether a text string contains numeric characters. + * + * @param string $string Input string. + * @param array $expected Expected function output for the various functions. + * + * @dataProvider dataStringsWithNumbers + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::hasNumbers + * + * @return void + */ + public function testHasNumbers($string, $expected) + { + $this->assertSame($expected['has'], ConstructNames::hasNumbers($string)); + + }//end testHasNumbers() + + + /** + * Test trimming numbers from the beginning of a text string. + * + * @param string $string Input string. + * @param array $expected Expected function output for the various functions. + * + * @dataProvider dataStringsWithNumbers + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::ltrimNumbers + * + * @return void + */ + public function testLtrimNumbers($string, $expected) + { + $this->assertSame($expected['ltrim'], ConstructNames::ltrimNumbers($string)); + + }//end testLtrimNumbers() + + + /** + * Test removing all numbers from a text string. + * + * @param string $string Input string. + * @param array $expected Expected function output for the various functions. + * + * @dataProvider dataStringsWithNumbers + * @covers \PHP_CodeSniffer\Util\Sniffs\ConstructNames::removeNumbers + * + * @return void + */ + public function testRemoveNumbers($string, $expected) + { + $this->assertSame($expected['remove'], ConstructNames::removeNumbers($string)); + + }//end testRemoveNumbers() + + + /** + * Data provider. + * + * @see testHasNumbers() + * @see testLtrimNumbers() + * @see testRemoveNumbers() + * + * @return array + */ + public function dataStringsWithNumbers() + { + return [ + [ + // Deliberately empty. + '', + [ + 'has' => false, + 'ltrim' => '', + 'remove' => '', + ], + ], + [ + 'NoNumbers', + [ + 'has' => false, + 'ltrim' => 'NoNumbers', + 'remove' => 'NoNumbers', + ], + ], + [ + '1', + [ + 'has' => true, + 'ltrim' => '', + 'remove' => '', + ], + ], + [ + '1234567890', + [ + 'has' => true, + 'ltrim' => '', + 'remove' => '', + ], + ], + [ + '1Pancake', + [ + 'has' => true, + 'ltrim' => 'Pancake', + 'remove' => 'Pancake', + ], + ], + [ + '123Pancakes', + [ + 'has' => true, + 'ltrim' => 'Pancakes', + 'remove' => 'Pancakes', + ], + ], + [ + '1Pancake2Pancakes', + [ + 'has' => true, + 'ltrim' => 'Pancake2Pancakes', + 'remove' => 'PancakePancakes', + ], + ], + [ + '123Pancake456Pancakes789Pancakes', + [ + 'has' => true, + 'ltrim' => 'Pancake456Pancakes789Pancakes', + 'remove' => 'PancakePancakesPancakes', + ], + ], + [ + '½Pancake⅝Pancake', + [ + 'has' => true, + 'ltrim' => 'Pancake⅝Pancake', + 'remove' => 'PancakePancake', + ], + ], + [ + 'ⅦPancakesⅲPancakes', + [ + 'has' => true, + 'ltrim' => 'PancakesⅲPancakes', + 'remove' => 'PancakesPancakes', + ], + ], + [ + '๑٦⑱Pancake٨๔⓳Pancakes㊱௫Pancakes', + [ + 'has' => true, + 'ltrim' => 'Pancake٨๔⓳Pancakes㊱௫Pancakes', + 'remove' => 'PancakePancakesPancakes', + ], + ], + ]; + + }//end dataStringsWithNumbers() + + +}//end class diff --git a/tests/Core/Util/Sniffs/FunctionDeclarations/GetParametersTest.inc b/tests/Core/Util/Sniffs/FunctionDeclarations/GetParametersTest.inc new file mode 100644 index 0000000000..0bb5001380 --- /dev/null +++ b/tests/Core/Util/Sniffs/FunctionDeclarations/GetParametersTest.inc @@ -0,0 +1,64 @@ + + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\FunctionDeclarations; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; + +class GetParametersTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a non function token is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_FUNCTION or T_CLOSURE + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testNotAFunctionException() + { + $interface = $this->getTargetToken('/* testNotAFunction */', T_INTERFACE); + $result = FunctionDeclarations::getParameters(self::$phpcsFile, $interface); + + }//end testNotAFunctionException() + + + /** + * Verify function declaration without parameters. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testFunctionNoParams() + { + $expected = []; + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testFunctionNoParams() + + + /** + * Verify pass-by-reference parsing. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testPassByReference() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => '&$var', + 'pass_by_reference' => true, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPassByReference() + + + /** + * Verify array hint parsing. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testArrayHint() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'array $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'array', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testArrayHint() + + + /** + * Verify type hint parsing. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testTypeHint() + { + $expected = []; + $expected[0] = [ + 'name' => '$var1', + 'content' => 'foo $var1', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'foo', + 'nullable_type' => false, + ]; + + $expected[1] = [ + 'name' => '$var2', + 'content' => 'bar $var2', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'bar', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testTypeHint() + + + /** + * Verify self type hint parsing. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testSelfTypeHint() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'self $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'self', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testSelfTypeHint() + + + /** + * Verify callable type hint parsing. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testCallableTypeHint() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'callable $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'callable', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testCallableTypeHint() + + + /** + * Verify nullable callable type hint parsing. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testNullableCallableTypeHint() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => '?callable $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '?callable', + 'nullable_type' => true, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testNullableCallableTypeHint() + + + /** + * Verify nullable type hint parsing. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testNullableTypeHint() + { + $expected = []; + $expected[0] = [ + 'name' => '$var1', + 'content' => '?int $var1', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '?int', + 'nullable_type' => true, + ]; + + $expected[1] = [ + 'name' => '$var2', + 'content' => '?\bar $var2', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '?\bar', + 'nullable_type' => true, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testNullableTypeHint() + + + /** + * Verify iterable type hint parsing. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testIterableTypeHint() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'iterable $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'iterable', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testIterableTypeHint() + + + /** + * Verify variable. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testVariable() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => '$var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testVariable() + + + /** + * Verify default value parsing with a single function param. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testSingleDefaultValue() + { + $expected = []; + $expected[0] = [ + 'name' => '$var1', + 'content' => '$var1=self::CONSTANT', + 'default' => 'self::CONSTANT', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testSingleDefaultValue() + + + /** + * Verify default value parsing. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testDefaultValues() + { + $expected = []; + $expected[0] = [ + 'name' => '$var1', + 'content' => '$var1=1', + 'default' => '1', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + ]; + $expected[1] = [ + 'name' => '$var2', + 'content' => "\$var2='value'", + 'default' => "'value'", + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testDefaultValues() + + + /** + * Verify default value parsing with array values. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testArrayDefaultValues() + { + $expected = []; + $expected[0] = [ + 'name' => '$var1', + 'content' => '$var1 = []', + 'default' => '[]', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + ]; + $expected[1] = [ + 'name' => '$var2', + 'content' => '$var2 = array(1, 2, 3)', + 'default' => 'array(1, 2, 3)', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testArrayDefaultValues() + + + /** + * Verify having a T_STRING constant as a default value for the second parameter. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testConstantDefaultValueSecondParam() + { + $expected = []; + $expected[0] = [ + 'name' => '$var1', + 'content' => '$var1', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + ]; + $expected[1] = [ + 'name' => '$var2', + 'content' => '$var2 = M_PI', + 'default' => 'M_PI', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testConstantDefaultValueSecondParam() + + + /** + * Verify using ellipsis with a typehint. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testVariableLengthArgument() + { + $expected = []; + $expected[0] = [ + 'name' => '$unit', + 'content' => '$unit', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + ]; + $expected[1] = [ + 'name' => '$intervals', + 'content' => 'DateInterval ...$intervals', + 'pass_by_reference' => false, + 'variable_length' => true, + 'type_hint' => 'DateInterval', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testVariableLengthArgument() + + + /** + * Verify "bitwise and" in default value !== pass-by-reference. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testBitwiseAndConstantExpressionDefaultValue() + { + $expected = []; + $expected[0] = [ + 'name' => '$a', + 'content' => '$a = 10 & 20', + 'default' => '10 & 20', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testBitwiseAndConstantExpressionDefaultValue() + + + /** + * Verify a fully qualified class name being set as type declaration. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testFQCNTypeHint() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => '\MyNS\SubCat\MyClass $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '\MyNS\SubCat\MyClass', + 'nullable_type' => false, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testFQCNTypeHint() + + + /** + * Verify a fully qualified class name being set as type declaration interlaced + * with whitespace and comments. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getParameters + * + * @return void + */ + public function testFQCNTypeHintWithCommentsAndWhiteSpace() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => '?\MyNS /* comment */ + \SubCat // phpcs:ignore Standard.Cat.Sniff -- for reasons. + \MyClass $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '?\MyNS\SubCat\MyClass', + 'nullable_type' => true, + ]; + + $this->getParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testFQCNTypeHintWithCommentsAndWhiteSpace() + + + /** + * Test helper. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected function output. + * + * @return void + */ + private function getParametersTestHelper($testMarker, $expected) + { + $function = $this->getTargetToken($testMarker, [T_FUNCTION]); + $found = FunctionDeclarations::getParameters(self::$phpcsFile, $function); + + foreach ($found as $key => $value) { + unset($found[$key]['token'], $found[$key]['type_hint_token']); + } + + $this->assertSame($expected, $found); + + }//end getParametersTestHelper() + + +}//end class diff --git a/tests/Core/File/GetMethodPropertiesTest.inc b/tests/Core/Util/Sniffs/FunctionDeclarations/GetPropertiesTest.inc similarity index 91% rename from tests/Core/File/GetMethodPropertiesTest.inc rename to tests/Core/Util/Sniffs/FunctionDeclarations/GetPropertiesTest.inc index ced6c13f01..015cc0079f 100644 --- a/tests/Core/File/GetMethodPropertiesTest.inc +++ b/tests/Core/Util/Sniffs/FunctionDeclarations/GetPropertiesTest.inc @@ -1,12 +1,16 @@ - * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence */ -namespace PHP_CodeSniffer\Tests\Core\File; +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\FunctionDeclarations; -use PHP_CodeSniffer\Config; -use PHP_CodeSniffer\Ruleset; -use PHP_CodeSniffer\Files\DummyFile; -use PHPUnit\Framework\TestCase; +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; -class GetMethodPropertiesTest extends TestCase +class GetPropertiesTest extends AbstractMethodUnitTest { - /** - * The PHP_CodeSniffer_File object containing parsed contents of the test case file. - * - * @var \PHP_CodeSniffer\Files\File - */ - private $phpcsFile; - /** - * Initialize & tokenize PHP_CodeSniffer_File with code from the test case file. + * Test receiving an expected exception when a non function token is passed. * - * Methods used for these tests can be found in a test case file in the same - * directory and with the same name, using the .inc extension. + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_FUNCTION or T_CLOSURE * - * @return void - */ - public function setUp() - { - $config = new Config(); - $config->standards = ['Generic']; - - $ruleset = new Ruleset($config); - - $pathToTestFile = dirname(__FILE__).'/'.basename(__FILE__, '.php').'.inc'; - $this->phpcsFile = new DummyFile(file_get_contents($pathToTestFile), $ruleset, $config); - $this->phpcsFile->process(); - - }//end setUp() - - - /** - * Clean up after finished test. + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties * * @return void */ - public function tearDown() + public function testNotAFunctionException() { - unset($this->phpcsFile); + $interface = $this->getTargetToken('/* testNotAFunction */', T_INTERFACE); + $result = FunctionDeclarations::getProperties(self::$phpcsFile, $interface); - }//end tearDown() + }//end testNotAFunctionException() /** * Test a basic function. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testBasicFunction() @@ -77,18 +54,7 @@ public function testBasicFunction() 'has_body' => true, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testBasicFunction */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 2)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testBasicFunction() @@ -96,6 +62,8 @@ public function testBasicFunction() /** * Test a function with a return type. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testReturnFunction() @@ -111,18 +79,7 @@ public function testReturnFunction() 'has_body' => true, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testReturnFunction */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 2)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testReturnFunction() @@ -130,6 +87,8 @@ public function testReturnFunction() /** * Test a closure used as a function argument. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testNestedClosure() @@ -145,18 +104,7 @@ public function testNestedClosure() 'has_body' => true, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testNestedClosure */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 1)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testNestedClosure() @@ -164,6 +112,8 @@ public function testNestedClosure() /** * Test a basic method. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testBasicMethod() @@ -179,18 +129,7 @@ public function testBasicMethod() 'has_body' => true, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testBasicMethod */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 3)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testBasicMethod() @@ -198,6 +137,8 @@ public function testBasicMethod() /** * Test a private static method. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testPrivateStaticMethod() @@ -213,18 +154,7 @@ public function testPrivateStaticMethod() 'has_body' => true, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testPrivateStaticMethod */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 7)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testPrivateStaticMethod() @@ -232,6 +162,8 @@ public function testPrivateStaticMethod() /** * Test a basic final method. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testFinalMethod() @@ -247,18 +179,7 @@ public function testFinalMethod() 'has_body' => true, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testFinalMethod */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 7)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testFinalMethod() @@ -266,6 +187,8 @@ public function testFinalMethod() /** * Test a protected method with a return type. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testProtectedReturnMethod() @@ -281,18 +204,7 @@ public function testProtectedReturnMethod() 'has_body' => true, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testProtectedReturnMethod */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 5)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testProtectedReturnMethod() @@ -300,6 +212,8 @@ public function testProtectedReturnMethod() /** * Test a public method with a return type. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testPublicReturnMethod() @@ -315,18 +229,7 @@ public function testPublicReturnMethod() 'has_body' => true, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testPublicReturnMethod */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 5)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testPublicReturnMethod() @@ -334,6 +237,8 @@ public function testPublicReturnMethod() /** * Test a public method with a nullable return type. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testNullableReturnMethod() @@ -349,18 +254,7 @@ public function testNullableReturnMethod() 'has_body' => true, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testNullableReturnMethod */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 5)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testNullableReturnMethod() @@ -368,6 +262,8 @@ public function testNullableReturnMethod() /** * Test a public method with a nullable return type. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testMessyNullableReturnMethod() @@ -383,18 +279,7 @@ public function testMessyNullableReturnMethod() 'has_body' => true, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testMessyNullableReturnMethod */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 5)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testMessyNullableReturnMethod() @@ -402,6 +287,8 @@ public function testMessyNullableReturnMethod() /** * Test a method with a namespaced return type. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testReturnNamespace() @@ -417,18 +304,7 @@ public function testReturnNamespace() 'has_body' => true, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testReturnNamespace */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 3)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testReturnNamespace() @@ -436,6 +312,8 @@ public function testReturnNamespace() /** * Test a method with a messy namespaces return type. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testReturnMultilineNamespace() @@ -451,18 +329,7 @@ public function testReturnMultilineNamespace() 'has_body' => true, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testReturnMultilineNamespace */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 3)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testReturnMultilineNamespace() @@ -470,6 +337,8 @@ public function testReturnMultilineNamespace() /** * Test a basic abstract method. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testAbstractMethod() @@ -485,18 +354,7 @@ public function testAbstractMethod() 'has_body' => false, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testAbstractMethod */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 5)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testAbstractMethod() @@ -504,6 +362,8 @@ public function testAbstractMethod() /** * Test an abstract method with a return type. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testAbstractReturnMethod() @@ -519,18 +379,7 @@ public function testAbstractReturnMethod() 'has_body' => false, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testAbstractReturnMethod */' - ); - - $found = $this->phpcsFile->getMethodProperties(($function + 7)); - unset($found['return_type_token']); - $this->assertSame($expected, $found); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); }//end testAbstractReturnMethod() @@ -538,6 +387,8 @@ public function testAbstractReturnMethod() /** * Test a basic interface method. * + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::getProperties + * * @return void */ public function testInterfaceMethod() @@ -553,20 +404,27 @@ public function testInterfaceMethod() 'has_body' => false, ]; - $start = ($this->phpcsFile->numTokens - 1); - $function = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testInterfaceMethod */' - ); + $this->getPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); - $found = $this->phpcsFile->getMethodProperties(($function + 3)); + }//end testInterfaceMethod() + + + /** + * Test helper. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected function output. + * + * @return void + */ + private function getPropertiesTestHelper($testMarker, $expected) + { + $function = $this->getTargetToken($testMarker, [T_FUNCTION, T_CLOSURE]); + $found = FunctionDeclarations::getProperties(self::$phpcsFile, $function); unset($found['return_type_token']); $this->assertSame($expected, $found); - }//end testInterfaceMethod() + }//end getPropertiesTestHelper() }//end class diff --git a/tests/Core/Util/Sniffs/FunctionDeclarations/IsMagicFunctionNameTest.php b/tests/Core/Util/Sniffs/FunctionDeclarations/IsMagicFunctionNameTest.php new file mode 100644 index 0000000000..b75997c055 --- /dev/null +++ b/tests/Core/Util/Sniffs/FunctionDeclarations/IsMagicFunctionNameTest.php @@ -0,0 +1,90 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\FunctionDeclarations; + +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; +use PHPUnit\Framework\TestCase; + +class IsMagicFunctionNameTest extends TestCase +{ + + + /** + * Test valid PHP magic function names. + * + * @param string $name The function name to test. + * + * @dataProvider dataIsMagicFunctionName + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isMagicFunctionName + * + * @return void + */ + public function testIsMagicFunctionName($name) + { + $this->assertTrue(FunctionDeclarations::isMagicFunctionName($name)); + + }//end testIsMagicFunctionName() + + + /** + * Data provider. + * + * @see testIsMagicFunctionName() + * + * @return array + */ + public function dataIsMagicFunctionName() + { + return [ + 'lowercase' => ['__autoload'], + 'uppercase' => ['__AUTOLOAD'], + 'mixedcase' => ['__AutoLoad'], + ]; + + }//end dataIsMagicFunctionName() + + + /** + * Test non-magic function names. + * + * @param string $name The function name to test. + * + * @dataProvider dataIsMagicFunctionNameFalse + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isMagicFunctionName + * + * @return void + */ + public function testIsMagicFunctionNameFalse($name) + { + $this->assertFalse(FunctionDeclarations::isMagicFunctionName($name)); + + }//end testIsMagicFunctionNameFalse() + + + /** + * Data provider. + * + * @see testIsMagicFunctionNameFalse() + * + * @return array + */ + public function dataIsMagicFunctionNameFalse() + { + return [ + 'no_underscore' => ['noDoubleUnderscore'], + 'single_underscore' => ['_autoload'], + 'triple_underscore' => ['___autoload'], + 'not_magic_function_name' => ['__notAutoload'], + ]; + + }//end dataIsMagicFunctionNameFalse() + + +}//end class diff --git a/tests/Core/Util/Sniffs/FunctionDeclarations/IsMagicMethodNameTest.php b/tests/Core/Util/Sniffs/FunctionDeclarations/IsMagicMethodNameTest.php new file mode 100644 index 0000000000..8095d753a0 --- /dev/null +++ b/tests/Core/Util/Sniffs/FunctionDeclarations/IsMagicMethodNameTest.php @@ -0,0 +1,154 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\FunctionDeclarations; + +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; +use PHPUnit\Framework\TestCase; + +class IsMagicMethodNameTest extends TestCase +{ + + + /** + * Test valid PHP magic method names. + * + * @param string $name The function name to test. + * + * @dataProvider dataIsMagicMethodName + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isMagicMethodName + * + * @return void + */ + public function testIsMagicMethodName($name) + { + $this->assertTrue(FunctionDeclarations::isMagicMethodName($name)); + + }//end testIsMagicMethodName() + + + /** + * Test valid PHP magic method names. + * + * @param string $name The function name to test. + * + * @dataProvider dataIsMagicMethodName + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isSpecialMethodName + * + * @return void + */ + public function testIsSpecialMethodName($name) + { + $this->assertTrue(FunctionDeclarations::isSpecialMethodName($name)); + + }//end testIsSpecialMethodName() + + + /** + * Data provider. + * + * @see testIsMagicMethodName() + * + * @return array + */ + public function dataIsMagicMethodName() + { + return [ + // Normal case. + ['__construct'], + ['__destruct'], + ['__call'], + ['__callStatic'], + ['__get'], + ['__set'], + ['__isset'], + ['__unset'], + ['__sleep'], + ['__wakeup'], + ['__toString'], + ['__set_state'], + ['__clone'], + ['__invoke'], + ['__debugInfo'], + + // Uppercase et al. + ['__CONSTRUCT'], + ['__Destruct'], + ['__Call'], + ['__callstatic'], + ['__GET'], + ['__SeT'], + ['__isSet'], + ['__unSet'], + ['__SleeP'], + ['__wakeUp'], + ['__TOString'], + ['__Set_State'], + ['__CLONE'], + ['__Invoke'], + ['__Debuginfo'], + ]; + + }//end dataIsMagicMethodName() + + + /** + * Test non-magic method names. + * + * @param string $name The function name to test. + * + * @dataProvider dataIsMagicMethodNameFalse + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isMagicMethodName + * + * @return void + */ + public function testIsMagicMethodNameFalse($name) + { + $this->assertFalse(FunctionDeclarations::isMagicMethodName($name)); + + }//end testIsMagicMethodNameFalse() + + + /** + * Test non-magic method names. + * + * @param string $name The function name to test. + * + * @dataProvider dataIsMagicMethodNameFalse + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isSpecialMethodName + * + * @return void + */ + public function testIsSpecialMethodNameFalse($name) + { + $this->assertFalse(FunctionDeclarations::isSpecialMethodName($name)); + + }//end testIsSpecialMethodNameFalse() + + + /** + * Data provider. + * + * @see testIsMagicMethodNameFalse() + * + * @return array + */ + public function dataIsMagicMethodNameFalse() + { + return [ + 'no_underscore' => ['construct'], + 'single_underscore' => ['_destruct'], + 'triple_underscore' => ['___call'], + 'not_magic_method_name' => ['__myFunction'], + ]; + + }//end dataIsMagicMethodNameFalse() + + +}//end class diff --git a/tests/Core/Util/Sniffs/FunctionDeclarations/IsPHPDoubleUnderscoreMethodNameTest.php b/tests/Core/Util/Sniffs/FunctionDeclarations/IsPHPDoubleUnderscoreMethodNameTest.php new file mode 100644 index 0000000000..1c8e183612 --- /dev/null +++ b/tests/Core/Util/Sniffs/FunctionDeclarations/IsPHPDoubleUnderscoreMethodNameTest.php @@ -0,0 +1,148 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\FunctionDeclarations; + +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; +use PHPUnit\Framework\TestCase; + +class IsPHPDoubleUnderscoreMethodNameTest extends TestCase +{ + + + /** + * Test valid PHP native double underscore method names. + * + * @param string $name The function name to test. + * + * @dataProvider dataIsPHPDoubleUnderscoreMethodName + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isPHPDoubleUnderscoreMethodName + * + * @return void + */ + public function testIsPHPDoubleUnderscoreMethodName($name) + { + $this->assertTrue(FunctionDeclarations::isPHPDoubleUnderscoreMethodName($name)); + + }//end testIsPHPDoubleUnderscoreMethodName() + + + /** + * Test valid PHP native double underscore method names. + * + * @param string $name The function name to test. + * + * @dataProvider dataIsPHPDoubleUnderscoreMethodName + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isSpecialMethodName + * + * @return void + */ + public function testIsSpecialMethodName($name) + { + $this->assertTrue(FunctionDeclarations::isSpecialMethodName($name)); + + }//end testIsSpecialMethodName() + + + /** + * Data provider. + * + * @see testIsPHPDoubleUnderscoreMethodName() + * + * @return array + */ + public function dataIsPHPDoubleUnderscoreMethodName() + { + return [ + // Normal case. + ['__doRequest'], + ['__getCookies'], + ['__getFunctions'], + ['__getLastRequest'], + ['__getLastRequestHeaders'], + ['__getLastResponse'], + ['__getLastResponseHeaders'], + ['__getTypes'], + ['__setCookie'], + ['__setLocation'], + ['__setSoapHeaders'], + ['__soapCall'], + + // Uppercase et al. + ['__DOREQUEST'], + ['__getcookies'], + ['__Getfunctions'], + ['__GETLASTREQUEST'], + ['__getlastrequestheaders'], + ['__GetlastResponse'], + ['__GETLASTRESPONSEHEADERS'], + ['__GetTypes'], + ['__SETCookie'], + ['__sETlOCATION'], + ['__SetSOAPHeaders'], + ['__SOAPCall'], + ]; + + }//end dataIsPHPDoubleUnderscoreMethodName() + + + /** + * Test function names which are not valid PHP native double underscore methods. + * + * @param string $name The function name to test. + * + * @dataProvider dataIsPHPDoubleUnderscoreMethodNameFalse + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isPHPDoubleUnderscoreMethodName + * + * @return void + */ + public function testIsPHPDoubleUnderscoreMethodNameFalse($name) + { + $this->assertFalse(FunctionDeclarations::isPHPDoubleUnderscoreMethodName($name)); + + }//end testIsPHPDoubleUnderscoreMethodNameFalse() + + + /** + * Test function names which are not valid PHP native double underscore methods. + * + * @param string $name The function name to test. + * + * @dataProvider dataIsPHPDoubleUnderscoreMethodNameFalse + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isSpecialMethodName + * + * @return void + */ + public function testIsSpecialMethodNameFalse($name) + { + $this->assertFalse(FunctionDeclarations::isSpecialMethodName($name)); + + }//end testIsSpecialMethodNameFalse() + + + /** + * Data provider. + * + * @see testIsPHPDoubleUnderscoreMethodNameFalse() + * + * @return array + */ + public function dataIsPHPDoubleUnderscoreMethodNameFalse() + { + return [ + 'no_underscore' => ['getLastResponseHeaders'], + 'single_underscore' => ['_setLocation'], + 'triple_underscore' => ['___getCookies'], + 'not_magic_function_name' => ['__getFirstRequestHeader'], + ]; + + }//end dataIsPHPDoubleUnderscoreMethodNameFalse() + + +}//end class diff --git a/tests/Core/Util/Sniffs/FunctionDeclarations/SpecialFunctionsTest.inc b/tests/Core/Util/Sniffs/FunctionDeclarations/SpecialFunctionsTest.inc new file mode 100644 index 0000000000..a52f0c5fdb --- /dev/null +++ b/tests/Core/Util/Sniffs/FunctionDeclarations/SpecialFunctionsTest.inc @@ -0,0 +1,82 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\FunctionDeclarations; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations; + +class SpecialFunctionsTest extends AbstractMethodUnitTest +{ + + + /** + * Test correctly detecting magic methods. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected return values for the various functions. + * + * @dataProvider dataItsAKindOfMagic + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isMagicMethod + * + * @return void + */ + public function testIsMagicMethod($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_FUNCTION); + $result = FunctionDeclarations::isMagicMethod(self::$phpcsFile, $stackPtr); + $this->assertSame($expected['method'], $result); + + }//end testIsMagicMethod() + + + /** + * Test correctly detecting PHP native double underscore methods. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected return values for the various functions. + * + * @dataProvider dataItsAKindOfMagic + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isPHPDoubleUnderscoreMethod + * + * @return void + */ + public function testIsPHPDoubleUnderscoreMethod($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_FUNCTION); + $result = FunctionDeclarations::isPHPDoubleUnderscoreMethod(self::$phpcsFile, $stackPtr); + $this->assertSame($expected['double'], $result); + + }//end testIsPHPDoubleUnderscoreMethod() + + + /** + * Test correctly detecting magic functions. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected return values for the various functions. + * + * @dataProvider dataItsAKindOfMagic + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isMagicFunction + * + * @return void + */ + public function testIsMagicFunction($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_FUNCTION); + $result = FunctionDeclarations::isMagicFunction(self::$phpcsFile, $stackPtr); + $this->assertSame($expected['function'], $result); + + }//end testIsMagicFunction() + + + /** + * Test correctly detecting magic methods and double underscore methods. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected return values for the various functions. + * + * @dataProvider dataItsAKindOfMagic + * @covers \PHP_CodeSniffer\Util\Sniffs\FunctionDeclarations::isSpecialMethod + * + * @return void + */ + public function testIsSpecialMethod($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_FUNCTION); + $result = FunctionDeclarations::isSpecialMethod(self::$phpcsFile, $stackPtr); + $this->assertSame($expected['special'], $result); + + }//end testIsSpecialMethod() + + + /** + * Data provider. + * + * @see testIsMagicMethod() + * @see testIsPHPDoubleUnderscoreMethod() + * @see testIsMagicFunction() + * @see testIsSpecialMethod() + * + * @return array + */ + public function dataItsAKindOfMagic() + { + return [ + [ + '/* testMagicMethodInClass */', + [ + 'method' => true, + 'double' => false, + 'function' => false, + 'special' => true, + ], + ], + [ + '/* testMagicMethodInClassUppercase */', + [ + 'method' => true, + 'double' => false, + 'function' => false, + 'special' => true, + ], + ], + [ + '/* testMagicMethodInClassMixedCase */', + [ + 'method' => true, + 'double' => false, + 'function' => false, + 'special' => true, + ], + ], + [ + '/* testMagicFunctionInClassNotGlobal */', + [ + 'method' => false, + 'double' => false, + 'function' => false, + 'special' => false, + ], + ], + [ + '/* testMethodInClassNotMagicName */', + [ + 'method' => false, + 'double' => false, + 'function' => false, + 'special' => false, + ], + ], + [ + '/* testMagicMethodNotInClass */', + [ + 'method' => false, + 'double' => false, + 'function' => false, + 'special' => false, + ], + ], + [ + '/* testMagicFunction */', + [ + 'method' => false, + 'double' => false, + 'function' => true, + 'special' => false, + ], + ], + [ + '/* testMagicFunctionInConditionMixedCase */', + [ + 'method' => false, + 'double' => false, + 'function' => true, + 'special' => false, + ], + ], + [ + '/* testFunctionNotMagicName */', + [ + 'method' => false, + 'double' => false, + 'function' => false, + 'special' => false, + ], + ], + [ + '/* testMagicMethodInAnonClass */', + [ + 'method' => true, + 'double' => false, + 'function' => false, + 'special' => true, + ], + ], + [ + '/* testMagicMethodInAnonClassUppercase */', + [ + 'method' => true, + 'double' => false, + 'function' => false, + 'special' => true, + ], + ], + [ + '/* testMagicFunctionInAnonClassNotGlobal */', + [ + 'method' => false, + 'double' => false, + 'function' => false, + 'special' => false, + ], + ], + [ + '/* testMethodInAnonClassNotMagicName */', + [ + 'method' => false, + 'double' => false, + 'function' => false, + 'special' => false, + ], + ], + [ + '/* testDoubleUnderscoreMethodInClass */', + [ + 'method' => false, + 'double' => true, + 'function' => false, + 'special' => true, + ], + ], + [ + '/* testDoubleUnderscoreMethodInClassMixedcase */', + [ + 'method' => false, + 'double' => true, + 'function' => false, + 'special' => true, + ], + ], + [ + '/* testDoubleUnderscoreMethodNotInClass */', + [ + 'method' => false, + 'double' => false, + 'function' => false, + 'special' => false, + ], + ], + [ + '/* testMagicMethodInTrait */', + [ + 'method' => true, + 'double' => false, + 'function' => false, + 'special' => true, + ], + ], + [ + '/* testMagicFunctionInTraitNotGlobal */', + [ + 'method' => false, + 'double' => false, + 'function' => false, + 'special' => false, + ], + ], + [ + '/* testMethodInTraitNotMagicName */', + [ + 'method' => false, + 'double' => false, + 'function' => false, + 'special' => false, + ], + ], + [ + '/* testMagicMethodInInterface */', + [ + 'method' => true, + 'double' => false, + 'function' => false, + 'special' => true, + ], + ], + [ + '/* testMagicFunctionInInterfaceNotGlobal */', + [ + 'method' => false, + 'double' => false, + 'function' => false, + 'special' => false, + ], + ], + [ + '/* testMethodInInterfaceNotMagicName */', + [ + 'method' => false, + 'double' => false, + 'function' => false, + 'special' => false, + ], + ], + ]; + + }//end dataItsAKindOfMagic() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Namespaces/DetermineNamespaceTest.inc b/tests/Core/Util/Sniffs/Namespaces/DetermineNamespaceTest.inc new file mode 100644 index 0000000000..e12003615d --- /dev/null +++ b/tests/Core/Util/Sniffs/Namespaces/DetermineNamespaceTest.inc @@ -0,0 +1,119 @@ + + * @copyright 2017-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Namespaces; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Namespaces; + +class DetermineNamespaceTest extends AbstractMethodUnitTest +{ + + + /** + * Test that false is returned when an invalid token is passed. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::findNamespacePtr + * + * @return void + */ + public function testInvalidTokenPassed() + { + $this->assertFalse(Namespaces::findNamespacePtr(self::$phpcsFile, 100000)); + + }//end testInvalidTokenPassed() + + + /** + * Test finding the correct namespace token for an arbitrary token in a file. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected output for the functions. + * + * @dataProvider dataDetermineNamespace + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::findNamespacePtr + * + * @return void + */ + public function testFindNamespacePtr($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_ECHO); + + if ($expected['ptr'] !== false) { + $expected['ptr'] = $this->getTargetToken($expected['ptr'], T_NAMESPACE); + } + + $result = Namespaces::findNamespacePtr(self::$phpcsFile, $stackPtr); + + $this->assertSame($expected['ptr'], $result); + + }//end testFindNamespacePtr() + + + /** + * Test retrieving the applicabe namespace name for an arbitrary token in a file. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected output for the functions. + * + * @dataProvider dataDetermineNamespace + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::determineNamespace + * + * @return void + */ + public function testDetermineNamespace($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_ECHO); + $result = Namespaces::determineNamespace(self::$phpcsFile, $stackPtr); + + $this->assertSame($expected['name'], $result); + + }//end testDetermineNamespace() + + + /** + * Data provider. + * + * @see testDetermineNamespace() + * + * @return array + */ + public function dataDetermineNamespace() + { + return [ + [ + '/* testNoNamespace */', + [ + 'ptr' => false, + 'name' => '', + ], + ], + [ + '/* testNoNamespaceNested */', + [ + 'ptr' => false, + 'name' => '', + ], + ], + [ + '/* testNonScopedNamedNamespace1 */', + [ + 'ptr' => '/* Non-scoped named namespace 1 */', + 'name' => 'Vendor\Package\Baz', + ], + ], + [ + '/* testNonScopedNamedNamespace1Nested */', + [ + 'ptr' => '/* Non-scoped named namespace 1 */', + 'name' => 'Vendor\Package\Baz', + ], + ], + [ + '/* testGlobalNamespaceScoped */', + [ + 'ptr' => '/* Scoped global namespace */', + 'name' => '', + ], + ], + [ + '/* testGlobalNamespaceScopedNested */', + [ + 'ptr' => '/* Scoped global namespace */', + 'name' => '', + ], + ], + [ + '/* testNoNamespaceAfterScoped */', + [ + 'ptr' => false, + 'name' => '', + ], + ], + [ + '/* testNoNamespaceNestedAfterScoped */', + [ + 'ptr' => false, + 'name' => '', + ], + ], + [ + '/* testNamedNamespaceScoped */', + [ + 'ptr' => '/* Scoped named namespace */', + 'name' => 'Vendor\Package\Foo', + ], + ], + [ + '/* testNamedNamespaceScopedNested */', + [ + 'ptr' => '/* Scoped named namespace */', + 'name' => 'Vendor\Package\Foo', + ], + ], + [ + '/* testNonScopedGlobalNamespace */', + [ + 'ptr' => '/* Non-scoped global namespace */', + 'name' => '', + ], + ], + [ + '/* testNonScopedGlobalNamespaceNested */', + [ + 'ptr' => '/* Non-scoped global namespace */', + 'name' => '', + ], + ], + [ + '/* testNonScopedNamedNamespace2 */', + [ + 'ptr' => '/* Non-scoped named namespace 2 */', + 'name' => 'Vendor\Package\Foz', + ], + ], + [ + '/* testNonScopedNamedNamespace2Nested */', + [ + 'ptr' => '/* Non-scoped named namespace 2 */', + 'name' => 'Vendor\Package\Foz', + ], + ], + ]; + + }//end dataDetermineNamespace() + + + /** + * Test returning an empty string if the namespace could not be determined (parse error). + * + * @dataProvider dataDetermineNamespace + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::findNamespacePtr + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::determineNamespace + * + * @return void + */ + public function testFallbackToEmptyString() + { + $stackPtr = $this->getTargetToken('/* testParseError */', T_COMMENT, '/* comment */'); + + $expected = $this->getTargetToken('/* testParseError */', T_NAMESPACE); + $result = Namespaces::findNamespacePtr(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + $result = Namespaces::determineNamespace(self::$phpcsFile, $stackPtr, false); + $this->assertSame('', $result); + + }//end testFallbackToEmptyString() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Namespaces/GetDeclaredNameTest.inc b/tests/Core/Util/Sniffs/Namespaces/GetDeclaredNameTest.inc new file mode 100644 index 0000000000..b202d4dda6 --- /dev/null +++ b/tests/Core/Util/Sniffs/Namespaces/GetDeclaredNameTest.inc @@ -0,0 +1,52 @@ + + + + + * @copyright 2017-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Namespaces; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Namespaces; + +class GetDeclaredNameTest extends AbstractMethodUnitTest +{ + + + /** + * Test that false is returned when an invalid token is passed. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::getDeclaredName + * + * @return void + */ + public function testInvalidTokenPassed() + { + // Non-existent token. + $this->assertFalse(Namespaces::getDeclaredName(self::$phpcsFile, 100000)); + + // Non namespace token. + $this->assertFalse(Namespaces::getDeclaredName(self::$phpcsFile, 0)); + + }//end testInvalidTokenPassed() + + + /** + * Test retrieving the cleaned up namespace name based on a T_NAMESPACE token. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected output for the function. + * + * @dataProvider dataGetDeclaredName + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::getDeclaredName + * + * @return void + */ + public function testGetDeclaredNameClean($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_NAMESPACE); + $result = Namespaces::getDeclaredName(self::$phpcsFile, $stackPtr, true); + + $this->assertSame($expected['clean'], $result); + + }//end testGetDeclaredNameClean() + + + /** + * Test retrieving the "dirty" namespace name based on a T_NAMESPACE token. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected output for the function. + * + * @dataProvider dataGetDeclaredName + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::getDeclaredName + * + * @return void + */ + public function testGetDeclaredNameDirty($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_NAMESPACE); + $result = Namespaces::getDeclaredName(self::$phpcsFile, $stackPtr, false); + + $this->assertSame($expected['dirty'], $result); + + }//end testGetDeclaredNameDirty() + + + /** + * Data provider. + * + * @see testGetDeclaredName() + * + * @return array + */ + public function dataGetDeclaredName() + { + return [ + [ + '/* testNamespaceOperator */', + [ + 'clean' => false, + 'dirty' => false, + ], + ], + [ + '/* testGlobalNamespaceSemiColon */', + [ + 'clean' => '', + 'dirty' => '', + ], + ], + [ + '/* testGlobalNamespaceCurlies */', + [ + 'clean' => '', + 'dirty' => '', + ], + ], + [ + '/* testGlobalNamespaceCloseTag */', + [ + 'clean' => '', + 'dirty' => '', + ], + ], + [ + '/* testNamespaceSemiColon */', + [ + 'clean' => 'Vendor', + 'dirty' => 'Vendor', + ], + ], + [ + '/* testNamespaceCurlies */', + [ + 'clean' => 'Vendor\Package\Sublevel\Deeperlevel\End', + 'dirty' => 'Vendor\Package\Sublevel\Deeperlevel\End', + ], + ], + [ + '/* testNamespaceCurliesNoSpaceAtEnd */', + [ + 'clean' => 'Vendor\Package\Sublevel\Deeperlevel\End', + 'dirty' => 'Vendor\Package\Sublevel\Deeperlevel\End', + ], + ], + [ + '/* testNamespaceCloseTag */', + [ + 'clean' => 'PHP_CodeSniffer\Exceptions\RuntimeException', + 'dirty' => 'PHP_CodeSniffer\Exceptions\RuntimeException', + ], + ], + [ + '/* testNamespaceCloseTagNoSpaceAtEnd */', + [ + 'clean' => 'PHP_CodeSniffer\Exceptions\RuntimeException', + 'dirty' => 'PHP_CodeSniffer\Exceptions\RuntimeException', + ], + ], + [ + '/* testNamespaceLotsOfWhitespace */', + [ + 'clean' => 'Vendor\Package\Sublevel\Deeperlevel\End', + 'dirty' => 'Vendor \ + Package\ + Sublevel \ + Deeperlevel\ + End', + ], + ], + [ + '/* testNamespaceWithCommentsWhitespaceAndAnnotations */', + [ + 'clean' => 'Vendor\Package\Sublevel\Deeperlevel\End', + 'dirty' => 'Vendor\/*comment*/ + Package\Sublevel \ //phpcs:ignore Standard.Category.Sniff -- for reasons. + Deeperlevel\ // Another comment + End', + ], + ], + [ + '/* testNamespaceParseError */', + [ + 'clean' => 'Vendor\while\Package\protected\name\try\this', + 'dirty' => 'Vendor\while\Package\protected\name\try\this', + ], + ], + [ + '/* testLiveCoding */', + [ + 'clean' => false, + 'dirty' => false, + ], + ], + ]; + + }//end dataGetDeclaredName() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Namespaces/NamespaceTypeTest.inc b/tests/Core/Util/Sniffs/Namespaces/NamespaceTypeTest.inc new file mode 100644 index 0000000000..1f54b0580d --- /dev/null +++ b/tests/Core/Util/Sniffs/Namespaces/NamespaceTypeTest.inc @@ -0,0 +1,26 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Namespaces; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Namespaces; + +class NamespaceTypeTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when passing a non-existent token pointer. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_NAMESPACE + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::getType + * + * @return void + */ + public function testNonExistentToken() + { + $result = Namespaces::getType(self::$phpcsFile, 100000); + + }//end testNonExistentToken() + + + /** + * Test receiving an expected exception when passing a non T_NAMESPACE token. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_NAMESPACE + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::getType + * + * @return void + */ + public function testNonNamespaceToken() + { + $result = Namespaces::getType(self::$phpcsFile, 0); + + }//end testNonNamespaceToken() + + + /** + * Test whether a T_NAMESPACE token is used as the keyword for a namespace declaration. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected output for the functions. + * + * @dataProvider dataNamespaceType + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::isDeclaration + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::getType + * + * @return void + */ + public function testIsDeclaration($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_NAMESPACE); + $result = Namespaces::isDeclaration(self::$phpcsFile, $stackPtr); + + $this->assertSame($expected['declaration'], $result); + + }//end testIsDeclaration() + + + /** + * Test whether a T_NAMESPACE token is used as an operator. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected output for the functions. + * + * @dataProvider dataNamespaceType + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::isOperator + * @covers \PHP_CodeSniffer\Util\Sniffs\Namespaces::getType + * + * @return void + */ + public function testIsOperator($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_NAMESPACE); + $result = Namespaces::isOperator(self::$phpcsFile, $stackPtr); + + $this->assertSame($expected['operator'], $result); + + }//end testIsOperator() + + + /** + * Data provider. + * + * @see testIsDeclaration() + * @see testIsOperator() + * + * @return array + */ + public function dataNamespaceType() + { + return [ + [ + '/* testNamespaceDeclaration */', + [ + 'declaration' => true, + 'operator' => false, + ], + ], + [ + '/* testNamespaceDeclarationWithComment */', + [ + 'declaration' => true, + 'operator' => false, + ], + ], + [ + '/* testNamespaceDeclarationScoped */', + [ + 'declaration' => true, + 'operator' => false, + ], + ], + [ + '/* testNamespaceOperator */', + [ + 'declaration' => false, + 'operator' => true, + ], + ], + [ + '/* testNamespaceOperatorWithAnnotation */', + [ + 'declaration' => false, + 'operator' => true, + ], + ], + [ + '/* testParseError */', + [ + 'declaration' => false, + 'operator' => false, + ], + ], + [ + '/* testLiveCoding */', + [ + 'declaration' => false, + 'operator' => false, + ], + ], + ]; + + }//end dataNamespaceType() + + +}//end class diff --git a/tests/Core/File/FindExtendedClassNameTest.inc b/tests/Core/Util/Sniffs/ObjectDeclarations/FindExtendedClassNameTest.inc similarity index 73% rename from tests/Core/File/FindExtendedClassNameTest.inc rename to tests/Core/Util/Sniffs/ObjectDeclarations/FindExtendedClassNameTest.inc index aead06cd9d..7f2a3b1925 100644 --- a/tests/Core/File/FindExtendedClassNameTest.inc +++ b/tests/Core/Util/Sniffs/ObjectDeclarations/FindExtendedClassNameTest.inc @@ -1,6 +1,6 @@ + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\ObjectDeclarations; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations; + +class FindExtendedClassNameTest extends AbstractMethodUnitTest +{ + + + /** + * Test retrieving the name of the class being extended by another class + * (or interface). + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param string|false $expected Expected function output. + * + * @dataProvider dataExtendedClass + * @covers \PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations::findExtendedClassName + * @covers \PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations::findExtendedImplemented + * + * @return void + */ + public function testFindExtendedClassName($testMarker, $expected) + { + $OOToken = $this->getTargetToken($testMarker, [T_CLASS, T_ANON_CLASS, T_INTERFACE]); + $result = ObjectDeclarations::findExtendedClassName(self::$phpcsFile, $OOToken); + $this->assertSame($expected, $result); + + }//end testFindExtendedClassName() + + + /** + * Data provider for the FindExtendedClassName test. + * + * @see testFindExtendedClassName() + * + * @return array + */ + public function dataExtendedClass() + { + return [ + [ + '/* testExtendedClass */', + 'testFECNClass', + ], + [ + '/* testNamespacedClass */', + '\PHP_CodeSniffer\Tests\Core\File\testFECNClass', + ], + [ + '/* testNonExtendedClass */', + false, + ], + [ + '/* testInterface */', + false, + ], + [ + '/* testInterfaceThatExtendsInterface */', + 'testFECNInterface', + ], + [ + '/* testInterfaceThatExtendsFQCNInterface */', + '\PHP_CodeSniffer\Tests\Core\File\testFECNInterface', + ], + [ + '/* testNestedExtendedClass */', + false, + ], + [ + '/* testNestedExtendedAnonClass */', + 'testFECNAnonClass', + ], + [ + '/* testClassThatExtendsAndImplements */', + 'testFECNClass', + ], + [ + '/* testClassThatImplementsAndExtends */', + 'testFECNClass', + ], + [ + '/* testExtendedClassWithComments */', + '\PHP_CodeSniffer\Tests\testFECNClass', + ], + [ + '/* testParseError */', + false, + ], + ]; + + }//end dataExtendedClass() + + +}//end class diff --git a/tests/Core/Util/Sniffs/ObjectDeclarations/FindExtendedInterfaceNamesTest.inc b/tests/Core/Util/Sniffs/ObjectDeclarations/FindExtendedInterfaceNamesTest.inc new file mode 100644 index 0000000000..b6d81669d1 --- /dev/null +++ b/tests/Core/Util/Sniffs/ObjectDeclarations/FindExtendedInterfaceNamesTest.inc @@ -0,0 +1,26 @@ + + * @copyright 2018-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\ObjectDeclarations; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations; + +class FindExtendedInterfaceNamesTest extends AbstractMethodUnitTest +{ + + + /** + * Test retrieving the names of the interfaces being extended by another interface. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array|false $expected Expected function output. + * + * @dataProvider dataExtendedInterface + * @covers \PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations::findExtendedInterfaceNames + * @covers \PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations::findExtendedImplemented + * + * @return void + */ + public function testFindExtendedInterfaceNames($testMarker, $expected) + { + $interface = $this->getTargetToken($testMarker, [T_INTERFACE]); + $result = ObjectDeclarations::findExtendedInterfaceNames(self::$phpcsFile, $interface); + $this->assertSame($expected, $result); + + }//end testFindExtendedInterfaceNames() + + + /** + * Data provider. + * + * @see testFindExtendedInterfaceNames() + * + * @return array + */ + public function dataExtendedInterface() + { + return [ + [ + '/* testInterface */', + false, + ], + [ + '/* testExtendedInterface */', + ['testFEINInterface'], + ], + [ + '/* testMultiExtendedInterface */', + [ + 'testFEINInterface', + 'testFEINInterface2', + ], + ], + [ + '/* testNamespacedInterface */', + ['\PHP_CodeSniffer\Tests\Core\File\testFEINInterface'], + ], + [ + '/* testMultiNamespacedInterface */', + [ + '\PHP_CodeSniffer\Tests\Core\File\testFEINInterface', + '\PHP_CodeSniffer\Tests\Core\File\testFEINInterface2', + ], + ], + [ + '/* testMultiExtendedInterfaceWithComment */', + [ + 'testFEINInterface', + '\PHP_CodeSniffer\Tests\Core\File\testFEINInterface2', + '\testFEINInterface3', + ], + ], + ]; + + }//end dataExtendedInterface() + + +}//end class diff --git a/tests/Core/File/FindImplementedInterfaceNamesTest.inc b/tests/Core/Util/Sniffs/ObjectDeclarations/FindImplementedInterfaceNamesTest.inc similarity index 70% rename from tests/Core/File/FindImplementedInterfaceNamesTest.inc rename to tests/Core/Util/Sniffs/ObjectDeclarations/FindImplementedInterfaceNamesTest.inc index 3885b27e1d..edb5889244 100644 --- a/tests/Core/File/FindImplementedInterfaceNamesTest.inc +++ b/tests/Core/Util/Sniffs/ObjectDeclarations/FindImplementedInterfaceNamesTest.inc @@ -1,6 +1,6 @@ + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\ObjectDeclarations; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations; + +class FindImplementedInterfaceNamesTest extends AbstractMethodUnitTest +{ + + + /** + * Test getting a `false` result when a non-existent token is passed. + * + * @dataProvider dataImplementedInterface + * @covers \PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations::findExtendedImplemented + * + * @return void + */ + public function testNonExistentToken() + { + $result = ObjectDeclarations::findImplementedInterfaceNames(self::$phpcsFile, 100000); + $this->assertFalse($result); + + }//end testNonExistentToken() + + + /** + * Test retrieving the name(s) of the interfaces being implemented by a class. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array|false $expected Expected function output. + * + * @dataProvider dataImplementedInterface + * @covers \PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations::findImplementedInterfaceNames + * @covers \PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations::findExtendedImplemented + * + * @return void + */ + public function testFindImplementedInterfaceNames($testMarker, $expected) + { + $OOToken = $this->getTargetToken($testMarker, [T_CLASS, T_ANON_CLASS, T_INTERFACE]); + $result = ObjectDeclarations::findImplementedInterfaceNames(self::$phpcsFile, $OOToken); + $this->assertSame($expected, $result); + + }//end testFindImplementedInterfaceNames() + + + /** + * Data provider for the FindImplementedInterfaceNames test. + * + * @see testFindImplementedInterfaceNames() + * + * @return array + */ + public function dataImplementedInterface() + { + return [ + [ + '/* testImplementedClass */', + ['testFIINInterface'], + ], + [ + '/* testMultiImplementedClass */', + [ + 'testFIINInterface', + 'testFIINInterface2', + ], + ], + [ + '/* testNamespacedClass */', + ['\PHP_CodeSniffer\Tests\Core\File\testFIINInterface'], + ], + [ + '/* testNonImplementedClass */', + false, + ], + [ + '/* testInterface */', + false, + ], + [ + '/* testClassThatExtendsAndImplements */', + [ + 'InterfaceA', + '\NameSpaced\Cat\InterfaceB', + ], + ], + [ + '/* testClassThatImplementsAndExtends */', + [ + '\InterfaceA', + 'InterfaceB', + ], + ], + [ + '/* testImplementedClassWithComments */', + ['\PHP_CodeSniffer\Tests\Core\File\testFIINInterface'], + ], + ]; + + }//end dataImplementedInterface() + + +}//end class diff --git a/tests/Core/Util/Sniffs/ObjectDeclarations/GetClassPropertiesTest.inc b/tests/Core/Util/Sniffs/ObjectDeclarations/GetClassPropertiesTest.inc new file mode 100644 index 0000000000..f39c3d327c --- /dev/null +++ b/tests/Core/Util/Sniffs/ObjectDeclarations/GetClassPropertiesTest.inc @@ -0,0 +1,45 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\ObjectDeclarations; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations; + +class GetClassPropertiesTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a non class token is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_CLASS + * + * @covers \PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations::getClassProperties + * + * @return void + */ + public function testNotAClassException() + { + $interface = $this->getTargetToken('/* testNotAClass */', T_INTERFACE); + $result = ObjectDeclarations::getClassProperties(self::$phpcsFile, $interface); + + }//end testNotAClassException() + + + /** + * Test the retrieving the properties for a class declaration. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected Expected function output. + * + * @dataProvider dataGetClassProperties + * @covers \PHP_CodeSniffer\Util\Sniffs\ObjectDeclarations::getClassProperties + * + * @return void + */ + public function testGetClassProperties($testMarker, $expected) + { + $class = $this->getTargetToken($testMarker, T_CLASS); + $result = ObjectDeclarations::getClassProperties(self::$phpcsFile, $class); + $this->assertSame($expected, $result); + + }//end testGetClassProperties() + + + /** + * Data provider. + * + * @see testGetClassProperties() + * + * @return array + */ + public function dataGetClassProperties() + { + return [ + [ + '/* testClassWithoutProperties */', + [ + 'is_abstract' => false, + 'is_final' => false, + ], + ], + [ + '/* testAbstractClass */', + [ + 'is_abstract' => true, + 'is_final' => false, + ], + ], + [ + '/* testFinalClass */', + [ + 'is_abstract' => false, + 'is_final' => true, + ], + ], + [ + '/* testWithCommentsAndNewLines */', + [ + 'is_abstract' => true, + 'is_final' => false, + ], + ], + [ + '/* testWithDocblockWithoutProperties */', + [ + 'is_abstract' => false, + 'is_final' => false, + ], + ], + [ + '/* testWithPHPCSAnnotation */', + [ + 'is_abstract' => false, + 'is_final' => true, + ], + ], + [ + '/* testWithDocblockWithWeirdlyPlacedProperty */', + [ + 'is_abstract' => false, + 'is_final' => true, + ], + ], + ]; + + }//end dataGetClassProperties() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Orthography/FirstCharTest.php b/tests/Core/Util/Sniffs/Orthography/FirstCharTest.php new file mode 100644 index 0000000000..cd0c117c54 --- /dev/null +++ b/tests/Core/Util/Sniffs/Orthography/FirstCharTest.php @@ -0,0 +1,277 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Orthography; + +use PHPUnit\Framework\TestCase; +use PHP_CodeSniffer\Util\Sniffs\Orthography; + +class FirstCharTest extends TestCase +{ + + + /** + * Test correctly detecting whether the first character of a phrase is capitalized. + * + * @param string $input The input string. + * @param array $expected The expected function output for the respective functions. + * + * @dataProvider dataFirstChar + * @covers \PHP_CodeSniffer\Util\Sniffs\Orthography::isFirstCharCapitalized + * + * @return void + */ + public function testIsFirstCharCapitalized($input, $expected) + { + $this->assertSame($expected['capitalized'], Orthography::isFirstCharCapitalized($input)); + + }//end testIsFirstCharCapitalized() + + + /** + * Test correctly detecting whether the first character of a phrase is lowercase. + * + * @param string $input The input string. + * @param array $expected The expected function output for the respective functions. + * + * @dataProvider dataFirstChar + * @covers \PHP_CodeSniffer\Util\Sniffs\Orthography::isFirstCharLowercase + * + * @return void + */ + public function testIsFirstCharLowercase($input, $expected) + { + $this->assertSame($expected['lowercase'], Orthography::isFirstCharLowercase($input)); + + }//end testIsFirstCharLowercase() + + + /** + * Data provider. + * + * @see testIsFirstCharCapitalized() + * @see testIsFirstCharLowercase() + * + * @return array + */ + public function dataFirstChar() + { + $data = [ + // Quotes should be stripped before passing the string. + 'double-quoted' => [ + '"This is a test"', + [ + 'capitalized' => false, + 'lowercase' => false, + ], + ], + 'single-quoted' => [ + "'This is a test'", + [ + 'capitalized' => false, + 'lowercase' => false, + ], + ], + + // Not starting with a letter. + 'start-numeric' => [ + '12 Foostreet', + [ + 'capitalized' => false, + 'lowercase' => false, + ], + ], + 'start-bracket' => [ + '[Optional]', + [ + 'capitalized' => false, + 'lowercase' => false, + ], + ], + + // Leading whitespace. + 'english-lowercase-leading-whitespace' => [ + ' + this is a test', + [ + 'capitalized' => false, + 'lowercase' => true, + ], + ], + 'english-propercase-leading-whitespace' => [ + ' + This is a test', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ], + + // First character lowercase. + 'english-lowercase' => [ + 'this is a test', + [ + 'capitalized' => false, + 'lowercase' => true, + ], + ], + 'russian-lowercase' => [ + 'предназначена для‎', + [ + 'capitalized' => false, + 'lowercase' => true, + ], + ], + 'latvian-lowercase' => [ + 'ir domāta', + [ + 'capitalized' => false, + 'lowercase' => true, + ], + ], + 'armenian-lowercase' => [ + 'սա թեստ է', + [ + 'capitalized' => false, + 'lowercase' => true, + ], + ], + 'mandinka-lowercase' => [ + 'ŋanniya', + [ + 'capitalized' => false, + 'lowercase' => true, + ], + ], + 'greek-lowercase' => [ + 'δημιουργήθηκε από', + [ + 'capitalized' => false, + 'lowercase' => true, + ], + ], + + // First character capitalized. + 'english-propercase' => [ + 'This is a test', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ], + 'russian-propercase' => [ + 'Дата написания этой книги', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ], + 'latvian-propercase' => [ + 'Šodienas datums', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ], + 'armenian-propercase' => [ + 'Սա թեստ է', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ], + 'igbo-propercase' => [ + 'Ụbọchị tata bụ', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ], + 'greek-propercase' => [ + 'Η σημερινή ημερομηνία', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ], + + // No concept of "case", but starting with a letter. + 'arabic' => [ + 'هذا اختبار', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ], + 'pashto' => [ + 'دا یوه آزموینه ده', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ], + 'hebrew' => [ + 'זה מבחן', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ], + 'chinese-traditional' => [ + '這是一個測試', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ], + 'urdu' => [ + 'کا منشاء برائے', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ], + ]; + + /* + * PCRE2 - included in PHP 7.3+ - recognizes Georgian as a language with + * upper and lowercase letters as defined in Unicode v 11.0 / June 2018. + * While, as far as I can tell, this is linguistically incorrect - the upper + * and lowercase letters are from different alphabets used to write Georgian -, + * the unit test should allow for the reality as implemented in ICU/PCRE2/PHP. + * + * @link https://en.wikipedia.org/wiki/Georgian_scripts#Unicode + * @link https://unicode.org/charts/PDF/U10A0.pdf + */ + + if (PCRE_VERSION >= 10) { + $data['georgian'] = [ + 'ეს ტესტია', + [ + 'capitalized' => false, + 'lowercase' => true, + ], + ]; + } else { + $data['georgian'] = [ + 'ეს ტესტია', + [ + 'capitalized' => true, + 'lowercase' => false, + ], + ]; + } + + return $data; + + }//end dataFirstChar() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Orthography/IsLastCharPunctuationTest.php b/tests/Core/Util/Sniffs/Orthography/IsLastCharPunctuationTest.php new file mode 100644 index 0000000000..4af1295ca6 --- /dev/null +++ b/tests/Core/Util/Sniffs/Orthography/IsLastCharPunctuationTest.php @@ -0,0 +1,127 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Orthography; + +use PHPUnit\Framework\TestCase; +use PHP_CodeSniffer\Util\Sniffs\Orthography; + +class IsLastCharPunctuationTest extends TestCase +{ + + + /** + * Test correctly detecting sentence end punctuation. + * + * @param string $input The input string. + * @param bool $expected The expected function output. + * @param string $allowedChars Optional. Custom punctuation character set. + * + * @dataProvider dataIsLastCharPunctuation + * @covers \PHP_CodeSniffer\Util\Sniffs\Orthography::isLastCharPunctuation + * + * @return void + */ + public function testIsLastCharPunctuation($input, $expected, $allowedChars=null) + { + if (isset($allowedChars) === true) { + $result = Orthography::isLastCharPunctuation($input, $allowedChars); + } else { + $result = Orthography::isLastCharPunctuation($input); + } + + $this->assertSame($expected, $result); + + }//end testIsLastCharPunctuation() + + + /** + * Data provider. + * + * @see testIsLastCharPunctuation() + * + * @return array + */ + public function dataIsLastCharPunctuation() + { + return [ + // Quotes should be stripped before passing the string. + 'double-quoted' => [ + '"This is a test."', + false, + ], + 'single-quoted' => [ + "'This is a test?'", + false, + ], + + // Invalid end char. + 'no-punctuation' => [ + 'This is a test', + false, + ], + 'invalid-punctuation' => [ + 'This is a test;', + false, + ], + 'invalid-punctuationtrailing-whitespace' => [ + 'This is a test; ', + false, + ], + + // Valid end char, default charset. + 'valid' => [ + 'This is a test.', + true, + ], + 'valid-trailing-whitespace' => [ + 'This is a test. +', + true, + ], + + // Invalid end char, custom charset. + 'invalid-custom' => [ + 'This is a test.', + false, + '!?,;#', + ], + + // Valid end char, custom charset. + 'valid-custom-1' => [ + 'This is a test;', + true, + '!?,;#', + ], + 'valid-custom-2' => [ + 'This is a test!', + true, + '!?,;#', + ], + 'valid-custom-3' => [ + 'Is this is a test?', + true, + '!?,;#', + ], + 'valid-custom-4' => [ + 'This is a test,', + true, + '!?,;#', + ], + 'valid-custom-5' => [ + 'This is a test#', + true, + '!?,;#', + ], + ]; + + }//end dataIsLastCharPunctuation() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Parentheses/ParenthesesTest.inc b/tests/Core/Util/Sniffs/Parentheses/ParenthesesTest.inc new file mode 100644 index 0000000000..788446a4d5 --- /dev/null +++ b/tests/Core/Util/Sniffs/Parentheses/ParenthesesTest.inc @@ -0,0 +1,40 @@ + 0 && ((function($p) {/* do something */})($result) === true)) {} + +/* testAnonClass */ +$anonClass = new class( + new class implements Countable { + function test($param) { + do { + try { + } catch( Exception $e ) { + } + } while($a === true); + } + } +) extends DateTime { +}; + +// Intentional parse error. This has to be the last test in the file. +/* testParseError */ +declare(ticks=1 diff --git a/tests/Core/Util/Sniffs/Parentheses/ParenthesesTest.php b/tests/Core/Util/Sniffs/Parentheses/ParenthesesTest.php new file mode 100644 index 0000000000..b0c8cf7ba4 --- /dev/null +++ b/tests/Core/Util/Sniffs/Parentheses/ParenthesesTest.php @@ -0,0 +1,975 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Parentheses; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Parentheses; +use PHP_CodeSniffer\Util\Tokens; + +class ParenthesesTest extends AbstractMethodUnitTest +{ + + /** + * List of all the test markers with their target token info in the test case file. + * + * @var array + */ + public static $testTargets = [ + 'testIfWithArray-$a' => [ + 'marker' => '/* testIfWithArray */', + 'code' => T_VARIABLE, + 'content' => '$a', + ], + 'testIfWithArray-array' => [ + 'marker' => '/* testIfWithArray */', + 'code' => T_ARRAY, + ], + 'testIfWithArray-$c' => [ + 'marker' => '/* testIfWithArray */', + 'code' => T_VARIABLE, + 'content' => '$c', + ], + 'testElseIfWithClosure-$a' => [ + 'marker' => '/* testElseIfWithClosure */', + 'code' => T_VARIABLE, + 'content' => '$a', + ], + 'testElseIfWithClosure-closure' => [ + 'marker' => '/* testElseIfWithClosure */', + 'code' => T_CLOSURE, + ], + 'testElseIfWithClosure-$array' => [ + 'marker' => '/* testElseIfWithClosure */', + 'code' => T_VARIABLE, + 'content' => '$array', + ], + 'testForeach-45' => [ + 'marker' => '/* testForeach */', + 'code' => T_LNUMBER, + 'content' => '45', + ], + 'testForeach-$a' => [ + 'marker' => '/* testForeach */', + 'code' => T_VARIABLE, + 'content' => '$a', + ], + 'testForeach-$c' => [ + 'marker' => '/* testForeach */', + 'code' => T_VARIABLE, + 'content' => '$c', + ], + 'testFunctionwithArray-$param' => [ + 'marker' => '/* testFunctionwithArray */', + 'code' => T_VARIABLE, + 'content' => '$param', + ], + 'testFunctionwithArray-2' => [ + 'marker' => '/* testFunctionwithArray */', + 'code' => T_LNUMBER, + 'content' => '2', + ], + 'testForWithTernary-$a' => [ + 'marker' => '/* testForWithTernary */', + 'code' => T_VARIABLE, + 'content' => '$a', + ], + 'testForWithTernary-$c' => [ + 'marker' => '/* testForWithTernary */', + 'code' => T_VARIABLE, + 'content' => '$c', + ], + 'testForWithTernary-$array' => [ + 'marker' => '/* testForWithTernary */', + 'code' => T_VARIABLE, + 'content' => '$array', + ], + 'testWhileWithClosure-$a' => [ + 'marker' => '/* testWhileWithClosure */', + 'code' => T_VARIABLE, + 'content' => '$a', + ], + 'testWhileWithClosure-$p' => [ + 'marker' => '/* testWhileWithClosure */', + 'code' => T_VARIABLE, + 'content' => '$p', + ], + 'testWhileWithClosure-$result' => [ + 'marker' => '/* testWhileWithClosure */', + 'code' => T_VARIABLE, + 'content' => '$result', + ], + 'testAnonClass-implements' => [ + 'marker' => '/* testAnonClass */', + 'code' => T_IMPLEMENTS, + ], + 'testAnonClass-$param' => [ + 'marker' => '/* testAnonClass */', + 'code' => T_VARIABLE, + 'content' => '$param', + ], + 'testAnonClass-$e' => [ + 'marker' => '/* testAnonClass */', + 'code' => T_VARIABLE, + 'content' => '$e', + ], + 'testAnonClass-$a' => [ + 'marker' => '/* testAnonClass */', + 'code' => T_VARIABLE, + 'content' => '$a', + ], + 'testParseError-1' => [ + 'marker' => '/* testParseError */', + 'code' => T_LNUMBER, + 'content' => '1', + ], + ]; + + /** + * Cache for the test token stack pointers. + * + * @var array => + */ + private $testTokens = []; + + + /** + * Base array with all the tokens which are assigned parenthesis owners. + * + * This array is merged with expected result arrays for various unit tests + * to make sure all possible parentheses owners are tested. + * + * This array should be kept in sync with the Tokens::$parenthesisOpeners array. + * This array isn't auto-generated based on the array in Tokens as for these + * tests we want to have access to the token constant names, not just their values. + * + * @var array => + */ + private $ownerDefaults = [ + 'T_ARRAY' => false, + 'T_FUNCTION' => false, + 'T_CLOSURE' => false, + 'T_WHILE' => false, + 'T_FOR' => false, + 'T_FOREACH' => false, + 'T_SWITCH' => false, + 'T_IF' => false, + 'T_ELSEIF' => false, + 'T_CATCH' => false, + 'T_DECLARE' => false, + ]; + + + /** + * Set up the token position caches for the tests. + * + * Retrieves the test tokens and marker token stack pointer positions + * only once and caches them as they won't change between the tests anyway. + * + * @return void + */ + protected function setUp() + { + if (empty($this->testTokens) === true) { + foreach (self::$testTargets as $testName => $targetDetails) { + if (isset($targetDetails['content']) === true) { + $this->testTokens[$testName] = $this->getTargetToken( + $targetDetails['marker'], + $targetDetails['code'], + $targetDetails['content'] + ); + } else { + $this->testTokens[$testName] = $this->getTargetToken( + $targetDetails['marker'], + $targetDetails['code'] + ); + } + } + } + + }//end setUp() + + + /** + * Test passing a non-existent token pointer. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::isOwnerIn + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::hasOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::nestedParensWalker + * + * @return void + */ + public function testNonExistentToken() + { + $result = Parentheses::getOwner(self::$phpcsFile, 100000); + $this->assertFalse($result); + + $result = Parentheses::isOwnerIn(self::$phpcsFile, 100000, T_FUNCTION); + $this->assertFalse($result); + + $result = Parentheses::hasOwner(self::$phpcsFile, 100000, T_FOR); + $this->assertFalse($result); + + }//end testNonExistentToken() + + + /** + * Test passing a token which isn't in parentheses. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::isOwnerIn + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::hasOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getFirstOpener + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getFirstCloser + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getFirstOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getLastOpener + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getLastCloser + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getLastOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::lastOwnerIn + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::nestedParensWalker + * + * @return void + */ + public function testNoParentheses() + { + $stackPtr = $this->getTargetToken('/* testNoParentheses */', T_VARIABLE); + + $result = Parentheses::getOwner(self::$phpcsFile, $stackPtr); + $this->assertFalse($result); + + $result = Parentheses::isOwnerIn(self::$phpcsFile, $stackPtr, T_IF); + $this->assertFalse($result); + + $result = Parentheses::hasOwner(self::$phpcsFile, $stackPtr, T_FOREACH); + $this->assertFalse($result); + + $result = Parentheses::getFirstOpener(self::$phpcsFile, $stackPtr); + $this->assertFalse($result); + + $result = Parentheses::getFirstCloser(self::$phpcsFile, $stackPtr); + $this->assertFalse($result); + + $result = Parentheses::getFirstOwner(self::$phpcsFile, $stackPtr); + $this->assertFalse($result); + + $result = Parentheses::getLastOpener(self::$phpcsFile, $stackPtr); + $this->assertFalse($result); + + $result = Parentheses::getLastCloser(self::$phpcsFile, $stackPtr); + $this->assertFalse($result); + + $result = Parentheses::getLastOwner(self::$phpcsFile, $stackPtr); + $this->assertFalse($result); + + $result = Parentheses::lastOwnerIn(self::$phpcsFile, $stackPtr, [T_FUNCTION, T_CLOSURE]); + $this->assertFalse($result); + + }//end testNoParentheses() + + + /** + * Test passing a non-parenthesis token to methods which expect to receive an open/close parenthesis. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::isOwnerIn + * + * @return void + */ + public function testPassingNonParenthesisTokenToMethodsWhichExpectParenthesis() + { + $stackPtr = $this->testTokens['testIfWithArray-$a']; + + $result = Parentheses::getOwner(self::$phpcsFile, $stackPtr); + $this->assertFalse($result); + + $result = Parentheses::isOwnerIn(self::$phpcsFile, $stackPtr, T_IF); + $this->assertFalse($result); + + }//end testPassingNonParenthesisTokenToMethodsWhichExpectParenthesis() + + + /** + * Test passing a parenthesis token to methods which expect to receive an open/close parenthesis. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::isOwnerIn + * + * @return void + */ + public function testPassingParenthesisTokenToMethodsWhichExpectParenthesis() + { + $stackPtr = ($this->testTokens['testIfWithArray-$c'] - 1); + + $result = Parentheses::getOwner(self::$phpcsFile, $stackPtr); + $this->assertSame(($stackPtr - 1), $result); + + $result = Parentheses::isOwnerIn(self::$phpcsFile, $stackPtr, T_IF); + $this->assertFalse($result); + + $result = Parentheses::isOwnerIn(self::$phpcsFile, $stackPtr, T_ARRAY); + $this->assertTrue($result); + + }//end testPassingParenthesisTokenToMethodsWhichExpectParenthesis() + + + /** + * Test correctly retrieving the first parenthesis opener for an arbitrary token. + * + * @param string $testName The name of this test as set in the cached $testTokens array. + * @param array $expectedResults Expected function output for the various functions. + * + * @dataProvider dataWalkParentheses + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getFirstOpener + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::nestedParensWalker + * + * @return void + */ + public function testGetFirstOpener($testName, $expectedResults) + { + $stackPtr = $this->testTokens[$testName]; + + $result = Parentheses::getFirstOpener(self::$phpcsFile, $stackPtr); + $expected = $expectedResults['firstOpener']; + if ($expected !== false) { + $expected += $stackPtr; + } + + $this->assertSame($expected, $result, 'Assertion without owners failed'); + + $result = Parentheses::getFirstOpener(self::$phpcsFile, $stackPtr, Tokens::$scopeOpeners); + $expected = $expectedResults['firstScopeOwnerOpener']; + if ($expected !== false) { + $expected += $stackPtr; + } + + $this->assertSame($expected, $result, 'Assertion with $validOwners failed'); + + }//end testGetFirstOpener() + + + /** + * Test correctly retrieving the first parenthesis closer for an arbitrary token. + * + * @param string $testName The name of this test as set in the cached $testTokens array. + * @param array $expectedResults Expected function output for the various functions. + * + * @dataProvider dataWalkParentheses + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getFirstCloser + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::nestedParensWalker + * + * @return void + */ + public function testGetFirstCloser($testName, $expectedResults) + { + $stackPtr = $this->testTokens[$testName]; + + $result = Parentheses::getFirstCloser(self::$phpcsFile, $stackPtr); + $expected = $expectedResults['firstCloser']; + if ($expected !== false) { + $expected += $stackPtr; + } + + $this->assertSame($expected, $result, 'Assertion without owners failed'); + + $result = Parentheses::getFirstCloser(self::$phpcsFile, $stackPtr, Tokens::$scopeOpeners); + $expected = $expectedResults['firstScopeOwnerCloser']; + if ($expected !== false) { + $expected += $stackPtr; + } + + $this->assertSame($expected, $result, 'Assertion with $validOwners failed'); + + }//end testGetFirstCloser() + + + /** + * Test correctly retrieving the first parenthesis owner for an arbitrary token. + * + * @param string $testName The name of this test as set in the cached $testTokens array. + * @param array $expectedResults Expected function output for the various functions. + * + * @dataProvider dataWalkParentheses + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getFirstOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::nestedParensWalker + * + * @return void + */ + public function testGetFirstOwner($testName, $expectedResults) + { + $stackPtr = $this->testTokens[$testName]; + + $result = Parentheses::getFirstOwner(self::$phpcsFile, $stackPtr); + $expected = $expectedResults['firstOwner']; + if ($expected !== false) { + $expected += $stackPtr; + } + + $this->assertSame($expected, $result, 'Assertion without owners failed'); + + $result = Parentheses::getFirstOwner(self::$phpcsFile, $stackPtr, Tokens::$scopeOpeners); + $expected = $expectedResults['firstScopeOwnerOwner']; + if ($expected !== false) { + $expected += $stackPtr; + } + + $this->assertSame($expected, $result, 'Assertion with $validOwners failed'); + + }//end testGetFirstOwner() + + + /** + * Test correctly retrieving the last parenthesis opener for an arbitrary token. + * + * @param string $testName The name of this test as set in the cached $testTokens array. + * @param array $expectedResults Expected function output for the various functions. + * + * @dataProvider dataWalkParentheses + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getLastOpener + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::nestedParensWalker + * + * @return void + */ + public function testGetLastOpener($testName, $expectedResults) + { + $stackPtr = $this->testTokens[$testName]; + + $result = Parentheses::getLastOpener(self::$phpcsFile, $stackPtr); + $expected = $expectedResults['lastOpener']; + if ($expected !== false) { + $expected += $stackPtr; + } + + $this->assertSame($expected, $result, 'Assertion without owners failed'); + + $result = Parentheses::getLastOpener(self::$phpcsFile, $stackPtr, [T_ARRAY]); + $expected = $expectedResults['lastArrayOpener']; + if ($expected !== false) { + $expected += $stackPtr; + } + + $this->assertSame($expected, $result, 'Assertion with $validOwners failed'); + + }//end testGetLastOpener() + + + /** + * Test correctly retrieving the last parenthesis closer for an arbitrary token. + * + * @param string $testName The name of this test as set in the cached $testTokens array. + * @param array $expectedResults Expected function output for the various functions. + * + * @dataProvider dataWalkParentheses + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getLastCloser + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::nestedParensWalker + * + * @return void + */ + public function testGetLastCloser($testName, $expectedResults) + { + $stackPtr = $this->testTokens[$testName]; + + $result = Parentheses::getLastCloser(self::$phpcsFile, $stackPtr); + $expected = $expectedResults['lastCloser']; + if ($expected !== false) { + $expected += $stackPtr; + } + + $this->assertSame($expected, $result, 'Assertion without owners failed'); + + $result = Parentheses::getLastCloser(self::$phpcsFile, $stackPtr, [T_FUNCTION, T_CLOSURE]); + $expected = $expectedResults['lastFunctionCloser']; + if ($expected !== false) { + $expected += $stackPtr; + } + + $this->assertSame($expected, $result, 'Assertion with $validOwners failed'); + + }//end testGetLastCloser() + + + /** + * Test correctly retrieving the last parenthesis owner for an arbitrary token. + * + * @param string $testName The name of this test as set in the cached $testTokens array. + * @param array $expectedResults Expected function output for the various functions. + * + * @dataProvider dataWalkParentheses + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getLastOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::nestedParensWalker + * + * @return void + */ + public function testGetLastOwner($testName, $expectedResults) + { + $stackPtr = $this->testTokens[$testName]; + + $result = Parentheses::getLastOwner(self::$phpcsFile, $stackPtr); + $expected = $expectedResults['lastOwner']; + if ($expected !== false) { + $expected += $stackPtr; + } + + $this->assertSame($expected, $result, 'Assertion without owners failed'); + + $result = Parentheses::getLastOwner(self::$phpcsFile, $stackPtr, [T_IF, T_ELSEIF, T_ELSE]); + $expected = $expectedResults['lastIfElseOwner']; + if ($expected !== false) { + $expected += $stackPtr; + } + + $this->assertSame($expected, $result, 'Assertion with $validOwners failed'); + + }//end testGetLastOwner() + + + /** + * Data provider. + * + * @see testGetFirstOpener() + * @see testGetFirstCloser(() + * @see testGetFirstOwner() + * @see testGetFirstOwnerTypes() + * @see testGetLastOpener() + * @see testGetLastCloser() + * @see testGetLastOwner() + * @see testGetLastOwnerTypes() + * + * @return array + */ + public function dataWalkParentheses() + { + $data = [ + 'testIfWithArray-$a' => [ + 'testIfWithArray-$a', + [ + 'firstOpener' => -2, + 'firstCloser' => 19, + 'firstOwner' => -4, + 'firstScopeOwnerOpener' => -2, + 'firstScopeOwnerCloser' => 19, + 'firstScopeOwnerOwner' => -4, + 'lastOpener' => -1, + 'lastCloser' => 5, + 'lastOwner' => false, + 'lastArrayOpener' => false, + 'lastFunctionCloser' => false, + 'lastIfElseOwner' => -4, + ], + ], + 'testIfWithArray-array' => [ + 'testIfWithArray-array', + [ + 'firstOpener' => -13, + 'firstCloser' => 8, + 'firstOwner' => -15, + 'firstScopeOwnerOpener' => -13, + 'firstScopeOwnerCloser' => 8, + 'firstScopeOwnerOwner' => -15, + 'lastOpener' => -1, + 'lastCloser' => 7, + 'lastOwner' => false, + 'lastArrayOpener' => false, + 'lastFunctionCloser' => false, + 'lastIfElseOwner' => -15, + ], + ], + 'testIfWithArray-$c' => [ + 'testIfWithArray-$c', + [ + 'firstOpener' => -15, + 'firstCloser' => 6, + 'firstOwner' => -17, + 'firstScopeOwnerOpener' => -15, + 'firstScopeOwnerCloser' => 6, + 'firstScopeOwnerOwner' => -17, + 'lastOpener' => -1, + 'lastCloser' => 4, + 'lastOwner' => -2, + 'lastArrayOpener' => -1, + 'lastFunctionCloser' => false, + 'lastIfElseOwner' => -17, + ], + ], + 'testWhileWithClosure-$a' => [ + 'testWhileWithClosure-$a', + [ + 'firstOpener' => -9, + 'firstCloser' => 30, + 'firstOwner' => -11, + 'firstScopeOwnerOpener' => -9, + 'firstScopeOwnerCloser' => 30, + 'firstScopeOwnerOwner' => -11, + 'lastOpener' => -2, + 'lastCloser' => 2, + 'lastOwner' => false, + 'lastArrayOpener' => false, + 'lastFunctionCloser' => false, + 'lastIfElseOwner' => false, + ], + ], + 'testWhileWithClosure-$p' => [ + 'testWhileWithClosure-$p', + [ + 'firstOpener' => -24, + 'firstCloser' => 15, + 'firstOwner' => -26, + 'firstScopeOwnerOpener' => -24, + 'firstScopeOwnerCloser' => 15, + 'firstScopeOwnerOwner' => -26, + 'lastOpener' => -1, + 'lastCloser' => 1, + 'lastOwner' => -2, + 'lastArrayOpener' => false, + 'lastFunctionCloser' => 1, + 'lastIfElseOwner' => false, + ], + ], + 'testWhileWithClosure-$result' => [ + 'testWhileWithClosure-$result', + [ + 'firstOpener' => -2, + 'firstCloser' => 37, + 'firstOwner' => -4, + 'firstScopeOwnerOpener' => -2, + 'firstScopeOwnerCloser' => 37, + 'firstScopeOwnerOwner' => -4, + 'lastOpener' => -1, + 'lastCloser' => 10, + 'lastOwner' => false, + 'lastArrayOpener' => false, + 'lastFunctionCloser' => false, + 'lastIfElseOwner' => false, + ], + ], + 'testParseError-1' => [ + 'testParseError-1', + [ + 'firstOpener' => false, + 'firstCloser' => false, + 'firstOwner' => false, + 'firstScopeOwnerOpener' => false, + 'firstScopeOwnerCloser' => false, + 'firstScopeOwnerOwner' => false, + 'lastOpener' => false, + 'lastCloser' => false, + 'lastOwner' => false, + 'lastArrayOpener' => false, + 'lastFunctionCloser' => false, + 'lastIfElseOwner' => false, + ], + ], + ]; + + return $data; + + }//end dataWalkParentheses() + + + /** + * Test correctly determining whether a token has an owner of a certain type. + * + * @param string $testName The name of this test as set in the cached $testTokens array. + * @param array $expectedResults Array with the owner token type to search for as key + * and the expected result as a value. + * + * @dataProvider dataHasOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::hasOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::nestedParensWalker + * + * @return void + */ + public function testHasOwner($testName, $expectedResults) + { + $stackPtr = $this->testTokens[$testName]; + + // Add expected results for all owner types not listed in the data provider. + $expectedResults += $this->ownerDefaults; + + foreach ($expectedResults as $ownerType => $expected) { + $result = Parentheses::hasOwner(self::$phpcsFile, $stackPtr, constant($ownerType)); + $this->assertSame( + $expected, + $result, + "Assertion failed for test marker '{$testName}' with owner {$ownerType}" + ); + } + + }//end testHasOwner() + + + /** + * Data Provider. + * + * Only list the "true" owners in the $results array. + * All other potential owners will automatically also be tested + * and will expect "false" as a result. + * + * @see testHasOwner() + * + * @return array + */ + public function dataHasOwner() + { + return [ + 'testIfWithArray-$a' => [ + 'testIfWithArray-$a', + ['T_IF' => true], + ], + + 'testIfWithArray-array' => [ + 'testIfWithArray-array', + ['T_IF' => true], + ], + 'testIfWithArray-$c' => [ + 'testIfWithArray-$c', + [ + 'T_ARRAY' => true, + 'T_IF' => true, + ], + ], + 'testElseIfWithClosure-$a' => [ + 'testElseIfWithClosure-$a', + [ + 'T_CLOSURE' => true, + 'T_ELSEIF' => true, + ], + ], + 'testElseIfWithClosure-closure' => [ + 'testElseIfWithClosure-closure', + ['T_ELSEIF' => true], + ], + 'testElseIfWithClosure-$array' => [ + 'testElseIfWithClosure-$array', + ['T_ELSEIF' => true], + ], + 'testForeach-45' => [ + 'testForeach-45', + [ + 'T_ARRAY' => true, + 'T_FOREACH' => true, + ], + ], + 'testForeach-$a' => [ + 'testForeach-$a', + ['T_FOREACH' => true], + ], + 'testForeach-$c' => [ + 'testForeach-$c', + ['T_FOREACH' => true], + ], + 'testFunctionwithArray-$param' => [ + 'testFunctionwithArray-$param', + ['T_FUNCTION' => true], + ], + 'testFunctionwithArray-2' => [ + 'testFunctionwithArray-2', + [ + 'T_ARRAY' => true, + 'T_FUNCTION' => true, + ], + ], + 'testForWithTernary-$a' => [ + 'testForWithTernary-$a', + ['T_FOR' => true], + ], + 'testForWithTernary-$c' => [ + 'testForWithTernary-$c', + ['T_FOR' => true], + ], + 'testForWithTernary-$array' => [ + 'testForWithTernary-$array', + ['T_FOR' => true], + ], + 'testWhileWithClosure-$a' => [ + 'testWhileWithClosure-$a', + ['T_WHILE' => true], + ], + 'testWhileWithClosure-$p' => [ + 'testWhileWithClosure-$p', + [ + 'T_CLOSURE' => true, + 'T_WHILE' => true, + ], + ], + 'testWhileWithClosure-$result' => [ + 'testWhileWithClosure-$result', + ['T_WHILE' => true], + ], + 'testAnonClass-implements' => [ + 'testAnonClass-implements', + [], + ], + 'testAnonClass-$param' => [ + 'testAnonClass-$param', + ['T_FUNCTION' => true], + ], + 'testAnonClass-$e' => [ + 'testAnonClass-$e', + ['T_CATCH' => true], + ], + 'testAnonClass-$a' => [ + 'testAnonClass-$a', + ['T_WHILE' => true], + ], + 'testParseError-1' => [ + 'testParseError-1', + [], + ], + + ]; + + }//end dataHasOwner() + + + /** + * Test correctly determining whether a token is nested in parentheses with an owner + * of a certain type, with multiple allowed possibilities. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::hasOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::nestedParensWalker + * + * @return void + */ + public function testHasOwnerMultipleTypes() + { + $stackPtr = $this->testTokens['testElseIfWithClosure-$array']; + + $result = Parentheses::hasOwner(self::$phpcsFile, $stackPtr, [T_FUNCTION, T_CLOSURE]); + $this->assertFalse( + $result, + 'Failed asserting that $array in "testElseIfWithClosure" does not have a "function" nor a "closure" owner' + ); + + $result = Parentheses::hasOwner(self::$phpcsFile, $stackPtr, [T_IF, T_ELSEIF, T_ELSE]); + $this->assertTrue( + $result, + 'Failed asserting that $array in "testElseIfWithClosure" has an "if", "elseif" or "else" owner' + ); + + $stackPtr = $this->testTokens['testForWithTernary-$array']; + + $result = Parentheses::hasOwner(self::$phpcsFile, $stackPtr, [T_ARRAY, T_LIST]); + $this->assertFalse( + $result, + 'Failed asserting that $array in "testForWithTernary" does not have an anonymous class nor a closure condition' + ); + + $result = Parentheses::hasOwner(self::$phpcsFile, $stackPtr, Tokens::$scopeOpeners); + $this->assertTrue( + $result, + 'Failed asserting that $array in "testForWithTernary" has an owner which is also a scope opener' + ); + + }//end testHasOwnerMultipleTypes() + + + /** + * Test correctly determining whether the last set of parenthesis around an arbitrary token + * has an owner of a certain type. + * + * @param string $testName The name of this test as set in the cached $testTokens array. + * @param array $validOwners Valid owners to test against. + * @param int|false $expected Expected function output + * + * @dataProvider datalastOwnerIn + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::lastOwnerIn + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getOwner + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::getLastOpener + * @covers \PHP_CodeSniffer\Util\Sniffs\Parentheses::nestedParensWalker + * + * @return void + */ + public function testLastOwnerIn($testName, $validOwners, $expected) + { + $stackPtr = $this->testTokens[$testName]; + if ($expected !== false) { + $expected += $stackPtr; + } + + $result = Parentheses::lastOwnerIn(self::$phpcsFile, $stackPtr, $validOwners); + $this->assertSame($expected, $result); + + }//end testLastOwnerIn() + + + /** + * Data provider. + * + * @see testlastOwnerIn() + * + * @return array + */ + public function dataLastOwnerIn() + { + return [ + 'testElseIfWithClosure-$a' => [ + 'testElseIfWithClosure-$a', + [T_FUNCTION], + -3, + ], + 'testElseIfWithClosure-$a' => [ + 'testElseIfWithClosure-$a', + [T_ARRAY], + false, + ], + 'testForeach-45' => [ + 'testForeach-45', + [T_ARRAY], + -2, + ], + 'testForeach-45' => [ + 'testForeach-45', + [ + T_FOREACH, + T_FOR, + ], + false, + ], + 'testForeach-$a' => [ + 'testForeach-$a', + [ + T_FOREACH, + T_FOR, + ], + false, + ], + 'testFunctionwithArray-$param' => [ + 'testFunctionwithArray-$param', + [ + T_FUNCTION, + T_CLOSURE, + ], + -4, + ], + 'testFunctionwithArray-$param' => [ + 'testFunctionwithArray-$param', + [ + T_IF, + T_ELSEIF, + T_ELSE, + ], + false, + ], + 'testAnonClass-$e' => [ + 'testAnonClass-$e', + [T_FUNCTION], + false, + ], + 'testAnonClass-$e' => [ + 'testAnonClass-$e', + [T_CATCH], + -5, + ], + ]; + + }//end dataLastOwnerIn() + + +}//end class diff --git a/tests/Core/Util/Sniffs/PassedParameters/GetDoubleArrowPositionTest.inc b/tests/Core/Util/Sniffs/PassedParameters/GetDoubleArrowPositionTest.inc new file mode 100644 index 0000000000..8ced21d9e8 --- /dev/null +++ b/tests/Core/Util/Sniffs/PassedParameters/GetDoubleArrowPositionTest.inc @@ -0,0 +1,57 @@ + 'arrow numeric index', + + /* testArrowStringIndex */ + 'foo' => 'arrow string index', + + /* testArrowMultiTokenIndex */ + 'concat' . 'index' => 'arrow multi token index', + + /* testNoArrowValueShortArray */ + [ + 'value only' => 'arrow belongs to value', + ], + + /* testNoArrowValueLongArray */ + array( + 'value only' => 'arrow belongs to value', + ), + + /* testNoArrowValueNestedArrays */ + array( + [ + array( + ['key' => 'arrow belongs to nested array'], + ), + ], + ), + + /* testNoArrowValueClosure */ + function() { + echo 'closure as value arrow belongs to value'; + return array( $a => $b ); + }, + + /* testArrowValueShortArray */ + 'index and value short array' => [ + 'index and value' => '', + ], + + /* testArrowValueLongArray */ + 'index and value long array' => array( + 'index and value' => '', + ), + + /* testArrowValueClosure */ + 'index and value closure' => function() { + echo 'closure as value arrow belongs to value'; + return array( $a => $b ); + }, +]; diff --git a/tests/Core/Util/Sniffs/PassedParameters/GetDoubleArrowPositionTest.php b/tests/Core/Util/Sniffs/PassedParameters/GetDoubleArrowPositionTest.php new file mode 100644 index 0000000000..cc47e528c8 --- /dev/null +++ b/tests/Core/Util/Sniffs/PassedParameters/GetDoubleArrowPositionTest.php @@ -0,0 +1,193 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\PassedParameters; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\PassedParameters; + +class GetDoubleArrowPositionTest extends AbstractMethodUnitTest +{ + + + /** + * Cache for the parsed parameters array. + * + * @var array => + */ + private $parameters = []; + + + /** + * Set up the parsed parameters cache for the tests. + * + * Retrieves the parsed parameters array only once and caches + * it as it won't change between the tests anyway. + * + * @return void + */ + protected function setUp() + { + if (empty($this->parameters) === true) { + $target = $this->getTargetToken('/* testGetDoubleArrowPosition */', [T_OPEN_SHORT_ARRAY]); + $this->parameters = PassedParameters::getParameters(self::$phpcsFile, $target); + } + + }//end setUp() + + + /** + * Test receiving an expected exception when an invalid start position is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage Invalid start and/or end position passed to getDoubleArrowPosition(). Received: $start -10, $end 10 + * + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::getDoubleArrowPosition + * + * @return void + */ + public function testInvalidStartPositionException() + { + $result = PassedParameters::getDoubleArrowPosition(self::$phpcsFile, -10, 10); + + }//end testInvalidStartPositionException() + + + /** + * Test receiving an expected exception when an invalid end position is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage Invalid start and/or end position passed to getDoubleArrowPosition(). Received: $start 0, $end 100000 + * + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::getDoubleArrowPosition + * + * @return void + */ + public function testInvalidEndPositionException() + { + $result = PassedParameters::getDoubleArrowPosition(self::$phpcsFile, 0, 100000); + + }//end testInvalidEndPositionException() + + + /** + * Test receiving an expected exception when the start position is after the end position. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage Invalid start and/or end position passed to getDoubleArrowPosition(). Received: $start 10, $end 5 + * + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::getDoubleArrowPosition + * + * @return void + */ + public function testInvalidStartEndPositionException() + { + $result = PassedParameters::getDoubleArrowPosition(self::$phpcsFile, 10, 5); + + }//end testInvalidStartEndPositionException() + + + /** + * Test retrieving the position of the double arrow for an array parameter. + * + * @param string $testMarker The comment which is part of the target array item in the test file. + * @param array $expected The expected function call result. + * + * @dataProvider dataGetDoubleArrowPosition + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::getDoubleArrowPosition + * + * @return void + */ + public function testGetDoubleArrowPosition($testMarker, $expected) + { + foreach ($this->parameters as $index => $values) { + if (strpos($values['raw'], $testMarker) !== false) { + $start = $values['start']; + $end = $values['end']; + break; + } + } + + if (isset($start, $end) === false) { + $this->markTestIncomplete('Test case not found for '.$testMarker); + } + + // Expected double arrow positions are set as offsets + // in relation to the start of the array item. + // Change these to exact positions. + if ($expected !== false) { + $expected = ($start + $expected); + } + + $result = PassedParameters::getDoubleArrowPosition(self::$phpcsFile, $start, $end); + $this->assertSame($expected, $result); + + }//end testGetDoubleArrowPosition() + + + /** + * Data provider. + * + * @see testGetDoubleArrowPosition() + * + * @return array + */ + public function dataGetDoubleArrowPosition() + { + return [ + [ + '/* testValueNoArrow */', + false, + ], + [ + '/* testArrowNumericIndex */', + 8, + ], + [ + '/* testArrowStringIndex */', + 8, + ], + [ + '/* testArrowMultiTokenIndex */', + 12, + ], + [ + '/* testNoArrowValueShortArray */', + false, + ], + [ + '/* testNoArrowValueLongArray */', + false, + ], + [ + '/* testNoArrowValueNestedArrays */', + false, + ], + [ + '/* testNoArrowValueClosure */', + false, + ], + [ + '/* testArrowValueShortArray */', + 8, + ], + [ + '/* testArrowValueLongArray */', + 8, + ], + [ + '/* testArrowValueClosure */', + 8, + ], + ]; + + }//end dataGetDoubleArrowPosition() + + +}//end class diff --git a/tests/Core/Util/Sniffs/PassedParameters/GetParameterCountTest.inc b/tests/Core/Util/Sniffs/PassedParameters/GetParameterCountTest.inc new file mode 100644 index 0000000000..781e2c30d0 --- /dev/null +++ b/tests/Core/Util/Sniffs/PassedParameters/GetParameterCountTest.inc @@ -0,0 +1,199 @@ + 'b',]); + +/* testFunctionCall27 */ +json_encode(['a' => $a,]); + +/* testFunctionCall28 */ +json_encode(['a' => $a,] + (isset($b) ? ['b' => $b,] : [])); + +/* testFunctionCall29 */ +json_encode(['a' => $a,] + (isset($b) ? ['b' => $b, 'c' => $c,] : [])); + +/* testFunctionCall30 */ +json_encode(['a' => $a, 'b' => $b] + (isset($c) ? ['c' => $c, 'd' => $d] : [])); + +/* testFunctionCall31 */ +json_encode(['a' => $a, 'b' => $b] + (isset($c) ? ['c' => $c, 'd' => $d,] : [])); + +/* testFunctionCall32 */ +json_encode(['a' => $a, 'b' => $b] + (isset($c) ? ['c' => $c, 'd' => $d, $c => 'c'] : [])); + +/* testFunctionCall33 */ +json_encode(['a' => $a,] + (isset($b) ? ['b' => $b,] : []) + ['c' => $c, 'd' => $d,]); + +/* testFunctionCall34 */ +json_encode(['a' => 'b', 'c' => 'd',]); + +/* testFunctionCall35 */ +json_encode(['a' => ['b',],]); + +/* testFunctionCall36 */ +json_encode(['a' => ['b' => 'c',],]); + +/* testFunctionCall37 */ +json_encode(['a' => ['b' => 'c',], 'd' => ['e' => 'f',],]); + +/* testFunctionCall38 */ +json_encode(['a' => $a, 'b' => $b,]); + +/* testFunctionCall39 */ +json_encode(['a' => $a,] + ['b' => $b,]); + +/* testFunctionCall40 */ +json_encode(['a' => $a] + ['b' => $b, 'c' => $c,]); + +/* testFunctionCall41 */ +json_encode(['a' => $a, 'b' => $b] + ['c' => $c, 'd' => $d]); + +/* testFunctionCall42 */ +json_encode(['a' => $a, 'b' => $b] + ['c' => $c, 'd' => $d,]); + +/* testFunctionCall43 */ +json_encode(['a' => $a, 'b' => $b] + ['c' => $c, 'd' => $d, $c => 'c']); + +/* testFunctionCall44 */ +json_encode(['a' => $a, 'b' => $b,] + ['c' => $c]); + +/* testFunctionCall45 */ +json_encode(['a' => $a, 'b' => $b,] + ['c' => $c,]); + +/* testFunctionCall46 */ +json_encode(['a' => $a, 'b' => $b, 'c' => $c]); + +/* testFunctionCall47 */ +json_encode(['a' => $a, 'b' => $b, 'c' => $c,] + ['c' => $c, 'd' => $d,]); + +/* testLongArray1 */ +$foo = array( 1, 2, 3, 4, 5, 6, true ); + +/* testLongArray2 */ +$foo = array(str_replace("../", "/", trim($value))); // 1 + +/* testLongArray3 */ +$foo = array($stHour, 0, 0, $arrStDt[0], $arrStDt[1], $arrStDt[2]); // 6 + +/* testLongArray4 */ +$foo = array(0, 0, date('s'), date('m'), date('d'), date('Y')); // 6 + +/* testLongArray5 */ +$foo = array(some_call(5, 1), another(1), why(5, 1, 2), 4, 5, 6); // 6 + +/* testLongArray6 */ +$foo = array('a' => $a, 'b' => $b, 'c' => $c); + +/* testLongArray7 */ +$foo = array('a' => $a, 'b' => $b, 'c' => (isset($c) ? $c : null)); + +/* testLongArray8 */ +$foo = array(0 => $a, 2 => $b, 6 => (isset($c) ? $c : null)); + +/* testShortArray1 */ +$bar = [ 1, 2, 3, 4, 5, 6, true ]; + +/* testShortArray2 */ +$bar = [str_replace("../", "/", trim($value))]; // 1 + +/* testShortArray3 */ +$bar = [$stHour, 0, 0, $arrStDt[0], $arrStDt[1], $arrStDt[2]]; // 6 + +/* testShortArray4 */ +$bar = [0, 0, date('s'), date('m'), date('d'), date('Y')]; // 6 + +/* testShortArray5 */ +$bar = [some_call(5, 1), another(1), why(5, 1, 2), 4, 5, 6]; // 6 + +/* testShortArray6 */ +$bar = ['a' => $a, 'b' => $b, 'c' => $c]; + +/* testShortArray7 */ +$bar = ['a' => $a, 'b' => $b, 'c' => (isset($c) ? $c : null)]; + +/* testShortArray8 */ +$bar = [0 => $a, 2 => $b, 6 => (isset($c) ? $c : null)]; diff --git a/tests/Core/Util/Sniffs/PassedParameters/GetParameterCountTest.php b/tests/Core/Util/Sniffs/PassedParameters/GetParameterCountTest.php new file mode 100644 index 0000000000..15ad63cde7 --- /dev/null +++ b/tests/Core/Util/Sniffs/PassedParameters/GetParameterCountTest.php @@ -0,0 +1,316 @@ + + * @copyright 2016-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\PassedParameters; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\PassedParameters; + +class GetParameterCountTest extends AbstractMethodUnitTest +{ + + + /** + * Test correctly counting the number of passed parameters. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param int $expected The expected parameter count. + * + * @dataProvider dataGetParameterCount + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::getParameterCount + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::getParameters + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::hasParameters + * + * @return void + */ + public function testGetParameterCount($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, [T_STRING, T_ARRAY, T_OPEN_SHORT_ARRAY, T_LIST, T_ISSET, T_UNSET]); + $result = PassedParameters::getParameterCount(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testGetParameterCount() + + + /** + * Data provider. + * + * @see testGetParameterCount() + * + * @return array + */ + public function dataGetParameterCount() + { + return [ + [ + '/* testFunctionCall0 */', + 0, + ], + [ + '/* testFunctionCall1 */', + 1, + ], + [ + '/* testFunctionCall2 */', + 2, + ], + [ + '/* testFunctionCall3 */', + 3, + ], + [ + '/* testFunctionCall4 */', + 4, + ], + [ + '/* testFunctionCall5 */', + 5, + ], + [ + '/* testFunctionCall6 */', + 6, + ], + [ + '/* testFunctionCall7 */', + 7, + ], + [ + '/* testFunctionCall8 */', + 1, + ], + [ + '/* testFunctionCall9 */', + 1, + ], + [ + '/* testFunctionCall10 */', + 1, + ], + [ + '/* testFunctionCall11 */', + 2, + ], + [ + '/* testFunctionCall12 */', + 1, + ], + [ + '/* testFunctionCall13 */', + 1, + ], + [ + '/* testFunctionCall14 */', + 1, + ], + [ + '/* testFunctionCall15 */', + 2, + ], + [ + '/* testFunctionCall16 */', + 6, + ], + [ + '/* testFunctionCall17 */', + 6, + ], + [ + '/* testFunctionCall18 */', + 6, + ], + [ + '/* testFunctionCall19 */', + 6, + ], + [ + '/* testFunctionCall20 */', + 6, + ], + [ + '/* testFunctionCall21 */', + 6, + ], + [ + '/* testFunctionCall22 */', + 6, + ], + [ + '/* testFunctionCall23 */', + 3, + ], + [ + '/* testFunctionCall24 */', + 1, + ], + [ + '/* testFunctionCall25 */', + 1, + ], + [ + '/* testFunctionCall26 */', + 1, + ], + [ + '/* testFunctionCall27 */', + 1, + ], + [ + '/* testFunctionCall28 */', + 1, + ], + [ + '/* testFunctionCall29 */', + 1, + ], + [ + '/* testFunctionCall30 */', + 1, + ], + [ + '/* testFunctionCall31 */', + 1, + ], + [ + '/* testFunctionCall32 */', + 1, + ], + [ + '/* testFunctionCall33 */', + 1, + ], + [ + '/* testFunctionCall34 */', + 1, + ], + [ + '/* testFunctionCall35 */', + 1, + ], + [ + '/* testFunctionCall36 */', + 1, + ], + [ + '/* testFunctionCall37 */', + 1, + ], + [ + '/* testFunctionCall38 */', + 1, + ], + [ + '/* testFunctionCall39 */', + 1, + ], + [ + '/* testFunctionCall40 */', + 1, + ], + [ + '/* testFunctionCall41 */', + 1, + ], + [ + '/* testFunctionCall42 */', + 1, + ], + [ + '/* testFunctionCall43 */', + 1, + ], + [ + '/* testFunctionCall44 */', + 1, + ], + [ + '/* testFunctionCall45 */', + 1, + ], + [ + '/* testFunctionCall46 */', + 1, + ], + [ + '/* testFunctionCall47 */', + 1, + ], + + // Long arrays. + [ + '/* testLongArray1 */', + 7, + ], + [ + '/* testLongArray2 */', + 1, + ], + [ + '/* testLongArray3 */', + 6, + ], + [ + '/* testLongArray4 */', + 6, + ], + [ + '/* testLongArray5 */', + 6, + ], + [ + '/* testLongArray6 */', + 3, + ], + [ + '/* testLongArray7 */', + 3, + ], + [ + '/* testLongArray8 */', + 3, + ], + + // Short arrays. + [ + '/* testShortArray1 */', + 7, + ], + [ + '/* testShortArray2 */', + 1, + ], + [ + '/* testShortArray3 */', + 6, + ], + [ + '/* testShortArray4 */', + 6, + ], + [ + '/* testShortArray5 */', + 6, + ], + [ + '/* testShortArray6 */', + 3, + ], + [ + '/* testShortArray7 */', + 3, + ], + [ + '/* testShortArray8 */', + 3, + ], + ]; + + }//end dataGetParameterCount() + + +}//end class diff --git a/tests/Core/Util/Sniffs/PassedParameters/GetParametersTest.inc b/tests/Core/Util/Sniffs/PassedParameters/GetParametersTest.inc new file mode 100644 index 0000000000..c655863ecb --- /dev/null +++ b/tests/Core/Util/Sniffs/PassedParameters/GetParametersTest.inc @@ -0,0 +1,123 @@ + $a,] + (isset($b) ? ['b' => $b,] : [])); + +/* testLongArrayNestedFunctionCalls */ +$foo = array(some_call(5, 1), another(1), why(5, 1, 2), 4, 5, 6); // 6 + +/* testSimpleLongArray */ +$foo = array( 1, 2, 3, 4, 5, 6, true ); + +/* testLongArrayWithKeys */ +$foo = array('a' => $a, 'b' => $b, 'c' => $c); + +/* testShortArrayNestedFunctionCalls */ +$bar = [0, 0, date('s', $timestamp), date('m'), date('d'), date('Y')]; // 6 + +/* testShortArrayMoreNestedFunctionCalls */ +$bar = [str_replace("../", "/", trim($value))]; // 1 + +/* testShortArrayWithKeysAndTernary */ +$bar = [0 => $a, 2 => $b, 6 => (isset($c) ? $c : null)]; + +/* testNestedArraysToplevel */ +$array = array( + '1' => array( + 0 => 'more nesting', + /* testNestedArraysLevel2 */ + 1 => array(1,2,3), + ), + /* testNestedArraysLevel1 */ + '2' => [ + 0 => 'more nesting', + 1 => [1,2,3], + ], +); + +/* testFunctionCallNestedArrayNestedClosureWithCommas */ +preg_replace_callback_array( + /* testShortArrayNestedClosureWithCommas */ + [ + '~'.$dyn.'~J' => function ($match) { + echo strlen($match[0]), ' matches for "a" found', PHP_EOL; + }, + '~'.function_call().'~i' => function ($match) { + echo strlen($match[0]), ' matches for "b" found', PHP_EOL; + }, + ], + $subject +); + +/* testShortArrayNestedAnonClass */ +$array = [ + 'class' => new class() { + public $prop = [1,2,3]; + public function test( $foo, $bar ) { + echo $foo, $bar; + } + }, + 'anotherclass' => new class() { + public function test( $foo, $bar ) { + echo $foo, $bar; + } + }, +]; + +/* testVariableFunctionCall */ +$closure($a, (1 + 20), $a & $b ); + +/* testStaticVariableFunctionCall */ +self::$closureInStaticProperty($a->property, $b->call() ); + +/* testSimpleList */ +list($id, $name) = $data; + +/* testNestedList */ +list($a, list($b, $c)) = array(1, array(2, 3)); + +/* testListWithKeys */ +list('name' => $a, 'id' => $b, 'field' => $a) = ['name' => 1, 'id' => 2, 'field' => 3]; + +/* testListWithEmptyEntries */ +list( , $a, , $b, , $a, ,) = [1, 2, 3, 4, 5, 6, 7, 8]; + +/* testMultiLineKeyedListWithTrailingComma */ + list( + "name" => $this->name, + "colour" => $this->colour, + "age" => $this->age, + "cuteness" => $this->cuteness, + ) = $attributes; + +/* testShortList */ +[$a, $b, $c] = [1, 2 => 'x', 'z' => 'c']; + +/* testNestedShortList */ +[[$a, $b], [$b, $a]] = array(array(10, 11), array(2, 3)); + +/* testIsset */ +if ( isset( + $variable, + $object->property, + static::$property, + $array[$name][$sub], +)) {} + +/* testUnset */ +unset( $variable, $object->property, static::$property, $array[$name], ); diff --git a/tests/Core/Util/Sniffs/PassedParameters/GetParametersTest.php b/tests/Core/Util/Sniffs/PassedParameters/GetParametersTest.php new file mode 100644 index 0000000000..32b5d2c082 --- /dev/null +++ b/tests/Core/Util/Sniffs/PassedParameters/GetParametersTest.php @@ -0,0 +1,752 @@ + + * @copyright 2016-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\PassedParameters; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\PassedParameters; + +class GetParametersTest extends AbstractMethodUnitTest +{ + + + /** + * Test retrieving the parameter details from a function call without parameters. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::getParameters + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::getParameter + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::hasParameters + * + * @return void + */ + public function testGetParametersNoParams() + { + $stackPtr = $this->getTargetToken('/* testNoParams */', T_STRING); + + $result = PassedParameters::getParameters(self::$phpcsFile, $stackPtr); + $this->assertSame([], $result); + + $result = PassedParameters::getParameter(self::$phpcsFile, $stackPtr, 2); + $this->assertFalse($result); + + }//end testGetParametersNoParams() + + + /** + * Test retrieving the parameter details from a function call or construct. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param int|string $targetType The type of token to look for. + * @param array $expected The expected parameter array. + * + * @dataProvider dataGetParameters + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::getParameters + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::hasParameters + * + * @return void + */ + public function testGetParameters($testMarker, $targetType, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, [$targetType]); + + // Start/end token position values in the expected array are set as offsets + // in relation to the target token. + // Change these to exact positions based on the retrieved stackPtr. + foreach ($expected as $key => $value) { + $expected[$key]['start'] = ($stackPtr + $value['start']); + $expected[$key]['end'] = ($stackPtr + $value['end']); + } + + $result = PassedParameters::getParameters(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testGetParameters() + + + /** + * Data provider. + * + * @see testGetParameters() + * + * @return array + */ + public function dataGetParameters() + { + return [ + [ + '/* testFunctionCall */', + T_STRING, + [ + 1 => [ + 'start' => 2, + 'end' => 3, + 'raw' => '1', + ], + 2 => [ + 'start' => 5, + 'end' => 6, + 'raw' => '2', + ], + 3 => [ + 'start' => 8, + 'end' => 9, + 'raw' => '3', + ], + 4 => [ + 'start' => 11, + 'end' => 12, + 'raw' => '4', + ], + 5 => [ + 'start' => 14, + 'end' => 15, + 'raw' => '5', + ], + 6 => [ + 'start' => 17, + 'end' => 18, + 'raw' => '6', + ], + 7 => [ + 'start' => 20, + 'end' => 22, + 'raw' => 'true', + ], + ], + ], + [ + '/* testFunctionCallNestedFunctionCall */', + T_STRING, + [ + 1 => [ + 'start' => 2, + 'end' => 9, + 'raw' => 'dirname( __FILE__ )', + ], + ], + ], + [ + '/* testAnotherFunctionCall */', + T_STRING, + [ + 1 => [ + 'start' => 2, + 'end' => 2, + 'raw' => '$stHour', + ], + 2 => [ + 'start' => 4, + 'end' => 5, + 'raw' => '0', + ], + 3 => [ + 'start' => 7, + 'end' => 8, + 'raw' => '0', + ], + 4 => [ + 'start' => 10, + 'end' => 14, + 'raw' => '$arrStDt[0]', + ], + 5 => [ + 'start' => 16, + 'end' => 20, + 'raw' => '$arrStDt[1]', + ], + 6 => [ + 'start' => 22, + 'end' => 26, + 'raw' => '$arrStDt[2]', + ], + ], + + ], + [ + '/* testFunctionCallTrailingComma */', + T_STRING, + [ + 1 => [ + 'start' => 2, + 'end' => 5, + 'raw' => 'array()', + ], + ], + ], + [ + '/* testFunctionCallNestedShortArray */', + T_STRING, + [ + 1 => [ + 'start' => 2, + 'end' => 34, + 'raw' => '[\'a\' => $a,] + (isset($b) ? [\'b\' => $b,] : [])', + ], + ], + ], + [ + '/* testFunctionCallNestedArrayNestedClosureWithCommas */', + T_STRING, + [ + 1 => [ + 'start' => 2, + 'end' => 90, + 'raw' => '/* testShortArrayNestedClosureWithCommas */ + [ + \'~\'.$dyn.\'~J\' => function ($match) { + echo strlen($match[0]), \' matches for "a" found\', PHP_EOL; + }, + \'~\'.function_call().\'~i\' => function ($match) { + echo strlen($match[0]), \' matches for "b" found\', PHP_EOL; + }, + ]', + ], + 2 => [ + 'start' => 92, + 'end' => 95, + 'raw' => '$subject', + ], + ], + ], + + // Long array. + [ + '/* testLongArrayNestedFunctionCalls */', + T_ARRAY, + [ + 1 => [ + 'start' => 2, + 'end' => 8, + 'raw' => 'some_call(5, 1)', + ], + 2 => [ + 'start' => 10, + 'end' => 14, + 'raw' => 'another(1)', + ], + 3 => [ + 'start' => 16, + 'end' => 26, + 'raw' => 'why(5, 1, 2)', + ], + 4 => [ + 'start' => 28, + 'end' => 29, + 'raw' => '4', + ], + 5 => [ + 'start' => 31, + 'end' => 32, + 'raw' => '5', + ], + 6 => [ + 'start' => 34, + 'end' => 35, + 'raw' => '6', + ], + ], + ], + + // Short array. + [ + '/* testShortArrayNestedFunctionCalls */', + T_OPEN_SHORT_ARRAY, + [ + 1 => [ + 'start' => 1, + 'end' => 1, + 'raw' => '0', + ], + 2 => [ + 'start' => 3, + 'end' => 4, + 'raw' => '0', + ], + 3 => [ + 'start' => 6, + 'end' => 13, + 'raw' => 'date(\'s\', $timestamp)', + ], + 4 => [ + 'start' => 15, + 'end' => 19, + 'raw' => 'date(\'m\')', + ], + 5 => [ + 'start' => 21, + 'end' => 25, + 'raw' => 'date(\'d\')', + ], + 6 => [ + 'start' => 27, + 'end' => 31, + 'raw' => 'date(\'Y\')', + ], + ], + ], + + // Nested arrays. + [ + '/* testNestedArraysToplevel */', + T_ARRAY, + [ + 1 => [ + 'start' => 2, + 'end' => 38, + 'raw' => '\'1\' => array( + 0 => \'more nesting\', + /* testNestedArraysLevel2 */ + 1 => array(1,2,3), + )', + ], + 2 => [ + 'start' => 40, + 'end' => 74, + 'raw' => '/* testNestedArraysLevel1 */ + \'2\' => [ + 0 => \'more nesting\', + 1 => [1,2,3], + ]', + ], + ], + ], + + // Array containing closure. + [ + '/* testShortArrayNestedClosureWithCommas */', + T_OPEN_SHORT_ARRAY, + [ + 1 => [ + 'start' => 1, + 'end' => 38, + 'raw' => '\'~\'.$dyn.\'~J\' => function ($match) { + echo strlen($match[0]), \' matches for "a" found\', PHP_EOL; + }', + ], + 2 => [ + 'start' => 40, + 'end' => 79, + 'raw' => '\'~\'.function_call().\'~i\' => function ($match) { + echo strlen($match[0]), \' matches for "b" found\', PHP_EOL; + }', + ], + ], + ], + + // Array containing anonymous class. + [ + '/* testShortArrayNestedAnonClass */', + T_OPEN_SHORT_ARRAY, + [ + 1 => [ + 'start' => 1, + 'end' => 61, + 'raw' => '\'class\' => new class() { + public $prop = [1,2,3]; + public function test( $foo, $bar ) { + echo $foo, $bar; + } + }', + ], + 2 => [ + 'start' => 63, + 'end' => 107, + 'raw' => '\'anotherclass\' => new class() { + public function test( $foo, $bar ) { + echo $foo, $bar; + } + }', + ], + ], + ], + + // Function calling closure in variable. + [ + '/* testVariableFunctionCall */', + T_VARIABLE, + [ + 1 => [ + 'start' => 2, + 'end' => 2, + 'raw' => '$a', + ], + 2 => [ + 'start' => 4, + 'end' => 11, + 'raw' => '(1 + 20)', + ], + 3 => [ + 'start' => 13, + 'end' => 19, + 'raw' => '$a & $b', + ], + ], + ], + [ + '/* testStaticVariableFunctionCall */', + T_VARIABLE, + [ + 1 => [ + 'start' => 2, + 'end' => 4, + 'raw' => '$a->property', + ], + 2 => [ + 'start' => 6, + 'end' => 12, + 'raw' => '$b->call()', + ], + ], + ], + + // Lists. + [ + '/* testNestedList */', + T_LIST, + [ + 1 => [ + 'start' => 2, + 'end' => 2, + 'raw' => '$a', + ], + 2 => [ + 'start' => 4, + 'end' => 11, + 'raw' => 'list($b, $c)', + ], + ], + ], + [ + '/* testListWithEmptyEntries */', + T_LIST, + [ + 1 => [ + 'start' => 2, + 'end' => 2, + 'raw' => '', + ], + 2 => [ + 'start' => 4, + 'end' => 5, + 'raw' => '$a', + ], + 3 => [ + 'start' => 7, + 'end' => 7, + 'raw' => '', + ], + 4 => [ + 'start' => 9, + 'end' => 10, + 'raw' => '$b', + ], + 5 => [ + 'start' => 12, + 'end' => 12, + 'raw' => '', + ], + 6 => [ + 'start' => 14, + 'end' => 15, + 'raw' => '$a', + ], + 7 => [ + 'start' => 17, + 'end' => 17, + 'raw' => '', + ], + ], + ], + [ + '/* testMultiLineKeyedListWithTrailingComma */', + T_LIST, + [ + 1 => [ + 'start' => 2, + 'end' => 10, + 'raw' => '"name" => $this->name', + ], + 2 => [ + 'start' => 12, + 'end' => 20, + 'raw' => '"colour" => $this->colour', + ], + 3 => [ + 'start' => 22, + 'end' => 30, + 'raw' => '"age" => $this->age', + ], + 4 => [ + 'start' => 32, + 'end' => 40, + 'raw' => '"cuteness" => $this->cuteness', + ], + ], + ], + [ + '/* testNestedShortList */', + T_OPEN_SHORT_ARRAY, + [ + 1 => [ + 'start' => 1, + 'end' => 6, + 'raw' => '[$a, $b]', + ], + 2 => [ + 'start' => 8, + 'end' => 14, + 'raw' => '[$b, $a]', + ], + ], + ], + [ + '/* testIsset */', + T_ISSET, + [ + 1 => [ + 'start' => 2, + 'end' => 4, + 'raw' => '$variable', + ], + 2 => [ + 'start' => 6, + 'end' => 10, + 'raw' => '$object->property', + ], + 3 => [ + 'start' => 12, + 'end' => 16, + 'raw' => 'static::$property', + ], + 4 => [ + 'start' => 18, + 'end' => 26, + 'raw' => '$array[$name][$sub]', + ], + ], + ], + [ + '/* testUnset */', + T_UNSET, + [ + 1 => [ + 'start' => 2, + 'end' => 3, + 'raw' => '$variable', + ], + 2 => [ + 'start' => 5, + 'end' => 8, + 'raw' => '$object->property', + ], + 3 => [ + 'start' => 10, + 'end' => 13, + 'raw' => 'static::$property', + ], + 4 => [ + 'start' => 15, + 'end' => 19, + 'raw' => '$array[$name]', + ], + ], + ], + ]; + + }//end dataGetParameters() + + + /** + * Test retrieving the details for a specific parameter from a function call or construct. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param int|string $targetType The type of token to look for. + * @param int $paramPosition The position of the parameter we want to retrieve the details for. + * @param array $expected The expected array for the specific parameter. + * + * @dataProvider dataGetParameter + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::getParameter + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::getParameters + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::hasParameters + * + * @return void + */ + public function testGetParameter($testMarker, $targetType, $paramPosition, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, [$targetType]); + + // Start/end token position values in the expected array are set as offsets + // in relation to the target token. + // Change these to exact positions based on the retrieved stackPtr. + $expected['start'] += $stackPtr; + $expected['end'] += $stackPtr; + + $result = PassedParameters::getParameter(self::$phpcsFile, $stackPtr, $paramPosition); + $this->assertSame($expected, $result); + + }//end testGetParameter() + + + /** + * Data provider. + * + * @see testGetParameter() + * + * @return array + */ + public function dataGetParameter() + { + return [ + [ + '/* testFunctionCall */', + T_STRING, + 4, + [ + 'start' => 11, + 'end' => 12, + 'raw' => '4', + ], + ], + [ + '/* testFunctionCallNestedFunctionCall */', + T_STRING, + 1, + [ + 'start' => 2, + 'end' => 9, + 'raw' => 'dirname( __FILE__ )', + ], + ], + [ + '/* testAnotherFunctionCall */', + T_STRING, + 1, + [ + 'start' => 2, + 'end' => 2, + 'raw' => '$stHour', + ], + ], + [ + '/* testAnotherFunctionCall */', + T_STRING, + 6, + [ + 'start' => 22, + 'end' => 26, + 'raw' => '$arrStDt[2]', + ], + ], + [ + '/* testLongArrayNestedFunctionCalls */', + T_ARRAY, + 3, + [ + 'start' => 16, + 'end' => 26, + 'raw' => 'why(5, 1, 2)', + ], + ], + [ + '/* testSimpleLongArray */', + T_ARRAY, + 1, + [ + 'start' => 2, + 'end' => 3, + 'raw' => '1', + ], + ], + [ + '/* testSimpleLongArray */', + T_ARRAY, + 7, + [ + 'start' => 20, + 'end' => 22, + 'raw' => 'true', + ], + ], + [ + '/* testLongArrayWithKeys */', + T_ARRAY, + 2, + [ + 'start' => 8, + 'end' => 13, + 'raw' => '\'b\' => $b', + ], + ], + [ + '/* testShortArrayMoreNestedFunctionCalls */', + T_OPEN_SHORT_ARRAY, + 1, + [ + 'start' => 1, + 'end' => 13, + 'raw' => 'str_replace("../", "/", trim($value))', + ], + ], + [ + '/* testShortArrayWithKeysAndTernary */', + T_OPEN_SHORT_ARRAY, + 3, + [ + 'start' => 14, + 'end' => 32, + 'raw' => '6 => (isset($c) ? $c : null)', + ], + ], + [ + '/* testNestedArraysLevel2 */', + T_ARRAY, + 1, + [ + 'start' => 2, + 'end' => 2, + 'raw' => '1', + ], + ], + [ + '/* testNestedArraysLevel1 */', + T_OPEN_SHORT_ARRAY, + 2, + [ + 'start' => 9, + 'end' => 21, + 'raw' => '1 => [1,2,3]', + ], + ], + [ + '/* testListWithKeys */', + T_LIST, + 2, + [ + 'start' => 8, + 'end' => 13, + 'raw' => '\'id\' => $b', + ], + ], + [ + '/* testShortList */', + T_OPEN_SHORT_ARRAY, + 3, + [ + 'start' => 6, + 'end' => 7, + 'raw' => '$c', + ], + ], + ]; + + }//end dataGetParameter() + + +}//end class diff --git a/tests/Core/Util/Sniffs/PassedParameters/HasParametersTest.inc b/tests/Core/Util/Sniffs/PassedParameters/HasParametersTest.inc new file mode 100644 index 0000000000..7c2e99dde9 --- /dev/null +++ b/tests/Core/Util/Sniffs/PassedParameters/HasParametersTest.inc @@ -0,0 +1,172 @@ +methodName(); + +// Function calls: no parameters. + +/* testNoParamsFunctionCall1 */ +some_function(); + +/* testNoParamsFunctionCall2 */ +some_function( ); + +/* testNoParamsFunctionCall3 */ +some_function( /*nothing here*/ ); + +/* testNoParamsFunctionCall4 */ +$closure(/*nothing here*/); + +// Function calls: has parameters. + +/* testHasParamsFunctionCall1 */ +some_function( 1 ); + +/* testHasParamsFunctionCall2 */ +$closure(1,2,3); + +class Bar { + public static function getInstance() { + /* testHasParamsFunctionCall3 */ + return new self(true); + } +} + +/* testHasParamsFunctionCall4 */ +${$functionname}(1,2,3); + +/* testHasParamsFunctionCall5 */ +$obj->{$var}('foo'); + +/* testHasParamsFunctionCall6 */ +$this->{self::$methodname}([]); + +/* testHasParamsFunctionCall7 */ +${$this->functionname}(array(1)); + +/* testHasParamsFunctionCall8 */ +${FUNCTION_NAME}(false); + +$closureCall = (function ($a, $b) { + /* testHasParamsFunctionCall9 */ + })('a','b'); + +/* testNotAClosureDeclarationWithCall2 */ +(function ($a, $b) { + /* testNotAClosureDeclarationWithCall3 */ + return function ($c, $d) + /* testNotAClosureDeclarationWithCall4 */ + use ($a, $b) + { + echo $a, $b, $c, $d; + }; +/* testHasParamsFunctionCall10 */ +}) + /* testHasParamsFunctionCall11 */ + ('a','b') + /* testNotAClosureDeclarationWithCall5 */ + ('c','d'); + +// Arrays: no parameters. + +/* testNoParamsLongArray1 */ +$foo = array(); + +/* testNoParamsLongArray2 */ +$foo = array( ); + +/* testNoParamsLongArray3 */ +$foo = array( /*nothing here*/ ); + +/* testNoParamsLongArray4 */ +$foo = array(/*nothing here*/); + +/* testNoParamsShortArray1 */ +$bar = []; + +/* testNoParamsShortArray2 */ +$bar = [ ]; + +/* testNoParamsShortArray3 */ +$bar = [ /*nothing here*/ ]; + +/* testNoParamsShortArray4 */ +$bar = [/*nothing here*/]; + +// Arrays: has parameters. + +/* testHasParamsLongArray1 */ +$foo = array( 1 ); + +/* testHasParamsLongArray2 */ +$foo = array(1,2,3); + +/* testHasParamsLongArray3 */ +$foo = array(true); + +/* testHasParamsShortArray1 */ +$bar = [ 1 ]; + +/* testHasParamsShortArray2 */ +$bar = [1,2,3]; + +/* testHasParamsShortArray3 */ +$bar = [true]; + +/* testNoParamsLongList */ +list() = $array; // Intentional fatal error. + +/* testHasParamsLongList */ +list($a) = $array; + +/* testNoParamsShortList */ +[ + // phpcs:ignore Standard.Cat.Sniff -- for reasons +] = $array; // Intentional fatal error. + +/* testHasParamsShortList */ +[$this->prop] = $array; + +/* testNoParamsIsset */ +$a = isset( /* comment */ ); // Intentional parse error. + +/* testHasParamsIsset */ +$a = isset( $array[$key] ); + +/* testNoParamsUnset */ +unset( + + +); // Intentional parse error. + +/* testHasParamsUnset */ +unset( + + $hello, + +); + +// Intentional parse error. +/* testNoCloseParenthesis */ +$array = array(1, 2, 3 + +// Intentional parse error. +/* testNoOpenParenthesis */ +$array = function_call[]; + +// Intentional parse error. This has to be the last test in the file without a new line after it. +/* testLiveCoding */ +$array = array \ No newline at end of file diff --git a/tests/Core/Util/Sniffs/PassedParameters/HasParametersTest.php b/tests/Core/Util/Sniffs/PassedParameters/HasParametersTest.php new file mode 100644 index 0000000000..9213f52def --- /dev/null +++ b/tests/Core/Util/Sniffs/PassedParameters/HasParametersTest.php @@ -0,0 +1,341 @@ + + * @copyright 2016-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\PassedParameters; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\PassedParameters; + +class HasParametersTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a token which is not supported by + * these methods is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage The hasParameters() method expects a function call, array, list, isset or unset token to be passed. Received "T_INTERFACE" instead + * + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::hasParameters + * + * @return void + */ + public function testNotAnAcceptedTokenException() + { + $interface = $this->getTargetToken('/* testNotAnAcceptedToken */', T_INTERFACE); + $result = PassedParameters::hasParameters(self::$phpcsFile, $interface); + + }//end testNotAnAcceptedTokenException() + + + /** + * Test receiving an expected exception when T_SELF is passed not preceeded by `new`. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage The hasParameters() method expects a function call, array, list, isset or unset token to be passed. Received "T_SELF" instead + * + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::hasParameters + * + * @return void + */ + public function testNotACallToConstructor() + { + $self = $this->getTargetToken('/* testNotACallToConstructor */', T_SELF); + $result = PassedParameters::hasParameters(self::$phpcsFile, $self); + + }//end testNotACallToConstructor() + + + /** + * Test receiving an expected exception when T_CLOSE_CURLY_BRACKET is passed which isn't a function call. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage The hasParameters() method expects a function call, array, list, isset or unset token to be passed. Received a "T_CLOSE_CURLY_BRACKET" which is not function call + * + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::hasParameters + * + * @return void + */ + public function testNotAVariableFunctionCall() + { + $self = $this->getTargetToken('/* testNotAVariableFunctionCall */', T_CLOSE_CURLY_BRACKET); + $result = PassedParameters::hasParameters(self::$phpcsFile, $self); + + }//end testNotAVariableFunctionCall() + + + /** + * Test receiving an expected exception when T_CLOSE_PARENTHESIS is passed which isn't a function call. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage The hasParameters() method expects a function call, array, list, isset or unset token to be passed. Received a "T_CLOSE_PARENTHESIS" which is not function call + * + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::hasParameters + * + * @return void + */ + public function testNotAClosureDeclarationWithCall() + { + for ($i = 1; $i <= 5; $i++) { + $closeParen = $this->getTargetToken('/* testNotAClosureDeclarationWithCall'.$i.' */', T_CLOSE_PARENTHESIS); + $result = PassedParameters::hasParameters(self::$phpcsFile, $closeParen); + } + + }//end testNotAClosureDeclarationWithCall() + + + /** + * Test correctly identifying whether parameters were passed to a function call or construct. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param int|string $targetType The type of token to look for. + * @param bool $expected Whether or not the function/array has parameters/values. + * + * @dataProvider dataHasParameters + * @covers \PHP_CodeSniffer\Util\Sniffs\PassedParameters::hasParameters + * + * @return void + */ + public function testHasParameters($testMarker, $targetType, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, [$targetType]); + $result = PassedParameters::hasParameters(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testHasParameters() + + + /** + * Data provider. + * + * @see testHasParameters() + * + * @return array + */ + public function dataHasParameters() + { + return [ + // Function calls. + [ + '/* testNoParamsFunctionCall1 */', + T_STRING, + false, + ], + [ + '/* testNoParamsFunctionCall2 */', + T_STRING, + false, + ], + [ + '/* testNoParamsFunctionCall3 */', + T_STRING, + false, + ], + [ + '/* testNoParamsFunctionCall4 */', + T_VARIABLE, + false, + ], + [ + '/* testHasParamsFunctionCall1 */', + T_STRING, + true, + ], + [ + '/* testHasParamsFunctionCall2 */', + T_VARIABLE, + true, + ], + [ + '/* testHasParamsFunctionCall3 */', + T_SELF, + true, + ], + [ + '/* testHasParamsFunctionCall4 */', + T_CLOSE_CURLY_BRACKET, + true, + ], + [ + '/* testHasParamsFunctionCall5 */', + T_CLOSE_CURLY_BRACKET, + true, + ], + [ + '/* testHasParamsFunctionCall6 */', + T_CLOSE_CURLY_BRACKET, + true, + ], + [ + '/* testHasParamsFunctionCall7 */', + T_CLOSE_CURLY_BRACKET, + true, + ], + [ + '/* testHasParamsFunctionCall8 */', + T_CLOSE_CURLY_BRACKET, + true, + ], + [ + '/* testHasParamsFunctionCall9 */', + T_CLOSE_PARENTHESIS, + true, + ], + [ + '/* testHasParamsFunctionCall10 */', + T_CLOSE_PARENTHESIS, + true, + ], + [ + '/* testHasParamsFunctionCall11 */', + T_CLOSE_PARENTHESIS, + true, + ], + + // Arrays. + [ + '/* testNoParamsLongArray1 */', + T_ARRAY, + false, + ], + [ + '/* testNoParamsLongArray2 */', + T_ARRAY, + false, + ], + [ + '/* testNoParamsLongArray3 */', + T_ARRAY, + false, + ], + [ + '/* testNoParamsLongArray4 */', + T_ARRAY, + false, + ], + [ + '/* testNoParamsShortArray1 */', + T_OPEN_SHORT_ARRAY, + false, + ], + [ + '/* testNoParamsShortArray2 */', + T_OPEN_SHORT_ARRAY, + false, + ], + [ + '/* testNoParamsShortArray3 */', + T_OPEN_SHORT_ARRAY, + false, + ], + [ + '/* testNoParamsShortArray4 */', + T_OPEN_SHORT_ARRAY, + false, + ], + [ + '/* testHasParamsLongArray1 */', + T_ARRAY, + true, + ], + [ + '/* testHasParamsLongArray2 */', + T_ARRAY, + true, + ], + [ + '/* testHasParamsLongArray3 */', + T_ARRAY, + true, + ], + [ + '/* testHasParamsShortArray1 */', + T_OPEN_SHORT_ARRAY, + true, + ], + [ + '/* testHasParamsShortArray2 */', + T_OPEN_SHORT_ARRAY, + true, + ], + [ + '/* testHasParamsShortArray3 */', + T_OPEN_SHORT_ARRAY, + true, + ], + + // Lists. + [ + '/* testNoParamsLongList */', + T_LIST, + false, + ], + [ + '/* testHasParamsLongList */', + T_LIST, + true, + ], + [ + '/* testNoParamsShortList */', + T_OPEN_SHORT_ARRAY, + false, + ], + [ + '/* testHasParamsShortList */', + T_OPEN_SHORT_ARRAY, + true, + ], + + // Isset. + [ + '/* testNoParamsIsset */', + T_ISSET, + false, + ], + [ + '/* testHasParamsIsset */', + T_ISSET, + true, + ], + + // Unset. + [ + '/* testNoParamsUnset */', + T_UNSET, + false, + ], + [ + '/* testHasParamsUnset */', + T_UNSET, + true, + ], + + // Defensive coding against parse errors and live coding. + [ + '/* testNoCloseParenthesis */', + T_ARRAY, + false, + ], + [ + '/* testNoOpenParenthesis */', + T_STRING, + false, + ], + [ + '/* testLiveCoding */', + T_ARRAY, + false, + ], + ]; + + }//end dataHasParameters() + + +}//end class diff --git a/tests/Core/Util/Sniffs/TextStrings/GetCompleteTextStringTest.inc b/tests/Core/Util/Sniffs/TextStrings/GetCompleteTextStringTest.inc new file mode 100644 index 0000000000..fe0df4fb67 --- /dev/null +++ b/tests/Core/Util/Sniffs/TextStrings/GetCompleteTextStringTest.inc @@ -0,0 +1,44 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\TextStrings; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\TextStrings; + +class GetCompleteTextStringTest extends AbstractMethodUnitTest +{ + + /** + * Token types to target for these tests. + * + * @var array + */ + private $targets = [ + T_START_HEREDOC, + T_START_NOWDOC, + T_CONSTANT_ENCAPSED_STRING, + T_DOUBLE_QUOTED_STRING, + ]; + + + /** + * Test receiving an expected exception when a non text string is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_START_HEREDOC, T_START_NOWDOC, T_CONSTANT_ENCAPSED_STRING or T_DOUBLE_QUOTED_STRING + * + * @covers \PHP_CodeSniffer\Util\Sniffs\TextStrings::getCompleteTextString + * + * @return void + */ + public function testNotATextStringException() + { + $next = $this->getTargetToken('/* testNotATextString */', T_RETURN); + $result = TextStrings::getCompleteTextString(self::$phpcsFile, $next); + + }//end testNotATextStringException() + + + /** + * Test receiving an expected exception when a text string token is not the first token + * of a multi-line text string. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be the start of the text string + * + * @covers \PHP_CodeSniffer\Util\Sniffs\TextStrings::getCompleteTextString + * + * @return void + */ + public function testNotFirstTextStringException() + { + $next = $this->getTargetToken( + '/* testNotFirstTextStringToken */', + T_CONSTANT_ENCAPSED_STRING, + 'second line +' + ); + $result = TextStrings::getCompleteTextString(self::$phpcsFile, $next); + + }//end testNotFirstTextStringException() + + + /** + * Test correctly retrieving the contents of a (potentially) multi-line text string. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param string $expected The expected function return value. + * @param string $expectedWithQuotes The expected function return value when $stripQuotes is set to "false". + * + * @dataProvider dataGetCompleteTextString + * @covers \PHP_CodeSniffer\Util\Sniffs\TextStrings::getCompleteTextString + * + * @return void + */ + public function testGetCompleteTextString($testMarker, $expected, $expectedWithQuotes) + { + $stackPtr = $this->getTargetToken($testMarker, $this->targets); + + $result = TextStrings::getCompleteTextString(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + $result = TextStrings::getCompleteTextString(self::$phpcsFile, $stackPtr, false); + $this->assertSame($expectedWithQuotes, $result); + + }//end testGetCompleteTextString() + + + /** + * Data provider. + * + * @see testGetCompleteTextString() + * + * @return array + */ + public function dataGetCompleteTextString() + { + return [ + [ + '/* testSingleLineConstantEncapsedString */', + 'single line text string', + "'single line text string'", + ], + [ + '/* testMultiLineConstantEncapsedString */', + 'first line +second line +third line +fourth line', + '"first line +second line +third line +fourth line"', + ], + [ + '/* testSingleLineDoubleQuotedString */', + 'single $line text string', + '"single $line text string"', + ], + [ + '/* testMultiLineDoubleQuotedString */', + 'first line +second $line +third line +fourth line', + '"first line +second $line +third line +fourth line"', + ], + [ + '/* testHeredocString */', + 'first line +second $line +third line +fourth line +', + 'first line +second $line +third line +fourth line +', + ], + [ + '/* testNowdocString */', + 'first line +second line +third line +fourth line +', + 'first line +second line +third line +fourth line +', + ], + ]; + + }//end dataGetCompleteTextString() + + +}//end class diff --git a/tests/Core/Util/Sniffs/TextStrings/StripQuotesTest.php b/tests/Core/Util/Sniffs/TextStrings/StripQuotesTest.php new file mode 100644 index 0000000000..f4add2b239 --- /dev/null +++ b/tests/Core/Util/Sniffs/TextStrings/StripQuotesTest.php @@ -0,0 +1,84 @@ + + * @copyright 2016-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\TextStrings; + +use PHPUnit\Framework\TestCase; +use PHP_CodeSniffer\Util\Sniffs\TextStrings; + +class StripQuotesTest extends TestCase +{ + + + /** + * Test correctly stripping quotes surrounding text strings. + * + * @param string $input The input string. + * @param string $expected The expected function output. + * + * @dataProvider dataStripQuotes + * @covers \PHP_CodeSniffer\Util\Sniffs\TextStrings::stripQuotes + * + * @return void + */ + public function testStripQuotes($input, $expected) + { + $this->assertSame($expected, TextStrings::stripQuotes($input)); + + }//end testStripQuotes() + + + /** + * Data provider. + * + * @see testStripQuotes() + * + * @return array + */ + public function dataStripQuotes() + { + return [ + [ + '"dir_name"', + 'dir_name', + ], + [ + "'soap.wsdl_cache'", + "soap.wsdl_cache", + ], + [ + '"arbitrary-\'string\" with\' quotes within"', + 'arbitrary-\'string\" with\' quotes within', + ], + [ + '"\'quoted_name\'"', + '\'quoted_name\'', + ], + [ + "'\"quoted\" start of string'", + '"quoted" start of string', + ], + [ + "'no stripping when there is only a start quote", + "'no stripping when there is only a start quote", + ], + [ + 'no stripping when there is only an end quote"', + 'no stripping when there is only an end quote"', + ], + [ + "'no stripping when quotes at start/end are mismatched\"", + "'no stripping when quotes at start/end are mismatched\"", + ], + ]; + + }//end dataStripQuotes() + + +}//end class diff --git a/tests/Core/File/IsReferenceTest.inc b/tests/Core/Util/Sniffs/TokenIs/IsReferenceTest.inc similarity index 56% rename from tests/Core/File/IsReferenceTest.inc rename to tests/Core/Util/Sniffs/TokenIs/IsReferenceTest.inc index c0c867b33e..82cf068b33 100644 --- a/tests/Core/File/IsReferenceTest.inc +++ b/tests/Core/Util/Sniffs/TokenIs/IsReferenceTest.inc @@ -1,138 +1,136 @@ $first, 'b' => $something & $somethingElse ]; -/* bitwiseAndF */ +/* testBitwiseAndF */ $a = array( 'a' => $first, 'b' => $something & \MyClass::$somethingElse ); -/* bitwiseAndG */ +/* testBitwiseAndG */ $a = $something & $somethingElse; -/* bitwiseAndH */ +/* testBitwiseAndH */ function myFunction($a = 10 & 20) {} -/* bitwiseAndI */ +/* testBitwiseAndI */ $closure = function ($a = MY_CONSTANT & parent::OTHER_CONSTANT) {}; -/* functionReturnByReference */ +/* testFunctionReturnByReference */ function &myFunction() {} -/* functionPassByReferenceA */ +/* testFunctionPassByReferenceA */ function myFunction( &$a ) {} -/* functionPassByReferenceB */ +/* testFunctionPassByReferenceB */ function myFunction( $a, &$b ) {} -/* functionPassByReferenceC */ +/* testFunctionPassByReferenceC */ $closure = function ( &$a ) {}; -/* functionPassByReferenceD */ +/* testFunctionPassByReferenceD */ $closure = function ( $a, &$b ) {}; -/* functionPassByReferenceE */ +/* testFunctionPassByReferenceE */ function myFunction(array &$one) {} -/* functionPassByReferenceF */ +/* testFunctionPassByReferenceF */ $closure = function (\MyClass &$one) {}; -/* functionPassByReferenceG */ -$closure = function myFunc($param, &...$moreParams) {}; +/* testFunctionPassByReferenceG */ +$closure = function ($param, &...$moreParams) {}; -/* foreachValueByReference */ +/* testForeachValueByReference */ foreach( $array as $key => &$value ) {} -/* foreachKeyByReference */ +/* testForeachKeyByReference */ foreach( $array as &$key => $value ) {} -/* arrayValueByReferenceA */ +/* testArrayValueByReferenceA */ $a = [ 'a' => &$something ]; -/* arrayValueByReferenceB */ +/* testArrayValueByReferenceB */ $a = [ 'a' => $something, 'b' => &$somethingElse ]; -/* arrayValueByReferenceC */ +/* testArrayValueByReferenceC */ $a = [ &$something ]; -/* arrayValueByReferenceD */ +/* testArrayValueByReferenceD */ $a = [ $something, &$somethingElse ]; -/* arrayValueByReferenceE */ +/* testArrayValueByReferenceE */ $a = array( 'a' => &$something ); -/* arrayValueByReferenceF */ +/* testArrayValueByReferenceF */ $a = array( 'a' => $something, 'b' => &$somethingElse ); -/* arrayValueByReferenceG */ +/* testArrayValueByReferenceG */ $a = array( &$something ); -/* arrayValueByReferenceH */ +/* testArrayValueByReferenceH */ $a = array( $something, &$somethingElse ); -/* assignByReferenceA */ +/* testAssignByReferenceA */ $b = &$something; -/* assignByReferenceB */ +/* testAssignByReferenceB */ $b =& $something; -/* assignByReferenceC */ +/* testAssignByReferenceC */ $b .= &$something; -/* assignByReferenceD */ +/* testAssignByReferenceD */ $myValue = &$obj->getValue(); -/* assignByReferenceE */ +/* testAssignByReferenceE */ $collection = &collector(); -/* passByReferenceA */ +/* testPassByReferenceA */ functionCall(&$something, $somethingElse); -/* passByReferenceB */ +/* testPassByReferenceB */ functionCall($something, &$somethingElse); -/* passByReferenceC */ +/* testPassByReferenceC */ functionCall($something, &$this->somethingElse); -/* passByReferenceD */ +/* testPassByReferenceD */ functionCall($something, &self::$somethingElse); -/* passByReferenceE */ +/* testPassByReferenceE */ functionCall($something, &parent::$somethingElse); -/* passByReferenceF */ +/* testPassByReferenceF */ functionCall($something, &static::$somethingElse); -/* passByReferenceG */ +/* testPassByReferenceG */ functionCall($something, &SomeClass::$somethingElse); -/* passByReferenceH */ +/* testPassByReferenceH */ functionCall(&\SomeClass::$somethingElse); -/* passByReferenceI */ +/* testPassByReferenceI */ functionCall($something, &\SomeNS\SomeClass::$somethingElse); -/* passByReferenceJ */ +/* testPassByReferenceJ */ functionCall($something, &namespace\SomeClass::$somethingElse); -/* newByReferenceA */ +/* testNewByReferenceA */ $foobar2 = &new Foobar(); -/* newByReferenceB */ +/* testNewByReferenceB */ functionCall( $something , &new Foobar() ); -/* useByReference */ +/* testUseByReference */ $closure = function() use (&$var){}; diff --git a/tests/Core/Util/Sniffs/TokenIs/IsReferenceTest.php b/tests/Core/Util/Sniffs/TokenIs/IsReferenceTest.php new file mode 100644 index 0000000000..e966ac8a52 --- /dev/null +++ b/tests/Core/Util/Sniffs/TokenIs/IsReferenceTest.php @@ -0,0 +1,249 @@ + + * @copyright 2018-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\TokenIs; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\TokenIs; + +class IsReferenceTest extends AbstractMethodUnitTest +{ + + + /** + * Test that false is returned when a non-"bitwise and" token is passed. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\TokenIs::isReference + * + * @return void + */ + public function testNotBitwiseAndToken() + { + $target = $this->getTargetToken('/* testBitwiseAndA */', T_STRING); + $this->assertFalse(TokenIs::isReference(self::$phpcsFile, $target)); + + }//end testNotBitwiseAndToken() + + + /** + * Test whether a bitwise-and token is used as a reference. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expected Expected function output. + * + * @dataProvider dataIsReference + * @covers \PHP_CodeSniffer\Util\Sniffs\TokenIs::isReference + * + * @return void + */ + public function testIsReference($testMarker, $expected) + { + $bitwiseAnd = $this->getTargetToken($testMarker, T_BITWISE_AND); + $result = TokenIs::isReference(self::$phpcsFile, $bitwiseAnd); + $this->assertSame($expected, $result); + + }//end testIsReference() + + + /** + * Data provider for the IsReference test. + * + * @see testIsReference() + * + * @return array + */ + public function dataIsReference() + { + return [ + [ + '/* testBitwiseAndA */', + false, + ], + [ + '/* testBitwiseAndB */', + false, + ], + [ + '/* testBitwiseAndC */', + false, + ], + [ + '/* testBitwiseAndD */', + false, + ], + [ + '/* testBitwiseAndE */', + false, + ], + [ + '/* testBitwiseAndF */', + false, + ], + [ + '/* testBitwiseAndG */', + false, + ], + [ + '/* testBitwiseAndH */', + false, + ], + [ + '/* testBitwiseAndI */', + false, + ], + [ + '/* testFunctionReturnByReference */', + true, + ], + [ + '/* testFunctionPassByReferenceA */', + true, + ], + [ + '/* testFunctionPassByReferenceB */', + true, + ], + [ + '/* testFunctionPassByReferenceC */', + true, + ], + [ + '/* testFunctionPassByReferenceD */', + true, + ], + [ + '/* testFunctionPassByReferenceE */', + true, + ], + [ + '/* testFunctionPassByReferenceF */', + true, + ], + [ + '/* testFunctionPassByReferenceG */', + true, + ], + [ + '/* testForeachValueByReference */', + true, + ], + [ + '/* testForeachKeyByReference */', + true, + ], + [ + '/* testArrayValueByReferenceA */', + true, + ], + [ + '/* testArrayValueByReferenceB */', + true, + ], + [ + '/* testArrayValueByReferenceC */', + true, + ], + [ + '/* testArrayValueByReferenceD */', + true, + ], + [ + '/* testArrayValueByReferenceE */', + true, + ], + [ + '/* testArrayValueByReferenceF */', + true, + ], + [ + '/* testArrayValueByReferenceG */', + true, + ], + [ + '/* testArrayValueByReferenceH */', + true, + ], + [ + '/* testAssignByReferenceA */', + true, + ], + [ + '/* testAssignByReferenceB */', + true, + ], + [ + '/* testAssignByReferenceC */', + true, + ], + [ + '/* testAssignByReferenceD */', + true, + ], + [ + '/* testAssignByReferenceE */', + true, + ], + [ + '/* testPassByReferenceA */', + true, + ], + [ + '/* testPassByReferenceB */', + true, + ], + [ + '/* testPassByReferenceC */', + true, + ], + [ + '/* testPassByReferenceD */', + true, + ], + [ + '/* testPassByReferenceE */', + true, + ], + [ + '/* testPassByReferenceF */', + true, + ], + [ + '/* testPassByReferenceG */', + true, + ], + [ + '/* testPassByReferenceH */', + true, + ], + [ + '/* testPassByReferenceI */', + true, + ], + [ + '/* testPassByReferenceJ */', + true, + ], + [ + '/* testNewByReferenceA */', + true, + ], + [ + '/* testNewByReferenceB */', + true, + ], + [ + '/* testUseByReference */', + true, + ], + ]; + + }//end dataIsReference() + + +}//end class diff --git a/tests/Core/Util/Sniffs/TokenIs/IsShortListTest.inc b/tests/Core/Util/Sniffs/TokenIs/IsShortListTest.inc new file mode 100644 index 0000000000..171e1e5927 --- /dev/null +++ b/tests/Core/Util/Sniffs/TokenIs/IsShortListTest.inc @@ -0,0 +1,83 @@ + [$id, $name, $info]) {} + +foreach ($array as [$a, /* testShortListInForeachNested */ [$b, $c]]) {} + +/* testMultiAssignShortlist */ +$foo = [$baz, $bat] = [$a, $b]; + +/* testShortListWithKeys */ +["id" => $id1, "name" => $name1] = $data[0]; + +/* testShortListInForeachWithKeysDetectOnCloseBracket */ +foreach ($data as ["id" => $id, "name" => $name]) {} + +// Invalid as empty lists are not allowed, but it is short list syntax. +[$x, /* testNestedShortListEmpty */ [], $y] = $a; + +[$x, [ $y, /* testDeeplyNestedShortList */ [$z]], $q] = $a; + +/* testShortListWithNestingAndKeys */ +[ + /* testNestedShortListWithKeys_1 */ + ["x" => $x1, "y" => $y1], + /* testNestedShortListWithKeys_2 */ + ["x" => $x2, "y" => $y2], + /* testNestedShortListWithKeys_3 */ + ["x" => $x3, "y" => $y3], +] = $points; + +/* testShortListWithoutVars */ +// Invalid list as it doesn't contain variables, but it is short list syntax. +[42] = [1]; + +/* testShortListNestedLongList */ +// Invalid list as mixing short list syntax with list() is not allowed, but it is short list syntax. +[list($a, $b), list($c, $d)] = [[1, 2], [3, 4]]; + +/* testNestedAnonClassWithTraitUseAs */ +// Parse error, but not short list syntax. +array_map(function($a) { + return new class() { + use MyTrait { + MyTrait::functionName as []; + } + }; +}, $array); + +/* testParseError */ +// Parse error, but not short list syntax. +use Something as [$a]; + +/* testLiveCoding */ +// This has to be the last test in the file. +[$a, [$b] diff --git a/tests/Core/Util/Sniffs/TokenIs/IsShortListTest.php b/tests/Core/Util/Sniffs/TokenIs/IsShortListTest.php new file mode 100644 index 0000000000..2959db613e --- /dev/null +++ b/tests/Core/Util/Sniffs/TokenIs/IsShortListTest.php @@ -0,0 +1,175 @@ + + * @copyright 2018-2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\TokenIs; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\TokenIs; + +class IsShortListTest extends AbstractMethodUnitTest +{ + + + /** + * Test whether a T_OPEN_SHORT_ARRAY token is a short array or a short list. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expected The expected boolean return value. + * @param int|string|array $targetToken The token type(s) to test. Defaults to T_OPEN_SHORT_ARRAY. + * + * @dataProvider dataIsShortList + * @covers \PHP_CodeSniffer\Util\Sniffs\TokenIs::isShortList + * + * @return void + */ + public function testIsShortList($testMarker, $expected, $targetToken=T_OPEN_SHORT_ARRAY) + { + $stackPtr = $this->getTargetToken($testMarker, $targetToken); + $result = TokenIs::isShortList(self::$phpcsFile, $stackPtr); + + $this->assertSame($expected, $result); + + }//end testIsShortList() + + + /** + * Data provider. + * + * @see testIsShortList() + * + * @return array + */ + public function dataIsShortList() + { + return [ + [ + '/* testLongList */', + false, + \T_LIST, + ], + [ + '/* testArrayAssignment */', + false, + [ + \T_OPEN_SHORT_ARRAY, + \T_OPEN_SQUARE_BRACKET, + ], + ], + [ + '/* testNonNestedShortArray */', + false, + ], + [ + '/* testNoAssignment */', + false, + ], + [ + '/* testNestedNoAssignment */', + false, + ], + [ + '/* testShortArrayInForeach */', + false, + ], + [ + '/* testShortList */', + true, + ], + [ + '/* testShortListDetectOnCloseBracket */', + true, + \T_CLOSE_SHORT_ARRAY, + ], + [ + '/* testShortListWithNesting */', + true, + ], + [ + '/* testNestedShortList */', + true, + ], + [ + '/* testShortListInForeach */', + true, + ], + [ + '/* testShortListInForeachWithKey */', + true, + ], + [ + '/* testShortListInForeachNested */', + true, + ], + [ + '/* testMultiAssignShortlist */', + true, + ], + [ + '/* testShortListWithKeys */', + true, + ], + [ + '/* testShortListInForeachWithKeysDetectOnCloseBracket */', + true, + \T_CLOSE_SHORT_ARRAY, + ], + [ + '/* testNestedShortListEmpty */', + true, + ], + [ + '/* testDeeplyNestedShortList */', + true, + ], + [ + '/* testShortListWithNestingAndKeys */', + true, + ], + [ + '/* testNestedShortListWithKeys_1 */', + true, + ], + [ + '/* testNestedShortListWithKeys_2 */', + true, + ], + [ + '/* testNestedShortListWithKeys_3 */', + true, + ], + [ + '/* testShortListWithoutVars */', + true, + ], + [ + '/* testShortListNestedLongList */', + true, + ], + [ + '/* testNestedAnonClassWithTraitUseAs */', + false, + ], + [ + '/* testParseError */', + false, + ], + [ + '/* testLiveCoding */', + false, + [ + \T_OPEN_SHORT_ARRAY, + \T_OPEN_SQUARE_BRACKET, + ], + ], + ]; + + }//end dataIsShortList() + + +}//end class diff --git a/tests/Core/Util/Sniffs/TokenIs/IsUnaryPlusMinusJSTest.js b/tests/Core/Util/Sniffs/TokenIs/IsUnaryPlusMinusJSTest.js new file mode 100644 index 0000000000..f67a181ca0 --- /dev/null +++ b/tests/Core/Util/Sniffs/TokenIs/IsUnaryPlusMinusJSTest.js @@ -0,0 +1,23 @@ +/* testNonUnaryPlus */ +result = 1 + 2; + +/* testNonUnaryMinus */ +result = 1-2; + +/* testUnaryMinusColon */ +$.localScroll({offset: {top: -32}}); + +switch (result) { + /* testUnaryMinusCase */ + case -1: + break; +} + +/* testUnaryMinusInlineIf */ +result = x?-y:z; + +/* testUnaryPlusInlineThen */ +result = x ? y : +z; + +/* testUnaryMinusInlineLogical */ +if (true || -1 == b) {} diff --git a/tests/Core/Util/Sniffs/TokenIs/IsUnaryPlusMinusJSTest.php b/tests/Core/Util/Sniffs/TokenIs/IsUnaryPlusMinusJSTest.php new file mode 100644 index 0000000000..327a7af007 --- /dev/null +++ b/tests/Core/Util/Sniffs/TokenIs/IsUnaryPlusMinusJSTest.php @@ -0,0 +1,66 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\TokenIs; + +class IsUnaryPlusMinusJSTest extends IsUnaryPlusMinusTest +{ + + /** + * The file extension of the test case file (without leading dot). + * + * @var string + */ + protected static $fileExtension = 'js'; + + + /** + * Data provider. + * + * @see IsUnaryPlusMinusTest::testIsUnaryPlusMinus() + * + * @return array + */ + public function dataIsUnaryPlusMinus() + { + return [ + [ + '/* testNonUnaryPlus */', + false, + ], + [ + '/* testNonUnaryMinus */', + false, + ], + [ + '/* testUnaryMinusColon */', + true, + ], + [ + '/* testUnaryMinusCase */', + true, + ], + [ + '/* testUnaryMinusInlineIf */', + true, + ], + [ + '/* testUnaryPlusInlineThen */', + true, + ], + [ + '/* testUnaryMinusInlineLogical */', + true, + ], + ]; + + }//end dataIsUnaryPlusMinus() + + +}//end class diff --git a/tests/Core/Util/Sniffs/TokenIs/IsUnaryPlusMinusTest.inc b/tests/Core/Util/Sniffs/TokenIs/IsUnaryPlusMinusTest.inc new file mode 100644 index 0000000000..514b849259 --- /dev/null +++ b/tests/Core/Util/Sniffs/TokenIs/IsUnaryPlusMinusTest.inc @@ -0,0 +1,124 @@ + + /* testUnaryPlusLongArrayAssignmentValue */ + +20, +); + +$array = [ + /* testUnaryPlusShortArrayAssignment */ + +1 => + /* testNonUnaryMinusShortArrayAssignment */ + 5-20, +]; + +/* testUnaryMinusCast */ +$a = (bool) -2; + +functionCall( + /* testUnaryPlusFunctionCallParam */ + +2, + /* testUnaryMinusFunctionCallParam */ + - 123.456, +); + +switch ($a) { + /* testUnaryPlusCase */ + case +20: + // Something. + break; + + /* testUnaryMinusCase */ + case -1.23: + // Something. + break; +} + +// Testing `$a = -+-+10`; +$a = + /* testSequenceNonUnary1 */ + - + /* testSequenceNonUnary2 */ + + + /* testSequenceNonUnary3 */ + - + /* testSequenceUnaryEnd */ + + /*comment*/ 10; + +// Intentional parse error. This has to be the last test in the file. +/* testParseError */ +$a = - diff --git a/tests/Core/Util/Sniffs/TokenIs/IsUnaryPlusMinusTest.php b/tests/Core/Util/Sniffs/TokenIs/IsUnaryPlusMinusTest.php new file mode 100644 index 0000000000..bfe27ff2e3 --- /dev/null +++ b/tests/Core/Util/Sniffs/TokenIs/IsUnaryPlusMinusTest.php @@ -0,0 +1,222 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\TokenIs; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\TokenIs; + +class IsUnaryPlusMinusTest extends AbstractMethodUnitTest +{ + + + /** + * Test that false is returned when a non-plus/minus token is passed. + * + * @covers \PHP_CodeSniffer\Util\Sniffs\TokenIs::isUnaryPlusMinus + * + * @return void + */ + public function testNotPlusMinusToken() + { + $target = $this->getTargetToken('/* testNonUnaryPlus */', T_LNUMBER); + $this->assertFalse(TokenIs::isUnaryPlusMinus(self::$phpcsFile, $target)); + + }//end testNotPlusMinusToken() + + + /** + * Test whether a T_PLUS or T_MINUS token is a unary operator. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expected The expected boolean return value. + * + * @dataProvider dataIsUnaryPlusMinus + * @covers \PHP_CodeSniffer\Util\Sniffs\TokenIs::isUnaryPlusMinus + * + * @return void + */ + public function testIsUnaryPlusMinus($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, [T_PLUS, T_MINUS]); + $result = TokenIs::isUnaryPlusMinus(self::$phpcsFile, $stackPtr); + + $this->assertSame($expected, $result); + + }//end testIsUnaryPlusMinus() + + + /** + * Data provider. + * + * @see testIsUnaryPlusMinus() + * + * @return array + */ + public function dataIsUnaryPlusMinus() + { + return [ + [ + '/* testNonUnaryPlus */', + false, + ], + [ + '/* testNonUnaryMinus */', + false, + ], + [ + '/* testNonUnaryPlusArrays */', + false, + ], + [ + '/* testUnaryPlusIntAssignment */', + true, + ], + [ + '/* testUnaryMinusVariableAssignment */', + true, + ], + [ + '/* testUnaryPlusFloatAssignment */', + true, + ], + [ + '/* testUnaryMinusBoolAssignment */', + true, + ], + [ + '/* testUnaryPlusStringAssignmentWithComment */', + true, + ], + [ + '/* testUnaryMinusStringAssignment */', + true, + ], + [ + '/* testUnaryPlusNullAssignment */', + true, + ], + [ + '/* testUnaryMinusVariableVariableAssignment */', + true, + ], + [ + '/* testUnaryPlusIntComparison */', + true, + ], + [ + '/* testUnaryPlusIntComparisonYoda */', + true, + ], + [ + '/* testUnaryMinusFloatComparison */', + true, + ], + [ + '/* testUnaryMinusStringComparisonYoda */', + true, + ], + [ + '/* testUnaryPlusVariableLogical */', + true, + ], + [ + '/* testUnaryMinusVariableLogical */', + true, + ], + [ + '/* testUnaryMinusInlineIf */', + true, + ], + [ + '/* testUnaryPlusInlineThen */', + true, + ], + [ + '/* testUnaryPlusIntReturn */', + true, + ], + [ + '/* testUnaryMinusFloatReturn */', + true, + ], + [ + '/* testUnaryPlusArrayAccess */', + true, + ], + [ + '/* testUnaryMinusStringArrayAccess */', + true, + ], + [ + '/* testUnaryPlusLongArrayAssignment */', + true, + ], + [ + '/* testUnaryMinusLongArrayAssignmentKey */', + true, + ], + [ + '/* testUnaryPlusLongArrayAssignmentValue */', + true, + ], + [ + '/* testUnaryPlusShortArrayAssignment */', + true, + ], + [ + '/* testNonUnaryMinusShortArrayAssignment */', + false, + ], + [ + '/* testUnaryMinusCast */', + true, + ], + [ + '/* testUnaryPlusFunctionCallParam */', + true, + ], + [ + '/* testUnaryMinusFunctionCallParam */', + true, + ], + [ + '/* testUnaryPlusCase */', + true, + ], + [ + '/* testUnaryMinusCase */', + true, + ], + [ + '/* testSequenceNonUnary1 */', + false, + ], + [ + '/* testSequenceNonUnary2 */', + false, + ], + [ + '/* testSequenceNonUnary3 */', + false, + ], + [ + '/* testSequenceUnaryEnd */', + true, + ], + [ + '/* testParseError */', + false, + ], + ]; + + }//end dataIsUnaryPlusMinus() + + +}//end class diff --git a/tests/Core/Util/Sniffs/UseStatements/SplitImportUseStatementTest.inc b/tests/Core/Util/Sniffs/UseStatements/SplitImportUseStatementTest.inc new file mode 100644 index 0000000000..f09fc342f2 --- /dev/null +++ b/tests/Core/Util/Sniffs/UseStatements/SplitImportUseStatementTest.inc @@ -0,0 +1,76 @@ + + + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\UseStatements; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\UseStatements; + +class SplitImportUseStatementTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a non-supported token is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_USE + * + * @covers \PHP_CodeSniffer\Util\Sniffs\UseStatements::splitImportUseStatement + * + * @return void + */ + public function testInvalidTokenPassed() + { + // 0 = PHP open tag. + $result = UseStatements::splitImportUseStatement(self::$phpcsFile, 0); + + }//end testInvalidTokenPassed() + + + /** + * Test receiving an expected exception when a non-import use statement token is passed. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be an import use statement + * + * @dataProvider dataNonImportUseTokenPassed + * @covers \PHP_CodeSniffer\Util\Sniffs\UseStatements::splitImportUseStatement + * + * @return void + */ + public function testNonImportUseTokenPassed($testMarker) + { + $stackPtr = $this->getTargetToken($testMarker, T_USE); + $result = UseStatements::splitImportUseStatement(self::$phpcsFile, $stackPtr); + + }//end testNonImportUseTokenPassed() + + + /** + * Data provider. + * + * @see testSplitImportUseStatement() + * + * @return array + */ + public function dataNonImportUseTokenPassed() + { + return [ + ['/* testClosureUse */'], + ['/* testTraitUse */'], + ]; + + }//end dataNonImportUseTokenPassed() + + + /** + * Test correctly splitting a T_USE statement into individual statements. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected return value of the function. + * + * @dataProvider dataSplitImportUseStatement + * @covers \PHP_CodeSniffer\Util\Sniffs\UseStatements::splitImportUseStatement + * + * @return void + */ + public function testSplitImportUseStatement($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_USE); + $result = UseStatements::splitImportUseStatement(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testSplitImportUseStatement() + + + /** + * Data provider. + * + * @see testSplitImportUseStatement() + * + * @return array + */ + public function dataSplitImportUseStatement() + { + return [ + [ + '/* testUsePlain */', + [ + 'name' => ['MyClass' => 'MyNamespace\MyClass'], + 'function' => [], + 'const' => [], + ], + ], + [ + '/* testUsePlainAliased */', + [ + 'name' => ['ClassAlias' => 'MyNamespace\YourClass'], + 'function' => [], + 'const' => [], + ], + ], + [ + '/* testUseMultiple */', + [ + 'name' => [ + 'ClassABC' => 'Vendor\Foo\ClassA', + 'InterfaceB' => 'Vendor\Bar\InterfaceB', + 'ClassC' => 'Vendor\Baz\ClassC', + ], + 'function' => [], + 'const' => [], + ], + ], + [ + '/* testUseFunctionPlainEndsOnCloseTag */', + [ + 'name' => [], + 'function' => ['myFunction' => 'MyNamespace\myFunction'], + 'const' => [], + ], + ], + [ + '/* testUseFunctionPlainAliased */', + [ + 'name' => [], + 'function' => ['FunctionAlias' => 'Vendor\YourNamespace\const\yourFunction'], + 'const' => [], + ], + ], + [ + '/* testUseFunctionMultiple */', + [ + 'name' => [], + 'function' => [ + 'sin' => 'foo\math\sin', + 'FooCos' => 'foo\math\cos', + 'cosh' => 'foo\math\cosh', + ], + 'const' => [], + ], + ], + [ + '/* testUseConstPlainUppercaseConstKeyword */', + [ + 'name' => [], + 'function' => [], + 'const' => ['MY_CONST' => 'MyNamespace\MY_CONST'], + ], + ], + [ + '/* testUseConstPlainAliased */', + [ + 'name' => [], + 'function' => [], + 'const' => ['CONST_ALIAS' => 'MyNamespace\YOUR_CONST'], + ], + ], + [ + '/* testUseConstMultiple */', + [ + 'name' => [], + 'function' => [], + 'const' => [ + 'PI' => 'foo\math\PI', + 'MATH_GOLDEN' => 'foo\math\GOLDEN_RATIO', + ], + ], + ], + [ + '/* testGroupUse */', + [ + 'name' => [ + 'SomeClassA' => 'some\namespacing\SomeClassA', + 'SomeClassB' => 'some\namespacing\deeper\level\SomeClassB', + 'C' => 'some\namespacing\another\level\SomeClassC', + ], + 'function' => [], + 'const' => [], + ], + ], + [ + '/* testGroupUseFunctionTrailingComma */', + [ + 'name' => [], + 'function' => [ + 'Msin' => 'bar\math\Msin', + 'BarCos' => 'bar\math\level\Mcos', + 'Mcosh' => 'bar\math\Mcosh', + ], + 'const' => [], + ], + ], + [ + '/* testGroupUseConst */', + [ + 'name' => [], + 'function' => [], + 'const' => [ + 'BAR_GAMMA' => 'bar\math\BGAMMA', + 'BGOLDEN_RATIO' => 'bar\math\BGOLDEN_RATIO', + ], + ], + ], + [ + '/* testGroupUseMixed */', + [ + 'name' => [ + 'ClassName' => 'Some\NS\ClassName', + 'AnotherLevel' => 'Some\NS\AnotherLevel', + ], + 'function' => [ + 'functionName' => 'Some\NS\SubLevel\functionName', + 'AnotherName' => 'Some\NS\SubLevel\AnotherName', + ], + 'const' => ['SOME_CONSTANT' => 'Some\NS\Constants\CONSTANT_NAME'], + ], + ], + [ + '/* testParseError */', + [ + 'name' => [], + 'function' => [], + 'const' => [], + ], + ], + ]; + + }//end dataSplitImportUseStatement() + + +}//end class diff --git a/tests/Core/Util/Sniffs/UseStatements/UseTypeTest.inc b/tests/Core/Util/Sniffs/UseStatements/UseTypeTest.inc new file mode 100644 index 0000000000..cd0582ed00 --- /dev/null +++ b/tests/Core/Util/Sniffs/UseStatements/UseTypeTest.inc @@ -0,0 +1,53 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\UseStatements; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\UseStatements; + +class UseTypeTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when passing a non-existent token pointer. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_USE + * + * @covers \PHP_CodeSniffer\Util\Sniffs\UseStatements::getType + * + * @return void + */ + public function testNonExistentToken() + { + $result = UseStatements::getType(self::$phpcsFile, 100000); + + }//end testNonExistentToken() + + + /** + * Test receiving an expected exception when passing a non T_USE token. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_USE + * + * @covers \PHP_CodeSniffer\Util\Sniffs\UseStatements::getType + * + * @return void + */ + public function testNonUseToken() + { + $result = UseStatements::getType(self::$phpcsFile, 0); + + }//end testNonUseToken() + + + /** + * Test correctly identifying whether a T_USE token is used as a closure use statement. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected return values for the various functions. + * + * @dataProvider dataUseType + * @covers \PHP_CodeSniffer\Util\Sniffs\UseStatements::isClosureUse + * @covers \PHP_CodeSniffer\Util\Sniffs\UseStatements::getType + * + * @return void + */ + public function testIsClosureUse($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_USE); + + $result = UseStatements::isClosureUse(self::$phpcsFile, $stackPtr); + $this->assertSame($expected['closure'], $result); + + }//end testIsClosureUse() + + + /** + * Test correctly identifying whether a T_USE token is used as an import use statement. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected return values for the various functions. + * + * @dataProvider dataUseType + * @covers \PHP_CodeSniffer\Util\Sniffs\UseStatements::isImportUse + * @covers \PHP_CodeSniffer\Util\Sniffs\UseStatements::getType + * + * @return void + */ + public function testIsImportUse($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_USE); + + $result = UseStatements::isImportUse(self::$phpcsFile, $stackPtr); + $this->assertSame($expected['import'], $result); + + }//end testIsImportUse() + + + /** + * Test correctly identifying whether a T_USE token is used as a trait import use statement. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected The expected return values for the various functions. + * + * @dataProvider dataUseType + * @covers \PHP_CodeSniffer\Util\Sniffs\UseStatements::isTraitUse + * @covers \PHP_CodeSniffer\Util\Sniffs\UseStatements::getType + * + * @return void + */ + public function testIsTraitUse($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_USE); + + $result = UseStatements::isTraitUse(self::$phpcsFile, $stackPtr); + $this->assertSame($expected['trait'], $result, 'isTraitUseStatement() test failed'); + + }//end testIsTraitUse() + + + /** + * Data provider. + * + * @see testIsClosureUse() + * @see testIsImportUse() + * @see testIsTraitUse() + * + * @return array + */ + public function dataUseType() + { + return [ + [ + '/* testUseImport1 */', + [ + 'closure' => false, + 'import' => true, + 'trait' => false, + ], + ], + [ + '/* testUseImport2 */', + [ + 'closure' => false, + 'import' => true, + 'trait' => false, + ], + ], + [ + '/* testUseImport3 */', + [ + 'closure' => false, + 'import' => true, + 'trait' => false, + ], + ], + [ + '/* testUseImport4 */', + [ + 'closure' => false, + 'import' => true, + 'trait' => false, + ], + ], + [ + '/* testClosureUse */', + [ + 'closure' => true, + 'import' => false, + 'trait' => false, + ], + ], + [ + '/* testUseTrait */', + [ + 'closure' => false, + 'import' => false, + 'trait' => true, + ], + ], + [ + '/* testClosureUseNestedInClass */', + [ + 'closure' => true, + 'import' => false, + 'trait' => false, + ], + ], + [ + '/* testUseTraitInNestedAnonClass */', + [ + 'closure' => false, + 'import' => false, + 'trait' => true, + ], + ], + [ + '/* testUseTraitInTrait */', + [ + 'closure' => false, + 'import' => false, + 'trait' => true, + ], + ], + [ + '/* testClosureUseNestedInTrait */', + [ + 'closure' => true, + 'import' => false, + 'trait' => false, + ], + ], + [ + '/* testUseTraitInInterface */', + [ + 'closure' => false, + 'import' => false, + 'trait' => false, + ], + ], + [ + '/* testLiveCoding */', + [ + 'closure' => false, + 'import' => false, + 'trait' => false, + ], + ], + ]; + + }//end dataUseType() + + +}//end class diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/Util/Sniffs/Variables/GetMemberPropertiesTest.inc similarity index 98% rename from tests/Core/File/GetMemberPropertiesTest.inc rename to tests/Core/Util/Sniffs/Variables/GetMemberPropertiesTest.inc index 614e3743da..4fee3b37bd 100644 --- a/tests/Core/File/GetMemberPropertiesTest.inc +++ b/tests/Core/Util/Sniffs/Variables/GetMemberPropertiesTest.inc @@ -94,7 +94,7 @@ class TestMemberProperties /* testMethodParam */ public function methodName($param) { /* testImportedGlobal */ - global $importedGlobal = true; + global $importedGlobal; /* testLocalVariable */ $localVariable = true; diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/Util/Sniffs/Variables/GetMemberPropertiesTest.php similarity index 74% rename from tests/Core/File/GetMemberPropertiesTest.php rename to tests/Core/Util/Sniffs/Variables/GetMemberPropertiesTest.php index 597e96c306..226cf7576c 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/Util/Sniffs/Variables/GetMemberPropertiesTest.php @@ -1,87 +1,36 @@ - * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence */ -namespace PHP_CodeSniffer\Tests\Core\File; +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Variables; -use PHP_CodeSniffer\Config; -use PHP_CodeSniffer\Ruleset; -use PHP_CodeSniffer\Files\DummyFile; -use PHPUnit\Framework\TestCase; +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Variables; -class GetMemberPropertiesTest extends TestCase +class GetMemberPropertiesTest extends AbstractMethodUnitTest { - /** - * The PHP_CodeSniffer_File object containing parsed contents of the test case file. - * - * @var \PHP_CodeSniffer\Files\File - */ - private $phpcsFile; - - - /** - * Initialize & tokenize PHP_CodeSniffer_File with code from the test case file. - * - * Methods used for these tests can be found in a test case file in the same - * directory and with the same name, using the .inc extension. - * - * @return void - */ - public function setUp() - { - $config = new Config(); - $config->standards = ['Generic']; - - $ruleset = new Ruleset($config); - - $pathToTestFile = dirname(__FILE__).'/'.basename(__FILE__, '.php').'.inc'; - $this->phpcsFile = new DummyFile(file_get_contents($pathToTestFile), $ruleset, $config); - $this->phpcsFile->process(); - - }//end setUp() - - - /** - * Clean up after finished test. - * - * @return void - */ - public function tearDown() - { - unset($this->phpcsFile); - - }//end tearDown() - /** * Test the getMemberProperties() method. * - * @param string $identifier Comment which precedes the test case. - * @param bool $expected Expected function output. + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected Expected function output. * * @dataProvider dataGetMemberProperties + * @covers \PHP_CodeSniffer\Util\Sniffs\Variables::getMemberProperties * * @return void */ - public function testGetMemberProperties($identifier, $expected) + public function testGetMemberProperties($testMarker, $expected) { - $start = ($this->phpcsFile->numTokens - 1); - $delim = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - $identifier - ); - $variable = $this->phpcsFile->findNext(T_VARIABLE, ($delim + 1)); - - $result = $this->phpcsFile->getMemberProperties($variable); + $variable = $this->getTargetToken($testMarker, T_VARIABLE); + $result = Variables::getMemberProperties(self::$phpcsFile, $variable); $this->assertSame($expected, $result); }//end testGetMemberProperties() @@ -325,28 +274,20 @@ public function dataGetMemberProperties() /** * Test receiving an expected exception when a non property is passed. * - * @param string $identifier Comment which precedes the test case. + * @param string $testMarker The comment which prefaces the target token in the test file. * - * @expectedException PHP_CodeSniffer\Exceptions\TokenizerException + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException * @expectedExceptionMessage $stackPtr is not a class member var * * @dataProvider dataNotClassProperty + * @covers \PHP_CodeSniffer\Util\Sniffs\Variables::getMemberProperties * * @return void */ - public function testNotClassPropertyException($identifier) + public function testNotClassPropertyException($testMarker) { - $start = ($this->phpcsFile->numTokens - 1); - $delim = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - $identifier - ); - $variable = $this->phpcsFile->findNext(T_VARIABLE, ($delim + 1)); - - $result = $this->phpcsFile->getMemberProperties($variable); + $variable = $this->getTargetToken($testMarker, T_VARIABLE); + $result = Variables::getMemberProperties(self::$phpcsFile, $variable); }//end testNotClassPropertyException() @@ -375,24 +316,17 @@ public function dataNotClassProperty() /** * Test receiving an expected exception when a non variable is passed. * - * @expectedException PHP_CodeSniffer\Exceptions\TokenizerException + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException * @expectedExceptionMessage $stackPtr must be of type T_VARIABLE * + * @covers \PHP_CodeSniffer\Util\Sniffs\Variables::getMemberProperties + * * @return void */ public function testNotAVariableException() { - $start = ($this->phpcsFile->numTokens - 1); - $delim = $this->phpcsFile->findPrevious( - T_COMMENT, - $start, - null, - false, - '/* testNotAVariable */' - ); - $next = $this->phpcsFile->findNext(T_WHITESPACE, ($delim + 1), null, true); - - $result = $this->phpcsFile->getMemberProperties($next); + $next = $this->getTargetToken('/* testNotAVariable */', T_RETURN); + $result = Variables::getMemberProperties(self::$phpcsFile, $next); }//end testNotAVariableException() diff --git a/tests/Core/Util/Sniffs/Variables/IsForeachAsTest.inc b/tests/Core/Util/Sniffs/Variables/IsForeachAsTest.inc new file mode 100644 index 0000000000..90f72cd53f --- /dev/null +++ b/tests/Core/Util/Sniffs/Variables/IsForeachAsTest.inc @@ -0,0 +1,41 @@ + 10) {} + +/* testForeachWithoutAs */ +foreach($something) {} // Intentional parse error. + +/* testForeachVarBeforeAs */ +foreach($something as $k => $v) {} + +/* testForeachVarAfterAs */ +foreach($array as $something) {} + +/* testForeachVarAfterAsKey */ +foreach($array as $something => $value) {} + +/* testForeachVarAfterAsValue */ +foreach($array as $key => $something) {} + +/* testForeachVarAfterAsList */ +foreach($array as [$something, $else]) {} + +/* testNestedForeachVarAfterAs */ +add_action('something', function($array) { + foreach($array as $something) {} + return; +}); + +// Intentional parse error. This has to be the last test in the file. +/* testParseError */ +foreach ($array as $something {} diff --git a/tests/Core/Util/Sniffs/Variables/IsForeachAsTest.php b/tests/Core/Util/Sniffs/Variables/IsForeachAsTest.php new file mode 100644 index 0000000000..35a101466d --- /dev/null +++ b/tests/Core/Util/Sniffs/Variables/IsForeachAsTest.php @@ -0,0 +1,116 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Variables; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Variables; + +class IsForeachAsTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a non variable is passed. + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_VARIABLE + * + * @covers \PHP_CodeSniffer\Util\Sniffs\Variables::isForeachAs + * + * @return void + */ + public function testNotAVariableException() + { + $next = $this->getTargetToken('/* testNotAVariable */', T_RETURN); + $result = Variables::isForeachAs(self::$phpcsFile, $next); + + }//end testNotAVariableException() + + + /** + * Test correctly identifying whether a T_VARIABLE token in the `as ...` part of a foreach statement. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expected The expected function return value. + * + * @dataProvider dataIsForeachAs + * @covers \PHP_CodeSniffer\Util\Sniffs\Variables::isForeachAs + * + * @return void + */ + public function testIsForeachAs($testMarker, $expected) + { + $stackPtr = $this->getTargetToken($testMarker, T_VARIABLE, '$something'); + $result = Variables::isForeachAs(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testIsForeachAs() + + + /** + * Data provider. + * + * @see testIsForeachAs() + * + * @return array + */ + public function dataIsForeachAs() + { + return [ + [ + '/* testNoParenthesis */', + false, + ], + [ + '/* testNoParenthesisOwner */', + false, + ], + [ + '/* testOwnerNotForeach */', + false, + ], + [ + '/* testForeachWithoutAs */', + false, + ], + [ + '/* testForeachVarBeforeAs */', + false, + ], + [ + '/* testForeachVarAfterAs */', + true, + ], + [ + '/* testForeachVarAfterAsKey */', + true, + ], + [ + '/* testForeachVarAfterAsValue */', + true, + ], + [ + '/* testForeachVarAfterAsList */', + true, + ], + [ + '/* testNestedForeachVarAfterAs */', + true, + ], + [ + '/* testParseError */', + false, + ], + ]; + + }//end dataIsForeachAs() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Variables/IsPHPReservedVarNameTest.php b/tests/Core/Util/Sniffs/Variables/IsPHPReservedVarNameTest.php new file mode 100644 index 0000000000..11a183a562 --- /dev/null +++ b/tests/Core/Util/Sniffs/Variables/IsPHPReservedVarNameTest.php @@ -0,0 +1,153 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Variables; + +use PHP_CodeSniffer\Util\Sniffs\Variables; +use PHPUnit\Framework\TestCase; + +class IsPHPReservedVarNameTest extends TestCase +{ + + + /** + * Test valid PHP reserved variable names. + * + * @param string $name The variable name to test. + * + * @dataProvider dataIsPHPReservedVarName + * @covers \PHP_CodeSniffer\Util\Sniffs\Variables::isPHPReservedVarName + * + * @return void + */ + public function testIsPHPReservedVarName($name) + { + $this->assertTrue(Variables::isPHPReservedVarName($name)); + + }//end testIsPHPReservedVarName() + + + /** + * Data provider. + * + * @see testIsPHPReservedVarName() + * + * @return array + */ + public function dataIsPHPReservedVarName() + { + return [ + // With dollar sign. + ['$_SERVER'], + ['$_GET'], + ['$_POST'], + ['$_REQUEST'], + ['$_SESSION'], + ['$_ENV'], + ['$_COOKIE'], + ['$_FILES'], + ['$GLOBALS'], + ['$http_response_header'], + ['$argc'], + ['$argv'], + ['$HTTP_RAW_POST_DATA'], + ['$php_errormsg'], + ['$HTTP_SERVER_VARS'], + ['$HTTP_GET_VARS'], + ['$HTTP_POST_VARS'], + ['$HTTP_SESSION_VARS'], + ['$HTTP_ENV_VARS'], + ['$HTTP_COOKIE_VARS'], + ['$HTTP_POST_FILES'], + + // Without dollar sign. + ['_SERVER'], + ['_GET'], + ['_POST'], + ['_REQUEST'], + ['_SESSION'], + ['_ENV'], + ['_COOKIE'], + ['_FILES'], + ['GLOBALS'], + ['http_response_header'], + ['argc'], + ['argv'], + ['HTTP_RAW_POST_DATA'], + ['php_errormsg'], + ['HTTP_SERVER_VARS'], + ['HTTP_GET_VARS'], + ['HTTP_POST_VARS'], + ['HTTP_SESSION_VARS'], + ['HTTP_ENV_VARS'], + ['HTTP_COOKIE_VARS'], + ['HTTP_POST_FILES'], + ]; + + }//end dataIsPHPReservedVarName() + + + /** + * Test non-reserved variable names. + * + * @param string $name The variable name to test. + * + * @dataProvider dataIsPHPReservedVarNameFalse + * @covers \PHP_CodeSniffer\Util\Sniffs\Variables::isPHPReservedVarName + * + * @return void + */ + public function testIsPHPReservedVarNameFalse($name) + { + $this->assertFalse(Variables::isPHPReservedVarName($name)); + + }//end testIsPHPReservedVarNameFalse() + + + /** + * Data provider. + * + * @see testIsPHPReservedVarNameFalse() + * + * @return array + */ + public function dataIsPHPReservedVarNameFalse() + { + return [ + // Different case. + ['$_Server'], + ['$_get'], + ['$_pOST'], + ['$HTTP_RESPONSE_HEADER'], + ['_EnV'], + ['PHP_errormsg'], + + // Shouldn't be possible, but all the same: double dollar. + ['$$_REQUEST'], + + // No underscore. + ['$SERVER'], + ['SERVER'], + + // Double underscore. + ['$__SERVER'], + ['__SERVER'], + + // Globals with underscore. + ['$_GLOBALS'], + ['_GLOBALS'], + + // Some completely different variable name. + ['my_php_errormsg'], + ]; + + }//end dataIsPHPReservedVarNameFalse() + + +}//end class diff --git a/tests/Core/Util/Sniffs/Variables/IsSuperglobalTest.inc b/tests/Core/Util/Sniffs/Variables/IsSuperglobalTest.inc new file mode 100644 index 0000000000..9186bd658a --- /dev/null +++ b/tests/Core/Util/Sniffs/Variables/IsSuperglobalTest.inc @@ -0,0 +1,41 @@ + + * @copyright 2019 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Sniffs\Variables; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Sniffs\Variables; + +class IsSuperglobalTest extends AbstractMethodUnitTest +{ + + + /** + * Test correctly detecting superglobal variables. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expected The expected function return value. + * @param int|string $testTargetType Optional. The token type for the target token in the test file. + * @param string $testTargetValue Optional. The token content for the target token in the test file. + * + * @dataProvider dataIsSuperglobal + * @covers \PHP_CodeSniffer\Util\Sniffs\Variables::isSuperglobal + * + * @return void + */ + public function testIsSuperglobal($testMarker, $expected, $testTargetType=T_VARIABLE, $testTargetValue=null) + { + $stackPtr = $this->getTargetToken($testMarker, $testTargetType, $testTargetValue); + $result = Variables::isSuperglobal(self::$phpcsFile, $stackPtr); + $this->assertSame($expected, $result); + + }//end testIsSuperglobal() + + + /** + * Data provider. + * + * @see testIsSuperglobal() + * + * @return array + */ + public function dataIsSuperglobal() + { + return [ + [ + '/* testNotAVariable */', + false, + T_RETURN, + ], + [ + '/* testNotAReservedVar */', + false, + ], + [ + '/* testReservedVarNotSuperglobal */', + false, + ], + [ + '/* testReservedVarIsSuperglobal */', + true, + ], + [ + '/* testGLOBALSArrayKeyNotAReservedVar */', + false, + T_CONSTANT_ENCAPSED_STRING, + ], + [ + '/* testGLOBALSArrayKeyVar */', + false, + T_VARIABLE, + '$something', + ], + [ + '/* testGLOBALSArrayKeyReservedVar */', + false, + T_VARIABLE, + '$php_errormsg', + ], + [ + '/* testGLOBALSArrayKeySuperglobal */', + true, + T_VARIABLE, + '$_COOKIE', + ], + [ + '/* testGLOBALSArrayKeyNotSingleString */', + false, + T_CONSTANT_ENCAPSED_STRING, + ], + [ + '/* testGLOBALSArrayKeyInterpolatedVar */', + false, + T_DOUBLE_QUOTED_STRING, + ], + [ + '/* testGLOBALSArrayKeySingleStringSuperglobal */', + true, + T_CONSTANT_ENCAPSED_STRING, + ], + [ + '/* testGLOBALSArrayKeySuperglobalWithKey */', + true, + T_VARIABLE, + '$_GET', + ], + [ + '/* testSuperglobalKeyNotGLOBALSArray */', + false, + T_CONSTANT_ENCAPSED_STRING, + ], + ]; + + }//end dataIsSuperglobal() + + + /** + * Test valid PHP superglobal names. + * + * @param string $name The variable name to test. + * + * @dataProvider dataIsSuperglobalName + * @covers \PHP_CodeSniffer\Util\Sniffs\Variables::isSuperglobalName + * + * @return void + */ + public function testIsSuperglobalName($name) + { + $this->assertTrue(Variables::isSuperglobalName($name)); + + }//end testIsSuperglobalName() + + + /** + * Data provider. + * + * @see testIsSuperglobalName() + * + * @return array + */ + public function dataIsSuperglobalName() + { + return [ + ['$_SERVER'], + ['$_GET'], + ['$_POST'], + ['$_REQUEST'], + ['_SESSION'], + ['_ENV'], + ['_COOKIE'], + ['_FILES'], + ['GLOBALS'], + ]; + + }//end dataIsSuperglobalName() + + + /** + * Test non-superglobal variable names. + * + * @param string $name The variable name to test. + * + * @dataProvider dataIsSuperglobalNameFalse + * @covers \PHP_CodeSniffer\Util\Sniffs\Variables::isSuperglobalName + * + * @return void + */ + public function testIsSuperglobalNameFalse($name) + { + $this->assertFalse(Variables::isSuperglobalName($name)); + + }//end testIsSuperglobalNameFalse() + + + /** + * Data provider. + * + * @see testIsSuperglobalNameFalse() + * + * @return array + */ + public function dataIsSuperglobalNameFalse() + { + return [ + ['$not_a_superglobal'], + ['$http_response_header'], + ['$argc'], + ['$argv'], + ['$HTTP_RAW_POST_DATA'], + ['$php_errormsg'], + ['HTTP_SERVER_VARS'], + ['HTTP_GET_VARS'], + ['HTTP_POST_VARS'], + ['HTTP_SESSION_VARS'], + ['HTTP_ENV_VARS'], + ['HTTP_COOKIE_VARS'], + ['HTTP_POST_FILES'], + ]; + + }//end dataIsSuperglobalNameFalse() + + +}//end class