IDE+codetools: started heuristic to find unused units

git-svn-id: trunk@19281 -
This commit is contained in:
mattias 2009-04-08 23:45:15 +00:00
parent b96aabcd76
commit 26ef5cd496
14 changed files with 399 additions and 4 deletions

3
.gitattributes vendored
View File

@ -2824,6 +2824,9 @@ ide/unitdependencies.pas svneol=native#text/pascal
ide/unitinfodlg.lfm svneol=native#text/plain
ide/unitinfodlg.lrs svneol=native#text/plain
ide/unitinfodlg.pp svneol=native#text/pascal
ide/unusedunitsdlg.lfm svneol=native#text/plain
ide/unusedunitsdlg.lrs svneol=native#text/plain
ide/unusedunitsdlg.pas svneol=native#text/plain
ide/version.inc svneol=native#text/plain
ide/versioninfoadditionalinfo.lfm svneol=native#text/plain
ide/versioninfoadditionalinfo.lrs svneol=native#text/plain

View File

@ -488,6 +488,7 @@ type
out AllRemoved: boolean;
const Attr: TProcHeadAttributes;
out RemovedProcHeads: TStrings): boolean;
function FindUnusedUnits(Code: TCodeBuffer; Units: TStrings): boolean;
// custom class completion
function InitClassCompletion(Code: TCodeBuffer;
@ -3480,6 +3481,21 @@ begin
end;
end;
function TCodeToolManager.FindUnusedUnits(Code: TCodeBuffer; Units: TStrings
): boolean;
begin
{$IFDEF CTDEBUG}
DebugLn('TCodeToolManager.FindEmptyMethods A ',Code.Filename);
{$ENDIF}
Result:=false;
if not InitCurCodeTool(Code) then exit;
try
Result:=FCurCodeTool.FindUnusedUnits(Units);
except
on e: Exception do Result:=HandleException(e);
end;
end;
function TCodeToolManager.InitClassCompletion(Code: TCodeBuffer;
const UpperClassName: string; out CodeTool: TCodeTool): boolean;
begin

View File

@ -1752,7 +1752,7 @@ begin
UnitNamePos.EndPos-UnitNamePos.StartPos);
if UnitInFilePos.StartPos>=1 then begin
UnitInFilename:=copy(Src,UnitInFilePos.StartPos+1,
UnitInFilePos.EndPos-UnitInFilePos.StartPos-2)
UnitInFilePos.EndPos-UnitInFilePos.StartPos-2);
end else
UnitInFilename:='';
NewPos.Code:=FindUnitSource(UnitName,UnitInFilename,true);

View File

@ -1787,7 +1787,7 @@ begin
then
RaiseException('[TPascalParserTool.MoveCursorToUsesStart] '
+'internal error: invalid UsesNode');
// search backwards through the uses section
// search through the uses section
MoveCursorToCleanPos(UsesNode.StartPos);
ReadNextAtom;
if (not UpAtomIs('USES')) and (not UpAtomIs('CONTAINS')) then

View File

