ai_upgrade_assistant-0.2.0-alpha2/src/Service/NodeVisitor/DrupalUpgradePatternVisitor.php
src/Service/NodeVisitor/DrupalUpgradePatternVisitor.php
<?php
namespace Drupal\ai_upgrade_assistant\Service\NodeVisitor;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\TraitUse;
use PhpParser\Node\Name;
/**
* Advanced visitor for detecting complex Drupal upgrade patterns.
*/
class DrupalUpgradePatternVisitor extends NodeVisitorAbstract {
/**
* Collected findings.
*
* @var array
*/
protected $findings = [];
/**
* Current class context.
*
* @var array
*/
protected $classContext = [];
/**
* Current namespace.
*
* @var string
*/
protected $currentNamespace = '';
/**
* Use statements map.
*
* @var array
*/
protected $useStatements = [];
/**
* Known Drupal version-specific patterns.
*
* @var array
*/
protected $versionPatterns = [
'8.x' => [
'deprecated_services' => [
'entity.manager' => ['replacement' => 'entity_type.manager', 'version' => '8.0.0'],
'string_translation' => ['replacement' => 'string_translation', 'version' => '8.0.0'],
],
'deprecated_methods' => [
'entityManager' => ['replacement' => 'entityTypeManager', 'version' => '8.0.0'],
'entity_get_form_display' => ['replacement' => 'EntityDisplayRepository::getFormDisplay', 'version' => '8.0.0'],
],
],
'9.x' => [
'deprecated_services' => [
'path.alias_manager' => ['replacement' => 'path_alias.manager', 'version' => '9.0.0'],
'file.usage' => ['replacement' => 'file.usage', 'version' => '9.0.0'],
],
'deprecated_methods' => [
'url' => ['replacement' => 'toUrl', 'version' => '9.0.0'],
'urlInfo' => ['replacement' => 'toUrl', 'version' => '9.0.0'],
],
],
'10.x' => [
'deprecated_services' => [
'twig' => ['replacement' => 'twig.factory', 'version' => '10.0.0'],
],
'deprecated_methods' => [
'drupal_set_message' => ['replacement' => 'messenger()->addMessage', 'version' => '10.0.0'],
],
],
];
/**
* {@inheritdoc}
*/
public function beforeTraverse(array $nodes) {
$this->findings = [
'version_specific_patterns' => [],
'database_patterns' => [],
'config_patterns' => [],
'entity_patterns' => [],
'form_patterns' => [],
'routing_patterns' => [],
'service_patterns' => [],
'theme_patterns' => [],
'cache_patterns' => [],
'access_patterns' => [],
'batch_patterns' => [],
'migration_patterns' => [],
];
return null;
}
/**
* {@inheritdoc}
*/
public function enterNode(Node $node) {
if ($node instanceof Node\Stmt\Namespace_) {
$this->currentNamespace = $node->name->toString();
}
elseif ($node instanceof Use_ || $node instanceof GroupUse) {
$this->processUseStatements($node);
}
elseif ($node instanceof Class_) {
$this->enterClass($node);
}
elseif ($node instanceof ClassMethod) {
$this->analyzeMethod($node);
}
elseif ($node instanceof Property) {
$this->analyzeProperty($node);
}
elseif ($node instanceof FuncCall || $node instanceof StaticCall || $node instanceof MethodCall) {
$this->analyzeCall($node);
}
}
/**
* Process use statements to track imported classes and namespaces.
*/
protected function processUseStatements(Node $node) {
if ($node instanceof Use_) {
foreach ($node->uses as $use) {
$this->useStatements[$use->getAlias()->name] = $use->name->toString();
}
}
elseif ($node instanceof GroupUse) {
$prefix = $node->prefix->toString();
foreach ($node->uses as $use) {
$this->useStatements[$use->getAlias()->name] = $prefix . '\\' . $use->name->toString();
}
}
}
/**
* Analyze class definition for upgrade patterns.
*/
protected function enterClass(Class_ $node) {
$this->classContext = [
'name' => $node->name->toString(),
'namespace' => $this->currentNamespace,
'implements' => [],
'extends' => null,
'traits' => [],
];
// Analyze class inheritance
if ($node->extends) {
$this->classContext['extends'] = $this->resolveClassName($node->extends);
$this->analyzeClassInheritance($this->classContext['extends']);
}
// Analyze interfaces
foreach ($node->implements as $interface) {
$interfaceName = $this->resolveClassName($interface);
$this->classContext['implements'][] = $interfaceName;
$this->analyzeInterface($interfaceName);
}
// Analyze traits
if (isset($node->stmts)) {
foreach ($node->stmts as $stmt) {
if ($stmt instanceof TraitUse) {
foreach ($stmt->traits as $trait) {
$traitName = $this->resolveClassName($trait);
$this->classContext['traits'][] = $traitName;
$this->analyzeTraitUsage($traitName);
}
}
}
}
}
/**
* Analyze method for upgrade patterns.
*/
protected function analyzeMethod(ClassMethod $node) {
$methodName = $node->name->toString();
// Analyze method signature
$this->analyzeMethodSignature($node);
// Analyze method body for patterns
if ($node->stmts) {
foreach ($node->stmts as $stmt) {
$this->analyzeStatement($stmt);
}
}
// Check for known deprecated methods
foreach ($this->versionPatterns as $version => $patterns) {
if (isset($patterns['deprecated_methods'][$methodName])) {
$this->findings['version_specific_patterns'][] = [
'type' => 'deprecated_method',
'name' => $methodName,
'version' => $version,
'replacement' => $patterns['deprecated_methods'][$methodName]['replacement'],
'line' => $node->getLine(),
];
}
}
}
/**
* Analyze method signature for upgrade patterns.
*/
protected function analyzeMethodSignature(ClassMethod $node) {
foreach ($node->params as $param) {
if ($param->type instanceof Name) {
$typeName = $this->resolveClassName($param->type);
$this->analyzeTypeHint($typeName, $node->getLine());
}
}
if ($node->returnType instanceof Name) {
$returnType = $this->resolveClassName($node->returnType);
$this->analyzeTypeHint($returnType, $node->getLine());
}
}
/**
* Analyze property for upgrade patterns.
*/
protected function analyzeProperty(Property $node) {
foreach ($node->props as $prop) {
if ($node->type instanceof Name) {
$typeName = $this->resolveClassName($node->type);
$this->analyzeTypeHint($typeName, $node->getLine());
}
}
}
/**
* Analyze function/method calls for upgrade patterns.
*/
protected function analyzeCall(Node $node) {
if ($node instanceof StaticCall) {
$this->analyzeStaticCall($node);
}
elseif ($node instanceof MethodCall) {
$this->analyzeMethodCall($node);
}
elseif ($node instanceof FuncCall) {
$this->analyzeFunctionCall($node);
}
}
/**
* Analyze static call patterns.
*/
protected function analyzeStaticCall(StaticCall $node) {
if ($node->class instanceof Name) {
$className = $this->resolveClassName($node->class);
// Analyze Drupal static service calls
if ($className === 'Drupal') {
$methodName = $node->name->toString();
if ($methodName === 'service') {
$this->analyzeServiceCall($node);
}
elseif (in_array($methodName, ['config', 'state', 'database'])) {
$this->findings[strtolower($methodName) . '_patterns'][] = [
'type' => 'static_' . strtolower($methodName) . '_call',
'line' => $node->getLine(),
'method' => $methodName,
];
}
}
}
}
/**
* Analyze method call patterns.
*/
protected function analyzeMethodCall(MethodCall $node) {
$methodName = $node->name->toString();
// Analyze database query patterns
if ($methodName === 'query' || $methodName === 'select' || $methodName === 'insert' || $methodName === 'update' || $methodName === 'delete') {
$this->findings['database_patterns'][] = [
'type' => 'query',
'method' => $methodName,
'line' => $node->getLine(),
];
}
// Analyze entity API patterns
elseif (in_array($methodName, ['load', 'loadMultiple', 'create', 'save', 'delete'])) {
$this->findings['entity_patterns'][] = [
'type' => 'entity_operation',
'method' => $methodName,
'line' => $node->getLine(),
];
}
}
/**
* Analyze function call patterns.
*/
protected function analyzeFunctionCall(FuncCall $node) {
if ($node->name instanceof Name) {
$functionName = $node->name->toString();
// Analyze theme function calls
if (strpos($functionName, 'theme_') === 0 || $functionName === 'theme') {
$this->findings['theme_patterns'][] = [
'type' => 'theme_function',
'name' => $functionName,
'line' => $node->getLine(),
];
}
// Analyze batch API calls
elseif (strpos($functionName, 'batch_') === 0) {
$this->findings['batch_patterns'][] = [
'type' => 'batch_operation',
'function' => $functionName,
'line' => $node->getLine(),
];
}
}
}
/**
* Analyze service usage patterns.
*/
protected function analyzeServiceCall(StaticCall $node) {
if (isset($node->args[0]) && $node->args[0]->value instanceof Node\Scalar\String_) {
$serviceName = $node->args[0]->value->value;
// Check for deprecated services
foreach ($this->versionPatterns as $version => $patterns) {
if (isset($patterns['deprecated_services'][$serviceName])) {
$this->findings['version_specific_patterns'][] = [
'type' => 'deprecated_service',
'name' => $serviceName,
'version' => $version,
'replacement' => $patterns['deprecated_services'][$serviceName]['replacement'],
'line' => $node->getLine(),
];
}
}
$this->findings['service_patterns'][] = [
'type' => 'service_usage',
'service' => $serviceName,
'line' => $node->getLine(),
];
}
}
/**
* Analyze class inheritance patterns.
*/
protected function analyzeClassInheritance($className) {
$baseClasses = [
'Drupal\Core\Form\FormBase' => 'form',
'Drupal\Core\Config\Entity\ConfigEntityBase' => 'config_entity',
'Drupal\Core\Entity\ContentEntityBase' => 'content_entity',
'Drupal\Core\Plugin\PluginBase' => 'plugin',
];
foreach ($baseClasses as $baseClass => $type) {
if ($className === $baseClass || is_subclass_of($className, $baseClass)) {
$this->findings[$type . '_patterns'][] = [
'type' => $type . '_class',
'class' => $this->classContext['name'],
'base_class' => $className,
];
}
}
}
/**
* Analyze interface implementation patterns.
*/
protected function analyzeInterface($interfaceName) {
$interfaces = [
'Drupal\Core\Access\AccessibleInterface' => 'access',
'Drupal\Core\Cache\CacheableDependencyInterface' => 'cache',
'Drupal\Core\Config\Entity\ConfigEntityInterface' => 'config',
'Drupal\Core\Entity\EntityInterface' => 'entity',
];
foreach ($interfaces as $interface => $type) {
if ($interfaceName === $interface) {
$this->findings[$type . '_patterns'][] = [
'type' => $type . '_interface',
'class' => $this->classContext['name'],
'interface' => $interfaceName,
];
}
}
}
/**
* Analyze trait usage patterns.
*/
protected function analyzeTraitUsage($traitName) {
$traits = [
'Drupal\Core\StringTranslation\StringTranslationTrait' => 'translation',
'Drupal\Core\Messenger\MessengerTrait' => 'messenger',
'Drupal\Core\Entity\EntityTypeManagerTrait' => 'entity_type_manager',
];
foreach ($traits as $trait => $type) {
if ($traitName === $trait) {
$this->findings['service_patterns'][] = [
'type' => $type . '_trait',
'class' => $this->classContext['name'],
'trait' => $traitName,
];
}
}
}
/**
* Analyze type hints for upgrade patterns.
*/
protected function analyzeTypeHint($typeName, $line) {
$deprecatedTypes = [
'Drupal\Core\Entity\EntityManager' => [
'replacement' => 'Drupal\Core\Entity\EntityTypeManagerInterface',
'version' => '8.0.0',
],
'Drupal\Core\Path\AliasManager' => [
'replacement' => 'Drupal\path_alias\AliasManagerInterface',
'version' => '8.8.0',
],
];
if (isset($deprecatedTypes[$typeName])) {
$this->findings['version_specific_patterns'][] = [
'type' => 'deprecated_type_hint',
'name' => $typeName,
'replacement' => $deprecatedTypes[$typeName]['replacement'],
'version' => $deprecatedTypes[$typeName]['version'],
'line' => $line,
];
}
}
/**
* Resolve class name with use statements and current namespace.
*/
protected function resolveClassName(Name $name) {
if ($name->isFullyQualified()) {
return $name->toString();
}
$firstPart = $name->getFirst();
if (isset($this->useStatements[$firstPart])) {
$nameParts = $name->getParts();
array_shift($nameParts);
return $this->useStatements[$firstPart] . ($nameParts ? '\\' . implode('\\', $nameParts) : '');
}
return $this->currentNamespace . '\\' . $name->toString();
}
/**
* Gets all findings.
*
* @return array
* Array of findings.
*/
public function getFindings() {
return $this->findings;
}
/**
* Resets the visitor's state.
*/
public function reset() {
$this->findings = [];
$this->classContext = [];
$this->currentNamespace = '';
$this->useStatements = [];
}
}
