Skip to content

Commit

Permalink
Fixed source generators for use in Visual Studio
Browse files Browse the repository at this point in the history
  • Loading branch information
jezzsantos committed Jan 13, 2024
1 parent 0244c50 commit 1e76b92
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 66 deletions.
8 changes: 6 additions & 2 deletions README_DERIVATIVE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@
You will need the following development tools to build, run, and test this project:

* Windows or MacOS.
* Jetbrains Rider or Visual Studio (There is most support for JetBrains dotUltimate)
* JetBrains Rider (recommended) or Visual Studio
* Note: Using JetBrains Rider is recommended since the codebase includes more comprehensive customized tooling (coding standards, live templates, etc.)
* Note: If using Visual Studio, you will need to install the additional component `.NET Compiler Platform SDK` in order to run the built-in Roslyn source generators.
* Note: if using Visual Studio, the built-in Roslyn analyzers will not work (due to .netstandard2.0 [restrictions between Visual Studio and Roslyn](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview))

* Install the .NET7.0 SDK (specifically version 7.0.14). Available for [download here](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)

> We have ensured that you wont need any other infrastructure running on your local machine (i.e. SQLServer database), unless you want to run infrastructure specific integration tests.
> We have ensured that you won't need any other infrastructure running on your local machine (i.e., a Microsoft SQLServer database) unless you want to run infrastructure-specific integration tests.
# Setup Environment

Expand Down
14 changes: 10 additions & 4 deletions src/Infrastructure.Web.Api.Interfaces/RouteAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
using System.ComponentModel;
#if !NETSTANDARD2_0
using System.Diagnostics.CodeAnalysis;
#endif

namespace Infrastructure.Web.Api.Interfaces;

/// <summary>
/// Provides a declarative way to define a REST route and service operation
/// Provides a declarative way to define a REST route service operation, and configuration
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class RouteAttribute : Attribute
{
public RouteAttribute([StringSyntax("Route")] string routeTemplate, ServiceOperation operation,
public RouteAttribute(
#if !NETSTANDARD2_0
[StringSyntax("Route")]
#endif
string routeTemplate, ServiceOperation operation,
AccessType access = AccessType.Anonymous, bool isTestingOnly = false)
{
if (!Enum.IsDefined(typeof(ServiceOperation), operation))
Expand All @@ -23,10 +29,10 @@ public RouteAttribute([StringSyntax("Route")] string routeTemplate, ServiceOpera
IsTestingOnly = isTestingOnly;
}

public bool IsTestingOnly { get; }

public AccessType Access { get; }

public bool IsTestingOnly { get; }

public ServiceOperation Operation { get; }

public string RouteTemplate { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,47 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
using WebApi_MinimalApiMediatRGenerator = Generators::Tools.Generators.WebApi.MinimalApiMediatRGenerator;
using MinimalApiMediatRGenerator = Generators::Tools.Generators.WebApi.MinimalApiMediatRGenerator;

namespace Tools.Generators.WebApi.UnitTests;

[UsedImplicitly]
public class MinimalApiMediatRGeneratorSpec
{
private static readonly string[]
AdditionalCompilationAssemblies =
{ "System.Runtime.dll", "netstandard.dll" }; //HACK: required to analyze custom attributes

private static CSharpCompilation CreateCompilation(string sourceCode)
{
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location)!;

var references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(typeof(MinimalApiMediatRGenerator).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location)
};
AdditionalCompilationAssemblies.ToList()
.ForEach(item => references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, item))));
var compilation = CSharpCompilation.Create("compilation",
new[]
{
CSharpSyntaxTree.ParseText(sourceCode)
},
new[]
{
MetadataReference.CreateFromFile(typeof(WebApi_MinimalApiMediatRGenerator).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(Path.Combine(assemblyPath,
"System.Runtime.dll")) //HACK: this is required to make custom attributes work
},
references,
new CSharpCompilationOptions(OutputKind.ConsoleApplication));

return compilation;
}

[Trait("Category", "Unit")]
public class GivenAServiceCLass
public class GivenAServiceClass
{
private GeneratorDriver _driver;

public GivenAServiceCLass()
public GivenAServiceClass()
{
var generator = new WebApi_MinimalApiMediatRGenerator();
var generator = new MinimalApiMediatRGenerator();
_driver = CSharpGeneratorDriver.Create(generator);
}

Expand Down
21 changes: 12 additions & 9 deletions src/Tools.Generators.WebApi.UnitTests/WebApiAssemblyVisitorSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,26 @@ namespace Tools.Generators.WebApi.UnitTests;
public class WebApiAssemblyVisitorSpec
{
private const string CompilationSourceCode = "";
private static readonly string[]
AdditionalCompilationAssemblies =
{ "System.Runtime.dll", "netstandard.dll" }; //HACK: required to analyze use custom attributes

private static CSharpCompilation CreateCompilation(string sourceCode)
{
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location)!;

var references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(typeof(WebApiAssemblyVisitor).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location)
};
AdditionalCompilationAssemblies.ToList()
.ForEach(item => references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, item))));
var compilation = CSharpCompilation.Create("compilation",
new[]
{
CSharpSyntaxTree.ParseText(sourceCode)
},
new[]
{
MetadataReference.CreateFromFile(typeof(WebApiAssemblyVisitor).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(Path.Combine(assemblyPath,
"System.Runtime.dll")) //HACK: this is required to make custom attributes work
},
references,
new CSharpCompilationOptions(OutputKind.ConsoleApplication));

return compilation;
Expand Down Expand Up @@ -371,7 +374,7 @@ namespace ANamespace;
public class AResponse : IWebResponse
{
}
[Route("aroute", ServiceOperation.Get)]
[Infrastructure.Web.Api.Interfaces.RouteAttribute("aroute", ServiceOperation.Get)]
public class ARequest : IWebRequest<AResponse>
{
}
Expand Down
4 changes: 2 additions & 2 deletions src/Tools.Generators.WebApi/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ public static class StringExtensions
/// <summary>
/// Whether the string value contains no value: it is either: null, empty or only whitespaces
/// </summary>
public static bool HasNoValue(this string? value)
public static bool HasNoValue(this string value)
{
return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value);
}

