(view source code of checkvarsphp.cs as plain text)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace RobvanderWoude
{
internal class CheckVarsPHP
{
static readonly string progver = "1.02";
static string scriptcode = String.Empty;
static int Main( string[] args )
{
#region Initialize Variables
SortedList<string, int> functions = new SortedList<string, int>( );
SortedList<string, int> variables = new SortedList<string, int>( );
bool showfuncs = true;
bool showvars = true;
bool unusedonly = false;
Regex regex;
string pattern;
string scriptext = String.Empty;
string scriptfile = String.Empty;
int columnwidth = 12;
int unusedfuncs = 0;
int unusedvars = 0;
#endregion Initialize Variables
#region Command Line Parsing
if ( args.Length == 0 )
{
return ShowHelp( );
}
foreach ( string arg in args )
{
if ( arg[0] == '/' )
{
if ( arg.ToUpper( ) == "/?" )
{
return ShowHelp( );
}
else if ( arg.ToUpper( ) == "/F" )
{
if ( !showvars )
{
return ShowHelp( "Duplicate command line switch /F" );
}
if ( !showfuncs )
{
return ShowHelp( "Use /F or /V or neither, but not both" );
}
showvars = false;
}
else if ( arg.ToUpper( ) == "/U" )
{
if ( unusedonly )
{
return ShowHelp( "Duplicate command line switch /U" );
}
unusedonly = true;
}
else if ( arg.ToUpper( ) == "/V" )
{
if ( !showfuncs )
{
return ShowHelp( "Duplicate command line switch /V" );
}
if ( !showvars )
{
return ShowHelp( "Use /F or /V or neither, but not both" );
}
showfuncs = false;
}
else
{
return ShowHelp( "Invalid command line switch \"{0}\"", arg );
}
}
else
{
if ( !String.IsNullOrWhiteSpace( scriptfile ) )
{
return ShowHelp( "Duplicate command line argument for PHP file" );
}
if ( !File.Exists( arg ) )
{
return ShowHelp( "Invalid file name or file not found: \"{0}\"", arg );
}
scriptext = Path.GetExtension( arg ).ToLower( );
if ( scriptext != ".php" )
{
return ShowHelp( "Invalid file type \"{0}\"", arg );
}
scriptfile = Path.GetFullPath( arg );
}
}
if ( String.IsNullOrWhiteSpace( scriptfile ) )
{
return ShowHelp( "Please specify a source file" );
}
#endregion Command Line Parsing
#region Read File
// Read the code from the file
scriptcode = File.ReadAllText( scriptfile, Encoding.UTF8 );
// Strip everything NOT PHP
if ( scriptcode.Contains( "<?php" ) )
{
string purephp = string.Empty;
pattern = @"<\?php[\w\W]*?\?>";
regex = new Regex( pattern );
if ( regex.IsMatch( scriptcode ) )
{
foreach ( Match match in regex.Matches( scriptcode ) )
{
purephp += match.Value + "\n\n";
}
}
scriptcode = purephp;
}
// Remove comment lines from the code (does NOT strip comments starting halfway on a line)
pattern = @"(^|\n|\r)[ \t]*//[^\n\r]+";
regex = new Regex( pattern );
scriptcode = regex.Replace( scriptcode, String.Empty );
// Remove comment blocks from the code (does NOT strip comments starting halfway on a line)
pattern = @"(^|\n|\r)[ \t]*/\*[\w\W]*\*/";
regex = new Regex( pattern );
scriptcode = regex.Replace( scriptcode, String.Empty );
#endregion Read File
#region List Functions
// Create a list of subroutines found in the code
// function names are NOT case sensitive
if ( showfuncs )
{
pattern = @"(?:^|\n|\r)[ \t]*(?:function)[ \t]+([A-Z_][^\s\(]+)\(";
regex = new Regex( pattern, RegexOptions.IgnoreCase );
if ( regex.IsMatch( scriptcode ) )
{
MatchCollection matches = regex.Matches( scriptcode );
if ( matches.Count > 0 )
{
foreach ( Match match in matches )
{
bool listed = false;
string func = match.Groups[1].Value;
foreach ( string key in functions.Keys )
{
if ( func.ToLower( ) == key.ToLower( ) )
{
listed = true;
}
}
if ( !listed )
{
functions[func] = 0;
columnwidth = Math.Max( func.Length, columnwidth );
}
}
}
}
}
#endregion List Functions
#region Check for Nested Functions
if ( showfuncs && functions.Count > 1 )
{
pattern = @"function[\t ]+([_A-Z]\w*)[\t ]*\([^\n\r\)]*\)([\w\W]*?)function[\t ]+[_A-Z]";
regex = new Regex( pattern, RegexOptions.IgnoreCase );
if ( regex.IsMatch( scriptcode ) )
{
foreach ( Match match in regex.Matches( scriptcode ) )
{
int errorcount = 0;
string funcname = match.Groups[1].Value;
string funccode = match.Groups[2].Value;
int curlybraces = 0;
foreach ( char chr in funccode.ToCharArray( ) )
{
if ( chr == '{' )
{
curlybraces++;
}
else if ( chr == '}' )
{
curlybraces--;
}
if ( curlybraces < 0 )
{
errorcount++;
}
}
if ( curlybraces != 0 )
{
errorcount++;
}
if ( errorcount > 0 )
{
RedLine( "Nested function or unterminated curly braces detected in function \"{0}\"\n\n", funcname );
}
}
}
}
#endregion Check for Nested Functions
#region Check for Unterminated Curly Braces
if ( !CheckCurlyBraces( ) )
{
RedLine( "Unterminated curly braces detected\n\n" );
}
#endregion Check for Unterminated Curly Braces
#region List Variables
// Create a list of variables found in the code and count their occurrence
// Variable names ARE case sensitive
if ( showvars )
{
pattern = @"\$[A-Z_][A-Z0-9_]*(\[([\$'\""]?[A-Z_][A-Z0-9_]*['\""]?)\])?";
regex = new Regex( pattern, RegexOptions.IgnoreCase );
if ( regex.IsMatch( scriptcode ) )
{
MatchCollection matches = regex.Matches( scriptcode );
if ( matches.Count > 0 )
{
foreach ( Match match in matches )
{
string varstring = match.ToString( );
if ( !variables.ContainsKey( varstring ) )
{
variables[varstring] = 1;
if ( varstring.Length > columnwidth )
{
columnwidth = varstring.Length;
}
}
else
{
variables[varstring] += 1;
}
}
}
}
}
#endregion List Variables
#region Count and Display Functions Usage
// Iterate through the list of subroutines and count the occurrences of its name
if ( showfuncs )
{
List<string> keys = new List<string>( functions.Keys );
foreach ( string func in keys )
{
pattern = string.Format( @"\b{0}\(", func );
regex = new Regex( pattern );
if ( regex.IsMatch( scriptcode ) )
{
functions[func] = regex.Matches( scriptcode ).Count - 1;
}
}
// Show the results
if ( unusedonly )
{
Console.WriteLine( "{0} Unused Function{1}{2}", unusedfuncs, ( unusedfuncs == 1 ? String.Empty : "s" ), ( unusedfuncs == 0 ? String.Empty : ":" ) );
Console.WriteLine( "{0}==============={1}{2}", new String( '=', unusedfuncs.ToString( ).Length ), ( unusedfuncs == 1 ? String.Empty : "=" ), ( unusedfuncs == 0 ? String.Empty : "=" ) );
}
else
{
Console.WriteLine( "{0,-" + columnwidth + "} Occurrences:", "Function:" );
Console.WriteLine( "{0,-" + columnwidth + "} ============", "=========" );
}
foreach ( string key in functions.Keys )
{
if ( functions[key] == 0 )
{
if ( unusedonly )
{
Console.WriteLine( key );
}
else
{
RedLine( string.Format( "{0,-" + columnwidth + "} {1}", key, functions[key] ) );
}
unusedfuncs += 1;
}
else if ( !unusedonly )
{
Console.WriteLine( "{0,-" + columnwidth + "} {1}", key, functions[key] );
}
}
Console.WriteLine( );
}
#endregion Count and Display Functions Usage
#region Count and Display Variables Usage
if ( showvars )
{
// Show the results
if ( unusedonly )
{
Console.WriteLine( "{0} Unused Variable{1}{2}", unusedvars, ( unusedvars == 1 ? String.Empty : "s" ), ( unusedvars == 0 ? String.Empty : ":" ) );
Console.WriteLine( "{0}================{1}{2}", new String( '=', unusedvars.ToString( ).Length ), ( unusedvars == 1 ? String.Empty : "=" ), ( unusedvars == 0 ? String.Empty : "=" ) );
}
else
{
Console.WriteLine( "{0,-" + columnwidth + "} Occurrences:", "Variable:" );
Console.WriteLine( "{0,-" + columnwidth + "} ============", "=========" );
}
foreach ( string key in variables.Keys )
{
if ( variables[key] == 1 && !IsSuperGlobal( key ) && key[0] != '$' )
{
if ( unusedonly )
{
Console.WriteLine( key );
}
else
{
RedLine( String.Format( "{0,-" + columnwidth + "} {1}", key, variables[key] ) );
}
unusedvars += 1;
}
else if ( !unusedonly )
{
Console.WriteLine( "{0,-" + columnwidth + "} {1}", key, variables[key] );
}
}
Console.WriteLine( );
}
#endregion Count and Display Variables Usage
int rc = 0;
if ( showfuncs )
{
rc += unusedfuncs;
}
if ( showvars )
{
rc += unusedvars;
}
return rc;
}
static bool CheckCurlyBraces( )
{
if ( scriptcode.Count( o => ( o == '{' ) ) != scriptcode.Count( c => ( c == '}' ) ) )
{
return false;
}
string testtext = Regex.Replace( scriptcode, @"[^\{\}]", "" );
int state = 0;
for ( int i = 0; i < testtext.Length; i++ )
{
if ( testtext[i] == '{' )
{
state++;
}
else
{
state--;
}
if ( state < 0 )
{
return false;
}
}
return true;
}
static bool IsSuperGlobal( string var )
{
string pattern = @"^\$(GLOBALS|_(SERVER|GET|POST|FILES|COOKIE|SESSION|REQUEST|ENV))(\[[^\]]+\])?$";
Regex regex = new Regex( pattern );
return regex.IsMatch( var.Trim( ) );
}
static void RedLine( string line, params object[] rlargs )
{
Console.ForegroundColor = ConsoleColor.Red;
if ( rlargs.Length > 0 )
{
Console.WriteLine( line, rlargs );
}
else
{
Console.WriteLine( line );
}
Console.ResetColor( );
}
static int ShowHelp( params string[] errmsg )
{
#region Error Message
if ( errmsg.Length > 0 )
{
List<string> errargs = new List<string>( errmsg );
errargs.RemoveAt( 0 );
Console.Error.WriteLine( );
Console.ForegroundColor = ConsoleColor.Red;
Console.Error.Write( "ERROR:\t" );
Console.ForegroundColor = ConsoleColor.White;
Console.Error.WriteLine( errmsg[0], errargs.ToArray( ) );
Console.ResetColor( );
}
#endregion Error Message
#region Help Text
/*
CheckVarsPHP.exe, Version 1.02
Check PHP code for unused variables and functions
Usage: CheckVarsPHP.exe "phpfile" [ /F | /V ] [ /U ]
Where: "phpfile" is the PHP file to be examined
/F tests Functions only
(default: functions as well as variables)
/V tests Variables only
(default: functions as well as variables)
/U list Unused functions and variables only
(default: list all functions and variables)
Notes: If functions are tested, a (limited) test for nested functions
and unterminated curly braces is performed as well.
In PHP variables are not declared, so a single occurrence of a
variable means it is not used, unless it is a superglobal which
may be declared elsewhere. Keep in mind though, that this program
does not take into account the variables' scope, so a variable
name used once in two functions may in fact be unused but will
escape detection by this program.
The program's return code equals the number of unused functions
and/or variables, or -1 in case of (command line) errors.
Written by Rob van der Woude
https://www.robvanderwoude.com
*/
#endregion Help Text
#region Display Help Text
Console.Error.WriteLine( );
Console.Error.WriteLine( "CheckVarsPHP.exe, Version {0}", progver );
Console.Error.WriteLine( "Check PHP code for unused variables and functions" );
Console.Error.WriteLine( );
Console.Error.Write( "Usage: " );
Console.ForegroundColor = ConsoleColor.White;
Console.Error.WriteLine( "CheckVarsPHP.exe \"phpfile\" [ /F | /V ] [ /U ]" );
Console.ResetColor( );
Console.Error.WriteLine( );
Console.Error.Write( "Where: " );
Console.ForegroundColor = ConsoleColor.White;
Console.Error.Write( "\"phpfile\"" );
Console.ResetColor( );
Console.Error.WriteLine( " is the PHP file to be examined" );
Console.ForegroundColor = ConsoleColor.White;
Console.Error.Write( " /F" );
Console.ResetColor( );
Console.Error.Write( " tests " );
Console.ForegroundColor = ConsoleColor.White;
Console.Error.Write( "F" );
Console.ResetColor( );
Console.Error.WriteLine( "unctions only" );
Console.Error.WriteLine( " (default: functions as well as variables)" );
Console.ForegroundColor = ConsoleColor.White;
Console.Error.Write( " /V" );
Console.ResetColor( );
Console.Error.Write( " tests " );
Console.ForegroundColor = ConsoleColor.White;
Console.Error.Write( "V" );
Console.ResetColor( );
Console.Error.WriteLine( "ariables only" );
Console.Error.WriteLine( " (default: functions as well as variables)" );
Console.ForegroundColor = ConsoleColor.White;
Console.Error.Write( " /U" );
Console.ResetColor( );
Console.Error.Write( " list " );
Console.ForegroundColor = ConsoleColor.White;
Console.Error.Write( "U" );
Console.ResetColor( );
Console.Error.WriteLine( "nused functions and variables only" );
Console.Error.WriteLine( " (default: list all functions and variables)" );
Console.Error.WriteLine( );
Console.Error.WriteLine( "Notes: If functions are tested, a (limited) test for nested functions" );
Console.Error.WriteLine( " and unterminated curly braces is performed as well." );
Console.Error.WriteLine( " In PHP variables are not declared, so a single occurrence of a" );
Console.Error.WriteLine( " variable means it is not used, unless it is a superglobal which" );
Console.Error.WriteLine( " may be declared elsewhere. Keep in mind though, that this program" );
Console.Error.WriteLine( " does not take into account the variables' scope, so a variable" );
Console.Error.WriteLine( " name used once in two functions may in fact be unused but will" );
Console.Error.WriteLine( " escape detection by this program." );
Console.Error.WriteLine( " The program's return code equals the number of unused functions" );
Console.Error.WriteLine( " and/or variables, or -1 in case of (command line) errors." );
Console.Error.WriteLine( );
Console.Error.WriteLine( "Written by Rob van der Woude" );
Console.Error.WriteLine( "https://www.robvanderwoude.com" );
#endregion Display Help Text
return -1;
}
}
}
page last modified: 2024-04-16; loaded in 0.0085 seconds