Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 37 additions & 6 deletions PhpCollective/Sniffs/AbstractSniffs/AbstractSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,27 @@ abstract class AbstractSniff implements Sniff
'@noinspection',
];

/**
* Per-file cache for class-name-with-namespace resolution.
*
* `getClassNameWithNamespace()` walks the token stream backwards from
* the end of the file to find the class/trait/interface/enum keyword.
* The result is stable for the lifetime of a phpcs run on a given file,
* but the original implementation re-computed it on every sniff call.
* Caching by filename turns dozens-to-hundreds of full-file walks per
* large file into a single walk.
*
* @var array<string, string|null>
*/
private static array $classNameWithNamespaceCache = [];

/**
* Per-file cache for class name resolution (with filename fallback).
*
* @var array<string, string>
*/
private static array $classNameCache = [];

/**
* @param \PHP_CodeSniffer\Files\File $phpcsFile
* @param int $stackPtr
Expand Down Expand Up @@ -98,22 +119,27 @@ protected function getNamespace(File $phpcsFile): string
*/
protected function getClassNameWithNamespace(File $phpcsFile): ?string
{
$cacheKey = $phpcsFile->getFilename();
if (array_key_exists($cacheKey, self::$classNameWithNamespaceCache)) {
return self::$classNameWithNamespaceCache[$cacheKey];
}

try {
$lastToken = TokenHelper::getLastTokenPointer($phpcsFile);
} catch (EmptyFileException $e) {
return null;
return self::$classNameWithNamespaceCache[$cacheKey] = null;
}

if (!NamespaceHelper::findCurrentNamespaceName($phpcsFile, $lastToken)) {
return null;
return self::$classNameWithNamespaceCache[$cacheKey] = null;
}

$prevIndex = $phpcsFile->findPrevious([T_CLASS, T_TRAIT, T_INTERFACE, T_ENUM], $lastToken);
if (!$prevIndex) {
return null;
return self::$classNameWithNamespaceCache[$cacheKey] = null;
}

return ClassHelper::getFullyQualifiedName(
return self::$classNameWithNamespaceCache[$cacheKey] = ClassHelper::getFullyQualifiedName(
$phpcsFile,
$prevIndex,
);
Expand All @@ -126,10 +152,15 @@ protected function getClassNameWithNamespace(File $phpcsFile): ?string
*/
protected function getClassName(File $phpcsFile): string
{
$cacheKey = $phpcsFile->getFilename();
if (isset(self::$classNameCache[$cacheKey])) {
return self::$classNameCache[$cacheKey];
}

$namespace = $this->getClassNameWithNamespace($phpcsFile);

if ($namespace) {
return trim($namespace, '\\');
return self::$classNameCache[$cacheKey] = trim($namespace, '\\');
}

$fileName = $phpcsFile->getFilename();
Expand All @@ -143,7 +174,7 @@ protected function getClassName(File $phpcsFile): string
$className = implode('\\', $classNameParts);
$className = str_replace('.php', '', $className);

return $className;
return self::$classNameCache[$cacheKey] = $className;
}

/**
Expand Down
24 changes: 24 additions & 0 deletions PhpCollective/Sniffs/Commenting/DocBlockReturnSelfSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public function process(File $phpcsFile, $stackPointer): void

$docBlockStartIndex = $tokens[$docBlockEndIndex]['comment_opener'];

$ownClassName = null;

for ($i = $docBlockStartIndex + 1; $i < $docBlockEndIndex; $i++) {
if ($tokens[$i]['type'] !== 'T_DOC_COMMENT_TAG') {
continue;
Expand Down Expand Up @@ -75,6 +77,28 @@ public function process(File $phpcsFile, $stackPointer): void
}

$parts = explode('|', $content);

// The three assertions below all act only when `parts` contains
// `self`, `$this`, or the own (fully qualified) class name.
// Skipping the expensive return-type body scan when none of these
// are present turns this sniff from O(methods * body_size) into
// ~O(methods) for files where most @return tags are plain types
// like `void`, `int`, `array<...>`, etc.
if ($ownClassName === null) {
$ownClassName = '\\' . $this->getClassName($phpcsFile);
}
$needsReturnTypeCheck = false;
foreach ($parts as $part) {
if ($part === 'self' || $part === '$this' || $part === $ownClassName) {
$needsReturnTypeCheck = true;

break;
}
}
if (!$needsReturnTypeCheck) {
continue;
}

$returnTypes = $this->getReturnTypes($phpcsFile, $stackPointer);

$this->assertCorrectDocBlockParts($phpcsFile, $classNameIndex, $parts, $returnTypes, $appendix);
Expand Down
Loading