diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-04 03:36:10 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-04 05:43:22 +0900 |
| commit | 261516d5ce6f8b0d69cf9e3da7dd2f0ef1cdc36a (patch) | |
| tree | f635a416745065b52ed0bb0ebb18154b9ef89f3b /scripts/plugin-stub-generator/src/NamePrinter.php | |
| parent | 6cb1849473792bd73dbfb6265d363f149f687572 (diff) | |
| download | php-shirabe-261516d5ce6f8b0d69cf9e3da7dd2f0ef1cdc36a.tar.gz php-shirabe-261516d5ce6f8b0d69cf9e3da7dd2f0ef1cdc36a.tar.zst php-shirabe-261516d5ce6f8b0d69cf9e3da7dd2f0ef1cdc36a.zip | |
feat(plugin): generate the worker proxy stubs from the Composer sources
Replace the hand-written proxy stubs under crates/shirabe-php-rpc/php/stubs
with output of scripts/plugin-stub-generator, a deterministic emitter that
derives every stub from the Composer checkout and the classifier report.
Anything it cannot faithfully proxy (by-ref/variadic parameters, magic
methods, public properties, diverging omitted overrides, stale stub files,
a STUB_FILES entry missing in lib.rs) fails generation instead of degrading
silently, so future Composer releases surface new members as explicit
errors rather than silent gaps.
Regenerating the stubs also normalizes the hand-written inconsistencies
(uniform guarded constructors, import-based type spellings) and fixes real
gaps the review of the generated diff uncovered: the BaseIO authentication
methods now carry the real class's untyped signatures, and the previously
missing ConsoleIO::sanitize is materialized together with its private
static helper. A cargo test runs generate-stubs --check to keep the
committed stubs, the generator and the embedded list from drifting apart.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'scripts/plugin-stub-generator/src/NamePrinter.php')
| -rw-r--r-- | scripts/plugin-stub-generator/src/NamePrinter.php | 92 |
1 files changed, 92 insertions, 0 deletions
diff --git a/scripts/plugin-stub-generator/src/NamePrinter.php b/scripts/plugin-stub-generator/src/NamePrinter.php new file mode 100644 index 00000000..1675fee1 --- /dev/null +++ b/scripts/plugin-stub-generator/src/NamePrinter.php @@ -0,0 +1,92 @@ +<?php + +declare(strict_types=1); + +namespace Shirabe\PluginStubGenerator; + +use PhpParser\Node; +use PhpParser\Node\Identifier; +use PhpParser\Node\IntersectionType; +use PhpParser\Node\Name; +use PhpParser\Node\NullableType; +use PhpParser\Node\UnionType; +use PhpParser\PrettyPrinter\Standard; + +/** + * Renders type and default-value nodes into a target file context: class names resolve to + * their FQCN and are re-spelled through the target's import table (alias if imported, bare + * name if in the stub's namespace, `\FQCN` otherwise). This lets signatures declared in one + * file (an interface) be emitted into a stub that carries another file's `use` block. + */ +final class NamePrinter extends Standard +{ + private SourceFile $context; + + public function __construct() + { + parent::__construct(['shortArraySyntax' => true]); + } + + public function renderType(?Node $type, SourceFile $context): string + { + if ($type === null) { + return ''; + } + if ($type instanceof Identifier) { + return $type->toString(); + } + if ($type instanceof NullableType) { + return '?' . $this->renderType($type->type, $context); + } + if ($type instanceof UnionType) { + return implode('|', array_map(fn (Node $t): string => $this->renderType($t, $context), $type->types)); + } + if ($type instanceof IntersectionType) { + return implode('&', array_map(fn (Node $t): string => $this->renderType($t, $context), $type->types)); + } + if ($type instanceof Name) { + return $this->renderName($type, $context); + } + throw new GenerationError(['unsupported type node ' . get_class($type)]); + } + + public function renderName(Name $name, SourceFile $context): string + { + if ($name->isSpecialClassName()) { + return $name->toString(); + } + $resolved = $name->getAttribute('resolvedName'); + $fqcn = $resolved instanceof Name ? $resolved->toString() : $name->toString(); + if ($name->isUnqualified() && !str_contains($fqcn, '\\')) { + // Names that resolved into the global namespace only occur in constant space + // (true, false, null, ...); they keep their source spelling. + return $name->toString(); + } + return $this->renderFqcn($fqcn, $context); + } + + public function renderFqcn(string $fqcn, SourceFile $context): string + { + foreach ($context->aliases as $alias => $target) { + if ($target === $fqcn) { + return $alias; + } + } + $prefix = $context->namespace === null ? '' : $context->namespace . '\\'; + if ($prefix !== '' && str_starts_with($fqcn, $prefix) && !str_contains(substr($fqcn, strlen($prefix)), '\\')) { + return substr($fqcn, strlen($prefix)); + } + return '\\' . $fqcn; + } + + public function renderExpr(Node\Expr $expr, SourceFile $context): string + { + $this->context = $context; + return $this->prettyPrintExpr($expr); + } + + protected function pName(Name $node): string + { + return $this->renderName($node, $this->context); + } +} |
