Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .ddev/commands/web/phpstan
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@
## Usage: phpstan
## Example: "ddev phpstan"

PHP_MEMORY_LIMIT=2G ./vendor/bin/phpstan --no-progress analyse -c phpstan.neon
if [ $# -eq 0 ]; then
# No arguments provided, run default analyse command
PHP_MEMORY_LIMIT=2G ./vendor/bin/phpstan --no-progress analyse -c phpstan.neon
else
# Arguments provided, pass them through with configuration
PHP_MEMORY_LIMIT=2G ./vendor/bin/phpstan "$@" -c phpstan.neon
fi
175 changes: 175 additions & 0 deletions phpstan-rules/NoRedundantTraitUseRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<?php

declare(strict_types=1);

namespace Drupal\PHPStan\Custom;

use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\TraitUse;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function array_filter;
use function array_merge;
use function array_unique;
use function basename;
use function count;
use function in_array;
use function sprintf;
use function str_replace;

/**
* Disallows redundant trait usage when a trait is already included via another.
*
* This rule checks classes that use multiple traits and ensures that they don't
* use a trait that is already being used by another trait. For example, if
* trait A uses trait B, then a class shouldn't use both A and B.
*
* @implements Rule<Class_>
*/
class NoRedundantTraitUseRule implements Rule {

private const ERROR_MESSAGE = 'Class uses trait "%s" redundantly as it is already included via trait "%s".';

/**
* The reflection provider.
*
* @var \PHPStan\Reflection\ReflectionProvider
*/
private ReflectionProvider $reflectionProvider;

/**
* Constructs a new NoRedundantTraitUseRule.
*
* @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider
* The reflection provider.
*/
public function __construct(ReflectionProvider $reflectionProvider) {
$this->reflectionProvider = $reflectionProvider;
}

/**
* {@inheritdoc}
*/
public function getNodeType(): string {
return Class_::class;
}

/**
* {@inheritdoc}
*/
public function processNode(Node $node, Scope $scope): array {
$errors = [];

// Get all trait use statements from the class.
$traitUseNodes = array_filter($node->stmts, static fn ($stmt): bool => $stmt instanceof TraitUse);

if (count($traitUseNodes) < 2) {
// Need at least 2 traits to have redundancy.
return [];
}

// Collect all directly used trait names with their resolved names.
$directlyUsedTraits = [];
foreach ($traitUseNodes as $traitUseNode) {
foreach ($traitUseNode->traits as $trait) {
$traitName = $scope->resolveName($trait);
$directlyUsedTraits[] = $traitName;
}
}

// Build a map of trait -> [traits it uses] with full resolution.
$traitDependencies = [];
foreach ($directlyUsedTraits as $traitName) {
try {
if ($this->reflectionProvider->hasClass($traitName)) {
$traitReflection = $this->reflectionProvider->getClass($traitName);
if ($traitReflection->isTrait()) {
$traitDependencies[$traitName] = $this->getAllTraitsUsedByTrait($traitName, []);
}
}
}
catch (\Throwable $e) {
// Skip traits that can't be reflected.
continue;
}
}

// Check for redundancies.
foreach ($directlyUsedTraits as $traitA) {
foreach ($directlyUsedTraits as $traitB) {
if ($traitA === $traitB) {
continue;
}

// Check if traitA uses traitB (directly or transitively).
if (isset($traitDependencies[$traitA]) && in_array($traitB, $traitDependencies[$traitA], TRUE)) {
$shortNameA = basename(str_replace('\\', '/', $traitA));
$shortNameB = basename(str_replace('\\', '/', $traitB));

$errors[] = RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $shortNameB, $shortNameA))
->line($node->getStartLine())
->identifier('traits.redundantTraitUse')
->build();

// Only report each redundant trait once.
break;
}
}
}

return $errors;
}

