diff --git a/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj b/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj
index f0b0404..87e7223 100644
--- a/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj
+++ b/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj
@@ -24,11 +24,10 @@
MIT
True
latest-Recommended
- 5.2.6
+ 6.0.0
README.md
true
1701;1702;NU5128
- mproves inheritdoc so that developer documentation is properly referenced on the autogenerated interfaces
TRACE;DEBUGGENERATOR
diff --git a/AutomaticInterface/AutomaticInterface/AutomaticInterfaceGenerator.cs b/AutomaticInterface/AutomaticInterface/AutomaticInterfaceGenerator.cs
index f147501..f9c86a4 100644
--- a/AutomaticInterface/AutomaticInterface/AutomaticInterfaceGenerator.cs
+++ b/AutomaticInterface/AutomaticInterface/AutomaticInterfaceGenerator.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Immutable;
+using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -40,13 +41,19 @@ ImmutableArray enumerations
return;
}
+ var generatedInterfaceNames = enumerations
+ .Select(Builder.GetInterfaceNameFor)
+ .Where(name => name != null)
+ .Cast()
+ .ToList();
+
foreach (var type in enumerations)
{
var typeNamespace = type.ContainingNamespace.IsGlobalNamespace
? $"${Guid.NewGuid()}"
: $"{type.ContainingNamespace}";
- var code = Builder.BuildInterfaceFor(type);
+ var code = Builder.BuildInterfaceFor(type, generatedInterfaceNames);
var hintName = $"{typeNamespace}.I{type.Name}";
context.AddSource(hintName, code);
diff --git a/AutomaticInterface/AutomaticInterface/Builder.cs b/AutomaticInterface/AutomaticInterface/Builder.cs
index 49893ee..3be544d 100644
--- a/AutomaticInterface/AutomaticInterface/Builder.cs
+++ b/AutomaticInterface/AutomaticInterface/Builder.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -41,17 +40,37 @@ private static string InheritDoc(ISymbol source) =>
miscellaneousOptions: FullyQualifiedDisplayFormat.MiscellaneousOptions
);
- public static string BuildInterfaceFor(ITypeSymbol typeSymbol)
+ public static string? GetInterfaceNameFor(ITypeSymbol typeSymbol)
{
- if (
- typeSymbol.DeclaringSyntaxReferences.First().GetSyntax()
- is not ClassDeclarationSyntax classSyntax
- || typeSymbol is not INamedTypeSymbol namedTypeSymbol
- )
+ var declarationAndNamedTypeSymbol = GetClassDeclarationMetadata(typeSymbol);
+ if (declarationAndNamedTypeSymbol == null)
+ {
+ return null;
+ }
+
+ var (classSyntax, _) = declarationAndNamedTypeSymbol.Value;
+
+ var symbolDetails = GetSymbolDetails(typeSymbol, classSyntax);
+
+ return $"global::{symbolDetails.NamespaceName}.{symbolDetails.InterfaceName}";
+ }
+
+ /// The symbol from which the interface will be built
+ /// A list of interface names that will be generated in this session. Used to resolve type references to interfaces that haven't yet been generated
+ ///
+ public static string BuildInterfaceFor(
+ ITypeSymbol typeSymbol,
+ List generatedInterfaceNames
+ )
+ {
+ var declarationAndNamedTypeSymbol = GetClassDeclarationMetadata(typeSymbol);
+ if (declarationAndNamedTypeSymbol == null)
{
return string.Empty;
}
+ var (classSyntax, namedTypeSymbol) = declarationAndNamedTypeSymbol.Value;
+
var symbolDetails = GetSymbolDetails(typeSymbol, classSyntax);
var interfaceGenerator = new InterfaceBuilder(
symbolDetails.NamespaceName,
@@ -60,7 +79,9 @@ is not ClassDeclarationSyntax classSyntax
);
interfaceGenerator.AddClassDocumentation(GetDocumentationForClass(classSyntax));
- interfaceGenerator.AddGeneric(GetGeneric(classSyntax, namedTypeSymbol));
+ interfaceGenerator.AddGeneric(
+ GetGeneric(classSyntax, namedTypeSymbol, generatedInterfaceNames)
+ );
var members = typeSymbol
.GetAllMembers()
@@ -69,15 +90,32 @@ is not ClassDeclarationSyntax classSyntax
.Where(x => !HasIgnoreAttribute(x))
.ToList();
- AddPropertiesToInterface(members, interfaceGenerator);
- AddMethodsToInterface(members, interfaceGenerator);
- AddEventsToInterface(members, interfaceGenerator);
+ AddPropertiesToInterface(members, interfaceGenerator, generatedInterfaceNames);
+ AddMethodsToInterface(members, interfaceGenerator, generatedInterfaceNames);
+ AddEventsToInterface(members, interfaceGenerator, generatedInterfaceNames);
var generatedCode = interfaceGenerator.Build();
return generatedCode;
}
+ private static (
+ ClassDeclarationSyntax Syntax,
+ INamedTypeSymbol NamedTypeSymbol
+ )? GetClassDeclarationMetadata(ITypeSymbol typeSymbol)
+ {
+ if (
+ typeSymbol.DeclaringSyntaxReferences.First().GetSyntax()
+ is not ClassDeclarationSyntax classSyntax
+ || typeSymbol is not INamedTypeSymbol namedTypeSymbol
+ )
+ {
+ return null;
+ }
+
+ return (classSyntax, namedTypeSymbol);
+ }
+
private static GeneratedSymbolDetails GetSymbolDetails(
ITypeSymbol typeSymbol,
ClassDeclarationSyntax classSyntax
@@ -93,7 +131,11 @@ ClassDeclarationSyntax classSyntax
return new GeneratedSymbolDetails(generationAttribute, typeSymbol, classSyntax);
}
- private static void AddMethodsToInterface(List members, InterfaceBuilder codeGenerator)
+ private static void AddMethodsToInterface(
+ List members,
+ InterfaceBuilder codeGenerator,
+ List generatedInterfaceNames
+ )
{
members
.Where(x => x.Kind == SymbolKind.Method)
@@ -104,10 +146,14 @@ private static void AddMethodsToInterface(List members, InterfaceBuilde
.GroupBy(x => x.ToDisplayString(FullyQualifiedDisplayFormatForGrouping))
.Select(g => g.First())
.ToList()
- .ForEach(method => AddMethod(codeGenerator, method));
+ .ForEach(method => AddMethod(codeGenerator, method, generatedInterfaceNames));
}
- private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol method)
+ private static void AddMethod(
+ InterfaceBuilder codeGenerator,
+ IMethodSymbol method,
+ List generatedInterfaceNames
+ )
{
var returnType = method.ReturnType;
var name = method.Name;
@@ -116,7 +162,13 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth
var paramResult = new HashSet();
method
- .Parameters.Select(p => GetParameterDisplayString(p, codeGenerator.HasNullable))
+ .Parameters.Select(p =>
+ p.ToDisplayString(
+ FullyQualifiedDisplayFormat,
+ codeGenerator.HasNullable,
+ generatedInterfaceNames
+ )
+ )
.ToList()
.ForEach(x => paramResult.Add(x));
@@ -124,14 +176,14 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth
.TypeParameters.Select(arg =>
(
arg.ToDisplayString(FullyQualifiedDisplayFormat),
- arg.GetWhereStatement(FullyQualifiedDisplayFormat)
+ arg.GetWhereStatement(FullyQualifiedDisplayFormat, generatedInterfaceNames)
)
)
.ToList();
codeGenerator.AddMethodToInterface(
name,
- returnType.ToDisplayString(FullyQualifiedDisplayFormat),
+ returnType.ToDisplayString(FullyQualifiedDisplayFormat, generatedInterfaceNames),
InheritDoc(method),
paramResult,
typedArgs
@@ -187,46 +239,11 @@ private static bool IsNullable(ITypeSymbol typeSymbol)
return false;
}
- private static string GetParameterDisplayString(
- IParameterSymbol param,
- bool nullableContextEnabled
+ private static void AddEventsToInterface(
+ List members,
+ InterfaceBuilder codeGenerator,
+ List generatedInterfaceNames
)
- {
- 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
.Where(x => x.Kind == SymbolKind.Event)
@@ -243,7 +260,7 @@ private static void AddEventsToInterface(List members, InterfaceBuilder
codeGenerator.AddEventToInterface(
name,
- type.ToDisplayString(FullyQualifiedDisplayFormat),
+ type.ToDisplayString(FullyQualifiedDisplayFormat, generatedInterfaceNames),
InheritDoc(evt)
);
});
@@ -251,7 +268,8 @@ private static void AddEventsToInterface(List members, InterfaceBuilder
private static void AddPropertiesToInterface(
List members,
- InterfaceBuilder interfaceGenerator
+ InterfaceBuilder interfaceGenerator,
+ List generatedInterfaceNames
)
{
members
@@ -274,7 +292,7 @@ InterfaceBuilder interfaceGenerator
interfaceGenerator.AddPropertyToInterface(
name,
- type.ToDisplayString(FullyQualifiedDisplayFormat),
+ type.ToDisplayString(FullyQualifiedDisplayFormat, generatedInterfaceNames),
hasGet,
hasSet,
isRef,
@@ -331,11 +349,18 @@ private static string GetDocumentationForClass(CSharpSyntaxNode classSyntax)
return trivia.ToFullString().Trim();
}
- private static string GetGeneric(TypeDeclarationSyntax classSyntax, INamedTypeSymbol typeSymbol)
+ private static string GetGeneric(
+ TypeDeclarationSyntax classSyntax,
+ INamedTypeSymbol typeSymbol,
+ List generatedInterfaceNames
+ )
{
var whereStatements = typeSymbol
.TypeParameters.Select(typeParameter =>
- typeParameter.GetWhereStatement(FullyQualifiedDisplayFormat)
+ typeParameter.GetWhereStatement(
+ FullyQualifiedDisplayFormat,
+ generatedInterfaceNames
+ )
)
.Where(constraint => !string.IsNullOrEmpty(constraint));
diff --git a/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs b/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs
index 6606fe4..6776397 100644
--- a/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs
+++ b/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs
@@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
-using System.Collections.Immutable;
using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -37,7 +38,8 @@ public static string GetClassName(this ClassDeclarationSyntax proxy)
///
public static string GetWhereStatement(
this ITypeParameterSymbol typeParameterSymbol,
- SymbolDisplayFormat typeDisplayFormat
+ SymbolDisplayFormat typeDisplayFormat,
+ List generatedInterfaceNames
)
{
var result = $"where {typeParameterSymbol.Name} : ";
@@ -61,7 +63,7 @@ SymbolDisplayFormat typeDisplayFormat
constraints.AddRange(
typeParameterSymbol.ConstraintTypes.Select(t =>
- t.ToDisplayString(typeDisplayFormat)
+ t.ToDisplayString(typeDisplayFormat, generatedInterfaceNames)
)
);
@@ -80,5 +82,161 @@ SymbolDisplayFormat typeDisplayFormat
return result;
}
+
+ public static string ToDisplayString(
+ this IParameterSymbol symbol,
+ SymbolDisplayFormat displayFormat,
+ bool nullableContextEnabled,
+ List generatedInterfaceNames
+ )
+ {
+ string? RenderTypeSymbolWithNullableAnnotation(SymbolDisplayPart part) =>
+ part.Symbol is ITypeSymbol typeSymbol
+ ? typeSymbol
+ .WithNullableAnnotation(NullableAnnotation.Annotated)
+ .ToDisplayString(displayFormat)
+ : null;
+
+ // Special case for reference parameters with default value null (e.g. string x = null) - the nullable
+ // context isn't applied automatically, so it must be forced explicitly
+ var forceNullableAnnotation =
+ nullableContextEnabled
+ && symbol
+ is {
+ Type.IsReferenceType: true,
+ HasExplicitDefaultValue: true,
+ ExplicitDefaultValue: null
+ }
+ && symbol.NullableAnnotation != NullableAnnotation.Annotated;
+
+ return ToDisplayString(
+ symbol,
+ displayFormat,
+ generatedInterfaceNames,
+ forceNullableAnnotation ? RenderTypeSymbolWithNullableAnnotation : null
+ );
+ }
+
+ public static string ToDisplayString(
+ this ITypeSymbol symbol,
+ SymbolDisplayFormat displayFormat,
+ List generatedInterfaceNames
+ ) => ToDisplayString((ISymbol)symbol, displayFormat, generatedInterfaceNames);
+
+ ///
+ /// Wraps with custom resolution for generated types
+ ///
+ private static string ToDisplayString(
+ this ISymbol symbol,
+ SymbolDisplayFormat displayFormat,
+ List generatedInterfaceNames,
+ Func? customRenderDisplayPart = null
+ )
+ {
+ var displayStringBuilder = new StringBuilder();
+
+ var displayParts = GetDisplayParts(symbol, displayFormat);
+
+ foreach (var part in displayParts)
+ {
+ if (part.Kind == SymbolDisplayPartKind.ErrorTypeName)
+ {
+ var unrecognisedName = part.ToString();
+
+ var inferredName = ReplaceWithInferredInterfaceName(
+ unrecognisedName,
+ generatedInterfaceNames
+ );
+
+ displayStringBuilder.Append(inferredName);
+ }
+ else
+ {
+ var customRender = customRenderDisplayPart?.Invoke(part);
+ displayStringBuilder.Append(customRender ?? part.ToString());
+ }
+ }
+
+ return displayStringBuilder.ToString();
+ }
+
+ ///
+ /// The same as but with adjacent SymbolDisplayParts merged into qualified type references, e.g. [Parent, ., Child] => Parent.Child
+ ///
+ private static IEnumerable GetDisplayParts(
+ ISymbol symbol,
+ SymbolDisplayFormat displayFormat
+ )
+ {
+ var cache = new List();
+
+ foreach (var part in symbol.ToDisplayParts(displayFormat))
+ {
+ if (cache.Count == 0)
+ {
+ cache.Add(part);
+ continue;
+ }
+
+ var previousPart = cache.Last();
+
+ if (
+ IsPartQualificationPunctuation(previousPart)
+ ^ IsPartQualificationPunctuation(part)
+ )
+ {
+ cache.Add(part);
+ }
+ else
+ {
+ yield return CombineQualifiedTypeParts(cache);
+ cache.Clear();
+ cache.Add(part);
+ }
+ }
+
+ if (cache.Count > 0)
+ {
+ yield return CombineQualifiedTypeParts(cache);
+ }
+
+ static SymbolDisplayPart CombineQualifiedTypeParts(
+ ICollection qualifiedTypeParts
+ )
+ {
+ var qualifiedType = qualifiedTypeParts.Last();
+
+ return qualifiedTypeParts.Count == 1
+ ? qualifiedType
+ : new SymbolDisplayPart(
+ qualifiedType.Kind,
+ qualifiedType.Symbol,
+ string.Join("", qualifiedTypeParts)
+ );
+ }
+
+ static bool IsPartQualificationPunctuation(SymbolDisplayPart part) =>
+ part.ToString() is "." or "::";
+ }
+
+ private static string ReplaceWithInferredInterfaceName(
+ string unrecognisedName,
+ List generatedInterfaceNames
+ )
+ {
+ var matches = generatedInterfaceNames
+ .Where(name => Regex.IsMatch(name, $"[.:]{Regex.Escape(unrecognisedName)}$"))
+ .ToList();
+
+ if (matches.Count != 1)
+ {
+ // Either there's no match or an ambiguous match - we can't safely infer the interface name.
+ // This is very much a "best effort" approach - if there are two interfaces with the same name,
+ // there's no obvious way to work out which one the symbol is referring to.
+ return unrecognisedName;
+ }
+
+ return matches[0];
+ }
}
}
diff --git a/AutomaticInterface/Tests/Infrastructure.cs b/AutomaticInterface/Tests/Infrastructure.cs
index 9a5c662..0420f83 100644
--- a/AutomaticInterface/Tests/Infrastructure.cs
+++ b/AutomaticInterface/Tests/Infrastructure.cs
@@ -26,7 +26,7 @@ public static string GenerateCode(string code)
var sourceDiagnostics = compilation.GetDiagnostics();
var sourceErrors = sourceDiagnostics
.Where(d => d.Severity == DiagnosticSeverity.Error)
- .Where(x => x.Id != "CS0246") // missing references are ok
+ .Where(x => x.Id != "CS0246" && x.Id != "CS0234") // missing references are ok
.ToList();
Assert.Empty(sourceErrors);
@@ -45,6 +45,10 @@ out var diagnostics
Assert.Empty(errors);
- return outputCompilation.SyntaxTrees.Skip(1).LastOrDefault()?.ToString();
+ // The first syntax tree is the input code, the second two are the two generated attribute classes, and the rest is the generated code.
+ return string.Join(
+ Environment.NewLine + Environment.NewLine,
+ outputCompilation.SyntaxTrees.Skip(3)
+ );
}
}
diff --git a/AutomaticInterface/Tests/Methods/Methods.WorksWithGenericMethods.verified.txt b/AutomaticInterface/Tests/Methods/Methods.WorksWithGenericMethods.verified.txt
index c65520d..c1c55fd 100644
--- a/AutomaticInterface/Tests/Methods/Methods.WorksWithGenericMethods.verified.txt
+++ b/AutomaticInterface/Tests/Methods/Methods.WorksWithGenericMethods.verified.txt
@@ -12,7 +12,7 @@ namespace AutomaticInterfaceExample
public partial interface IDemoClass
{
///
- string CMethod(string x, string y) where T : class where T1 : struct where T3 : global::AutomaticInterfaceExample.DemoClass where T4 : IDemoClass where T5 : new();
+ string CMethod(string x, string y) where T : class where T1 : struct where T3 : global::AutomaticInterfaceExample.DemoClass where T4 : global::AutomaticInterfaceExample.IDemoClass where T5 : new();
}
}
diff --git a/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithGeneratedGenericInterfaceReferences.verified.txt b/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithGeneratedGenericInterfaceReferences.verified.txt
new file mode 100644
index 0000000..10967e0
--- /dev/null
+++ b/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithGeneratedGenericInterfaceReferences.verified.txt
@@ -0,0 +1,41 @@
+//--------------------------------------------------------------------------------------------------
+//
+// 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 Processor
+{
+ [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
+ public partial interface IModelProcessor
+ {
+ ///
+ global::Models.IModel Template { get; }
+
+ ///
+ global::Models.IModel> Process(global::Models.IModel model) where T1 : global::Models.IModel>;
+
+ ///
+ event EventHandler> ModelChanged;
+
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------
+//
+// 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 Models
+{
+ [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
+ public partial interface IModel
+ {
+ }
+}
diff --git a/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithGeneratedInterfaceReferences.verified.txt b/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithGeneratedInterfaceReferences.verified.txt
new file mode 100644
index 0000000..6c26967
--- /dev/null
+++ b/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithGeneratedInterfaceReferences.verified.txt
@@ -0,0 +1,41 @@
+//--------------------------------------------------------------------------------------------------
+//
+// 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 Processor
+{
+ [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
+ public partial interface IModelProcessor
+ {
+ ///
+ global::Models.IModel Template { get; }
+
+ ///
+ global::Models.IModel Process(global::Models.IModel model);
+
+ ///
+ event EventHandler ModelChanged;
+
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------
+//
+// 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 Models
+{
+ [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
+ public partial interface IModel
+ {
+ }
+}
diff --git a/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithOverlappingGeneratedInterfaceReferences.verified.txt b/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithOverlappingGeneratedInterfaceReferences.verified.txt
new file mode 100644
index 0000000..5f28270
--- /dev/null
+++ b/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithOverlappingGeneratedInterfaceReferences.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithQualifiedGeneratedInterfaceReferences.verified.txt b/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithQualifiedGeneratedInterfaceReferences.verified.txt
new file mode 100644
index 0000000..47ff738
--- /dev/null
+++ b/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithQualifiedGeneratedInterfaceReferences.verified.txt
@@ -0,0 +1,41 @@
+//--------------------------------------------------------------------------------------------------
+//
+// 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 Processor
+{
+ [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
+ public partial interface IModelProcessor
+ {
+ ///
+ global::ModelsRoot.Models.IModel Template { get; }
+
+ ///
+ global::ModelsRoot.Models.IModel Process(global::ModelsRoot.Models.IModel model);
+
+ ///
+ event EventHandler ModelChanged;
+
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------
+//
+// 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 ModelsRoot.Models
+{
+ [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
+ public partial interface IModel
+ {
+ }
+}
diff --git a/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithQualifiedGeneratedInterfaceReferencesAndOverlappingNamespaces.verified.txt b/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithQualifiedGeneratedInterfaceReferencesAndOverlappingNamespaces.verified.txt
new file mode 100644
index 0000000..de8da35
--- /dev/null
+++ b/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.WorksWithQualifiedGeneratedInterfaceReferencesAndOverlappingNamespaces.verified.txt
@@ -0,0 +1,38 @@
+//--------------------------------------------------------------------------------------------------
+//
+// 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 Root.Processor
+{
+ [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
+ public partial interface IModelProcessor
+ {
+ ///
+ global::Root.ModelsRoot.Models.IModel ProcessFullyQualified(global::Root.ModelsRoot.Models.IModel model);
+
+ ///
+ global::Root.ModelsRoot.Models.IModel ProcessRelativeQualified(global::Root.ModelsRoot.Models.IModel model);
+
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------
+//
+// 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 Root.ModelsRoot.Models
+{
+ [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
+ public partial interface IModel
+ {
+ }
+}
diff --git a/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.cs b/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.cs
index 5e75813..18ead60 100644
--- a/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.cs
+++ b/AutomaticInterface/Tests/TypeResolutions/TypeResolutions.cs
@@ -170,4 +170,132 @@ public class DemoClass
await Verify(Infrastructure.GenerateCode(code));
}
+
+ [Fact]
+ public async Task WorksWithGeneratedInterfaceReferences()
+ {
+ const string code = """
+ using AutomaticInterface;
+
+ namespace Processor
+ {
+ using Models;
+
+ [GenerateAutomaticInterface]
+ public class ModelProcessor : IModelProcessor
+ {
+ public IModel Process(IModel model) => null;
+
+ public event EventHandler ModelChanged;
+
+ public IModel Template => null;
+ }
+ }
+
+ namespace Models
+ {
+
+ [GenerateAutomaticInterface]
+ public class Model : IModel;
+ }
+ """;
+
+ await Verify(Infrastructure.GenerateCode(code));
+ }
+
+ [Fact]
+ public async Task WorksWithGeneratedGenericInterfaceReferences()
+ {
+ const string code = """
+ using AutomaticInterface;
+ using System.Collections.Generic;
+
+ namespace Processor
+ {
+ using Models;
+
+ [GenerateAutomaticInterface]
+ public class ModelProcessor : IModelProcessor
+ {
+ public IModel> Process(IModel model) where T1: IModel> => null;
+
+ public event EventHandler> ModelChanged;
+
+ public IModel Template => null;
+ }
+ }
+
+ namespace Models
+ {
+
+ [GenerateAutomaticInterface]
+ public class Model;
+ }
+ """;
+
+ await Verify(Infrastructure.GenerateCode(code));
+ }
+
+ [Fact]
+ public async Task WorksWithQualifiedGeneratedInterfaceReferences()
+ {
+ const string code = """
+ using AutomaticInterface;
+
+ namespace Processor
+ {
+ using ModelsRoot;
+
+ [GenerateAutomaticInterface]
+ public class ModelProcessor : IModelProcessor
+ {
+ public Models.IModel Process(Models.IModel model) => null;
+
+ public event EventHandler ModelChanged;
+
+ public Models.IModel Template => null;
+ }
+ }
+
+ namespace ModelsRoot.Models
+ {
+
+ [GenerateAutomaticInterface]
+ public class Model : IModel;
+ }
+ """;
+
+ await Verify(Infrastructure.GenerateCode(code));
+ }
+
+ [Fact]
+ public async Task WorksWithQualifiedGeneratedInterfaceReferencesAndOverlappingNamespaces()
+ {
+ const string code = """
+ using AutomaticInterface;
+
+ namespace Root
+ {
+ namespace Processor
+ {
+ [GenerateAutomaticInterface]
+ public class ModelProcessor : IModelProcessor
+ {
+ public Root.ModelsRoot.Models.IModel ProcessFullyQualified(Root.ModelsRoot.Models.IModel model) => null;
+
+ public ModelsRoot.Models.IModel ProcessRelativeQualified(ModelsRoot.Models.IModel model) => null;
+ }
+ }
+
+ namespace ModelsRoot.Models
+ {
+
+ [GenerateAutomaticInterface]
+ public class Model : IModel;
+ }
+ }
+ """;
+
+ await Verify(Infrastructure.GenerateCode(code));
+ }
}
diff --git a/README.md b/README.md
index 6566726..bdee03d 100644
--- a/README.md
+++ b/README.md
@@ -188,9 +188,13 @@ Note that we use [Verify](https://github.com/VerifyTests/Verify) for testing. It
## Changelog
+### 6.0.0
+
+- Added feature to allow generated interfaces to reference other generated interfaces
+
### 5.2.6
-- Fix wrong documenation on interface for this library
+- Fix wrong documentation on interface for this library
- Fix handling of parameters with default null. Thanks paramamue!
### 5.2.5