Skip to content

Commit 710774a

Browse files
authored
feat: expose error length and raw error message on ParseError (#3820)
Add length and rawError as public parameters of ParseError to allow for richer error UIs in editors. This information was already there, but only encoded into the errors message. This change is purely additional, the existing parameters have not changed.
1 parent 5dd9bc4 commit 710774a

2 files changed

Lines changed: 63 additions & 5 deletions

File tree

src/ParseError.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,21 @@ import type {AnyParseNode} from "./parseNode";
1212
* about where in the source string the problem occurred.
1313
*/
1414
class ParseError {
15+
name: "ParseError";
1516
position: number | void;
16-
// Error position based on passed-in Token or ParseNode.
17+
// Error start position based on passed-in Token or ParseNode.
18+
length: number | void;
19+
// Length of affected text based on passed-in Token or ParseNode.
20+
rawMessage: string | void;
21+
// The underlying error message without any context added.
1722

1823
constructor(
1924
message: string, // The error message
2025
token?: ?Token | AnyParseNode, // An object providing position information
21-
): Error {
26+
): ParseError {
2227
let error = "KaTeX parse error: " + message;
2328
let start;
29+
let end;
2430

2531
const loc = token && token.loc;
2632
if (loc && loc.start <= loc.end) {
@@ -31,7 +37,7 @@ class ParseError {
3137

3238
// Prepend some information
3339
start = loc.start;
34-
const end = loc.end;
40+
end = loc.end;
3541
if (start === input.length) {
3642
error += " at end of input: ";
3743
} else {
@@ -60,12 +66,16 @@ class ParseError {
6066

6167
// Some hackery to make ParseError a prototype of Error
6268
// See http://stackoverflow.com/a/8460753
63-
const self = new Error(error);
69+
// $FlowFixMe
70+
const self: ParseError = new Error(error);
6471
self.name = "ParseError";
6572
// $FlowFixMe
6673
self.__proto__ = ParseError.prototype;
67-
// $FlowFixMe
6874
self.position = start;
75+
if (start != null && end != null) {
76+
self.length = end - start;
77+
}
78+
self.rawMessage = message;
6979
return self;
7080
}
7181
}

test/katex-spec.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import buildTree from "../src/buildTree";
55
import katex from "../katex";
66
import parseTree from "../src/parseTree";
77
import Options from "../src/Options";
8+
import ParseError from "../src/ParseError";
89
import Settings from "../src/Settings";
910
import Style from "../src/Style";
1011
import {
@@ -3060,6 +3061,53 @@ describe("A parser that does not throw on unsupported commands", function() {
30603061
});
30613062
});
30623063

3064+
describe("ParseError properties", function() {
3065+
it("should contain affected position and length information", function() {
3066+
try {
3067+
katex.renderToString("1 + \\fraq{}{}");
3068+
3069+
// Render is expected to throw, so this should not be called.
3070+
expect(true).toBe(false);
3071+
} catch (error) {
3072+
expect(error).toBeInstanceOf(ParseError);
3073+
expect(error.message).toBe("KaTeX parse error: Undefined control sequence: \\fraq at position 5: 1 + \\̲f̲r̲a̲q̲{}{}");
3074+
expect(error.rawMessage).toBe("Undefined control sequence: \\fraq");
3075+
expect(error.position).toBe(4);
3076+
expect(error.length).toBe(5);
3077+
}
3078+
});
3079+
3080+
it("should contain position and length information at end of input", function() {
3081+
try {
3082+
katex.renderToString("\\frac{}");
3083+
3084+
// Render is expected to throw, so this should not be called.
3085+
expect(true).toBe(false);
3086+
} catch (error) {
3087+
expect(error).toBeInstanceOf(ParseError);
3088+
expect(error.message).toBe("KaTeX parse error: Unexpected end of input in a macro argument, expected '}' at end of input: \\frac{}");
3089+
expect(error.rawMessage).toBe("Unexpected end of input in a macro argument, expected '}'");
3090+
expect(error.position).toBe(7);
3091+
expect(error.length).toBe(0);
3092+
}
3093+
});
3094+
3095+
it("should contain no position and length information if unavailable", function() {
3096+
try {
3097+
katex.renderToString("\\verb|hello\nworld|");
3098+
3099+
// Render is expected to throw, so this should not be called.
3100+
expect(true).toBe(false);
3101+
} catch (error) {
3102+
expect(error).toBeInstanceOf(ParseError);
3103+
expect(error.message).toBe("KaTeX parse error: \\verb ended by end of line instead of matching delimiter");
3104+
expect(error.rawMessage).toBe("\\verb ended by end of line instead of matching delimiter");
3105+
expect(error.position).toBeUndefined();
3106+
expect(error.length).toBeUndefined();
3107+
}
3108+
});
3109+
});
3110+
30633111
describe("The symbol table integrity", function() {
30643112
it("should treat certain symbols as synonyms", function() {
30653113
expect`<`.toBuildLike`\lt`;

0 commit comments

Comments
 (0)