//-------------------------------------------------------------> // 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 // //------------------------------------------------------------> // 4/08/2008 RLW - Make into a class so the process can be // incorporated into other applications // 4/12/2008 RLW - Change read/write registry process // 4/12/2008 RLW - Fix so that wrapped variables on the function // or procedure line won't show odd results // 6/22/2008 RLW - Add missing Seq_Release_Channel calls // 8/17/2017 RLW - Change showln's to a TextEdit - about time! //-------------------------------------------------------------> // vdf-Guidance.com Use vWin32fh.pkg Use cRegistry.pkg // AUTHOR: Torkild Resheim // Original 1.0 version modified slightly to work // better when using a space as a token. Presence // of two spaces between variables would result // in a blank "not found" variable. // Original version will work fine with this caution. Use StringTokenizer.pkg Register_Object oVarDisplay Enum_List Define VarName Define VarStatus Define MethodName Define Line# End_Enum_List // Necessary for eliminating comments Define gsComments for "//" // Defines the end of the method and beginning of the comparison Define gsEndP for "END_PROCEDURE" Define gsEndF for "END_FUNCTION" // Enter all variable types you will be using here. Some users don't use the more exotic // ones like "BigInt" or "Real" Define gsVarTypes for "STRING;INTEGER;NUMBER;DATE;DATETIME;BOOLEAN;HANDLE;REAL;VARIANT;TIMESPAN;ROWID;DECIMAL" // This registry code is only to "remember" the last source code directory used Class cUVRegistry is a cRegistry Procedure Construct_object String sRegKey sWorkspaceName Forward Send Construct_Object Move ("Software\Data Access Worldwide\Visual DataFlex\" + Sysconf (SYSCONF_DATAFLEX_REV) + "\Workspaces\UnusedVariables\") to sRegKey Property String psRegWorkspacePath sRegKey End_Procedure Function GetReadTheKey String sSection Returns String String sValue sRegKey sWorkSpaceName sHome Boolean bOpened Integer iError Handle hoWorkspace Get psRegWorkspacePath to sRegKey Get OpenKey sRegKey to bOpened // Main Key is open If bOpened Begin // Now read the value of the section If (ValueExists(Self, sSection)) Get ReadString sSection to sValue End Else Begin // Doesn't exist, create the key Get CreateKey sRegKey to iError If (iError =0) Send WriteString sSection "" End Send CloseKey Function_Return sValue End_Function Procedure DoWriteTheKey String sSection String sValue String sRegKey sWorkspaceName Boolean bOpened Get psRegWorkspacePath to sRegKey Get OpenKey sRegKey to bOpened If bOpened Begin Send WriteString sSection sValue Send CloseKey End End_Procedure End_Class Class cReportUnusedLocals is a BusinessProcess Procedure Construct_object Forward Send Construct_object // Set the following properties if this package is used in another application Property String psSourceName // Name of the code file being worked on Property String psOutputFileName // Output file name for individual use - defaults to UnusedVariables.log if left blank. // Primarily available for users who want to incorporate the class into another product Property String psOutputPath // same usage as psOutputFileName Property Integer piOutputType // Output box or output to a file Object oTokenizer is a StringTokenizer Set Separator to " " End_Object Object oUVRegistry is a cUVRegistry End_Object End_Procedure Function sGetSourceDirectory Returns String Handle hoWorkspace String sPath Integer iPos Get phoWorkspace of oApplication to hoWorkspace Get psAppSrcPath of hoWorkspace to sPath Get vFolderFormat sPath to sPath Get GetReadTheKey of oUVRegistry "LastDirectory" to sPath If (sPath = "") Begin // In case of multiple paths appended in config.sys Move (pos(";", sPath)) to iPos If (iPos > 0) Begin Move (Left(sPath, (iPos-1))) to sPath Get vFolderFormat sPath to sPath End End Function_Return sPath End_Function // Simply removes the first word of any input string // all up to the first space encountered Function sGetRemoveFirstWord String sString Returns String Integer iPos Move (trim(sString)) to sString Move (Pos(" ", sString)) to iPos Function_Return (Right(sString, (Length(sString)-iPos))) End_Function // Parses out the name of any method from the rest of the line Function sGetMethodName String sMethod Returns String Integer iPos Get sGetRemoveFirstWord sMethod to sMethod Move (Right(sMethod, (Length(sMethod)-iPos))) to sMethod If (uppercase(Left(trim(sMethod), 4)) = "SET "); Get sGetRemoveFirstWord sMethod to sMethod // Remove any trailing arguments if any Move (Pos(" ", sMethod)) to iPos Function_Return (left(sMethod, (iPos-1))) End_Function Procedure onProcess String sPath sLine sVars sSingleVar sMethodName sTest sOrigMethodName sFileName sOutputFileName sLineMethod String sVarLine sTab Integer iPos iArrayCount iMax iCounter iItems iLineCount iFound iOutputType iChIn iChOut String[][4] sLocalVars Move (Seq_New_Channel()) to iChIn if (iChIn=DF_SEQ_CHANNEL_NOT_AVAILABLE) begin send Info_Box "No Channel Available for Input" "Error" procedure_return end move (Seq_New_Channel()) to iChOut if (iChOut=DF_SEQ_CHANNEL_NOT_AVAILABLE) begin send Info_Box "No Channel Available for Output" "Error" procedure_return end Move (Character(09)) to sTab Get piOutputType to iOutputType Get psSourceName to sFileName Get psOutputFileName to sOutputFileName If (sOutputFileName = "") Move "UnusedVariables.log" to sOutputFileName Direct_Input channel iChIn sFileName If (SeqEof) Begin Send Stop_Box "Program not found!" Abort End If (psOutputPath(Self) = "") Begin Move (ParseFolderName(sFileName)) to sPath Send DoWriteTheKey to oUVRegistry "LastDirectory" sPath End Else Begin Get psOutputPath to sPath Get vFolderFormat sPath to sPath End If (iOutputType = 1) Begin Direct_Output channel iChOut (sPath+sOutputFileName) Writeln channel iChOut "Line # - Method Name - Variable Name" Writeln channel iChOut "------------------------------------" End Else Begin Send AppendTextLn to oVarDisplay ("Line #" + sTab + "Method Name" + sTab + sTab + "Variable Name") Send AppendTextLn to oVarDisplay "------------------------------------------------------" End Move (ResizeArray(sLocalVars, 0)) to sLocalVars Repeat Readln channel iChIn sLine Increment iLineCount // Don't bother with comments If (Left(trim(sLine), 2) <> gsComments and trim(sLine) <> "") Begin Move (uppercase(trim(sLine))) to sMethodName If (Left(sMethodName, 10) = "PROCEDURE " or left(sMethodName, 9) = "FUNCTION ") Begin Get sGetMethodName sLine to sOrigMethodName // Looks nice for the report Repeat //-------------------------------------------------> Readln channel iChIn sLine // go to the next line Increment iLineCount // Remove any trailing comments Move (Pos(gsComments, sLine)) to iPos If (iPos > 0) Move (Left(sLine, (iPos-1))) to sLine // Parse the variables // Remove "local" if there If (uppercase(left(trim(sLine), 5)) = "LOCAL") Get sGetRemoveFirstWord sLine to sLine Move (Uppercase(trim(sLine))) to sTest // The most common variable types are included here, if you require others, // feel free to add them. Be sure the length of the type is correct. // i.e. "STRING" is 6 characters. We need this for parsing If (gsVarTypes contains trim(left(sTest, (pos(" ", sTest))))) Begin Move (trim(sLine)) to sLine // Remove the data type command Move (pos(" ", sLine)) to iPos // first space, all after are variables Move (Mid(sLine, (Length(sLine)-iPos), (iPos+1))) to sVars // Now parse out all variables in the line Set ParseString of oTokenizer to (trim(sVars)) Get ItemCount of oTokenizer to iItems // Go through the Tokenizer and // add to array of locals // Need to do this here because we don't know where // the programmer will place local variables, and // we need to update the array as they occur. And // SearchArray doesn't work for this style of array Move 0 to iCounter While iCounter lt iItems Get TokenAt of oTokenizer iCounter to sSingleVar Move (SizeOfArray(sLocalVars)) 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(gsVarTypes contains uppercase(sSingleVar))) Begin Move sSingleVar to sLocalVars[iArrayCount][VarName] Move "0" to sLocalVars[iArrayCount][VarStatus] // Usage indicator Move sOrigMethodName to sLocalVars[iArrayCount][MethodName] Move iLineCount to sLocalVars[iArrayCount][Line#] End Increment iCounter Loop End Else Begin // *** The rest of the method *** // for each line here we look in the LocalVars array // If the localvar is found before "END" then we mark // it in the array, after "END" we go through the local // array and look for any not marked and add to a report If (trim(sLine) <> "") Begin Move (SizeOfArray(sLocalVars)) to iMax For iCounter from 0 to (iMax-1) If (uppercase(sLine) contains uppercase(sLocalVars[iCounter][VarName])) Begin // Mark array as found Move "1" to sLocalVars[iCounter][VarStatus] End Loop End End Until (uppercase(sLine) contains gsEndP or uppercase(sLine) contains gsEndF) // End_Procedure or End_Function // Check the array for non used variables Move (SizeOfArray(sLocalVars)) to iMax For iCounter from 0 to (iMax-1) If (sLocalVars[iCounter][VarStatus] = 0) Begin Increment iFound Move (trim(sLocalVars[iCounter][MethodName])) to sLineMethod // Attempt to get the variable name column to line // up correctly given the potential difference in // name lengths. Might take some adjustment if overly // long names are used If (Length(trim(sLineMethod)) < 11) Begin Move (Pad(sLocalVars[iCounter][MethodName], 28)) to sLineMethod End Else; Move (Pad(sLocalVars[iCounter][MethodName], 24)) to sLineMethod Move (sLocalVars[iCounter][Line#] + sTab + sLineMethod + sTab + sLocalVars[iCounter][VarName]) to sVarLine If (iOutputType = 0); Send AppendTextLn to oVarDisplay sVarLine Else; Writeln channel iChOut sVarLine End Loop // Zero the array Move (ResizeArray(sLocalVars, 0)) to sLocalVars End End Until (SeqEof) Close_Input channel iChIn Send Seq_Release_Channel iChIn If (iOutputType = 1) Begin If (iFound > 0) Begin Writeln channel iChOut "------------------------------------" Writeln channel iChOut (String(iFound) + " unused variables found!") Close_Output channel iChOut Send Seq_Release_Channel iChOut Send Info_Box "All done, look at UnusedLocals.log in the source directory." End Else Begin Close_Output channel iChOut Send Seq_Release_Channel iChOut Send Info_Box "None found, congratulations!" End End Else Begin If (iFound = 0) Send AppendTextLn to oVarDisplay "None found, congratulations!" Else Begin Send AppendTextLn to oVarDisplay "------------------------------------------------------" Send AppendTextLn to oVarDisplay (String(iFound) + " unused variables found!") End End End_Procedure End_Class