aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts/plugin-stub-generator/src/NamePrinter.php
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/plugin-stub-generator/src/NamePrinter.php')
-rw-r--r--scripts/plugin-stub-generator/src/NamePrinter.php92
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);
+ }
+}