skipNode() public static method

Skip current node
public static skipNode ( )
 public function __invoke(ValidationContext $context)
 {
     return [Node::FIELD => function (Field $fieldAST) use($context) {
         $fieldDef = $context->getFieldDef();
         if (!$fieldDef) {
             return Visitor::skipNode();
         }
         $errors = [];
         $argASTs = $fieldAST->arguments ?: [];
         $argASTMap = Utils::keyMap($argASTs, function (Argument $arg) {
             return $arg->name->value;
         });
         foreach ($fieldDef->args as $argDef) {
             $argAST = isset($argASTMap[$argDef->name]) ? $argASTMap[$argDef->name] : null;
             if (!$argAST && $argDef->getType() instanceof NonNull) {
                 $errors[] = new Error(Messages::missingArgMessage($fieldAST->name->value, $argDef->name, $argDef->getType()), [$fieldAST]);
             }
         }
         $argDefMap = Utils::keyMap($fieldDef->args, function ($def) {
             return $def->name;
         });
         foreach ($argASTs as $argAST) {
             $argDef = $argDefMap[$argAST->name->value];
             if ($argDef && !DocumentValidator::isValidLiteralValue($argAST->value, $argDef->getType())) {
                 $errors[] = new Error(Messages::badValueMessage($argAST->name->value, $argDef->getType(), Printer::doPrint($argAST->value)), [$argAST->value]);
             }
         }
         return !empty($errors) ? $errors : null;
     }];
 }
 public function __invoke(ValidationContext $context)
 {
     $operation = null;
     $visitedFragmentNames = [];
     $definedVariableNames = [];
     return ['visitSpreadFragments' => true, Node::OPERATION_DEFINITION => function (OperationDefinition $node, $key, $parent, $path, $ancestors) use(&$operation, &$visitedFragmentNames, &$definedVariableNames) {
         $operation = $node;
         $visitedFragmentNames = [];
         $definedVariableNames = [];
     }, Node::VARIABLE_DEFINITION => function (VariableDefinition $def) use(&$definedVariableNames) {
         $definedVariableNames[$def->variable->name->value] = true;
     }, Node::VARIABLE => function (Variable $variable, $key, $parent, $path, $ancestors) use(&$definedVariableNames, &$visitedFragmentNames, &$operation) {
         $varName = $variable->name->value;
         if (empty($definedVariableNames[$varName])) {
             $withinFragment = false;
             foreach ($ancestors as $ancestor) {
                 if ($ancestor instanceof FragmentDefinition) {
                     $withinFragment = true;
                     break;
                 }
             }
             if ($withinFragment && $operation && $operation->name) {
                 return new Error(self::undefinedVarByOpMessage($varName, $operation->name->value), [$variable, $operation]);
             }
             return new Error(self::undefinedVarMessage($varName), [$variable]);
         }
     }, Node::FRAGMENT_SPREAD => function (FragmentSpread $spreadAST) use(&$visitedFragmentNames) {
         // Only visit fragments of a particular name once per operation
         if (!empty($visitedFragmentNames[$spreadAST->name->value])) {
             return Visitor::skipNode();
         }
         $visitedFragmentNames[$spreadAST->name->value] = true;
     }];
 }
 public function __invoke(ValidationContext $context)
 {
     $varDefMap = new \ArrayObject();
     $visitedFragmentNames = new \ArrayObject();
     return ['visitSpreadFragments' => true, Node::OPERATION_DEFINITION => function () use(&$varDefMap, &$visitedFragmentNames) {
         $varDefMap = new \ArrayObject();
         $visitedFragmentNames = new \ArrayObject();
     }, Node::VARIABLE_DEFINITION => function (VariableDefinition $varDefAST) use($varDefMap) {
         $varDefMap[$varDefAST->variable->name->value] = $varDefAST;
     }, Node::FRAGMENT_SPREAD => function (FragmentSpread $spreadAST) use($visitedFragmentNames) {
         // Only visit fragments of a particular name once per operation
         if (!empty($visitedFragmentNames[$spreadAST->name->value])) {
             return Visitor::skipNode();
         }
         $visitedFragmentNames[$spreadAST->name->value] = true;
     }, Node::VARIABLE => function (Variable $variableAST) use($context, $varDefMap) {
         $varName = $variableAST->name->value;
         $varDef = isset($varDefMap[$varName]) ? $varDefMap[$varName] : null;
         $varType = $varDef ? TypeInfo::typeFromAST($context->getSchema(), $varDef->type) : null;
         $inputType = $context->getInputType();
         if ($varType && $inputType && !$this->varTypeAllowedForType($this->effectiveType($varType, $varDef), $inputType)) {
             return new Error(Messages::badVarPosMessage($varName, $varType, $inputType), [$variableAST]);
         }
     }];
 }
 public function __invoke(ValidationContext $context)
 {
     $this->operationDefs = [];
     $this->fragmentDefs = [];
     return [NodeKind::OPERATION_DEFINITION => function ($node) {
         $this->operationDefs[] = $node;
         return Visitor::skipNode();
     }, NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $def) {
         $this->fragmentDefs[] = $def;
         return Visitor::skipNode();
     }, NodeKind::DOCUMENT => ['leave' => function () use($context) {
         $fragmentNameUsed = [];
         foreach ($this->operationDefs as $operation) {
             foreach ($context->getRecursivelyReferencedFragments($operation) as $fragment) {
                 $fragmentNameUsed[$fragment->name->value] = true;
             }
         }
         foreach ($this->fragmentDefs as $fragmentDef) {
             $fragName = $fragmentDef->name->value;
             if (empty($fragmentNameUsed[$fragName])) {
                 $context->reportError(new Error(self::unusedFragMessage($fragName), [$fragmentDef]));
             }
         }
     }]];
 }
