Repository: spaze/phpstan-disallowed-calls (php)

Issue:
Child constructor should be disallowed when parent constructor is disallowed
Given code like this (this is a code from a test actually):
```php
$sub = new Inheritance\Sub();
```

and configuration (from a test as well, so no neon syntax) like:
```php
'method' => 'Inheritance\Base::__construct()',
```

the call is not reported.

The call is reported if I change the configuration to
```php
'method' => 'Inheritance\Sub::__construct()',
```

Pull request:
Child constructors should be disallowed when parent constructor or an interface is disallowed
Have to check parents and interfaces too

Close #146

Patch:
diff --git a/src/Calls/NewCalls.php b/src/Calls/NewCalls.php
index 56aeeed..128d5b3 100644
--- a/src/Calls/NewCalls.php
+++ b/src/Calls/NewCalls.php
@@ -11,7 +11,7 @@ use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
 use PHPStan\Rules\RuleError;
 use PHPStan\ShouldNotHappenException;
-use PHPStan\Type\ConstantScalarType;
+use PHPStan\Type\Constant\ConstantStringType;
 use Spaze\PHPStan\Rules\Disallowed\DisallowedCall;
 use Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory;
 use Spaze\PHPStan\Rules\Disallowed\DisallowedHelper;
@@ -24,6 +24,7 @@ use Spaze\PHPStan\Rules\Disallowed\DisallowedHelper;
  */
 class NewCalls implements Rule
 {
+	private const CONSTRUCT = '::__construct';
 
 	/** @var DisallowedHelper */
 	private $disallowedHelper;
@@ -61,13 +62,41 @@ class NewCalls implements Rule
 	public function processNode(Node $node, Scope $scope): array
 	{
 		if ($node->class instanceof Name) {
-			$name = "{$node->class}::__construct";
-		} elseif ($node->class instanceof Expr && $scope->getType($node->class) instanceof ConstantScalarType) {
-			$name = $scope->getType($node->class)->getValue() . '::__construct';
-		} else {
+			$className = $node->class;
+		} elseif ($node->class instanceof Expr) {
+			$type = $scope->getType($node->class);
+			if ($type instanceof ConstantStringType) {
+				$className = new Name($type->getValue());
+			}
+		}
+		if (!isset($className)) {
 			return [];
 		}
-		return $this->disallowedHelper->getDisallowedMessage($node, $scope, $name, $name, $this->disallowedCalls);
+
+		$type = $scope->resolveTypeByName($className);
+		$names = [
+			$type->getClassName(),
+		];
+		$reflection = $type->getClassReflection();
+		if ($reflection) {
+			foreach ($reflection->getParents() as $parent) {
+				$names[] = $parent->getName();
+			}
+			foreach ($reflection->getInterfaces() as $interface) {
+				$names[] = $interface->getName();
+			}
+		}
+
+		$errors = [];
+		foreach ($names as $name) {
+			$name .= self::CONSTRUCT;
+			$errors = array_merge(
+				$errors,
+				$this->disallowedHelper->getDisallowedMessage($node, $scope, $name, $type->getClassName() . self::CONSTRUCT, $this->disallowedCalls)
+			);
+		}
+
+		return $errors;
 	}
 
 }


Test patch:
diff --git a/tests/Calls/NewCallsTest.php b/tests/Calls/NewCallsTest.php
index 55cfa17..04d6543 100644
--- a/tests/Calls/NewCallsTest.php
+++ b/tests/Calls/NewCallsTest.php
@@ -35,6 +35,14 @@ class NewCallsTest extends RuleTestCase
 						'../src/*-allow/*.*',
 					],
 				],
+				[
+					'method' => 'Inheritance\Base::__construct()',
+					'message' => 'all your base are belong to us',
+					'allowIn' => [
+						'../src/disallowed-allowed/*.php',
+						'../src/*-allow/*.*',
+					],
+				],
 				[
 					'function' => 'DateTime::__construct()',
 					'message' => 'no future',
@@ -55,6 +63,10 @@ class NewCallsTest extends RuleTestCase
 	{
 		// Based on the configuration above, in this file:
 		$this->analyse([__DIR__ . '/../src/disallowed/methodCalls.php'], [
+			[
+				'Calling Inheritance\Base::__construct() (as Inheritance\Sub::__construct()) is forbidden, all your base are belong to us',
+				19,
+			],
 			[
 				'Calling Constructor\ClassWithConstructor::__construct() is forbidden, class ClassWithConstructor should not be created',
 				32,