![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/vendor/webonyx/graphql-php/src/Language/ |
<?php declare(strict_types=1); namespace GraphQL\Language; use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeList; use GraphQL\Utils\TypeInfo; use GraphQL\Utils\Utils; /** * Utility for efficient AST traversal and modification. * * `visit()` will walk through an AST using a depth first traversal, calling * the visitor's enter function at each node in the traversal, and calling the * leave function after visiting that node and all of its child nodes. * * By returning different values from the `enter` and `leave` functions, the * behavior of the visitor can be altered. * - no return (`void`) or return `null`: no action * - `Visitor::skipNode()`: skips over the subtree at the current node of the AST * - `Visitor::stop()`: stop the Visitor completely * - `Visitor::removeNode()`: remove the current node * - return any other value: replace this node with the returned value * * When using `visit()` to edit an AST, the original AST will not be modified, and * a new version of the AST with the changes applied will be returned from the * visit function. * * $editedAST = Visitor::visit($ast, [ * 'enter' => function ($node, $key, $parent, $path, $ancestors) { * // ... * }, * 'leave' => function ($node, $key, $parent, $path, $ancestors) { * // ... * } * ]); * * Alternatively to providing `enter` and `leave` functions, a visitor can * instead provide functions named the same as the [kinds of AST nodes](class-reference.md#graphqllanguageastnodekind), * or enter/leave visitors at a named key, leading to four permutations of * visitor API: * * 1. Named visitors triggered when entering a node a specific kind. * * Visitor::visit($ast, [ * 'Kind' => function ($node) { * // enter the "Kind" node * } * ]); * * 2. Named visitors that trigger upon entering and leaving a node of * a specific kind. * * Visitor::visit($ast, [ * 'Kind' => [ * 'enter' => function ($node) { * // enter the "Kind" node * } * 'leave' => function ($node) { * // leave the "Kind" node * } * ] * ]); * * 3. Generic visitors that trigger upon entering and leaving any node. * * Visitor::visit($ast, [ * 'enter' => function ($node) { * // enter any node * }, * 'leave' => function ($node) { * // leave any node * } * ]); * * 4. Parallel visitors for entering and leaving nodes of a specific kind. * * Visitor::visit($ast, [ * 'enter' => [ * 'Kind' => function($node) { * // enter the "Kind" node * } * }, * 'leave' => [ * 'Kind' => function ($node) { * // leave the "Kind" node * } * ] * ]); * * @phpstan-type NodeVisitor callable(Node): (VisitorOperation|null|false|void) * @phpstan-type VisitorArray array<string, NodeVisitor>|array<string, array<string, NodeVisitor>> * * @see \GraphQL\Tests\Language\VisitorTest */ class Visitor { public const VISITOR_KEYS = [ NodeKind::NAME => [], NodeKind::DOCUMENT => ['definitions'], NodeKind::OPERATION_DEFINITION => ['name', 'variableDefinitions', 'directives', 'selectionSet'], NodeKind::VARIABLE_DEFINITION => ['variable', 'type', 'defaultValue', 'directives'], NodeKind::VARIABLE => ['name'], NodeKind::SELECTION_SET => ['selections'], NodeKind::FIELD => ['alias', 'name', 'arguments', 'directives', 'selectionSet'], NodeKind::ARGUMENT => ['name', 'value'], NodeKind::FRAGMENT_SPREAD => ['name', 'directives'], NodeKind::INLINE_FRAGMENT => ['typeCondition', 'directives', 'selectionSet'], NodeKind::FRAGMENT_DEFINITION => [ 'name', // Note: fragment variable definitions are experimental and may be changed // or removed in the future. 'variableDefinitions', 'typeCondition', 'directives', 'selectionSet', ], NodeKind::INT => [], NodeKind::FLOAT => [], NodeKind::STRING => [], NodeKind::BOOLEAN => [], NodeKind::NULL => [], NodeKind::ENUM => [], NodeKind::LST => ['values'], NodeKind::OBJECT => ['fields'], NodeKind::OBJECT_FIELD => ['name', 'value'], NodeKind::DIRECTIVE => ['name', 'arguments'], NodeKind::NAMED_TYPE => ['name'], NodeKind::LIST_TYPE => ['type'], NodeKind::NON_NULL_TYPE => ['type'], NodeKind::SCHEMA_DEFINITION => ['directives', 'operationTypes'], NodeKind::OPERATION_TYPE_DEFINITION => ['type'], NodeKind::SCALAR_TYPE_DEFINITION => ['description', 'name', 'directives'], NodeKind::OBJECT_TYPE_DEFINITION => ['description', 'name', 'interfaces', 'directives', 'fields'], NodeKind::FIELD_DEFINITION => ['description', 'name', 'arguments', 'type', 'directives'], NodeKind::INPUT_VALUE_DEFINITION => ['description', 'name', 'type', 'defaultValue', 'directives'], NodeKind::INTERFACE_TYPE_DEFINITION => ['description', 'name', 'interfaces', 'directives', 'fields'], NodeKind::UNION_TYPE_DEFINITION => ['description', 'name', 'directives', 'types'], NodeKind::ENUM_TYPE_DEFINITION => ['description', 'name', 'directives', 'values'], NodeKind::ENUM_VALUE_DEFINITION => ['description', 'name', 'directives'], NodeKind::INPUT_OBJECT_TYPE_DEFINITION => ['description', 'name', 'directives', 'fields'], NodeKind::SCALAR_TYPE_EXTENSION => ['name', 'directives'], NodeKind::OBJECT_TYPE_EXTENSION => ['name', 'interfaces', 'directives', 'fields'], NodeKind::INTERFACE_TYPE_EXTENSION => ['name', 'interfaces', 'directives', 'fields'], NodeKind::UNION_TYPE_EXTENSION => ['name', 'directives', 'types'], NodeKind::ENUM_TYPE_EXTENSION => ['name', 'directives', 'values'], NodeKind::INPUT_OBJECT_TYPE_EXTENSION => ['name', 'directives', 'fields'], NodeKind::DIRECTIVE_DEFINITION => ['description', 'name', 'arguments', 'locations'], NodeKind::SCHEMA_EXTENSION => ['directives', 'operationTypes'], ]; /** * Visit the AST (see class description for details). * * @param NodeList<Node>|Node $root * @param VisitorArray $visitor * @param array<string, mixed>|null $keyMap * * @throws \Exception * * @return mixed * * @api */ public static function visit(object $root, array $visitor, array $keyMap = null) { $visitorKeys = $keyMap ?? self::VISITOR_KEYS; /** * @var list<array{ * inList: bool, * index: int, * keys: Node|NodeList|mixed, * edits: array<int, array{mixed, mixed}>, * }> $stack */ $stack = []; $inList = $root instanceof NodeList; $keys = [$root]; $index = -1; $edits = []; $parent = null; $path = []; $ancestors = []; do { ++$index; $isLeaving = $index === \count($keys); $key = null; $node = null; $isEdited = $isLeaving && $edits !== []; if ($isLeaving) { $key = $ancestors === [] ? null : $path[\count($path) - 1]; $node = $parent; $parent = \array_pop($ancestors); if ($isEdited) { if ($node instanceof Node || $node instanceof NodeList) { $node = $node->cloneDeep(); } $editOffset = 0; foreach ($edits as [$editKey, $editValue]) { if ($inList) { $editKey -= $editOffset; } if ($inList && $editValue === null) { assert($node instanceof NodeList, 'Follows from $inList'); $node->splice($editKey, 1); ++$editOffset; } elseif ($node instanceof NodeList) { if (! $editValue instanceof Node) { $notNode = Utils::printSafe($editValue); throw new \Exception("Can only add Node to NodeList, got: {$notNode}."); } $node[$editKey] = $editValue; } else { $node->{$editKey} = $editValue; } } } // @phpstan-ignore-next-line the stack is guaranteed to be non-empty at this point [ 'index' => $index, 'keys' => $keys, 'edits' => $edits, 'inList' => $inList, ] = \array_pop($stack); } elseif ($parent === null) { $node = $root; } else { $key = $inList ? $index : $keys[$index]; $node = $parent instanceof NodeList ? $parent[$key] : $parent->{$key}; if ($node === null) { continue; } $path[] = $key; } $result = null; if (! $node instanceof NodeList) { if (! ($node instanceof Node)) { $notNode = Utils::printSafe($node); throw new \Exception("Invalid AST Node: {$notNode}."); } $visitFn = self::extractVisitFn($visitor, $node->kind, $isLeaving); if ($visitFn !== null) { $result = $visitFn($node, $key, $parent, $path, $ancestors); if ($result !== null) { if ($result instanceof VisitorStop) { break; } if ($result instanceof VisitorSkipNode) { if (! $isLeaving) { \array_pop($path); } continue; } $editValue = $result instanceof VisitorRemoveNode ? null : $result; $edits[] = [$key, $editValue]; if (! $isLeaving) { if (! ($editValue instanceof Node)) { \array_pop($path); continue; } $node = $editValue; } } } } if ($result === null && $isEdited) { $edits[] = [$key, $node]; } if ($isLeaving) { \array_pop($path); } else { $stack[] = [ 'inList' => $inList, 'index' => $index, 'keys' => $keys, 'edits' => $edits, ]; $inList = $node instanceof NodeList; $keys = ($inList ? $node : $visitorKeys[$node->kind]) ?? []; $index = -1; $edits = []; if ($parent !== null) { $ancestors[] = $parent; } $parent = $node; } } while ($stack !== []); return $edits === [] ? $root : $edits[0][1]; } /** * Returns marker for stopping. * * @api */ public static function stop(): VisitorStop { static $stop; return $stop ??= new VisitorStop(); } /** * Returns marker for skipping the subtree at the current node. * * @api */ public static function skipNode(): VisitorSkipNode { static $skipNode; return $skipNode ??= new VisitorSkipNode(); } /** * Returns marker for removing the current node. * * @api */ public static function removeNode(): VisitorRemoveNode { static $removeNode; return $removeNode ??= new VisitorRemoveNode(); } /** * Combines the given visitors to run in parallel. * * @phpstan-param array<int, VisitorArray> $visitors * * @return VisitorArray */ public static function visitInParallel(array $visitors): array { $visitorsCount = \count($visitors); $skipping = new \SplFixedArray($visitorsCount); return [ 'enter' => static function (Node $node) use ($visitors, $skipping, $visitorsCount) { for ($i = 0; $i < $visitorsCount; ++$i) { if ($skipping[$i] !== null) { continue; } $fn = self::extractVisitFn( $visitors[$i], $node->kind, false ); if ($fn === null) { continue; } $result = $fn(...\func_get_args()); if ($result instanceof VisitorSkipNode) { $skipping[$i] = $node; } elseif ($result instanceof VisitorStop) { $skipping[$i] = $result; } elseif ($result instanceof VisitorRemoveNode) { return $result; } elseif ($result !== null) { return $result; } } return null; }, 'leave' => static function (Node $node) use ($visitors, $skipping, $visitorsCount) { for ($i = 0; $i < $visitorsCount; ++$i) { if ($skipping[$i] === null) { $fn = self::extractVisitFn( $visitors[$i], $node->kind, true ); if ($fn !== null) { $result = $fn(...\func_get_args()); if ($result instanceof VisitorStop) { $skipping[$i] = $result; } elseif ($result instanceof VisitorRemoveNode) { return $result; } elseif ($result !== null) { return $result; } } } elseif ($skipping[$i] === $node) { $skipping[$i] = null; } } return null; }, ]; } /** * Creates a new visitor that updates TypeInfo and delegates to the given visitor. * * @phpstan-param VisitorArray $visitor * * @phpstan-return VisitorArray */ public static function visitWithTypeInfo(TypeInfo $typeInfo, array $visitor): array { return [ 'enter' => static function (Node $node) use ($typeInfo, $visitor) { $typeInfo->enter($node); $fn = self::extractVisitFn($visitor, $node->kind, false); if ($fn === null) { return null; } $result = $fn(...\func_get_args()); if ($result === null) { return null; } $typeInfo->leave($node); if ($result instanceof Node) { $typeInfo->enter($result); } return $result; }, 'leave' => static function (Node $node) use ($typeInfo, $visitor) { $fn = self::extractVisitFn($visitor, $node->kind, true); $result = $fn !== null ? $fn(...\func_get_args()) : null; $typeInfo->leave($node); return $result; }, ]; } /** * @phpstan-param VisitorArray $visitor * * @return callable(Node $node, string $key, Node|NodeList $parent, array<int, int|string $path, array<int, Node|NodeList> $ancestors): VisitorOperation|Node|null */ protected static function extractVisitFn(array $visitor, string $kind, bool $isLeaving): ?callable { $kindVisitor = $visitor[$kind] ?? null; if (\is_array($kindVisitor)) { return $isLeaving ? $kindVisitor['leave'] ?? null : $kindVisitor['enter'] ?? null; } if ($kindVisitor !== null && ! $isLeaving) { return $kindVisitor; } $specificVisitor = $isLeaving ? $visitor['leave'] ?? null : $visitor['enter'] ?? null; if (\is_array($specificVisitor)) { return $specificVisitor[$kind] ?? null; } return $specificVisitor; } }