Example #5
0
 public function __invoke(ValidationContext $context)
 {
     $visitedFragmentNames = new \stdClass();
     $variableDefs = [];
     $variableNameUsed = new \stdClass();
     return ['visitSpreadFragments' => true, Node::OPERATION_DEFINITION => ['enter' => function () use(&$visitedFragmentNames, &$variableDefs, &$variableNameUsed) {
         $visitedFragmentNames = new \stdClass();
         $variableDefs = [];
         $variableNameUsed = new \stdClass();
     }, 'leave' => function () use(&$visitedFragmentNames, &$variableDefs, &$variableNameUsed) {
         $errors = [];
         foreach ($variableDefs as $def) {
             if (empty($variableNameUsed->{$def->variable->name->value})) {
                 $errors[] = new Error(Messages::unusedVariableMessage($def->variable->name->value), [$def]);
             }
         }
         return !empty($errors) ? $errors : null;
     }], Node::VARIABLE_DEFINITION => function ($def) use(&$variableDefs) {
         $variableDefs[] = $def;
         return Visitor::skipNode();
     }, Node::VARIABLE => function ($variable) use(&$variableNameUsed) {
         $variableNameUsed->{$variable->name->value} = true;
     }, Node::FRAGMENT_SPREAD => function ($spreadAST) use(&$visitedFragmentNames) {
         // Only visit fragments of a particular name once per operation
         if (!empty($visitedFragmentNames->{$spreadAST->name->value})) {
             return Visitor::skipNode();
         }
         $visitedFragmentNames->{$spreadAST->name->value} = true;
     }];
 }
Example #6
0
 public function __invoke(ValidationContext $context)
 {
     $skip = function () {
         return Visitor::skipNode();
     };
     return [Node::OBJECT_TYPE_DEFINITION => $skip, Node::INTERFACE_TYPE_DEFINITION => $skip, Node::UNION_TYPE_DEFINITION => $skip, Node::INPUT_OBJECT_TYPE_DEFINITION => $skip, Node::NAMED_TYPE => function (NamedType $node, $key) use($context) {
         $typeName = $node->name->value;
         $type = $context->getSchema()->getType($typeName);
         if (!$type) {
             $context->reportError(new Error(self::unknownTypeMessage($typeName), [$node]));
         }
     }];
 }
 public function __invoke(ValidationContext $context)
 {
     return [Node::ARGUMENT => function (Argument $argAST) use($context) {
         $argDef = $context->getArgument();
         if ($argDef) {
             $errors = DocumentValidator::isValidLiteralValue($argDef->getType(), $argAST->value);
             if (!empty($errors)) {
                 $context->reportError(new Error(self::badValueMessage($argAST->name->value, $argDef->getType(), Printer::doPrint($argAST->value), $errors), [$argAST->value]));
             }
         }
         return Visitor::skipNode();
     }];
 }
Example #8
0
 public function __invoke(ValidationContext $context)
 {
     $this->knownFragmentNames = [];
     return [Node::OPERATION_DEFINITION => function () {
         return Visitor::skipNode();
     }, Node::FRAGMENT_DEFINITION => function (FragmentDefinition $node) use($context) {
         $fragmentName = $node->name->value;
         if (!empty($this->knownFragmentNames[$fragmentName])) {
             $context->reportError(new Error(self::duplicateFragmentNameMessage($fragmentName), [$this->knownFragmentNames[$fragmentName], $node->name]));
         } else {
             $this->knownFragmentNames[$fragmentName] = $node->name;
         }
         return Visitor::skipNode();
     }];
 }
Example #9
0
 public function testAllowsSkippingASubTree()
 {
     $visited = [];
     $ast = Parser::parse('{ a, b { x }, c }');
     Visitor::visit($ast, ['enter' => function (Node $node) use(&$visited) {
         $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null];
         if ($node instanceof Field && $node->name->value === 'b') {
             return Visitor::skipNode();
         }
     }, 'leave' => function (Node $node) use(&$visited) {
         $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null];
     }]);
     $expected = [['enter', 'Document', null], ['enter', 'OperationDefinition', null], ['enter', 'SelectionSet', null], ['enter', 'Field', null], ['enter', 'Name', 'a'], ['leave', 'Name', 'a'], ['leave', 'Field', null], ['enter', 'Field', null], ['enter', 'Field', null], ['enter', 'Name', 'c'], ['leave', 'Name', 'c'], ['leave', 'Field', null], ['leave', 'SelectionSet', null], ['leave', 'OperationDefinition', null], ['leave', 'Document', null]];
     $this->assertEquals($expected, $visited);
 }
 public function __invoke(ValidationContext $context)
 {
     $this->knownOperationNames = [];
     return [NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) use($context) {
         $operationName = $node->name;
         if ($operationName) {
             if (!empty($this->knownOperationNames[$operationName->value])) {
                 $context->reportError(new Error(self::duplicateOperationNameMessage($operationName->value), [$this->knownOperationNames[$operationName->value], $operationName]));
             } else {
                 $this->knownOperationNames[$operationName->value] = $operationName;
             }
         }
         return Visitor::skipNode();
     }, NodeKind::FRAGMENT_DEFINITION => function () {
         return Visitor::skipNode();
     }];
 }
Example #11
0
 public function __invoke(ValidationContext $context)
 {
     $this->knownArgNames = [];
     return [Node::FIELD => function () {
         $this->knownArgNames = [];
     }, Node::DIRECTIVE => function () {
         $this->knownArgNames = [];
     }, Node::ARGUMENT => function (Argument $node) use($context) {
         $argName = $node->name->value;
         if (!empty($this->knownArgNames[$argName])) {
             $context->reportError(new Error(self::duplicateArgMessage($argName), [$this->knownArgNames[$argName], $node->name]));
         } else {
             $this->knownArgNames[$argName] = $node->name;
         }
         return Visitor::skipNode();
     }];
 }
