Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Field type deprecated translated category #788

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 0 additions & 1 deletion src/Drupal/DrupalAutoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ public function register(Container $container): void
throw new RuntimeException("Unable to detect Drupal with webflo/drupal-finder.");
}
$this->drupalRoot = $drupalRoot;

$this->autoloader = include $drupalVendorRoot . '/autoload.php';

$this->serviceYamls['core'] = $drupalRoot . '/core/core.services.yml';
Expand Down
103 changes: 103 additions & 0 deletions src/Rules/Deprecations/FieldTypeCategoryRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Rules\Deprecations;

use Drupal;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\Reflection\ClassReflection;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionException;
use function array_key_exists;
use function preg_match;

/**
* Defines a rule for catching translated categories on field types.
*
* @see https://www.drupal.org/node/3375748
*/
final class FieldTypeCategoryRule extends DeprecatedAnnotationsRuleBase
{

private const DEPRECATION_MESSAGE = 'Using a translatable string as a category for field type is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. See https://www.drupal.org/node/3375748';

protected function getExpectedInterface(): string
{
return FieldItemInterface::class;
}

protected function doProcessNode(ClassReflection $reflection, Node\Stmt\Class_ $node, Scope $scope): array
{
if (version_compare(Drupal::VERSION, '10.2.0', '<')) {
return [];
}

$errors = [];

$phpDoc = $reflection->getResolvedPhpDoc();
if ($phpDoc instanceof ResolvedPhpDocBlock) {
if ($this->hasFieldTypeAnnotation($phpDoc) && preg_match('/category\s?=\s?@Translation/', $phpDoc->getPhpDocString()) === 1) {
$errors[] = self::DEPRECATION_MESSAGE;
}
}

$fieldTypeAttributes = $this->getFieldTypeAttributes($reflection);
if ($fieldTypeAttributes instanceof ReflectionAttribute) {
$arguments = $fieldTypeAttributes->getArguments();
if (array_key_exists('category', $arguments) && $arguments['category'] instanceof TranslatableMarkup) {
$errors[] = self::DEPRECATION_MESSAGE;
}
}

return $errors;

Check failure on line 58 in src/Rules/Deprecations/FieldTypeCategoryRule.php

View workflow job for this annotation

GitHub Actions / Linting | PHP 8.3

Method mglaman\PHPStanDrupal\Rules\Deprecations\FieldTypeCategoryRule::doProcessNode() should return list<PHPStan\Rules\IdentifierRuleError> but returns array{}|array{0: 'Using a…', 1?: 'Using a…'}.

Check failure on line 58 in src/Rules/Deprecations/FieldTypeCategoryRule.php

View workflow job for this annotation

GitHub Actions / Linting | PHP 8.1

Method mglaman\PHPStanDrupal\Rules\Deprecations\FieldTypeCategoryRule::doProcessNode() should return list<PHPStan\Rules\IdentifierRuleError> but returns array{}|array{0: 'Using a…', 1?: 'Using a…'}.

Check failure on line 58 in src/Rules/Deprecations/FieldTypeCategoryRule.php

View workflow job for this annotation

GitHub Actions / Linting | PHP 8.2

Method mglaman\PHPStanDrupal\Rules\Deprecations\FieldTypeCategoryRule::doProcessNode() should return list<PHPStan\Rules\IdentifierRuleError> but returns array{}|array{0: 'Using a…', 1?: 'Using a…'}.
}

/**
* Checks whether a PHP doc block contains a field type annotation.
*
* @param \PHPStan\PhpDoc\ResolvedPhpDocBlock $phpDoc
* The PHP doc block object.
*
* @return bool
* True if it does, otherwise false.
*/
private function hasFieldTypeAnnotation(ResolvedPhpDocBlock $phpDoc): bool
{
foreach ($phpDoc->getPhpDocNodes() as $docNode) {
foreach ($docNode->children as $childNode) {
if (($childNode instanceof PhpDocTagNode) && $childNode->name === '@FieldType') {
return true;
}
}
}

return false;
}

/**
* Checks whether a given class has a field type attribute.
*
* @param \PHPStan\Reflection\ClassReflection $reflection
* The class reflection object.
*
* @return ReflectionAttribute|null
* The attribute, or null.
*/
private function getFieldTypeAttributes(ClassReflection $reflection): ?ReflectionAttribute
{
try {
$nativeReflection = new ReflectionClass($reflection->getName());

Check failure on line 95 in src/Rules/Deprecations/FieldTypeCategoryRule.php

View workflow job for this annotation

GitHub Actions / Linting | PHP 8.3

Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.

Check failure on line 95 in src/Rules/Deprecations/FieldTypeCategoryRule.php

View workflow job for this annotation

GitHub Actions / Linting | PHP 8.1

Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.

Check failure on line 95 in src/Rules/Deprecations/FieldTypeCategoryRule.php

View workflow job for this annotation

GitHub Actions / Linting | PHP 8.2

Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.
$attribute = $nativeReflection->getAttributes(FieldType::class);
} catch (ReflectionException $e) {
return null;
}

return $attribute[0] ?? null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Drupal\phpstan_fixtures\Plugin\Field\FieldType;

use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\Plugin\Field\FieldType\DecimalItem;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
* Represents a field type with a translated category argument.
*/
#[FieldType(
id: "field_type_with_valid_attribute",
label: new TranslatableMarkup("Field Type With Valid Attribute"),
description: [
new TranslatableMarkup("This attribute is valid because the category is a string."),
],
category: "number",
weight: -10,
default_widget: "number",
default_formatter: "number_decimal"
)]
class FieldTypeWithStringCategoryAttribute extends DecimalItem
{

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

namespace Drupal\phpstan_fixtures\Plugin\Field\FieldType;

use Drupal\Core\Field\Plugin\Field\FieldType\DecimalItem;

/**
* An invalid field type using annotations.
*
* @FieldType(
* id = "field_type_with_invalid_annotation",
* label = @Translation("Field Type With Invalid Annotation"),
* description = @Translation("This field type annotation is invalid because the category is not a string."),
* category = @Translation("Number"),
* default_widget = "number",
* default_formatter = "number_decimal"
* )
*/
class FieldTypeWithTranslatedCategoryAnnotation extends DecimalItem
{

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

namespace Drupal\phpstan_fixtures\Plugin\Field\FieldType;

use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\Plugin\Field\FieldType\DecimalItem;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
* Represents a field type with a translated category argument.
*/
#[FieldType(
id: "field_type_with_invalid_attribute",
label: new TranslatableMarkup("Field Type With Invalid Attribute"),
description: [
new TranslatableMarkup("This attribute is invalid because the category is not a string."),
],
category: new TranslatableMarkup("Number"),
weight: -10,
default_widget: "number",
default_formatter: "number_decimal"
)]
class FieldTypeWithTranslatedCategoryAttribute extends DecimalItem
{

}
70 changes: 70 additions & 0 deletions tests/src/Rules/FieldTypeCategoryRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace mglaman\PHPStanDrupal\Tests\Rules;

