Skip to content

Commit

Permalink
Merge pull request #9 from VeyronSakai/feature/use-entrypoints
Browse files Browse the repository at this point in the history
Added Analyzer for UseEntryPoints
  • Loading branch information
VeyronSakai authored Apr 7, 2024
2 parents 98bf1bb + d871828 commit d38844d
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 8 deletions.
39 changes: 39 additions & 0 deletions VContainerAnalyzer.Test/PreserveAttributeAnalyzerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Dena.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Text;
using NUnit.Framework;
using VContainerAnalyzer.Analyzers;
using Assert = NUnit.Framework.Assert;

namespace VContainerAnalyzer.Test;
Expand Down Expand Up @@ -230,4 +231,42 @@ public async ValueTask AnalyzeRegisterMethod_ConstructorHasInjectAttribute_Repor

Assert.That(actual.Length, Is.EqualTo(0));
}

[Test]
public async ValueTask AnalyzeAddMethod_ConstructorDoesNotHaveInjectAttribute_ReportDiagnostics()
{
var source = ReadCodes("ConstructorWithoutInjectAttributeClass.cs",
"EmptyClassStub.cs",
"Interfaces.cs",
"AddConstructorWithoutInjectAttributeClassLifetimeScope.cs");

var analyzer = new PreserveAttributeAnalyzer();
var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);

var actual = diagnostics
.Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
.Where(x => x.Id != "CS8019") // Ignore "Unnecessary using directive"
.ToArray();

Assert.Multiple(() =>
{
Assert.That(actual.First().Id, Is.EqualTo("VContainer0001"));
Assert.That(actual.First().GetMessage(),
Is.EqualTo(
"The constructor of 'ConstructorWithoutInjectAttributeClass' have no attribute that extends PreserveAttribute, such as InjectAttribute."));
});

var expectedPositions = new[] { new { Start = new LinePosition(15, 32), End = new LinePosition(15, 70) }, };

Assert.That(actual, Has.Length.EqualTo(expectedPositions.Length));