Example #12
0
 public function __invoke(ValidationContext $context)
 {
     $this->context = $context;
     $this->variableDefs = new \ArrayObject();
     $this->fieldNodeAndDefs = new \ArrayObject();
     $complexity = 0;
     return $this->invokeIfNeeded($context, [NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use($context) {
         $this->fieldNodeAndDefs = $this->collectFieldASTsAndDefs($context, $context->getParentType(), $selectionSet, null, $this->fieldNodeAndDefs);
     }, NodeKind::VARIABLE_DEFINITION => function ($def) {
         $this->variableDefs[] = $def;
         return Visitor::skipNode();
     }, NodeKind::OPERATION_DEFINITION => ['leave' => function (OperationDefinitionNode $operationDefinition) use($context, &$complexity) {
         $complexity = $this->fieldComplexity($operationDefinition, $complexity);
         if ($complexity > $this->getMaxQueryComplexity()) {
             $context->reportError(new Error($this->maxQueryComplexityErrorMessage($this->getMaxQueryComplexity(), $complexity)));
         }
     }]]);
 }
Example #13
0
 public function __invoke(ValidationContext $context)
 {
     // Tracks already visited fragments to maintain O(N) and to ensure that cycles
     // are not redundantly reported.
     $this->visitedFrags = [];
     // Array of AST nodes used to produce meaningful errors
     $this->spreadPath = [];
     // Position in the spread path
     $this->spreadPathIndexByName = [];
     return [Node::OPERATION_DEFINITION => function () {
         return Visitor::skipNode();
     }, Node::FRAGMENT_DEFINITION => function (FragmentDefinition $node) use($context) {
         if (!isset($this->visitedFrags[$node->name->value])) {
             $this->detectCycleRecursive($node, $context);
         }
         return Visitor::skipNode();
     }];
 }
 public function __invoke(ValidationContext $context)
 {
     return [Node::FIELD => ['leave' => function (Field $fieldAST) use($context) {
         $fieldDef = $context->getFieldDef();
         if (!$fieldDef) {
             return Visitor::skipNode();
         }
         $errors = [];
         $argASTs = $fieldAST->arguments ?: [];
         $argASTMap = [];
         foreach ($argASTs as $argAST) {
             $argASTMap[$argAST->name->value] = $argASTs;
         }
         foreach ($fieldDef->args as $argDef) {
             $argAST = isset($argASTMap[$argDef->name]) ? $argASTMap[$argDef->name] : null;
             if (!$argAST && $argDef->getType() instanceof NonNull) {
                 $errors[] = new Error(self::missingFieldArgMessage($fieldAST->name->value, $argDef->name, $argDef->getType()), [$fieldAST]);
             }
         }
         if (!empty($errors)) {
             return $errors;
         }
     }], Node::DIRECTIVE => ['leave' => function (Directive $directiveAST) use($context) {
         $directiveDef = $context->getDirective();
         if (!$directiveDef) {
             return Visitor::skipNode();
         }
         $errors = [];
         $argASTs = $directiveAST->arguments ?: [];
         $argASTMap = [];
         foreach ($argASTs as $argAST) {
             $argASTMap[$argAST->name->value] = $argASTs;
         }
         foreach ($directiveDef->args as $argDef) {
             $argAST = isset($argASTMap[$argDef->name]) ? $argASTMap[$argDef->name] : null;
             if (!$argAST && $argDef->getType() instanceof NonNull) {
                 $errors[] = new Error(self::missingDirectiveArgMessage($directiveAST->name->value, $argDef->name, $argDef->getType()), [$directiveAST]);
             }
         }
         if (!empty($errors)) {
             return $errors;
         }
     }]];
 }
 public function __invoke(ValidationContext $context)
 {
     $this->knownNames = [];
     $this->knownNameStack = [];
     return [NodeKind::OBJECT => ['enter' => function () {
         $this->knownNameStack[] = $this->knownNames;
         $this->knownNames = [];
     }, 'leave' => function () {
         $this->knownNames = array_pop($this->knownNameStack);
     }], NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) use($context) {
         $fieldName = $node->name->value;
         if (!empty($this->knownNames[$fieldName])) {
             $context->reportError(new Error(self::duplicateInputFieldMessage($fieldName), [$this->knownNames[$fieldName], $node->name]));
         } else {
             $this->knownNames[$fieldName] = $node->name;
         }
         return Visitor::skipNode();
     }];
 }
 public function __invoke(ValidationContext $context)
 {
     return [Node::VARIABLE_DEFINITION => function (VariableDefinition $varDefAST) use($context) {
         $name = $varDefAST->variable->name->value;
         $defaultValue = $varDefAST->defaultValue;
         $type = $context->getInputType();
         if ($type instanceof NonNull && $defaultValue) {
             $context->reportError(new Error(static::defaultForNonNullArgMessage($name, $type, $type->getWrappedType()), [$defaultValue]));
         }
         if ($type && $defaultValue) {
             $errors = DocumentValidator::isValidLiteralValue($type, $defaultValue);
             if (!empty($errors)) {
                 $context->reportError(new Error(static::badValueForDefaultArgMessage($name, $type, Printer::doPrint($defaultValue), $errors), [$defaultValue]));
             }
         }
         return Visitor::skipNode();
     }, Node::SELECTION_SET => function () {
         return Visitor::skipNode();
     }, Node::FRAGMENT_DEFINITION => function () {
         return Visitor::skipNode();
     }];
 }
 public function __invoke(ValidationContext $context)
 {
     return [NodeKind::FIELD => ['leave' => function (FieldNode $fieldNode) use($context) {
         $fieldDef = $context->getFieldDef();
         if (!$fieldDef) {
             return Visitor::skipNode();
         }
         $argNodes = $fieldNode->arguments ?: [];
         $argNodeMap = [];
         foreach ($argNodes as $argNode) {
             $argNodeMap[$argNode->name->value] = $argNodes;
         }
         foreach ($fieldDef->args as $argDef) {
             $argNode = isset($argNodeMap[$argDef->name]) ? $argNodeMap[$argDef->name] : null;
             if (!$argNode && $argDef->getType() instanceof NonNull) {
                 $context->reportError(new Error(self::missingFieldArgMessage($fieldNode->name->value, $argDef->name, $argDef->getType()), [$fieldNode]));
             }
         }
     }], NodeKind::DIRECTIVE => ['leave' => function (DirectiveNode $directiveNode) use($context) {
         $directiveDef = $context->getDirective();
         if (!$directiveDef) {
             return Visitor::skipNode();
         }
         $argNodes = $directiveNode->arguments ?: [];
         $argNodeMap = [];
         foreach ($argNodes as $argNode) {
             $argNodeMap[$argNode->name->value] = $argNodes;
         }
         foreach ($directiveDef->args as $argDef) {
             $argNode = isset($argNodeMap[$argDef->name]) ? $argNodeMap[$argDef->name] : null;
             if (!$argNode && $argDef->getType() instanceof NonNull) {
                 $context->reportError(new Error(self::missingDirectiveArgMessage($directiveNode->name->value, $argDef->name, $argDef->getType()), [$directiveNode]));
             }
         }
     }]];
 }
