From 296d31cd095ef6cfb208bce62ac1734836c70cf7 Mon Sep 17 00:00:00 2001 From: Simon McKenzie <17579442+simonmckenzie@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:13:04 +1000 Subject: [PATCH] Fully qualify type references; remove usings This updates the interface generation to always fully qualify type references, and no longer generate "using" statements in generated interfaces. This makes the interface generation resilient to namespace collisions and other namespace edge-cases (like resolving global usings in parent assemblies). It also means the generator is now able to correctly resolve types with duplicate names --- .../AutomaticInterface/Builder.cs | 53 +-- .../AutomaticInterface/InterfaceBuilder.cs | 20 +- .../AutomaticInterface/RoslynExtensions.cs | 26 +- .../Tests/GeneratorTests.Properties.cs | 63 +--- .../Tests/GeneratorTests.TypeResolution.cs | 280 ++++++++++++++ AutomaticInterface/Tests/GeneratorTests.cs | 344 +++--------------- .../Tests/GeneratorsTests.MethodParameters.cs | 18 +- README.md | 43 +-- 8 files changed, 406 insertions(+), 441 deletions(-) create mode 100644 AutomaticInterface/Tests/GeneratorTests.TypeResolution.cs diff --git a/AutomaticInterface/AutomaticInterface/Builder.cs b/AutomaticInterface/AutomaticInterface/Builder.cs index b97e564..a75cd87 100644 --- a/AutomaticInterface/AutomaticInterface/Builder.cs +++ b/AutomaticInterface/AutomaticInterface/Builder.cs @@ -18,6 +18,15 @@ public static class Builder | SymbolDisplayParameterOptions.IncludeParamsRefOut ); + private static readonly SymbolDisplayFormat TypeDisplayFormat = + new( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes + | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier + ); + public static string BuildInterfaceFor(ITypeSymbol typeSymbol) { if ( @@ -34,7 +43,6 @@ is not ClassDeclarationSyntax classSyntax var interfaceGenerator = new InterfaceBuilder(namespaceName, interfaceName); - interfaceGenerator.AddUsings(GetUsings(typeSymbol)); interfaceGenerator.AddClassDocumentation(GetDocumentationForClass(classSyntax)); interfaceGenerator.AddGeneric(GetGeneric(classSyntax)); AddPropertiesToInterface(typeSymbol, interfaceGenerator); @@ -102,16 +110,13 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth var typedArgs = method .TypeParameters.Select(arg => - ( - arg.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), - arg.GetWhereStatement() - ) + (arg.ToDisplayString(TypeDisplayFormat), arg.GetWhereStatement(TypeDisplayFormat)) ) .ToList(); codeGenerator.AddMethodToInterface( name, - returnType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), + returnType.ToDisplayString(TypeDisplayFormat), InheritDoc, paramResult, typedArgs @@ -189,7 +194,11 @@ InterfaceBuilder codeGenerator ActivateNullableIfNeeded(codeGenerator, type); - codeGenerator.AddEventToInterface(name, type.ToDisplayString(), InheritDoc); + codeGenerator.AddEventToInterface( + name, + type.ToDisplayString(TypeDisplayFormat), + InheritDoc + ); }); } @@ -199,7 +208,7 @@ private static string GetMethodSignature(IParameterSymbol x) var refKindText = GetRefKind(x); var optionalValue = GetMethodOptionalValue(x); - return $"{refKindText}{x.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} {name}{optionalValue}"; + return $"{refKindText}{x.Type.ToDisplayString(TypeDisplayFormat)} {name}{optionalValue}"; } private static string GetMethodOptionalValue(IParameterSymbol x) @@ -215,7 +224,7 @@ private static string GetMethodOptionalValue(IParameterSymbol x) bool value => $" = {(value ? "true" : "false")}", // struct null when x.Type.IsValueType - => $" = default({x.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)})", + => $" = default({x.Type.ToDisplayString(TypeDisplayFormat)})", null => " = null", _ => $" = {x.ExplicitDefaultValue}" }; @@ -272,7 +281,7 @@ InterfaceBuilder interfaceGenerator interfaceGenerator.AddPropertyToInterface( name, - type.ToDisplayString(), + type.ToDisplayString(TypeDisplayFormat), hasGet, hasSet, isRef, @@ -330,30 +339,6 @@ private static string GetDocumentationForClass(CSharpSyntaxNode classSyntax) return trivia.ToFullString().Trim(); } - private static IEnumerable GetUsings(ISymbol classSymbol) - { - var allUsings = SyntaxFactory.List(); - foreach (var syntaxRef in classSymbol.DeclaringSyntaxReferences) - { - allUsings = syntaxRef - .GetSyntax() - .Ancestors(false) - .Aggregate( - allUsings, - (current, parent) => - parent switch - { - NamespaceDeclarationSyntax ndSyntax - => current.AddRange(ndSyntax.Usings), - CompilationUnitSyntax cuSyntax => current.AddRange(cuSyntax.Usings), - _ => current - } - ); - } - - return [.. allUsings.Select(x => x.ToString())]; - } - private static string GetGeneric(TypeDeclarationSyntax classSyntax) { if (classSyntax.TypeParameterList?.Parameters.Count == 0) diff --git a/AutomaticInterface/AutomaticInterface/InterfaceBuilder.cs b/AutomaticInterface/AutomaticInterface/InterfaceBuilder.cs index 26a3cb6..41a9715 100644 --- a/AutomaticInterface/AutomaticInterface/InterfaceBuilder.cs +++ b/AutomaticInterface/AutomaticInterface/InterfaceBuilder.cs @@ -38,7 +38,6 @@ public class InterfaceBuilder(string nameSpaceName, string interfaceName) """; - private readonly HashSet usings = ["using System.CodeDom.Compiler;"]; private readonly List propertyInfos = []; private readonly List methodInfos = []; private readonly List events = []; @@ -69,14 +68,6 @@ public void AddClassDocumentation(string documentation) classDocumentation = documentation; } - public void AddUsings(IEnumerable additionalUsings) - { - foreach (var usg in additionalUsings) - { - usings.Add(usg); - } - } - public void AddMethodToInterface( string name, string returnType, @@ -103,13 +94,6 @@ public string Build() cb.AppendLine("#nullable enable"); } - foreach (var usg in usings) - { - cb.AppendLine(usg); - } - - cb.AppendLine(""); - cb.AppendLine($"namespace {nameSpaceName}"); cb.AppendLine("{"); @@ -117,7 +101,9 @@ public string Build() cb.AppendAndNormalizeMultipleLines(classDocumentation); - cb.AppendLine($"[GeneratedCode(\"AutomaticInterface\", \"\")]"); + cb.AppendLine( + "[global::System.CodeDom.Compiler.GeneratedCode(\"AutomaticInterface\", \"\")]" + ); cb.AppendLine($"public partial interface {interfaceName}{genericType}"); cb.AppendLine("{"); diff --git a/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs b/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs index ad9ac0d..0174b84 100644 --- a/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs +++ b/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs @@ -35,9 +35,10 @@ public static string GetClassName(this ClassDeclarationSyntax proxy) /// /// Thanks to https://www.codeproject.com/Articles/871704/Roslyn-Code-Analysis-in-Easy-Samples-Part-2 /// - /// - /// - public static string GetWhereStatement(this ITypeParameterSymbol typeParameterSymbol) + public static string GetWhereStatement( + this ITypeParameterSymbol typeParameterSymbol, + SymbolDisplayFormat typeDisplayFormat + ) { var result = $"where {typeParameterSymbol.Name} : "; @@ -69,7 +70,7 @@ public static string GetWhereStatement(this ITypeParameterSymbol typeParameterSy isFirstConstraint = false; } - constraints += constraintType.GetFullTypeString(); + constraints += constraintType.GetFullTypeString(typeDisplayFormat); } if (string.IsNullOrEmpty(constraints)) @@ -82,14 +83,21 @@ public static string GetWhereStatement(this ITypeParameterSymbol typeParameterSy return result; } - private static string GetFullTypeString(this ISymbol type) + private static string GetFullTypeString( + this ISymbol type, + SymbolDisplayFormat typeDisplayFormat + ) { - return type.Name - + type.GetTypeArgsStr(symbol => ((INamedTypeSymbol)symbol).TypeArguments); + return type.ToDisplayString(typeDisplayFormat) + + type.GetTypeArgsStr( + typeDisplayFormat, + symbol => ((INamedTypeSymbol)symbol).TypeArguments + ); } private static string GetTypeArgsStr( this ISymbol symbol, + SymbolDisplayFormat typeDisplayFormat, Func> typeArgGetter ) { @@ -106,13 +114,13 @@ Func> typeArgGetter if (arg is ITypeParameterSymbol typeParameterSymbol) { // this is a generic argument - strToAdd = typeParameterSymbol.Name; + strToAdd = typeParameterSymbol.ToDisplayString(typeDisplayFormat); } else { // this is a generic argument value. var namedTypeSymbol = arg as INamedTypeSymbol; - strToAdd = namedTypeSymbol!.GetFullTypeString(); + strToAdd = namedTypeSymbol!.GetFullTypeString(typeDisplayFormat); } stringsToAdd.Add(strToAdd); diff --git a/AutomaticInterface/Tests/GeneratorTests.Properties.cs b/AutomaticInterface/Tests/GeneratorTests.Properties.cs index 45bfc92..278919a 100644 --- a/AutomaticInterface/Tests/GeneratorTests.Properties.cs +++ b/AutomaticInterface/Tests/GeneratorTests.Properties.cs @@ -34,12 +34,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -83,12 +80,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -137,16 +131,12 @@ class DemoClass //-------------------------------------------------------------------------------------------------- #nullable enable - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - namespace AutomaticInterfaceExample { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -197,21 +187,16 @@ class DemoClass //-------------------------------------------------------------------------------------------------- #nullable enable - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - using System.Threading.Tasks; - namespace AutomaticInterfaceExample { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - System.Threading.Tasks.Task NullableProperty { get; set; } + global::System.Threading.Tasks.Task NullableProperty { get; set; } } } @@ -253,13 +238,9 @@ public partial class SecondClass : FirstClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.Threading.Tasks; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface ISecondClass { /// @@ -302,13 +283,9 @@ public class DemoClass : BaseClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -352,13 +329,9 @@ public class DemoClass : BaseClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -399,12 +372,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -446,12 +416,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -493,12 +460,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -539,12 +503,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// diff --git a/AutomaticInterface/Tests/GeneratorTests.TypeResolution.cs b/AutomaticInterface/Tests/GeneratorTests.TypeResolution.cs new file mode 100644 index 0000000..df77c97 --- /dev/null +++ b/AutomaticInterface/Tests/GeneratorTests.TypeResolution.cs @@ -0,0 +1,280 @@ +using FluentAssertions; +using Xunit; + +namespace Tests; + +public partial class GeneratorTests +{ + [Fact] + public void WorksWithFileScopedNamespace() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample; + + [GenerateAutomaticInterface] + class DemoClass + { + public string Hello { get; set; } + } + """; + + 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 + { + /// + string Hello { get; set; } + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void WorksWithBracedNamespace() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + + [GenerateAutomaticInterface] + class DemoClass + { + public string Hello { get; set; } + } + } + """; + + 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 + { + /// + string Hello { get; set; } + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void WorksWithUsingAliases() + { + const string code = """ + + using AutomaticInterface; + using TaskAlias = System.Threading.Tasks.Task; + + namespace AutomaticInterfaceExample; + + [GenerateAutomaticInterface] + class DemoClass + { + public TaskAlias Hello { get; set; } + } + + + """; + + 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 + { + /// + global::System.Threading.Tasks.Task Hello { get; set; } + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void WorksWithIdenticalTypeNames() + { + const string code = """ + + using AutomaticInterface; + namespace AutomaticInterfaceExample + { + namespace Models1 { + public class Model; + } + + namespace Models2 { + public class Model; + } + + [GenerateAutomaticInterface] + public class ModelManager + { + public Models1.Model GetModel1() => null!; + public Models2.Model GetModel2() => null!; + } + } + """; + + 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 IModelManager + { + /// + global::AutomaticInterfaceExample.Models1.Model GetModel1(); + + /// + global::AutomaticInterfaceExample.Models2.Model GetModel2(); + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void WorksWithNestedUsings() + { + const string code = """ + + using AutomaticInterface; + namespace RootNamespace + { + namespace Models + { + public class Model; + } + + namespace ModelManager + { + using Models; + + [GenerateAutomaticInterface] + public class ModelManager + { + public Model GetModel() => null!; + } + } + } + """; + + 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 RootNamespace.ModelManager + { + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] + public partial interface IModelManager + { + /// + global::RootNamespace.Models.Model GetModel(); + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void WorksWithGlobalUsing() + { + const string code = """ + global using GlobalNamespace; + using AutomaticInterface; + + namespace GlobalNamespace + { + public class AClass; + } + + namespace AutomaticInterfaceExample + { + [GenerateAutomaticInterface] + public class DemoClass + { + public AClass GetClass() => null!; + } + } + """; + + 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 + { + /// + global::GlobalNamespace.AClass GetClass(); + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } +} diff --git a/AutomaticInterface/Tests/GeneratorTests.cs b/AutomaticInterface/Tests/GeneratorTests.cs index b548d97..0abba0f 100644 --- a/AutomaticInterface/Tests/GeneratorTests.cs +++ b/AutomaticInterface/Tests/GeneratorTests.cs @@ -98,12 +98,9 @@ public bool TryStartTransaction( // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -150,16 +147,13 @@ public class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - bool TryStartTransaction(MyStruct data = default(MyStruct)); + bool TryStartTransaction(global::AutomaticInterfaceExample.MyStruct data = default(global::AutomaticInterfaceExample.MyStruct)); } } @@ -197,12 +191,9 @@ public bool TryStartTransaction(string data = null) // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -241,12 +232,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { } @@ -257,61 +245,12 @@ public partial interface IDemoClass GenerateCode(code).Should().Be(expected); } - [Fact] - public void AddsUsingsToInterface() - { - const string code = """ - - using AutomaticInterface; - using System.IO; - - namespace AutomaticInterfaceExample - { - - [GenerateAutomaticInterface] - class DemoClass - { - public DirectoryInfo Hello { get; set; } - } - } - - """; - - 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. - // - //-------------------------------------------------------------------------------------------------- - - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.IO; - - namespace AutomaticInterfaceExample - { - [GeneratedCode("AutomaticInterface", "")] - public partial interface IDemoClass - { - /// - System.IO.DirectoryInfo Hello { get; set; } - - } - } - - """; - GenerateCode(code).Should().Be(expected); - } - [Fact] public void AddsPublicMethodToInterface() { const string code = """ using AutomaticInterface; - using System.IO; namespace AutomaticInterfaceExample { @@ -335,13 +274,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.IO; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -384,18 +319,13 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.IO; - using System.Threading.Tasks; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - Task Hello(); + global::System.Threading.Tasks.Task Hello(); } } @@ -434,13 +364,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.IO; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -483,18 +409,13 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.IO; - using System.Threading.Tasks; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - string Hello(Task x); + string Hello(global::System.Threading.Tasks.Task x); } } @@ -532,13 +453,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.IO; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -581,13 +498,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.IO; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { } @@ -598,7 +511,7 @@ public partial interface IDemoClass } [Fact] - public void IgnoresMembersAttributedWithSkip() + public void IgnoresMembersAttributedWithIgnore() { const string code = """ @@ -629,14 +542,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - using System.IO; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { } @@ -680,13 +588,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.IO; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -732,13 +636,9 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.IO; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -781,15 +681,12 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -837,15 +734,12 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -893,15 +787,12 @@ public static string StaticMethod() // method // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { } @@ -940,15 +831,12 @@ class DemoClass where T:class // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass where T:class { } @@ -995,20 +883,16 @@ class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - namespace AutomaticInterfaceExample { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - event System.EventHandler ShapeChanged; + event global::System.EventHandler ShapeChanged; } } @@ -1058,16 +942,12 @@ public int this[int index] // currently ignored // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - namespace AutomaticInterfaceExample { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { } @@ -1149,16 +1029,12 @@ public int this[int index] // currently ignored // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - namespace AutomaticInterfaceExample { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -1171,52 +1047,7 @@ public partial interface IDemoClass string AMethod(string x, string y); /// - event System.EventHandler ShapeChanged; - - } - } - - """; - GenerateCode(code).Should().Be(expected); - } - - [Fact] - public void WorksWithFileScopedNamespace() - { - const string code = """ - - using AutomaticInterface; - - namespace AutomaticInterfaceExample; - - [GenerateAutomaticInterface] - class DemoClass - { - public string Hello { get; set; } - } - - - """; - - 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. - // - //-------------------------------------------------------------------------------------------------- - - using System.CodeDom.Compiler; - using AutomaticInterface; - - namespace AutomaticInterfaceExample - { - [GeneratedCode("AutomaticInterface", "")] - public partial interface IDemoClass - { - /// - string Hello { get; set; } + event global::System.EventHandler ShapeChanged; } } @@ -1260,16 +1091,13 @@ public string CMethod(string x, string y) // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - string CMethod(string x, string y) where T : class where T1 : struct where T3 : DemoClass where T4 : IDemoClass; + string CMethod(string x, string y) where T : class where T1 : struct where T3 : global::AutomaticInterfaceExample.DemoClass where T4 : IDemoClass; } } @@ -1314,12 +1142,9 @@ public string AMethod(string x, string y, string crash) // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -1363,16 +1188,13 @@ public string AMethod(DemoClass? x, string y) //-------------------------------------------------------------------------------------------------- #nullable enable - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - string AMethod(DemoClass? x, string y); + string AMethod(global::AutomaticInterfaceExample.DemoClass? x, string y); } } @@ -1410,16 +1232,13 @@ public class DemoClass //-------------------------------------------------------------------------------------------------- #nullable enable - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - string? AMethod(DemoClass x, string y); + string? AMethod(global::AutomaticInterfaceExample.DemoClass x, string y); } } @@ -1465,20 +1284,16 @@ class DemoClass //-------------------------------------------------------------------------------------------------- #nullable enable - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - namespace AutomaticInterfaceExample { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - event System.EventHandler? ShapeChangedNullable; + event global::System.EventHandler? ShapeChangedNullable; } } @@ -1524,20 +1339,16 @@ class DemoClass //-------------------------------------------------------------------------------------------------- #nullable enable - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - namespace AutomaticInterfaceExample { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - event System.EventHandler ShapeChangedNullable; + event global::System.EventHandler ShapeChangedNullable; } } @@ -1593,23 +1404,16 @@ class DemoClass //-------------------------------------------------------------------------------------------------- #nullable enable - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - namespace AutomaticInterfaceExample { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - Task GetFinalDocumentsByIDFails(string agreementID, string docType, bool amt = false, bool? attachSupportingDocuments = true, CancellationToken cancellationToken = default(CancellationToken)); + global::System.Threading.Tasks.Task GetFinalDocumentsByIDFails(string agreementID, string docType, bool amt = false, bool? attachSupportingDocuments = true, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)); } } @@ -1665,23 +1469,16 @@ class DemoClass //-------------------------------------------------------------------------------------------------- #nullable enable - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - namespace CustomNameSpace { /// /// Bla bla /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - Task GetFinalDocumentsByIDFails(string agreementID, string docType, bool amt = false, bool? attachSupportingDocuments = true, CancellationToken cancellationToken = default(CancellationToken)); + global::System.Threading.Tasks.Task GetFinalDocumentsByIDFails(string agreementID, string docType, bool amt = false, bool? attachSupportingDocuments = true, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)); } } @@ -1721,17 +1518,13 @@ public class DemoClass //-------------------------------------------------------------------------------------------------- #nullable enable - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.Threading.Tasks; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - Task AMethodAsync(DemoClass x, string y); + global::System.Threading.Tasks.Task AMethodAsync(global::AutomaticInterfaceExample.DemoClass x, string y); } } @@ -1771,17 +1564,13 @@ public string AMethod(Task x, string y) //-------------------------------------------------------------------------------------------------- #nullable enable - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.Threading.Tasks; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - string AMethod(Task x, string y); + string AMethod(global::System.Threading.Tasks.Task x, string y); } } @@ -1818,13 +1607,9 @@ public class DemoClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.Threading.Tasks; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -1865,13 +1650,9 @@ public void AMethod(int @event) // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.Threading.Tasks; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -1915,12 +1696,9 @@ public class DemoClass : BaseClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -1964,12 +1742,9 @@ public class DemoClass : BaseClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -2010,12 +1785,9 @@ public void AMethod(ref int val){} // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -2063,17 +1835,13 @@ public class DemoClass : BaseClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - event System.EventHandler AnEvent; + event global::System.EventHandler AnEvent; } } @@ -2114,17 +1882,13 @@ public class DemoClass : BaseClass // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// - event System.EventHandler AnEvent; + event global::System.EventHandler AnEvent; } } diff --git a/AutomaticInterface/Tests/GeneratorsTests.MethodParameters.cs b/AutomaticInterface/Tests/GeneratorsTests.MethodParameters.cs index 7aef0f5..9b94df0 100644 --- a/AutomaticInterface/Tests/GeneratorsTests.MethodParameters.cs +++ b/AutomaticInterface/Tests/GeneratorsTests.MethodParameters.cs @@ -34,13 +34,9 @@ public void AMethod(out int someOutParameter) // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.Threading.Tasks; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -81,13 +77,9 @@ public void AMethod(in int someOutParameter) // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.Threading.Tasks; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// @@ -128,13 +120,9 @@ public void AMethod(ref int someOutParameter) // //-------------------------------------------------------------------------------------------------- - using System.CodeDom.Compiler; - using AutomaticInterface; - using System.Threading.Tasks; - namespace AutomaticInterfaceExample { - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { /// diff --git a/README.md b/README.md index c78106c..d9a03e3 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ A C# Source Generator to automatically create Interfaces from classes. ## What does it do? -Not all .NET Interfaces are created equal. Some Interfaces are lovingly handcrafted, e.g. the public interface of your .NET package which is used by your customers. Other interfaces are far from lovingly crafted, they are birthed because you need an interface for testing or for the DI container. They are often implemented only once or twice: The class itself and a mock for testing. They are noise at best and often create lots of friction. Adding a new method / field? You have to edit the interface, too!. Change parameters? Edit the interface. Add documentation? Hopefully you add it to the interface, too! +Not all .NET Interfaces are created equal. Some interfaces are lovingly handcrafted, e.g. the public interface of your .NET package which is used by your customers. Other interfaces are far from lovingly crafted, they are birthed because you need an interface for testing or for the DI container. They are often implemented only once or twice: The class itself and a mock for testing. They are noise at best and often create lots of friction. Adding a new method / field? You have to edit the interface, too!. Change parameters? Edit the interface. Add documentation? Hopefully you add it to the interface, too! This Source Generator aims to eliminate this cost by generating an interface from the class, without you needing to do anything. -This interface will be generated on each subsequent build, eliminating the the friction. +This interface will be generated on each subsequent build, eliminating the friction. ## Example @@ -22,8 +22,8 @@ namespace AutomaticInterfaceExample /// /// Class Documentation will be copied /// - [GenerateAutomaticInterface] // you need to create an Attribute with exactly this name in your solution. You cannot reference Code from the Analyzer. - class DemoClass: IDemoClass // You Interface will get the Name I+classname, here IDemoclass. + [GenerateAutomaticInterface] + class DemoClass: IDemoClass // Your interface will get the Name I+classname, here IDemoclass. // Generics, including constraints are allowed, too. E.g. MyClass where T: class { @@ -89,9 +89,6 @@ This will create this interface: ```C# #nullable enable -using System.CodeDom.Compiler; -using AutomaticInterface; -using System; /// /// Result of the generator @@ -99,30 +96,26 @@ using System; namespace AutomaticInterfaceExample { /// - /// Bla bla + /// Class documentation will be copied /// - [GeneratedCode("AutomaticInterface", "")] + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] public partial interface IDemoClass { - /// - /// Property Documentation will be copied - /// + /// string Hello { get; set; } - + + /// string OnlyGet { get; } - - /// - /// Method Documentation will be copied - /// + + /// string AMethod(string x, string y); - - string CMethod(string? x, string y) where T : class where T1 : struct where T3 : DemoClass where T4 : IDemoClass; - - /// - /// event Documentation will be copied - /// - event System.EventHandler ShapeChanged; - + + /// + string CMethod(string? x, string y) where T : class where T1 : struct where T3 : global::AutomaticInterfaceExample.DemoClass where T4 : IDemoClass; + + /// + event global::System.EventHandler ShapeChanged; + } } #nullable restore