Ver código fonte

* Export statement

Michaël Van Canneyt 3 anos atrás
pai
commit
dcf64d8fee

+ 122 - 24
packages/fcl-js/src/jsparser.pp

@@ -30,6 +30,7 @@ Const
    minLetVersion = ecma2021;
    MinDebuggerVersion = ecma2021;
    MinImportVersion = ecma2021;
+   MinExportVersion = ecma2021;
 
 Type
   TECMAVersion = jsScanner.TECMAVersion;
@@ -67,6 +68,7 @@ Type
     procedure LeaveLabel;
     function LookupLabel(ALabelName: String; Kind: TJSToken): TJSLabel;
     function ParseAdditiveExpression: TJSElement;
+    procedure ParseAliasElements(aElements: TJSAliasElements);
     function ParseArguments: TJSarguments;
     function ParseArrayLiteral: TJSElement;
     function ParseAssignmentExpression: TJSElement;
@@ -86,9 +88,11 @@ Type
     function ParseFunctionExpression: TJSFunctionDeclarationStatement;
     function ParseFunctionStatement: TJSElement;
     function ParseFunctionBody: TJSFunctionBody;
+    function ParseClassDeclaration : TJSClassDeclaration;
     function ParseIdentifier: String;
     function ParseIfStatement: TJSElement;
     function ParseImportStatement: TJSElement;
+    function ParseExportStatement: TJSElement;
     function ParseIterationStatement: TJSElement;
     function ParseLabeledStatement: TJSElement;
     function ParseLeftHandSideExpression: TJSElement;
@@ -178,6 +182,7 @@ Resourcestring
   SErrFunctionNotAllowedHere = 'function keyword not allowed here';
   SErrExpectedButFound       = 'Unexpected token. Expected "%s" but found "%s"';
   SErrExpectedMulOrCurlyBrace = 'Unexpected token: Expected * or {, got: %s';
+  SErrExpectedMulOrCurlyBraceOrDefault = 'Unexpected token: Expected * or { or default , got: %s';
 
 { TJSScanner }
 
@@ -1641,11 +1646,42 @@ begin
   end;
 end;
 
+Procedure TJSParser.ParseAliasElements(aElements : TJSAliasElements);
+// Parse { N [as M] }. On entry, must be on {, on exit curtoken is token after }
+Var
+  aName,aAlias : String;
+begin
+  Consume(tjsCurlyBraceOpen);
+  if (CurrentToken<>tjsCurlyBraceClose) then
+    begin
+    Repeat
+      Expect(tjsIdentifier);
+      aName:=CurrentTokenString;
+      aAlias:='';
+      Consume(tjsIdentifier);
+      if IdentifierIsLiteral('as') then
+        begin
+        Consume(tjsIdentifier);
+        Expect(tjsIdentifier);
+        aAlias:=CurrentTokenString;
+        Consume(tjsIdentifier);
+        end;
+      With aElements.AddAlias do
+        begin
+        Name:=aName;
+        Alias:=aAlias;
+        end;
+      if CurrentToken=tjsComma then
+        GetNextToken;
+    Until (CurrentToken=tjsCurlyBraceClose);
+    end;
+  Consume(tjsCurlyBraceClose);
+end;
+
 function TJSParser.ParseImportStatement: TJSElement;
 
 Var
   Imp : TJSImportStatement;
-  aName,aAlias : String;
   aExpectMore : Boolean;
 begin
   aExpectMore:=True;
@@ -1679,28 +1715,7 @@ begin
         end;
       tjsCurlyBraceOpen:
         begin
-        Consume(tjsCurlyBraceOpen);
-        Repeat
-          Expect(tjsIdentifier);
-          aName:=CurrentTokenString;
-          aAlias:='';
-          Consume(tjsIdentifier);
-          if IdentifierIsLiteral('as') then
-            begin
-            Consume(tjsIdentifier);
-            Expect(tjsIdentifier);
-            aAlias:=CurrentTokenString;
-            Consume(tjsIdentifier);
-            end;
-          With Imp.NamedImports.AddElement do
-            begin
-            Name:=aName;
-            Alias:=aAlias;
-            end;
-          if CurrentToken=tjsComma then
-            GetNextToken;
-        Until (CurrentToken=tjsCurlyBraceClose);
-        Consume(tjsCurlyBraceClose);
+        ParseAliasElements(Imp.NamedImports);
         end
     else
       if aExpectMore then
@@ -1716,6 +1731,82 @@ begin
   end;
 end;
 