use Drupal;
use Generator;
use mglaman\PHPStanDrupal\Rules\Deprecations\FieldTypeCategoryRule;
use mglaman\PHPStanDrupal\Tests\DrupalRuleTestCase;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;

/**
* Test the rule to detect translated field type categories.
*/
class FieldTypeCategoryRuleTest extends DrupalRuleTestCase
{

/**
* {@inheritdoc}
*/
protected function getRule(): Rule
{
return new FieldTypeCategoryRule(
self::getContainer()->getByType(ReflectionProvider::class)
);
}

/**
* @dataProvider ruleData
*/
public function testRule(string $path, array $errorMessages): void
{
$this->analyse([$path], $errorMessages);

Check failure on line 33 in tests/src/Rules/FieldTypeCategoryRuleTest.php

View workflow job for this annotation

GitHub Actions / Linting | PHP 8.3

Parameter #2 $expectedErrors of method PHPStan\Testing\RuleTestCase<PHPStan\Rules\Rule>::analyse() expects list<array{0: string, 1: int, 2?: string|null}>, array given.

Check failure on line 33 in tests/src/Rules/FieldTypeCategoryRuleTest.php

View workflow job for this annotation

GitHub Actions / Linting | PHP 8.1

Parameter #2 $expectedErrors of method PHPStan\Testing\RuleTestCase<PHPStan\Rules\Rule>::analyse() expects list<array{0: string, 1: int, 2?: string|null}>, array given.

Check failure on line 33 in tests/src/Rules/FieldTypeCategoryRuleTest.php

View workflow job for this annotation

GitHub Actions / Linting | PHP 8.2

Parameter #2 $expectedErrors of method PHPStan\Testing\RuleTestCase<PHPStan\Rules\Rule>::analyse() expects list<array{0: string, 1: int, 2?: string|null}>, array given.
}

public static function ruleData(): Generator
{
if (version_compare(Drupal::VERSION, '10.2.0', '>=')) {
yield [
__DIR__ . '/../../fixtures/drupal/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/FloatItem.php',
[],
];

yield [
__DIR__ . '/../../fixtures/drupal/modules/phpstan_fixtures/src/Plugin/Field/FieldType/FieldTypeWithTranslatedCategoryAnnotation.php',
[
[
'Using a translatable string as a category for field type is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. See https://www.drupal.org/node/3375748',
19,
],
]
];

yield [
__DIR__ . '/../../fixtures/drupal/modules/phpstan_fixtures/src/Plugin/Field/FieldType/FieldTypeWithStringCategoryAttribute.php',
[]
];

yield [
__DIR__ . '/../../fixtures/drupal/modules/phpstan_fixtures/src/Plugin/Field/FieldType/FieldTypeWithTranslatedCategoryAttribute.php',
[
[
'Using a translatable string as a category for field type is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. See https://www.drupal.org/node/3375748',
12,
],
]
];
}
}
}
Loading