Example #18
0
 /**
  * @it allows skipping different sub-trees
  */
 public function testAllowsSkippingDifferentSubTrees()
 {
     $visited = [];
     $ast = Parser::parse('{ a { x }, b { y} }');
     Visitor::visit($ast, Visitor::visitInParallel([['enter' => function ($node) use(&$visited) {
         $visited[] = ['no-a', 'enter', $node->kind, isset($node->value) ? $node->value : null];
         if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'a') {
             return Visitor::skipNode();
         }
     }, 'leave' => function ($node) use(&$visited) {
         $visited[] = ['no-a', 'leave', $node->kind, isset($node->value) ? $node->value : null];
     }], ['enter' => function ($node) use(&$visited) {
         $visited[] = ['no-b', 'enter', $node->kind, isset($node->value) ? $node->value : null];
         if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') {
             return Visitor::skipNode();
         }
     }, 'leave' => function ($node) use(&$visited) {
         $visited[] = ['no-b', 'leave', $node->kind, isset($node->value) ? $node->value : null];
     }]]));
     $this->assertEquals([['no-a', 'enter', 'Document', null], ['no-b', 'enter', 'Document', null], ['no-a', 'enter', 'OperationDefinition', null], ['no-b', 'enter', 'OperationDefinition', null], ['no-a', 'enter', 'SelectionSet', null], ['no-b', 'enter', 'SelectionSet', null], ['no-a', 'enter', 'Field', null], ['no-b', 'enter', 'Field', null], ['no-b', 'enter', 'Name', 'a'], ['no-b', 'leave', 'Name', 'a'], ['no-b', 'enter', 'SelectionSet', null], ['no-b', 'enter', 'Field', null], ['no-b', 'enter', 'Name', 'x'], ['no-b', 'leave', 'Name', 'x'], ['no-b', 'leave', 'Field', null], ['no-b', 'leave', 'SelectionSet', null], ['no-b', 'leave', 'Field', null], ['no-a', 'enter', 'Field', null], ['no-b', 'enter', 'Field', null], ['no-a', 'enter', 'Name', 'b'], ['no-a', 'leave', 'Name', 'b'], ['no-a', 'enter', 'SelectionSet', null], ['no-a', 'enter', 'Field', null], ['no-a', 'enter', 'Name', 'y'], ['no-a', 'leave', 'Name', 'y'], ['no-a', 'leave', 'Field', null], ['no-a', 'leave', 'SelectionSet', null], ['no-a', 'leave', 'Field', null], ['no-a', 'leave', 'SelectionSet', null], ['no-b', 'leave', 'SelectionSet', null], ['no-a', 'leave', 'OperationDefinition', null], ['no-b', 'leave', 'OperationDefinition', null], ['no-a', 'leave', 'Document', null], ['no-b', 'leave', 'Document', null]], $visited);
 }
 /**
  * This uses a specialized visitor which runs multiple visitors in parallel,
  * while maintaining the visitor skip and break API.
  *
  * @param Schema $schema
  * @param Document $documentAST
  * @param array $rules
  * @return array
  */
 public static function visitUsingRules(Schema $schema, Document $documentAST, array $rules)
 {
     $typeInfo = new TypeInfo($schema);
     $context = new ValidationContext($schema, $documentAST, $typeInfo);
     $errors = [];
     // TODO: convert to class
     $visitInstances = function ($ast, $instances) use($typeInfo, $context, &$errors, &$visitInstances) {
         $skipUntil = new \SplFixedArray(count($instances));
         $skipCount = 0;
         Visitor::visit($ast, ['enter' => function ($node, $key) use($typeInfo, $instances, $skipUntil, &$skipCount, &$errors, $context, $visitInstances) {
             $typeInfo->enter($node);
             for ($i = 0; $i < count($instances); $i++) {
                 // Do not visit this instance if it returned false for a previous node
                 if ($skipUntil[$i]) {
                     continue;
                 }
                 $result = null;
                 // Do not visit top level fragment definitions if this instance will
                 // visit those fragments inline because it
                 // provided `visitSpreadFragments`.
                 if ($node->kind === Node::FRAGMENT_DEFINITION && $key !== null && !empty($instances[$i]['visitSpreadFragments'])) {
                     $result = Visitor::skipNode();
                 } else {
                     $enter = Visitor::getVisitFn($instances[$i], false, $node->kind);
                     if ($enter instanceof \Closure) {
                         // $enter = $enter->bindTo($instances[$i]);
                         $result = call_user_func_array($enter, func_get_args());
                     } else {
                         $result = null;
                     }
                 }
                 if ($result instanceof VisitorOperation) {
                     if ($result->doContinue) {
                         $skipUntil[$i] = $node;
                         $skipCount++;
                         // If all instances are being skipped over, skip deeper traversal
                         if ($skipCount === count($instances)) {
                             for ($k = 0; $k < count($instances); $k++) {
                                 if ($skipUntil[$k] === $node) {
                                     $skipUntil[$k] = null;
                                     $skipCount--;
                                 }
                             }
                             return Visitor::skipNode();
                         }
                     } else {
                         if ($result->doBreak) {
                             $instances[$i] = null;
                         }
                     }
                 } else {
                     if ($result && self::isError($result)) {
                         self::append($errors, $result);
                         for ($j = $i - 1; $j >= 0; $j--) {
                             $leaveFn = Visitor::getVisitFn($instances[$j], true, $node->kind);
                             if ($leaveFn) {
                                 // $leaveFn = $leaveFn->bindTo($instances[$j])
                                 $result = call_user_func_array($leaveFn, func_get_args());
                                 if ($result instanceof VisitorOperation) {
                                     if ($result->doBreak) {
                                         $instances[$j] = null;
                                     }
                                 } else {
                                     if (self::isError($result)) {
                                         self::append($errors, $result);
                                     } else {
                                         if ($result !== null) {
                                             throw new \Exception("Config cannot edit document.");
                                         }
                                     }
                                 }
                             }
                         }
                         $typeInfo->leave($node);
                         return Visitor::skipNode();
                     } else {
                         if ($result !== null) {
                             throw new \Exception("Config cannot edit document.");
                         }
                     }
                 }
             }
             // If any validation instances provide the flag `visitSpreadFragments`
             // and this node is a fragment spread, validate the fragment from
             // this point.
             if ($node instanceof FragmentSpread) {
                 $fragment = $context->getFragment($node->name->value);
                 if ($fragment) {
                     $fragVisitingInstances = [];
                     foreach ($instances as $idx => $inst) {
                         if (!empty($inst['visitSpreadFragments']) && !$skipUntil[$idx]) {
                             $fragVisitingInstances[] = $inst;
                         }
                     }
                     if (!empty($fragVisitingInstances)) {
                         $visitInstances($fragment, $fragVisitingInstances);
                     }
                 }
             }
         }, 'leave' => function ($node) use($instances, $typeInfo, $skipUntil, &$skipCount, &$errors) {
             for ($i = count($instances) - 1; $i >= 0; $i--) {
                 if ($skipUntil[$i]) {
                     if ($skipUntil[$i] === $node) {
                         $skipUntil[$i] = null;
                         $skipCount--;
                     }
                     continue;
                 }
                 $leaveFn = Visitor::getVisitFn($instances[$i], true, $node->kind);
                 if ($leaveFn) {
                     // $leaveFn = $leaveFn.bindTo($instances[$i]);
                     $result = call_user_func_array($leaveFn, func_get_args());
                     if ($result instanceof VisitorOperation) {
                         if ($result->doBreak) {
                             $instances[$i] = null;
                         }
                     } else {
                         if (self::isError($result)) {
                             self::append($errors, $result);
                         } else {
                             if ($result !== null) {
                                 throw new \Exception("Config cannot edit document.");
                             }
                         }
                     }
                 }
             }
             $typeInfo->leave($node);
         }]);
     };
     // Visit the whole document with instances of all provided rules.
     $allRuleInstances = [];
     foreach ($rules as $rule) {
         $allRuleInstances[] = $rule($context);
     }
     $visitInstances($documentAST, $allRuleInstances);
     return $errors;
 }