Skip to content

Commit c480be7

Browse files
committed
fix: Fixed API generator to consider Aliases types
1 parent 64ee569 commit c480be7

File tree

12 files changed

+391
-173
lines changed

12 files changed

+391
-173
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Tools.Generators.Web.Api.UnitTests.TestData.ADirectory;
2+
3+
public interface INestedAliasedDependency
4+
{
5+
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace Tools.Generators.Web.Api.UnitTests.TestData;
2+
3+
public interface IAliasedDependency
4+
{
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
</Project>

src/Framework/Tools/Tools.Generators.Web.Api.UnitTests/MinimalApiGeneratorSpec.cs

Lines changed: 118 additions & 21 deletions
Large diffs are not rendered by default.

src/Framework/Tools/Tools.Generators.Web.Api.UnitTests/Tools.Generators.Web.Api.UnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
</PropertyGroup>
77

88
<ItemGroup>
9+
<ProjectReference Include="..\Tools.Generators.Web.Api.UnitTests.TestData\Tools.Generators.Web.Api.UnitTests.TestData.csproj" />
910
<ProjectReference Include="..\Tools.Generators.Web.Api\Tools.Generators.Web.Api.csproj" Aliases="Generators" />
1011
<ProjectReference Include="..\..\Testing\UnitTesting.CodeAnalysis.Common\UnitTesting.CodeAnalysis.Common.csproj" />
1112
</ItemGroup>

src/Framework/Tools/Tools.Generators.Web.Api.UnitTests/WebApiAssemblyVisitorSpec.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ public string AMethod(ARequest request)
403403
registration.Class.Constructors.First().MethodBody.Should().BeEmpty();
404404
registration.Class.TypeName.Name.Should().Be("AServiceClass");
405405
registration.Class.TypeName.Namespace.Should().Be("ANamespace");
406-
registration.Class.TypeName.FullName.Should().Be("ANamespace.AServiceClass");
406+
registration.Class.TypeName.FullName.Should().Be("global::ANamespace.AServiceClass");
407407
registration.Class.UsingNamespaces.Count().Should().Be(2);
408408
registration.MethodBody.Should().Be($" {{{Environment.NewLine}"
409409
+ $" return \"\";{Environment.NewLine}"
@@ -458,7 +458,7 @@ public string AMethod(ARequest request)
458458
registration.Class.Constructors.First().MethodBody.Should().BeEmpty();
459459
registration.Class.TypeName.Name.Should().Be("AServiceClass");
460460
registration.Class.TypeName.Namespace.Should().Be("ANamespace");
461-
registration.Class.TypeName.FullName.Should().Be("ANamespace.AServiceClass");
461+
registration.Class.TypeName.FullName.Should().Be("global::ANamespace.AServiceClass");
462462
registration.Class.UsingNamespaces.Count().Should().Be(2);
463463
registration.MethodBody.Should().Be($" {{{Environment.NewLine}"
464464
+ $" return \"\";{Environment.NewLine}"
@@ -515,7 +515,7 @@ public string AMethod(ARequest request)
515515
registration.Class.Constructors.First().MethodBody.Should().BeEmpty();
516516
registration.Class.TypeName.Name.Should().Be("AServiceClass");
517517
registration.Class.TypeName.Namespace.Should().Be("ANamespace");
518-
registration.Class.TypeName.FullName.Should().Be("ANamespace.AServiceClass");
518+
registration.Class.TypeName.FullName.Should().Be("global::ANamespace.AServiceClass");
519519
registration.Class.UsingNamespaces.Count().Should().Be(2);
520520
registration.MethodBody.Should().Be($" {{{Environment.NewLine}"
521521
+ $" return \"\";{Environment.NewLine}"

src/Framework/Tools/Tools.Generators.Web.Api/Extensions/SymbolExtensions.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,30 @@ public static IEnumerable<string> GetUsingNamespaces(this INamedTypeSymbol symbo
4343
: symbol.DeclaringSyntaxReferences.FirstOrDefault();
4444
if (syntaxReference is null)
4545
{
46-
return Enumerable.Empty<string>();
46+
return [];
4747
}
4848

49-
var usingSyntaxes = syntaxReference.SyntaxTree.GetRoot()
49+
var descendantNodes = syntaxReference.SyntaxTree.GetRoot();
50+
var usingSyntaxes = descendantNodes
5051
.DescendantNodes()
5152
.OfType<UsingDirectiveSyntax>();
53+
var aliasSyntaxes = descendantNodes
54+
.DescendantNodes()
55+
.OfType<ExternAliasDirectiveSyntax>();
5256

53-
return usingSyntaxes.Select(us => us.Name!.ToString())
57+
var aliasDeclarations = aliasSyntaxes
58+
.Select(us => us.ToString())
59+
.Distinct()
60+
.OrderByDescending(s => s);
61+
var usingDeclarations = usingSyntaxes
62+
.Select(us => us.Name!.ToString())
5463
.Distinct()
55-
.OrderByDescending(s => s)
64+
.OrderByDescending(s => s);
65+
var namespaces = aliasDeclarations
66+
.Concat(usingDeclarations)
5667
.ToList();
68+
69+
return namespaces;
5770
}
5871

5972
public static bool IsClass(this ITypeSymbol type)

src/Framework/Tools/Tools.Generators.Web.Api/MinimalApiGenerator.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,14 @@ private static string BuildUsingList(
8484
.SelectMany(registration => registration.Class.UsingNamespaces)
8585
.Concat(RequiredUsingNamespaces)
8686
.Distinct()
87-
.OrderByDescending(s => s)
87+
.Select(s => s.StartsWith("extern alias")
88+
? s
89+
: $"using {s};")
90+
.OrderByDescending(s => s.StartsWith("extern alias"))
91+
.ThenByDescending(s => s)
8892
.ToList();
8993

90-
allNamespaces.ForEach(@using => usingList.AppendLine($"using {@using};"));
94+
allNamespaces.ForEach(@using => usingList.AppendLine(@using));
9195

9296
return usingList.ToString();
9397
}
@@ -123,7 +127,7 @@ private static void BuildEndpointRegistrations(
123127
endpointRegistrations.AppendLine(
124128
$" {groupName}.{endPointMethodName}(\"{registration.RoutePath}\",");
125129
endpointRegistrations.AppendLine(
126-
$" async (global::System.IServiceProvider serviceProvider, global::{registration.RequestDto.FullName} request) =>");
130+
$" async (global::System.IServiceProvider serviceProvider, {registration.RequestDto.FullName} request) =>");
127131
endpointRegistrations.AppendLine(
128132
$" {methodBody})");
129133

@@ -268,7 +272,7 @@ private static string BuildHandlerBody(WebApiAssemblyVisitor.ServiceOperationReg
268272
handlerMethod.AppendLine(
269273
$" static async Task<global::Microsoft.AspNetCore.Http.IResult>"
270274
+ $" Handle(global::System.IServiceProvider services, "
271-
+ $"global::{registration.RequestDto.FullName} request, "
275+
+ $"{registration.RequestDto.FullName} request, "
272276
+ $"global::System.Threading.CancellationToken cancellationToken)");
273277
handlerMethod.AppendLine(" {");
274278

@@ -294,7 +298,7 @@ private static string BuildHandlerBody(WebApiAssemblyVisitor.ServiceOperationReg
294298
}
295299

296300
handlerMethod.AppendLine(
297-
$" var api = new global::{registration.Class.TypeName.FullName}({callingParameters});");
301+
$" var api = new {registration.Class.TypeName.FullName}({callingParameters});");
298302
var asyncAwait = registration.IsAsync
299303
? "await "
300304
: string.Empty;

src/Framework/Tools/Tools.Generators.Web.Api/Tools.Generators.Web.Api.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
<Compile Include="..\..\Common\Extensions\ObjectExtensions.cs">
3939
<Link>Reference\Common\Extensions\ObjectExtensions.cs</Link>
4040
</Compile>
41+
<Compile Include="..\..\Infrastructure\Infrastructure.Interfaces\ICallerContextFactory.cs">
42+
<Link>Reference\Infrastructure.Interfaces\ICallerContextFactory.cs</Link>
43+
</Compile>
4144
<Compile Include="..\..\Infrastructure\Infrastructure.Web.Api.Interfaces\IWebApiService.cs">
4245
<Link>Reference\Infrastructure.Web.Api.Interfaces\IWebApiService.cs</Link>
4346
</Compile>

src/Framework/Tools/Tools.Generators.Web.Api/WebApiAssemblyVisitor.cs

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Common.Extensions;
22
using Infrastructure.Web.Api.Interfaces;
33
using Microsoft.CodeAnalysis;
4+
using Microsoft.CodeAnalysis.CSharp.Syntax;
45
using Tools.Generators.Web.Api.Extensions;
56

67
namespace Tools.Generators.Web.Api;
@@ -31,8 +32,8 @@ public class WebApiAssemblyVisitor : SymbolVisitor
3132
private readonly INamedTypeSymbol _baseApiAttributeSymbol;
3233
private readonly CancellationToken _cancellationToken;
3334
private readonly INamedTypeSymbol _cancellationTokenSymbol;
34-
private readonly INamedTypeSymbol _multipartFormDataSymbol;
3535
private readonly INamedTypeSymbol _formUrlEncodedSymbol;
36+
private readonly INamedTypeSymbol _multipartFormDataSymbol;
3637
private readonly INamedTypeSymbol _routeAttributeSymbol;
3738
private readonly INamedTypeSymbol _serviceInterfaceSymbol;
3839
private readonly INamedTypeSymbol _tenantedWebRequestInterfaceSymbol;
@@ -193,12 +194,8 @@ private void AddRegistration(INamedTypeSymbol symbol)
193194
var operationAuthorization = FromAuthorizeAttribute(authorizeAttributes);
194195
var isTestingOnly = bool.Parse(routeAttributeParameters[3].Value!.ToString()!);
195196
var requestType = method.Parameters[0].Type;
196-
var requestTypeName = requestType.Name;
197-
var requestTypeNamespace = requestType.ContainingNamespace.ToDisplayString();
198197
var requestTypeIsMultiTenanted = requestType.IsDerivedFrom(_tenantedWebRequestInterfaceSymbol);
199198
var responseType = GetResponseType(method.Parameters[0].Type);
200-
var responseTypeName = responseType.Name;
201-
var responseTypeNamespace = responseType.ContainingNamespace.ToDisplayString();
202199
var methodBody = method.GetMethodBody();
203200
var methodName = method.Name;
204201
var isAsync = method.IsAsync;
@@ -209,9 +206,9 @@ private void AddRegistration(INamedTypeSymbol symbol)
209206
OperationRegistrations.Add(new ServiceOperationRegistration
210207
{
211208
Class = classRegistration,
212-
RequestDto = new TypeName(requestTypeNamespace, requestTypeName),
209+
RequestDto = new TypeName(requestType),
213210
IsRequestDtoTenanted = requestTypeIsMultiTenanted,
214-
ResponseDto = new TypeName(responseTypeNamespace, responseTypeName),
211+
ResponseDto = new TypeName(responseType),
215212
OperationMethod = operationType,
216213
OperationAccess = operationAccess,
217214
OperationAuthorization = operationAuthorization,
@@ -240,7 +237,7 @@ private void AddRegistration(INamedTypeSymbol symbol)
240237

241238
TypeName GetServiceName()
242239
{
243-
return new TypeName(symbol.ContainingNamespace.ToDisplayString(), symbol.Name);
240+
return new TypeName(symbol);
244241
}
245242

246243
static OperationMethod FromOperationVerb(string? operation)
@@ -354,7 +351,7 @@ List<Constructor> GetConstructors()
354351
IsInjectionCtor = isInjectionCtor,
355352
CtorParameters = constructor.Parameters.Select(param => new ConstructorParameter
356353
{
357-
TypeName = new TypeName(param.Type.ContainingNamespace.ToDisplayString(), param.Type.Name),
354+
TypeName = new TypeName(param),
358355
VariableName = param.Name
359356
})
360357
.ToList(),
@@ -473,10 +470,10 @@ public record ServiceOperationRegistration
473470

474471
public bool IsAsync { get; set; }
475472

476-
public bool IsMultipartFormData { get; set; }
477-
478473
public bool IsFormUrlEncoded { get; set; }
479474

475+
public bool IsMultipartFormData { get; set; }
476+
480477
public bool IsRequestDtoTenanted { get; set; }
481478

482479
public bool IsTestingOnly { get; set; }
@@ -510,13 +507,49 @@ public OperationAuthorization(string policyName)
510507

511508
public record TypeName
512509
{
513-
public TypeName(string @namespace, string name)
510+
public TypeName(IParameterSymbol symbol) : this(symbol, symbol.Type.ContainingNamespace.ToDisplayString(), symbol.Type.Name)
511+
{
512+
}
513+
514+
public TypeName(ITypeSymbol symbol) : this(symbol, symbol.ContainingNamespace.ToDisplayString(), symbol.Name)
515+
{
516+
}
517+
518+
public TypeName(INamedTypeSymbol symbol) : this(symbol, symbol.ContainingNamespace.ToDisplayString(),
519+
symbol.Name)
514520
{
515-
Namespace = @namespace;
521+
}
522+
523+
private TypeName(ISymbol symbol, string @namespace, string name)
524+
{
525+
if (IsAliasedNamespace(symbol, @namespace, out var alias))
526+
{
527+
Namespace = $"{alias}::{@namespace}";
528+
IsAliased = true;
529+
}
530+
else
531+
{
532+
Namespace = @namespace;
533+
IsAliased = false;
534+
}
535+
516536
Name = name;
517537
}
518538

519-
public string FullName => $"{Namespace}.{Name}";
539+
public string FullName
540+
{
541+
get
542+
{
543+
if (IsAliased)
544+
{
545+
return $"{Namespace}.{Name}";
546+
}
547+
548+
return $"global::{Namespace}.{Name}";
549+
}
550+
}
551+
552+
public bool IsAliased { get; }
520553

521554
public string Name { get; }
522555

@@ -534,7 +567,7 @@ public virtual bool Equals(TypeName? other)
534567
return true;
535568
}
536569

537-
return Name == other.Name && Namespace == other.Namespace;
570+
return Name == other.Name && Namespace == other.Namespace && IsAliased == other.IsAliased;
538571
}
539572

540573
public override int GetHashCode()
@@ -543,11 +576,51 @@ public override int GetHashCode()
543576
var hash = 17;
544577
hash = hash * 23 + Namespace.GetHashCode();
545578
hash = hash * 23 + Name.GetHashCode();
579+
hash = hash * 23 + IsAliased.GetHashCode();
546580
return hash;
547581
#else
548-
return HashCode.Combine(Namespace, Name);
582+
return HashCode.Combine(Namespace, Name, IsAliased);
549583
#endif
550584
}
585+
586+
private static bool IsAliasedNamespace(ISymbol symbol, string @namespace, out string alias)
587+
{
588+
alias = null!;
589+
var syntaxReference = symbol.DeclaringSyntaxReferences.IsDefaultOrEmpty
590+
? null
591+
: symbol.DeclaringSyntaxReferences.FirstOrDefault();
592+
if (syntaxReference is null)
593+
{
594+
return false;
595+
}
596+
597+
var syntax = syntaxReference.SyntaxTree.GetRoot();
598+
if (syntax.NotExists())
599+
{
600+
return false;
601+
}
602+
603+
var aliases = syntax
604+
.DescendantNodes()
605+
.OfType<UsingDirectiveSyntax>()
606+
.Select(s => s.Name!.ToString())
607+
.Distinct()
608+
.Where(s => s.Contains("::"))
609+
.ToDictionary(pair => pair.Substring(pair.IndexOf("::", StringComparison.Ordinal) + 2),
610+
pair => pair.Substring(0, pair.IndexOf("::", StringComparison.Ordinal)));
611+
if (!aliases.HasAny())
612+
{
613+
return false;
614+
}
615+
616+
if (!aliases.TryGetValue(@namespace, out var found))
617+
{
618+
return false;
619+
}
620+
621+
alias = found;
622+
return true;
623+
}
551624
}
552625

553626
public record ApiServiceClassRegistration

0 commit comments

Comments
 (0)