Skip to content
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

Introduce ForbiddenClassNameExtension for append additional forbidden… #2979

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
kamil-zacek marked this conversation as resolved.
Show resolved Hide resolved
{

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