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

Issue:
disallowedClasses does not work when nullable
Hello, I found out that nullable types are not reported by this extension. 
```php
class MyClass
{
    public \DateTime $datetime; // is reported
    public ?\DateTime $datetime2; // does not report
}
```



Pull request:
Support disallowing classes in nullable & union & intersection types
Fix #131

Patch:
diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
index 001112b..0da29e7 100644
--- a/.github/workflows/php.yml
+++ b/.github/workflows/php.yml
@@ -10,11 +10,34 @@ jobs:
     strategy:
       matrix:
         php-version:
-          - 7.2
-          - 7.3
-          - 7.4
-          - 8.0
-          - 8.1
+          - "7.2"
+          - "7.3"
+          - "7.4"
+          - "8.0"
+          - "8.1"
+        run:
+          - composer lint
+          - composer phpcs
+          - composer phpstan
+          - composer phpunit
+        exclude:
+          - php-version: "7.2"
+            run: composer lint
+          - php-version: "7.3"
+            run: composer lint
+          - php-version: "7.4"
+            run: composer lint
+          - php-version: "8.0"
+            run: composer lint
+        include:
+          - php-version: "7.2"
+            run: composer lint -- --exclude tests/libs/TypesEverywhere.php
+          - php-version: "7.3"
+            run: composer lint -- --exclude tests/libs/TypesEverywhere.php
+          - php-version: "7.4"
+            run: composer lint -- --exclude tests/libs/TypesEverywhere.php
+          - php-version: "8.0"
+            run: composer lint -- --exclude tests/libs/TypesEverywhere.php
 
     steps:
     - uses: actions/checkout@v3
@@ -40,4 +63,4 @@ jobs:
       run: composer update --no-progress --no-interaction
 
     - name: Run tests
-      run: composer test
+      run: ${{ matrix.run }}
diff --git a/src/Usages/NamespaceUsages.php b/src/Usages/NamespaceUsages.php
index 3ba0b35..4bf066b 100644
--- a/src/Usages/NamespaceUsages.php
+++ b/src/Usages/NamespaceUsages.php
@@ -7,11 +7,14 @@ use PhpParser\Node;
 use PhpParser\Node\Expr\ClassConstFetch;
 use PhpParser\Node\Expr\New_;
 use PhpParser\Node\Expr\StaticCall;
+use PhpParser\Node\IntersectionType;
 use PhpParser\Node\Name;
 use PhpParser\Node\Name\FullyQualified;
+use PhpParser\Node\NullableType;
 use PhpParser\Node\Stmt\Class_;
 use PhpParser\Node\Stmt\TraitUse;
 use PhpParser\Node\Stmt\UseUse;
+use PhpParser\Node\UnionType;
 use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
 use PHPStan\Rules\RuleError;
@@ -60,6 +63,17 @@ class NamespaceUsages implements Rule
 		if ($node instanceof FullyQualified) {
 			$description = 'Class';
 			$namespaces = [$node->toString()];
+		} elseif ($node instanceof NullableType && $node->type instanceof FullyQualified) {
+			$description = 'Class';
+			$namespaces = [$node->type->toString()];
+		} elseif ($node instanceof UnionType || $node instanceof IntersectionType) {
+			$description = 'Class';
+			$namespaces = [];
+			foreach ($node->types as $type) {
+				if ($type instanceof FullyQualified) {
+					$namespaces[] = $type->toString();
+				}
+			}
 		} elseif ($node instanceof UseUse) {
 			$namespaces = [$node->name->toString()];
 		} elseif ($node instanceof StaticCall && $node->class instanceof Name) {


Test patch:
diff --git a/tests/Usages/NamespaceUsagesTypesTest.php b/tests/Usages/NamespaceUsagesTypesTest.php
new file mode 100644
index 0000000..f832bb0
--- /dev/null
+++ b/tests/Usages/NamespaceUsagesTypesTest.php
@@ -0,0 +1,111 @@
+<?php
+declare(strict_types = 1);
+
+namespace Spaze\PHPStan\Rules\Disallowed\Usages;
+
+use PHPStan\File\FileHelper;
+use PHPStan\Rules\Rule;
+use PHPStan\Testing\RuleTestCase;
+use Spaze\PHPStan\Rules\Disallowed\DisallowedNamespaceFactory;
+use Spaze\PHPStan\Rules\Disallowed\DisallowedNamespaceHelper;
+use Spaze\PHPStan\Rules\Disallowed\IsAllowedFileHelper;
+
+/**
+ * @requires PHP > 8.1
+ */
+class NamespaceUsagesTypesTest extends RuleTestCase
+{
+
+	protected function getRule(): Rule
+	{
+		return new NamespaceUsages(
+			new DisallowedNamespaceHelper(new IsAllowedFileHelper(new FileHelper(__DIR__))),
+			new DisallowedNamespaceFactory(),
+			[
+				[
+					'class' => 'Waldo\Quux\Blade',
+					'message' => 'do androids dream of electric sheep?',
+				],
+			]
+		);
+	}
+
+
+	public function testRule(): void
+	{
+		// Based on the configuration above, in this file:
+		$this->analyse([__DIR__ . '/../libs/TypesEverywhere.php'], [
+			[
+				// expect this error message:
+				'Namespace Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				// on this line:
+				7,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				12,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				13,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				14,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				15,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				19,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				20,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				21,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				22,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				28,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				29,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				34,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				35,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				40,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				41,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				46,
+			],
+			[
+				'Class Waldo\Quux\Blade is forbidden, do androids dream of electric sheep?',
+				47,
+			],
+		]);
+	}
+
+}
diff --git a/tests/libs/TypesEverywhere.php b/tests/libs/TypesEverywhere.php
new file mode 100644
index 0000000..c561a58
--- /dev/null
+++ b/tests/libs/TypesEverywhere.php
@@ -0,0 +1,50 @@
+<?php
+declare(strict_types = 1);
+
+namespace Types;
+
+use Waldo\Foo\Bar;
+use Waldo\Quux\Blade;
+
+class TypesEverywhere
+{
+
+	public Blade $blade;
+	public ?Blade $maybeBlade;
+	public Blade|null $doYouEvenBlade;
+	public Blade&Bar $uWotBl8;
+
+
+	public function __construct(
+		private readonly Blade $privateBlade,
+		private readonly ?Blade $privateMaybeBlade,
+		private readonly Blade|null $privateDoYouEvenBlade,
+		private readonly Blade&Bar $privateUWotBl8,
+	) {
+	}
+
+
+	public function blade(
+		Blade $blade,
+	): Blade {
+	}
+
+
+	public function maybeBlade(
+		?Blade $blade,
+	): ?Blade {
+	}
+
+
+	public function doYouEvenBlade(
+		Blade|null $blade,
+	): Blade|null {
+	}
+
+
+	public function uWotBl8(
+		Blade&Bar $blade,
+	): Blade&Bar {
+	}
+
+}