Skip to content

Commit

Permalink
Introduce ForbiddenClassNameExtension for append additional forbidden…
Browse files Browse the repository at this point in the history
… class prefixes
  • Loading branch information
kamil-zacek authored and ondrejmirtes committed Mar 20, 2024
1 parent 200641e commit 0f8a352
Show file tree
Hide file tree
Showing 47 changed files with 205 additions and 48 deletions.
32 changes: 32 additions & 0 deletions src/Classes/ForbiddenClassNameExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);

namespace PHPStan\Classes;

/**
* This is the extension interface to implement if you want to dynamically
* add forbidden class prefixes to the ClassForbiddenNameCheck rule.
*
* The idea is that you want to report usages of classes that you're not supposed to use in application.
* For example: Generated Doctrine proxies from their configured namespace.
*
* To register it in the configuration file use the `phpstan.forbiddenClassNamesExtension` service tag:
*
* ```
* services:
* -
* class: App\PHPStan\MyExtension
* tags:
* - phpstan.forbiddenClassNamesExtension
* ```
*
* @api
*/
interface ForbiddenClassNameExtension
{

public const EXTENSION_TAG = 'phpstan.forbiddenClassNamesExtension';

/** @return array<string, string> */
public function getClassPrefixes(): array;

}
40 changes: 34 additions & 6 deletions src/Rules/ClassForbiddenNameCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

namespace PHPStan\Rules;

use PHPStan\Classes\ForbiddenClassNameExtension;
use PHPStan\DependencyInjection\Container;
use function array_map;
use function array_merge;
use function sprintf;
use function str_starts_with;
use function strlen;
use function strpos;
use function substr;

Expand All @@ -16,22 +21,45 @@ class ClassForbiddenNameCheck
'PHP-Scoper' => '_PhpScoper',
];

public function __construct(private Container $container)
{
}

