Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Incremental Source Generator #592

Merged
merged 2 commits into from Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 16 additions & 5 deletions .github/workflows/release.yaml
Expand Up @@ -23,16 +23,27 @@ jobs:
# set release tag(*.*.*) to env.GIT_TAG
- run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV

# Execute scripts: Export Package
- name: Export unitypackage
run: $UNITY_BIN -quit -batchmode -nographics -logFile /dev/stdout -exportPackage Assets/VContainer/Runtime Assets/VContainer/Editor VContainer.${{ env.GIT_TAG }}.unitypackage -projectPath ./
working-directory: VContainer

# Store artifacts.
- uses: actions/upload-artifact@v2
- uses: actions/setup-dotnet@v3
with:
name: VContainer.${{ env.GIT_TAG }}.unitypackage
path: ./VContainer/VContainer.${{ env.GIT_TAG }}.unitypackage
dotnet-version: |
8.0.x

- name: Build SourceGenerator
run: |
dotnet build -c Release ./VContainer.SourceGenerator
dotnet build -c Release ./VContainer.SourceGenerator.Roslyn3

- uses: actions/upload-artifact@v3
with:
name: VContainer.${{ env.GIT_TAG }}
path: |
./VContainer/VContainer.${{ env.GIT_TAG }}.unitypackage
./VContainer.SourceGenerator/bin/Release/netstandard2.0/VContainer.SourceGenerator.dll
./VContainer.SourceGenerator.Roslyn3/bin/Release/netstandard2.0/VContainer.SourceGenerator.Roslyn3.dll

# create-release:
# needs: [build-unity]
Expand Down
90 changes: 90 additions & 0 deletions VContainer.SourceGenerator.Roslyn3/CodeWriter.cs
@@ -0,0 +1,90 @@
using System;
using System.Text;

namespace VContainer.SourceGenerator
{
public class CodeWriter
{
readonly struct IndentScope : IDisposable
{
readonly CodeWriter source;

public IndentScope(CodeWriter source)
{
this.source = source;
source.IncreasaeIndent();
}

public void Dispose()
{
source.DecreaseIndent();
}
}

readonly struct BlockScope : IDisposable
{
readonly CodeWriter source;

public BlockScope(CodeWriter source, string? startLine = null)
{
this.source = source;
source.AppendLine(startLine);
source.BeginBlock();
}

public void Dispose()
{
source.EndBlock();
}
}

readonly StringBuilder buffer = new();
int indentLevel;

public void AppendLine(string value = "")
{
if (string.IsNullOrEmpty(value))
{
buffer.AppendLine();
}
else
{
buffer.AppendLine($"{new string(' ', indentLevel * 4)} {value}");
}
}

public override string ToString() => buffer.ToString();

public IDisposable BeginIndentScope() => new IndentScope(this);
public IDisposable BeginBlockScope(string? startLine = null) => new BlockScope(this, startLine);

public void IncreasaeIndent()
{
indentLevel++;
}

public void DecreaseIndent()
{
if (indentLevel > 0)
indentLevel--;
}

public void BeginBlock()
{
AppendLine("{");
IncreasaeIndent();
}

public void EndBlock()
{
DecreaseIndent();
AppendLine("}");
}

public void Clear()
{
buffer.Clear();
indentLevel = 0;
}
}
}
89 changes: 89 additions & 0 deletions VContainer.SourceGenerator.Roslyn3/DiagnosticDescriptors.cs
@@ -0,0 +1,89 @@
using Microsoft.CodeAnalysis;

