-
Notifications
You must be signed in to change notification settings - Fork 504
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add stringable access check to ClassConstantRule #3910
base: 2.1.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,6 +39,7 @@ public function __construct( | |
private RuleLevelHelper $ruleLevelHelper, | ||
private ClassNameCheck $classCheck, | ||
private PhpVersion $phpVersion, | ||
private bool $checkNonStringableDynamicAccess = true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand that we shouldn't use the default value, but I was getting the following error, probably due to a missing DI configuration, so I temporarily added
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My suggestion above will help you get rid of this error. |
||
) | ||
{ | ||
} | ||
|
@@ -60,6 +61,24 @@ public function processNode(Node $node, Scope $scope): array | |
$name = $constantString->getValue(); | ||
$constantNameScopes[$name] = $scope->filterByTruthyValue(new Identical($node->name, new String_($name))); | ||
} | ||
|
||
if ($this->checkNonStringableDynamicAccess) { | ||
$accepts = $this->ruleLevelHelper->accepts(new StringType(), $nameType, true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete this line, it's not used anywhere. |
||
$typeResult = $this->ruleLevelHelper->findTypeToCheck( | ||
$scope, | ||
$node->name, | ||
'', | ||
static fn (Type $type) => $type->toString()->isString()->yes() | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After this call there should be: $type = $typeResult->getType();
if ($type instanceof ErrorType) {
return [];
} Some rules return "unknown class errors" but that's irrelevant here. |
||
|
||
if (! $typeResult->getType()->isString()->yes() || | ||
$typeResult->getType()->toString()->isNumericString()->yes() | ||
) { | ||
$errors[] = RuleErrorBuilder::message(sprintf('Cannot fetch class constant with a non-stringable type %s.', $nameType->describe(VerbosityLevel::typeOnly()))) | ||
->identifier('classConstant.fetchInvalidExpression') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message and the identifier should reflect we're trying to access a class constant by non-stringable name. That's not obvious. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also it might be nice to mention the class name here. |
||
->build(); | ||
} | ||
} | ||
} | ||
|
||
foreach ($constantNameScopes as $constantName => $constantScope) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,8 @@ class ClassConstantRuleTest extends RuleTestCase | |
|
||
private int $phpVersion; | ||
|
||
private bool $checkNonStringableDynamicAccess; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If all the test cases use |
||
|
||
protected function getRule(): Rule | ||
{ | ||
$reflectionProvider = $this->createReflectionProvider(); | ||
|
@@ -30,12 +32,14 @@ protected function getRule(): Rule | |
new ClassForbiddenNameCheck(self::getContainer()), | ||
), | ||
new PhpVersion($this->phpVersion), | ||
$this->checkNonStringableDynamicAccess, | ||
); | ||
} | ||
|
||
public function testClassConstant(): void | ||
{ | ||
$this->phpVersion = PHP_VERSION_ID; | ||
$this->checkNonStringableDynamicAccess = true; | ||
$this->analyse( | ||
[ | ||
__DIR__ . '/data/class-constant.php', | ||
|
@@ -99,6 +103,7 @@ public function testClassConstant(): void | |
public function testClassConstantVisibility(): void | ||
{ | ||
$this->phpVersion = PHP_VERSION_ID; | ||
$this->checkNonStringableDynamicAccess = true; | ||
$this->analyse([__DIR__ . '/data/class-constant-visibility.php'], [ | ||
[ | ||
'Access to private constant PRIVATE_BAR of class ClassConstantVisibility\Bar.', | ||
|
@@ -168,6 +173,7 @@ public function testClassConstantVisibility(): void | |
public function testClassExists(): void | ||
{ | ||
$this->phpVersion = PHP_VERSION_ID; | ||
$this->checkNonStringableDynamicAccess = true; | ||
$this->analyse([__DIR__ . '/data/class-exists.php'], [ | ||
[ | ||
'Class UnknownClass\Bar not found.', | ||
|
@@ -242,12 +248,14 @@ public function dataClassConstantOnExpression(): array | |
public function testClassConstantOnExpression(int $phpVersion, array $errors): void | ||
{ | ||
$this->phpVersion = $phpVersion; | ||
$this->checkNonStringableDynamicAccess = true; | ||
$this->analyse([__DIR__ . '/data/class-constant-on-expr.php'], $errors); | ||
} | ||
|
||
public function testAttributes(): void | ||
{ | ||
$this->phpVersion = PHP_VERSION_ID; | ||
$this->checkNonStringableDynamicAccess = true; | ||
$this->analyse([__DIR__ . '/data/class-constant-attribute.php'], [ | ||
[ | ||
'Access to undefined constant ClassConstantAttribute\Foo::BAR.', | ||
|
@@ -287,18 +295,21 @@ public function testRuleWithNullsafeVariant(): void | |
} | ||
|
||
$this->phpVersion = PHP_VERSION_ID; | ||
$this->checkNonStringableDynamicAccess = true; | ||
$this->analyse([__DIR__ . '/data/class-constant-nullsafe.php'], []); | ||
} | ||
|
||
public function testBug7675(): void | ||
{ | ||
$this->phpVersion = PHP_VERSION_ID; | ||
$this->checkNonStringableDynamicAccess = true; | ||
$this->analyse([__DIR__ . '/data/bug-7675.php'], []); | ||
} | ||
|
||
public function testBug8034(): void | ||
{ | ||
$this->phpVersion = PHP_VERSION_ID; | ||
$this->checkNonStringableDynamicAccess = true; | ||
$this->analyse([__DIR__ . '/data/bug-8034.php'], [ | ||
[ | ||
'Access to undefined constant static(Bug8034\HelloWorld)::FIELDS.', | ||
|
@@ -310,6 +321,7 @@ public function testBug8034(): void | |
public function testClassConstFetchDefined(): void | ||
{ | ||
$this->phpVersion = PHP_VERSION_ID; | ||
$this->checkNonStringableDynamicAccess = true; | ||
$this->analyse([__DIR__ . '/data/class-const-fetch-defined.php'], [ | ||
[ | ||
'Access to undefined constant ClassConstFetchDefined\Foo::TEST.', | ||
|
@@ -411,6 +423,7 @@ public function testPhpstanInternalClass(): void | |
$tip = 'This is most likely unintentional. Did you mean to type \AClass?'; | ||
|
||
$this->phpVersion = PHP_VERSION_ID; | ||
$this->checkNonStringableDynamicAccess = true; | ||
$this->analyse([__DIR__ . '/data/phpstan-internal-class.php'], [ | ||
[ | ||
'Referencing prefixed PHPStan class: _PHPStan_156ee64ba\AClass.', | ||
|
@@ -427,6 +440,7 @@ public function testClassConstantAccessedOnTrait(): void | |
} | ||
|
||
$this->phpVersion = PHP_VERSION_ID; | ||
$this->checkNonStringableDynamicAccess = true; | ||
$this->analyse([__DIR__ . '/data/class-constant-accessed-on-trait.php'], [ | ||
[ | ||
'Cannot access constant TEST on trait ClassConstantAccessedOnTrait\Foo.', | ||
|
@@ -442,8 +456,17 @@ public function testDynamicAccess(): void | |
} | ||
|
||
$this->phpVersion = PHP_VERSION_ID; | ||
$this->checkNonStringableDynamicAccess = true; | ||
|
||
$this->analyse([__DIR__ . '/data/dynamic-constant-access.php'], [ | ||
[ | ||
'Access to undefined constant ClassConstantDynamicAccess\Foo::FOO.', | ||
17, | ||
], | ||
[ | ||
'Cannot fetch class constant with a non-stringable type object.', | ||
19, | ||
], | ||
[ | ||
'Access to undefined constant ClassConstantDynamicAccess\Foo::FOO.', | ||
20, | ||
|
@@ -479,4 +502,41 @@ public function testDynamicAccess(): void | |
]); | ||
} | ||
|
||
public function testStringableDynamicAccess(): void | ||
{ | ||
if (PHP_VERSION_ID < 80300) { | ||
$this->markTestSkipped('Test requires PHP 8.3.'); | ||
} | ||
|
||
$this->phpVersion = PHP_VERSION_ID; | ||
$this->checkNonStringableDynamicAccess = true; | ||
|
||
$this->analyse([__DIR__ . '/data/dynamic-constant-stringable-access.php'], [ | ||
[ | ||
'Cannot fetch class constant with a non-stringable type mixed.', | ||
13, | ||
], | ||
[ | ||
'Cannot fetch class constant with a non-stringable type string|null.', | ||
14, | ||
], | ||
[ | ||
'Cannot fetch class constant with a non-stringable type Stringable|null.', | ||
15, | ||
], | ||
[ | ||
'Cannot fetch class constant with a non-stringable type int.', | ||
16, | ||
], | ||
[ | ||
'Cannot fetch class constant with a non-stringable type int|null.', | ||
17, | ||
], | ||
[ | ||
'Cannot fetch class constant with a non-stringable type DateTime|string.', | ||
18, | ||
], | ||
]); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php // lint >= 8.3 | ||
|
||
namespace ClassConstantDynamicStringableAccess; | ||
|
||
use Stringable; | ||
use DateTime; | ||
|
||
final class Foo | ||
{ | ||
|
||
public function test(mixed $mixed, ?string $nullableStr, ?Stringable $nullableStringable, int $int, ?int $nullableInt, DateTime|string $datetimeOrStr): void | ||
{ | ||
echo self::{$mixed}; | ||
echo self::{$nullableStr}; | ||
echo self::{$nullableStringable}; | ||
echo self::{$int}; | ||
echo self::{$nullableInt}; | ||
echo self::{$datetimeOrStr}; | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you adding the service here in this file? Remove it please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead, config.level0.neon should be modified:
rules
section.tags
section with the corresponding tag.