From 8795b5f91415fa067ff865b476bbb991fc469ce2 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Thu, 18 Jul 2024 15:54:38 +1200 Subject: [PATCH] NEW Add rule to avoid use of "new" keyword --- composer.json | 2 +- rules.neon | 1 + src/PHPStan/KeywordSelfRule.php | 75 +++++++++++++++++++ src/PHPStan/MethodAnnotationsRule.php | 2 + tests/PHPStan/KeywordSelfRuleTest.php | 69 +++++++++++++++++ .../KeywordSelfRuleTest/ClassUsesTrait.php | 11 +++ .../ClassUsesTraitCorrect.php | 11 +++ .../PHPStan/KeywordSelfRuleTest/TestClass.php | 33 ++++++++ .../PHPStan/KeywordSelfRuleTest/TestEnum.php | 32 ++++++++ .../KeywordSelfRuleTest/TestInterface.php | 21 ++++++ .../PHPStan/KeywordSelfRuleTest/TestTrait.php | 32 ++++++++ .../KeywordSelfRuleTest/TestTraitCorrect.php | 34 +++++++++ 12 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 src/PHPStan/KeywordSelfRule.php create mode 100644 tests/PHPStan/KeywordSelfRuleTest.php create mode 100644 tests/PHPStan/KeywordSelfRuleTest/ClassUsesTrait.php create mode 100644 tests/PHPStan/KeywordSelfRuleTest/ClassUsesTraitCorrect.php create mode 100644 tests/PHPStan/KeywordSelfRuleTest/TestClass.php create mode 100644 tests/PHPStan/KeywordSelfRuleTest/TestEnum.php create mode 100644 tests/PHPStan/KeywordSelfRuleTest/TestInterface.php create mode 100644 tests/PHPStan/KeywordSelfRuleTest/TestTrait.php create mode 100644 tests/PHPStan/KeywordSelfRuleTest/TestTraitCorrect.php diff --git a/composer.json b/composer.json index 4a335a8..29152e3 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "license": "BSD-3-Clause", "require": { "php": "^8.1", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.11" }, "require-dev": { "phpunit/phpunit": "^9.6", diff --git a/rules.neon b/rules.neon index 5588bfd..56a6367 100644 --- a/rules.neon +++ b/rules.neon @@ -1,5 +1,6 @@ rules: - SilverStripe\Standards\PHPStan\MethodAnnotationsRule + - SilverStripe\Standards\PHPStan\KeywordSelfRule parameters: # Setting customRulestUsed to true allows us to avoid using the built-in rules diff --git a/src/PHPStan/KeywordSelfRule.php b/src/PHPStan/KeywordSelfRule.php new file mode 100644 index 0000000..24ef7eb --- /dev/null +++ b/src/PHPStan/KeywordSelfRule.php @@ -0,0 +1,75 @@ + + */ +class KeywordSelfRule implements Rule +{ + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + switch (get_class($node)) { + // fetching a constant, e.g. `self::MY_CONST` or `self::class` + 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 (!is_a($node->class, Name::class) || $node->class->toString() !== 'self') { + return []; + } + break; + default: + return []; + } + + $actualClass = $scope->isInTrait() + ? 'self::class' + : $scope->getClassReflection()->getNativeReflection()->getShortName(); + return [ + RuleErrorBuilder::message( + "Can't use keyword 'self'. Use '$actualClass' instead." + )->build() + ]; + } +} diff --git a/src/PHPStan/MethodAnnotationsRule.php b/src/PHPStan/MethodAnnotationsRule.php index 8619184..d3c2d09 100644 --- a/src/PHPStan/MethodAnnotationsRule.php +++ b/src/PHPStan/MethodAnnotationsRule.php @@ -24,6 +24,8 @@ * Validates that `@method` annotations in DataObject and Extension classes are correct, * according to the relations defined within those classes. * + * See https://phpstan.org/developing-extensions/rules + * * @implements Rule */ class MethodAnnotationsRule implements Rule diff --git a/tests/PHPStan/KeywordSelfRuleTest.php b/tests/PHPStan/KeywordSelfRuleTest.php new file mode 100644 index 0000000..6c82fbb --- /dev/null +++ b/tests/PHPStan/KeywordSelfRuleTest.php @@ -0,0 +1,69 @@ + + */ +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' instead.", + 'errorLines' => [13, 18, 18], + ], + 'class' => [ + 'filePaths' => [__DIR__ . '/KeywordSelfRuleTest/TestClass.php'], + 'errorMessage' => "Can't use keyword 'self'. Use 'TestClass' instead.", + 'errorLines' => [9, 11, 16, 16, 18, 20, 21, 21, 25], + ], + 'enum' => [ + 'filePaths' => [__DIR__ . '/KeywordSelfRuleTest/TestEnum.php'], + 'errorMessage' => "Can't use keyword 'self'. Use 'TestEnum' instead.", + 'errorLines' => [9, 14, 14, 16, 17, 18, 20, 24], + ], + '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/TestTraitCorrect.php', + __DIR__ . '/KeywordSelfRuleTest/ClassUsesTraitCorrect.php', + ], + 'errorMessage' => '', + '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/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 @@ +