Skip to content

Commit

Permalink
Init - 0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
Юрий Турбин committed Apr 9, 2024
1 parent fc4d429 commit 101073d
Show file tree
Hide file tree
Showing 12 changed files with 1,449 additions and 1 deletion.
402 changes: 402 additions & 0 deletions .editorconfig

Large diffs are not rendered by default.

597 changes: 597 additions & 0 deletions .gitignore

Large diffs are not rendered by default.

52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,52 @@
# SourceGenLinq
Simple implementation of flexible parameterized sorting for IQuariable using IncrementalGenerator

Simple implementation of flexible parameterized sorting for IQuariable using IncrementalGenerator.

## Usage

> Tested on net8
0. Install library:

```xml
<ItemGroup>
<PackageReference Include="SourceGenLinq.Abstractions" Version="0.0.1" />
<PackageReference Include="SourceGenLinq.Generator" Version="0.0.1" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>
```

1. Define DTO class:

```csharp
public class Tester
{
public Guid Id { get; set; }

public required string Name { get; set; }
public required string Comment { get; set; }
public required double Test { get; set; }

public QazWsx? QazWsx { get; set; }
}

public class QazWsx;
```

2. Define sorter with generic attribute `SortQuariable`:

```csharp
[SortQuariable<Tester>]
public static partial class MyTesterSorter;
```

3. Use:

```csharp
IQueryable<Tester> testers = default!;

testers = testers.Sort(new()
{
{ TesterSort.TesterProperty.IdProperty, SortMode.Asc },
{ TesterSort.TesterProperty.CommentProperty, SortMode.Desc },
});
```
59 changes: 59 additions & 0 deletions Sample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using SourceGenLinq.Abstractions;

using TesterSample;

Console.WriteLine("Hello, World!");

IQueryable<Tester> testers = new Tester[]
{
new()
{
Id = Guid.NewGuid(),
Name = "Test",
Comment = "Test",
Test = 22,
},
new()
{
Id = Guid.NewGuid(),
Name = "Test 2",
Comment = "Test 3",
Test = 100,
},
}.AsQueryable();

IQueryable<Tester> orderedTesters = testers.Sort(new()
{
{ TesterSort.TesterProperty.IdProperty, SortMode.Asc },
{ TesterSort.TesterProperty.CommentProperty, SortMode.Desc },
});

Console.WriteLine();

namespace TesterSample
{
[SortQuariable<Tester>]
public static partial class TesterSort2;

public class Tester
{
public Guid Id { get; set; }

public required string Name { get; set; }
public required string Comment { get; set; }
public required double Test { get; set; }

public QazWsx? QazWsx { get; set; }
}

[SortQuariable<QazWsx>]
public static partial class QazWsxSorter;

public class QazWsx
{
public Guid Id { get; set; }
public Guid Говно { get; set; }

public required string Name { get; set; }
}
}
19 changes: 19 additions & 0 deletions Sample/Sample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SourceGenLinq.Abstractions\SourceGenLinq.Abstractions.csproj" />
<ProjectReference Include="..\SourceGenLinq.Generator\SourceGenLinq.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions SourceGenLinq.Abstractions/SortMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace SourceGenLinq.Abstractions;

public enum SortMode
{
Asc,
Desc,
}
4 changes: 4 additions & 0 deletions SourceGenLinq.Abstractions/SortQuariableAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace SourceGenLinq.Abstractions;

[AttributeUsage(AttributeTargets.Class)]
public class SortQuariableAttribute<T> : Attribute where T : class;
26 changes: 26 additions & 0 deletions SourceGenLinq.Abstractions/SourceGenLinq.Abstractions.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<Version>0.0.1</Version>
<PackageId>SourceGenLinq.Abstractions</PackageId>
<Title>SourceGenLinq.Abstractions</Title>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Description>Simple implementation of flexible parameterized sorting for IQuariable using IncrementalGenerator</Description>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<RepositoryUrl>https://github.com/yurvon-screamo/SourceGenLinq</RepositoryUrl>
<IncludeSymbols>True</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

<ItemGroup>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

