(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 VariablesSortedList<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 Parsingif ( 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 filescriptcode = File.ReadAllText( scriptfile, Encoding.UTF8 );
// Strip everything NOT PHPif ( 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 sensitiveif ( 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 Functionsif ( 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 Bracesif ( !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 sensitiveif ( 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 functionsUsage: 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 Textreturn -1;
} }}page last modified: 2025-10-11; loaded in 0.0112 seconds