+function TJSParser.ParseExportStatement: TJSElement;
+Var
+  Exp : TJSExportStatement;
+  async, aExpectFrom : Boolean;
+  F : TJSFunctionDeclarationStatement;
+
+begin
+  aExpectFrom:=True;
+  Consume(tjsExport);
+  Exp:=TJSExportStatement(CreateElement(TJSExportStatement));
+  try
+    Result:=Exp;
+    Case CurrentToken of
+      tjsMUL : // NameSpaceImport
+        begin
+        Consume(tjsMul);
+        if not IdentifierIsLiteral('as') then
+          Exp.NameSpaceExport:='*'
+        else
+          begin
+          ConsumeIdentifierLiteral('as');
+          Expect(tjsIdentifier);
+          Exp.NameSpaceExport:=CurrentTokenString;
+          Consume(tjsIdentifier);
+          end
+        end;
+      tjsCurlyBraceOpen:
+        begin
+        ParseAliasElements(Exp.ExportNames);
+        end;
+      tjsVAR:
+        Exp.Declaration:=ParseVariableStatement(vtVar);
+      tjsConst:
+        Exp.Declaration:=ParseVariableStatement(vtConst);
+      tjsLet:
+        Exp.Declaration:=ParseVariableStatement(vtLet);
+      tjsFunction :
+        Exp.Declaration:=ParseFunctionDeclaration;
+      tjsClass :
+        Exp.Declaration:=ParseClassDeclaration;
+      tjsDEFAULT:
+        begin
+        Exp.IsDefault:=True;
+        aExpectFrom:=False;
+        Consume(tjsDefault);
+        async:=IdentifierIsLiteral('async');
+        if Async then
+          GetNextToken;
+        case CurrentToken of
+          tjsFunction :
+            begin
+            F:=ParseFunctionDeclaration;
+            F.AFunction.IsAsync:=async;
+            Exp.Declaration:=F;
+            end;
+          tjsClass : Exp.Declaration:=ParseClassDeclaration;
+        else
+          Exp.Declaration:=ParseAssignmentExpression;
+        end;
+        end;
+    else
+      Error(SErrExpectedMulOrCurlyBraceOrDefault,[CurrentTokenString]);
+    end;
+    if aExpectFrom and IdentifierIsLiteral('from') then
+      begin
+      ConsumeIdentifierLiteral('from');
+      Expect(tjsString);
+      Exp.ModuleName:=CurrentTokenString;
+      Consume(tjsString);
+      end;
+  except
+    FreeAndNil(Result);
+    Raise;
+  end;
+end;
+
 function TJSParser.ParseContinueStatement : TJSElement;
 
 Var
@@ -2149,6 +2240,8 @@ begin
       Result:=ParseContinueStatement;
     tjsImport:
       Result:=ParseImportStatement;
+    tjsExport:
+      Result:=ParseExportStatement;
     tjsBreak:
       Result:=ParseBreakStatement;
     tjsReturn:
@@ -2188,7 +2281,7 @@ Const
   StatementTokens = [tjsNULL, tjsTRUE, tjsFALSE,
       tjsAWait, tjsTHIS, tjsIdentifier,jstoken.tjsSTRING,tjsNUMBER,
       tjsBraceOpen,tjsCurlyBraceOpen,tjsSquaredBraceOpen,
-      tjsLet, tjsConst, tjsDebugger, tjsImport,
+      tjsLet, tjsConst, tjsDebugger, tjsImport, tjsExport,
       tjsNew,tjsDelete,tjsVoid,tjsTypeOf,
       tjsPlusPlus,tjsMinusMinus,
       tjsPlus,tjsMinus,tjsNot,tjsNE,tjsSNE,tjsSemicolon,
@@ -2267,6 +2360,11 @@ begin
   {$ifdef debugparser} Writeln('<<< Exiting FunctionBody');{$endif}
 end;
 
+function TJSParser.ParseClassDeclaration: TJSClassDeclaration;
+begin
+  Result:=Nil;
+end;
+
 Function TJSParser.ParseProgram: TJSFunctionDeclarationStatement;
 
 Var

+ 100 - 4
packages/fcl-js/src/jstree.pp

@@ -81,6 +81,7 @@ Type
   TJSElementFlags = set of TJSElementFlag;
 
   TJSFunctionBody = Class;
+  TJSClassDeclaration = Class;
 
   { TJSElement }
 
@@ -872,7 +873,7 @@ Type
     Property List : TJSElement Read FList Write FList;
   end;
 
-  TJSNamedImportElement = Class(TCollectionItem)
+  TJSAliasElement = Class(TCollectionItem)
   private
     FName : String;
     FAlias : String;
@@ -881,14 +882,27 @@ Type
     Property Alias : String Read FAlias Write FAlias;
   end;
 
+  { TJSAliasElements }
+
+  TJSAliasElements = Class(TCollection)
+  private
+    function GetA(AIndex : Integer): TJSAliasElement;
+  Public
+    Function AddAlias : TJSAliasElement;
+    Property Imports[AIndex : Integer] : TJSAliasElement Read GetA; default;
+  end;
+
+  TJSNamedImportElement = Class(TJSAliasElement);
+
+
   { TJSNamedImportElements - NamedImports property of TJSImportStatement }
 
-  TJSNamedImportElements = Class(TCollection)
+  TJSNamedImportElements = Class(TJSAliasElements)
   private
     function GetE(AIndex : Integer): TJSNamedImportElement;
   Public
     Function AddElement : TJSNamedImportElement;
-    Property Imports[AIndex : Integer] : TJSNamedImportElement Read GetE ;default;
+    Property Imports[AIndex : Integer] : TJSNamedImportElement Read GetE; default;
   end;
 
   { TJSImportStatement }
@@ -910,6 +924,39 @@ Type
     Property NamedImports : TJSNamedImportElements Read GetNamedImports;
   end;
 
+  TJSExportNameElement = Class(TJSAliasElement);
+
+  { TJSExportNameElements - NamedExports property of TJSExportStatement }
+
+  TJSExportNameElements = Class(TJSAliasElements)
+  private
+    function GetE(AIndex : Integer): TJSExportNameElement;
+  Public
+    Function AddElement : TJSExportNameElement;
+    Property ExportedNames[AIndex : Integer] : TJSExportNameElement Read GetE ;default;
+  end;
+
+  { TJSExportStatement }
+
+  TJSExportStatement = class(TJSElement)
+  Private
+    FDeclaration: TJSElement;
+    FIsDefault: Boolean;
+    FModuleName: String;
+    FNamedExports: TJSExportNameElements;
+    FNameSpaceExport: String;
+    function GetHaveNamedExports: Boolean;
+    function GetNamedExports: TJSExportNameElements;
+  Public
+    Destructor Destroy; override;
+    Property IsDefault : Boolean Read FIsDefault Write FIsDefault;
+    Property Declaration : TJSElement Read FDeclaration Write FDeclaration;
+    Property NameSpaceExport : String Read FNameSpaceExport Write FNameSpaceExport;
+    Property ModuleName : String Read FModuleName Write FModuleName;
+    Property HaveExportNames : Boolean Read GetHaveNamedExports;
+    Property ExportNames : TJSExportNameElements Read GetNamedExports;
+  end;
+
   { TJSContinueStatement - e.g. 'continue'}
 
   TJSContinueStatement = Class(TJSTargetStatement);
@@ -1017,6 +1064,10 @@ Type
     Property isProgram : Boolean Read FIsProgram Write FIsProgram;
   end;
 
+  TJSClassDeclaration = Class(TJSElement)
+  end;
+
+
   { TJSElementNode - element of TJSElementNodes }
 
   TJSElementNode = Class(TCollectionItem)
@@ -1056,6 +1107,51 @@ Type
 
 implementation
 
+{ TJSAliasElements }
+
+function TJSAliasElements.GetA(AIndex: Integer): TJSAliasElement;
+begin
+  Result:=TJSAliasElement(Items[aIndex])
+end;
+
+function TJSAliasElements.AddAlias: TJSAliasElement;
+begin
+  Result:=TJSAliasElement(add);
+end;
+
+{ TJSNamedExportElements }
+
+function TJSExportNameElements.GetE(AIndex: Integer): TJSExportNameElement;
+begin
+  Result:=TJSExportNameElement(Items[aIndex]);
+end;
+
+function TJSExportNameElements.AddElement: TJSExportNameElement;
+begin
+  Result:=TJSExportNameElement(Add);
+end;
+
+{ TJSExportStatement }
+
+function TJSExportStatement.GetNamedExports: TJSExportNameElements;
+begin
+  If FNamedExports=Nil then
+    FNamedExports:=TJSExportNameElements.Create(TJSExportNameElement);
+  Result:=FNamedExports;
+end;
+
+function TJSExportStatement.GetHaveNamedExports: Boolean;
+begin
+  Result:=Assigned(FNamedExports)
+end;
+
+destructor TJSExportStatement.Destroy;
+begin
+  FreeAndNil(FNamedExports);
+  FreeAndNil(FDeclaration);
+  inherited Destroy;
+end;
+
 { TJSImportStatement }
 
 function TJSImportStatement.GetNamedImports: TJSNamedImportElements;
@@ -1067,7 +1163,7 @@ end;
 
 function TJSImportStatement.GetHaveNamedImports: Boolean;
 begin
-  Result:=Assigned(FNamedImports) and (FNamedImports.Count>0);
+  Result:=Assigned(FNamedImports);
 end;
 
 destructor TJSImportStatement.Destroy;

+ 41 - 0
packages/fcl-js/src/jswriter.pp

@@ -207,6 +207,7 @@ Type
     Procedure WriteForInStatement(El: TJSForInStatement);virtual;
     Procedure WriteWhileStatement(El: TJSWhileStatement);virtual;
     Procedure WriteImportStatement(El: TJSImportStatement);virtual;
+    Procedure WriteExportStatement(El: TJSExportStatement);virtual;
     Procedure WriteForStatement(El: TJSForStatement);virtual;
     Procedure WriteIfStatement(El: TJSIfStatement);virtual;
     Procedure WriteSourceElements(El: TJSSourceElements);virtual;
@@ -1720,6 +1721,44 @@ begin
   write('"'+El.ModuleName+'"');
 end;
 
+procedure TJSWriter.WriteExportStatement(El: TJSExportStatement);
+Var
+  I : integer;
+  N : TJSExportNameElement;
+
+begin
+  Write('export ');
+  if El.IsDefault then
+    Write('default ');
+  if assigned(El.Declaration) then
+    WriteJS(EL.Declaration)
+  else if (El.NameSpaceExport<>'') then
+    begin
+    if El.NameSpaceExport<>'*' then
+      Write('* as '+El.NameSpaceExport)
+    else
+      Write('*');
+    if EL.ModuleName<>'' then
+      Write(' from "'+EL.ModuleName+'"');
+    end
+  else if El.HaveExportNames then
+    begin
+    Write('{ ');
+    For I:=0 to EL.ExportNames.Count-1 do
+      begin
+      N:=EL.ExportNames[i];
+      if I>0 then
+        Write(', ');
+      Write(N.Name+' ');
+      if N.Alias<>'' then
+        Write('as '+N.Alias+' ');
+      end;
+    Write('}');
+    if El.ModuleName<>'' then
+      Write(' from "'+El.ModuleName+'"');
+    end;
+end;
+
 procedure TJSWriter.WriteSwitchStatement(El: TJSSwitchStatement);
 
 Var
@@ -2026,6 +2065,8 @@ begin
     WriteIfStatement(TJSIfStatement(El))
   else if (C=TJSImportStatement) then
     WriteImportStatement(TJSImportStatement(El))
+  else if (C=TJSExportStatement) then
+    WriteExportStatement(TJSExportStatement(El))
   else if C.InheritsFrom(TJSTargetStatement) then
     WriteTargetStatement(TJSTargetStatement(El))
   else if (C=TJSReturnStatement) then

+ 262 - 1
packages/fcl-js/tests/tcparser.pp

@@ -5,7 +5,7 @@ unit tcparser;
 interface
 
 uses
-  Classes, SysUtils, fpcunit, testregistry,  jsParser, jstree, jsbase;
+  Classes, SysUtils, fpcunit, testregistry, jstoken, jsParser, jstree, jsbase;
 
 type
 
@@ -22,6 +22,7 @@ type
     procedure TearDown; override;
     Procedure CreateParser(Const ASource : string; aVersion : TECMAVersion = TECMAVersion.ecma5);
     Procedure CheckClass(E : TJSElement; C : TJSElementClass);
+    Procedure AssertEquals(Const AMessage : String; Expected, Actual : TJSToken); overload;
     Procedure AssertEquals(Const AMessage : String; Expected, Actual : TJSType); overload;
     Procedure AssertEquals(Const AMessage : String; Expected, Actual : TJSVarType); overload;
     Procedure AssertIdentifier(Msg : String; El : TJSElement; Const AName : TJSString);
@@ -151,6 +152,18 @@ type
     Procedure TestImport2NamedImportAlias;
     Procedure TestImport2NamedImportsComma;
     Procedure TestImportDefaultAndNamedImport;
+    Procedure TestExportAll;
+    Procedure TestExportAllFrom;
+    Procedure TestExportExportNameFrom;
+    Procedure TestExportExportName;
+    Procedure TestExportExportNameAlias;
+    Procedure TestExportVar;
+    Procedure TestExportLet;
+    Procedure TestExportConst;
+    Procedure TestExportFunction;
+    Procedure TestExportDefaultAssignment;
+    Procedure TestExportDefaultFunction;
+    Procedure TestExportDefaultAsyncFunction;
   end;
 
 implementation
@@ -2638,6 +2651,244 @@ begin
   AssertEquals('Named import alias','',NamedImp.Alias);
 end;
 
+procedure TTestJSParser.TestExportAll;
+
+Var
+  E : TJSElement;
+  Exp : TJSExportStatement absolute E;
+
+begin
+  CreateParser('export *',MinExportVersion);
+  E:=GetFirstStatement;
+  AssertFalse('Default',Exp.IsDefault);
+  CheckClass(E,TJSExportStatement);
+  AssertEquals('NameSpaceExport','*',Exp.NameSpaceExport);
+  AssertNull('Declaration',Exp.Declaration);
+  AssertEquals('ModuleName','',Exp.ModuleName);
+  AssertFalse('ExportNames',Exp.HaveExportNames);
+end;
+
+procedure TTestJSParser.TestExportAllFrom;
+
+Var
+  E : TJSElement;
+  Exp : TJSExportStatement absolute E;
+
+begin
+  CreateParser('export * from "a.js"',MinExportVersion);
+  E:=GetFirstStatement;
+  CheckClass(E,TJSExportStatement);
+  AssertEquals('NameSpaceExport','*',Exp.NameSpaceExport);
+  AssertFalse('Default',Exp.IsDefault);
+  AssertNull('Declaration',Exp.Declaration);
+  AssertEquals('ModuleName','a.js',Exp.ModuleName);
+  AssertFalse('ExportNames',Exp.HaveExportNames);
+end;
+
+procedure TTestJSParser.TestExportExportName;
+Var
+  E : TJSElement;
+  Exp : TJSExportStatement absolute E;
+  El : TJSExportNameElement;
+
+begin
+  CreateParser('export { a }',MinExportVersion);
+  E:=GetFirstStatement;
+  CheckClass(E,TJSExportStatement);
+  AssertEquals('NameSpaceExport','',Exp.NameSpaceExport);
+  AssertFalse('Default',Exp.IsDefault);
+  AssertNull('Declaration',Exp.Declaration);
+  AssertEquals('ModuleName','',Exp.ModuleName);
+  AssertTrue('ExportNames',Exp.HaveExportNames);
+  AssertEquals('ExportNames count',1,Exp.ExportNames.Count);
+  El:=Exp.ExportNames[0];
+  AssertEquals('ExportNames[0].Name','a',El.Name);
+  AssertEquals('ExportNames[0].Name','',El.Alias);
+end;
+
+procedure TTestJSParser.TestExportExportNameAlias;
+Var
+  E : TJSElement;
+  Exp : TJSExportStatement absolute E;
+  El : TJSExportNameElement;
+
+begin
+  CreateParser('export { a as b }',MinExportVersion);
+  E:=GetFirstStatement;
+  CheckClass(E,TJSExportStatement);
+  AssertEquals('NameSpaceExport','',Exp.NameSpaceExport);
+  AssertFalse('Default',Exp.IsDefault);
+  AssertNull('Declaration',Exp.Declaration);
+  AssertEquals('ModuleName','',Exp.ModuleName);
+  AssertTrue('ExportNames',Exp.HaveExportNames);
+  AssertEquals('ExportNames count',1,Exp.ExportNames.Count);
+  El:=Exp.ExportNames[0];
+  AssertEquals('ExportNames[0].Name','a',El.Name);
+  AssertEquals('ExportNames[0].Name','b',El.Alias);
+end;
+
+procedure TTestJSParser.TestExportVar;
+
+Var
+  E : TJSElement;
+  Exp : TJSExportStatement absolute E;
+  V : TJSVariableStatement;
+
+begin
+  CreateParser('export var a = 1',MinExportVersion);
+  E:=GetFirstStatement;
+  CheckClass(E,TJSExportStatement);
+  AssertEquals('NameSpaceExport','',Exp.NameSpaceExport);
+  AssertFalse('Default',Exp.IsDefault);
+  AssertNotNull('Declaration',Exp.Declaration);
+  CheckClass(Exp.Declaration,TJSVariableStatement);
+  V:=TJSVariableStatement(Exp.Declaration);
+  AssertEquals('var type',vtVar,V.varType);
+  CheckClass(V.A,TJSVarDeclaration);
+  AssertEquals('Variable name','a',TJSVarDeclaration(V.a).Name);
+end;
+
+procedure TTestJSParser.TestExportLet;
+
+Var
+  E : TJSElement;
+  Exp : TJSExportStatement absolute E;
+  V : TJSVariableStatement;
+
+begin
+  CreateParser('export let a = 1',MinExportVersion);
+  E:=GetFirstStatement;
+  CheckClass(E,TJSExportStatement);
+  AssertEquals('NameSpaceExport','',Exp.NameSpaceExport);
+  AssertFalse('Default',Exp.IsDefault);
+  AssertNotNull('Declaration',Exp.Declaration);
+  CheckClass(Exp.Declaration,TJSVariableStatement);
+  V:=TJSVariableStatement(Exp.Declaration);
+  AssertEquals('var type',vtLet,V.varType);
+  CheckClass(V.A,TJSVarDeclaration);
+  AssertEquals('Variable name','a',TJSVarDeclaration(V.a).Name);
+end;
+
+procedure TTestJSParser.TestExportConst;
+Var
+  E : TJSElement;
+  Exp : TJSExportStatement absolute E;
+  V : TJSVariableStatement;
+
+begin
+  CreateParser('export const a = 1',MinExportVersion);
+  E:=GetFirstStatement;
+  CheckClass(E,TJSExportStatement);
+  AssertEquals('NameSpaceExport','',Exp.NameSpaceExport);
+  AssertFalse('Default',Exp.IsDefault);
+  AssertNotNull('Declaration',Exp.Declaration);
+  CheckClass(Exp.Declaration,TJSVariableStatement);
+  V:=TJSVariableStatement(Exp.Declaration);
+  AssertEquals('var type',vtConst,V.varType);
+  CheckClass(V.A,TJSVarDeclaration);
+  AssertEquals('Variable name','a',TJSVarDeclaration(V.a).Name);
+end;
+
+procedure TTestJSParser.TestExportFunction;
+Var
+  E : TJSElement;
+  Exp : TJSExportStatement absolute E;
+  F : TJSFunctionDeclarationStatement;
+
+begin
+  CreateParser('export function a () { return 1; } ',MinExportVersion);
+  E:=GetFirstStatement;
+  CheckClass(E,TJSExportStatement);
+  AssertEquals('NameSpaceExport','',Exp.NameSpaceExport);
+  AssertFalse('Default',Exp.IsDefault);
+  AssertNotNull('Declaration',Exp.Declaration);
+  CheckClass(Exp.Declaration,TJSFunctionDeclarationStatement);
+  F:=TJSFunctionDeclarationStatement(Exp.Declaration);
+  AssertNotNull('Have function', F.AFunction);
+  AssertEquals('Variable name','a',F.AFunction.Name);
+end;
+
+procedure TTestJSParser.TestExportDefaultAssignment;
+Var
+  E : TJSElement;
+  Exp : TJSExportStatement absolute E;
+  A : TJSAssignStatement;
+
+begin
+  CreateParser('export default a = 1',MinExportVersion);
+  E:=GetFirstStatement;
+  CheckClass(E,TJSExportStatement);
+  AssertEquals('NameSpaceExport','',Exp.NameSpaceExport);
+  AssertTrue('Default',Exp.IsDefault);
+  AssertNotNull('Declaration',Exp.Declaration);
+  CheckClass(Exp.Declaration,TJSSimpleAssignStatement);
+  A:=TJSSimpleAssignStatement(Exp.Declaration);
+  CheckClass(A.LHS,TJSPrimaryExpressionIdent);
+  AssertEquals('Operator token',jstoken.tjsASSIGN,A.OperatorToken);
+  AssertEquals('Variable name','a',TJSPrimaryExpressionIdent(A.LHS).Name);
+end;
+
+procedure TTestJSParser.TestExportDefaultFunction;
+Var
+  E : TJSElement;
+  Exp : TJSExportStatement absolute E;
+  F : TJSFunctionDeclarationStatement;
+
+begin
+  CreateParser('export default function a () { return 1; } ',MinExportVersion);
+  E:=GetFirstStatement;
+  CheckClass(E,TJSExportStatement);
+  AssertEquals('NameSpaceExport','',Exp.NameSpaceExport);
+  AssertTrue('Default',Exp.IsDefault);
+  AssertNotNull('Declaration',Exp.Declaration);
+  CheckClass(Exp.Declaration,TJSFunctionDeclarationStatement);
+  F:=TJSFunctionDeclarationStatement(Exp.Declaration);
+  AssertNotNull('Have function', F.AFunction);
+  AssertEquals('Variable name','a',F.AFunction.Name);
+  AssertFalse('Async',F.AFunction.IsAsync);
+end;
+
+procedure TTestJSParser.TestExportDefaultAsyncFunction;
+Var
+  E : TJSElement;
+  Exp : TJSExportStatement absolute E;
+  F : TJSFunctionDeclarationStatement;
+
+begin
+  CreateParser('export default async function a () { return 1; } ',MinExportVersion);
+  E:=GetFirstStatement;
+  CheckClass(E,TJSExportStatement);
+  AssertEquals('NameSpaceExport','',Exp.NameSpaceExport);
+  AssertTrue('Default',Exp.IsDefault);
+  AssertNotNull('Declaration',Exp.Declaration);
+  CheckClass(Exp.Declaration,TJSFunctionDeclarationStatement);
+  F:=TJSFunctionDeclarationStatement(Exp.Declaration);
+  AssertNotNull('Have function', F.AFunction);
+  AssertEquals('Variable name','a',F.AFunction.Name);
+  AssertTrue('Async',F.AFunction.IsAsync);
+end;
+
+procedure TTestJSParser.TestExportExportNameFrom;
+
+Var
+  E : TJSElement;
+  Exp : TJSExportStatement absolute E;
+  El : TJSExportNameElement;
+
+begin
+  CreateParser('export { a } from "a.js"',MinExportVersion);
+  E:=GetFirstStatement;
+  CheckClass(E,TJSExportStatement);
+  AssertEquals('NameSpaceExport','',Exp.NameSpaceExport);
+  AssertNull('Declaration',Exp.Declaration);
+  AssertEquals('ModuleName','a.js',Exp.ModuleName);
+  AssertTrue('ExportNames',Exp.HaveExportNames);
+  AssertEquals('ExportNames count',1,Exp.ExportNames.Count);
+  El:=Exp.ExportNames[0];
+  AssertEquals('ExportNames[0].Name','a',El.Name);
+  AssertEquals('ExportNames[0].Name','',El.Alias);
+end;
+
 procedure TTestJSParser.TestBreak;
 Var
   E : TJSElement;
@@ -2849,6 +3100,16 @@ begin
   AssertEquals(C,E.ClassType);
 end;
 
+procedure TTestJSParser.AssertEquals(const AMessage: String; Expected, Actual: TJSToken);
+Var
+  NE,NA : String;
+
+begin
+  NE:=GetEnumName(TypeInfo(TJSToken),Ord(Expected));
+  NA:=GetEnumName(TypeInfo(TJSToken),Ord(Actual));
+  AssertEquals(AMessage,NE,NA);
+end;
+
 Function TTestJSParser.GetSourceElements: TJSSourceElements;
 
 Var

+ 183 - 0
packages/fcl-js/tests/tcwriter.pp

@@ -189,6 +189,18 @@ type
     Procedure TestImportNamedImportAlias;
     Procedure TestImport2NamedImport;
     Procedure TestImportDefaultBindingNamedImport;
+    Procedure TestExportAll;
+    Procedure TestExportAllFrom;
+    Procedure TestExportExportName;
+    Procedure TestExportExportNameAlias;
+    Procedure TestExportExportNameFrom;
+    Procedure TestExportVar;
+    Procedure TestExportLet;
+    Procedure TestExportConst;
+    Procedure TestExportFunction;
+    Procedure TestExportDefaultAssignment;
+    Procedure TestExportDefaultFunction;
+    Procedure TestExportDefaultAsyncFunction;
   end;
 
   { TTestExpressionWriter }
@@ -2357,6 +2369,177 @@ begin
   AssertWrite('Import statement','import C , { A } from "a.js"',Imp);
 end;
 
+procedure TTestStatementWriter.TestExportAll;
+
+Var
+  Exp : TJSExportStatement;
+
+begin
+  Exp:=TJSExportStatement.Create(0,0);
+  Exp.NameSpaceExport:='*';
+  AssertWrite('Export statement','export *',Exp);
+end;
+
+procedure TTestStatementWriter.TestExportAllFrom;
+Var
+  Exp : TJSExportStatement;
+
+begin
+  Exp:=TJSExportStatement.Create(0,0);
+  Exp.NameSpaceExport:='*';
+  Exp.ModuleName:='a.js';
+  AssertWrite('Export statement','export * from "a.js"',Exp);
+end;
+
+procedure TTestStatementWriter.TestExportExportName;
+Var
+  Exp : TJSExportStatement;
+
+begin
+  Exp:=TJSExportStatement.Create(0,0);
+  Exp.ExportNames.AddAlias.Name:='a';
+  AssertWrite('Export statement','export { a }',Exp);
+end;
+
+procedure TTestStatementWriter.TestExportExportNameAlias;
+Var
+  Exp : TJSExportStatement;
+
+begin
+  Exp:=TJSExportStatement.Create(0,0);
+  With Exp.ExportNames.AddAlias do
+    begin
+    Name:='a';
+    Alias:='b';
+    end;
+  AssertWrite('Export statement','export { a as b }',Exp);
+end;
+
+procedure TTestStatementWriter.TestExportExportNameFrom;
+Var
+  Exp : TJSExportStatement;
+
+begin
+  Exp:=TJSExportStatement.Create(0,0);
+  Exp.ExportNames.AddAlias.Name:='a';
+  Exp.ModuleName:='a.js';
+  AssertWrite('Export statement','export { a } from "a.js"',Exp);
+end;
+
+procedure TTestStatementWriter.TestExportVar;
+Var
+  Exp : TJSExportStatement;
+  VS : TJSVariableStatement;
+  VV : TJSVarDeclaration;
+begin
+  Exp:=TJSExportStatement.Create(0,0);
+  VS:=TJSVariableStatement.Create(0,0);
+  VV:=TJSVarDeclaration.Create(0,0);
+  VS.A:=VV;
+  VV.Name:='a';
+  Exp.Declaration:=VS;
+  AssertWrite('Export statement','export var a',Exp);
+end;
+
+procedure TTestStatementWriter.TestExportLet;
+Var
+  Exp : TJSExportStatement;
+  VS : TJSVariableStatement;
+  VV : TJSVarDeclaration;
+begin
+  Exp:=TJSExportStatement.Create(0,0);
+  VS:=TJSVariableStatement.Create(0,0);
+  VS.varType:=vtLet;
+  VV:=TJSVarDeclaration.Create(0,0);
+  VS.A:=VV;
+  VV.varType:=vtLet;
+  VV.Name:='a';
+  Exp.Declaration:=VS;
+  AssertWrite('Export statement','export let a',Exp);
+end;
+
+procedure TTestStatementWriter.TestExportConst;
+Var
+  Exp : TJSExportStatement;
+  VS : TJSVariableStatement;
+  VV : TJSVarDeclaration;
+begin
+  Exp:=TJSExportStatement.Create(0,0);
+  VS:=TJSVariableStatement.Create(0,0);
+  VS.varType:=vtConst;
+  VV:=TJSVarDeclaration.Create(0,0);
+  VS.A:=VV;
+  VV.varType:=vtConst;
+  VV.Name:='a';
+  Exp.Declaration:=VS;
+  AssertWrite('Export statement','export const a',Exp);
+end;
+
+procedure TTestStatementWriter.TestExportFunction;
+Var
+  Exp : TJSExportStatement;
+  FD : TJSFunctionDeclarationStatement;
+
+begin
+  Exp:=TJSExportStatement.Create(0,0);
+  FD:=TJSFunctionDeclarationStatement.Create(0,0);
+  FD.AFunction:=TJSFuncDef.Create;
+  FD.AFunction.Name:='a';
+  Exp.Declaration:=FD;
+  AssertWrite('Empty function',
+     'export function a() {'+sLineBreak
+    +'}',Exp);
+
+end;
+
+procedure TTestStatementWriter.TestExportDefaultAssignment;
+Var
+  Exp : TJSExportStatement;
+  U : TJSAssignStatement;
+begin
+  Exp:=TJSExportStatement.Create(0,0);
+  Exp.IsDefault:=True;
+  U:=CreateAssignment(TJSSimpleAssignStatement);
+  Exp.Declaration:=U;
+  AssertWrite('Export','export default a = b',Exp);
+end;
+
+procedure TTestStatementWriter.TestExportDefaultFunction;
+
+Var
+  Exp : TJSExportStatement;
+  FD : TJSFunctionDeclarationStatement;
+
+begin
+  Exp:=TJSExportStatement.Create(0,0);
+  Exp.IsDefault:=True;
+  FD:=TJSFunctionDeclarationStatement.Create(0,0);
+  FD.AFunction:=TJSFuncDef.Create;
+  FD.AFunction.Name:='a';
+  Exp.Declaration:=FD;
+  AssertWrite('Empty function',
+     'export default function a() {'+sLineBreak
+    +'}',Exp);
+end;
+
+procedure TTestStatementWriter.TestExportDefaultAsyncFunction;
+Var
+  Exp : TJSExportStatement;
+  FD : TJSFunctionDeclarationStatement;
+
+begin
+  Exp:=TJSExportStatement.Create(0,0);
+  Exp.IsDefault:=True;
+  FD:=TJSFunctionDeclarationStatement.Create(0,0);
+  FD.AFunction:=TJSFuncDef.Create;
+  FD.AFunction.IsAsync:=True;
+  FD.AFunction.Name:='a';
+  Exp.Declaration:=FD;
+  AssertWrite('Empty function',
+     'export default async function a() {'+sLineBreak
+    +'}',Exp);
+end;
+
 { ---------------------------------------------------------------------
   TTestLiteralWriter
   ---------------------------------------------------------------------}