namespace VContainer.SourceGenerator
{
static class DiagnosticDescriptors
{
const string Category = "VContainer.SourceGenerator.Roslyn3";

public static readonly DiagnosticDescriptor UnexpectedErrorDescriptor = new(
id: "VCON0001",
title: "Unexpected error during generation",
messageFormat: "Unexpected error occurred during code generation: {0}",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor AbstractNotAllow = new(
id: "VCON0002",
title: "Injectable type must not be abstract/interface",
messageFormat: "The injectable type of '{0}' is abstract/interface. It is not allowed",
category: Category,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor MultipleCtorAttributeNotSupported = new(
id: "VCON0003",
title: "[Inject] exists in multiple constructors",
messageFormat: "Multiple [Inject] constructors exists in '{0}'",
category: Category,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor MultipleInjectMethodNotSupported = new(
id: "VCON0004",
title: "[Inject] exists in multiple methods",
messageFormat: "Multiple [Inject] methods exists in '{0}'",
category: Category,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor NestedNotSupported = new(
id: "VCON0005",
title: "Nested type is not support to code generation.",
messageFormat: "The injectable object '{0}' is a nested type. It cannot support code generation ",
category: Category,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor PrivateConstructorNotSupported = new(
id: "VCON0006",
title: "The private constructor is not supported to code generation.",
messageFormat: "The injectable constructor of '{0}' is private. It cannot support source generator.",
category: Category,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor PrivateFieldNotSupported = new(
id: "VCON0007",
title: "The private [Inject] field is not supported to code generation.",
messageFormat: "The [Inject] field '{0}' does not have accessible to set from the same dll. It cannot support to inject by the source generator.",
category: Category,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor PrivatePropertyNotSupported = new(
id: "VCON0008",
title: "The private [Inject] property is not supported to code generation",
messageFormat: "The [Inject] '{0}' does not have accessible to set from the same dll. It cannot support to inject by the source generator.",
category: Category,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor PrivateMethodNotSupported = new(
id: "VCON0009",
title: "The private [Inject] method is not supported to code generation.",
messageFormat: "The [Inject] '{0}' does not have accessible to call from the same dll. It cannot support inject by the source generator.",
category: Category,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor GenericsNotSupported = new(
id: "VCON0010",
title: "The [Inject] constructor or method that require generics argument is not supported to code generation.",
messageFormat: "[Inject] '{0}' needs generic arguments. It cannot inject by the source generator.",
category: Category,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
}
}
27 changes: 27 additions & 0 deletions VContainer.SourceGenerator.Roslyn3/ReferenceSymbols.cs
@@ -0,0 +1,27 @@
using Microsoft.CodeAnalysis;

namespace VContainer.SourceGenerator
{
public class ReferenceSymbols
{
public static ReferenceSymbols? Create(Compilation compilation)
{
var injectAttribute = compilation.GetTypeByMetadataName("VContainer.InjectAttribute");
if (injectAttribute is null)
return null;

return new ReferenceSymbols
{
VContainerInjectAttribute = injectAttribute,
VContainerInjectIgnoreAttribute = compilation.GetTypeByMetadataName("VContainer.InjectIgnoreAttribute")!,
AttributeBase = compilation.GetTypeByMetadataName("System.Attribute")!,
UnityEngineComponent = compilation.GetTypeByMetadataName("UnityEngine.Component"),
};
}

public INamedTypeSymbol VContainerInjectAttribute { get; private set; } = default!;
public INamedTypeSymbol VContainerInjectIgnoreAttribute { get; private set; } = default!;
public INamedTypeSymbol AttributeBase { get; private set; } = default!;
public INamedTypeSymbol? UnityEngineComponent { get; private set; }
}
}
112 changes: 112 additions & 0 deletions VContainer.SourceGenerator.Roslyn3/SymbolExtensions.cs
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;

namespace VContainer.SourceGenerator
{
public static class SymbolExtensions
{
public static IEnumerable<ISymbol> GetAllMembers(this INamedTypeSymbol symbol, bool withoutOverride = true)
{
// Iterate Parent -> Derived
if (symbol.BaseType != null)
{
foreach (var item in GetAllMembers(symbol.BaseType))
{
// override item already iterated in parent type
if (!withoutOverride || !item.IsOverride)
{
yield return item;
}
}
}

foreach (var item in symbol.GetMembers())
{
if (!withoutOverride || !item.IsOverride)
{
yield return item;
}
}
}

public static bool ContainsAttribute(this ISymbol symbol, INamedTypeSymbol attribtue)
{
return symbol.GetAttributes().Any(x => SymbolEqualityComparer.Default.Equals(x.AttributeClass, attribtue));
}

public static IEnumerable<INamedTypeSymbol> GetAllBaseTypes(this INamedTypeSymbol symbol)
{
var t = symbol.BaseType;
while (t != null)
{
yield return t;
t = t.BaseType;
}
}

public static bool CanBeCallFromInternal(this ISymbol symbol)
{
return symbol.DeclaredAccessibility >= Accessibility.Internal;
}

public static string GetClassDeclarationName(this INamedTypeSymbol symbol)
{
if (symbol.TypeArguments.Length == 0)
{
return symbol.Name;
}

var sb = new StringBuilder();

sb.Append(symbol.Name);
sb.Append('<');

var first = true;
foreach (var typeArg in symbol.TypeArguments)
{
if (!first)
{
sb.Append(", ");
}
else
{
first = false;
}
sb.Append(typeArg.Name);
}

sb.Append('>');

return sb.ToString();
}

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) => DistinctBy(source, keySelector, null);

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer)
{
return DistinctByIterator(source, keySelector, comparer);
}

static IEnumerable<TSource> DistinctByIterator<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer)
{
using IEnumerator<TSource> enumerator = source.GetEnumerator();

if (enumerator.MoveNext())
{
var set = new HashSet<TKey>(comparer);
do
{
TSource element = enumerator.Current;
if (set.Add(keySelector(element)))
{
yield return element;
}
}
while (enumerator.MoveNext());
}
}
}
}