codetools: started Find- and RenameSourceNameReferences

This commit is contained in:
mattias 2025-02-03 15:10:28 +01:00
parent 658b9adb86
commit a8004c0845
12 changed files with 1163 additions and 103 deletions

View File

@ -194,11 +194,13 @@ function dbgsDiff(Expected, Actual: string): string; overload;
function DottedIdentifierLength(Identifier: PChar): integer;
function GetDottedIdentifier(Identifier: PChar): string;
function IsDottedIdentifier(const Identifier: string; AllowAmp: boolean = True): boolean;
function GetDotCountInIdentifier(Identifier: PChar): integer; // -1 if not an identifier
function CompareDottedIdentifiers(Identifier1, Identifier2: PChar): integer;
function CompareDottedIdentifiersCaseSensitive(Identifier1, Identifier2: PChar): integer;
function ChompDottedIdentifier(const Identifier: string): string;
function SkipDottedIdentifierPart(var Identifier: PChar): boolean;
function DottedIdentifierStartsWith(Identifier, StartsWithIdent: PChar): boolean; // true if equal or longer
function DottedIdentifierEndsWith(Identifier, EndsWithIdent: PChar): boolean; // true if equal or longer
// space and special chars
function TrimCodeSpace(const ACode: string): string;
@ -602,20 +604,24 @@ function ReadDottedIdentifier(var Position: PChar; SrcEnd: PChar;
NestedComments: boolean): string;
var
AtomStart, p: PChar;
s: String;
begin
Result:='';
p:=Position;
ReadRawNextPascalAtom(p,AtomStart,SrcEnd,NestedComments);
Position:=AtomStart;
if (AtomStart>=p) or not IsIdentStartChar[AtomStart^] then exit;
if (AtomStart>=p) then exit;
Result:=GetIdentifier(AtomStart);
if Result='' then exit;
repeat
Position:=p;
ReadRawNextPascalAtom(p,AtomStart,SrcEnd,NestedComments);
if (AtomStart+1<>p) or (AtomStart^<>'.') then exit;
ReadRawNextPascalAtom(p,AtomStart,SrcEnd,NestedComments);
if (AtomStart>=p) or not IsIdentStartChar[AtomStart^] then exit;
Position:=AtomStart;
Result:=Result+'.'+GetIdentifier(AtomStart);
if (AtomStart>=p) then exit;
s:=GetIdentifier(AtomStart);
if s='' then exit;
Result:=Result+'.'+s;
until false;
end;
@ -1869,8 +1875,18 @@ function GetIdentLen(Identifier: PChar): integer;
begin
Result:=0;
if Identifier=nil then exit;
if not IsIdentStartChar[Identifier^] then exit;
while (IsIdentChar[Identifier[Result]]) do inc(Result);
if not IsIdentStartChar[Identifier^] then begin
if Identifier^='&' then begin
inc(Identifier);
if not IsIdentStartChar[Identifier^] then exit;
Result:=1;
end else
exit;
end;
repeat
inc(Result);
inc(Identifier);
until not IsIdentChar[Identifier^];
end;
function FindFirstProcSpecifier(const ProcText: string; NestedComments: boolean
@ -5318,20 +5334,14 @@ begin
Result:=0;
if Identifier=nil then exit;
p:=Identifier;
if p^='&' then
inc(p);
repeat
if not IsIdentStartChar[p^] then exit;
repeat
c:=p^;
inc(p);
until not IsIdentChar[p^];
if p^<>'.' then begin
if not IsIdentChar[c] then exit;
break;
end;
if p^='&' then
inc(p);
if not IsIdentStartChar[p^] then exit;
inc(p);
while IsIdentChar[p^] do inc(p);
if p^<>'.' then
break;
inc(p);
until false;
Result:=p-Identifier;
@ -5368,6 +5378,21 @@ begin
Result:=(p-StartP)=length(Identifier);
end;
function GetDotCountInIdentifier(Identifier: PChar): integer;
begin
Result:=0;
repeat
if Identifier^='&' then inc(Identifier);
if not IsIdentStartChar[Identifier^] then exit;
inc(Identifier);
while IsIdentChar[Identifier^] do inc(Identifier);
if Identifier^<>'.' then
exit;
inc(Result);
inc(Identifier);
until false;
end;
function CompareDottedIdentifiers(Identifier1, Identifier2: PChar): integer;
var
c: Char;
@ -5491,6 +5516,27 @@ begin
Result:=not IsIdentChar[Identifier^] and not IsIdentChar[StartsWithIdent^];
end;
function DottedIdentifierEndsWith(Identifier, EndsWithIdent: PChar): boolean;
var
IdentifierDotCnt, EndsWithIdentDotCnt: Integer;
begin
Result:=false;
if (EndsWithIdent=nil) then exit;
if not (IsIdentStartChar[EndsWithIdent^]
or ((EndsWithIdent^='&') and IsIdentStartChar[EndsWithIdent[1]])) then
exit;
IdentifierDotCnt:=GetDotCountInIdentifier(Identifier);
EndsWithIdentDotCnt:=GetDotCountInIdentifier(EndsWithIdent);
if EndsWithIdentDotCnt>IdentifierDotCnt then
exit;
while IdentifierDotCnt>EndsWithIdentDotCnt do begin
while Identifier^<>'.' do inc(Identifier);
inc(Identifier);
dec(IdentifierDotCnt);
end;
Result:=CompareDottedIdentifiers(Identifier,EndsWithIdent)=0;
end;
function CompareDottedIdentifiersCaseSensitive(Identifier1, Identifier2: PChar): integer;
var
c: Char;

View File

@ -36,7 +36,7 @@ uses
Classes, SysUtils, Contnrs, AVL_Tree,
// Codetools
CodeAtom, CodeCache, FileProcs, CodeTree, ExtractProcTool, FindDeclarationTool,
BasicCodeTools, KeywordFuncLists, LinkScanner, SourceChanger;
BasicCodeTools, KeywordFuncLists, LinkScanner, SourceChanger, CustomCodeTool;
type
TChangeParamListAction = (
@ -65,6 +65,25 @@ type
constructor CreateChangeDefaultValue(TheIndex: integer; aValue: string);
end;
TChangeDeclarationTool = class;
{ TSrcNameRefs - holds the references of a source name in one module }
TSrcNameRefs = class
public
Tool: TChangeDeclarationTool;
LocalSrcName: string;
InFilenameCleanPos: integer;
TreeOfPCodeXYPosition: TAVLTree;
NewLocalSrcName: string; // for rename
destructor Destroy; override;
end;
TReplaceDottedIdentifierParam = record
OldNames, NewNames: TStringArray;
SameStart, SameEnd: integer;
end;
{ TChangeDeclarationTool }
TChangeDeclarationTool = class(TExtractCodeTool)
@ -86,6 +105,13 @@ type
function AddProcModifier(const CursorPos: TCodeXYPosition; aModifier: string;
SourceChanger: TSourceChangeCache): boolean;
function InitReplaceDottedIdentifier(const OldDottedIdentifier, NewDottedIdentifier: string;
out Param: TReplaceDottedIdentifierParam): boolean;
function ReplaceDottedIdentifier(CleanPos: integer; const Param: TReplaceDottedIdentifierParam;
SourceChanger: TSourceChangeCache): boolean;
function RenameSourceNameReferences(OldTargetFilename, NewTargetFilename: string;
Refs: TSrcNameRefs; SourceChanger: TSourceChangeCache): boolean;
end;
implementation
@ -1051,5 +1077,189 @@ begin
Result:=SourceChanger.Apply;
end;
function TChangeDeclarationTool.InitReplaceDottedIdentifier(const OldDottedIdentifier,
NewDottedIdentifier: string; out Param: TReplaceDottedIdentifierParam): boolean;
function SplitDotty(const Dotted: string; var Arr: TStringArray): boolean;
var
p, l, StartP: integer;
begin
Result:=false;
p:=1;
l:=length(Dotted);
repeat
StartP:=p;
if p>l then exit;
if Dotted[p]='&' then inc(p);
if p>l then exit;
if not IsIdentStartChar[Dotted[p]] then exit;
inc(p);
while (p<=l) and IsIdentChar[Dotted[p]] do inc(p);
Insert(copy(Dotted,StartP,p-StartP),Arr,length(Arr));
if p>l then exit(true);
if Dotted[p]<>'.' then exit;
inc(p);
until false;
end;
var
OldCount, NewCount: integer;
begin
Result:=false;
Param:=Default(TReplaceDottedIdentifierParam);
if not SplitDotty(OldDottedIdentifier,Param.OldNames) then exit;
if not SplitDotty(NewDottedIdentifier,Param.NewNames) then exit;
OldCount:=length(Param.OldNames);
NewCount:=length(Param.NewNames);
Param.SameStart:=0;
while (Param.SameStart<OldCount)
and (Param.SameStart<NewCount)
and (CompareIdentifiers(PChar(Param.OldNames[Param.SameStart]),
PChar(Param.NewNames[Param.SameStart]))=0) do
inc(Param.SameStart);
Param.SameEnd:=0;
while (Param.SameEnd<OldCount)
and (Param.SameEnd<NewCount)
and (CompareIdentifiers(PChar(Param.OldNames[OldCount-Param.SameEnd-1]),
PChar(Param.NewNames[NewCount-Param.SameEnd-1]))=0) do
inc(Param.SameEnd);
Result:=true;
end;
function TChangeDeclarationTool.ReplaceDottedIdentifier(CleanPos: integer;
const Param: TReplaceDottedIdentifierParam; SourceChanger: TSourceChangeCache): boolean;
type
TItem = record
StartPos, EndPos: integer;
Name: string;
DotPos: integer;
end;
function Replace(CleanStartPos, CleanEndPos: integer; const NewCode: string): boolean;
var
StartCodePos, EndCodePos: TCodePosition;
OldCode: String;
begin
CleanPosToCodePos(CleanStartPos,StartCodePos);
CleanPosToCodePos(CleanEndPos,EndCodePos);
if (StartCodePos.Code<>EndCodePos.Code) or (StartCodePos.P>EndCodePos.P) then
begin
debugln(['Error: [20250203111039] ReplaceDottedIdentifier dotted identifier spans over multiple files: ',CleanPosToStr(CleanStartPos,true)]);
exit(false);
end;
OldCode:=copy(StartCodePos.Code.Source,StartCodePos.P,EndCodePos.P-StartCodePos.P);
{$IFDEF VerboseFindSourceNameReferences}
debugln(['ReplaceDottedIdentifier ',CleanPosToStr(CleanStartPos),' OldCode="',OldCode,'" NewCode="',NewCode,'"']);
{$ENDIF}
if OldCode=NewCode then
exit(true);
Result:=SourceChanger.ReplaceEx(gtNone,gtNone,1,1,StartCodePos.Code,
StartCodePos.P,EndCodePos.P, NewCode);
if not Result then
debugln(['Error: [20250203111611] SourceChanger.ReplaceEx failed at: ',CleanPosToStr(CleanStartPos,true)]);
end;
var
Item: TItem;
Items: array of TItem;
NewCode: String;
EndPos, OldCount, NewCount, i: Integer;
HasComments: Boolean;
begin
Result:=false;
OldCount:=length(Param.OldNames);
NewCount:=length(Param.NewNames);
// parse and collect atoms
SetLength(Items{%H-},OldCount);
MoveCursorToCleanPos(CleanPos);
HasComments:=false;
for i:=0 to OldCount-1 do begin
ReadNextAtom;
Item.StartPos:=CurPos.StartPos;
Item.EndPos:=CurPos.EndPos;
Item.Name:=GetAtom;
if (i>0) and (Item.StartPos>Items[i-1].DotPos+1) then
HasComments:=true;
if CompareIdentifiers(PChar(Item.Name),PChar(Param.OldNames[i]))<>0 then begin
debugln(['TChangeDeclarationTool.ReplaceDottedIdentifier expected "',Param.OldNames[i],'", but found "',Item.Name,'" at '+CleanPosToStr(CurPos.StartPos,true)]);
exit;
end;
if i<OldCount-1 then begin
ReadNextAtom;
if CurPos.Flag<>cafPoint then begin
debugln(['TChangeDeclarationTool.ReplaceDottedIdentifier expected ., but found "',GetAtom,'" at '+CleanPosToStr(CurPos.StartPos,true)]);
exit;
end;
Item.DotPos:=CurPos.StartPos;
if Item.EndPos<Item.DotPos then
HasComments:=true;
end;
Items[i]:=Item;
end;
EndPos:=CurPos.EndPos;
if not HasComments then begin
// simple replace
NewCode:=Param.NewNames[0];
for i:=1 to NewCount-1 do
NewCode:=NewCode+'.'+Param.NewNames[i];
Result:=Replace(CleanPos,EndPos,NewCode);
exit;
end;
// ToDo:
debugln(['TChangeDeclarationTool.ReplaceDottedIdentifier Complex: OldCode="',copy(Src,CleanPos,EndPos-CleanPos),'"']);
end;
function TChangeDeclarationTool.RenameSourceNameReferences(OldTargetFilename,
NewTargetFilename: string; Refs: TSrcNameRefs; SourceChanger: TSourceChangeCache): boolean;
var
i, p: Integer;
Param: TReplaceDottedIdentifierParam;
Node: TAVLTreeNode;
CodePos: PCodeXYPosition;
begin
Result:=false;
if Refs=nil then exit;
if (Refs.InFilenameCleanPos>0) and (OldTargetFilename<>NewTargetFilename) then
begin
// todo: change in-filename
end;
{$IFDEF VerboseFindSourceNameReferences}
debugln(['TChangeDeclarationTool.RenameSourceNameReferences ',Scanner.MainFilename,' ']);
{$ENDIF}
if (Refs.TreeOfPCodeXYPosition<>nil) and (Refs.TreeOfPCodeXYPosition.Count>0) then begin
InitReplaceDottedIdentifier(Refs.LocalSrcName,Refs.NewLocalSrcName,Param);
Node:=Refs.TreeOfPCodeXYPosition.FindLowest;
while Node<>nil do begin
CodePos:=PCodeXYPosition(Node.Data);
debugln(['AAA1 TChangeDeclarationTool.RenameSourceNameReferences ',dbgs(CodePos^)]);
if CaretToCleanPos(CodePos^,p)<>0 then begin
debugln(['TChangeDeclarationTool.RenameSourceNameReferences invalid codepos: ',dbgs(CodePos^)]);
end else begin
ReplaceDottedIdentifier(p,Param,SourceChanger);
end;
Node:=Refs.TreeOfPCodeXYPosition.FindSuccessor(Node);
end;
end;
Result:=true;
end;
{ TSrcNameRefs }
destructor TSrcNameRefs.Destroy;
begin
if TreeOfPCodeXYPosition<>nil then
FreeTreeOfPCodeXYPosition(TreeOfPCodeXYPosition);
inherited Destroy;
end;
end.

View File

@ -75,6 +75,7 @@ type
Flag: TCommonAtomFlag;
end;
PAtomPosition = ^TAtomPosition;
TAtomPositionArray = array of TAtomPosition;
const
StartAtomPosition: TAtomPosition = (StartPos:1; EndPos:1; Flag:cafNone);

View File

@ -280,15 +280,14 @@ function CompareCodeXYPositions(Pos1, Pos2: PCodeXYPosition): integer;
function CompareCodePositions(Pos1, Pos2: PCodePosition): integer;
procedure AddCodePosition(var ListOfPCodeXYPosition: TFPList;
const NewCodePos: TCodeXYPosition);
procedure AddCodePosition(var ListOfPCodeXYPosition: TFPList; const NewCodePos: TCodeXYPosition);
function IndexOfCodePosition(var ListOfPCodeXYPosition: TFPList;
const APosition: PCodeXYPosition): integer;
procedure FreeListOfPCodeXYPosition(ListOfPCodeXYPosition: TFPList);
function CreateTreeOfPCodeXYPosition: TAVLTree;
procedure AddCodePosition(var TreeOfPCodeXYPosition: TAVLTree;
const NewCodePos: TCodeXYPosition);
function AddCodePosition(var TreeOfPCodeXYPosition: TAVLTree;
const NewCodePos: TCodeXYPosition; Unique: boolean = true): boolean; // false if duplicate not added
procedure FreeTreeOfPCodeXYPosition(TreeOfPCodeXYPosition: TAVLTree);
procedure AddListToTreeOfPCodeXYPosition(SrcList: TFPList;
DestTree: TAVLTree; ClearList, CreateCopies: boolean);
@ -351,7 +350,7 @@ begin
else if Pos1^.Y<Pos2^.Y then Result:=1
else if Pos1^.Y>Pos2^.Y then Result:=-1
else if Pos1^.X<Pos2^.X then Result:=1
else if Pos1^.Y<Pos2^.Y then Result:=-1
else if Pos1^.X>Pos2^.X then Result:=-1
else Result:=0;
end;
@ -408,16 +407,19 @@ begin
Result:=TAVLTree.Create(TListSortCompare(@CompareCodeXYPositions));
end;
procedure AddCodePosition(var TreeOfPCodeXYPosition: TAVLTree;
const NewCodePos: TCodeXYPosition);
function AddCodePosition(var TreeOfPCodeXYPosition: TAVLTree; const NewCodePos: TCodeXYPosition;
Unique: boolean): boolean;
var
AddCodePos: PCodeXYPosition;
begin
if TreeOfPCodeXYPosition=nil then
TreeOfPCodeXYPosition:=TAVLTree.Create(TListSortCompare(@CompareCodeXYPositions));
TreeOfPCodeXYPosition:=TAVLTree.Create(TListSortCompare(@CompareCodeXYPositions))
else if Unique and (TreeOfPCodeXYPosition.Find(@NewCodePos)<>nil) then
exit(false);
New(AddCodePos);
AddCodePos^:=NewCodePos;
TreeOfPCodeXYPosition.Add(AddCodePos);
Result:=true;
end;
procedure FreeTreeOfPCodeXYPosition(TreeOfPCodeXYPosition: TAVLTree);

View File

@ -52,7 +52,7 @@ uses
PPUCodeTools, LFMTrees, DirectivesTree, CodeCompletionTemplater,
PascalParserTool, CodeToolsConfig, CustomCodeTool, FindDeclarationTool,
IdentCompletionTool, StdCodeTools, ResourceCodeTool, CodeToolsStructs,
CTUnitGraph, ExtractProcTool, SourceLog;
CTUnitGraph, ExtractProcTool, SourceLog, ChangeDeclarationTool;
type
TCodeToolManager = class;
@ -561,11 +561,6 @@ type
var ListOfPCodeXYPosition: TFPList;
var Cache: TFindIdentifierReferenceCache; // you must free Cache
const Flags: TFindRefsFlags = []): boolean;
function FindUnitReferences(UnitCode, TargetCode: TCodeBuffer;
SkipComments: boolean; var ListOfPCodeXYPosition: TFPList): boolean;
function FindUsedUnitReferences(Code: TCodeBuffer; X, Y: integer;
SkipComments: boolean; out UsedUnitFilename: string;
var ListOfPCodeXYPosition: TFPList): boolean;
function FindReferencesInFiles(Files: TStringList;
DeclarationCode: TCodeBuffer; const DeclarationCaretXY: TPoint;
SearchInComments: boolean;
@ -573,6 +568,20 @@ type
function RenameIdentifier(TreeOfPCodeXYPosition: TAVLTree;
const OldIdentifier, NewIdentifier: string;
DeclarationCode: TCodeBuffer; DeclarationCaretXY: PPoint): boolean;
function FindSourceNameReferences(TargetFilename: string;
Files: TStringList; SkipComments: boolean;
out ListOfSrcNameRefs: TObjectList): boolean;
function RenameSourceNameReferences(OldFilename, NewFilename, NewSrcname: string;
ListOfSrcNameRefs: TObjectList): boolean;
// todo: deprecate FindUnitReferences
function FindUnitReferences(UnitCode, TargetCode: TCodeBuffer;
SkipComments: boolean; var ListOfPCodeXYPosition: TFPList): boolean;
// todo: deprecate FindUsedUnitReferences
function FindUsedUnitReferences(Code: TCodeBuffer; X, Y: integer;
SkipComments: boolean; out UsedUnitFilename: string;
var ListOfPCodeXYPosition: TFPList): boolean;
function ReplaceWord(Code: TCodeBuffer; const OldWord, NewWord: string;
ChangeStrings: boolean): boolean;
function RemoveIdentifierDefinition(Code: TCodeBuffer; X, Y: integer
@ -3016,7 +3025,7 @@ begin
if TreeOfPCodeXYPosition=nil then
TreeOfPCodeXYPosition:=CreateTreeOfPCodeXYPosition;
AddListToTreeOfPCodeXYPosition(ListOfPCodeXYPosition,
TreeOfPCodeXYPosition,true,false);
TreeOfPCodeXYPosition,true,false);
end;
end;
@ -3036,6 +3045,137 @@ begin
end;
end;
function TCodeToolManager.FindSourceNameReferences(TargetFilename: string; Files: TStringList;
SkipComments: boolean; out ListOfSrcNameRefs: TObjectList): boolean;
var
i, j, InFilenameCleanPos: Integer;
Filename, Dir, TargetUnitName, InFilename, LocalSrcName: String;
Code: TCodeBuffer;
Tools, DirCachesSearch, DirCachesSkip: TFPList;
DirCache: TCTDirectoryCache;
TreeOfPCodeXYPosition: TAVLTree;
Param: TSrcNameRefs;
begin
{$IFDEF VerboseFindSourceNameReferences}
debugln(['TCodeToolManager.FindReferencesInFiles TargetFile="',TargetFilename,'" FileCount=',Files.Count,' SkipComments=',SkipComments]);
{$ENDIF}
Result:=false;
ListOfSrcNameRefs:=nil;
Tools:=TFPList.Create;
DirCachesSearch:=TFPList.Create;
DirCachesSkip:=TFPList.Create;
try
// search in every file
for i:=0 to Files.Count-1 do begin
Filename:=Files[i];
case ExtractFileNameOnly(Filename) of
'','.','..': continue; // invalid filename
end;
{$IFDEF VerboseFindSourceNameReferences}
debugln(['TCodeToolManager.FindReferencesInFiles File ',Filename]);
{$ENDIF}
j:=i-1;
while (j>=0) and (CompareFilenames(Filename,Files[j])<>0) do dec(j);
if j>=0 then continue; // skip duplicate
if CompareFilenames(TargetFilename,Filename)<>0 then begin
// check if directory has target in unitpath
Dir:=ExtractFilePath(Filename);
DirCache:=DirectoryCachePool.GetCache(Dir,true,false);
if DirCachesSkip.IndexOf(DirCache)>=0 then continue;
if DirCachesSearch.IndexOf(DirCache)<0 then begin
TargetUnitName:=ExtractFileNameOnly(TargetFilename);
InFilename:='';
if DirCache.FindUnitSourceInCompletePath(TargetUnitName,InFilename,true)<>'' then
begin
{$IFDEF VerboseFindSourceNameReferences}
debugln(['TCodeToolManager.FindReferencesInFiles File ',Filename,', target in unit path']);
{$ENDIF}
DirCachesSearch.Add(DirCache);
end else begin
{$IFDEF VerboseFindSourceNameReferences}
debugln(['TCodeToolManager.FindReferencesInFiles File ',Filename,', target NOT in unit path, SKIP']);
{$ENDIF}
DirCachesSkip.Add(DirCache);
continue;
end;
end;
end;
Code:=LoadFile(Filename,true,false);
if Code=nil then begin
debugln('TCodeToolManager.FindReferencesInFiles unable to load "',Filename,'"');
exit;
end;
if not InitCurCodeTool(Code) then exit;
if Tools.IndexOf(FCurCodeTool)>=0 then continue;
Tools.Add(FCurCodeTool);
// search references
if not FCurCodeTool.FindSourceNameReferences(TargetFilename,SkipComments,LocalSrcName,
InFilenameCleanPos, TreeOfPCodeXYPosition, false)
then begin
debugln(['TCodeToolManager.FindSourceNameReferences FindSourceNameReferences FAILED in "',Code.Filename,'"']);
if TreeOfPCodeXYPosition<>nil then
FreeTreeOfPCodeXYPosition(TreeOfPCodeXYPosition);
continue;
end;
{$IFDEF VerboseFindSourceNameReferences}
if TreeOfPCodeXYPosition<>nil then
debugln(['TCodeToolManager.FindSourceNameReferences SrcName="',LocalSrcName,'" Count=',TreeOfPCodeXYPosition.Count])
else
debugln(['TCodeToolManager.FindSourceNameReferences SrcName="',LocalSrcName,'" Count=0']);
{$ENDIF}
Param:=TSrcNameRefs.Create;
Param.Tool:=FCurCodeTool;
Param.LocalSrcName:=LocalSrcName;
Param.InFilenameCleanPos:=InFilenameCleanPos;
Param.TreeOfPCodeXYPosition:=TreeOfPCodeXYPosition;
if ListOfSrcNameRefs=nil then
ListOfSrcNameRefs:=TObjectList.Create(true);
ListOfSrcNameRefs.Add(Param);
end;
finally
DirCachesSearch.Free;
DirCachesSkip.Free;
Tools.Free;
end;
Result:=true;
end;
function TCodeToolManager.RenameSourceNameReferences(OldFilename, NewFilename,
NewSrcname: string; ListOfSrcNameRefs: TObjectList): boolean;
var
i: Integer;
Param: TSrcNameRefs;
Tool: TChangeDeclarationTool;
NewTargetSrcName: string;
begin
Result:=true;
if (ListOfSrcNameRefs=nil) or (ListOfSrcNameRefs.Count=0) then exit;
{$IFDEF VerboseFindSourceNameReferences}
debugln(['TCodeToolManager.RenameSourceNameReferences OldFile="',OldFilename,'" NewFile="',NewFilename,'" NewSrcName="',NewSrcname,'" FileCount=',ListOfSrcNameRefs.Count]);
{$ENDIF}
ClearCurCodeTool;
SourceChangeCache.Clear;
for i:=0 to ListOfSrcNameRefs.Count-1 do begin
Param:=TSrcNameRefs(ListOfSrcNameRefs[i]);
Tool:=Param.Tool;
if Param.NewLocalSrcName='' then
Param.NewLocalSrcName:=NewSrcName;
if not Tool.RenameSourceNameReferences(OldFilename,NewFilename,
Param,SourceChangeCache) then
begin
debugln(['TCodeToolManager.RenameSourceNameReferences Failed: ',Tool.MainFilename]);
end;
end;
Result:=SourceChangeCache.Apply;
end;
function TCodeToolManager.RenameIdentifier(TreeOfPCodeXYPosition: TAVLTree; const OldIdentifier,
NewIdentifier: string; DeclarationCode: TCodeBuffer; DeclarationCaretXY: PPoint): boolean;
var
@ -3139,8 +3279,9 @@ begin
@Code.Source[IdentStartPos],PChar(Pointer(NewIdentifier)));
IdentEndPos:=IdentStartPos+length(OldIdentifier);
if (UpCase(Code.Source[IdentStartPos])<>UpCase(OldIdentifier[1])) and
((Code.Source[IdentStartPos]='&') or (OldIdentifier[1]='&')) then begin
if (UpCase(Code.Source[IdentStartPos])<>UpCase(OldIdentifier[1]))
and ((Code.Source[IdentStartPos]='&') or (OldIdentifier[1]='&')) then
begin
if OldIdentifier[1]='&' then
dec(IdentEndPos)
else

View File

@ -87,8 +87,8 @@ const
ctnVarArgs = 24; // macpas ... parameter
ctnSrcName = 25; // children are ctnIdentifier
ctnUseUnit = 26; // StartPos=unit, EndPos=unitname+inFilename, children ctnUseUnitNamespace, ctnUseUnitClearName, parent ctnUsesSection
ctnUseUnitNamespace = 27; // <namespace>.clearname.pas, parent ctnUseUnit
ctnUseUnitClearName = 28; // namespace.<clearname>.pas, parent ctnUseUnit
ctnUseUnitNamespace = 27; // <namespace>.clearname, parent ctnUseUnit
ctnUseUnitClearName = 28; // namespace.<clearname>, parent ctnUseUnit
ctnClass = 30;
ctnClassInterface = 31;
@ -197,13 +197,15 @@ const
+[ctnGenericType,ctnGlobalProperty];
AllPascalTypes =
AllClasses+
[ctnGenericType,ctnSpecialize,
ctnIdentifier,ctnOpenArrayType,ctnRangedArrayType,
ctnRecordCase,ctnRecordVariant,
[ctnIdentifier{alias},
ctnGenericType,ctnSpecialize,
ctnOpenArrayType,ctnRangedArrayType,
ctnProcedureType,ctnReferenceTo,
ctnSetType,ctnRangeType,ctnEnumerationType,
ctnEnumIdentifier,ctnLabel,ctnTypeType,ctnFileType,ctnPointerType,
ctnClassOfType,ctnVariantType,ctnConstant];
ctnLabel,ctnTypeType,ctnFileType,ctnPointerType,
ctnClassOfType,ctnVariantType];
AllPascalTypeParts = AllPascalTypes
+[ctnEnumIdentifier,ctnConstant,ctnRecordCase,ctnRecordVariant];
AllProcTypes = [ctnProcedureType,ctnReferenceTo];
AllPascalStatements = [ctnBeginBlock,ctnWithStatement,ctnWithVariable,
ctnOnBlock,ctnOnIdentifier,ctnOnStatement,

View File

@ -3217,7 +3217,7 @@ end;
function TCustomCodeTool.ExtractDottedIdentifier(CleanStartPos: integer): string;
begin
Result:=GetDottedIdentifier(@Src[CleanStartPos]);
Result:=ReadDottedIdentifier(Src,CleanStartPos,Scanner.NestedComments);
end;
function TCustomCodeTool.ExtractIdentifierWithPointsOutEndPos(StartPos: integer;
@ -3229,7 +3229,7 @@ var aLen: integer;
begin
Result:='';
EndPos:=StartPos;
if src='' then exit;
if Src='' then exit;
MoveCursorToCleanPos(StartPos);
ReadNextAtom;

View File

@ -69,6 +69,7 @@ interface
{ $DEFINE VerboseFindDeclarationAndOverload}
{ $DEFINE VerboseFindFileAtCursor}
{ $DEFINE VerboseFindRefMethodOverrides}
{ $DEFINE VerboseFindSourceNameReferences}
{$IFDEF CTDEBUG}{$DEFINE DebugPrefix}{$ENDIF}
{$IFDEF ShowTriedIdentifiers}{$DEFINE DebugPrefix}{$ENDIF}
@ -79,7 +80,7 @@ uses
{$IFDEF MEM_CHECK}
MemCheck,
{$ENDIF}
Classes, SysUtils, AVL_Tree,
Classes, SysUtils, Types, AVL_Tree,
// LazUtils
LazFileUtils, LazStringUtils, LazUtilities,
// Codetools
@ -252,9 +253,9 @@ type
xtLongBool, // longbool
xtQWordBool, // qwordbool
xtBoolean8, // boolean8
xtBoolean16, // boolean16
xtBoolean32, // boolean32
xtBoolean64, // boolean64
xtBoolean16, // boolean16
xtBoolean32, // boolean32
xtBoolean64, // boolean64
xtString, // string
xtAnsiString, // ansistring
xtShortString, // shortstring
@ -1063,6 +1064,9 @@ type
function FindReferences(const CursorPos: TCodeXYPosition;
SkipComments: boolean; out ListOfPCodeXYPosition: TFPList;
Flags: TFindRefsFlags = []): boolean;
function FindSourceNameReferences(const TargetFilename: string; SkipComments: boolean;
out LocalSrcName: string; out InFilenameCleanPos: integer;
out TreeOfPCodeXYPosition: TAVLTree; SyntaxExceptions: boolean = false): boolean;
function FindUnitReferences(UnitCode: TCodeBuffer;
SkipComments: boolean; out ListOfPCodeXYPosition: TFPList): boolean; // searches unitname of UnitCode
procedure FindUsedUnitReferences(const CursorPos: TCodeXYPosition;
@ -1458,41 +1462,12 @@ end;
function FindContextToString(const FindContext: TFindContext;
RelativeFilename: boolean): string;
var
IdentNode: TCodeTreeNode;
begin
Result:='';
if FindContext.Node<>nil then begin
Result:=Result+'Node="'+FindContext.Node.DescAsString+'"';
IdentNode:=FindContext.Node;
while IdentNode<>nil do begin
if IdentNode.Desc in AllSimpleIdentifierDefinitions
+[ctnIdentifier,ctnEnumIdentifier,ctnLabel]
then begin
Result:=Result+' Ident="'+
FindContext.Tool.ExtractIdentifier(IdentNode.StartPos)+'"';
break;
end else if IdentNode.Desc=ctnGenericType then begin
if IdentNode.FirstChild<>nil then
Result:=Result+' Generic="'+
FindContext.Tool.ExtractIdentifier(IdentNode.FirstChild.StartPos)+'"'
else
Result:=Result+' Generic=?';
end else if IdentNode.Desc in [ctnProperty,ctnGlobalProperty] then begin
Result:=Result+' PropName="'+
FindContext.Tool.ExtractPropName(IdentNode,false)+'"';
break;
end else if IdentNode.Desc=ctnProcedure then begin
Result:=Result+' Proc="'+FindContext.Tool.ExtractProcName(IdentNode,[])+'"';
break;
end;
IdentNode:=IdentNode.Parent;
end;
if RelativeFilename then
Result:=Result+' at "'+FindContext.Tool.CleanPosToStr(FindContext.Node.StartPos,true)+'"'
else
Result:=Result+' at "'+FindContext.Tool.CleanPosToRelativeStr(FindContext.Node.StartPos,'')+'"'
end else
if RelativeFilename then ;
if FindContext.Node<>nil then
Result:=FindContext.Tool.GetNodeNamePath(FindContext.Node,true,true)
else
Result:='nil';
end;
@ -7448,6 +7423,555 @@ begin
Result:=true;
end;
function TFindDeclarationTool.FindSourceNameReferences(const TargetFilename: string;
SkipComments: boolean; out LocalSrcName: string; out InFilenameCleanPos: integer; out
TreeOfPCodeXYPosition: TAVLTree; SyntaxExceptions: boolean): boolean;
var
IsSelf: Boolean; // true = searching references of my program/unit name
LocalSrcNamePos: integer;
CleanPositions: TIntegerDynArray;
CleanPosCount: integer;
TargetUnitName: string; // the target file unit name, can differ from LocalSrcName
TargetShortFilename: string;
InterfaceUsesNode, ImplementationUsesNode: TCodeTreeNode;
IntfUseNames, ImplUseNames: TStringArray;
Params: TFindDeclarationParams;
procedure AddPos(CleanPos: integer);
begin
{$IFDEF VerboseFindSourceNameReferences}
debugln([' AddPos Index=',CleanPosCount,' ',CleanPosToStr(CleanPos),' "',copy(Src,CleanPos,100),'"']);
{$ENDIF}
if (CleanPosCount>0) and (CleanPos<=CleanPositions[CleanPosCount-1]) then begin
debugln([' AddPos Index=',CleanPosCount,' ',CleanPosToStr(CleanPos),' "',copy(Src,CleanPos,100),'"']);
raise Exception.Create('20250202212016');
end;
if CleanPosCount=Length(CleanPositions) then begin
if Length(CleanPositions)<8 then
SetLength(CleanPositions,8)
else
SetLength(CleanPositions,CleanPosCount*2);
end;
CleanPositions[CleanPosCount]:=CleanPos;
inc(CleanPosCount);
end;
procedure DeletePos(CleanPos: integer);
var
i: Integer;
begin
for i:=CleanPosCount-1 downto 0 do
if CleanPositions[i]=CleanPos then
System.Delete(CleanPositions,i,1);
end;
function CheckUsesSection(UsesSection: TCodeTreeNode; var UseNames: TStringArray): boolean;
var
Node: TCodeTreeNode;
AUnitName, UnitInFilename: string;
StartPos: Integer;
Code: TCodeBuffer;
MatchInFile, MatchUses: Boolean;
UnitNameRange, InAtom: TAtomPosition;
begin
if UsesSection=nil then exit(true);
Node:=UsesSection.FirstChild;
if Node=nil then exit(true);
//debugln(['CheckUsesSection START ',CleanPosToStr(Node.StartPos)]);
Result:=false;
MoveCursorToNodeStart(Node);
repeat
ReadNextAtom; // read name
StartPos:=CurPos.StartPos;
AUnitName:=ExtractUsedUnitNameAtCursor(@UnitInFilename);
if AUnitName='' then exit;
//debugln(['CheckUsesSection AUnitName="',AUnitName,'" InFile="',UnitInFilename,'"']);
Insert(AUnitName,UseNames,length(UseNames));
if not IsSelf then begin
// check if uses refers to target
MatchInFile:=false;
MatchUses:=false;
if UnitInFilename<>'' then begin
if TargetShortFilename='' then
TargetShortFilename:=ExtractFilename(TargetFilename);
if CompareDottedIdentifiers(PChar(ExtractFilename(UnitInFilename)),
PChar(TargetShortFilename))=0 then
begin
MatchInFile:=true;
MatchUses:=CompareDottedIdentifiers(PChar(AUnitName),PChar(TargetUnitName))=0;
end;
end else begin
// Note: with scopes/namespaces like -FN, a 'uses Bar;' can use a 'foo.bar.pas'
MatchUses:=DottedIdentifierEndsWith(PChar(TargetUnitName),PChar(AUnitName));
end;
if MatchUses or MatchInFile then begin
// search in search paths
Code:=FindUnitSource(AUnitName,UnitInFilename,false,StartPos);
if (Code<>nil) and (CompareFilenames(Code.Filename,TargetFilename)=0) then
begin
// found matching uses
{$IFDEF VerboseFindSourceNameReferences}
if UnitInFilename<>'' then
debugln([' CheckUsesSection uses found ',CleanPosToStr(StartPos,true),', uses="',AUnitName,'" in "',UnitInFilename,'"'])
else
debugln([' CheckUsesSection uses found ',CleanPosToStr(StartPos,true),', uses="',AUnitName,'"']);
{$ENDIF}
if MatchUses then begin
// matches uses
if LocalSrcNamePos>=0 then begin
// duplicate uses
debugln(['Warning: [20250202120622] TFindDeclarationTool.FindSourceNameReferences duplicate uses at ',CleanPosToStr(LocalSrcNamePos,true),' and ',CleanPosToStr(StartPos)]);
DeletePos(LocalSrcNamePos);
end;
LocalSrcName:=AUnitName;
LocalSrcNamePos:=StartPos;
AddPos(StartPos);
end;
if MatchInFile then begin
// matches in-filename
MoveCursorToCleanPos(StartPos);
ReadNextAtom;
ReadNextUsedUnit(UnitNameRange,InAtom);
if InFilenameCleanPos>=0 then begin
debugln(['Warning: [20250202122909] TFindDeclarationTool.FindSourceNameReferences duplicate uses IN filenames at ',CleanPosToStr(InFilenameCleanPos,true),' and ',CleanPosToStr(InAtom.StartPos)]);
end;
InFilenameCleanPos:=InAtom.StartPos;
end;
end;
end;
end;
if AtomIsChar(';') then break;
if not AtomIsChar(',') then
RaiseExceptionFmt(20250201102057,ctsStrExpectedButAtomFound,[';',GetAtom])
until CurPos.StartPos>SrcLen;
Result:=true;
end;
procedure FindLongestUsesName(UseNames: TStringArray; const Expr: string;
var BestUseName: string; var BestDotCount: integer);
var
i, DotCount: Integer;
ExprP, UseNameP: PChar;
begin
ExprP:=PChar(Expr);
for i:=0 to length(UseNames)-1 do begin
UseNameP:=PChar(UseNames[i]);
if CompareIdentifiers(UseNameP,ExprP)=0 then begin
DotCount:=GetDotCountInIdentifier(UseNameP);
if DotCount>BestDotCount then begin
BestDotCount:=DotCount;
BestUseName:=UseNames[i];
end;
end;
end;
end;
function CheckIdentifier(var p: integer): boolean;
// check the identifier at start of an expression
var
StartP, Ident: PChar;
StartPos, BestDotCount, DotCnt, LastIdentPos: Integer;
Expr, BestUseName: String;
CursorNode, Node: TCodeTreeNode;
Found: Boolean;
begin
Result:=true;
StartPos:=p;
StartP:=@Src[StartPos];
Ident:=PChar(LocalSrcName);
if CompareIdentifiers(StartP,Ident)<>0 then begin
inc(p,GetIdentLen(StartP));
exit;
end;
Expr:=ReadDottedIdentifier(Src,p,Scanner.NestedComments);
//debugln([' CheckIdentifier At ',CleanPosToStr(p),' Expr="',Expr,'"']);
if not DottedIdentifierStartsWith(PChar(LocalSrcName),PChar(Expr)) then exit;
{$IFDEF VerboseFindSourceNameReferences}
debugln([' CheckIdentifier found expression starting with LocalSrcName: "',Expr,'" at ',CleanPosToStr(StartPos,true)]);
{$ENDIF}
// quick check: find the longest matching uses name
BestUseName:='';
BestDotCount:=0;
if (ImplementationUsesNode<>nil) and (ImplementationUsesNode.EndPos<=StartPos) then
FindLongestUsesName(ImplUseNames,Expr,BestUseName,BestDotCount);
if (InterfaceUsesNode<>nil) and (InterfaceUsesNode.EndPos<=StartPos) then
FindLongestUsesName(IntfUseNames,Expr,BestUseName,BestDotCount);
if (BestUseName<>'')
and (CompareDottedIdentifiers(PChar(BestUseName),PChar(LocalSrcName))<>0) then
begin
// a longer uses fits -> this expr does not refer to the LocalSrcName
exit;
end;
// find declaration
{$IFDEF VerboseFindSourceNameReferences}
debugln([' CheckIdentifier search expression "',Expr,'" at ',CleanPosToStr(StartPos,true)]);
{$ENDIF}
DotCnt:=GetDotCountInIdentifier(Ident);
MoveCursorToCleanPos(StartPos);
repeat
ReadNextAtom;
if not AtomIsIdentifier then begin
debugln(['Error: [20250202181504] FindSourceNameReferences CheckIdentifier identifier expected at ',CleanPosToStr(CurPos.StartPos,true),', but "',GetAtom,'" found']);
exit;
end;
if DotCnt=0 then
break;
dec(DotCnt);
ReadNextAtom;
if CurPos.Flag<>cafPoint then begin
debugln(['Error: [20250202181625] FindSourceNameReferences CheckIdentifier . expected at ',CleanPosToStr(CurPos.StartPos,true),', but "',GetAtom,'" found']);
exit;
end;
until CurPos.StartPos>=SrcLen;
LastIdentPos:=CurPos.StartPos;
CursorNode:=BuildSubTreeAndFindDeepestNodeAtPos(StartPos,true);
if CleanPosIsDeclarationIdentifier(LastIdentPos,CursorNode) then
exit;
if Params=nil then
Params:=TFindDeclarationParams.Create(Self, CursorNode)
else
Params.Clear;
Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,fdfSearchInHelpers,
fdfIgnoreCurContextNode];
Params.ContextNode:=CursorNode;
//debugln([' CheckIdentifier "',copy(Src,LastIdentPos,200),'"']);
Params.SetIdentifier(Self,@Src[LastIdentPos],@CheckSrcIdentifier);
// search identifier also in comments -> if not found, this is no bug
// => silently ignore
try
Found:=FindDeclarationOfIdentAtParam(Params);
except
on E: ECodeToolError do begin
if E.Sender<>Self then begin
// there is an error in another unit, which prevents searching
// stop further searching in this unit
raise;
end;
// continue
end;
on E: Exception do
raise;
end;
if not Found then exit;
Node:=Params.NewNode;
if Node=nil then exit;
{$IFDEF VerboseFindSourceNameReferences}
debugln([' CheckIdentifier Found Node=',GetNodeNamePath(Node,true)]);
{$ENDIF}
if Node.Desc=ctnSrcName then begin
if IsSelf then
AddPos(StartPos);
end else begin
// todo: found uses node
end;
end;
function CheckComment(var StartPos: integer; MaxPos: integer): boolean;
var
c: Char;
CommentLvl: Integer;
InStrConst, LastTokenWasPoint, IsDirective: Boolean;
begin
Result:=true;
c:=Src[StartPos];
//debugln([' CheckComment c=',c,' "',copy(Src,StartPos,20),'"']);
IsDirective:=false;
case c of
'/': inc(StartPos,2);
'(':
begin
inc(StartPos,2);
IsDirective:=(StartPos<=MaxPos) and (Src[StartPos]='$');
end;
'{':
begin
inc(StartPos);
IsDirective:=(StartPos<=MaxPos) and (Src[StartPos]='$');
if (StartPos<=MaxPos) and (Src[StartPos]=#3) then begin
// codetools skip comment {#3 #3}
inc(StartPos);
while (StartPos<=MaxPos) do begin
if (Src[StartPos]=#3) and (StartPos<MaxPos) and (Src[StartPos+1]='}')
then begin
inc(StartPos,2);
exit;
end;
inc(StartPos);
end;
exit;
end;
end;
end;
CommentLvl:=1;
InStrConst:=false;
LastTokenWasPoint:=false;
while StartPos<=MaxPos do begin
case Src[StartPos] of
'{':
begin
if (c='{') and Scanner.NestedComments then inc(CommentLvl);
LastTokenWasPoint:=false;
end;
'}':
begin
LastTokenWasPoint:=false;
if c='{' then begin
dec(CommentLvl);
if CommentLvl=0 then begin
inc(StartPos);
exit;
end;
end;
end;
')':
begin
LastTokenWasPoint:=false;
if (c='(') and (Src[StartPos-1]='*') then begin
inc(StartPos);
exit;
end;
end;
'a'..'z','A'..'Z','_','&':
begin
if (not IsDirective) and (not SkipComments) and (not InStrConst)
and (not LastTokenWasPoint) then
if not CheckIdentifier(StartPos) then exit(false);
LastTokenWasPoint:=false;
end;
'''':
begin
InStrConst:=not InStrConst;
LastTokenWasPoint:=false;
end;
#10,#13:
begin
InStrConst:=false;
if c='/' then begin
inc(StartPos);
if (StartPos<=MaxPos) and (Src[StartPos] in [#10,#13])
and (Src[StartPos-1]<>Src[StartPos]) then
inc(StartPos);
exit;
end;
end;
' ',#9: ;
'.': LastTokenWasPoint:=true;
else
LastTokenWasPoint:=false;
end;
inc(StartPos);
end;
end;
function CheckSource(MinPos, MaxPos: integer): boolean;
var
StartPos: Integer;
LastTokenWasPoint, LastCommentTokenWasPoint: Boolean;
begin
Result:=true;
if MinPos<LocalSrcNamePos then
MinPos:=LocalSrcNamePos;
if MinPos>SrcLen then exit;
if MaxPos>SrcLen then MaxPos:=SrcLen+1;
//debugln([' CheckSource ',MinPos,'..',MaxPos]);
StartPos:=MinPos;
LastTokenWasPoint:=false;
while StartPos<=MaxPos do begin
case Src[StartPos] of
' ',#9,#10,#13:
inc(StartPos);
'{':
CheckComment(StartPos,MaxPos);
'/': // Delphi comment
if (Src[StartPos+1]<>'/') then begin
inc(StartPos);
LastTokenWasPoint:=false;
end else begin
if not CheckComment(StartPos,MaxPos) then exit(false);
end;
'(': // turbo pascal comment
if (Src[StartPos+1]<>'*') then begin
inc(StartPos);
LastTokenWasPoint:=false;
end else begin
if not CheckComment(StartPos,MaxPos) then exit(false);
end;
'''':
begin
// skip string constant
inc(StartPos);
while (StartPos<=MaxPos) do begin
if (not (Src[StartPos] in ['''',#10,#13])) then
inc(StartPos)
else begin
inc(StartPos);
break;
end;
end;
LastTokenWasPoint:=false;
end;
'a'..'z','A'..'Z','_','&':
begin
if not LastTokenWasPoint then
if not CheckIdentifier(StartPos) then exit;
LastTokenWasPoint:=false;
end;
'.':
begin
LastTokenWasPoint:=true;
inc(StartPos);
end;
else
LastTokenWasPoint:=false;
inc(StartPos);
end;
end;
end;
procedure CreateTree;
var
i, p: Integer;
CodePos: TCodeXYPosition;
Node: TAVLTreeNode;
begin
for i:=0 to CleanPosCount-1 do begin
p:=CleanPositions[i];
if not CleanPosToCaret(p,CodePos) then
raise Exception.Create('20250203105516');
//debugln(['TFindDeclarationTool.FindSourceNameReferences CreateTree ',dbgs(CodePos)]);
AddCodePosition(TreeOfPCodeXYPosition,CodePos);
end;
end;
var
NamePos: TAtomPosition;
MySrcName: String;
StartPos, MaxPos: Integer;
begin
Result:=false;
{$IFDEF VerboseFindSourceNameReferences}
debugln(['TFindDeclarationTool.FindSourceNameReferences Self="',Scanner.MainFilename,'" TargetFile="',TargetFilename,'" SkipComments=',SkipComments,' SyntaxExceptions=',SyntaxExceptions]);
{$ENDIF}
LocalSrcName:='';
LocalSrcNamePos:=-1;
InFilenameCleanPos:=-1;
TreeOfPCodeXYPosition:=nil;
TargetUnitName:=ExtractFileNameOnly(TargetFilename);
MySrcName:='';
IntfUseNames:=[];
ImplUseNames:=[];
CleanPosCount:=0;
CleanPositions:=[];
IsSelf:=CompareFilenames(Scanner.MainFilename,TargetFilename)=0;
if not IsSelf then begin
if not IsValidDottedIdent(TargetUnitName) then begin
debugln(['Error: TFindDeclarationTool.FindSourceNameReferences invalid unit file name: "',TargetFilename,'"']);
exit(false);
end;
end;
BuildTree(lsrEnd);
Result:=true;
//debugln(['TFindDeclarationTool.FindSourceNameReferences IsSelf=',IsSelf]);
if GetSourceNamePos(NamePos) then begin
MySrcName:=ExtractSourceName;
if IsSelf then begin
LocalSrcName:=MySrcName;
LocalSrcNamePos:=NamePos.StartPos;
AddPos(LocalSrcNamePos);
end;
end else begin
// program has no source name
MySrcName:=ExtractFileNameOnly(Scanner.MainFilename);
if not IsValidDottedIdent(MySrcName) then begin
MySrcName:='';
if IsSelf then begin
debugln(['TFindDeclarationTool.FindSourceNameReferences program has no pascal name -> nothing to do']);
exit(true);
end;
end;
if IsSelf then
LocalSrcName:=MySrcName;
end;
//debugln(['TFindDeclarationTool.FindSourceNameReferences MySrcName=',MySrcName]);
InterfaceUsesNode:=FindMainUsesNode;
if not CheckUsesSection(InterfaceUsesNode,IntfUseNames) then
exit; // syntax error
ImplementationUsesNode:=FindImplementationUsesNode;
if not CheckUsesSection(ImplementationUsesNode,ImplUseNames) then
exit; // syntax error
if (LocalSrcNamePos<0) and not IsSelf then begin
{$IFDEF VerboseFindSourceNameReferences}
debugln(['TFindDeclarationTool.FindSourceNameReferences Unit not used']);
{$ENDIF}
exit; // unit not used
end;
MaxPos:=Tree.FindLastPosition;
if MaxPos>SrcLen then MaxPos:=SrcLen;
// find references in source
Params:=nil;
try
if InterfaceUsesNode<>nil then begin
if ImplementationUsesNode<>nil then begin
if not CheckSource(InterfaceUsesNode.EndPos,ImplementationUsesNode.StartPos) then exit;
if not CheckSource(ImplementationUsesNode.EndPos,MaxPos) then exit;
end else begin
if not CheckSource(InterfaceUsesNode.EndPos,MaxPos) then exit;
end;
end else if IsSelf then begin
if LocalSrcNamePos>0 then
StartPos:=NamePos.EndPos
else
StartPos:=1;
if ImplementationUsesNode<>nil then begin
if not CheckSource(StartPos,ImplementationUsesNode.StartPos) then exit;
if not CheckSource(ImplementationUsesNode.EndPos,MaxPos) then exit;
end else begin
if not CheckSource(StartPos,SrcLen) then exit;
end;
end else begin
if not CheckSource(ImplementationUsesNode.EndPos,MaxPos) then exit;
end;
finally
Params.Free;
CreateTree;
end;
end;
function TFindDeclarationTool.FindUnitReferences(UnitCode: TCodeBuffer;
SkipComments: boolean; out ListOfPCodeXYPosition: TFPList): boolean;
var
@ -9690,7 +10214,7 @@ var
if Node.Desc in [ctnTypeDefinition,ctnGenericType] then begin
FlagCanBeForwardDefined:=true;
break;
end else if not (Node.Desc in AllPascalTypes) then
end else if not (Node.Desc in AllPascalTypeParts) then
break;
Node:=Node.Parent;
end;
@ -12973,39 +13497,39 @@ procedure TFindDeclarationTool.OnFindUsedUnitIdentifier(
var
Identifier: PChar;
CacheEntry: PInterfaceIdentCacheEntry;
refs: TFindUsedUnitReferences;
Refs: TFindUsedUnitReferences;
Found: Boolean;
ReferencePos: TCodeXYPosition;
begin
if Range=epriInDirective then exit;
if not (Node.Desc in (AllPascalTypes+AllPascalStatements)) then exit;
if not (Node.Desc in (AllPascalTypeParts+AllPascalStatements)) then exit;
Identifier:=@Src[IdentifierCleanPos];
refs:=TFindUsedUnitReferences(Data);
CacheEntry:=refs.TargetTool.FInterfaceIdentifierCache.FindIdentifier(Identifier);
Refs:=TFindUsedUnitReferences(Data);
CacheEntry:=Refs.TargetTool.FInterfaceIdentifierCache.FindIdentifier(Identifier);
//debugln(['TFindUsedUnitReferences.OnIdentifier Identifier=',GetIdentifier(Identifier),' Found=',CacheEntry<>nil]);
if (CacheEntry=nil)
and (CompareIdentifiers(Identifier,PChar(refs.TargetUnitName))<>0) then
and (CompareIdentifiers(Identifier,PChar(Refs.TargetUnitName))<>0) then
exit;
Sender.MoveCursorToCleanPos(IdentifierCleanPos);
Sender.ReadPriorAtom;
if (Sender.CurPos.Flag=cafPoint) or (Sender.UpAtomIs('inherited')) then exit;
//debugln(['TFindUsedUnitReferences.OnIdentifier Identifier=',GetIdentifier(Identifier),' at begin of term']);
// find declaration
refs.Params.Clear;
refs.Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,
Refs.Params.Clear;
Refs.Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,
fdfIgnoreCurContextNode];
refs.Params.ContextNode:=Node;
Refs.Params.ContextNode:=Node;
//debugln(copy(Src,Params.ContextNode.StartPos,200));
refs.Params.SetIdentifier(Self,Identifier,@CheckSrcIdentifier);
Refs.Params.SetIdentifier(Self,Identifier,@CheckSrcIdentifier);
if Range=epriInCode then begin
// search identifier in code
Found:=FindDeclarationOfIdentAtParam(refs.Params);
Found:=FindDeclarationOfIdentAtParam(Refs.Params);
end else begin
// search identifier in comment -> if not found, this is no problem
// => silently ignore
try
Found:=FindDeclarationOfIdentAtParam(refs.Params);
Found:=FindDeclarationOfIdentAtParam(Refs.Params);
except
on E: ECodeToolError do begin
// continue
@ -13019,7 +13543,7 @@ begin
if not Found then exit;
if CleanPosToCaret(IdentifierCleanPos,ReferencePos) then
AddCodePosition(refs.ListOfPCodeXYPosition,ReferencePos);
AddCodePosition(Refs.ListOfPCodeXYPosition,ReferencePos);
end;
function TFindDeclarationTool.FindNthParameterNode(Node: TCodeTreeNode;

View File

@ -2351,10 +2351,15 @@ begin
case Node.Desc of
ctnIdentifier:
s:=ReadSrc(Node.StartPos,Node.EndPos);
ctnTypeDefinition, ctnVarDefinition, ctnConstDefinition:
ctnTypeDefinition, ctnVarDefinition, ctnConstDefinition, ctnLabel, ctnEnumIdentifier:
s:=GetIdentifier(@Src[Node.StartPos]);
ctnGenericType:
s:=ExtractClassName(Node,false,false,true);
s:='generic-'+ExtractClassName(Node,false,false,true);
ctnProperty,ctnGlobalProperty:
begin
RestoreCurPos:=true;
s:=ExtractPropName(Node,false)
end;
ctnProcedure:
begin
RestoreCurPos:=true;
@ -2421,7 +2426,7 @@ begin
Result:=Result.NextBrother;
if Result=nil then exit;
end;
if (not (Result.Desc in AllPascalTypes)) then
if (not (Result.Desc in AllPascalTypeParts)) then
Result:=nil;
exit;
end;
@ -3690,6 +3695,7 @@ begin
end;
function TPascalReaderTool.ExtractUsedUnitNameAtCursor(InFilename: PAnsiString): string;
// after reading CurPos is on atom behind, i.e. comma or semicolon
begin
Result:='';
if InFilename<>nil then
@ -3697,8 +3703,7 @@ begin
while CurPos.Flag=cafWord do begin
if Result<>'' then
Result:=Result+'.';
//Result:=Result+GetAtomIdentifier;
Result:=Result+GetAtom;//&-ident allowed - preferred "&begin.&end" over "begin.end"
Result:=Result+GetAtom; // read with &
ReadNextAtom;
if CurPos.Flag<>cafPoint then break;
ReadNextAtom;

View File

@ -5307,7 +5307,7 @@ begin
Node:=FindDeepestNodeAtPos(CleanCursorPos,false);
//if Node<>nil then debugln(['TStandardCodeTool.FindBlockStart ',Node.DescAsString]);
if (Node=nil)
or (Node.Desc in (AllPascalStatements+AllPascalTypes-AllClasses))
or (Node.Desc in (AllPascalStatements+AllPascalTypeParts-AllClasses))
or (Src[CurPos.StartPos] in [')',']','}'])
then begin
MinPos:=1;

View File

@ -54,6 +54,7 @@ type
procedure TestCompareIdentifiersCaseSensitive;
procedure TestCompareDottedIdentifiers;
procedure TestCompareDottedIdentifiersCaseSensitive;
procedure TestDottedIdentifierEndsWith;
procedure TestReadRawPascal;
// FileProcs
procedure TestDateToCfgStr;
@ -675,6 +676,35 @@ begin
t('a.&','a.&1',0); // compares 'a.' and 'a.'
end;
procedure TTestBasicCodeTools.TestDottedIdentifierEndsWith;
procedure t(Identifier, EndsWithIdent: PChar; Expected: boolean);
var
Actual: Boolean;
begin
Actual:=DottedIdentifierEndsWith(Identifier,EndsWithIdent);
if Actual=Expected then exit;
Fail('Expected '+dbgs(Expected)+' for Identifier="'+Identifier+'" EndsWithIdent="'+EndsWithIdent+'"');
end;
begin
t('','',false);
t('a','',false);
t('','b',false);
t('a','$',false);
t('$','b',false);
t('a','a',true);
t('a','ab',false);
t('ab','a',false);
t('ab','ab',true);
t('a.b','a',false);
t('a.b','b',true);
t('a.b.c','c',true);
t('a.b.c','b.c',true);
t('a.b.c','a.b.c',true);
t('a.b.&c','&b.c',true);
end;
procedure TTestBasicCodeTools.TestReadRawPascal;
procedure t(const Src: string; StartPos, EndPos: integer; const Expected: string;

View File

@ -10,8 +10,8 @@ unit TestRefactoring;
interface
uses
Classes, SysUtils, CodeToolManager, CodeCache, CodeTree, BasicCodeTools,
CTUnitGraph, FindDeclarationTool, LazLogger, LazFileUtils, AVL_Tree, fpcunit, testregistry,
Classes, SysUtils, CodeToolManager, CodeCache, CodeTree, BasicCodeTools, CTUnitGraph,
FindDeclarationTool, LazLogger, LazFileUtils, AVL_Tree, Contnrs, fpcunit, testregistry,
TestFinddeclaration;
const
@ -23,6 +23,7 @@ type
TCustomTestRefactoring = class(TCustomTestFindDeclaration)
protected
procedure RenameReferences(NewIdentifier: string; const Flags: TFindRefsFlags = []);
procedure RenameSourceName(NewName, NewFilename: string);
procedure CheckDiff(CurCode: TCodeBuffer; const ExpLines: array of string);
end;
@ -30,24 +31,46 @@ type
TTestRefactoring = class(TCustomTestRefactoring)
private
protected
published
procedure TestExplodeWith;
procedure TestRenameReferences;
procedure TestRenameProcReferences;
procedure TestRenameProcedureArg;
procedure TestRenameProcedureArgCaseSensitive;
procedure TestRenameForwardProcedureArgDown;
procedure TestRenameForwardProcedureArgUp;
procedure TestRenameMethodArgDown;
procedure TestRenameMethodArgUp;
procedure TestRenameMethodInherited;
procedure TestRenameMethodWithOverrides;
procedure TestRenameMethodWithOverridesOtherUnit;
procedure TestRenameClassMethodWithOverrides;
procedure TestRenameNestedProgramProcDown;
procedure TestRenameNestedProgramProcUp;
procedure TestRenameNestedUnitProcDown;
procedure TestRenameTypeToAmp;
// rename program
procedure TestRenameProgramName_Amp;
procedure TestRenameProgramName_DottedPostfix; // todo
// rename uses
// todo: rename unit &Type to &End
// todo: rename unit Foo.Bar to Foo.Red
// todo: rename unit Foo.Bar to Red.Bar
// todo: rename unit Foo to Foo.Bar
// todo: rename unit Foo.Bar to Foo
// todo: rename unit Foo.Bar to Bar
// todo: search in an include file should not stop searching in other files
// todo: missing used unit should not stop searching in other files
// todo: rename with ifdefs
// todo: rename with -FN, unit Foo.Bar to Foo.Red, uses Bar;
// todo: rename a.b->c.d must not change { a.}b
end;
implementation
@ -108,7 +131,7 @@ begin
try
Files.Add(DeclCode.Filename);
if CompareFilenames(DeclCode.Filename,Code.Filename)<>0 then
Files.Add(DeclCode.Filename);
Files.Add(Code.Filename);
Graph:=CodeToolBoss.CreateUsesGraph;
Graph.AddStartUnit(Code.Filename);
@ -143,6 +166,34 @@ begin
end;
end;
procedure TCustomTestRefactoring.RenameSourceName(NewName, NewFilename: string);
var
Files: TStringList;
ListOfSrcNameRefs: TObjectList;
begin
// create the file list
ListOfSrcNameRefs:=nil;
Files:=TStringList.Create;
try
Files.Add(Code.Filename);
// search pascal source references
if not CodeToolBoss.FindSourceNameReferences(Code.Filename,Files,false,ListOfSrcNameRefs) then
begin
Fail('CodeToolBoss.FindSourceNameReferences failed File='+Code.Filename);
end;
// todo: check for conflicts
if not CodeToolBoss.RenameSourceNameReferences(Code.Filename,NewFilename,NewName,ListOfSrcNameRefs)
then
Fail('CodeToolBoss.RenameSourceNameReferences failed');
finally
ListOfSrcNameRefs.Free;
Files.Free;
end;
end;
procedure TCustomTestRefactoring.CheckDiff(CurCode: TCodeBuffer;
const ExpLines: array of string);
var
@ -1167,6 +1218,54 @@ begin
'']);
end;
procedure TTestRefactoring.TestRenameProgramName_Amp;
begin
Add([
'program test1;',
'{$mode objfpc}{$H+}',
'type TRed = word;',
'var c: test1 . TRed;',
'begin',
' test1.c:=&test1 . &c;',
'end.',
'']);
RenameSourceName('&End','end.pas');
CheckDiff(Code,[
'program &End;',
'{$mode objfpc}{$H+}',
'type TRed = word;',
'var c: &End . TRed;',
'begin',
' &End.c:=&End . &c;',
'end.',
'']);
end;
procedure TTestRefactoring.TestRenameProgramName_DottedPostfix;
begin
exit;
Add([
'program Foo.Bar;',
'{$mode objfpc}{$H+}',
'type TRed = word;',
'var c: foo . bar . TRed;',
'begin',
' foo.bar.c:=&foo . &bar . &c;',
'end.',
'']);
RenameSourceName('Foo.&End','foo.end.pas');
CheckDiff(Code,[
'program Foo.&End;',
'{$mode objfpc}{$H+}',
'type TRed = word;',
'var c: Foo . &End . TRed;',
'begin',
' Foo.&End.c:=Foo . &End . &c;',
'end.',
'']);
end;
initialization
RegisterTests([TTestRefactoring]);
end.