adams85 / acornima

Acornima is a standard-compliant JavaScript parser for .NET. It is a fork of Esprima.NET combined with the .NET port of the acornjs parser.
BSD 3-Clause "New" or "Revised" License
9 stars 3 forks source link

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

Method Runtime FileName Mean Allocated
Acornima v1.0.0 .NET 8.0 angular-1.2.5 10.679 ms 3978.22 KB
Acornima v1.0.0 .NET Framework 4.8 angular-1.2.5 22.905 ms 3999.01 KB
Esprima v3.0.5 .NET 8.0 angular-1.2.5 11.443 ms 3828.11 KB
Esprima v3.0.5 .NET Framework 4.8 angular-1.2.5 20.483 ms 3879.53 KB
Acornima v1.0.0 .NET 8.0 backbone-1.1.0 1.428 ms 629.26 KB
Acornima v1.0.0 .NET Framework 4.8 backbone-1.1.0 3.218 ms 633.09 KB
Esprima v3.0.5 .NET 8.0 backbone-1.1.0 1.440 ms 613.88 KB
Esprima v3.0.5 .NET Framework 4.8 backbone-1.1.0 2.903 ms 620.3 KB
Acornima v1.0.0 .NET 8.0 jquery-1.9.1 8.066 ms 3271.63 KB
Acornima v1.0.0 .NET Framework 4.8 jquery-1.9.1 18.210 ms 3288.41 KB
Esprima v3.0.5 .NET 8.0 jquery-1.9.1 8.391 ms 3305.23 KB
Esprima v3.0.5 .NET Framework 4.8 jquery-1.9.1 16.456 ms 3355.15 KB
Acornima v1.0.0 .NET 8.0 jquery.mobile-1.4.2 14.253 ms 5449.24 KB
Acornima v1.0.0 .NET Framework 4.8 jquery.mobile-1.4.2 29.750 ms 5480.16 KB
Esprima v3.0.5 .NET 8.0 jquery.mobile-1.4.2 14.566 ms 5428.48 KB
Esprima v3.0.5 .NET Framework 4.8 jquery.mobile-1.4.2 27.084 ms 5497.48 KB
Acornima v1.0.0 .NET 8.0 mootools-1.4.5 6.735 ms 2755.9 KB
Acornima v1.0.0 .NET Framework 4.8 mootools-1.4.5 14.818 ms 2771.45 KB
Esprima v3.0.5 .NET 8.0 mootools-1.4.5 6.877 ms 2777.83 KB
Esprima v3.0.5 .NET Framework 4.8 mootools-1.4.5 13.740 ms 2816.33 KB
Acornima v1.0.0 .NET 8.0 underscore-1.5.2 1.214 ms 529.61 KB
Acornima v1.0.0 .NET Framework 4.8 underscore-1.5.2 2.775 ms 532.29 KB
Esprima v3.0.5 .NET 8.0 underscore-1.5.2 1.235 ms 539.42 KB
Esprima v3.0.5 .NET Framework 4.8 underscore-1.5.2 2.501 ms 547.18 KB
Acornima v1.0.0 .NET 8.0 yui-3.12.0 6.408 ms 2611.82 KB
Acornima v1.0.0 .NET Framework 4.8 yui-3.12.0 13.831 ms 2628.61 KB
Esprima v3.0.5 .NET 8.0 yui-3.12.0 6.667 ms 2585.78 KB
Esprima v3.0.5 .NET Framework 4.8 yui-3.12.0 12.636 ms 2624.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!