antlr4: ANTLR4 runtime cannot be transpiled to ES5 because of incorrect inheritance
Background
Web applications must be transpiled to ECMAScript 5 (ES5) if they need to support older JavaScript runtimes, for example Internet Explorer or hardware devices that shipped with older JavaScript engines. In the past, the ANTLR runtime was written using ES5 compatible code. But version 4.9.x recently introduced ES6 class definitions that require transpilation. This is fine.
However, the ANTLR library has some classes that inherit from the system Error
class, which is incompatible with transpilation. The reason is that ES5 system APIs are modeled as ES6 classes, which use a different inheritance mechanism from transpiled classes. (See
this article for some details.)
Repro
- Transpile ANTLR4 to ES5 and bundle it into a web application.
This code fails to catch LexerNoViableAltException
:
catch (e) {
if (e instanceof RecognitionException) {
this.notifyListeners(e); // report error
this.recover(e);
}
else {
console.log(e.stack);
throw e;
}
}
The e instanceof RecognitionException
test returns false, even though LexerNoViableAltException
inherits from RecognitionException
. This happens because of the incorrect prototype introduced when RecognitionException
inherits from the Error
base class.
Possible fixes
The two standard solutions are:
- Don’t inherit from the
Error
class; instead simply implement its contract using a custom base class. This will breakinstanceof Error
, but generally nobody ever tests that, so this approach works fine in practice. The callstack property can be calculated using(new Error()).stack
. - OR - - Call
Object.setPrototypeOf()
in the constructor for every subclass of theError
class. This also works pretty well. Its main downside is that people sometimes forget to apply the workaround when adding new subclasses.
I would be willing to make a PR to fix this. Would you accept a fix?
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 30 (15 by maintainers)
Commits related to this issue
- Fix for incompatibility with the TypeScript transpiler (GitHub issue #3032) — committed to octogonz/antlr4 by octogonz 3 years ago
- Fix for incompatibility with the TypeScript transpiler (GitHub issue #3032) — committed to octogonz/antlr4 by octogonz 3 years ago
My advice is: don’t use tools written by lazy people (it not incompetent)
We will soon be shipping a typescript target, so closing this.
@octogonz can you move your last comment to the PR itself - best to discuss there ?
@octogonz you could try using this branch https://github.com/ericvergnaud/antlr4/tree/purify-javascript-runtime-code it supposedly generates an ES5 script file
The code fails if you specify
"target": "es5"
in your tsconfig.json. The actual JavaScript runtime does not matter – for example my example will fail even with the latest Chrome.The ECMAScript 5 target is needed mainly for Internet Explorer 11, and for devices that shipped with an older web browser (e.g. Playstation, Tizen, etc). These devices can last for many years beyond their initial release, and the OEM generally will never update the browser beyond security fixes. Recently at work I encountered a Comcast TV box that mostly supports ECMAScript 6, but with arbitrary gaps that are most easily solved by transpiling to ES5.
ES6 is now 6 years old. I’ll be overjoyed on the day when we can finally forget about ES5, but we’re not there yet…
I’m not recommending to add redundant code. My recommendation is to avoid extending the system
Error
class. This is the best practice for a library that intends to be used in many different environments.I can also open a TypeScript issue proposing for the transpiler to call
Object.setPrototypeOf()
, but (from previous experience) they might have some complicated justification for why they don’t do that.ANTLR’s TypeScript target is still experimental and not officially supported. It might be nice to have, but frankly since humans don’t edit the generated parsers, it doesn’t matter much whether the generated code is typed or not.
@types/antlr4
provides typings for the ANTLR runtime, and for my case at least, I only had to write about 10 lines of code to add typings for the generated classes. So I’m fine using the JavaScript target. Except for this one problem. 😃