@ -118,6 +118,7 @@ type
SourceChangeCache: TSourceChangeCache): boolean;
function CommentUnitsInUsesSections(MissingUnits: TStrings;
SourceChangeCache: TSourceChangeCache): boolean;
function FindUnusedUnits(Units: TStrings): boolean;
// lazarus resources
function FindNextIncludeInInitialization(
@ -1327,6 +1328,198 @@ begin
Result:=true;
end;
function TStandardCodeTool.FindUnusedUnits(Units: TStrings): boolean;
// returns a list of unitname=flags
// flags are a comma separated list of words:
// 'implementation': unit is in implementation uses section
// 'used': an identifier of the interface is used
// 'code': unit has non empty initialization/finalization section
var
Identifiers: TAVLTree;// all identifiers used in this unit
procedure RaiseUsesExpected;
begin
RaiseExceptionFmt(ctsStrExpectedButAtomFound,['"uses"',GetAtom]);
end;
procedure RaiseStrConstExpected;
begin
RaiseExceptionFmt(ctsStrExpectedButAtomFound,[ctsStringConstant,GetAtom]);
end;
function IsUnitAlreadyChecked(const AnUnitName: string): boolean;
var
i: Integer;
begin
for i:=0 to Units.Count-1 do
if SysUtils.CompareText(Units.Names[i],AnUnitName)=0 then exit(true);
Result:=false;
end;
procedure GatherIdentifiers;
var
Node: TCodeTreeNode;
Identifier: PChar;
begin
if Identifiers<>nil then exit;
Identifiers:=TAVLTree.Create(@CompareIdentifierPtrs);
Node:=Tree.Root;
while Node<>nil do begin
if (Node.Desc in [ctnBeginBlock,ctnAsmBlock])
or ((Node.FirstChild=nil)
and (Node.Desc in [ctnIdentifier,ctnRangedArrayType,ctnOpenArrayType,
ctnOfConstType,ctnRecordVariant,ctnProcedureType,ctnRangeType,
ctnTypeType,ctnFileType,ctnPointerType,ctnClassOfType,
ctnSpecializeParams,ctnGenericParameter,ctnConstant]))
then begin
MoveCursorToNodeStart(Node);
repeat
ReadNextAtom;
if CurPos.StartPos>=Node.EndPos then break;
if IsIdentStartChar[Src[CurPos.StartPos]] then begin
Identifier:=@Src[CurPos.StartPos];
if Identifiers.Find(Identifier)=nil then begin
DebugLn(['GatherIdentifiers ',GetIdentifier(Identifier)]);
Identifiers.Add(Identifier);
end;
end;
until false;
Node:=Node.NextSkipChilds;
end else
Node:=Node.Next;
end;
end;
function InterfaceIsUsed(Tool: TFindDeclarationTool;
IntfNode: TCodeTreeNode): boolean;
function IsIdentifierUsed(StartPos: integer): boolean;
begin
Result:=Identifiers.Find(@Tool.Src[StartPos])<>nil;
end;
var
Node: TCodeTreeNode;
begin
Result:=true;
Node:=IntfNode.FirstChild;
while Node<>nil do begin
case Node.Desc of
ctnEnumIdentifier:
if IsIdentifierUsed(Node.StartPos) then exit;
end;
Node:=Node.Next;
end;
Result:=false;
end;
procedure CheckUnit(Tool: TFindDeclarationTool;
out HasCode, UseInterface: boolean);
var
Node: TCodeTreeNode;
Identifier: String;
begin
GatherIdentifiers;
HasCode:=false;
UseInterface:=false;
// parse used unit
Tool.BuildTree(false);
Node:=Tool.Tree.Root;
while (Node<>nil) do begin
case Node.Desc of
ctnUnit,ctnPackage,ctnLibrary:
begin
Identifier:=Tool.ExtractSourceName;
if Identifiers.Find(PChar(Identifier))<>nil then
UseInterface:=true;
end;
ctnInterface:
if not UseInterface then
UseInterface:=InterfaceIsUsed(Tool,Node);
ctnInitialization,ctnFinalization,ctnBeginBlock:
begin
HasCode:=true;
break;
end;
end;
Node:=Node.NextBrother;
end;
end;
procedure CheckUsesSection(UsesNode: TCodeTreeNode; InImplementation: boolean);
var
UnitNamePos: TAtomPosition;
UnitInFilePos: TAtomPosition;
UnitName: String;
UnitInFilename: String;
Tool: TFindDeclarationTool;
HasCode: boolean;
UseInterface: boolean;
Flags: String;
begin
HasCode:=false;
UseInterface:=false;
if UsesNode=nil then exit;
MoveCursorToNodeStart(UsesNode);
ReadNextAtom;
if not UpAtomIs('USES') then
RaiseUsesExpected;
repeat
ReadNextAtom; // read name
if AtomIsChar(';') then break;
AtomIsIdentifier(true);
UnitNamePos:=CurPos;
ReadNextAtom;
if UpAtomIs('IN') then begin
ReadNextAtom;
if not AtomIsStringConstant then RaiseStrConstExpected;
UnitInFilePos:=CurPos;
ReadNextAtom;
end else
UnitInFilePos.StartPos:=-1;
UnitName:=copy(Src,UnitNamePos.StartPos,
UnitNamePos.EndPos-UnitNamePos.StartPos);
if not IsUnitAlreadyChecked(UnitName) then begin
if UnitInFilePos.StartPos>=1 then begin
UnitInFilename:=copy(Src,UnitInFilePos.StartPos+1,
UnitInFilePos.EndPos-UnitInFilePos.StartPos-2);
end else
UnitInFilename:='';
// try to load the used unit
DebugLn(['CheckUsesSection ',UnitName,UnitInFilename]);
Tool:=FindCodeToolForUsedUnit(UnitName,UnitInFilename,true);
// parse the used unit
CheckUnit(Tool,HasCode,UseInterface);
Flags:='';
if InImplementation then
Flags:=Flags+',implementation';
if HasCode then
Flags:=Flags+',code';
if UseInterface then
Flags:=Flags+',used';
DebugLn(['CheckUsesSection ',UnitName,'=',Flags]);
Units.Add(UnitName+'='+Flags);
end;
if AtomIsChar(';') then break;
if not AtomIsChar(',') then
RaiseExceptionFmt(ctsStrExpectedButAtomFound,[';',GetAtom])
until (CurPos.StartPos>SrcLen);
end;
begin
Result:=false;
DebugLn(['TStandardCodeTool.FindUnusedUnits ']);
BuildTree(false);
Identifiers:=nil;
try
CheckUsesSection(FindMainUsesSection,false);
CheckUsesSection(FindImplementationUsesSection,true);
finally
Identifiers.Free;
end;
Result:=true;
end;
function TStandardCodeTool.FindNextIncludeInInitialization(
var LinkIndex: integer): TCodeBuffer;
// LinkIndex < 0 -> search first

View File

@ -94,6 +94,7 @@ var
ListOfPCodeXYPosition: TFPList;
AllEmpty: boolean;
begin
Result:=mrCancel;
ListOfPCodeXYPosition:=TFPList.Create;
try
// init codetools

View File

@ -403,6 +403,7 @@ begin
ecGotoIncludeDirective: SetResult(VK_UNKNOWN,[],VK_UNKNOWN,[]);
ecShowAbstractMethods: SetResult(VK_UNKNOWN,[],VK_UNKNOWN,[]);
ecRemoveEmptyMethods: SetResult(VK_UNKNOWN,[],VK_UNKNOWN,[]);
ecRemoveUnusedUnits: SetResult(VK_UNKNOWN,[],VK_UNKNOWN,[]);
// source notebook
ecNextEditor: SetResult(VK_TAB, [ssCtrl], VK_UNKNOWN, []);
@ -1760,6 +1761,7 @@ begin
ecFindBlockStart : Result:= srkmecFindBlockStart;
ecShowAbstractMethods : Result:= srkmecShowAbstractMethods;
ecRemoveEmptyMethods : Result:= srkmecRemoveEmptyMethods;
ecRemoveUnusedUnits : Result:= srkmecRemoveEmptyMethods;
// project (menu string resource)
ecNewProject : Result:= lisMenuNewProject;
@ -2220,6 +2222,8 @@ begin
ecShowAbstractMethods);
AddDefault(C, 'Remove empty methods', srkmecRemoveEmptyMethods,
ecRemoveEmptyMethods);
AddDefault(C, 'Remove unused units', srkmecRemoveUnusedUnits,
ecRemoveUnusedUnits);
// source notebook - without menu items in the IDE bar
C:=Categories[AddCategory('SourceNotebook',srkmCatSrcNoteBook,

View File

@ -1785,6 +1785,7 @@ resourcestring
lisUEDoNotSho = 'Do not show this message again.';
uemInsertTodo = 'Insert Todo';
lisCodeHelpShowEmptyMethods = 'Show empty methods';
lisCodeHelpShowUnusedUnits = 'Show unused units';
uemHighlighter = 'Highlighter';
uemEncoding = 'Encoding';
@ -2076,6 +2077,7 @@ resourcestring
srkmecFindBlockStart = 'Find block start';
srkmecShowAbstractMethods = 'Show abstract methods';
srkmecRemoveEmptyMethods = 'Remove empty methods';
srkmecRemoveUnusedUnits = 'Remove unused units';
// run menu
srkmecBuild = 'build program/project';

View File

@ -133,8 +133,9 @@ uses
ProcessList, InitialSetupDlgs, NewDialog, MakeResStrDlg, ToDoList,
DialogProcs, FindReplaceDialog, FindInFilesDlg, CodeExplorer, BuildFileDlg,
ProcedureList, ExtractProcDlg, FindRenameIdentifier, AbstractsMethodsDlg,
EmptyMethodsDlg, CleanDirDlg, CodeContextForm, AboutFrm, BuildManager,
EmptyMethodsDlg, UnusedUnitsDlg, CleanDirDlg, CodeContextForm, AboutFrm,
CompatibilityRestrictions, RestrictionBrowser, ProjectWizardDlg, IDECmdLine,
BuildManager,
// main ide
MainBar, MainIntf, MainBase;
@ -864,6 +865,7 @@ type
NewFilename, NewUnitName: string): TModalResult;
function DoShowAbstractMethods: TModalResult;
function DoRemoveEmptyMethods: TModalResult;
function DoRemoveUnusedUnits: TModalResult;
function DoInitIdentCompletion(JumpToError: boolean): boolean;
function DoShowCodeContext(JumpToError: boolean): boolean;
procedure DoCompleteCodeAtCursor;
@ -2771,6 +2773,9 @@ begin
ecRemoveEmptyMethods:
DoRemoveEmptyMethods;
ecRemoveUnusedUnits:
DoRemoveUnusedUnits;
ecFindBlockOtherEnd:
DoGoToPascalBlockOtherEnd;
@ -12713,6 +12718,11 @@ begin
Result:=ShowEmptyMethodsDialog;
end;
function TMainIDE.DoRemoveUnusedUnits: TModalResult;
begin
Result:=ShowUnusedUnitsDialog;
end;
{-------------------------------------------------------------------------------
function TMainIDE.DoInitIdentCompletion(JumpToError: boolean): boolean;
-------------------------------------------------------------------------------}

View File

@ -433,6 +433,7 @@ type
procedure RenameIdentifierMenuItemClick(Sender: TObject);
procedure ShowAbstractMethodsMenuItemClick(Sender: TObject);
procedure ShowEmptyMethodsMenuItemClick(Sender: TObject);
procedure ShowUnusedUnitsMenuItemClick(Sender: TObject);
procedure RunToClicked(Sender: TObject);
procedure ViewCallStackClick(Sender: TObject);
procedure AddWatchAtCursor(Sender: TObject);
@ -857,6 +858,7 @@ var
SrcEditMenuInvertAssignment: TIDEMenuCommand;
SrcEditMenuShowAbstractMethods: TIDEMenuCommand;
SrcEditMenuShowEmptyMethods: TIDEMenuCommand;
SrcEditMenuShowUnusedUnits: TIDEMenuCommand;
SrcEditMenuInsertTodo: TIDEMenuCommand;
SrcEditMenuMoveEditorLeft: TIDEMenuCommand;
SrcEditMenuMoveEditorRight: TIDEMenuCommand;
@ -1015,9 +1017,14 @@ begin
'ShowAbstractMethods',srkmecShowAbstractMethods);
SrcEditMenuShowEmptyMethods:=RegisterIDEMenuCommand(AParent,
'ShowEmptyMethods', lisCodeHelpShowEmptyMethods);
SrcEditMenuShowUnusedUnits:=RegisterIDEMenuCommand(AParent,
'ShowUnusedUnits', lisCodeHelpShowUnusedUnits);
{$IFNDEF EnableUnusedUnits}
SrcEditMenuShowUnusedUnits.Visible:=false;
{$ENDIF}
SrcEditMenuInsertTodo:=RegisterIDEMenuCommand(SourceEditorMenuRoot,
'InsertTodo',uemInsertTodo, nil, nil, nil, 'item_todo');
'InsertTodo',uemInsertTodo, nil, nil, nil, 'item_todo');
// register the Flags section
SrcEditSubMenuFlags:=RegisterIDESubMenu(SourceEditorMenuRoot,
@ -4383,6 +4390,7 @@ begin
SrcEditMenuRenameIdentifier.OnClick:=@RenameIdentifierMenuItemClick;
SrcEditMenuShowAbstractMethods.OnClick:=@ShowAbstractMethodsMenuItemClick;
SrcEditMenuShowEmptyMethods.OnClick:=@ShowEmptyMethodsMenuItemClick;
SrcEditMenuShowUnusedUnits.OnClick:=@ShowUnusedUnitsMenuItemClick;
SrcEditMenuReadOnly.OnClick:=@ReadOnlyClicked;
SrcEditMenuShowLineNumbers.OnClick:=@ToggleLineNumbersClicked;
@ -5627,6 +5635,11 @@ begin
MainIDEInterface.DoCommand(ecRemoveEmptyMethods);
end;
procedure TSourceNotebook.ShowUnusedUnitsMenuItemClick(Sender: TObject);
begin
MainIDEInterface.DoCommand(ecRemoveUnusedUnits);
end;
procedure TSourceNotebook.RunToClicked(Sender: TObject);
var
ASrcEdit: TSourceEditor;

28
ide/unusedunitsdlg.lfm Normal file
View File

@ -0,0 +1,28 @@
object UnusedUnitsDialog: TUnusedUnitsDialog
Left = 375
Height = 365
Top = 236
Width = 340
Caption = 'UnusedUnitsDialog'
ClientHeight = 365
ClientWidth = 340
OnCreate = FormCreate
LCLVersion = '0.9.27'
object ButtonPanel1: TButtonPanel
Left = 6
Height = 44
Top = 315
Width = 328
TabOrder = 0
ShowButtons = [pbOK, pbCancel]
end
object UnitsTreeView: TTreeView
Left = 0
Height = 309
Top = 0
Width = 340
Align = alClient
DefaultItemHeight = 19
TabOrder = 1
end
end

12
ide/unusedunitsdlg.lrs Normal file
View File

@ -0,0 +1,12 @@
{ This is an automatically generated lazarus resource file }
LazarusResources.Add('TUnusedUnitsDialog','FORMDATA',[
'TPF0'#18'TUnusedUnitsDialog'#17'UnusedUnitsDialog'#4'Left'#3'w'#1#6'Height'#3
+'m'#1#3'Top'#3#236#0#5'Width'#3'T'#1#7'Caption'#6#17'UnusedUnitsDialog'#12'C'
+'lientHeight'#3'm'#1#11'ClientWidth'#3'T'#1#8'OnCreate'#7#10'FormCreate'#10
+'LCLVersion'#6#6'0.9.27'#0#12'TButtonPanel'#12'ButtonPanel1'#4'Left'#2#6#6'H'
+'eight'#2','#3'Top'#3';'#1#5'Width'#3'H'#1#8'TabOrder'#2#0#11'ShowButtons'#11
+#4'pbOK'#8'pbCancel'#0#0#0#9'TTreeView'#13'UnitsTreeView'#4'Left'#2#0#6'Heig'
+'ht'#3'5'#1#3'Top'#2#0#5'Width'#3'T'#1#5'Align'#7#8'alClient'#17'DefaultItem'
+'Height'#2#19#8'TabOrder'#2#1#0#0#0
]);

112
ide/unusedunitsdlg.pas Normal file
View File

@ -0,0 +1,112 @@
{
***************************************************************************
* *
* This source is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This code is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* A copy of the GNU General Public License is available on the World *
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
* obtain it by writing to the Free Software Foundation, *
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
A dialog showing the unused units of the current unit
(at cursor in source editor).
With the ability to remove them automatically.
}
unit UnusedUnitsDlg;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, LCLProc,FileUtil, LResources, Forms, Controls, Graphics,
Dialogs, ButtonPanel, ComCtrls,
SrcEditorIntf, LazIDEIntf,
CodeCache, CodeToolManager,
LazarusIDEStrConsts;
type
{ TUnusedUnitsDialog }
TUnusedUnitsDialog = class(TForm)
ButtonPanel1: TButtonPanel;
UnitsTreeView: TTreeView;
procedure FormCreate(Sender: TObject);
procedure OkClick(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
UnusedUnitsDialog: TUnusedUnitsDialog;
function ShowUnusedUnitsDialog: TModalResult;
implementation
function ShowUnusedUnitsDialog: TModalResult;
var
SrcEdit: TSourceEditorInterface;
Code: TCodeBuffer;
Units: TStringList;
begin
Result:=mrOk;
if not LazarusIDE.BeginCodeTools then exit;
// get cursor position
SrcEdit:=SourceEditorWindow.ActiveEditor;
if SrcEdit=nil then exit;
Code:=TCodeBuffer(SrcEdit.CodeToolsBuffer);
if Code=nil then exit;
Units:=TStringList.Create;
try
if not CodeToolBoss.FindUnusedUnits(Code,Units) then begin
DebugLn(['ShowUnusedUnitsDialog CodeToolBoss.FindUnusedUnits failed']);
LazarusIDE.DoJumpToCodeToolBossError;
exit(mrCancel);
end;
finally
Units.Free;
end;
end;
{ TUnusedUnitsDialog }
procedure TUnusedUnitsDialog.FormCreate(Sender: TObject);
begin
Caption:='Unused units';
ButtonPanel1.OKButton.Caption:='Remove selected units';
ButtonPanel1.OKButton.OnClick:=@OkClick;
ButtonPanel1.CancelButton.Caption:='Cancel';
end;
procedure TUnusedUnitsDialog.OkClick(Sender: TObject);
begin
end;
initialization
{$I unusedunitsdlg.lrs}
end.

View File

@ -139,6 +139,7 @@ const
ecShowCodeContext = ecFirstLazarus + 118;
ecShowAbstractMethods = ecFirstLazarus + 119;
ecRemoveEmptyMethods = ecFirstLazarus + 120;
ecRemoveUnusedUnits = ecFirstLazarus + 121;
// file menu
ecNew = ecFirstLazarus + 201;