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 = [];
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc