Przeglądaj źródła

pastojs: started test TPas2JSUseAnalyzer

mattias 3 lat temu
rodzic
commit
e5ac07db13

+ 2 - 287
packages/fcl-passrc/tests/tcbaseparser.pas

@@ -5,7 +5,8 @@ unit tcbaseparser;
 interface
 
 uses
-  Classes, SysUtils, fpcunit, pastree, pscanner, pparser, testregistry;
+  Classes, SysUtils, fpcunit, pastree, pscanner, pparser, TestPasUtils,
+  testregistry;
 
 const
   DefaultMainFilename = 'afile.pp';
@@ -103,296 +104,10 @@ Type
     Property MainFilename: string read FMainFilename write FMainFilename;
   end;
 
-function ExtractFileUnitName(aFilename: string): string;
-function GetPasElementDesc(El: TPasElement): string;
-procedure ReadNextPascalToken(var Position: PChar; out TokenStart: PChar;
-  NestedComments: boolean; SkipDirectives: boolean);
-
 implementation
 
 uses typinfo;
 
-function ExtractFileUnitName(aFilename: string): string;
-var
-  p: Integer;
-begin
-  Result:=ExtractFileName(aFilename);
-  if Result='' then exit;
-  for p:=length(Result) downto 1 do
-    case Result[p] of
-    '/','\': exit;
-    '.':
-      begin
-      Delete(Result,p,length(Result));
-      exit;
-      end;
-    end;
-end;
-
-function GetPasElementDesc(El: TPasElement): string;
-begin
-  if El=nil then exit('nil');
-  Result:=El.Name+':'+El.ClassName+'['+El.SourceFilename+','+IntToStr(El.SourceLinenumber)+']';
-end;
-
-procedure ReadNextPascalToken(var Position: PChar; out TokenStart: PChar;
-  NestedComments: boolean; SkipDirectives: boolean);
-const
-  IdentChars = ['a'..'z','A'..'Z','_','0'..'9'];
-  HexNumberChars = ['0'..'9','a'..'f','A'..'F'];
-var
-  c1:char;
-  CommentLvl: Integer;
-  Src: PChar;
-begin
-  Src:=Position;
-  // read till next atom
-  while true do
-    begin
-    case Src^ of
-    #0: break;
-    #1..#32:  // spaces and special characters
-      inc(Src);
-    #$EF:
-      if (Src[1]=#$BB)
-      and (Src[2]=#$BF) then
-        begin
-        // skip UTF BOM
-        inc(Src,3);
-        end
-      else
-        break;
-    '{':    // comment start or compiler directive
-      if (Src[1]='$') and (not SkipDirectives) then
-        // compiler directive
-        break
-      else begin
-        // Pascal comment => skip
-        CommentLvl:=1;
-        while true do
-          begin
-          inc(Src);
-          case Src^ of
-          #0: break;
-          '{':
-            if NestedComments then
-              inc(CommentLvl);
-          '}':
-            begin
-            dec(CommentLvl);
-            if CommentLvl=0 then
-              begin
-              inc(Src);
-              break;
-              end;
-            end;
-          end;
-        end;
-      end;
-    '/':  // comment or real division
-      if (Src[1]='/') then
-        begin
-        // comment start -> read til line end
-        inc(Src);
-        while not (Src^ in [#0,#10,#13]) do
-          inc(Src);
-        end
-      else
-        break;
-    '(':  // comment, bracket or compiler directive
-      if (Src[1]='*') then
-        begin
-        if (Src[2]='$') and (not SkipDirectives) then
-          // compiler directive
-          break
-        else
-          begin
-          // comment start -> read til comment end
-          inc(Src,2);
-          CommentLvl:=1;
-          while true do
-            begin
-            case Src^ of
-            #0: break;
-            '(':
-              if NestedComments and (Src[1]='*') then
-                inc(CommentLvl);
-            '*':
-              if (Src[1]=')') then
-                begin
-                dec(CommentLvl);
-                if CommentLvl=0 then
-                  begin
-                  inc(Src,2);
-                  break;
-                  end;
-                inc(Position);
-                end;
-            end;
-            inc(Src);
-            end;
-        end;
-      end else
-        // round bracket open
-        break;
-    else
-      break;
-    end;
-    end;
-  // read token
-  TokenStart:=Src;
-  c1:=Src^;
-  case c1 of
-  #0:
-    ;
-  'A'..'Z','a'..'z','_':
-    begin
-    // identifier
-    inc(Src);
-    while Src^ in IdentChars do
-      inc(Src);
-    end;
-  '0'..'9': // number
-    begin
-    inc(Src);
-    // read numbers
-    while (Src^ in ['0'..'9']) do
-      inc(Src);
-    if (Src^='.') and (Src[1]<>'.') then
-      begin
-      // real type number
-      inc(Src);
-      while (Src^ in ['0'..'9']) do
-        inc(Src);
-      end;
-    if (Src^ in ['e','E']) then
-      begin
-      // read exponent
-      inc(Src);
-      if (Src^='-') then inc(Src);
-      while (Src^ in ['0'..'9']) do
-        inc(Src);
-      end;
-    end;
-  '''','#':  // string constant
-    while true do
-      case Src^ of
-      #0: break;
-      '#':
-        begin
-        inc(Src);
-        while Src^ in ['0'..'9'] do
-          inc(Src);
-        end;
-      '''':
-        begin
-        inc(Src);
-        while not (Src^ in ['''',#0]) do
-          inc(Src);
-        if Src^='''' then
-          inc(Src);
-        end;
-      else
-        break;
-      end;
-  '$':  // hex constant
-    begin
-    inc(Src);
-    while Src^ in HexNumberChars do
-      inc(Src);
-    end;
-  '&':  // octal constant or keyword as identifier (e.g. &label)
-    begin
-    inc(Src);
-    if Src^ in ['0'..'7'] then
-      while Src^ in ['0'..'7'] do
-        inc(Src)
-    else
-      while Src^ in IdentChars do
-        inc(Src);
-    end;
-  '{':  // compiler directive (it can't be a comment, because see above)
-    begin
-    CommentLvl:=1;
-    while true do
-      begin
-      inc(Src);
-      case Src^ of
-      #0: break;
-      '{':
-        if NestedComments then
-          inc(CommentLvl);
-      '}':
-        begin
-        dec(CommentLvl);
-        if CommentLvl=0 then
-          begin
-          inc(Src);
-          break;
-          end;
-        end;
-      end;
-      end;
-    end;
-  '(':  // bracket or compiler directive
-    if (Src[1]='*') then
-      begin
-      // compiler directive -> read til comment end
-      inc(Src,2);
-      while (Src^<>#0) and ((Src^<>'*') or (Src[1]<>')')) do
-        inc(Src);
-      inc(Src,2);
-      end
-    else
-      // round bracket open
-      inc(Src);
-  #192..#255:
-    begin
-    // read UTF8 character
-    inc(Src);
-    if ((ord(c1) and %11100000) = %11000000) then
-      begin
-      // could be 2 byte character
-      if (ord(Src[0]) and %11000000) = %10000000 then
-        inc(Src);
-      end
-    else if ((ord(c1) and %11110000) = %11100000) then
-      begin
-      // could be 3 byte character
-      if ((ord(Src[0]) and %11000000) = %10000000)
-      and ((ord(Src[1]) and %11000000) = %10000000) then
-        inc(Src,2);
-      end
-    else if ((ord(c1) and %11111000) = %11110000) then
-      begin
-      // could be 4 byte character
-      if ((ord(Src[0]) and %11000000) = %10000000)
-      and ((ord(Src[1]) and %11000000) = %10000000)
-      and ((ord(Src[2]) and %11000000) = %10000000) then
-        inc(Src,3);
-      end;
-    end;
-  else
-    inc(Src);
-    case c1 of
-    '<': if Src^ in ['>','='] then inc(Src);
-    '.': if Src^='.' then inc(Src);
-    '@':
-      if Src^='@' then
-        begin
-        // @@ label
-        repeat
-          inc(Src);
-        until not (Src^ in IdentChars);
-        end
-    else
-      if (Src^='=') and (c1 in [':','+','-','/','*','<','>']) then
-        inc(Src);
-    end;
-  end;
-  Position:=Src;
-end;
-
 { TTestEngine }
 
 destructor TTestEngine.Destroy;

+ 1 - 1
packages/fcl-passrc/tests/tcexprparser.pas

@@ -5,7 +5,7 @@ unit tcexprparser;
 interface
 
 uses
-  Classes, SysUtils, fpcunit,  testregistry, tcbaseparser, pastree;
+  Classes, SysUtils, fpcunit, testregistry, tcbaseparser, TestPasUtils, pastree;
 
 type
 

+ 1 - 1
packages/fcl-passrc/tests/tcresolver.pas

@@ -20,7 +20,7 @@ interface
 uses
   Classes, SysUtils, contnrs, strutils, fpcunit, testregistry,
   PasTree, PScanner, PParser, PasResolver, PasResolveEval,
-  tcbaseparser;
+  tcbaseparser, TestPasUtils;
 
 type
   TSrcMarkerKind = (

+ 1 - 1
packages/fcl-passrc/tests/tcstatements.pas

@@ -10,7 +10,7 @@ interface
 
 uses
   Classes, SysUtils, fpcunit, pastree, pscanner, pparser,
-  tcbaseparser, testregistry;
+  tcbaseparser, TestPasUtils, testregistry;
 
 Type
   { TTestStatementParser }

+ 6 - 1
packages/fcl-passrc/tests/tcuseanalyzer.pas

@@ -10,7 +10,8 @@ interface
 
 uses
   Classes, SysUtils, fpcunit, PasTree, PScanner, PasResolver, tcbaseparser,
-  testregistry, strutils, tcresolver, PasUseAnalyzer, PasResolveEval;
+  testregistry, strutils, tcresolver, PasUseAnalyzer,
+  PasResolveEval;
 
 type
 
@@ -280,7 +281,9 @@ begin
   aMarker:=FirstSrcMarker;
   while aMarker<>nil do
     begin
+    {$IFDEF VerbosePasAnalyzer}
     writeln('TCustomTestUseAnalyzer.CheckUsedMarkers ',aMarker^.Identifier,' Line=',aMarker^.Row,' StartCol=',aMarker^.StartCol,' EndCol=',aMarker^.EndCol);
+    {$ENDIF}
     p:=RPos('_',aMarker^.Identifier);
     if p>1 then
       begin
@@ -303,7 +306,9 @@ begin
         for i:=0 to Elements.Count-1 do
           begin
           El:=TPasElement(Elements[i]);
+          {$IFDEF VerbosePasAnalyzer}
           writeln('TCustomTestUseAnalyzer.CheckUsedMarkers ',aMarker^.Identifier,' ',i,'/',Elements.Count,' El=',GetObjName(El),' ',GetObjName(El.CustomData));
+          {$ENDIF}
           case ExpectedUsed of
           uUsed,uNotUsed:
             if Analyzer.IsUsed(El) then

+ 1 - 1
packages/fcl-passrc/tests/testpassrc.lpr

@@ -7,7 +7,7 @@ uses
   Classes, consoletestrunner, tcscanner,  tctypeparser, tcstatements,
   tcbaseparser, tcmoduleparser, tconstparser, tcvarparser, tcclasstype,
   tcexprparser, tcprocfunc, tcpassrcutil, tcresolver,
-  tcuseanalyzer, pasresolveeval, tcresolvegenerics, tcgenerics;
+  tcuseanalyzer, pasresolveeval, tcresolvegenerics, tcgenerics, TestPasUtils;
 
 type
 

+ 299 - 0
packages/fcl-passrc/tests/testpasutils.pas

@@ -0,0 +1,299 @@
+unit TestPasUtils;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, PasTree;
+
+function ExtractFileUnitName(aFilename: string): string;
+function GetPasElementDesc(El: TPasElement): string;
+procedure ReadNextPascalToken(var Position: PChar; out TokenStart: PChar;
+  NestedComments: boolean; SkipDirectives: boolean);
+
+implementation
+
+function ExtractFileUnitName(aFilename: string): string;
+var
+  p: Integer;
+begin
+  Result:=ExtractFileName(aFilename);
+  if Result='' then exit;
+  for p:=length(Result) downto 1 do
+    case Result[p] of
+    '/','\': exit;
+    '.':
+      begin
+      Delete(Result,p,length(Result));
+      exit;
+      end;
+    end;
+end;
+
+function GetPasElementDesc(El: TPasElement): string;
+begin
+  if El=nil then exit('nil');
+  Result:=El.Name+':'+El.ClassName+'['+El.SourceFilename+','+IntToStr(El.SourceLinenumber)+']';
+end;
+
+procedure ReadNextPascalToken(var Position: PChar; out TokenStart: PChar;
+  NestedComments: boolean; SkipDirectives: boolean);
+const
+  IdentChars = ['a'..'z','A'..'Z','_','0'..'9'];
+  HexNumberChars = ['0'..'9','a'..'f','A'..'F'];
+var
+  c1:char;
+  CommentLvl: Integer;
+  Src: PChar;
+begin
+  Src:=Position;
+  // read till next atom
+  while true do
+    begin
+    case Src^ of
+    #0: break;
+    #1..#32:  // spaces and special characters
+      inc(Src);
+    #$EF:
+      if (Src[1]=#$BB)
+      and (Src[2]=#$BF) then
+        begin
+        // skip UTF BOM
+        inc(Src,3);
+        end
+      else
+        break;
+    '{':    // comment start or compiler directive
+      if (Src[1]='$') and (not SkipDirectives) then
+        // compiler directive
+        break
+      else begin
+        // Pascal comment => skip
+        CommentLvl:=1;
+        while true do
+          begin
+          inc(Src);
+          case Src^ of
+          #0: break;
+          '{':
+            if NestedComments then
+              inc(CommentLvl);
+          '}':
+            begin
+            dec(CommentLvl);
+            if CommentLvl=0 then
+              begin
+              inc(Src);
+              break;
+              end;
+            end;
+          end;
+        end;
+      end;
+    '/':  // comment or real division
+      if (Src[1]='/') then
+        begin
+        // comment start -> read til line end
+        inc(Src);
+        while not (Src^ in [#0,#10,#13]) do
+          inc(Src);
+        end
+      else
+        break;
+    '(':  // comment, bracket or compiler directive
+      if (Src[1]='*') then
+        begin
+        if (Src[2]='$') and (not SkipDirectives) then
+          // compiler directive
+          break
+        else
+          begin
+          // comment start -> read til comment end
+          inc(Src,2);
+          CommentLvl:=1;
+          while true do
+            begin
+            case Src^ of
+            #0: break;
+            '(':
+              if NestedComments and (Src[1]='*') then
+                inc(CommentLvl);
+            '*':
+              if (Src[1]=')') then
+                begin
+                dec(CommentLvl);
+                if CommentLvl=0 then
+                  begin
+                  inc(Src,2);
+                  break;
+                  end;
+                inc(Position);
+                end;
+            end;
+            inc(Src);
+            end;
+        end;
+      end else
+        // round bracket open
+        break;
+    else
+      break;
+    end;
+    end;
+  // read token
+  TokenStart:=Src;
+  c1:=Src^;
+  case c1 of
+  #0:
+    ;
+  'A'..'Z','a'..'z','_':
+    begin
+    // identifier
+    inc(Src);
+    while Src^ in IdentChars do
+      inc(Src);
+    end;
+  '0'..'9': // number
+    begin
+    inc(Src);
+    // read numbers
+    while (Src^ in ['0'..'9']) do
+      inc(Src);
+    if (Src^='.') and (Src[1]<>'.') then
+      begin
+      // real type number
+      inc(Src);
+      while (Src^ in ['0'..'9']) do
+        inc(Src);
+      end;
+    if (Src^ in ['e','E']) then
+      begin
+      // read exponent
+      inc(Src);
+      if (Src^='-') then inc(Src);
+      while (Src^ in ['0'..'9']) do
+        inc(Src);
+      end;
+    end;
+  '''','#':  // string constant
+    while true do
+      case Src^ of
+      #0: break;
+      '#':
+        begin
+        inc(Src);
+        while Src^ in ['0'..'9'] do
+          inc(Src);
+        end;
+      '''':
+        begin
+        inc(Src);
+        while not (Src^ in ['''',#0]) do
+          inc(Src);
+        if Src^='''' then
+          inc(Src);
+        end;
+      else
+        break;
+      end;
+  '$':  // hex constant
+    begin
+    inc(Src);
+    while Src^ in HexNumberChars do
+      inc(Src);
+    end;
+  '&':  // octal constant or keyword as identifier (e.g. &label)
+    begin
+    inc(Src);
+    if Src^ in ['0'..'7'] then
+      while Src^ in ['0'..'7'] do
+        inc(Src)
+    else
+      while Src^ in IdentChars do
+        inc(Src);
+    end;
+  '{':  // compiler directive (it can't be a comment, because see above)
+    begin
+    CommentLvl:=1;
+    while true do
+      begin
+      inc(Src);
+      case Src^ of
+      #0: break;
+      '{':
+        if NestedComments then
+          inc(CommentLvl);
+      '}':
+        begin
+        dec(CommentLvl);
+        if CommentLvl=0 then
+          begin
+          inc(Src);
+          break;
+          end;
+        end;
+      end;
+      end;
+    end;
+  '(':  // bracket or compiler directive
+    if (Src[1]='*') then
+      begin
+      // compiler directive -> read til comment end
+      inc(Src,2);
+      while (Src^<>#0) and ((Src^<>'*') or (Src[1]<>')')) do
+        inc(Src);
+      inc(Src,2);
+      end
+    else
+      // round bracket open
+      inc(Src);
+  #192..#255:
+    begin
+    // read UTF8 character
+    inc(Src);
+    if ((ord(c1) and %11100000) = %11000000) then
+      begin
+      // could be 2 byte character
+      if (ord(Src[0]) and %11000000) = %10000000 then
+        inc(Src);
+      end
+    else if ((ord(c1) and %11110000) = %11100000) then
+      begin
+      // could be 3 byte character
+      if ((ord(Src[0]) and %11000000) = %10000000)
+      and ((ord(Src[1]) and %11000000) = %10000000) then
+        inc(Src,2);
+      end
+    else if ((ord(c1) and %11111000) = %11110000) then
+      begin
+      // could be 4 byte character
+      if ((ord(Src[0]) and %11000000) = %10000000)
+      and ((ord(Src[1]) and %11000000) = %10000000)
+      and ((ord(Src[2]) and %11000000) = %10000000) then
+        inc(Src,3);
+      end;
+    end;
+  else
+    inc(Src);
+    case c1 of
+    '<': if Src^ in ['>','='] then inc(Src);
+    '.': if Src^='.' then inc(Src);
+    '@':
+      if Src^='@' then
+        begin
+        // @@ label
+        repeat
+          inc(Src);
+        until not (Src^ in IdentChars);
+        end
+    else
+      if (Src^='=') and (c1 in [':','+','-','/','*','<','>']) then
+        inc(Src);
+    end;
+  end;
+  Position:=Src;
+end;
+
+end.
+

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

@@ -370,7 +370,7 @@ begin
   FInitialFlags:=TPCUInitialFlags.Create;
   FAnalyzer:=TPas2JSAnalyzer.Create;
   FCheckedElements:=TPasAnalyzerKeySet.Create(@CompareCheckedElementPairs,@CompareElWithCheckedElementPair);
-  Analyzer.Resolver:=Engine;
+  Analyzer.Resolver:=ResolverEngine;
   Analyzer.Options:=Analyzer.Options+[paoImplReferences];
   Converter.OnIsElementUsed:=@OnConverterIsElementUsed;
   Converter.OnIsTypeInfoUsed:=@OnConverterIsTypeInfoUsed;
@@ -433,7 +433,7 @@ begin
     try
       PCUWriter.OnGetSrc:=@OnFilerGetSrc;
       PCUWriter.OnIsElementUsed:=@OnConverterIsElementUsed;
-      PCUWriter.WritePCU(Engine,Converter,InitialFlags,ms,false);
+      PCUWriter.WritePCU(ResolverEngine,Converter,InitialFlags,ms,false);
     except
       on E: Exception do
       begin
@@ -457,7 +457,7 @@ begin
       RestScanner:=TPas2jsPasScanner.Create(RestFileResolver);
       InitScanner(RestScanner);
       RestResolver:=TTestEnginePasResolver.Create;
-      RestResolver.Filename:=Engine.Filename;
+      RestResolver.Filename:=ResolverEngine.Filename;
       RestResolver.AddObjFPCBuiltInIdentifiers(btAllJSBaseTypes,bfAllJSBaseProcs);
       RestResolver.OnFindUnit:=@OnRestResolverFindUnit;
       RestParser:=TPasParser.Create(RestScanner,RestFileResolver,RestResolver);
@@ -493,7 +493,7 @@ begin
       end;
     end;
     // check parser+resolver+analyzer
-    CheckRestoredResolver(Engine,RestResolver,[]);
+    CheckRestoredResolver(ResolverEngine,RestResolver,[]);
 
     // convert using the precompiled procs
     RestConverter:=CreateConverter;
@@ -1059,7 +1059,7 @@ begin
     if Orig.ModeSwitches<>Rest.ModeSwitches then
       Fail(Path+'.ModeSwitches');
 
-    if Engine.ProcCanBePrecompiled(DeclProc) then
+    if ResolverEngine.ProcCanBePrecompiled(DeclProc) then
       begin
       CheckRestoredScopeRefs(Path+'.References',Orig.References,Rest.References,Flags);
       end;
@@ -2041,7 +2041,7 @@ begin
   // Body
   if Orig.Body<>nil then
     begin
-    if not Engine.ProcCanBePrecompiled(DeclProc) then
+    if not ResolverEngine.ProcCanBePrecompiled(DeclProc) then
       begin
       // generic body
       if OrigScope.ImplJS<>nil then

+ 615 - 7
packages/pastojs/tests/tcmodules.pas

@@ -26,7 +26,7 @@ interface
 uses
   Classes, SysUtils, fpcunit, testregistry, contnrs,
   jstree, jswriter, jsbase,
-  PasTree, PScanner, PasResolver, PParser, PasResolveEval,
+  PasTree, PScanner, PasResolver, PParser, PasResolveEval, TestPasUtils,
   FPPas2Js;
 
 const
@@ -39,6 +39,13 @@ type
     mkResolverReference,
     mkDirectReference
     );
+const
+  SrcMarker: array[TSrcMarkerKind] of char = (
+    '#', // mkLabel
+    '@', // mkResolverReference
+    '='  // mkDirectReference
+    );
+type
   PSrcMarker = ^TSrcMarker;
   TSrcMarker = record
     Kind: TSrcMarkerKind;
@@ -69,6 +76,15 @@ type
     SourcePos: TPasSourcePos;
   end;
 
+  TTestResolverReferenceData = record
+    Filename: string;
+    Row: integer;
+    StartCol: integer;
+    EndCol: integer;
+    Found: TFPList; // list of TPasElement at this token
+  end;
+  PTestResolverReferenceData = ^TTestResolverReferenceData;
+
   { TTestPasParser }
 
   TTestPasParser = Class(TPasParser)
@@ -137,6 +153,9 @@ type
     {$IFDEF EnablePasTreeGlobalRefCount}
     FElementRefCountAtSetup: int64;
     {$ENDIF}
+    procedure FreeSrcMarkers;
+    function GetModuleCount: integer;
+    function GetModules(Index: integer): TTestEnginePasResolver;
     function GetMsgCount: integer;
     function GetMsgs(Index: integer): TTestHintMessage;
     function GetResolverCount: integer;
@@ -145,6 +164,8 @@ type
     procedure OnParserLog(Sender: TObject; const Msg: String);
     procedure OnPasResolverLog(Sender: TObject; const Msg: String);
     procedure OnScannerLog(Sender: TObject; const Msg: String);
+    procedure OnCheckElementParent(El: TPasElement; arg: pointer);
+    procedure OnFindReference(El: TPasElement; FindData: pointer);
     procedure SetWithTypeInfo(const AValue: boolean);
   protected
     procedure SetUp; override;
@@ -161,6 +182,7 @@ type
     procedure ParseLibrary; virtual;
     procedure ParseUnit; virtual;
   protected
+    FirstSrcMarker, LastSrcMarker: PSrcMarker;
     function FindModuleWithFilename(aFilename: string): TTestEnginePasResolver; virtual;
     function AddModule(aFilename: string): TTestEnginePasResolver; virtual;
     function AddModuleWithSrc(aFilename, Src: string): TTestEnginePasResolver; virtual;
@@ -182,6 +204,7 @@ type
     procedure CheckFullSource(Msg,ExpectedSrc: String); virtual;
     procedure CheckDiff(Msg, Expected, Actual: string); virtual;
     procedure CheckUnit(Filename, ExpectedSrc: string); virtual;
+    procedure CheckReferenceDirectives; virtual;
     procedure CheckHint(MsgType: TMessageType; MsgNumber: integer;
       Msg: string; Marker: PSrcMarker = nil); virtual;
     procedure CheckResolverUnexpectedHints(WithSourcePos: boolean = false); virtual;
@@ -190,6 +213,8 @@ type
     procedure SetExpectedPasResolverError(Msg: string; MsgNumber: integer);
     procedure SetExpectedConverterError(Msg: string; MsgNumber: integer);
     function IsErrorExpected(E: Exception): boolean;
+    procedure RaiseErrorAtSrc(Msg: string; const aFilename: string; aRow, aCol: integer);
+    procedure RaiseErrorAtSrcMarker(Msg: string; aMarker: PSrcMarker);
     procedure HandleScannerError(E: EScannerError);
     procedure HandleParserError(E: EParserError);
     procedure HandlePasResolveError(E: EPasResolve);
@@ -199,12 +224,17 @@ type
     procedure WriteSources(const aFilename: string; aRow, aCol: integer);
     function IndexOfResolver(const Filename: string): integer;
     function GetResolver(const Filename: string): TTestEnginePasResolver;
+    procedure GetSrc(Index: integer; out SrcLines: TStringList; out aFilename: string);
+    function FindElementsAt(aFilename: string; aLine, aStartCol, aEndCol: integer): TFPList;// list of TPasElement
+    function FindElementsAt(aMarker: PSrcMarker; ErrorOnNoElements: boolean = true): TFPList;// list of TPasElement
+    function FindSrcLabel(const Identifier: string): PSrcMarker;
+    function FindElementsAtSrcLabel(const Identifier: string; ErrorOnNoElements: boolean = true): TFPList;// list of TPasElement
     function GetDefaultNamespace: string;
     property PasProgram: TPasProgram Read FPasProgram;
     property PasLibrary: TPasLibrary Read FPasLibrary;
     property Resolvers[Index: integer]: TTestEnginePasResolver read GetResolvers;
     property ResolverCount: integer read GetResolverCount;
-    property Engine: TTestEnginePasResolver read FEngine;
+    property ResolverEngine: TTestEnginePasResolver read FEngine;
     property Filename: string read FFilename;
     Property Module: TPasModule Read FModule;
     property FirstPasStatement: TPasImplBlock read FFirstPasStatement;
@@ -229,6 +259,8 @@ type
     property FileResolver: TStreamResolver read FFileResolver;
     property Scanner: TPas2jsPasScanner read FScanner;
     property Parser: TTestPasParser read FParser;
+    property Modules[Index: integer]: TTestEnginePasResolver read GetModules;
+    property ModuleCount: integer read GetModuleCount;
     property MsgCount: integer read GetMsgCount;
     property Msgs[Index: integer]: TTestHintMessage read GetMsgs;
     property WithTypeInfo: boolean read FWithTypeInfo write SetWithTypeInfo;
@@ -1219,6 +1251,31 @@ end;
 
 { TCustomTestModule }
 
+procedure TCustomTestModule.FreeSrcMarkers;
+var
+  aMarker, Last: PSrcMarker;
+begin
+  aMarker:=FirstSrcMarker;
+  while aMarker<>nil do
+    begin
+    Last:=aMarker;
+    aMarker:=aMarker^.Next;
+    Dispose(Last);
+    end;
+  FirstSrcMarker:=nil;
+  LastSrcMarker:=nil;
+end;
+
+function TCustomTestModule.GetModuleCount: integer;
+begin
+  Result:=FModules.Count;
+end;
+
+function TCustomTestModule.GetModules(Index: integer): TTestEnginePasResolver;
+begin
+  Result:=TTestEnginePasResolver(FModules[Index]);
+end;
+
 function TCustomTestModule.GetMsgCount: integer;
 begin
   Result:=FHintMsgs.Count;
@@ -1318,6 +1375,101 @@ begin
   FHintMsgs.Add(Item);
 end;
 
+procedure TCustomTestModule.OnCheckElementParent(El: TPasElement; arg: pointer);
+var
+  SubEl: TPasElement;
+  i: Integer;
+
+  procedure E(Msg: string);
+  var
+    s: String;
+  begin
+    s:='TCustomTestModule.OnCheckElementParent El='+GetTreeDbg(El)+' '+
+      ResolverEngine.GetElementSourcePosStr(El)+' '+Msg;
+    writeln('ERROR: ',s);
+    Fail(s);
+  end;
+
+begin
+  if arg=nil then ;
+  if El=nil then exit;
+  if El.Parent=El then
+    E('El.Parent=El='+GetObjName(El));
+  if El is TBinaryExpr then
+    begin
+    if (TBinaryExpr(El).left<>nil) and (TBinaryExpr(El).left.Parent<>El) then
+      E('TBinaryExpr(El).left.Parent='+GetObjName(TBinaryExpr(El).left.Parent)+'<>El');
+    if (TBinaryExpr(El).right<>nil) and (TBinaryExpr(El).right.Parent<>El) then
+      E('TBinaryExpr(El).right.Parent='+GetObjName(TBinaryExpr(El).right.Parent)+'<>El');
+    end
+  else if El is TParamsExpr then
+    begin
+    if (TParamsExpr(El).Value<>nil) and (TParamsExpr(El).Value.Parent<>El) then
+      E('TParamsExpr(El).Value.Parent='+GetObjName(TParamsExpr(El).Value.Parent)+'<>El');
+    for i:=0 to length(TParamsExpr(El).Params)-1 do
+      if TParamsExpr(El).Params[i].Parent<>El then
+        E('TParamsExpr(El).Params[i].Parent='+GetObjName(TParamsExpr(El).Params[i].Parent)+'<>El');
+    end
+  else if El is TProcedureExpr then
+    begin
+    if (TProcedureExpr(El).Proc<>nil) and (TProcedureExpr(El).Proc.Parent<>El) then
+      E('TProcedureExpr(El).Proc.Parent='+GetObjName(TProcedureExpr(El).Proc.Parent)+'<>El');
+    end
+  else if El is TPasDeclarations then
+    begin
+    for i:=0 to TPasDeclarations(El).Declarations.Count-1 do
+      begin
+      SubEl:=TPasElement(TPasDeclarations(El).Declarations[i]);
+      if SubEl.Parent<>El then
+        E('SubEl=TPasElement(TPasDeclarations(El).Declarations[i])='+GetObjName(SubEl)+' SubEl.Parent='+GetObjName(SubEl.Parent)+'<>El');
+      end;
+    end
+  else if El is TPasImplBlock then
+    begin
+    for i:=0 to TPasImplBlock(El).Elements.Count-1 do
+      begin
+      SubEl:=TPasElement(TPasImplBlock(El).Elements[i]);
+      if SubEl.Parent<>El then
+        E('TPasElement(TPasImplBlock(El).Elements[i]).Parent='+GetObjName(SubEl.Parent)+'<>El');
+      end;
+    end
+  else if El is TPasImplWithDo then
+    begin
+    for i:=0 to TPasImplWithDo(El).Expressions.Count-1 do
+      begin
+      SubEl:=TPasExpr(TPasImplWithDo(El).Expressions[i]);
+      if SubEl.Parent<>El then
+        E('TPasExpr(TPasImplWithDo(El).Expressions[i]).Parent='+GetObjName(SubEl.Parent)+'<>El');
+      end;
+    end
+  else if El is TPasProcedure then
+    begin
+    if TPasProcedure(El).ProcType.Parent<>El then
+      E('TPasProcedure(El).ProcType.Parent='+GetObjName(TPasProcedure(El).ProcType.Parent)+'<>El');
+    end
+  else if El is TPasProcedureType then
+    begin
+    for i:=0 to TPasProcedureType(El).Args.Count-1 do
+      if TPasArgument(TPasProcedureType(El).Args[i]).Parent<>El then
+        E('TPasArgument(TPasProcedureType(El).Args[i]).Parent='+GetObjName(TPasArgument(TPasProcedureType(El).Args[i]).Parent)+'<>El');
+    end;
+end;
+
+procedure TCustomTestModule.OnFindReference(El: TPasElement; FindData: pointer);
+var
+  Data: PTestResolverReferenceData absolute FindData;
+  Line, Col: integer;
+begin
+  ResolverEngine.UnmangleSourceLineNumber(El.SourceLinenumber,Line,Col);
+  //writeln('TCustomTestModule.OnFindReference ',El.SourceFilename,' Line=',Line,',Col=',Col,' ',GetObjName(El),' SearchFile=',Data^.Filename,',Line=',Data^.Row,',Col=',Data^.StartCol,'-',Data^.EndCol);
+  if (Data^.Filename=El.SourceFilename)
+  and (Data^.Row=Line)
+  and (Data^.StartCol<=Col)
+  and (Data^.EndCol>=Col)
+  then
+    Data^.Found.Add(El);
+end;
+
 procedure TCustomTestModule.SetWithTypeInfo(const AValue: boolean);
 begin
   if FWithTypeInfo=AValue then Exit;
@@ -1460,6 +1612,7 @@ var
   i: Integer;
   CurModule: TPasModule;
 begin
+  FreeSrcMarkers;
   FHintMsgs.Clear;
   FHintMsgsGood.Clear;
   FSkipTests:=false;
@@ -1473,7 +1626,7 @@ begin
   FreeAndNil(FJSSource);
   FreeAndNil(FJSModule);
   FreeAndNil(FConverter);
-  Engine.Clear;
+  ResolverEngine.Clear;
   FreeAndNil(FSource);
   FreeAndNil(FFileResolver);
   if FModules<>nil then
@@ -1599,7 +1752,7 @@ begin
 
   AssertNotNull('Module resulted in Module',Module);
   AssertEquals('modulename',lowercase(ChangeFileExt(FFileName,'')),lowercase(Module.Name));
-  TAssert.AssertSame('Has resolver',Engine,Parser.Engine);
+  TAssert.AssertSame('Has resolver',ResolverEngine,Parser.Engine);
 end;
 
 procedure TCustomTestModule.ParseProgram;
@@ -2004,7 +2157,7 @@ begin
     IsLib:=true;
 
   try
-    FJSModule:=FConverter.ConvertPasElement(Module,Engine) as TJSSourceElements;
+    FJSModule:=FConverter.ConvertPasElement(Module,ResolverEngine) as TJSSourceElements;
   except
     on E: Exception do
       HandleException(E);
@@ -2249,6 +2402,366 @@ begin
   end;
 end;
 
+procedure TCustomTestModule.CheckReferenceDirectives;
+var
+  CurFilename: string;
+  LineNumber: Integer;
+  SrcLine: String;
+  CommentStartP, CommentEndP: PChar;
+
+  procedure RaiseError(Msg: string; p: PChar);
+  begin
+    RaiseErrorAtSrc(Msg,CurFilename,LineNumber,p-PChar(SrcLine)+1);
+  end;
+
+  procedure AddMarker(Marker: PSrcMarker);
+  begin
+    if LastSrcMarker<>nil then
+      LastSrcMarker^.Next:=Marker
+    else
+      FirstSrcMarker:=Marker;
+    LastSrcMarker:=Marker;
+  end;
+
+  function AddMarker(Kind: TSrcMarkerKind; const aFilename: string;
+    aLine, aStartCol, aEndCol: integer; const Identifier: string): PSrcMarker;
+  begin
+    New(Result);
+    Result^.Kind:=Kind;
+    Result^.Filename:=aFilename;
+    Result^.Row:=aLine;
+    Result^.StartCol:=aStartCol;
+    Result^.EndCol:=aEndCol;
+    Result^.Identifier:=Identifier;
+    Result^.Next:=nil;
+    //writeln('AddMarker Line="',SrcLine,'" Identifier=',Identifier,' Col=',aStartCol,'-',aEndCol,' "',copy(SrcLine,aStartCol,aEndCol-aStartCol),'"');
+    AddMarker(Result);
+  end;
+
+  function AddMarkerForTokenBehindComment(Kind: TSrcMarkerKind;
+    const Identifier: string): PSrcMarker;
+  var
+    TokenStart, p: PChar;
+  begin
+    p:=CommentEndP;
+    ReadNextPascalToken(p,TokenStart,false,false);
+    Result:=AddMarker(Kind,CurFilename,LineNumber,
+      CommentEndP-PChar(SrcLine)+1,p-PChar(SrcLine)+1,Identifier);
+  end;
+
+  function ReadIdentifier(var p: PChar): string;
+  var
+    StartP: PChar;
+  begin
+    if not (p^ in ['a'..'z','A'..'Z','_']) then
+      RaiseError('identifier expected',p);
+    StartP:=p;
+    inc(p);
+    while p^ in ['a'..'z','A'..'Z','_','0'..'9'] do inc(p);
+    Result:='';
+    SetLength(Result,p-StartP);
+    Move(StartP^,Result[1],length(Result));
+  end;
+
+  procedure AddLabel;
+  var
+    Identifier: String;
+    p: PChar;
+  begin
+    p:=CommentStartP+2;
+    Identifier:=ReadIdentifier(p);
+    //writeln('TCustomTestModule.CheckReferenceDirectives.AddLabel ',Identifier);
+    if FindSrcLabel(Identifier)<>nil then
+      RaiseError('duplicate label "'+Identifier+'"',p);
+    AddMarkerForTokenBehindComment(mkLabel,Identifier);
+  end;
+
+  procedure AddResolverReference;
+  var
+    Identifier: String;
+    p: PChar;
+  begin
+    p:=CommentStartP+2;
+    Identifier:=ReadIdentifier(p);
+    //writeln('TCustomTestModule.CheckReferenceDirectives.AddReference ',Identifier);
+    AddMarkerForTokenBehindComment(mkResolverReference,Identifier);
+  end;
+
+  procedure AddDirectReference;
+  var
+    Identifier: String;
+    p: PChar;
+  begin
+    p:=CommentStartP+2;
+    Identifier:=ReadIdentifier(p);
+    //writeln('TCustomTestModule.CheckReferenceDirectives.AddDirectReference ',Identifier);
+    AddMarkerForTokenBehindComment(mkDirectReference,Identifier);
+  end;
+
+  procedure ParseCode(SrcLines: TStringList; aFilename: string);
+  var
+    p: PChar;
+    IsDirective: Boolean;
+  begin
+    //writeln('TCustomTestModule.CheckReferenceDirectives.ParseCode File=',aFilename);
+    CurFilename:=aFilename;
+    // parse code, find all labels
+    LineNumber:=0;
+    while LineNumber<SrcLines.Count do
+      begin
+      inc(LineNumber);
+      SrcLine:=SrcLines[LineNumber-1];
+      if SrcLine='' then continue;
+      //writeln('TCustomTestModule.CheckReferenceDirectives Line=',SrcLine);
+      p:=PChar(SrcLine);
+      repeat
+        case p^ of
+          #0: if (p-PChar(SrcLine)=length(SrcLine)) then break;
+          '{':
+            begin
+            CommentStartP:=p;
+            inc(p);
+            IsDirective:=p^ in ['#','@','='];
+
+            // skip to end of comment
+            repeat
+              case p^ of
+              #0:
+                if (p-PChar(SrcLine)=length(SrcLine)) then
+                  begin
+                  // multi line comment
+                  if IsDirective then
+                    RaiseError('directive missing closing bracket',CommentStartP);
+                  repeat
+                    inc(LineNumber);
+                    if LineNumber>SrcLines.Count then exit;
+                    SrcLine:=SrcLines[LineNumber-1];
+                    //writeln('TCustomTestModule.CheckReferenceDirectives Comment Line=',SrcLine);
+                  until SrcLine<>'';
+                  p:=PChar(SrcLine);
+                  continue;
+                  end;
+              '}':
+                begin
+                inc(p);
+                break;
+                end;
+              end;
+              inc(p);
+            until false;
+
+            CommentEndP:=p;
+            case CommentStartP[1] of
+            '#': AddLabel;
+            '@': AddResolverReference;
+            '=': AddDirectReference;
+            end;
+            p:=CommentEndP;
+            continue;
+
+            end;
+          '/':
+            if p[1]='/' then
+              break; // rest of line is comment -> skip
+        end;
+        inc(p);
+      until false;
+      end;
+  end;
+
+  procedure CheckResolverReference(aMarker: PSrcMarker);
+  // check if one element at {@a} has a TResolvedReference to an element labeled {#a}
+  var
+    aLabel: PSrcMarker;
+    ReferenceElements, LabelElements: TFPList;
+    i, j, aLine, aCol: Integer;
+    El, Ref, LabelEl: TPasElement;
+  begin
+    //writeln('TCustomTestModule.CheckResolverReference searching reference: ',aMarker^.Filename,' Line=',aMarker^.Row,' Col=',aMarker^.StartCol,'-',aMarker^.EndCol,' Label="',aMarker^.Identifier,'"');
+    aLabel:=FindSrcLabel(aMarker^.Identifier);
+    if aLabel=nil then
+      RaiseErrorAtSrc('label "'+aMarker^.Identifier+'" not found',aMarker^.Filename,aMarker^.Row,aMarker^.StartCol);
+
+    LabelElements:=nil;
+    ReferenceElements:=nil;
+    try
+      LabelElements:=FindElementsAt(aLabel);
+      ReferenceElements:=FindElementsAt(aMarker);
+
+      for i:=0 to ReferenceElements.Count-1 do
+        begin
+        El:=TPasElement(ReferenceElements[i]);
+        Ref:=nil;
+        if El.CustomData is TResolvedReference then
+          Ref:=TResolvedReference(El.CustomData).Declaration
+        else if El.CustomData is TPasPropertyScope then
+          Ref:=TPasPropertyScope(El.CustomData).AncestorProp
+        else if El.CustomData is TPasSpecializeTypeData then
+          Ref:=TPasSpecializeTypeData(El.CustomData).SpecializedType;
+        if Ref<>nil then
+          for j:=0 to LabelElements.Count-1 do
+            begin
+            LabelEl:=TPasElement(LabelElements[j]);
+            if Ref=LabelEl then
+              exit; // success
+            end;
+        end;
+
+      // failure write candidates
+      for i:=0 to ReferenceElements.Count-1 do
+        begin
+        El:=TPasElement(ReferenceElements[i]);
+        write('Reference candidate for "',aMarker^.Identifier,'" at reference ',aMarker^.Filename,'(',aMarker^.Row,',',aMarker^.StartCol,'-',aMarker^.EndCol,')');
+        write(' El=',GetObjName(El));
+        if EL is TPrimitiveExpr then
+          begin
+           writeln('TCustomTestModule.CheckResolverReference ',TPrimitiveExpr(El).Value);
+          end;
+        Ref:=nil;
+        if El.CustomData is TResolvedReference then
+          Ref:=TResolvedReference(El.CustomData).Declaration
+        else if El.CustomData is TPasPropertyScope then
+          Ref:=TPasPropertyScope(El.CustomData).AncestorProp
+        else if El.CustomData is TPasSpecializeTypeData then
+          Ref:=TPasSpecializeTypeData(El.CustomData).SpecializedType;
+        if Ref<>nil then
+          begin
+          write(' Decl=',GetObjName(Ref));
+          ResolverEngine.UnmangleSourceLineNumber(Ref.SourceLinenumber,aLine,aCol);
+          write(',',Ref.SourceFilename,'(',aLine,',',aCol,')');
+          end
+        else
+          write(' has no TResolvedReference. El.CustomData=',GetObjName(El.CustomData));
+        writeln;
+        end;
+      for i:=0 to LabelElements.Count-1 do
+        begin
+        El:=TPasElement(LabelElements[i]);
+        write('Label candidate for "',aLabel^.Identifier,'" at reference ',aLabel^.Filename,'(',aLabel^.Row,',',aLabel^.StartCol,'-',aLabel^.EndCol,')');
+        write(' El=',GetObjName(El));
+        writeln;
+        end;
+
+      RaiseErrorAtSrcMarker('wrong resolved reference "'+aMarker^.Identifier+'"',aMarker);
+    finally
+      LabelElements.Free;
+      ReferenceElements.Free;
+    end;
+  end;
+
+  procedure CheckDirectReference(aMarker: PSrcMarker);
+  // check if one element at {=a} is a TPasAliasType pointing to an element labeled {#a}
+  var
+    aLabel: PSrcMarker;
+    ReferenceElements, LabelElements: TFPList;
+    i, LabelLine, LabelCol, j: Integer;
+    El, LabelEl: TPasElement;
+    DeclEl, TypeEl: TPasType;
+  begin
+    //writeln('CheckDirectReference searching pointer: ',aMarker^.Filename,' Line=',aMarker^.Row,' Col=',aMarker^.StartCol,'-',aMarker^.EndCol,' Label="',aMarker^.Identifier,'"');
+    aLabel:=FindSrcLabel(aMarker^.Identifier);
+    if aLabel=nil then
+      RaiseErrorAtSrcMarker('label "'+aMarker^.Identifier+'" not found',aMarker);
+
+    LabelElements:=nil;
+    ReferenceElements:=nil;
+    try
+      //writeln('CheckDirectReference finding elements at label ...');
+      LabelElements:=FindElementsAt(aLabel);
+      //writeln('CheckDirectReference finding elements at reference ...');
+      ReferenceElements:=FindElementsAt(aMarker);
+
+      for i:=0 to ReferenceElements.Count-1 do
+        begin
+        El:=TPasElement(ReferenceElements[i]);
+        //writeln('CheckDirectReference ',i,'/',ReferenceElements.Count,' ',GetTreeDbg(El,2));
+        if El.ClassType=TPasVariable then
+          begin
+          if TPasVariable(El).VarType=nil then
+            begin
+            //writeln('CheckDirectReference Var without Type: ',GetObjName(El),' El.Parent=',GetObjName(El.Parent));
+            AssertNotNull('TPasVariable(El='+El.Name+').VarType',TPasVariable(El).VarType);
+            end;
+          TypeEl:=TPasVariable(El).VarType;
+          for j:=0 to LabelElements.Count-1 do
+            begin
+            LabelEl:=TPasElement(LabelElements[j]);
+            if TypeEl=LabelEl then
+              exit; // success
+            end;
+          end
+        else if El is TPasAliasType then
+          begin
+          DeclEl:=TPasAliasType(El).DestType;
+          ResolverEngine.UnmangleSourceLineNumber(DeclEl.SourceLinenumber,LabelLine,LabelCol);
+          if (aLabel^.Filename=DeclEl.SourceFilename)
+          and (integer(aLabel^.Row)=LabelLine)
+          and (aLabel^.StartCol<=LabelCol)
+          and (aLabel^.EndCol>=LabelCol) then
+            exit; // success
+          end
+        else if El.ClassType=TPasArgument then
+          begin
+          TypeEl:=TPasArgument(El).ArgType;
+          for j:=0 to LabelElements.Count-1 do
+            begin
+            LabelEl:=TPasElement(LabelElements[j]);
+            if TypeEl=LabelEl then
+              exit; // success
+            end;
+          end;
+        end;
+      // failed -> show candidates
+      writeln('CheckDirectReference failed: Labels:');
+      for j:=0 to LabelElements.Count-1 do
+        begin
+        LabelEl:=TPasElement(LabelElements[j]);
+        writeln('  Label ',GetObjName(LabelEl),' at ',ResolverEngine.GetElementSourcePosStr(LabelEl));
+        end;
+      writeln('CheckDirectReference failed: References:');
+      for i:=0 to ReferenceElements.Count-1 do
+        begin
+        El:=TPasElement(ReferenceElements[i]);
+        writeln('  Reference ',GetObjName(El),' at ',ResolverEngine.GetElementSourcePosStr(El));
+        //if EL is TPasVariable then
+        //  writeln('CheckDirectReference ',GetObjPath(TPasVariable(El).VarType),' ',ResolverEngine.GetElementSourcePosStr(TPasVariable(EL).VarType));
+        end;
+      RaiseErrorAtSrcMarker('wrong direct reference "'+aMarker^.Identifier+'"',aMarker);
+    finally
+      LabelElements.Free;
+      ReferenceElements.Free;
+    end;
+  end;
+
+var
+  aMarker: PSrcMarker;
+  i: Integer;
+  SrcLines: TStringList;
+begin
+  Module.ForEachCall(@OnCheckElementParent,nil);
+  //writeln('TCustomTestModule.CheckReferenceDirectives find all markers');
+  // find all markers
+  for i:=0 to FileResolver.Streams.Count-1 do
+    begin
+    GetSrc(i,SrcLines,CurFilename);
+    ParseCode(SrcLines,CurFilename);
+    SrcLines.Free;
+    end;
+
+  //writeln('TCustomTestModule.CheckReferenceDirectives check references');
+  // check references
+  aMarker:=FirstSrcMarker;
+  while aMarker<>nil do
+    begin
+    case aMarker^.Kind of
+    mkResolverReference: CheckResolverReference(aMarker);
+    mkDirectReference: CheckDirectReference(aMarker);
+    end;
+    aMarker:=aMarker^.Next;
+    end;
+  //writeln('TCustomTestModule.CheckReferenceDirectives COMPLETE');
+end;
+
 procedure TCustomTestModule.CheckHint(MsgType: TMessageType;
   MsgNumber: integer; Msg: string; Marker: PSrcMarker);
 var
@@ -2374,6 +2887,23 @@ begin
     SkipTests:=true;
 end;
 
+procedure TCustomTestModule.RaiseErrorAtSrc(Msg: string;
+  const aFilename: string; aRow, aCol: integer);
+var
+  s: String;
+begin
+  WriteSources(aFilename,aRow,aCol);
+  s:='[TCustomTestModule.RaiseErrorAtSrc] '+aFilename+'('+IntToStr(aRow)+','+IntToStr(aCol)+') Error: '+Msg;
+  writeln('ERROR: ',s);
+  Fail(s);
+end;
+
+procedure TCustomTestModule.RaiseErrorAtSrcMarker(Msg: string;
+  aMarker: PSrcMarker);
+begin
+  RaiseErrorAtSrc(Msg,aMarker^.Filename,aMarker^.Row,aMarker^.StartCol);
+end;
+
 procedure TCustomTestModule.HandleScannerError(E: EScannerError);
 begin
   if IsErrorExpected(E) then exit;
@@ -2412,7 +2942,7 @@ var
   Row, Col: integer;
 begin
   if IsErrorExpected(E) then exit;
-  Engine.UnmangleSourceLineNumber(E.PasElement.SourceLinenumber,Row,Col);
+  ResolverEngine.UnmangleSourceLineNumber(E.PasElement.SourceLinenumber,Row,Col);
   WriteSources(E.PasElement.SourceFilename,Row,Col);
   writeln('ERROR: TCustomTestModule.HandlePas2JSError '+E.ClassName+':'+E.Message
     +' '+E.PasElement.SourceFilename
@@ -2523,6 +3053,84 @@ begin
   Result:=Resolvers[i];
 end;
 
+procedure TCustomTestModule.GetSrc(Index: integer; out SrcLines: TStringList;
+  out aFilename: string);
+var
+  aStream: TStream;
+begin
+  SrcLines:=TStringList.Create;
+  aStream:=FileResolver.Streams.Objects[Index] as TStream;
+  aStream.Position:=0;
+  SrcLines.LoadFromStream(aStream);
+  aFilename:=FileResolver.Streams[Index];
+end;
+
+function TCustomTestModule.FindElementsAt(aFilename: string; aLine, aStartCol,
+  aEndCol: integer): TFPList;
+var
+  ok: Boolean;
+  FoundRefs: TTestResolverReferenceData;
+  i: Integer;
+  CurResolver: TTestEnginePasResolver;
+begin
+  //writeln('TCustomTestModule.FindElementsAt START "',aFilename,'" Line=',aLine,' Col=',aStartCol,'-',aEndCol);
+  FoundRefs:=Default(TTestResolverReferenceData);
+  FoundRefs.Filename:=aFilename;
+  FoundRefs.Row:=aLine;
+  FoundRefs.StartCol:=aStartCol;
+  FoundRefs.EndCol:=aEndCol;
+  FoundRefs.Found:=TFPList.Create;
+  ok:=false;
+  try
+    // find all markers
+    Module.ForEachCall(@OnFindReference,@FoundRefs);
+    for i:=0 to ModuleCount-1 do
+      begin
+      CurResolver:=Modules[i];
+      if CurResolver.Module=Module then continue;
+      //writeln('TCustomTestResolver.FindElementsAt ',CurResolver.Filename);
+      CurResolver.Module.ForEachCall(@OnFindReference,@FoundRefs);
+      end;
+    ok:=true;
+  finally
+    if not ok then
+      FreeAndNil(FoundRefs.Found);
+  end;
+  Result:=FoundRefs.Found;
+  FoundRefs.Found:=nil;
+end;
+
+function TCustomTestModule.FindElementsAt(aMarker: PSrcMarker;
+  ErrorOnNoElements: boolean): TFPList;
+begin
+  Result:=FindElementsAt(aMarker^.Filename,aMarker^.Row,aMarker^.StartCol,aMarker^.EndCol);
+  if ErrorOnNoElements and ((Result=nil) or (Result.Count=0)) then
+    RaiseErrorAtSrcMarker('marker '+SrcMarker[aMarker^.Kind]+aMarker^.Identifier+' has no elements',aMarker);
+end;
+
+function TCustomTestModule.FindSrcLabel(const Identifier: string): PSrcMarker;
+begin
+  Result:=FirstSrcMarker;
+  while Result<>nil do
+    begin
+    if (Result^.Kind=mkLabel)
+    and (CompareText(Result^.Identifier,Identifier)=0) then
+      exit;
+    Result:=Result^.Next;
+    end;
+end;
+
+function TCustomTestModule.FindElementsAtSrcLabel(const Identifier: string;
+  ErrorOnNoElements: boolean): TFPList;
+var
+  SrcLabel: PSrcMarker;
+begin
+  SrcLabel:=FindSrcLabel(Identifier);
+  if SrcLabel=nil then
+    Fail('missing label "'+Identifier+'"');
+  Result:=FindElementsAt(SrcLabel,ErrorOnNoElements);
+end;
+
 function TCustomTestModule.GetDefaultNamespace: string;
 var
   C: TClass;
@@ -2531,7 +3139,7 @@ begin
   if FModule=nil then exit;
   C:=FModule.ClassType;
   if (C=TPasProgram) or (C=TPasLibrary) or (C=TPasPackage) then
-    Result:=Engine.DefaultNameSpace;
+    Result:=ResolverEngine.DefaultNameSpace;
 end;
 
 constructor TCustomTestModule.Create;

+ 2 - 2
packages/pastojs/tests/tcoptimizations.pas

@@ -160,9 +160,9 @@ begin
   inherited SetUp;
   FWholeProgramOptimization:=false;
   FAnalyzerModule:=TPas2JSAnalyzer.Create;
-  FAnalyzerModule.Resolver:=Engine;
+  FAnalyzerModule.Resolver:=ResolverEngine;
   FAnalyzerProgram:=TPas2JSAnalyzer.Create;
-  FAnalyzerProgram.Resolver:=Engine;
+  FAnalyzerProgram.Resolver:=ResolverEngine;
 end;
 
 procedure TCustomTestOptimizations.TearDown;

+ 468 - 0
packages/pastojs/tests/tcpas2jsanalyzer.pas

@@ -0,0 +1,468 @@
+unit TCPas2JSAnalyzer;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testregistry, StrUtils, TCModules, PasTree,
+  PScanner, PasResolver, PasUseAnalyzer, PasResolveEval, Pas2jsUseAnalyzer;
+
+type
+
+  { TCustomTestPas2jsAnalyzer }
+
+  TCustomTestPas2jsAnalyzer = class(TCustomTestModule)
+  private
+    FAnalyzer: TPas2JSAnalyzer;
+    FPAMessages: TFPList; // list of TPAMessage
+    FPAGoodMessages: TFPList;
+    FProcAnalyzer: TPas2JSAnalyzer;
+    function GetPAMessages(Index: integer): TPAMessage;
+    procedure OnAnalyzerMessage(Sender: TObject; Msg: TPAMessage);
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+    procedure AnalyzeModule; virtual;
+    procedure AnalyzeProgram; virtual;
+    procedure AnalyzeUnit; virtual;
+    procedure AnalyzeWholeProgram; virtual;
+    procedure CheckUsedMarkers; virtual;
+    procedure CheckUseAnalyzerHint(MsgType: TMessageType; MsgNumber: integer;
+      const MsgText: string); virtual;
+    procedure CheckUseAnalyzerUnexpectedHints; virtual;
+    procedure CheckUnitUsed(const aFilename: string; Used: boolean); virtual;
+    procedure CheckScopeReferences(const ScopeName: string;
+      const RefNames: array of string);
+  public
+    property Analyzer: TPas2JSAnalyzer read FAnalyzer;
+    property ProcAnalyzer: TPas2JSAnalyzer read FProcAnalyzer;
+    function PAMessageCount: integer;
+    property PAMessages[Index: integer]: TPAMessage read GetPAMessages;
+  end;
+
+  { TTestPas2jsAnalyzer }
+
+  TTestPas2jsAnalyzer = class(TCustomTestPas2jsAnalyzer)
+  Published
+    procedure TestM_ProgramLocalVar;
+  end;
+
+
+implementation
+
+{ TTestPas2jsAnalyzer }
+
+procedure TTestPas2jsAnalyzer.TestM_ProgramLocalVar;
+begin
+  StartProgram(false);
+  Add([
+  'procedure {#DoIt_used}DoIt;',
+  'var {#l_notused}l: longint;',
+  'begin',
+  'end;',
+  'begin',
+  '  DoIt;',
+  'end.']);
+  AnalyzeProgram;
+end;
+
+{ TCustomTestPas2jsAnalyzer }
+
+function TCustomTestPas2jsAnalyzer.GetPAMessages(Index: integer): TPAMessage;
+begin
+  Result:=TPAMessage(FPAMessages[Index]);
+end;
+
+procedure TCustomTestPas2jsAnalyzer.OnAnalyzerMessage(Sender: TObject;
+  Msg: TPAMessage);
+begin
+  Msg.AddRef;
+  FPAMessages.Add(Msg);
+end;
+
+procedure TCustomTestPas2jsAnalyzer.SetUp;
+begin
+  inherited SetUp;
+  FPAMessages:=TFPList.Create;
+  FPAGoodMessages:=TFPList.Create;
+  FAnalyzer:=TPas2JSAnalyzer.Create;
+  FAnalyzer.Resolver:=ResolverEngine;
+  Analyzer.OnMessage:=@OnAnalyzerMessage;
+end;
+
+procedure TCustomTestPas2jsAnalyzer.TearDown;
+var
+  i: Integer;
+begin
+  FreeAndNil(FPAGoodMessages);
+  for i:=0 to FPAMessages.Count-1 do
+    TPAMessage(FPAMessages[i]).Release;
+  FreeAndNil(FPAMessages);
+  FreeAndNil(FAnalyzer);
+  FreeAndNil(FProcAnalyzer);
+  inherited TearDown;
+end;
+
+procedure TCustomTestPas2jsAnalyzer.AnalyzeModule;
+begin
+  Analyzer.AnalyzeModule(Module);
+  Analyzer.EmitModuleHints(Module);
+  CheckUsedMarkers;
+end;
+
+procedure TCustomTestPas2jsAnalyzer.AnalyzeProgram;
+begin
+  ParseProgram;
+  AnalyzeModule;
+end;
+
+procedure TCustomTestPas2jsAnalyzer.AnalyzeUnit;
+begin
+  ParseUnit;
+  AnalyzeModule;
+end;
+
+procedure TCustomTestPas2jsAnalyzer.AnalyzeWholeProgram;
+begin
+  ParseProgram;
+  Analyzer.AnalyzeWholeProgram(Module as TPasProgram);
+  CheckUsedMarkers;
+end;
+
+procedure TCustomTestPas2jsAnalyzer.CheckUsedMarkers;
+type
+  TUsed = (
+    uUsed,
+    uNotUsed,
+    uTypeInfo,
+    uNoTypeinfo
+    );
+var
+  aMarker: PSrcMarker;
+  p: SizeInt;
+  Postfix: String;
+  Elements: TFPList;
+  i: Integer;
+  El, FoundEl: TPasElement;
+  ExpectedUsed: TUsed;
+begin
+  aMarker:=FirstSrcMarker;
+  while aMarker<>nil do
+    begin
+    {$IFDEF VerbosePasAnalyzer}
+    writeln('TCustomTestPas2jsAnalyzer.CheckUsedMarkers ',aMarker^.Identifier,' Line=',aMarker^.Row,' StartCol=',aMarker^.StartCol,' EndCol=',aMarker^.EndCol);
+    {$ENDIF}
+    p:=RPos('_',aMarker^.Identifier);
+    if p>1 then
+      begin
+      Postfix:=copy(aMarker^.Identifier,p+1);
+
+      if Postfix='used' then
+        ExpectedUsed:=uUsed
+      else if Postfix='notused' then
+        ExpectedUsed:=uNotUsed
+      else if Postfix='typeinfo' then
+        ExpectedUsed:=uTypeInfo
+      else if Postfix='notypeinfo' then
+        ExpectedUsed:=uNoTypeInfo
+      else
+        RaiseErrorAtSrcMarker('TCustomTestPas2jsAnalyzer.CheckUsedMarkers unknown postfix "'+Postfix+'"',aMarker);
+
+      Elements:=FindElementsAt(aMarker);
+      try
+        FoundEl:=nil;
+        for i:=0 to Elements.Count-1 do
+          begin
+          El:=TPasElement(Elements[i]);
+          {$IFDEF VerbosePasAnalyzer}
+          writeln('TCustomTestPas2jsAnalyzer.CheckUsedMarkers ',aMarker^.Identifier,' ',i,'/',Elements.Count,' El=',GetObjName(El),' ',GetObjName(El.CustomData));
+          {$ENDIF}
+          case ExpectedUsed of
+          uUsed,uNotUsed:
+            if Analyzer.IsUsed(El) then
+              begin
+              FoundEl:=El;
+              break;
+              end;
+          uTypeInfo,uNoTypeinfo:
+            if Analyzer.IsTypeInfoUsed(El) then
+              begin
+              FoundEl:=El;
+              break;
+              end;
+          end;
+          end;
+        if FoundEl<>nil then
+          case ExpectedUsed of
+          uNotUsed:
+            RaiseErrorAtSrcMarker('expected element to be *not* used, but it is marked',aMarker);
+          uNoTypeinfo:
+            RaiseErrorAtSrcMarker('expected element to have *no* typeinfo, but it is marked',aMarker);
+          end
+        else
+          case ExpectedUsed of
+          uUsed:
+            RaiseErrorAtSrcMarker('expected element to be used, but it is not marked',aMarker);
+          uTypeInfo:
+            RaiseErrorAtSrcMarker('expected element to have typeinfo, but it is not marked',aMarker);
+          end;
+      finally
+        Elements.Free;
+      end;
+      end;
+    aMarker:=aMarker^.Next;
+    end;
+end;
+
+procedure TCustomTestPas2jsAnalyzer.CheckUseAnalyzerHint(MsgType: TMessageType;
+  MsgNumber: integer; const MsgText: string);
+var
+  i: Integer;
+  Msg: TPAMessage;
+  s: string;
+begin
+  i:=PAMessageCount-1;
+  while i>=0 do
+    begin
+    Msg:=PAMessages[i];
+    if (Msg.MsgNumber=MsgNumber) then
+      begin
+      if (Msg.MsgType=MsgType) and (Msg.MsgText=MsgText) then
+        begin
+        FPAGoodMessages.Add(Msg);
+        exit;
+        end;
+      end;
+    dec(i);
+    end;
+  // mismatch
+  writeln('TCustomTestPas2jsAnalyzer.CheckHasHint: ');
+  for i:=0 to PAMessageCount-1 do
+    begin
+    Msg:=PAMessages[i];
+    writeln('  ',i,'/',PAMessageCount,': [',Msg.Id,'] ',Msg.MsgType,': (',Msg.MsgNumber,') {',Msg.MsgText,'}');
+    end;
+  s:='';
+  str(MsgType,s);
+  Fail('Analyzer Message not found: '+s+': ('+IntToStr(MsgNumber)+') {'+MsgText+'}');
+end;
+
+procedure TCustomTestPas2jsAnalyzer.CheckUseAnalyzerUnexpectedHints;
+var
+  i: Integer;
+  Msg: TPAMessage;
+  s: String;
+begin
+  for i:=0 to PAMessageCount-1 do
+    begin
+    Msg:=PAMessages[i];
+    if FPAGoodMessages.IndexOf(Msg)>=0 then continue;
+    s:='';
+    str(Msg.MsgType,s);
+    Fail('Unexpected analyzer message found ['+IntToStr(Msg.Id)+'] '+s+': ('+IntToStr(Msg.MsgNumber)+') {'+Msg.MsgText+'}');
+    end;
+end;
+
+procedure TCustomTestPas2jsAnalyzer.CheckUnitUsed(const aFilename: string;
+  Used: boolean);
+var
+  aResolver: TTestEnginePasResolver;
+  PAEl: TPAElement;
+begin
+  aResolver:=FindModuleWithFilename(aFilename);
+  AssertNotNull('unit not found "'+aFilename+'"',aResolver);
+  AssertNotNull('unit module not found "'+aFilename+'"',aResolver.Module);
+  PAEl:=Analyzer.FindElement(aResolver.Module);
+  if PAEl<>nil then
+    begin
+    // unit is used
+    if not Used then
+      Fail('expected unit "'+aFilename+'" not used, but it is used');
+    end
+  else
+    begin
+    // unit is not used
+    if Used then
+      Fail('expected unit "'+aFilename+'" used, but it is not used');
+    end;
+end;
+
+procedure TCustomTestPas2jsAnalyzer.CheckScopeReferences(
+  const ScopeName: string; const RefNames: array of string);
+type
+  TEntry = record
+    Name: string;
+    Access: TPSRefAccess;
+  end;
+
+var
+  Entries: array of TEntry;
+
+  procedure CheckRefs(ScopeRefs: TPasScopeReferences; const Prefix: string);
+
+    procedure DumpRefsAndFail(Refs: TFPList; const Msg: string);
+    var
+      i: Integer;
+      Ref: TPasScopeReference;
+    begin
+      {$IFDEF VerbosePasAnalyzer}
+      if Refs.Count=0 then
+        writeln('DumpRefsAndFail ',Prefix,' NO REFS');
+      {$ENDIF}
+      for i:=0 to Refs.Count-1 do
+        begin
+        Ref:=TPasScopeReference(Refs[i]);
+        if Ref=nil then break;
+        {$IFDEF VerbosePasAnalyzer}
+        writeln('DumpRefsAndFail ',Prefix,' ',i,' ',GetObjName(Ref.Element),' ',Ref.Access);
+        {$ENDIF}
+        end;
+      Fail(Prefix+': '+Msg);
+    end;
+
+  var
+    Refs: TFPList;
+    j, i: Integer;
+    o: TObject;
+    Ref: TPasScopeReference;
+  begin
+    if ScopeRefs=nil then
+      Refs:=TFPList.Create
+    else
+      Refs:=ScopeRefs.GetList;
+    try
+      // check that Refs only contains TPasProcScopeReference
+      for i:=0 to Refs.Count-1 do
+        begin
+        o:=TObject(Refs[i]);
+        if not (o is TPasScopeReference) then
+          Fail(Prefix+': Refs['+IntToStr(i)+'] '+GetObjName(o));
+        end;
+      // check that all Entries are referenced
+      for i:=0 to length(Entries)-1 do
+        begin
+        j:=Refs.Count-1;
+        while (j>=0)
+            and (CompareText(Entries[i].Name,TPasScopeReference(Refs[j]).Element.Name)<>0) do
+          dec(j);
+        if j<0 then
+          DumpRefsAndFail(Refs,'Missing reference "'+Entries[i].Name+'"');
+        Ref:=TPasScopeReference(Refs[j]);
+        if (Entries[i].Access<>psraNone) and (Ref.Access<>Entries[i].Access) then
+          DumpRefsAndFail(Refs,'Wrong reference access "'+Entries[i].Name+'",'
+            +' expected '+dbgs(Entries[i].Access)+', but got '+dbgs(Ref.Access));
+        end;
+      // check that no other references are in Refs
+      for i:=0 to Refs.Count-1 do
+        begin
+        Ref:=TPasScopeReference(Refs[i]);
+        j:=length(Entries)-1;
+        while (j>=0)
+            and (CompareText(Ref.Element.Name,Entries[j].Name)<>0) do
+          dec(j);
+        if j<0 then
+          DumpRefsAndFail(Refs,'Unneeded reference "'+GetObjName(Ref.Element)+'"');
+        end;
+    finally
+      Refs.Free;
+    end;
+  end;
+
+  function FindProc(Section: TPasSection): boolean;
+  var
+    i: Integer;
+    El: TPasElement;
+    Proc: TPasProcedure;
+    Scope: TPasProcedureScope;
+  begin
+    for i:=0 to Section.Declarations.Count-1 do
+      begin
+      El:=TPasElement(Section.Declarations[i]);
+      if CompareText(El.Name,ScopeName)<>0 then continue;
+      if not (El is TPasProcedure) then
+        Fail('El is not proc '+GetObjName(El));
+      Proc:=TPasProcedure(El);
+      Scope:=Proc.CustomData as TPasProcedureScope;
+      if Scope.DeclarationProc<>nil then continue;
+
+      // check references created by AnalyzeModule
+      CheckRefs(Scope.References,'AnalyzeModule');
+
+      exit(true);
+      end;
+    Result:=false;
+  end;
+
+  procedure CheckInitialFinalization(El: TPasImplBlock);
+  var
+    Scope: TPasInitialFinalizationScope;
+  begin
+    Scope:=El.CustomData as TPasInitialFinalizationScope;
+    CheckRefs(Scope.References,'AnalyzeModule');
+  end;
+
+var
+  i: Integer;
+begin
+  Entries:=nil;
+  SetLength(Entries,High(RefNames)-low(RefNames)+1);
+  for i:=low(RefNames) to high(RefNames) do
+    begin
+    Entries[i].Name:=RefNames[i];
+    Entries[i].Access:=psraNone;
+    end;
+
+  if Module is TPasProgram then
+    begin
+    if CompareText(ScopeName,'begin')=0 then
+      begin
+      // check begin-block references created by AnalyzeModule
+      CheckInitialFinalization(Module.InitializationSection);
+      exit;
+      end
+    else if FindProc(TPasProgram(Module).ProgramSection) then
+      exit;
+    end
+  else if Module is TPasLibrary then
+    begin
+    if CompareText(ScopeName,'begin')=0 then
+      begin
+      // check begin-block references created by AnalyzeModule
+      CheckInitialFinalization(Module.InitializationSection);
+      exit;
+      end
+    else if FindProc(TPasLibrary(Module).LibrarySection) then
+      exit;
+    end
+  else if Module.ClassType=TPasModule then
+    begin
+    if CompareText(ScopeName,'initialization')=0 then
+      begin
+      // check initialization references created by AnalyzeModule
+      CheckInitialFinalization(Module.InitializationSection);
+      exit;
+      end
+    else if CompareText(ScopeName,'finalization')=0 then
+      begin
+      // check finalization references created by AnalyzeModule
+      CheckInitialFinalization(Module.FinalizationSection);
+      exit;
+      end
+    else if FindProc(Module.InterfaceSection) then
+      exit
+    else if FindProc(Module.ImplementationSection) then
+      exit;
+    end;
+  Fail('missing proc '+ScopeName);
+end;
+
+function TCustomTestPas2jsAnalyzer.PAMessageCount: integer;
+begin
+  Result:=FPAMessages.Count;
+end;
+
+Initialization
+  RegisterTests([TTestPas2jsAnalyzer]);
+end.
+

+ 1 - 1
packages/pastojs/tests/tcsrcmap.pas

@@ -180,7 +180,7 @@ begin
   // collect markers in Pascal
   PasSrc:=TStringList.Create;
   try
-    PasSrc.Text:=Engine.Source;
+    PasSrc.Text:=ResolverEngine.Source;
     for i:=1 to PasSrc.Count do
       begin
       Line:=PasSrc[i-1];

+ 12 - 2
packages/pastojs/tests/testpas2js.lpi

@@ -37,7 +37,7 @@
         <PackageName Value="FCL"/>
       </Item2>
     </RequiredPackages>
-    <Units Count="13">
+    <Units Count="15">
       <Unit0>
         <Filename Value="testpas2js.pp"/>
         <IsPartOfProject Value="True"/>
@@ -101,6 +101,16 @@
         <Filename Value="tcconverter.pas"/>
         <IsPartOfProject Value="True"/>
       </Unit12>
+      <Unit13>
+        <Filename Value="tcpas2jsanalyzer.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="TCPas2JSAnalyzer"/>
+      </Unit13>
+      <Unit14>
+        <Filename Value="../../fcl-passrc/tests/testpasutils.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="TestPasUtils"/>
+      </Unit14>
     </Units>
   </ProjectOptions>
   <CompilerOptions>
@@ -110,7 +120,7 @@
     </Target>
     <SearchPaths>
       <IncludeFiles Value="$(ProjOutDir)"/>
-      <OtherUnitFiles Value="../src;../../fcl-js/src;../../fcl-json/src;../../fcl-passrc/src;../../pastojs/tests"/>
+      <OtherUnitFiles Value="../src;../../fcl-js/src;../../fcl-json/src;../../fcl-passrc/src;../../pastojs/tests;../../fcl-passrc/tests"/>
       <UnitOutputDirectory Value="lib"/>
     </SearchPaths>
     <CodeGeneration>

+ 2 - 1
packages/pastojs/tests/testpas2js.pp

@@ -22,7 +22,8 @@ uses
   MemCheck,
   {$ENDIF}
   Classes, consoletestrunner, tcconverter, TCModules, TCSrcMap,
-  TCFiler, TCUnitSearch, TCOptimizations, TCGenerics, TCPrecompile;
+  TCFiler, TCUnitSearch, TCOptimizations, TCGenerics, TCPrecompile, 
+  TCPas2JSAnalyzer, TestPasUtils;
 
 type