diff --git a/src/ParseError.js b/src/ParseError.js index f0c62613..2b8d1742 100644 --- a/src/ParseError.js +++ b/src/ParseError.js @@ -12,15 +12,21 @@ import type {AnyParseNode} from "./parseNode"; * about where in the source string the problem occurred. */ class ParseError { + name: "ParseError"; position: number | void; - // Error position based on passed-in Token or ParseNode. + // Error start position based on passed-in Token or ParseNode. + length: number | void; + // Length of affected text based on passed-in Token or ParseNode. + rawMessage: string | void; + // The underlying error message without any context added. constructor( message: string, // The error message token?: ?Token | AnyParseNode, // An object providing position information - ): Error { + ): ParseError { let error = "KaTeX parse error: " + message; let start; + let end; const loc = token && token.loc; if (loc && loc.start <= loc.end) { @@ -31,7 +37,7 @@ class ParseError { // Prepend some information start = loc.start; - const end = loc.end; + end = loc.end; if (start === input.length) { error += " at end of input: "; } else { @@ -60,12 +66,16 @@ class ParseError { // Some hackery to make ParseError a prototype of Error // See http://stackoverflow.com/a/8460753 - const self = new Error(error); + // $FlowFixMe + const self: ParseError = new Error(error); self.name = "ParseError"; // $FlowFixMe self.__proto__ = ParseError.prototype; - // $FlowFixMe self.position = start; + if (start != null && end != null) { + self.length = end - start; + } + self.rawMessage = message; return self; } } diff --git a/test/katex-spec.js b/test/katex-spec.js index 8f6a2e59..dae9a1b2 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -5,6 +5,7 @@ import buildTree from "../src/buildTree"; import katex from "../katex"; import parseTree from "../src/parseTree"; import Options from "../src/Options"; +import ParseError from "../src/ParseError"; import Settings from "../src/Settings"; import Style from "../src/Style"; import { @@ -3060,6 +3061,53 @@ describe("A parser that does not throw on unsupported commands", function() { }); }); +describe("ParseError properties", function() { + it("should contain affected position and length information", function() { + try { + katex.renderToString("1 + \\fraq{}{}"); + + // Render is expected to throw, so this should not be called. + expect(true).toBe(false); + } catch (error) { + expect(error).toBeInstanceOf(ParseError); + expect(error.message).toBe("KaTeX parse error: Undefined control sequence: \\fraq at position 5: 1 + \\̲f̲r̲a̲q̲{}{}"); + expect(error.rawMessage).toBe("Undefined control sequence: \\fraq"); + expect(error.position).toBe(4); + expect(error.length).toBe(5); + } + }); + + it("should contain position and length information at end of input", function() { + try { + katex.renderToString("\\frac{}"); + + // Render is expected to throw, so this should not be called. + expect(true).toBe(false); + } catch (error) { + expect(error).toBeInstanceOf(ParseError); + expect(error.message).toBe("KaTeX parse error: Unexpected end of input in a macro argument, expected '}' at end of input: \\frac{}"); + expect(error.rawMessage).toBe("Unexpected end of input in a macro argument, expected '}'"); + expect(error.position).toBe(7); + expect(error.length).toBe(0); + } + }); + + it("should contain no position and length information if unavailable", function() { + try { + katex.renderToString("\\verb|hello\nworld|"); + + // Render is expected to throw, so this should not be called. + expect(true).toBe(false); + } catch (error) { + expect(error).toBeInstanceOf(ParseError); + expect(error.message).toBe("KaTeX parse error: \\verb ended by end of line instead of matching delimiter"); + expect(error.rawMessage).toBe("\\verb ended by end of line instead of matching delimiter"); + expect(error.position).toBeUndefined(); + expect(error.length).toBeUndefined(); + } + }); +}); + describe("The symbol table integrity", function() { it("should treat certain symbols as synonyms", function() { expect`<`.toBuildLike`\lt`;