diff --git a/tests/Language/LexerTest.php b/tests/Language/LexerTest.php index a109232..3cdd6ef 100644 --- a/tests/Language/LexerTest.php +++ b/tests/Language/LexerTest.php @@ -1,13 +1,18 @@ expectException(SyntaxError::class); + $this->expectExceptionMessage($message); + try { + $this->lexOne($text); + } catch (SyntaxError $error) { + $this->assertEquals([$location], $error->getLocations()); + throw $error; + } + } + + /** + * @param string $body + * @return Token + */ + private function lexOne($body) + { + $lexer = new Lexer(new Source($body)); + + return $lexer->advance(); + } + + private function loc($line, $column) + { + return new SourceLocation($line, $column); + } + /** * @see it('accepts BOM header') */ public function testAcceptsBomHeader() : void { - $bom = Utils::chr(0xFEFF); + $bom = Utils::chr(0xFEFF); $expected = [ - 'kind' => Token::NAME, + 'kind' => Token::NAME, 'start' => 2, - 'end' => 5, - 'value' => 'foo' + 'end' => 5, + 'value' => 'foo', ]; $this->assertArraySubset($expected, (array) $this->lexOne($bom . ' foo')); @@ -45,12 +78,12 @@ class LexerTest extends TestCase public function testRecordsLineAndColumn() : void { $expected = [ - 'kind' => Token::NAME, - 'start' => 8, - 'end' => 11, - 'line' => 4, + 'kind' => Token::NAME, + 'start' => 8, + 'end' => 11, + 'line' => 4, 'column' => 3, - 'value' => 'foo' + 'value' => 'foo', ]; $this->assertArraySubset($expected, (array) $this->lexOne("\n \r\n \r foo\n")); } @@ -67,10 +100,10 @@ class LexerTest extends TestCase '; $expected = [ - 'kind' => Token::NAME, + 'kind' => Token::NAME, 'start' => 6, - 'end' => 9, - 'value' => 'foo' + 'end' => 9, + 'value' => 'foo', ]; $this->assertArraySubset($expected, (array) $this->lexOne($example1)); @@ -80,18 +113,18 @@ class LexerTest extends TestCase '; $expected = [ - 'kind' => Token::NAME, + 'kind' => Token::NAME, 'start' => 18, - 'end' => 21, - 'value' => 'foo' + 'end' => 21, + 'value' => 'foo', ]; $this->assertArraySubset($expected, (array) $this->lexOne($example2)); $expected = [ - 'kind' => Token::NAME, + 'kind' => Token::NAME, 'start' => 3, - 'end' => 6, - 'value' => 'foo' + 'end' => 6, + 'value' => 'foo', ]; $example3 = ',,,foo,,,'; @@ -131,7 +164,7 @@ class LexerTest extends TestCase */ public function testUpdatesLineNumbersInErrorForFileContext() : void { - $str = '' . + $str = '' . "\n" . "\n" . " ?\n" . @@ -181,63 +214,86 @@ class LexerTest extends TestCase */ public function testLexesStrings() : void { - $this->assertArraySubset([ - 'kind' => Token::STRING, - 'start' => 0, - 'end' => 8, - 'value' => 'simple' - ], (array) $this->lexOne('"simple"')); + $this->assertArraySubset( + [ + 'kind' => Token::STRING, + 'start' => 0, + 'end' => 8, + 'value' => 'simple', + ], + (array) $this->lexOne('"simple"') + ); + $this->assertArraySubset( + [ + 'kind' => Token::STRING, + 'start' => 0, + 'end' => 15, + 'value' => ' white space ', + ], + (array) $this->lexOne('" white space "') + ); - $this->assertArraySubset([ - 'kind' => Token::STRING, - 'start' => 0, - 'end' => 15, - 'value' => ' white space ' - ], (array) $this->lexOne('" white space "')); + $this->assertArraySubset( + [ + 'kind' => Token::STRING, + 'start' => 0, + 'end' => 10, + 'value' => 'quote "', + ], + (array) $this->lexOne('"quote \\""') + ); - $this->assertArraySubset([ - 'kind' => Token::STRING, - 'start' => 0, - 'end' => 10, - 'value' => 'quote "' - ], (array) $this->lexOne('"quote \\""')); + $this->assertArraySubset( + [ + 'kind' => Token::STRING, + 'start' => 0, + 'end' => 25, + 'value' => 'escaped \n\r\b\t\f', + ], + (array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"') + ); - $this->assertArraySubset([ - 'kind' => Token::STRING, - 'start' => 0, - 'end' => 25, - 'value' => 'escaped \n\r\b\t\f' - ], (array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"')); + $this->assertArraySubset( + [ + 'kind' => Token::STRING, + 'start' => 0, + 'end' => 16, + 'value' => 'slashes \\ \/', + ], + (array) $this->lexOne('"slashes \\\\ \\\\/"') + ); - $this->assertArraySubset([ - 'kind' => Token::STRING, - 'start' => 0, - 'end' => 16, - 'value' => 'slashes \\ \/' - ], (array) $this->lexOne('"slashes \\\\ \\\\/"')); - - $this->assertArraySubset([ - 'kind' => Token::STRING, - 'start' => 0, - 'end' => 13, - 'value' => 'unicode яуц' - ], (array) $this->lexOne('"unicode яуц"')); + $this->assertArraySubset( + [ + 'kind' => Token::STRING, + 'start' => 0, + 'end' => 13, + 'value' => 'unicode яуц', + ], + (array) $this->lexOne('"unicode яуц"') + ); $unicode = json_decode('"\u1234\u5678\u90AB\uCDEF"'); - $this->assertArraySubset([ - 'kind' => Token::STRING, - 'start' => 0, - 'end' => 34, - 'value' => 'unicode ' . $unicode - ], (array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"')); + $this->assertArraySubset( + [ + 'kind' => Token::STRING, + 'start' => 0, + 'end' => 34, + 'value' => 'unicode ' . $unicode, + ], + (array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"') + ); - $this->assertArraySubset([ - 'kind' => Token::STRING, - 'start' => 0, - 'end' => 26, - 'value' => $unicode - ], (array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"')); + $this->assertArraySubset( + [ + 'kind' => Token::STRING, + 'start' => 0, + 'end' => 26, + 'value' => $unicode, + ], + (array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"') + ); } /** @@ -245,86 +301,128 @@ class LexerTest extends TestCase */ public function testLexesBlockString() : void { - $this->assertArraySubset([ - 'kind' => Token::BLOCK_STRING, - 'start' => 0, - 'end' => 12, - 'value' => 'simple' - ], (array) $this->lexOne('"""simple"""')); + $this->assertArraySubset( + [ + 'kind' => Token::BLOCK_STRING, + 'start' => 0, + 'end' => 12, + 'value' => 'simple', + ], + (array) $this->lexOne('"""simple"""') + ); - $this->assertArraySubset([ - 'kind' => Token::BLOCK_STRING, - 'start' => 0, - 'end' => 19, - 'value' => ' white space ' - ], (array) $this->lexOne('""" white space """')); + $this->assertArraySubset( + [ + 'kind' => Token::BLOCK_STRING, + 'start' => 0, + 'end' => 19, + 'value' => ' white space ', + ], + (array) $this->lexOne('""" white space """') + ); - $this->assertArraySubset([ - 'kind' => Token::BLOCK_STRING, - 'start' => 0, - 'end' => 22, - 'value' => 'contains " quote' - ], (array) $this->lexOne('"""contains " quote"""')); + $this->assertArraySubset( + [ + 'kind' => Token::BLOCK_STRING, + 'start' => 0, + 'end' => 22, + 'value' => 'contains " quote', + ], + (array) $this->lexOne('"""contains " quote"""') + ); - $this->assertArraySubset([ - 'kind' => Token::BLOCK_STRING, - 'start' => 0, - 'end' => 31, - 'value' => 'contains """ triplequote' - ], (array) $this->lexOne('"""contains \\""" triplequote"""')); + $this->assertArraySubset( + [ + 'kind' => Token::BLOCK_STRING, + 'start' => 0, + 'end' => 31, + 'value' => 'contains """ triplequote', + ], + (array) $this->lexOne('"""contains \\""" triplequote"""') + ); - $this->assertArraySubset([ - 'kind' => Token::BLOCK_STRING, - 'start' => 0, - 'end' => 16, - 'value' => "multi\nline" - ], (array) $this->lexOne("\"\"\"multi\nline\"\"\"")); + $this->assertArraySubset( + [ + 'kind' => Token::BLOCK_STRING, + 'start' => 0, + 'end' => 16, + 'value' => "multi\nline", + ], + (array) $this->lexOne("\"\"\"multi\nline\"\"\"") + ); - $this->assertArraySubset([ - 'kind' => Token::BLOCK_STRING, - 'start' => 0, - 'end' => 28, - 'value' => "multi\nline\nnormalized" - ], (array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\"")); + $this->assertArraySubset( + [ + 'kind' => Token::BLOCK_STRING, + 'start' => 0, + 'end' => 28, + 'value' => "multi\nline\nnormalized", + ], + (array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\"") + ); - $this->assertArraySubset([ - 'kind' => Token::BLOCK_STRING, - 'start' => 0, - 'end' => 32, - 'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234' - ], (array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""')); + $this->assertArraySubset( + [ + 'kind' => Token::BLOCK_STRING, + 'start' => 0, + 'end' => 32, + 'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234', + ], + (array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""') + ); - $this->assertArraySubset([ - 'kind' => Token::BLOCK_STRING, - 'start' => 0, - 'end' => 19, - 'value' => 'slashes \\\\ \\/' - ], (array) $this->lexOne('"""slashes \\\\ \\/"""')); + $this->assertArraySubset( + [ + 'kind' => Token::BLOCK_STRING, + 'start' => 0, + 'end' => 19, + 'value' => 'slashes \\\\ \\/', + ], + (array) $this->lexOne('"""slashes \\\\ \\/"""') + ); - $this->assertArraySubset([ - 'kind' => Token::BLOCK_STRING, - 'start' => 0, - 'end' => 68, - 'value' => "spans\n multiple\n lines" - ], (array) $this->lexOne("\"\"\" + $this->assertArraySubset( + [ + 'kind' => Token::BLOCK_STRING, + 'start' => 0, + 'end' => 68, + 'value' => "spans\n multiple\n lines", + ], + (array) $this->lexOne('""" spans multiple lines - \"\"\"")); + """') + ); } - public function reportsUsefulStringErrors() { + public function reportsUsefulStringErrors() + { return [ - ['"', "Unterminated string.", $this->loc(1, 2)], - ['"no end quote', "Unterminated string.", $this->loc(1, 14)], - ["'single quotes'", "Unexpected single quote character ('), did you mean to use a double quote (\")?", $this->loc(1, 1)], - ['"contains unescaped \u0007 control char"', "Invalid character within String: \"\\u0007\"", $this->loc(1, 21)], + ['"', 'Unterminated string.', $this->loc(1, 2)], + ['"no end quote', 'Unterminated string.', $this->loc(1, 14)], + [ + "'single quotes'", + "Unexpected single quote character ('), did you mean to use a double quote (\")?", + $this->loc( + 1, + 1 + ), + ], + [ + '"contains unescaped \u0007 control char"', + "Invalid character within String: \"\\u0007\"", + $this->loc( + 1, + 21 + ), + ], ['"null-byte is not \u0000 end of file"', 'Invalid character within String: "\\u0000"', $this->loc(1, 19)], - ['"multi' . "\n" . 'line"', "Unterminated string.", $this->loc(1, 7)], - ['"multi' . "\r" . 'line"', "Unterminated string.", $this->loc(1, 7)], - ['"bad \\z esc"', "Invalid character escape sequence: \\z", $this->loc(1, 7)], + ['"multi' . "\n" . 'line"', 'Unterminated string.', $this->loc(1, 7)], + ['"multi' . "\r" . 'line"', 'Unterminated string.', $this->loc(1, 7)], + ['"bad \\z esc"', 'Invalid character escape sequence: \\z', $this->loc(1, 7)], ['"bad \\x esc"', "Invalid character escape sequence: \\x", $this->loc(1, 7)], ['"bad \\u1 esc"', "Invalid character escape sequence: \\u1 es", $this->loc(1, 7)], ['"bad \\u0XX1 esc"', "Invalid character escape sequence: \\u0XX1", $this->loc(1, 7)], @@ -336,25 +434,40 @@ class LexerTest extends TestCase /** * @dataProvider reportsUsefulStringErrors - * @see it('lex reports useful string errors') + * @see it('lex reports useful string errors') */ public function testLexReportsUsefulStringErrors($str, $expectedMessage, $location) : void { $this->expectSyntaxError($str, $expectedMessage, $location); } - public function reportsUsefulBlockStringErrors() { + public function reportsUsefulBlockStringErrors() + { return [ - ['"""', "Unterminated string.", $this->loc(1, 4)], - ['"""no end quote', "Unterminated string.", $this->loc(1, 16)], - ['"""contains unescaped ' . json_decode('"\u0007"') . ' control char"""', "Invalid character within String: \"\\u0007\"", $this->loc(1, 23)], - ['"""null-byte is not ' . json_decode('"\u0000"') . ' end of file"""', "Invalid character within String: \"\\u0000\"", $this->loc(1, 21)], + ['"""', 'Unterminated string.', $this->loc(1, 4)], + ['"""no end quote', 'Unterminated string.', $this->loc(1, 16)], + [ + '"""contains unescaped ' . json_decode('"\u0007"') . ' control char"""', + "Invalid character within String: \"\\u0007\"", + $this->loc( + 1, + 23 + ), + ], + [ + '"""null-byte is not ' . json_decode('"\u0000"') . ' end of file"""', + "Invalid character within String: \"\\u0000\"", + $this->loc( + 1, + 21 + ), + ], ]; } /** * @dataProvider reportsUsefulBlockStringErrors - * @see it('lex reports useful block string errors') + * @see it('lex reports useful block string errors') */ public function testReportsUsefulBlockStringErrors($str, $expectedMessage, $location) : void { @@ -435,21 +548,21 @@ class LexerTest extends TestCase public function reportsUsefulNumberErrors() { return [ - [ '00', "Invalid number, unexpected digit after 0: \"0\"", $this->loc(1, 2)], - [ '+1', "Cannot parse the unexpected character \"+\".", $this->loc(1, 1)], - [ '1.', "Invalid number, expected digit but got: ", $this->loc(1, 3)], - [ '1.e1', "Invalid number, expected digit but got: \"e\"", $this->loc(1, 3)], - [ '.123', "Cannot parse the unexpected character \".\".", $this->loc(1, 1)], - [ '1.A', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 3)], - [ '-A', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 2)], - [ '1.0e', "Invalid number, expected digit but got: ", $this->loc(1, 5)], - [ '1.0eA', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 5)], + ['00', 'Invalid number, unexpected digit after 0: "0"', $this->loc(1, 2)], + ['+1', 'Cannot parse the unexpected character "+".', $this->loc(1, 1)], + ['1.', 'Invalid number, expected digit but got: ', $this->loc(1, 3)], + ['1.e1', 'Invalid number, expected digit but got: "e"', $this->loc(1, 3)], + ['.123', 'Cannot parse the unexpected character ".".', $this->loc(1, 1)], + ['1.A', 'Invalid number, expected digit but got: "A"', $this->loc(1, 3)], + ['-A', 'Invalid number, expected digit but got: "A"', $this->loc(1, 2)], + ['1.0e', 'Invalid number, expected digit but got: ', $this->loc(1, 5)], + ['1.0eA', 'Invalid number, expected digit but got: "A"', $this->loc(1, 5)], ]; } /** * @dataProvider reportsUsefulNumberErrors - * @see it('lex reports useful number errors') + * @see it('lex reports useful number errors') */ public function testReportsUsefulNumberErrors($str, $expectedMessage, $location) : void { @@ -521,8 +634,8 @@ class LexerTest extends TestCase $unicode2 = json_decode('"\u200b"'); return [ - ['..', "Cannot parse the unexpected character \".\".", $this->loc(1, 1)], - ['?', "Cannot parse the unexpected character \"?\".", $this->loc(1, 1)], + ['..', 'Cannot parse the unexpected character ".".', $this->loc(1, 1)], + ['?', 'Cannot parse the unexpected character "?".', $this->loc(1, 1)], [$unicode1, "Cannot parse the unexpected character \"\\u203b\".", $this->loc(1, 1)], [$unicode2, "Cannot parse the unexpected character \"\\u200b\".", $this->loc(1, 1)], ]; @@ -530,7 +643,7 @@ class LexerTest extends TestCase /** * @dataProvider reportsUsefulUnknownCharErrors - * @see it('lex reports useful unknown character error') + * @see it('lex reports useful unknown character error') */ public function testReportsUsefulUnknownCharErrors($str, $expectedMessage, $location) : void { @@ -542,17 +655,20 @@ class LexerTest extends TestCase */ public function testReportsUsefulDashesInfo() : void { - $q = 'a-b'; + $q = 'a-b'; $lexer = new Lexer(new Source($q)); - $this->assertArraySubset(['kind' => Token::NAME, 'start' => 0, 'end' => 1, 'value' => 'a'], (array) $lexer->advance()); + $this->assertArraySubset( + ['kind' => Token::NAME, 'start' => 0, 'end' => 1, 'value' => 'a'], + (array) $lexer->advance() + ); $this->expectException(SyntaxError::class); $this->expectExceptionMessage('Syntax Error: Invalid number, expected digit but got: "b"'); try { $lexer->advance(); $this->fail('Expected exception not thrown'); - } catch(SyntaxError $error) { - $this->assertEquals([$this->loc(1,3)], $error->getLocations()); + } catch (SyntaxError $error) { + $this->assertEquals([$this->loc(1, 3)], $error->getLocations()); throw $error; } } @@ -580,49 +696,28 @@ class LexerTest extends TestCase $tokens = []; for ($tok = $startToken; $tok; $tok = $tok->next) { - if (!empty($tokens)) { + if (! empty($tokens)) { // Tokens are double-linked, prev should point to last seen token. $this->assertSame($tokens[count($tokens) - 1], $tok->prev); } $tokens[] = $tok; } - $this->assertEquals([ - '', - '{', - 'Comment', - 'Name', - '}', - '' - ], Utils::map($tokens, function ($tok) { - return $tok->kind; - })); - } - - /** - * @param string $body - * @return Token - */ - private function lexOne($body) - { - $lexer = new Lexer(new Source($body)); - return $lexer->advance(); - } - - private function loc($line, $column) - { - return new SourceLocation($line, $column); - } - - private function expectSyntaxError($text, $message, $location) - { - $this->expectException(SyntaxError::class); - $this->expectExceptionMessage($message); - try { - $this->lexOne($text); - } catch (SyntaxError $error) { - $this->assertEquals([$location], $error->getLocations()); - throw $error; - } + $this->assertEquals( + [ + '', + '{', + 'Comment', + 'Name', + '}', + '', + ], + Utils::map( + $tokens, + function ($tok) { + return $tok->kind; + } + ) + ); } } diff --git a/tests/Language/ParserTest.php b/tests/Language/ParserTest.php index c50ecfb..360e5a1 100644 --- a/tests/Language/ParserTest.php +++ b/tests/Language/ParserTest.php @@ -1,7 +1,11 @@ ", "Syntax Error: Expected Name, found \n\nGraphQL request (1:2)\n1: {\n ^\n", [1], [new SourceLocation(1, 2)]], - ['{ ...MissingOn } + [ + '{', + 'Syntax Error: Expected Name, found ', + "Syntax Error: Expected Name, found \n\nGraphQL request (1:2)\n1: {\n ^\n", + [1], + [new SourceLocation( + 1, + 2 + ), + ], + ], + [ + '{ ...MissingOn } fragment MissingOn Type -', "Syntax Error: Expected \"on\", found Name \"Type\"", "Syntax Error: Expected \"on\", found Name \"Type\"\n\nGraphQL request (2:20)\n1: { ...MissingOn }\n2: fragment MissingOn Type\n ^\n3: \n",], - ['{ field: {} }', "Syntax Error: Expected Name, found {", "Syntax Error: Expected Name, found {\n\nGraphQL request (1:10)\n1: { field: {} }\n ^\n"], - ['notanoperation Foo { field }', "Syntax Error: Unexpected Name \"notanoperation\"", "Syntax Error: Unexpected Name \"notanoperation\"\n\nGraphQL request (1:1)\n1: notanoperation Foo { field }\n ^\n"], - ['...', "Syntax Error: Unexpected ...", "Syntax Error: Unexpected ...\n\nGraphQL request (1:1)\n1: ...\n ^\n"], +', 'Syntax Error: Expected "on", found Name "Type"', + "Syntax Error: Expected \"on\", found Name \"Type\"\n\nGraphQL request (2:20)\n1: { ...MissingOn }\n2: fragment MissingOn Type\n ^\n3: \n", + ], + ['{ field: {} }', 'Syntax Error: Expected Name, found {', "Syntax Error: Expected Name, found {\n\nGraphQL request (1:10)\n1: { field: {} }\n ^\n"], + ['notanoperation Foo { field }', 'Syntax Error: Unexpected Name "notanoperation"', "Syntax Error: Unexpected Name \"notanoperation\"\n\nGraphQL request (1:1)\n1: notanoperation Foo { field }\n ^\n"], + ['...', 'Syntax Error: Unexpected ...', "Syntax Error: Unexpected ...\n\nGraphQL request (1:1)\n1: ...\n ^\n"], ]; } /** * @dataProvider parseProvidesUsefulErrors - * @see it('parse provides useful errors') + * @see it('parse provides useful errors') */ - public function testParseProvidesUsefulErrors($str, $expectedMessage, $stringRepresentation, $expectedPositions = null, $expectedLocations = null) : void - { + public function testParseProvidesUsefulErrors( + $str, + $expectedMessage, + $stringRepresentation, + $expectedPositions = null, + $expectedLocations = null + ) : void { try { Parser::parse($str); $this->fail('Expected exception not thrown'); @@ -110,10 +133,27 @@ fragment MissingOn Type $this->expectSyntaxError( 'query Foo($x: Complex = { a: { b: [ $var ] } }) { field }', 'Unexpected $', - $this->loc(1,37) + $this->loc(1, 37) ); } + private function expectSyntaxError($text, $message, $location) + { + $this->expectException(SyntaxError::class); + $this->expectExceptionMessage($message); + try { + Parser::parse($text); + } catch (SyntaxError $error) { + $this->assertEquals([$location], $error->getLocations()); + throw $error; + } + } + + private function loc($line, $column) + { + return new SourceLocation($line, $column); + } + /** * @see it('does not accept fragments spread of "on"') */ @@ -122,7 +162,7 @@ fragment MissingOn Type $this->expectSyntaxError( 'fragment on on on { on }', 'Unexpected Name "on"', - $this->loc(1,10) + $this->loc(1, 10) ); } @@ -134,7 +174,7 @@ fragment MissingOn Type $this->expectSyntaxError( '{ ...on }', 'Expected Name, found }', - $this->loc(1,9) + $this->loc(1, 9) ); } @@ -145,7 +185,7 @@ fragment MissingOn Type { // Note: \u0A0A could be naively interpretted as two line-feed chars. - $char = Utils::chr(0x0A0A); + $char = Utils::chr(0x0A0A); $query = << new NodeList([ new FieldNode([ - 'name' => new NameNode(['value' => 'field']), - 'arguments' => new NodeList([ + 'name' => new NameNode(['value' => 'field']), + 'arguments' => new NodeList([ new ArgumentNode([ - 'name' => new NameNode(['value' => 'arg']), - 'value' => new StringValueNode([ - 'value' => "Has a $char multi-byte character." - ]) - ]) + 'name' => new NameNode(['value' => 'arg']), + 'value' => new StringValueNode( + ['value' => sprintf('Has a %s multi-byte character.', $char)] + ), + ]), ]), - 'directives' => new NodeList([]) - ]) - ]) + 'directives' => new NodeList([]), + ]), + ]), ]); $this->assertEquals($expected, $result->definitions[0]->selectionSet); @@ -180,7 +220,7 @@ HEREDOC; { // Following should not throw: $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); - $result = Parser::parse($kitchenSink); + $result = Parser::parse($kitchenSink); $this->assertNotEmpty($result); } @@ -196,7 +236,7 @@ HEREDOC; 'mutation', 'subscription', 'true', - 'false' + 'false', ]; foreach ($nonKeywords as $keyword) { $fragmentName = $keyword; @@ -205,14 +245,19 @@ HEREDOC; } // Expected not to throw: - $result = Parser::parse("query $keyword { - ... $fragmentName - ... on $keyword { field } + $result = Parser::parse(<<assertNotEmpty($result); } } @@ -286,94 +331,102 @@ fragment $fragmentName on Type { '); $result = Parser::parse($source); - $loc = function($start, $end) use ($source) { + $loc = function ($start, $end) use ($source) { return [ 'start' => $start, - 'end' => $end + 'end' => $end, ]; }; $expected = [ - 'kind' => NodeKind::DOCUMENT, - 'loc' => $loc(0, 41), + 'kind' => NodeKind::DOCUMENT, + 'loc' => $loc(0, 41), 'definitions' => [ [ - 'kind' => NodeKind::OPERATION_DEFINITION, - 'loc' => $loc(0, 40), - 'operation' => 'query', - 'name' => null, + 'kind' => NodeKind::OPERATION_DEFINITION, + 'loc' => $loc(0, 40), + 'operation' => 'query', + 'name' => null, 'variableDefinitions' => [], - 'directives' => [], - 'selectionSet' => [ - 'kind' => NodeKind::SELECTION_SET, - 'loc' => $loc(0, 40), + 'directives' => [], + 'selectionSet' => [ + 'kind' => NodeKind::SELECTION_SET, + 'loc' => $loc(0, 40), 'selections' => [ [ - 'kind' => NodeKind::FIELD, - 'loc' => $loc(4, 38), - 'alias' => null, - 'name' => [ - 'kind' => NodeKind::NAME, - 'loc' => $loc(4, 8), - 'value' => 'node' + 'kind' => NodeKind::FIELD, + 'loc' => $loc(4, 38), + 'alias' => null, + 'name' => [ + 'kind' => NodeKind::NAME, + 'loc' => $loc(4, 8), + 'value' => 'node', ], - 'arguments' => [ + 'arguments' => [ [ - 'kind' => NodeKind::ARGUMENT, - 'name' => [ - 'kind' => NodeKind::NAME, - 'loc' => $loc(9, 11), - 'value' => 'id' + 'kind' => NodeKind::ARGUMENT, + 'name' => [ + 'kind' => NodeKind::NAME, + 'loc' => $loc(9, 11), + 'value' => 'id', ], 'value' => [ - 'kind' => NodeKind::INT, - 'loc' => $loc(13, 14), - 'value' => '4' + 'kind' => NodeKind::INT, + 'loc' => $loc(13, 14), + 'value' => '4', ], - 'loc' => $loc(9, 14, $source) - ] + 'loc' => $loc(9, 14, $source), + ], ], - 'directives' => [], + 'directives' => [], 'selectionSet' => [ - 'kind' => NodeKind::SELECTION_SET, - 'loc' => $loc(16, 38), + 'kind' => NodeKind::SELECTION_SET, + 'loc' => $loc(16, 38), 'selections' => [ [ - 'kind' => NodeKind::FIELD, - 'loc' => $loc(22, 24), - 'alias' => null, - 'name' => [ - 'kind' => NodeKind::NAME, - 'loc' => $loc(22, 24), - 'value' => 'id' + 'kind' => NodeKind::FIELD, + 'loc' => $loc(22, 24), + 'alias' => null, + 'name' => [ + 'kind' => NodeKind::NAME, + 'loc' => $loc(22, 24), + 'value' => 'id', ], - 'arguments' => [], - 'directives' => [], - 'selectionSet' => null + 'arguments' => [], + 'directives' => [], + 'selectionSet' => null, ], [ - 'kind' => NodeKind::FIELD, - 'loc' => $loc(30, 34), - 'alias' => null, - 'name' => [ - 'kind' => NodeKind::NAME, - 'loc' => $loc(30, 34), - 'value' => 'name' + 'kind' => NodeKind::FIELD, + 'loc' => $loc(30, 34), + 'alias' => null, + 'name' => [ + 'kind' => NodeKind::NAME, + 'loc' => $loc(30, 34), + 'value' => 'name', ], - 'arguments' => [], - 'directives' => [], - 'selectionSet' => null - ] - ] - ] - ] - ] - ] - ] - ] + 'arguments' => [], + 'directives' => [], + 'selectionSet' => null, + ], + ], + ], + ], + ], + ], + ], + ], ]; - $this->assertEquals($expected, $this->nodeToArray($result)); + $this->assertEquals($expected, self::nodeToArray($result)); + } + + /** + * @return mixed[] + */ + public static function nodeToArray(Node $node) : array + { + return TestUtils::nodeToArray($node); } /** @@ -389,63 +442,63 @@ fragment $fragmentName on Type { '); $result = Parser::parse($source); - $loc = function($start, $end) use ($source) { + $loc = function ($start, $end) use ($source) { return [ 'start' => $start, - 'end' => $end + 'end' => $end, ]; }; $expected = [ - 'kind' => NodeKind::DOCUMENT, - 'loc' => $loc(0, 30), + 'kind' => NodeKind::DOCUMENT, + 'loc' => $loc(0, 30), 'definitions' => [ [ - 'kind' => NodeKind::OPERATION_DEFINITION, - 'loc' => $loc(0, 29), - 'operation' => 'query', - 'name' => null, + 'kind' => NodeKind::OPERATION_DEFINITION, + 'loc' => $loc(0, 29), + 'operation' => 'query', + 'name' => null, 'variableDefinitions' => [], - 'directives' => [], - 'selectionSet' => [ - 'kind' => NodeKind::SELECTION_SET, - 'loc' => $loc(6, 29), + 'directives' => [], + 'selectionSet' => [ + 'kind' => NodeKind::SELECTION_SET, + 'loc' => $loc(6, 29), 'selections' => [ [ - 'kind' => NodeKind::FIELD, - 'loc' => $loc(10, 27), - 'alias' => null, - 'name' => [ - 'kind' => NodeKind::NAME, - 'loc' => $loc(10, 14), - 'value' => 'node' + 'kind' => NodeKind::FIELD, + 'loc' => $loc(10, 27), + 'alias' => null, + 'name' => [ + 'kind' => NodeKind::NAME, + 'loc' => $loc(10, 14), + 'value' => 'node', ], - 'arguments' => [], - 'directives' => [], + 'arguments' => [], + 'directives' => [], 'selectionSet' => [ - 'kind' => NodeKind::SELECTION_SET, - 'loc' => $loc(15, 27), + 'kind' => NodeKind::SELECTION_SET, + 'loc' => $loc(15, 27), 'selections' => [ [ - 'kind' => NodeKind::FIELD, - 'loc' => $loc(21, 23), - 'alias' => null, - 'name' => [ - 'kind' => NodeKind::NAME, - 'loc' => $loc(21, 23), - 'value' => 'id' + 'kind' => NodeKind::FIELD, + 'loc' => $loc(21, 23), + 'alias' => null, + 'name' => [ + 'kind' => NodeKind::NAME, + 'loc' => $loc(21, 23), + 'value' => 'id', ], - 'arguments' => [], - 'directives' => [], - 'selectionSet' => null - ] - ] - ] - ] - ] - ] - ] - ] + 'arguments' => [], + 'directives' => [], + 'selectionSet' => null, + ], + ], + ], + ], + ], + ], + ], + ], ]; $this->assertEquals($expected, $this->nodeToArray($result)); @@ -475,6 +528,8 @@ fragment $fragmentName on Type { Parser::parse($source); } + // Describe: parseValue + /** * @see it('contains location information that only stringifys start/end') */ @@ -495,6 +550,8 @@ fragment $fragmentName on Type { $this->assertEquals($source, $result->loc->source); } + // Describe: parseType + /** * @see it('contains references to start and end tokens') */ @@ -506,17 +563,18 @@ fragment $fragmentName on Type { $this->assertEquals('', $result->loc->endToken->kind); } - // Describe: parseValue - /** * @see it('parses null value') */ public function testParsesNullValues() : void { - $this->assertEquals([ - 'kind' => NodeKind::NULL, - 'loc' => ['start' => 0, 'end' => 4] - ], $this->nodeToArray(Parser::parseValue('null'))); + $this->assertEquals( + [ + 'kind' => NodeKind::NULL, + 'loc' => ['start' => 0, 'end' => 4], + ], + $this->nodeToArray(Parser::parseValue('null')) + ); } /** @@ -524,41 +582,45 @@ fragment $fragmentName on Type { */ public function testParsesListValues() : void { - $this->assertEquals([ - 'kind' => NodeKind::LST, - 'loc' => ['start' => 0, 'end' => 11], - 'values' => [ - [ - 'kind' => NodeKind::INT, - 'loc' => ['start' => 1, 'end' => 4], - 'value' => '123' + $this->assertEquals( + [ + 'kind' => NodeKind::LST, + 'loc' => ['start' => 0, 'end' => 11], + 'values' => [ + [ + 'kind' => NodeKind::INT, + 'loc' => ['start' => 1, 'end' => 4], + 'value' => '123', + ], + [ + 'kind' => NodeKind::STRING, + 'loc' => ['start' => 5, 'end' => 10], + 'value' => 'abc', + 'block' => false, + ], ], - [ - 'kind' => NodeKind::STRING, - 'loc' => ['start' => 5, 'end' => 10], - 'value' => 'abc', - 'block' => false - ] - ] - ], $this->nodeToArray(Parser::parseValue('[123 "abc"]'))); + ], + $this->nodeToArray(Parser::parseValue('[123 "abc"]')) + ); } - // Describe: parseType - /** * @see it('parses well known types') */ public function testParsesWellKnownTypes() : void { - $this->assertEquals([ - 'kind' => NodeKind::NAMED_TYPE, - 'loc' => ['start' => 0, 'end' => 6], - 'name' => [ - 'kind' => NodeKind::NAME, - 'loc' => ['start' => 0, 'end' => 6], - 'value' => 'String' - ] - ], $this->nodeToArray(Parser::parseType('String'))); + $this->assertEquals( + [ + 'kind' => NodeKind::NAMED_TYPE, + 'loc' => ['start' => 0, 'end' => 6], + 'name' => [ + 'kind' => NodeKind::NAME, + 'loc' => ['start' => 0, 'end' => 6], + 'value' => 'String', + ], + ], + $this->nodeToArray(Parser::parseType('String')) + ); } /** @@ -566,15 +628,18 @@ fragment $fragmentName on Type { */ public function testParsesCustomTypes() : void { - $this->assertEquals([ - 'kind' => NodeKind::NAMED_TYPE, - 'loc' => ['start' => 0, 'end' => 6], - 'name' => [ - 'kind' => NodeKind::NAME, - 'loc' => ['start' => 0, 'end' => 6], - 'value' => 'MyType' - ] - ], $this->nodeToArray(Parser::parseType('MyType'))); + $this->assertEquals( + [ + 'kind' => NodeKind::NAMED_TYPE, + 'loc' => ['start' => 0, 'end' => 6], + 'name' => [ + 'kind' => NodeKind::NAME, + 'loc' => ['start' => 0, 'end' => 6], + 'value' => 'MyType', + ], + ], + $this->nodeToArray(Parser::parseType('MyType')) + ); } /** @@ -582,19 +647,22 @@ fragment $fragmentName on Type { */ public function testParsesListTypes() : void { - $this->assertEquals([ - 'kind' => NodeKind::LIST_TYPE, - 'loc' => ['start' => 0, 'end' => 8], - 'type' => [ - 'kind' => NodeKind::NAMED_TYPE, - 'loc' => ['start' => 1, 'end' => 7], - 'name' => [ - 'kind' => NodeKind::NAME, - 'loc' => ['start' => 1, 'end' => 7], - 'value' => 'MyType' - ] - ] - ], $this->nodeToArray(Parser::parseType('[MyType]'))); + $this->assertEquals( + [ + 'kind' => NodeKind::LIST_TYPE, + 'loc' => ['start' => 0, 'end' => 8], + 'type' => [ + 'kind' => NodeKind::NAMED_TYPE, + 'loc' => ['start' => 1, 'end' => 7], + 'name' => [ + 'kind' => NodeKind::NAME, + 'loc' => ['start' => 1, 'end' => 7], + 'value' => 'MyType', + ], + ], + ], + $this->nodeToArray(Parser::parseType('[MyType]')) + ); } /** @@ -602,19 +670,22 @@ fragment $fragmentName on Type { */ public function testParsesNonNullTypes() : void { - $this->assertEquals([ - 'kind' => NodeKind::NON_NULL_TYPE, - 'loc' => ['start' => 0, 'end' => 7], - 'type' => [ - 'kind' => NodeKind::NAMED_TYPE, - 'loc' => ['start' => 0, 'end' => 6], - 'name' => [ - 'kind' => NodeKind::NAME, - 'loc' => ['start' => 0, 'end' => 6], - 'value' => 'MyType' - ] - ] - ], $this->nodeToArray(Parser::parseType('MyType!'))); + $this->assertEquals( + [ + 'kind' => NodeKind::NON_NULL_TYPE, + 'loc' => ['start' => 0, 'end' => 7], + 'type' => [ + 'kind' => NodeKind::NAMED_TYPE, + 'loc' => ['start' => 0, 'end' => 6], + 'name' => [ + 'kind' => NodeKind::NAME, + 'loc' => ['start' => 0, 'end' => 6], + 'value' => 'MyType', + ], + ], + ], + $this->nodeToArray(Parser::parseType('MyType!')) + ); } /** @@ -622,48 +693,25 @@ fragment $fragmentName on Type { */ public function testParsesNestedTypes() : void { - $this->assertEquals([ - 'kind' => NodeKind::LIST_TYPE, - 'loc' => ['start' => 0, 'end' => 9], - 'type' => [ - 'kind' => NodeKind::NON_NULL_TYPE, - 'loc' => ['start' => 1, 'end' => 8], + $this->assertEquals( + [ + 'kind' => NodeKind::LIST_TYPE, + 'loc' => ['start' => 0, 'end' => 9], 'type' => [ - 'kind' => NodeKind::NAMED_TYPE, - 'loc' => ['start' => 1, 'end' => 7], - 'name' => [ - 'kind' => NodeKind::NAME, - 'loc' => ['start' => 1, 'end' => 7], - 'value' => 'MyType' - ] - ] - ] - ], $this->nodeToArray(Parser::parseType('[MyType!]'))); - } - - /** - * @param Node $node - * @return array - */ - public static function nodeToArray(Node $node) - { - return TestUtils::nodeToArray($node); - } - - private function loc($line, $column) - { - return new SourceLocation($line, $column); - } - - private function expectSyntaxError($text, $message, $location) - { - $this->expectException(SyntaxError::class); - $this->expectExceptionMessage($message); - try { - Parser::parse($text); - } catch (SyntaxError $error) { - $this->assertEquals([$location], $error->getLocations()); - throw $error; - } + 'kind' => NodeKind::NON_NULL_TYPE, + 'loc' => ['start' => 1, 'end' => 8], + 'type' => [ + 'kind' => NodeKind::NAMED_TYPE, + 'loc' => ['start' => 1, 'end' => 7], + 'name' => [ + 'kind' => NodeKind::NAME, + 'loc' => ['start' => 1, 'end' => 7], + 'value' => 'MyType', + ], + ], + ], + ], + $this->nodeToArray(Parser::parseType('[MyType!]')) + ); } } diff --git a/tests/Language/PrinterTest.php b/tests/Language/PrinterTest.php index cba6b11..53312fb 100644 --- a/tests/Language/PrinterTest.php +++ b/tests/Language/PrinterTest.php @@ -1,18 +1,15 @@ cloneDeep(); $this->assertEquals($astCopy, $ast); @@ -66,7 +63,7 @@ class PrinterTest extends TestCase $this->assertEquals($expected, Printer::doPrint($queryAstShorthanded)); $mutationAst = Parser::parse('mutation { id, name }'); - $expected = 'mutation { + $expected = 'mutation { id name } @@ -76,7 +73,7 @@ class PrinterTest extends TestCase $queryAstWithArtifacts = Parser::parse( 'query ($foo: TestType) @testDirective { id, name }' ); - $expected = 'query ($foo: TestType) @testDirective { + $expected = 'query ($foo: TestType) @testDirective { id name } @@ -86,7 +83,7 @@ class PrinterTest extends TestCase $mutationAstWithArtifacts = Parser::parse( 'mutation ($foo: TestType) @testDirective { id, name }' ); - $expected = 'mutation ($foo: TestType) @testDirective { + $expected = 'mutation ($foo: TestType) @testDirective { id name } @@ -100,13 +97,13 @@ class PrinterTest extends TestCase public function testCorrectlyPrintsSingleLineBlockStringsWithLeadingSpace() : void { $mutationAstWithArtifacts = Parser::parse( - '{ field(arg: """ space-led value""") }' + '{ field(arg: """ space-led value""") }' ); - $expected = '{ + $expected = '{ field(arg: """ space-led value""") } '; - $this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts)); + $this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts)); } /** @@ -122,8 +119,8 @@ class PrinterTest extends TestCase indentation """) }' - ); - $expected = '{ + ); + $expected = '{ field(arg: """ first line @@ -131,7 +128,7 @@ class PrinterTest extends TestCase """) } '; - $this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts)); + $this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts)); } /** @@ -145,7 +142,7 @@ class PrinterTest extends TestCase """) } '); - $expected = <<assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts)); + $this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts)); } /** @@ -202,7 +200,7 @@ END; public function testPrintsKitchenSink() : void { $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); - $ast = Parser::parse($kitchenSink); + $ast = Parser::parse($kitchenSink); $printed = Printer::doPrint($ast); diff --git a/tests/Language/SchemaParserTest.php b/tests/Language/SchemaParserTest.php index 465cf27..36571b7 100644 --- a/tests/Language/SchemaParserTest.php +++ b/tests/Language/SchemaParserTest.php @@ -1,4 +1,7 @@ NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(6, 11)), - 'interfaces' => [], - 'directives' => [], - 'fields' => [ + 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(6, 11)), + 'interfaces' => [], + 'directives' => [], + 'fields' => [ $this->fieldNode( $this->nameNode('world', $loc(16, 21)), $this->typeNode('String', $loc(23, 29)), $loc(16, 29) - ) + ), ], - 'loc' => $loc(1, 31), - 'description' => null - ] + 'loc' => $loc(1, 31), + 'description' => null, + ], ], - 'loc' => $loc(0, 31) + 'loc' => $loc(0, 31), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } + private function nameNode($name, $loc) + { + return [ + 'kind' => NodeKind::NAME, + 'value' => $name, + 'loc' => $loc, + ]; + } + + private function fieldNode($name, $type, $loc) + { + return $this->fieldNodeWithArgs($name, $type, [], $loc); + } + + private function fieldNodeWithArgs($name, $type, $args, $loc) + { + return [ + 'kind' => NodeKind::FIELD_DEFINITION, + 'name' => $name, + 'arguments' => $args, + 'type' => $type, + 'directives' => [], + 'loc' => $loc, + 'description' => null, + ]; + } + + private function typeNode($name, $loc) + { + return [ + 'kind' => NodeKind::NAMED_TYPE, + 'name' => ['kind' => NodeKind::NAME, 'value' => $name, 'loc' => $loc], + 'loc' => $loc, + ]; + } + /** * @see it('parses type with description string') */ @@ -57,34 +97,36 @@ type Hello { type Hello { world: String }'; - $doc = Parser::parse($body); - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(20, 25)), - 'interfaces' => [], - 'directives' => [], - 'fields' => [ + 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(20, 25)), + 'interfaces' => [], + 'directives' => [], + 'fields' => [ $this->fieldNode( $this->nameNode('world', $loc(30, 35)), $this->typeNode('String', $loc(37, 43)), $loc(30, 43) - ) + ), ], - 'loc' => $loc(1, 45), + 'loc' => $loc(1, 45), 'description' => [ - 'kind' => NodeKind::STRING, + 'kind' => NodeKind::STRING, 'value' => 'Description', - 'loc' => $loc(1, 14), - 'block' => false - ] - ] + 'loc' => $loc(1, 14), + 'block' => false, + ], + ], ], - 'loc' => $loc(0, 45) + 'loc' => $loc(0, 45), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } @@ -102,34 +144,36 @@ Description type Hello { world: String }'; - $doc = Parser::parse($body); - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(60, 65)), - 'interfaces' => [], - 'directives' => [], - 'fields' => [ + 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(60, 65)), + 'interfaces' => [], + 'directives' => [], + 'fields' => [ $this->fieldNode( $this->nameNode('world', $loc(70, 75)), $this->typeNode('String', $loc(77, 83)), $loc(70, 83) - ) + ), ], - 'loc' => $loc(1, 85), + 'loc' => $loc(1, 85), 'description' => [ - 'kind' => NodeKind::STRING, + 'kind' => NodeKind::STRING, 'value' => 'Description', - 'loc' => $loc(1, 20), - 'block' => true - ] - ] + 'loc' => $loc(1, 20), + 'block' => true, + ], + ], ], - 'loc' => $loc(0, 85) + 'loc' => $loc(0, 85), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } @@ -144,29 +188,30 @@ extend type Hello { world: String } '; - $doc = Parser::parse($body); - $loc = function($start, $end) { + $doc = Parser::parse($body); + $loc = function ($start, $end) { return TestUtils::locArray($start, $end); }; + $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::OBJECT_TYPE_EXTENSION, - 'name' => $this->nameNode('Hello', $loc(13, 18)), + 'kind' => NodeKind::OBJECT_TYPE_EXTENSION, + 'name' => $this->nameNode('Hello', $loc(13, 18)), 'interfaces' => [], 'directives' => [], - 'fields' => [ + 'fields' => [ $this->fieldNode( $this->nameNode('world', $loc(23, 28)), $this->typeNode('String', $loc(30, 36)), $loc(23, 36) - ) + ), ], - 'loc' => $loc(1, 38) - ] + 'loc' => $loc(1, 38), + ], ], - 'loc' => $loc(0, 39) + 'loc' => $loc(0, 39), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } @@ -177,25 +222,26 @@ extend type Hello { public function testExtensionWithoutFields() : void { $body = 'extend type Hello implements Greeting'; - $doc = Parser::parse($body); - $loc = function($start, $end) { + $doc = Parser::parse($body); + $loc = function ($start, $end) { return TestUtils::locArray($start, $end); }; + $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::OBJECT_TYPE_EXTENSION, - 'name' => $this->nameNode('Hello', $loc(12, 17)), + 'kind' => NodeKind::OBJECT_TYPE_EXTENSION, + 'name' => $this->nameNode('Hello', $loc(12, 17)), 'interfaces' => [ $this->typeNode('Greeting', $loc(29, 37)), ], 'directives' => [], - 'fields' => [], - 'loc' => $loc(0, 37) - ] + 'fields' => [], + 'loc' => $loc(0, 37), + ], ], - 'loc' => $loc(0, 37) + 'loc' => $loc(0, 37), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } @@ -205,33 +251,33 @@ extend type Hello { */ public function testExtensionWithoutFieldsFollowedByExtension() : void { - $body = ' + $body = ' extend type Hello implements Greeting extend type Hello implements SecondGreeting '; - $doc = Parser::parse($body); + $doc = Parser::parse($body); $expected = [ - 'kind' => 'Document', + 'kind' => 'Document', 'definitions' => [ [ - 'kind' => 'ObjectTypeExtension', - 'name' => $this->nameNode('Hello', ['start' => 23, 'end' => 28]), + 'kind' => 'ObjectTypeExtension', + 'name' => $this->nameNode('Hello', ['start' => 23, 'end' => 28]), 'interfaces' => [$this->typeNode('Greeting', ['start' => 40, 'end' => 48])], 'directives' => [], - 'fields' => [], - 'loc' => ['start' => 11, 'end' => 48], + 'fields' => [], + 'loc' => ['start' => 11, 'end' => 48], ], [ - 'kind' => 'ObjectTypeExtension', - 'name' => $this->nameNode('Hello', ['start' => 76, 'end' => 81]), + 'kind' => 'ObjectTypeExtension', + 'name' => $this->nameNode('Hello', ['start' => 76, 'end' => 81]), 'interfaces' => [$this->typeNode('SecondGreeting', ['start' => 93, 'end' => 107])], 'directives' => [], - 'fields' => [], - 'loc' => ['start' => 64, 'end' => 107], + 'fields' => [], + 'loc' => ['start' => 64, 'end' => 107], ], ], - 'loc' => ['start' => 0, 'end' => 116], + 'loc' => ['start' => 0, 'end' => 116], ]; $this->assertEquals($expected, $doc->toArray(true)); } @@ -248,6 +294,23 @@ extend type Hello { ); } + private function expectSyntaxError($text, $message, $location) + { + $this->expectException(SyntaxError::class); + $this->expectExceptionMessage($message); + try { + Parser::parse($text); + } catch (SyntaxError $error) { + $this->assertEquals([$location], $error->getLocations()); + throw $error; + } + } + + private function loc($line, $column) + { + return new SourceLocation($line, $column); + } + /** * @see it('Extension do not include descriptions') */ @@ -291,35 +354,36 @@ extend type Hello { type Hello { world: String! }'; - $loc = function($start, $end) { + $doc = Parser::parse($body); + + $loc = function ($start, $end) { return TestUtils::locArray($start, $end); }; - $doc = Parser::parse($body); $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(6,11)), - 'interfaces' => [], - 'directives' => [], - 'fields' => [ + 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(6, 11)), + 'interfaces' => [], + 'directives' => [], + 'fields' => [ $this->fieldNode( $this->nameNode('world', $loc(16, 21)), [ 'kind' => NodeKind::NON_NULL_TYPE, 'type' => $this->typeNode('String', $loc(23, 29)), - 'loc' => $loc(23, 30) + 'loc' => $loc(23, 30), ], - $loc(16,30) - ) + $loc(16, 30) + ), ], - 'loc' => $loc(1,32), - 'description' => null - ] + 'loc' => $loc(1, 32), + 'description' => null, + ], ], - 'loc' => $loc(0,32) + 'loc' => $loc(0, 32), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); @@ -331,31 +395,33 @@ type Hello { public function testSimpleTypeInheritingInterface() : void { $body = 'type Hello implements World { field: String }'; - $loc = function($start, $end) { return TestUtils::locArray($start, $end); }; - $doc = Parser::parse($body); + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(5, 10)), - 'interfaces' => [ - $this->typeNode('World', $loc(22, 27)) + 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(5, 10)), + 'interfaces' => [ + $this->typeNode('World', $loc(22, 27)), ], - 'directives' => [], - 'fields' => [ + 'directives' => [], + 'fields' => [ $this->fieldNode( $this->nameNode('field', $loc(30, 35)), $this->typeNode('String', $loc(37, 43)), $loc(30, 43) - ) + ), ], - 'loc' => $loc(0, 45), - 'description' => null - ] + 'loc' => $loc(0, 45), + 'description' => null, + ], ], - 'loc' => $loc(0, 45) + 'loc' => $loc(0, 45), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); @@ -367,32 +433,34 @@ type Hello { public function testSimpleTypeInheritingMultipleInterfaces() : void { $body = 'type Hello implements Wo & rld { field: String }'; - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; - $doc = Parser::parse($body); + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(5, 10)), - 'interfaces' => [ + 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(5, 10)), + 'interfaces' => [ $this->typeNode('Wo', $loc(22, 24)), - $this->typeNode('rld', $loc(27, 30)) + $this->typeNode('rld', $loc(27, 30)), ], - 'directives' => [], - 'fields' => [ + 'directives' => [], + 'fields' => [ $this->fieldNode( $this->nameNode('field', $loc(33, 38)), $this->typeNode('String', $loc(40, 46)), $loc(33, 46) - ) + ), ], - 'loc' => $loc(0, 48), - 'description' => null - ] + 'loc' => $loc(0, 48), + 'description' => null, + ], ], - 'loc' => $loc(0, 48) + 'loc' => $loc(0, 48), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); @@ -404,31 +472,34 @@ type Hello { public function testSimpleTypeInheritingMultipleInterfacesWithLeadingAmpersand() : void { $body = 'type Hello implements & Wo & rld { field: String }'; - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; - $doc = Parser::parse($body); + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; + $expected = [ - 'kind' => 'Document', + 'kind' => 'Document', 'definitions' => [ [ - 'kind' => 'ObjectTypeDefinition', - 'name' => $this->nameNode('Hello', $loc(5, 10)), - 'interfaces' => [ + 'kind' => 'ObjectTypeDefinition', + 'name' => $this->nameNode('Hello', $loc(5, 10)), + 'interfaces' => [ $this->typeNode('Wo', $loc(24, 26)), $this->typeNode('rld', $loc(29, 32)), ], - 'directives' => [], - 'fields' => [ + 'directives' => [], + 'fields' => [ $this->fieldNode( $this->nameNode('field', $loc(35, 40)), $this->typeNode('String', $loc(42, 48)), $loc(35, 48) ), ], - 'loc' => $loc(0, 50), + 'loc' => $loc(0, 50), 'description' => null, ], ], - 'loc' => $loc(0, 50), + 'loc' => $loc(0, 50), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } @@ -439,52 +510,67 @@ type Hello { public function testSingleValueEnum() : void { $body = 'enum Hello { WORLD }'; - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; - $doc = Parser::parse($body); + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::ENUM_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(5, 10)), - 'directives' => [], - 'values' => [$this->enumValueNode('WORLD', $loc(13, 18))], - 'loc' => $loc(0, 20), - 'description' => null - ] + 'kind' => NodeKind::ENUM_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(5, 10)), + 'directives' => [], + 'values' => [$this->enumValueNode('WORLD', $loc(13, 18))], + 'loc' => $loc(0, 20), + 'description' => null, + ], ], - 'loc' => $loc(0, 20) + 'loc' => $loc(0, 20), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } + private function enumValueNode($name, $loc) + { + return [ + 'kind' => NodeKind::ENUM_VALUE_DEFINITION, + 'name' => $this->nameNode($name, $loc), + 'directives' => [], + 'loc' => $loc, + 'description' => null, + ]; + } + /** * @see it('Double value enum') */ public function testDoubleValueEnum() : void { $body = 'enum Hello { WO, RLD }'; - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; - $doc = Parser::parse($body); + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::ENUM_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(5, 10)), - 'directives' => [], - 'values' => [ + 'kind' => NodeKind::ENUM_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(5, 10)), + 'directives' => [], + 'values' => [ $this->enumValueNode('WO', $loc(13, 15)), - $this->enumValueNode('RLD', $loc(17, 20)) + $this->enumValueNode('RLD', $loc(17, 20)), ], - 'loc' => $loc(0, 22), - 'description' => null - ] + 'loc' => $loc(0, 22), + 'description' => null, + ], ], - 'loc' => $loc(0, 22) + 'loc' => $loc(0, 22), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); @@ -499,28 +585,30 @@ type Hello { interface Hello { world: String }'; - $doc = Parser::parse($body); - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::INTERFACE_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(11, 16)), - 'directives' => [], - 'fields' => [ + 'kind' => NodeKind::INTERFACE_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(11, 16)), + 'directives' => [], + 'fields' => [ $this->fieldNode( $this->nameNode('world', $loc(21, 26)), $this->typeNode('String', $loc(28, 34)), $loc(21, 34) - ) + ), ], - 'loc' => $loc(1, 36), - 'description' => null - ] + 'loc' => $loc(1, 36), + 'description' => null, + ], ], - 'loc' => $loc(0,36) + 'loc' => $loc(0, 36), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } @@ -534,18 +622,20 @@ interface Hello { type Hello { world(flag: Boolean): String }'; - $doc = Parser::parse($body); - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(6, 11)), - 'interfaces' => [], - 'directives' => [], - 'fields' => [ + 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(6, 11)), + 'interfaces' => [], + 'directives' => [], + 'fields' => [ $this->fieldNodeWithArgs( $this->nameNode('world', $loc(16, 21)), $this->typeNode('String', $loc(38, 44)), @@ -555,21 +645,34 @@ type Hello { $this->typeNode('Boolean', $loc(28, 35)), null, $loc(22, 35) - ) + ), ], $loc(16, 44) - ) + ), ], - 'loc' => $loc(1, 46), - 'description' => null - ] + 'loc' => $loc(1, 46), + 'description' => null, + ], ], - 'loc' => $loc(0, 46) + 'loc' => $loc(0, 46), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } + private function inputValueNode($name, $type, $defaultValue, $loc) + { + return [ + 'kind' => NodeKind::INPUT_VALUE_DEFINITION, + 'name' => $name, + 'type' => $type, + 'defaultValue' => $defaultValue, + 'directives' => [], + 'loc' => $loc, + 'description' => null, + ]; + } + /** * @see it('Simple field with arg with default value') */ @@ -579,18 +682,20 @@ type Hello { type Hello { world(flag: Boolean = true): String }'; - $doc = Parser::parse($body); - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(6, 11)), - 'interfaces' => [], - 'directives' => [], - 'fields' => [ + 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(6, 11)), + 'interfaces' => [], + 'directives' => [], + 'fields' => [ $this->fieldNodeWithArgs( $this->nameNode('world', $loc(16, 21)), $this->typeNode('String', $loc(45, 51)), @@ -600,16 +705,16 @@ type Hello { $this->typeNode('Boolean', $loc(28, 35)), ['kind' => NodeKind::BOOLEAN, 'value' => true, 'loc' => $loc(38, 42)], $loc(22, 42) - ) + ), ], $loc(16, 51) - ) + ), ], - 'loc' => $loc(1, 53), - 'description' => null - ] + 'loc' => $loc(1, 53), + 'description' => null, + ], ], - 'loc' => $loc(0, 53) + 'loc' => $loc(0, 53), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } @@ -623,37 +728,45 @@ type Hello { type Hello { world(things: [String]): String }'; - $doc = Parser::parse($body); - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(6, 11)), - 'interfaces' => [], - 'directives' => [], - 'fields' => [ + 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(6, 11)), + 'interfaces' => [], + 'directives' => [], + 'fields' => [ $this->fieldNodeWithArgs( $this->nameNode('world', $loc(16, 21)), $this->typeNode('String', $loc(41, 47)), [ $this->inputValueNode( - $this->nameNode('things', $loc(22,28)), - ['kind' => NodeKind::LIST_TYPE, 'type' => $this->typeNode('String', $loc(31, 37)), 'loc' => $loc(30, 38)], + $this->nameNode('things', $loc(22, 28)), + [ + 'kind' => NodeKind::LIST_TYPE, + 'type' => $this->typeNode( + 'String', + $loc(31, 37) + ), 'loc' => $loc(30, 38), + ], null, $loc(22, 38) - ) + ), ], $loc(16, 47) - ) + ), ], - 'loc' => $loc(1, 49), - 'description' => null - ] + 'loc' => $loc(1, 49), + 'description' => null, + ], ], - 'loc' => $loc(0, 49) + 'loc' => $loc(0, 49), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); @@ -668,18 +781,20 @@ type Hello { type Hello { world(argOne: Boolean, argTwo: Int): String }'; - $doc = Parser::parse($body); - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(6, 11)), - 'interfaces' => [], - 'directives' => [], - 'fields' => [ + 'kind' => NodeKind::OBJECT_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(6, 11)), + 'interfaces' => [], + 'directives' => [], + 'fields' => [ $this->fieldNodeWithArgs( $this->nameNode('world', $loc(16, 21)), $this->typeNode('String', $loc(53, 59)), @@ -695,16 +810,16 @@ type Hello { $this->typeNode('Int', $loc(47, 50)), null, $loc(39, 50) - ) + ), ], $loc(16, 59) - ) + ), ], - 'loc' => $loc(1, 61), - 'description' => null - ] + 'loc' => $loc(1, 61), + 'description' => null, + ], ], - 'loc' => $loc(0, 61) + 'loc' => $loc(0, 61), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); @@ -716,21 +831,24 @@ type Hello { public function testSimpleUnion() : void { $body = 'union Hello = World'; - $doc = Parser::parse($body); - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; + $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::UNION_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(6, 11)), - 'directives' => [], - 'types' => [$this->typeNode('World', $loc(14, 19))], - 'loc' => $loc(0, 19), - 'description' => null - ] + 'kind' => NodeKind::UNION_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(6, 11)), + 'directives' => [], + 'types' => [$this->typeNode('World', $loc(14, 19))], + 'loc' => $loc(0, 19), + 'description' => null, + ], ], - 'loc' => $loc(0, 19) + 'loc' => $loc(0, 19), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); @@ -742,53 +860,54 @@ type Hello { public function testUnionWithTwoTypes() : void { $body = 'union Hello = Wo | Rld'; - $doc = Parser::parse($body); - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::UNION_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(6, 11)), - 'directives' => [], - 'types' => [ + 'kind' => NodeKind::UNION_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(6, 11)), + 'directives' => [], + 'types' => [ $this->typeNode('Wo', $loc(14, 16)), - $this->typeNode('Rld', $loc(19, 22)) + $this->typeNode('Rld', $loc(19, 22)), ], - 'loc' => $loc(0, 22), - 'description' => null - ] + 'loc' => $loc(0, 22), + 'description' => null, + ], ], - 'loc' => $loc(0, 22) + 'loc' => $loc(0, 22), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } - /** * @see it('Union with two types and leading pipe') */ public function testUnionWithTwoTypesAndLeadingPipe() : void { - $body = 'union Hello = | Wo | Rld'; - $doc = Parser::parse($body); + $body = 'union Hello = | Wo | Rld'; + $doc = Parser::parse($body); $expected = [ - 'kind' => 'Document', + 'kind' => 'Document', 'definitions' => [ [ - 'kind' => 'UnionTypeDefinition', - 'name' => $this->nameNode('Hello', ['start' => 6, 'end' => 11]), - 'directives' => [], - 'types' => [ + 'kind' => 'UnionTypeDefinition', + 'name' => $this->nameNode('Hello', ['start' => 6, 'end' => 11]), + 'directives' => [], + 'types' => [ $this->typeNode('Wo', ['start' => 16, 'end' => 18]), $this->typeNode('Rld', ['start' => 21, 'end' => 24]), ], - 'loc' => ['start' => 0, 'end' => 24], - 'description' => null - ] + 'loc' => ['start' => 0, 'end' => 24], + 'description' => null, + ], ], - 'loc' => ['start' => 0, 'end' => 24], + 'loc' => ['start' => 0, 'end' => 24], ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } @@ -847,20 +966,23 @@ type Hello { public function testScalar() : void { $body = 'scalar Hello'; - $doc = Parser::parse($body); - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; + $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::SCALAR_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(7, 12)), - 'directives' => [], - 'loc' => $loc(0, 12), - 'description' => null - ] + 'kind' => NodeKind::SCALAR_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(7, 12)), + 'directives' => [], + 'loc' => $loc(0, 12), + 'description' => null, + ], ], - 'loc' => $loc(0, 12) + 'loc' => $loc(0, 12), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } @@ -874,29 +996,31 @@ type Hello { input Hello { world: String }'; - $doc = Parser::parse($body); - $loc = function($start, $end) {return TestUtils::locArray($start, $end);}; + $doc = Parser::parse($body); + $loc = function ($start, $end) { + return TestUtils::locArray($start, $end); + }; $expected = [ - 'kind' => NodeKind::DOCUMENT, + 'kind' => NodeKind::DOCUMENT, 'definitions' => [ [ - 'kind' => NodeKind::INPUT_OBJECT_TYPE_DEFINITION, - 'name' => $this->nameNode('Hello', $loc(7, 12)), - 'directives' => [], - 'fields' => [ + 'kind' => NodeKind::INPUT_OBJECT_TYPE_DEFINITION, + 'name' => $this->nameNode('Hello', $loc(7, 12)), + 'directives' => [], + 'fields' => [ $this->inputValueNode( $this->nameNode('world', $loc(17, 22)), $this->typeNode('String', $loc(24, 30)), null, $loc(17, 30) - ) + ), ], - 'loc' => $loc(1, 32), - 'description' => null - ] + 'loc' => $loc(1, 32), + 'description' => null, + ], ], - 'loc' => $loc(0, 32) + 'loc' => $loc(0, 32), ]; $this->assertEquals($expected, TestUtils::nodeToArray($doc)); } @@ -943,8 +1067,8 @@ input Hello { */ public function testAllowLegacySDLEmptyFieldsOption() : void { - $body = 'type Hello { }'; - $doc = Parser::parse($body, ['allowLegacySDLEmptyFields' => true]); + $body = 'type Hello { }'; + $doc = Parser::parse($body, ['allowLegacySDLEmptyFields' => true]); $expected = [ 'definitions' => [ [ @@ -966,8 +1090,8 @@ input Hello { */ public function testDefaultSDLImplementsInterfaces() : void { - $body = 'type Hello implements Wo rld { field: String }'; - $doc = Parser::parse($body, ['allowLegacySDLImplementsInterfaces' => true]); + $body = 'type Hello implements Wo rld { field: String }'; + $doc = Parser::parse($body, ['allowLegacySDLImplementsInterfaces' => true]); $expected = [ 'definitions' => [ [ @@ -980,81 +1104,4 @@ input Hello { ]; $this->assertArraySubset($expected, $doc->toArray(true)); } - - private function typeNode($name, $loc) - { - return [ - 'kind' => NodeKind::NAMED_TYPE, - 'name' => ['kind' => NodeKind::NAME, 'value' => $name, 'loc' => $loc], - 'loc' => $loc - ]; - } - - private function nameNode($name, $loc) - { - return [ - 'kind' => NodeKind::NAME, - 'value' => $name, - 'loc' => $loc - ]; - } - - private function fieldNode($name, $type, $loc) - { - return $this->fieldNodeWithArgs($name, $type, [], $loc); - } - - private function fieldNodeWithArgs($name, $type, $args, $loc) - { - return [ - 'kind' => NodeKind::FIELD_DEFINITION, - 'name' => $name, - 'arguments' => $args, - 'type' => $type, - 'directives' => [], - 'loc' => $loc, - 'description' => null - ]; - } - - private function enumValueNode($name, $loc) - { - return [ - 'kind' => NodeKind::ENUM_VALUE_DEFINITION, - 'name' => $this->nameNode($name, $loc), - 'directives' => [], - 'loc' => $loc, - 'description' => null - ]; - } - - private function inputValueNode($name, $type, $defaultValue, $loc) - { - return [ - 'kind' => NodeKind::INPUT_VALUE_DEFINITION, - 'name' => $name, - 'type' => $type, - 'defaultValue' => $defaultValue, - 'directives' => [], - 'loc' => $loc, - 'description' => null - ]; - } - - private function loc($line, $column) - { - return new SourceLocation($line, $column); - } - - private function expectSyntaxError($text, $message, $location) - { - $this->expectException(SyntaxError::class); - $this->expectExceptionMessage($message); - try { - Parser::parse($text); - } catch (SyntaxError $error) { - $this->assertEquals([$location], $error->getLocations()); - throw $error; - } - } } diff --git a/tests/Language/SchemaPrinterTest.php b/tests/Language/SchemaPrinterTest.php index b0cc05f..760d036 100644 --- a/tests/Language/SchemaPrinterTest.php +++ b/tests/Language/SchemaPrinterTest.php @@ -1,4 +1,7 @@ new NameNode(['value' => 'foo']) + 'name' => new NameNode(['value' => 'foo']), ]); $this->assertEquals('scalar foo', Printer::doPrint($ast)); } @@ -41,7 +45,7 @@ class SchemaPrinterTest extends TestCase { $kitchenSink = file_get_contents(__DIR__ . '/schema-kitchen-sink.graphql'); - $ast = Parser::parse($kitchenSink); + $ast = Parser::parse($kitchenSink); $astCopy = $ast->cloneDeep(); Printer::doPrint($ast); @@ -52,7 +56,7 @@ class SchemaPrinterTest extends TestCase { $kitchenSink = file_get_contents(__DIR__ . '/schema-kitchen-sink.graphql'); - $ast = Parser::parse($kitchenSink); + $ast = Parser::parse($kitchenSink); $printed = Printer::doPrint($ast); $expected = 'schema { diff --git a/tests/Language/SerializationTest.php b/tests/Language/SerializationTest.php index 7624fdc..a55a509 100644 --- a/tests/Language/SerializationTest.php +++ b/tests/Language/SerializationTest.php @@ -1,5 +1,8 @@ assertEquals($expectedAst, $ast->toArray(true)); } public function testUnserializesAst() : void { - $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); + $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); $serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true); - $actualAst = AST::fromArray($serializedAst); - $parsedAst = Parser::parse($kitchenSink); - $this->assertNodesAreEqual($parsedAst, $actualAst); - } - - public function testSerializeSupportsNoLocationOption() : void - { - $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); - $ast = Parser::parse($kitchenSink, ['noLocation' => true]); - $expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true); - $this->assertEquals($expectedAst, $ast->toArray(true)); - } - - public function testUnserializeSupportsNoLocationOption() : void - { - $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); - $serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true); - $actualAst = AST::fromArray($serializedAst); - $parsedAst = Parser::parse($kitchenSink, ['noLocation' => true]); + $actualAst = AST::fromArray($serializedAst); + $parsedAst = Parser::parse($kitchenSink); $this->assertNodesAreEqual($parsedAst, $actualAst); } /** * Compares two nodes by actually iterating over all NodeLists, properly comparing locations (ignoring tokens), etc * - * @param $expected - * @param $actual - * @param array $path + * @param string[] $path */ - private function assertNodesAreEqual($expected, $actual, $path = []) + private function assertNodesAreEqual(Node $expected, Node $actual, array $path = []) : void { - $err = "Mismatch at AST path: " . implode(', ', $path); + $err = 'Mismatch at AST path: ' . implode(', ', $path); $this->assertInstanceOf(Node::class, $actual, $err); $this->assertEquals(get_class($expected), get_class($actual), $err); $expectedVars = get_object_vars($expected); - $actualVars = get_object_vars($actual); + $actualVars = get_object_vars($actual); $this->assertSame(count($expectedVars), count($actualVars), $err); $this->assertEquals(array_keys($expectedVars), array_keys($actualVars), $err); foreach ($expectedVars as $name => $expectedValue) { $actualValue = $actualVars[$name]; - $tmpPath = $path; - $tmpPath[] = $name; - $err = "Mismatch at AST path: " . implode(', ', $tmpPath); + $tmpPath = $path; + $tmpPath[] = $name; + $err = 'Mismatch at AST path: ' . implode(', ', $tmpPath); if ($expectedValue instanceof Node) { $this->assertNodesAreEqual($expectedValue, $actualValue, $tmpPath); - } else if ($expectedValue instanceof NodeList) { + } elseif ($expectedValue instanceof NodeList) { $this->assertEquals(count($expectedValue), count($actualValue), $err); $this->assertInstanceOf(NodeList::class, $actualValue, $err); foreach ($expectedValue as $index => $listNode) { - $tmpPath2 = $tmpPath; - $tmpPath2 [] = $index; + $tmpPath2 = $tmpPath; + $tmpPath2[] = $index; $this->assertNodesAreEqual($listNode, $actualValue[$index], $tmpPath2); } - } else if ($expectedValue instanceof Location) { + } elseif ($expectedValue instanceof Location) { $this->assertInstanceOf(Location::class, $actualValue, $err); $this->assertSame($expectedValue->start, $actualValue->start, $err); $this->assertSame($expectedValue->end, $actualValue->end, $err); @@ -89,4 +80,21 @@ class SerializationTest extends TestCase } } } + + public function testSerializeSupportsNoLocationOption() : void + { + $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); + $ast = Parser::parse($kitchenSink, ['noLocation' => true]); + $expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true); + $this->assertEquals($expectedAst, $ast->toArray(true)); + } + + public function testUnserializeSupportsNoLocationOption() : void + { + $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); + $serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true); + $actualAst = AST::fromArray($serializedAst); + $parsedAst = Parser::parse($kitchenSink, ['noLocation' => true]); + $this->assertNodesAreEqual($parsedAst, $actualAst); + } } diff --git a/tests/Language/TestUtils.php b/tests/Language/TestUtils.php index b36377c..c6e3cbe 100644 --- a/tests/Language/TestUtils.php +++ b/tests/Language/TestUtils.php @@ -1,36 +1,41 @@ $node->kind, - 'loc' => self::locationToArray($node->loc) + 'loc' => self::locationToArray($node->loc), ]; foreach (get_object_vars($node) as $prop => $propValue) { - if (isset($result[$prop])) + if (isset($result[$prop])) { continue; + } if (is_array($propValue) || $propValue instanceof NodeList) { $tmp = []; foreach ($propValue as $tmp1) { $tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1; } - } else if ($propValue instanceof Node) { + } elseif ($propValue instanceof Node) { $tmp = self::nodeToArray($propValue); - } else if (is_scalar($propValue) || null === $propValue) { + } elseif (is_scalar($propValue) || $propValue === null) { $tmp = $propValue; } else { $tmp = null; @@ -38,27 +43,25 @@ class TestUtils $result[$prop] = $tmp; } + return $result; } /** - * @param Location $loc - * @return array + * @return int[] */ - public static function locationToArray(Location $loc) + public static function locationToArray(Location $loc) : array { return [ 'start' => $loc->start, - 'end' => $loc->end + 'end' => $loc->end, ]; } /** - * @param $start - * @param $end - * @return array + * @return int[] */ - public static function locArray($start, $end) + public static function locArray(int $start, int $end) : array { return ['start' => $start, 'end' => $end]; } diff --git a/tests/Language/TokenTest.php b/tests/Language/TokenTest.php index ea21d20..cb78c4e 100644 --- a/tests/Language/TokenTest.php +++ b/tests/Language/TokenTest.php @@ -1,4 +1,7 @@ 'Kind', - 'value' => null, - 'line' => 3, - 'column' => 5 + 'kind' => 'Kind', + 'value' => null, + 'line' => 3, + 'column' => 5, ]; $this->assertEquals($expected, $token->toArray()); diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php index 300dea8..b3c81f7 100644 --- a/tests/Language/VisitorTest.php +++ b/tests/Language/VisitorTest.php @@ -29,15 +29,40 @@ use function iterator_to_array; class VisitorTest extends ValidatorTestCase { - private function getNodeByPath(DocumentNode $ast, $path) + public function testValidatesPathArgument() : void { - $result = $ast; - foreach ($path as $key) { - $resultArray = $result instanceof NodeList ? iterator_to_array($result) : $result->toArray(); - $this->assertArrayHasKey($key, $resultArray); - $result = $resultArray[$key]; - } - return $result; + $visited = []; + + $ast = Parser::parse('{ a }', ['noLocation' => true]); + + Visitor::visit( + $ast, + [ + 'enter' => function ($node, $key, $parent, $path) use ($ast, &$visited) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['enter', $path]; + }, + 'leave' => function ($node, $key, $parent, $path) use ($ast, &$visited) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['leave', $path]; + }, + ] + ); + + $expected = [ + ['enter', []], + ['enter', ['definitions', 0]], + ['enter', ['definitions', 0, 'selectionSet']], + ['enter', ['definitions', 0, 'selectionSet', 'selections', 0]], + ['enter', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']], + ['leave', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']], + ['leave', ['definitions', 0, 'selectionSet', 'selections', 0]], + ['leave', ['definitions', 0, 'selectionSet']], + ['leave', ['definitions', 0]], + ['leave', []], + ]; + + $this->assertEquals($expected, $visited); } private function checkVisitorFnArgs($ast, $args, $isEdited = false) @@ -58,6 +83,7 @@ class VisitorTest extends ValidatorTestCase $this->assertEquals(null, $parent); $this->assertEquals([], $path); $this->assertEquals([], $ancestors); + return; } @@ -84,66 +110,50 @@ class VisitorTest extends ValidatorTestCase } } - public function testValidatesPathArgument() : void + private function getNodeByPath(DocumentNode $ast, $path) { - $visited = []; + $result = $ast; + foreach ($path as $key) { + $resultArray = $result instanceof NodeList ? iterator_to_array($result) : $result->toArray(); + $this->assertArrayHasKey($key, $resultArray); + $result = $resultArray[$key]; + } - $ast = Parser::parse('{ a }', ['noLocation' => true]); - - Visitor::visit($ast, [ - 'enter' => function ($node, $key, $parent, $path) use ($ast, &$visited) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $path]; - }, - 'leave' => function ($node, $key, $parent, $path) use ($ast, &$visited) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $path]; - }, - ]); - - $expected = [ - ['enter', []], - ['enter', ['definitions', 0]], - ['enter', ['definitions', 0, 'selectionSet']], - ['enter', ['definitions', 0, 'selectionSet', 'selections', 0]], - ['enter', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']], - ['leave', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']], - ['leave', ['definitions', 0, 'selectionSet', 'selections', 0]], - ['leave', ['definitions', 0, 'selectionSet']], - ['leave', ['definitions', 0]], - ['leave', []], - ]; - - $this->assertEquals($expected, $visited); + return $result; } public function testAllowsEditingNodeOnEnterAndOnLeave() : void { - $ast = Parser::parse('{ a, b, c { a, b, c } }', [ 'noLocation' => true ]); + $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); $selectionSet = null; - $editedAst = Visitor::visit($ast, [ - NodeKind::OPERATION_DEFINITION => [ - 'enter' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $selectionSet = $node->selectionSet; + $editedAst = Visitor::visit( + $ast, + [ + NodeKind::OPERATION_DEFINITION => [ + 'enter' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $selectionSet = $node->selectionSet; - $newNode = clone $node; - $newNode->selectionSet = new SelectionSetNode([ - 'selections' => [], - ]); - $newNode->didEnter = true; - return $newNode; - }, - 'leave' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args(), true); - $newNode = clone $node; - $newNode->selectionSet = $selectionSet; - $newNode->didLeave = true; - return $newNode; - }, - ], - ]); + $newNode = clone $node; + $newNode->selectionSet = new SelectionSetNode([ + 'selections' => [], + ]); + $newNode->didEnter = true; + + return $newNode; + }, + 'leave' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args(), true); + $newNode = clone $node; + $newNode->selectionSet = $selectionSet; + $newNode->didLeave = true; + + return $newNode; + }, + ], + ] + ); $this->assertNotEquals($ast, $editedAst); @@ -156,25 +166,29 @@ class VisitorTest extends ValidatorTestCase public function testAllowsEditingRootNodeOnEnterAndLeave() : void { - $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->definitions = []; - $tmp->didEnter = true; - return $tmp; - }, - 'leave' => function (DocumentNode $node) use ($definitions, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args(), true); - $node->definitions = $definitions; - $node->didLeave = true; - }, - ], - ]); + $editedAst = Visitor::visit( + $ast, + [ + NodeKind::DOCUMENT => [ + 'enter' => function (DocumentNode $node) use ($ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $tmp = clone $node; + $tmp->definitions = []; + $tmp->didEnter = true; + + return $tmp; + }, + 'leave' => function (DocumentNode $node) use ($definitions, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args(), true); + $node->definitions = $definitions; + $node->didLeave = true; + }, + ], + ] + ); $this->assertNotEquals($ast, $editedAst); @@ -188,14 +202,17 @@ class VisitorTest extends ValidatorTestCase public function testAllowsForEditingOnEnter() : void { $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); - $editedAst = Visitor::visit($ast, [ - 'enter' => function ($node) use ($ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - if ($node instanceof FieldNode && $node->name->value === 'b') { - return Visitor::removeNode(); - } - }, - ]); + $editedAst = Visitor::visit( + $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( Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]), @@ -210,14 +227,17 @@ class VisitorTest extends ValidatorTestCase public function testAllowsForEditingOnLeave() : void { $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); - $editedAst = Visitor::visit($ast, [ - 'leave' => function ($node) use ($ast) { - $this->checkVisitorFnArgs($ast, func_get_args(), true); - if ($node instanceof FieldNode && $node->name->value === 'b') { - return Visitor::removeNode(); - } - }, - ]); + $editedAst = Visitor::visit( + $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( Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]), @@ -240,23 +260,26 @@ class VisitorTest extends ValidatorTestCase $ast = Parser::parse('{ a { x } }', ['noLocation' => true]); - Visitor::visit($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([ - 'selections' => NodeList::create([$addedField])->merge($node->selectionSet->selections), - ]), - ]); - } - if ($node !== $addedField) { - return; - } + Visitor::visit( + $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([ + 'selections' => NodeList::create([$addedField])->merge($node->selectionSet->selections), + ]), + ]); + } + if ($node !== $addedField) { + return; + } - $didVisitAddedField = true; - }, - ]); + $didVisitAddedField = true; + }, + ] + ); $this->assertTrue($didVisitAddedField); } @@ -266,36 +289,39 @@ class VisitorTest extends ValidatorTestCase $visited = []; $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]); - Visitor::visit($ast, [ - 'enter' => function (Node $node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $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, $node->value ?? null]; - }, - ]); + Visitor::visit( + $ast, + [ + 'enter' => function (Node $node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $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, $node->value ?? null]; + }, + ] + ); $expected = [ - [ 'enter', 'Document', null ], - [ 'enter', 'OperationDefinition', null ], - [ 'enter', 'SelectionSet', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'a' ], - [ 'leave', 'Name', 'a' ], - [ 'leave', 'Field', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'c' ], - [ 'leave', 'Name', 'c' ], - [ 'leave', 'Field', null ], - [ 'leave', 'SelectionSet', null ], - [ 'leave', 'OperationDefinition', null ], - [ 'leave', 'Document', null ], + ['enter', 'Document', null], + ['enter', 'OperationDefinition', null], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'a'], + ['leave', 'Name', 'a'], + ['leave', 'Field', null], + ['enter', 'Field', null], + ['enter', 'Field', null], + ['enter', 'Name', 'c'], + ['leave', 'Name', 'c'], + ['leave', 'Field', null], + ['leave', 'SelectionSet', null], + ['leave', 'OperationDefinition', null], + ['leave', 'Document', null], ]; $this->assertEquals($expected, $visited); @@ -306,34 +332,37 @@ class VisitorTest extends ValidatorTestCase $visited = []; $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]); - Visitor::visit($ast, [ - 'enter' => function (Node $node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $node->kind, $node->value ?? null]; - if ($node instanceof NameNode && $node->value === 'x') { - return Visitor::stop(); - } - }, - 'leave' => function (Node $node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $node->kind, $node->value ?? null]; - }, - ]); + Visitor::visit( + $ast, + [ + 'enter' => function (Node $node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['enter', $node->kind, $node->value ?? null]; + if ($node instanceof NameNode && $node->value === 'x') { + return Visitor::stop(); + } + }, + 'leave' => function (Node $node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['leave', $node->kind, $node->value ?? null]; + }, + ] + ); $expected = [ - [ 'enter', 'Document', null ], - [ 'enter', 'OperationDefinition', null ], - [ 'enter', 'SelectionSet', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'a' ], - [ 'leave', 'Name', 'a' ], - [ 'leave', 'Field', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'b' ], - [ 'leave', 'Name', 'b' ], - [ 'enter', 'SelectionSet', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'x' ], + ['enter', 'Document', null], + ['enter', 'OperationDefinition', null], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'a'], + ['leave', 'Name', 'a'], + ['leave', 'Field', null], + ['enter', 'Field', null], + ['enter', 'Name', 'b'], + ['leave', 'Name', 'b'], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'x'], ]; $this->assertEquals($expected, $visited); @@ -344,37 +373,43 @@ class VisitorTest extends ValidatorTestCase $visited = []; $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]); - Visitor::visit($ast, [ - 'enter' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $node->kind, $node->value ?? null]; - }, - 'leave' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $node->kind, $node->value ?? null]; + Visitor::visit( + $ast, + [ + 'enter' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['enter', $node->kind, $node->value ?? null]; + }, + 'leave' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['leave', $node->kind, $node->value ?? null]; - if ($node->kind === NodeKind::NAME && $node->value === 'x') { - return Visitor::stop(); - } - }, - ]); + if ($node->kind === NodeKind::NAME && $node->value === 'x') { + return Visitor::stop(); + } + }, + ] + ); - $this->assertEquals($visited, [ - [ 'enter', 'Document', null ], - [ 'enter', 'OperationDefinition', null ], - [ 'enter', 'SelectionSet', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'a' ], - [ 'leave', 'Name', 'a' ], - [ 'leave', 'Field', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'b' ], - [ 'leave', 'Name', 'b' ], - [ 'enter', 'SelectionSet', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'x' ], - [ 'leave', 'Name', 'x' ], - ]); + $this->assertEquals( + $visited, + [ + ['enter', 'Document', null], + ['enter', 'OperationDefinition', null], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'a'], + ['leave', 'Name', 'a'], + ['leave', 'Field', null], + ['enter', 'Field', null], + ['enter', 'Name', 'b'], + ['leave', 'Name', 'b'], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'x'], + ['leave', 'Name', 'x'], + ] + ); } public function testAllowsANamedFunctionsVisitorAPI() : void @@ -382,32 +417,35 @@ class VisitorTest extends ValidatorTestCase $visited = []; $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]); - Visitor::visit($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) { + Visitor::visit( + $ast, + [ + NodeKind::NAME => function (NameNode $node) use (&$visited, $ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $node->kind, null]; + $visited[] = ['enter', $node->kind, $node->value]; }, - 'leave' => function (SelectionSetNode $node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $node->kind, null]; - }, - ], - ]); + NodeKind::SELECTION_SET => [ + '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) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['leave', $node->kind, null]; + }, + ], + ] + ); $expected = [ - [ 'enter', 'SelectionSet', null ], - [ 'enter', 'Name', 'a' ], - [ 'enter', 'Name', 'b' ], - [ 'enter', 'SelectionSet', null ], - [ 'enter', 'Name', 'x' ], - [ 'leave', 'SelectionSet', null ], - [ 'enter', 'Name', 'c' ], - [ 'leave', 'SelectionSet', null ], + ['enter', 'SelectionSet', null], + ['enter', 'Name', 'a'], + ['enter', 'Name', 'b'], + ['enter', 'SelectionSet', null], + ['enter', 'Name', 'x'], + ['leave', 'SelectionSet', null], + ['enter', 'Name', 'c'], + ['leave', 'SelectionSet', null], ]; $this->assertEquals($expected, $visited); @@ -418,22 +456,25 @@ class VisitorTest extends ValidatorTestCase $ast = Parser::parse( 'fragment a($v: Boolean = false) on t { f }', [ - 'noLocation' => true, + 'noLocation' => true, 'experimentalFragmentVariables' => true, ] ); $visited = []; - Visitor::visit($ast, [ - 'enter' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $node->kind, $node->value ?? null]; - }, - 'leave' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $node->kind, $node->value ?? null]; - }, - ]); + Visitor::visit( + $ast, + [ + 'enter' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['enter', $node->kind, $node->value ?? null]; + }, + 'leave' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['leave', $node->kind, $node->value ?? null]; + }, + ] + ); $expected = [ ['enter', 'Document', null], @@ -475,330 +516,333 @@ class VisitorTest extends ValidatorTestCase $ast = Parser::parse($kitchenSink); $visited = []; - Visitor::visit($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]; - $visited[] = $r; - }, - '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]; - $visited[] = $r; - }, - ]); + Visitor::visit( + $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]; + $visited[] = $r; + }, + '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]; + $visited[] = $r; + }, + ] + ); $expected = [ - [ 'enter', 'Document', null, null ], - [ 'enter', 'OperationDefinition', 0, null ], - [ 'enter', 'Name', 'name', 'OperationDefinition' ], - [ 'leave', 'Name', 'name', 'OperationDefinition' ], - [ 'enter', 'VariableDefinition', 0, null ], - [ 'enter', 'Variable', 'variable', 'VariableDefinition' ], - [ 'enter', 'Name', 'name', 'Variable' ], - [ 'leave', 'Name', 'name', 'Variable' ], - [ 'leave', 'Variable', 'variable', 'VariableDefinition' ], - [ 'enter', 'NamedType', 'type', 'VariableDefinition' ], - [ 'enter', 'Name', 'name', 'NamedType' ], - [ 'leave', 'Name', 'name', 'NamedType' ], - [ 'leave', 'NamedType', 'type', 'VariableDefinition' ], - [ 'leave', 'VariableDefinition', 0, null ], - [ 'enter', 'VariableDefinition', 1, null ], - [ 'enter', 'Variable', 'variable', 'VariableDefinition' ], - [ 'enter', 'Name', 'name', 'Variable' ], - [ 'leave', 'Name', 'name', 'Variable' ], - [ 'leave', 'Variable', 'variable', 'VariableDefinition' ], - [ 'enter', 'NamedType', 'type', 'VariableDefinition' ], - [ 'enter', 'Name', 'name', 'NamedType' ], - [ 'leave', 'Name', 'name', 'NamedType' ], - [ 'leave', 'NamedType', 'type', 'VariableDefinition' ], - [ 'enter', 'EnumValue', 'defaultValue', 'VariableDefinition' ], - [ 'leave', 'EnumValue', 'defaultValue', 'VariableDefinition' ], - [ 'leave', 'VariableDefinition', 1, null ], - [ 'enter', 'SelectionSet', 'selectionSet', 'OperationDefinition' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'alias', 'Field' ], - [ 'leave', 'Name', 'alias', 'Field' ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'enter', 'Argument', 0, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'ListValue', 'value', 'Argument' ], - [ 'enter', 'IntValue', 0, null ], - [ 'leave', 'IntValue', 0, null ], - [ 'enter', 'IntValue', 1, null ], - [ 'leave', 'IntValue', 1, null ], - [ 'leave', 'ListValue', 'value', 'Argument' ], - [ 'leave', 'Argument', 0, null ], - [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'enter', 'InlineFragment', 1, null ], - [ 'enter', 'NamedType', 'typeCondition', 'InlineFragment' ], - [ 'enter', 'Name', 'name', 'NamedType' ], - [ 'leave', 'Name', 'name', 'NamedType' ], - [ 'leave', 'NamedType', 'typeCondition', 'InlineFragment' ], - [ 'enter', 'Directive', 0, null ], - [ 'enter', 'Name', 'name', 'Directive' ], - [ 'leave', 'Name', 'name', 'Directive' ], - [ 'leave', 'Directive', 0, null ], - [ 'enter', 'SelectionSet', 'selectionSet', 'InlineFragment' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'enter', 'Field', 1, null ], - [ 'enter', 'Name', 'alias', 'Field' ], - [ 'leave', 'Name', 'alias', 'Field' ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'enter', 'Argument', 0, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'IntValue', 'value', 'Argument' ], - [ 'leave', 'IntValue', 'value', 'Argument' ], - [ 'leave', 'Argument', 0, null ], - [ 'enter', 'Argument', 1, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'Variable', 'value', 'Argument' ], - [ 'enter', 'Name', 'name', 'Variable' ], - [ 'leave', 'Name', 'name', 'Variable' ], - [ 'leave', 'Variable', 'value', 'Argument' ], - [ 'leave', 'Argument', 1, null ], - [ 'enter', 'Directive', 0, null ], - [ 'enter', 'Name', 'name', 'Directive' ], - [ 'leave', 'Name', 'name', 'Directive' ], - [ 'enter', 'Argument', 0, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'Variable', 'value', 'Argument' ], - [ 'enter', 'Name', 'name', 'Variable' ], - [ 'leave', 'Name', 'name', 'Variable' ], - [ 'leave', 'Variable', 'value', 'Argument' ], - [ 'leave', 'Argument', 0, null ], - [ 'leave', 'Directive', 0, null ], - [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'enter', 'FragmentSpread', 1, null ], - [ 'enter', 'Name', 'name', 'FragmentSpread' ], - [ 'leave', 'Name', 'name', 'FragmentSpread' ], - [ 'leave', 'FragmentSpread', 1, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'leave', 'Field', 1, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'InlineFragment' ], - [ 'leave', 'InlineFragment', 1, null ], - [ 'enter', 'InlineFragment', 2, null ], - [ 'enter', 'Directive', 0, null ], - [ 'enter', 'Name', 'name', 'Directive' ], - [ 'leave', 'Name', 'name', 'Directive' ], - [ 'enter', 'Argument', 0, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'Variable', 'value', 'Argument' ], - [ 'enter', 'Name', 'name', 'Variable' ], - [ 'leave', 'Name', 'name', 'Variable' ], - [ 'leave', 'Variable', 'value', 'Argument' ], - [ 'leave', 'Argument', 0, null ], - [ 'leave', 'Directive', 0, null ], - [ 'enter', 'SelectionSet', 'selectionSet', 'InlineFragment' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'InlineFragment' ], - [ 'leave', 'InlineFragment', 2, null ], - [ 'enter', 'InlineFragment', 3, null ], - [ 'enter', 'SelectionSet', 'selectionSet', 'InlineFragment' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'InlineFragment' ], - [ 'leave', 'InlineFragment', 3, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ], - [ 'leave', 'OperationDefinition', 0, null ], - [ 'enter', 'OperationDefinition', 1, null ], - [ 'enter', 'Name', 'name', 'OperationDefinition' ], - [ 'leave', 'Name', 'name', 'OperationDefinition' ], - [ 'enter', 'SelectionSet', 'selectionSet', 'OperationDefinition' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'enter', 'Argument', 0, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'IntValue', 'value', 'Argument' ], - [ 'leave', 'IntValue', 'value', 'Argument' ], - [ 'leave', 'Argument', 0, null ], - [ 'enter', 'Directive', 0, null ], - [ 'enter', 'Name', 'name', 'Directive' ], - [ 'leave', 'Name', 'name', 'Directive' ], - [ 'leave', 'Directive', 0, null ], - [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ], - [ 'leave', 'OperationDefinition', 1, null ], - [ 'enter', 'OperationDefinition', 2, null ], - [ 'enter', 'Name', 'name', 'OperationDefinition' ], - [ 'leave', 'Name', 'name', 'OperationDefinition' ], - [ 'enter', 'VariableDefinition', 0, null ], - [ 'enter', 'Variable', 'variable', 'VariableDefinition' ], - [ 'enter', 'Name', 'name', 'Variable' ], - [ 'leave', 'Name', 'name', 'Variable' ], - [ 'leave', 'Variable', 'variable', 'VariableDefinition' ], - [ 'enter', 'NamedType', 'type', 'VariableDefinition' ], - [ 'enter', 'Name', 'name', 'NamedType' ], - [ 'leave', 'Name', 'name', 'NamedType' ], - [ 'leave', 'NamedType', 'type', 'VariableDefinition' ], - [ 'leave', 'VariableDefinition', 0, null ], - [ 'enter', 'SelectionSet', 'selectionSet', 'OperationDefinition' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'enter', 'Argument', 0, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'Variable', 'value', 'Argument' ], - [ 'enter', 'Name', 'name', 'Variable' ], - [ 'leave', 'Name', 'name', 'Variable' ], - [ 'leave', 'Variable', 'value', 'Argument' ], - [ 'leave', 'Argument', 0, null ], - [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'enter', 'Field', 1, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'leave', 'Field', 1, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ], - [ 'leave', 'Field', 0, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ], - [ 'leave', 'OperationDefinition', 2, null ], - [ 'enter', 'FragmentDefinition', 3, null ], - [ 'enter', 'Name', 'name', 'FragmentDefinition' ], - [ 'leave', 'Name', 'name', 'FragmentDefinition' ], - [ 'enter', 'NamedType', 'typeCondition', 'FragmentDefinition' ], - [ 'enter', 'Name', 'name', 'NamedType' ], - [ 'leave', 'Name', 'name', 'NamedType' ], - [ 'leave', 'NamedType', 'typeCondition', 'FragmentDefinition' ], - [ 'enter', 'SelectionSet', 'selectionSet', 'FragmentDefinition' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'enter', 'Argument', 0, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'Variable', 'value', 'Argument' ], - [ 'enter', 'Name', 'name', 'Variable' ], - [ 'leave', 'Name', 'name', 'Variable' ], - [ 'leave', 'Variable', 'value', 'Argument' ], - [ 'leave', 'Argument', 0, null ], - [ 'enter', 'Argument', 1, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'Variable', 'value', 'Argument' ], - [ 'enter', 'Name', 'name', 'Variable' ], - [ 'leave', 'Name', 'name', 'Variable' ], - [ 'leave', 'Variable', 'value', 'Argument' ], - [ 'leave', 'Argument', 1, null ], - [ 'enter', 'Argument', 2, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'ObjectValue', 'value', 'Argument' ], - [ 'enter', 'ObjectField', 0, null ], - [ 'enter', 'Name', 'name', 'ObjectField' ], - [ 'leave', 'Name', 'name', 'ObjectField' ], - [ 'enter', 'StringValue', 'value', 'ObjectField' ], - [ 'leave', 'StringValue', 'value', 'ObjectField' ], - [ 'leave', 'ObjectField', 0, null ], - [ 'enter', 'ObjectField', 1, null ], - [ 'enter', 'Name', 'name', 'ObjectField' ], - [ 'leave', 'Name', 'name', 'ObjectField' ], - [ 'enter', 'StringValue', 'value', 'ObjectField' ], - [ 'leave', 'StringValue', 'value', 'ObjectField' ], - [ 'leave', 'ObjectField', 1, null ], - [ 'leave', 'ObjectValue', 'value', 'Argument' ], - [ 'leave', 'Argument', 2, null ], - [ 'leave', 'Field', 0, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'FragmentDefinition' ], - [ 'leave', 'FragmentDefinition', 3, null ], - [ 'enter', 'OperationDefinition', 4, null ], - [ 'enter', 'SelectionSet', 'selectionSet', 'OperationDefinition' ], - [ 'enter', 'Field', 0, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'enter', 'Argument', 0, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'BooleanValue', 'value', 'Argument' ], - [ 'leave', 'BooleanValue', 'value', 'Argument' ], - [ 'leave', 'Argument', 0, null ], - [ 'enter', 'Argument', 1, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'BooleanValue', 'value', 'Argument' ], - [ 'leave', 'BooleanValue', 'value', 'Argument' ], - [ 'leave', 'Argument', 1, null ], - [ 'enter', 'Argument', 2, null ], - [ 'enter', 'Name', 'name', 'Argument' ], - [ 'leave', 'Name', 'name', 'Argument' ], - [ 'enter', 'NullValue', 'value', 'Argument' ], - [ 'leave', 'NullValue', 'value', 'Argument' ], - [ 'leave', 'Argument', 2, null ], - [ 'leave', 'Field', 0, null ], - [ 'enter', 'Field', 1, null ], - [ 'enter', 'Name', 'name', 'Field' ], - [ 'leave', 'Name', 'name', 'Field' ], - [ 'leave', 'Field', 1, null ], - [ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ], - [ 'leave', 'OperationDefinition', 4, null ], - [ 'leave', 'Document', null, null ], + ['enter', 'Document', null, null], + ['enter', 'OperationDefinition', 0, null], + ['enter', 'Name', 'name', 'OperationDefinition'], + ['leave', 'Name', 'name', 'OperationDefinition'], + ['enter', 'VariableDefinition', 0, null], + ['enter', 'Variable', 'variable', 'VariableDefinition'], + ['enter', 'Name', 'name', 'Variable'], + ['leave', 'Name', 'name', 'Variable'], + ['leave', 'Variable', 'variable', 'VariableDefinition'], + ['enter', 'NamedType', 'type', 'VariableDefinition'], + ['enter', 'Name', 'name', 'NamedType'], + ['leave', 'Name', 'name', 'NamedType'], + ['leave', 'NamedType', 'type', 'VariableDefinition'], + ['leave', 'VariableDefinition', 0, null], + ['enter', 'VariableDefinition', 1, null], + ['enter', 'Variable', 'variable', 'VariableDefinition'], + ['enter', 'Name', 'name', 'Variable'], + ['leave', 'Name', 'name', 'Variable'], + ['leave', 'Variable', 'variable', 'VariableDefinition'], + ['enter', 'NamedType', 'type', 'VariableDefinition'], + ['enter', 'Name', 'name', 'NamedType'], + ['leave', 'Name', 'name', 'NamedType'], + ['leave', 'NamedType', 'type', 'VariableDefinition'], + ['enter', 'EnumValue', 'defaultValue', 'VariableDefinition'], + ['leave', 'EnumValue', 'defaultValue', 'VariableDefinition'], + ['leave', 'VariableDefinition', 1, null], + ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'alias', 'Field'], + ['leave', 'Name', 'alias', 'Field'], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['enter', 'Argument', 0, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'ListValue', 'value', 'Argument'], + ['enter', 'IntValue', 0, null], + ['leave', 'IntValue', 0, null], + ['enter', 'IntValue', 1, null], + ['leave', 'IntValue', 1, null], + ['leave', 'ListValue', 'value', 'Argument'], + ['leave', 'Argument', 0, null], + ['enter', 'SelectionSet', 'selectionSet', 'Field'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['leave', 'Field', 0, null], + ['enter', 'InlineFragment', 1, null], + ['enter', 'NamedType', 'typeCondition', 'InlineFragment'], + ['enter', 'Name', 'name', 'NamedType'], + ['leave', 'Name', 'name', 'NamedType'], + ['leave', 'NamedType', 'typeCondition', 'InlineFragment'], + ['enter', 'Directive', 0, null], + ['enter', 'Name', 'name', 'Directive'], + ['leave', 'Name', 'name', 'Directive'], + ['leave', 'Directive', 0, null], + ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['enter', 'SelectionSet', 'selectionSet', 'Field'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['leave', 'Field', 0, null], + ['enter', 'Field', 1, null], + ['enter', 'Name', 'alias', 'Field'], + ['leave', 'Name', 'alias', 'Field'], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['enter', 'Argument', 0, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'IntValue', 'value', 'Argument'], + ['leave', 'IntValue', 'value', 'Argument'], + ['leave', 'Argument', 0, null], + ['enter', 'Argument', 1, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'Variable', 'value', 'Argument'], + ['enter', 'Name', 'name', 'Variable'], + ['leave', 'Name', 'name', 'Variable'], + ['leave', 'Variable', 'value', 'Argument'], + ['leave', 'Argument', 1, null], + ['enter', 'Directive', 0, null], + ['enter', 'Name', 'name', 'Directive'], + ['leave', 'Name', 'name', 'Directive'], + ['enter', 'Argument', 0, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'Variable', 'value', 'Argument'], + ['enter', 'Name', 'name', 'Variable'], + ['leave', 'Name', 'name', 'Variable'], + ['leave', 'Variable', 'value', 'Argument'], + ['leave', 'Argument', 0, null], + ['leave', 'Directive', 0, null], + ['enter', 'SelectionSet', 'selectionSet', 'Field'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['leave', 'Field', 0, null], + ['enter', 'FragmentSpread', 1, null], + ['enter', 'Name', 'name', 'FragmentSpread'], + ['leave', 'Name', 'name', 'FragmentSpread'], + ['leave', 'FragmentSpread', 1, null], + ['leave', 'SelectionSet', 'selectionSet', 'Field'], + ['leave', 'Field', 1, null], + ['leave', 'SelectionSet', 'selectionSet', 'Field'], + ['leave', 'Field', 0, null], + ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], + ['leave', 'InlineFragment', 1, null], + ['enter', 'InlineFragment', 2, null], + ['enter', 'Directive', 0, null], + ['enter', 'Name', 'name', 'Directive'], + ['leave', 'Name', 'name', 'Directive'], + ['enter', 'Argument', 0, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'Variable', 'value', 'Argument'], + ['enter', 'Name', 'name', 'Variable'], + ['leave', 'Name', 'name', 'Variable'], + ['leave', 'Variable', 'value', 'Argument'], + ['leave', 'Argument', 0, null], + ['leave', 'Directive', 0, null], + ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['leave', 'Field', 0, null], + ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], + ['leave', 'InlineFragment', 2, null], + ['enter', 'InlineFragment', 3, null], + ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['leave', 'Field', 0, null], + ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], + ['leave', 'InlineFragment', 3, null], + ['leave', 'SelectionSet', 'selectionSet', 'Field'], + ['leave', 'Field', 0, null], + ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], + ['leave', 'OperationDefinition', 0, null], + ['enter', 'OperationDefinition', 1, null], + ['enter', 'Name', 'name', 'OperationDefinition'], + ['leave', 'Name', 'name', 'OperationDefinition'], + ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['enter', 'Argument', 0, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'IntValue', 'value', 'Argument'], + ['leave', 'IntValue', 'value', 'Argument'], + ['leave', 'Argument', 0, null], + ['enter', 'Directive', 0, null], + ['enter', 'Name', 'name', 'Directive'], + ['leave', 'Name', 'name', 'Directive'], + ['leave', 'Directive', 0, null], + ['enter', 'SelectionSet', 'selectionSet', 'Field'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['enter', 'SelectionSet', 'selectionSet', 'Field'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['leave', 'Field', 0, null], + ['leave', 'SelectionSet', 'selectionSet', 'Field'], + ['leave', 'Field', 0, null], + ['leave', 'SelectionSet', 'selectionSet', 'Field'], + ['leave', 'Field', 0, null], + ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], + ['leave', 'OperationDefinition', 1, null], + ['enter', 'OperationDefinition', 2, null], + ['enter', 'Name', 'name', 'OperationDefinition'], + ['leave', 'Name', 'name', 'OperationDefinition'], + ['enter', 'VariableDefinition', 0, null], + ['enter', 'Variable', 'variable', 'VariableDefinition'], + ['enter', 'Name', 'name', 'Variable'], + ['leave', 'Name', 'name', 'Variable'], + ['leave', 'Variable', 'variable', 'VariableDefinition'], + ['enter', 'NamedType', 'type', 'VariableDefinition'], + ['enter', 'Name', 'name', 'NamedType'], + ['leave', 'Name', 'name', 'NamedType'], + ['leave', 'NamedType', 'type', 'VariableDefinition'], + ['leave', 'VariableDefinition', 0, null], + ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['enter', 'Argument', 0, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'Variable', 'value', 'Argument'], + ['enter', 'Name', 'name', 'Variable'], + ['leave', 'Name', 'name', 'Variable'], + ['leave', 'Variable', 'value', 'Argument'], + ['leave', 'Argument', 0, null], + ['enter', 'SelectionSet', 'selectionSet', 'Field'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['enter', 'SelectionSet', 'selectionSet', 'Field'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['enter', 'SelectionSet', 'selectionSet', 'Field'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['leave', 'Field', 0, null], + ['leave', 'SelectionSet', 'selectionSet', 'Field'], + ['leave', 'Field', 0, null], + ['enter', 'Field', 1, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['enter', 'SelectionSet', 'selectionSet', 'Field'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['leave', 'Field', 0, null], + ['leave', 'SelectionSet', 'selectionSet', 'Field'], + ['leave', 'Field', 1, null], + ['leave', 'SelectionSet', 'selectionSet', 'Field'], + ['leave', 'Field', 0, null], + ['leave', 'SelectionSet', 'selectionSet', 'Field'], + ['leave', 'Field', 0, null], + ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], + ['leave', 'OperationDefinition', 2, null], + ['enter', 'FragmentDefinition', 3, null], + ['enter', 'Name', 'name', 'FragmentDefinition'], + ['leave', 'Name', 'name', 'FragmentDefinition'], + ['enter', 'NamedType', 'typeCondition', 'FragmentDefinition'], + ['enter', 'Name', 'name', 'NamedType'], + ['leave', 'Name', 'name', 'NamedType'], + ['leave', 'NamedType', 'typeCondition', 'FragmentDefinition'], + ['enter', 'SelectionSet', 'selectionSet', 'FragmentDefinition'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['enter', 'Argument', 0, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'Variable', 'value', 'Argument'], + ['enter', 'Name', 'name', 'Variable'], + ['leave', 'Name', 'name', 'Variable'], + ['leave', 'Variable', 'value', 'Argument'], + ['leave', 'Argument', 0, null], + ['enter', 'Argument', 1, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'Variable', 'value', 'Argument'], + ['enter', 'Name', 'name', 'Variable'], + ['leave', 'Name', 'name', 'Variable'], + ['leave', 'Variable', 'value', 'Argument'], + ['leave', 'Argument', 1, null], + ['enter', 'Argument', 2, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'ObjectValue', 'value', 'Argument'], + ['enter', 'ObjectField', 0, null], + ['enter', 'Name', 'name', 'ObjectField'], + ['leave', 'Name', 'name', 'ObjectField'], + ['enter', 'StringValue', 'value', 'ObjectField'], + ['leave', 'StringValue', 'value', 'ObjectField'], + ['leave', 'ObjectField', 0, null], + ['enter', 'ObjectField', 1, null], + ['enter', 'Name', 'name', 'ObjectField'], + ['leave', 'Name', 'name', 'ObjectField'], + ['enter', 'StringValue', 'value', 'ObjectField'], + ['leave', 'StringValue', 'value', 'ObjectField'], + ['leave', 'ObjectField', 1, null], + ['leave', 'ObjectValue', 'value', 'Argument'], + ['leave', 'Argument', 2, null], + ['leave', 'Field', 0, null], + ['leave', 'SelectionSet', 'selectionSet', 'FragmentDefinition'], + ['leave', 'FragmentDefinition', 3, null], + ['enter', 'OperationDefinition', 4, null], + ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], + ['enter', 'Field', 0, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['enter', 'Argument', 0, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'BooleanValue', 'value', 'Argument'], + ['leave', 'BooleanValue', 'value', 'Argument'], + ['leave', 'Argument', 0, null], + ['enter', 'Argument', 1, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'BooleanValue', 'value', 'Argument'], + ['leave', 'BooleanValue', 'value', 'Argument'], + ['leave', 'Argument', 1, null], + ['enter', 'Argument', 2, null], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'NullValue', 'value', 'Argument'], + ['leave', 'NullValue', 'value', 'Argument'], + ['leave', 'Argument', 2, null], + ['leave', 'Field', 0, null], + ['enter', 'Field', 1, null], + ['enter', 'Name', 'name', 'Field'], + ['leave', 'Name', 'name', 'Field'], + ['leave', 'Field', 1, null], + ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], + ['leave', 'OperationDefinition', 4, null], + ['leave', 'Document', null, null], ]; $this->assertEquals($expected, $visited); @@ -813,41 +857,47 @@ class VisitorTest extends ValidatorTestCase $visited = []; $ast = Parser::parse('{ a, b { x }, c }'); - Visitor::visit($ast, Visitor::visitInParallel([ + Visitor::visit( + $ast, + Visitor::visitInParallel([ + [ + 'enter' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $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) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['leave', $node->kind, $node->value ?? null]; + }, + ], + ]) + ); + + $this->assertEquals( [ - 'enter' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $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) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $node->kind, $node->value ?? null]; - }, + ['enter', 'Document', null], + ['enter', 'OperationDefinition', null], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'a'], + ['leave', 'Name', 'a'], + ['leave', 'Field', null], + ['enter', 'Field', null], + ['enter', 'Field', null], + ['enter', 'Name', 'c'], + ['leave', 'Name', 'c'], + ['leave', 'Field', null], + ['leave', 'SelectionSet', null], + ['leave', 'OperationDefinition', null], + ['leave', 'Document', null], ], - ])); - - $this->assertEquals([ - [ 'enter', 'Document', null ], - [ 'enter', 'OperationDefinition', null ], - [ 'enter', 'SelectionSet', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'a' ], - [ 'leave', 'Name', 'a' ], - [ 'leave', 'Field', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'c' ], - [ 'leave', 'Name', 'c' ], - [ 'leave', 'Field', null ], - [ 'leave', 'SelectionSet', null ], - [ 'leave', 'OperationDefinition', null ], - [ 'leave', 'Document', null ], - ], $visited); + $visited + ); } public function testAllowsSkippingDifferentSubTrees() : void @@ -855,71 +905,77 @@ class VisitorTest extends ValidatorTestCase $visited = []; $ast = Parser::parse('{ a { x }, b { y} }'); - Visitor::visit($ast, Visitor::visitInParallel([ - [ - 'enter' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $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) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = [ 'no-a', 'leave', $node->kind, $node->value ?? null ]; - }, - ], - [ - 'enter' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $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) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['no-b', 'leave', $node->kind, $node->value ?? null]; - }, - ], - ])); + Visitor::visit( + $ast, + Visitor::visitInParallel([ + [ + 'enter' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $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) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['no-a', 'leave', $node->kind, $node->value ?? null]; + }, + ], + [ + 'enter' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $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) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['no-b', 'leave', $node->kind, $node->value ?? null]; + }, + ], + ]) + ); - $this->assertEquals([ - [ 'no-a', 'enter', 'Document', null ], - [ 'no-b', 'enter', 'Document', null ], - [ 'no-a', 'enter', 'OperationDefinition', null ], - [ 'no-b', 'enter', 'OperationDefinition', null ], - [ 'no-a', 'enter', 'SelectionSet', null ], - [ 'no-b', 'enter', 'SelectionSet', null ], - [ 'no-a', 'enter', 'Field', null ], - [ 'no-b', 'enter', 'Field', null ], - [ 'no-b', 'enter', 'Name', 'a' ], - [ 'no-b', 'leave', 'Name', 'a' ], - [ 'no-b', 'enter', 'SelectionSet', null ], - [ 'no-b', 'enter', 'Field', null ], - [ 'no-b', 'enter', 'Name', 'x' ], - [ 'no-b', 'leave', 'Name', 'x' ], - [ 'no-b', 'leave', 'Field', null ], - [ 'no-b', 'leave', 'SelectionSet', null ], - [ 'no-b', 'leave', 'Field', null ], - [ 'no-a', 'enter', 'Field', null ], - [ 'no-b', 'enter', 'Field', null ], - [ 'no-a', 'enter', 'Name', 'b' ], - [ 'no-a', 'leave', 'Name', 'b' ], - [ 'no-a', 'enter', 'SelectionSet', null ], - [ 'no-a', 'enter', 'Field', null ], - [ 'no-a', 'enter', 'Name', 'y' ], - [ 'no-a', 'leave', 'Name', 'y' ], - [ 'no-a', 'leave', 'Field', null ], - [ 'no-a', 'leave', 'SelectionSet', null ], - [ 'no-a', 'leave', 'Field', null ], - [ 'no-a', 'leave', 'SelectionSet', null ], - [ 'no-b', 'leave', 'SelectionSet', null ], - [ 'no-a', 'leave', 'OperationDefinition', null ], - [ 'no-b', 'leave', 'OperationDefinition', null ], - [ 'no-a', 'leave', 'Document', null ], - [ 'no-b', 'leave', 'Document', null ], - ], $visited); + $this->assertEquals( + [ + ['no-a', 'enter', 'Document', null], + ['no-b', 'enter', 'Document', null], + ['no-a', 'enter', 'OperationDefinition', null], + ['no-b', 'enter', 'OperationDefinition', null], + ['no-a', 'enter', 'SelectionSet', null], + ['no-b', 'enter', 'SelectionSet', null], + ['no-a', 'enter', 'Field', null], + ['no-b', 'enter', 'Field', null], + ['no-b', 'enter', 'Name', 'a'], + ['no-b', 'leave', 'Name', 'a'], + ['no-b', 'enter', 'SelectionSet', null], + ['no-b', 'enter', 'Field', null], + ['no-b', 'enter', 'Name', 'x'], + ['no-b', 'leave', 'Name', 'x'], + ['no-b', 'leave', 'Field', null], + ['no-b', 'leave', 'SelectionSet', null], + ['no-b', 'leave', 'Field', null], + ['no-a', 'enter', 'Field', null], + ['no-b', 'enter', 'Field', null], + ['no-a', 'enter', 'Name', 'b'], + ['no-a', 'leave', 'Name', 'b'], + ['no-a', 'enter', 'SelectionSet', null], + ['no-a', 'enter', 'Field', null], + ['no-a', 'enter', 'Name', 'y'], + ['no-a', 'leave', 'Name', 'y'], + ['no-a', 'leave', 'Field', null], + ['no-a', 'leave', 'SelectionSet', null], + ['no-a', 'leave', 'Field', null], + ['no-a', 'leave', 'SelectionSet', null], + ['no-b', 'leave', 'SelectionSet', null], + ['no-a', 'leave', 'OperationDefinition', null], + ['no-b', 'leave', 'OperationDefinition', null], + ['no-a', 'leave', 'Document', null], + ['no-b', 'leave', 'Document', null], + ], + $visited + ); } public function testAllowsEarlyExitWhileVisiting2() : void @@ -927,37 +983,43 @@ class VisitorTest extends ValidatorTestCase $visited = []; $ast = Parser::parse('{ a, b { x }, c }'); - Visitor::visit($ast, Visitor::visitInParallel([ [ - 'enter' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $value = $node->value ?? null; - $visited[] = ['enter', $node->kind, $value]; - if ($node->kind === 'Name' && $value === 'x') { - return Visitor::stop(); - } - }, - 'leave' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['leave', $node->kind, $node->value ?? null]; - }, - ], - ])); + Visitor::visit( + $ast, + Visitor::visitInParallel([[ + 'enter' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $value = $node->value ?? null; + $visited[] = ['enter', $node->kind, $value]; + if ($node->kind === 'Name' && $value === 'x') { + return Visitor::stop(); + } + }, + 'leave' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['leave', $node->kind, $node->value ?? null]; + }, + ], + ]) + ); - $this->assertEquals([ - [ 'enter', 'Document', null ], - [ 'enter', 'OperationDefinition', null ], - [ 'enter', 'SelectionSet', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'a' ], - [ 'leave', 'Name', 'a' ], - [ 'leave', 'Field', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'b' ], - [ 'leave', 'Name', 'b' ], - [ 'enter', 'SelectionSet', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'x' ], - ], $visited); + $this->assertEquals( + [ + ['enter', 'Document', null], + ['enter', 'OperationDefinition', null], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'a'], + ['leave', 'Name', 'a'], + ['leave', 'Field', null], + ['enter', 'Field', null], + ['enter', 'Name', 'b'], + ['leave', 'Name', 'b'], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'x'], + ], + $visited + ); } public function testAllowsEarlyExitFromDifferentPoints() : void @@ -965,59 +1027,65 @@ class VisitorTest extends ValidatorTestCase $visited = []; $ast = Parser::parse('{ a { y }, b { x } }'); - Visitor::visit($ast, Visitor::visitInParallel([ - [ - 'enter' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $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) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = [ 'break-a', 'leave', $node->kind, $node->value ?? null ]; - }, - ], - [ - 'enter' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $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) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null]; - }, - ], - ])); + Visitor::visit( + $ast, + Visitor::visitInParallel([ + [ + 'enter' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $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) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['break-a', 'leave', $node->kind, $node->value ?? null]; + }, + ], + [ + 'enter' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $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) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null]; + }, + ], + ]) + ); - $this->assertEquals([ - [ 'break-a', 'enter', 'Document', null ], - [ 'break-b', 'enter', 'Document', null ], - [ 'break-a', 'enter', 'OperationDefinition', null ], - [ 'break-b', 'enter', 'OperationDefinition', null ], - [ 'break-a', 'enter', 'SelectionSet', null ], - [ 'break-b', 'enter', 'SelectionSet', null ], - [ 'break-a', 'enter', 'Field', null ], - [ 'break-b', 'enter', 'Field', null ], - [ 'break-a', 'enter', 'Name', 'a' ], - [ 'break-b', 'enter', 'Name', 'a' ], - [ 'break-b', 'leave', 'Name', 'a' ], - [ 'break-b', 'enter', 'SelectionSet', null ], - [ 'break-b', 'enter', 'Field', null ], - [ 'break-b', 'enter', 'Name', 'y' ], - [ 'break-b', 'leave', 'Name', 'y' ], - [ 'break-b', 'leave', 'Field', null ], - [ 'break-b', 'leave', 'SelectionSet', null ], - [ 'break-b', 'leave', 'Field', null ], - [ 'break-b', 'enter', 'Field', null ], - [ 'break-b', 'enter', 'Name', 'b' ], - ], $visited); + $this->assertEquals( + [ + ['break-a', 'enter', 'Document', null], + ['break-b', 'enter', 'Document', null], + ['break-a', 'enter', 'OperationDefinition', null], + ['break-b', 'enter', 'OperationDefinition', null], + ['break-a', 'enter', 'SelectionSet', null], + ['break-b', 'enter', 'SelectionSet', null], + ['break-a', 'enter', 'Field', null], + ['break-b', 'enter', 'Field', null], + ['break-a', 'enter', 'Name', 'a'], + ['break-b', 'enter', 'Name', 'a'], + ['break-b', 'leave', 'Name', 'a'], + ['break-b', 'enter', 'SelectionSet', null], + ['break-b', 'enter', 'Field', null], + ['break-b', 'enter', 'Name', 'y'], + ['break-b', 'leave', 'Name', 'y'], + ['break-b', 'leave', 'Field', null], + ['break-b', 'leave', 'SelectionSet', null], + ['break-b', 'leave', 'Field', null], + ['break-b', 'enter', 'Field', null], + ['break-b', 'enter', 'Name', 'b'], + ], + $visited + ); } public function testAllowsEarlyExitWhileLeaving2() : void @@ -1025,38 +1093,44 @@ class VisitorTest extends ValidatorTestCase $visited = []; $ast = Parser::parse('{ a, b { x }, c }'); - Visitor::visit($ast, Visitor::visitInParallel([ [ - 'enter' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['enter', $node->kind, $node->value ?? null]; - }, - 'leave' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $value = $node->value ?? null; - $visited[] = ['leave', $node->kind, $value]; - if ($node->kind === 'Name' && $value === 'x') { - return Visitor::stop(); - } - }, - ], - ])); + Visitor::visit( + $ast, + Visitor::visitInParallel([[ + 'enter' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['enter', $node->kind, $node->value ?? null]; + }, + 'leave' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $value = $node->value ?? null; + $visited[] = ['leave', $node->kind, $value]; + if ($node->kind === 'Name' && $value === 'x') { + return Visitor::stop(); + } + }, + ], + ]) + ); - $this->assertEquals([ - [ 'enter', 'Document', null ], - [ 'enter', 'OperationDefinition', null ], - [ 'enter', 'SelectionSet', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'a' ], - [ 'leave', 'Name', 'a' ], - [ 'leave', 'Field', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'b' ], - [ 'leave', 'Name', 'b' ], - [ 'enter', 'SelectionSet', null ], - [ 'enter', 'Field', null ], - [ 'enter', 'Name', 'x' ], - [ 'leave', 'Name', 'x' ], - ], $visited); + $this->assertEquals( + [ + ['enter', 'Document', null], + ['enter', 'OperationDefinition', null], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'a'], + ['leave', 'Name', 'a'], + ['leave', 'Field', null], + ['enter', 'Field', null], + ['enter', 'Name', 'b'], + ['leave', 'Name', 'b'], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'x'], + ['leave', 'Name', 'x'], + ], + $visited + ); } public function testAllowsEarlyExitFromLeavingDifferentPoints() : void @@ -1064,73 +1138,79 @@ class VisitorTest extends ValidatorTestCase $visited = []; $ast = Parser::parse('{ a { y }, b { x } }'); - Visitor::visit($ast, Visitor::visitInParallel([ - [ - 'enter' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['break-a', 'enter', $node->kind, $node->value ?? null]; - }, - 'leave' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $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) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['break-b', 'enter', $node->kind, $node->value ?? null]; - }, - 'leave' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null]; - if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { - return Visitor::stop(); - } - }, - ], - ])); + Visitor::visit( + $ast, + Visitor::visitInParallel([ + [ + 'enter' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['break-a', 'enter', $node->kind, $node->value ?? null]; + }, + 'leave' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $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) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['break-b', 'enter', $node->kind, $node->value ?? null]; + }, + 'leave' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null]; + if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { + return Visitor::stop(); + } + }, + ], + ]) + ); - $this->assertEquals([ - [ 'break-a', 'enter', 'Document', null ], - [ 'break-b', 'enter', 'Document', null ], - [ 'break-a', 'enter', 'OperationDefinition', null ], - [ 'break-b', 'enter', 'OperationDefinition', null ], - [ 'break-a', 'enter', 'SelectionSet', null ], - [ 'break-b', 'enter', 'SelectionSet', null ], - [ 'break-a', 'enter', 'Field', null ], - [ 'break-b', 'enter', 'Field', null ], - [ 'break-a', 'enter', 'Name', 'a' ], - [ 'break-b', 'enter', 'Name', 'a' ], - [ 'break-a', 'leave', 'Name', 'a' ], - [ 'break-b', 'leave', 'Name', 'a' ], - [ 'break-a', 'enter', 'SelectionSet', null ], - [ 'break-b', 'enter', 'SelectionSet', null ], - [ 'break-a', 'enter', 'Field', null ], - [ 'break-b', 'enter', 'Field', null ], - [ 'break-a', 'enter', 'Name', 'y' ], - [ 'break-b', 'enter', 'Name', 'y' ], - [ 'break-a', 'leave', 'Name', 'y' ], - [ 'break-b', 'leave', 'Name', 'y' ], - [ 'break-a', 'leave', 'Field', null ], - [ 'break-b', 'leave', 'Field', null ], - [ 'break-a', 'leave', 'SelectionSet', null ], - [ 'break-b', 'leave', 'SelectionSet', null ], - [ 'break-a', 'leave', 'Field', null ], - [ 'break-b', 'leave', 'Field', null ], - [ 'break-b', 'enter', 'Field', null ], - [ 'break-b', 'enter', 'Name', 'b' ], - [ 'break-b', 'leave', 'Name', 'b' ], - [ 'break-b', 'enter', 'SelectionSet', null ], - [ 'break-b', 'enter', 'Field', null ], - [ 'break-b', 'enter', 'Name', 'x' ], - [ 'break-b', 'leave', 'Name', 'x' ], - [ 'break-b', 'leave', 'Field', null ], - [ 'break-b', 'leave', 'SelectionSet', null ], - [ 'break-b', 'leave', 'Field', null ], - ], $visited); + $this->assertEquals( + [ + ['break-a', 'enter', 'Document', null], + ['break-b', 'enter', 'Document', null], + ['break-a', 'enter', 'OperationDefinition', null], + ['break-b', 'enter', 'OperationDefinition', null], + ['break-a', 'enter', 'SelectionSet', null], + ['break-b', 'enter', 'SelectionSet', null], + ['break-a', 'enter', 'Field', null], + ['break-b', 'enter', 'Field', null], + ['break-a', 'enter', 'Name', 'a'], + ['break-b', 'enter', 'Name', 'a'], + ['break-a', 'leave', 'Name', 'a'], + ['break-b', 'leave', 'Name', 'a'], + ['break-a', 'enter', 'SelectionSet', null], + ['break-b', 'enter', 'SelectionSet', null], + ['break-a', 'enter', 'Field', null], + ['break-b', 'enter', 'Field', null], + ['break-a', 'enter', 'Name', 'y'], + ['break-b', 'enter', 'Name', 'y'], + ['break-a', 'leave', 'Name', 'y'], + ['break-b', 'leave', 'Name', 'y'], + ['break-a', 'leave', 'Field', null], + ['break-b', 'leave', 'Field', null], + ['break-a', 'leave', 'SelectionSet', null], + ['break-b', 'leave', 'SelectionSet', null], + ['break-a', 'leave', 'Field', null], + ['break-b', 'leave', 'Field', null], + ['break-b', 'enter', 'Field', null], + ['break-b', 'enter', 'Name', 'b'], + ['break-b', 'leave', 'Name', 'b'], + ['break-b', 'enter', 'SelectionSet', null], + ['break-b', 'enter', 'Field', null], + ['break-b', 'enter', 'Name', 'x'], + ['break-b', 'leave', 'Name', 'x'], + ['break-b', 'leave', 'Field', null], + ['break-b', 'leave', 'SelectionSet', null], + ['break-b', 'leave', 'Field', null], + ], + $visited + ); } public function testAllowsForEditingOnEnter2() : void @@ -1138,26 +1218,29 @@ class VisitorTest extends ValidatorTestCase $visited = []; $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); - $editedAst = Visitor::visit($ast, Visitor::visitInParallel([ - [ - 'enter' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - 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, $node->value ?? null]; - }, - 'leave' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args(), true); - $visited[] = ['leave', $node->kind, $node->value ?? null]; - }, - ], - ])); + $editedAst = Visitor::visit( + $ast, + Visitor::visitInParallel([ + [ + 'enter' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + 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, $node->value ?? null]; + }, + 'leave' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args(), true); + $visited[] = ['leave', $node->kind, $node->value ?? null]; + }, + ], + ]) + ); $this->assertEquals( Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]), @@ -1169,7 +1252,8 @@ class VisitorTest extends ValidatorTestCase $editedAst ); - $this->assertEquals([ + $this->assertEquals( + [ ['enter', 'Document', null], ['enter', 'OperationDefinition', null], ['enter', 'SelectionSet', null], @@ -1194,7 +1278,9 @@ class VisitorTest extends ValidatorTestCase ['leave', 'SelectionSet', null], ['leave', 'OperationDefinition', null], ['leave', 'Document', null], - ], $visited); + ], + $visited + ); } public function testAllowsForEditingOnLeave2() : void @@ -1202,26 +1288,29 @@ class VisitorTest extends ValidatorTestCase $visited = []; $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); - $editedAst = Visitor::visit($ast, Visitor::visitInParallel([ - [ - 'leave' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args(), true); - 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, $node->value ?? null]; - }, - 'leave' => function ($node) use (&$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args(), true); - $visited[] = ['leave', $node->kind, $node->value ?? null]; - }, - ], - ])); + $editedAst = Visitor::visit( + $ast, + Visitor::visitInParallel([ + [ + 'leave' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args(), true); + 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, $node->value ?? null]; + }, + 'leave' => function ($node) use (&$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args(), true); + $visited[] = ['leave', $node->kind, $node->value ?? null]; + }, + ], + ]) + ); $this->assertEquals( Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]), @@ -1233,7 +1322,8 @@ class VisitorTest extends ValidatorTestCase $editedAst ); - $this->assertEquals([ + $this->assertEquals( + [ ['enter', 'Document', null], ['enter', 'OperationDefinition', null], ['enter', 'SelectionSet', null], @@ -1264,10 +1354,11 @@ class VisitorTest extends ValidatorTestCase ['leave', 'SelectionSet', null], ['leave', 'OperationDefinition', null], ['leave', 'Document', null], - ], $visited); + ], + $visited + ); } - /** * Describe: visitWithTypeInfo */ @@ -1278,38 +1369,45 @@ class VisitorTest extends ValidatorTestCase $typeInfo = new TypeInfo(ValidatorTestCase::getTestSchema()); $ast = Parser::parse('{ human(id: 4) { name, pets { ... { name } }, unknown } }'); - Visitor::visit($ast, Visitor::visitWithTypeInfo($typeInfo, [ - 'enter' => function ($node) use ($typeInfo, &$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $parentType = $typeInfo->getParentType(); - $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, - ]; - }, - 'leave' => function ($node) use ($typeInfo, &$visited, $ast) { - $this->checkVisitorFnArgs($ast, func_get_args()); - $parentType = $typeInfo->getParentType(); - $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, - ]; - }, - ])); + Visitor::visit( + $ast, + Visitor::visitWithTypeInfo( + $typeInfo, + [ + 'enter' => function ($node) use ($typeInfo, &$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $parentType = $typeInfo->getParentType(); + $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, + ]; + }, + 'leave' => function ($node) use ($typeInfo, &$visited, $ast) { + $this->checkVisitorFnArgs($ast, func_get_args()); + $parentType = $typeInfo->getParentType(); + $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, + ]; + }, + ] + ) + ); - $this->assertEquals([ + $this->assertEquals( + [ ['enter', 'Document', null, null, null, null], ['enter', 'OperationDefinition', null, null, 'QueryRoot', null], ['enter', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null], @@ -1350,7 +1448,9 @@ class VisitorTest extends ValidatorTestCase ['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null], ['leave', 'OperationDefinition', null, null, 'QueryRoot', null], ['leave', 'Document', null, null, null, null], - ], $visited); + ], + $visited + ); } public function testMaintainsTypeInfoDuringEdit() : void @@ -1361,66 +1461,79 @@ class VisitorTest extends ValidatorTestCase $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[] = [ - 'enter', - $node->kind, - $node->kind === 'Name' ? $node->value : null, - $parentType ? (string) $parentType : null, - $type ? (string) $type : null, - $inputType ? (string) $inputType : null, - ]; + $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[] = [ + 'enter', + $node->kind, + $node->kind === 'Name' ? $node->value : 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 && - Type::isCompositeType(Type::getNamedType($type)) - ) { - return new FieldNode([ - 'alias' => $node->alias, - 'name' => $node->name, - 'arguments' => $node->arguments, - 'directives' => $node->directives, - 'selectionSet' => new SelectionSetNode([ - 'kind' => 'SelectionSet', - 'selections' => [new FieldNode([ - 'name' => new NameNode(['value' => '__typename']), + // Make a query valid by adding missing selection sets. + if ($node->kind === 'Field' && + ! $node->selectionSet && + Type::isCompositeType(Type::getNamedType($type)) + ) { + return new FieldNode([ + 'alias' => $node->alias, + 'name' => $node->name, + 'arguments' => $node->arguments, + 'directives' => $node->directives, + 'selectionSet' => new SelectionSetNode([ + 'kind' => 'SelectionSet', + '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[] = [ - 'leave', - $node->kind, - $node->kind === 'Name' ? $node->value : 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(), true); + $parentType = $typeInfo->getParentType(); + $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, + ]; + }, + ] + ) + ); - $this->assertEquals(Printer::doPrint(Parser::parse( - '{ human(id: 4) { name, pets }, alien }' - )), Printer::doPrint($ast)); + $this->assertEquals( + Printer::doPrint(Parser::parse( + '{ human(id: 4) { name, pets }, alien }' + )), + Printer::doPrint($ast) + ); - $this->assertEquals(Printer::doPrint(Parser::parse( - '{ human(id: 4) { name, pets { __typename } }, alien { __typename } }' - )), Printer::doPrint($editedAst)); + $this->assertEquals( + Printer::doPrint(Parser::parse( + '{ human(id: 4) { name, pets { __typename } }, alien { __typename } }' + )), + Printer::doPrint($editedAst) + ); - $this->assertEquals([ + $this->assertEquals( + [ ['enter', 'Document', null, null, null, null], ['enter', 'OperationDefinition', null, null, 'QueryRoot', null], ['enter', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null], @@ -1463,6 +1576,8 @@ class VisitorTest extends ValidatorTestCase ['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null], ['leave', 'OperationDefinition', null, null, 'QueryRoot', null], ['leave', 'Document', null, null, null, null], - ], $visited); + ], + $visited + ); } }