//-------------------------------------------------------------> // ReportUnusedLocals.pkg // R.Worsley 3/09/2008 // Locate any unused local variables // Conditions: // Variables declared in the method line are not checked // Read notes below on StringTokenizer // Will work with the old "Local" as a descriptor // Variables can be declared anywhere in the method // // If used as a class in an another application, there are // a couple of properties that can be set to automate the // input and output file names. See the property descriptions // in class cReportUnusedLocals // //------------------------------------------------------------> // 04/08/2008 RLW - Make into a class so the process can be // incorporated into other applications // 04/12/2008 RLW - Change read/write registry process // 04/12/2008 RLW - Fix so that wrapped variables on the function // or procedure line won't show odd results // 06/22/2008 RLW - Add missing Seq_Release_Channel calls // 06/30/2018 Nils - Changed to use registry functions of the cApplication class. // 10/26/2108 Nils - Rewrote to use a struct instead of multi-dimensional array, // and to actually remove unused local variables (!), not just create a report. // 11/03/2108 Nils - Rewrote most of the logic to make it more robust. Removed the StringTokenizer // code to instead use the build in StrSplitToArray function. //-------------------------------------------------------------> Use cApplication.pkg Use Batchdd.pkg Use vWin32fh.pkg Struct tUnusedLocals Boolean bInUse String VarName String MethodName Integer Line# End_Struct Define CS_UnusedVariablesLogFile For "UnusedVariables.txt" #IFNDEF CS_Comment Define CS_Comment For ("/"+"/") // Also defined in SourceCode.inc #ENDIF Define CS_UnusedVariables For "UnusedVariables" Define CS_LastDirectory For "LastDirectory" Define CS_NoOfErrorFiles For "NoOfErrorFiles" // Name all variable types to be used here; Define CS_VarTypes For "STRING;INTEGER;NUMBER;DATE;DATETIME;BOOLEAN;HANDLE;REAL;VARIANT;TIMESPAN;ROWID;DECIMAL;BIGINT;REAL;UCHAR;ADDRESS;CHAR;CURRENCY;DWORD;FLOAT;POINTER;SHORT;TIME;UBIGINT;USHORT" // careful, cannot end on a semicolumn as you will introduce variable type equal to "" Define CS_VarArrayTypes For "STRING[];INTEGER[];NUMBER[];DATE[];DATETIME[];BOOLEAN[];HANDLE[];REAL[];VARIANT[];TIMESPAN[];ROWID[];DECIMAL[];BIGINT[];REAL[];UCHAR[];ADDRESS[];CHAR[];CURRENCY[];DWORD[];FLOAT[];POINTER[];SHORT[];TIME[];UBIGINT[];USHORT[];STRING[][];INTEGER[][];NUMBER[][];DATE[][];DATETIME[][];BOOLEAN[][];HANDLE[][];REAL[][];VARIANT[][];TIMESPAN[][];ROWID[][];DECIMAL[][];BIGINT[][];REAL[][];UCHAR[][];ADDRESS[][];CHAR[][];CURRENCY[][];DWORD[][];FLOAT[][];POINTER[][];SHORT[][];TIME[][];UBIGINT[][];USHORT[][]" Define CS_ValidLeftCharacters for ("()[];,&+-*<>= " + Character(9)) // Character 9=tab char //"()[];,.&+-*<>= " Class cRemoveUnusedLocals is a BusinessProcess Procedure Construct_Object String[] asLocalVariableTypes asLocalVariableArrayTypes Forward Send Construct_Object Property String[] pasLocalVariableTypes Get StrSplitToArray CS_VarTypes ";" to asLocalVariableTypes Set pasLocalVariableTypes to asLocalVariableTypes Property String[] pasLocalVariableArrayTypes Get StrSplitToArray CS_VarArrayTypes ";" to asLocalVariableArrayTypes Set pasLocalVariableArrayTypes to asLocalVariableArrayTypes Property String[] pasSourceFile Property Integer piNoOfUnusedLocalVariables 0 Set Status_Panel_State to False End_Procedure // For declarations like "string [] saAddress" Procedure NormalizeArrayNotation String ByRef sLine While (Pos(" [",sLine)>0) Move (Replace(" [",sLine,"[")) to sLine Loop End_Procedure // Removes the first word of any input string // all up to the first space encountered Function RemoveFirstWord String sLine Returns String Integer iPos Move (Trim(sLine)) to sLine Send NormalizeArrayNotation (&sLine) Move (Pos(" ", sLine)) to iPos Function_Return (Right(sLine, (Length(sLine) - iPos))) End_Function // Returns the first word of any input string // all up to the first space encountered Function RetrieveFirstWord String sLine Returns String String sFirstWord Get RemoveFirstWord sLine to sFirstWord Move (Replace(sFirstWord, sLine, "")) to sFirstWord Move (Trim(sFirstWord)) to sFirstWord Send NormalizeArrayNotation (&sFirstWord) Function_Return sFirstWord End_Function // Parses the method name from the string Function MethodName String sMethod Returns String Integer iPos Get RemoveFirstWord sMethod to sMethod Move (Right(sMethod, (Length(sMethod) - iPos))) to sMethod If (Uppercase(Left(Trim(sMethod), 4)) = "SET ") Begin Get RemoveFirstWord sMethod to sMethod End // Remove any trailing arguments if any Move (Pos(" ", sMethod)) to iPos Function_Return (Left(sMethod, (iPos - 1))) End_Function Function IsComment String sLine Returns Boolean Boolean bComment // Replaces all quotes string contents to "_" so we don't get false positives. Get OverstrikeStrings sLine to sLine Move (Left(Trim(sLine), 2) = CS_Comment) to bComment Function_Return bComment End_Function Function IsMethodStart String sLine Returns Boolean Boolean bMethodStart Get OverstrikeStrings sLine to sLine Move (Uppercase(Trim(sLine))) to sLine Move (Left(sLine, 10) = "PROCEDURE " or Left(sLine, 9) = "FUNCTION ") to bMethodStart Function_Return bMethodStart End_Function Function IsMethodEnd String sLine Returns Boolean Boolean bMethodEnd Get OverstrikeStrings sLine to sLine Get RemoveEndComment sLine to sLine Move (Uppercase(Trim(sLine))) to sLine Move (Left(sLine, 14) = "END_PROCEDURE " or Left(sLine, 13) = "END_FUNCTION ") to bMethodEnd Function_Return bMethodEnd End_Function Function IsVariableDeclarationLine String sLine Returns Boolean Boolean bVariableDeclaration String[] asLocalVariableTypes asLocalVariableArrayTypes Integer iRetval String sFirstWord Move False to bVariableDeclaration Get pasLocalVariableTypes to asLocalVariableTypes Get pasLocalVariableArrayTypes to asLocalVariableArrayTypes Move (Trim(sLine)) to sLine Get RetrieveFirstWord sLine to sFirstWord Move (Uppercase(sFirstWord)) to sFirstWord Move (SearchArray(sFirstWord, asLocalVariableTypes)) to iRetval If (iRetval <> -1) Begin Move True to bVariableDeclaration End Else Begin Move (SearchArray(sFirstWord, asLocalVariableArrayTypes)) to iRetval If (iRetval <> -1) Begin Move True to bVariableDeclaration End End Function_Return bVariableDeclaration End_Function Function IsVariableInLine String sLine String sVariableName Returns Boolean Boolean bIsUseLine String sChar //sTab Integer iPos // Move (Character(9)) to sTab Move False to bIsUseLine Move (Uppercase(sLine)) to sLine Move (Uppercase(sVariableName)) to sVariableName If (sLine contains sVariableName) Begin Move (Pos(sVariableName, sLine)) to iPos If (iPos > 0) Begin Repeat Move (Mid(sLine, 1, (iPos - 1))) to sChar If (CS_ValidLeftCharacters contains sChar) Begin Move True to bIsUseLine End // Else Begin // Move (sChar = sTab) to bIsUseLine // End If (bIsUseLine = False) Begin Move (Replace(sVariableName, sLine, "")) to sLine Move (Pos(sVariableName, sLine)) to iPos End Until (bIsUseLine = True or iPos = 0) End End Function_Return bIsUseLine End_Function // Returns an end comment if exists. It also removes the end comment // from the passed string. Function RemoveEndComment String sLine Returns String Integer iPos String sTest Move (Trim(sLine)) to sTest Move (Pos(CS_CommentSymbol, sTest)) to iPos // Only remove comment if the line is not commented at the beginning. If (iPos > 1) Begin Move (Pos(CS_CommentSymbol, sLine)) to iPos Move (Left(sLine, iPos - 1)) to sLine End Function_Return sLine End_Function // Remove all variables that are used. Function CleanupLocalVariables tUnusedLocals[] asLocalVariables Returns tUnusedLocals Integer iCount iSize Move (SortArray(asLocalVariables)) to asLocalVariables Move (SizeOfArray(asLocalVariables)) to iSize Decrement iSize For iCount From 0 to iSize If (asLocalVariables[iCount].bInUse = True) Begin Move (RemoveFromArray(asLocalVariables, iCount)) to asLocalVariables Decrement iSize Decrement iCount End Loop Function_Return asLocalVariables End_Function // Custom comparison function: // Returns (GT) struct value in first parameter > struct value in second parameter. // Returns (LT) struct value in first parameter < struct value in second parameter. // Otherwise returns (EQ). Function CompareLineNum tUnusedLocals Local1 tUnusedLocals Local2 Returns Integer If (Local1.Line# > Local2.Line#) ; Function_Return (GT) If (Local1.Line# < Local2.Line#) ; Function_Return (LT) Function_Return (EQ) End_Function // We now have the source file array and the unused variables struct array. // Remove those unused variables from the source file array. Procedure RemoveUnusedVariablesFromSourceArray String[] ByRef asSourceFile tUnusedLocals[] asLocalVariables Integer ByRef iUnusedLocalVariables Integer iCount iSize iItem iLength iOffset String sVarName sLine sVarType sVariableDeclarationConstant Boolean bInUse bFound bIgnore Move 0 to iOffset Get CleanupLocalVariables asLocalVariables to asLocalVariables Move (SizeOfArray(asLocalVariables)) to iSize // sort array by line number as otherwise the iOffset logic won't work! Move (SortArray(asLocalVariables, Self, (RefFunc(CompareLineNum)))) to asLocalVariables Decrement iSize For iCount From 0 to iSize Move asLocalVariables[iCount].bInUse to bInUse If (bInUse = False) Begin Move asLocalVariables[iCount].Line# to iItem Move (iItem - 1 + iOffset) to iItem Move asLocalVariables[iCount].VarName to sVarName Move (Length(sVarName)) to iLength Move asSourceFile[iItem] to sLine Get RemoveLocal (&sLine) to bIgnore Get RetrieveFirstWord sLine to sVarType Move ((sLine+" ") contains (" " + sVarName + " ")) to bFound If (bFound = False) Begin Move (sLine contains sVarName) to bFound End If (bFound = True) Begin Move (Replace((" " + sVarName + " "), sLine+" ", " ")) to sLine End Send NormalizeArrayNotation (&sLine) // If all that remains on the line is the variable type declaration, // remove the line from the source array. Move sLine to sVariableDeclarationConstant Get RemoveLocal (&sVariableDeclarationConstant) to bIgnore Get RemoveEndComment sVariableDeclarationConstant to sVariableDeclarationConstant Move (Trim(sVariableDeclarationConstant)) to sVariableDeclarationConstant If (sVarType = sVariableDeclarationConstant) Begin Move (RemoveFromArray(asSourceFile, iItem)) to asSourceFile Decrement iOffset End Else Begin Move (RTrim(sLine)) to sLine Move sLine to asSourceFile[iItem] End Increment iUnusedLocalVariables End Loop Procedure_Return End_Procedure Function MainProcedure String[] ByRef asSourceFile Returns Boolean //String sSourceFile Integer iOutputType String sOutputPath Move False to Err Send ParseFile (&asSourceFile) Function_Return (Err = False) End_Function Procedure ParseFile String[] ByRef asSourceFile String sLine sVars sVariableName sMethodName sSourceFile Integer iArrayCount iMax iCounter iItems iLineCount iUnusedLocalVariables iSize iCount tUnusedLocals[] asLocalVars String[] asVars Boolean bComment bInMethod bVariableDeclaration bIsVariable bMethodEnd bIgnore Move (SizeOfArray(asSourceFile)) to iSize If (iSize = 0) Begin Get psIdleText of (phoStatusBar(ghoCommandBars)) to sSourceFile Send Info_Box ("Source File Array is empty.\n" + String(sSourceFile)) "Error in ParseFile method [cRemoveUnusedLocals.pkg]" Move True to Err Procedure_Return End Move False to bMethodEnd Decrement iSize Move False to Err Move 0 to iUnusedLocalVariables For iCount From 0 to iSize Move asSourceFile[iCount] to sLine Move (Trim(sLine)) to sLine Get OverstrikeStrings sLine to sLine Get RemoveEndComment sLine to sLine Get IsComment sLine to bComment If (bComment = False and Trim(sLine) <> "") Begin Get IsMethodStart sLine to bInMethod Get IsMethodEnd sLine to bMethodEnd If (bInMethod = True and bMethodEnd = False) Begin Get MethodName sLine to sMethodName // To start reading the next line after the start "Procedure" or "Function" line Move (iCount + 1) to iLineCount // If the method declaration is split over multiple lines, then we need to get past that part While (Right(Trim(sLine),1)=";") If (iLineCount <= iSize) Begin Move asSourceFile[iLineCount] to sLine End Else ; Move "" to sLine Get OverstrikeStrings sLine to sLine Get RemoveEndComment sLine to sLine Increment iLineCount Loop // Begin search line-by-line until "End_Procedure" or "End_Function" Repeat If (iLineCount <= iSize) Begin Move asSourceFile[iLineCount] to sLine End Move (Trim(sLine)) to sLine Get OverstrikeStrings sLine to sLine Get RemoveEndComment sLine to sLine Get IsComment sLine to bComment Get IsMethodEnd sLine to bMethodEnd If (bComment = False and bMethodEnd = False and Trim(sLine) <> "") Begin Get RemoveLocal (&sLine) to bIgnore // Does the line contain one or more variable declarations? Get IsVariableDeclarationLine sLine to bVariableDeclaration If (bVariableDeclaration = True) Begin // Remove the data type command Get RemoveFirstWord sLine to sVars Get StrSplitToArray (Trim(sVars)) " " to asVars // Loop through the variable's array and add to struct array of locally declared variables. // Need to do this here because we don't know where the programmer will place variable declarations, // so we need to update the array as they occur. Move (SizeOfArray(asVars)) to iItems Decrement iItems For iCounter From 0 to iItems Move asVars[iCounter] to sVariableName Move (SizeOfArray(asLocalVars)) to iArrayCount // If the procedure or function line is wrapped, it may // contain the variable type, and we don't want that // showing up as an unused variable If ((not((CS_VarTypes + ";") contains Uppercase(sVariableName+";")) or not(CS_VarArrayTypes contains Uppercase(sVariableName))) and Trim(sVariableName) <> "") Begin Move sVariableName to asLocalVars[iArrayCount].VarName Move False to asLocalVars[iArrayCount].bInUse Move sMethodName to asLocalVars[iArrayCount].MethodName // Array item number (zero based) to source code line number: Move (iLineCount + 1) to asLocalVars[iArrayCount].Line# End Loop End // If not a variable declaration line. For each line, we look in the asLocalVars array, // and if the variable is found we mark it as "used" Else Begin If (Trim(sLine) <> "") Begin Move (SizeOfArray(asLocalVars)) to iMax Decrement iMax For iCounter From 0 to iMax If (asLocalVars[iCounter].bInUse = False and asLocalVars[iCounter].MethodName = sMethodName) Begin Get IsVariableInLine sLine asLocalVars[iCounter].VarName to bIsVariable If (bIsVariable = True) Begin // Mark as found Move True to asLocalVars[iCounter].bInUse End End Loop End End End Increment iLineCount Until (bMethodEnd or iCount = iSize or iLineCount >= iSize) End End If (iCount < iLineCount) Begin Move iLineCount to iCount End Loop If (SizeOfArray(asLocalVars)) Begin Send RemoveUnusedVariablesFromSourceArray (&asSourceFile) asLocalVars (&iUnusedLocalVariables) End Get piNoOfUnusedLocalVariables to iCounter Set piNoOfUnusedLocalVariables to (iCounter + iUnusedLocalVariables) End_Procedure End_Class