diff --git a/src/parser/PhutilJSONParser.php b/src/parser/PhutilJSONParser.php index 0c7421a..11c784d 100644 --- a/src/parser/PhutilJSONParser.php +++ b/src/parser/PhutilJSONParser.php @@ -1,35 +1,43 @@ parse($json); } catch (JsonLintParsingException $ex) { - throw new PhutilJSONParserException($ex->getMessage()); + $details = $ex->getDetails(); + $message = preg_replace("/^Parse error .*\\^\n/s", '', $ex->getMessage()); + + throw new PhutilJSONParserException( + $message, + $details['loc']['last_line'], + $details['loc']['last_column'], + $details['token'], + $details['expected']); } if (!is_array($output)) { throw new PhutilJSONParserException( pht( '%s is not a valid JSON object.', PhutilReadableSerializer::printShort($json))); } return $output; } } diff --git a/src/parser/__tests__/PhutilJSONParserTestCase.php b/src/parser/__tests__/PhutilJSONParserTestCase.php index f8de4d1..6fff65e 100644 --- a/src/parser/__tests__/PhutilJSONParserTestCase.php +++ b/src/parser/__tests__/PhutilJSONParserTestCase.php @@ -1,56 +1,89 @@ array(), '[]' => array(), '{"foo": "bar"}' => array('foo' => 'bar'), '[1, "foo", true, null]' => array(1, 'foo', true, null), '{"foo": {"bar": "baz"}}' => array('foo' => array('bar' => 'baz')), '{"foo": "bar", "bar": ["baz"]}' => array('foo' => 'bar', 'bar' => array('baz')), '{"foo": "bar", "bar": {"baz": "foo"}}' => array('foo' => 'bar', 'bar' => array('baz' => 'foo')), '{"": ""}' => array('' => ''), ); foreach ($tests as $input => $expect) { $this->assertEqual( $expect, $parser->parse($input), 'Parsing JSON: '.$input); } } public function testInvalidJSON() { $parser = new PhutilJSONParser(); $tests = array( - '', - 'null', - 'false', - 'true', - '"quack quack I am a duck lol"', - '{', - '[', - '{"foo":', - '{"foo":"bar",}', - '{{}', + '{' => array( + 'line' => 1, + 'char' => 1, + 'token' => 'EOF', + ), + '[' => array( + 'line' => 1, + 'char' => 1, + 'token' => 'EOF', + ), + '{"foo":' => array( + 'line' => 1, + 'char' => 7, + 'token' => 'EOF', + ), + '{"foo":"bar",}' => array( + 'line' => 1, + 'char' => 13, + 'token' => '}', + ), + '{{}' => array( + 'line' => 1, + 'char' => 1, + 'token' => '{', + ), + '{}}' => array( + 'line' => 1, + 'char' => 2, + 'token' => '}', + ), + "{\"foo\":\"bar\",\n\"bar\":\"baz\",}" => array( + 'line' => 2, + 'char' => 12, + 'token' => '}', + ), + "{'foo': 'bar'}" => array( + 'line' => 1, + 'char' => 1, + 'token' => 'INVALID', + ), ); - foreach ($tests as $input) { + foreach ($tests as $input => $expected) { $caught = null; try { $parser->parse($input); } catch (Exception $ex) { $caught = $ex; } $this->assertTrue($caught instanceof PhutilJSONParserException); + $this->assertEqual($expected['line'], $caught->getSourceLine()); + $this->assertEqual($expected['char'], $caught->getSourceChar()); + $this->assertEqual($expected['token'], $caught->getSourceToken()); } } } diff --git a/src/parser/exception/PhutilJSONParserException.php b/src/parser/exception/PhutilJSONParserException.php index ef44939..7dd4c4b 100644 --- a/src/parser/exception/PhutilJSONParserException.php +++ b/src/parser/exception/PhutilJSONParserException.php @@ -1,3 +1,41 @@ sourceLine = $line; + $this->sourceChar = $char; + $this->sourceToken = $token; + $this->expected = $expected; + + parent::__construct($message); + } + + public function getSourceLine() { + return $this->sourceLine; + } + + public function getSourceChar() { + return $this->sourceChar; + } + + public function getSourceToken() { + return $this->sourceToken; + } + + public function getExpectedTokens() { + return $this->expected; + } + +}