assertEquals(new Token(Token::NAME, 6, 9, 'foo'), $this->lexOne($example1)); $example2 = ' #comment foo#comment '; $this->assertEquals(new Token(Token::NAME, 18, 21, 'foo'), $this->lexOne($example2)); $example3 = ',,,foo,,,'; $this->assertEquals(new Token(Token::NAME, 3, 6, 'foo'), $this->lexOne($example3)); } public function testErrorsRespectWhitespace() { $example = " ? "; try { $this->lexOne($example); $this->fail('Expected exception not thrown'); } catch (Exception $e) { $this->assertEquals( 'Syntax Error GraphQL (3:5) Unexpected character "?"' . "\n" . "\n" . "2: \n" . "3: ?\n" . " ^\n" . "4: \n", $e->getMessage() ); } } public function testLexesStrings() { $this->assertEquals(new Token(Token::STRING, 0, 8, 'simple'), $this->lexOne('"simple"')); $this->assertEquals(new Token(Token::STRING, 0, 15, ' white space '), $this->lexOne('" white space "')); $this->assertEquals(new Token(Token::STRING, 0, 10, 'quote "'), $this->lexOne('"quote \\""')); $this->assertEquals(new Token(Token::STRING, 0, 20, 'escaped \n\r\b\t\f'), $this->lexOne('"escaped \\n\\r\\b\\t\\f"')); $this->assertEquals(new Token(Token::STRING, 0, 15, 'slashes \\ \/'), $this->lexOne('"slashes \\\\ \\/"')); $this->assertEquals(new Token(Token::STRING, 0, 13, 'unicode яуц'), $this->lexOne('"unicode яуц"')); $unicode = json_decode('"\u1234\u5678\u90AB\uCDEF"'); $this->assertEquals(new Token(Token::STRING, 0, 34, 'unicode ' . $unicode), $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"')); } public function testReportsUsefulErrors() { $run = function($num, $str, $expectedMessage) { try { $this->lexOne($str); $this->fail('Expected exception not thrown in example: ' . $num); } catch (Exception $e) { $this->assertEquals($expectedMessage, $e->getMessage(), "Test case $num failed"); } }; $run(1, '"no end quote', "Syntax Error GraphQL (1:14) Unterminated string\n\n1: \"no end quote\n ^\n"); $run(2, '"multi'."\n".'line"', "Syntax Error GraphQL (1:7) Unterminated string\n\n1: \"multi\n ^\n2: line\"\n"); $run(3, '"multi'."\r".'line"', "Syntax Error GraphQL (1:7) Unterminated string\n\n1: \"multi\n ^\n2: line\"\n"); $run(4, '"multi' . json_decode('"\u2028"') . 'line"', "Syntax Error GraphQL (1:7) Unterminated string\n\n1: \"multi\n ^\n2: line\"\n"); $run(5, '"multi' . json_decode('"\u2029"') . 'line"', "Syntax Error GraphQL (1:7) Unterminated string\n\n1: \"multi\n ^\n2: line\"\n"); $run(6, '"bad \\z esc"', "Syntax Error GraphQL (1:7) Bad character escape sequence\n\n1: \"bad \\z esc\"\n ^\n"); $run(7, '"bad \\x esc"', "Syntax Error GraphQL (1:7) Bad character escape sequence\n\n1: \"bad \\x esc\"\n ^\n"); $run(8, '"bad \\u1 esc"', "Syntax Error GraphQL (1:7) Bad character escape sequence\n\n1: \"bad \\u1 esc\"\n ^\n"); $run(9, '"bad \\u0XX1 esc"', "Syntax Error GraphQL (1:7) Bad character escape sequence\n\n1: \"bad \\u0XX1 esc\"\n ^\n"); $run(10, '"bad \\uXXXX esc"', "Syntax Error GraphQL (1:7) Bad character escape sequence\n\n1: \"bad \\uXXXX esc\"\n ^\n"); $run(11, '"bad \\uFXXX esc"', "Syntax Error GraphQL (1:7) Bad character escape sequence\n\n1: \"bad \\uFXXX esc\"\n ^\n"); $run(12, '"bad \\uXXXF esc"', "Syntax Error GraphQL (1:7) Bad character escape sequence\n\n1: \"bad \\uXXXF esc\"\n ^\n"); } public function testLexesNumbers() { $this->assertEquals( new Token(Token::STRING, 0, 8, 'simple'), $this->lexOne('"simple"') ); $this->assertEquals( new Token(Token::STRING, 0, 15, ' white space '), $this->lexOne('" white space "') ); $this->assertEquals( new Token(Token::STRING, 0, 20, 'escaped \n\r\b\t\f'), $this->lexOne('"escaped \\n\\r\\b\\t\\f"') ); $this->assertEquals( new Token(Token::STRING, 0, 15, 'slashes \\ \/'), $this->lexOne('"slashes \\\\ \\/"') ); $this->assertEquals( new Token(Token::STRING, 0, 34, 'unicode ' . json_decode('"\u1234\u5678\u90AB\uCDEF"')), $this->lexOne('"unicode \\u1234\\u5678\\u90AB\\uCDEF"') ); $this->assertEquals( new Token(Token::INT, 0, 1, '4'), $this->lexOne('4') ); $this->assertEquals( new Token(Token::FLOAT, 0, 5, '4.123'), $this->lexOne('4.123') ); $this->assertEquals( new Token(Token::INT, 0, 2, '-4'), $this->lexOne('-4') ); $this->assertEquals( new Token(Token::INT, 0, 1, '9'), $this->lexOne('9') ); $this->assertEquals( new Token(Token::INT, 0, 1, '0'), $this->lexOne('0') ); $this->assertEquals( new Token(Token::INT, 0, 1, '0'), $this->lexOne('00') ); $this->assertEquals( new Token(Token::FLOAT, 0, 6, '-4.123'), $this->lexOne('-4.123') ); $this->assertEquals( new Token(Token::FLOAT, 0, 5, '0.123'), $this->lexOne('0.123') ); $this->assertEquals( new Token(Token::FLOAT, 0, 8, '-1.123e4'), $this->lexOne('-1.123e4') ); $this->assertEquals( new Token(Token::FLOAT, 0, 9, '-1.123e-4'), $this->lexOne('-1.123e-4') ); $this->assertEquals( new Token(Token::FLOAT, 0, 11, '-1.123e4567'), $this->lexOne('-1.123e4567') ); } public function testReportsUsefulNumberErrors() { $run = function($num, $str, $expectedMessage) { try { $this->lexOne($str); $this->fail('Expected exception not thrown in example: ' . $num); } catch (Exception $e) { $this->assertEquals($expectedMessage, $e->getMessage(), "Test case $num failed"); } }; $run(1, '+1', "Syntax Error GraphQL (1:1) Unexpected character \"+\"\n\n1: +1\n ^\n"); $run(2, '1.', "Syntax Error GraphQL (1:3) Invalid number\n\n1: 1.\n ^\n"); $run(3, '1.A', "Syntax Error GraphQL (1:3) Invalid number\n\n1: 1.A\n ^\n"); $run(4, '-A', "Syntax Error GraphQL (1:2) Invalid number\n\n1: -A\n ^\n"); $run(5, '1.0e+4', "Syntax Error GraphQL (1:5) Invalid number\n\n1: 1.0e+4\n ^\n"); $run(6, '1.0e', "Syntax Error GraphQL (1:5) Invalid number\n\n1: 1.0e\n ^\n"); $run(7, '1.0eA', "Syntax Error GraphQL (1:5) Invalid number\n\n1: 1.0eA\n ^\n"); } public function testLexesPunctuation() { $this->assertEquals( new Token(Token::BANG, 0, 1, null), $this->lexOne('!') ); $this->assertEquals( new Token(Token::DOLLAR, 0, 1, null), $this->lexOne('$') ); $this->assertEquals( new Token(Token::PAREN_L, 0, 1, null), $this->lexOne('(') ); $this->assertEquals( new Token(Token::PAREN_R, 0, 1, null), $this->lexOne(')') ); $this->assertEquals( new Token(Token::SPREAD, 0, 3, null), $this->lexOne('...') ); $this->assertEquals( new Token(Token::COLON, 0, 1, null), $this->lexOne(':') ); $this->assertEquals( new Token(Token::EQUALS, 0, 1, null), $this->lexOne('=') ); $this->assertEquals( new Token(Token::AT, 0, 1, null), $this->lexOne('@') ); $this->assertEquals( new Token(Token::BRACKET_L, 0, 1, null), $this->lexOne('[') ); $this->assertEquals( new Token(Token::BRACKET_R, 0, 1, null), $this->lexOne(']') ); $this->assertEquals( new Token(Token::BRACE_L, 0, 1, null), $this->lexOne('{') ); $this->assertEquals( new Token(Token::BRACE_R, 0, 1, null), $this->lexOne('}') ); $this->assertEquals( new Token(Token::PIPE, 0, 1, null), $this->lexOne('|') ); } public function testReportsUsefulUnknownCharErrors() { $run = function($num, $str, $expectedMessage) { try { $this->lexOne($str); $this->fail('Expected exception not thrown in example: ' . $num); } catch (Exception $e) { $this->assertEquals($expectedMessage, $e->getMessage(), "Test case $num failed"); } }; $run(1, '..', "Syntax Error GraphQL (1:1) Unexpected character \".\"\n\n1: ..\n ^\n"); $run(2, '?', "Syntax Error GraphQL (1:1) Unexpected character \"?\"\n\n1: ?\n ^\n"); $unicode = json_decode('"\u203B"'); $run(3, $unicode, "Syntax Error GraphQL (1:1) Unexpected character \"$unicode\"\n\n1: $unicode\n ^\n"); } /** * @param string $body * @return Token */ private function lexOne($body) { $lexer = new Lexer(new Source($body)); return $lexer->nextToken(); } }