/**
* Get all traits used by a given trait recursively.
*
* @param string $traitName
* The fully qualified trait name.
* @param array<string> $visited
* Array to track visited traits (for cycle detection).
*
* @return array<string>
* Array of all trait names used by the given trait (directly and
* transitively).
*/
private function getAllTraitsUsedByTrait(string $traitName, array $visited = []): array {
// Prevent infinite loops.
if (in_array($traitName, $visited, TRUE)) {
return [];
}

$visited[] = $traitName;

try {
if (!$this->reflectionProvider->hasClass($traitName)) {
return [];
}

$traitReflection = $this->reflectionProvider->getClass($traitName);
if (!$traitReflection->isTrait()) {
return [];
}

$allTraits = [];

// Get direct traits used by this trait.
foreach ($traitReflection->getTraits() as $trait) {
$usedTraitName = $trait->getName();
$allTraits[] = $usedTraitName;

// Recursively get traits used by the used trait.
$nestedTraits = $this->getAllTraitsUsedByTrait($usedTraitName, $visited);
$allTraits = array_merge($allTraits, $nestedTraits);
}

return array_unique($allTraits);
}
catch (\Throwable $e) {
return [];
}
}

}
4 changes: 4 additions & 0 deletions phpstan-rules/phpstan-extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ services:
class: Drupal\PHPStan\Custom\CacheableDependency
tags:
- phpstan.rules.rule
-
class: Drupal\PHPStan\Custom\NoRedundantTraitUseRule
tags:
- phpstan.rules.rule
1 change: 1 addition & 0 deletions robo-components/DeploymentTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ trait DeploymentTrait {
'ci-scripts',
'pantheon.upstream.yml',
'phpstan.neon',
'phpstan-rules',
'phpunit.xml.dist',
'README.md',
'RoboFile.php',
Expand Down
3 changes: 0 additions & 3 deletions robo-components/TranslationManagement/ImportToUi.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace RoboComponents\TranslationManagement;

use Robo\ResultData;
use RoboComponents\DeploymentTrait;

/**
* Logic to import translations into Drupal UI translations.
Expand All @@ -12,8 +11,6 @@
*/
trait ImportToUi {

use DeploymentTrait;

/**
* Import the interface translations from a PO file.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Drupal\media\MediaInterface;
use Drupal\pluggable_entity_view_builder\EntityViewBuilderPluginAbstract;
use Drupal\server_general\ThemeTrait\ElementMediaThemeTrait;
use Drupal\server_general\ThemeTrait\ElementWrapThemeTrait;

/**
* The "Media: Image" plugin.
Expand All @@ -21,7 +20,6 @@
class MediaImage extends EntityViewBuilderPluginAbstract {

use ElementMediaThemeTrait;
use ElementWrapThemeTrait;

/**
* The iFrame URL helper service.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Drupal\media\MediaInterface;
use Drupal\pluggable_entity_view_builder\EntityViewBuilderPluginAbstract;
use Drupal\server_general\ThemeTrait\ElementMediaThemeTrait;
use Drupal\server_general\ThemeTrait\ElementWrapThemeTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
Expand All @@ -22,7 +21,6 @@
class MediaVideo extends EntityViewBuilderPluginAbstract {

use ElementMediaThemeTrait;
use ElementWrapThemeTrait;

// Update from design as needed.
const VIDEO_FULL_MAX_WIDTH = 1920;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,12 @@

use Drupal\media\MediaInterface;
use Drupal\node\NodeInterface;
use Drupal\server_general\EntityDateTrait;
use Drupal\server_general\EntityViewBuilder\NodeViewBuilderAbstract;
use Drupal\server_general\SocialShareTrait;
use Drupal\server_general\TagTrait;
use Drupal\server_general\ThemeTrait\ElementLayoutThemeTrait;
use Drupal\server_general\ThemeTrait\ElementNodeNewsThemeTrait;
use Drupal\server_general\ThemeTrait\LineSeparatorThemeTrait;
use Drupal\server_general\ThemeTrait\LinkThemeTrait;
use Drupal\server_general\ThemeTrait\NewsTeasersThemeTrait;
use Drupal\server_general\ThemeTrait\SearchThemeTrait;
use Drupal\server_general\ThemeTrait\TitleAndLabelsThemeTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
Expand All @@ -28,16 +23,11 @@
*/
class NodeNews extends NodeViewBuilderAbstract {

use ElementLayoutThemeTrait;
use ElementNodeNewsThemeTrait;
use EntityDateTrait;
use LineSeparatorThemeTrait;
use LinkThemeTrait;
use NewsTeasersThemeTrait;
use SearchThemeTrait;
use SocialShareTrait;
use TagTrait;
use TitleAndLabelsThemeTrait;

/**
* The renderer.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Drupal\server_general\ProcessedTextBuilderTrait;
use Drupal\server_general\ThemeTrait\AccordionThemeTrait;
use Drupal\server_general\ThemeTrait\ElementLayoutThemeTrait;
use Drupal\server_general\ThemeTrait\ElementWrapThemeTrait;

/**
* The "Accordion" paragraph plugin.
Expand All @@ -26,7 +25,6 @@ class ParagraphAccordion extends EntityViewBuilderPluginAbstract {

use AccordionThemeTrait;
use ElementLayoutThemeTrait;
use ElementWrapThemeTrait;
use ProcessedTextBuilderTrait;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
use Drupal\paragraphs\ParagraphInterface;
use Drupal\pluggable_entity_view_builder\EntityViewBuilderPluginAbstract;
use Drupal\server_general\ProcessedTextBuilderTrait;
use Drupal\server_general\ThemeTrait\ButtonThemeTrait;
use Drupal\server_general\ThemeTrait\CtaThemeTrait;
use Drupal\server_general\ThemeTrait\ElementWrapThemeTrait;

/**
* The "Call to Action" paragraph plugin.
Expand All @@ -21,9 +19,7 @@
*/
class ParagraphCta extends EntityViewBuilderPluginAbstract {

use ButtonThemeTrait;
use CtaThemeTrait;
use ElementWrapThemeTrait;
use ProcessedTextBuilderTrait;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
use Drupal\paragraphs\ParagraphInterface;
use Drupal\pluggable_entity_view_builder\EntityViewBuilderPluginAbstract;
use Drupal\server_general\ProcessedTextBuilderTrait;
use Drupal\server_general\ThemeTrait\ButtonThemeTrait;
use Drupal\server_general\ThemeTrait\DocumentsThemeTrait;
use Drupal\server_general\ThemeTrait\ElementWrapThemeTrait;

/**
* The "Documents" paragraph plugin.
Expand All @@ -20,9 +18,7 @@
*/
class ParagraphDocuments extends EntityViewBuilderPluginAbstract {

use ButtonThemeTrait;
use DocumentsThemeTrait;
use ElementWrapThemeTrait;
use ProcessedTextBuilderTrait;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use Drupal\paragraphs\ParagraphInterface;
use Drupal\pluggable_entity_view_builder\EntityViewBuilderPluginAbstract;
use Drupal\server_general\ProcessedTextBuilderTrait;
use Drupal\server_general\WebformTrait;

/**
Expand All @@ -18,7 +17,6 @@
*/
class ParagraphForm extends EntityViewBuilderPluginAbstract {

use ProcessedTextBuilderTrait;
use WebformTrait;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

use Drupal\Core\Link;
use Drupal\paragraphs\ParagraphInterface;
use Drupal\pluggable_entity_view_builder\BuildFieldTrait;
use Drupal\pluggable_entity_view_builder\EntityViewBuilderPluginAbstract;
use Drupal\server_general\ThemeTrait\ButtonThemeTrait;
use Drupal\server_general\ThemeTrait\HeroThemeTrait;

/**
Expand All @@ -20,8 +18,6 @@
*/
class ParagraphHeroImage extends EntityViewBuilderPluginAbstract {

use BuildFieldTrait;
use ButtonThemeTrait;
use HeroThemeTrait;

const RESPONSIVE_IMAGE_STYLE_ID = 'hero';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Drupal\paragraphs\ParagraphInterface;
use Drupal\pluggable_entity_view_builder\EntityViewBuilderPluginAbstract;
use Drupal\server_general\ProcessedTextBuilderTrait;
use Drupal\server_general\ThemeTrait\ElementWrapThemeTrait;
use Drupal\server_general\ThemeTrait\InfoCardThemeTrait;

/**
Expand All @@ -24,7 +23,6 @@
class ParagraphInfoCard extends EntityViewBuilderPluginAbstract {


use ElementWrapThemeTrait;
use InfoCardThemeTrait;
use ProcessedTextBuilderTrait;

Expand Down
Loading