getTokens();
return $tokens[TokenHelper::findNext(
$phpcsFile,
T_STRING,
$functionPointer + 1,
$tokens[$functionPointer]['parenthesis_opener'],
)]['content'];
}
public static function getFullyQualifiedName(File $phpcsFile, int $functionPointer): string
{
$name = self::getName($phpcsFile, $functionPointer);
$namespace = NamespaceHelper::findCurrentNamespaceName($phpcsFile, $functionPointer);
if (self::isMethod($phpcsFile, $functionPointer)) {
foreach (array_reverse(
$phpcsFile->getTokens()[$functionPointer]['conditions'],
true,
) as $conditionPointer => $conditionTokenCode) {
if ($conditionTokenCode === T_ANON_CLASS) {
return sprintf('class@anonymous::%s', $name);
}
if (in_array($conditionTokenCode, [T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM], true)) {
$name = sprintf(
'%s%s::%s',
NamespaceHelper::NAMESPACE_SEPARATOR,
ClassHelper::getName($phpcsFile, $conditionPointer),
$name,
);
break;
}
}
return $namespace !== null ? sprintf('%s%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $namespace, $name) : $name;
}
return $namespace !== null
? sprintf('%s%s%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $namespace, NamespaceHelper::NAMESPACE_SEPARATOR, $name)
: $name;
}
public static function isAbstract(File $phpcsFile, int $functionPointer): bool
{
return !isset($phpcsFile->getTokens()[$functionPointer]['scope_opener']);
}
public static function isMethod(File $phpcsFile, int $functionPointer): bool
{
$functionPointerConditions = $phpcsFile->getTokens()[$functionPointer]['conditions'];
if ($functionPointerConditions === []) {
return false;
}
$lastFunctionPointerCondition = array_pop($functionPointerConditions);
return in_array($lastFunctionPointerCondition, Tokens::$ooScopeTokens, true);
}
public static function findClassPointer(File $phpcsFile, int $functionPointer): ?int
{
$tokens = $phpcsFile->getTokens();
if ($tokens[$functionPointer]['code'] === T_CLOSURE) {
return null;
}
foreach (array_reverse($tokens[$functionPointer]['conditions'], true) as $conditionPointer => $conditionTokenCode) {
if (!in_array($conditionTokenCode, Tokens::$ooScopeTokens, true)) {
continue;
}
return $conditionPointer;
}
return null;
}
/**
* @return list
*/
public static function getParametersNames(File $phpcsFile, int $functionPointer): array
{
$tokens = $phpcsFile->getTokens();
$parametersNames = [];
for ($i = $tokens[$functionPointer]['parenthesis_opener'] + 1; $i < $tokens[$functionPointer]['parenthesis_closer']; $i++) {
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
$parametersNames[] = $tokens[$i]['content'];
}
return $parametersNames;
}
/**
* @return array
*/
public static function getParametersTypeHints(File $phpcsFile, int $functionPointer): array
{
$tokens = $phpcsFile->getTokens();
$parametersTypeHints = [];
for ($i = $tokens[$functionPointer]['parenthesis_opener'] + 1; $i < $tokens[$functionPointer]['parenthesis_closer']; $i++) {
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
$parameterName = $tokens[$i]['content'];
$pointerBeforeVariable = TokenHelper::findPreviousExcluding(
$phpcsFile,
[...TokenHelper::INEFFECTIVE_TOKEN_CODES, T_BITWISE_AND, T_ELLIPSIS],
$i - 1,
);
if (!in_array($tokens[$pointerBeforeVariable]['code'], TokenHelper::TYPE_HINT_TOKEN_CODES, true)) {
$parametersTypeHints[$parameterName] = null;
continue;
}
$typeHintEndPointer = $pointerBeforeVariable;
$typeHintStartPointer = TypeHintHelper::getStartPointer($phpcsFile, $typeHintEndPointer);
$pointerBeforeTypeHint = TokenHelper::findPreviousEffective($phpcsFile, $typeHintStartPointer - 1);
$isNullable = $tokens[$pointerBeforeTypeHint]['code'] === T_NULLABLE;
if ($isNullable) {
$typeHintStartPointer = $pointerBeforeTypeHint;
}
$typeHint = TokenHelper::getContent($phpcsFile, $typeHintStartPointer, $typeHintEndPointer);
/** @var string $typeHint */
$typeHint = preg_replace('~\s+~', '', $typeHint);
if (!$isNullable) {
$isNullable = preg_match('~(?:^|\|)null(?:\||$)~i', $typeHint) === 1;
}
$parametersTypeHints[$parameterName] = new TypeHint($typeHint, $isNullable, $typeHintStartPointer, $typeHintEndPointer);
}
return $parametersTypeHints;
}
public static function returnsValue(File $phpcsFile, int $functionPointer): bool
{
$tokens = $phpcsFile->getTokens();
$firstPointerInScope = $tokens[$functionPointer]['scope_opener'] + 1;
for ($i = $firstPointerInScope; $i < $tokens[$functionPointer]['scope_closer']; $i++) {
if (!in_array($tokens[$i]['code'], [T_YIELD, T_YIELD_FROM], true)) {
continue;
}
if (!ScopeHelper::isInSameScope($phpcsFile, $i, $firstPointerInScope)) {
continue;
}
return true;
}
for ($i = $firstPointerInScope; $i < $tokens[$functionPointer]['scope_closer']; $i++) {
if ($tokens[$i]['code'] !== T_RETURN) {
continue;
}
if (!ScopeHelper::isInSameScope($phpcsFile, $i, $firstPointerInScope)) {
continue;
}
$nextEffectiveTokenPointer = TokenHelper::findNextEffective($phpcsFile, $i + 1);
return $tokens[$nextEffectiveTokenPointer]['code'] !== T_SEMICOLON;
}
return false;
}
public static function findReturnTypeHint(File $phpcsFile, int $functionPointer): ?TypeHint
{
$tokens = $phpcsFile->getTokens();
$nextPointer = TokenHelper::findNextEffective($phpcsFile, $tokens[$functionPointer]['parenthesis_closer'] + 1);
if ($tokens[$nextPointer]['code'] === T_USE) {
$useParenthesisOpener = TokenHelper::findNextEffective($phpcsFile, $nextPointer + 1);
$colonPointer = TokenHelper::findNextEffective($phpcsFile, $tokens[$useParenthesisOpener]['parenthesis_closer'] + 1);
} else {
$colonPointer = $nextPointer;
}
if ($tokens[$colonPointer]['code'] !== T_COLON) {
return null;
}
$typeHintStartPointer = TokenHelper::findNextEffective($phpcsFile, $colonPointer + 1);
$nullable = $tokens[$typeHintStartPointer]['code'] === T_NULLABLE;
$pointerAfterTypeHint = self::isAbstract($phpcsFile, $functionPointer)
? TokenHelper::findNext($phpcsFile, T_SEMICOLON, $typeHintStartPointer + 1)
: $tokens[$functionPointer]['scope_opener'];
$typeHintEndPointer = TokenHelper::findPreviousEffective($phpcsFile, $pointerAfterTypeHint - 1);
$typeHint = TokenHelper::getContent($phpcsFile, $typeHintStartPointer, $typeHintEndPointer);
/** @var string $typeHint */
$typeHint = preg_replace('~\s+~', '', $typeHint);
if (!$nullable) {
$nullable = preg_match('~(?:^|\|)null(?:\||$)~i', $typeHint) === 1;
}
return new TypeHint($typeHint, $nullable, $typeHintStartPointer, $typeHintEndPointer);
}
public static function hasReturnTypeHint(File $phpcsFile, int $functionPointer): bool
{
return self::findReturnTypeHint($phpcsFile, $functionPointer) !== null;
}
/**
* @return list|Annotation>
*/
public static function getParametersAnnotations(File $phpcsFile, int $functionPointer): array
{
return AnnotationHelper::getAnnotations($phpcsFile, $functionPointer, '@param');
}
/**
* @return array|Annotation|Annotation>
*/
public static function getValidParametersAnnotations(File $phpcsFile, int $functionPointer): array
{
$tokens = $phpcsFile->getTokens();
$parametersAnnotations = [];
if (self::getName($phpcsFile, $functionPointer) === '__construct') {
for ($i = $tokens[$functionPointer]['parenthesis_opener'] + 1; $i < $tokens[$functionPointer]['parenthesis_closer']; $i++) {
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
$varAnnotations = AnnotationHelper::getAnnotations($phpcsFile, $i, '@var');
if ($varAnnotations === []) {
continue;
}
$parametersAnnotations[$tokens[$i]['content']] = $varAnnotations[0];
}
}
foreach (self::getParametersAnnotations($phpcsFile, $functionPointer) as $parameterAnnotation) {
if ($parameterAnnotation->isInvalid()) {
continue;
}
$parametersAnnotations[$parameterAnnotation->getValue()->parameterName] = $parameterAnnotation;
}
return $parametersAnnotations;
}
/**
* @return array|Annotation>
*/
public static function getValidPrefixedParametersAnnotations(File $phpcsFile, int $functionPointer): array
{
$tokens = $phpcsFile->getTokens();
$parametersAnnotations = [];
foreach (AnnotationHelper::STATIC_ANALYSIS_PREFIXES as $prefix) {
if (self::getName($phpcsFile, $functionPointer) === '__construct') {
for ($i = $tokens[$functionPointer]['parenthesis_opener'] + 1; $i < $tokens[$functionPointer]['parenthesis_closer']; $i++) {
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
/** @var list> $varAnnotations */
$varAnnotations = AnnotationHelper::getAnnotations($phpcsFile, $i, sprintf('@%s-var', $prefix));
if ($varAnnotations === []) {
continue;
}
$parametersAnnotations[$tokens[$i]['content']] = $varAnnotations[0];
}
}
/** @var list> $annotations */
$annotations = AnnotationHelper::getAnnotations($phpcsFile, $functionPointer, sprintf('@%s-param', $prefix));
foreach ($annotations as $parameterAnnotation) {
if ($parameterAnnotation->isInvalid()) {
continue;
}
$parametersAnnotations[$parameterAnnotation->getValue()->parameterName] = $parameterAnnotation;
}
}
return $parametersAnnotations;
}
/**
* @return Annotation|null
*/
public static function findReturnAnnotation(File $phpcsFile, int $functionPointer): ?Annotation
{
/** @var list> $returnAnnotations */
$returnAnnotations = AnnotationHelper::getAnnotations($phpcsFile, $functionPointer, '@return');
if ($returnAnnotations === []) {
return null;
}
return $returnAnnotations[0];
}
/**
* @return list
*/
public static function getValidPrefixedReturnAnnotations(File $phpcsFile, int $functionPointer): array
{
$returnAnnotations = [];
$annotations = AnnotationHelper::getAnnotations($phpcsFile, $functionPointer);
foreach (AnnotationHelper::STATIC_ANALYSIS_PREFIXES as $prefix) {
$prefixedAnnotationName = sprintf('@%s-return', $prefix);
foreach ($annotations as $annotation) {
if ($annotation->isInvalid()) {
continue;
}
if ($annotation->getName() === $prefixedAnnotationName) {
$returnAnnotations[] = $annotation;
}
}
}
return $returnAnnotations;
}
/**
* @return list
*/
public static function getAllFunctionNames(File $phpcsFile): array
{
$previousFunctionPointer = 0;
return array_map(
static fn (int $functionPointer): string => self::getName($phpcsFile, $functionPointer),
array_values(array_filter(
iterator_to_array(self::getAllFunctionOrMethodPointers($phpcsFile, $previousFunctionPointer)),
static fn (int $functionOrMethodPointer): bool => !self::isMethod($phpcsFile, $functionOrMethodPointer),
)),
);
}
/**
* @param int $flags optional bitmask of self::LINE_INCLUDE_* constants
*/
public static function getFunctionLengthInLines(File $file, int $functionPosition, int $flags = 0): int
{
if (self::isAbstract($file, $functionPosition)) {
return 0;
}
return self::getLineCount($file, $functionPosition, $flags);
}
public static function getLineCount(File $file, int $tokenPosition, int $flags = 0): int
{
$includeWhitespace = ($flags & self::LINE_INCLUDE_WHITESPACE) === self::LINE_INCLUDE_WHITESPACE;
$includeComments = ($flags & self::LINE_INCLUDE_COMMENT) === self::LINE_INCLUDE_COMMENT;
$tokens = $file->getTokens();
$token = $tokens[$tokenPosition];
$tokenOpenerPosition = $token['scope_opener'] ?? $tokenPosition;
$tokenCloserPosition = $token['scope_closer'] ?? $file->numTokens - 1;
$tokenOpenerLine = $tokens[$tokenOpenerPosition]['line'];
$tokenCloserLine = $tokens[$tokenCloserPosition]['line'];
$lineCount = 0;
$lastCommentLine = null;
$previousIncludedPosition = null;
for ($position = $tokenOpenerPosition; $position <= $tokenCloserPosition - 1; $position++) {
$token = $tokens[$position];
if ($includeComments === false) {
if (in_array($token['code'], Tokens::$commentTokens, true)) {
if (
$previousIncludedPosition !== null &&
substr_count($token['content'], $file->eolChar) > 0 &&
$token['line'] === $tokens[$previousIncludedPosition]['line']
) {
// Comment with linebreak starting on same line as included Token
$lineCount++;
}
// Don't include comment
$lastCommentLine = $token['line'];
continue;
}
if (
$previousIncludedPosition !== null &&
$token['code'] === T_WHITESPACE &&
$token['line'] === $lastCommentLine &&
$token['line'] !== $tokens[$previousIncludedPosition]['line']
) {
// Whitespace after block comment... still on comment line...
// Ignore along with the comment
continue;
}
}
if ($token['code'] === T_WHITESPACE) {
$nextNonWhitespacePosition = $file->findNext(T_WHITESPACE, $position + 1, $tokenCloserPosition + 1, true);
if (
$includeWhitespace === false &&
$token['column'] === 1 &&
$nextNonWhitespacePosition !== false &&
$tokens[$nextNonWhitespacePosition]['line'] !== $token['line']
) {
// This line is nothing but whitepace
$position = $nextNonWhitespacePosition - 1;
continue;
}
if ($previousIncludedPosition === $tokenOpenerPosition && $token['line'] === $tokenOpenerLine) {
// Don't linclude line break after opening "{"
// Unless there was code or an (included) comment following the "{"
continue;
}
}
if ($token['code'] !== T_WHITESPACE) {
$previousIncludedPosition = $position;
}
$newLineFoundCount = substr_count($token['content'], $file->eolChar);
$lineCount += $newLineFoundCount;
}
if ($tokens[$previousIncludedPosition]['line'] === $tokenCloserLine) {
// There is code or comment on the closing "}" line...
$lineCount++;
}
return $lineCount;
}
/**
* @return Generator
*/
private static function getAllFunctionOrMethodPointers(File $phpcsFile, int &$previousFunctionPointer): Generator
{
do {
$nextFunctionPointer = TokenHelper::findNext($phpcsFile, T_FUNCTION, $previousFunctionPointer + 1);
if ($nextFunctionPointer === null) {
break;
}
$previousFunctionPointer = $nextFunctionPointer;
yield $nextFunctionPointer;
} while (true);
}
}