Merge pull request #339 from simPod/cs-language-test

Fix CS in test/Language
This commit is contained in:
Vladimir Razuvaev 2018-09-02 21:22:39 +07:00 committed by GitHub
commit a7af4663b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 2157 additions and 1836 deletions

View File

@ -1,13 +1,18 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Language; namespace GraphQL\Tests\Language;
use GraphQL\Error\SyntaxError;
use GraphQL\Language\Lexer; use GraphQL\Language\Lexer;
use GraphQL\Language\Source; use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation; use GraphQL\Language\SourceLocation;
use GraphQL\Language\Token; use GraphQL\Language\Token;
use GraphQL\Error\SyntaxError;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function count;
use function json_decode;
class LexerTest extends TestCase class LexerTest extends TestCase
{ {
@ -23,17 +28,45 @@ class LexerTest extends TestCase
); );
} }
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;
}
}
/**
* @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') * @see it('accepts BOM header')
*/ */
public function testAcceptsBomHeader() : void public function testAcceptsBomHeader() : void
{ {
$bom = Utils::chr(0xFEFF); $bom = Utils::chr(0xFEFF);
$expected = [ $expected = [
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 2, 'start' => 2,
'end' => 5, 'end' => 5,
'value' => 'foo' 'value' => 'foo',
]; ];
$this->assertArraySubset($expected, (array) $this->lexOne($bom . ' foo')); $this->assertArraySubset($expected, (array) $this->lexOne($bom . ' foo'));
@ -45,12 +78,12 @@ class LexerTest extends TestCase
public function testRecordsLineAndColumn() : void public function testRecordsLineAndColumn() : void
{ {
$expected = [ $expected = [
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 8, 'start' => 8,
'end' => 11, 'end' => 11,
'line' => 4, 'line' => 4,
'column' => 3, 'column' => 3,
'value' => 'foo' 'value' => 'foo',
]; ];
$this->assertArraySubset($expected, (array) $this->lexOne("\n \r\n \r foo\n")); $this->assertArraySubset($expected, (array) $this->lexOne("\n \r\n \r foo\n"));
} }
@ -67,10 +100,10 @@ class LexerTest extends TestCase
'; ';
$expected = [ $expected = [
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 6, 'start' => 6,
'end' => 9, 'end' => 9,
'value' => 'foo' 'value' => 'foo',
]; ];
$this->assertArraySubset($expected, (array) $this->lexOne($example1)); $this->assertArraySubset($expected, (array) $this->lexOne($example1));
@ -80,18 +113,18 @@ class LexerTest extends TestCase
'; ';
$expected = [ $expected = [
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 18, 'start' => 18,
'end' => 21, 'end' => 21,
'value' => 'foo' 'value' => 'foo',
]; ];
$this->assertArraySubset($expected, (array) $this->lexOne($example2)); $this->assertArraySubset($expected, (array) $this->lexOne($example2));
$expected = [ $expected = [
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 3, 'start' => 3,
'end' => 6, 'end' => 6,
'value' => 'foo' 'value' => 'foo',
]; ];
$example3 = ',,,foo,,,'; $example3 = ',,,foo,,,';
@ -131,7 +164,7 @@ class LexerTest extends TestCase
*/ */
public function testUpdatesLineNumbersInErrorForFileContext() : void public function testUpdatesLineNumbersInErrorForFileContext() : void
{ {
$str = '' . $str = '' .
"\n" . "\n" .
"\n" . "\n" .
" ?\n" . " ?\n" .
@ -181,63 +214,86 @@ class LexerTest extends TestCase
*/ */
public function testLexesStrings() : void public function testLexesStrings() : void
{ {
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 8, 'start' => 0,
'value' => 'simple' 'end' => 8,
], (array) $this->lexOne('"simple"')); '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([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 15, 'start' => 0,
'value' => ' white space ' 'end' => 10,
], (array) $this->lexOne('" white space "')); 'value' => 'quote "',
],
(array) $this->lexOne('"quote \\""')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 10, 'start' => 0,
'value' => 'quote "' 'end' => 25,
], (array) $this->lexOne('"quote \\""')); 'value' => 'escaped \n\r\b\t\f',
],
(array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 25, 'start' => 0,
'value' => 'escaped \n\r\b\t\f' 'end' => 16,
], (array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"')); 'value' => 'slashes \\ \/',
],
(array) $this->lexOne('"slashes \\\\ \\\\/"')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 16, 'start' => 0,
'value' => 'slashes \\ \/' 'end' => 13,
], (array) $this->lexOne('"slashes \\\\ \\\\/"')); 'value' => 'unicode яуц',
],
$this->assertArraySubset([ (array) $this->lexOne('"unicode яуц"')
'kind' => Token::STRING, );
'start' => 0,
'end' => 13,
'value' => 'unicode яуц'
], (array) $this->lexOne('"unicode яуц"'));
$unicode = json_decode('"\u1234\u5678\u90AB\uCDEF"'); $unicode = json_decode('"\u1234\u5678\u90AB\uCDEF"');
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 34, 'start' => 0,
'value' => 'unicode ' . $unicode 'end' => 34,
], (array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"')); 'value' => 'unicode ' . $unicode,
],
(array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::STRING, [
'start' => 0, 'kind' => Token::STRING,
'end' => 26, 'start' => 0,
'value' => $unicode 'end' => 26,
], (array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"')); 'value' => $unicode,
],
(array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"')
);
} }
/** /**
@ -245,86 +301,128 @@ class LexerTest extends TestCase
*/ */
public function testLexesBlockString() : void public function testLexesBlockString() : void
{ {
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 12, 'start' => 0,
'value' => 'simple' 'end' => 12,
], (array) $this->lexOne('"""simple"""')); 'value' => 'simple',
],
(array) $this->lexOne('"""simple"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 19, 'start' => 0,
'value' => ' white space ' 'end' => 19,
], (array) $this->lexOne('""" white space """')); 'value' => ' white space ',
],
(array) $this->lexOne('""" white space """')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 22, 'start' => 0,
'value' => 'contains " quote' 'end' => 22,
], (array) $this->lexOne('"""contains " quote"""')); 'value' => 'contains " quote',
],
(array) $this->lexOne('"""contains " quote"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 31, 'start' => 0,
'value' => 'contains """ triplequote' 'end' => 31,
], (array) $this->lexOne('"""contains \\""" triplequote"""')); 'value' => 'contains """ triplequote',
],
(array) $this->lexOne('"""contains \\""" triplequote"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 16, 'start' => 0,
'value' => "multi\nline" 'end' => 16,
], (array) $this->lexOne("\"\"\"multi\nline\"\"\"")); 'value' => "multi\nline",
],
(array) $this->lexOne("\"\"\"multi\nline\"\"\"")
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 28, 'start' => 0,
'value' => "multi\nline\nnormalized" 'end' => 28,
], (array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\"")); 'value' => "multi\nline\nnormalized",
],
(array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\"")
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 32, 'start' => 0,
'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234' 'end' => 32,
], (array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""')); 'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234',
],
(array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 19, 'start' => 0,
'value' => 'slashes \\\\ \\/' 'end' => 19,
], (array) $this->lexOne('"""slashes \\\\ \\/"""')); 'value' => 'slashes \\\\ \\/',
],
(array) $this->lexOne('"""slashes \\\\ \\/"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
'kind' => Token::BLOCK_STRING, [
'start' => 0, 'kind' => Token::BLOCK_STRING,
'end' => 68, 'start' => 0,
'value' => "spans\n multiple\n lines" 'end' => 68,
], (array) $this->lexOne("\"\"\" 'value' => "spans\n multiple\n lines",
],
(array) $this->lexOne('"""
spans spans
multiple multiple
lines lines
\"\"\"")); """')
);
} }
public function reportsUsefulStringErrors() { public function reportsUsefulStringErrors()
{
return [ return [
['"', "Unterminated string.", $this->loc(1, 2)], ['"', 'Unterminated string.', $this->loc(1, 2)],
['"no end quote', "Unterminated string.", $this->loc(1, 14)], ['"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)], "'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)], ['"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' . "\n" . 'line"', 'Unterminated string.', $this->loc(1, 7)],
['"multi' . "\r" . '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 \\z esc"', 'Invalid character escape sequence: \\z', $this->loc(1, 7)],
['"bad \\x esc"', "Invalid character escape sequence: \\x", $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 \\u1 esc"', "Invalid character escape sequence: \\u1 es", $this->loc(1, 7)],
['"bad \\u0XX1 esc"', "Invalid character escape sequence: \\u0XX1", $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 * @dataProvider reportsUsefulStringErrors
* @see it('lex reports useful string errors') * @see it('lex reports useful string errors')
*/ */
public function testLexReportsUsefulStringErrors($str, $expectedMessage, $location) : void public function testLexReportsUsefulStringErrors($str, $expectedMessage, $location) : void
{ {
$this->expectSyntaxError($str, $expectedMessage, $location); $this->expectSyntaxError($str, $expectedMessage, $location);
} }
public function reportsUsefulBlockStringErrors() { public function reportsUsefulBlockStringErrors()
{
return [ return [
['"""', "Unterminated string.", $this->loc(1, 4)], ['"""', 'Unterminated string.', $this->loc(1, 4)],
['"""no end quote', "Unterminated string.", $this->loc(1, 16)], ['"""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)], '"""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 * @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 public function testReportsUsefulBlockStringErrors($str, $expectedMessage, $location) : void
{ {
@ -435,21 +548,21 @@ class LexerTest extends TestCase
public function reportsUsefulNumberErrors() public function reportsUsefulNumberErrors()
{ {
return [ return [
[ '00', "Invalid number, unexpected digit after 0: \"0\"", $this->loc(1, 2)], ['00', 'Invalid number, unexpected digit after 0: "0"', $this->loc(1, 2)],
[ '+1', "Cannot parse the unexpected character \"+\".", $this->loc(1, 1)], ['+1', 'Cannot parse the unexpected character "+".', $this->loc(1, 1)],
[ '1.', "Invalid number, expected digit but got: <EOF>", $this->loc(1, 3)], ['1.', 'Invalid number, expected digit but got: <EOF>', $this->loc(1, 3)],
[ '1.e1', "Invalid number, expected digit but got: \"e\"", $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)], ['.123', 'Cannot parse the unexpected character ".".', $this->loc(1, 1)],
[ '1.A', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 3)], ['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)], ['-A', 'Invalid number, expected digit but got: "A"', $this->loc(1, 2)],
[ '1.0e', "Invalid number, expected digit but got: <EOF>", $this->loc(1, 5)], ['1.0e', 'Invalid number, expected digit but got: <EOF>', $this->loc(1, 5)],
[ '1.0eA', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 5)], ['1.0eA', 'Invalid number, expected digit but got: "A"', $this->loc(1, 5)],
]; ];
} }
/** /**
* @dataProvider reportsUsefulNumberErrors * @dataProvider reportsUsefulNumberErrors
* @see it('lex reports useful number errors') * @see it('lex reports useful number errors')
*/ */
public function testReportsUsefulNumberErrors($str, $expectedMessage, $location) : void public function testReportsUsefulNumberErrors($str, $expectedMessage, $location) : void
{ {
@ -521,8 +634,8 @@ class LexerTest extends TestCase
$unicode2 = json_decode('"\u200b"'); $unicode2 = json_decode('"\u200b"');
return [ 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)], [$unicode1, "Cannot parse the unexpected character \"\\u203b\".", $this->loc(1, 1)],
[$unicode2, "Cannot parse the unexpected character \"\\u200b\".", $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 * @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 public function testReportsUsefulUnknownCharErrors($str, $expectedMessage, $location) : void
{ {
@ -542,17 +655,20 @@ class LexerTest extends TestCase
*/ */
public function testReportsUsefulDashesInfo() : void public function testReportsUsefulDashesInfo() : void
{ {
$q = 'a-b'; $q = 'a-b';
$lexer = new Lexer(new Source($q)); $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->expectException(SyntaxError::class);
$this->expectExceptionMessage('Syntax Error: Invalid number, expected digit but got: "b"'); $this->expectExceptionMessage('Syntax Error: Invalid number, expected digit but got: "b"');
try { try {
$lexer->advance(); $lexer->advance();
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch(SyntaxError $error) { } catch (SyntaxError $error) {
$this->assertEquals([$this->loc(1,3)], $error->getLocations()); $this->assertEquals([$this->loc(1, 3)], $error->getLocations());
throw $error; throw $error;
} }
} }
@ -580,49 +696,28 @@ class LexerTest extends TestCase
$tokens = []; $tokens = [];
for ($tok = $startToken; $tok; $tok = $tok->next) { for ($tok = $startToken; $tok; $tok = $tok->next) {
if (!empty($tokens)) { if (! empty($tokens)) {
// Tokens are double-linked, prev should point to last seen token. // Tokens are double-linked, prev should point to last seen token.
$this->assertSame($tokens[count($tokens) - 1], $tok->prev); $this->assertSame($tokens[count($tokens) - 1], $tok->prev);
} }
$tokens[] = $tok; $tokens[] = $tok;
} }
$this->assertEquals([ $this->assertEquals(
'<SOF>', [
'{', '<SOF>',
'Comment', '{',
'Name', 'Comment',
'}', 'Name',
'<EOF>' '}',
], Utils::map($tokens, function ($tok) { '<EOF>',
return $tok->kind; ],
})); 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;
}
} }
} }

View File

@ -1,7 +1,11 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Language; namespace GraphQL\Tests\Language;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Error\SyntaxError;
use GraphQL\Language\AST\ArgumentNode; use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NameNode; use GraphQL\Language\AST\NameNode;
@ -13,9 +17,10 @@ use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\Source; use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation; use GraphQL\Language\SourceLocation;
use GraphQL\Error\SyntaxError;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function file_get_contents;
use function sprintf;
class ParserTest extends TestCase class ParserTest extends TestCase
{ {
@ -43,22 +48,40 @@ class ParserTest extends TestCase
public function parseProvidesUsefulErrors() public function parseProvidesUsefulErrors()
{ {
return [ return [
['{', "Syntax Error: Expected Name, found <EOF>", "Syntax Error: Expected Name, found <EOF>\n\nGraphQL request (1:2)\n1: {\n ^\n", [1], [new SourceLocation(1, 2)]], [
['{ ...MissingOn } '{',
'Syntax Error: Expected Name, found <EOF>',
"Syntax Error: Expected Name, found <EOF>\n\nGraphQL request (1:2)\n1: {\n ^\n",
[1],
[new SourceLocation(
1,
2
),
],
],
[
'{ ...MissingOn }
fragment MissingOn Type 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",], ', 'Syntax Error: Expected "on", found Name "Type"',
['{ field: {} }', "Syntax Error: Expected Name, found {", "Syntax Error: Expected Name, found {\n\nGraphQL request (1:10)\n1: { field: {} }\n ^\n"], "Syntax Error: Expected \"on\", found Name \"Type\"\n\nGraphQL request (2:20)\n1: { ...MissingOn }\n2: fragment MissingOn Type\n ^\n3: \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"], ['{ 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 * @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 { try {
Parser::parse($str); Parser::parse($str);
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
@ -110,10 +133,27 @@ fragment MissingOn Type
$this->expectSyntaxError( $this->expectSyntaxError(
'query Foo($x: Complex = { a: { b: [ $var ] } }) { field }', 'query Foo($x: Complex = { a: { b: [ $var ] } }) { field }',
'Unexpected $', '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"') * @see it('does not accept fragments spread of "on"')
*/ */
@ -122,7 +162,7 @@ fragment MissingOn Type
$this->expectSyntaxError( $this->expectSyntaxError(
'fragment on on on { on }', 'fragment on on on { on }',
'Unexpected Name "on"', 'Unexpected Name "on"',
$this->loc(1,10) $this->loc(1, 10)
); );
} }
@ -134,7 +174,7 @@ fragment MissingOn Type
$this->expectSyntaxError( $this->expectSyntaxError(
'{ ...on }', '{ ...on }',
'Expected Name, found }', '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. // Note: \u0A0A could be naively interpretted as two line-feed chars.
$char = Utils::chr(0x0A0A); $char = Utils::chr(0x0A0A);
$query = <<<HEREDOC $query = <<<HEREDOC
# This comment has a $char multi-byte character. # This comment has a $char multi-byte character.
{ field(arg: "Has a $char multi-byte character.") } { field(arg: "Has a $char multi-byte character.") }
@ -156,18 +196,18 @@ HEREDOC;
$expected = new SelectionSetNode([ $expected = new SelectionSetNode([
'selections' => new NodeList([ 'selections' => new NodeList([
new FieldNode([ new FieldNode([
'name' => new NameNode(['value' => 'field']), 'name' => new NameNode(['value' => 'field']),
'arguments' => new NodeList([ 'arguments' => new NodeList([
new ArgumentNode([ new ArgumentNode([
'name' => new NameNode(['value' => 'arg']), 'name' => new NameNode(['value' => 'arg']),
'value' => new StringValueNode([ 'value' => new StringValueNode(
'value' => "Has a $char multi-byte character." ['value' => sprintf('Has a %s multi-byte character.', $char)]
]) ),
]) ]),
]), ]),
'directives' => new NodeList([]) 'directives' => new NodeList([]),
]) ]),
]) ]),
]); ]);
$this->assertEquals($expected, $result->definitions[0]->selectionSet); $this->assertEquals($expected, $result->definitions[0]->selectionSet);
@ -180,7 +220,7 @@ HEREDOC;
{ {
// Following should not throw: // Following should not throw:
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$result = Parser::parse($kitchenSink); $result = Parser::parse($kitchenSink);
$this->assertNotEmpty($result); $this->assertNotEmpty($result);
} }
@ -196,7 +236,7 @@ HEREDOC;
'mutation', 'mutation',
'subscription', 'subscription',
'true', 'true',
'false' 'false',
]; ];
foreach ($nonKeywords as $keyword) { foreach ($nonKeywords as $keyword) {
$fragmentName = $keyword; $fragmentName = $keyword;
@ -205,14 +245,19 @@ HEREDOC;
} }
// Expected not to throw: // Expected not to throw:
$result = Parser::parse("query $keyword { $result = Parser::parse(<<<GRAPHQL
... $fragmentName query $keyword {
... on $keyword { field } ... $fragmentName
... on $keyword { field }
}
fragment $fragmentName on Type {
$keyword($keyword: \$$keyword) @$keyword($keyword: $keyword)
} }
fragment $fragmentName on Type { fragment $fragmentName on Type {
$keyword($keyword: \$$keyword) @$keyword($keyword: $keyword) $keyword($keyword: \$$keyword) @$keyword($keyword: $keyword)
} }
"); GRAPHQL
);
$this->assertNotEmpty($result); $this->assertNotEmpty($result);
} }
} }
@ -286,94 +331,102 @@ fragment $fragmentName on Type {
'); ');
$result = Parser::parse($source); $result = Parser::parse($source);
$loc = function($start, $end) use ($source) { $loc = function ($start, $end) use ($source) {
return [ return [
'start' => $start, 'start' => $start,
'end' => $end 'end' => $end,
]; ];
}; };
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
'loc' => $loc(0, 41), 'loc' => $loc(0, 41),
'definitions' => [ 'definitions' => [
[ [
'kind' => NodeKind::OPERATION_DEFINITION, 'kind' => NodeKind::OPERATION_DEFINITION,
'loc' => $loc(0, 40), 'loc' => $loc(0, 40),
'operation' => 'query', 'operation' => 'query',
'name' => null, 'name' => null,
'variableDefinitions' => [], 'variableDefinitions' => [],
'directives' => [], 'directives' => [],
'selectionSet' => [ 'selectionSet' => [
'kind' => NodeKind::SELECTION_SET, 'kind' => NodeKind::SELECTION_SET,
'loc' => $loc(0, 40), 'loc' => $loc(0, 40),
'selections' => [ 'selections' => [
[ [
'kind' => NodeKind::FIELD, 'kind' => NodeKind::FIELD,
'loc' => $loc(4, 38), 'loc' => $loc(4, 38),
'alias' => null, 'alias' => null,
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => $loc(4, 8), 'loc' => $loc(4, 8),
'value' => 'node' 'value' => 'node',
], ],
'arguments' => [ 'arguments' => [
[ [
'kind' => NodeKind::ARGUMENT, 'kind' => NodeKind::ARGUMENT,
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => $loc(9, 11), 'loc' => $loc(9, 11),
'value' => 'id' 'value' => 'id',
], ],
'value' => [ 'value' => [
'kind' => NodeKind::INT, 'kind' => NodeKind::INT,
'loc' => $loc(13, 14), 'loc' => $loc(13, 14),
'value' => '4' 'value' => '4',
], ],
'loc' => $loc(9, 14, $source) 'loc' => $loc(9, 14, $source),
] ],
], ],
'directives' => [], 'directives' => [],
'selectionSet' => [ 'selectionSet' => [
'kind' => NodeKind::SELECTION_SET, 'kind' => NodeKind::SELECTION_SET,
'loc' => $loc(16, 38), 'loc' => $loc(16, 38),
'selections' => [ 'selections' => [
[ [
'kind' => NodeKind::FIELD, 'kind' => NodeKind::FIELD,
'loc' => $loc(22, 24), 'loc' => $loc(22, 24),
'alias' => null, 'alias' => null,
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => $loc(22, 24), 'loc' => $loc(22, 24),
'value' => 'id' 'value' => 'id',
], ],
'arguments' => [], 'arguments' => [],
'directives' => [], 'directives' => [],
'selectionSet' => null 'selectionSet' => null,
], ],
[ [
'kind' => NodeKind::FIELD, 'kind' => NodeKind::FIELD,
'loc' => $loc(30, 34), 'loc' => $loc(30, 34),
'alias' => null, 'alias' => null,
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => $loc(30, 34), 'loc' => $loc(30, 34),
'value' => 'name' 'value' => 'name',
], ],
'arguments' => [], 'arguments' => [],
'directives' => [], 'directives' => [],
'selectionSet' => null '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); $result = Parser::parse($source);
$loc = function($start, $end) use ($source) { $loc = function ($start, $end) use ($source) {
return [ return [
'start' => $start, 'start' => $start,
'end' => $end 'end' => $end,
]; ];
}; };
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
'loc' => $loc(0, 30), 'loc' => $loc(0, 30),
'definitions' => [ 'definitions' => [
[ [
'kind' => NodeKind::OPERATION_DEFINITION, 'kind' => NodeKind::OPERATION_DEFINITION,
'loc' => $loc(0, 29), 'loc' => $loc(0, 29),
'operation' => 'query', 'operation' => 'query',
'name' => null, 'name' => null,
'variableDefinitions' => [], 'variableDefinitions' => [],
'directives' => [], 'directives' => [],
'selectionSet' => [ 'selectionSet' => [
'kind' => NodeKind::SELECTION_SET, 'kind' => NodeKind::SELECTION_SET,
'loc' => $loc(6, 29), 'loc' => $loc(6, 29),
'selections' => [ 'selections' => [
[ [
'kind' => NodeKind::FIELD, 'kind' => NodeKind::FIELD,
'loc' => $loc(10, 27), 'loc' => $loc(10, 27),
'alias' => null, 'alias' => null,
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => $loc(10, 14), 'loc' => $loc(10, 14),
'value' => 'node' 'value' => 'node',
], ],
'arguments' => [], 'arguments' => [],
'directives' => [], 'directives' => [],
'selectionSet' => [ 'selectionSet' => [
'kind' => NodeKind::SELECTION_SET, 'kind' => NodeKind::SELECTION_SET,
'loc' => $loc(15, 27), 'loc' => $loc(15, 27),
'selections' => [ 'selections' => [
[ [
'kind' => NodeKind::FIELD, 'kind' => NodeKind::FIELD,
'loc' => $loc(21, 23), 'loc' => $loc(21, 23),
'alias' => null, 'alias' => null,
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => $loc(21, 23), 'loc' => $loc(21, 23),
'value' => 'id' 'value' => 'id',
], ],
'arguments' => [], 'arguments' => [],
'directives' => [], 'directives' => [],
'selectionSet' => null 'selectionSet' => null,
] ],
] ],
] ],
] ],
] ],
] ],
] ],
] ],
]; ];
$this->assertEquals($expected, $this->nodeToArray($result)); $this->assertEquals($expected, $this->nodeToArray($result));
@ -475,6 +528,8 @@ fragment $fragmentName on Type {
Parser::parse($source); Parser::parse($source);
} }
// Describe: parseValue
/** /**
* @see it('contains location information that only stringifys start/end') * @see it('contains location information that only stringifys start/end')
*/ */
@ -495,6 +550,8 @@ fragment $fragmentName on Type {
$this->assertEquals($source, $result->loc->source); $this->assertEquals($source, $result->loc->source);
} }
// Describe: parseType
/** /**
* @see it('contains references to start and end tokens') * @see it('contains references to start and end tokens')
*/ */
@ -506,17 +563,18 @@ fragment $fragmentName on Type {
$this->assertEquals('<EOF>', $result->loc->endToken->kind); $this->assertEquals('<EOF>', $result->loc->endToken->kind);
} }
// Describe: parseValue
/** /**
* @see it('parses null value') * @see it('parses null value')
*/ */
public function testParsesNullValues() : void public function testParsesNullValues() : void
{ {
$this->assertEquals([ $this->assertEquals(
'kind' => NodeKind::NULL, [
'loc' => ['start' => 0, 'end' => 4] 'kind' => NodeKind::NULL,
], $this->nodeToArray(Parser::parseValue('null'))); 'loc' => ['start' => 0, 'end' => 4],
],
$this->nodeToArray(Parser::parseValue('null'))
);
} }
/** /**
@ -524,41 +582,45 @@ fragment $fragmentName on Type {
*/ */
public function testParsesListValues() : void public function testParsesListValues() : void
{ {
$this->assertEquals([ $this->assertEquals(
'kind' => NodeKind::LST, [
'loc' => ['start' => 0, 'end' => 11], 'kind' => NodeKind::LST,
'values' => [ 'loc' => ['start' => 0, 'end' => 11],
[ 'values' => [
'kind' => NodeKind::INT, [
'loc' => ['start' => 1, 'end' => 4], 'kind' => NodeKind::INT,
'value' => '123' 'loc' => ['start' => 1, 'end' => 4],
'value' => '123',
],
[
'kind' => NodeKind::STRING,
'loc' => ['start' => 5, 'end' => 10],
'value' => 'abc',
'block' => false,
],
], ],
[ ],
'kind' => NodeKind::STRING, $this->nodeToArray(Parser::parseValue('[123 "abc"]'))
'loc' => ['start' => 5, 'end' => 10], );
'value' => 'abc',
'block' => false
]
]
], $this->nodeToArray(Parser::parseValue('[123 "abc"]')));
} }
// Describe: parseType
/** /**
* @see it('parses well known types') * @see it('parses well known types')
*/ */
public function testParsesWellKnownTypes() : void public function testParsesWellKnownTypes() : void
{ {
$this->assertEquals([ $this->assertEquals(
'kind' => NodeKind::NAMED_TYPE, [
'loc' => ['start' => 0, 'end' => 6], 'kind' => NodeKind::NAMED_TYPE,
'name' => [ 'loc' => ['start' => 0, 'end' => 6],
'kind' => NodeKind::NAME, 'name' => [
'loc' => ['start' => 0, 'end' => 6], 'kind' => NodeKind::NAME,
'value' => 'String' 'loc' => ['start' => 0, 'end' => 6],
] 'value' => 'String',
], $this->nodeToArray(Parser::parseType('String'))); ],
],
$this->nodeToArray(Parser::parseType('String'))
);
} }
/** /**
@ -566,15 +628,18 @@ fragment $fragmentName on Type {
*/ */
public function testParsesCustomTypes() : void public function testParsesCustomTypes() : void
{ {
$this->assertEquals([ $this->assertEquals(
'kind' => NodeKind::NAMED_TYPE, [
'loc' => ['start' => 0, 'end' => 6], 'kind' => NodeKind::NAMED_TYPE,
'name' => [ 'loc' => ['start' => 0, 'end' => 6],
'kind' => NodeKind::NAME, 'name' => [
'loc' => ['start' => 0, 'end' => 6], 'kind' => NodeKind::NAME,
'value' => 'MyType' 'loc' => ['start' => 0, 'end' => 6],
] 'value' => 'MyType',
], $this->nodeToArray(Parser::parseType('MyType'))); ],
],
$this->nodeToArray(Parser::parseType('MyType'))
);
} }
/** /**
@ -582,19 +647,22 @@ fragment $fragmentName on Type {
*/ */
public function testParsesListTypes() : void public function testParsesListTypes() : void
{ {
$this->assertEquals([ $this->assertEquals(
'kind' => NodeKind::LIST_TYPE, [
'loc' => ['start' => 0, 'end' => 8], 'kind' => NodeKind::LIST_TYPE,
'type' => [ 'loc' => ['start' => 0, 'end' => 8],
'kind' => NodeKind::NAMED_TYPE, 'type' => [
'loc' => ['start' => 1, 'end' => 7], 'kind' => NodeKind::NAMED_TYPE,
'name' => [ 'loc' => ['start' => 1, 'end' => 7],
'kind' => NodeKind::NAME, 'name' => [
'loc' => ['start' => 1, 'end' => 7], 'kind' => NodeKind::NAME,
'value' => 'MyType' 'loc' => ['start' => 1, 'end' => 7],
] 'value' => 'MyType',
] ],
], $this->nodeToArray(Parser::parseType('[MyType]'))); ],
],
$this->nodeToArray(Parser::parseType('[MyType]'))
);
} }
/** /**
@ -602,19 +670,22 @@ fragment $fragmentName on Type {
*/ */
public function testParsesNonNullTypes() : void public function testParsesNonNullTypes() : void
{ {
$this->assertEquals([ $this->assertEquals(
'kind' => NodeKind::NON_NULL_TYPE, [
'loc' => ['start' => 0, 'end' => 7], 'kind' => NodeKind::NON_NULL_TYPE,
'type' => [ 'loc' => ['start' => 0, 'end' => 7],
'kind' => NodeKind::NAMED_TYPE, 'type' => [
'loc' => ['start' => 0, 'end' => 6], 'kind' => NodeKind::NAMED_TYPE,
'name' => [ 'loc' => ['start' => 0, 'end' => 6],
'kind' => NodeKind::NAME, 'name' => [
'loc' => ['start' => 0, 'end' => 6], 'kind' => NodeKind::NAME,
'value' => 'MyType' 'loc' => ['start' => 0, 'end' => 6],
] 'value' => 'MyType',
] ],
], $this->nodeToArray(Parser::parseType('MyType!'))); ],
],
$this->nodeToArray(Parser::parseType('MyType!'))
);
} }
/** /**
@ -622,48 +693,25 @@ fragment $fragmentName on Type {
*/ */
public function testParsesNestedTypes() : void public function testParsesNestedTypes() : void
{ {
$this->assertEquals([ $this->assertEquals(
'kind' => NodeKind::LIST_TYPE, [
'loc' => ['start' => 0, 'end' => 9], 'kind' => NodeKind::LIST_TYPE,
'type' => [ 'loc' => ['start' => 0, 'end' => 9],
'kind' => NodeKind::NON_NULL_TYPE,
'loc' => ['start' => 1, 'end' => 8],
'type' => [ 'type' => [
'kind' => NodeKind::NAMED_TYPE, 'kind' => NodeKind::NON_NULL_TYPE,
'loc' => ['start' => 1, 'end' => 7], 'loc' => ['start' => 1, 'end' => 8],
'name' => [ 'type' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 1, 'end' => 7], 'loc' => ['start' => 1, 'end' => 7],
'value' => 'MyType' 'name' => [
] 'kind' => NodeKind::NAME,
] 'loc' => ['start' => 1, 'end' => 7],
] 'value' => 'MyType',
], $this->nodeToArray(Parser::parseType('[MyType!]'))); ],
} ],
],
/** ],
* @param Node $node $this->nodeToArray(Parser::parseType('[MyType!]'))
* @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;
}
} }
} }

View File

@ -1,18 +1,15 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Language; namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NameNode; use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\Printer; use GraphQL\Language\Printer;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function file_get_contents;
class PrinterTest extends TestCase class PrinterTest extends TestCase
{ {
@ -22,7 +19,7 @@ class PrinterTest extends TestCase
public function testDoesntAlterAST() : void public function testDoesntAlterAST() : void
{ {
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink); $ast = Parser::parse($kitchenSink);
$astCopy = $ast->cloneDeep(); $astCopy = $ast->cloneDeep();
$this->assertEquals($astCopy, $ast); $this->assertEquals($astCopy, $ast);
@ -66,7 +63,7 @@ class PrinterTest extends TestCase
$this->assertEquals($expected, Printer::doPrint($queryAstShorthanded)); $this->assertEquals($expected, Printer::doPrint($queryAstShorthanded));
$mutationAst = Parser::parse('mutation { id, name }'); $mutationAst = Parser::parse('mutation { id, name }');
$expected = 'mutation { $expected = 'mutation {
id id
name name
} }
@ -76,7 +73,7 @@ class PrinterTest extends TestCase
$queryAstWithArtifacts = Parser::parse( $queryAstWithArtifacts = Parser::parse(
'query ($foo: TestType) @testDirective { id, name }' 'query ($foo: TestType) @testDirective { id, name }'
); );
$expected = 'query ($foo: TestType) @testDirective { $expected = 'query ($foo: TestType) @testDirective {
id id
name name
} }
@ -86,7 +83,7 @@ class PrinterTest extends TestCase
$mutationAstWithArtifacts = Parser::parse( $mutationAstWithArtifacts = Parser::parse(
'mutation ($foo: TestType) @testDirective { id, name }' 'mutation ($foo: TestType) @testDirective { id, name }'
); );
$expected = 'mutation ($foo: TestType) @testDirective { $expected = 'mutation ($foo: TestType) @testDirective {
id id
name name
} }
@ -100,13 +97,13 @@ class PrinterTest extends TestCase
public function testCorrectlyPrintsSingleLineBlockStringsWithLeadingSpace() : void public function testCorrectlyPrintsSingleLineBlockStringsWithLeadingSpace() : void
{ {
$mutationAstWithArtifacts = Parser::parse( $mutationAstWithArtifacts = Parser::parse(
'{ field(arg: """ space-led value""") }' '{ field(arg: """ space-led value""") }'
); );
$expected = '{ $expected = '{
field(arg: """ space-led value""") 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 indentation
""") """)
}' }'
); );
$expected = '{ $expected = '{
field(arg: """ field(arg: """
first first
line 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 = <<<END $expected = <<<END
{ {
field(arg: """ space-led value "quoted string" field(arg: """ space-led value "quoted string"
""") """)
@ -160,7 +157,8 @@ END;
*/ */
public function testExperimentalCorrectlyPrintsFragmentDefinedVariables() : void public function testExperimentalCorrectlyPrintsFragmentDefinedVariables() : void
{ {
$fragmentWithVariable = Parser::parse(' $fragmentWithVariable = Parser::parse(
'
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType { fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
id id
} }
@ -187,13 +185,13 @@ END;
field(arg: """ space-led value "quoted string" field(arg: """ space-led value "quoted string"
""") """)
}' }'
); );
$expected = '{ $expected = '{
field(arg: """ space-led value "quoted string" field(arg: """ space-led value "quoted string"
""") """)
} }
'; ';
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts)); $this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
} }
/** /**
@ -202,7 +200,7 @@ END;
public function testPrintsKitchenSink() : void public function testPrintsKitchenSink() : void
{ {
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink); $ast = Parser::parse($kitchenSink);
$printed = Printer::doPrint($ast); $printed = Printer::doPrint($ast);

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests; namespace GraphQL\Tests;
use GraphQL\Language\AST\NameNode; use GraphQL\Language\AST\NameNode;
@ -7,6 +10,7 @@ use GraphQL\Language\Parser;
use GraphQL\Language\Printer; use GraphQL\Language\Printer;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Throwable; use Throwable;
use function file_get_contents;
class SchemaPrinterTest extends TestCase class SchemaPrinterTest extends TestCase
{ {
@ -16,7 +20,7 @@ class SchemaPrinterTest extends TestCase
public function testPrintsMinimalAst() : void public function testPrintsMinimalAst() : void
{ {
$ast = new ScalarTypeDefinitionNode([ $ast = new ScalarTypeDefinitionNode([
'name' => new NameNode(['value' => 'foo']) 'name' => new NameNode(['value' => 'foo']),
]); ]);
$this->assertEquals('scalar foo', Printer::doPrint($ast)); $this->assertEquals('scalar foo', Printer::doPrint($ast));
} }
@ -41,7 +45,7 @@ class SchemaPrinterTest extends TestCase
{ {
$kitchenSink = file_get_contents(__DIR__ . '/schema-kitchen-sink.graphql'); $kitchenSink = file_get_contents(__DIR__ . '/schema-kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink); $ast = Parser::parse($kitchenSink);
$astCopy = $ast->cloneDeep(); $astCopy = $ast->cloneDeep();
Printer::doPrint($ast); Printer::doPrint($ast);
@ -52,7 +56,7 @@ class SchemaPrinterTest extends TestCase
{ {
$kitchenSink = file_get_contents(__DIR__ . '/schema-kitchen-sink.graphql'); $kitchenSink = file_get_contents(__DIR__ . '/schema-kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink); $ast = Parser::parse($kitchenSink);
$printed = Printer::doPrint($ast); $printed = Printer::doPrint($ast);
$expected = 'schema { $expected = 'schema {

View File

@ -1,5 +1,8 @@
<?php <?php
namespace GraphQL\Tests;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\Location; use GraphQL\Language\AST\Location;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
@ -7,80 +10,68 @@ use GraphQL\Language\AST\NodeList;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Utils\AST; use GraphQL\Utils\AST;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function array_keys;
use function count;
use function file_get_contents;
use function get_class;
use function get_object_vars;
use function implode;
use function json_decode;
class SerializationTest extends TestCase class SerializationTest extends TestCase
{ {
public function testSerializesAst() : void public function testSerializesAst() : void
{ {
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink); $ast = Parser::parse($kitchenSink);
$expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true); $expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true);
$this->assertEquals($expectedAst, $ast->toArray(true)); $this->assertEquals($expectedAst, $ast->toArray(true));
} }
public function testUnserializesAst() : void 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); $serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true);
$actualAst = AST::fromArray($serializedAst); $actualAst = AST::fromArray($serializedAst);
$parsedAst = Parser::parse($kitchenSink); $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]);
$this->assertNodesAreEqual($parsedAst, $actualAst); $this->assertNodesAreEqual($parsedAst, $actualAst);
} }
/** /**
* Compares two nodes by actually iterating over all NodeLists, properly comparing locations (ignoring tokens), etc * Compares two nodes by actually iterating over all NodeLists, properly comparing locations (ignoring tokens), etc
* *
* @param $expected * @param string[] $path
* @param $actual
* @param array $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->assertInstanceOf(Node::class, $actual, $err);
$this->assertEquals(get_class($expected), get_class($actual), $err); $this->assertEquals(get_class($expected), get_class($actual), $err);
$expectedVars = get_object_vars($expected); $expectedVars = get_object_vars($expected);
$actualVars = get_object_vars($actual); $actualVars = get_object_vars($actual);
$this->assertSame(count($expectedVars), count($actualVars), $err); $this->assertSame(count($expectedVars), count($actualVars), $err);
$this->assertEquals(array_keys($expectedVars), array_keys($actualVars), $err); $this->assertEquals(array_keys($expectedVars), array_keys($actualVars), $err);
foreach ($expectedVars as $name => $expectedValue) { foreach ($expectedVars as $name => $expectedValue) {
$actualValue = $actualVars[$name]; $actualValue = $actualVars[$name];
$tmpPath = $path; $tmpPath = $path;
$tmpPath[] = $name; $tmpPath[] = $name;
$err = "Mismatch at AST path: " . implode(', ', $tmpPath); $err = 'Mismatch at AST path: ' . implode(', ', $tmpPath);
if ($expectedValue instanceof Node) { if ($expectedValue instanceof Node) {
$this->assertNodesAreEqual($expectedValue, $actualValue, $tmpPath); $this->assertNodesAreEqual($expectedValue, $actualValue, $tmpPath);
} else if ($expectedValue instanceof NodeList) { } elseif ($expectedValue instanceof NodeList) {
$this->assertEquals(count($expectedValue), count($actualValue), $err); $this->assertEquals(count($expectedValue), count($actualValue), $err);
$this->assertInstanceOf(NodeList::class, $actualValue, $err); $this->assertInstanceOf(NodeList::class, $actualValue, $err);
foreach ($expectedValue as $index => $listNode) { foreach ($expectedValue as $index => $listNode) {
$tmpPath2 = $tmpPath; $tmpPath2 = $tmpPath;
$tmpPath2 [] = $index; $tmpPath2[] = $index;
$this->assertNodesAreEqual($listNode, $actualValue[$index], $tmpPath2); $this->assertNodesAreEqual($listNode, $actualValue[$index], $tmpPath2);
} }
} else if ($expectedValue instanceof Location) { } elseif ($expectedValue instanceof Location) {
$this->assertInstanceOf(Location::class, $actualValue, $err); $this->assertInstanceOf(Location::class, $actualValue, $err);
$this->assertSame($expectedValue->start, $actualValue->start, $err); $this->assertSame($expectedValue->start, $actualValue->start, $err);
$this->assertSame($expectedValue->end, $actualValue->end, $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);
}
} }

View File

@ -1,36 +1,41 @@
<?php <?php
namespace GraphQL\Tests\Language;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\Location; use GraphQL\Language\AST\Location;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\NodeList;
use function get_object_vars;
use function is_array;
use function is_scalar;
class TestUtils class TestUtils
{ {
/** /**
* @param Node $node * @return mixed[]
* @return array
*/ */
public static function nodeToArray(Node $node) public static function nodeToArray(Node $node) : array
{ {
$result = [ $result = [
'kind' => $node->kind, 'kind' => $node->kind,
'loc' => self::locationToArray($node->loc) 'loc' => self::locationToArray($node->loc),
]; ];
foreach (get_object_vars($node) as $prop => $propValue) { foreach (get_object_vars($node) as $prop => $propValue) {
if (isset($result[$prop])) if (isset($result[$prop])) {
continue; continue;
}
if (is_array($propValue) || $propValue instanceof NodeList) { if (is_array($propValue) || $propValue instanceof NodeList) {
$tmp = []; $tmp = [];
foreach ($propValue as $tmp1) { foreach ($propValue as $tmp1) {
$tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1; $tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1;
} }
} else if ($propValue instanceof Node) { } elseif ($propValue instanceof Node) {
$tmp = self::nodeToArray($propValue); $tmp = self::nodeToArray($propValue);
} else if (is_scalar($propValue) || null === $propValue) { } elseif (is_scalar($propValue) || $propValue === null) {
$tmp = $propValue; $tmp = $propValue;
} else { } else {
$tmp = null; $tmp = null;
@ -38,27 +43,25 @@ class TestUtils
$result[$prop] = $tmp; $result[$prop] = $tmp;
} }
return $result; return $result;
} }
/** /**
* @param Location $loc * @return int[]
* @return array
*/ */
public static function locationToArray(Location $loc) public static function locationToArray(Location $loc) : array
{ {
return [ return [
'start' => $loc->start, 'start' => $loc->start,
'end' => $loc->end 'end' => $loc->end,
]; ];
} }
/** /**
* @param $start * @return int[]
* @param $end
* @return array
*/ */
public static function locArray($start, $end) public static function locArray(int $start, int $end) : array
{ {
return ['start' => $start, 'end' => $end]; return ['start' => $start, 'end' => $end];
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests; namespace GraphQL\Tests;
use GraphQL\Language\Token; use GraphQL\Language\Token;
@ -8,12 +11,12 @@ class TokenTest extends TestCase
{ {
public function testReturnTokenOnArray() : void public function testReturnTokenOnArray() : void
{ {
$token = new Token('Kind', 1, 10, 3, 5); $token = new Token('Kind', 1, 10, 3, 5);
$expected = [ $expected = [
'kind' => 'Kind', 'kind' => 'Kind',
'value' => null, 'value' => null,
'line' => 3, 'line' => 3,
'column' => 5 'column' => 5,
]; ];
$this->assertEquals($expected, $token->toArray()); $this->assertEquals($expected, $token->toArray());

File diff suppressed because it is too large Load Diff