for (var i = 0; i < expectedPositions.Length; i++)
{
LocationAssert.HaveTheSpan(
expectedPositions[i].Start,
expectedPositions[i].End,
actual[i].Location
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) 2020-2024 VeyronSakai.
// This software is released under the MIT License.

using VContainer;
using VContainer.Unity;

namespace VContainerAnalyzer.Test.TestData
{
public class AddConstructorWithoutInjectAttributeClassLifetimeScope
{
// ReSharper disable once UnusedMember.Global
public void Configure(IContainerBuilder builder)
{
builder.UseEntryPoints(Lifetime.Singleton, entryPoints =>
{
entryPoints.Add<ConstructorWithoutInjectAttributeClass>();
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
// Copyright (c) 2020-2024 VeyronSakai.
// This software is released under the MIT License.

// ReSharper disable once CheckNamespace
using System;

// ReSharper disable once CheckNamespace
namespace VContainer.Unity
{
public readonly struct EntryPointsBuilder
{
private readonly IContainerBuilder _containerBuilder;
private readonly Lifetime _lifetime;

// ReSharper disable once ConvertToPrimaryConstructor
public EntryPointsBuilder(IContainerBuilder containerBuilder, Lifetime lifetime)
{
this._containerBuilder = containerBuilder;
this._lifetime = lifetime;
}

public RegistrationBuilder Add<T>() => _containerBuilder.Register<T>(_lifetime).AsImplementedInterfaces();
}

public static class ContainerBuilderUnityExtensions
{
public static RegistrationBuilder RegisterEntryPoint<T>(this IContainerBuilder builder,
Lifetime lifetime = Lifetime.Singleton)
{
return new RegistrationBuilder();
}

public static void UseEntryPoints(
this IContainerBuilder builder,
Lifetime lifetime,
Action<EntryPointsBuilder> configuration)
{
configuration(new EntryPointsBuilder(builder, lifetime));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@ public RegistrationBuilder As<TInterface>()
{
return this;
}

public virtual RegistrationBuilder AsImplementedInterfaces()
{
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace VContainerAnalyzer;
namespace VContainerAnalyzer.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class PreserveAttributeAnalyzer : DiagnosticAnalyzer
Expand Down Expand Up @@ -38,22 +38,34 @@ private static void AnalyzeAttributes(OperationAnalysisContext context)
var invocation = (IInvocationOperation)context.Operation;
var methodSymbol = invocation.TargetMethod;
var namespaceSymbol = methodSymbol.ContainingNamespace;

if (IsContainerBuilderUnityExtensions(namespaceSymbol, methodSymbol))
{
switch (invocation.TargetMethod.Name)
{
case "RegisterEntryPoint":
AnalyzeRegisterEntryPointMethod(ref context, invocation);
break;
return;
}
}
else if (IsContainerBuilderExtensions(namespaceSymbol, methodSymbol))

if (IsContainerBuilderExtensions(namespaceSymbol, methodSymbol))
{
switch (invocation.TargetMethod.Name)
{
case "Register":
AnalyzeRegisterMethod(ref context, invocation);
break;
return;
}
}

if (IsEntryPointBuilder(namespaceSymbol, methodSymbol))
{
switch (invocation.TargetMethod.Name)
{
case "Add":
AnalyzeAddMethod(ref context, invocation);
return;
}
}
}
Expand All @@ -80,6 +92,28 @@ private static bool IsContainerBuilderUnityExtensions(INamespaceSymbol namespace
return methodSymbol.ContainingType.Name == "ContainerBuilderUnityExtensions";
}

private static bool IsEntryPointBuilder(INamespaceSymbol namespaceSymbol, IMethodSymbol methodSymbol)
{
if (namespaceSymbol is not { Name: "Unity" })
{
return false;
}

namespaceSymbol = namespaceSymbol.ContainingNamespace;
if (namespaceSymbol is not { Name: "VContainer" })
{
return false;
}

namespaceSymbol = namespaceSymbol.ContainingNamespace;
if (namespaceSymbol is not { Name: "" })
{
return false;
}

return methodSymbol.ContainingType.Name == "EntryPointsBuilder";
}

private static bool IsContainerBuilderExtensions(INamespaceSymbol namespaceSymbol, IMethodSymbol methodSymbol)
{
if (namespaceSymbol is not { Name: "VContainer" })
Expand Down Expand Up @@ -119,6 +153,29 @@ private static void AnalyzeRegisterMethod(ref OperationAnalysisContext context,
context.ReportDiagnostic(Diagnostic.Create(s_rule, targetLocation, concreteType.Name));
}

private static void AnalyzeAddMethod(ref OperationAnalysisContext context, IInvocationOperation invocation)
{
var typeArgument = invocation.TargetMethod.TypeArguments.SingleOrDefault();
if (typeArgument is not INamedTypeSymbol concreteType)
{
return;
}

if (concreteType.TypeKind != TypeKind.Class)
{
return;
}

if (HasConstructorWithPreserveAttribute(concreteType) || !HasCustomConstructor(concreteType))
{
return;
}

var typeArgumentLocation = GetTypeArgumentLocation(invocation);
var targetLocation = typeArgumentLocation == default ? invocation.Syntax.GetLocation() : typeArgumentLocation;
context.ReportDiagnostic(Diagnostic.Create(s_rule, targetLocation, concreteType.Name));
}

private static void AnalyzeRegisterInstanceMethod(ref OperationAnalysisContext context,
IInvocationOperation invocation)
{
Expand Down
3 changes: 0 additions & 3 deletions VContainerAnalyzer/VContainerAnalyzer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@
<ItemGroup Condition="Exists('$(TargetPath)')">
<Analyzer Include="$(TargetPath)"/>
</ItemGroup>
<ItemGroup>
<Folder Include="Analyzers\" />
</ItemGroup>

<!-- For pack -->
<PropertyGroup>
Expand Down

0 comments on commit d38844d

Please sign in to comment.