/// <summary>
/// Whether the string value contains any value except: null, empty or only whitespaces
/// </summary>
public static bool HasValue(this string? value)
public static bool HasValue(this string value)
{
return !string.IsNullOrEmpty(value) && !string.IsNullOrWhiteSpace(value);
}
Expand Down
7 changes: 3 additions & 4 deletions src/Tools.Generators.WebApi/Extensions/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ namespace Tools.Generators.WebApi.Extensions;

public static class SymbolExtensions
{
public static AttributeData? GetAttribute(this ISymbol symbol, INamedTypeSymbol attributeType)
public static AttributeData GetAttribute(this ISymbol symbol, INamedTypeSymbol attributeType)
{
return symbol.GetAttributes()
.FirstOrDefault(attribute => attribute.AttributeClass!.IsOfType(attributeType));
}

public static INamedTypeSymbol? GetBaseType(this ITypeSymbol symbol, INamedTypeSymbol baseType)
public static INamedTypeSymbol GetBaseType(this ITypeSymbol symbol, INamedTypeSymbol baseType)
{
return symbol.AllInterfaces.FirstOrDefault(@interface => @interface.IsOfType(baseType));
}
Expand Down Expand Up @@ -45,7 +45,7 @@ public static IEnumerable<string> GetUsingNamespaces(this INamedTypeSymbol symbo

return usingSyntaxes.Select(us => us.Name!.ToString())
.Distinct()
.OrderDescending()
.OrderByDescending(s => s)
.ToList();
}

Expand All @@ -61,7 +61,6 @@ public static bool IsConcreteInstanceClass(this INamedTypeSymbol symbol)

public static bool IsDerivedFrom(this ITypeSymbol symbol, INamedTypeSymbol baseType)
{
ArgumentNullException.ThrowIfNull(baseType);
return symbol.AllInterfaces.Any(@interface => @interface.IsOfType(baseType));
}

Expand Down
4 changes: 2 additions & 2 deletions src/Tools.Generators.WebApi/MinimalApiMediatRGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void Execute(GeneratorExecutionContext context)

return;

static string BuildFile(string? assemblyNamespace, string allUsingNamespaces, string allEndpointRegistrations,
static string BuildFile(string assemblyNamespace, string allUsingNamespaces, string allEndpointRegistrations,
string allHandlerClasses)
{
return $@"// <auto-generated/>
Expand Down Expand Up @@ -221,7 +221,7 @@ private static void BuildHandlerClasses(
handlerClasses.AppendLine();
}

private static string BuildInjectorConstructorAndFields(string? handlerClassName,
private static string BuildInjectorConstructorAndFields(string handlerClassName,
List<WebApiAssemblyVisitor.Constructor> constructors)
{
var handlerClassConstructorAndFields = new StringBuilder();
Expand Down
11 changes: 8 additions & 3 deletions src/Tools.Generators.WebApi/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
# Source Generator

This source generator project is meant to be included by every Api Project for every subdomain.
This source generator project is meant to be included by every API Project for every module/subdomain.

It's job is to convert any `IWebApiService` class definitions (found in the assembly) into Minimal API registrations and MediatR handlers.

# Development Workarounds

C# Source Generators have difficulties running in the IDE if the code used in them has dependencies on other projects in the solution (and other nugets).
Source Generators are required to run to build the rest of the codebase.

This is especially problematic when those referenced projects have transient dependencies to types in AspNet.
Source Generators have to be built in NETSTANDARD2.0 for them to run in Visual Studio, but this is not the case to run in JetBrains Rider.
> This constraint exists to support source generators working in older versions of the .NET Framework, and will exist until Microsoft fix the issue Visual Studio. This is another reason to use JetBrains Rider as the preferred IDE for working with this codebase.
C# Source Generators have difficulties running in any IDE if the code used in them references code in other projects in the solution, and they also suffer problems if they reference any nuget packages.

This is especially problematic when those referenced projects have transient dependencies to types in ASP.NET

If any dependencies are taken, special workarounds (in the project file of this project) are required in order for this source generators to work properly.

Expand Down
4 changes: 3 additions & 1 deletion src/Tools.Generators.WebApi/Tools.Generators.WebApi.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework> <!-- Source Generators must be netstandard2.0 to work in Visual Studio -->
<LangVersion>latest</LangVersion>
<Nullable>disable</Nullable>
<IsPlatformProject>true</IsPlatformProject>
<IsRoslynComponent>true</IsRoslynComponent>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
Expand Down
Loading

0 comments on commit 1e76b92

Please sign in to comment.