NoClassMetadataOnAbstractTestClasses.php

Namespace

Drupal\PHPStan\Rules

File

core/tests/PHPStan/Rules/NoClassMetadataOnAbstractTestClasses.php

View source
<?php

declare (strict_types=1);
namespace Drupal\PHPStan\Rules;

// cspell:ignore analyse testdox
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassNode;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPUnit\Framework\TestCase;

/**
 * Ensures abstract test base classes do not carry metadata.
 *
 * @implements Rule<\PHPStan\Node\InClassNode>
 *
 * @internal
 */
final class NoClassMetadataOnAbstractTestClasses implements Rule {
  
  /**
   * PHPUnit metadata annotations.
   *
   * @var list<string>
   */
  private array $annotationTargets = [
    '@after',
    '@afterClass',
    '@author',
    '@backupGlobals',
    '@backupStaticAttributes',
    '@before',
    '@beforeClass',
    '@covers',
    '@coversDefaultClass',
    '@coversNothing',
    '@dataProvider',
    '@depends',
    '@doesNotPerformAssertions',
    '@group',
    '@large',
    '@medium',
    '@postCondition',
    '@preCondition',
    '@preserveGlobalState',
    '@requires',
    '@runInSeparateProcess',
    '@runTestsInSeparateProcesses',
    '@small',
    '@test',
    '@testdox',
    '@testWith',
    '@ticket',
    '@uses',
  ];
  public function __construct(private ReflectionProvider $reflectionProvider) {
  }
  
  /**
   * {@inheritdoc}
   */
  public function getNodeType() : string {
    return InClassNode::class;
  }
  
  /**
   * {@inheritdoc}
   */
  public function processNode(Node $node, Scope $scope) : array {
    $class = $node->getClassReflection();
    if ($class->isSubclassOfClass($this->reflectionProvider
      ->getClass(TestCase::class)) && $class->isAbstract()) {
      $fails = [];
      foreach ($class->getAttributes() as $attribute) {
        if (str_starts_with($attribute->getName(), 'PHPUnit\\Framework\\Attributes\\')) {
          $fails[] = RuleErrorBuilder::message("Abstract test class {$class->getName()} must not add attribute {$attribute->getName()}.")
            ->identifier('abstractTestClass.metadataForbidden')
            ->line($node->getStartLine())
            ->build();
        }
      }
      $resolvedPhpDoc = $class->getResolvedPhpDoc();
      if ($resolvedPhpDoc) {
        foreach ($resolvedPhpDoc->getPhpDocNodes() as $phpDocNode) {
          foreach ($phpDocNode->getTags() as $tag) {
            if (in_array($tag->name, $this->annotationTargets, TRUE)) {
              $fails[] = RuleErrorBuilder::message("Abstract test class {$class->getName()} must not add annotation {$tag->name}.")
                ->identifier('abstractTestClass.metadataForbidden')
                ->line($node->getStartLine())
                ->build();
            }
          }
        }
      }
      return $fails;
    }
    return [];
  }

}

Classes

Title Deprecated Summary
NoClassMetadataOnAbstractTestClasses Ensures abstract test base classes do not carry metadata.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.