Skip to content

Commit

Permalink
Possibility to re-allow disallowed call or attribute when used in a c…
Browse files Browse the repository at this point in the history
…lass with specified attribute
  • Loading branch information
spaze committed Jan 26, 2025
1 parent 2228b2e commit d299254
Show file tree
Hide file tree
Showing 11 changed files with 298 additions and 0 deletions.
36 changes: 36 additions & 0 deletions src/Allowed/Allowed.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

use PhpParser\Node\Arg;
use PHPStan\Analyser\Scope;
use PHPStan\BetterReflection\Reflection\Adapter\FakeReflectionAttribute;
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionAttribute;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\Type;
Expand Down Expand Up @@ -68,6 +70,18 @@ public function isAllowed(Scope $scope, ?array $args, DisallowedWithParams $disa
if ($disallowed->getAllowParamsAnywhere()) {
return $this->hasAllowedParams($scope, $args, $disallowed->getAllowParamsAnywhere(), true);
}
if ($disallowed->getAllowInClassWithAttributes() && $scope->isInClass()) {
return $this->hasAllowedAttribute(
$scope->getClassReflection()->getNativeReflection()->getAttributes(),
$disallowed->getAllowInClassWithAttributes(),
);
}
if ($disallowed->getAllowExceptInClassWithAttributes() && $scope->isInClass()) {
return !$this->hasAllowedAttribute(
$scope->getClassReflection()->getNativeReflection()->getAttributes(),
$disallowed->getAllowExceptInClassWithAttributes(),
);
}
return false;
}

Expand Down Expand Up @@ -139,6 +153,28 @@ private function hasAllowedParamsInAllowed(Scope $scope, ?array $args, Disallowe
}


/**
* @param list<ReflectionAttribute|FakeReflectionAttribute> $attributes
* @param list<string> $allowConfig
* @return bool
*/
private function hasAllowedAttribute(array $attributes, array $allowConfig): bool
{
$names = [];
foreach ($attributes as $attribute) {
$names[] = $attribute->getName();
}
foreach ($allowConfig as $allowAttribute) {
foreach ($names as $name) {
if (fnmatch($allowAttribute, $name, FNM_NOESCAPE | FNM_CASEFOLD)) {
return true;
}
}
}
return false;
}


/**
* @param array<Arg> $args
* @param Scope $scope
Expand Down
12 changes: 12 additions & 0 deletions src/Disallowed.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,16 @@ public function getAllowInCalls(): array;
*/
public function getAllowExceptInCalls(): array;


/**
* @return list<string>
*/
public function getAllowInClassWithAttributes(): array;


/**
* @return list<string>
*/
public function getAllowExceptInClassWithAttributes(): array;

}
12 changes: 12 additions & 0 deletions src/DisallowedAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,18 @@ public function getAllowExceptParams(): array
}


public function getAllowInClassWithAttributes(): array
{
return $this->allowedConfig->getAllowInClassWithAttributes();
}


public function getAllowExceptInClassWithAttributes(): array
{
return $this->allowedConfig->getAllowExceptInClassWithAttributes();
}


public function getErrorIdentifier(): ?string
{
return $this->errorIdentifier;
Expand Down
12 changes: 12 additions & 0 deletions src/DisallowedCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ public function getAllowExceptParams(): array
}


public function getAllowInClassWithAttributes(): array
{
return $this->allowedConfig->getAllowInClassWithAttributes();
}


public function getAllowExceptInClassWithAttributes(): array
{
return $this->allowedConfig->getAllowExceptInClassWithAttributes();
}


public function getErrorIdentifier(): ?string
{
return $this->errorIdentifier;
Expand Down
12 changes: 12 additions & 0 deletions src/DisallowedConstant.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ public function getAllowExceptInCalls(): array
}


public function getAllowInClassWithAttributes(): array
{
throw new NotImplementedYetException();
}


public function getAllowExceptInClassWithAttributes(): array
{
throw new NotImplementedYetException();
}


public function getErrorIdentifier(): ?string
{
return $this->errorIdentifier;
Expand Down
12 changes: 12 additions & 0 deletions src/DisallowedControlStructure.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ public function getAllowExceptInCalls(): array
}


public function getAllowInClassWithAttributes(): array
{
throw new NotImplementedYetException();
}


public function getAllowExceptInClassWithAttributes(): array
{
throw new NotImplementedYetException();
}


public function getErrorIdentifier(): ?string
{
return $this->errorIdentifier;
Expand Down
12 changes: 12 additions & 0 deletions src/DisallowedNamespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ public function getAllowExceptInCalls(): array
}