/**
* @param ClassNameNodePair[] $pairs
* @return RuleError[]
*/
public function checkClassNames(array $pairs): array
{
$extensions = $this->container->getServicesByTag(ForbiddenClassNameExtension::EXTENSION_TAG);

$classPrefixes = array_merge(
self::INTERNAL_CLASS_PREFIXES,
...array_map(
static fn (ForbiddenClassNameExtension $extension): array => $extension->getClassPrefixes(),
$extensions,
),
);

$errors = [];
foreach ($pairs as $pair) {
$className = $pair->getClassName();

$projectName = null;
foreach (self::INTERNAL_CLASS_PREFIXES as $project => $prefix) {
if (str_starts_with($className, $prefix)) {
$projectName = $project;
break;
$withoutPrefixClassName = null;
foreach ($classPrefixes as $project => $prefix) {
if (!str_starts_with($className, $prefix)) {
continue;
}

$projectName = $project;
$withoutPrefixClassName = substr($className, strlen($prefix));

if (strpos($withoutPrefixClassName, '\\') === false) {
continue;
}

$withoutPrefixClassName = substr($withoutPrefixClassName, strpos($withoutPrefixClassName, '\\'));
}

if ($projectName === null) {
Expand All @@ -44,10 +72,10 @@ public function checkClassNames(array $pairs): array
$className,
))->line($pair->getNode()->getLine())->nonIgnorable();

if (strpos($className, '\\') !== false) {
if ($withoutPrefixClassName !== null) {
$error->tip(sprintf(
'This is most likely unintentional. Did you mean to type %s?',
substr($className, strpos($className, '\\')),
$withoutPrefixClassName,
));
}

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ protected function getRule(): Rule
),
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, false),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
true,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected function getRule(): Rule
),
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, false),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
true,
),
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ protected function getRule(): Rule
new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false),
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
new PhpVersion($this->phpVersion),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected function getRule(): Rule
return new ExistingClassInClassExtendsRule(
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
$reflectionProvider,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected function getRule(): Rule
$reflectionProvider,
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
true,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected function getRule(): Rule
return new ExistingClassInTraitUseRule(
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
$reflectionProvider,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected function getRule(): Rule
return new ExistingClassesInClassImplementsRule(
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
$reflectionProvider,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protected function getRule(): Rule
return new ExistingClassesInEnumImplementsRule(
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
$reflectionProvider,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected function getRule(): Rule
return new ExistingClassesInInterfaceExtendsRule(
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
$reflectionProvider,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Classes;

use PHPStan\Php\PhpVersion;
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
use PHPStan\Rules\ClassNameCheck;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Properties\PropertyReflectionFinder;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
use function array_merge;

/**
* @extends RuleTestCase<InstantiationRule>
*/
class ForbiddenNameCheckExtensionRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
$reflectionProvider = $this->createReflectionProvider();
return new InstantiationRule(
$reflectionProvider,
new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true),
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(self::getContainer()),
),
);
}

public static function getAdditionalConfigFiles(): array
{
return array_merge(parent::getAdditionalConfigFiles(), [
__DIR__ . '/data/forbidden-name-class-extension.neon',
]);
}

public function testInternalClassFromExtensions(): void
{
$this->analyse([__DIR__ . '/data/forbidden-name-class-extension.php'], [
[
'Referencing prefixed Doctrine class: App\GeneratedProxy\__CG__\App\TestDoctrineEntity.',
31,
'This is most likely unintentional. Did you mean to type \App\TestDoctrineEntity?',
],
[
'Referencing prefixed PHPStan class: _PHPStan_15755dag8c\TestPhpStanEntity.',
32,
'This is most likely unintentional. Did you mean to type \TestPhpStanEntity?',
],
]);
}

}
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Classes/InstantiationRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protected function getRule(): Rule
new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true),
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Classes/MixinRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ protected function getRule(): Rule
$reflectionProvider,
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
new GenericObjectTypeCheck(),
new MissingTypehintCheck(true, true, true, true, []),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
services:
-
class: ForbiddenNameClassExtension\CGForbiddenNameClassExtension
tags:
- phpstan.forbiddenClassNamesExtension
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);

namespace App\GeneratedProxy\__CG__\App;

class TestDoctrineEntity
{
}

namespace _PHPStan_15755dag8c;

class TestPhpStanEntity
{
}

namespace ForbiddenNameClassExtension;

use App\GeneratedProxy\__CG__\App\TestEntity;

class CGForbiddenNameClassExtension implements \PHPStan\Classes\ForbiddenClassNameExtension
{

public function getClassPrefixes(): array
{
return [
'Doctrine' => 'App\GeneratedProxy\__CG__',
];
}

}

$doctrineEntity = new \App\GeneratedProxy\__CG__\App\TestDoctrineEntity();
$phpStanEntity = new \_PHPStan_15755dag8c\TestPhpStanEntity();
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected function getRule(): Rule
),
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, false),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
true,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected function getRule(): Rule
$reflectionProvider,
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
true,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected function getRule(): Rule
),
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, false),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
true,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected function getRule(): Rule
),
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, false),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
true,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protected function getRule(): Rule
$reflectionProvider,
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
new UnresolvableTypeHelper(),
new PhpVersion($this->phpVersionId),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protected function getRule(): Rule
$reflectionProvider,
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
new UnresolvableTypeHelper(),
new PhpVersion($this->phpVersionId),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protected function getRule(): Rule
$reflectionProvider,
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
new UnresolvableTypeHelper(),
new PhpVersion($this->phpVersionId),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected function getRule(): Rule
),
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, false),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
true,
),
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected function getRule(): Rule
),
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, false),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
true,
),
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ protected function getRule(): Rule
$reflectionProvider,
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
new ClassForbiddenNameCheck(self::getContainer()),
),
new GenericObjectTypeCheck(),
$typeAliasResolver,
Expand Down

0 comments on commit 0f8a352

Please sign in to comment.