unit TestChangeDeclaration; {$mode objfpc}{$H+} interface uses Classes, SysUtils, LazLogger, LazFileUtils, fpcunit, testregistry, AVL_Tree, CodeToolManager, CodeCache, PascalParserTool, BasicCodeTools, CTUnitGraph, TestFinddeclaration, TestStdCodetools; type { TTestChangeDeclaration } TTestChangeDeclaration = class(TCustomTestCTStdCodetools) protected FDefFilename: string; procedure SetUp; override; procedure TestRenameIdentifier(DeclCode: TCodeBuffer; SearchInCodeBufs: TFPList); overload; procedure TestRenameIdentifier(DeclCode: TCodeBuffer; Module1: TCodeBuffer = nil); overload; published procedure TestCTAddProcedureModifier; procedure TestCTRenameIdentifier_MultiInLine; procedure TestCTRenameIdentifier_MultiInLine_Amp; end; implementation procedure TTestChangeDeclaration.SetUp; begin inherited SetUp; FDefFilename:='TestChangeDeclaration.pas'; end; procedure TTestChangeDeclaration.TestRenameIdentifier(DeclCode: TCodeBuffer; SearchInCodeBufs: TFPList); var p, DeclPos, l: SizeInt; NewIdentifier, OldIdentifier, Src, CurIdentifier: String; SearchInCode: TCodeBuffer; i, j: integer; DeclXY: TPoint; TreeOfPCodeXYPosition: TAVLTree; ListOfPCodeXYPosition: TFPList; Cache: TFindIdentifierReferenceCache; RefXYPos: TCodeXYPosition; begin Cache:=nil; ListOfPCodeXYPosition:=nil; TreeOfPCodeXYPosition:=nil; try // parse %rename directive Src:=DeclCode.Source; DeclPos:=Pos('{%rename:',Src); if DeclPos<1 then Fail('missing declaration marker'); inc(DeclPos,length('{%rename:')); NewIdentifier:=GetIdentifier(@Src[DeclPos]); if NewIdentifier='' then Fail('missing rename-to identifier'); while Src[DeclPos]<>'}' do inc(DeclPos); inc(DeclPos); OldIdentifier:=GetIdentifier(@Src[DeclPos]); if OldIdentifier='' then Fail('missing rename-from identifier'); DeclCode.AbsoluteToLineCol(DeclPos,DeclXY.Y,DeclXY.X); // find all references if SearchInCodeBufs=nil then SearchInCodeBufs:=TFPList.Create; if SearchInCodeBufs.IndexOf(DeclCode)<0 then SearchInCodeBufs.Add(DeclCode); for i:=0 to SearchInCodeBufs.Count-1 do begin SearchInCode:=TCodeBuffer(SearchInCodeBufs[i]); if not CodeToolBoss.FindReferences(DeclCode,DeclXY.X,DeclXY.Y,SearchInCode,false,ListOfPCodeXYPosition,Cache) then Fail('FindReferences failed at '+DeclCode.Filename+'('+dbgs(DeclXY.Y)+','+dbgs(DeclXY.X)+')'); // check that all %R directives were found p:=1; Src:=SearchInCode.Source; l:=length(Src); while p<=l do begin if (Src[p]='{') and (Src[p+1]='%') and (Src[p+2]='R') and (Src[p+3]='}') then begin inc(p,4); RefXYPos.Code:=SearchInCode; SearchInCode.AbsoluteToLineCol(p,RefXYPos.Y,RefXYPos.X); j:=IndexOfCodePosition(ListOfPCodeXYPosition,@RefXYPos); if j<0 then begin if ListOfPCodeXYPosition=nil then debugln(['TTestChangeDeclaration.TestRenameIdentifier ListOfPCodeXYPosition empty']) else begin debugln(['TTestChangeDeclaration.TestRenameIdentifier ListOfPCodeXYPosition: Count=',ListOfPCodeXYPosition.Count]); for j:=0 to ListOfPCodeXYPosition.Count-1 do begin debugln([' ',i,':',dbgs(PCodeXYPosition(ListOfPCodeXYPosition[j])^)]); end; end; Fail('missing reference: '+dbgs(RefXYPos)); end; end else inc(p); end; // add to tree if ListOfPCodeXYPosition<>nil then begin if TreeOfPCodeXYPosition=nil then TreeOfPCodeXYPosition:=CodeToolBoss.CreateTreeOfPCodeXYPosition; CodeToolBoss.AddListToTreeOfPCodeXYPosition(ListOfPCodeXYPosition, TreeOfPCodeXYPosition,true,false); // this empties ListOfPCodeXYPosition end; end; if (TreeOfPCodeXYPosition=nil) or (TreeOfPCodeXYPosition.Count=0) then Fail('TreeOfPCodeXYPosition empty'); // rename references if not CodeToolBoss.RenameIdentifier(TreeOfPCodeXYPosition,OldIdentifier,NewIdentifier,DeclCode,@DeclXY,false) then Fail('RenameIdentifier failed'); // check all {%R} directives were replaced for i:=0 to SearchInCodeBufs.Count-1 do begin SearchInCode:=TCodeBuffer(SearchInCodeBufs[i]); // check that all %R directives were found p:=1; Src:=SearchInCode.Source; l:=length(Src); while p<=l do begin if (Src[p]='{') and (Src[p+1]='%') and (Src[p+2]='R') and (Src[p+3]='}') then begin inc(p,4); CurIdentifier:=copy(Src,p,length(NewIdentifier)); if CurIdentifier<>NewIdentifier then Fail('reference differ: expected "'+NewIdentifier+'", but found "'+CurIdentifier+'"'); end else inc(p); end; end; finally CodeToolBoss.FreeListOfPCodeXYPosition(ListOfPCodeXYPosition); CodeToolBoss.FreeTreeOfPCodeXYPosition(TreeOfPCodeXYPosition); Cache.Free; SearchInCodeBufs.Free; end; end; procedure TTestChangeDeclaration.TestRenameIdentifier(DeclCode: TCodeBuffer; Module1: TCodeBuffer); var SearchInModules: TFPList; begin SearchInModules:=TFPList.Create; if Module1<>nil then SearchInModules.Add(Module1); TestRenameIdentifier(DeclCode,SearchInModules); end; procedure TTestChangeDeclaration.TestCTAddProcedureModifier; procedure Test(ProcCode, aModifier, Expected: string); var Code: TCodeBuffer; Src, ProcHead: String; begin Src:='unit '+FDefFilename+';'+sLineBreak +'interface'+sLineBreak +ProcCode+sLineBreak +'implementation'+sLineBreak +'end.'; Code:=CodeToolBoss.CreateFile(FDefFilename); Code.Source:=Src; if not CodeToolBoss.AddProcModifier(Code,3,3,aModifier) then begin Fail('AddProcModifier failed: '+CodeToolBoss.ErrorMessage); end else begin if not CodeToolBoss.ExtractProcedureHeader(Code,3,3, [phpWithStart,phpWithResultType,phpWithOfObject,phpWithProcModifiers,phpWithComments,phpDoNotAddSemicolon], ProcHead) then Fail('ExtractProcedureHeader failed: '+CodeToolBoss.ErrorMessage); if ProcHead<>Expected then begin writeln('Test ProcCode="',ProcCode,'"'); Src:=Code.Source; writeln('SrcSTART:======================'); writeln(Src); writeln('SrcEND:========================'); AssertEquals('ProcHead',Expected,ProcHead); end; end; end; begin // remove first unit Test('procedure DoIt;','overload','procedure DoIt; overload;'); Test('procedure DoIt ;','overload','procedure DoIt; overload ;'); Test('procedure DoIt ; ;','overload','procedure DoIt; overload ;'); Test('procedure DoIt; overload;','overload','procedure DoIt; overload;'); Test('procedure DoIt; {$IFDEF FPC}overload{$ENDIF};','overload','procedure DoIt; {$IFDEF FPC}overload{$ENDIF};'); Test('procedure DoIt; procedure Bla;','overload','procedure DoIt; overload;'); Test(' procedure DoIt;'+sLineBreak+' procedure Bla;', 'overload','procedure DoIt; overload;'); Test(' procedure DoIt; external name ''doit'';'+sLineBreak+' procedure Bla;', 'overload','procedure DoIt; external name ''doit''; overload;'); end; procedure TTestChangeDeclaration.TestCTRenameIdentifier_MultiInLine; var DeclCode: TCodeBuffer; begin DeclCode:=CodeToolBoss.CreateFile(FDefFilename); DeclCode.Source:='unit '+FDefFilename+';'+sLineBreak +'interface'+sLineBreak +'type'+sLineBreak +' {%rename:TWhale}TFoo = word;'+sLineBreak +' TBar = low({%R}TFoo)..high({%R}TFoo);' +'implementation'+sLineBreak +'type'+sLineBreak +' TBird = low({%R}TFoo)..high({%R}TFoo);' +'end.'; TestRenameIdentifier(DeclCode); end; procedure TTestChangeDeclaration.TestCTRenameIdentifier_MultiInLine_Amp; var DeclCode: TCodeBuffer; begin DeclCode:=CodeToolBoss.CreateFile(FDefFilename); DeclCode.Source:='unit '+FDefFilename+';'+sLineBreak +'interface'+sLineBreak +'type'+sLineBreak +' {%rename:&Type}TFoo = word;'+sLineBreak +' TBar = low({%R}TFoo)..high({%R}TFoo);' +'implementation'+sLineBreak +'type'+sLineBreak +' TBird = low({%R}TFoo)..high({%R}TFoo);' +'end.'; TestRenameIdentifier(DeclCode); end; initialization RegisterTests([TTestChangeDeclaration]); end.