Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add import attributes feature #381

Merged
merged 3 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/Esprima/Ast/ExportAllDeclaration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,32 @@

namespace Esprima.Ast;

[VisitableNode(ChildProperties = new[] { nameof(Exported), nameof(Source) })]
[VisitableNode(ChildProperties = new[] { nameof(Exported), nameof(Source), nameof(Attributes) })]
public sealed partial class ExportAllDeclaration : ExportDeclaration
{
public ExportAllDeclaration(Literal source) : this(source, null)
private readonly NodeList<ImportAttribute> _attributes;

public ExportAllDeclaration(Literal source) : this(source, null, new NodeList<ImportAttribute>())
{
}

public ExportAllDeclaration(Literal source, Expression? exported) : base(Nodes.ExportAllDeclaration)
public ExportAllDeclaration(Literal source, Expression? exported, in NodeList<ImportAttribute> attributes) : base(Nodes.ExportAllDeclaration)
{
Source = source;
Exported = exported;
_attributes = attributes;
}

public Literal Source { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
/// <remarks>
/// <see cref="Identifier"/> | <see cref="Literal"/> (string)
/// </remarks>
public Expression? Exported { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public ref readonly NodeList<ImportAttribute> Attributes { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _attributes; }

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ExportAllDeclaration Rewrite(Expression? exported, Literal source)
private ExportAllDeclaration Rewrite(Expression? exported, Literal source, in NodeList<ImportAttribute> attributes)
{
return new ExportAllDeclaration(source, exported);
return new ExportAllDeclaration(source, exported, attributes);
}
}
12 changes: 8 additions & 4 deletions src/Esprima/Ast/ExportNamedDeclaration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@

namespace Esprima.Ast;

[VisitableNode(ChildProperties = new[] { nameof(Declaration), nameof(Specifiers), nameof(Source) })]
[VisitableNode(ChildProperties = new[] { nameof(Declaration), nameof(Specifiers), nameof(Source), nameof(Attributes) })]
public sealed partial class ExportNamedDeclaration : ExportDeclaration
{
private readonly NodeList<ExportSpecifier> _specifiers;
private readonly NodeList<ImportAttribute> _attributes;

public ExportNamedDeclaration(
Declaration? declaration,
in NodeList<ExportSpecifier> specifiers,
Literal? source)
Literal? source,
in NodeList<ImportAttribute> attributes)
: base(Nodes.ExportNamedDeclaration)
{
Declaration = declaration;
_specifiers = specifiers;
Source = source;
_attributes = attributes;
}

/// <remarks>
Expand All @@ -24,10 +27,11 @@ public ExportNamedDeclaration(
public Declaration? Declaration { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public ref readonly NodeList<ExportSpecifier> Specifiers { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _specifiers; }
public Literal? Source { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public ref readonly NodeList<ImportAttribute> Attributes { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _attributes; }

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ExportNamedDeclaration Rewrite(Declaration? declaration, in NodeList<ExportSpecifier> specifiers, Literal? source)
private ExportNamedDeclaration Rewrite(Declaration? declaration, in NodeList<ExportSpecifier> specifiers, Literal? source, in NodeList<ImportAttribute> attributes)
{
return new ExportNamedDeclaration(declaration, specifiers, source);
return new ExportNamedDeclaration(declaration, specifiers, source, attributes);
}
}
25 changes: 25 additions & 0 deletions src/Esprima/Ast/ImportAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Runtime.CompilerServices;

namespace Esprima.Ast;

[VisitableNode(ChildProperties = new[] { nameof(Key), nameof(Value) })]
public sealed partial class ImportAttribute : Node
{
public ImportAttribute(Expression key, Literal value) : base(Nodes.ImportAttribute)
{
Key = key;
Value = value;
}

/// <remarks>
/// <see cref="Identifier"/> | <see cref="Literal"/> (string or numeric)
/// </remarks>
public Expression Key { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public Literal Value { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ImportAttribute Rewrite(Expression key, Literal value)
{
return new ImportAttribute(key, value);
}
}
12 changes: 8 additions & 4 deletions src/Esprima/Ast/ImportDeclaration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,30 @@

namespace Esprima.Ast;

[VisitableNode(ChildProperties = new[] { nameof(Specifiers), nameof(Source) })]
[VisitableNode(ChildProperties = new[] { nameof(Specifiers), nameof(Source), nameof(Attributes) })]
public sealed partial class ImportDeclaration : Declaration
{
private readonly NodeList<ImportDeclarationSpecifier> _specifiers;
private readonly NodeList<ImportAttribute> _attributes;

public ImportDeclaration(
in NodeList<ImportDeclarationSpecifier> specifiers,
Literal source)
Literal source,
in NodeList<ImportAttribute> attributes)
: base(Nodes.ImportDeclaration)
{
_specifiers = specifiers;
Source = source;
_attributes = attributes;
}

public ref readonly NodeList<ImportDeclarationSpecifier> Specifiers { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _specifiers; }
public Literal Source { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public ref readonly NodeList<ImportAttribute> Attributes { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _attributes; }

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ImportDeclaration Rewrite(in NodeList<ImportDeclarationSpecifier> specifiers, Literal source)
private ImportDeclaration Rewrite(in NodeList<ImportDeclarationSpecifier> specifiers, Literal source, in NodeList<ImportAttribute> attributes)
{
return new ImportDeclaration(specifiers, source);
return new ImportDeclaration(specifiers, source, attributes);
}
}
14 changes: 10 additions & 4 deletions src/Esprima/Ast/ImportExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@

namespace Esprima.Ast;

[VisitableNode(ChildProperties = new[] { nameof(Source) })]
[VisitableNode(ChildProperties = new[] { nameof(Source), nameof(Options) })]
public sealed partial class ImportExpression : Expression
{
public ImportExpression(Expression source) : base(Nodes.ImportExpression)
public ImportExpression(Expression source) : this(source, null)
{
}

public ImportExpression(Expression source, Expression? options) : base(Nodes.ImportExpression)
{
Source = source;
Options = options;
}

public Expression Source { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public Expression? Options { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ImportExpression Rewrite(Expression source)
private ImportExpression Rewrite(Expression source, Expression? options)
{
return new ImportExpression(source);
return new ImportExpression(source, options);
}
}
1 change: 1 addition & 0 deletions src/Esprima/Ast/Nodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public enum Nodes
ImportSpecifier,
ImportDefaultSpecifier,
ImportNamespaceSpecifier,
ImportAttribute,
ImportDeclaration,
ExportSpecifier,
ExportNamedDeclaration,
Expand Down
85 changes: 79 additions & 6 deletions src/Esprima/JavascriptParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1725,6 +1725,15 @@ private ImportExpression ParseImportCall()

var source = ParseAssignmentExpression();

Expression? attributes = null;
if (Match(","))
{
NextToken();

if (!Match(")"))
attributes = ParseAssignmentExpression();
}

_context.IsAssignmentTarget = previousIsAssignmentTarget;

if (!this.Match(")") && _tolerant)
Expand All @@ -1738,7 +1747,7 @@ private ImportExpression ParseImportCall()
this.Expect(")");
}

return Finalize(node, new ImportExpression(source));
return Finalize(node, new ImportExpression(source, attributes));
}

private bool MatchImportMeta()
Expand Down Expand Up @@ -5057,6 +5066,66 @@ private Literal ParseModuleSpecifier()
return Finalize(node, new Literal((string) token.Value!, raw));
}

private ArrayList<ImportAttribute> ParseImportAttributes()
{
var attributes = new ArrayList<ImportAttribute>();
if (!MatchKeyword("with"))
{
return attributes;
}

NextToken();
Expect("{");

var parameterSet = new HashSet<string?>();
while (!Match("}"))
{
var importAttribute = ParseImportAttribute();

string? key = string.Empty;
switch (importAttribute.Key)
{
case Identifier identifier:
key = identifier.Name;
break;
case Literal literal:
key = literal.StringValue;
break;
}

if (!parameterSet.Add(key))
{
ThrowError(Messages.DuplicateKeyInImportAttributes, key);
}

attributes.Add(importAttribute);
if (!Match("}"))
{
ExpectCommaSeparator();
}
}
Expect("}");
return attributes;
}

private ImportAttribute ParseImportAttribute()
{
var node = CreateNode();

Expression key = ParseObjectPropertyKey();
if (!Match(":"))
{
ThrowUnexpectedToken(NextToken());
}

NextToken();
var literalToken = NextToken();
var raw = GetTokenRaw(literalToken);
Literal value = Finalize(node, new Literal((string) literalToken.Value!, raw));

return Finalize(node, new ImportAttribute(key, value));
}

// import {<foo as bar>} ...;
private ImportSpecifier ParseImportSpecifier()
{
Expand Down Expand Up @@ -5207,9 +5276,10 @@ private ImportDeclaration ParseImportDeclaration()
src = ParseModuleSpecifier();
}

var attributes = ParseImportAttributes();
ConsumeSemicolon();

return Finalize(node, new ImportDeclaration(NodeList.From(ref specifiers), src));
return Finalize(node, new ImportDeclaration(NodeList.From(ref specifiers), src, NodeList.From(ref attributes)));
}

// https://tc39.github.io/ecma262/#sec-exports
Expand Down Expand Up @@ -5331,8 +5401,9 @@ private ExportDeclaration ParseExportDeclaration()

NextToken();
var src = ParseModuleSpecifier();
var attributes = ParseImportAttributes();
ConsumeSemicolon();
exportDeclaration = Finalize(node, new ExportAllDeclaration(src, exported));
exportDeclaration = Finalize(node, new ExportAllDeclaration(src, exported, NodeList.From(ref attributes)));
}
else if (_lookahead.Type == TokenType.Keyword)
{
Expand All @@ -5354,18 +5425,19 @@ private ExportDeclaration ParseExportDeclaration()
break;
}

exportDeclaration = Finalize(node, new ExportNamedDeclaration(declaration.As<Declaration>(), new NodeList<ExportSpecifier>(), null));
exportDeclaration = Finalize(node, new ExportNamedDeclaration(declaration.As<Declaration>(), new NodeList<ExportSpecifier>(), null, new NodeList<ImportAttribute>()));
}
else if (MatchAsyncFunction())
{
var declaration = ParseFunctionDeclaration();
exportDeclaration = Finalize(node, new ExportNamedDeclaration(declaration, new NodeList<ExportSpecifier>(), null));
exportDeclaration = Finalize(node, new ExportNamedDeclaration(declaration, new NodeList<ExportSpecifier>(), null, new NodeList<ImportAttribute>()));
}
else
{
var specifiers = new ArrayList<ExportSpecifier>();
Literal? source = null;
var isExportFromIdentifier = false;
ArrayList<ImportAttribute> attributes = new();

Expect("{");
while (!Match("}"))
Expand All @@ -5386,6 +5458,7 @@ private ExportDeclaration ParseExportDeclaration()
// export {foo} from 'foo';
NextToken();
source = ParseModuleSpecifier();
attributes = ParseImportAttributes();
ConsumeSemicolon();
}
else if (isExportFromIdentifier)
Expand All @@ -5400,7 +5473,7 @@ private ExportDeclaration ParseExportDeclaration()
ConsumeSemicolon();
}

exportDeclaration = Finalize(node, new ExportNamedDeclaration(null, NodeList.From(ref specifiers), source));
exportDeclaration = Finalize(node, new ExportNamedDeclaration(null, NodeList.From(ref specifiers), source, NodeList.From(ref attributes)));
}

