diff --git a/src/CommandLine/BaseAttribute.cs b/src/CommandLine/BaseAttribute.cs index be0a3826..0ecfbcdc 100644 --- a/src/CommandLine/BaseAttribute.cs +++ b/src/CommandLine/BaseAttribute.cs @@ -1,6 +1,7 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. using System; +using System.Diagnostics.CodeAnalysis; namespace CommandLine { @@ -14,6 +15,11 @@ public abstract class BaseAttribute : Attribute private object @default; private Infrastructure.LocalizableAttributeProperty helpText; private string metaValue; + +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties)] +#endif private Type resourceType; /// @@ -31,11 +37,7 @@ protected internal BaseAttribute() /// /// Gets or sets a value indicating whether a command line option is required. /// - public bool Required - { - get; - set; - } + public bool Required { get; set; } /// /// When applied to properties defines @@ -81,10 +83,7 @@ public int Max public object Default { get { return @default; } - set - { - @default = value; - } + set { @default = value; } } /// @@ -92,7 +91,7 @@ public object Default /// public string HelpText { - get => helpText.Value??string.Empty; + get => helpText.Value ?? string.Empty; set => helpText.Value = value ?? throw new ArgumentNullException("value"); } @@ -116,22 +115,22 @@ public string MetaValue /// /// Gets or sets a value indicating whether a command line option is visible in the help text. /// - public bool Hidden - { - get; - set; - } + public bool Hidden { get; set; } /// /// Gets or sets the that contains the resources for . /// +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties)] +#endif public Type ResourceType { get { return resourceType; } set { resourceType = - helpText.ResourceType = value; + helpText.ResourceType = value; } } } diff --git a/src/CommandLine/CastExtensions.cs b/src/CommandLine/CastExtensions.cs index fa34928c..315d5937 100644 --- a/src/CommandLine/CastExtensions.cs +++ b/src/CommandLine/CastExtensions.cs @@ -1,4 +1,7 @@ using System; +#if NET8_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif using System.Linq; using System.Reflection; @@ -9,11 +12,18 @@ internal static class CastExtensions private const string ImplicitCastMethodName = "op_Implicit"; private const string ExplicitCastMethodName = "op_Explicit"; - public static bool CanCast(this Type baseType) + public static bool CanCast( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + this Type baseType) { return baseType.CanImplicitCast() || baseType.CanExplicitCast(); } +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing annotations on type", "IL2072")] +#endif public static bool CanCast(this object obj) { var objType = obj.GetType(); @@ -24,7 +34,7 @@ public static T Cast(this object obj) { try { - return (T) obj; + return (T)obj; } catch (InvalidCastException) { @@ -37,29 +47,48 @@ public static T Cast(this object obj) } } - private static bool CanImplicitCast(this Type baseType) + private static bool CanImplicitCast( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + this Type baseType) { return baseType.CanCast(ImplicitCastMethodName); } +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing annotations on type", "IL2072")] +#endif private static bool CanImplicitCast(this object obj) { var baseType = obj.GetType(); return baseType.CanImplicitCast(); } - private static bool CanExplicitCast(this Type baseType) + private static bool CanExplicitCast( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + this Type baseType) { return baseType.CanCast(ExplicitCastMethodName); } +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing annotations on type", "IL2072")] +#endif private static bool CanExplicitCast(this object obj) { var baseType = obj.GetType(); return baseType.CanExplicitCast(); } - private static bool CanCast(this Type baseType, string castMethodName) + private static bool CanCast( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + this Type baseType, + string castMethodName) { var targetType = typeof(T); return baseType.GetMethods(BindingFlags.Public | BindingFlags.Static) @@ -81,6 +110,9 @@ private static T ExplicitCast(this object obj) return obj.Cast(ExplicitCastMethodName); } +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Reflection on object", "IL2075")] +#endif private static T Cast(this object obj, string castMethodName) { var objType = obj.GetType(); @@ -91,10 +123,9 @@ private static T Cast(this object obj, string castMethodName) ParameterInfo pi = mi.GetParameters().FirstOrDefault(); return pi != null && pi.ParameterType == objType; }); - if (conversionMethod != null) - return (T) conversionMethod.Invoke(null, new[] {obj}); - else - throw new InvalidCastException($"No method to cast {objType.FullName} to {typeof(T).FullName}"); + return conversionMethod != null + ? (T)conversionMethod.Invoke(null, new[] { obj }) + : throw new InvalidCastException($"No method to cast {objType.FullName} to {typeof(T).FullName}"); } } } diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 04496eb8..dba28afb 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -3,7 +3,7 @@ CommandLine Library - netstandard2.0;net40;net45;net461 + netstandard2.0;net40;net45;net461;net8.0 $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC;CSX_REM_CRYPTORAND;ERRH_ADD_MAYBE_METHODS $(DefineConstants);SKIP_FSHARP true @@ -24,9 +24,10 @@ command line;commandline;argument;option;parser;parsing;library;syntax;shell https://github.com/commandlineparser/commandline/blob/master/CHANGELOG.md true - 8.0 + latest true snupkg + true diff --git a/src/CommandLine/Core/GetoptTokenizer.cs b/src/CommandLine/Core/GetoptTokenizer.cs index b8c97fc2..1765f647 100644 --- a/src/CommandLine/Core/GetoptTokenizer.cs +++ b/src/CommandLine/Core/GetoptTokenizer.cs @@ -147,7 +147,7 @@ private static IEnumerable TokenizeShortName( // But if there is no rest of the string, then instead we swallow the next argument string chars = arg.Substring(1); int len = chars.Length; - if (len > 0 && Char.IsDigit(chars[0])) + if (len > 0 && char.IsDigit(chars[0])) { // Assume it's a negative number yield return Token.Value(arg); @@ -155,7 +155,7 @@ private static IEnumerable TokenizeShortName( } for (int i = 0; i < len; i++) { - var s = new String(chars[i], 1); + var s = new string(chars[i], 1); switch(nameLookup(s)) { case NameLookupResult.OtherOptionFound: @@ -196,7 +196,7 @@ private static IEnumerable TokenizeLongName( string name = parts[0]; string value = (parts.Length > 1) ? parts[1] : null; // A parameter like "--stringvalue=" is acceptable, and makes stringvalue be the empty string - if (String.IsNullOrWhiteSpace(name) || name.Contains(" ")) + if (string.IsNullOrWhiteSpace(name) || name.Contains(" ")) { onBadFormatToken(arg); yield break; diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index f48127b1..5576d4f3 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using CommandLine.Infrastructure; @@ -11,9 +12,17 @@ namespace CommandLine.Core { - static class InstanceBuilder + internal static class InstanceBuilder { - public static ParserResult Build( + public static ParserResult Build< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | + DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties)] +#endif + T>( Maybe> factory, Func, IEnumerable, Result, Error>> tokenizer, IEnumerable arguments, @@ -37,7 +46,20 @@ public static ParserResult Build( nonFatalErrors); } - public static ParserResult Build( +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing type annotation", "IL2067")] + [UnconditionalSuppressMessage("Missing type annotation", "IL2072")] +#endif + public static ParserResult Build< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | + DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties)] +#endif + T>( Maybe> factory, Func, IEnumerable, Result, Error>> tokenizer, IEnumerable arguments, @@ -47,124 +69,144 @@ public static ParserResult Build( bool autoHelp, bool autoVersion, bool allowMultiInstance, - IEnumerable nonFatalErrors) { - var typeInfo = factory.MapValueOrDefault(f => f().GetType(), typeof(T)); + IEnumerable nonFatalErrors) + { + var typeInfo = GetTypeInfo(); var specProps = typeInfo.GetSpecifications(pi => SpecificationProperty.Create( Specification.FromProperty(pi), pi, Maybe.Nothing())) .Memoize(); - var specs = from pt in specProps select pt.Specification; + var specificationProperties = specProps as SpecificationProperty[] ?? specProps.ToArray(); + var specs = from pt in specificationProperties select pt.Specification; var optionSpecs = specs .ThrowingValidate(SpecificationGuards.Lookup) .OfType() .Memoize(); - Func makeDefault = () => - typeof(T).IsMutable() - ? factory.MapValueOrDefault(f => f(), () => Activator.CreateInstance()) - : ReflectionHelper.CreateDefaultImmutableInstance( - (from p in specProps select p.Specification.ConversionType).ToArray()); - Func, ParserResult> notParsed = - errs => new NotParsed(makeDefault().GetType().ToTypeInfo(), errs); + errs => new NotParsed(MakeDefault().GetType().ToTypeInfo(), errs); + + var enumerable = arguments as string[] ?? arguments.ToArray(); + var argumentsList = enumerable.Memoize().ToArray(); - var argumentsList = arguments.Memoize(); - Func> buildUp = () => + var preprocessorErrors = ( + argumentsList.Length != 0 + ? enumerable.Preprocess(PreprocessorGuards.Lookup(nameComparer, autoHelp, autoVersion)) + : Enumerable.Empty() + ).Memoize(); + + var errors = preprocessorErrors as Error[] ?? preprocessorErrors.ToArray(); + var result = argumentsList.Length != 0 + ? errors.Length != 0 + ? notParsed(errors) + : BuildUp() + : BuildUp(); + + return result; + + ParserResult BuildUp() { - var tokenizerResult = tokenizer(argumentsList, optionSpecs); + var optionSpecifications = optionSpecs as OptionSpecification[] ?? optionSpecs.ToArray(); + var tokenizerResult = tokenizer(argumentsList, optionSpecifications); var tokens = tokenizerResult.SucceededWith().Memoize(); - var partitions = TokenPartitioner.Partition( - tokens, - name => TypeLookup.FindTypeDescriptorAndSibling(name, optionSpecs, nameComparer)); + var partitions = TokenPartitioner.Partition(tokens, + name => TypeLookup.FindTypeDescriptorAndSibling(name, optionSpecifications, nameComparer)); var optionsPartition = partitions.Item1.Memoize(); var valuesPartition = partitions.Item2.Memoize(); var errorsPartition = partitions.Item3.Memoize(); - var optionSpecPropsResult = - OptionMapper.MapValues( - (from pt in specProps where pt.Specification.IsOption() select pt), - optionsPartition, - (vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, parsingCulture, ignoreValueCase), - nameComparer); + var optionSpecPropsResult = OptionMapper.MapValues( + (from pt in specificationProperties where pt.Specification.IsOption() select pt), optionsPartition, + (vals, type, isScalar, isFlag) => + TypeConverter.ChangeType(vals, type, isScalar, isFlag, parsingCulture, ignoreValueCase), + nameComparer); - var valueSpecPropsResult = - ValueMapper.MapValues( - (from pt in specProps where pt.Specification.IsValue() orderby ((ValueSpecification)pt.Specification).Index select pt), - valuesPartition, - (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, false, parsingCulture, ignoreValueCase)); + var valueSpecPropsResult = ValueMapper.MapValues( + (from pt in specificationProperties + where pt.Specification.IsValue() + orderby ((ValueSpecification)pt.Specification).Index + select pt), valuesPartition, + (vals, type, isScalar) => + TypeConverter.ChangeType(vals, type, isScalar, false, parsingCulture, ignoreValueCase)); var missingValueErrors = from token in errorsPartition - select - new MissingValueOptionError( - optionSpecs.Single(o => token.Text.MatchName(o.ShortName, o.LongName, nameComparer)) - .FromOptionSpecification()); + select new MissingValueOptionError(optionSpecifications + .Single(o => token.Text.MatchName(o.ShortName, o.LongName, nameComparer)) + .FromOptionSpecification()); - var specPropsWithValue = - optionSpecPropsResult.SucceededWith().Concat(valueSpecPropsResult.SucceededWith()).Memoize(); + var specPropsWithValue = optionSpecPropsResult.SucceededWith() + .Concat(valueSpecPropsResult.SucceededWith()).Memoize(); var setPropertyErrors = new List(); //build the instance, determining if the type is mutable or not. - T instance; - if(typeInfo.IsMutable() == true) - { - instance = BuildMutable(factory, specPropsWithValue, setPropertyErrors); - } - else - { - instance = BuildImmutable(typeInfo, factory, specProps, specPropsWithValue, setPropertyErrors); - } - - var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens, allowMultiInstance)); - - var allErrors = - tokenizerResult.SuccessMessages() - .Concat(missingValueErrors) - .Concat(optionSpecPropsResult.SuccessMessages()) - .Concat(valueSpecPropsResult.SuccessMessages()) - .Concat(validationErrors) - .Concat(setPropertyErrors) - .Memoize(); + var propsWithValue = specPropsWithValue as SpecificationProperty[] ?? specPropsWithValue.ToArray(); + var instance = typeInfo.IsMutable() == true + ? BuildMutable(factory, propsWithValue, setPropertyErrors) + : BuildImmutable(typeInfo, factory, specificationProperties, propsWithValue, setPropertyErrors); + + var validationErrors = + propsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens, allowMultiInstance)); + + var allErrors = tokenizerResult.SuccessMessages() + .Concat(missingValueErrors) + .Concat(optionSpecPropsResult.SuccessMessages()) + .Concat(valueSpecPropsResult.SuccessMessages()) + .Concat(validationErrors) + .Concat(setPropertyErrors) + .Memoize(); var warnings = from e in allErrors where nonFatalErrors.Contains(e.Tag) select e; return allErrors.Except(warnings).ToParserResult(instance); - }; - - var preprocessorErrors = ( - argumentsList.Any() - ? arguments.Preprocess(PreprocessorGuards.Lookup(nameComparer, autoHelp, autoVersion)) - : Enumerable.Empty() - ).Memoize(); - - var result = argumentsList.Any() - ? preprocessorErrors.Any() - ? notParsed(preprocessorErrors) - : buildUp() - : buildUp(); + } - return result; + T MakeDefault() => + typeof(T).IsMutable() + ? factory.MapValueOrDefault(f => f(), () => Activator.CreateInstance()) + : ReflectionHelper.CreateDefaultImmutableInstance( + (from p in specificationProperties select p.Specification.ConversionType).ToArray()); + +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing type annotation", "IL2111")] + [UnconditionalSuppressMessage("Missing type annotation", "IL2026")] + [UnconditionalSuppressMessage("Missing type annotation", "IL2073")] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.Interfaces)] +#endif + Type GetTypeInfo() + { + return factory.MapValueOrDefault(f => f().GetType(), typeof(T)); + } } - private static T BuildMutable(Maybe> factory, IEnumerable specPropsWithValue, List setPropertyErrors ) + private static T BuildMutable< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +#endif + T>( + Maybe> factory, + IEnumerable specPropsWithValue, + List setPropertyErrors) { - var mutable = factory.MapValueOrDefault(f => f(), () => Activator.CreateInstance()); + var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance); + var specificationProperties = specPropsWithValue as SpecificationProperty[] ?? specPropsWithValue.ToArray(); setPropertyErrors.AddRange( mutable.SetProperties( - specPropsWithValue, - sp => sp.Value.IsJust(), + specificationProperties, + sp => sp.Value.IsJust(), sp => sp.Value.FromJustOrFail() ) ); setPropertyErrors.AddRange( mutable.SetProperties( - specPropsWithValue, + specificationProperties, sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(), sp => sp.Specification.DefaultValue.FromJustOrFail() ) @@ -172,10 +214,10 @@ private static T BuildMutable(Maybe> factory, IEnumerable sp.Value.IsNothing() - && sp.Specification.TargetType == TargetType.Sequence - && sp.Specification.DefaultValue.MatchNothing(), + specificationProperties, + sp => sp.Value.IsNothing() + && sp.Specification.TargetType == TargetType.Sequence + && sp.Specification.DefaultValue.MatchNothing(), sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray() ) ); @@ -183,49 +225,73 @@ private static T BuildMutable(Maybe> factory, IEnumerable(Type typeInfo, Maybe> factory, IEnumerable specProps, IEnumerable specPropsWithValue, List setPropertyErrors) + private static T BuildImmutable( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +#endif + Type typeInfo, + Maybe> factory, + IEnumerable specProps, + IEnumerable specPropsWithValue, + List setPropertyErrors) { + var specificationProperties = specProps as SpecificationProperty[] ?? specProps.ToArray(); var ctor = typeInfo.GetTypeInfo().GetConstructor( - specProps.Select(sp => sp.Property.PropertyType).ToArray() + specificationProperties.Select(sp => sp.Property.PropertyType).ToArray() ); - if(ctor == null) + if (ctor == null) { - throw new InvalidOperationException($"Type {typeInfo.FullName} appears to be immutable, but no constructor found to accept values."); + throw new InvalidOperationException( + $"Type {typeInfo.FullName} appears to be immutable, but no constructor found to accept values."); } + + var propsWithValue = specPropsWithValue as SpecificationProperty[] ?? specPropsWithValue.ToArray(); try { var values = (from prms in ctor.GetParameters() - join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToLower() into spv + join sp in propsWithValue on prms.Name?.ToLower() equals sp.Property.Name.ToLower() into spv from sp in spv.DefaultIfEmpty() - select - sp == null - ? specProps.First(s => String.Equals(s.Property.Name, prms.Name, StringComparison.CurrentCultureIgnoreCase)) - .Property.PropertyType.GetDefaultValue() - : sp.Value.GetValueOrDefault( - sp.Specification.DefaultValue.GetValueOrDefault( - sp.Specification.ConversionType.CreateDefaultForImmutable()))).ToArray(); + select ExtractValue(sp, prms)).ToArray(); - var immutable = (T)ctor.Invoke(values); + var immutable = (T)ctor.Invoke(values); - return immutable; + return immutable; } catch (Exception) { - var ctorArgs = specPropsWithValue + var ctorArgs = propsWithValue .Select(x => x.Property.Name.ToLowerInvariant()).ToArray(); throw GetException(ctorArgs); } + +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing type annotation", "IL2072")] +#endif + object ExtractValue(SpecificationProperty sp, ParameterInfo prms) + { + return sp == null + ? specificationProperties.First(s => + string.Equals(s.Property.Name, prms.Name, + StringComparison.CurrentCultureIgnoreCase)) + .Property.PropertyType.GetDefaultValue() + : sp.Value.GetValueOrDefault( + sp.Specification.DefaultValue.GetValueOrDefault( + sp.Specification.ConversionType.CreateDefaultForImmutable())); + } + Exception GetException(string[] s) { - var ctorSyntax = s != null ? " Constructor Parameters can be ordered as: " + $"'({string.Join(", ", s)})'" : string.Empty; + var ctorSyntax = s != null + ? " Constructor Parameters can be ordered as: " + $"'({string.Join(", ", s)})'" + : string.Empty; var msg = $"Type {typeInfo.FullName} appears to be Immutable with invalid constructor. Check that constructor arguments have the same name and order of their underlying Type. {ctorSyntax}"; InvalidOperationException invalidOperationException = new InvalidOperationException(msg); return invalidOperationException; } } - } } diff --git a/src/CommandLine/Core/InstanceChooser.cs b/src/CommandLine/Core/InstanceChooser.cs index 72307bf2..e0c32bfa 100644 --- a/src/CommandLine/Core/InstanceChooser.cs +++ b/src/CommandLine/Core/InstanceChooser.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using CommandLine.Infrastructure; @@ -48,39 +49,48 @@ public static ParserResult Choose( bool allowMultiInstance, IEnumerable nonFatalErrors) { - var verbs = Verb.SelectFromTypes(types); + var types1 = types as Type[] ?? types.ToArray(); + var verbs = Verb.SelectFromTypes(types1).ToArray(); var defaultVerbs = verbs.Where(t => t.Item1.IsDefault); - int defaultVerbCount = defaultVerbs.Count(); + var enumerable = defaultVerbs as Tuple[] ?? defaultVerbs.ToArray(); + int defaultVerbCount = enumerable.Length; if (defaultVerbCount > 1) - return MakeNotParsed(types, new MultipleDefaultVerbsError()); + return MakeNotParsed(types1, new MultipleDefaultVerbsError()); - var defaultVerb = defaultVerbCount == 1 ? defaultVerbs.First() : null; + var defaultVerb = defaultVerbCount == 1 ? enumerable.First() : null; + + var arguments1 = arguments as string[] ?? arguments.ToArray(); ParserResult choose() { - var firstArg = arguments.First(); + var firstArg = arguments1.First(); bool preprocCompare(string command) => - nameComparer.Equals(command, firstArg) || - nameComparer.Equals(string.Concat("--", command), firstArg); + nameComparer.Equals(command, firstArg) || + nameComparer.Equals(string.Concat("--", command), firstArg); - return (autoHelp && preprocCompare("help")) - ? MakeNotParsed(types, + return autoHelp && preprocCompare("help") + ? MakeNotParsed(types1, MakeHelpVerbRequestedError(verbs, - arguments.Skip(1).FirstOrDefault() ?? string.Empty, nameComparer)) + arguments1.Skip(1).FirstOrDefault() ?? string.Empty, nameComparer)) : (autoVersion && preprocCompare("version")) - ? MakeNotParsed(types, new VersionRequestedError()) - : MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, allowMultiInstance, nonFatalErrors); + ? MakeNotParsed(types1, new VersionRequestedError()) + : MatchVerb(tokenizer, verbs, defaultVerb, arguments1, nameComparer, ignoreValueCase, + parsingCulture, autoHelp, autoVersion, allowMultiInstance, nonFatalErrors); } - return arguments.Any() + return arguments1.Length != 0 ? choose() : (defaultVerbCount == 1 - ? MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors) - : MakeNotParsed(types, new NoVerbSelectedError())); + ? MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments1, nameComparer, ignoreValueCase, + parsingCulture, autoHelp, autoVersion, nonFatalErrors) + : MakeNotParsed(types1, new NoVerbSelectedError())); } +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing annotations on type", "IL2072")] +#endif private static ParserResult MatchDefaultVerb( Func, IEnumerable, Result, Error>> tokenizer, IEnumerable> verbs, @@ -93,7 +103,7 @@ private static ParserResult MatchDefaultVerb( bool autoVersion, IEnumerable nonFatalErrors) { - return !(defaultVerb is null) + return defaultVerb is not null ? InstanceBuilder.Build( Maybe.Just>(() => defaultVerb.Item2.AutoDefault()), tokenizer, @@ -107,11 +117,14 @@ private static ParserResult MatchDefaultVerb( : MakeNotParsed(verbs.Select(v => v.Item2), new BadVerbSelectedError(arguments.First())); } +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing annotations on type", "IL2072")] +#endif private static ParserResult MatchVerb( Func, IEnumerable, Result, Error>> tokenizer, IEnumerable> verbs, Tuple defaultVerb, - IEnumerable arguments, + string[] arguments, StringComparer nameComparer, bool ignoreValueCase, CultureInfo parsingCulture, @@ -120,19 +133,21 @@ private static ParserResult MatchVerb( bool allowMultiInstance, IEnumerable nonFatalErrors) { - string firstArg = arguments.First(); + string firstArg = arguments[0]; var verbUsed = verbs.FirstOrDefault(vt => - nameComparer.Equals(vt.Item1.Name, firstArg) - || vt.Item1.Aliases.Any(alias => nameComparer.Equals(alias, firstArg)) + nameComparer.Equals(vt.Item1.Name, firstArg) + || vt.Item1.Aliases.Any(alias => nameComparer.Equals(alias, firstArg)) ); if (verbUsed == default) { - return MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); + return MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, + parsingCulture, autoHelp, autoVersion, nonFatalErrors); } + return InstanceBuilder.Build( - Maybe.Just>( + Maybe.Just( () => verbUsed.Item2.AutoDefault()), tokenizer, arguments.Skip(1), @@ -141,7 +156,7 @@ private static ParserResult MatchVerb( parsingCulture, autoHelp, autoVersion, - allowMultiInstance, + allowMultiInstance, nonFatalErrors); } @@ -152,10 +167,10 @@ private static HelpVerbRequestedError MakeHelpVerbRequestedError( { return verb.Length > 0 ? verbs.SingleOrDefault(v => nameComparer.Equals(v.Item1.Name, verb)) - .ToMaybe() - .MapValueOrDefault( - v => new HelpVerbRequestedError(v.Item1.Name, v.Item2, true), - new HelpVerbRequestedError(null, null, false)) + .ToMaybe() + .MapValueOrDefault( + v => new HelpVerbRequestedError(v.Item1.Name, v.Item2, true), + new HelpVerbRequestedError(null, null, false)) : new HelpVerbRequestedError(null, null, false); } diff --git a/src/CommandLine/Core/OptionSpecification.cs b/src/CommandLine/Core/OptionSpecification.cs index 1c2e4f88..ed9e4b49 100644 --- a/src/CommandLine/Core/OptionSpecification.cs +++ b/src/CommandLine/Core/OptionSpecification.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using CSharpx; @@ -16,11 +17,29 @@ sealed class OptionSpecification : Specification private readonly string group; private readonly bool flagCounter; - public OptionSpecification(string shortName, string longName, bool required, string setName, Maybe min, Maybe max, - char separator, Maybe defaultValue, string helpText, string metaValue, IEnumerable enumValues, - Type conversionType, TargetType targetType, string group, bool flagCounter = false, bool hidden = false) + public OptionSpecification( + string shortName, + string longName, + bool required, + string setName, + Maybe min, + Maybe max, + char separator, + Maybe defaultValue, + string helpText, + string metaValue, + IEnumerable enumValues, +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +#endif + Type conversionType, + TargetType targetType, + string group, + bool flagCounter = false, + bool hidden = false) : base(SpecificationType.Option, - required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, conversionType == typeof(int) && flagCounter ? TargetType.Switch : targetType, hidden) + required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, + conversionType == typeof(int) && flagCounter ? TargetType.Switch : targetType, hidden) { this.shortName = shortName; this.longName = longName; @@ -30,7 +49,13 @@ public OptionSpecification(string shortName, string longName, bool required, str this.flagCounter = flagCounter; } - public static OptionSpecification FromAttribute(OptionAttribute attribute, Type conversionType, IEnumerable enumValues) + public static OptionSpecification FromAttribute( + OptionAttribute attribute, +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +#endif + Type conversionType, + IEnumerable enumValues) { return new OptionSpecification( attribute.ShortName, @@ -51,10 +76,18 @@ public static OptionSpecification FromAttribute(OptionAttribute attribute, Type attribute.Hidden); } - public static OptionSpecification NewSwitch(string shortName, string longName, bool required, string helpText, string metaValue, bool hidden = false) + public static OptionSpecification NewSwitch( + string shortName, + string longName, + bool required, + string helpText, + string metaValue, + bool hidden = false) { - return new OptionSpecification(shortName, longName, required, string.Empty, Maybe.Nothing(), Maybe.Nothing(), - '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, string.Empty, false, hidden); + return new OptionSpecification(shortName, longName, required, string.Empty, Maybe.Nothing(), + Maybe.Nothing(), + '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), + TargetType.Switch, string.Empty, false, hidden); } public string ShortName diff --git a/src/CommandLine/Core/ReflectionExtensions.cs b/src/CommandLine/Core/ReflectionExtensions.cs index 622e1e6e..d5d90f1b 100644 --- a/src/CommandLine/Core/ReflectionExtensions.cs +++ b/src/CommandLine/Core/ReflectionExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using CommandLine.Infrastructure; @@ -11,31 +12,57 @@ namespace CommandLine.Core { - static class ReflectionExtensions + internal static class ReflectionExtensions { - public static IEnumerable GetSpecifications(this Type type, Func selector) +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing type annotation", "IL2070")] +#endif + public static IEnumerable GetSpecifications( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.Interfaces)] +#endif + this Type type, + Func selector) { - return from pi in type.FlattenHierarchy().SelectMany(x => x.GetTypeInfo().GetProperties()) + return from pi in type.FlattenHierarchy().Select(x => x.GetTypeInfo()).SelectMany(x => x.GetProperties()) let attrs = pi.GetCustomAttributes(true) where attrs.OfType().Any() || attrs.OfType().Any() - group pi by pi.Name into g + group pi by pi.Name + into g select selector(g.First()); } - public static Maybe GetVerbSpecification(this Type type) + public static Maybe GetVerbSpecification( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.Interfaces)] +#endif + this Type type) { return (from attr in - type.FlattenHierarchy().SelectMany(x => x.GetTypeInfo().GetCustomAttributes(typeof(VerbAttribute), true)) + type.FlattenHierarchy() + .SelectMany(x => x.GetTypeInfo().GetCustomAttributes(typeof(VerbAttribute), true)) let vattr = (VerbAttribute)attr select vattr) - .SingleOrDefault() - .ToMaybe(); + .SingleOrDefault() + .ToMaybe(); } - public static Maybe> GetUsageData(this Type type) +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Accessed via reflection", "IL2070")] +#endif + public static Maybe> GetUsageData( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.Interfaces)] +#endif + this Type type) { return (from pi in type.FlattenHierarchy().SelectMany(x => x.GetTypeInfo().GetProperties()) @@ -46,37 +73,48 @@ select Tuple.Create(pi, (UsageAttribute)attrs.First())) .ToMaybe(); } - private static IEnumerable FlattenHierarchy(this Type type) + private static IEnumerable FlattenHierarchy( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.Interfaces)] +#endif + this Type type) { if (type == null) { yield break; } + yield return type; foreach (var @interface in type.SafeGetInterfaces()) { yield return @interface; } + foreach (var @interface in FlattenHierarchy(type.GetTypeInfo().BaseType)) { yield return @interface; } } - private static IEnumerable SafeGetInterfaces(this Type type) + private static Type[] SafeGetInterfaces( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + this Type type) { - return type == null ? Enumerable.Empty() : type.GetTypeInfo().GetInterfaces(); + return type.GetTypeInfo().GetInterfaces(); } public static TargetType ToTargetType(this Type type) { return type == typeof(bool) - ? TargetType.Switch - : type == typeof(string) - ? TargetType.Scalar - : type.IsArray || typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(type) - ? TargetType.Sequence - : TargetType.Scalar; + ? TargetType.Switch + : type == typeof(string) + ? TargetType.Scalar + : type.IsArray || typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(type) + ? TargetType.Sequence + : TargetType.Scalar; } public static IEnumerable SetProperties( @@ -97,7 +135,8 @@ private static IEnumerable SetValue(this SpecificationProperty specPro } catch (TargetInvocationException e) { - return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e.InnerException, value) }; + return new[] + { new SetValueExceptionError(specProp.Specification.FromSpecification(), e.InnerException, value) }; } catch (ArgumentException e) { @@ -110,7 +149,6 @@ private static IEnumerable SetValue(this SpecificationProperty specPro { return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e, value) }; } - } public static object CreateEmptyArray(this Type type) @@ -118,25 +156,38 @@ public static object CreateEmptyArray(this Type type) return Array.CreateInstance(type, 0); } - public static object GetDefaultValue(this Type type) +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing type annotation", "IL2067")] +#endif + internal static object GetDefaultValue(this Type type) { return type.IsValueType ? Activator.CreateInstance(type) : null; } - public static bool IsMutable(this Type type) +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Requires reflection on members", "IL2075")] +#endif + public static bool IsMutable( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.Interfaces)] +#endif + this Type type) { - if(type == typeof(object)) + if (type == typeof(object)) return true; // Find all inherited defined properties and fields on the type var inheritedTypes = type.GetTypeInfo().FlattenHierarchy().Select(i => i.GetTypeInfo()); - foreach (var inheritedType in inheritedTypes) + foreach (var inheritedType in inheritedTypes) { if ( - inheritedType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite) || + inheritedType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Any(p => p.CanWrite) || inheritedType.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any() - ) + ) { return true; } @@ -145,16 +196,29 @@ public static bool IsMutable(this Type type) return false; } - public static object CreateDefaultForImmutable(this Type type) +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing type annotation", "IL2070")] +#endif + public static object CreateDefaultForImmutable( + this Type type) { - if (type.GetTypeInfo().IsGenericType && type.GetTypeInfo().GetGenericTypeDefinition() == typeof(IEnumerable<>)) + if (type.GetTypeInfo().IsGenericType && + type.GetTypeInfo().GetGenericTypeDefinition() == typeof(IEnumerable<>)) { return type.GetTypeInfo().GetGenericArguments()[0].CreateEmptyArray(); } + return type.GetDefaultValue(); } - public static object AutoDefault(this Type type) +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing type annotation", "IL2067")] +#endif + public static object AutoDefault( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +#endif + this Type type) { if (type.IsMutable()) { @@ -162,7 +226,7 @@ public static object AutoDefault(this Type type) } var ctorTypes = type.GetSpecifications(pi => pi.PropertyType).ToArray(); - + return ReflectionHelper.CreateDefaultImmutableInstance(type, ctorTypes); } @@ -171,7 +235,13 @@ public static TypeInfo ToTypeInfo(this Type type) return TypeInfo.Create(type); } - public static object StaticMethod(this Type type, string name, params object[] args) + public static object StaticMethod( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] +#endif + this Type type, + string name, + params object[] args) { return type.GetTypeInfo().InvokeMember( name, @@ -181,7 +251,12 @@ public static object StaticMethod(this Type type, string name, params object[] a args); } - public static object StaticProperty(this Type type, string name) + public static object StaticProperty( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] +#endif + this Type type, + string name) { return type.GetTypeInfo().InvokeMember( name, @@ -191,7 +266,13 @@ public static object StaticProperty(this Type type, string name) new object[] { }); } - public static object InstanceProperty(this Type type, string name, object target) + public static object InstanceProperty( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] +#endif + this Type type, + string name, + object target) { return type.GetTypeInfo().InvokeMember( name, @@ -204,21 +285,23 @@ public static object InstanceProperty(this Type type, string name, object target public static bool IsPrimitiveEx(this Type type) { return - (type.GetTypeInfo().IsValueType && type != typeof(Guid)) - || type.GetTypeInfo().IsPrimitive - || new [] { - typeof(string) - ,typeof(decimal) - ,typeof(DateTime) - ,typeof(DateTimeOffset) - ,typeof(TimeSpan) - }.Contains(type) - || Convert.GetTypeCode(type) != TypeCode.Object; + (type.GetTypeInfo().IsValueType && type != typeof(Guid)) + || type.GetTypeInfo().IsPrimitive + || new[] + { + typeof(string), typeof(decimal), typeof(DateTime), typeof(DateTimeOffset), typeof(TimeSpan) + }.Contains(type) + || Convert.GetTypeCode(type) != TypeCode.Object; } - public static bool IsCustomStruct(this Type type) + public static bool IsCustomStruct( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] +#endif + this Type type) { - var isStruct = type.GetTypeInfo().IsValueType && !type.GetTypeInfo().IsPrimitive && !type.GetTypeInfo().IsEnum && type != typeof(Guid); + var isStruct = type.GetTypeInfo().IsValueType && !type.GetTypeInfo().IsPrimitive && + !type.GetTypeInfo().IsEnum && type != typeof(Guid); if (!isStruct) return false; var ctor = type.GetTypeInfo().GetConstructor(new[] { typeof(string) }); return ctor != null; diff --git a/src/CommandLine/Core/Specification.cs b/src/CommandLine/Core/Specification.cs index b95b998c..0d56efad 100644 --- a/src/CommandLine/Core/Specification.cs +++ b/src/CommandLine/Core/Specification.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using CommandLine.Infrastructure; @@ -33,13 +34,30 @@ abstract class Specification private readonly string helpText; private readonly string metaValue; private readonly IEnumerable enumValues; + /// This information is denormalized to decouple Specification from PropertyInfo. +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +#endif private readonly Type conversionType; + private readonly TargetType targetType; - protected Specification(SpecificationType tag, bool required, Maybe min, Maybe max, - Maybe defaultValue, string helpText, string metaValue, IEnumerable enumValues, - Type conversionType, TargetType targetType, bool hidden = false) + protected Specification( + SpecificationType tag, + bool required, + Maybe min, + Maybe max, + Maybe defaultValue, + string helpText, + string metaValue, + IEnumerable enumValues, +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +#endif + Type conversionType, + TargetType targetType, + bool hidden = false) { this.tag = tag; this.required = required; @@ -54,7 +72,7 @@ protected Specification(SpecificationType tag, bool required, Maybe min, Ma this.hidden = hidden; } - public SpecificationType Tag + public SpecificationType Tag { get { return tag; } } @@ -94,6 +112,9 @@ public IEnumerable EnumValues get { return enumValues; } } +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +#endif public Type ConversionType { get { return conversionType; } @@ -109,28 +130,34 @@ public bool Hidden get { return hidden; } } +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Reflection", "IL2072")] +#endif public static Specification FromProperty(PropertyInfo property) - { + { var attrs = property.GetCustomAttributes(true); - var oa = attrs.OfType(); - if (oa.Count() == 1) + var oa = attrs.OfType().ToArray(); + + var conversionType = property.PropertyType; + if (oa.Length == 1) { - var spec = OptionSpecification.FromAttribute(oa.Single(), property.PropertyType, - ReflectionHelper.GetNamesOfEnum(property.PropertyType)); + var spec = OptionSpecification.FromAttribute(oa.Single(), conversionType, + ReflectionHelper.GetNamesOfEnum(conversionType)); if (spec.ShortName.Length == 0 && spec.LongName.Length == 0) { return spec.WithLongName(property.Name.ToLowerInvariant()); } + return spec; } - var va = attrs.OfType(); - if (va.Count() == 1) + var va = attrs.OfType().ToArray(); + if (va.Length == 1) { - return ValueSpecification.FromAttribute(va.Single(), property.PropertyType, - property.PropertyType.GetTypeInfo().IsEnum - ? Enum.GetNames(property.PropertyType) + return ValueSpecification.FromAttribute(va.Single(), conversionType, + conversionType.GetTypeInfo().IsEnum + ? Enum.GetNames(conversionType) : Enumerable.Empty()); } diff --git a/src/CommandLine/Core/TokenPartitioner.cs b/src/CommandLine/Core/TokenPartitioner.cs index 4dc25f7f..d1dd5125 100644 --- a/src/CommandLine/Core/TokenPartitioner.cs +++ b/src/CommandLine/Core/TokenPartitioner.cs @@ -11,11 +11,12 @@ namespace CommandLine.Core static class TokenPartitioner { public static - Tuple>>, IEnumerable, IEnumerable> Partition( - IEnumerable tokens, - Func> typeLookup) + Tuple>>, IEnumerable, IEnumerable> + Partition( + IEnumerable tokens, + Func> typeLookup) { - IEqualityComparer tokenComparer = ReferenceEqualityComparer.Default; + IEqualityComparer tokenComparer = CommandLine.Infrastructure.ReferenceEqualityComparer.Default; var tokenList = tokens.Memoize(); var partitioned = PartitionTokensByType(tokenList, typeLookup); @@ -28,14 +29,15 @@ Tuple>>, IEnumerable t.Text), errors); } - public static Tuple, IEnumerable, IEnumerable, IEnumerable> PartitionTokensByType( + public static Tuple, IEnumerable, IEnumerable, IEnumerable> + PartitionTokensByType( IEnumerable tokens, Func> typeLookup) { @@ -63,26 +65,27 @@ public static Tuple, IEnumerable, IEnumerable, { switch (info.TargetType) { - case TargetType.Switch: - nameToken = null; - switchTokens.Add(token); - state = SequenceState.TokenSearch; - break; - case TargetType.Scalar: - nameToken = token; - scalarTokens.Add(nameToken); - state = SequenceState.ScalarTokenFound; - break; - case TargetType.Sequence: - nameToken = token; - if (! sequences.ContainsKey(nameToken)) - { - sequences[nameToken] = new List(); - count[nameToken] = 0; - max[nameToken] = info.MaxItems; - } - state = SequenceState.SequenceTokenFound; - break; + case TargetType.Switch: + nameToken = null; + switchTokens.Add(token); + state = SequenceState.TokenSearch; + break; + case TargetType.Scalar: + nameToken = token; + scalarTokens.Add(nameToken); + state = SequenceState.ScalarTokenFound; + break; + case TargetType.Sequence: + nameToken = token; + if (!sequences.ContainsKey(nameToken)) + { + sequences[nameToken] = new List(); + count[nameToken] = 0; + max[nameToken] = info.MaxItems; + } + + state = SequenceState.SequenceTokenFound; + break; } } else @@ -113,7 +116,8 @@ public static Tuple, IEnumerable, IEnumerable, break; case SequenceState.SequenceTokenFound: - if (sequences.TryGetValue(nameToken, out var sequence)) { + if (sequences.TryGetValue(nameToken, out var sequence)) + { if (max[nameToken].MatchJust(out int m) && count[nameToken] >= m) { // This sequence is completed, so this and any further values are non-option values @@ -149,14 +153,16 @@ public static Tuple, IEnumerable, IEnumerable, count[nameToken] = 0; max[nameToken] = Maybe.Nothing(); } + break; - } } } + } foreach (var kvp in sequences) { - if (kvp.Value.Empty()) { + if (kvp.Value.Empty()) + { nonOptionTokens.Add(kvp.Key); } else @@ -165,6 +171,7 @@ public static Tuple, IEnumerable, IEnumerable, sequenceTokens.AddRange(kvp.Value); } } + return Tuple.Create( (IEnumerable)switchTokens, (IEnumerable)scalarTokens, @@ -179,6 +186,5 @@ private enum SequenceState SequenceTokenFound, ScalarTokenFound, } - } } diff --git a/src/CommandLine/Core/TypeConverter.cs b/src/CommandLine/Core/TypeConverter.cs index 2e27af40..e680de97 100644 --- a/src/CommandLine/Core/TypeConverter.cs +++ b/src/CommandLine/Core/TypeConverter.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using CommandLine.Infrastructure; @@ -11,65 +12,107 @@ namespace CommandLine.Core { - static class TypeConverter + internal static class TypeConverter { - public static Maybe ChangeType(IEnumerable values, Type conversionType, bool scalar, bool isFlag, CultureInfo conversionCulture, bool ignoreValueCase) + public static Maybe ChangeType( + IEnumerable values, +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] +#endif + Type conversionType, + bool scalar, + bool isFlag, + CultureInfo conversionCulture, + bool ignoreValueCase) { return isFlag - ? ChangeTypeFlagCounter(values, conversionType, conversionCulture, ignoreValueCase) + ? ChangeTypeFlagCounter(values, conversionCulture, ignoreValueCase) : scalar ? ChangeTypeScalar(values.Last(), conversionType, conversionCulture, ignoreValueCase) : ChangeTypeSequence(values, conversionType, conversionCulture, ignoreValueCase); } - private static Maybe ChangeTypeSequence(IEnumerable values, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase) +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing annotations on type", "IL2072")] +#endif + private static Maybe ChangeTypeSequence( + IEnumerable values, +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] +#endif + Type conversionType, + CultureInfo conversionCulture, + bool ignoreValueCase) { var type = conversionType.GetTypeInfo() - .GetGenericArguments() - .SingleOrDefault() - .ToMaybe() - .FromJustOrFail( - new InvalidOperationException("Non scalar properties should be sequence of type IEnumerable.") + .GetGenericArguments() + .SingleOrDefault() + .ToMaybe() + .FromJustOrFail( + new InvalidOperationException( + "Non scalar properties should be sequence of type IEnumerable.") ); - var converted = values.Select(value => ChangeTypeScalar(value, type, conversionCulture, ignoreValueCase)); + var converted = values.Select(value => ChangeTypeScalar(value, type, conversionCulture, ignoreValueCase)) + .ToArray(); return converted.Any(a => a.MatchNothing()) ? Maybe.Nothing() : Maybe.Just(converted.Select(c => ((Just)c).Value).ToUntypedArray(type)); } - private static Maybe ChangeTypeScalar(string value, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase) + private static Maybe ChangeTypeScalar( + string value, +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] +#endif + Type conversionType, + CultureInfo conversionCulture, + bool ignoreValueCase) { var result = ChangeTypeScalarImpl(value, conversionType, conversionCulture, ignoreValueCase); - result.Match((_,__) => { }, e => e.First().RethrowWhenAbsentIn( + result.Match((_, __) => { }, e => e.First().RethrowWhenAbsentIn( new[] { typeof(InvalidCastException), typeof(FormatException), typeof(OverflowException) })); return result.ToMaybe(); } - private static Maybe ChangeTypeFlagCounter(IEnumerable values, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase) + private static Maybe ChangeTypeFlagCounter( + IEnumerable values, + CultureInfo conversionCulture, + bool ignoreValueCase) { - var converted = values.Select(value => ChangeTypeScalar(value, typeof(bool), conversionCulture, ignoreValueCase)); + var converted = values.Select(value => + ChangeTypeScalar(value, typeof(bool), conversionCulture, ignoreValueCase)).ToArray(); return converted.Any(maybe => maybe.MatchNothing()) ? Maybe.Nothing() : Maybe.Just((object)converted.Count(value => value.IsJust())); } - private static object ConvertString(string value, Type type, CultureInfo conversionCulture) + private static object ConvertString( + string value, +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] +#endif + Type type, + CultureInfo conversionCulture) { - try - { - return Convert.ChangeType(value, type, conversionCulture); - } - catch (InvalidCastException) - { - // Required for converting from string to TimeSpan because Convert.ChangeType can't - return System.ComponentModel.TypeDescriptor.GetConverter(type).ConvertFrom(null, conversionCulture, value); - } + return typeof(TimeSpan).IsAssignableFrom(type) + ? TimeSpan.Parse(value) + : Convert.ChangeType(value, type, conversionCulture); } - private static Result ChangeTypeScalarImpl(string value, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase) +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing annotations on type", "IL2072")] +#endif + private static Result ChangeTypeScalarImpl( + string value, +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] +#endif + Type conversionType, + CultureInfo conversionCulture, + bool ignoreValueCase) { Func changeType = () => { @@ -83,7 +126,7 @@ private static Result ChangeTypeScalarImpl(string value, Type isFsOption ? FSharpOptionHelper.GetUnderlyingType(conversionType) : #endif - Nullable.GetUnderlyingType(conversionType); + Nullable.GetUnderlyingType(conversionType); var type = getUnderlyingType() ?? conversionType; @@ -92,20 +135,23 @@ private static Result ChangeTypeScalarImpl(string value, Type #if !SKIP_FSHARP isFsOption ? FSharpOptionHelper.Some(type, ConvertString(value, type, conversionCulture)) : +#else + ConvertString(value, type, conversionCulture); #endif - ConvertString(value, type, conversionCulture); #if !SKIP_FSHARP Func empty = () => isFsOption ? FSharpOptionHelper.None(type) : null; #else Func empty = () => null; #endif - return (value == null) ? empty() : withValue(); + return value == null ? empty() : withValue(); }; return value.IsBooleanString() && conversionType == typeof(bool) - ? value.ToBoolean() : conversionType.GetTypeInfo().IsEnum - ? value.ToEnum(conversionType, ignoreValueCase) : safeChangeType(); + ? value.ToBoolean() + : conversionType.GetTypeInfo().IsEnum + ? value.ToEnum(conversionType, ignoreValueCase) + : safeChangeType(); }; Func makeType = () => @@ -117,7 +163,8 @@ private static Result ChangeTypeScalarImpl(string value, Type } catch (Exception) { - throw new FormatException("Destination conversion type must have a constructor that accepts a string."); + throw new FormatException( + "Destination conversion type must have a constructor that accepts a string."); } }; @@ -139,20 +186,19 @@ private static object ToEnum(this string value, Type conversionType, bool ignore { throw new FormatException(); } + if (IsDefinedEx(parsedValue)) { return parsedValue; } + throw new FormatException(); } private static bool IsDefinedEx(object enumValue) { char firstChar = enumValue.ToString()[0]; - if (Char.IsDigit(firstChar) || firstChar == '-') - return false; - - return true; + return !char.IsDigit(firstChar) && firstChar != '-'; } } } diff --git a/src/CommandLine/Core/ValueSpecification.cs b/src/CommandLine/Core/ValueSpecification.cs index bd90252e..00a1b65f 100644 --- a/src/CommandLine/Core/ValueSpecification.cs +++ b/src/CommandLine/Core/ValueSpecification.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using CSharpx; namespace CommandLine.Core @@ -11,16 +12,36 @@ sealed class ValueSpecification : Specification private readonly int index; private readonly string metaName; - public ValueSpecification(int index, string metaName, bool required, Maybe min, Maybe max, Maybe defaultValue, - string helpText, string metaValue, IEnumerable enumValues, - Type conversionType, TargetType targetType, bool hidden = false) - : base(SpecificationType.Value, required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden) + public ValueSpecification( + int index, + string metaName, + bool required, + Maybe min, + Maybe max, + Maybe defaultValue, + string helpText, + string metaValue, + IEnumerable enumValues, +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +#endif + Type conversionType, + TargetType targetType, + bool hidden = false) + : base(SpecificationType.Value, required, min, max, defaultValue, helpText, metaValue, enumValues, + conversionType, targetType, hidden) { this.index = index; this.metaName = metaName; } - public static ValueSpecification FromAttribute(ValueAttribute attribute, Type conversionType, IEnumerable enumValues) + public static ValueSpecification FromAttribute( + ValueAttribute attribute, +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +#endif + Type conversionType, + IEnumerable enumValues) { return new ValueSpecification( attribute.Index, @@ -44,7 +65,7 @@ public int Index public string MetaName { - get { return metaName;} + get { return metaName; } } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Infrastructure/CSharpx/Maybe.cs b/src/CommandLine/Infrastructure/CSharpx/Maybe.cs index 044bb681..35874dc7 100644 --- a/src/CommandLine/Infrastructure/CSharpx/Maybe.cs +++ b/src/CommandLine/Infrastructure/CSharpx/Maybe.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace CSharpx { #region Maybe Type + /// /// Discriminator for . /// @@ -39,9 +41,13 @@ protected Maybe(MaybeType tag) /// /// Type discriminator. /// - public MaybeType Tag { get { return tag; } } + public MaybeType Tag + { + get { return tag; } + } #region Basic Match Methods + /// /// Matches a value returning true and value itself via output parameter. /// @@ -58,8 +64,10 @@ public bool MatchNothing() { return Tag == MaybeType.Nothing; } + #endregion } + #endregion /// @@ -67,6 +75,8 @@ public bool MatchNothing() /// #if !CSX_MAYBE_INTERNAL public +#else + internal #endif sealed class Nothing : Maybe { @@ -82,7 +92,7 @@ internal Nothing() #if !CSX_MAYBE_INTERNAL public #endif - sealed class Just : Maybe + sealed class Just : Maybe { private readonly T value; @@ -110,6 +120,7 @@ public T Value static class Maybe { #region Value Case Constructors + /// /// Builds the empty case of . /// @@ -125,9 +136,11 @@ public static Just Just(T value) { return new Just(value); } + #endregion #region Monad + /// /// Inject a value into the monadic type. /// @@ -144,9 +157,11 @@ public static Maybe Bind(Maybe maybe, Func> func) T1 value1; return maybe.MatchJust(out value1) ? func(value1) : Maybe.Nothing(); } + #endregion #region Functor + /// /// Transforms an maybe value by using a specified mapping function. /// @@ -155,6 +170,7 @@ public static Maybe Map(Maybe maybe, Func func) T1 value1; return maybe.MatchJust(out value1) ? Maybe.Just(func(value1)) : Maybe.Nothing(); } + #endregion /// @@ -164,9 +180,11 @@ public static Maybe> Merge(Maybe first, Maybe seco { T1 value1; T2 value2; - if (first.MatchJust(out value1) && second.MatchJust(out value2)) { + if (first.MatchJust(out value1) && second.MatchJust(out value2)) + { return Maybe.Just(Tuple.Create(value1, value2)); } + return Maybe.Nothing>(); } @@ -193,16 +211,19 @@ public static Maybe FromEither(Either eith static class MaybeExtensions { #region Alternative Match Methods + /// /// Provides pattern matching using delegates. /// public static void Match(this Maybe maybe, Action ifJust, Action ifNothing) { T value; - if (maybe.MatchJust(out value)) { + if (maybe.MatchJust(out value)) + { ifJust(value); return; } + ifNothing(); } @@ -213,10 +234,12 @@ public static void Match(this Maybe> maybe, Action { T1 value1; T2 value2; - if (maybe.MatchJust(out value1, out value2)) { + if (maybe.MatchJust(out value1, out value2)) + { ifJust(value1, value2); return; } + ifNothing(); } @@ -226,15 +249,18 @@ public static void Match(this Maybe> maybe, Action public static bool MatchJust(this Maybe> maybe, out T1 value1, out T2 value2) { Tuple value; - if (maybe.MatchJust(out value)) { + if (maybe.MatchJust(out value)) + { value1 = value.Item1; value2 = value.Item2; return true; } + value1 = default(T1); value2 = default(T2); return false; } + #endregion /// @@ -263,6 +289,7 @@ public static Maybe Map(this Maybe maybe, Func func) } #region Linq Operators + /// /// Map operation compatible with Linq. /// @@ -283,19 +310,22 @@ public static Maybe SelectMany( { return maybe .Bind(sourceValue => - valueSelector(sourceValue) - .Map(resultValue => resultSelector(sourceValue, resultValue))); + valueSelector(sourceValue) + .Map(resultValue => resultSelector(sourceValue, resultValue))); } + #endregion #region Do Semantic + /// /// If contains a value executes an delegate over it. /// public static void Do(this Maybe maybe, Action action) { T value; - if (maybe.MatchJust(out value)) { + if (maybe.MatchJust(out value)) + { action(value); } } @@ -307,10 +337,12 @@ public static void Do(this Maybe> maybe, Action ac { T1 value1; T2 value2; - if (maybe.MatchJust(out value1, out value2)) { + if (maybe.MatchJust(out value1, out value2)) + { action(value1, value2); } } + #endregion /// @@ -335,9 +367,11 @@ public static bool IsNothing(this Maybe maybe) public static T FromJust(this Maybe maybe) { T value; - if (maybe.MatchJust(out value)) { + if (maybe.MatchJust(out value)) + { return value; } + return default(T); } @@ -346,10 +380,11 @@ public static T FromJust(this Maybe maybe) /// public static T FromJustOrFail(this Maybe maybe, Exception exceptionToThrow = null) { - T value; - if (maybe.MatchJust(out value)) { + if (maybe.MatchJust(out var value)) + { return value; } + throw exceptionToThrow ?? new ArgumentException("Value empty."); } @@ -365,7 +400,16 @@ public static T GetValueOrDefault(this Maybe maybe, T noneValue) /// /// If contains a values executes a mapping function over it, otherwise returns . /// - public static T2 MapValueOrDefault(this Maybe maybe, Func func, T2 noneValue) + public static T2 MapValueOrDefault< + T1, +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + T2>( + this Maybe maybe, + Func func, + T2 noneValue) { T1 value1; return maybe.MatchJust(out value1) ? func(value1) : noneValue; @@ -374,7 +418,8 @@ public static T2 MapValueOrDefault(this Maybe maybe, Func fu /// /// If contains a values executes a mapping function over it, otherwise returns the value from . /// - public static T2 MapValueOrDefault(this Maybe maybe, Func func, Func noneValueFactory) { + public static T2 MapValueOrDefault(this Maybe maybe, Func func, Func noneValueFactory) + { T1 value1; return maybe.MatchJust(out value1) ? func(value1) : noneValueFactory(); } @@ -385,10 +430,12 @@ public static T2 MapValueOrDefault(this Maybe maybe, Func fu public static IEnumerable ToEnumerable(this Maybe maybe) { T value; - if (maybe.MatchJust(out value)) { + if (maybe.MatchJust(out value)) + { return Enumerable.Empty().Concat(new[] { value }); } + return Enumerable.Empty(); } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs index b8bd1398..5532511c 100644 --- a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs +++ b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace CommandLine.Infrastructure @@ -7,7 +8,13 @@ internal class LocalizableAttributeProperty { private string _propertyName; private string _value; + +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] +#endif private Type _type; + private PropertyInfo _localizationPropertyInfo; public LocalizableAttributeProperty(string propertyName) @@ -25,6 +32,10 @@ public string Value } } +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties)] +#endif public Type ResourceType { set @@ -34,23 +45,42 @@ public Type ResourceType } } +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Reflection", "IL2072")] +#endif private string GetLocalizedValue() { - if (String.IsNullOrEmpty(_value) || _type == null) + if (string.IsNullOrEmpty(_value) || _type == null) return _value; - if (_localizationPropertyInfo == null) + if (_localizationPropertyInfo != null) { - // Static class IsAbstract - if (!_type.IsVisible) - throw new ArgumentException($"Invalid resource type '{_type.FullName}'! {_type.Name} is not visible for the parser! Change resources 'Access Modifier' to 'Public'", _propertyName); - PropertyInfo propertyInfo = _type.GetProperty(_value, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static); - if (propertyInfo == null || !propertyInfo.CanRead || (propertyInfo.PropertyType != typeof(string) && !propertyInfo.PropertyType.CanCast())) - throw new ArgumentException($"Invalid resource property name! Localized value: {_value}", _propertyName); - _localizationPropertyInfo = propertyInfo; + return _localizationPropertyInfo.GetValue(null, null).Cast(); } + // Static class IsAbstract + if (!_type.IsVisible) + throw new ArgumentException( + $"Invalid resource type '{_type.FullName}'! {_type.Name} is not visible for the parser! Change resources 'Access Modifier' to 'Public'", + _propertyName); + PropertyInfo propertyInfo = _type.GetProperty(_value, + BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static); + + bool IsStringable( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + Type type) => + type != typeof(string) && !type.CanCast(); + + if (propertyInfo == null || !propertyInfo.CanRead || IsStringable(propertyInfo.PropertyType)) + { + throw new ArgumentException($"Invalid resource property name! Localized value: {_value}", + _propertyName); + } + + _localizationPropertyInfo = propertyInfo; + return _localizationPropertyInfo.GetValue(null, null).Cast(); } } - } diff --git a/src/CommandLine/Infrastructure/ReflectionHelper.cs b/src/CommandLine/Infrastructure/ReflectionHelper.cs index 47fe70ea..18e2fd3b 100644 --- a/src/CommandLine/Infrastructure/ReflectionHelper.cs +++ b/src/CommandLine/Infrastructure/ReflectionHelper.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using CommandLine.Core; @@ -46,9 +47,9 @@ public static Maybe GetAttribute() if (_overrides != null) { return - _overrides.ContainsKey(typeof(TAttribute)) ? - Maybe.Just((TAttribute)_overrides[typeof(TAttribute)]) : - Maybe.Nothing(); + _overrides.ContainsKey(typeof(TAttribute)) + ? Maybe.Just((TAttribute)_overrides[typeof(TAttribute)]) + : Maybe.Nothing(); } var assembly = GetExecutingOrEntryAssembly(); @@ -82,23 +83,43 @@ public static bool IsFSharpOptionType(Type type) "Microsoft.FSharp.Core.FSharpOption`1", StringComparison.Ordinal); } - public static T CreateDefaultImmutableInstance(Type[] constructorTypes) + public static T CreateDefaultImmutableInstance< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] +#endif + T>(Type[] constructorTypes) { var t = typeof(T); return (T)CreateDefaultImmutableInstance(t, constructorTypes); } - public static object CreateDefaultImmutableInstance(Type type, Type[] constructorTypes) + public static object CreateDefaultImmutableInstance( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +#endif + Type type, + Type[] constructorTypes) { var ctor = type.GetTypeInfo().GetConstructor(constructorTypes); if (ctor == null) { - throw new InvalidOperationException($"Type {type.FullName} appears to be immutable, but no constructor found to accept values."); + throw new InvalidOperationException( + $"Type {type.FullName} appears to be immutable, but no constructor found to accept values."); } - var values = (from prms in ctor.GetParameters() - select prms.ParameterType.CreateDefaultForImmutable()).ToArray(); + var objects = (from prms in ctor.GetParameters() + select GetDefaultValue(prms.ParameterType)).ToArray(); + var values = objects; return ctor.Invoke(values); + +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Reflection instantiation", "IL2072")] +#endif + object GetDefaultValue(Type t) + { + return t.CreateDefaultForImmutable(); + } } private static Assembly GetExecutingOrEntryAssembly() @@ -108,7 +129,7 @@ private static Assembly GetExecutingOrEntryAssembly() return Assembly.GetEntryAssembly() ?? Assembly.GetCallingAssembly(); } - public static IEnumerable GetNamesOfEnum(Type t) + public static IEnumerable GetNamesOfEnum(Type t) { if (t.IsEnum) return Enum.GetNames(t); diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index 4301aa52..a1ecca2b 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using CommandLine.Core; @@ -18,6 +19,7 @@ public class Parser : IDisposable { private bool disposed; private readonly ParserSettings settings; + private static readonly Lazy DefaultParser = new Lazy( () => new Parser(new ParserSettings { HelpWriter = Console.Error })); @@ -83,9 +85,23 @@ public ParserSettings Settings /// A containing an instance of type with parsed values /// and a sequence of . /// Thrown if one or more arguments are null. - public ParserResult ParseArguments(IEnumerable args) + public ParserResult ParseArguments< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | + DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.Interfaces)] +#endif + T>( + IEnumerable args) { - if (args == null) throw new ArgumentNullException("args"); +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(args); +#else + if (args == null) throw new ArgumentNullException(nameof(args)); +#endif var factory = typeof(T).IsMutable() ? Maybe.Just>(Activator.CreateInstance) @@ -116,11 +132,27 @@ public ParserResult ParseArguments(IEnumerable args) /// A containing an instance of type with parsed values /// and a sequence of . /// Thrown if one or more arguments are null. - public ParserResult ParseArguments(Func factory, IEnumerable args) + public ParserResult ParseArguments< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | + DynamicallyAccessedMemberTypes.Interfaces)] +#endif + T>( + Func factory, + IEnumerable args) { - if (factory == null) throw new ArgumentNullException("factory"); - if (!typeof(T).IsMutable()) throw new ArgumentException("factory"); - if (args == null) throw new ArgumentNullException("args"); +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(factory); + if (!typeof(T).IsMutable()) throw new ArgumentException(null, nameof(factory)); + ArgumentNullException.ThrowIfNull(args); +#else + if (factory == null) throw new ArgumentNullException(nameof(factory)); + if (!typeof(T).IsMutable()) throw new ArgumentException(null, nameof(factory)); + if (args == null) throw new ArgumentNullException(nameof(args)); +#endif return MakeParserResult( InstanceBuilder.Build( @@ -151,9 +183,14 @@ public ParserResult ParseArguments(Func factory, IEnumerable ar /// All types must expose a parameterless constructor. It's strongly recommended to use a generic overload. public ParserResult ParseArguments(IEnumerable args, params Type[] types) { - if (args == null) throw new ArgumentNullException("args"); - if (types == null) throw new ArgumentNullException("types"); - if (types.Length == 0) throw new ArgumentOutOfRangeException("types"); +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(args); + ArgumentNullException.ThrowIfNull(types); +#else + if (args == null) throw new ArgumentNullException(nameof(args)); + if (types == null) throw new ArgumentNullException(nameof(types)); +#endif + if (types.Length == 0) throw new ArgumentOutOfRangeException(nameof(types)); return MakeParserResult( InstanceChooser.Choose( @@ -181,9 +218,9 @@ public void Dispose() } private static Result, Error> Tokenize( - IEnumerable arguments, - IEnumerable optionSpecs, - ParserSettings settings) + IEnumerable arguments, + IEnumerable optionSpecs, + ParserSettings settings) { return settings.GetoptMode ? GetoptTokenizer.ConfigureTokenizer( @@ -197,7 +234,14 @@ private static Result, Error> Tokenize( settings.EnableDashDash)(arguments, optionSpecs); } - private static ParserResult MakeParserResult(ParserResult parserResult, ParserSettings settings) + private static ParserResult MakeParserResult< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties)] +#endif + T>( + ParserResult parserResult, + ParserSettings settings) { return DisplayHelp( parserResult, @@ -205,13 +249,21 @@ private static ParserResult MakeParserResult(ParserResult parserResult, settings.MaximumDisplayWidth); } - private static ParserResult DisplayHelp(ParserResult parserResult, TextWriter helpWriter, int maxDisplayWidth) + private static ParserResult DisplayHelp< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties)] +#endif + T>( + ParserResult parserResult, + TextWriter helpWriter, + int maxDisplayWidth) { parserResult.WithNotParsed( errors => Maybe.Merge(errors.ToMaybe(), helpWriter.ToMaybe()) .Do((_, writer) => writer.Write(HelpText.AutoBuild(parserResult, maxDisplayWidth))) - ); + ); return parserResult; } diff --git a/src/CommandLine/ParserExtensions.cs b/src/CommandLine/ParserExtensions.cs index 1c78ce9d..b602ee67 100644 --- a/src/CommandLine/ParserExtensions.cs +++ b/src/CommandLine/ParserExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace CommandLine { @@ -25,7 +26,7 @@ public static class ParserExtensions /// All types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2) }); } @@ -46,7 +47,7 @@ public static ParserResult ParseArguments(this Parser parser, IE /// All types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3) }); } @@ -68,7 +69,7 @@ public static ParserResult ParseArguments(this Parser parser /// All types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }); } @@ -91,7 +92,7 @@ public static ParserResult ParseArguments(this Parser pa /// All types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }); } @@ -115,7 +116,7 @@ public static ParserResult ParseArguments(this Parse /// All types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6) }); } @@ -140,7 +141,7 @@ public static ParserResult ParseArguments(this P /// All types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7) }); } @@ -166,7 +167,7 @@ public static ParserResult ParseArguments(th /// All types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8) }); } @@ -193,7 +194,7 @@ public static ParserResult ParseArgumentsAll types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9) }); @@ -222,7 +223,7 @@ public static ParserResult ParseArgumentsAll types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10) }); @@ -252,7 +253,7 @@ public static ParserResult ParseArgumentsAll types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11) }); @@ -283,7 +284,7 @@ public static ParserResult ParseArgumentsAll types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12) }); @@ -315,7 +316,7 @@ public static ParserResult ParseArgumentsAll types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13) }); @@ -348,7 +349,7 @@ public static ParserResult ParseArgumentsAll types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14) }); @@ -382,7 +383,7 @@ public static ParserResult ParseArgumentsAll types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15) }); @@ -417,10 +418,10 @@ public static ParserResult ParseArgumentsAll types must expose a parameterless constructor. public static ParserResult ParseArguments(this Parser parser, IEnumerable args) { - if (parser == null) throw new ArgumentNullException("parser"); + if (parser == null) throw new ArgumentNullException(nameof(parser)); return parser.ParseArguments(args, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16) }); } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index f5e9a7b9..bc0f90bb 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -2,12 +2,11 @@ using CommandLine.Core; using CommandLine.Infrastructure; - using CSharpx; - using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; @@ -19,9 +18,6 @@ namespace CommandLine.Text /// Provides means to format an help screen. /// You can assign it in place of a instance. /// - - - public struct ComparableOption { public bool Required; @@ -34,7 +30,6 @@ public struct ComparableOption public class HelpText { - #region ordering ComparableOption ToComparableOption(Specification spec, int index) @@ -57,48 +52,52 @@ ComparableOption ToComparableOption(Specification spec, int index) public Comparison OptionComparison { get; set; } = null; - public static Comparison RequiredThenAlphaComparison = (ComparableOption attr1, ComparableOption attr2) => - { - if (attr1.IsOption && attr2.IsOption) - { - if (attr1.Required && !attr2.Required) - { - return -1; - } - else if (!attr1.Required && attr2.Required) - { - return 1; - } - - return String.Compare(attr1.LongName, attr2.LongName, StringComparison.Ordinal); - - } - else if (attr1.IsOption && attr2.IsValue) - { - return -1; - } - else - { - return 1; - } - }; + public static Comparison RequiredThenAlphaComparison = + (ComparableOption attr1, ComparableOption attr2) => + { + if (attr1.IsOption && attr2.IsOption) + { + if (attr1.Required && !attr2.Required) + { + return -1; + } + else if (!attr1.Required && attr2.Required) + { + return 1; + } + + return string.Compare(attr1.LongName, attr2.LongName, StringComparison.Ordinal); + } + else if (attr1.IsOption && attr2.IsValue) + { + return -1; + } + else + { + return 1; + } + }; #endregion private const int BuilderCapacity = 128; private const int DefaultMaximumLength = 80; // default console width + /// /// The number of spaces between an option and its associated help text /// private const int OptionToHelpTextSeparatorWidth = 4; + /// /// The width of the option prefix (either "--" or " " /// private const int OptionPrefixWidth = 2; + /// /// The total amount of extra space that needs to accounted for when indenting Option help text /// private const int TotalOptionPadding = OptionToHelpTextSeparatorWidth + OptionPrefixWidth; + private readonly StringBuilder preOptionsHelp; private readonly StringBuilder postOptionsHelp; private readonly SentenceBuilder sentenceBuilder; @@ -122,7 +121,7 @@ public HelpText() } /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// specifying the sentence builder. /// /// @@ -195,6 +194,7 @@ public HelpText(SentenceBuilder sentenceBuilder, string heading, string copyrigh { maximumDisplayWidth = DefaultMaximumLength; } + this.sentenceBuilder = sentenceBuilder; this.heading = heading; this.copyright = copyright; @@ -317,7 +317,12 @@ public SentenceBuilder SentenceBuilder /// If true the output style is consistent with verb commands (no dashes), otherwise it outputs options. /// The maximum width of the display. /// The parameter is not ontly a metter of formatting, it controls whether to handle verbs or options. - public static HelpText AutoBuild( + public static HelpText AutoBuild< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + T>( ParserResult parserResult, Func onError, Func onExample, @@ -377,7 +382,7 @@ public static HelpText AutoBuild( lines => auto.AddPreOptionsLines(lines)); if ((verbsIndex && parserResult.TypeInfo.Choices.Any()) - || errors.Any(e => e.Tag == ErrorType.NoVerbSelectedError)) + || errors.Any(e => e.Tag == ErrorType.NoVerbSelectedError)) { auto.AddDashesToOption = false; auto.AddVerbs(parserResult.TypeInfo.Choices.ToArray()); @@ -399,7 +404,12 @@ public static HelpText AutoBuild( /// /// This feature is meant to be invoked automatically by the parser, setting the HelpWriter property /// of . - public static HelpText AutoBuild(ParserResult parserResult, int maxDisplayWidth = DefaultMaximumLength) + public static HelpText AutoBuild< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + T>(ParserResult parserResult, int maxDisplayWidth = DefaultMaximumLength) { return AutoBuild(parserResult, h => h, maxDisplayWidth); } @@ -416,17 +426,26 @@ public static HelpText AutoBuild(ParserResult parserResult, int maxDisplay /// /// This feature is meant to be invoked automatically by the parser, setting the HelpWriter property /// of . - public static HelpText AutoBuild(ParserResult parserResult, Func onError, int maxDisplayWidth = DefaultMaximumLength) + public static HelpText AutoBuild< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + T>( + ParserResult parserResult, + Func onError, + int maxDisplayWidth = DefaultMaximumLength) { if (parserResult.Tag != ParserResultType.NotParsed) throw new ArgumentException("Excepting NotParsed type.", "parserResult"); - var errors = ((NotParsed)parserResult).Errors; + var errors = ((NotParsed)parserResult).Errors.ToArray(); if (errors.Any(e => e.Tag == ErrorType.VersionRequestedError)) - return new HelpText($"{HeadingInfo.Default}{Environment.NewLine}") { MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine); + return new HelpText($"{HeadingInfo.Default}{Environment.NewLine}") + { MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine); - if (!errors.Any(e => e.Tag == ErrorType.HelpVerbRequestedError)) + if (errors.All(e => e.Tag != ErrorType.HelpVerbRequestedError)) return AutoBuild(parserResult, current => { onError?.Invoke(current); @@ -436,7 +455,7 @@ public static HelpText AutoBuild(ParserResult parserResult, Func().Single(); var pr = new NotParsed(TypeInfo.Create(err.Type), new Error[] { err }); return err.Matched - ? AutoBuild(pr, current => + ? AutoBuild(pr, current => { onError?.Invoke(current); return DefaultParsingErrorsHandler(pr, current); @@ -556,17 +575,38 @@ public HelpText AddPostOptionsText(string text) /// /// A parsing computation result. /// Thrown when parameter is null. - public HelpText AddOptions(ParserResult result) + public HelpText AddOptions< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + T>(ParserResult result) { - if (result == null) throw new ArgumentNullException("result"); +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(result); +#else + if (result == null) throw new ArgumentNullException(nameof(result)); +#endif + + var currentType = GetCurrentType(result.TypeInfo); return AddOptionsImpl( - GetSpecificationsFromType(result.TypeInfo.Current), + GetSpecificationsFromType(currentType).ToArray(), SentenceBuilder.RequiredWord(), SentenceBuilder.OptionGroupWord(), MaximumDisplayWidth); } +#if NET8_0_OR_GREATER + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.Interfaces)] + [UnconditionalSuppressMessage("Requires member reflection", "IL2073")] +#endif + private static Type GetCurrentType(TypeInfo typeInfo) + { + return typeInfo.Current; + } + /// /// Adds a text block with verbs usage string. /// @@ -579,7 +619,7 @@ public HelpText AddVerbs(params Type[] types) if (types.Length == 0) throw new ArgumentOutOfRangeException("types"); return AddOptionsImpl( - AdaptVerbsToSpecifications(types), + AdaptVerbsToSpecifications(types).ToArray(), SentenceBuilder.RequiredWord(), SentenceBuilder.OptionGroupWord(), MaximumDisplayWidth); @@ -590,13 +630,16 @@ public HelpText AddVerbs(params Type[] types) /// /// The maximum length of the help screen. /// A parsing computation result. - /// Thrown when parameter is null. + /// Thrown when parameter is null. public HelpText AddOptions(int maximumLength, ParserResult result) { - if (result == null) throw new ArgumentNullException("result"); - +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(result); +#else + if (result == null) throw new ArgumentNullException(nameof(result)); +#endif return AddOptionsImpl( - GetSpecificationsFromType(result.TypeInfo.Current), + GetSpecificationsFromType(GetCurrentType(result.TypeInfo)).ToArray(), SentenceBuilder.RequiredWord(), SentenceBuilder.OptionGroupWord(), maximumLength); @@ -615,7 +658,7 @@ public HelpText AddVerbs(int maximumLength, params Type[] types) if (types.Length == 0) throw new ArgumentOutOfRangeException("types"); return AddOptionsImpl( - AdaptVerbsToSpecifications(types), + AdaptVerbsToSpecifications(types).ToArray(), SentenceBuilder.RequiredWord(), SentenceBuilder.OptionGroupWord(), maximumLength); @@ -711,11 +754,17 @@ public static string RenderUsageText(ParserResult parserResult, FuncA parsing computation result. /// A mapping lambda normally used to translate text in other languages. /// Resulting formatted text. - public static IEnumerable RenderUsageTextAsLines(ParserResult parserResult, Func mapperFunc) + public static IEnumerable RenderUsageTextAsLines( + ParserResult parserResult, + Func mapperFunc) { - if (parserResult == null) throw new ArgumentNullException("parserResult"); +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(parserResult); +#else + if (parserResult == null) throw new ArgumentNullException(nameof(parserResult)); +#endif - var usage = GetUsageFromType(parserResult.TypeInfo.Current); + var usage = GetUsageFromType(GetCurrentType(parserResult.TypeInfo)); if (usage.MatchNothing()) yield break; @@ -757,32 +806,32 @@ public override string ToString() const int ExtraLength = 10; var sbLength = heading.SafeLength() + copyright.SafeLength() + preOptionsHelp.SafeLength() - + optionsHelp.SafeLength() + postOptionsHelp.SafeLength() + ExtraLength; + + optionsHelp.SafeLength() + postOptionsHelp.SafeLength() + ExtraLength; var result = new StringBuilder(sbLength); result.Append(heading) - .AppendWhen(!string.IsNullOrEmpty(copyright), - Environment.NewLine, - copyright) - .AppendWhen(preOptionsHelp.SafeLength() > 0, - NewLineIfNeededBefore(preOptionsHelp), - Environment.NewLine, - preOptionsHelp.ToString()) - .AppendWhen(optionsHelp.SafeLength() > 0, - Environment.NewLine, - Environment.NewLine, - optionsHelp.SafeToString()) - .AppendWhen(postOptionsHelp.SafeLength() > 0, - NewLineIfNeededBefore(postOptionsHelp), - Environment.NewLine, - postOptionsHelp.ToString()); + .AppendWhen(!string.IsNullOrEmpty(copyright), + Environment.NewLine, + copyright) + .AppendWhen(preOptionsHelp.SafeLength() > 0, + NewLineIfNeededBefore(preOptionsHelp), + Environment.NewLine, + preOptionsHelp.ToString()) + .AppendWhen(optionsHelp.SafeLength() > 0, + Environment.NewLine, + Environment.NewLine, + optionsHelp.SafeToString()) + .AppendWhen(postOptionsHelp.SafeLength() > 0, + NewLineIfNeededBefore(postOptionsHelp), + Environment.NewLine, + postOptionsHelp.ToString()); string NewLineIfNeededBefore(StringBuilder sb) { if (AddNewLineBetweenHelpSections - && result.Length > 0 - && !result.SafeEndsWith(Environment.NewLine) - && !sb.SafeStartsWith(Environment.NewLine)) + && result.Length > 0 + && !result.SafeEndsWith(Environment.NewLine) + && !sb.SafeStartsWith(Environment.NewLine)) return Environment.NewLine; else return null; @@ -798,15 +847,11 @@ internal static void AddLine(StringBuilder builder, string value, int maximumLen throw new ArgumentNullException(nameof(builder)); } - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - if (maximumLength < 1) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(value); + ArgumentOutOfRangeException.ThrowIfLessThan(maximumLength, 1); +#else +#endif value = value.TrimEnd(); @@ -814,9 +859,15 @@ internal static void AddLine(StringBuilder builder, string value, int maximumLen builder.Append(TextWrapper.WrapAndIndentText(value, 0, maximumLength)); } - private IEnumerable GetSpecificationsFromType(Type type) + private IEnumerable GetSpecificationsFromType( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + Type type) { - var specs = type.GetSpecifications(Specification.FromProperty); + var specs = type.GetSpecifications(Specification.FromProperty).ToArray(); var optionSpecs = specs .OfType(); if (autoHelp) @@ -831,7 +882,13 @@ private IEnumerable GetSpecificationsFromType(Type type) .Concat(valueSpecs); } - private static Maybe>> GetUsageFromType(Type type) + private static Maybe>> GetUsageFromType( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + Type type) { return type.GetUsageData().Map( tuple => @@ -840,7 +897,8 @@ private static Maybe>> GetUsageFromTy var attr = tuple.Item2; var examples = (IEnumerable)prop - .GetValue(null, BindingFlags.Public | BindingFlags.Static | BindingFlags.GetProperty, null, null, null); + .GetValue(null, BindingFlags.Public | BindingFlags.Static | BindingFlags.GetProperty, null, + null, null); return Tuple.Create(attr, examples); }); @@ -854,7 +912,9 @@ private IEnumerable AdaptVerbsToSpecifications(IEnumerable string.Empty, verbTuple.Item1.Name.Concat(verbTuple.Item1.Aliases).ToDelimitedString(", "), false, - verbTuple.Item1.IsDefault ? "(Default Verb) " + verbTuple.Item1.HelpText : verbTuple.Item1.HelpText, //Default verb + verbTuple.Item1.IsDefault + ? "(Default Verb) " + verbTuple.Item1.HelpText + : verbTuple.Item1.HelpText, //Default verb string.Empty, verbTuple.Item1.Hidden); if (autoHelp) @@ -865,7 +925,7 @@ private IEnumerable AdaptVerbsToSpecifications(IEnumerable } private HelpText AddOptionsImpl( - IEnumerable specifications, + Specification[] specifications, string requiredWord, string optionGroupWord, int maximumLength) @@ -873,7 +933,6 @@ private HelpText AddOptionsImpl( var maxLength = GetMaxLength(specifications); - optionsHelp = new StringBuilder(BuilderCapacity); var remainingSpace = maximumLength - (maxLength + TotalOptionPadding); @@ -900,7 +959,6 @@ private HelpText AddOptionsImpl( specifications.ForEach( option => AddOption(requiredWord, optionGroupWord, maxLength, option, remainingSpace)); - } return this; @@ -935,14 +993,19 @@ private HelpText AddPreOptionsLine(string value, int maximumLength) return this; } - private HelpText AddOption(string requiredWord, string optionGroupWord, int maxLength, Specification specification, int widthOfHelpText) + private HelpText AddOption( + string requiredWord, + string optionGroupWord, + int maxLength, + Specification specification, + int widthOfHelpText) { OptionSpecification GetOptionGroupSpecification() { if (specification.Tag == SpecificationType.Option && specification is OptionSpecification optionSpecification && optionSpecification.Group.Length > 0 - ) + ) { @@ -972,7 +1035,9 @@ specification is OptionSpecification optionSpecification && optionHelpText += " Valid values: " + string.Join(", ", specification.EnumValues); specification.DefaultValue.Do( - defaultValue => optionHelpText = "(Default: {0}) ".FormatInvariant(FormatDefaultValue(defaultValue)) + optionHelpText); + defaultValue => + optionHelpText = "(Default: {0}) ".FormatInvariant(FormatDefaultValue(defaultValue)) + + optionHelpText); var optionGroupSpecification = GetOptionGroupSpecification(); @@ -981,12 +1046,14 @@ specification is OptionSpecification optionSpecification && if (optionGroupSpecification != null) { - optionHelpText = "({0}: {1}) ".FormatInvariant(optionGroupWord, optionGroupSpecification.Group) + optionHelpText; + optionHelpText = "({0}: {1}) ".FormatInvariant(optionGroupWord, optionGroupSpecification.Group) + + optionHelpText; } - //note that we need to indent trim the start of the string because it's going to be + //note that we need to indent trim the start of the string because it's going to be //appended to an existing line that is as long as the indent-level - var indented = TextWrapper.WrapAndIndentText(optionHelpText, maxLength + TotalOptionPadding, widthOfHelpText).TrimStart(); + var indented = TextWrapper + .WrapAndIndentText(optionHelpText, maxLength + TotalOptionPadding, widthOfHelpText).TrimStart(); optionsHelp .Append(indented) @@ -1043,8 +1110,8 @@ private int GetMaxLength(IEnumerable specifications) if (spec.Hidden) return length; var specLength = spec.Tag == SpecificationType.Option - ? GetMaxOptionLength((OptionSpecification)spec) - : GetMaxValueLength((ValueSpecification)spec); + ? GetMaxOptionLength((OptionSpecification)spec) + : GetMaxValueLength((ValueSpecification)spec); return Math.Max(length, specLength); }); @@ -1128,8 +1195,5 @@ private static string FormatDefaultValue(T value) ? builder.ToString(0, builder.Length - 1) : string.Empty; } - - - } } diff --git a/src/CommandLine/UnParserExtensions.cs b/src/CommandLine/UnParserExtensions.cs index e823a7fa..bf63a74a 100644 --- a/src/CommandLine/UnParserExtensions.cs +++ b/src/CommandLine/UnParserExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using CommandLine.Core; @@ -57,6 +58,7 @@ public bool ShowHidden get { return showHidden; } set { PopsicleSetter.Set(Consumed, ref showHidden, value); } } + /// /// Gets or sets a value indicating whether unparsing process shall skip options with DefaultValue. /// @@ -65,6 +67,7 @@ public bool SkipDefault get { return skipDefault; } set { PopsicleSetter.Set(Consumed, ref skipDefault, value); } } + /// /// Factory method that creates an instance of with GroupSwitches set to true. /// @@ -92,31 +95,42 @@ public static UnParserSettings WithUseEqualTokenOnly() public static class UnParserExtensions { /// - /// Format a command line argument string from a parsed instance. + /// Format a command line argument string from a parsed instance. /// /// Type of . /// Parser instance. /// A parsed (or manually correctly constructed instance). /// A string with command line arguments. - public static string FormatCommandLine(this Parser parser, T options) + public static string FormatCommandLine< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.Interfaces)] +#endif + T>(this Parser parser, T options) { return parser.FormatCommandLine(options, config => { }); } /// - /// Format a command line argument string from a parsed instance in the form of string[]. + /// Format a command line argument string from a parsed instance in the form of string[]. /// /// Type of . /// Parser instance. /// A parsed (or manually correctly constructed instance). /// A string[] with command line arguments. - public static string[] FormatCommandLineArgs(this Parser parser, T options) + public static string[] FormatCommandLineArgs< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.Interfaces)] +#endif + T>(this Parser parser, T options) { return parser.FormatCommandLine(options, config => { }).SplitArgs(); } /// - /// Format a command line argument string from a parsed instance. + /// Format a command line argument string from a parsed instance. /// /// Type of . /// Parser instance. @@ -124,9 +138,24 @@ public static string[] FormatCommandLineArgs(this Parser parser, T options) /// The lambda used to configure /// aspects and behaviors of the unparsersing process. /// A string with command line arguments. - public static string FormatCommandLine(this Parser parser, T options, Action configuration) +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Missing annotations on method", "IL2072")] +#endif + public static string FormatCommandLine< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.Interfaces)] +#endif + T>( + this Parser parser, + T options, + Action configuration) { - if (options == null) throw new ArgumentNullException("options"); +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(options); +#else + if (options == null) throw new ArgumentNullException(nameof(options)); +#endif var settings = new UnParserSettings(); configuration(settings); @@ -140,22 +169,22 @@ public static string FormatCommandLine(this Parser parser, T options, Action< var specs = (from info in - type.GetSpecifications( - pi => new - { - Specification = Specification.FromProperty(pi), - Value = pi.GetValue(options, null).NormalizeValue(), - PropertyValue = pi.GetValue(options, null) - }) + type.GetSpecifications( + pi => new + { + Specification = Specification.FromProperty(pi), + Value = pi.GetValue(options, null).NormalizeValue(), + PropertyValue = pi.GetValue(options, null) + }) where !info.PropertyValue.IsEmpty(info.Specification, settings.SkipDefault) select info) - .Memoize(); + .Memoize(); var allOptSpecs = from info in specs.Where(i => i.Specification.Tag == SpecificationType.Option) let o = (OptionSpecification)info.Specification where o.TargetType != TargetType.Switch || - (o.TargetType == TargetType.Switch && o.FlagCounter && ((int)info.Value > 0)) || - (o.TargetType == TargetType.Switch && ((bool)info.Value)) + (o.TargetType == TargetType.Switch && o.FlagCounter && ((int)info.Value > 0)) || + (o.TargetType == TargetType.Switch && ((bool)info.Value)) where !o.Hidden || settings.ShowHidden orderby o.UniqueName() select info; @@ -178,7 +207,8 @@ orderby v.Index builder = settings.GroupSwitches && shortSwitches.Any() ? builder.Append('-').Append(string.Join(string.Empty, shortSwitches.Select( - info => { + info => + { var o = (OptionSpecification)info.Specification; return o.FlagCounter ? string.Concat(Enumerable.Repeat(o.ShortName, (int)info.Value)) @@ -190,7 +220,7 @@ orderby v.Index builder .Append(FormatOption((OptionSpecification)opt.Specification, opt.Value, settings)) .Append(' ') - ); + ); builder.AppendWhen(valSpecs.Any() && parser.Settings.EnableDashDash, "-- "); @@ -200,8 +230,9 @@ orderby v.Index return builder .ToString().TrimEnd(' '); } + /// - /// Format a command line argument string[] from a parsed instance. + /// Format a command line argument string[] from a parsed instance. /// /// Type of . /// Parser instance. @@ -209,10 +240,19 @@ orderby v.Index /// The lambda used to configure /// aspects and behaviors of the unparsersing process. /// A string[] with command line arguments. - public static string[] FormatCommandLineArgs(this Parser parser, T options, Action configuration) + public static string[] FormatCommandLineArgs< +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.Interfaces)] +#endif + T>( + this Parser parser, + T options, + Action configuration) { return FormatCommandLine(parser, options, configuration).SplitArgs(); } + private static string FormatValue(Specification spec, object value) { var builder = new StringBuilder(); @@ -231,6 +271,7 @@ private static string FormatValue(Specification spec, object value) builder.TrimEndIfMatch(sep); break; } + return builder.ToString(); } @@ -244,8 +285,9 @@ private static object FormatWithQuotesIfString(object value) => v.Contains("\"") ? v.Replace("\"", "\\\"") : v; return s.ToMaybe() - .MapValueOrDefault(v => v.Contains(' ') || v.Contains("\"") - ? "\"".JoinTo(doubQt(v), "\"") : v, value); + .MapValueOrDefault(v => v.Contains(' ') || v.Contains("\"") + ? "\"".JoinTo(doubQt(v), "\"") + : v, value); } private static char SeperatorOrSpace(this Specification spec) @@ -257,25 +299,28 @@ private static char SeperatorOrSpace(this Specification spec) private static string FormatOption(OptionSpecification spec, object value, UnParserSettings settings) { return new StringBuilder() - .Append(spec.FormatName(value, settings)) - .AppendWhen(spec.TargetType != TargetType.Switch, FormatValue(spec, value)) + .Append(spec.FormatName(value, settings)) + .AppendWhen(spec.TargetType != TargetType.Switch, FormatValue(spec, value)) .ToString(); } private static string FormatName(this OptionSpecification optionSpec, object value, UnParserSettings settings) { - // Have a long name and short name not preferred? Go with long! + // Have a long name and short name not preferred? Go with long! // No short name? Has to be long! var longName = (optionSpec.LongName.Length > 0 && !settings.PreferShortName) - || optionSpec.ShortName.Length == 0; + || optionSpec.ShortName.Length == 0; var formattedName = new StringBuilder(longName - ? "--".JoinTo(optionSpec.LongName) - : "-".JoinTo(optionSpec.ShortName)) - .AppendWhen(optionSpec.TargetType != TargetType.Switch, longName && settings.UseEqualToken ? "=" : " ") + ? "--".JoinTo(optionSpec.LongName) + : "-".JoinTo(optionSpec.ShortName)) + .AppendWhen(optionSpec.TargetType != TargetType.Switch, + longName && settings.UseEqualToken ? "=" : " ") .ToString(); - return optionSpec.FlagCounter ? String.Join(" ", Enumerable.Repeat(formattedName, (int)value)) : formattedName; + return optionSpec.FlagCounter + ? string.Join(" ", Enumerable.Repeat(formattedName, (int)value)) + : formattedName; } private static object NormalizeValue(this object value) @@ -291,7 +336,13 @@ private static object NormalizeValue(this object value) return value; } - private static bool IsEmpty(this object value, Specification specification, bool skipDefault) +#if NET8_0_OR_GREATER + [UnconditionalSuppressMessage("Types are safe", "IL2072")] +#endif + private static bool IsEmpty( + this object value, + Specification specification, + bool skipDefault) { if (value == null) return true; @@ -309,6 +360,7 @@ private static bool IsEmpty(this object value, Specification specification, bool #region splitter + /// /// Returns a string array that contains the substrings in this instance that are delimited by space considering string between double quote. /// @@ -335,6 +387,5 @@ public static string[] SplitArgs(this string command, bool keepQuote = false) } #endregion - } } diff --git a/src/CommandLine/VerbAttribute.cs b/src/CommandLine/VerbAttribute.cs index 6ee6024d..54ca746e 100644 --- a/src/CommandLine/VerbAttribute.cs +++ b/src/CommandLine/VerbAttribute.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace CommandLine { @@ -13,6 +14,11 @@ namespace CommandLine public class VerbAttribute : Attribute { private readonly Infrastructure.LocalizableAttributeProperty helpText; + +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties)] +#endif private Type resourceType; /// @@ -41,23 +47,24 @@ public VerbAttribute(string name, bool isDefault = false, string[] aliases = nul /// /// Gets or sets a value indicating whether a command line verb is visible in the help text. /// - public bool Hidden - { - get; - set; - } + public bool Hidden { get; set; } /// - /// Gets or sets a short description of this command line option. Usually a sentence summary. + /// Gets or sets a short description of this command line option. Usually a sentence summary. /// public string HelpText { get => helpText.Value ?? string.Empty; set => helpText.Value = value ?? throw new ArgumentNullException("value"); } + /// /// Gets or sets the that contains the resources for . /// +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties)] +#endif public Type ResourceType { get => resourceType; diff --git a/tests/CommandLine.Tests/CommandLine.Tests.csproj b/tests/CommandLine.Tests/CommandLine.Tests.csproj index d4dbcab0..05c560e0 100644 --- a/tests/CommandLine.Tests/CommandLine.Tests.csproj +++ b/tests/CommandLine.Tests/CommandLine.Tests.csproj @@ -2,7 +2,7 @@ Library - net461;netcoreapp3.1 + net8.0 $(DefineConstants);SKIP_FSHARP ..\..\CommandLine.snk true @@ -35,4 +35,4 @@ - \ No newline at end of file + diff --git a/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs index 337a9a3f..672c23dd 100644 --- a/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs @@ -87,10 +87,10 @@ public void Should_return_error_if_option_format_with_equals_is_not_correct() var result = GetoptTokenizer.Tokenize(args, name => NameLookupResult.OtherOptionFound); - var errors = result.SuccessMessages(); + var errors = result.SuccessMessages().ToArray(); Assert.NotNull(errors); - Assert.Equal(1, errors.Count()); + Assert.Single(errors); Assert.Equal(ErrorType.BadFormatTokenError, errors.First().Tag); var tokens = result.SucceededWith(); diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index b079ce0f..f114da4d 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -407,7 +407,7 @@ public void Explicit_version_request_generates_version_info_screen() // Verify outcome result.Length.Should().BeGreaterThan(0); var lines = result.ToNotEmptyLines().TrimStringArray(); - lines.Should().HaveCount(x => x == 1); + lines.Should().HaveCount(x => x == 1); lines[0].Should().Be(HeadingInfo.Default.ToString()); // Teardown } @@ -437,7 +437,7 @@ public void Implicit_help_screen_in_verb_scenario() lines[8].Should().BeEquivalentTo("version Display version information."); // Teardown } - + [Fact] public void Help_screen_in_default_verb_scenario() { @@ -448,7 +448,7 @@ public void Help_screen_in_default_verb_scenario() // Exercise system sut.ParseArguments(new string[] {"--help" }); var result = help.ToString(); - + // Verify outcome result.Length.Should().BeGreaterThan(0); var lines = result.ToNotEmptyLines().TrimStringArray(); @@ -459,7 +459,7 @@ public void Help_screen_in_default_verb_scenario() lines[4].Should().BeEquivalentTo("clone Clone a repository into a new directory."); lines[5].Should().BeEquivalentTo("help Display more information on a specific command."); lines[6].Should().BeEquivalentTo("version Display version information."); - + } [Fact] public void Double_dash_help_dispalys_verbs_index_in_verbs_scenario() @@ -558,7 +558,7 @@ public void Properly_formatted_help_screen_is_displayed_when_usage_is_defined_in config.MaximumDisplayWidth = 80; }); - // Exercize system + // Exercise system sut.ParseArguments( new[] { "clone", "--badoption=@bad?value" }); var result = help.ToString(); @@ -596,7 +596,7 @@ public void Properly_formatted_help_screen_is_displayed_when_there_is_a_hidden_v // Exercize system sut.ParseArguments(new string[] { }); var result = help.ToString(); - + // Verify outcome var lines = result.ToNotEmptyLines().TrimStringArray(); lines[0].Should().Be(HeadingInfo.Default.ToString()); @@ -620,7 +620,7 @@ public void Properly_formatted_help_screen_is_displayed_when_there_is_a_hidden_v // Exercize system sut.ParseArguments(new string[] { "secert", "--help" }); var result = help.ToString(); - + // Verify outcome var lines = result.ToNotEmptyLines().TrimStringArray(); lines[0].Should().Be(HeadingInfo.Default.ToString()); @@ -631,7 +631,7 @@ public void Properly_formatted_help_screen_is_displayed_when_there_is_a_hidden_v // Teardown } - + [Fact] public void Parse_options_when_given_hidden_verb() { @@ -642,7 +642,7 @@ public void Parse_options_when_given_hidden_verb() // Exercize system var result = sut.ParseArguments(new string[] { "secert", "--force" }); - + // Verify outcome result.Tag.Should().BeEquivalentTo(ParserResultType.Parsed); @@ -662,7 +662,7 @@ public void Parse_options_when_given_hidden_verb_with_hidden_option() // Exercize system var result = sut.ParseArguments(new string[] { "secert", "--force", "--secert-option", "shhh" }); - + // Verify outcome result.Tag.Should().BeEquivalentTo(ParserResultType.Parsed); result.GetType().Should().Be>(); @@ -783,13 +783,13 @@ public static void Breaking_mutually_exclusive_set_constraint_with_both_set_name }; var sut = new Parser(); - // Exercize system + // Exercize system var result = sut.ParseArguments( new[] { "--weburl", "value", "--somethingelse", "othervalue" }); // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - + } [Fact] @@ -867,7 +867,7 @@ public void Parse_verb_with_same_option_and_value_args() { var parser = Parser.Default; var result = parser.ParseArguments( - new[] { "test", "arg", "-o", "arg" }, + new[] { "test", "arg", "-o", "arg" }, typeof(Verb_With_Option_And_Value_Of_String_Type)); result .WithNotParsed(errors => { throw new InvalidOperationException("Must be parsed."); }) @@ -905,7 +905,7 @@ public void Blank_lines_are_inserted_between_verbs() // Exercize system sut.ParseArguments(new string[] { }); var result = help.ToString(); - + // Verify outcome var lines = result.ToLines().TrimStringArray(); lines[6].Should().BeEquivalentTo("add Add file contents to the index."); @@ -1018,7 +1018,7 @@ public void Parse_default_verb_with_empty_name() public void When_HelpWriter_is_null_it_should_not_fire_exception() { // Arrange - + //Act var sut = new Parser(config => config.HelpWriter = null); sut.ParseArguments(new[] {"--dummy"});