Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: expose error length and raw error message on ParseError #3820

Merged
merged 2 commits into from Jun 24, 2023

Conversation

mangerlahn
Copy link
Contributor

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.

What is the previous behavior before this PR?
ParseError consists of a position (integer) and message (string) that describe the error. Inside the message string, the position, length (through underline) and actual cause are encoded.

What is the new behavior after this PR?
The existing behavior stays the same, ParseError gains two new properties, length (integer) and rawError (string). length together with the existing position describes the range affected by the error. rawError exposes the underlying error message, without the formatting that is added within ParseError.

Reason
We currently implement formula editing and rendering in our app and found this information useful yet missing from the current implementation.

This is what we are currently doing. Showing the raw error below the code while underlining the affected text.
Inline - Error

Testing
Since this only exposes existing values, the behavior has not changed. I am happy to update the tests in error-specs.js to both check for position and length of the error. However, I did not do that yet because both values are indirectly tested through the message string (rendering the underline).

Copy link
Member

@edemaine edemaine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay in reviewing this. This looks great, and useful, and the changes are minimal.

Please tweak the comment placement (perhaps on their own line) to satisfy the line length lint.

Regarding testing, please add one or two tests that check that these properties are properly set on the returned error (perhaps where position is already tested, if any). I don't think we need to check the properties for every erroring test, just in a couple of examples so that we ensure that these properties accidentally don't get set in a future regression.

src/ParseError.js Outdated Show resolved Hide resolved
// Error position based on passed-in Token or ParseNode.
position: number | void; // Error position based on passed-in Token or ParseNode.
length: number | void; // Length of affected text based on passed-in Token or ParseNode.
rawError: string | void; // The underlying error message without any context added.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder about this property name. Would rawMessage make more sense, given that the contextualized message becomes message?

@mangerlahn
Copy link
Contributor Author

Thanks for the review! I did not find any tests specific to property access, so I added some to katex-spec.js. Not sure, if that is the correct place though.

@@ -66,6 +71,8 @@ class ParseError {
self.__proto__ = ParseError.prototype;
// $FlowFixMe
self.position = start;
self.length = end - start;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It occurs to me that this computes NaN if start and end are undefined. Indeed, Flow caught this as well. Perhaps this line should be wrapped in if (start != null && end != null)?

Apparently we also need // $FlowFixMe because self is typed as Error here. But maybe this could be fixed via type casting? (In TypeScript it'd be as.)

(You can run yarn test to run Lint, Jest, and Flow yourself.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, I always just used yarn test:jest, makes more sense now 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to cast this error, but that does not seem to help. I get syntax errors, so I assume that this is not TypeScript?
Sorry, I am super unexperienced with JS.

Using // $FlowFixMe works fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this is Flow, not TypeScript. Perhaps you can try a Type Cast Expression (: with parens instead of as)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the hint! However this all does not appear to work. As far as I can see, ParseError and Error are distinct classes, having nothing to do with each other. So if I cast self to ParseError, I just get errors when returning self from the constructor, which expects Error. So it's just the other way around. I followed the StackOverflow link at the top of the class declaration. The top answer provides an alternative available since ES6 by just extending Error. This would allow to remove some of the hackery around ParseError.

However, lint is unhappy with this, because extending classes appears to be prohibited in the eslintrc file (ClassDeclaration[superClass]).

If I remove this restriction, the tests appear to run fine on my machine 😅

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these need to be toBeUndefined(), which is why the length NaN bug wasn't detected.

katex.renderToString("1 + \\fraq{}{}");

// Render is expected to throw, so this should not be called.
fail();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter throws an error on the fail command I used in the tests, because this function appears to be defined in a different package called Jasmine. I could add that to the "env" section within the .eslintrc file (appears to work), but I'm not sure if that's desired.

Alternatively I could replace fail with some other failing check like expect(true).toBe(false). What do you prefer?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either is fine with me! I didn't know about fail so it was neat to learn. But expect... might be more clear to those who also don't know fail. 🙂

@mangerlahn
Copy link
Contributor Author

I just updated the PR, maybe it's better to see some code rather than discussing this in the comments. ParseError now extends Error, not sure if that's desired. I isolated this change in its own commit for now.

@codecov
Copy link

codecov bot commented Jun 22, 2023

Codecov Report

Merging #3820 (7426bba) into main (5dd9bc4) will not change coverage.
The diff coverage is 100.00%.

❗ Current head 7426bba differs from pull request most recent head c302c9f. Consider uploading reports for the commit c302c9f to get more accurate results

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #3820   +/-   ##
=======================================
  Coverage   92.98%   92.98%           
=======================================
  Files          91       91           
  Lines        6770     6770           
  Branches     1574     1575    +1     
=======================================
  Hits         6295     6295           
  Misses        437      437           
  Partials       38       38           
Impacted Files Coverage Δ
src/ParseError.js 100.00% <100.00%> (ø)

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5dd9bc4...c302c9f. Read the comment docs.

@edemaine
Copy link
Member

I just updated the PR, maybe it's better to see some code rather than discussing this in the comments. ParseError now extends Error, not sure if that's desired. I isolated this change in its own commit for now.

This code is so much cleaner... However, I read https://stackoverflow.com/q/1382107 a bunch (linked from the source here and from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) and it looks like class ParseError extends Error cannot be reasonably transpiled for IE 11. Currently, IE 11 is listed as an officially supported platform of KaTeX. That said, there's some good reason to remove that support, mentioned in #2384, and given that IE 11 has been end-of-life for over a year (though you can still download it). Thoughts from anyone on this? I do like how much cleaner this code is, but the code was also OK as it was before...

@edemaine
Copy link
Member

By the way, I took a crack at making the old code compatible with Flow. Here's the best I came up with, which uses "only" two $FlowFixMes.

class ParseError {
    name: "ParseError";
    ...
    constructor(
        message: string,               // The error message
        token?: ?Token | AnyParseNode, // An object providing position information
    ): ParseError {
        ...
        // $FlowFixMe
        const self: ParseError = new Error(error);
        self.name = "ParseError";
        // $FlowFixMe
        self.__proto__ = ParseError.prototype;
        self.position = start;
        if (start != null && end != null) {
            self.length = end - start;
        }
        self.rawMessage = message;
        return self;
    }
}

I'm still not sure which version is best though, as described in my previous message.

@mangerlahn
Copy link
Contributor Author

Either is fine by me (again having little experience with web dev). If IE11 support is blocking the first solution, I don't see a reason to not use your second version. Could still be changed when that support is removed, right?

@edemaine
Copy link
Member

Yes. Perhaps we could merge the IE 11 compatible version now, and make separate cleanup PR for dropping IE 11?

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.
@edemaine edemaine enabled auto-merge (squash) June 23, 2023 12:21
@edemaine edemaine disabled auto-merge June 23, 2023 12:21
@mangerlahn
Copy link
Contributor Author

Thanks for your patience! 😃

@edemaine
Copy link
Member

Sorry, one more thing: can you update against the current main branch? Merge is fine; we squash PRs. (You don't seem to have given me permission to do so.)

@edemaine edemaine enabled auto-merge (squash) June 24, 2023 05:26
@edemaine edemaine merged commit 710774a into KaTeX:main Jun 24, 2023
7 checks passed
KaTeX-bot added a commit that referenced this pull request Jun 24, 2023
## [0.16.8](v0.16.7...v0.16.8) (2023-06-24)

### Features

* expose error length and raw error message on ParseError ([#3820](#3820)) ([710774a](710774a))
@KaTeX-bot
Copy link
Member

🎉 This PR is included in version 0.16.8 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants