-
-
Save italopessoa/76cbf60fc826986e52dd4a191e6163a6 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