Skip to content

Commit

Permalink
Merge pull request #188 from mglaman/config_export-check
Browse files Browse the repository at this point in the history
Detect missing `config_export` annotation key
  • Loading branch information
mglaman authored Jul 16, 2021
2 parents 2105e85 + cc48682 commit 569a1ea
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 31 deletions.
4 changes: 4 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ services:
-
class: PHPStan\Rules\Deprecations\PluginAnnotationContextDefinitionsRule
tags: [phpstan.rules.rule]

-
class: PHPStan\Rules\Deprecations\ConfigEntityConfigExportRule
tags: [phpstan.rules.rule]
34 changes: 34 additions & 0 deletions src/Rules/Deprecations/ConfigEntityConfigExportRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types=1);

namespace PHPStan\Rules\Deprecations;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;

final class ConfigEntityConfigExportRule extends DeprecatedAnnotationsRuleBase
{

protected function getExpectedInterface(): string
{
return 'Drupal\Core\Config\Entity\ConfigEntityInterface';
}

protected function doProcessNode(ClassReflection $reflection, Node\Stmt\Class_ $node, Scope $scope): array
{
$annotation = $reflection->getResolvedPhpDoc();
// Plugins should always be annotated, but maybe this class is missing its
// annotation since it swaps an existing one.
if ($annotation === null) {
return [];
}
$hasMatch = strpos($annotation->getPhpDocString(), 'config_export = {') === false;
if ($hasMatch) {
return [
'Configuration entity must define a `config_export` key. See https://www.drupal.org/node/2481909',
];
}
return [];
}
}
56 changes: 56 additions & 0 deletions src/Rules/Deprecations/DeprecatedAnnotationsRuleBase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php declare(strict_types=1);

namespace PHPStan\Rules\Deprecations;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;

abstract class DeprecatedAnnotationsRuleBase implements Rule
{

/**
* @var \PHPStan\Reflection\ReflectionProvider
*/
protected $reflectionProvider;

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

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

abstract protected function getExpectedInterface(): string;

abstract protected function doProcessNode(
ClassReflection $reflection,
Node\Stmt\Class_ $node,
Scope $scope
): array;

public function processNode(Node $node, Scope $scope): array
{
assert($node instanceof Node\Stmt\Class_);
if ($node->extends === null) {
return [];
}
if ($node->name === null) {
return [];
}
$className = $node->name->name;
$namespace = $scope->getNamespace();
$reflection = $this->reflectionProvider->getClass($namespace . '\\' . $className);
$implementsExpectedInterface = $reflection->implementsInterface($this->getExpectedInterface());
if (!$implementsExpectedInterface) {
return [];
}

return $this->doProcessNode($reflection, $node, $scope);
}
}
36 changes: 5 additions & 31 deletions src/Rules/Deprecations/PluginAnnotationContextDefinitionsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,19 @@

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\ShouldNotHappenException;

final class PluginAnnotationContextDefinitionsRule implements Rule
final class PluginAnnotationContextDefinitionsRule extends DeprecatedAnnotationsRuleBase
{

/**
* @var \PHPStan\Reflection\ReflectionProvider
*/
private $reflectionProvider;

public function __construct(ReflectionProvider $reflectionProvider)
protected function getExpectedInterface(): string
{
$this->reflectionProvider = $reflectionProvider;
return 'Drupal\Component\Plugin\ContextAwarePluginInterface';
}

public function getNodeType(): string
protected function doProcessNode(ClassReflection $reflection, Node\Stmt\Class_ $node, Scope $scope): array
{
return Node\Stmt\Class_::class;
}

public function processNode(Node $node, Scope $scope): array
{
assert($node instanceof Node\Stmt\Class_);
if ($node->extends === null) {
return [];
}
if ($node->name === null) {
return [];
}
$className = $node->name->name;
$namespace = $scope->getNamespace();
$reflection = $this->reflectionProvider->getClass($namespace . '\\' . $className);
$isContextAwarePlugin = $reflection->implementsInterface('Drupal\Component\Plugin\ContextAwarePluginInterface');
// We only process context aware plugins.
if (!$isContextAwarePlugin) {
return [];
}
$annotation = $reflection->getResolvedPhpDoc();
// Plugins should always be annotated, but maybe this class is missing its
// annotation since it swaps an existing one.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types=1);

namespace Drupal\phpstan_fixtures\Entity;

use Drupal\Core\Config\Entity\ConfigEntityBase;

/**
* Defines the ConfigTest configuration entity.
*
* @ConfigEntityType(
* id = "config_test",
* label = @Translation("Test configuration"),
* config_export = {
* "id",
* "label",
* "weight",
* "style",
* "size",
* "size_value",
* "protected_property",
* },
* )
*/
final class ConfigWithExport extends ConfigEntityBase {

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

namespace Drupal\phpstan_fixtures\Entity;

use Drupal\Core\Config\Entity\ConfigEntityBase;

/**
* Defines the ConfigTest configuration entity.
*
* @ConfigEntityType(
* id = "config_test",
* label = @Translation("Test configuration"),
* )
*/
final class ConfigWithoutExport extends ConfigEntityBase {

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

namespace PHPStan\Drupal\Rules;

use PHPStan\Drupal\AnalyzerTestBase;

final class ConfigEntityConfigExportRuleTest extends AnalyzerTestBase {

/**
* @dataProvider pluginData
*/
public function testConfigExportRuleCheck(string $path, int $count, array $errorMessages): void
{
$errors = $this->runAnalyze($path);
self::assertCount($count, $errors->getErrors(), var_export($errors, true));
foreach ($errors->getErrors() as $key => $error) {
self::assertEquals($errorMessages[$key], $error->getMessage());
}
}

public function pluginData(): \Generator
{
yield [
__DIR__ . '/../../fixtures/drupal/modules/phpstan_fixtures/src/Entity/ConfigWithoutExport.php',
1,
[
'Configuration entity must define a `config_export` key. See https://www.drupal.org/node/2481909',
]
];
yield [
__DIR__ . '/../../fixtures/drupal/modules/phpstan_fixtures/src/Entity/ConfigWithExport.php',
0,
[]
];
}


}

0 comments on commit 569a1ea

Please sign in to comment.