diff --git a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems index 5589f69c2..3c02c35ff 100644 --- a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems +++ b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems @@ -6,10 +6,12 @@ b591423d-f92d-4e00-b0eb-615c9853506c - InterfaceStubGenerator.Shared + Refit.Generator + + \ No newline at end of file diff --git a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs index c479e7972..c8a02b1b1 100644 --- a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs +++ b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs @@ -119,6 +119,8 @@ ImmutableArray candidateInterfaces return; } + var refitMetadata = new RefitMetadata(disposableInterfaceSymbol, httpMethodBaseAttributeSymbol); + // Check the candidates and keep the ones we're actually interested in #pragma warning disable RS1024 // Compare symbols correctly @@ -134,7 +136,7 @@ ImmutableArray candidateInterfaces { // Get the symbol being declared by the method var methodSymbol = model.GetDeclaredSymbol(method); - if (IsRefitMethod(methodSymbol, httpMethodBaseAttributeSymbol)) + if (refitMetadata.IsRefitMethod(methodSymbol)) { var isAnnotated = compilation.Options.NullableContextOptions @@ -170,9 +172,9 @@ ImmutableArray candidateInterfaces continue; // The interface has no refit methods, but its base interfaces might - var hasDerivedRefit = ifaceSymbol - .AllInterfaces.SelectMany(i => i.GetMembers().OfType()) - .Any(m => IsRefitMethod(m, httpMethodBaseAttributeSymbol)); + var hasDerivedRefit = ifaceSymbol.AllInterfaces + .SelectMany(i => i.GetMembers().OfType()) + .Any(refitMetadata.IsRefitMethod); if (hasDerivedRefit) { @@ -276,20 +278,15 @@ public static void Initialize() // each group is keyed by the Interface INamedTypeSymbol and contains the members // with a refit attribute on them. Types may contain other members, without the attribute, which we'll // need to check for and error out on - - var classSource = ProcessInterface( - context, - reportDiagnostic, - group.Key, - group.Value, - preserveAttributeSymbol, - disposableInterfaceSymbol, - httpMethodBaseAttributeSymbol, - supportsNullable, - interfaceToNullableEnabledMap[group.Key] - ); - - var keyName = group.Key.Name; + var model = new RefitClientModel(group.Key, group.Value, refitMetadata); + var classSource = ProcessInterface(context, + reportDiagnostic, + model, + preserveAttributeSymbol, + supportsNullable, + interfaceToNullableEnabledMap[model.RefitInterface]); + + var keyName = model.FileName; int value; while (keyCount.TryGetValue(keyName, out value)) { @@ -304,39 +301,14 @@ public static void Initialize() static string ProcessInterface( TContext context, Action reportDiagnostic, - INamedTypeSymbol interfaceSymbol, - List refitMethods, + RefitClientModel interfaceModel, ISymbol preserveAttributeSymbol, - ISymbol disposableInterfaceSymbol, - INamedTypeSymbol httpMethodBaseAttributeSymbol, bool supportsNullable, bool nullableEnabled ) { - // Get the class name with the type parameters, then remove the namespace - var className = interfaceSymbol.ToDisplayString(); - var lastDot = className.LastIndexOf('.'); - if (lastDot > 0) - { - className = className.Substring(lastDot + 1); - } - var classDeclaration = $"{interfaceSymbol.ContainingType?.Name}{className}"; - - // Get the class name itself - var classSuffix = $"{interfaceSymbol.ContainingType?.Name}{interfaceSymbol.Name}"; - var ns = interfaceSymbol.ContainingNamespace?.ToDisplayString(); - - // if it's the global namespace, our lookup rules say it should be the same as the class name - if ( - interfaceSymbol.ContainingNamespace != null - && interfaceSymbol.ContainingNamespace.IsGlobalNamespace - ) - { - ns = string.Empty; - } - - // Remove dots - ns = ns!.Replace(".", ""); + INamedTypeSymbol interfaceSymbol = interfaceModel.RefitInterface; + List refitMethods = interfaceModel.RefitMethods; // See what the nullable context is @@ -371,8 +343,8 @@ partial class Generated [{preserveAttributeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}] [global::System.Reflection.Obfuscation(Exclude=true)] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - partial class {ns}{classDeclaration} - : {interfaceSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}{GenerateConstraints(interfaceSymbol.TypeParameters, false)} + partial class {interfaceModel.NamespacePrefix}{interfaceModel.ClassDeclaration} + : {interfaceModel.BaseInterfaceDeclaration}{GenerateConstraints(interfaceSymbol.TypeParameters, false)} {{ /// @@ -380,49 +352,13 @@ partial class {ns}{classDeclaration} readonly global::Refit.IRequestBuilder requestBuilder; /// - public {ns}{classSuffix}(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) + public {interfaceModel.NamespacePrefix}{interfaceModel.ClassSuffix}(global::System.Net.Http.HttpClient client, global::Refit.IRequestBuilder requestBuilder) {{ Client = client; this.requestBuilder = requestBuilder; }} -" - ); - // Get any other methods on the refit interfaces. We'll need to generate something for them and warn - var nonRefitMethods = interfaceSymbol - .GetMembers() - .OfType() - .Except(refitMethods, SymbolEqualityComparer.Default) - .Cast() - .ToList(); - - // get methods for all inherited - var derivedMethods = interfaceSymbol - .AllInterfaces.SelectMany(i => i.GetMembers().OfType()) - .ToList(); - - // Look for disposable - var disposeMethod = derivedMethods.Find( - m => - m.ContainingType?.Equals( - disposableInterfaceSymbol, - SymbolEqualityComparer.Default - ) == true - ); - if (disposeMethod != null) - { - //remove it from the derived methods list so we don't process it with the rest - derivedMethods.Remove(disposeMethod); - } - - // Pull out the refit methods from the derived types - var derivedRefitMethods = derivedMethods - .Where(m => IsRefitMethod(m, httpMethodBaseAttributeSymbol)) - .ToList(); - var derivedNonRefitMethods = derivedMethods - .Except(derivedMethods, SymbolEqualityComparer.Default) - .Cast() - .ToList(); +"); var memberNames = new HashSet(interfaceSymbol.GetMembers().Select(x => x.Name)); @@ -432,30 +368,22 @@ partial class {ns}{classDeclaration} ProcessRefitMethod(source, method, true, memberNames); } - foreach (var method in refitMethods.Concat(derivedRefitMethods)) + foreach (var method in interfaceModel.AllRefitMethods) { ProcessRefitMethod(source, method, false, memberNames); } // Handle non-refit Methods that aren't static or properties or have a method body - foreach (var method in nonRefitMethods.Concat(derivedNonRefitMethods)) + foreach (var method in interfaceModel.NonRefitMethods) { - if ( - method.IsStatic - || method.MethodKind == MethodKind.PropertyGet - || method.MethodKind == MethodKind.PropertySet - || !method.IsAbstract - ) // If an interface method has a body, it won't be abstract - continue; - ProcessNonRefitMethod(context, reportDiagnostic, source, method); } // Handle Dispose - if (disposeMethod != null) + if (interfaceModel.DisposeMethod != null) { - ProcessDisposableMethod(source, disposeMethod); - } + ProcessDisposableMethod(source, interfaceModel.DisposeMethod); + } source.Append( @" @@ -779,14 +707,6 @@ static string UniqueName(string name, HashSet methodNames) return candidateName; } - static bool IsRefitMethod(IMethodSymbol? methodSymbol, INamedTypeSymbol httpMethodAttibute) - { - return methodSymbol - ?.GetAttributes() - .Any(ad => ad.AttributeClass?.InheritsFromOrEquals(httpMethodAttibute) == true) - == true; - } - #if ROSLYN_4 /// diff --git a/InterfaceStubGenerator.Shared/RefitClientModel.cs b/InterfaceStubGenerator.Shared/RefitClientModel.cs new file mode 100644 index 000000000..d6ad313d7 --- /dev/null +++ b/InterfaceStubGenerator.Shared/RefitClientModel.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using System.Linq; + +using Microsoft.CodeAnalysis; + +namespace Refit.Generator; + +internal class RefitClientModel +{ + readonly RefitMetadata refitMetadata; + + public RefitClientModel(INamedTypeSymbol refitInterface, List refitMethods, RefitMetadata refitMetadata) + { + RefitInterface = refitInterface; + RefitMethods = refitMethods; + this.refitMetadata = refitMetadata; + + // Get any other methods on the refit interfaces. We'll need to generate something for them and warn + var nonRefitMethods = refitInterface + .GetMembers() + .OfType() + .Except(refitMethods, SymbolEqualityComparer.Default) + .Cast() + .ToList(); + + // get methods for all inherited + var derivedMethods = refitInterface + .AllInterfaces.SelectMany(i => i.GetMembers().OfType()) + .ToList(); + + // Look for disposable + DisposeMethod = derivedMethods.Find( + m => + m.ContainingType?.Equals( + refitMetadata.DisposableInterfaceSymbol, + SymbolEqualityComparer.Default + ) == true + ); + if (DisposeMethod != null) + { + //remove it from the derived methods list so we don't process it with the rest + derivedMethods.Remove(DisposeMethod); + } + + // Pull out the refit methods from the derived types + var derivedRefitMethods = derivedMethods.Where(refitMetadata.IsRefitMethod).ToList(); + var derivedNonRefitMethods = derivedMethods.Except(derivedMethods, SymbolEqualityComparer.Default).Cast().ToList(); + + AllRefitMethods = refitMethods.Concat(derivedRefitMethods); + NonRefitMethods = nonRefitMethods.Concat(derivedNonRefitMethods) + .Where(static method => + { + return !(method.IsStatic || + method.MethodKind == MethodKind.PropertyGet || + method.MethodKind == MethodKind.PropertySet || + !method.IsAbstract); + }); + } + + public INamedTypeSymbol RefitInterface { get; } + public List RefitMethods { get; } + public IEnumerable AllRefitMethods { get; } + public IEnumerable NonRefitMethods { get; } + + public string FileName => RefitInterface.Name; + + public string ClassDeclaration + { + get + { + // Get the class name with the type parameters, then remove the namespace + var className = RefitInterface.ToDisplayString(); + var lastDot = className.LastIndexOf('.'); + if (lastDot > 0) + { + className = className.Substring(lastDot + 1); + } + var classDeclaration = $"{RefitInterface.ContainingType?.Name}{className}"; + return classDeclaration; + } + } + + public string ClassSuffix + { + get + { + // Get the class name itself + var classSuffix = $"{RefitInterface.ContainingType?.Name}{RefitInterface.Name}"; + return classSuffix; + } + } + + public string NamespacePrefix + { + get + { + var ns = RefitInterface.ContainingNamespace?.ToDisplayString(); + + // if it's the global namespace, our lookup rules say it should be the same as the class name + if (RefitInterface.ContainingNamespace != null && RefitInterface.ContainingNamespace.IsGlobalNamespace) + { + return string.Empty; + } + + // Remove dots + ns = ns!.Replace(".", ""); + return ns; + } + } + public string BaseInterfaceDeclaration => $"{RefitInterface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}"; + + public IMethodSymbol DisposeMethod { get; } +} diff --git a/InterfaceStubGenerator.Shared/RefitMetadata.cs b/InterfaceStubGenerator.Shared/RefitMetadata.cs new file mode 100644 index 000000000..b6fa0bbd0 --- /dev/null +++ b/InterfaceStubGenerator.Shared/RefitMetadata.cs @@ -0,0 +1,22 @@ +using System.Linq; + +using Microsoft.CodeAnalysis; + +namespace Refit.Generator; + +internal class RefitMetadata +{ + public RefitMetadata(INamedTypeSymbol? disposableInterfaceSymbol, INamedTypeSymbol httpMethodBaseAttributeSymbol) + { + DisposableInterfaceSymbol = disposableInterfaceSymbol; + HttpMethodBaseAttributeSymbol = httpMethodBaseAttributeSymbol; + } + + public INamedTypeSymbol? DisposableInterfaceSymbol { get; } + public INamedTypeSymbol HttpMethodBaseAttributeSymbol { get; } + + public bool IsRefitMethod(IMethodSymbol? methodSymbol) + { + return methodSymbol?.GetAttributes().Any(ad => ad.AttributeClass?.InheritsFromOrEquals(HttpMethodBaseAttributeSymbol) == true) == true; + } +}