diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index 9becb9e8c5..b1d812a924 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -101,40 +101,66 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOpti Dictionary> extensionOptionsByProvider, Dictionary> systemOptionsByProvider) { - IEnumerable allExtensionOptions = extensionOptionsByProvider.Values.SelectMany(x => x).Select(x => x.Name).Distinct(); - IEnumerable allSystemOptions = systemOptionsByProvider.Values.SelectMany(x => x).Select(x => x.Name).Distinct(); - - IEnumerable invalidReservedOptions = allSystemOptions.Intersect(allExtensionOptions); - if (invalidReservedOptions.Any()) + // Create a HashSet of all system option names for faster lookup + HashSet systemOptionNames = new(); + foreach (KeyValuePair> provider in systemOptionsByProvider) { - var stringBuilder = new StringBuilder(); - foreach (string reservedOption in invalidReservedOptions) + foreach (CommandLineOption option in provider.Value) { - IEnumerable faultyProviderNames = extensionOptionsByProvider.Where(tuple => tuple.Value.Any(x => x.Name == reservedOption)).Select(tuple => tuple.Key.DisplayName); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionIsReserved, reservedOption, string.Join("', '", faultyProviderNames))); + systemOptionNames.Add(option.Name); } + } - return ValidationResult.Invalid(stringBuilder.ToTrimmedString()); + StringBuilder? stringBuilder = null; + foreach (KeyValuePair> provider in extensionOptionsByProvider) + { + foreach (CommandLineOption option in provider.Value) + { + if (systemOptionNames.Contains(option.Name)) + { + stringBuilder ??= new StringBuilder(); + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionIsReserved, option.Name, provider.Key.DisplayName)); + } + } } - return ValidationResult.Valid(); + return stringBuilder?.Length > 0 + ? ValidationResult.Invalid(stringBuilder.ToTrimmedString()) + : ValidationResult.Valid(); } private static ValidationResult ValidateOptionsAreNotDuplicated( Dictionary> extensionOptionsByProvider) { - IEnumerable duplications = extensionOptionsByProvider.Values.SelectMany(x => x) - .Select(x => x.Name) - .GroupBy(x => x) - .Where(x => x.Skip(1).Any()) - .Select(x => x.Key); + // Use a dictionary to track option names and their providers + Dictionary> optionNameToProviders = new(); + foreach (KeyValuePair> kvp in extensionOptionsByProvider) + { + ICommandLineOptionsProvider provider = kvp.Key; + foreach (CommandLineOption option in kvp.Value) + { + string name = option.Name; + if (!optionNameToProviders.TryGetValue(name, out List? providers)) + { + providers = new List(); + optionNameToProviders[name] = providers; + } + providers.Add(provider); + } + } + + // Check for duplications StringBuilder? stringBuilder = null; - foreach (string duplicatedOption in duplications) + foreach (KeyValuePair> kvp in optionNameToProviders) { - IEnumerable faultyProvidersDisplayNames = extensionOptionsByProvider.Where(tuple => tuple.Value.Any(x => x.Name == duplicatedOption)).Select(tuple => tuple.Key.DisplayName); - stringBuilder ??= new(); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionIsDeclaredByMultipleProviders, duplicatedOption, string.Join("', '", faultyProvidersDisplayNames))); + if (kvp.Value.Count > 1) + { + string duplicatedOption = kvp.Key; + stringBuilder ??= new(); + IEnumerable faultyProvidersDisplayNames = kvp.Value.Select(p => p.DisplayName); + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionIsDeclaredByMultipleProviders, duplicatedOption, string.Join("', '", faultyProvidersDisplayNames))); + } } return stringBuilder?.Length > 0 @@ -147,10 +173,28 @@ private static ValidationResult ValidateNoUnknownOptions( Dictionary> extensionOptionsByProvider, Dictionary> systemOptionsByProvider) { + // Create a HashSet of all valid option names for faster lookup + HashSet validOptionNames = new(); + foreach (KeyValuePair> provider in extensionOptionsByProvider) + { + foreach (CommandLineOption option in provider.Value) + { + validOptionNames.Add(option.Name); + } + } + + foreach (KeyValuePair> provider in systemOptionsByProvider) + { + foreach (CommandLineOption option in provider.Value) + { + validOptionNames.Add(option.Name); + } + } + StringBuilder? stringBuilder = null; foreach (CommandLineParseOption optionRecord in parseResult.Options) { - if (!extensionOptionsByProvider.Union(systemOptionsByProvider).Any(tuple => tuple.Value.Any(x => x.Name == optionRecord.Name))) + if (!validOptionNames.Contains(optionRecord.Name)) { stringBuilder ??= new(); stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineUnknownOption, optionRecord.Name)); @@ -166,7 +210,7 @@ private static ValidationResult ValidateOptionsArgumentArity( CommandLineParseResult parseResult, Dictionary providerAndOptionByOptionName) { - StringBuilder stringBuilder = new(); + StringBuilder? stringBuilder = null; foreach (IGrouping groupedOptions in parseResult.Options.GroupBy(x => x.Name)) { // getting the arguments count for an option. @@ -181,19 +225,22 @@ private static ValidationResult ValidateOptionsArgumentArity( if (arity > option.Arity.Max && option.Arity.Max == 0) { + stringBuilder ??= new(); stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionExpectsNoArguments, optionName, provider.DisplayName, provider.Uid)); } else if (arity < option.Arity.Min) { + stringBuilder ??= new(); stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionExpectsAtLeastArguments, optionName, provider.DisplayName, provider.Uid, option.Arity.Min)); } else if (arity > option.Arity.Max) { + stringBuilder ??= new(); stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionExpectsAtMostArguments, optionName, provider.DisplayName, provider.Uid, option.Arity.Max)); } } - return stringBuilder.Length > 0 + return stringBuilder?.Length > 0 ? ValidationResult.Invalid(stringBuilder.ToTrimmedString()) : ValidationResult.Valid(); } @@ -254,7 +301,23 @@ private static async Task ValidateConfigurationAsync( } private static string ToTrimmedString(this StringBuilder stringBuilder) -#pragma warning disable RS0030 // Do not use banned APIs - => stringBuilder.ToString().TrimEnd(Environment.NewLine.ToCharArray()); -#pragma warning restore RS0030 // Do not use banned APIs + { + // Use a more efficient approach to trim without creating unnecessary intermediate strings + string result = stringBuilder.ToString(); + int end = result.Length; + + // Find the last non-whitespace char + while (end > 0) + { + char c = result[end - 1]; + if (c is not ('\r' or '\n')) + { + break; + } + + end--; + } + + return end == result.Length ? result : result.Substring(0, end); + } }