Skip to content

Commit

Permalink
fix: Parse implemented interfaces using token instead of reflection, …
Browse files Browse the repository at this point in the history
…to avoid FatalError. Used in IteratorItemTypeSniff
  • Loading branch information
gskema committed May 24, 2024
1 parent 2976a27 commit 3e8e46f
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 17 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to `phpcs-type-sniff` will be documented in this file.

Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles.

## 81.6.3 - 2024-05-24
### Fixed
- Parse implemented interfaces using token instead of reflection, to avoid FatalError. Used in IteratorItemTypeSniff

## 81.6.2 - 2024-05-24
### Fixed
- Catch `Throwable` when doing reflection, for fatal errors
Expand Down
3 changes: 2 additions & 1 deletion src/Core/CodeElement/CodeElementDetector.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ public static function detectFromTokens(File $file, bool $useReflection): FileEl
$docBlock = TokenHelper::getPrevDocBlock($file, $ptr, $skip);
$attrNames = TokenHelper::getPrevAttributeNames($file, $ptr);
$extended = TokenHelper::isClassExtended($file, $ptr);
$currentElement = new ClassElement($line, $docBlock, $fqcn, [], $extended);
$interfaceNames = TokenHelper::getInterfaceNames($file, $ptr);
$currentElement = new ClassElement($line, $docBlock, $fqcn, [], $extended, interfaceNames: $interfaceNames);
$currentElement->setAttributeNames($attrNames);
$fileElement->addClass($currentElement);
$parentElement = $currentElement;
Expand Down
2 changes: 2 additions & 0 deletions src/Core/CodeElement/Element/ClassElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public function __construct(
protected array $constants = [],
array $properties = [],
array $methods = [],
/** @var string[] */
public array $interfaceNames = [],
) {
parent::__construct($line, $docBlock, $fqcn, $attributeNames);
array_walk($properties, [$this, 'addProperty']);
Expand Down
30 changes: 30 additions & 0 deletions src/Core/TokenHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,36 @@ public static function parseAttributeName(string $rawAttribute): ?string
return $matches[1] ?? null;
}

/**
* @return string[]
*/
public static function getInterfaceNames(File $file, int $classPtr): array
{
$openBracePtr = $file->findNext(T_OPEN_CURLY_BRACKET, $classPtr + 1);
if (false === $openBracePtr) {
return []; // not finished editing?
}

$implementsPtr = $file->findNext(T_IMPLEMENTS, $classPtr + 1, $openBracePtr);
if (false === $implementsPtr) {
return []; // does not implement anything
}

$binIndex = 0;
$bins = [];
$tokens = $file->getTokens();
for ($i = $implementsPtr + 1; $i < $openBracePtr; $i++) {
$code = $tokens[$i]['code'];
if (in_array($code, [T_STRING, T_NS_SEPARATOR])) {
$bins[$binIndex][] = $tokens[$i]['content'];
} elseif (T_COMMA === $code) {
$binIndex++;
}
}

return array_map(join(...), $bins);
}

public static function isClassExtended(File $file, int $classPtr): bool
{
$classNamePtr = $file->findNext(Tokens::$emptyTokens, $classPtr + 1, null, true);
Expand Down
26 changes: 11 additions & 15 deletions src/Sniffs/CodeElement/IteratorItemTypeSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@

namespace Gskema\TypeSniff\Sniffs\CodeElement;

use Gskema\TypeSniff\Core\CodeElement\Element\AbstractFqcnElement;
use Gskema\TypeSniff\Core\CodeElement\Element\ClassElement;
use Gskema\TypeSniff\Core\CodeElement\Element\CodeElementInterface;
use Gskema\TypeSniff\Core\SniffHelper;
use IteratorAggregate;
use PHP_CodeSniffer\Files\File;
use ReflectionClass;
use ReflectionException;
use Throwable;

class IteratorItemTypeSniff implements CodeElementSniffInterface
{
Expand Down Expand Up @@ -40,21 +35,22 @@ public function register(): array

/**
* @inheritDoc
* @param AbstractFqcnElement $element
* @param ClassElement $element
*/
public function process(File $file, CodeElementInterface $element, CodeElementInterface $parentElement): void
{
try {
$ref = new ReflectionClass($element->getFqcn());
} catch (Throwable | ReflectionException) {
return; // give up...
}

if ($ref->getParentClass() && $ref->getParentClass()->implementsInterface(IteratorAggregate::class)) {
return; // we only check direct implementations for now
if (empty($element->interfaceNames)) {
return;
}

if (!in_array(IteratorAggregate::class, $ref->getInterfaceNames())) {
// We don't want to do reflection because it may do FatalError
// which we don't want to do custom handlers or libs for now.
// For now just check direct interface names with taking imports into account - collision chance is low-ish.
// Also, it should be direct implementation, if it's parent implementation that various other template tags
// can be used instead.
$hasIteratorAggregate = in_array('\\IteratorAggregate', $element->interfaceNames)
|| in_array('IteratorAggregate', $element->interfaceNames);
if (!$hasIteratorAggregate) {
return;
}

Expand Down
4 changes: 4 additions & 0 deletions tests/Core/CodeElement/CodeElementDetectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,10 @@ public static function dataDetectFromTokens(): array
new ClassMethodMetadata([], null, [], false),
),
],
[
'TestRef0',
'\Gskema\TypeSniff\Core\CodeElement\fixtures\TestInterface0',
]
),
],
[],
Expand Down
2 changes: 1 addition & 1 deletion tests/Core/CodeElement/fixtures/TestRef2.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Gskema\TypeSniff\Core\CodeElement\fixtures;

class TestRef2 extends TestRef1 implements TestRef0
class TestRef2 extends TestRef1 implements TestRef0, \Gskema\TypeSniff\Core\CodeElement\fixtures\TestInterface0
{
public function func0(): void
{
Expand Down

0 comments on commit 3e8e46f

Please sign in to comment.