diff --git a/composer.json b/composer.json index 6145303..9679bbe 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ ], "require": { "php": "^7.1", + "ext-json": "*", "ext-mbstring": "*" }, "require-dev": { diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 4be54dd..aba1cb2 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -90,4 +90,11 @@ /> + + + + + + + diff --git a/src/Language/AST/ArgumentNode.php b/src/Language/AST/ArgumentNode.php index 3c942e7..1519429 100644 --- a/src/Language/AST/ArgumentNode.php +++ b/src/Language/AST/ArgumentNode.php @@ -1,17 +1,19 @@ start = $start; - $tmp->end = $end; + $tmp->end = $end; return $tmp; } - public function __construct(Token $startToken = null, Token $endToken = null, Source $source = null) + public function __construct(?Token $startToken = null, ?Token $endToken = null, ?Source $source = null) { $this->startToken = $startToken; - $this->endToken = $endToken; - $this->source = $source; + $this->endToken = $endToken; + $this->source = $source; - if ($startToken && $endToken) { - $this->start = $startToken->start; - $this->end = $endToken->end; + if (! $startToken || ! $endToken) { + return; } + + $this->start = $startToken->start; + $this->end = $endToken->end; } } diff --git a/src/Language/AST/NameNode.php b/src/Language/AST/NameNode.php index 7860775..91f6d05 100644 --- a/src/Language/AST/NameNode.php +++ b/src/Language/AST/NameNode.php @@ -1,12 +1,15 @@ $arrValue) { $cloned[$key] = $this->cloneValue($arrValue); } - } else if ($value instanceof Node) { + } elseif ($value instanceof self) { $cloned = clone $value; foreach (get_object_vars($cloned) as $prop => $propValue) { $cloned->{$prop} = $this->cloneValue($propValue); @@ -84,34 +88,34 @@ abstract class Node public function __toString() { $tmp = $this->toArray(true); + return (string) json_encode($tmp); } /** * @param bool $recursive - * @return array + * @return mixed[] */ public function toArray($recursive = false) { if ($recursive) { return $this->recursiveToArray($this); - } else { - $tmp = (array) $this; - - if ($this->loc) { - $tmp['loc'] = [ - 'start' => $this->loc->start, - 'end' => $this->loc->end - ]; - } - - return $tmp; } + + $tmp = (array) $this; + + if ($this->loc) { + $tmp['loc'] = [ + 'start' => $this->loc->start, + 'end' => $this->loc->end, + ]; + } + + return $tmp; } /** - * @param Node $node - * @return array + * @return mixed[] */ private function recursiveToArray(Node $node) { @@ -122,25 +126,27 @@ abstract class Node if ($node->loc) { $result['loc'] = [ 'start' => $node->loc->start, - 'end' => $node->loc->end + 'end' => $node->loc->end, ]; } foreach (get_object_vars($node) as $prop => $propValue) { - if (isset($result[$prop])) + if (isset($result[$prop])) { continue; + } - if ($propValue === null) + if ($propValue === null) { continue; + } if (is_array($propValue) || $propValue instanceof NodeList) { $tmp = []; foreach ($propValue as $tmp1) { $tmp[] = $tmp1 instanceof Node ? $this->recursiveToArray($tmp1) : (array) $tmp1; } - } else if ($propValue instanceof Node) { + } elseif ($propValue instanceof Node) { $tmp = $this->recursiveToArray($propValue); - } else if (is_scalar($propValue) || null === $propValue) { + } elseif (is_scalar($propValue) || $propValue === null) { $tmp = $propValue; } else { $tmp = null; @@ -148,6 +154,7 @@ abstract class Node $result[$prop] = $tmp; } + return $result; } } diff --git a/src/Language/AST/NodeKind.php b/src/Language/AST/NodeKind.php index 1091c6e..d321cdc 100644 --- a/src/Language/AST/NodeKind.php +++ b/src/Language/AST/NodeKind.php @@ -1,5 +1,7 @@ NameNode::class, + self::NAME => NameNode::class, // Document - NodeKind::DOCUMENT => DocumentNode::class, - NodeKind::OPERATION_DEFINITION => OperationDefinitionNode::class, - NodeKind::VARIABLE_DEFINITION => VariableDefinitionNode::class, - NodeKind::VARIABLE => VariableNode::class, - NodeKind::SELECTION_SET => SelectionSetNode::class, - NodeKind::FIELD => FieldNode::class, - NodeKind::ARGUMENT => ArgumentNode::class, + self::DOCUMENT => DocumentNode::class, + self::OPERATION_DEFINITION => OperationDefinitionNode::class, + self::VARIABLE_DEFINITION => VariableDefinitionNode::class, + self::VARIABLE => VariableNode::class, + self::SELECTION_SET => SelectionSetNode::class, + self::FIELD => FieldNode::class, + self::ARGUMENT => ArgumentNode::class, // Fragments - NodeKind::FRAGMENT_SPREAD => FragmentSpreadNode::class, - NodeKind::INLINE_FRAGMENT => InlineFragmentNode::class, - NodeKind::FRAGMENT_DEFINITION => FragmentDefinitionNode::class, + self::FRAGMENT_SPREAD => FragmentSpreadNode::class, + self::INLINE_FRAGMENT => InlineFragmentNode::class, + self::FRAGMENT_DEFINITION => FragmentDefinitionNode::class, // Values - NodeKind::INT => IntValueNode::class, - NodeKind::FLOAT => FloatValueNode::class, - NodeKind::STRING => StringValueNode::class, - NodeKind::BOOLEAN => BooleanValueNode::class, - NodeKind::ENUM => EnumValueNode::class, - NodeKind::NULL => NullValueNode::class, - NodeKind::LST => ListValueNode::class, - NodeKind::OBJECT => ObjectValueNode::class, - NodeKind::OBJECT_FIELD => ObjectFieldNode::class, + self::INT => IntValueNode::class, + self::FLOAT => FloatValueNode::class, + self::STRING => StringValueNode::class, + self::BOOLEAN => BooleanValueNode::class, + self::ENUM => EnumValueNode::class, + self::NULL => NullValueNode::class, + self::LST => ListValueNode::class, + self::OBJECT => ObjectValueNode::class, + self::OBJECT_FIELD => ObjectFieldNode::class, // Directives - NodeKind::DIRECTIVE => DirectiveNode::class, + self::DIRECTIVE => DirectiveNode::class, // Types - NodeKind::NAMED_TYPE => NamedTypeNode::class, - NodeKind::LIST_TYPE => ListTypeNode::class, - NodeKind::NON_NULL_TYPE => NonNullTypeNode::class, + self::NAMED_TYPE => NamedTypeNode::class, + self::LIST_TYPE => ListTypeNode::class, + self::NON_NULL_TYPE => NonNullTypeNode::class, // Type System Definitions - NodeKind::SCHEMA_DEFINITION => SchemaDefinitionNode::class, - NodeKind::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class, + self::SCHEMA_DEFINITION => SchemaDefinitionNode::class, + self::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class, // Type Definitions - NodeKind::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class, - NodeKind::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class, - NodeKind::FIELD_DEFINITION => FieldDefinitionNode::class, - NodeKind::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class, - NodeKind::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class, - NodeKind::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class, - NodeKind::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class, - NodeKind::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class, - NodeKind::INPUT_OBJECT_TYPE_DEFINITION =>InputObjectTypeDefinitionNode::class, + self::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class, + self::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class, + self::FIELD_DEFINITION => FieldDefinitionNode::class, + self::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class, + self::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class, + self::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class, + self::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class, + self::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class, + self::INPUT_OBJECT_TYPE_DEFINITION => InputObjectTypeDefinitionNode::class, // Type Extensions - NodeKind::SCALAR_TYPE_EXTENSION => ScalarTypeExtensionNode::class, - NodeKind::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class, - NodeKind::INTERFACE_TYPE_EXTENSION => InterfaceTypeExtensionNode::class, - NodeKind::UNION_TYPE_EXTENSION => UnionTypeExtensionNode::class, - NodeKind::ENUM_TYPE_EXTENSION => EnumTypeExtensionNode::class, - NodeKind::INPUT_OBJECT_TYPE_EXTENSION => InputObjectTypeExtensionNode::class, + self::SCALAR_TYPE_EXTENSION => ScalarTypeExtensionNode::class, + self::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class, + self::INTERFACE_TYPE_EXTENSION => InterfaceTypeExtensionNode::class, + self::UNION_TYPE_EXTENSION => UnionTypeExtensionNode::class, + self::ENUM_TYPE_EXTENSION => EnumTypeExtensionNode::class, + self::INPUT_OBJECT_TYPE_EXTENSION => InputObjectTypeExtensionNode::class, // Directive Definitions - NodeKind::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class + self::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class, ]; } diff --git a/src/Language/AST/NodeList.php b/src/Language/AST/NodeList.php index b0adb81..5028022 100644 --- a/src/Language/AST/NodeList.php +++ b/src/Language/AST/NodeList.php @@ -1,22 +1,22 @@ nodes; } return new NodeList(array_merge($this->nodes, $list)); diff --git a/src/Language/AST/NonNullTypeNode.php b/src/Language/AST/NonNullTypeNode.php index e450404..da0ce3d 100644 --- a/src/Language/AST/NonNullTypeNode.php +++ b/src/Language/AST/NonNullTypeNode.php @@ -1,12 +1,15 @@ self::QUERY, self::MUTATION => self::MUTATION, diff --git a/src/Language/Lexer.php b/src/Language/Lexer.php index 835b947..223b6f8 100644 --- a/src/Language/Lexer.php +++ b/src/Language/Lexer.php @@ -1,9 +1,16 @@ source = $source; - $this->options = $options; + $this->source = $source; + $this->options = $options; $this->lastToken = $startOfFileToken; - $this->token = $startOfFileToken; - $this->line = 1; + $this->token = $startOfFileToken; + $this->line = 1; $this->lineStart = 0; - $this->position = $this->byteStreamPosition = 0; + $this->position = $this->byteStreamPosition = 0; } /** @@ -93,7 +93,8 @@ class Lexer public function advance() { $this->lastToken = $this->token; - $token = $this->token = $this->lookahead(); + $token = $this->token = $this->lookahead(); + return $token; } @@ -105,11 +106,11 @@ class Lexer $token = $token->next ?: ($token->next = $this->readToken($token)); } while ($token->kind === Token::COMMENT); } + return $token; } /** - * @param Token $prev * @return Token * @throws SyntaxError */ @@ -121,7 +122,7 @@ class Lexer $position = $this->position; $line = $this->line; - $col = 1 + $position - $this->lineStart; + $col = 1 + $position - $this->lineStart; if ($position >= $bodyLength) { return new Token(Token::EOF, $bodyLength, $bodyLength, $line, $col, $prev); @@ -144,6 +145,7 @@ class Lexer return new Token(Token::BANG, $position, $position + 1, $line, $col, $prev); case 35: // # $this->moveStringCursor(-1, -1 * $bytes); + return $this->readComment($line, $col, $prev); case 36: // $ return new Token(Token::DOLLAR, $position, $position + 1, $line, $col, $prev); @@ -178,30 +180,82 @@ class Lexer case 125: // } return new Token(Token::BRACE_R, $position, $position + 1, $line, $col, $prev); // A-Z - case 65: case 66: case 67: case 68: case 69: case 70: case 71: case 72: - case 73: case 74: case 75: case 76: case 77: case 78: case 79: case 80: - case 81: case 82: case 83: case 84: case 85: case 86: case 87: case 88: - case 89: case 90: - // _ + case 65: + case 66: + case 67: + case 68: + case 69: + case 70: + case 71: + case 72: + case 73: + case 74: + case 75: + case 76: + case 77: + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + case 84: + case 85: + case 86: + case 87: + case 88: + case 89: + case 90: + // _ case 95: - // a-z - case 97: case 98: case 99: case 100: case 101: case 102: case 103: case 104: - case 105: case 106: case 107: case 108: case 109: case 110: case 111: - case 112: case 113: case 114: case 115: case 116: case 117: case 118: - case 119: case 120: case 121: case 122: + // a-z + case 97: + case 98: + case 99: + case 100: + case 101: + case 102: + case 103: + case 104: + case 105: + case 106: + case 107: + case 108: + case 109: + case 110: + case 111: + case 112: + case 113: + case 114: + case 115: + case 116: + case 117: + case 118: + case 119: + case 120: + case 121: + case 122: return $this->moveStringCursor(-1, -1 * $bytes) ->readName($line, $col, $prev); // - case 45: - // 0-9 - case 48: case 49: case 50: case 51: case 52: - case 53: case 54: case 55: case 56: case 57: + // 0-9 + case 48: + case 49: + case 50: + case 51: + case 52: + case 53: + case 54: + case 55: + case 56: + case 57: return $this->moveStringCursor(-1, -1 * $bytes) ->readNumber($line, $col, $prev); // " case 34: - list(,$nextCode) = $this->readChar(); - list(,$nextNextCode) = $this->moveStringCursor(1, 1)->readChar(); + list(, $nextCode) = $this->readChar(); + list(, $nextNextCode) = $this->moveStringCursor(1, 1)->readChar(); if ($nextCode === 34 && $nextNextCode === 34) { return $this->moveStringCursor(-2, (-1 * $bytes) - 1) @@ -213,7 +267,7 @@ class Lexer } $errMessage = $code === 39 - ? "Unexpected single quote character ('), did you mean to use ". 'a double quote (")?' + ? "Unexpected single quote character ('), did you mean to use " . 'a double quote (")?' : 'Cannot parse the unexpected character ' . Utils::printCharCode($code) . '.'; throw new SyntaxError( @@ -230,24 +284,24 @@ class Lexer * * @param int $line * @param int $col - * @param Token $prev * @return Token */ private function readName($line, $col, Token $prev) { - $value = ''; - $start = $this->position; + $value = ''; + $start = $this->position; list ($char, $code) = $this->readChar(); while ($code && ( - $code === 95 || // _ - $code >= 48 && $code <= 57 || // 0-9 - $code >= 65 && $code <= 90 || // A-Z - $code >= 97 && $code <= 122 // a-z - )) { - $value .= $char; + $code === 95 || // _ + $code >= 48 && $code <= 57 || // 0-9 + $code >= 65 && $code <= 90 || // A-Z + $code >= 97 && $code <= 122 // a-z + )) { + $value .= $char; list ($char, $code) = $this->moveStringCursor(1, 1)->readChar(); } + return new Token( Token::NAME, $start, @@ -268,33 +322,36 @@ class Lexer * * @param int $line * @param int $col - * @param Token $prev * @return Token * @throws SyntaxError */ private function readNumber($line, $col, Token $prev) { - $value = ''; - $start = $this->position; + $value = ''; + $start = $this->position; list ($char, $code) = $this->readChar(); $isFloat = false; if ($code === 45) { // - - $value .= $char; + $value .= $char; list ($char, $code) = $this->moveStringCursor(1, 1)->readChar(); } // guard against leading zero's if ($code === 48) { // 0 - $value .= $char; + $value .= $char; list ($char, $code) = $this->moveStringCursor(1, 1)->readChar(); if ($code >= 48 && $code <= 57) { - throw new SyntaxError($this->source, $this->position, "Invalid number, unexpected digit after 0: " . Utils::printCharCode($code)); + throw new SyntaxError( + $this->source, + $this->position, + 'Invalid number, unexpected digit after 0: ' . Utils::printCharCode($code) + ); } } else { - $value .= $this->readDigits(); + $value .= $this->readDigits(); list ($char, $code) = $this->readChar(); } @@ -302,14 +359,14 @@ class Lexer $isFloat = true; $this->moveStringCursor(1, 1); - $value .= $char; - $value .= $this->readDigits(); + $value .= $char; + $value .= $this->readDigits(); list ($char, $code) = $this->readChar(); } if ($code === 69 || $code === 101) { // E e - $isFloat = true; - $value .= $char; + $isFloat = true; + $value .= $char; list ($char, $code) = $this->moveStringCursor(1, 1)->readChar(); if ($code === 43 || $code === 45) { // + - @@ -341,7 +398,7 @@ class Lexer $value = ''; do { - $value .= $char; + $value .= $char; list ($char, $code) = $this->moveStringCursor(1, 1)->readChar(); } while ($code >= 48 && $code <= 57); // 0 - 9 @@ -362,7 +419,6 @@ class Lexer /** * @param int $line * @param int $col - * @param Token $prev * @return Token * @throws SyntaxError */ @@ -371,13 +427,12 @@ class Lexer $start = $this->position; // Skip leading quote and read first string char: - list ($char, $code, $bytes) = $this->moveStringCursor(1, 1)->readChar(); + [$char, $code, $bytes] = $this->moveStringCursor(1, 1)->readChar(); $chunk = ''; $value = ''; - while ( - $code !== null && + while ($code !== null && // not LineTerminator $code !== 10 && $code !== 13 ) { @@ -403,22 +458,38 @@ class Lexer $this->moveStringCursor(1, $bytes); if ($code === 92) { // \ - $value .= $chunk; + $value .= $chunk; list (, $code) = $this->readChar(true); switch ($code) { - case 34: $value .= '"'; break; - case 47: $value .= '/'; break; - case 92: $value .= '\\'; break; - case 98: $value .= chr(8); break; // \b (backspace) - case 102: $value .= "\f"; break; - case 110: $value .= "\n"; break; - case 114: $value .= "\r"; break; - case 116: $value .= "\t"; break; + case 34: + $value .= '"'; + break; + case 47: + $value .= '/'; + break; + case 92: + $value .= '\\'; + break; + case 98: + $value .= chr(8); + break; // \b (backspace) + case 102: + $value .= "\f"; + break; + case 110: + $value .= "\n"; + break; + case 114: + $value .= "\r"; + break; + case 116: + $value .= "\t"; + break; case 117: - $position = $this->position; + $position = $this->position; list ($hex) = $this->readChars(4, true); - if (!preg_match('/[0-9a-fA-F]{4}/', $hex)) { + if (! preg_match('/[0-9a-fA-F]{4}/', $hex)) { throw new SyntaxError( $this->source, $position - 1, @@ -470,8 +541,8 @@ class Lexer // Closing Triple-Quote (""") if ($code === 34) { // Move 2 quotes - list(,$nextCode) = $this->moveStringCursor(1, 1)->readChar(); - list(,$nextNextCode) = $this->moveStringCursor(1, 1)->readChar(); + list(, $nextCode) = $this->moveStringCursor(1, 1)->readChar(); + list(, $nextNextCode) = $this->moveStringCursor(1, 1)->readChar(); if ($nextCode === 34 && $nextNextCode === 34) { $value .= $chunk; @@ -496,9 +567,9 @@ class Lexer $this->assertValidBlockStringCharacterCode($code, $this->position); $this->moveStringCursor(1, $bytes); - list(,$nextCode) = $this->readChar(); - list(,$nextNextCode) = $this->moveStringCursor(1, 1)->readChar(); - list(,$nextNextNextCode) = $this->moveStringCursor(1, 1)->readChar(); + list(, $nextCode) = $this->readChar(); + list(, $nextNextCode) = $this->moveStringCursor(1, 1)->readChar(); + list(, $nextNextNextCode) = $this->moveStringCursor(1, 1)->readChar(); // Escape Triple-Quote (\""") if ($code === 92 && @@ -508,7 +579,7 @@ class Lexer ) { $this->moveStringCursor(1, 1); $value .= $chunk . '"""'; - $chunk = ''; + $chunk = ''; } else { $this->moveStringCursor(-2, -2); $chunk .= $char; @@ -561,11 +632,11 @@ class Lexer // tab | space | comma | BOM if ($code === 9 || $code === 32 || $code === 44 || $code === 0xFEFF) { $this->moveStringCursor(1, $bytes); - } else if ($code === 10) { // new line + } elseif ($code === 10) { // new line $this->moveStringCursor(1, $bytes); $this->line++; $this->lineStart = $this->position; - } else if ($code === 13) { // carriage return + } elseif ($code === 13) { // carriage return list(, $nextCode, $nextBytes) = $this->moveStringCursor(1, $bytes)->readChar(); if ($nextCode === 10) { // lf after cr @@ -584,9 +655,8 @@ class Lexer * * #[\u0009\u0020-\uFFFF]* * - * @param $line - * @param $col - * @param Token $prev + * @param int $line + * @param int $col * @return Token */ private function readComment($line, $col, Token $prev) @@ -597,11 +667,10 @@ class Lexer do { list ($char, $code, $bytes) = $this->moveStringCursor(1, $bytes)->readChar(); - $value .= $char; - } while ( - $code && - // SourceCharacter but not LineTerminator - ($code > 0x001F || $code === 0x0009) + $value .= $char; + } while ($code && + // SourceCharacter but not LineTerminator + ($code > 0x001F || $code === 0x0009) ); return new Token( @@ -619,8 +688,8 @@ class Lexer * Reads next UTF8Character from the byte stream, starting from $byteStreamPosition. * * @param bool $advance - * @param int $byteStreamPosition - * @return array + * @param int $byteStreamPosition + * @return (string|int)[] */ private function readChar($advance = false, $byteStreamPosition = null) { @@ -628,9 +697,9 @@ class Lexer $byteStreamPosition = $this->byteStreamPosition; } - $code = null; - $utf8char = ''; - $bytes = 0; + $code = null; + $utf8char = ''; + $bytes = 0; $positionOffset = 0; if (isset($this->source->body[$byteStreamPosition])) { @@ -638,7 +707,7 @@ class Lexer if ($ord < 128) { $bytes = 1; - } else if ($ord < 224) { + } elseif ($ord < 224) { $bytes = 2; } elseif ($ord < 240) { $bytes = 3; @@ -651,7 +720,7 @@ class Lexer $utf8char .= $this->source->body[$pos]; } $positionOffset = 1; - $code = $bytes === 1 ? $ord : Utils::ord($utf8char); + $code = $bytes === 1 ? $ord : Utils::ord($utf8char); } if ($advance) { @@ -664,40 +733,42 @@ class Lexer /** * Reads next $numberOfChars UTF8 characters from the byte stream, starting from $byteStreamPosition. * - * @param $numberOfChars + * @param int $charCount * @param bool $advance * @param null $byteStreamPosition - * @return array + * @return (string|int)[] */ - private function readChars($numberOfChars, $advance = false, $byteStreamPosition = null) + private function readChars($charCount, $advance = false, $byteStreamPosition = null) { - $result = ''; + $result = ''; $totalBytes = 0; $byteOffset = $byteStreamPosition ?: $this->byteStreamPosition; - for ($i = 0; $i < $numberOfChars; $i++) { + for ($i = 0; $i < $charCount; $i++) { list ($char, $code, $bytes) = $this->readChar(false, $byteOffset); - $totalBytes += $bytes; - $byteOffset += $bytes; - $result .= $char; + $totalBytes += $bytes; + $byteOffset += $bytes; + $result .= $char; } if ($advance) { - $this->moveStringCursor($numberOfChars, $totalBytes); + $this->moveStringCursor($charCount, $totalBytes); } + return [$result, $totalBytes]; } /** * Moves internal string cursor position * - * @param $positionOffset - * @param $byteStreamOffset + * @param int $positionOffset + * @param int $byteStreamOffset * @return self */ private function moveStringCursor($positionOffset, $byteStreamOffset) { - $this->position += $positionOffset; + $this->position += $positionOffset; $this->byteStreamPosition += $byteStreamOffset; + return $this; } } diff --git a/src/Language/Parser.php b/src/Language/Parser.php index b920583..6f0ce0a 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -1,38 +1,43 @@ parseDocument(); } @@ -121,16 +127,17 @@ class Parser * * @api * @param Source|string $source - * @param array $options + * @param bool[] $options * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode */ public static function parseValue($source, array $options = []) { $sourceObj = $source instanceof Source ? $source : new Source($source); - $parser = new Parser($sourceObj, $options); + $parser = new Parser($sourceObj, $options); $parser->expect(Token::SOF); $value = $parser->parseValueLiteral(false); $parser->expect(Token::EOF); + return $value; } @@ -146,30 +153,28 @@ class Parser * * @api * @param Source|string $source - * @param array $options + * @param bool[] $options * @return ListTypeNode|NameNode|NonNullTypeNode */ public static function parseType($source, array $options = []) { $sourceObj = $source instanceof Source ? $source : new Source($source); - $parser = new Parser($sourceObj, $options); + $parser = new Parser($sourceObj, $options); $parser->expect(Token::SOF); $type = $parser->parseTypeReference(); $parser->expect(Token::EOF); + return $type; } - /** - * @var Lexer - */ + /** @var Lexer */ private $lexer; /** - * Parser constructor. - * @param Source $source - * @param array $options + * + * @param bool[] $options */ - function __construct(Source $source, array $options = []) + public function __construct(Source $source, array $options = []) { $this->lexer = new Lexer($source, $options); } @@ -178,24 +183,24 @@ class Parser * Returns a location object, used to identify the place in * the source that created a given parsed object. * - * @param Token $startToken * @return Location|null */ - function loc(Token $startToken) + private function loc(Token $startToken) { if (empty($this->lexer->options['noLocation'])) { return new Location($startToken, $this->lexer->lastToken, $this->lexer->source); } + return null; } /** * Determines if the next token is of a given kind * - * @param $kind + * @param string $kind * @return bool */ - function peek($kind) + private function peek($kind) { return $this->lexer->token->kind === $kind; } @@ -204,16 +209,17 @@ class Parser * If the next token is of the given kind, return true after advancing * the parser. Otherwise, do not change the parser state and return false. * - * @param $kind + * @param string $kind * @return bool */ - function skip($kind) + private function skip($kind) { $match = $this->lexer->token->kind === $kind; if ($match) { $this->lexer->advance(); } + return $match; } @@ -224,19 +230,20 @@ class Parser * @return Token * @throws SyntaxError */ - function expect($kind) + private function expect($kind) { $token = $this->lexer->token; if ($token->kind === $kind) { $this->lexer->advance(); + return $token; } throw new SyntaxError( $this->lexer->source, $token->start, - "Expected $kind, found " . $token->getDescription() + sprintf('Expected %s, found %s', $kind, $token->getDescription()) ); } @@ -249,12 +256,13 @@ class Parser * @return Token * @throws SyntaxError */ - function expectKeyword($value) + private function expectKeyword($value) { $token = $this->lexer->token; if ($token->kind === Token::NAME && $token->value === $value) { $this->lexer->advance(); + return $token; } throw new SyntaxError( @@ -265,13 +273,13 @@ class Parser } /** - * @param Token|null $atToken * @return SyntaxError */ - function unexpected(Token $atToken = null) + private function unexpected(?Token $atToken = null) { $token = $atToken ?: $this->lexer->token; - return new SyntaxError($this->lexer->source, $token->start, "Unexpected " . $token->getDescription()); + + return new SyntaxError($this->lexer->source, $token->start, 'Unexpected ' . $token->getDescription()); } /** @@ -280,20 +288,21 @@ class Parser * and ends with a lex token of closeKind. Advances the parser * to the next lex token after the closing token. * - * @param int $openKind + * @param string $openKind * @param callable $parseFn - * @param int $closeKind + * @param string $closeKind * @return NodeList * @throws SyntaxError */ - function any($openKind, $parseFn, $closeKind) + private function any($openKind, $parseFn, $closeKind) { $this->expect($openKind); $nodes = []; - while (!$this->skip($closeKind)) { + while (! $this->skip($closeKind)) { $nodes[] = $parseFn($this); } + return new NodeList($nodes); } @@ -303,20 +312,21 @@ class Parser * and ends with a lex token of closeKind. Advances the parser * to the next lex token after the closing token. * - * @param $openKind - * @param $parseFn - * @param $closeKind + * @param string $openKind + * @param callable $parseFn + * @param string $closeKind * @return NodeList * @throws SyntaxError */ - function many($openKind, $parseFn, $closeKind) + private function many($openKind, $parseFn, $closeKind) { $this->expect($openKind); $nodes = [$parseFn($this)]; - while (!$this->skip($closeKind)) { + while (! $this->skip($closeKind)) { $nodes[] = $parseFn($this); } + return new NodeList($nodes); } @@ -326,13 +336,13 @@ class Parser * @return NameNode * @throws SyntaxError */ - function parseName() + private function parseName() { $token = $this->expect(Token::NAME); return new NameNode([ 'value' => $token->value, - 'loc' => $this->loc($token) + 'loc' => $this->loc($token), ]); } @@ -342,7 +352,7 @@ class Parser * @return DocumentNode * @throws SyntaxError */ - function parseDocument() + private function parseDocument() { $start = $this->lexer->token; $this->expect(Token::SOF); @@ -350,11 +360,11 @@ class Parser $definitions = []; do { $definitions[] = $this->parseDefinition(); - } while (!$this->skip(Token::EOF)); + } while (! $this->skip(Token::EOF)); return new DocumentNode([ 'definitions' => new NodeList($definitions), - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } @@ -362,7 +372,7 @@ class Parser * @return ExecutableDefinitionNode|TypeSystemDefinitionNode * @throws SyntaxError */ - function parseDefinition() + private function parseDefinition() { if ($this->peek(Token::NAME)) { switch ($this->lexer->token->value) { @@ -385,9 +395,9 @@ class Parser // Note: The schema definition language is an experimental addition. return $this->parseTypeSystemDefinition(); } - } else if ($this->peek(Token::BRACE_L)) { + } elseif ($this->peek(Token::BRACE_L)) { return $this->parseExecutableDefinition(); - } else if ($this->peekDescription()) { + } elseif ($this->peekDescription()) { // Note: The schema definition language is an experimental addition. return $this->parseTypeSystemDefinition(); } @@ -399,7 +409,7 @@ class Parser * @return ExecutableDefinitionNode * @throws SyntaxError */ - function parseExecutableDefinition() + private function parseExecutableDefinition() { if ($this->peek(Token::NAME)) { switch ($this->lexer->token->value) { @@ -411,7 +421,7 @@ class Parser case 'fragment': return $this->parseFragmentDefinition(); } - } else if ($this->peek(Token::BRACE_L)) { + } elseif ($this->peek(Token::BRACE_L)) { return $this->parseOperationDefinition(); } @@ -424,17 +434,17 @@ class Parser * @return OperationDefinitionNode * @throws SyntaxError */ - function parseOperationDefinition() + private function parseOperationDefinition() { $start = $this->lexer->token; if ($this->peek(Token::BRACE_L)) { return new OperationDefinitionNode([ - 'operation' => 'query', - 'name' => null, + 'operation' => 'query', + 'name' => null, 'variableDefinitions' => new NodeList([]), - 'directives' => new NodeList([]), - 'selectionSet' => $this->parseSelectionSet(), - 'loc' => $this->loc($start) + 'directives' => new NodeList([]), + 'selectionSet' => $this->parseSelectionSet(), + 'loc' => $this->loc($start), ]); } @@ -446,12 +456,12 @@ class Parser } return new OperationDefinitionNode([ - 'operation' => $operation, - 'name' => $name, + 'operation' => $operation, + 'name' => $name, 'variableDefinitions' => $this->parseVariableDefinitions(), - 'directives' => $this->parseDirectives(false), - 'selectionSet' => $this->parseSelectionSet(), - 'loc' => $this->loc($start) + 'directives' => $this->parseDirectives(false), + 'selectionSet' => $this->parseSelectionSet(), + 'loc' => $this->loc($start), ]); } @@ -459,13 +469,16 @@ class Parser * @return string * @throws SyntaxError */ - function parseOperationType() + private function parseOperationType() { $operationToken = $this->expect(Token::NAME); switch ($operationToken->value) { - case 'query': return 'query'; - case 'mutation': return 'mutation'; - case 'subscription': return 'subscription'; + case 'query': + return 'query'; + case 'mutation': + return 'mutation'; + case 'subscription': + return 'subscription'; } throw $this->unexpected($operationToken); @@ -474,12 +487,14 @@ class Parser /** * @return VariableDefinitionNode[]|NodeList */ - function parseVariableDefinitions() + private function parseVariableDefinitions() { return $this->peek(Token::PAREN_L) ? $this->many( Token::PAREN_L, - [$this, 'parseVariableDefinition'], + function () { + return $this->parseVariableDefinition(); + }, Token::PAREN_R ) : new NodeList([]); @@ -489,20 +504,20 @@ class Parser * @return VariableDefinitionNode * @throws SyntaxError */ - function parseVariableDefinition() + private function parseVariableDefinition() { $start = $this->lexer->token; - $var = $this->parseVariable(); + $var = $this->parseVariable(); $this->expect(Token::COLON); $type = $this->parseTypeReference(); return new VariableDefinitionNode([ - 'variable' => $var, - 'type' => $type, + 'variable' => $var, + 'type' => $type, 'defaultValue' => ($this->skip(Token::EQUALS) ? $this->parseValueLiteral(true) : null), - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } @@ -510,27 +525,36 @@ class Parser * @return VariableNode * @throws SyntaxError */ - function parseVariable() + private function parseVariable() { $start = $this->lexer->token; $this->expect(Token::DOLLAR); return new VariableNode([ 'name' => $this->parseName(), - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } /** * @return SelectionSetNode */ - function parseSelectionSet() + private function parseSelectionSet() { $start = $this->lexer->token; - return new SelectionSetNode([ - 'selections' => $this->many(Token::BRACE_L, [$this, 'parseSelection'], Token::BRACE_R), - 'loc' => $this->loc($start) - ]); + + return new SelectionSetNode( + [ + 'selections' => $this->many( + Token::BRACE_L, + function () { + return $this->parseSelection(); + }, + Token::BRACE_R + ), + 'loc' => $this->loc($start), + ] + ); } /** @@ -541,7 +565,7 @@ class Parser * * @return mixed */ - function parseSelection() + private function parseSelection() { return $this->peek(Token::SPREAD) ? $this->parseFragment() : @@ -552,26 +576,26 @@ class Parser * @return FieldNode * @throws SyntaxError */ - function parseField() + private function parseField() { - $start = $this->lexer->token; + $start = $this->lexer->token; $nameOrAlias = $this->parseName(); if ($this->skip(Token::COLON)) { $alias = $nameOrAlias; - $name = $this->parseName(); + $name = $this->parseName(); } else { $alias = null; - $name = $nameOrAlias; + $name = $nameOrAlias; } return new FieldNode([ - 'alias' => $alias, - 'name' => $name, - 'arguments' => $this->parseArguments(false), - 'directives' => $this->parseDirectives(false), + 'alias' => $alias, + 'name' => $name, + 'arguments' => $this->parseArguments(false), + 'directives' => $this->parseDirectives(false), 'selectionSet' => $this->peek(Token::BRACE_L) ? $this->parseSelectionSet() : null, - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } @@ -580,11 +604,18 @@ class Parser * @return ArgumentNode[]|NodeList * @throws SyntaxError */ - function parseArguments($isConst) + private function parseArguments($isConst) { - $item = $isConst ? 'parseConstArgument' : 'parseArgument'; + $parseFn = $isConst ? + function () { + return $this->parseConstArgument(); + } : + function () { + return $this->parseArgument(); + }; + return $this->peek(Token::PAREN_L) ? - $this->many(Token::PAREN_L, [$this, $item], Token::PAREN_R) : + $this->many(Token::PAREN_L, $parseFn, Token::PAREN_R) : new NodeList([]); } @@ -592,18 +623,18 @@ class Parser * @return ArgumentNode * @throws SyntaxError */ - function parseArgument() + private function parseArgument() { $start = $this->lexer->token; - $name = $this->parseName(); + $name = $this->parseName(); $this->expect(Token::COLON); $value = $this->parseValueLiteral(false); return new ArgumentNode([ - 'name' => $name, + 'name' => $name, 'value' => $value, - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } @@ -611,18 +642,18 @@ class Parser * @return ArgumentNode * @throws SyntaxError */ - function parseConstArgument() + private function parseConstArgument() { $start = $this->lexer->token; - $name = $this->parseName(); + $name = $this->parseName(); $this->expect(Token::COLON); $value = $this->parseConstValue(); return new ArgumentNode([ - 'name' => $name, + 'name' => $name, 'value' => $value, - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } @@ -632,16 +663,16 @@ class Parser * @return FragmentSpreadNode|InlineFragmentNode * @throws SyntaxError */ - function parseFragment() + private function parseFragment() { $start = $this->lexer->token; $this->expect(Token::SPREAD); if ($this->peek(Token::NAME) && $this->lexer->token->value !== 'on') { return new FragmentSpreadNode([ - 'name' => $this->parseFragmentName(), + 'name' => $this->parseFragmentName(), 'directives' => $this->parseDirectives(false), - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } @@ -653,9 +684,9 @@ class Parser return new InlineFragmentNode([ 'typeCondition' => $typeCondition, - 'directives' => $this->parseDirectives(false), - 'selectionSet' => $this->parseSelectionSet(), - 'loc' => $this->loc($start) + 'directives' => $this->parseDirectives(false), + 'selectionSet' => $this->parseSelectionSet(), + 'loc' => $this->loc($start), ]); } @@ -663,7 +694,7 @@ class Parser * @return FragmentDefinitionNode * @throws SyntaxError */ - function parseFragmentDefinition() + private function parseFragmentDefinition() { $start = $this->lexer->token; $this->expectKeyword('fragment'); @@ -679,13 +710,14 @@ class Parser } $this->expectKeyword('on'); $typeCondition = $this->parseNamedType(); + return new FragmentDefinitionNode([ - 'name' => $name, + 'name' => $name, 'variableDefinitions' => $variableDefinitions, - 'typeCondition' => $typeCondition, - 'directives' => $this->parseDirectives(false), - 'selectionSet' => $this->parseSelectionSet(), - 'loc' => $this->loc($start) + 'typeCondition' => $typeCondition, + 'directives' => $this->parseDirectives(false), + 'selectionSet' => $this->parseSelectionSet(), + 'loc' => $this->loc($start), ]); } @@ -693,11 +725,12 @@ class Parser * @return NameNode * @throws SyntaxError */ - function parseFragmentName() + private function parseFragmentName() { if ($this->lexer->token->value === 'on') { throw $this->unexpected(); } + return $this->parseName(); } @@ -721,11 +754,11 @@ class Parser * * EnumValue : Name but not `true`, `false` or `null` * - * @param $isConst + * @param bool $isConst * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode|ListValueNode|ObjectValueNode|NullValueNode * @throws SyntaxError */ - function parseValueLiteral($isConst) + private function parseValueLiteral($isConst) { $token = $this->lexer->token; switch ($token->kind) { @@ -735,15 +768,17 @@ class Parser return $this->parseObject($isConst); case Token::INT: $this->lexer->advance(); + return new IntValueNode([ 'value' => $token->value, - 'loc' => $this->loc($token) + 'loc' => $this->loc($token), ]); case Token::FLOAT: $this->lexer->advance(); + return new FloatValueNode([ 'value' => $token->value, - 'loc' => $this->loc($token) + 'loc' => $this->loc($token), ]); case Token::STRING: case Token::BLOCK_STRING: @@ -751,26 +786,29 @@ class Parser case Token::NAME: if ($token->value === 'true' || $token->value === 'false') { $this->lexer->advance(); + return new BooleanValueNode([ 'value' => $token->value === 'true', - 'loc' => $this->loc($token) + 'loc' => $this->loc($token), ]); - } else if ($token->value === 'null') { + } elseif ($token->value === 'null') { $this->lexer->advance(); + return new NullValueNode([ - 'loc' => $this->loc($token) + 'loc' => $this->loc($token), ]); } else { $this->lexer->advance(); + return new EnumValueNode([ 'value' => $token->value, - 'loc' => $this->loc($token) + 'loc' => $this->loc($token), ]); } break; case Token::DOLLAR: - if (!$isConst) { + if (! $isConst) { return $this->parseVariable(); } break; @@ -781,14 +819,15 @@ class Parser /** * @return StringValueNode */ - function parseStringLiteral() { + private function parseStringLiteral() + { $token = $this->lexer->token; $this->lexer->advance(); return new StringValueNode([ 'value' => $token->value, 'block' => $token->kind === Token::BLOCK_STRING, - 'loc' => $this->loc($token) + 'loc' => $this->loc($token), ]); } @@ -796,7 +835,7 @@ class Parser * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode * @throws SyntaxError */ - function parseConstValue() + private function parseConstValue() { return $this->parseValueLiteral(true); } @@ -804,7 +843,7 @@ class Parser /** * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode */ - function parseVariableValue() + private function parseVariableValue() { return $this->parseValueLiteral(false); } @@ -813,49 +852,57 @@ class Parser * @param bool $isConst * @return ListValueNode */ - function parseArray($isConst) + private function parseArray($isConst) { - $start = $this->lexer->token; - $item = $isConst ? 'parseConstValue' : 'parseVariableValue'; - return new ListValueNode([ - 'values' => $this->any(Token::BRACKET_L, [$this, $item], Token::BRACKET_R), - 'loc' => $this->loc($start) - ]); + $start = $this->lexer->token; + $parseFn = $isConst ? function () { + return $this->parseConstValue(); + } : function () { + return $this->parseVariableValue(); + }; + + return new ListValueNode( + [ + 'values' => $this->any(Token::BRACKET_L, $parseFn, Token::BRACKET_R), + 'loc' => $this->loc($start), + ] + ); } /** - * @param $isConst + * @param bool $isConst * @return ObjectValueNode */ - function parseObject($isConst) + private function parseObject($isConst) { $start = $this->lexer->token; $this->expect(Token::BRACE_L); $fields = []; - while (!$this->skip(Token::BRACE_R)) { + while (! $this->skip(Token::BRACE_R)) { $fields[] = $this->parseObjectField($isConst); } + return new ObjectValueNode([ 'fields' => new NodeList($fields), - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } /** - * @param $isConst + * @param bool $isConst * @return ObjectFieldNode */ - function parseObjectField($isConst) + private function parseObjectField($isConst) { $start = $this->lexer->token; - $name = $this->parseName(); + $name = $this->parseName(); $this->expect(Token::COLON); return new ObjectFieldNode([ - 'name' => $name, + 'name' => $name, 'value' => $this->parseValueLiteral($isConst), - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } @@ -866,12 +913,13 @@ class Parser * @return DirectiveNode[]|NodeList * @throws SyntaxError */ - function parseDirectives($isConst) + private function parseDirectives($isConst) { $directives = []; while ($this->peek(Token::AT)) { $directives[] = $this->parseDirective($isConst); } + return new NodeList($directives); } @@ -880,14 +928,15 @@ class Parser * @return DirectiveNode * @throws SyntaxError */ - function parseDirective($isConst) + private function parseDirective($isConst) { $start = $this->lexer->token; $this->expect(Token::AT); + return new DirectiveNode([ - 'name' => $this->parseName(), + 'name' => $this->parseName(), 'arguments' => $this->parseArguments($isConst), - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } @@ -899,7 +948,7 @@ class Parser * @return ListTypeNode|NameNode|NonNullTypeNode * @throws SyntaxError */ - function parseTypeReference() + private function parseTypeReference() { $start = $this->lexer->token; @@ -908,7 +957,7 @@ class Parser $this->expect(Token::BRACKET_R); $type = new ListTypeNode([ 'type' => $type, - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } else { $type = $this->parseNamedType(); @@ -916,20 +965,20 @@ class Parser if ($this->skip(Token::BANG)) { return new NonNullTypeNode([ 'type' => $type, - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); - } + return $type; } - function parseNamedType() + private function parseNamedType() { $start = $this->lexer->token; return new NamedTypeNode([ 'name' => $this->parseName(), - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } @@ -953,7 +1002,7 @@ class Parser * @return TypeSystemDefinitionNode * @throws SyntaxError */ - function parseTypeSystemDefinition() + private function parseTypeSystemDefinition() { // Many definitions begin with a description and require a lookahead. $keywordToken = $this->peekDescription() @@ -962,15 +1011,24 @@ class Parser if ($keywordToken->kind === Token::NAME) { switch ($keywordToken->value) { - case 'schema': return $this->parseSchemaDefinition(); - case 'scalar': return $this->parseScalarTypeDefinition(); - case 'type': return $this->parseObjectTypeDefinition(); - case 'interface': return $this->parseInterfaceTypeDefinition(); - case 'union': return $this->parseUnionTypeDefinition(); - case 'enum': return $this->parseEnumTypeDefinition(); - case 'input': return $this->parseInputObjectTypeDefinition(); - case 'extend': return $this->parseTypeExtension(); - case 'directive': return $this->parseDirectiveDefinition(); + case 'schema': + return $this->parseSchemaDefinition(); + case 'scalar': + return $this->parseScalarTypeDefinition(); + case 'type': + return $this->parseObjectTypeDefinition(); + case 'interface': + return $this->parseInterfaceTypeDefinition(); + case 'union': + return $this->parseUnionTypeDefinition(); + case 'enum': + return $this->parseEnumTypeDefinition(); + case 'input': + return $this->parseInputObjectTypeDefinition(); + case 'extend': + return $this->parseTypeExtension(); + case 'directive': + return $this->parseDirectiveDefinition(); } } @@ -980,14 +1038,16 @@ class Parser /** * @return bool */ - function peekDescription() { + private function peekDescription() + { return $this->peek(Token::STRING) || $this->peek(Token::BLOCK_STRING); } /** * @return StringValueNode|null */ - function parseDescription() { + private function parseDescription() + { if ($this->peekDescription()) { return $this->parseStringLiteral(); } @@ -997,7 +1057,7 @@ class Parser * @return SchemaDefinitionNode * @throws SyntaxError */ - function parseSchemaDefinition() + private function parseSchemaDefinition() { $start = $this->lexer->token; $this->expectKeyword('schema'); @@ -1005,14 +1065,16 @@ class Parser $operationTypes = $this->many( Token::BRACE_L, - [$this, 'parseOperationTypeDefinition'], + function () { + return $this->parseOperationTypeDefinition(); + }, Token::BRACE_R ); return new SchemaDefinitionNode([ - 'directives' => $directives, + 'directives' => $directives, 'operationTypes' => $operationTypes, - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } @@ -1020,17 +1082,17 @@ class Parser * @return OperationTypeDefinitionNode * @throws SyntaxError */ - function parseOperationTypeDefinition() + private function parseOperationTypeDefinition() { - $start = $this->lexer->token; + $start = $this->lexer->token; $operation = $this->parseOperationType(); $this->expect(Token::COLON); $type = $this->parseNamedType(); return new OperationTypeDefinitionNode([ 'operation' => $operation, - 'type' => $type, - 'loc' => $this->loc($start) + 'type' => $type, + 'loc' => $this->loc($start), ]); } @@ -1038,19 +1100,19 @@ class Parser * @return ScalarTypeDefinitionNode * @throws SyntaxError */ - function parseScalarTypeDefinition() + private function parseScalarTypeDefinition() { - $start = $this->lexer->token; + $start = $this->lexer->token; $description = $this->parseDescription(); $this->expectKeyword('scalar'); - $name = $this->parseName(); + $name = $this->parseName(); $directives = $this->parseDirectives(true); return new ScalarTypeDefinitionNode([ - 'name' => $name, - 'directives' => $directives, - 'loc' => $this->loc($start), - 'description' => $description + 'name' => $name, + 'directives' => $directives, + 'loc' => $this->loc($start), + 'description' => $description, ]); } @@ -1058,23 +1120,23 @@ class Parser * @return ObjectTypeDefinitionNode * @throws SyntaxError */ - function parseObjectTypeDefinition() + private function parseObjectTypeDefinition() { - $start = $this->lexer->token; + $start = $this->lexer->token; $description = $this->parseDescription(); $this->expectKeyword('type'); - $name = $this->parseName(); + $name = $this->parseName(); $interfaces = $this->parseImplementsInterfaces(); $directives = $this->parseDirectives(true); - $fields = $this->parseFieldsDefinition(); + $fields = $this->parseFieldsDefinition(); return new ObjectTypeDefinitionNode([ - 'name' => $name, - 'interfaces' => $interfaces, - 'directives' => $directives, - 'fields' => $fields, - 'loc' => $this->loc($start), - 'description' => $description + 'name' => $name, + 'interfaces' => $interfaces, + 'directives' => $directives, + 'fields' => $fields, + 'loc' => $this->loc($start), + 'description' => $description, ]); } @@ -1085,7 +1147,7 @@ class Parser * * @return NamedTypeNode[] */ - function parseImplementsInterfaces() + private function parseImplementsInterfaces() { $types = []; if ($this->lexer->token->value === 'implements') { @@ -1094,12 +1156,12 @@ class Parser $this->skip(Token::AMP); do { $types[] = $this->parseNamedType(); - } while ( - $this->skip(Token::AMP) || - // Legacy support for the SDL? - (!empty($this->lexer->options['allowLegacySDLImplementsInterfaces']) && $this->peek(Token::NAME)) + } while ($this->skip(Token::AMP) || + // Legacy support for the SDL? + (! empty($this->lexer->options['allowLegacySDLImplementsInterfaces']) && $this->peek(Token::NAME)) ); } + return $types; } @@ -1107,22 +1169,25 @@ class Parser * @return FieldDefinitionNode[]|NodeList * @throws SyntaxError */ - function parseFieldsDefinition() + private function parseFieldsDefinition() { // Legacy support for the SDL? - if ( - !empty($this->lexer->options['allowLegacySDLEmptyFields']) && + if (! empty($this->lexer->options['allowLegacySDLEmptyFields']) && $this->peek(Token::BRACE_L) && $this->lexer->lookahead()->kind === Token::BRACE_R ) { $this->lexer->advance(); $this->lexer->advance(); + return []; } + return $this->peek(Token::BRACE_L) ? $this->many( Token::BRACE_L, - [$this, 'parseFieldDefinition'], + function () { + return $this->parseFieldDefinition(); + }, Token::BRACE_R ) : new NodeList([]); @@ -1132,23 +1197,23 @@ class Parser * @return FieldDefinitionNode * @throws SyntaxError */ - function parseFieldDefinition() + private function parseFieldDefinition() { - $start = $this->lexer->token; + $start = $this->lexer->token; $description = $this->parseDescription(); - $name = $this->parseName(); - $args = $this->parseArgumentDefs(); + $name = $this->parseName(); + $args = $this->parseArgumentDefs(); $this->expect(Token::COLON); - $type = $this->parseTypeReference(); + $type = $this->parseTypeReference(); $directives = $this->parseDirectives(true); return new FieldDefinitionNode([ - 'name' => $name, - 'arguments' => $args, - 'type' => $type, - 'directives' => $directives, - 'loc' => $this->loc($start), - 'description' => $description + 'name' => $name, + 'arguments' => $args, + 'type' => $type, + 'directives' => $directives, + 'loc' => $this->loc($start), + 'description' => $description, ]); } @@ -1156,37 +1221,45 @@ class Parser * @return InputValueDefinitionNode[]|NodeList * @throws SyntaxError */ - function parseArgumentDefs() + private function parseArgumentDefs() { - if (!$this->peek(Token::PAREN_L)) { + if (! $this->peek(Token::PAREN_L)) { return new NodeList([]); } - return $this->many(Token::PAREN_L, [$this, 'parseInputValueDef'], Token::PAREN_R); + + return $this->many( + Token::PAREN_L, + function () { + return $this->parseInputValueDef(); + }, + Token::PAREN_R + ); } /** * @return InputValueDefinitionNode * @throws SyntaxError */ - function parseInputValueDef() + private function parseInputValueDef() { - $start = $this->lexer->token; + $start = $this->lexer->token; $description = $this->parseDescription(); - $name = $this->parseName(); + $name = $this->parseName(); $this->expect(Token::COLON); - $type = $this->parseTypeReference(); + $type = $this->parseTypeReference(); $defaultValue = null; if ($this->skip(Token::EQUALS)) { $defaultValue = $this->parseConstValue(); } $directives = $this->parseDirectives(true); + return new InputValueDefinitionNode([ - 'name' => $name, - 'type' => $type, + 'name' => $name, + 'type' => $type, 'defaultValue' => $defaultValue, - 'directives' => $directives, - 'loc' => $this->loc($start), - 'description' => $description + 'directives' => $directives, + 'loc' => $this->loc($start), + 'description' => $description, ]); } @@ -1194,21 +1267,21 @@ class Parser * @return InterfaceTypeDefinitionNode * @throws SyntaxError */ - function parseInterfaceTypeDefinition() + private function parseInterfaceTypeDefinition() { - $start = $this->lexer->token; + $start = $this->lexer->token; $description = $this->parseDescription(); $this->expectKeyword('interface'); - $name = $this->parseName(); + $name = $this->parseName(); $directives = $this->parseDirectives(true); - $fields = $this->parseFieldsDefinition(); + $fields = $this->parseFieldsDefinition(); return new InterfaceTypeDefinitionNode([ - 'name' => $name, - 'directives' => $directives, - 'fields' => $fields, - 'loc' => $this->loc($start), - 'description' => $description + 'name' => $name, + 'directives' => $directives, + 'fields' => $fields, + 'loc' => $this->loc($start), + 'description' => $description, ]); } @@ -1219,21 +1292,21 @@ class Parser * @return UnionTypeDefinitionNode * @throws SyntaxError */ - function parseUnionTypeDefinition() + private function parseUnionTypeDefinition() { - $start = $this->lexer->token; + $start = $this->lexer->token; $description = $this->parseDescription(); $this->expectKeyword('union'); - $name = $this->parseName(); + $name = $this->parseName(); $directives = $this->parseDirectives(true); - $types = $this->parseUnionMemberTypes(); + $types = $this->parseUnionMemberTypes(); return new UnionTypeDefinitionNode([ - 'name' => $name, - 'directives' => $directives, - 'types' => $types, - 'loc' => $this->loc($start), - 'description' => $description + 'name' => $name, + 'directives' => $directives, + 'types' => $types, + 'loc' => $this->loc($start), + 'description' => $description, ]); } @@ -1244,7 +1317,7 @@ class Parser * * @return NamedTypeNode[] */ - function parseUnionMemberTypes() + private function parseUnionMemberTypes() { $types = []; if ($this->skip(Token::EQUALS)) { @@ -1254,6 +1327,7 @@ class Parser $types[] = $this->parseNamedType(); } while ($this->skip(Token::PIPE)); } + return $types; } @@ -1261,21 +1335,21 @@ class Parser * @return EnumTypeDefinitionNode * @throws SyntaxError */ - function parseEnumTypeDefinition() + private function parseEnumTypeDefinition() { - $start = $this->lexer->token; + $start = $this->lexer->token; $description = $this->parseDescription(); $this->expectKeyword('enum'); - $name = $this->parseName(); + $name = $this->parseName(); $directives = $this->parseDirectives(true); - $values = $this->parseEnumValuesDefinition(); + $values = $this->parseEnumValuesDefinition(); return new EnumTypeDefinitionNode([ - 'name' => $name, - 'directives' => $directives, - 'values' => $values, - 'loc' => $this->loc($start), - 'description' => $description + 'name' => $name, + 'directives' => $directives, + 'values' => $values, + 'loc' => $this->loc($start), + 'description' => $description, ]); } @@ -1283,14 +1357,16 @@ class Parser * @return EnumValueDefinitionNode[]|NodeList * @throws SyntaxError */ - function parseEnumValuesDefinition() + private function parseEnumValuesDefinition() { return $this->peek(Token::BRACE_L) ? $this->many( Token::BRACE_L, - [$this, 'parseEnumValueDefinition'], + function () { + return $this->parseEnumValueDefinition(); + }, Token::BRACE_R - ) + ) : new NodeList([]); } @@ -1298,18 +1374,18 @@ class Parser * @return EnumValueDefinitionNode * @throws SyntaxError */ - function parseEnumValueDefinition() + private function parseEnumValueDefinition() { - $start = $this->lexer->token; + $start = $this->lexer->token; $description = $this->parseDescription(); - $name = $this->parseName(); - $directives = $this->parseDirectives(true); + $name = $this->parseName(); + $directives = $this->parseDirectives(true); return new EnumValueDefinitionNode([ - 'name' => $name, - 'directives' => $directives, - 'loc' => $this->loc($start), - 'description' => $description + 'name' => $name, + 'directives' => $directives, + 'loc' => $this->loc($start), + 'description' => $description, ]); } @@ -1317,21 +1393,21 @@ class Parser * @return InputObjectTypeDefinitionNode * @throws SyntaxError */ - function parseInputObjectTypeDefinition() + private function parseInputObjectTypeDefinition() { - $start = $this->lexer->token; + $start = $this->lexer->token; $description = $this->parseDescription(); $this->expectKeyword('input'); - $name = $this->parseName(); + $name = $this->parseName(); $directives = $this->parseDirectives(true); - $fields = $this->parseInputFieldsDefinition(); + $fields = $this->parseInputFieldsDefinition(); return new InputObjectTypeDefinitionNode([ - 'name' => $name, - 'directives' => $directives, - 'fields' => $fields, - 'loc' => $this->loc($start), - 'description' => $description + 'name' => $name, + 'directives' => $directives, + 'fields' => $fields, + 'loc' => $this->loc($start), + 'description' => $description, ]); } @@ -1339,11 +1415,14 @@ class Parser * @return InputValueDefinitionNode[]|NodeList * @throws SyntaxError */ - function parseInputFieldsDefinition() { + private function parseInputFieldsDefinition() + { return $this->peek(Token::BRACE_L) ? $this->many( Token::BRACE_L, - [$this, 'parseInputValueDef'], + function () { + return $this->parseInputValueDef(); + }, Token::BRACE_R ) : new NodeList([]); @@ -1361,7 +1440,7 @@ class Parser * @return TypeExtensionNode * @throws SyntaxError */ - function parseTypeExtension() + private function parseTypeExtension() { $keywordToken = $this->lexer->lookahead(); @@ -1389,20 +1468,21 @@ class Parser * @return ScalarTypeExtensionNode * @throws SyntaxError */ - function parseScalarTypeExtension() { + private function parseScalarTypeExtension() + { $start = $this->lexer->token; $this->expectKeyword('extend'); $this->expectKeyword('scalar'); - $name = $this->parseName(); + $name = $this->parseName(); $directives = $this->parseDirectives(true); if (count($directives) === 0) { throw $this->unexpected(); } return new ScalarTypeExtensionNode([ - 'name' => $name, + 'name' => $name, 'directives' => $directives, - 'loc' => $this->loc($start) + 'loc' => $this->loc($start), ]); } @@ -1410,17 +1490,17 @@ class Parser * @return ObjectTypeExtensionNode * @throws SyntaxError */ - function parseObjectTypeExtension() { + private function parseObjectTypeExtension() + { $start = $this->lexer->token; $this->expectKeyword('extend'); $this->expectKeyword('type'); - $name = $this->parseName(); + $name = $this->parseName(); $interfaces = $this->parseImplementsInterfaces(); $directives = $this->parseDirectives(true); - $fields = $this->parseFieldsDefinition(); + $fields = $this->parseFieldsDefinition(); - if ( - !$interfaces && + if (count($interfaces) === 0 && count($directives) === 0 && count($fields) === 0 ) { @@ -1428,11 +1508,11 @@ class Parser } return new ObjectTypeExtensionNode([ - 'name' => $name, + 'name' => $name, 'interfaces' => $interfaces, 'directives' => $directives, - 'fields' => $fields, - 'loc' => $this->loc($start) + 'fields' => $fields, + 'loc' => $this->loc($start), ]); } @@ -1440,25 +1520,25 @@ class Parser * @return InterfaceTypeExtensionNode * @throws SyntaxError */ - function parseInterfaceTypeExtension() { + private function parseInterfaceTypeExtension() + { $start = $this->lexer->token; $this->expectKeyword('extend'); $this->expectKeyword('interface'); - $name = $this->parseName(); + $name = $this->parseName(); $directives = $this->parseDirectives(true); - $fields = $this->parseFieldsDefinition(); - if ( - count($directives) === 0 && + $fields = $this->parseFieldsDefinition(); + if (count($directives) === 0 && count($fields) === 0 ) { throw $this->unexpected(); } return new InterfaceTypeExtensionNode([ - 'name' => $name, + 'name' => $name, 'directives' => $directives, - 'fields' => $fields, - 'loc' => $this->loc($start) + 'fields' => $fields, + 'loc' => $this->loc($start), ]); } @@ -1470,25 +1550,25 @@ class Parser * @return UnionTypeExtensionNode * @throws SyntaxError */ - function parseUnionTypeExtension() { + private function parseUnionTypeExtension() + { $start = $this->lexer->token; $this->expectKeyword('extend'); $this->expectKeyword('union'); - $name = $this->parseName(); + $name = $this->parseName(); $directives = $this->parseDirectives(true); - $types = $this->parseUnionMemberTypes(); - if ( - count($directives) === 0 && - !$types + $types = $this->parseUnionMemberTypes(); + if (count($directives) === 0 && + ! $types ) { throw $this->unexpected(); } return new UnionTypeExtensionNode([ - 'name' => $name, + 'name' => $name, 'directives' => $directives, - 'types' => $types, - 'loc' => $this->loc($start) + 'types' => $types, + 'loc' => $this->loc($start), ]); } @@ -1496,25 +1576,25 @@ class Parser * @return EnumTypeExtensionNode * @throws SyntaxError */ - function parseEnumTypeExtension() { + private function parseEnumTypeExtension() + { $start = $this->lexer->token; $this->expectKeyword('extend'); $this->expectKeyword('enum'); - $name = $this->parseName(); + $name = $this->parseName(); $directives = $this->parseDirectives(true); - $values = $this->parseEnumValuesDefinition(); - if ( - count($directives) === 0 && + $values = $this->parseEnumValuesDefinition(); + if (count($directives) === 0 && count($values) === 0 ) { throw $this->unexpected(); } return new EnumTypeExtensionNode([ - 'name' => $name, + 'name' => $name, 'directives' => $directives, - 'values' => $values, - 'loc' => $this->loc($start) + 'values' => $values, + 'loc' => $this->loc($start), ]); } @@ -1522,25 +1602,25 @@ class Parser * @return InputObjectTypeExtensionNode * @throws SyntaxError */ - function parseInputObjectTypeExtension() { + private function parseInputObjectTypeExtension() + { $start = $this->lexer->token; $this->expectKeyword('extend'); $this->expectKeyword('input'); - $name = $this->parseName(); + $name = $this->parseName(); $directives = $this->parseDirectives(true); - $fields = $this->parseInputFieldsDefinition(); - if ( - count($directives) === 0 && + $fields = $this->parseInputFieldsDefinition(); + if (count($directives) === 0 && count($fields) === 0 ) { throw $this->unexpected(); } return new InputObjectTypeExtensionNode([ - 'name' => $name, + 'name' => $name, 'directives' => $directives, - 'fields' => $fields, - 'loc' => $this->loc($start) + 'fields' => $fields, + 'loc' => $this->loc($start), ]); } @@ -1551,9 +1631,9 @@ class Parser * @return DirectiveDefinitionNode * @throws SyntaxError */ - function parseDirectiveDefinition() + private function parseDirectiveDefinition() { - $start = $this->lexer->token; + $start = $this->lexer->token; $description = $this->parseDescription(); $this->expectKeyword('directive'); $this->expect(Token::AT); @@ -1563,11 +1643,11 @@ class Parser $locations = $this->parseDirectiveLocations(); return new DirectiveDefinitionNode([ - 'name' => $name, - 'arguments' => $args, - 'locations' => $locations, - 'loc' => $this->loc($start), - 'description' => $description + 'name' => $name, + 'arguments' => $args, + 'locations' => $locations, + 'loc' => $this->loc($start), + 'description' => $description, ]); } @@ -1575,7 +1655,7 @@ class Parser * @return NameNode[] * @throws SyntaxError */ - function parseDirectiveLocations() + private function parseDirectiveLocations() { // Optional leading pipe $this->skip(Token::PIPE); @@ -1583,6 +1663,7 @@ class Parser do { $locations[] = $this->parseDirectiveLocation(); } while ($this->skip(Token::PIPE)); + return $locations; } @@ -1590,10 +1671,10 @@ class Parser * @return NameNode * @throws SyntaxError */ - function parseDirectiveLocation() + private function parseDirectiveLocation() { $start = $this->lexer->token; - $name = $this->parseName(); + $name = $this->parseName(); if (DirectiveLocation::has($name->value)) { return $name; } diff --git a/src/Language/Printer.php b/src/Language/Printer.php index 49ea385..3c9873f 100644 --- a/src/Language/Printer.php +++ b/src/Language/Printer.php @@ -1,29 +1,32 @@ printAST($ast); } protected function __construct() - {} + { + } public function printAST($ast) { - return Visitor::visit($ast, [ - 'leave' => [ - NodeKind::NAME => function(Node $node) { - return '' . $node->value; - }, - NodeKind::VARIABLE => function($node) { - return '$' . $node->name; - }, - NodeKind::DOCUMENT => function(DocumentNode $node) { - return $this->join($node->definitions, "\n\n") . "\n"; - }, - NodeKind::OPERATION_DEFINITION => function(OperationDefinitionNode $node) { - $op = $node->operation; - $name = $node->name; - $varDefs = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')'); - $directives = $this->join($node->directives, ' '); - $selectionSet = $node->selectionSet; - // Anonymous queries with no directives or variable definitions can use - // the query short form. - return !$name && !$directives && !$varDefs && $op === 'query' - ? $selectionSet - : $this->join([$op, $this->join([$name, $varDefs]), $directives, $selectionSet], ' '); - }, - NodeKind::VARIABLE_DEFINITION => function(VariableDefinitionNode $node) { - return $node->variable . ': ' . $node->type . $this->wrap(' = ', $node->defaultValue); - }, - NodeKind::SELECTION_SET => function(SelectionSetNode $node) { - return $this->block($node->selections); - }, - NodeKind::FIELD => function(FieldNode $node) { - return $this->join([ - $this->wrap('', $node->alias, ': ') . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')'), - $this->join($node->directives, ' '), - $node->selectionSet - ], ' '); - }, - NodeKind::ARGUMENT => function(ArgumentNode $node) { - return $node->name . ': ' . $node->value; - }, + return Visitor::visit( + $ast, + [ + 'leave' => [ + NodeKind::NAME => function (Node $node) { + return '' . $node->value; + }, - // Fragments - NodeKind::FRAGMENT_SPREAD => function(FragmentSpreadNode $node) { - return '...' . $node->name . $this->wrap(' ', $this->join($node->directives, ' ')); - }, - NodeKind::INLINE_FRAGMENT => function(InlineFragmentNode $node) { - return $this->join([ - "...", - $this->wrap('on ', $node->typeCondition), - $this->join($node->directives, ' '), - $node->selectionSet - ], ' '); - }, - NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $node) { - // Note: fragment variable definitions are experimental and may be changed - // or removed in the future. - return "fragment {$node->name}" - . $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')') - . " on {$node->typeCondition} " - . $this->wrap('', $this->join($node->directives, ' '), ' ') - . $node->selectionSet; - }, + NodeKind::VARIABLE => function ($node) { + return '$' . $node->name; + }, - // Value - NodeKind::INT => function(IntValueNode $node) { - return $node->value; - }, - NodeKind::FLOAT => function(FloatValueNode $node) { - return $node->value; - }, - NodeKind::STRING => function(StringValueNode $node, $key) { - if ($node->block) { - return $this->printBlockString($node->value, $key === 'description'); - } - return json_encode($node->value); - }, - NodeKind::BOOLEAN => function(BooleanValueNode $node) { - return $node->value ? 'true' : 'false'; - }, - NodeKind::NULL => function(NullValueNode $node) { - return 'null'; - }, - NodeKind::ENUM => function(EnumValueNode $node) { - return $node->value; - }, - NodeKind::LST => function(ListValueNode $node) { - return '[' . $this->join($node->values, ', ') . ']'; - }, - NodeKind::OBJECT => function(ObjectValueNode $node) { - return '{' . $this->join($node->fields, ', ') . '}'; - }, - NodeKind::OBJECT_FIELD => function(ObjectFieldNode $node) { - return $node->name . ': ' . $node->value; - }, + NodeKind::DOCUMENT => function (DocumentNode $node) { + return $this->join($node->definitions, "\n\n") . "\n"; + }, - // DirectiveNode - NodeKind::DIRECTIVE => function(DirectiveNode $node) { - return '@' . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')'); - }, + NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) { + $op = $node->operation; + $name = $node->name; + $varDefs = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')'); + $directives = $this->join($node->directives, ' '); + $selectionSet = $node->selectionSet; + // Anonymous queries with no directives or variable definitions can use + // the query short form. + return ! $name && ! $directives && ! $varDefs && $op === 'query' + ? $selectionSet + : $this->join([$op, $this->join([$name, $varDefs]), $directives, $selectionSet], ' '); + }, - // Type - NodeKind::NAMED_TYPE => function(NamedTypeNode $node) { - return $node->name; - }, - NodeKind::LIST_TYPE => function(ListTypeNode $node) { - return '[' . $node->type . ']'; - }, - NodeKind::NON_NULL_TYPE => function(NonNullTypeNode $node) { - return $node->type . '!'; - }, + NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) { + return $node->variable . ': ' . $node->type . $this->wrap(' = ', $node->defaultValue); + }, - // Type System Definitions - NodeKind::SCHEMA_DEFINITION => function(SchemaDefinitionNode $def) { - return $this->join([ - 'schema', - $this->join($def->directives, ' '), - $this->block($def->operationTypes) - ], ' '); - }, - NodeKind::OPERATION_TYPE_DEFINITION => function(OperationTypeDefinitionNode $def) { - return $def->operation . ': ' . $def->type; - }, + NodeKind::SELECTION_SET => function (SelectionSetNode $node) { + return $this->block($node->selections); + }, - NodeKind::SCALAR_TYPE_DEFINITION => $this->addDescription(function(ScalarTypeDefinitionNode $def) { - return $this->join(['scalar', $def->name, $this->join($def->directives, ' ')], ' '); - }), - NodeKind::OBJECT_TYPE_DEFINITION => $this->addDescription(function(ObjectTypeDefinitionNode $def) { - return $this->join([ - 'type', - $def->name, - $this->wrap('implements ', $this->join($def->interfaces, ' & ')), - $this->join($def->directives, ' '), - $this->block($def->fields) - ], ' '); - }), - NodeKind::FIELD_DEFINITION => $this->addDescription(function(FieldDefinitionNode $def) { - return $def->name - . $this->wrap('(', $this->join($def->arguments, ', '), ')') - . ': ' . $def->type - . $this->wrap(' ', $this->join($def->directives, ' ')); - }), - NodeKind::INPUT_VALUE_DEFINITION => $this->addDescription(function(InputValueDefinitionNode $def) { - return $this->join([ - $def->name . ': ' . $def->type, - $this->wrap('= ', $def->defaultValue), - $this->join($def->directives, ' ') - ], ' '); - }), - NodeKind::INTERFACE_TYPE_DEFINITION => $this->addDescription(function(InterfaceTypeDefinitionNode $def) { - return $this->join([ - 'interface', - $def->name, - $this->join($def->directives, ' '), - $this->block($def->fields) - ], ' '); - }), - NodeKind::UNION_TYPE_DEFINITION => $this->addDescription(function(UnionTypeDefinitionNode $def) { - return $this->join([ - 'union', - $def->name, - $this->join($def->directives, ' '), - $def->types - ? '= ' . $this->join($def->types, ' | ') - : '' - ], ' '); - }), - NodeKind::ENUM_TYPE_DEFINITION => $this->addDescription(function(EnumTypeDefinitionNode $def) { - return $this->join([ - 'enum', - $def->name, - $this->join($def->directives, ' '), - $this->block($def->values) - ], ' '); - }), - NodeKind::ENUM_VALUE_DEFINITION => $this->addDescription(function(EnumValueDefinitionNode $def) { - return $this->join([$def->name, $this->join($def->directives, ' ')], ' '); - }), - NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $this->addDescription(function(InputObjectTypeDefinitionNode $def) { - return $this->join([ - 'input', - $def->name, - $this->join($def->directives, ' '), - $this->block($def->fields) - ], ' '); - }), - NodeKind::SCALAR_TYPE_EXTENSION => function(ScalarTypeExtensionNode $def) { - return $this->join([ - 'extend scalar', - $def->name, - $this->join($def->directives, ' '), - ], ' '); - }, - NodeKind::OBJECT_TYPE_EXTENSION => function(ObjectTypeExtensionNode $def) { - return $this->join([ - 'extend type', - $def->name, - $this->wrap('implements ', $this->join($def->interfaces, ' & ')), - $this->join($def->directives, ' '), - $this->block($def->fields), - ], ' '); - }, - NodeKind::INTERFACE_TYPE_EXTENSION => function(InterfaceTypeExtensionNode $def) { - return $this->join([ - 'extend interface', - $def->name, - $this->join($def->directives, ' '), - $this->block($def->fields), - ], ' '); - }, - NodeKind::UNION_TYPE_EXTENSION => function(UnionTypeExtensionNode $def) { - return $this->join([ - 'extend union', - $def->name, - $this->join($def->directives, ' '), - $def->types - ? '= ' . $this->join($def->types, ' | ') - : '' - ], ' '); - }, - NodeKind::ENUM_TYPE_EXTENSION => function(EnumTypeExtensionNode $def) { - return $this->join([ - 'extend enum', - $def->name, - $this->join($def->directives, ' '), - $this->block($def->values), - ], ' '); - }, - NodeKind::INPUT_OBJECT_TYPE_EXTENSION => function(InputObjectTypeExtensionNode $def) { - return $this->join([ - 'extend input', - $def->name, - $this->join($def->directives, ' '), - $this->block($def->fields), - ], ' '); - }, - NodeKind::DIRECTIVE_DEFINITION => $this->addDescription(function(DirectiveDefinitionNode $def) { - return 'directive @' - . $def->name - . $this->wrap('(', $this->join($def->arguments, ', '), ')') - . ' on ' . $this->join($def->locations, ' | '); - }) + NodeKind::FIELD => function (FieldNode $node) { + return $this->join( + [ + $this->wrap('', $node->alias, ': ') . $node->name . $this->wrap( + '(', + $this->join($node->arguments, ', '), + ')' + ), + $this->join($node->directives, ' '), + $node->selectionSet, + ], + ' ' + ); + }, + + NodeKind::ARGUMENT => function (ArgumentNode $node) { + return $node->name . ': ' . $node->value; + }, + + NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) { + return '...' . $node->name . $this->wrap(' ', $this->join($node->directives, ' ')); + }, + + NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) { + return $this->join( + [ + '...', + $this->wrap('on ', $node->typeCondition), + $this->join($node->directives, ' '), + $node->selectionSet, + ], + ' ' + ); + }, + + NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) { + // Note: fragment variable definitions are experimental and may be changed or removed in the future. + return sprintf('fragment %s', $node->name) + . $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')') + . sprintf(' on %s ', $node->typeCondition) + . $this->wrap('', $this->join($node->directives, ' '), ' ') + . $node->selectionSet; + }, + + NodeKind::INT => function (IntValueNode $node) { + return $node->value; + }, + + NodeKind::FLOAT => function (FloatValueNode $node) { + return $node->value; + }, + + NodeKind::STRING => function (StringValueNode $node, $key) { + if ($node->block) { + return $this->printBlockString($node->value, $key === 'description'); + } + + return json_encode($node->value); + }, + + NodeKind::BOOLEAN => function (BooleanValueNode $node) { + return $node->value ? 'true' : 'false'; + }, + + NodeKind::NULL => function (NullValueNode $node) { + return 'null'; + }, + + NodeKind::ENUM => function (EnumValueNode $node) { + return $node->value; + }, + + NodeKind::LST => function (ListValueNode $node) { + return '[' . $this->join($node->values, ', ') . ']'; + }, + + NodeKind::OBJECT => function (ObjectValueNode $node) { + return '{' . $this->join($node->fields, ', ') . '}'; + }, + + NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) { + return $node->name . ': ' . $node->value; + }, + + NodeKind::DIRECTIVE => function (DirectiveNode $node) { + return '@' . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')'); + }, + + NodeKind::NAMED_TYPE => function (NamedTypeNode $node) { + return $node->name; + }, + + NodeKind::LIST_TYPE => function (ListTypeNode $node) { + return '[' . $node->type . ']'; + }, + + NodeKind::NON_NULL_TYPE => function (NonNullTypeNode $node) { + return $node->type . '!'; + }, + + NodeKind::SCHEMA_DEFINITION => function (SchemaDefinitionNode $def) { + return $this->join( + [ + 'schema', + $this->join($def->directives, ' '), + $this->block($def->operationTypes), + ], + ' ' + ); + }, + + NodeKind::OPERATION_TYPE_DEFINITION => function (OperationTypeDefinitionNode $def) { + return $def->operation . ': ' . $def->type; + }, + + NodeKind::SCALAR_TYPE_DEFINITION => $this->addDescription(function (ScalarTypeDefinitionNode $def) { + return $this->join(['scalar', $def->name, $this->join($def->directives, ' ')], ' '); + }), + + NodeKind::OBJECT_TYPE_DEFINITION => $this->addDescription(function (ObjectTypeDefinitionNode $def) { + return $this->join( + [ + 'type', + $def->name, + $this->wrap('implements ', $this->join($def->interfaces, ' & ')), + $this->join($def->directives, ' '), + $this->block($def->fields), + ], + ' ' + ); + }), + + NodeKind::FIELD_DEFINITION => $this->addDescription(function (FieldDefinitionNode $def) { + return $def->name + . $this->wrap('(', $this->join($def->arguments, ', '), ')') + . ': ' . $def->type + . $this->wrap(' ', $this->join($def->directives, ' ')); + }), + + NodeKind::INPUT_VALUE_DEFINITION => $this->addDescription(function (InputValueDefinitionNode $def) { + return $this->join( + [ + $def->name . ': ' . $def->type, + $this->wrap('= ', $def->defaultValue), + $this->join($def->directives, ' '), + ], + ' ' + ); + }), + + NodeKind::INTERFACE_TYPE_DEFINITION => $this->addDescription( + function (InterfaceTypeDefinitionNode $def) { + return $this->join( + [ + 'interface', + $def->name, + $this->join($def->directives, ' '), + $this->block($def->fields), + ], + ' ' + ); + } + ), + + NodeKind::UNION_TYPE_DEFINITION => $this->addDescription(function (UnionTypeDefinitionNode $def) { + return $this->join( + [ + 'union', + $def->name, + $this->join($def->directives, ' '), + $def->types + ? '= ' . $this->join($def->types, ' | ') + : '', + ], + ' ' + ); + }), + + NodeKind::ENUM_TYPE_DEFINITION => $this->addDescription(function (EnumTypeDefinitionNode $def) { + return $this->join( + [ + 'enum', + $def->name, + $this->join($def->directives, ' '), + $this->block($def->values), + ], + ' ' + ); + }), + + NodeKind::ENUM_VALUE_DEFINITION => $this->addDescription(function (EnumValueDefinitionNode $def) { + return $this->join([$def->name, $this->join($def->directives, ' ')], ' '); + }), + + NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $this->addDescription(function ( + InputObjectTypeDefinitionNode $def + ) { + return $this->join( + [ + 'input', + $def->name, + $this->join($def->directives, ' '), + $this->block($def->fields), + ], + ' ' + ); + }), + + NodeKind::SCALAR_TYPE_EXTENSION => function (ScalarTypeExtensionNode $def) { + return $this->join( + [ + 'extend scalar', + $def->name, + $this->join($def->directives, ' '), + ], + ' ' + ); + }, + + NodeKind::OBJECT_TYPE_EXTENSION => function (ObjectTypeExtensionNode $def) { + return $this->join( + [ + 'extend type', + $def->name, + $this->wrap('implements ', $this->join($def->interfaces, ' & ')), + $this->join($def->directives, ' '), + $this->block($def->fields), + ], + ' ' + ); + }, + + NodeKind::INTERFACE_TYPE_EXTENSION => function (InterfaceTypeExtensionNode $def) { + return $this->join( + [ + 'extend interface', + $def->name, + $this->join($def->directives, ' '), + $this->block($def->fields), + ], + ' ' + ); + }, + + NodeKind::UNION_TYPE_EXTENSION => function (UnionTypeExtensionNode $def) { + return $this->join( + [ + 'extend union', + $def->name, + $this->join($def->directives, ' '), + $def->types + ? '= ' . $this->join($def->types, ' | ') + : '', + ], + ' ' + ); + }, + + NodeKind::ENUM_TYPE_EXTENSION => function (EnumTypeExtensionNode $def) { + return $this->join( + [ + 'extend enum', + $def->name, + $this->join($def->directives, ' '), + $this->block($def->values), + ], + ' ' + ); + }, + + NodeKind::INPUT_OBJECT_TYPE_EXTENSION => function (InputObjectTypeExtensionNode $def) { + return $this->join( + [ + 'extend input', + $def->name, + $this->join($def->directives, ' '), + $this->block($def->fields), + ], + ' ' + ); + }, + + NodeKind::DIRECTIVE_DEFINITION => $this->addDescription(function (DirectiveDefinitionNode $def) { + return 'directive @' + . $def->name + . $this->wrap('(', $this->join($def->arguments, ', '), ')') + . ' on ' . $this->join($def->locations, ' | '); + }), + ], ] - ]); + ); } public function addDescription(\Closure $cb) @@ -371,7 +469,9 @@ class Printer $separator, Utils::filter( $maybeArray, - function($x) { return !!$x;} + function ($x) { + return ! ! $x; + } ) ) : ''; @@ -382,8 +482,10 @@ class Printer * trailing blank line. However, if a block string starts with whitespace and is * a single-line, adding a leading blank line would strip that whitespace. */ - private function printBlockString($value, $isDescription) { + private function printBlockString($value, $isDescription) + { $escaped = str_replace('"""', '\\"""', $value); + return (($value[0] === ' ' || $value[0] === "\t") && strpos($value, "\n") === false) ? ('"""' . preg_replace('/"$/', "\"\n", $escaped) . '"""') : ("\"\"\"\n" . ($isDescription ? $escaped : $this->indent($escaped)) . "\n\"\"\""); diff --git a/src/Language/Source.php b/src/Language/Source.php index 899d450..1beb8bc 100644 --- a/src/Language/Source.php +++ b/src/Language/Source.php @@ -1,36 +1,33 @@ body = $body; - $this->length = mb_strlen($body, 'UTF-8'); - $this->name = $name ?: 'GraphQL request'; + $this->body = $body; + $this->length = mb_strlen($body, 'UTF-8'); + $this->name = $name ?: 'GraphQL request'; $this->locationOffset = $location ?: new SourceLocation(1, 1); Utils::invariant( @@ -66,21 +62,22 @@ class Source } /** - * @param $position + * @param int $position * @return SourceLocation */ public function getLocation($position) { - $line = 1; + $line = 1; $column = $position + 1; - $utfChars = json_decode('"\u2028\u2029"'); - $lineRegexp = '/\r\n|[\n\r'.$utfChars.']/su'; - $matches = []; + $utfChars = json_decode('"\u2028\u2029"'); + $lineRegexp = '/\r\n|[\n\r' . $utfChars . ']/su'; + $matches = []; preg_match_all($lineRegexp, mb_substr($this->body, 0, $position, 'UTF-8'), $matches, PREG_OFFSET_CAPTURE); foreach ($matches[0] as $index => $match) { $line += 1; + $column = $position + 1 - ($match[1] + mb_strlen($match[0], 'UTF-8')); } diff --git a/src/Language/SourceLocation.php b/src/Language/SourceLocation.php index d09363b..2c406ca 100644 --- a/src/Language/SourceLocation.php +++ b/src/Language/SourceLocation.php @@ -1,30 +1,40 @@ line = $line; + $this->line = $line; $this->column = $col; } /** - * @return array + * @return int[] */ public function toArray() { return [ - 'line' => $this->line, - 'column' => $this->column + 'line' => $this->line, + 'column' => $this->column, ]; } /** - * @return array + * @return int[] */ public function toSerializableArray() { @@ -32,13 +42,9 @@ class SourceLocation implements \JsonSerializable } /** - * Specify data which should be serialized to JSON - * @link http://php.net/manual/en/jsonserializable.jsonserialize.php - * @return mixed data which can be serialized by json_encode, - * which is a value of any type other than a resource. - * @since 5.4.0 + * @return int[] */ - function jsonSerialize() + public function jsonSerialize() { return $this->toSerializableArray(); } diff --git a/src/Language/Token.php b/src/Language/Token.php index 880cb0d..a1c966c 100644 --- a/src/Language/Token.php +++ b/src/Language/Token.php @@ -1,4 +1,7 @@ '; - const EOF = ''; - const BANG = '!'; - const DOLLAR = '$'; - const AMP = '&'; - const PAREN_L = '('; - const PAREN_R = ')'; - const SPREAD = '...'; - const COLON = ':'; - const EQUALS = '='; - const AT = '@'; - const BRACKET_L = '['; - const BRACKET_R = ']'; - const BRACE_L = '{'; - const PIPE = '|'; - const BRACE_R = '}'; - const NAME = 'Name'; - const INT = 'Int'; - const FLOAT = 'Float'; - const STRING = 'String'; + const SOF = ''; + const EOF = ''; + const BANG = '!'; + const DOLLAR = '$'; + const AMP = '&'; + const PAREN_L = '('; + const PAREN_R = ')'; + const SPREAD = '...'; + const COLON = ':'; + const EQUALS = '='; + const AT = '@'; + const BRACKET_L = '['; + const BRACKET_R = ']'; + const BRACE_L = '{'; + const PIPE = '|'; + const BRACE_R = '}'; + const NAME = 'Name'; + const INT = 'Int'; + const FLOAT = 'Float'; + const STRING = 'String'; const BLOCK_STRING = 'BlockString'; - const COMMENT = 'Comment'; + const COMMENT = 'Comment'; /** * The kind of Token (see one of constants above). @@ -66,9 +69,7 @@ class Token */ public $column; - /** - * @var string|null - */ + /** @var string|null */ public $value; /** @@ -80,31 +81,28 @@ class Token */ public $prev; - /** - * @var Token - */ + /** @var Token */ public $next; /** - * Token constructor. - * @param $kind - * @param $start - * @param $end - * @param $line - * @param $column - * @param Token $previous - * @param null $value + * + * @param string $kind + * @param int $start + * @param int $end + * @param int $line + * @param int $column + * @param mixed|null $value */ - public function __construct($kind, $start, $end, $line, $column, Token $previous = null, $value = null) + public function __construct($kind, $start, $end, $line, $column, ?Token $previous = null, $value = null) { - $this->kind = $kind; - $this->start = (int) $start; - $this->end = (int) $end; - $this->line = (int) $line; - $this->column = (int) $column; - $this->prev = $previous; - $this->next = null; - $this->value = $value; + $this->kind = $kind; + $this->start = $start; + $this->end = $end; + $this->line = $line; + $this->column = $column; + $this->prev = $previous; + $this->next = null; + $this->value = $value; } /** @@ -112,19 +110,19 @@ class Token */ public function getDescription() { - return $this->kind . ($this->value ? ' "' . $this->value . '"' : ''); + return $this->kind . ($this->value ? ' "' . $this->value . '"' : ''); } /** - * @return array + * @return (string|int|null)[] */ public function toArray() { return [ - 'kind' => $this->kind, - 'value' => $this->value, - 'line' => $this->line, - 'column' => $this->column + 'kind' => $this->kind, + 'value' => $this->value, + 'line' => $this->line, + 'column' => $this->column, ]; } } diff --git a/src/Language/Visitor.php b/src/Language/Visitor.php index ab20d1e..e266f0f 100644 --- a/src/Language/Visitor.php +++ b/src/Language/Visitor.php @@ -1,19 +1,24 @@ [], - NodeKind::DOCUMENT => ['definitions'], + NodeKind::NAME => [], + NodeKind::DOCUMENT => ['definitions'], NodeKind::OPERATION_DEFINITION => ['name', 'variableDefinitions', 'directives', 'selectionSet'], - NodeKind::VARIABLE_DEFINITION => ['variable', 'type', 'defaultValue'], - 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 => [ + NodeKind::VARIABLE_DEFINITION => ['variable', 'type', 'defaultValue'], + 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' + '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::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', '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::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', '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', 'directives', 'fields'], - NodeKind::UNION_TYPE_EXTENSION => ['name', 'directives', 'types'], - NodeKind::ENUM_TYPE_EXTENSION => ['name', 'directives', 'values'], + NodeKind::SCALAR_TYPE_EXTENSION => ['name', 'directives'], + NodeKind::OBJECT_TYPE_EXTENSION => ['name', 'interfaces', 'directives', 'fields'], + NodeKind::INTERFACE_TYPE_EXTENSION => ['name', '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::DIRECTIVE_DEFINITION => ['description', 'name', 'arguments', 'locations'], ]; /** * Visit the AST (see class description for details) * * @api - * @param Node $root - * @param array $visitor - * @param array $keyMap + * @param Node|ArrayObject|stdClass $root + * @param callable[] $visitor + * @param mixed[]|null $keyMap * @return Node|mixed * @throws \Exception */ @@ -175,28 +181,28 @@ class Visitor { $visitorKeys = $keyMap ?: self::$visitorKeys; - $stack = null; - $inArray = $root instanceof NodeList || is_array($root); - $keys = [$root]; - $index = -1; - $edits = []; - $parent = null; - $path = []; + $stack = null; + $inArray = $root instanceof NodeList || is_array($root); + $keys = [$root]; + $index = -1; + $edits = []; + $parent = null; + $path = []; $ancestors = []; - $newRoot = $root; + $newRoot = $root; $UNDEFINED = null; do { $index++; $isLeaving = $index === count($keys); - $key = null; - $node = null; - $isEdited = $isLeaving && count($edits) !== 0; + $key = null; + $node = null; + $isEdited = $isLeaving && count($edits) !== 0; if ($isLeaving) { - $key = !$ancestors ? $UNDEFINED : $path[count($path) - 1]; - $node = $parent; + $key = ! $ancestors ? $UNDEFINED : $path[count($path) - 1]; + $node = $parent; $parent = array_pop($ancestors); if ($isEdited) { @@ -210,7 +216,7 @@ class Visitor } $editOffset = 0; for ($ii = 0; $ii < count($edits); $ii++) { - $editKey = $edits[$ii][0]; + $editKey = $edits[$ii][0]; $editValue = $edits[$ii][1]; if ($inArray) { @@ -232,13 +238,13 @@ class Visitor } } } - $index = $stack['index']; - $keys = $stack['keys']; - $edits = $stack['edits']; + $index = $stack['index']; + $keys = $stack['keys']; + $edits = $stack['edits']; $inArray = $stack['inArray']; - $stack = $stack['prev']; + $stack = $stack['prev']; } else { - $key = $parent ? ($inArray ? $index : $keys[$index]) : $UNDEFINED; + $key = $parent ? ($inArray ? $index : $keys[$index]) : $UNDEFINED; $node = $parent ? (($parent instanceof NodeList || is_array($parent)) ? $parent[$key] : $parent->{$key}) : $newRoot; if ($node === null || $node === $UNDEFINED) { continue; @@ -249,8 +255,8 @@ class Visitor } $result = null; - if (!$node instanceof NodeList && !is_array($node)) { - if (!($node instanceof Node)) { + if (! $node instanceof NodeList && ! is_array($node)) { + if (! ($node instanceof Node)) { throw new \Exception('Invalid AST Node: ' . json_encode($node)); } @@ -264,7 +270,7 @@ class Visitor if ($result->doBreak) { break; } - if (!$isLeaving && $result->doContinue) { + if (! $isLeaving && $result->doContinue) { array_pop($path); continue; } @@ -276,13 +282,13 @@ class Visitor } $edits[] = [$key, $editValue]; - if (!$isLeaving) { - if ($editValue instanceof Node) { - $node = $editValue; - } else { + if (! $isLeaving) { + if (! ($editValue instanceof Node)) { array_pop($path); continue; } + + $node = $editValue; } } } @@ -295,16 +301,16 @@ class Visitor if ($isLeaving) { array_pop($path); } else { - $stack = [ + $stack = [ 'inArray' => $inArray, - 'index' => $index, - 'keys' => $keys, - 'edits' => $edits, - 'prev' => $stack + 'index' => $index, + 'keys' => $keys, + 'edits' => $edits, + 'prev' => $stack, ]; $inArray = $node instanceof NodeList || is_array($node); - $keys = ($inArray ? $node : $visitorKeys[$node->kind]) ?: []; + $keys = ($inArray ? $node : $visitorKeys[$node->kind]) ?: []; $index = -1; $edits = []; if ($parent) { @@ -312,7 +318,6 @@ class Visitor } $parent = $node; } - } while ($stack); if (count($edits) !== 0) { @@ -330,8 +335,9 @@ class Visitor */ public static function stop() { - $r = new VisitorOperation(); + $r = new VisitorOperation(); $r->doBreak = true; + return $r; } @@ -343,8 +349,9 @@ class Visitor */ public static function skipNode() { - $r = new VisitorOperation(); + $r = new VisitorOperation(); $r->doContinue = true; + return $r; } @@ -356,66 +363,79 @@ class Visitor */ public static function removeNode() { - $r = new VisitorOperation(); + $r = new VisitorOperation(); $r->removeNode = true; + return $r; } /** - * @param $visitors - * @return array + * @param callable[][] $visitors + * @return callable[][] */ - static function visitInParallel($visitors) + public static function visitInParallel($visitors) { $visitorsCount = count($visitors); - $skipping = new \SplFixedArray($visitorsCount); + $skipping = new \SplFixedArray($visitorsCount); return [ - 'enter' => function ($node) use ($visitors, $skipping, $visitorsCount) { + 'enter' => function (Node $node) use ($visitors, $skipping, $visitorsCount) { for ($i = 0; $i < $visitorsCount; $i++) { - if (empty($skipping[$i])) { - $fn = self::getVisitFn($visitors[$i], $node->kind, /* isLeaving */ false); + if (! empty($skipping[$i])) { + continue; + } - if ($fn) { - $result = call_user_func_array($fn, func_get_args()); + $fn = self::getVisitFn( + $visitors[$i], + $node->kind, /* isLeaving */ + false + ); - if ($result instanceof VisitorOperation) { - if ($result->doContinue) { - $skipping[$i] = $node; - } else if ($result->doBreak) { - $skipping[$i] = $result; - } else if ($result->removeNode) { - return $result; - } - } else if ($result !== null) { - return $result; - } + if (! $fn) { + continue; + } + + $result = call_user_func_array($fn, func_get_args()); + + if ($result instanceof VisitorOperation) { + if ($result->doContinue) { + $skipping[$i] = $node; + } elseif ($result->doBreak) { + $skipping[$i] = $result; + } elseif ($result->removeNode) { + return $result; } + } elseif ($result !== null) { + return $result; } } }, - 'leave' => function ($node) use ($visitors, $skipping, $visitorsCount) { + 'leave' => function (Node $node) use ($visitors, $skipping, $visitorsCount) { for ($i = 0; $i < $visitorsCount; $i++) { if (empty($skipping[$i])) { - $fn = self::getVisitFn($visitors[$i], $node->kind, /* isLeaving */ true); + $fn = self::getVisitFn( + $visitors[$i], + $node->kind, /* isLeaving */ + true + ); if ($fn) { $result = call_user_func_array($fn, func_get_args()); if ($result instanceof VisitorOperation) { if ($result->doBreak) { $skipping[$i] = $result; - } else if ($result->removeNode) { + } elseif ($result->removeNode) { return $result; } - } else if ($result !== null) { + } elseif ($result !== null) { return $result; } } - } else if ($skipping[$i] === $node) { + } elseif ($skipping[$i] === $node) { $skipping[$i] = null; } } - } + }, ]; } @@ -423,10 +443,10 @@ class Visitor * Creates a new visitor instance which maintains a provided TypeInfo instance * along with visiting visitor. */ - static function visitWithTypeInfo(TypeInfo $typeInfo, $visitor) + public static function visitWithTypeInfo(TypeInfo $typeInfo, $visitor) { return [ - 'enter' => function ($node) use ($typeInfo, $visitor) { + 'enter' => function (Node $node) use ($typeInfo, $visitor) { $typeInfo->enter($node); $fn = self::getVisitFn($visitor, $node->kind, false); @@ -438,52 +458,58 @@ class Visitor $typeInfo->enter($result); } } + return $result; } + return null; }, - 'leave' => function ($node) use ($typeInfo, $visitor) { - $fn = self::getVisitFn($visitor, $node->kind, true); + 'leave' => function (Node $node) use ($typeInfo, $visitor) { + $fn = self::getVisitFn($visitor, $node->kind, true); $result = $fn ? call_user_func_array($fn, func_get_args()) : null; $typeInfo->leave($node); + return $result; - } + }, ]; } /** - * @param $visitor - * @param $kind - * @param $isLeaving - * @return null + * @param callable[]|null $visitor + * @param string $kind + * @param bool $isLeaving + * @return callable|null */ public static function getVisitFn($visitor, $kind, $isLeaving) { - if (!$visitor) { + if ($visitor === null) { return null; } - $kindVisitor = isset($visitor[$kind]) ? $visitor[$kind] : null; - if (!$isLeaving && is_callable($kindVisitor)) { + $kindVisitor = $visitor[$kind] ?? null; + + if (! $isLeaving && is_callable($kindVisitor)) { // { Kind() {} } return $kindVisitor; } if (is_array($kindVisitor)) { if ($isLeaving) { - $kindSpecificVisitor = isset($kindVisitor['leave']) ? $kindVisitor['leave'] : null; + $kindSpecificVisitor = $kindVisitor['leave'] ?? null; } else { - $kindSpecificVisitor = isset($kindVisitor['enter']) ? $kindVisitor['enter'] : null; + $kindSpecificVisitor = $kindVisitor['enter'] ?? null; } if ($kindSpecificVisitor && is_callable($kindSpecificVisitor)) { // { Kind: { enter() {}, leave() {} } } return $kindSpecificVisitor; } + return null; } $visitor += ['leave' => null, 'enter' => null]; + $specificVisitor = $isLeaving ? $visitor['leave'] : $visitor['enter']; if ($specificVisitor) { @@ -491,13 +517,14 @@ class Visitor // { enter() {}, leave() {} } return $specificVisitor; } - $specificKindVisitor = isset($specificVisitor[$kind]) ? $specificVisitor[$kind] : null; + $specificKindVisitor = $specificVisitor[$kind] ?? null; if (is_callable($specificKindVisitor)) { // { enter: { Kind() {} }, leave: { Kind() {} } } return $specificKindVisitor; } } + return null; } } diff --git a/src/Language/VisitorOperation.php b/src/Language/VisitorOperation.php new file mode 100644 index 0000000..2316261 --- /dev/null +++ b/src/Language/VisitorOperation.php @@ -0,0 +1,17 @@ +loc = Location::create($node['loc']['start'], $node['loc']['end']); } - foreach ($node as $key => $value) { - if ('loc' === $key || 'kind' === $key) { - continue ; + if ($key === 'loc' || $key === 'kind') { + continue; } if (is_array($value)) { if (isset($value[0]) || empty($value)) { @@ -92,6 +110,7 @@ class AST } $instance->{$key} = $value; } + return $instance; } @@ -99,8 +118,7 @@ class AST * Convert AST node to serializable array * * @api - * @param Node $node - * @return array + * @return mixed[] */ public static function toArray(Node $node) { @@ -126,17 +144,17 @@ class AST * | null | NullValue | * * @api - * @param $value - * @param InputType $type + * @param Type|mixed|null $value * @return ObjectValueNode|ListValueNode|BooleanValueNode|IntValueNode|FloatValueNode|EnumValueNode|StringValueNode|NullValueNode */ - static function astFromValue($value, InputType $type) + public static function astFromValue($value, InputType $type) { if ($type instanceof NonNull) { $astValue = self::astFromValue($value, $type->getWrappedType()); if ($astValue instanceof NullValueNode) { return null; } + return $astValue; } @@ -152,56 +170,65 @@ class AST $valuesNodes = []; foreach ($value as $item) { $itemNode = self::astFromValue($item, $itemType); - if ($itemNode) { - $valuesNodes[] = $itemNode; + if (! $itemNode) { + continue; } + + $valuesNodes[] = $itemNode; } + return new ListValueNode(['values' => $valuesNodes]); } + return self::astFromValue($value, $itemType); } // Populate the fields of the input object by creating ASTs from each value // in the PHP object according to the fields in the input type. if ($type instanceof InputObjectType) { - $isArray = is_array($value); + $isArray = is_array($value); $isArrayLike = $isArray || $value instanceof \ArrayAccess; - if ($value === null || (!$isArrayLike && !is_object($value))) { + if ($value === null || (! $isArrayLike && ! is_object($value))) { return null; } - $fields = $type->getFields(); + $fields = $type->getFields(); $fieldNodes = []; foreach ($fields as $fieldName => $field) { if ($isArrayLike) { - $fieldValue = isset($value[$fieldName]) ? $value[$fieldName] : null; + $fieldValue = $value[$fieldName] ?? null; } else { - $fieldValue = isset($value->{$fieldName}) ? $value->{$fieldName} : null; + $fieldValue = $value->{$fieldName} ?? null; } // Have to check additionally if key exists, since we differentiate between // "no key" and "value is null": - if (null !== $fieldValue) { + if ($fieldValue !== null) { $fieldExists = true; - } else if ($isArray) { + } elseif ($isArray) { $fieldExists = array_key_exists($fieldName, $value); - } else if ($isArrayLike) { + } elseif ($isArrayLike) { /** @var \ArrayAccess $value */ $fieldExists = $value->offsetExists($fieldName); } else { $fieldExists = property_exists($value, $fieldName); } - if ($fieldExists) { - $fieldNode = self::astFromValue($fieldValue, $field->getType()); - - if ($fieldNode) { - $fieldNodes[] = new ObjectFieldNode([ - 'name' => new NameNode(['value' => $fieldName]), - 'value' => $fieldNode - ]); - } + if (! $fieldExists) { + continue; } + + $fieldNode = self::astFromValue($fieldValue, $field->getType()); + + if (! $fieldNode) { + continue; + } + + $fieldNodes[] = new ObjectFieldNode([ + 'name' => new NameNode(['value' => $fieldName]), + 'value' => $fieldNode, + ]); } + return new ObjectValueNode(['fields' => $fieldNodes]); } @@ -230,9 +257,12 @@ class AST return new IntValueNode(['value' => $serialized]); } if (is_float($serialized)) { + // int cast with == used for performance reasons + // @codingStandardsIgnoreLine if ((int) $serialized == $serialized) { return new IntValueNode(['value' => $serialized]); } + return new FloatValueNode(['value' => $serialized]); } if (is_string($serialized)) { @@ -250,7 +280,7 @@ class AST // Use json_encode, which uses the same string encoding as GraphQL, // then remove the quotes. return new StringValueNode([ - 'value' => substr(json_encode($serialized), 1, -1) + 'value' => substr(json_encode($serialized), 1, -1), ]); } @@ -280,17 +310,16 @@ class AST * | Null Value | null | * * @api - * @param $valueNode - * @param InputType $type - * @param mixed[]|null $variables - * @return array|null|\stdClass + * @param ValueNode|null $valueNode + * @param mixed[]|null $variables + * @return mixed[]|null|\stdClass * @throws \Exception */ public static function valueFromAST($valueNode, InputType $type, $variables = null) { $undefined = Utils::undefined(); - if (!$valueNode) { + if ($valueNode === null) { // When there is no AST, then there is also no value. // Importantly, this is different from returning the GraphQL null value. return $undefined; @@ -301,6 +330,7 @@ class AST // Invalid: intentionally return no value. return $undefined; } + return self::valueFromAST($valueNode, $type->getWrappedType(), $variables); } @@ -312,7 +342,7 @@ class AST if ($valueNode instanceof VariableNode) { $variableName = $valueNode->name->value; - if (!$variables || !array_key_exists($variableName, $variables)) { + if (! $variables || ! array_key_exists($variableName, $variables)) { // No valid return value. return $undefined; } @@ -327,7 +357,7 @@ class AST if ($valueNode instanceof ListValueNode) { $coercedValues = []; - $itemNodes = $valueNode->values; + $itemNodes = $valueNode->values; foreach ($itemNodes as $itemNode) { if (self::isMissingVariable($itemNode, $variables)) { // If an array contains a missing variable, it is either coerced to @@ -346,6 +376,7 @@ class AST $coercedValues[] = $itemValue; } } + return $coercedValues; } $coercedValue = self::valueFromAST($valueNode, $itemType, $variables); @@ -353,31 +384,37 @@ class AST // Invalid: intentionally return no value. return $undefined; } + return [$coercedValue]; } if ($type instanceof InputObjectType) { - if (!$valueNode instanceof ObjectValueNode) { + if (! $valueNode instanceof ObjectValueNode) { // Invalid: intentionally return no value. return $undefined; } $coercedObj = []; - $fields = $type->getFields(); - $fieldNodes = Utils::keyMap($valueNode->fields, function($field) {return $field->name->value;}); + $fields = $type->getFields(); + $fieldNodes = Utils::keyMap( + $valueNode->fields, + function ($field) { + return $field->name->value; + } + ); foreach ($fields as $field) { /** @var ValueNode $fieldNode */ $fieldName = $field->name; - $fieldNode = isset($fieldNodes[$fieldName]) ? $fieldNodes[$fieldName] : null; + $fieldNode = $fieldNodes[$fieldName] ?? null; - if (!$fieldNode || self::isMissingVariable($fieldNode->value, $variables)) { + if (! $fieldNode || self::isMissingVariable($fieldNode->value, $variables)) { if ($field->defaultValueExists()) { $coercedObj[$fieldName] = $field->defaultValue; - } else if ($field->getType() instanceof NonNull) { + } elseif ($field->getType() instanceof NonNull) { // Invalid: intentionally return no value. return $undefined; } - continue ; + continue; } $fieldValue = self::valueFromAST($fieldNode ? $fieldNode->value : null, $field->getType(), $variables); @@ -388,15 +425,16 @@ class AST } $coercedObj[$fieldName] = $fieldValue; } + return $coercedObj; } if ($type instanceof EnumType) { - if (!$valueNode instanceof EnumValueNode) { + if (! $valueNode instanceof EnumValueNode) { return $undefined; } $enumValue = $type->getValue($valueNode->value); - if (!$enumValue) { + if (! $enumValue) { return $undefined; } @@ -436,12 +474,13 @@ class AST * | Null | null | * * @api - * @param Node $valueNode - * @param array|null $variables + * @param Node $valueNode + * @param mixed[]|null $variables * @return mixed * @throws \Exception */ - public static function valueFromASTUntyped($valueNode, array $variables = null) { + public static function valueFromASTUntyped($valueNode, ?array $variables = null) + { switch (true) { case $valueNode instanceof NullValueNode: return null; @@ -455,24 +494,29 @@ class AST return $valueNode->value; case $valueNode instanceof ListValueNode: return array_map( - function($node) use ($variables) { + function ($node) use ($variables) { return self::valueFromASTUntyped($node, $variables); }, iterator_to_array($valueNode->values) ); case $valueNode instanceof ObjectValueNode: - return array_combine( - array_map( - function($field) { return $field->name->value; }, - iterator_to_array($valueNode->fields) - ), - array_map( - function($field) use ($variables) { return self::valueFromASTUntyped($field->value, $variables); }, - iterator_to_array($valueNode->fields) - ) - ); + return array_combine( + array_map( + function ($field) { + return $field->name->value; + }, + iterator_to_array($valueNode->fields) + ), + array_map( + function ($field) use ($variables) { + return self::valueFromASTUntyped($field->value, $variables); + }, + iterator_to_array($valueNode->fields) + ) + ); case $valueNode instanceof VariableNode: $variableName = $valueNode->name->value; + return ($variables && isset($variables[$variableName])) ? $variables[$variableName] : null; @@ -485,7 +529,6 @@ class AST * Returns type definition for given AST Type node * * @api - * @param Schema $schema * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode * @return Type|null * @throws \Exception @@ -494,10 +537,12 @@ class AST { if ($inputTypeNode instanceof ListTypeNode) { $innerType = self::typeFromAST($schema, $inputTypeNode->type); + return $innerType ? new ListOfType($innerType) : null; } if ($inputTypeNode instanceof NonNullTypeNode) { $innerType = self::typeFromAST($schema, $inputTypeNode->type); + return $innerType ? new NonNull($innerType) : null; } if ($inputTypeNode instanceof NamedTypeNode) { @@ -510,21 +555,20 @@ class AST /** * Returns true if the provided valueNode is a variable which is not defined * in the set of variables. - * @param $valueNode - * @param $variables + * @param ValueNode $valueNode + * @param mixed[] $variables * @return bool */ private static function isMissingVariable($valueNode, $variables) { return $valueNode instanceof VariableNode && - (!$variables || !array_key_exists($valueNode->name->value, $variables)); + (count($variables) === 0 || ! array_key_exists($valueNode->name->value, $variables)); } /** * Returns operation type ("query", "mutation" or "subscription") given a document and operation name * * @api - * @param DocumentNode $document * @param string $operationName * @return bool */ @@ -532,13 +576,16 @@ class AST { if ($document->definitions) { foreach ($document->definitions as $def) { - if ($def instanceof OperationDefinitionNode) { - if (!$operationName || (isset($def->name->value) && $def->name->value === $operationName)) { - return $def->operation; - } + if (! ($def instanceof OperationDefinitionNode)) { + continue; + } + + if (! $operationName || (isset($def->name->value) && $def->name->value === $operationName)) { + return $def->operation; } } } + return false; } } diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index 36c8ec8..8ca7d16 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -1,16 +1,21 @@ typeDefintionsMap = $typeDefintionsMap; + /** + * @param Node[] $typeDefintionsMap + * @param bool[] $options + */ + public function __construct( + array $typeDefintionsMap, + $options, + callable $resolveType, + ?callable $typeConfigDecorator = null + ) { + $this->typeDefintionsMap = $typeDefintionsMap; $this->typeConfigDecorator = $typeConfigDecorator; - $this->options = $options; - $this->resolveType = $resolveType; + $this->options = $options; + $this->resolveType = $resolveType; $this->cache = Type::getAllBuiltInTypes(); } /** - * @param Type $innerType * @param TypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode * @return Type */ private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode) { - if ($inputTypeNode->kind == NodeKind::LIST_TYPE) { + if ($inputTypeNode->kind === NodeKind::LIST_TYPE) { return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type)); } - if ($inputTypeNode->kind == NodeKind::NON_NULL_TYPE) { + if ($inputTypeNode->kind === NodeKind::NON_NULL_TYPE) { $wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type); + return Type::nonNull(NonNull::assertNullableType($wrappedType)); } + return $innerType; } @@ -95,37 +103,29 @@ class ASTDefinitionBuilder while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) { $namedType = $namedType->type; } + return $namedType; } /** - * @param string $typeName + * @param string $typeName * @param NamedTypeNode|null $typeNode * @return Type * @throws Error */ - private function internalBuildType($typeName, $typeNode = null) { - if (!isset($this->cache[$typeName])) { + private function internalBuildType($typeName, $typeNode = null) + { + if (! isset($this->cache[$typeName])) { if (isset($this->typeDefintionsMap[$typeName])) { $type = $this->makeSchemaDef($this->typeDefintionsMap[$typeName]); if ($this->typeConfigDecorator) { $fn = $this->typeConfigDecorator; try { $config = $fn($type->config, $this->typeDefintionsMap[$typeName], $this->typeDefintionsMap); - } catch (\Exception $e) { - throw new Error( - "Type config decorator passed to " . (static::class) . " threw an error " . - "when building $typeName type: {$e->getMessage()}", - null, - null, - null, - null, - $e - ); } catch (\Throwable $e) { throw new Error( - "Type config decorator passed to " . (static::class) . " threw an error " . - "when building $typeName type: {$e->getMessage()}", + sprintf('Type config decorator passed to %s threw an error ', static::class) . + sprintf('when building %s type: %s', $typeName, $e->getMessage()), null, null, null, @@ -133,17 +133,20 @@ class ASTDefinitionBuilder $e ); } - if (!is_array($config) || isset($config[0])) { + if (! is_array($config) || isset($config[0])) { throw new Error( - "Type config decorator passed to " . (static::class) . " is expected to return an array, but got " . - Utils::getVariableType($config) + sprintf( + 'Type config decorator passed to %s is expected to return an array, but got %s', + static::class, + Utils::getVariableType($config) + ) ); } $type = $this->makeSchemaDefFromConfig($this->typeDefintionsMap[$typeName], $config); } $this->cache[$typeName] = $type; } else { - $fn = $this->resolveType; + $fn = $this->resolveType; $this->cache[$typeName] = $fn($typeName, $typeNode); } } @@ -166,26 +169,29 @@ class ASTDefinitionBuilder } /** - * @param TypeNode $typeNode * @return Type|InputType * @throws Error */ private function internalBuildWrappedType(TypeNode $typeNode) { $typeDef = $this->buildType($this->getNamedTypeNode($typeNode)); + return $this->buildWrappedType($typeDef, $typeNode); } public function buildDirective(DirectiveDefinitionNode $directiveNode) { return new Directive([ - 'name' => $directiveNode->name->value, + 'name' => $directiveNode->name->value, 'description' => $this->getDescription($directiveNode), - 'locations' => Utils::map($directiveNode->locations, function ($node) { - return $node->value; - }), - 'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null, - 'astNode' => $directiveNode, + 'locations' => Utils::map( + $directiveNode->locations, + function ($node) { + return $node->value; + } + ), + 'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null, + 'astNode' => $directiveNode, ]); } @@ -195,17 +201,22 @@ class ASTDefinitionBuilder // Note: While this could make assertions to get the correctly typed // value, that would throw immediately while type system validation // with validateSchema() will produce more actionable results. - 'type' => $this->internalBuildWrappedType($field->type), - 'description' => $this->getDescription($field), - 'args' => $field->arguments ? $this->makeInputValues($field->arguments) : null, + 'type' => $this->internalBuildWrappedType($field->type), + 'description' => $this->getDescription($field), + 'args' => $field->arguments ? $this->makeInputValues($field->arguments) : null, 'deprecationReason' => $this->getDeprecationReason($field), - 'astNode' => $field, + 'astNode' => $field, ]; } + /** + * @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeDefinitionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode|UnionTypeDefinitionNode $def + * @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType + * @throws Error + */ private function makeSchemaDef($def) { - if (!$def) { + if (! $def) { throw new Error('def must be defined.'); } switch ($def->kind) { @@ -222,13 +233,19 @@ class ASTDefinitionBuilder case NodeKind::INPUT_OBJECT_TYPE_DEFINITION: return $this->makeInputObjectDef($def); default: - throw new Error("Type kind of {$def->kind} not supported."); + throw new Error(sprintf('Type kind of %s not supported.', $def->kind)); } } + /** + * @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeExtensionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode $def + * @param mixed[] $config + * @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType + * @throws Error + */ private function makeSchemaDefFromConfig($def, array $config) { - if (!$def) { + if (! $def) { throw new Error('def must be defined.'); } switch ($def->kind) { @@ -245,23 +262,24 @@ class ASTDefinitionBuilder case NodeKind::INPUT_OBJECT_TYPE_DEFINITION: return new InputObjectType($config); default: - throw new Error("Type kind of {$def->kind} not supported."); + throw new Error(sprintf('Type kind of %s not supported.', $def->kind)); } } private function makeTypeDef(ObjectTypeDefinitionNode $def) { $typeName = $def->name->value; + return new ObjectType([ - 'name' => $typeName, + 'name' => $typeName, 'description' => $this->getDescription($def), - 'fields' => function () use ($def) { + 'fields' => function () use ($def) { return $this->makeFieldDefMap($def); }, - 'interfaces' => function () use ($def) { + 'interfaces' => function () use ($def) { return $this->makeImplementedInterfaces($def); }, - 'astNode' => $def + 'astNode' => $def, ]); } @@ -286,10 +304,14 @@ class ASTDefinitionBuilder // Note: While this could make early assertions to get the correctly // typed values, that would throw immediately while type system // validation with validateSchema() will produce more actionable results. - return Utils::map($def->interfaces, function ($iface) { - return $this->buildType($iface); - }); + return Utils::map( + $def->interfaces, + function ($iface) { + return $this->buildType($iface); + } + ); } + return null; } @@ -304,16 +326,17 @@ class ASTDefinitionBuilder // Note: While this could make assertions to get the correctly typed // value, that would throw immediately while type system validation // with validateSchema() will produce more actionable results. - $type = $this->internalBuildWrappedType($value->type); + $type = $this->internalBuildWrappedType($value->type); $config = [ - 'name' => $value->name->value, - 'type' => $type, + 'name' => $value->name->value, + 'type' => $type, 'description' => $this->getDescription($value), - 'astNode' => $value + 'astNode' => $value, ]; if (isset($value->defaultValue)) { $config['defaultValue'] = AST::valueFromAST($value->defaultValue, $type); } + return $config; } ); @@ -322,22 +345,23 @@ class ASTDefinitionBuilder private function makeInterfaceDef(InterfaceTypeDefinitionNode $def) { $typeName = $def->name->value; + return new InterfaceType([ - 'name' => $typeName, + 'name' => $typeName, 'description' => $this->getDescription($def), - 'fields' => function () use ($def) { + 'fields' => function () use ($def) { return $this->makeFieldDefMap($def); }, - 'astNode' => $def + 'astNode' => $def, ]); } private function makeEnumDef(EnumTypeDefinitionNode $def) { return new EnumType([ - 'name' => $def->name->value, + 'name' => $def->name->value, 'description' => $this->getDescription($def), - 'values' => $def->values + 'values' => $def->values ? Utils::keyValMap( $def->values, function ($enumValue) { @@ -345,41 +369,44 @@ class ASTDefinitionBuilder }, function ($enumValue) { return [ - 'description' => $this->getDescription($enumValue), + 'description' => $this->getDescription($enumValue), 'deprecationReason' => $this->getDeprecationReason($enumValue), - 'astNode' => $enumValue + 'astNode' => $enumValue, ]; } ) : [], - 'astNode' => $def, + 'astNode' => $def, ]); } private function makeUnionDef(UnionTypeDefinitionNode $def) { return new UnionType([ - 'name' => $def->name->value, + 'name' => $def->name->value, 'description' => $this->getDescription($def), // Note: While this could make assertions to get the correctly typed // values below, that would throw immediately while type system // validation with validateSchema() will produce more actionable results. - 'types' => $def->types - ? Utils::map($def->types, function ($typeNode) { - return $this->buildType($typeNode); - }): + 'types' => $def->types + ? Utils::map( + $def->types, + function ($typeNode) { + return $this->buildType($typeNode); + } + ) : [], - 'astNode' => $def, + 'astNode' => $def, ]); } private function makeScalarDef(ScalarTypeDefinitionNode $def) { return new CustomScalarType([ - 'name' => $def->name->value, + 'name' => $def->name->value, 'description' => $this->getDescription($def), - 'astNode' => $def, - 'serialize' => function($value) { + 'astNode' => $def, + 'serialize' => function ($value) { return $value; }, ]); @@ -388,14 +415,14 @@ class ASTDefinitionBuilder private function makeInputObjectDef(InputObjectTypeDefinitionNode $def) { return new InputObjectType([ - 'name' => $def->name->value, + 'name' => $def->name->value, 'description' => $this->getDescription($def), - 'fields' => function () use ($def) { + 'fields' => function () use ($def) { return $def->fields ? $this->makeInputValues($def->fields) : []; }, - 'astNode' => $def, + 'astNode' => $def, ]); } @@ -409,7 +436,8 @@ class ASTDefinitionBuilder private function getDeprecationReason($node) { $deprecated = Values::getDirectiveValues(Directive::deprecatedDirective(), $node); - return isset($deprecated['reason']) ? $deprecated['reason'] : null; + + return $deprecated['reason'] ?? null; } /** @@ -433,21 +461,20 @@ class ASTDefinitionBuilder private function getLeadingCommentBlock($node) { $loc = $node->loc; - if (!$loc || !$loc->startToken) { + if (! $loc || ! $loc->startToken) { return null; } $comments = []; - $token = $loc->startToken->prev; - while ( - $token && + $token = $loc->startToken->prev; + while ($token && $token->kind === Token::COMMENT && $token->next && $token->prev && $token->line + 1 === $token->next->line && $token->line !== $token->prev->line ) { - $value = $token->value; + $value = $token->value; $comments[] = $value; - $token = $token->prev; + $token = $token->prev; } return implode("\n", array_reverse($comments)); diff --git a/src/Utils/BlockString.php b/src/Utils/BlockString.php index eac943d..5bc40e4 100644 --- a/src/Utils/BlockString.php +++ b/src/Utils/BlockString.php @@ -53,9 +53,9 @@ class BlockString { private static function leadingWhitespace($str) { $i = 0; while ($i < mb_strlen($str) && ($str[$i] === ' ' || $str[$i] === '\t')) { - $i++; + $i++; } return $i; } -} \ No newline at end of file +} diff --git a/src/Validator/Rules/KnownArgumentNames.php b/src/Validator/Rules/KnownArgumentNames.php index bbe3a2f..05a52ef 100644 --- a/src/Validator/Rules/KnownArgumentNames.php +++ b/src/Validator/Rules/KnownArgumentNames.php @@ -6,7 +6,9 @@ namespace GraphQL\Validator\Rules; use GraphQL\Error\Error; use GraphQL\Language\AST\ArgumentNode; +use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeKind; +use GraphQL\Language\AST\NodeList; use GraphQL\Utils\Utils; use GraphQL\Validator\ValidationContext; use function array_map; @@ -25,6 +27,7 @@ class KnownArgumentNames extends ValidationRule { return [ NodeKind::ARGUMENT => function (ArgumentNode $node, $key, $parent, $path, $ancestors) use ($context) { + /** @var NodeList|Node[] $ancestors */ $argDef = $context->getArgument(); if ($argDef !== null) { return; diff --git a/src/Validator/Rules/LoneAnonymousOperation.php b/src/Validator/Rules/LoneAnonymousOperation.php index 631b6a8..06730f7 100644 --- a/src/Validator/Rules/LoneAnonymousOperation.php +++ b/src/Validator/Rules/LoneAnonymousOperation.php @@ -6,6 +6,7 @@ namespace GraphQL\Validator\Rules; use GraphQL\Error\Error; use GraphQL\Language\AST\DocumentNode; +use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Utils\Utils; @@ -28,7 +29,7 @@ class LoneAnonymousOperation extends ValidationRule NodeKind::DOCUMENT => function (DocumentNode $node) use (&$operationCount) { $tmp = Utils::filter( $node->definitions, - function ($definition) { + function (Node $definition) { return $definition->kind === NodeKind::OPERATION_DEFINITION; } ); diff --git a/src/Validator/Rules/QuerySecurityRule.php b/src/Validator/Rules/QuerySecurityRule.php index ab97fed..810a4d0 100644 --- a/src/Validator/Rules/QuerySecurityRule.php +++ b/src/Validator/Rules/QuerySecurityRule.php @@ -111,7 +111,7 @@ abstract class QuerySecurityRule extends ValidationRule $_astAndDefs = $astAndDefs ?: new \ArrayObject(); foreach ($selectionSet->selections as $selection) { - switch ($selection->getKind()) { + switch ($selection->kind) { case NodeKind::FIELD: /** @var FieldNode $selection */ $fieldName = $selection->name->value; diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php index a9c4e4b..d8a834e 100644 --- a/src/Validator/ValidationContext.php +++ b/src/Validator/ValidationContext.php @@ -11,6 +11,7 @@ use GraphQL\Language\AST\FragmentSpreadNode; use GraphQL\Language\AST\HasSelectionSet; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\OperationDefinitionNode; +use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\VariableNode; use GraphQL\Language\Visitor; use GraphQL\Type\Definition\FieldDefinition; @@ -191,6 +192,7 @@ class ValidationContext $spreads = $this->fragmentSpreads[$node] ?? null; if (! $spreads) { $spreads = []; + /** @var SelectionSetNode[] $setsToVisit */ $setsToVisit = [$node->selectionSet]; while (! empty($setsToVisit)) { $set = array_pop($setsToVisit); diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php index 4ed2805..fd7bb20 100644 --- a/tests/Language/VisitorTest.php +++ b/tests/Language/VisitorTest.php @@ -1,4 +1,7 @@ toArray()) : $parent; + $parentArray = $parent && ! is_array($parent) ? ($parent instanceof NodeList ? iterator_to_array($parent) : $parent->toArray()) : $parent; $this->assertInstanceOf(Node::class, $node); $this->assertContains($node->kind, array_keys(NodeKind::$classMap)); $isRoot = $key === null; if ($isRoot) { - if (!$isEdited) { + if (! $isEdited) { $this->assertEquals($ast, $node); } $this->assertEquals(null, $parent); @@ -60,14 +71,16 @@ class VisitorTest extends ValidatorTestCase $this->assertInternalType('array', $ancestors); $this->assertCount(count($path) - 1, $ancestors); - if (!$isEdited) { - $this->assertEquals($node, $parentArray[$key]); - $this->assertEquals($node, $this->getNodeByPath($ast, $path)); - $ancestorsLength = count($ancestors); - for ($i = 0; $i < $ancestorsLength; ++$i) { - $ancestorPath = array_slice($path, 0, $i); - $this->assertEquals($ancestors[$i], $this->getNodeByPath($ast, $ancestorPath)); - } + if ($isEdited) { + return; + } + + $this->assertEquals($node, $parentArray[$key]); + $this->assertEquals($node, $this->getNodeByPath($ast, $path)); + $ancestorsLength = count($ancestors); + for ($i = 0; $i < $ancestorsLength; ++$i) { + $ancestorPath = array_slice($path, 0, $i); + $this->assertEquals($ancestors[$i], $this->getNodeByPath($ast, $ancestorPath)); } } @@ -104,94 +117,84 @@ class VisitorTest extends ValidatorTestCase $this->assertEquals($expected, $visited); } - /** - * @it allows editing a node both on enter and on leave - */ public function testAllowsEditingNodeOnEnterAndOnLeave() { $ast = Parser::parse('{ a, b, c { a, b, c } }', [ 'noLocation' => true ]); $selectionSet = null; - $editedAst = Visitor::visit($ast, [ + $editedAst = Visitor::visit($ast, [ NodeKind::OPERATION_DEFINITION => [ - 'enter' => function(OperationDefinitionNode $node) use (&$selectionSet, $ast) { + 'enter' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); $selectionSet = $node->selectionSet; - $newNode = clone $node; + $newNode = clone $node; $newNode->selectionSet = new SelectionSetNode([ - 'selections' => [] + 'selections' => [], ]); - $newNode->didEnter = true; + $newNode->didEnter = true; return $newNode; }, - 'leave' => function(OperationDefinitionNode $node) use (&$selectionSet, $ast) { + 'leave' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) { $this->checkVisitorFnArgs($ast, func_get_args(), true); - $newNode = clone $node; + $newNode = clone $node; $newNode->selectionSet = $selectionSet; - $newNode->didLeave = true; + $newNode->didLeave = true; return $newNode; - } - ] + }, + ], ]); $this->assertNotEquals($ast, $editedAst); - $expected = $ast->cloneDeep(); + $expected = $ast->cloneDeep(); $expected->definitions[0]->didEnter = true; $expected->definitions[0]->didLeave = true; $this->assertEquals($expected, $editedAst); } - /** - * @it allows editing the root node on enter and on leave - */ public function testAllowsEditingRootNodeOnEnterAndLeave() { - $ast = Parser::parse('{ a, b, c { a, b, c } }', [ 'noLocation' => true ]); + $ast = Parser::parse('{ a, b, c { a, b, c } }', [ 'noLocation' => true ]); $definitions = $ast->definitions; $editedAst = Visitor::visit($ast, [ NodeKind::DOCUMENT => [ 'enter' => function (DocumentNode $node) use ($ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $tmp = clone $node; + $tmp = clone $node; $tmp->definitions = []; - $tmp->didEnter = true; + $tmp->didEnter = true; return $tmp; }, - 'leave' => function(DocumentNode $node) use ($definitions, $ast) { + 'leave' => function (DocumentNode $node) use ($definitions, $ast) { $this->checkVisitorFnArgs($ast, func_get_args(), true); - $tmp = clone $node; $node->definitions = $definitions; - $node->didLeave = true; - } - ] + $node->didLeave = true; + }, + ], ]); $this->assertNotEquals($ast, $editedAst); - $tmp = $ast->cloneDeep(); + $tmp = $ast->cloneDeep(); $tmp->didEnter = true; $tmp->didLeave = true; $this->assertEquals($tmp, $editedAst); } - /** - * @it allows for editing on enter - */ public function testAllowsForEditingOnEnter() { - $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); + $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); $editedAst = Visitor::visit($ast, [ - 'enter' => function($node) use ($ast) { + 'enter' => function ($node) use ($ast) { $this->checkVisitorFnArgs($ast, func_get_args()); if ($node instanceof FieldNode && $node->name->value === 'b') { return Visitor::removeNode(); } - } + }, ]); $this->assertEquals( @@ -204,19 +207,16 @@ class VisitorTest extends ValidatorTestCase ); } - /** - * @it allows for editing on leave - */ public function testAllowsForEditingOnLeave() { - $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); + $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); $editedAst = Visitor::visit($ast, [ - 'leave' => function($node) use ($ast) { + 'leave' => function ($node) use ($ast) { $this->checkVisitorFnArgs($ast, func_get_args(), true); if ($node instanceof FieldNode && $node->name->value === 'b') { return Visitor::removeNode(); } - } + }, ]); $this->assertEquals( @@ -230,60 +230,54 @@ class VisitorTest extends ValidatorTestCase ); } - /** - * @it visits edited node - */ public function testVisitsEditedNode() { - $addedField = new FieldNode(array( - 'name' => new NameNode(array( - 'value' => '__typename' - )) - )); + $addedField = new FieldNode([ + 'name' => new NameNode(['value' => '__typename']), + ]); $didVisitAddedField = false; $ast = Parser::parse('{ a { x } }', ['noLocation' => true]); Visitor::visit($ast, [ - 'enter' => function($node) use ($addedField, &$didVisitAddedField, $ast) { + 'enter' => function ($node) use ($addedField, &$didVisitAddedField, $ast) { $this->checkVisitorFnArgs($ast, func_get_args(), true); if ($node instanceof FieldNode && $node->name->value === 'a') { return new FieldNode([ - 'selectionSet' => new SelectionSetNode(array( - 'selections' => NodeList::create([$addedField])->merge($node->selectionSet->selections) - )) + 'selectionSet' => new SelectionSetNode([ + 'selections' => NodeList::create([$addedField])->merge($node->selectionSet->selections), + ]), ]); } - if ($node === $addedField) { - $didVisitAddedField = true; + if ($node !== $addedField) { + return; } - } + + $didVisitAddedField = true; + }, ]); $this->assertTrue($didVisitAddedField); } - /** - * @it allows skipping a sub-tree - */ public function testAllowsSkippingASubTree() { $visited = []; - $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]); + $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]); Visitor::visit($ast, [ - 'enter' => function(Node $node) use (&$visited, $ast) { + 'enter' => function (Node $node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['enter', $node->kind, $node->value ?? null]; if ($node instanceof FieldNode && $node->name->value === 'b') { return Visitor::skipNode(); } }, 'leave' => function (Node $node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null]; - } + $visited[] = ['leave', $node->kind, $node->value ?? null]; + }, ]); $expected = [ @@ -301,32 +295,29 @@ class VisitorTest extends ValidatorTestCase [ 'leave', 'Field', null ], [ 'leave', 'SelectionSet', null ], [ 'leave', 'OperationDefinition', null ], - [ 'leave', 'Document', null ] + [ 'leave', 'Document', null ], ]; $this->assertEquals($expected, $visited); } - /** - * @it allows early exit while visiting - */ public function testAllowsEarlyExitWhileVisiting() { $visited = []; - $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]); + $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]); Visitor::visit($ast, [ - 'enter' => function(Node $node) use (&$visited, $ast) { + 'enter' => function (Node $node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['enter', $node->kind, $node->value ?? null]; if ($node instanceof NameNode && $node->value === 'x') { return Visitor::stop(); } }, - 'leave' => function(Node $node) use (&$visited, $ast) { + 'leave' => function (Node $node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null]; - } + $visited[] = ['leave', $node->kind, $node->value ?? null]; + }, ]); $expected = [ @@ -342,33 +333,30 @@ class VisitorTest extends ValidatorTestCase [ 'leave', 'Name', 'b' ], [ 'enter', 'SelectionSet', null ], [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'x' ] + [ 'enter', 'Name', 'x' ], ]; $this->assertEquals($expected, $visited); } - /** - * @it allows early exit while leaving - */ public function testAllowsEarlyExitWhileLeaving() { $visited = []; $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]); Visitor::visit($ast, [ - 'enter' => function($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['enter', $node->kind, $node->value ?? null]; }, - 'leave' => function($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['leave', $node->kind, $node->value ?? null]; if ($node->kind === NodeKind::NAME && $node->value === 'x') { return Visitor::stop(); } - } + }, ]); $this->assertEquals($visited, [ @@ -385,33 +373,30 @@ class VisitorTest extends ValidatorTestCase [ 'enter', 'SelectionSet', null ], [ 'enter', 'Field', null ], [ 'enter', 'Name', 'x' ], - [ 'leave', 'Name', 'x' ] + [ 'leave', 'Name', 'x' ], ]); } - /** - * @it allows a named functions visitor API - */ public function testAllowsANamedFunctionsVisitorAPI() { $visited = []; - $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]); + $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]); Visitor::visit($ast, [ - NodeKind::NAME => function(NameNode $node) use (&$visited, $ast) { + NodeKind::NAME => function (NameNode $node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $node->kind, $node->value]; }, NodeKind::SELECTION_SET => [ - 'enter' => function(SelectionSetNode $node) use (&$visited, $ast) { + 'enter' => function (SelectionSetNode $node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $node->kind, null]; }, - 'leave' => function(SelectionSetNode $node) use (&$visited, $ast) { + 'leave' => function (SelectionSetNode $node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['leave', $node->kind, null]; - } - ] + }, + ], ]); $expected = [ @@ -428,12 +413,9 @@ class VisitorTest extends ValidatorTestCase $this->assertEquals($expected, $visited); } - /** - * @it Experimental: visits variables defined in fragments - */ public function testExperimentalVisitsVariablesDefinedInFragments() { - $ast = Parser::parse( + $ast = Parser::parse( 'fragment a($v: Boolean = false) on t { f }', [ 'noLocation' => true, @@ -443,13 +425,13 @@ class VisitorTest extends ValidatorTestCase $visited = []; Visitor::visit($ast, [ - 'enter' => function($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['enter', $node->kind, $node->value ?? null]; }, - 'leave' => function($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['leave', $node->kind, $node->value ?? null]; }, ]); @@ -487,26 +469,23 @@ class VisitorTest extends ValidatorTestCase $this->assertEquals($expected, $visited); } - /** - * @it visits kitchen sink - */ public function testVisitsKitchenSink() { $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); - $ast = Parser::parse($kitchenSink); + $ast = Parser::parse($kitchenSink); $visited = []; Visitor::visit($ast, [ - 'enter' => function(Node $node, $key, $parent) use (&$visited, $ast) { + 'enter' => function (Node $node, $key, $parent) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $r = ['enter', $node->kind, $key, $parent instanceof Node ? $parent->kind : null]; + $r = ['enter', $node->kind, $key, $parent instanceof Node ? $parent->kind : null]; $visited[] = $r; }, - 'leave' => function(Node $node, $key, $parent) use (&$visited, $ast) { + 'leave' => function (Node $node, $key, $parent) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $r = ['leave', $node->kind, $key, $parent instanceof Node ? $parent->kind : null]; + $r = ['leave', $node->kind, $key, $parent instanceof Node ? $parent->kind : null]; $visited[] = $r; - } + }, ]); $expected = [ @@ -819,17 +798,15 @@ class VisitorTest extends ValidatorTestCase [ 'leave', 'Field', 1, null ], [ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ], [ 'leave', 'OperationDefinition', 4, null ], - [ 'leave', 'Document', null, null ] + [ 'leave', 'Document', null, null ], ]; $this->assertEquals($expected, $visited); } - // Describe: visitInParallel - // Note: nearly identical to the above test of the same test but using visitInParallel. - /** - * @it allows skipping a sub-tree + * Describe: visitInParallel + * Note: nearly identical to the above test of the same test but using visitInParallel. */ public function testAllowsSkippingSubTree() { @@ -838,20 +815,20 @@ class VisitorTest extends ValidatorTestCase $ast = Parser::parse('{ a, b { x }, c }'); Visitor::visit($ast, Visitor::visitInParallel([ [ - 'enter' => function($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = [ 'enter', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = [ 'enter', $node->kind, $node->value ?? null]; if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { return Visitor::skipNode(); } }, - 'leave' => function($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null]; - } - ] + $visited[] = ['leave', $node->kind, $node->value ?? null]; + }, + ], ])); $this->assertEquals([ @@ -873,9 +850,6 @@ class VisitorTest extends ValidatorTestCase ], $visited); } - /** - * @it allows skipping different sub-trees - */ public function testAllowsSkippingDifferentSubTrees() { $visited = []; @@ -883,31 +857,31 @@ class VisitorTest extends ValidatorTestCase $ast = Parser::parse('{ a { x }, b { y} }'); Visitor::visit($ast, Visitor::visitInParallel([ [ - 'enter' => function($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['no-a', 'enter', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['no-a', 'enter', $node->kind, $node->value ?? null]; if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'a') { return Visitor::skipNode(); } }, - 'leave' => function($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = [ 'no-a', 'leave', $node->kind, isset($node->value) ? $node->value : null ]; - } + $visited[] = [ 'no-a', 'leave', $node->kind, $node->value ?? null ]; + }, ], [ - 'enter' => function($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['no-b', 'enter', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['no-b', 'enter', $node->kind, $node->value ?? null]; if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { return Visitor::skipNode(); } }, - 'leave' => function($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['no-b', 'leave', $node->kind, isset($node->value) ? $node->value : null]; - } - ] + $visited[] = ['no-b', 'leave', $node->kind, $node->value ?? null]; + }, + ], ])); $this->assertEquals([ @@ -948,28 +922,26 @@ class VisitorTest extends ValidatorTestCase ], $visited); } - /** - * @it allows early exit while visiting - */ public function testAllowsEarlyExitWhileVisiting2() { $visited = []; $ast = Parser::parse('{ a, b { x }, c }'); Visitor::visit($ast, Visitor::visitInParallel([ [ - 'enter' => function($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $value = isset($node->value) ? $node->value : null; + $value = $node->value ?? null; $visited[] = ['enter', $node->kind, $value]; if ($node->kind === 'Name' && $value === 'x') { return Visitor::stop(); } }, - 'leave' => function($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null]; - } - ] ])); + $visited[] = ['leave', $node->kind, $node->value ?? null]; + }, + ], + ])); $this->assertEquals([ [ 'enter', 'Document', null ], @@ -984,13 +956,10 @@ class VisitorTest extends ValidatorTestCase [ 'leave', 'Name', 'b' ], [ 'enter', 'SelectionSet', null ], [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'x' ] + [ 'enter', 'Name', 'x' ], ], $visited); } - /** - * @it allows early exit from different points - */ public function testAllowsEarlyExitFromDifferentPoints() { $visited = []; @@ -998,32 +967,32 @@ class VisitorTest extends ValidatorTestCase $ast = Parser::parse('{ a { y }, b { x } }'); Visitor::visit($ast, Visitor::visitInParallel([ [ - 'enter' => function($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $value = isset($node->value) ? $node->value : null; + $value = $node->value ?? null; $visited[] = ['break-a', 'enter', $node->kind, $value]; if ($node->kind === 'Name' && $value === 'a') { return Visitor::stop(); } }, - 'leave' => function($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = [ 'break-a', 'leave', $node->kind, isset($node->value) ? $node->value : null ]; - } + $visited[] = [ 'break-a', 'leave', $node->kind, $node->value ?? null ]; + }, ], [ - 'enter' => function($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $value = isset($node->value) ? $node->value : null; + $value = $node->value ?? null; $visited[] = ['break-b', 'enter', $node->kind, $value]; if ($node->kind === 'Name' && $value === 'b') { return Visitor::stop(); } }, - 'leave' => function($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['break-b', 'leave', $node->kind, isset($node->value) ? $node->value : null]; - } + $visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null]; + }, ], ])); @@ -1047,32 +1016,30 @@ class VisitorTest extends ValidatorTestCase [ 'break-b', 'leave', 'SelectionSet', null ], [ 'break-b', 'leave', 'Field', null ], [ 'break-b', 'enter', 'Field', null ], - [ 'break-b', 'enter', 'Name', 'b' ] + [ 'break-b', 'enter', 'Name', 'b' ], ], $visited); } - /** - * @it allows early exit while leaving - */ public function testAllowsEarlyExitWhileLeaving2() { $visited = []; $ast = Parser::parse('{ a, b { x }, c }'); Visitor::visit($ast, Visitor::visitInParallel([ [ - 'enter' => function($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['enter', $node->kind, $node->value ?? null]; }, - 'leave' => function($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $value = isset($node->value) ? $node->value : null; + $value = $node->value ?? null; $visited[] = ['leave', $node->kind, $value]; if ($node->kind === 'Name' && $value === 'x') { return Visitor::stop(); } - } - ] ])); + }, + ], + ])); $this->assertEquals([ [ 'enter', 'Document', null ], @@ -1088,13 +1055,10 @@ class VisitorTest extends ValidatorTestCase [ 'enter', 'SelectionSet', null ], [ 'enter', 'Field', null ], [ 'enter', 'Name', 'x' ], - [ 'leave', 'Name', 'x' ] + [ 'leave', 'Name', 'x' ], ], $visited); } - /** - * @it allows early exit from leaving different points - */ public function testAllowsEarlyExitFromLeavingDifferentPoints() { $visited = []; @@ -1102,30 +1066,30 @@ class VisitorTest extends ValidatorTestCase $ast = Parser::parse('{ a { y }, b { x } }'); Visitor::visit($ast, Visitor::visitInParallel([ [ - 'enter' => function($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['break-a', 'enter', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['break-a', 'enter', $node->kind, $node->value ?? null]; }, - 'leave' => function($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['break-a', 'leave', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['break-a', 'leave', $node->kind, $node->value ?? null]; if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'a') { return Visitor::stop(); } - } + }, ], [ - 'enter' => function($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['break-b', 'enter', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['break-b', 'enter', $node->kind, $node->value ?? null]; }, - 'leave' => function($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['break-b', 'leave', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null]; if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { return Visitor::stop(); } - } + }, ], ])); @@ -1165,18 +1129,15 @@ class VisitorTest extends ValidatorTestCase [ 'break-b', 'leave', 'Name', 'x' ], [ 'break-b', 'leave', 'Field', null ], [ 'break-b', 'leave', 'SelectionSet', null ], - [ 'break-b', 'leave', 'Field', null ] + [ 'break-b', 'leave', 'Field', null ], ], $visited); } - /** - * @it allows for editing on enter - */ public function testAllowsForEditingOnEnter2() { $visited = []; - $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); + $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); $editedAst = Visitor::visit($ast, Visitor::visitInParallel([ [ 'enter' => function ($node) use (&$visited, $ast) { @@ -1184,17 +1145,17 @@ class VisitorTest extends ValidatorTestCase if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { return Visitor::removeNode(); } - } + }, ], [ 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['enter', $node->kind, $node->value ?? null]; }, 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args(), true); - $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null]; - } + $visited[] = ['leave', $node->kind, $node->value ?? null]; + }, ], ])); @@ -1232,18 +1193,15 @@ class VisitorTest extends ValidatorTestCase ['leave', 'Field', null], ['leave', 'SelectionSet', null], ['leave', 'OperationDefinition', null], - ['leave', 'Document', null] + ['leave', 'Document', null], ], $visited); } - /** - * @it allows for editing on leave - */ public function testAllowsForEditingOnLeave2() { $visited = []; - $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); + $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); $editedAst = Visitor::visit($ast, Visitor::visitInParallel([ [ 'leave' => function ($node) use (&$visited, $ast) { @@ -1251,17 +1209,17 @@ class VisitorTest extends ValidatorTestCase if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { return Visitor::removeNode(); } - } + }, ], [ 'enter' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null]; + $visited[] = ['enter', $node->kind, $node->value ?? null]; }, 'leave' => function ($node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args(), true); - $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null]; - } + $visited[] = ['leave', $node->kind, $node->value ?? null]; + }, ], ])); @@ -1305,14 +1263,13 @@ class VisitorTest extends ValidatorTestCase ['leave', 'Field', null], ['leave', 'SelectionSet', null], ['leave', 'OperationDefinition', null], - ['leave', 'Document', null] + ['leave', 'Document', null], ], $visited); } - // Describe: visitWithTypeInfo /** - * @it maintains type info during visit + * Describe: visitWithTypeInfo */ public function testMaintainsTypeInfoDuringVisit() { @@ -1325,31 +1282,31 @@ class VisitorTest extends ValidatorTestCase 'enter' => function ($node) use ($typeInfo, &$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); $parentType = $typeInfo->getParentType(); - $type = $typeInfo->getType(); - $inputType = $typeInfo->getInputType(); - $visited[] = [ + $type = $typeInfo->getType(); + $inputType = $typeInfo->getInputType(); + $visited[] = [ 'enter', $node->kind, $node->kind === 'Name' ? $node->value : null, - $parentType ? (string)$parentType : null, - $type ? (string)$type : null, - $inputType ? (string)$inputType : null + $parentType ? (string) $parentType : null, + $type ? (string) $type : null, + $inputType ? (string) $inputType : null, ]; }, 'leave' => function ($node) use ($typeInfo, &$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); $parentType = $typeInfo->getParentType(); - $type = $typeInfo->getType(); - $inputType = $typeInfo->getInputType(); - $visited[] = [ + $type = $typeInfo->getType(); + $inputType = $typeInfo->getInputType(); + $visited[] = [ 'leave', $node->kind, $node->kind === 'Name' ? $node->value : null, - $parentType ? (string)$parentType : null, - $type ? (string)$type : null, - $inputType ? (string)$inputType : null + $parentType ? (string) $parentType : null, + $type ? (string) $type : null, + $inputType ? (string) $inputType : null, ]; - } + }, ])); $this->assertEquals([ @@ -1392,40 +1349,36 @@ class VisitorTest extends ValidatorTestCase ['leave', 'Field', null, 'QueryRoot', 'Human', null], ['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null], ['leave', 'OperationDefinition', null, null, 'QueryRoot', null], - ['leave', 'Document', null, null, null, null] + ['leave', 'Document', null, null, null, null], ], $visited); } - /** - * @it maintains type info during edit - */ public function testMaintainsTypeInfoDuringEdit() { - $visited = []; + $visited = []; $typeInfo = new TypeInfo(ValidatorTestCase::getTestSchema()); - $ast = Parser::parse( + $ast = Parser::parse( '{ human(id: 4) { name, pets }, alien }' ); $editedAst = Visitor::visit($ast, Visitor::visitWithTypeInfo($typeInfo, [ 'enter' => function ($node) use ($typeInfo, &$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args(), true); $parentType = $typeInfo->getParentType(); - $type = $typeInfo->getType(); - $inputType = $typeInfo->getInputType(); - $visited[] = [ + $type = $typeInfo->getType(); + $inputType = $typeInfo->getInputType(); + $visited[] = [ 'enter', $node->kind, $node->kind === 'Name' ? $node->value : null, - $parentType ? (string)$parentType : null, - $type ? (string)$type : null, - $inputType ? (string)$inputType : null + $parentType ? (string) $parentType : null, + $type ? (string) $type : null, + $inputType ? (string) $inputType : null, ]; // Make a query valid by adding missing selection sets. - if ( - $node->kind === 'Field' && - !$node->selectionSet && + if ($node->kind === 'Field' && + ! $node->selectionSet && Type::isCompositeType(Type::getNamedType($type)) ) { return new FieldNode([ @@ -1435,29 +1388,28 @@ class VisitorTest extends ValidatorTestCase 'directives' => $node->directives, 'selectionSet' => new SelectionSetNode([ 'kind' => 'SelectionSet', - 'selections' => [ - new FieldNode([ - 'name' => new NameNode(['value' => '__typename']) - ]) - ] - ]) + 'selections' => [new FieldNode([ + 'name' => new NameNode(['value' => '__typename']), + ]), + ], + ]), ]); } }, 'leave' => function ($node) use ($typeInfo, &$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args(), true); $parentType = $typeInfo->getParentType(); - $type = $typeInfo->getType(); - $inputType = $typeInfo->getInputType(); - $visited[] = [ + $type = $typeInfo->getType(); + $inputType = $typeInfo->getInputType(); + $visited[] = [ 'leave', $node->kind, $node->kind === 'Name' ? $node->value : null, - $parentType ? (string)$parentType : null, - $type ? (string)$type : null, - $inputType ? (string)$inputType : null + $parentType ? (string) $parentType : null, + $type ? (string) $type : null, + $inputType ? (string) $inputType : null, ]; - } + }, ])); $this->assertEquals(Printer::doPrint(Parser::parse( @@ -1510,7 +1462,7 @@ class VisitorTest extends ValidatorTestCase ['leave', 'Field', null, 'QueryRoot', 'Alien', null], ['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null], ['leave', 'OperationDefinition', null, null, 'QueryRoot', null], - ['leave', 'Document', null, null, null, null] + ['leave', 'Document', null, null, null, null], ], $visited); } }