return exportDeclaration;
Expand Down
2 changes: 2 additions & 0 deletions src/Esprima/Messages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ internal static class Messages
public const string DuplicateConstructor = "A class may only have one constructor";
public const string DuplicateParameter = "Duplicate parameter name not allowed in this context";
public const string DuplicateProtoProperty = "Duplicate __proto__ fields are not allowed in object literals";
// TODO: Replace this with the actual V8 message once it becomes available (see https://github.com/v8/v8/blob/main/src/common/message-template.h).
public const string DuplicateKeyInImportAttributes = "Import attributes has duplicate key '{0}'";
public const string ForInOfLoopInitializer = "'{0} loop variable declaration may not have an initializer";
public const string GeneratorInLegacyContext = "Generator declarations are not allowed in legacy contexts";
public const string IllegalBreak = "Illegal break statement";
Expand Down
13 changes: 13 additions & 0 deletions src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,19 @@ protected void VisitExpressionListItem(Expression expression, int index, int cou
_currentExpressionFlags = originalExpressionFlags;
}

private void VisitImportAttributes(in NodeList<ImportAttribute> attributes)
{
// https://github.com/tc39/proposal-import-attributes#import-statements

Writer.WriteKeyword("with", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);

Writer.StartObject(attributes.Count, ref _writeContext);

VisitAuxiliaryNodeList(in attributes, separator: ",");

Writer.EndObject(attributes.Count, ref _writeContext);
}

private void VisitExportOrImportSpecifierIdentifier(Expression identifierExpression)
{
if (identifierExpression is Identifier { Name: "default" } identifier)
Expand Down
Loading