</Project>
25 changes: 25 additions & 0 deletions SourceGenLinq.Abstractions/SourceGenLinq.Abstractions.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGenLinq.Abstractions", "SourceGenLinq.Abstractions.csproj", "{70FD2DB6-3039-4170-B474-6D9C959CA79F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{70FD2DB6-3039-4170-B474-6D9C959CA79F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70FD2DB6-3039-4170-B474-6D9C959CA79F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70FD2DB6-3039-4170-B474-6D9C959CA79F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70FD2DB6-3039-4170-B474-6D9C959CA79F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {ED9260E0-3F67-4748-8AD0-224C89971EDA}
EndGlobalSection
EndGlobal
177 changes: 177 additions & 0 deletions SourceGenLinq.Generator/OrderByIncrementalGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace SourceGenLinq.Generator;

[Generator]
public class OrderByIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValuesProvider<Target> valuesProvider = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (p, _) => IsSyntaxTargetForGeneration(p),
transform: (p, _) => GetSemanticTarget(p))
.Where(t => t.DataType is not null);

context.RegisterSourceOutput(valuesProvider, (c, p) =>
{
if (p.DataType is null || p.NamespacePrefix is null)
{
return;
}
string identity = p.DataType;
string enumSource = string.Empty;
string orderSource = string.Empty;
foreach (string propertyName in p.PropertyNames)
{
enumSource += $"\n {propertyName}Property,";
orderSource += $"\n {identity}Property.{propertyName}Property when item.Value is SourceGenLinq.Abstractions.SortMode.Asc => sources.OrderBy(t => t.{propertyName}),";
orderSource += $"\n {identity}Property.{propertyName}Property when item.Value is SourceGenLinq.Abstractions.SortMode.Desc => sources.OrderByDescending(t => t.{propertyName}),";
orderSource += '\n';
}
string source = @"using System;
using System.Collections.Generic;
using System.Linq;
";
source += p.NamespacePrefix == string.Empty
? string.Empty
: $"namespace {p.NamespacePrefix};\n\n";
source += $@"public static partial class {identity}Sort
{{
public enum {identity}Property
{{{enumSource}
}}
public sealed class {identity}SortInput : Dictionary<{identity}Property, SourceGenLinq.Abstractions.SortMode>;
public static IQueryable<{identity}> Sort(this IQueryable<{identity}> sources, {identity}SortInput input)
{{
foreach (KeyValuePair<{identity}Property, SourceGenLinq.Abstractions.SortMode> item in input)
{{
sources = item.Key switch
{{{orderSource}
_ => throw new NotImplementedException(),
}};
}}
return sources;
}}
}}
";
string file;
if (p.NamespacePrefix == string.Empty)
{
file = p.DataType;
}
else
{
file = p.NamespacePrefix + '.' + p.DataType;
}
c.AddSource($"{file}.Generated.cs", source);
});
}

static bool IsSyntaxTargetForGeneration(SyntaxNode node)
{
return node is ClassDeclarationSyntax classDeclaration &&
classDeclaration.AttributeLists.Any();
}

static Target GetSemanticTarget(GeneratorSyntaxContext context)
{
if (context.Node is ClassDeclarationSyntax classDeclaration)
{
foreach (AttributeListSyntax listSyntax in classDeclaration.AttributeLists)
{
foreach (AttributeSyntax attribute in listSyntax.Attributes)
{
TypeInfo typeInfo = context.SemanticModel.GetTypeInfo(attribute);

if (typeInfo.Type?.Name == "SortQuariableAttribute")
{
IEnumerable<GenericNameSyntax> childNodes = attribute
.ChildNodes()
.OfType<GenericNameSyntax>();

foreach (GenericNameSyntax node in childNodes)
{
TypeArgumentListSyntax argumentListSyntax = node.TypeArgumentList;

foreach (TypeSyntax typeSyntax in argumentListSyntax.Arguments)
{
ITypeSymbol? typeSymbol = context
.SemanticModel
.GetTypeInfo(typeSyntax)
.Type;

if (typeSymbol is null)
{
continue;
}

INamespaceSymbol namespaceSymbol = typeSymbol.ContainingNamespace;

IEnumerable<IPropertySymbol> propertySymbols = typeSymbol
.GetMembers()
.OfType<IPropertySymbol>();

List<string> propertyNames = new();

foreach (IPropertySymbol property in propertySymbols)
{
if (property.IsStatic || property.IsWriteOnly)
{
continue;
}

ITypeSymbol propertyType = property.Type;

if (propertyType.IsValueType || propertyType.Name == nameof(String))
{
propertyNames.Add(property.Name);
}
}

string namespacePrefix =
namespaceSymbol.IsGlobalNamespace
? string.Empty
: namespaceSymbol.ToDisplayString();

return new(
typeSymbol.Name,
namespacePrefix,
propertyNames.ToImmutableArray());
}
}
}
}
}
}

return new(null, null, default);
}

private readonly struct Target(string? dataType, string? namespacePrefix, ImmutableArray<string> propertyNames)
{
public string? DataType { get; } = dataType;
public string? NamespacePrefix { get; } = namespacePrefix;
public ImmutableArray<string> PropertyNames { get; } = propertyNames;
}
}
Loading

0 comments on commit 101073d

Please sign in to comment.