Home

Awesome

GitHub Actions Workflow Status NuGet Release Feedz Version Donate

Acorn + Esprima = Acornima

This 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. Had it not been for AcornSharp, this project would probably have never started.

Here is how this Frankenstein's monster looks like:

And what good comes out of this mix?

Getting started

1. Install the package from NuGet

dotnet add package Acornima

Or, if you want to use additional features like JSX parsing, JavaScript generation from AST or AST to JSON conversion:

dotnet add package Acornima.Extras

2. Import the Acornima namespace in your application

using Acornima;

3. Create a parser instance

var parser = new Parser();

Or, if you want to tweak the available settings:

var parser = new Parser(new ParserOptions { /* ... */ });

4. Use the parser instance to parse your JavaScript code

var ast = parser.ParseScript("console.log('Hello world!')");

AST

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:

JSX

The library also supports the syntax extension 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.

Installation & usage

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!</>");

AST

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]

Migration from Esprima.NET

Projects using Esprima.NET can be converted to Acornima relatively easily as the public API of the two libraries are very similar. (A pretty good proof of this statement is this PR, which migrates Jint to Acornima.)

The most notable changes to keep in mind with regard to migration are the following:

Benchmarks

MethodRuntimeFileNameMeanAllocated
Acornima v1.0.0.NET 8.0angular-1.2.510.679 ms3978.22 KB
Acornima v1.0.0.NET Framework 4.8angular-1.2.522.905 ms3999.01 KB
Esprima v3.0.5.NET 8.0angular-1.2.511.443 ms3828.11 KB
Esprima v3.0.5.NET Framework 4.8angular-1.2.520.483 ms3879.53 KB
Acornima v1.0.0.NET 8.0backbone-1.1.01.428 ms629.26 KB
Acornima v1.0.0.NET Framework 4.8backbone-1.1.03.218 ms633.09 KB
Esprima v3.0.5.NET 8.0backbone-1.1.01.440 ms613.88 KB
Esprima v3.0.5.NET Framework 4.8backbone-1.1.02.903 ms620.3 KB
Acornima v1.0.0.NET 8.0jquery-1.9.18.066 ms3271.63 KB
Acornima v1.0.0.NET Framework 4.8jquery-1.9.118.210 ms3288.41 KB
Esprima v3.0.5.NET 8.0jquery-1.9.18.391 ms3305.23 KB
Esprima v3.0.5.NET Framework 4.8jquery-1.9.116.456 ms3355.15 KB
Acornima v1.0.0.NET 8.0jquery.mobile-1.4.214.253 ms5449.24 KB
Acornima v1.0.0.NET Framework 4.8jquery.mobile-1.4.229.750 ms5480.16 KB
Esprima v3.0.5.NET 8.0jquery.mobile-1.4.214.566 ms5428.48 KB
Esprima v3.0.5.NET Framework 4.8jquery.mobile-1.4.227.084 ms5497.48 KB
Acornima v1.0.0.NET 8.0mootools-1.4.56.735 ms2755.9 KB
Acornima v1.0.0.NET Framework 4.8mootools-1.4.514.818 ms2771.45 KB
Esprima v3.0.5.NET 8.0mootools-1.4.56.877 ms2777.83 KB
Esprima v3.0.5.NET Framework 4.8mootools-1.4.513.740 ms2816.33 KB
Acornima v1.0.0.NET 8.0underscore-1.5.21.214 ms529.61 KB
Acornima v1.0.0.NET Framework 4.8underscore-1.5.22.775 ms532.29 KB
Esprima v3.0.5.NET 8.0underscore-1.5.21.235 ms539.42 KB
Esprima v3.0.5.NET Framework 4.8underscore-1.5.22.501 ms547.18 KB
Acornima v1.0.0.NET 8.0yui-3.12.06.408 ms2611.82 KB
Acornima v1.0.0.NET Framework 4.8yui-3.12.013.831 ms2628.61 KB
Esprima v3.0.5.NET 8.0yui-3.12.06.667 ms2585.78 KB
Esprima v3.0.5.NET Framework 4.8yui-3.12.012.636 ms2624.92 KB

Known issues and limitations

Regular expressions

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):

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.

Any feedback appreciated, contributions are welcome!