-
-
Notifications
You must be signed in to change notification settings - Fork 170
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
573 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
<?php | ||
|
||
namespace SlevomatCodingStandard\Helpers; | ||
|
||
class FunctionHelper | ||
{ | ||
|
||
/** | ||
* @param \PHP_CodeSniffer_File $codeSnifferFile | ||
* @param integer $functionPointer | ||
* @return string | ||
*/ | ||
public static function getName(\PHP_CodeSniffer_File $codeSnifferFile, $functionPointer) | ||
{ | ||
$tokens = $codeSnifferFile->getTokens(); | ||
return $tokens[$codeSnifferFile->findNext(T_STRING, $functionPointer + 1, $tokens[$functionPointer]['parenthesis_opener'])]['content']; | ||
} | ||
|
||
/** | ||
* @param \PHP_CodeSniffer_File $codeSnifferFile | ||
* @param integer $functionPointer | ||
* @return string | ||
*/ | ||
public static function getFullyQualifiedName(\PHP_CodeSniffer_File $codeSnifferFile, $functionPointer) | ||
{ | ||
$name = self::getName($codeSnifferFile, $functionPointer); | ||
$namespace = NamespaceHelper::findCurrentNamespaceName($codeSnifferFile, $functionPointer); | ||
|
||
if (self::isMethod($codeSnifferFile, $functionPointer)) { | ||
foreach (array_reverse($codeSnifferFile->getTokens()[$functionPointer]['conditions'], true) as $conditionPointer => $conditionTokenCode) { | ||
if ($conditionTokenCode === T_ANON_CLASS) { | ||
return sprintf('class@anonymous::%s', $name); | ||
} elseif (in_array($conditionTokenCode, [T_CLASS, T_INTERFACE, T_TRAIT])) { | ||
$name = sprintf('%s%s::%s', NamespaceHelper::NAMESPACE_SEPARATOR, ClassHelper::getName($codeSnifferFile, $conditionPointer), $name); | ||
break; | ||
} | ||
} | ||
|
||
return $namespace !== null ? sprintf('%s%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $namespace, $name) : $name; | ||
} else { | ||
return $namespace !== null ? sprintf('%s%s%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $namespace, NamespaceHelper::NAMESPACE_SEPARATOR, $name) : $name; | ||
} | ||
} | ||
|
||
/** | ||
* @param \PHP_CodeSniffer_File $codeSnifferFile | ||
* @param integer $functionPointer | ||
* @return boolean | ||
*/ | ||
public static function isAbstract(\PHP_CodeSniffer_File $codeSnifferFile, $functionPointer) | ||
{ | ||
return !isset($codeSnifferFile->getTokens()[$functionPointer]['scope_opener']); | ||
} | ||
|
||
/** | ||
* @param \PHP_CodeSniffer_File $codeSnifferFile | ||
* @param integer $functionPointer | ||
* @return boolean | ||
*/ | ||
public static function isMethod(\PHP_CodeSniffer_File $codeSnifferFile, $functionPointer) | ||
{ | ||
foreach (array_reverse($codeSnifferFile->getTokens()[$functionPointer]['conditions']) as $conditionTokenCode) { | ||
if (in_array($conditionTokenCode, [T_CLASS, T_INTERFACE, T_TRAIT, T_ANON_CLASS], true)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* @param \PHP_CodeSniffer_File $codeSnifferFile | ||
* @param integer $functionPointer | ||
* @return string[] | ||
*/ | ||
public static function getParametersWithoutTypeHint(\PHP_CodeSniffer_File $codeSnifferFile, $functionPointer) | ||
{ | ||
return array_keys(array_filter(self::getParametersTypeHints($codeSnifferFile, $functionPointer), function ($parameterTypeHint) { | ||
return $parameterTypeHint === null; | ||
})); | ||
} | ||
|
||
/** | ||
* @param \PHP_CodeSniffer_File $codeSnifferFile | ||
* @param integer $functionPointer | ||
* @return string[] | ||
*/ | ||
public static function getParametersTypeHints(\PHP_CodeSniffer_File $codeSnifferFile, $functionPointer) | ||
{ | ||
$tokens = $codeSnifferFile->getTokens(); | ||
|
||
$parametersTypeHints = []; | ||
for ($i = $tokens[$functionPointer]['parenthesis_opener'] + 1; $i < $tokens[$functionPointer]['parenthesis_closer']; $i++) { | ||
if ($tokens[$i]['code'] === T_VARIABLE) { | ||
$parameterName = $tokens[$i]['content']; | ||
$parameterTypeHint = null; | ||
|
||
$previousToken = $i; | ||
do { | ||
$previousToken = TokenHelper::findPreviousExcluding($codeSnifferFile, [T_WHITESPACE, T_COMMENT, T_BITWISE_AND, T_ELLIPSIS], $previousToken - 1, $tokens[$functionPointer]['parenthesis_opener'] + 1); | ||
$isTypeHint = $previousToken !== null && $tokens[$previousToken]['code'] !== T_COMMA; | ||
if ($isTypeHint) { | ||
$parameterTypeHint = $tokens[$previousToken]['content'] . $parameterTypeHint; | ||
} | ||
} while ($isTypeHint); | ||
|
||
$parametersTypeHints[$parameterName] = $parameterTypeHint; | ||
} | ||
} | ||
|
||
return $parametersTypeHints; | ||
} | ||
|
||
/** | ||
* @param \PHP_CodeSniffer_File $codeSnifferFile | ||
* @param integer $functionPointer | ||
* @return boolean | ||
*/ | ||
public static function returnsValue(\PHP_CodeSniffer_File $codeSnifferFile, $functionPointer) | ||
{ | ||
$tokens = $codeSnifferFile->getTokens(); | ||
|
||
for ($i = $tokens[$functionPointer]['scope_opener'] + 1; $i < $tokens[$functionPointer]['scope_closer']; $i++) { | ||
if ($tokens[$i]['code'] === T_RETURN && $tokens[TokenHelper::findNextNonWhitespace($codeSnifferFile, $i + 1)]['code'] !== T_SEMICOLON) { | ||
foreach (array_reverse($tokens[$i]['conditions'], true) as $conditionPointer => $conditionTokenCode) { | ||
if ($conditionTokenCode === T_CLOSURE || $conditionTokenCode === T_ANON_CLASS) { | ||
continue 2; | ||
} elseif ($conditionPointer === $functionPointer) { | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* @param \PHP_CodeSniffer_File $codeSnifferFile | ||
* @param integer $functionPointer | ||
* @return string|null | ||
*/ | ||
public static function findReturnTypeHint(\PHP_CodeSniffer_File $codeSnifferFile, $functionPointer) | ||
{ | ||
$tokens = $codeSnifferFile->getTokens(); | ||
|
||
$isAbstract = self::isAbstract($codeSnifferFile, $functionPointer); | ||
|
||
$colonToken = $isAbstract | ||
? $codeSnifferFile->findNext(T_COLON, $tokens[$functionPointer]['parenthesis_closer'] + 1, null, false, null, true) | ||
: $codeSnifferFile->findNext(T_COLON, $tokens[$functionPointer]['parenthesis_closer'] + 1, $tokens[$functionPointer]['scope_opener'] - 1); | ||
|
||
if ($colonToken === false) { | ||
return null; | ||
} | ||
|
||
$returnTypeHint = null; | ||
$nextToken = $colonToken; | ||
do { | ||
$nextToken = $isAbstract | ||
? $codeSnifferFile->findNext([T_WHITESPACE, T_COMMENT, T_SEMICOLON], $nextToken + 1, null, true, null, true) | ||
: $codeSnifferFile->findNext([T_WHITESPACE, T_COMMENT], $nextToken + 1, $tokens[$functionPointer]['scope_opener'] - 1, true); | ||
|
||
$isTypeHint = $nextToken !== false; | ||
if ($isTypeHint) { | ||
$returnTypeHint .= $tokens[$nextToken]['content']; | ||
} | ||
} while ($isTypeHint); | ||
|
||
return $returnTypeHint; | ||
} | ||
|
||
/** | ||
* @param \PHP_CodeSniffer_File $codeSnifferFile | ||
* @param integer $functionPointer | ||
* @return boolean | ||
*/ | ||
public static function hasReturnTypeHint(\PHP_CodeSniffer_File $codeSnifferFile, $functionPointer) | ||
{ | ||
return self::findReturnTypeHint($codeSnifferFile, $functionPointer) !== null; | ||
} | ||
|
||
/** | ||
* @param \PHP_CodeSniffer_File $codeSnifferFile | ||
* @param integer $functionPointer | ||
* @return string[] | ||
*/ | ||
public static function getParametersAnnotations(\PHP_CodeSniffer_File $codeSnifferFile, $functionPointer) | ||
{ | ||
return AnnotationHelper::getAnnotationsByName($codeSnifferFile, $functionPointer, '@param'); | ||
} | ||
|
||
/** | ||
* @param \PHP_CodeSniffer_File $codeSnifferFile | ||
* @param integer $functionPointer | ||
* @return string|null | ||
*/ | ||
public static function findReturnAnnotation(\PHP_CodeSniffer_File $codeSnifferFile, $functionPointer) | ||
{ | ||
$returnAnnotations = AnnotationHelper::getAnnotationsByName($codeSnifferFile, $functionPointer, '@return'); | ||
|
||
if (count($returnAnnotations) === 0) { | ||
return null; | ||
} | ||
|
||
return $returnAnnotations[0]; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
<?php | ||
|
||
namespace SlevomatCodingStandard\Helpers; | ||
|
||
class FunctionHelperTest extends \SlevomatCodingStandard\Helpers\TestCase | ||
{ | ||
|
||
public function testNameWithNamespace() | ||
{ | ||
$codeSnifferFile = $this->getCodeSnifferFile(__DIR__ . '/data/functionWithNamespace.php'); | ||
$this->assertSame('\FooNamespace\fooFunction', FunctionHelper::getFullyQualifiedName($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooFunction'))); | ||
$this->assertSame('fooFunction', FunctionHelper::getName($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooFunction'))); | ||
$this->assertSame('\FooNamespace\FooClass::fooMethod', FunctionHelper::getFullyQualifiedName($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooMethod'))); | ||
$this->assertSame('fooMethod', FunctionHelper::getName($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooMethod'))); | ||
} | ||
|
||
public function testNameWithoutNamespace() | ||
{ | ||
$codeSnifferFile = $this->getCodeSnifferFile(__DIR__ . '/data/functionWithoutNamespace.php'); | ||
$this->assertSame('fooFunction', FunctionHelper::getFullyQualifiedName($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooFunction'))); | ||
$this->assertSame('fooFunction', FunctionHelper::getName($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooFunction'))); | ||
$this->assertSame('\FooClass::fooMethod', FunctionHelper::getFullyQualifiedName($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooMethod'))); | ||
$this->assertSame('fooMethod', FunctionHelper::getName($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooMethod'))); | ||
} | ||
|
||
public function testNameInAnonymousClass() | ||
{ | ||
$codeSnifferFile = $this->getCodeSnifferFile(__DIR__ . '/data/php7/functionInAnonymousClass.php'); | ||
$this->assertSame('class@anonymous::fooMethod', FunctionHelper::getFullyQualifiedName($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooMethod'))); | ||
$this->assertSame('fooMethod', FunctionHelper::getName($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooMethod'))); | ||
} | ||
|
||
public function testAbstractOrNot() | ||
{ | ||
$codeSnifferFile = $this->getCodeSnifferFile(__DIR__ . '/data/functionAbstractOrNot.php'); | ||
$this->assertFalse(FunctionHelper::isAbstract($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooMethod'))); | ||
$this->assertTrue(FunctionHelper::isAbstract($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooAbstractMethod'))); | ||
$this->assertTrue(FunctionHelper::isAbstract($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooInterfaceMethod'))); | ||
} | ||
|
||
public function testFunctionOrMethod() | ||
{ | ||
$codeSnifferFile = $this->getCodeSnifferFile(__DIR__ . '/data/functionOrMethod.php'); | ||
$this->assertTrue(FunctionHelper::isMethod($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooMethod'))); | ||
$this->assertFalse(FunctionHelper::isMethod($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'fooFunction'))); | ||
} | ||
|
||
public function testParametersTypeHints() | ||
{ | ||
$codeSnifferFile = $this->getCodeSnifferFile(__DIR__ . '/data/php7/functionParametersTypeHints.php'); | ||
|
||
$functionPointer = $this->findFunctionPointerByName($codeSnifferFile, 'allParametersWithTypeHints'); | ||
$parametersTypeHints = FunctionHelper::getParametersTypeHints($codeSnifferFile, $functionPointer); | ||
$parametersWithoutTypeHints = FunctionHelper::getParametersWithoutTypeHint($codeSnifferFile, $functionPointer); | ||
$this->assertCount(7, $parametersTypeHints); | ||
$this->assertSame([ | ||
'$string' => 'string', | ||
'$int' => 'int', | ||
'$bool' => 'bool', | ||
'$float' => 'float', | ||
'$callable' => 'callable', | ||
'$array' => 'array', | ||
'$object' => '\FooNamespace\FooClass', | ||
], $parametersTypeHints); | ||
$this->assertCount(0, $parametersWithoutTypeHints); | ||
|
||
$functionPointer = $this->findFunctionPointerByName($codeSnifferFile, 'allParametersWithoutTypeHints'); | ||
$parametersTypeHints = FunctionHelper::getParametersTypeHints($codeSnifferFile, $functionPointer); | ||
$parametersWithoutTypeHints = FunctionHelper::getParametersWithoutTypeHint($codeSnifferFile, $functionPointer); | ||
$this->assertCount(7, $parametersTypeHints); | ||
$this->assertSame([ | ||
'$string' => null, | ||
'$int' => null, | ||
'$bool' => null, | ||
'$float' => null, | ||
'$callable' => null, | ||
'$array' => null, | ||
'$object' => null, | ||
], $parametersTypeHints); | ||
$this->assertCount(7, $parametersWithoutTypeHints); | ||
$this->assertSame([ | ||
'$string', | ||
'$int', | ||
'$bool', | ||
'$float', | ||
'$callable', | ||
'$array', | ||
'$object', | ||
], $parametersWithoutTypeHints); | ||
|
||
$functionPointer = $this->findFunctionPointerByName($codeSnifferFile, 'someParametersWithoutTypeHints'); | ||
$parametersTypeHints = FunctionHelper::getParametersTypeHints($codeSnifferFile, $functionPointer); | ||
$parametersWithoutTypeHints = FunctionHelper::getParametersWithoutTypeHint($codeSnifferFile, $functionPointer); | ||
$this->assertCount(7, $parametersTypeHints); | ||
$this->assertSame([ | ||
'$string' => 'string', | ||
'$int' => null, | ||
'$bool' => 'bool', | ||
'$float' => null, | ||
'$callable' => 'callable', | ||
'$array' => null, | ||
'$object' => '\FooNamespace\FooClass', | ||
], $parametersTypeHints); | ||
$this->assertCount(3, $parametersWithoutTypeHints); | ||
$this->assertSame([ | ||
'$int', | ||
'$float', | ||
'$array', | ||
], $parametersWithoutTypeHints); | ||
} | ||
|
||
public function testReturnsValueOrNot() | ||
{ | ||
$codeSnifferFile = $this->getCodeSnifferFile(__DIR__ . '/data/functionReturnsValueOrNot.php'); | ||
$this->assertTrue(FunctionHelper::returnsValue($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'returnsValue'))); | ||
$this->assertTrue(FunctionHelper::returnsValue($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'returnsVariable'))); | ||
$this->assertTrue(FunctionHelper::returnsValue($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'returnsValueInCondition'))); | ||
$this->assertFalse(FunctionHelper::returnsValue($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'noReturn'))); | ||
$this->assertFalse(FunctionHelper::returnsValue($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'returnsVoid'))); | ||
$this->assertFalse(FunctionHelper::returnsValue($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'containsClosure'))); | ||
$this->assertFalse(FunctionHelper::returnsValue($codeSnifferFile, $this->findFunctionPointerByName($codeSnifferFile, 'containsAnonymousClass'))); | ||
} | ||
|
||
public function testReturnTypeHint() | ||
{ | ||
$codeSnifferFile = $this->getCodeSnifferFile(__DIR__ . '/data/php7/functionReturnTypeHint.php'); | ||
|
||
$functionPointer = $this->findFunctionPointerByName($codeSnifferFile, 'withReturnTypeHint'); | ||
$this->assertTrue(FunctionHelper::hasReturnTypeHint($codeSnifferFile, $functionPointer)); | ||
$this->assertSame('\FooNamespace\FooInterface', FunctionHelper::findReturnTypeHint($codeSnifferFile, $functionPointer)); | ||
|
||
$functionPointer = $this->findFunctionPointerByName($codeSnifferFile, 'withoutReturnTypeHint'); | ||
$this->assertFalse(FunctionHelper::hasReturnTypeHint($codeSnifferFile, $functionPointer)); | ||
$this->assertNull(FunctionHelper::findReturnTypeHint($codeSnifferFile, $functionPointer)); | ||
|
||
$functionPointer = $this->findFunctionPointerByName($codeSnifferFile, 'abstractWithReturnTypeHint'); | ||
$this->assertTrue(FunctionHelper::hasReturnTypeHint($codeSnifferFile, $functionPointer)); | ||
$this->assertSame('bool', FunctionHelper::findReturnTypeHint($codeSnifferFile, $functionPointer)); | ||
|
||
$functionPointer = $this->findFunctionPointerByName($codeSnifferFile, 'abstractWithoutReturnTypeHint'); | ||
$this->assertFalse(FunctionHelper::hasReturnTypeHint($codeSnifferFile, $functionPointer)); | ||
$this->assertNull(FunctionHelper::findReturnTypeHint($codeSnifferFile, $functionPointer)); | ||
} | ||
|
||
public function testAnnotations() | ||
{ | ||
$codeSnifferFile = $this->getCodeSnifferFile(__DIR__ . '/data/functionAnnotations.php'); | ||
|
||
$functionPointer = $this->findFunctionPointerByName($codeSnifferFile, 'withAnnotations'); | ||
$this->assertSame([ | ||
'string $a', | ||
'integer $b', | ||
], FunctionHelper::getParametersAnnotations($codeSnifferFile, $functionPointer)); | ||
$this->assertSame('boolean', FunctionHelper::findReturnAnnotation($codeSnifferFile, $functionPointer)); | ||
|
||
$functionPointer = $this->findFunctionPointerByName($codeSnifferFile, 'withoutAnnotations'); | ||
$this->assertCount(0, FunctionHelper::getParametersAnnotations($codeSnifferFile, $functionPointer)); | ||
$this->assertNull(FunctionHelper::findReturnAnnotation($codeSnifferFile, $functionPointer)); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?php | ||
|
||
class FooClass | ||
{ | ||
|
||
public function fooMethod() | ||
{ | ||
|
||
} | ||
|
||
} | ||
|
||
abstract class FooAbstractClass | ||
{ | ||
|
||
abstract public function fooAbstractMethod(); | ||
|
||
} | ||
|
||
interface FooInterface | ||
{ | ||
|
||
public function fooInterfaceMethod(); | ||
|
||
} |
Oops, something went wrong.