From 744662eb5431b08f7ca1ae94fe931e76cc476efb Mon Sep 17 00:00:00 2001 From: Simon McKenzie <17579442+simonmckenzie@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:42:07 +1100 Subject: [PATCH 1/4] Add the new() type constraint to method parameters This addresses #64, where type parameters weren't having the `new()` constraint applied. Also updated some tests that were failing due to changes in the `inheritdoc` annotations and re-ran csharpier. --- .../AutomaticInterface/Builder.cs | 20 ++++++++----------- .../AutomaticInterface/RoslynExtensions.cs | 6 ++++++ AutomaticInterface/Tests/GeneratorTests.cs | 15 +++++++------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/AutomaticInterface/AutomaticInterface/Builder.cs b/AutomaticInterface/AutomaticInterface/Builder.cs index 7db6a00..37832f8 100644 --- a/AutomaticInterface/AutomaticInterface/Builder.cs +++ b/AutomaticInterface/AutomaticInterface/Builder.cs @@ -63,12 +63,9 @@ private static string GetNameSpace(ISymbol typeSymbol) { var generationAttribute = typeSymbol .GetAttributes() - .FirstOrDefault( - x => - x.AttributeClass != null - && x.AttributeClass - .Name - .Contains(AutomaticInterfaceGenerator.DefaultAttributeName) + .FirstOrDefault(x => + x.AttributeClass != null + && x.AttributeClass.Name.Contains(AutomaticInterfaceGenerator.DefaultAttributeName) ); if (generationAttribute == null) @@ -298,12 +295,11 @@ private static PropertySetKind GetSetKind(IMethodSymbol? setMethodSymbol) private static bool HasIgnoreAttribute(ISymbol x) { return x.GetAttributes() - .Any( - a => - a.AttributeClass != null - && a.AttributeClass - .Name - .Contains(AutomaticInterfaceGenerator.IgnoreAutomaticInterfaceAttributeName) + .Any(a => + a.AttributeClass != null + && a.AttributeClass.Name.Contains( + AutomaticInterfaceGenerator.IgnoreAutomaticInterfaceAttributeName + ) ); } diff --git a/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs b/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs index 0174b84..b31827a 100644 --- a/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs +++ b/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs @@ -58,6 +58,12 @@ SymbolDisplayFormat typeDisplayFormat isFirstConstraint = false; } + if (typeParameterSymbol.HasConstructorConstraint) + { + constraints += "new()"; + isFirstConstraint = false; + } + foreach (var constraintType in typeParameterSymbol.ConstraintTypes) { // if not first constraint prepend with comma diff --git a/AutomaticInterface/Tests/GeneratorTests.cs b/AutomaticInterface/Tests/GeneratorTests.cs index d2c65d8..129f389 100644 --- a/AutomaticInterface/Tests/GeneratorTests.cs +++ b/AutomaticInterface/Tests/GeneratorTests.cs @@ -1053,11 +1053,12 @@ namespace AutomaticInterfaceExample; public class DemoClass { /// - public string CMethod(string x, string y) + public string CMethod(string x, string y) where T : class where T1 : struct where T3 : DemoClass where T4 : IDemoClass + where T5 : new() { return "Ok"; } @@ -1080,8 +1081,8 @@ namespace AutomaticInterfaceExample [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { - /// - string CMethod(string x, string y) where T : class where T1 : struct where T3 : global::AutomaticInterfaceExample.DemoClass where T4 : IDemoClass; + /// + string CMethod(string x, string y) where T : class where T1 : struct where T3 : global::AutomaticInterfaceExample.DemoClass where T4 : IDemoClass where T5 : new(); } } @@ -1820,10 +1821,10 @@ namespace AutomaticInterfaceExample [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { - /// + /// void AMethod(Func> getValue); - /// + /// void AMethod(Func> getValue); } @@ -1875,10 +1876,10 @@ namespace AutomaticInterfaceExample [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { - /// + /// void AMethod(Func> getValue); - /// + /// void AMethod(Func> getValue); } From 147cd5e0060eabba565f4179131f60ab3fec4fd8 Mon Sep 17 00:00:00 2001 From: Simon McKenzie <17579442+simonmckenzie@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:23:42 +1100 Subject: [PATCH 2/4] Add support for `params` parameters Small fix - the existing code wasn't emitting the "params" keyword --- .../AutomaticInterface/Builder.cs | 20 +++++---- AutomaticInterface/Tests/GeneratorTests.cs | 43 +++++++++++++++++++ 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/AutomaticInterface/AutomaticInterface/Builder.cs b/AutomaticInterface/AutomaticInterface/Builder.cs index 37832f8..6d24694 100644 --- a/AutomaticInterface/AutomaticInterface/Builder.cs +++ b/AutomaticInterface/AutomaticInterface/Builder.cs @@ -102,7 +102,7 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth ActivateNullableIfNeeded(codeGenerator, method); var paramResult = new HashSet(); - method.Parameters.Select(GetMethodSignature).ToList().ForEach(x => paramResult.Add(x)); + method.Parameters.Select(GetParameterSignature).ToList().ForEach(x => paramResult.Add(x)); var typedArgs = method .TypeParameters.Select(arg => @@ -194,16 +194,18 @@ private static void AddEventsToInterface(List members, InterfaceBuilder }); } - private static string GetMethodSignature(IParameterSymbol x) + private static string GetParameterSignature(IParameterSymbol x) { - var name = GetMethodName(x); - var refKindText = GetRefKind(x); - var optionalValue = GetMethodOptionalValue(x); + var name = GetParameterName(x); + var refKindText = GetParameterRefKind(x); + var optionalValue = GetParameterOptionalValue(x); - return $"{refKindText}{x.Type.ToDisplayString(FullyQualifiedDisplayFormat)} {name}{optionalValue}"; + var paramsPrefix = x.IsParams ? "params " : ""; + + return $"{paramsPrefix}{refKindText}{x.Type.ToDisplayString(FullyQualifiedDisplayFormat)} {name}{optionalValue}"; } - private static string GetMethodOptionalValue(IParameterSymbol x) + private static string GetParameterOptionalValue(IParameterSymbol x) { if (!x.HasExplicitDefaultValue) { @@ -222,7 +224,7 @@ null when x.Type.IsValueType }; } - private static string GetMethodName(IParameterSymbol x) + private static string GetParameterName(IParameterSymbol x) { var syntaxReference = x.DeclaringSyntaxReferences.FirstOrDefault(); @@ -231,7 +233,7 @@ private static string GetMethodName(IParameterSymbol x) : x.Name; } - private static string GetRefKind(IParameterSymbol x) + private static string GetParameterRefKind(IParameterSymbol x) { return x.RefKind switch { diff --git a/AutomaticInterface/Tests/GeneratorTests.cs b/AutomaticInterface/Tests/GeneratorTests.cs index 129f389..1ee64b7 100644 --- a/AutomaticInterface/Tests/GeneratorTests.cs +++ b/AutomaticInterface/Tests/GeneratorTests.cs @@ -97,6 +97,49 @@ public partial interface IDemoClass GenerateCode(code).Should().Be(expected); } + [Fact] + public void WorksWithParamsParameters() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample; + + [GenerateAutomaticInterface] + public class DemoClass + { + public void AMethod(params int[] numbers) + { + } + } + + + """; + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + namespace AutomaticInterfaceExample + { + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + void AMethod(params int[] numbers); + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + [Fact] public void WorksWithOptionalStructParameters() { From b978bae42226bad7559b5c80aa0afa449d0b192c Mon Sep 17 00:00:00 2001 From: Simon McKenzie <17579442+simonmckenzie@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:59:00 +1100 Subject: [PATCH 3/4] Increment the package version; update changelog --- .../AutomaticInterface/AutomaticInterface.csproj | 2 +- README.md | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj b/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj index f114ed4..e03d7d7 100644 --- a/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj +++ b/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj @@ -25,7 +25,7 @@ MIT True latest-Recommended - 5.1.0 + 5.1.1 README.md true 1701;1702;NU5128 diff --git a/README.md b/README.md index de12120..8dd6a58 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,10 @@ Should be simply a build and run Tests ## Changelog +### 5.1.1 + +- Emit `new()` type constraints on generic type parameters; emit `params` keyword for method parameters. Thanks, @simonmckenzie! + ### 5.1.0 - Improves inheritdoc so that developer documentation is properly referenced on the autogenerated interfaces. Thanks, CFlorell! @@ -195,7 +199,7 @@ Should be simply a build and run Tests ### 5.0.2 -- Fully qualify type references; remove usings . Thanks, @simonmckenzie! +- Fully qualify type references; remove usings. Thanks, @simonmckenzie! ### 5.0.1 From c305b5e6c561b95ef99f37f0997a40887897282b Mon Sep 17 00:00:00 2001 From: Simon McKenzie <17579442+simonmckenzie@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:51:23 +1100 Subject: [PATCH 4/4] Replace custom "GetParameterSignature" code with call to `.ToDisplayString(FullyQualifiedDisplayFormat)` This removes a fair bit of code and ensures we're formatting our parameters consistently. --- .../AutomaticInterface/Builder.cs | 62 +++---------------- 1 file changed, 8 insertions(+), 54 deletions(-) diff --git a/AutomaticInterface/AutomaticInterface/Builder.cs b/AutomaticInterface/AutomaticInterface/Builder.cs index 6d24694..9b167ca 100644 --- a/AutomaticInterface/AutomaticInterface/Builder.cs +++ b/AutomaticInterface/AutomaticInterface/Builder.cs @@ -17,11 +17,14 @@ private static string InheritDoc(ISymbol source) => genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, memberOptions: SymbolDisplayMemberOptions.IncludeParameters, parameterOptions: SymbolDisplayParameterOptions.IncludeType - | SymbolDisplayParameterOptions.IncludeParamsRefOut, + | SymbolDisplayParameterOptions.IncludeParamsRefOut + | SymbolDisplayParameterOptions.IncludeDefaultValue + | SymbolDisplayParameterOptions.IncludeName, typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier + | SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers ); public static string BuildInterfaceFor(ITypeSymbol typeSymbol) @@ -102,7 +105,10 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth ActivateNullableIfNeeded(codeGenerator, method); var paramResult = new HashSet(); - method.Parameters.Select(GetParameterSignature).ToList().ForEach(x => paramResult.Add(x)); + method + .Parameters.Select(x => x.ToDisplayString(FullyQualifiedDisplayFormat)) + .ToList() + .ForEach(x => paramResult.Add(x)); var typedArgs = method .TypeParameters.Select(arg => @@ -194,58 +200,6 @@ private static void AddEventsToInterface(List members, InterfaceBuilder }); } - private static string GetParameterSignature(IParameterSymbol x) - { - var name = GetParameterName(x); - var refKindText = GetParameterRefKind(x); - var optionalValue = GetParameterOptionalValue(x); - - var paramsPrefix = x.IsParams ? "params " : ""; - - return $"{paramsPrefix}{refKindText}{x.Type.ToDisplayString(FullyQualifiedDisplayFormat)} {name}{optionalValue}"; - } - - private static string GetParameterOptionalValue(IParameterSymbol x) - { - if (!x.HasExplicitDefaultValue) - { - return string.Empty; - } - - return x.ExplicitDefaultValue switch - { - string => $" = \"{x.ExplicitDefaultValue}\"", - bool value => $" = {(value ? "true" : "false")}", - // struct - null when x.Type.IsValueType - => $" = default({x.Type.ToDisplayString(FullyQualifiedDisplayFormat)})", - null => " = null", - _ => $" = {x.ExplicitDefaultValue}", - }; - } - - private static string GetParameterName(IParameterSymbol x) - { - var syntaxReference = x.DeclaringSyntaxReferences.FirstOrDefault(); - - return syntaxReference != null - ? ((ParameterSyntax)syntaxReference.GetSyntax()).Identifier.Text - : x.Name; - } - - private static string GetParameterRefKind(IParameterSymbol x) - { - return x.RefKind switch - { - RefKind.Ref => "ref ", - RefKind.Out => "out ", - RefKind.In => "in ", - // Not sure why RefReadOnly and In both has Enum index 3. - // RefKind.RefReadOnly => "ref readonly ", - _ => string.Empty, - }; - } - private static void AddPropertiesToInterface( List members, InterfaceBuilder interfaceGenerator