فهرست منبع

pastojs: filer: test write/read multiple pcu

git-svn-id: trunk@38516 -
Mattias Gaertner 7 سال پیش
والد
کامیت
fa0bf267e2

+ 113 - 64
packages/pastojs/src/pas2jscompiler.pp

@@ -253,6 +253,10 @@ type
       out Count: integer);
     procedure OnPasResolverLog(Sender: TObject; const Msg: String);
     procedure OnParserLog(Sender: TObject; const Msg: String);
+    function OnPCUConverterIsElementUsed(Sender: TObject; El: TPasElement
+      ): boolean;
+    function OnPCUConverterIsTypeInfoUsed(Sender: TObject; El: TPasElement
+      ): boolean;
     procedure OnScannerLog(Sender: TObject; const Msg: String);
     procedure OnUseAnalyzerMessage(Sender: TObject; Msg: TPAMessage);
     procedure HandleEParserError(E: EParserError);
@@ -275,6 +279,7 @@ type
     function GetInitialConverterOptions: TPasToJsConverterOptions;
     procedure CreateScannerAndParser(aFileResolver: TPas2jsFileResolver);
     procedure CreatePCUReader;
+    procedure CreateConverter;
     function FindPCU(const UseUnitName: string; out aFormat: TPas2JSPrecompileFormat): string;
     function OnResolverFindModule(const UseUnitName, InFilename: String; NameExpr,
       InFileExpr: TPasExpr): TPasModule;
@@ -868,6 +873,15 @@ begin
   end;
 end;
 
+procedure TPas2jsCompilerFile.CreateConverter;
+begin
+  if FConverter<>nil then exit;
+  FConverter:=TPasToJSConverter.Create;
+  FConverter.Options:=GetInitialConverterOptions;
+  FConverter.TargetPlatform:=Compiler.TargetPlatform;
+  FConverter.TargetProcessor:=Compiler.TargetProcessor;
+end;
+
 procedure TPas2jsCompilerFile.OnResolverCheckSrcName(const Element: TPasElement);
 var
   SrcName, ExpectedSrcName: String;
@@ -968,6 +982,18 @@ begin
           aScanner.CurFilename,aScanner.CurRow,aScanner.CurColumn);
 end;
 
+function TPas2jsCompilerFile.OnPCUConverterIsElementUsed(Sender: TObject;
+  El: TPasElement): boolean;
+begin
+  Result:=UseAnalyzer.IsUsed(El);
+end;
+
+function TPas2jsCompilerFile.OnPCUConverterIsTypeInfoUsed(Sender: TObject;
+  El: TPasElement): boolean;
+begin
+  Result:=UseAnalyzer.IsTypeInfoUsed(El);
+end;
+
 procedure TPas2jsCompilerFile.OnScannerLog(Sender: TObject; const Msg: String);
 var
   aScanner: TPascalScanner;
@@ -1028,7 +1054,7 @@ end;
 procedure TPas2jsCompilerFile.HandleUnknownException(E: Exception);
 begin
   if not (E is ECompilerTerminate) then
-    Log.Log(mtFatal,'bug: uncaught ECompilerTerminate'+': '+E.Message,0); // must use on E:ECompilerTerminate do raise;
+    Log.Log(mtFatal,'bug: uncaught '+E.ClassName+': '+E.Message,0); // must use on E:ECompilerTerminate do raise;
   Log.Log(mtFatal,E.ClassName+': '+E.Message,0);
   Compiler.Terminate(ExitCodeErrorInternal);
   // Note: a "raise E" is not allowed by caught exceptions, try..except will free it
@@ -1118,7 +1144,7 @@ begin
     writeln('TPas2jsCompilerFile.ReaderFinished analyzing ',PasFilename,' ...');
     {$ENDIF}
     UseAnalyzer.AnalyzeModule(FPasModule);
