Skip to content

Commit

Permalink
Added CookieCrumble Module Source Generator (#7851)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Dec 19, 2024
1 parent 61f1f59 commit b184815
Show file tree
Hide file tree
Showing 122 changed files with 323 additions and 999 deletions.
7 changes: 7 additions & 0 deletions src/CookieCrumble/CookieCrumble.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{EB8F1D90-6
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CookieCrumble.Tests", "test\CookieCrumble.Tests\CookieCrumble.Tests.csproj", "{844E7501-7ED6-4548-8E99-D8E50D4F39A4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CookieCrumble.Analyzers", "src\CookieCrumble.Analyzers\CookieCrumble.Analyzers.csproj", "{F9595524-7EB4-449D-9A13-1C85B13EEE6F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -58,6 +60,10 @@ Global
{844E7501-7ED6-4548-8E99-D8E50D4F39A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{844E7501-7ED6-4548-8E99-D8E50D4F39A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{844E7501-7ED6-4548-8E99-D8E50D4F39A4}.Release|Any CPU.Build.0 = Release|Any CPU
{F9595524-7EB4-449D-9A13-1C85B13EEE6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F9595524-7EB4-449D-9A13-1C85B13EEE6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F9595524-7EB4-449D-9A13-1C85B13EEE6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F9595524-7EB4-449D-9A13-1C85B13EEE6F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{8E71FA9B-8352-4675-A9B4-A934E40AF9E0} = {2465C122-714C-4D0A-A24D-D9C22A25D73A}
Expand All @@ -67,5 +73,6 @@ Global
{CEE25A68-69B5-4CFD-9C35-E82736B1E205} = {2465C122-714C-4D0A-A24D-D9C22A25D73A}
{20ED55B7-E3E7-4CDB-A3CE-87BCB42A4264} = {2465C122-714C-4D0A-A24D-D9C22A25D73A}
{844E7501-7ED6-4548-8E99-D8E50D4F39A4} = {EB8F1D90-60D6-48FA-9744-D4180A0E4AC0}
{F9595524-7EB4-449D-9A13-1C85B13EEE6F} = {2465C122-714C-4D0A-A24D-D9C22A25D73A}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFramework>netstandard2.0</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput> <!-- Do not include the generator as a lib dependency -->
<IncludeSymbols>false</IncludeSymbols>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsPackable>false</IsPackable>
</PropertyGroup>

<PropertyGroup>
<PackageId>CookieCrumble.Analyzers</PackageId>
<AssemblyName>CookieCrumble.Analyzers</AssemblyName>
<RootNamespace>CookieCrumble.Analyzers</RootNamespace>
<Description>This package provides source generators for CookieCrumble.</Description>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" VersionOverride="4.1.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" VersionOverride="3.3.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.Bcl.HashCode" VersionOverride="1.1.0" PrivateAssets="all" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"Generators": {
"commandName": "DebugRoslynComponent",
"targetProject": "../../test/CookieCrumble.Tests/CookieCrumble.Tests.csproj"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;

namespace CookieCrumble.Analyzers;

[Generator]
public class SnapshotModuleGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var rootNamespaceProvider = context.AnalyzerConfigOptionsProvider
.Select((options, _) =>
{
options.GlobalOptions.TryGetValue("build_property.RootNamespace", out var rootNamespace);
return string.IsNullOrWhiteSpace(rootNamespace) ? "CookieCrumble" : rootNamespace;
});

var compilationProvider = context.CompilationProvider;

var providerInterfaceProvider = compilationProvider
.Select((comp, _) => comp.GetTypeByMetadataName("CookieCrumble.ISnapshotModule"));

var providerTypes = compilationProvider
.Combine(providerInterfaceProvider)
.SelectMany((tuple, _) =>
{
var (comp, providerInterface) = tuple;
if (providerInterface is null)
{
return ImmutableArray<INamedTypeSymbol>.Empty;
}

var providers = GetProviderTypes(comp.GlobalNamespace, providerInterface);
return providers.ToImmutableArray();
})
.Collect();

var combined = providerTypes.Combine(rootNamespaceProvider);

context.RegisterSourceOutput(combined, (spc, tuple) =>
{
var (types, rootNamespace) = tuple;
var source = GenerateModuleInitializerClass(types, rootNamespace);
spc.AddSource("ModuleInitializer.g.cs", source);
});
}

private static IEnumerable<INamedTypeSymbol> GetProviderTypes(
INamespaceSymbol namespaceSymbol,
INamedTypeSymbol providerInterface)
{
foreach (var member in namespaceSymbol.GetMembers())
{
if (member is INamespaceSymbol ns)
{
foreach (var nestedType in GetProviderTypes(ns, providerInterface))
{
if (IsProvider(nestedType, providerInterface))
{
yield return nestedType;
}
}
}
else if (member is INamedTypeSymbol type)
{
if (IsProvider(type, providerInterface))
{
yield return type;
}

foreach (var nested in type.GetTypeMembers())
{
foreach (var nestedType in GetAllNamedTypes(nested))
{
if (IsProvider(nestedType, providerInterface))
{
yield return nestedType;
}
}
}
}
}

static bool IsProvider(INamedTypeSymbol type, INamedTypeSymbol providerInterface)
=> type.TypeKind == TypeKind.Class
&& !type.IsAbstract
&& type.AllInterfaces.Contains(providerInterface, SymbolEqualityComparer.Default);
}

private static IEnumerable<INamedTypeSymbol> GetAllNamedTypes(INamedTypeSymbol typeSymbol)
{
yield return typeSymbol;

foreach (var nested in typeSymbol.GetTypeMembers())
{
foreach (var nestedType in GetAllNamedTypes(nested))
{
yield return nestedType;
}
}
}

private static string GenerateModuleInitializerClass(
ImmutableArray<INamedTypeSymbol> providerTypes,
string? rootNamespace)
{
rootNamespace ??= "CookieCrumble";

var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />");
sb.AppendLine("using System;");
sb.AppendLine("using System.Runtime.CompilerServices;");
sb.AppendLine();
sb.AppendLine($"namespace {rootNamespace}");
sb.AppendLine("{");
sb.AppendLine(" internal static partial class ModuleInitializer");
sb.AppendLine(" {");
sb.AppendLine(" [ModuleInitializer]");
sb.AppendLine(" public static void Initialize()");
sb.AppendLine(" {");

var count = 1;
foreach (var t in providerTypes)
{
var providerName = "provider" + count++;
sb.AppendLine($" var {providerName} = new {t.ToDisplayString(format: SymbolDisplayFormat.FullyQualifiedFormat)}();");
sb.AppendLine($" {providerName}.Initialize();");
}

sb.AppendLine(" OnInitialize(global::CookieCrumble.Snapshot.RegisterFormatter);");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" static partial void OnInitialize(global::System.Action<global::CookieCrumble.Formatters.ISnapshotValueFormatter> register);");
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using CookieCrumble.Fusion.Formatters;
using CookieCrumble.Formatters;
using SnapshotValueFormatters = CookieCrumble.Fusion.Formatters.SnapshotValueFormatters;

namespace CookieCrumble.Fusion;

public static class CookieCrumbleFusion
public class CookieCrumbleFusion : SnapshotModule
{
public static void Initialize()
protected override IEnumerable<ISnapshotValueFormatter> CreateFormatters()
{
Snapshot.TryRegisterFormatter(SnapshotValueFormatters.QueryPlan);
yield return SnapshotValueFormatters.QueryPlan;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
using CookieCrumble.HotChocolate.Formatters;
using CookieCrumble.Formatters;
using SnapshotValueFormatters = CookieCrumble.HotChocolate.Formatters.SnapshotValueFormatters;

namespace CookieCrumble.HotChocolate;

public static class CookieCrumbleHotChocolate
public sealed class CookieCrumbleHotChocolate : SnapshotModule
{
public static void Initialize()
protected override IEnumerable<ISnapshotValueFormatter> CreateFormatters()
{
Snapshot.TryRegisterFormatter(SnapshotValueFormatters.ExecutionResult);
Snapshot.TryRegisterFormatter(SnapshotValueFormatters.GraphQL);
Snapshot.TryRegisterFormatter(SnapshotValueFormatters.GraphQLHttp);
Snapshot.TryRegisterFormatter(SnapshotValueFormatters.OperationResult);
Snapshot.TryRegisterFormatter(SnapshotValueFormatters.Schema);
Snapshot.TryRegisterFormatter(SnapshotValueFormatters.SchemaError);
yield return SnapshotValueFormatters.ExecutionResult;
yield return SnapshotValueFormatters.GraphQL;
yield return SnapshotValueFormatters.GraphQLHttp;
yield return SnapshotValueFormatters.OperationResult;
yield return SnapshotValueFormatters.Schema;
yield return SnapshotValueFormatters.SchemaError;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
namespace CookieCrumble.TUnit;

public static class CookieCrumbleTUnit
public sealed class CookieCrumbleTUnit : SnapshotModule
{
public static void Initialize()
{
Snapshot.RegisterTestFramework(new TUnitFramework());
}
protected override ITestFramework TryCreateTestFramework()
=> new TUnitFramework();
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
namespace CookieCrumble.Xunit;

public static class CookieCrumbleXunit
public class CookieCrumbleXunit : SnapshotModule
{
public static void Initialize()
{
Snapshot.RegisterTestFramework(new XunitFramework());
}
protected override ITestFramework TryCreateTestFramework()
=> new XunitFramework();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

<ItemGroup>
<ProjectReference Include="../CookieCrumble/CookieCrumble.csproj" />
<ProjectReference Include="../CookieCrumble.Analyzers/CookieCrumble.Analyzers.csproj" PrivateAssets="All" ExcludeAssets="compile;runtime" />
</ItemGroup>

<ItemGroup>
<!-- Package the generator in the analyzer directory of the nuget package -->
<None Include="$(OutputPath)\NET9.0\CookieCrumble.Analyzers.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
namespace CookieCrumble.Xunit3;

public static class CookieCrumbleXunit3
public sealed class CookieCrumbleXunit3 : SnapshotModule
{
public static void Initialize()
{
Snapshot.RegisterTestFramework(new Xunit3Framework());
}
protected override ITestFramework TryCreateTestFramework()
=> new Xunit3Framework();
}
8 changes: 8 additions & 0 deletions src/CookieCrumble/src/CookieCrumble/DisposableSnapshot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace CookieCrumble;

public sealed class DisposableSnapshot(string? postFix = null, string? extension = null)
: Snapshot(postFix, extension)
, IDisposable
{
public void Dispose() => Match();
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ namespace CookieCrumble.Formatters;
/// <summary>
/// Formats a snapshot segment value for the snapshot file.
/// </summary>
public abstract class SnapshotValueFormatter<TValue>(string markdownKind = "text") : ISnapshotValueFormatter
public abstract class SnapshotValueFormatter<TValue>(string markdownKind = "text")
: ISnapshotValueFormatter
, IMarkdownSnapshotValueFormatter
{
public bool CanHandle(object? value)
Expand Down
32 changes: 32 additions & 0 deletions src/CookieCrumble/src/CookieCrumble/ISnapshotModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using CookieCrumble.Formatters;

namespace CookieCrumble;

public interface ISnapshotModule
{
void Initialize();
}

public abstract class SnapshotModule : ISnapshotModule
{
public void Initialize()
{
var framework = TryCreateTestFramework();
if (framework is not null)
{
Snapshot.RegisterTestFramework(framework);
}

foreach (var formatter in CreateFormatters())
{
Snapshot.RegisterFormatter(formatter);
}
}

protected virtual ITestFramework? TryCreateTestFramework() => null;

protected virtual IEnumerable<ISnapshotValueFormatter> CreateFormatters()
{
yield break;
}
}
3 changes: 3 additions & 0 deletions src/CookieCrumble/src/CookieCrumble/ISnapshotSegment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@ namespace CookieCrumble;

internal interface ISnapshotSegment
{
/// <summary>
/// Gets the name of the snapshot segment.
/// </summary>
string? Name { get; }
}
1 change: 1 addition & 0 deletions src/CookieCrumble/src/CookieCrumble/MarkdownLanguages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ public static class MarkdownLanguages
public const string CSharp = "csharp";
public const string GraphQL = "graphql";
public const string Json = "json";
public const string Yaml = "yaml";
public const string Text = "text";
}
Loading

0 comments on commit b184815

Please sign in to comment.