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, array_merge(TokenHelper::$ineffectiveTokenCodes, [T_BITWISE_AND, T_ELLIPSIS]), $i - 1 ); if (!in_array($tokens[$pointerBeforeVariable]['code'], TokenHelper::getTypeHintTokenCodes(), 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 function (int $functionPointer) use ($phpcsFile): string { return self::getName($phpcsFile, $functionPointer); }, array_filter( iterator_to_array(self::getAllFunctionOrMethodPointers($phpcsFile, $previousFunctionPointer)), static function (int $functionOrMethodPointer) use ($phpcsFile): bool { return !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); } }