views:

872

answers:

4

Is there any tool (preferably freeware) that can analyze Pascal/Delphi syntax and automatically remove unused vars?

In my case, I'm working with a very large Delphi code base, and the compiler hints report over one thousand cases of "Variable 'x' is declared but never used".

I would take hours to remove them by hand, and I might make errors, but the right tool should be able to do that safely and automatically.

I searched online but didn't find one... does anybody here know of such a tool?

Thanks...

Mark Brarford

+4  A: 

Hello! I see your point and totally agree that such a tool would be useful when working with legacy code. Unfortunately I don't know of any existing tool (I should add freeware tool here, static analyis tools should of course be able to do it easily, but I don't know of any free static code analysis tool) that is capable doing that.

But I guess you could easily write such a tool in a few minutes. A small GUI with a memo and a button should be enough. Then just copy the compiler hints to the memo and press the button. The tool then parses every line. It can easily check if the line contains the hint you are looking for and each such line has the same structure, so parsing should be relatively easy. It can then extract the file name and the line number, open the file and remove the variable declaration. This can be a bit tricky in case of multiple variable declarations in one line but I think it is doable.

I don't know if that's too much effort for you compared to the task of removing all variable declarations yourself. But I would like to see such a tool, so feel free to write it :)

Hope that helped at least a bit.

Okay, I really can't see any problems here. For the parsing part:

function ParseHint (const HintText : String; out HintInfo : THintInfo) : Boolean;
var
  I, J     : Integer;
  HintName : String;
begin
  Result := False;
  for I := 1 to Length (HintText) do
  begin
    if (HintText [I] = '(') then
    begin
      J := I + 1;
      while (HintText [J] <> ')') do Inc (J);
      HintInfo.LineNumber := StrToInt (MidStr (HintText, I+1, J-(I+1)));
      HintInfo.SourceFile := MidStr (HintText, 12, I-12);
      HintName := MidStr (HintText, J+3, 5);
      if (HintName <> 'H2164') then Exit (False);
    end;
    if (HintText [I] = '''') then
    begin
      J := I + 1;
      while (HintText [J] <> '''') do Inc (J);
      HintInfo.VarName := MidStr (HintText, I+1, J-(I+1));
      Exit (True);
    end;
  end;
end;

Well, reading the source file should be easy, so the only remaing part is removing the variable from its line of declaration. We can simply search for occurences of HintInfo.VarName in the line and check if the character before and after the occurence are no letters but only ' ', ',' or ':'. If this is the case we can just remove it. This covers all these cases:

var UnusedVar : Integer;
var
  UnusedVar,
  AnotherVar : Integer;
var
  UnusedVar, AnotherVar : Integer;

Tell me if I'm wrong or if I forgot any cases but I think this would work and woulde solve the problem of removing unused variables from delphi source files using the compiler-generated hints.

Smasher
A few minutes will be a very big misconception, because this is not a trivial problem.
Gamecat
Well, perhaps you could point out what you think the problems would be? Just to be clear: I'm not talking about finding unused variables but interpreting the Delphi compiler hints.
Smasher
Ok, interpreting hints shouldn't be that hard. But that is not the main part of the problem. Please have a look at my answer below:
Gamecat
Your approach is fine and as I said, I would be happy to see such a tool, but I can't see why my solution does not solve "the main part of the problem". Why parse the source code and not use the compiler output? It's not that general but it's a lot easier and it solves the problem at hand.
Smasher
Is solves only part of the problem. See also the remark of gabr on my answer.
Gamecat
Smasher, you answer is great and it does solve the main part of the problem, at least for me. I'm seriously considering writing the tool... it should ask confirmation before removing vars, allow scrolling to see if an {$ifdef} is there... and maybe a few other things, anyway great answer, thanks!
Mark Bradford
+2  A: 

If there is no such tool and you have some patience, I'm building a Delphi analysis and repair tool. And removal of unused symbols is on the list. It is a low proirity project so I can't give an estimate on when its ready.

Just to explain why this isn't a trivial task:

  1. read the source
  2. create a model that contains enough information for each symbol usage.
  3. mark all unused symbols.
  4. rewrite the source without the unneeded symbols.

Task 1 and 2 are hard (luckily for me those are already done). The Delphi language is quite complex. And you need all language elements to be able to recreate the source.

Task 3 is simple. Just flag all symbols that are unused. But beware of symbols in the interface section of a unit. They are possibly not used but needed later (or by some other project).

Task 4 depends.

Aproach A uses an intermediate format (for example a stringlist), you can then use the model to find the declaration of each unused symbol (bottom up else you possibly change the line numbers). You delete all not needed. And don't forget to delete the var keyword if it's the last var in the list!

Aproach B completely rewrites the source file. In this case, you must preserve all comments which is not really fun to do (but my model needs that too). You just removes the unused symbols from the model and rewrite it. Always be sure to create a backup, because this can end up in disaster.

Gamecat
AND there's a possibility that multiple vars are declared in one line which again complicates the removal. Or the var declaration could be split into two lines ('a' in first, ': integer' in second).I do agree - it is hard to write a stable solution to the problem.
gabr
Oh, and if you need a beta tester ...
gabr
AND some parts of the code could depend on a compiler directive ({$ifdef ... }) but the corresponding variables may be not not, so that the compiler could detect some variables as unused although they are used with oher compiler directives
Name
@gabr thanx, I keep you in mind ;-).
Gamecat
I look forward to check your software when ready... will it be a standalone app or something that integrates into the IDE?
Mark Bradford
+1  A: 

Are you sure the variables shouldn't be used? I know the compiler figures out that they aren't used right now, but is that correct, perhaps many of these should be used, but a developer used x2 instead of x1 for instance, copy and paste?

While you might want to remove all those variables unscrutinized, I wouldn't be so hasty, they might be indications of bugs in your code that you'd like to fix.

Example:

procedure PlotPixelAtCenter(rect: Rectangle)
var
    x, y: Integer;
begin
    x := (rect.Left + rect.Right) div 2;
    x := (rect.Top + rect.Bottom) div 2; // <-- bug here, should be y :=
    PlotPixel(x, y);
end;

In this example you'll get an error about an unused variable, but this is a bug lurking. Of course, in this example the bug should be easy to find since the plotting will probably be off, but other similar bugs might be harder to spot.

Lasse V. Karlsen
> I know the compiler figures out that they aren't used right now, but is that correct,Any example?
inzKulozik
Good point. I wouldn't want a tool to just blindly remove all vars, but to step through each one, like a global search and replace with a confirmation before each action, so I can have a look and see if there's a {$ifdef} involved, or something like you showed above.
Mark Bradford
+1  A: 

The solution is simple, but requires those hours to be sure you don't make a mistake. First off, You can use Alt-F8 to step through each report one after the other (and Alt-F7 to step backwards). That makes locating them very easy. The cursor is put on the line for you. Then just press the '/' key twice to comment it out. Don't delete it, comment it. This way if you make a mistake you haven't lost any information. The presence of the variable and its data type is still recorded. You can tidy it up later at some point.

One caveat to all this: Conditional compilation may render some variables unused when built different ways. If this happens, then just uncomment the variable again, and put the condition around the declaration too.

mj2008
thanks for the point about checking for conditional defines, it's something that will definitely happen from time to time.
Mark Bradford
Thanks for the Alt-F8 Alt-F7 shortcuts. That will save me some time. +1
lkessler