diff --git a/src/PHPStan/KeywordSelfRule.php b/src/PHPStan/KeywordSelfRule.php new file mode 100644 index 0000000..802a847 --- /dev/null +++ b/src/PHPStan/KeywordSelfRule.php @@ -0,0 +1,73 @@ + + */ +class KeywordSelfRule implements Rule +{ + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $gettingConst = false; + switch (get_class($node)) { + // fetching a constant, e.g. `self::MY_CONST` + case ClassConstFetch::class: + // Traits can use `self` to get const values - but otherwise follow same + // logic as methods or properties. + if ($scope->isInTrait()) { + return []; + } + // static method call, e.g. `self::myMethod()` + case StaticCall::class: + // fetching a static property, e.g. `self::$my_property` + case StaticPropertyFetch::class: + if (!is_a($node->class, Name::class) || $node->class->toString() !== 'self') { + return []; + } + break; + // `self` as a type (for a property, argument, or return type) + case Name::class: + // Trait can use `self` for typehinting + if ($scope->isInTrait() || $node->toString() !== 'self') { + return []; + } + break; + // instantiating a new object from `self`, e.g. `new self()` + case New_::class: + if ($node->class->toString() !== 'self') { + return []; + } + break; + default: + return []; + } + + $actualClass = $scope->isInTrait() ? 'self::class' : $scope->getClassReflection()->getName(); + return [ + RuleErrorBuilder::message( + "Can't use keyword 'self'. Use '$actualClass' instead." + )->build() + ]; + } +} diff --git a/tests/PHPStan/KeywordSelfRuleTest.php b/tests/PHPStan/KeywordSelfRuleTest.php new file mode 100644 index 0000000..6651b65 --- /dev/null +++ b/tests/PHPStan/KeywordSelfRuleTest.php @@ -0,0 +1,62 @@ + + */ +class KeywordSelfRuleTest extends RuleTestCase +{ + protected function getRule(): Rule + { + return new KeywordSelfRule(); + } + + public function provideRule() + { + return [ + 'interface' => [ + 'filePaths' => [__DIR__ . '/KeywordSelfRuleTest/TestInterface.php'], + 'errorMessage' => "Can't use keyword 'self'. Use '" . TestInterface::class . "' instead.", + 'errorLines' => [13, 18, 18], + ], + 'class' => [ + 'filePaths' => [__DIR__ . '/KeywordSelfRuleTest/TestClass.php'], + 'errorMessage' => "Can't use keyword 'self'. Use '" . TestClass::class . "' instead.", + 'errorLines' => [9, 11, 16, 16, 18, 20, 21, 21, 25], + ], + 'trait' => [ + 'filePaths' => [__DIR__ . '/KeywordSelfRuleTest/TestTrait.php', __DIR__ . '/KeywordSelfRuleTest/ClassUsesTrait.php'], + 'errorMessage' => "Can't use keyword 'self'. Use 'self::class' instead.", + 'errorLines' => [17, 19, 20, 24], + ], + 'trait no errors' => [ + 'filePaths' => [__DIR__ . '/KeywordSelfRuleTest/TestTrait.php', __DIR__ . '/KeywordSelfRuleTest/ClassUsesTrait.php'], + 'errorMessage' => "Can't use keyword 'self'. Use 'self::class' instead.", + 'errorLines' => [], + ], + ]; + } + + /** + * @dataProvider provideRule + */ + public function testRule(array $filePaths, string $errorMessage, array $errorLines): void + { + $errors = []; + foreach ($errorLines as $line) { + $errors[] = [$errorMessage, $line]; + } + $this->analyse($filePaths, $errors); + } +} diff --git a/tests/PHPStan/KeywordSelfRuleTest/Anothertest.php b/tests/PHPStan/KeywordSelfRuleTest/Anothertest.php new file mode 100644 index 0000000..e69de29 diff --git a/tests/PHPStan/KeywordSelfRuleTest/ClassUsesTrait.php b/tests/PHPStan/KeywordSelfRuleTest/ClassUsesTrait.php new file mode 100644 index 0000000..8594e10 --- /dev/null +++ b/tests/PHPStan/KeywordSelfRuleTest/ClassUsesTrait.php @@ -0,0 +1,11 @@ +