A standard-compliant JavaScript parser, which is a fork of Esprima.NET combined with the .NET port of the acornjs parser.
$ dotnet add package AcornimaThis project is an interbreeding of the acornjs and the Esprima.NET parsers, with the intention of creating an even more complete and performant ECMAScript (a.k.a JavaScript) parser library for .NET by combining the best bits of those.
It should also be mentioned that there is an earlier .NET port of acornjs, AcornSharp, which though is unmaintained for a long time, served as a good starting point. If it weren't for AcornSharp, this project probably have never started.
StackOverflowException using the same approach as Roslyn.dotnet add package AcornimaOr, if you want to use additional features like AST to JavaScript or AST to JSON conversion:
dotnet add package Acornima.Extrasusing Acornima;var parser = new Parser();Or, if you want to tweak the available settings:
var parser = new Parser(new ParserOptions { /* ... */ });var ast = parser.ParseScript("console.log('Hello world!')");Node [x]
├─AssignmentPattern : IDestructuringPatternElement [v,s]
├─CatchClause [v,s]
├─ClassBody [v,s]
├─ClassProperty : IClassElement, IProperty
│ ├─AccessorProperty : IClassElement, IProperty [v,s]
│ ├─MethodDefinition : IClassElement, IProperty [v,s]
│ └─PropertyDefinition : IClassElement, IProperty [v,s]
├─Decorator [v,s]
├─DestructuringPattern : IDestructuringPatternElement
│ ├─ArrayPattern : IDestructuringPatternElement [v,s]
│ └─ObjectPattern : IDestructuringPatternElement [v,s]
├─ImportAttribute [v,s]
├─ModuleSpecifier
│ ├─ExportSpecifier [v,s]
│ └─ImportDeclarationSpecifier
│ ├─ImportDefaultSpecifier [v,s]
│ ├─ImportNamespaceSpecifier [v,s]
│ └─ImportSpecifier [v,s]
├─Program : IHoistingScope [v]
│ ├─Module : IHoistingScope [s,t=Program]
│ └─Script : IHoistingScope [s,t=Program]
├─Property : IProperty
│ ├─AssignmentProperty : IProperty [v,s,t=Property]
│ └─ObjectProperty : IProperty [v,s,t=Property]
├─RestElement : IDestructuringPatternElement [v,s]
├─StatementOrExpression
│ ├─Expression [x]
│ │ ├─ArrayExpression [v,s]
│ │ ├─ArrowFunctionExpression : IFunction [v,s]
│ │ ├─AssignmentExpression [v,s]
│ │ ├─AwaitExpression [v,s]
│ │ ├─BinaryExpression [v]
│ │ │ ├─LogicalExpression [s]
│ │ │ └─NonLogicalBinaryExpression [s,t=BinaryExpression]
│ │ ├─CallExpression : IChainElement [v,s]
│ │ ├─ChainExpression [v,s]
│ │ ├─ClassExpression : IClass [v,s]
│ │ ├─ConditionalExpression [v,s]
│ │ ├─FunctionExpression : IFunction [v,s]
│ │ ├─Identifier : IDestructuringPatternElement [v,s]
│ │ ├─ImportExpression [v,s]
│ │ ├─Literal [v]
│ │ │ ├─BigIntLiteral [s,t=Literal]
│ │ │ ├─BooleanLiteral [s,t=Literal]
│ │ │ ├─NullLiteral [s,t=Literal]
│ │ │ ├─NumericLiteral [s,t=Literal]
│ │ │ ├─RegExpLiteral [s,t=Literal]
│ │ │ └─StringLiteral [s,t=Literal]
│ │ ├─MemberExpression : IChainElement, IDestructuringPatternElement [v,s]
│ │ ├─MetaProperty [v,s]
│ │ ├─NewExpression [v,s]
│ │ ├─ObjectExpression [v,s]
│ │ ├─ParenthesizedExpression [v,s]
│ │ ├─PrivateIdentifier [v,s]
│ │ ├─SequenceExpression [v,s]
│ │ ├─SpreadElement [v,s]
│ │ ├─Super [v,s]
│ │ ├─TaggedTemplateExpression [v,s]
│ │ ├─TemplateLiteral [v,s]
│ │ ├─ThisExpression [v,s]
│ │ ├─UnaryExpression [v]
│ │ │ ├─NonUpdateUnaryExpression [s,t=UnaryExpression]
│ │ │ └─UpdateExpression [s]
│ │ └─YieldExpression [v,s]
│ └─Statement [x]
│ ├─BlockStatement [v]
│ │ ├─FunctionBody : IHoistingScope [v,s,t=BlockStatement]
│ │ ├─NestedBlockStatement [s,t=BlockStatement]
│ │ └─StaticBlock : IClassElement, IHoistingScope [v,s]
│ ├─BreakStatement [v,s]
│ ├─ContinueStatement [v,s]
│ ├─DebuggerStatement [v,s]
│ ├─Declaration [x]
│ │ ├─ClassDeclaration : IClass [v,s]
│ │ ├─FunctionDeclaration : IFunction [v,s]
│ │ ├─ImportOrExportDeclaration
│ │ │ ├─ExportDeclaration
│ │ │ │ ├─ExportAllDeclaration [v,s]
│ │ │ │ ├─ExportDefaultDeclaration [v,s]
│ │ │ │ └─ExportNamedDeclaration [v,s]
│ │ │ └─ImportDeclaration [v,s]
│ │ └─VariableDeclaration [v,s]
│ ├─DoWhileStatement [v,s]
│ ├─EmptyStatement [v,s]
│ ├─ExpressionStatement [v]
│ │ ├─Directive [s,t=ExpressionStatement]
│ │ └─NonSpecialExpressionStatement [s,t=ExpressionStatement]
│ ├─ForInStatement [v,s]
│ ├─ForOfStatement [v,s]
│ ├─ForStatement [v,s]
│ ├─IfStatement [v,s]
│ ├─LabeledStatement [v,s]
│ ├─ReturnStatement [v,s]
│ ├─SwitchStatement [v,s]
│ ├─ThrowStatement [v,s]
│ ├─TryStatement [v,s]
│ ├─WhileStatement [v,s]
│ └─WithStatement [v,s]
├─SwitchCase [v,s]
├─TemplateElement [v,s]
└─VariableDeclarator [v,s]
Legend:
v - A visitation method is generated in the visitors for the node type.s - The node class is sealed. (It's beneficial to check for sealed types when possible.)t - The node type (the value of the Node.Type property) as specified by ESTree (shown only if it differs from the name of the node class).x - The node class can be subclassed. (The AST provides some limited extensibility for special use cases.)The library also supports the syntax extensions JSX.
However, mostly for performance reasons, the related functionality is separated from the core parser: it is available in the Acornima.Extras package, in the Acornima.Jsx namespace.
After installing the Acornima.Extras package as described in the Getting started section, you can parse JSX code like this:
using Acornima.Jsx;
var parser = new JsxParser(new JsxParserOptions { /* ... */ });
var ast = parser.ParseScript("<>Hello world!</>");Node [x]
└─StatementOrExpression
└─Expression [x]
└─JsxNode [x]
├─JsxAttributeLike
│ ├─JsxAttribute [v,s]
│ └─JsxSpreadAttribute [v,s]
├─JsxClosingTag
│ ├─JsxClosingElement [v,s]
│ └─JsxClosingFragment [v,s]
├─JsxElementOrFragment
│ ├─JsxElement [v,s]
│ └─JsxFragment [v,s]
├─JsxEmptyExpression [v,s]
├─JsxExpressionContainer [v,s]
├─JsxName
│ ├─JsxIdentifier [v,s]
│ ├─JsxMemberExpression [v,s]
│ └─JsxNamespacedName [v,s]
├─JsxOpeningTag
│ ├─JsxOpeningElement [v,s]
│ └─JsxOpeningFragment [v,s]
└─JsxText [v,s]
| Method | Runtime | FileName | Mean | Allocated |
|---|---|---|---|---|
| Acornima v0.9.0 | .NET 8.0 | angular-1.2.5 | 10.656 ms | 4067.62 KB |
| Acornima v0.9.0 | .NET Framework 4.8 | angular-1.2.5 | 21.035 ms | 4088.56 KB |
| Esprima v3.0.4 | .NET 8.0 | angular-1.2.5 | 11.291 ms | 3828.11 KB |
| Esprima v3.0.4 | .NET Framework 4.8 | angular-1.2.5 | 20.673 ms | 3879.54 KB |
| Acornima v0.9.0 | .NET 8.0 | backbone-1.1.0 | 1.430 ms | 638.85 KB |
| Acornima v0.9.0 | .NET Framework 4.8 | backbone-1.1.0 | 3.145 ms | 642.71 KB |
| Esprima v3.0.4 | .NET 8.0 | backbone-1.1.0 | 1.452 ms | 613.88 KB |
| Esprima v3.0.4 | .NET Framework 4.8 | backbone-1.1.0 | 2.883 ms | 620.3 KB |
| Acornima v0.9.0 | .NET 8.0 | jquery-1.9.1 | 8.438 ms | 3324.14 KB |
| Acornima v0.9.0 | .NET Framework 4.8 | jquery-1.9.1 | 17.668 ms | 3340.91 KB |
| Esprima v3.0.4 | .NET 8.0 | jquery-1.9.1 | 8.453 ms | 3305.23 KB |
| Esprima v3.0.4 | .NET Framework 4.8 | jquery-1.9.1 | 16.625 ms | 3355.15 KB |
| Acornima v0.9.0 | .NET 8.0 | jquery.mobile-1.4.2 | 14.394 ms | 5505.68 KB |
| Acornima v0.9.0 | .NET Framework 4.8 | jquery.mobile-1.4.2 | 28.905 ms | 5536.94 KB |
| Esprima v3.0.4 | .NET 8.0 | jquery.mobile-1.4.2 | 14.566 ms | 5428.48 KB |
| Esprima v3.0.4 | .NET Framework 4.8 | jquery.mobile-1.4.2 | 27.031 ms | 5497.48 KB |
| Acornima v0.9.0 | .NET 8.0 | mootools-1.4.5 | 6.788 ms | 2811.17 KB |
| Acornima v0.9.0 | .NET Framework 4.8 | mootools-1.4.5 | 14.466 ms | 2826.9 KB |
| Esprima v3.0.4 | .NET 8.0 | mootools-1.4.5 | 6.875 ms | 2777.83 KB |
| Esprima v3.0.4 | .NET Framework 4.8 | mootools-1.4.5 | 13.628 ms | 2816.33 KB |
| Acornima v0.9.0 | .NET 8.0 | underscore-1.5.2 | 1.234 ms | 541.64 KB |
| Acornima v0.9.0 | .NET Framework 4.8 | underscore-1.5.2 | 2.720 ms | 544.34 KB |
| Esprima v3.0.4 | .NET 8.0 | underscore-1.5.2 | 1.239 ms | 539.42 KB |
| Esprima v3.0.4 | .NET Framework 4.8 | underscore-1.5.2 | 2.501 ms | 547.18 KB |
| Acornima v0.9.0 | .NET 8.0 | yui-3.12.0 | 6.359 ms | 2639.25 KB |
| Acornima v0.9.0 | .NET Framework 4.8 | yui-3.12.0 | 13.410 ms | 2656.09 KB |
| Esprima v3.0.4 | .NET 8.0 | yui-3.12.0 | 6.516 ms | 2585.78 KB |
| Esprima v3.0.4 | .NET Framework 4.8 | yui-3.12.0 | 12.346 ms | 2624.92 KB |
The parser can be configured to convert JS regular expression literals to .NET Regex instances (see ParserOptions.RegExpParseMode).
However, because of the fundamental differences between the JS and .NET regex engines, in many cases this conversion can't be done perfectly (or, in some cases, at all):
RegExpParseResult object's ActualRegexGroupCount property.$group is valid in JS but invalid in .NET. So, as a workaround, the converter encodes the problematic group names to names that are valid in .NET and probably won't collide with other group names present in the pattern. For example, $group is encoded like __utf8_2467726F7570. The original group names can be obtained using the returned RegExpParseResult object's GetRegexGroupName method./((a+)(\1) ?)+/ may not produce the exact same captures. RegexOptions.ECMAScript is supposed to cover this, however even the MSDN example doesn't produce the same matches. (As a side note, RegexOptions.ECMAScript is kinda a false promise, it can't even get some basic cases right by itself.)/((a+)?(b+)?(c))*/ may produce different captures for the groups. (JS has an overwrite behavior while .NET doesn't).\1(\w) differently than JS and it's not possible to convert this kind of patterns reliably. (The converter could make some patterns work by rewriting them to something like (?:)(\w) but there are cases where even this wouldn't work.)/a?/u will cause this issue when the input string contains astral Unicode chars. There is no out-of-the-box workaround for this issue but it can be mitigated somewhat using a bit of "post-processing", i.e., by filtering out the false positive matches after evaluation like it's done here. Probably there is no way to improve this situation until .NET adds the option to treat the input string as Unicode code points.To sum it up, legacy pattern conversion is pretty solid apart from the minor issues listed above. However, support for unicode mode (flag u) patterns is partial and quirky, while conversion of the upcoming unicode sets mode (flag v) will be next to impossible - until the .NET regex engine gets some improved Unicode support.