public function getAllowInClassWithAttributes(): array
{
throw new NotImplementedYetException();
}


public function getAllowExceptInClassWithAttributes(): array
{
throw new NotImplementedYetException();
}


public function getErrorIdentifier(): ?string
{
return $this->errorIdentifier;
Expand Down
12 changes: 12 additions & 0 deletions src/DisallowedVariable.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ public function getAllowExceptInCalls(): array
}


public function getAllowInClassWithAttributes(): array
{
throw new NotImplementedYetException();
}


public function getAllowExceptInClassWithAttributes(): array
{
throw new NotImplementedYetException();
}


public function getErrorIdentifier(): ?string
{
return $this->errorIdentifier;
Expand Down
73 changes: 73 additions & 0 deletions tests/Calls/FunctionCallsAllowInClassWithAttributesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php
declare(strict_types = 1);

namespace Spaze\PHPStan\Rules\Disallowed\Calls;

use PHPStan\Rules\Rule;
use PHPStan\ShouldNotHappenException;
use PHPStan\Testing\RuleTestCase;
use Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory;
use Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedCallableParameterRuleErrors;
use Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedFunctionRuleErrors;

class FunctionCallsAllowInClassWithAttributesTest extends RuleTestCase
{

/**
* @throws ShouldNotHappenException
*/
protected function getRule(): Rule
{
$container = self::getContainer();
return new FunctionCalls(
$container->getByType(DisallowedFunctionRuleErrors::class),
$container->getByType(DisallowedCallableParameterRuleErrors::class),
$container->getByType(DisallowedCallFactory::class),
[
[
'function' => 'md5()',
'allowInClassWithAttributes' => [
'\Attribute',
],
],
[
'function' => 'sha1()',
'allowInClassWithAttributes' => [
'\Attributes\Attribute2',
'\Attributes\Attribute3',
],
],
[
'function' => 'strlen()',
'allowExceptInClassWithAttributes' => [
'\Attributes\Attribute3',
],
],
]
);
}


public function testRule(): void
{
$this->analyse([__DIR__ . '/../src/AttributeClass.php'], [
[
'Calling strlen() is forbidden.',
30,
],
[
'Calling md5() is forbidden.',
41,
],
]);
}


public static function getAdditionalConfigFiles(): array
{
return [
__DIR__ . '/../../extension.neon',
];
}

}
60 changes: 60 additions & 0 deletions tests/Usages/AttributeUsagesAllowInClassWithAttributesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
declare(strict_types = 1);

namespace Spaze\PHPStan\Rules\Disallowed\Usages;

use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use Spaze\PHPStan\Rules\Disallowed\DisallowedAttributeFactory;
use Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedAttributeRuleErrors;

class AttributeUsagesAllowInClassWithAttributesTest extends RuleTestCase
{

protected function getRule(): Rule
{
$container = self::getContainer();
return new AttributeUsages(
$container->getByType(DisallowedAttributeRuleErrors::class),
$container->getByType(DisallowedAttributeFactory::class),
[
[
'attribute' => '\Attributes\Attribute4',
'allowInClassWithAttributes' => [
'\Attributes\Attribute2',
],
],
[
'attribute' => '\Attributes\Attribute5',
'allowExceptInClassWithAttributes' => [
'\Attributes\Attribute3',
],
],
]
);
}


public function testRule(): void
{
$this->analyse([__DIR__ . '/../src/AttributeClass.php'], [
[
'Attribute Attributes\Attribute5 is forbidden.',
27,
],
[
'Attribute Attributes\Attribute4 is forbidden.',
38,
],
]);
}


public static function getAdditionalConfigFiles(): array
{
return [
__DIR__ . '/../../extension.neon',
];
}

}
45 changes: 45 additions & 0 deletions tests/src/AttributeClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,51 @@
use Attribute;

#[Attribute]
#[Attribute2]
class AttributeClass
{

#[Attribute4]
public function method(): void
{
md5('QNKCDZO');
sha1('aaroZmOk');
strlen('anazgoh');
}

}

#[Attribute3]
class AttributeClass2
{

#[Attribute5]
public function method(): void
{
strlen('anazgoh');
}

}

class ChildAttributeClass extends AttributeClass
{

#[Attribute4]
public function method(): void
{
md5('QNKCDZO');
strlen('anazgoh');
}

}

class ChildAttributeClass2
{

#[Attribute5]
public function method(): void
{
strlen('anazgoh');
}

}

0 comments on commit d299254

Please sign in to comment.