diff --git a/AutomaticInterface/AutomaticInterface/Builder.cs b/AutomaticInterface/AutomaticInterface/Builder.cs index 840657b..22e1d5d 100644 --- a/AutomaticInterface/AutomaticInterface/Builder.cs +++ b/AutomaticInterface/AutomaticInterface/Builder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -155,7 +156,7 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth var paramResult = new HashSet(); method - .Parameters.Select(x => x.ToDisplayString(FullyQualifiedDisplayFormat)) + .Parameters.Select(p => GetParameterDisplayString(p, codeGenerator.HasNullable)) .ToList() .ForEach(x => paramResult.Add(x)); @@ -226,6 +227,45 @@ private static bool IsNullable(ITypeSymbol typeSymbol) return false; } + private static string GetParameterDisplayString( + IParameterSymbol param, + bool nullableContextEnabled + ) + { + var paramParts = param.ToDisplayParts(FullyQualifiedDisplayFormat); + var typeSb = new StringBuilder(); + var restSb = new StringBuilder(); + var isInsideType = true; + // The part before the first space is the parameter type + foreach (var part in paramParts) + { + if (isInsideType && part.Kind == SymbolDisplayPartKind.Space) + { + isInsideType = false; + } + if (isInsideType) + { + typeSb.Append(part.ToString()); + } + else + { + restSb.Append(part.ToString()); + } + } + // If this parameter has default value null and we're enabling the nullable context, we need to force the nullable annotation if there isn't one already + if ( + param.HasExplicitDefaultValue + && param.ExplicitDefaultValue is null + && param.NullableAnnotation != NullableAnnotation.Annotated + && param.Type.IsReferenceType + && nullableContextEnabled + ) + { + typeSb.Append('?'); + } + return typeSb.Append(restSb).ToString(); + } + private static void AddEventsToInterface(List members, InterfaceBuilder codeGenerator) { members diff --git a/AutomaticInterface/Tests/Methods/Methods.WorksWithMixedOptionalNullParameters.verified.txt b/AutomaticInterface/Tests/Methods/Methods.WorksWithMixedOptionalNullParameters.verified.txt new file mode 100644 index 0000000..a8728ce --- /dev/null +++ b/AutomaticInterface/Tests/Methods/Methods.WorksWithMixedOptionalNullParameters.verified.txt @@ -0,0 +1,20 @@ +//-------------------------------------------------------------------------------------------------- +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. +// +//-------------------------------------------------------------------------------------------------- + +#nullable enable +namespace AutomaticInterfaceExample +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + bool TryStartTransaction(int? param, int param2 = 0, string? data = null); + + } +} +#nullable restore diff --git a/AutomaticInterface/Tests/Methods/Methods.cs b/AutomaticInterface/Tests/Methods/Methods.cs index 957f8e2..cc4f856 100644 --- a/AutomaticInterface/Tests/Methods/Methods.cs +++ b/AutomaticInterface/Tests/Methods/Methods.cs @@ -76,6 +76,29 @@ public bool TryStartTransaction(string data = null) await Verify(Infrastructure.GenerateCode(code)); } + [Fact] + public async Task WorksWithMixedOptionalNullParameters() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample; + + [GenerateAutomaticInterface] + public class DemoClass + { + public bool TryStartTransaction(int? param, int param2 = 0, string data = null) + { + return true; + } + } + + + """; + await Verify(Infrastructure.GenerateCode(code)); + } + [Fact] public async Task AddsPublicMethodToInterface() {