Skip to content

Commit

Permalink
Detect when a class extends a class that has been flagged as @internal
Browse files Browse the repository at this point in the history
  • Loading branch information
brambaud committed Nov 30, 2021
1 parent 337bc36 commit 0df5794
Show file tree
Hide file tree
Showing 18 changed files with 480 additions and 0 deletions.
5 changes: 5 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ services:
-
class: mglaman\PHPStanDrupal\Reflection\EntityFieldsViaMagicReflectionExtension
tags: [phpstan.broker.propertiesClassReflectionExtension]
-
class: mglaman\PHPStanDrupal\Rules\Classes\ClassExtendsInternalClassRule
tags: [phpstan.rules.rule]
arguments:
reflectionProvider: @reflectionProvider
-
class: mglaman\PHPStanDrupal\Rules\Drupal\LoadIncludes
tags: [phpstan.rules.rule]
Expand Down
47 changes: 47 additions & 0 deletions src/Internal/ClassHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Internal;

use PhpParser\Node\Stmt\Class_;

/**
* @internal
*/
final class ClassHelper
{
public static function isInDrupalNamespace(Class_ $class): bool
{
if (!isset($class->namespacedName)) {
return false;
}

return 'Drupal' === (string) $class->namespacedName->slice(0, 1);
}

public static function isSharedNamespace(Class_ $class): bool
{
if (!isset($class->extends)) {
return false;
}

if (!isset($class->namespacedName)) {
return false;
}

if (!self::isInDrupalNamespace($class)) {
return false;
}

$classNamespaceBase = (string) $class->namespacedName->slice(0, 2);
$extendedClassNamespaceBase = (string) $class->extends->slice(0, 2);

if ('Drupal\Core' === $classNamespaceBase && 'Drupal\Component' === $extendedClassNamespaceBase) {
return true;
}
if ('Drupal\Component' === $classNamespaceBase && 'Drupal\Core' === $extendedClassNamespaceBase) {
return true;
}

return $classNamespaceBase === $extendedClassNamespaceBase;
}
}
74 changes: 74 additions & 0 deletions src/Rules/Classes/ClassExtendsInternalClassRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Rules\Classes;

use mglaman\PHPStanDrupal\Internal\ClassHelper;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;

class ClassExtendsInternalClassRule implements Rule
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;

public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}

public function getNodeType(): string
{
return Class_::class;
}

public function processNode(Node $node, Scope $scope): array
{
/** @var Class_ $node */
if (!isset($node->extends)) {
return [];
}

$extendedClassName = $node->extends->toString();
if (!$this->reflectionProvider->hasClass($extendedClassName)) {
return [];
}

$extendedClassReflection = $this->reflectionProvider->getClass($extendedClassName);
if (!$extendedClassReflection->isInternal()) {
return [];
}

if (!isset($node->namespacedName)) {
return $this->buildError(null, $extendedClassName);
}

$currentClassName = $node->namespacedName->toString();

if (!ClassHelper::isInDrupalNamespace($node)) {
return $this->buildError($currentClassName, $extendedClassName);
}

if (ClassHelper::isSharedNamespace($node)) {
return [];
}

return $this->buildError($currentClassName, $extendedClassName);
}

private function buildError(?string $currentClassName, string $extendedClassName): array
{
return [
RuleErrorBuilder::message(\sprintf(
'%s extends @internal class %s.',
$currentClassName !== null ? \sprintf('Class %s', $currentClassName) : 'Anonymous class',
$extendedClassName
))->build()
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: module_with_internal_classes
type: module
core: 8.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php declare(strict_types=1);

namespace Drupal\module_with_internal_classes\Foo;

class ExternalClass {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types=1);

namespace Drupal\module_with_internal_classes\Foo;

/**
* @internal
*/
class InternalClass {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php declare(strict_types=1);

namespace Drupal\phpstan_fixtures\Internal;

final class DoesNotExtendsAnyClass {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);

namespace Drupal\phpstan_fixtures\Internal;

use Drupal\Core\PHPStanDrupalTests\ExternalClass;

final class ExtendsDrupalCoreExternalClass extends ExternalClass {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);

namespace Drupal\phpstan_fixtures\Internal;

use Drupal\Core\InternalClass;

final class ExtendsDrupalCoreInternalClass extends InternalClass {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);

namespace Drupal\phpstan_fixtures\Internal;

use Drupal\Core\PHPStanDrupalTests\InternalClass;

final class ExtendsDrupalCorePHPStanDrupalTestsInternalClass extends InternalClass {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php declare(strict_types=1);

namespace Drupal\phpstan_fixtures\Internal;

final class ExtendsInternalClass extends InternalClass {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);

namespace Drupal\phpstan_fixtures\Internal;

use Drupal\module_with_internal_classes\Foo\ExternalClass;

final class ExtendsPHPStanDrupalModuleWithInternalClassesExternalClass extends ExternalClass {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);

namespace Drupal\phpstan_fixtures\Internal;

use Drupal\module_with_internal_classes\Foo\InternalClass;

final class ExtendsPHPStanDrupalModuleWithInternalClassesInternalClass extends InternalClass {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);

namespace Drupal\phpstan_fixtures\Internal;

use Drupal\phpstan_fixtures\InternalClass;

final class ExtendsRootInternalClass extends InternalClass {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types=1);

namespace Drupal\phpstan_fixtures\Internal;

/**
* @internal
*/
class InternalClass {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace fixtures\drupal\modules\phpstan_fixtures\src\Internal;

use Drupal\module_with_internal_classes\Foo\InternalClass;

final class WithAnAnonymousClassExtendingAnInternalClass
{
public function foo(): void
{
$foo = new class() extends InternalClass {};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types=1);

namespace Drupal\phpstan_fixtures;

/**
* @internal
*/
class InternalClass {

}
Loading

0 comments on commit 0df5794

Please sign in to comment.