From 68f28de1c45d586bb6de304be9760c4705579240 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Tue, 21 Dec 2021 01:04:05 +0200 Subject: [PATCH] Fix module import/export identifier parsing (#217) --- src/Esprima/Ast/ExportAllDeclaration.cs | 8 +++++-- src/Esprima/Ast/ExportSpecifier.cs | 13 ++++++++--- src/Esprima/Ast/ImportDeclarationSpecifier.cs | 11 +++++---- src/Esprima/Ast/ImportDefaultSpecifier.cs | 6 +---- src/Esprima/Ast/ImportNamespaceSpecifier.cs | 6 +---- src/Esprima/Ast/ImportSpecifier.cs | 11 ++++----- src/Esprima/JavascriptParser.cs | 23 ++++++++++++++----- test/Esprima.Tests/ParserTests.cs | 10 ++++++++ 8 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/Esprima/Ast/ExportAllDeclaration.cs b/src/Esprima/Ast/ExportAllDeclaration.cs index d09499f8..0ecb976e 100644 --- a/src/Esprima/Ast/ExportAllDeclaration.cs +++ b/src/Esprima/Ast/ExportAllDeclaration.cs @@ -5,13 +5,17 @@ namespace Esprima.Ast public sealed class ExportAllDeclaration : ExportDeclaration { public readonly Literal Source; - public readonly Identifier? Exported; + + /// + /// Identifier | StringLiteral + /// + public readonly Expression? Exported; public ExportAllDeclaration(Literal source) : this(source, null) { } - public ExportAllDeclaration(Literal source, Identifier? exported) : base(Nodes.ExportAllDeclaration) + public ExportAllDeclaration(Literal source, Expression? exported) : base(Nodes.ExportAllDeclaration) { Source = source; Exported = exported; diff --git a/src/Esprima/Ast/ExportSpecifier.cs b/src/Esprima/Ast/ExportSpecifier.cs index 01feeeca..a684a354 100644 --- a/src/Esprima/Ast/ExportSpecifier.cs +++ b/src/Esprima/Ast/ExportSpecifier.cs @@ -4,10 +4,17 @@ namespace Esprima.Ast { public sealed class ExportSpecifier : Statement { - public readonly Identifier Exported; - public readonly Identifier Local; + /// + /// Identifier | StringLiteral + /// + public readonly Expression Exported; - public ExportSpecifier(Identifier local, Identifier exported) : base(Nodes.ExportSpecifier) + /// + /// Identifier | StringLiteral + /// + public readonly Expression Local; + + public ExportSpecifier(Expression local, Expression exported) : base(Nodes.ExportSpecifier) { Exported = exported; Local = local; diff --git a/src/Esprima/Ast/ImportDeclarationSpecifier.cs b/src/Esprima/Ast/ImportDeclarationSpecifier.cs index 51820fae..1db2d068 100644 --- a/src/Esprima/Ast/ImportDeclarationSpecifier.cs +++ b/src/Esprima/Ast/ImportDeclarationSpecifier.cs @@ -2,11 +2,14 @@ { public abstract class ImportDeclarationSpecifier : Declaration { - protected ImportDeclarationSpecifier(Nodes type) : base(type) + /// + /// Identifier | StringLiteral + /// + public readonly Expression Local; + + protected ImportDeclarationSpecifier(Expression local, Nodes type) : base(type) { + Local = local; } - - public Identifier Local => LocalId; - protected abstract Identifier LocalId { get; } } } diff --git a/src/Esprima/Ast/ImportDefaultSpecifier.cs b/src/Esprima/Ast/ImportDefaultSpecifier.cs index 7cf3e163..42694fa8 100644 --- a/src/Esprima/Ast/ImportDefaultSpecifier.cs +++ b/src/Esprima/Ast/ImportDefaultSpecifier.cs @@ -4,12 +4,8 @@ namespace Esprima.Ast { public sealed class ImportDefaultSpecifier : ImportDeclarationSpecifier { - public new readonly Identifier Local; - protected override Identifier LocalId => Local; - - public ImportDefaultSpecifier(Identifier local) : base(Nodes.ImportDefaultSpecifier) + public ImportDefaultSpecifier(Identifier local) : base(local, Nodes.ImportDefaultSpecifier) { - Local = local; } public override NodeCollection ChildNodes => new(Local); diff --git a/src/Esprima/Ast/ImportNamespaceSpecifier.cs b/src/Esprima/Ast/ImportNamespaceSpecifier.cs index cd181f92..848edfad 100644 --- a/src/Esprima/Ast/ImportNamespaceSpecifier.cs +++ b/src/Esprima/Ast/ImportNamespaceSpecifier.cs @@ -4,12 +4,8 @@ namespace Esprima.Ast { public sealed class ImportNamespaceSpecifier : ImportDeclarationSpecifier { - public new readonly Identifier Local; - protected override Identifier LocalId => Local; - - public ImportNamespaceSpecifier(Identifier local) : base(Nodes.ImportNamespaceSpecifier) + public ImportNamespaceSpecifier(Identifier local) : base(local, Nodes.ImportNamespaceSpecifier) { - Local = local; } public override NodeCollection ChildNodes => new(Local); diff --git a/src/Esprima/Ast/ImportSpecifier.cs b/src/Esprima/Ast/ImportSpecifier.cs index 9ea30047..f92356f1 100644 --- a/src/Esprima/Ast/ImportSpecifier.cs +++ b/src/Esprima/Ast/ImportSpecifier.cs @@ -4,14 +4,13 @@ namespace Esprima.Ast { public sealed class ImportSpecifier : ImportDeclarationSpecifier { - public new readonly Identifier Local; - protected override Identifier LocalId => Local; + /// + /// Identifier | StringLiteral + /// + public readonly Expression Imported; - public readonly Identifier Imported; - - public ImportSpecifier(Identifier local, Identifier imported) : base(Nodes.ImportSpecifier) + public ImportSpecifier(Expression local, Expression imported) : base(local, Nodes.ImportSpecifier) { - Local = local; Imported = imported; } diff --git a/src/Esprima/JavascriptParser.cs b/src/Esprima/JavascriptParser.cs index 52e274db..bf8d3346 100644 --- a/src/Esprima/JavascriptParser.cs +++ b/src/Esprima/JavascriptParser.cs @@ -4672,7 +4672,8 @@ private ImportSpecifier ParseImportSpecifier() { var node = CreateNode(); - Identifier local, imported; + Expression local; + Expression imported; if (_lookahead.Type == TokenType.Identifier) { @@ -4686,7 +4687,10 @@ private ImportSpecifier ParseImportSpecifier() } else { - imported = ParseIdentifierName(); + imported = this._lookahead.Type == TokenType.StringLiteral + ? ParseModuleSpecifier() + : ParseIdentifierName(); + local = imported; if (MatchContextualKeyword("as")) { @@ -4824,12 +4828,17 @@ private ExportSpecifier ParseExportSpecifier() { var node = CreateNode(); - var local = ParseIdentifierName(); + Expression local = this._lookahead.Type == TokenType.StringLiteral + ? ParseModuleSpecifier() + : ParseIdentifierName(); + var exported = local; if (MatchContextualKeyword("as")) { NextToken(); - exported = ParseIdentifierName(); + exported = this._lookahead.Type == TokenType.StringLiteral + ? ParseModuleSpecifier() + : ParseIdentifierName(); } return Finalize(node, new ExportSpecifier(local, exported)); @@ -4898,11 +4907,13 @@ private ExportDeclaration ParseExportDeclaration() NextToken(); //export * as ns from 'foo' - Identifier? exported = null; + Expression? exported = null; if (MatchContextualKeyword("as")) { NextToken(); - exported = ParseIdentifierName(); + exported = this._lookahead.Type == TokenType.StringLiteral + ? ParseModuleSpecifier() + : ParseIdentifierName(); } if (!MatchContextualKeyword("from")) diff --git a/test/Esprima.Tests/ParserTests.cs b/test/Esprima.Tests/ParserTests.cs index 083595d9..25dd283e 100644 --- a/test/Esprima.Tests/ParserTests.cs +++ b/test/Esprima.Tests/ParserTests.cs @@ -104,6 +104,16 @@ public void ShouldParseNumericLiterals(object expected, string source) Assert.Equal(expected, literal.NumericValue); } + [Theory] + [InlineData("export { Mercury as \"☿\" } from \"./export-expname_FIXTURE.js\";")] + [InlineData("export * as \"All\" from \"./export-expname_FIXTURE.js\";")] + [InlineData("export { \"☿\" as Ami } from \"./export-expname_FIXTURE.js\"")] + [InlineData("import { \"☿\" as Ami } from \"./export-expname_FIXTURE.js\";")] + public void ShouldParseModuleImportExportWithStringIdentifiers(string source) + { + new JavaScriptParser(source).ParseModule(); + } + [Fact] public void ShouldParseClassInheritance() {