TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

147 lines
4.6 KiB

<?php
/**
* This file is part of TypePHP.
*
* @link https://www.swoole.com/
* @contact service@swoole.com
*/
namespace TypePhp\Generator;
use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\Parser;
use PhpParser\PrettyPrinter;
final class LibraryImportStubGenerator
{
public function __construct(
private readonly Parser $parser,
private readonly PrettyPrinter $printer,
) {
}
/**
* @param array<string> $files
* @param array<string, true> $externalImportStubFiles
*/
public function generate(array $files, array $externalImportStubFiles): string
{
/** @var array<string, array<Node\Stmt>> $namespaces */
$namespaces = [];
foreach ($files as $file) {
$realFile = realpath($file);
if ($realFile === false || isset($externalImportStubFiles[$realFile])) {
continue;
}
if (pathinfo($realFile, PATHINFO_EXTENSION) !== 'php') {
continue;
}
$code = file_get_contents($realFile);
if ($code === false) {
throw new \RuntimeException('Can not read file: ' . $realFile);
}
$ast = $this->parser->parse($code) ?? [];
$traverser = new NodeTraverser();
$traverser->addVisitor(new NameResolver());
$ast = $traverser->traverse($ast);
foreach ($ast as $stmt) {
if ($stmt instanceof Node\Stmt\Namespace_) {
$namespace = $stmt->name?->toString() ?? '';
$this->appendDeclarations($namespaces, $namespace, $stmt->stmts);
continue;
}
$this->appendDeclarations($namespaces, '', [$stmt]);
}
}
$namespaceNodes = [];
foreach ($namespaces as $namespace => $stmts) {
if ($stmts === []) {
continue;
}
$namespaceNodes[] = new Node\Stmt\Namespace_(
$namespace === '' ? null : new Node\Name($namespace),
$stmts,
);
}
$code = "<?php\n\n/** @import-library */\n\n";
if ($namespaceNodes !== []) {
$code .= $this->printer->prettyPrint($namespaceNodes) . "\n";
}
return $code;
}
/**
* @param array<string, array<Node\Stmt>> $namespaces
* @param array<Node\Stmt> $stmts
*/
private function appendDeclarations(array &$namespaces, string $namespace, array $stmts): void
{
foreach ($stmts as $stmt) {
if ($namespace === '' && $stmt instanceof Node\Stmt\Function_
&& strtolower($stmt->name->toString()) === 'main') {
continue;
}
$declaration = $this->makeImportDeclaration($stmt);
if ($declaration !== null) {
$namespaces[$namespace][] = $declaration;
}
}
}
private function makeImportDeclaration(Node\Stmt $stmt): ?Node\Stmt
{
$comments = array_filter(
$stmt->getComments(),
static fn(\PhpParser\Comment $comment): bool => preg_match(
'/@import-library\b/',
$comment->getText(),
) !== 1,
);
$stmt->setAttribute('comments', array_values($comments));
if ($stmt instanceof Node\Stmt\Function_) {
$stmt->stmts = [];
return $stmt;
}
if ($stmt instanceof Node\Stmt\ClassLike) {
$members = [];
foreach ($stmt->stmts as $member) {
if ($member instanceof Node\Stmt\ClassMethod) {
if (!($member->flags & Modifiers::ABSTRACT)
&& !($stmt instanceof Node\Stmt\Interface_)) {
$member->stmts = [];
}
$members[] = $member;
continue;
}
if ($member instanceof Node\Stmt\Property) {
foreach ($member->hooks as $hook) {
if ($hook->body !== null) {
$hook->body = [];
}
}
$members[] = $member;
continue;
}
if ($member instanceof Node\Stmt\ClassConst
|| $member instanceof Node\Stmt\TraitUse
|| $member instanceof Node\Stmt\EnumCase) {
$members[] = $member;
}
}
$stmt->stmts = $members;
return $stmt;
}
return null;
}
}