Skip to content

Commit

Permalink
Add ConstantsInTraitsRule
Browse files Browse the repository at this point in the history
  • Loading branch information
paulbalandan committed Nov 8, 2023
1 parent 0205282 commit 71775c9
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 0 deletions.
6 changes: 6 additions & 0 deletions conf/config.level0.neon
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ rules:
- PHPStan\Rules\Properties\MissingReadOnlyPropertyAssignRule
- PHPStan\Rules\Properties\PropertyAttributesRule
- PHPStan\Rules\Properties\ReadOnlyPropertyRule
- PHPStan\Rules\Traits\ConstantsInTraitsRule
- PHPStan\Rules\Variables\UnsetRule
- PHPStan\Rules\Whitespace\FileWhitespaceRule

Expand Down Expand Up @@ -269,3 +270,8 @@ services:

-
class: PHPStan\Rules\Methods\MissingMagicSerializationMethodsRule

-
class: PHPStan\Rules\Traits\TraitConstantsCollector
tags:
- phpstan.collector
5 changes: 5 additions & 0 deletions src/Php/PhpVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,9 @@ public function supportsJsonValidate(): bool
return $this->versionId >= 80300;
}

public function supportsConstantsInTraits(): bool
{
return $this->versionId >= 80200;
}

}
63 changes: 63 additions & 0 deletions src/Rules/Traits/ConstantsInTraitsRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Traits;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\CollectedDataNode;
use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function sprintf;

/**
* @implements Rule<CollectedDataNode>
*/
class ConstantsInTraitsRule implements Rule
{

public function __construct(private PhpVersion $phpVersion)
{
}

public function getNodeType(): string
{
return CollectedDataNode::class;
}

/**
* @param CollectedDataNode $node
*/
public function processNode(Node $node, Scope $scope): array
{
if ($this->phpVersion->supportsConstantsInTraits()) {
return [];
}

$traitConstantsData = $node->get(TraitConstantsCollector::class);

$errors = [];

foreach ($traitConstantsData as $file => $collectedData) {
foreach ($collectedData as $classConstants) {
foreach ($classConstants as $constants) {
foreach ($constants as [$trait, $const, $line]) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Constant %s::%s is declared inside a trait but is only supported on PHP 8.2 and later.',
$trait,
$const,
))
->file($file)
->line($line)
->nonIgnorable()
->identifier('constants.inTraits')
->build();
}
}
}
}

return $errors;
}

}
53 changes: 53 additions & 0 deletions src/Rules/Traits/TraitConstantsCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Traits;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Collectors\Collector;
use PHPStan\ShouldNotHappenException;
use function array_map;
use function array_values;

/**
* @implements Collector<Node\Stmt\Trait_, list<array<int, array{string, string, int}>>>
*/
class TraitConstantsCollector implements Collector
{

public function getNodeType(): string
{
return Node\Stmt\Trait_::class;
}

/**
* @param Node\Stmt\Trait_ $node
*/
public function processNode(Node $node, Scope $scope)
{
$traitName = null;

if ($node->namespacedName !== null) {
$traitName = $node->namespacedName->toString();
} elseif ($node->name !== null) {
$traitName = $node->name->toString();
}

if ($traitName === null) {
throw new ShouldNotHappenException();
}

return array_values(array_map(
static fn (Node\Stmt\ClassConst $classConst): array => array_map(
static fn (Node\Const_ $const): array => [
$traitName,
$const->name->toString(),
$classConst->getLine(),
],
$classConst->consts,
),
$node->getConstants(),
));
}

}
68 changes: 68 additions & 0 deletions tests/PHPStan/Rules/Traits/ConstantsInTraitsRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Traits;

use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Rules\Traits\TraitConstantsCollector;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<ConstantsInTraitsRule>
*/
class ConstantsInTraitsRuleTest extends RuleTestCase
{

private int $phpVersionId;

protected function getRule(): Rule
{
return new ConstantsInTraitsRule(new PhpVersion($this->phpVersionId));
}

protected function getCollectors(): array
{
return [
new TraitConstantsCollector(),
];
}

public function dataRule(): array
{
return [
[
80100,
[
[
'Constant ConstantsInTraits\FooBar::FOO is declared inside a trait but is only supported on PHP 8.2 and later.',
7,
],
[
'Constant ConstantsInTraits\FooBar::BAR is declared inside a trait but is only supported on PHP 8.2 and later.',
8,
],
[
'Constant ConstantsInTraits\FooBar::QUX is declared inside a trait but is only supported on PHP 8.2 and later.',
8,
],
],
],
[
80200,
[],
],
];
}

/**
* @dataProvider dataRule
*
* @param list<array{0: string, 1: int, 2?: string}> $errors
*/
public function testRule(int $phpVersionId, array $errors): void
{
$this->phpVersionId = $phpVersionId;
$this->analyse([__DIR__ . '/data/constants-in-traits.php'], $errors);
}

}
9 changes: 9 additions & 0 deletions tests/PHPStan/Rules/Traits/data/constants-in-traits.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php // lint >= 8.2

namespace ConstantsInTraits;

trait FooBar
{
const FOO = 'foo';
public const BAR = 'bar', QUX = 'qux';
}

0 comments on commit 71775c9

Please sign in to comment.