Skip to content

Commit

Permalink
Merge pull request #1 from kandreyc/task/filter-system
Browse files Browse the repository at this point in the history
Filter System generator
  • Loading branch information
kandreyc authored Jun 2, 2023
2 parents ac54c66 + fe8327f commit 73dd7ab
Show file tree
Hide file tree
Showing 48 changed files with 1,056 additions and 114 deletions.
21 changes: 7 additions & 14 deletions AspectGenerator/AspectGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Scellecs.Morpeh.SourceGenerator.Aspect.Core;
using Scellecs.Morpeh.SourceGenerator.Aspect.Core.Extensions;
using Scellecs.Morpeh.SourceGenerator.Aspect.Model;
using Scellecs.Morpeh.SourceGenerator.Aspect.Templates;

namespace Scellecs.Morpeh.SourceGenerator.Aspect;
namespace Scellecs.Morpeh.SourceGenerator.AspectGenerator;

[Generator]
public class AspectGenerator : IncrementalGenerator<AspectData>
{
private const string DisposableInterface = "System.IDisposable";

protected override void OnPostInitialize()
{
AddPostInitializeSource(AspectInterfacesTemplate.GenerateFile());
Expand Down Expand Up @@ -42,7 +34,7 @@ protected override bool Filter(ISymbol? symbol)
return symbol is INamedTypeSymbol typeSymbol && typeSymbol.Interfaces.Any(IsAspectInterface);
}

protected override AspectData Select(ISymbol symbol)
protected override AspectData Select(SyntaxNode _, ISymbol symbol, SemanticModel __)
{
var typeSymbol = (INamedTypeSymbol)symbol;
var aspectInterface = typeSymbol.Interfaces.First(IsAspectInterface);
Expand All @@ -52,7 +44,8 @@ protected override AspectData Select(ISymbol symbol)
NamePascalCase = p.Name,
Type = p.ToDisplayString(),
Namespace = p.GetNamespaceName(),
NameCamelCase = p.Name.FirstLetterLowerCase()
NameCamelCase = p.Name.FirstLetterLowerCase(),
IsDisposable = p.AllInterfaces.Any(i => i.ToDisplayString() == DisposableInterface)
})
.Distinct()
.ToArray();
Expand All @@ -68,7 +61,7 @@ protected override AspectData Select(ISymbol symbol)
private static bool IsAspectInterface(INamedTypeSymbol symbol)
{
const string @name = "IAspect";
const string @namespace = "Scellecs.Morpeh.SourceGenerator.Aspect";
const string @namespace = "Scellecs.Morpeh.SourceGenerator.AspectGenerator";

return symbol is { IsGenericType: true, Name: @name, ContainingNamespace: not null }
&& symbol.GetNamespaceName() is @namespace;
Expand Down
6 changes: 4 additions & 2 deletions AspectGenerator/AspectGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
<IncludeBuildOutput>false</IncludeBuildOutput>
<DevelopmentDependency>true</DevelopmentDependency>
<IsRoslynComponent>true</IsRoslynComponent>
<AssemblyName>Scellecs.Morpeh.SourceGenerator.Aspect</AssemblyName>
<AssemblyName>Scellecs.Morpeh.SourceGenerator.AspectGenerator</AssemblyName>
<Nullable>enable</Nullable>
<TargetFramework>netstandard2.1</TargetFramework>
<RootNamespace>Scellecs.Morpeh.SourceGenerator.Aspect</RootNamespace>
<RootNamespace>Scellecs.Morpeh.SourceGenerator.AspectGenerator</RootNamespace>
</PropertyGroup>

<ItemGroup>
Expand All @@ -19,4 +19,6 @@
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>

<Import Project="..\Core\Core.projitems" Label="Shared" />

</Project>
12 changes: 12 additions & 0 deletions AspectGenerator/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
global using System.Linq;
global using System.Threading;

global using Microsoft.CodeAnalysis;
global using Microsoft.CodeAnalysis.CSharp;
global using Microsoft.CodeAnalysis.CSharp.Syntax;

global using Scellecs.Morpeh.SourceGenerator.Core;
global using Scellecs.Morpeh.SourceGenerator.Core.Extensions;

global using Scellecs.Morpeh.SourceGenerator.AspectGenerator.Model;
global using Scellecs.Morpeh.SourceGenerator.AspectGenerator.Templates;
3 changes: 2 additions & 1 deletion AspectGenerator/Model/Argument.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
namespace Scellecs.Morpeh.SourceGenerator.Aspect.Model;
namespace Scellecs.Morpeh.SourceGenerator.AspectGenerator.Model;

public struct Argument
{
public string Type { get; set; }
public bool IsDisposable { get; set; }
public string? Namespace { get; set; }
public string NameCamelCase { get; set; }
public string NamePascalCase { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion AspectGenerator/Model/AspectData.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Scellecs.Morpeh.SourceGenerator.Aspect.Model;
namespace Scellecs.Morpeh.SourceGenerator.AspectGenerator.Model;

public struct AspectData
{
Expand Down
15 changes: 7 additions & 8 deletions AspectGenerator/Templates/AspectFilterExtensionsTemplate.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
using Scellecs.Morpeh.SourceGenerator.Aspect.Core;
using Scellecs.Morpeh.SourceGenerator.Aspect.Model;

namespace Scellecs.Morpeh.SourceGenerator.Aspect.Templates;
namespace Scellecs.Morpeh.SourceGenerator.AspectGenerator.Templates;

public static class AspectFilterExtensionsTemplate
{
public static GeneratedFile GenerateFile()
{
const string @namespace = "Scellecs.Morpeh.SourceGenerator.AspectGenerator";

return new GeneratedFile
{
FileName = "AspectFilterExtensions.g.cs",
Content = """
FileName = $"{@namespace}.AspectFilterExtensions.g.cs",
Content = $$"""
using Scellecs.Morpeh;
namespace Scellecs.Morpeh.SourceGenerator.Aspect
namespace {{@namespace}}
{
public static class AspectFilterExtensions
{
public static FilterBuilder With<T>(this FilterBuilder filter) where T : struct, IAspect, IFilterExtension
public static FilterBuilder WithAspect<T>(this FilterBuilder filter) where T : struct, IAspect, IFilterExtension
{
return filter.Extend<T>();
}
Expand Down
7 changes: 2 additions & 5 deletions AspectGenerator/Templates/AspectInterfacesTemplate.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using Scellecs.Morpeh.SourceGenerator.Aspect.Core;
using Scellecs.Morpeh.SourceGenerator.Aspect.Model;

namespace Scellecs.Morpeh.SourceGenerator.Aspect.Templates;
namespace Scellecs.Morpeh.SourceGenerator.AspectGenerator.Templates;

public static class AspectInterfacesTemplate
{
Expand All @@ -13,7 +10,7 @@ public static GeneratedFile GenerateFile()
Content = """
using Scellecs.Morpeh;
namespace Scellecs.Morpeh.SourceGenerator.Aspect
namespace Scellecs.Morpeh.SourceGenerator.AspectGenerator
{
public interface IAspect<T1, T2> : IAspect, IFilterExtension
where T1 : struct, IComponent
Expand Down
8 changes: 2 additions & 6 deletions AspectGenerator/Templates/AspectTemplate.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System.Linq;
using Scellecs.Morpeh.SourceGenerator.Aspect.Core;
using Scellecs.Morpeh.SourceGenerator.Aspect.Model;

namespace Scellecs.Morpeh.SourceGenerator.Aspect.Templates;
namespace Scellecs.Morpeh.SourceGenerator.AspectGenerator.Templates;

public static class AspectTemplate
{
Expand Down Expand Up @@ -93,7 +89,7 @@ string GetProperties(int indent)
string GetCtors(int indent)
{
var str = data.Arguments.Select(
static a => $"{Stash(a.NameCamelCase)} = world.GetStash<{a.NamePascalCase}>();"
static a => $"{Stash(a.NameCamelCase)} = world.GetStash<{a.NamePascalCase}>(){(a.IsDisposable ? ".AsDisposable()" : string.Empty)};"
);

return string.Join($"\n{Tab(indent)}", str);
Expand Down
21 changes: 21 additions & 0 deletions Core/Core.projitems
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>E85D9420-616C-4636-A5C8-1181428E4C57</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>Core</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Extensions\AttributeDataExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\INamespaceSymbolExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\ITypeSymbolExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\StringExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GeneratedFile.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IncrementalGenerator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ISourceFileGenerator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Logger\Logger.cs" />
</ItemGroup>
</Project>
13 changes: 13 additions & 0 deletions Core/Core.shproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{B2A951FB-4BAD-4AA7-8986-A291B103D443}</ProjectGuid>
<AssemblyName>Scellecs.Morpeh.SourceGenerator.Core</AssemblyName>
<RootNamespace>Scellecs.Morpeh.SourceGenerator.Core</RootNamespace>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<Import Project="Core.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;

namespace Scellecs.Morpeh.SourceGenerator.Aspect.Core.Extensions;
namespace Scellecs.Morpeh.SourceGenerator.Core.Extensions;

internal static class AttributeDataExtensions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Microsoft.CodeAnalysis;

namespace Scellecs.Morpeh.SourceGenerator.Aspect.Core.Extensions;
namespace Scellecs.Morpeh.SourceGenerator.Core.Extensions;

// ReSharper disable once InconsistentNaming
public static class INamespaceSymbolExtensions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Microsoft.CodeAnalysis;

namespace Scellecs.Morpeh.SourceGenerator.Aspect.Core.Extensions;
namespace Scellecs.Morpeh.SourceGenerator.Core.Extensions;

// ReSharper disable once InconsistentNaming
public static class ITypeSymbolExtensions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Scellecs.Morpeh.SourceGenerator.Aspect.Core.Extensions;
namespace Scellecs.Morpeh.SourceGenerator.Core.Extensions;

public static class StringExtensions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Scellecs.Morpeh.SourceGenerator.Aspect.Core;
namespace Scellecs.Morpeh.SourceGenerator.Core;

public struct GeneratedFile
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Scellecs.Morpeh.SourceGenerator.Aspect.Core;
namespace Scellecs.Morpeh.SourceGenerator.Core;

public interface ISourceFileGenerator
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using System;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;

namespace Scellecs.Morpeh.SourceGenerator.Aspect.Core;
namespace Scellecs.Morpeh.SourceGenerator.Core;

public abstract class IncrementalGenerator<TData> : IIncrementalGenerator, ISourceFileGenerator
where TData: struct
Expand All @@ -31,15 +29,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.CreateSyntaxProvider(SyntaxFilter, (c, _) =>
{
var symbol = c.SemanticModel.GetDeclaredSymbol(c.Node);
return Filter(symbol) ? symbol : null;
return Filter(symbol) ? (node: c.Node, symbol, model: c.SemanticModel) : default;
})
.Where(symbol => symbol is not null)
.Select((symbol, _) => Select(symbol!));
.Where(t => t.symbol is not null)
.Select((t, _) => Select(t.node, t.symbol!, t.model));

context.RegisterSourceOutput(provider.Collect(), GenerateInternal);
}

protected abstract TData Select(ISymbol symbol);
protected abstract TData Select(SyntaxNode syntax, ISymbol symbol, SemanticModel model);

private void GenerateInternal(SourceProductionContext context, ImmutableArray<TData> data)
{
Expand Down
4 changes: 1 addition & 3 deletions AspectGenerator/Logger/Logger.cs → Core/Logger/Logger.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Scellecs.Morpeh.SourceGenerator.Aspect.Core;

namespace Scellecs.Morpeh.SourceGenerator.Aspect.Logger;
namespace Scellecs.Morpeh.SourceGenerator.Core.Logger;

public class Logger
{
Expand Down
92 changes: 92 additions & 0 deletions FilterSystemGenerator/FilterSystemGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
namespace Scellecs.Morpeh.SourceGenerator.FilterSystemGenerator;

[Generator]
public class FilterSystemGenerator : IncrementalGenerator<SystemInfo>
{
private const string MethodName = "GetFilter";
private const string DisposableInterface = "System.IDisposable";
private const string AspectInterface = "Scellecs.Morpeh.IAspect";
private const string FilterBuilderType = "Scellecs.Morpeh.FilterBuilder";
private const string FilterSystemType = "Scellecs.Morpeh.SourceGenerator.FilterSystemGenerator.FilterSystem";

private static readonly HashSet<string> WithMethodNames = new() { "With", "Extend", "WithAspect" };

protected override void OnPostInitialize()
{
AddPostInitializeSource(FilterSystemTemplate.GenerateFile());
}

protected override bool SyntaxFilter(SyntaxNode node, CancellationToken _)
{
return node is MethodDeclarationSyntax { Parent: ClassDeclarationSyntax classDeclarationSyntax }
&& classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)
&& !classDeclarationSyntax.Modifiers.Any(SyntaxKind.AbstractKeyword);
}

protected override bool Filter(ISymbol? symbol)
{

return symbol is IMethodSymbol methodSymbol
&& methodSymbol.ContainingType?.BaseType?.ToDisplayString() is FilterSystemType
&& IsGetFilterMethod(methodSymbol);
}

private static bool IsGetFilterMethod(IMethodSymbol method)
{

return method is { Name: MethodName, Parameters.Length: 1 }
&& method.ReturnType.ToDisplayString() is FilterBuilderType
&& method.Parameters[0].Type.ToDisplayString() is FilterBuilderType;
}

protected override SystemInfo Select(SyntaxNode methodSyntax, ISymbol symbol, SemanticModel model)
{
var methodSymbol = (IMethodSymbol)symbol;
var @class = methodSymbol.ContainingType;

return new SystemInfo
{
Name = @class.Name,
Namespace = @class.GetNamespaceName(),
Filters = GetFilters(methodSyntax, model).ToArray()
};
}

private static IEnumerable<FilterInfo> GetFilters(SyntaxNode methodSyntax, SemanticModel model)
{
foreach (var componentType in GetAllTypeArgumentsWithIdentifier(WithMethodNames, methodSyntax, model))
{
yield return new FilterInfo
{
NamePascalCase = componentType.Name,
NameCamelCase = componentType.Name.FirstLetterLowerCase(),
Namespace = componentType.GetNamespaceName(),
IsAspect = componentType.AllInterfaces.Any(i => i.ToDisplayString() is AspectInterface),
IsDisposable = componentType.AllInterfaces.Any(i => i.ToDisplayString() is DisposableInterface)
};
}
}

private static IEnumerable<ITypeSymbol> GetAllTypeArgumentsWithIdentifier(HashSet<string> identifiers, SyntaxNode methodSyntax, SemanticModel model)
{
foreach (var syntax in methodSyntax.DescendantNodes())
{
if (syntax is not GenericNameSyntax genericNameSyntax) continue;
if (!identifiers.Contains(genericNameSyntax.Identifier.Text)) continue;
if (genericNameSyntax.TypeArgumentList.Arguments.Count is not 1) continue;

var typeArgument = genericNameSyntax.TypeArgumentList.Arguments[0];
var type = model.GetTypeInfo(typeArgument).Type;

if (type is not null)
{
yield return type;
}
}
}

protected override void Generate(SystemInfo systemInfo)
{
AddSource(PartialImplTemplate.GenerateFile(systemInfo));
}
}
Loading

0 comments on commit 73dd7ab

Please sign in to comment.