Last active
September 8, 2017 16:48
-
-
Save danmoseley/9da8ef840afac4edc6efd6db19638a76 to your computer and use it in GitHub Desktop.
Remove unused strings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
using System.Threading.Tasks; | |
using System.Xml.Linq; | |
namespace FindDeadResources | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
if(args.Length < 1) | |
{ | |
Console.WriteLine("Usage: FindDeadResources paths\\to\\code ..."); | |
return; | |
} | |
HashSet<string> stringsUsed = new HashSet<string>(); | |
foreach (string codeDir in args) | |
{ | |
Console.Write($"===Processing {codeDir} "); | |
if (!Directory.Exists(codeDir)) | |
{ | |
throw new Exception($"Can't find directory {codeDir}"); | |
} | |
foreach (string path in Directory.EnumerateFiles(codeDir, "*.cs", SearchOption.AllDirectories)) | |
{ | |
//Console.Write("."); | |
ProcessCs(path, stringsUsed); | |
} | |
Console.Write("."); | |
foreach (string path in Directory.EnumerateFiles(codeDir, "*.cpp", SearchOption.AllDirectories)) | |
{ | |
//Console.Write("."); | |
ProcessCs(path, stringsUsed); | |
} | |
Console.Write("."); | |
foreach (string path in Directory.EnumerateFiles(codeDir, "*.h", SearchOption.AllDirectories)) | |
{ | |
//Console.Write("."); | |
ProcessCs(path, stringsUsed); | |
} | |
Console.Write("."); | |
foreach (string path in Directory.EnumerateFiles(codeDir, "*.hpp", SearchOption.AllDirectories)) | |
{ | |
//Console.Write("."); | |
ProcessCs(path, stringsUsed); | |
} | |
Console.Write("."); | |
//Console.WriteLine(); | |
} | |
bool foundResx = false; | |
foreach (string codeDir in args) | |
{ | |
//foreach (string resxPath in Directory.EnumerateFiles(codeDir, "*.resx", SearchOption.AllDirectories)) | |
string resxPath = @"c:\git\coreclr\src\mscorlib\Resources\strings.resx"; | |
{ | |
foundResx = true; | |
ProcessResx(resxPath, stringsUsed); | |
} | |
} | |
if (!foundResx) Console.WriteLine("Found no resx's"); | |
Console.WriteLine(); | |
} | |
static Regex[] srRegexs = { | |
// SR.ArgumentException_ValueTupleIncorrectType | |
new Regex(@"SR\.([\w_]+)", RegexOptions.Compiled), | |
// ThrowHelper.ThrowSecurityException(ExceptionResource.Security_RegistryPermission); | |
new Regex(@"ExceptionResource\.([\w_]+)", RegexOptions.Compiled), | |
// Resources.GetResourceString("Argument_InvalidTypeName") | |
new Regex(@"Resources.GetResourceString\(""([\w_]+)""", RegexOptions.Compiled), | |
// ResId.NotSupported_Constructor | |
new Regex(@"ResId\.([\w_]+)", RegexOptions.Compiled), | |
// Result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); | |
new Regex(@"\.SetFailure\([^,]+, *""([\w_]+)""", RegexOptions.Compiled), | |
// ResMgrGetString(W("ReflectionTypeLoad_LoadFailed"), &gc.str | |
new Regex(@"ResMgrGetString.*""([\w_]+)""", RegexOptions.Compiled), | |
// EEResourceException e(kAppDomainUnloadedException, W("Remoting_AppDomainUnloaded_ThreadUnwound")); | |
new Regex(@"EEResourceException.*""([\w_]+)""", RegexOptions.Compiled), | |
// EX_THROW(EEArgumentException, (kArgumentNullException, argName, W("ArgumentNull_Generic"))); | |
new Regex(@"EX_THROW.*""([\w_]+)""", RegexOptions.Compiled), | |
// TryDemand(SECURITY_SKIP_VER, kFieldAccessException, W("Acc_RvaStatic")); | |
new Regex(@"TryDemand.*""([\w_]+)""", RegexOptions.Compiled), | |
// LPCWSTR argName = W("Arg_InvalidHandle"); | |
new Regex(@"LPCWSTR argName =.*""([\w_]+)""", RegexOptions.Compiled), | |
// COMPlusThrow(kOverflowException, W("Overflow_Int32")); | |
new Regex(@"COMPlusThrow.*""([\w_]+)""", RegexOptions.Compiled), | |
// from CThrowArgumentOutOfRange(W("startIndex"), W("ArgumentOutOfRange_StartIndex")); etc | |
// new Regex(@"LPCWSTR argName =.*""([\w_]+)""", RegexOptions.Compiled), | |
// FCThrowXXXX(kNullReferenceException, W("NullReference_This")); | |
new Regex(@"FCThrow.*""([\w_]+)""", RegexOptions.Compiled), | |
}; | |
private static void ProcessCs(string csPath, HashSet<string> stringsUsed) | |
{ | |
foreach (Regex regex in srRegexs) | |
{ | |
string fileContents = File.ReadAllText(csPath); | |
MatchCollection matches = regex.Matches(fileContents); | |
foreach(Match match in matches) | |
{ | |
string stringName = match.Groups[1].Value; | |
//Console.WriteLine($"Found {stringName}"); | |
stringsUsed.Add(stringName); | |
} | |
} | |
} | |
private static void ProcessResx(string resxPath, HashSet<string> stringsUsed) | |
{ | |
// IDs that are constructed | |
var immune = new HashSet<string> { | |
"Globalization_cp_1200" , | |
"Globalization_cp_12000", | |
"Globalization_cp_12001", | |
"Globalization_cp_1201" , | |
"Globalization_cp_20127", | |
"Globalization_cp_28591", | |
"Globalization_cp_65000", | |
"Globalization_cp_65001", | |
"AssertionFailed", | |
"AssumptionFailed", | |
"InvariantFailed", | |
"PostconditionFailed", | |
"PostconditionOnExceptionFailed", | |
"PreconditionFailed", | |
"AssertionFailed_Cnd", | |
"AssumptionFailed_Cnd", | |
"InvariantFailed_Cnd", | |
"PostconditionFailed_Cnd", | |
"PostconditionOnExceptionFailed_Cnd", | |
"PreconditionFailed_Cnd", | |
"event_Barrier_PhaseFinished", | |
"event_ConcurrentBag_TryPeekSteals", | |
"event_ConcurrentBag_TryTakeSteals", | |
"event_ConcurrentDictionary_AcquiringAllLocks", | |
"event_ConcurrentStack_FastPopFailed", | |
"event_ConcurrentStack_FastPushFailed", | |
"event_ParallelFork", | |
"event_ParallelInvokeBegin", | |
"event_ParallelInvokeEnd", | |
"event_ParallelJoin", | |
"event_ParallelLoopBegin", | |
"event_ParallelLoopEnd", | |
"event_SpinLock_FastPathFailed", | |
"event_SpinWait_NextSpinWillYield", | |
"event_TaskCompleted", | |
"event_TaskScheduled", | |
"event_TaskStarted", | |
"event_TaskWaitBegin", | |
"event_TaskWaitEnd", | |
// from COMPlusThrow(kOverflowException, W("Overflow_Int32")); etc | |
"Acc_CreateAbst", | |
"Acc_CreateGeneric", | |
"Acc_CreateInterface", | |
"Acc_ReadOnly", | |
"Arg_CannotHaveNegativeValue", | |
"Arg_DlgtNullInst", | |
"Arg_DlgtTargMeth", | |
"Arg_InvalidANSIString", | |
"Arg_InvalidBase", | |
"Arg_InvalidHandle", | |
"Arg_InvalidOleVariantTypeException", | |
"Arg_InvalidUTF8String", | |
"Arg_MethodAccessException", | |
"Arg_MissingFieldException", | |
"Arg_MustBeEnum", | |
"Arg_MustBeInterface", | |
"Arg_MustBeString", | |
"Arg_MustBeStringPtrNotAtom", | |
"Arg_NoDefCTor", | |
"Arg_NoITypeInfo", | |
"Arg_NoITypeLib", | |
"Arg_NotFoundIFace", | |
"Arg_NullIndex", | |
"Arg_ObjObj", | |
"Arg_OleAutDateInvalid", | |
"Arg_OleAutDateScale", | |
"Arg_ParmCnt", | |
"Arg_PrimWiden", | |
"ArgumentNull_Array", | |
"ArgumentNull_ArrayElement", | |
"ArgumentNull_AssemblyName", | |
"ArgumentNull_AssemblyNameName", | |
"ArgumentNull_FileName", | |
"ArgumentNull_GUID", | |
"ArgumentNull_Generic", | |
"ArgumentNull_Obj", | |
"ArgumentNull_Path", | |
"ArgumentNull_SafeHandle", | |
"ArgumentNull_String", | |
"ArgumentNull_Type", | |
"ArgumentOutOfRange_ArrayLBAndLength", | |
"ArgumentOutOfRange_Capacity", | |
"ArgumentOutOfRange_Count", | |
"ArgumentOutOfRange_Enum", | |
"ArgumentOutOfRange_Index", | |
"ArgumentOutOfRange_NeedNonNegNum", | |
"ArgumentOutOfRange_NeedNonNegOrNegative1", | |
"Argument_AlreadyACCW", | |
"Argument_BadConstantValue", | |
"Argument_BadFormatSpecifier", | |
"Argument_BadObjRef", | |
"Argument_BadSigFormat", | |
"Argument_CORDBBadMethod", | |
"Argument_CORDBBadVarArgCallConv", | |
"Argument_CannotCreateString", | |
"Argument_CannotCreateTypedReference", | |
"Argument_CantCallSecObjFunc", | |
"Argument_DuplicateTypeName", | |
"Argument_EmptyFileName", | |
"Argument_GenericsInvalid", | |
"Argument_InterfaceMap", | |
"Argument_InvalidAssemblyName", | |
"Argument_InvalidFlag", | |
"Argument_InvalidGenericArg", | |
"Argument_InvalidValue", | |
"Argument_MissingDefaultConstructor", | |
"Argument_MustBeRuntimeType", | |
"Argument_MustHaveLayoutOrBeBlittable", | |
"Argument_NeedNonGenericObject", | |
"Argument_NeedNonGenericType", | |
"Argument_NoUnderlyingCCW", | |
"Argument_NoUninitializedStrings", | |
"Argument_NotATP", | |
"Argument_ObjIsWinRTObject", | |
"Argument_StringZeroLength", | |
"Argument_TypeMustBeVisibleFromCom", | |
"Argument_TypeNotValid", | |
"Argument_VerStringTooLong", | |
"ArrayTypeMismatch_CantAssignType", | |
"Format_BadBase", | |
"Format_EmptyInputString", | |
"Format_ExtraJunkAtEnd", | |
"Format_NoParsibleDigits", | |
"Format_StringZeroLength", | |
"InvalidCast_DownCastArrayElement", | |
"InvalidCast_OATypeMismatch", | |
"InvalidCast_StoreArrayElement", | |
"InvalidOperation_CantInstantiateAbstractClass", | |
"InvalidOperation_CantInstantiateFunctionPointer", | |
"InvalidOperation_CriticalTransparentAreMutuallyExclusive", | |
"InvalidOperation_EnumEnded", | |
"InvalidOperation_HandleIsNotInitialized", | |
"InvalidOperation_StrongNameKeyPairRequired", | |
"InvalidOperation_TypeCannotBeBoxed", | |
"Invoke", | |
"NotSupported_ByRefLike", | |
"NotSupported_ByRefLikeArray", | |
"NotSupported_ByRefReturn", | |
"NotSupported_VoidArray", | |
"NotSupported_ChangeType", | |
"NotSupported_CollectibleAssemblyResolve", | |
"NotSupported_CollectibleBoundNonCollectible", | |
"NotSupported_CollectibleCOM", | |
"NotSupported_CollectibleDelegateMarshal", | |
"NotSupported_CollectibleNotYet", | |
"NotSupported_CollectibleResolveFailure", | |
"NotSupported_DelegateMarshalToWrongDomain", | |
"NotSupported_GenericMethod", | |
"NotSupported_IDispInvokeDefaultMemberWithNamedArgs", | |
"NotSupported_ManagedActivation", | |
"NotSupported_NativeCallableTarget", | |
"NotSupported_NonBlittableTypes", | |
"NotSupported_NonReflectedType", | |
"NotSupported_NonStaticMethod", | |
"NotSupported_OleAutBadVarType", | |
"NotSupported_OpenType", | |
"NotSupported_PIAInAppxProcess", | |
"NotSupported_SignalAndWaitSTAThread", | |
"NotSupported_TooManyArgs", | |
"NotSupported_Type", | |
"NotSupported_ValueClassCM", | |
"NotSupported_WinRT_PartialTrust", | |
"NullReference_This", | |
"NumberFormatInfo", | |
"Overflow_Currency", | |
"Overflow_Int16", | |
"Overflow_Int32", | |
"Overflow_Int64", | |
"Overflow_NegativeUnsigned", | |
"Overflow_SByte", | |
"Overflow_UInt32", | |
"Overflow_UInt64", | |
"PlatformNotSupported_NamedSyncObjectWaitAnyWaitAll", | |
"PlatformNotSupported_WinRT", | |
"Policy_CannotLoadSemiTrustAssembliesDuringInit", | |
"Rank_MultiDimNotSupported", | |
"Remoting_AppDomainUnloaded_ThreadUnwound", | |
"UnauthorizedAccess_SystemDomain", | |
// from CThrowArgumentOutOfRange(W("startIndex"), W("ArgumentOutOfRange_StartIndex")); etc | |
"Arg_ArgumentOutOfRangeException", | |
"Arg_InvalidHandle", | |
"Arg_InvalidSwitchName", | |
"Arg_LongerThanDestArray", | |
"Arg_LongerThanSrcArray", | |
"Arg_MustBePrimArray", | |
"ArgumentNull_TypedRefType", | |
"ArgumentOutOfRange_ArrayLB", | |
"ArgumentOutOfRange_Count", | |
"ArgumentOutOfRange_DecimalRound", | |
"ArgumentOutOfRange_Index", | |
"ArgumentOutOfRange_NeedNonNegNum", | |
"ArgumentOutOfRange_NegativeLength", | |
"ArgumentOutOfRange_PartialWCHAR", | |
"ArgumentOutOfRange_StartIndex", | |
"Argument_ArgumentZero", | |
"Argument_HandleLeak", | |
"Argument_InvalidOffLen", | |
"Argument_MustHaveLayoutOrBeBlittable", | |
"Argument_NeedStructWithNoRefs", | |
"Argument_StructMustNotBeValueClass", | |
"ArrayTypeMismatch_CantAssignType", | |
"ArrayTypeMismatch_ConstrainedCopy", | |
"IndexOutOfRange_ArrayRankIndex", | |
"IndexOutOfRange_IORaceCondition", | |
"InvalidOperation_HandleIsNotInitialized", | |
"NotSupported_DynamicAssemblyNoRunAccess", | |
"NotSupported_Type", | |
"NullReference_This", | |
"Overflow_Currency", | |
"Overflow_Decimal", | |
"Overflow_Int32", | |
"Rank_MustMatch" | |
}; | |
XDocument resxDoc = XDocument.Load(resxPath, LoadOptions.PreserveWhitespace); | |
List<XElement> nodesToRemove = new List<XElement>(); | |
int stringsKept = 0; | |
int immuneKept = 0; | |
foreach(XElement dataNode in resxDoc.Descendants(XName.Get("data"))) | |
{ | |
string stringName = dataNode.Attribute("name").Value; | |
if (stringsUsed.Contains(stringName)) | |
{ | |
stringsKept++; | |
} | |
else if (immune.Contains(stringName)) | |
{ | |
immuneKept++; | |
} | |
else | |
{ | |
nodesToRemove.Add(dataNode); | |
} | |
} | |
foreach(XElement nodeToRemove in nodesToRemove) | |
{ | |
RemoveWithNextWhitespace(nodeToRemove); | |
} | |
Console.WriteLine($"Removed {nodesToRemove.Count} strings, kept {stringsKept}, immune {immuneKept}"); | |
resxDoc.Save(resxPath); | |
} | |
public static void RemoveWithNextWhitespace(XElement element) | |
{ | |
IEnumerable<XText> textNodes | |
= element.NodesAfterSelf() | |
.TakeWhile(node => node is XText).Cast<XText>(); | |
if (element.ElementsAfterSelf().Any()) { | |
// Easy case, remove following text nodes. | |
textNodes.ToList().ForEach(node => node.Remove()); | |
} else { | |
// Remove trailing whitespace. | |
textNodes.TakeWhile(text => !text.Value.Contains("\n")) | |
.ToList().ForEach(text => text.Remove()); | |
// Fetch text node containing newline, if any. | |
XText newLineTextNode | |
= element.NodesAfterSelf().OfType<XText>().FirstOrDefault(); | |
if (newLineTextNode != null) { | |
string value = newLineTextNode.Value; | |
if (value.Length > 1) { | |
// Composite text node, trim until newline (inclusive). | |
newLineTextNode.AddAfterSelf( | |
new XText(value.Substring(value.IndexOf('\n') + 1))); | |
} | |
// Remove original node. | |
newLineTextNode.Remove(); | |
} | |
} | |
element.Remove(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi @danmosemsft
Why did you remove the code that searches resource files path s?
foreach (string resxPath in Directory.EnumerateFiles(codeDir, "*.resx", SearchOption.AllDirectories))
and replace with a fixed path
string resxPath = @"c:\git\coreclr\src\mscorlib\Resources\strings.resx";
Was that to prevent the code from removing wrong strings?