Skip to content

Commit

Permalink
Add import attributes feature (#381)
Browse files Browse the repository at this point in the history
* Revert "Remove import assertions feature (#377)"

This reverts commit 695825c.

* Apply syntax ("with" keyword instead of "assert") and ESTree naming changes ("Attributes" properties instead of "Assertions")

* Fix tests
  • Loading branch information
adams85 authored Jul 28, 2023
1 parent f1401e2 commit 755bac3
Show file tree
Hide file tree
Showing 28 changed files with 921 additions and 69 deletions.
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

0 comments on commit 755bac3

Please sign in to comment.