Skip to content

Commit

Permalink
Fix false positive in multi-attribute usages (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
janedbal committed Apr 17, 2024
1 parent 2f65e4f commit 0501e73
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 18 deletions.
42 changes: 31 additions & 11 deletions src/UsedSymbolExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
use const T_USE;
use const T_WHITESPACE;

// phpcs:disable Squiz.PHP.CommentedOutCode.Found

class UsedSymbolExtractor
{

Expand Down Expand Up @@ -68,8 +70,10 @@ public function parseUsedSymbols(): array
$useStatements = [];
$useStatementKinds = [];

$level = 0;
$level = 0; // {, }, {$, ${
$squareLevel = 0; // [, ], #[
$inClassLevel = null;
$inAttributeSquareLevel = null;

$numTokens = $this->numTokens;
$tokens = $this->tokens;
Expand All @@ -96,13 +100,17 @@ public function parseUsedSymbols(): array

break;

case PHP_VERSION_ID > 80000 ? T_ATTRIBUTE : -1:
$inAttributeSquareLevel = ++$squareLevel;
break;

case PHP_VERSION_ID >= 80000 ? T_NAMESPACE : -1:
$useStatements = []; // reset use statements on namespace change
break;

case PHP_VERSION_ID >= 80000 ? T_NAME_FULLY_QUALIFIED : -1:
$symbolName = $this->normalizeBackslash($token[1]);
$kind = $this->getFqnSymbolKind($this->pointer - 2, $this->pointer);
$kind = $this->getFqnSymbolKind($this->pointer - 2, $this->pointer, $inAttributeSquareLevel !== null);
$usedSymbols[$kind][$symbolName][] = $token[2];
break;

Expand All @@ -111,7 +119,7 @@ public function parseUsedSymbols(): array

if (isset($useStatements[$neededAlias])) {
$symbolName = $useStatements[$neededAlias] . substr($token[1], strlen($neededAlias));
$kind = $this->getFqnSymbolKind($this->pointer - 2, $this->pointer);
$kind = $this->getFqnSymbolKind($this->pointer - 2, $this->pointer, $inAttributeSquareLevel !== null);
$usedSymbols[$kind][$symbolName][] = $token[2];
}

Expand Down Expand Up @@ -143,7 +151,7 @@ public function parseUsedSymbols(): array
$symbolName = $this->normalizeBackslash($this->parseNameForOldPhp());

if ($symbolName !== '') { // e.g. \array (NS separator followed by not-a-name)
$kind = $this->getFqnSymbolKind($pointerBeforeName, $this->pointer - 1);
$kind = $this->getFqnSymbolKind($pointerBeforeName, $this->pointer - 1, false);
$usedSymbols[$kind][$symbolName][] = $token[2];
}

Expand All @@ -163,7 +171,7 @@ public function parseUsedSymbols(): array

if (isset($useStatements[$neededAlias])) { // qualified name
$symbolName = $useStatements[$neededAlias] . substr($name, strlen($neededAlias));
$kind = $this->getFqnSymbolKind($pointerBeforeName, $this->pointer - 1);
$kind = $this->getFqnSymbolKind($pointerBeforeName, $this->pointer - 1, false);
$usedSymbols[$kind][$symbolName][] = $token[2];
}
}
Expand All @@ -183,6 +191,14 @@ public function parseUsedSymbols(): array
}

$level--;
} elseif ($token === '[') {
$squareLevel++;
} elseif ($token === ']') {
if ($squareLevel === $inAttributeSquareLevel) {
$inAttributeSquareLevel = null;
}

$squareLevel--;
}
}

Expand Down Expand Up @@ -306,8 +322,16 @@ private function normalizeBackslash(string $class): string
/**
* @return SymbolKind::CLASSLIKE|SymbolKind::FUNCTION
*/
private function getFqnSymbolKind(int $pointerBeforeName, int $pointerAfterName): int
private function getFqnSymbolKind(
int $pointerBeforeName,
int $pointerAfterName,
bool $inAttribute
): int
{
if ($inAttribute) {
return SymbolKind::CLASSLIKE;
}

do {
$tokenBeforeName = $this->tokens[$pointerBeforeName];

Expand Down Expand Up @@ -338,13 +362,9 @@ private function getFqnSymbolKind(int $pointerBeforeName, int $pointerAfterName)
break;
} while ($pointerAfterName < $this->numTokens);

// phpcs:disable Squiz.PHP.CommentedOutCode.Found
if (
$tokenAfterName === '('
&& !(
$tokenBeforeName[0] === T_NEW // eliminate new \ClassName(
|| (PHP_VERSION_ID > 80000 && $tokenBeforeName[0] === T_ATTRIBUTE) // eliminate #[\AttributeName(
)
&& $tokenBeforeName[0] !== T_NEW // eliminate new \ClassName(
) {
return SymbolKind::FUNCTION;
}
Expand Down
16 changes: 9 additions & 7 deletions tests/UsedSymbolExtractorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,18 @@ public function provideVariants(): iterable
[]
];

yield 'attribute' => [
__DIR__ . '/data/not-autoloaded/used-symbols/attribute.php',
PHP_VERSION_ID >= 80000
? [
if (PHP_VERSION_ID >= 80000) {
yield 'attribute' => [
__DIR__ . '/data/not-autoloaded/used-symbols/attribute.php',
[
SymbolKind::CLASSLIKE => [
'SomeAttribute' => [3],
'Assert\NotNull' => [7],
'Assert\NotBlank' => [8],
],
]
: []
];
],
];
}
}

}
6 changes: 6 additions & 0 deletions tests/data/not-autoloaded/used-symbols/attribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@

#[\SomeAttribute()]
class ClassWithAttribute {}

#[
\Assert\NotNull(foo: []),
\Assert\NotBlank(),
]
class ClassWithMultipleAttributes {}

0 comments on commit 0501e73

Please sign in to comment.