-    {$IF defined(VerboseUnitQueue) or defined(VerbosePJUFiler)}
+    {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
     writeln('TPas2jsCompilerFile.ReaderFinished analyzed ',PasFilename,' ScopeModule=',GetObjName(UseAnalyzer.ScopeModule));
     {$ENDIF}
 
@@ -1138,10 +1164,11 @@ var
   Writer: TPCUWriter;
   ms: TMemoryStream;
   DestDir: String;
+  JS: TJSElement;
 begin
   if PasModule.ClassType<>TPasModule then
   begin
-    {$IF defined(VerboseUnitQueue) or defined(VerbosePJUFiler)}
+    {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
     writeln('TPas2jsCompilerFile.WritePCU not a unit: ',PasFilename,' skip');
     {$ENDIF}
     exit;
@@ -1149,7 +1176,7 @@ begin
 
   if (PCUFilename<>'') or (PCUReader<>nil) then
   begin
-    {$IF defined(VerboseUnitQueue) or defined(VerbosePJUFiler)}
+    {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
     writeln('TPas2jsCompilerFile.WritePCU already precompiled "',PCUFilename,'" Reader=',GetObjName(PCUReader));
     {$ENDIF}
     exit;
@@ -1157,10 +1184,11 @@ begin
   FPCUFilename:=Compiler.CreatePrecompileFilename(Self);
   FPCUFormat:=Compiler.FileCache.PrecompileFormat;
 
-  {$IF defined(VerboseUnitQueue) or defined(VerbosePJUFiler)}
+  {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
   writeln('TPas2jsCompilerFile.WritePCU precompiling ',PCUFilename);
   {$ENDIF}
 
+  JS:=nil;
   PF:=Compiler.FileCache.PrecompileFormat;
   ms:=TMemoryStream.Create;
   Writer:=PF.WriterClass.Create;
@@ -1168,8 +1196,17 @@ begin
     Writer.GUID:=Compiler.PrecompileGUID;
     Writer.OnGetSrc:=@OnFilerGetSrc;
     Writer.OnIsElementUsed:=@OnWriterIsElementUsed;
+
+    // create JavaScript for procs, initialization, finalization
+    CreateConverter;
+    Converter.Options:=Converter.Options+[coStoreImplJS];
+    Converter.OnIsElementUsed:=@OnPCUConverterIsElementUsed;
+    Converter.OnIsTypeInfoUsed:=@OnPCUConverterIsTypeInfoUsed;
+    JS:=Converter.ConvertPasElement(PasModule,PascalResolver);
+    Converter.Options:=Converter.Options-[coStoreImplJS];
+
     Writer.WritePCU(PascalResolver,Converter,Compiler.PrecompileInitialFlags,ms,false);
-    {$IF defined(VerboseUnitQueue) or defined(VerbosePJUFiler)}
+    {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
     writeln('TPas2jsCompilerFile.WritePCU precompiled ',PCUFilename);
     {$ENDIF}
 
@@ -1180,7 +1217,7 @@ begin
     DestDir:=ChompPathDelim(ExtractFilePath(PCUFilename));
     if (DestDir<>'') and not Compiler.DirectoryExists(DestDir) then
     begin
-      {$IF defined(VerboseUnitQueue) or defined(VerbosePJUFiler)}
+      {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
       writeln('TPas2jsCompilerFile.WritePCU output dir not found "',DestDir,'"');
       {$ENDIF}
       Log.LogMsg(nOutputDirectoryNotFound,[Compiler.FileCache.FormatPath(DestDir)]);
@@ -1188,7 +1225,7 @@ begin
     end;
     if Compiler.DirectoryExists(PCUFilename) then
     begin
-      {$IF defined(VerboseUnitQueue) or defined(VerbosePJUFiler)}
+      {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
       writeln('TPas2jsCompilerFile.WritePCU file is folder "',DestDir,'"');
       {$ENDIF}
       Log.LogMsg(nFileIsFolder,[Compiler.FileCache.FormatPath(PCUFilename)]);
@@ -1197,10 +1234,11 @@ begin
 
     ms.Position:=0;
     Compiler.FileCache.SaveToFile(ms,PCUFilename);
-    {$IF defined(VerboseUnitQueue) or defined(VerbosePJUFiler)}
+    {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
     writeln('TPas2jsCompilerFile.WritePCU written ',PCUFilename);
     {$ENDIF}
   finally
+    JS.Free;
     Writer.Free;
     ms.Free;
   end;
@@ -1249,7 +1287,7 @@ begin
         FReaderState:=prsWaitingForUsedUnits;
     end;
     {$IFDEF VerboseUnitQueue}
-    writeln('TPas2jsCompilerFile.ReadUnit ',PasFilename,' Finished=',Parser.CurModule=nil);
+    writeln('TPas2jsCompilerFile.ReadUnit ',PasFilename,' ReaderState=',ReaderState);
     {$ENDIF}
   except
     on E: ECompilerTerminate do
@@ -1283,7 +1321,7 @@ begin
       Result:=Parser.CurModule=nil;
     end;
     {$IFDEF VerboseUnitQueue}
-    writeln('TPas2jsCompilerFile.ReadContinue ',PasFilename,' finished=',Parser.CurModule=nil);
+    writeln('TPas2jsCompilerFile.ReadContinue ',PasFilename,' finished=',Result);
     {$ENDIF}
     if Result then
       ReaderFinished
@@ -1322,12 +1360,9 @@ begin
     UseAnalyzer.EmitModuleHints(PasModule);
 
     // convert
-    FConverter:=TPasToJSConverter.Create;
-    FConverter.Options:=GetInitialConverterOptions;
-    FConverter.TargetPlatform:=Compiler.TargetPlatform;
-    FConverter.TargetProcessor:=Compiler.TargetProcessor;
-    FConverter.OnIsElementUsed:=@OnConverterIsElementUsed;
-    FConverter.OnIsTypeInfoUsed:=@OnConverterIsTypeInfoUsed;
+    CreateConverter;
+    Converter.OnIsElementUsed:=@OnConverterIsElementUsed;
+    Converter.OnIsTypeInfoUsed:=@OnConverterIsTypeInfoUsed;
     FJSModule:=Converter.ConvertPasElement(PasModule,PascalResolver);
   except
     on E: ECompilerTerminate do
@@ -1343,14 +1378,12 @@ var
 begin
   aModule:=GetCurPasModule;
   if aModule=nil then exit;
-  if aModule.ClassType=TPasUnitModule then
-    Result:=TPasUnitModule(aModule).InterfaceSection
-  else if aModule.ClassType=TPasProgram then
+  if aModule.ClassType=TPasProgram then
     Result:=TPasProgram(aModule).ProgramSection
   else if aModule.ClassType=TPasLibrary then
     Result:=TPasLibrary(aModule).LibrarySection
   else
-    Result:=nil;
+    Result:=aModule.InterfaceSection;
 end;
 
 function TPas2jsCompilerFile.GetPasImplSection: TPasSection;
@@ -1406,6 +1439,8 @@ function TPas2jsCompilerFile.GetCurPasModule: TPasModule;
 begin
   if PasModule<>nil then
     Result:=PasModule
+  else if (PascalResolver<>nil) and (PascalResolver.RootElement<>nil) then
+    Result:=PascalResolver.RootElement
   else if Parser<>nil then
     Result:=Parser.CurModule
   else
@@ -1492,32 +1527,41 @@ end;
 function TPas2jsCompilerFile.OnResolverFindModule(const UseUnitName,
   InFilename: String; NameExpr, InFileExpr: TPasExpr): TPasModule;
 var
-  FoundUnitName: string;
-  FoundIsForeign: Boolean;
+  FoundPasFilename, FoundPasUnitName, FoundPCUFilename, FoundPCUUnitName: string;
+  FoundPasIsForeign: Boolean;
+  FoundPCUFormat: TPas2JSPrecompileFormat;
 
-  function TryUnitName(const TestUnitName: string): string;
+  procedure TryUnitName(const TestUnitName: string);
   var
     aFile: TPas2jsCompilerFile;
   begin
-    // search loaded units
-    aFile:=Compiler.FindUnitWithName(TestUnitName);
-    if aFile<>nil then
+    if FoundPasFilename='' then
+    begin
+      // search loaded units
+      aFile:=Compiler.FindUnitWithName(TestUnitName);
+      if aFile<>nil then
+      begin
+        FoundPasFilename:=aFile.PasFilename;
+        FoundPasUnitName:=TestUnitName;
+      end else begin
+        // search pas in unit path
+        FoundPasFilename:=FileResolver.FindUnitFileName(TestUnitName,'',FoundPasIsForeign);
+        if FoundPasFilename<>'' then
+          FoundPasUnitName:=TestUnitName;
+      end;
+    end;
+    if FoundPCUFilename='' then
     begin
-      FoundUnitName:=TestUnitName;
-      exit(aFile.PasFilename);
+      FoundPCUFilename:=FindPCU(TestUnitName,FoundPCUFormat);
+      if FoundPCUFilename<>'' then
+        FoundPCUUnitName:=TestUnitName;
     end;
-    // search pas in unit path
-    Result:=FileResolver.FindUnitFileName(TestUnitName,'',FoundIsForeign);
-    if Result<>'' then
-      FoundUnitName:=TestUnitName;
   end;
 
 var
-  aNameSpace, DefNameSpace, FoundPasFilename, FoundPCUFilename: String;
-  LastEl: TPasElement;
+  aNameSpace, DefNameSpace: String;
   i: Integer;
   aFile: TPas2jsCompilerFile;
-  FoundPCUFormat: TPas2JSPrecompileFormat;
 begin
   Result:=nil;
   if CompareText(ExtractFilenameOnly(PasFilename),UseUnitname)=0 then
@@ -1526,29 +1570,19 @@ begin
     Parser.RaiseParserError(nUnitCycle,[UseUnitname]);
   end;
 
-  LastEl:=PascalResolver.LastElement;
-  if (LastEl<>nil)
-      and ((LastEl is TPasSection) or (LastEl.ClassType=TPasUsesUnit)
-        or (LastEl.Parent is TPasSection)) then
-    // ok
-  else
-    RaiseInternalError(20170504161408,'internal error TPas2jsCompilerFile.FindModule PasTree.LastElement='+GetObjName(LastEl)+' '+GetObjName(LastEl.Parent));
-
   FoundPasFilename:='';
-  FoundIsForeign:=false;
-  FoundUnitName:='';
+  FoundPasIsForeign:=false;
+  FoundPasUnitName:='';
   FoundPCUFilename:='';
   FoundPCUFormat:=nil;
+  FoundPCUUnitName:='';
   if (InFilename='') and (Pos('.',UseUnitname)<1) then
   begin
     // generic unit -> search with namespaces
     // first the default program namespace
     DefNameSpace:=Compiler.GetDefaultNamespace;
     if DefNameSpace<>'' then
-    begin
-      FoundPasFilename:=TryUnitName(DefNameSpace+'.'+UseUnitname);
-      FoundPCUFilename:=FindPCU(DefNameSpace+'.'+UseUnitname,FoundPCUFormat);
-    end;
+      TryUnitName(DefNameSpace+'.'+UseUnitname);
 
     if (FoundPasFilename='') or (FoundPCUFilename='') then
     begin
@@ -1557,10 +1591,7 @@ begin
         aNameSpace:=Compiler.FileCache.Namespaces[i];
         if aNameSpace='' then continue;
         if SameText(aNameSpace,DefNameSpace) then continue;
-        if FoundPasFilename='' then
-          FoundPasFilename:=TryUnitName(aNameSpace+'.'+UseUnitname);
-        if FoundPCUFilename='' then
-          FoundPCUFilename:=FindPCU(aNameSpace+'.'+UseUnitname,FoundPCUFormat);
+        TryUnitName(aNameSpace+'.'+UseUnitname);
       end;
     end;
   end;
@@ -1574,30 +1605,33 @@ begin
       if aFile<>nil then
       begin
         FoundPasFilename:=aFile.PasFilename;
-        FoundUnitName:=UseUnitName;
+        FoundPasUnitName:=UseUnitName;
       end;
     end;
     if FoundPasFilename='' then
     begin
       // search Pascal file
-      FoundPasFilename:=FileResolver.FindUnitFileName(UseUnitname,InFilename,FoundIsForeign);
+      FoundPasFilename:=FileResolver.FindUnitFileName(UseUnitname,InFilename,FoundPasIsForeign);
       if FoundPasFilename<>'' then
         begin
         if InFilename<>'' then
-          FoundUnitName:=ExtractFilenameOnly(InFilename)
+          FoundPasUnitName:=ExtractFilenameOnly(InFilename)
         else
-          FoundUnitName:=UseUnitName;
+          FoundPasUnitName:=UseUnitName;
         end
       else if InFilename<>'' then
         exit; // an in-filename unit source is missing -> stop
     end;
   end;
   if FoundPCUFilename='' then
+  begin
     FoundPCUFilename:=FindPCU(UseUnitName,FoundPCUFormat);
+    FoundPCUUnitName:=UseUnitName;
+  end;
 
   if (FoundPCUFilename<>'') and (FoundPasFilename='') then
   begin
-    aFile:=LoadUsedUnit(FoundPCUFilename,UseUnitName,'',NameExpr,nil,false,FoundPCUFormat);
+    aFile:=LoadUsedUnit(FoundPCUFilename,FoundPCUUnitName,'',NameExpr,nil,false,FoundPCUFormat);
     if aFile<>nil then
       Result:=aFile.PasModule;
     exit;
@@ -1606,8 +1640,8 @@ begin
   if FoundPasFilename<>'' then
   begin
     // load unit
-    aFile:=LoadUsedUnit(FoundPasFilename,FoundUnitName,InFilename,
-                         NameExpr,InFileExpr,FoundIsForeign,nil);
+    aFile:=LoadUsedUnit(FoundPasFilename,FoundPasUnitName,InFilename,
+                         NameExpr,InFileExpr,FoundPasIsForeign,nil);
     if aFile<>nil then
       Result:=aFile.PasModule;
   end;
@@ -1652,7 +1686,7 @@ var
     Cycle: TFPList;
     CyclePath: String;
   begin
-    if Parser.CurModule.ImplementationSection=nil then
+    if PasModule.ImplementationSection=nil then
     begin
       // main uses section (e.g. interface or program, not implementation)
       // -> check for cycles
@@ -1910,6 +1944,7 @@ var
   i: Integer;
   aFile: TPas2jsCompilerFile;
   Found: Boolean;
+  Section: TPasSection;
 begin
   // parse til exception or all modules have finished
   repeat
@@ -1934,6 +1969,13 @@ begin
       writeln('TPas2jsCompiler.ProcessQueue aFile=',aFile.PasFilename);
       {$ENDIF}
       aFile.ReadContinue;
+      if aFile.ReaderState=prsCanContinue then
+      begin
+        {$IF defined(VerbosePasResolver) or defined(VerboseUnitQueue)}
+        writeln('TPas2jsCompiler.ProcessQueue aFile=',aFile.PasFilename,' ReadContinue buggy');
+        {$ENDIF}
+        RaiseInternalError(20180313130300,'File='+aFile.PasFilename+' ReadContinue buggy');
+      end;
       break;
       end;
   until not Found;
@@ -1945,13 +1987,20 @@ begin
   for i:=0 to FReadingModules.Count-1 do
     begin
     aFile:=TPas2jsCompilerFile(FReadingModules[i]);
-    if aFile.Parser.CurModule<>nil then
+    if aFile.PascalResolver=nil then
+      RaiseInternalError(20180313124125,aFile.PasFilename);
+    if (aFile.PCUReader=nil) and (aFile.Parser.CurModule<>nil) then
       begin
       {$IF defined(VerbosePasResolver) or defined(VerboseUnitQueue)}
       writeln('TPas2jsCompiler.ProcessQueue aFile=',aFile.PasFilename,' was not finished');
       {$ENDIF}
       RaiseInternalError(20180305185342,aFile.PasFilename);
       end;
+    Section:=aFile.PascalResolver.GetLastSection;
+    if Section=nil then
+      RaiseInternalError(20180313124207,aFile.PasFilename);
+    if Section.PendingUsedIntf<>nil then
+      RaiseInternalError(20180313124226,aFile.PasFilename+' '+GetObjName(Section)+' PendingUsedIntf='+GetObjName(Section.PendingUsedIntf));
     end;
 end;
 
@@ -1998,7 +2047,7 @@ begin
   if FileCache.AllJSIntoMainJS and (WPOAnalyzer<>nil)
   and not WPOAnalyzer.IsUsed(aFile.PasModule) then
   begin
-    {$IF defined(VerboseUnitQueue) or defined(VerbosePJUFiler)}
+    {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
     writeln('TPas2jsCompiler.MarkNeedBuilding module not used by WPO: ',aFile.PasFilename);
     {$ENDIF}
     exit(false);

+ 60 - 40
packages/pastojs/src/pas2jsfiler.pp

@@ -820,7 +820,8 @@ type
     function ReadMemberHints(Obj: TJSONObject; El: TPasElement; const DefaultValue: TPasMemberHints): TPasMemberHints; virtual;
     procedure ReadPasElement(Obj: TJSONObject; El: TPasElement; aContext: TPCUReaderContext); virtual;
     procedure ReadExtRefs(Obj: TJSONObject; El: TPasElement); virtual;
-    procedure ReadUsedUnits(Obj: TJSONObject; Section: TPasSection; aContext: TPCUReaderContext); virtual;
+    procedure ReadUsedUnitsInit(Obj: TJSONObject; Section: TPasSection; aContext: TPCUReaderContext); virtual;
+    procedure ReadUsedUnitsFinish(Obj: TJSONObject; Section: TPasSection; aContext: TPCUReaderContext); virtual;
     procedure ReadSectionScope(Obj: TJSONObject; Scope: TPasSectionScope; aContext: TPCUReaderContext); virtual;
     procedure ReadSection(Obj: TJSONObject; Section: TPasSection; aContext: TPCUReaderContext); virtual;
     procedure ReadDeclarations(Obj: TJSONObject; Section: TPasSection; aContext: TPCUReaderContext); virtual;
@@ -1365,32 +1366,19 @@ end;
 { TPCUCustomReader }
 
 function TPCUCustomReader.ReadCanContinue: boolean;
-
-  function SectionCanContinue(Section: TPasSection): boolean;
-  var
-    Scope: TPasSectionScope;
-  begin
-    Scope:=Section.CustomData as TPasSectionScope;
-    if Scope.Finished then exit(false);
-    Result:=Section.PendingUsedIntf=nil;
-  end;
-
 var
   Module: TPasModule;
+  Section: TPasSection;
+  Scope: TPasSectionScope;
 begin
   Result:=false;
   Module:=Resolver.RootElement;
-  if Module is TPasProgram then
-    Result:=SectionCanContinue(TPasProgram(Module).ProgramSection)
-  else if Module is TPasLibrary then
-    Result:=SectionCanContinue(TPasLibrary(Module).LibrarySection)
-  else
-    begin
-    if Module.ImplementationSection<>nil then
-      Result:=SectionCanContinue(Module.ImplementationSection)
-    else
-      Result:=SectionCanContinue(Module.InterfaceSection);
-    end;
+  if Module=nil then exit(true); // not yet started
+  Section:=Resolver.GetLastSection;
+  if Section=nil then exit(true); // only header
+  Scope:=Section.CustomData as TPasSectionScope;
+  if Scope.Finished then exit(false); // finished
+  Result:=Section.PendingUsedIntf=nil;
 end;
 
 { TPCUFilerElementRef }
@@ -4626,20 +4614,19 @@ begin
     end;
 end;
 
-procedure TPCUReader.ReadUsedUnits(Obj: TJSONObject; Section: TPasSection;
+procedure TPCUReader.ReadUsedUnitsInit(Obj: TJSONObject; Section: TPasSection;
   aContext: TPCUReaderContext);
+// Note: can be called twice for each section if there are pending used interfaces
 var
   Arr: TJSONArray;
   i, Id: Integer;
   Data: TJSONData;
-  UsesObj, ModuleObj: TJSONObject;
+  UsesObj: TJSONObject;
   Name, InFilename, ModuleName: string;
   Use: TPasUsesUnit;
   Module: TPasModule;
-  Scope, UsedScope: TPasSectionScope;
 begin
   if not ReadArray(Obj,'Uses',Arr,Section) then exit;
-  Scope:=Section.CustomData as TPasSectionScope;
   SetLength(Section.UsesClause,Arr.Count);
   for i:=0 to length(Section.UsesClause)-1 do
     Section.UsesClause[i]:=nil;
@@ -4668,16 +4655,50 @@ begin
     if Module=nil then
       RaiseMsg(20180307231247,Use);
     Use.Module:=Module;
-    UsedScope:=Module.InterfaceSection.CustomData as TPasSectionScope;
-    Scope.UsesScopes.Add(UsedScope);
-
+    Module.AddRef;
     if ReadInteger(UsesObj,'Id',Id,Use) then
       AddElReference(Id,Use,Use);
+    end;
+  Resolver.CheckPendingUsedInterface(Section);
+  if aContext=nil then ;
+end;
+
+procedure TPCUReader.ReadUsedUnitsFinish(Obj: TJSONObject;
+  Section: TPasSection; aContext: TPCUReaderContext);
+var
+  Arr: TJSONArray;
+  Scope, UsedScope: TPasSectionScope;
+  i: Integer;
+  Use: TPasUsesUnit;
+  Module: TPasModule;
+  Data: TJSONData;
+  UsesObj, ModuleObj: TJSONObject;
+begin
+  if not ReadArray(Obj,'Uses',Arr,Section) then exit;
+  Scope:=Section.CustomData as TPasSectionScope;
+  if Scope.UsesFinished then
+    RaiseMsg(20180313133931,Section);
+  if Section.PendingUsedIntf<>nil then
+    RaiseMsg(20180313134142,Section,GetObjName(Section.PendingUsedIntf));
+  if Arr.Count<>length(Section.UsesClause) then
+    RaiseMsg(20180313134338,IntToStr(Arr.Count)+'<>'+IntToStr(length(Section.UsesClause)));
+  for i:=0 to Arr.Count-1 do
+  begin
+    Data:=Arr[i];
+    if not (Data is TJSONObject) then
+      RaiseMsg(20180313134409,Section,GetObjName(Data));
+    UsesObj:=TJSONObject(Data);
+    Use:=Section.UsesClause[i];
+
+    Module:=Use.Module as TPasModule;
+    UsedScope:=Module.InterfaceSection.CustomData as TPasSectionScope;
+    Scope.UsesScopes.Add(UsedScope);
     // Refs
     if ReadObject(UsesObj,'Module',ModuleObj,Use) then
       ReadExtRefs(ModuleObj,Module);
-    end;
-  Resolver.CheckPendingUsedInterface(Section);
+  end;
+  Scope.UsesFinished:=true;
+
   if aContext=nil then ;
 end;
 
@@ -4689,6 +4710,7 @@ end;
 
 procedure TPCUReader.ReadSection(Obj: TJSONObject; Section: TPasSection;
   aContext: TPCUReaderContext);
+// Note: can be called twice for each section if there are pending used interfaces
 var
   Scope: TPasSectionScope;
 begin
@@ -4699,24 +4721,22 @@ begin
     begin
     ReadPasElement(Obj,Section,aContext);
     Scope:=TPasSectionScope(Resolver.CreateScope(Section,TPasSectionScope));
-    ReadUsedUnits(Obj,Section,aContext);
+    ReadUsedUnitsInit(Obj,Section,aContext);
     if Section.PendingUsedIntf<>nil then exit;
     end
   else
     begin
     Scope:=Section.CustomData as TPasSectionScope;
+    if Scope.Finished then
+      RaiseMsg(20180308160336,Section);
     if Section.PendingUsedIntf<>nil then
       RaiseMsg(20180308160639,Section,GetObjName(Section.PendingUsedIntf));
     end;
-
-  if Scope.Finished then
-    RaiseMsg(20180308160336,Section);
-  if Scope.UsesFinished then
-    RaiseMsg(20180308160337,Section);
-  Scope.UsesFinished:=true;
-
+  // read external references
+  ReadUsedUnitsFinish(Obj,Section,aContext);
+  // read scope, needs external refs
   ReadSectionScope(Obj,Scope,aContext);
-
+  // read declarations, needs external refs
   ReadDeclarations(Obj,Section,aContext);
   Scope.Finished:=true;
   if Section is TInterfaceSection then

+ 24 - 24
packages/pastojs/tests/tcfiler.pas

@@ -36,8 +36,8 @@ type
   private
     FAnalyzer: TPasAnalyzer;
     FInitialFlags: TPCUInitialFlags;
-    FPJUReader: TPCUReader;
-    FPJUWriter: TPCUWriter;
+    FPCUReader: TPCUReader;
+    FPCUWriter: TPCUWriter;
     FRestAnalyzer: TPasAnalyzer;
     procedure OnFilerGetSrc(Sender: TObject; aFilename: string; out p: PChar;
       out Count: integer);
@@ -121,8 +121,8 @@ type
   public
     property Analyzer: TPasAnalyzer read FAnalyzer;
     property RestAnalyzer: TPasAnalyzer read FRestAnalyzer;
-    property PJUWriter: TPCUWriter read FPJUWriter write FPJUWriter;
-    property PJUReader: TPCUReader read FPJUReader write FPJUReader;
+    property PCUWriter: TPCUWriter read FPCUWriter write FPCUWriter;
+    property PCUReader: TPCUReader read FPCUReader write FPCUReader;
     property InitialFlags: TPCUInitialFlags read FInitialFlags;
   end;
 
@@ -220,7 +220,7 @@ function TCustomTestPrecompile.OnRestResolverFindUnit(const aUnitName: String
       begin
       CurEngine:=Resolvers[i];
       CurUnitName:=ExtractFileUnitName(CurEngine.Filename);
-      {$IFDEF VerbosePJUFiler}
+      {$IFDEF VerbosePCUFiler}
       //writeln('TCustomTestPrecompile.FindRestUnit Checking ',i,'/',ResolverCount,' ',CurEngine.Filename,' ',CurUnitName);
       {$ENDIF}
       if CompareText(Name,CurUnitName)=0 then
@@ -228,12 +228,12 @@ function TCustomTestPrecompile.OnRestResolverFindUnit(const aUnitName: String
         Result:=CurEngine.Module;
         if Result<>nil then
           begin
-          {$IFDEF VerbosePJUFiler}
+          {$IFDEF VerbosePCUFiler}
           //writeln('TCustomTestPrecompile.FindRestUnit Found parsed module: ',Result.Filename);
           {$ENDIF}
           exit;
           end;
-        {$IFDEF VerbosePJUFiler}
+        {$IFDEF VerbosePCUFiler}
         writeln('TCustomTestPrecompile.FindRestUnit PARSING unit "',CurEngine.Filename,'"');
         {$ENDIF}
         Fail('not parsed');
@@ -270,8 +270,8 @@ end;
 procedure TCustomTestPrecompile.TearDown;
 begin
   FreeAndNil(FAnalyzer);
-  FreeAndNil(FPJUWriter);
-  FreeAndNil(FPJUReader);
+  FreeAndNil(FPCUWriter);
+  FreeAndNil(FPCUReader);
   FreeAndNil(FInitialFlags);
   inherited TearDown;
 end;
@@ -291,7 +291,7 @@ end;
 procedure TCustomTestPrecompile.WriteReadUnit;
 var
   ms: TMemoryStream;
-  PJU, RestJSSrc, OrigJSSrc: string;
+  PCU, RestJSSrc, OrigJSSrc: string;
   // restored classes:
   RestResolver: TTestEnginePasResolver;
   RestFileResolver: TFileResolver;
@@ -302,8 +302,8 @@ var
 begin
   ConvertUnit;
 
-  FPJUWriter:=TPCUWriter.Create;
-  FPJUReader:=TPCUReader.Create;
+  FPCUWriter:=TPCUWriter.Create;
+  FPCUReader:=TPCUReader.Create;
   ms:=TMemoryStream.Create;
   RestParser:=nil;
   RestScanner:=nil;
@@ -313,9 +313,9 @@ begin
   RestJSModule:=nil;
   try
     try
-      PJUWriter.OnGetSrc:=@OnFilerGetSrc;
-      PJUWriter.OnIsElementUsed:=@OnConverterIsElementUsed;
-      PJUWriter.WritePCU(Engine,Converter,InitialFlags,ms,false);
+      PCUWriter.OnGetSrc:=@OnFilerGetSrc;
+      PCUWriter.OnIsElementUsed:=@OnConverterIsElementUsed;
+      PCUWriter.WritePCU(Engine,Converter,InitialFlags,ms,false);
     except
       on E: Exception do
       begin
@@ -327,12 +327,12 @@ begin
     end;
 
     try
-      SetLength(PJU,ms.Size);
-      System.Move(ms.Memory^,PJU[1],length(PJU));
+      SetLength(PCU,ms.Size);
+      System.Move(ms.Memory^,PCU[1],length(PCU));
 
-      writeln('TCustomTestPrecompile.WriteReadUnit PJU START-----');
-      writeln(PJU);
-      writeln('TCustomTestPrecompile.WriteReadUnit PJU END-------');
+      writeln('TCustomTestPrecompile.WriteReadUnit PCU START-----');
+      writeln(PCU);
+      writeln('TCustomTestPrecompile.WriteReadUnit PCU END-------');
 
       RestFileResolver:=TFileResolver.Create;
       RestScanner:=TPascalScanner.Create(RestFileResolver);
@@ -345,8 +345,8 @@ begin
       RestParser.Options:=po_tcmodules;
       RestResolver.CurrentParser:=RestParser;
       ms.Position:=0;
-      PJUReader.ReadPCU(RestResolver,ms);
-      if not PJUReader.ReadContinue then
+      PCUReader.ReadPCU(RestResolver,ms);
+      if not PCUReader.ReadContinue then
         Fail('ReadContinue=false, pending used interfaces');
     except
       on E: Exception do
@@ -641,8 +641,8 @@ begin
       Fail(Path+'.UsesScopes['+IntToStr(i)+'] Rest='+GetObjName(TObject(Rest.UsesScopes[i])));
     RestUses:=TPasSectionScope(Rest.UsesScopes[i]);
     if OrigUses.ClassType<>RestUses.ClassType then
-      Fail(Path+'.Uses['+IntToStr(i)+'] Orig='+GetObjName(OrigUses)+' Rest='+GetObjName(RestUses));
-    CheckRestoredReference(Path+'.Uses['+IntToStr(i)+']',OrigUses.Element,RestUses.Element);
+      Fail(Path+'.UsesScopes['+IntToStr(i)+'] Orig='+GetObjName(OrigUses)+' Rest='+GetObjName(RestUses));
+    CheckRestoredReference(Path+'.UsesScopes['+IntToStr(i)+']',OrigUses.Element,RestUses.Element);
     end;
   AssertEquals(Path+'.Finished',Orig.Finished,Rest.Finished);
   CheckRestoredIdentifierScope(Path,Orig,Rest);

+ 26 - 8
packages/pastojs/tests/tcmodules.pas

@@ -662,31 +662,40 @@ var
     Result:=p;
   end;
 
+  procedure SkipLineEnd(var p: PChar);
+  begin
+    if p^ in [#10,#13] then
+    begin
+      if (p[1] in [#10,#13]) and (p^<>p[1]) then
+        inc(p,2)
+      else
+        inc(p);
+    end;
+  end;
+
   procedure DiffFound;
   var
     ActLineStartP, ActLineEndP, p, StartPos: PChar;
     ExpLine, ActLine: String;
-    i: Integer;
+    i, LineNo, DiffLineNo: Integer;
   begin
     writeln('Diff found "',Msg,'". Lines:');
     // write correct lines
     p:=PChar(Expected);
+    LineNo:=0;
+    DiffLineNo:=0;
     repeat
       StartPos:=p;
       while not (p^ in [#0,#10,#13]) do inc(p);
       ExpLine:=copy(Expected,StartPos-PChar(Expected)+1,p-StartPos);
-      if p^ in [#10,#13] then
-      begin
-        if (p[1] in [#10,#13]) and (p^<>p[1]) then
-          inc(p,2)
-        else
-          inc(p);
-      end;
+      SkipLineEnd(p);
+      inc(LineNo);
       if (p<=ExpectedP) and (p^<>#0) then
       begin
         writeln('= ',ExpLine);
       end else begin
         // diff line
+        if DiffLineNo=0 then DiffLineNo:=LineNo;
         // write actual line
         ActLineStartP:=FindLineStart(ActualP,PChar(Actual));
         ActLineEndP:=FindLineEnd(ActualP);
@@ -699,6 +708,15 @@ var
         writeln('^');
         Msg:='expected "'+ExpLine+'", but got "'+ActLine+'".';
         CheckSrcDiff:=false;
+        // write up to three following actual lines to get some context
+        for i:=1 to 3 do begin
+          ActLineStartP:=ActLineEndP;
+          SkipLineEnd(ActLineStartP);
+          if ActLineStartP^=#0 then break;
+          ActLineEndP:=FindLineEnd(ActLineStartP);
+          ActLine:=copy(Actual,ActLineStartP-PChar(Actual)+1,ActLineEndP-ActLineStartP);
+          writeln('~ ',ActLine);
+        end;
         exit;
       end;
     until p^=#0;

+ 108 - 27
packages/pastojs/tests/tcprecompile.pas

@@ -26,52 +26,133 @@ interface
 uses
   Classes, SysUtils,
   fpcunit, testregistry,
-  tcunitsearch, tcmodules;
+  tcunitsearch, tcmodules, Pas2jsFileUtils;
 
 type
 
   { TTestCLI_Precompile }
 
   TTestCLI_Precompile = class(TCustomTestCLI)
-  public
+  protected
+    procedure CheckPrecompile(MainFile, UnitPaths: string;
+      SharedParams: TStringList = nil;
+      FirstRunParams: TStringList = nil;
+      SecondRunParams: TStringList = nil);
   published
     procedure TestPCU_EmptyUnit;
+    procedure TestPCU_ParamNS;
+    procedure TestPCU_UnitCycle;
   end;
 
+function LinesToList(const Lines: array of string): TStringList;
+
 implementation
 
+function LinesToList(const Lines: array of string): TStringList;
+var
+  i: Integer;
+begin
+  Result:=TStringList.Create;
+  for i:=Low(Lines) to High(Lines) do Result.Add(Lines[i]);
+end;
+
 { TTestCLI_Precompile }
 
-procedure TTestCLI_Precompile.TestPCU_EmptyUnit;
+procedure TTestCLI_Precompile.CheckPrecompile(MainFile, UnitPaths: string;
+  SharedParams: TStringList; FirstRunParams: TStringList;
+  SecondRunParams: TStringList);
 var
-  aFile, JSFile: TCLIFile;
-  OrigSrc, NewSrc, s: String;
+  UnitOutputDir, JSFilename, OrigSrc, NewSrc, s: String;
+  JSFile: TCLIFile;
+begin
+  try
+    UnitOutputDir:='units';
+    AddDir(UnitOutputDir);
+    // compile, create  .pcu files
+    {$IFDEF VerbosePCUFiler}
+    writeln('TTestCLI_Precompile.CheckPrecompile create pcu files=========================');
+    {$ENDIF}
+    Params.Clear;
+    if SharedParams<>nil then
+      Params.Assign(SharedParams);
+    if FirstRunParams<>nil then
+      Params.AddStrings(FirstRunParams);
+    Compile([MainFile,'-Jc','-Fu'+UnitPaths,'-JUpcu','-FU'+UnitOutputDir]);
+    AssertFileExists('units/system.pcu');
+    JSFilename:=UnitOutputDir+PathDelim+ExtractFilenameOnly(MainFile)+'.js';
+    AssertFileExists(JSFilename);
+    JSFile:=FindFile(JSFilename);
+    OrigSrc:=JSFile.Source;
+    // compile, using .pcu files
+    {$IFDEF VerbosePCUFiler}
+    writeln('TTestCLI_Precompile.CheckPrecompile compile using pcu files==================');
+    {$ENDIF}
+    JSFile.Source:='';
+    Compiler.Reset;
+    Params.Clear;
+    if SharedParams<>nil then
+      Params.Assign(SharedParams);
+    if SecondRunParams<>nil then
+      Params.AddStrings(SecondRunParams);
+    Compile([MainFile,'-Jc','-FU'+UnitOutputDir]);
+    NewSrc:=JSFile.Source;
+    if not CheckSrcDiff(OrigSrc,NewSrc,s) then
+    begin
+      WriteSources;
+      Fail('test1.js: '+s);
+    end;
+  finally
+    SharedParams.Free;
+    FirstRunParams.Free;
+    SecondRunParams.Free;
+  end;
+end;
+
+procedure TTestCLI_Precompile.TestPCU_EmptyUnit;
+begin
+  AddUnit('src/system.pp',[''],['']);
+  AddFile('test1.pas',[
+    'begin',
+    'end.']);
+  CheckPrecompile('test1.pas','src');
+end;
+
+procedure TTestCLI_Precompile.TestPCU_ParamNS;
+begin
+  AddUnit('src/system.pp',[''],['']);
+  AddUnit('src/foo.unit1.pp',['var i: longint;'],['']);
+  AddFile('test1.pas',[
+    'uses unit1;',
+    'begin',
+    '  i:=3;',
+    'end.']);
+  CheckPrecompile('test1.pas','src',LinesToList(['-NSfoo']));
+end;
+
+procedure TTestCLI_Precompile.TestPCU_UnitCycle;
 begin
-  AddUnit('sub/system.pp',[''],['']);
+  AddUnit('src/system.pp',['type integer = longint;'],['']);
+  AddUnit('src/unit1.pp',
+  ['var i: integer;',
+   'procedure Do1(j: integer);'],
+  ['uses unit2;',
+   'procedure Do1(j: integer);',
+   'begin',
+   '  Do2(j);',
+   'end;']);
+  AddUnit('src/unit2.pp',
+  ['uses unit1;',
+  'procedure Do2(j: integer);'],
+  ['procedure Do2(j: integer);',
+   'begin',
+   '  unit1.i:=j;',
+   'end;']);
   AddFile('test1.pas',[
+    'uses unit1;',
     'begin',
+    '  Do1(3);',
     'end.']);
-  AddDir('units');
-  // compile, create  .pcu files
-  {$IFDEF VerbosePJUFiler}
-  writeln('TTestCLI_Precompile.TestPCU_EmptyUnit create pcu files=========================');
-  {$ENDIF}
-  Compile(['test1.pas','-Jc','-Fusub','-JUpcu','-FUunits']);
-  aFile:=FindFile('units/system.pcu');
-  AssertNotNull('units/system.pcu',aFile);
-  JSFile:=FindFile('units/test1.js');
-  AssertNotNull('units/test1.js',JSFile);
-  OrigSrc:=JSFile.Source;
-
-  // compile, using .pcu files
-  {$IFDEF VerbosePJUFiler}
-  writeln('TTestCLI_Precompile.TestPCU_EmptyUnit compile using pcu files==================');
-  {$ENDIF}
-  Compiler.Reset;
-  Compile(['test1.pas','-Jc','-Fuunits']);
-  NewSrc:=JSFile.Source;
-  if not CheckSrcDiff(OrigSrc,NewSrc,s) then
-    Fail('test1.js: '+s);
+  CheckPrecompile('test1.pas','src');
 end;
 
 Initialization

+ 31 - 12
packages/pastojs/tests/tcunitsearch.pas

@@ -112,6 +112,7 @@ type
     function AddFile(Filename: string; const SourceLines: array of string): TCLIFile;
     function AddUnit(Filename: string; const Intf, Impl: array of string): TCLIFile;
     function AddDir(Filename: string): TCLIFile;
+    procedure AssertFileExists(Filename: string);
     property WorkDir: string read FWorkDir write SetWorkDir;
     property DefaultFileAge: longint read FDefaultFileAge write FDefaultFileAge;
     property ExitCode: integer read GetExitCode write SetExitCode;
@@ -240,7 +241,7 @@ procedure TCustomTestCLI.DoLog(Sender: TObject; const Msg: String);
 var
   LogMsg: TCLILogMsg;
 begin
-  {$IF defined(VerbosePasResolver) or defined(VerbosePJUFiler)}
+  {$IF defined(VerbosePasResolver) or defined(VerbosePCUFiler)}
   writeln('TCustomTestCLI.DoLog ',Msg,' File=',Compiler.Log.LastMsgFile,' Line=',Compiler.Log.LastMsgLine);
   {$ENDIF}
   LogMsg:=TCLILogMsg.Create;
@@ -296,7 +297,7 @@ begin
   if aFile=nil then exit(false);
   if (faDirectory and aFile.Attr)>0 then
   begin
-    {$IF defined(VerbosePasResolver) or defined(VerbosePJUFiler)}
+    {$IF defined(VerbosePasResolver) or defined(VerbosePCUFiler)}
     writeln('[20180224000557] TCustomTestCLI.OnReadFile ',aFilename);
     {$ENDIF}
     EPas2jsFileCache.Create('TCustomTestCLI.OnReadFile: reading directory '+aFilename);
@@ -309,29 +310,40 @@ end;
 procedure TCustomTestCLI.OnWriteFile(aFilename: string; Source: string);
 var
   aFile: TCLIFile;
+  {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
+  //i: Integer;
+  {$ENDIF}
 begin
   aFile:=FindFile(aFilename);
-  {$IF defined(VerbosePasResolver) or defined(VerbosePJUFiler)}
+  {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
   writeln('TCustomTestCLI.WriteFile ',aFilename,' Found=',aFile<>nil,' SrcLen=',length(Source));
   {$ENDIF}
   if aFile<>nil then
   begin
-    if faDirectory and aFile.Attr>0 then
+    if (faDirectory and aFile.Attr)>0 then
     begin
-      {$IF defined(VerbosePasResolver) or defined(VerbosePJUFiler)}
+      {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
       writeln('[20180223175616] TCustomTestCLI.OnWriteFile ',aFilename);
       {$ENDIF}
       raise EPas2jsFileCache.Create('unable to write file to directory "'+aFilename+'"');
     end;
   end else
   begin
+    {$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
+    //writeln('TCustomTestCLI.OnWriteFile FFiles: ',FFiles.Count);
+    //for i:=0 to FFiles.Count-1 do
+    //begin
+    //  aFile:=TCLIFile(FFiles[i]);
+    //  writeln('  ',i,': Filename=',aFile.Filename,' ',CompareFilenames(aFile.Filename,aFilename),' Dir=',(aFile.Attr and faDirectory)>0,' Len=',length(aFile.Source));
+    //end;
+    {$ENDIF}
     aFile:=TCLIFile.Create(aFilename,'',0,0);
     FFiles.Add(aFile);
   end;
   aFile.Source:=Source;
   aFile.Attr:=faNormal;
   aFile.Age:=DateTimeToFileDate(CurDate);
-  //writeln('TCustomTestCLI.OnWriteFile ',aFile.Filename,' "',LeftStr(aFile.Source,50),'"');
+  writeln('TCustomTestCLI.OnWriteFile ',aFile.Filename,' Found=',FindFile(aFilename)<>nil,' "',LeftStr(aFile.Source,50),'" ');
 end;
 
 procedure TCustomTestCLI.WriteSources;
@@ -394,7 +406,6 @@ var
   i: Integer;
 begin
   AssertEquals('Initial System.ExitCode',0,system.ExitCode);
-  Params.Clear;
   for i:=low(Args) to High(Args) do
     Params.Add(Args[i]);
   try
@@ -404,13 +415,13 @@ begin
     except
       on E: ECompilerTerminate do
       begin
-        {$IF defined(VerbosePasResolver) or defined(VerbosePJUFiler)}
+        {$IF defined(VerbosePasResolver) or defined(VerbosePCUFiler)}
         writeln('TCustomTestCLI.Compile ',E.ClassName,':',E.Message);
         {$ENDIF}
       end;
       on E: Exception do
       begin
-        {$IF defined(VerbosePasResolver) or defined(VerbosePJUFiler)}
+        {$IF defined(VerbosePasResolver) or defined(VerbosePCUFiler)}
         writeln('TCustomTestCLI.Compile ',E.ClassName,':',E.Message);
         {$ENDIF}
         Fail('TCustomTestCLI.Compile '+E.ClassName+':'+E.Message);
@@ -454,7 +465,7 @@ end;
 function TCustomTestCLI.AddFile(Filename, Source: string): TCLIFile;
 begin
   Filename:=ExpandFilename(Filename);
-  {$IFDEF VerbosePJUFiler}
+  {$IFDEF VerbosePCUFiler}
   writeln('TCustomTestCLI.AddFile ',Filename);
   {$ENDIF}
   Result:=FindFile(Filename);
@@ -504,14 +515,14 @@ begin
         Result:=aFile;
       if aFile=nil then
         begin
-        {$IFDEF VerbosePJUFiler}
+        {$IFDEF VerbosePCUFiler}
         writeln('TCustomTestCLI.AddDir add Dir=',Dir);
         {$ENDIF}
         FFiles.Add(TCLIFile.Create(Dir,'',DefaultFileAge,faDirectory));
         end
       else if (aFile.Attr and faDirectory)=0 then
       begin
-        {$IFDEF VerbosePJUFiler}
+        {$IFDEF VerbosePCUFiler}
         writeln('[20180224001036] TCustomTestCLI.AddDir file exists: Dir=',Dir);
         {$ENDIF}
         raise EPas2jsFileCache.Create('[20180224001036] TCustomTestCLI.AddDir Dir='+Dir);
@@ -522,6 +533,14 @@ begin
   end;
 end;
 
+procedure TCustomTestCLI.AssertFileExists(Filename: string);
+var
+  aFile: TCLIFile;
+begin
+  aFile:=FindFile(Filename);
+  AssertNotNull('File not found: '+Filename,aFile);
+end;
+
 function TCustomTestCLI.GetLogCount: integer;
 begin
   Result:=FLogMsgs.Count;