From 40b54b07ce0987ac4e51a5ce26a5509932071439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20K=C3=BChner?= Date: Mon, 1 Nov 2021 21:04:54 +0100 Subject: [PATCH] support es2022 class properties --- src/Esprima/Ast/Nodes.cs | 2 + src/Esprima/Ast/PrivateIdentifier.cs | 21 +++ src/Esprima/Ast/PropertyDefinition.cs | 27 +++ src/Esprima/Ast/PropertyKind.cs | 5 +- src/Esprima/JavascriptParser.cs | 125 ++++++++++--- src/Esprima/Utils/AstVisitor.cs | 14 ++ ...hod.js => async-line-terminator-method.js} | 0 .../async-line-terminator-method.tree.json | 171 ++++++++++++++++++ ...=> async-line-terminator-static-method.js} | 0 ...nc-line-terminator-static-method.tree.json | 171 ++++++++++++++++++ ...-async-line-terminator-method.failure.json | 1 - ...line-terminator-static-method.failure.json | 1 - 12 files changed, 512 insertions(+), 26 deletions(-) create mode 100644 src/Esprima/Ast/PrivateIdentifier.cs create mode 100644 src/Esprima/Ast/PropertyDefinition.cs rename test/Esprima.Tests/Fixtures/ES2017/async/methods/{invalid-async-line-terminator-method.js => async-line-terminator-method.js} (100%) create mode 100644 test/Esprima.Tests/Fixtures/ES2017/async/methods/async-line-terminator-method.tree.json rename test/Esprima.Tests/Fixtures/ES2017/async/methods/{invalid-async-line-terminator-static-method.js => async-line-terminator-static-method.js} (100%) create mode 100644 test/Esprima.Tests/Fixtures/ES2017/async/methods/async-line-terminator-static-method.tree.json delete mode 100644 test/Esprima.Tests/Fixtures/ES2017/async/methods/invalid-async-line-terminator-method.failure.json delete mode 100644 test/Esprima.Tests/Fixtures/ES2017/async/methods/invalid-async-line-terminator-static-method.failure.json diff --git a/src/Esprima/Ast/Nodes.cs b/src/Esprima/Ast/Nodes.cs index 370330c7..2f2046a8 100644 --- a/src/Esprima/Ast/Nodes.cs +++ b/src/Esprima/Ast/Nodes.cs @@ -29,8 +29,10 @@ public enum Nodes MemberExpression, NewExpression, ObjectExpression, + PrivateIdentifier, Program, Property, + PropertyDefinition, RestElement, ReturnStatement, SequenceExpression, diff --git a/src/Esprima/Ast/PrivateIdentifier.cs b/src/Esprima/Ast/PrivateIdentifier.cs new file mode 100644 index 00000000..75f2e08a --- /dev/null +++ b/src/Esprima/Ast/PrivateIdentifier.cs @@ -0,0 +1,21 @@ +using Esprima.Utils; + +namespace Esprima.Ast +{ + public sealed class PrivateIdentifier : Expression + { + public readonly string? Name; + + public PrivateIdentifier(string? name) : base(Nodes.PrivateIdentifier) + { + Name = name; + } + + public override NodeCollection ChildNodes => NodeCollection.Empty; + + protected internal override void Accept(AstVisitor visitor) + { + visitor.VisitPrivateIdentifier(this); + } + } +} diff --git a/src/Esprima/Ast/PropertyDefinition.cs b/src/Esprima/Ast/PropertyDefinition.cs new file mode 100644 index 00000000..7615499f --- /dev/null +++ b/src/Esprima/Ast/PropertyDefinition.cs @@ -0,0 +1,27 @@ +using Esprima.Utils; + +namespace Esprima.Ast +{ + public sealed class PropertyDefinition : ClassProperty + { + public readonly bool Static; + + public PropertyDefinition( + Expression key, + bool computed, + Expression value, + bool isStatic) + : base(Nodes.PropertyDefinition) + { + Static = isStatic; + Key = key; + Computed = computed; + Value = value; + } + + protected internal override void Accept(AstVisitor visitor) + { + visitor.VisitPropertyDefinition(this); + } + } +} diff --git a/src/Esprima/Ast/PropertyKind.cs b/src/Esprima/Ast/PropertyKind.cs index c4984537..ad2d5337 100644 --- a/src/Esprima/Ast/PropertyKind.cs +++ b/src/Esprima/Ast/PropertyKind.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Esprima.Ast { @@ -11,6 +11,7 @@ public enum PropertyKind Set = 4, Init = 8, Constructor = 16, - Method = 32 + Method = 32, + Property = 64 }; } diff --git a/src/Esprima/JavascriptParser.cs b/src/Esprima/JavascriptParser.cs index fd2b8869..e0ec331a 100644 --- a/src/Esprima/JavascriptParser.cs +++ b/src/Esprima/JavascriptParser.cs @@ -665,6 +665,10 @@ private Expression ParsePrimaryExpression() { expr = ParseClassExpression(); } + else if (MatchKeyword("new")) + { + expr = ParseNewExpression(); + } else if (MatchImportCall()) { expr = ParseImportCall(); @@ -833,7 +837,7 @@ private FunctionExpression ParsePropertyMethodAsyncFunction(bool isGenerator) return Finalize(node, new FunctionExpression(null, NodeList.From(ref parameters.Parameters), method, isGenerator, hasStrictDirective, true)); } - private Expression ParseObjectPropertyKey() + private Expression ParseObjectPropertyKey(Boolean isPrivate = false) { var node = CreateNode(); var token = NextToken(); @@ -865,7 +869,7 @@ private Expression ParseObjectPropertyKey() case TokenType.BooleanLiteral: case TokenType.NullLiteral: case TokenType.Keyword: - key = Finalize(node, new Identifier((string?) token.Value)); + key = isPrivate ? Finalize(node, new PrivateIdentifier((string?) token.Value)) : Finalize(node, new Identifier((string?) token.Value)); break; case TokenType.Punctuator: @@ -963,7 +967,7 @@ private Property ParseObjectProperty(Token hasProto) kind = PropertyKind.Init; computed = Match("["); key = ParseObjectPropertyKey(); - value = ParseGeneratorMethod(); + value = ParseGeneratorMethod(isAsync); method = true; } else @@ -1419,6 +1423,29 @@ private Identifier ParseIdentifierName() return Finalize(node, new Identifier((string?) token.Value)); } + private Expression ParseIdentifierOrPrivateIdentifierName() + { + var isPrivateField = false; + + var node = CreateNode(); + + var token = NextToken(); + + if (Equals(token.Value, "#")) + { + token = NextToken(); + token.Value = '#' + (string?)token.Value; + isPrivateField = true; + } + + if (!IsIdentifierName(token)) + { + return ThrowUnexpectedToken(token); + } + + return isPrivateField ? Finalize(node, new PrivateIdentifier((string?) token.Value)) : Finalize(node, new Identifier((string?) token.Value)); + } + private Expression ParseNewExpression() { var node = CreateNode(); @@ -1655,7 +1682,7 @@ private Expression ParseLeftHandSideExpressionAllowCall() Expect("."); } - var property = ParseIdentifierName(); + var property = ParseIdentifierOrPrivateIdentifierName(); expr = Finalize(StartNode(startToken), new StaticMemberExpression(expr, property, optional)); } else @@ -4143,7 +4170,7 @@ private static bool QualifiedPropertyName(Token token) TokenType.NullLiteral => true, TokenType.NumericLiteral => true, TokenType.Keyword => true, - TokenType.Punctuator => Equals(token.Value, "["), + TokenType.Punctuator => Equals(token.Value, "[") || Equals(token.Value, "#"), _ => false }; } @@ -4191,7 +4218,7 @@ private FunctionExpression ParseSetterMethod() return Finalize(node, new FunctionExpression(null, NodeList.From(ref formalParameters.Parameters), method, isGenerator, hasStrictDirective, false)); } - private FunctionExpression ParseGeneratorMethod() + private FunctionExpression ParseGeneratorMethod(bool isAsync = false) { var node = CreateNode(); @@ -4203,7 +4230,7 @@ private FunctionExpression ParseGeneratorMethod() var method = ParsePropertyMethod(parameters, out var hasStrictDirective); _context.AllowYield = previousAllowYield; - return Finalize(node, new FunctionExpression(null, NodeList.From(ref parameters.Parameters), method, true, hasStrictDirective, false)); + return Finalize(node, new FunctionExpression(null, NodeList.From(ref parameters.Parameters), method, true, hasStrictDirective, isAsync)); } // https://tc39.github.io/ecma262/#sec-generator-function-definitions @@ -4300,12 +4327,13 @@ private ClassProperty ParseClassElement(ref bool hasConstructor) var kind = PropertyKind.None; Expression? key = null; - FunctionExpression? value = null; + Expression? value = null; var computed = false; var method = false; var isStatic = false; var isAsync = false; - var isGenerator = false; + var isGenerator = false; + var isPrivate = false; if (Match("*")) { @@ -4315,10 +4343,17 @@ private ClassProperty ParseClassElement(ref bool hasConstructor) else { computed = Match("["); - key = ParseObjectPropertyKey(); + if (Match("#")) + { + isPrivate = true; + NextToken(); + token = _lookahead; + } + key = ParseObjectPropertyKey(isPrivate); var id = key switch { Identifier identifier => identifier.Name, + PrivateIdentifier privateIdentifier => privateIdentifier.Name, Literal literal => literal.StringValue, // "constructor" _ => null }; @@ -4331,9 +4366,21 @@ private ClassProperty ParseClassElement(ref bool hasConstructor) if (Match("*")) { NextToken(); + if (Match("#")) + { + isPrivate = true; + NextToken(); + token = _lookahead; + } } else { + if (Match("#")) + { + isPrivate = true; + NextToken(); + token = _lookahead; + } key = ParseObjectPropertyKey(); } } @@ -4347,11 +4394,17 @@ private ClassProperty ParseClassElement(ref bool hasConstructor) if (isGenerator) { NextToken(); + } + + if (Match("#")) + { + isPrivate = true; + NextToken(); } token = _lookahead; computed = Match("["); - key = ParseObjectPropertyKey(); + key = ParseObjectPropertyKey(isPrivate); if (token.Type == TokenType.Identifier && (string?) token.Value == "constructor") { TolerateUnexpectedToken(token, Messages.ConstructorIsAsync); @@ -4365,26 +4418,49 @@ private ClassProperty ParseClassElement(ref bool hasConstructor) { if (lookaheadPropertyKey && (string?) token.Value == "get") { - kind = PropertyKind.Get; + kind = PropertyKind.Get; + if (Match("#")) + { + isPrivate = true; + NextToken(); + token = _lookahead; + } computed = Match("["); - key = ParseObjectPropertyKey(); + key = ParseObjectPropertyKey(isPrivate); _context.AllowYield = false; value = ParseGetterMethod(); } else if (lookaheadPropertyKey && (string?) token.Value == "set") { kind = PropertyKind.Set; + if (Match("#")) + { + isPrivate = true; + NextToken(); + token = _lookahead; + } computed = Match("["); - key = ParseObjectPropertyKey(); - value = ParseSetterMethod(); + key = ParseObjectPropertyKey(isPrivate); + value = ParseSetterMethod(); + } + else if (!Match("(")) + { + kind = PropertyKind.Property; + computed = false; + + if (Match("=")) + { + NextToken(); + value = IsolateCoverGrammar(this.parseAssignmentExpression); + } } } else if (token.Type == TokenType.Punctuator && (string?) token.Value == "*" && lookaheadPropertyKey) { kind = PropertyKind.Init; computed = Match("["); - key = ParseObjectPropertyKey(); - value = ParseGeneratorMethod(); + key = ParseObjectPropertyKey(isPrivate); + value = ParseGeneratorMethod(isAsync); method = true; } @@ -4420,7 +4496,7 @@ private ClassProperty ParseClassElement(ref bool hasConstructor) if (!isStatic && IsPropertyKey(key!, "constructor")) { - if (kind != PropertyKind.Method || !method || value!.Generator) + if (kind != PropertyKind.Method || !method || ((FunctionExpression)value!).Generator) { ThrowUnexpectedToken(token, Messages.ConstructorSpecialMethod); } @@ -4436,10 +4512,15 @@ private ClassProperty ParseClassElement(ref bool hasConstructor) kind = PropertyKind.Constructor; } - } - - - return Finalize(node, new MethodDefinition(key!, computed, value!, kind, isStatic)); + } + + if (kind == PropertyKind.Property) + { + ConsumeSemicolon(); + return Finalize(node, new PropertyDefinition(key!, computed, value!, isStatic)); + } + + return Finalize(node, new MethodDefinition(key!, computed, (FunctionExpression)value!, kind, isStatic)); } private ArrayList ParseClassElementList() diff --git a/src/Esprima/Utils/AstVisitor.cs b/src/Esprima/Utils/AstVisitor.cs index a5aaf6ed..ecb02d29 100644 --- a/src/Esprima/Utils/AstVisitor.cs +++ b/src/Esprima/Utils/AstVisitor.cs @@ -258,6 +258,10 @@ protected internal virtual void VisitIdentifier(Identifier identifier) { } + protected internal virtual void VisitPrivateIdentifier(PrivateIdentifier privateIdentifier) + { + } + protected internal virtual void VisitFunctionExpression(IFunction function) { if (function.Id is not null) @@ -366,6 +370,16 @@ protected internal virtual void VisitMethodDefinition(MethodDefinition methodDef Visit(methodDefinition.Value); } + protected internal virtual void VisitPropertyDefinition(PropertyDefinition propertyDefinition) + { + Visit(propertyDefinition.Key); + + if (propertyDefinition.Value is not null) + { + Visit(propertyDefinition.Value); + } + } + protected internal virtual void VisitForOfStatement(ForOfStatement forOfStatement) { Visit(forOfStatement.Left); diff --git a/test/Esprima.Tests/Fixtures/ES2017/async/methods/invalid-async-line-terminator-method.js b/test/Esprima.Tests/Fixtures/ES2017/async/methods/async-line-terminator-method.js similarity index 100% rename from test/Esprima.Tests/Fixtures/ES2017/async/methods/invalid-async-line-terminator-method.js rename to test/Esprima.Tests/Fixtures/ES2017/async/methods/async-line-terminator-method.js diff --git a/test/Esprima.Tests/Fixtures/ES2017/async/methods/async-line-terminator-method.tree.json b/test/Esprima.Tests/Fixtures/ES2017/async/methods/async-line-terminator-method.tree.json new file mode 100644 index 00000000..4641f5ad --- /dev/null +++ b/test/Esprima.Tests/Fixtures/ES2017/async/methods/async-line-terminator-method.tree.json @@ -0,0 +1,171 @@ +{ + "type": "Program", + "body": [ + { + "type": "ClassDeclaration", + "id": { + "type": "Identifier", + "name": "X", + "range": [ + 6, + 7 + ], + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 7 + } + } + }, + "superClass": null, + "body": { + "type": "ClassBody", + "body": [ + { + "type": "Identifier", + "name": "async", + "range": [ + 10, + 15 + ], + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 15 + } + } + }, + { + "type": "MethodDefinition", + "key": { + "type": "Identifier", + "name": "f", + "range": [ + 16, + 17 + ], + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 1 + } + } + }, + "computed": false, + "value": { + "type": "FunctionExpression", + "id": null, + "params": [], + "body": { + "type": "BlockStatement", + "body": [], + "range": [ + 19, + 21 + ], + "loc": { + "start": { + "line": 2, + "column": 3 + }, + "end": { + "line": 2, + "column": 5 + } + } + }, + "generator": false, + "expression": false, + "async": false, + "range": [ + 17, + 21 + ], + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 5 + } + } + }, + "kind": "method", + "static": false, + "range": [ + 16, + 21 + ], + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 5 + } + } + } + ], + "range": [ + 8, + 23 + ], + "loc": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 2, + "column": 7 + } + } + }, + "range": [ + 0, + 23 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 7 + } + } + } + ], + "sourceType": "script", + "range": [ + 0, + 23 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 7 + } + } +} \ No newline at end of file diff --git a/test/Esprima.Tests/Fixtures/ES2017/async/methods/invalid-async-line-terminator-static-method.js b/test/Esprima.Tests/Fixtures/ES2017/async/methods/async-line-terminator-static-method.js similarity index 100% rename from test/Esprima.Tests/Fixtures/ES2017/async/methods/invalid-async-line-terminator-static-method.js rename to test/Esprima.Tests/Fixtures/ES2017/async/methods/async-line-terminator-static-method.js diff --git a/test/Esprima.Tests/Fixtures/ES2017/async/methods/async-line-terminator-static-method.tree.json b/test/Esprima.Tests/Fixtures/ES2017/async/methods/async-line-terminator-static-method.tree.json new file mode 100644 index 00000000..d035925c --- /dev/null +++ b/test/Esprima.Tests/Fixtures/ES2017/async/methods/async-line-terminator-static-method.tree.json @@ -0,0 +1,171 @@ +{ + "type": "Program", + "body": [ + { + "type": "ClassDeclaration", + "id": { + "type": "Identifier", + "name": "X", + "range": [ + 6, + 7 + ], + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 7 + } + } + }, + "superClass": null, + "body": { + "type": "ClassBody", + "body": [ + { + "type": "Identifier", + "name": "async", + "range": [ + 17, + 22 + ], + "loc": { + "start": { + "line": 1, + "column": 17 + }, + "end": { + "line": 1, + "column": 22 + } + } + }, + { + "type": "MethodDefinition", + "key": { + "type": "Identifier", + "name": "f", + "range": [ + 23, + 24 + ], + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 1 + } + } + }, + "computed": false, + "value": { + "type": "FunctionExpression", + "id": null, + "params": [], + "body": { + "type": "BlockStatement", + "body": [], + "range": [ + 26, + 28 + ], + "loc": { + "start": { + "line": 2, + "column": 3 + }, + "end": { + "line": 2, + "column": 5 + } + } + }, + "generator": false, + "expression": false, + "async": false, + "range": [ + 24, + 28 + ], + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 5 + } + } + }, + "kind": "method", + "static": false, + "range": [ + 23, + 28 + ], + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 5 + } + } + } + ], + "range": [ + 8, + 30 + ], + "loc": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 2, + "column": 7 + } + } + }, + "range": [ + 0, + 30 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 7 + } + } + } + ], + "sourceType": "script", + "range": [ + 0, + 30 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 7 + } + } +} \ No newline at end of file diff --git a/test/Esprima.Tests/Fixtures/ES2017/async/methods/invalid-async-line-terminator-method.failure.json b/test/Esprima.Tests/Fixtures/ES2017/async/methods/invalid-async-line-terminator-method.failure.json deleted file mode 100644 index 10acdf05..00000000 --- a/test/Esprima.Tests/Fixtures/ES2017/async/methods/invalid-async-line-terminator-method.failure.json +++ /dev/null @@ -1 +0,0 @@ -{"index":16,"lineNumber":2,"column":17,"message":"Error: Line 2: Unexpected identifier","description":"Unexpected identifier"} \ No newline at end of file diff --git a/test/Esprima.Tests/Fixtures/ES2017/async/methods/invalid-async-line-terminator-static-method.failure.json b/test/Esprima.Tests/Fixtures/ES2017/async/methods/invalid-async-line-terminator-static-method.failure.json deleted file mode 100644 index c98a6e8e..00000000 --- a/test/Esprima.Tests/Fixtures/ES2017/async/methods/invalid-async-line-terminator-static-method.failure.json +++ /dev/null @@ -1 +0,0 @@ -{"index":23,"lineNumber":2,"column":24,"message":"Error: Line 2: Unexpected identifier","description":"Unexpected identifier"} \ No newline at end of file