Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,17 @@ CancellationToken cancellationToken
{
var list = new List<ServiceRegistrationModel>();

foreach (var attributeSyntax in context.Node.DescendantNodes().OfType<AttributeSyntax>())
// The declaration's own attributes only. An attribute on a nested class or on a factory
// method in this class registers that class or that method, not this one.
var attributeLists = context.Node is MemberDeclarationSyntax member
? member.AttributeLists
: default;

foreach (
var attributeSyntax in attributeLists.SelectMany(attributeList =>
attributeList.Attributes
)
)
{
foreach (var typeDefinition in _attributeTypes)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using DependencyModules.Tests.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace DependencyModules.Tests.GeneratorTests;

/// <summary>
/// A service attribute registers the declaration it is written on. A nested class or a factory
/// method inside a service class has its own registration and does not add one for the class.
/// </summary>
public class NestedServiceAttributeTests
{
[Fact]
public void ANestedServiceClass_DoesNotRegisterItsContainingClassAgain()
{
var assembly = GeneratedAssembly.Create(
"""
using DependencyModules.Runtime.Attributes;

namespace TestNamespace;

public interface ICache { }

public interface IEntry { }

[SingletonService]
public class Cache : ICache
{
[TransientService]
public class Entry : IEntry { }
}

[DependencyModule]
public partial class TestModule;
"""
);

var cache = assembly.Descriptor("ICache");

Assert.Equal(ServiceLifetime.Singleton, cache.Lifetime);
Assert.Equal(ServiceLifetime.Transient, assembly.Descriptor("IEntry").Lifetime);

var provider = assembly.BuildProvider();

Assert.Same(
provider.GetRequiredService(assembly.Type("ICache")),
provider.GetRequiredService(assembly.Type("ICache"))
);
}

[Fact]
public void AFactoryMethodInAServiceClass_DoesNotRegisterItsContainingClassAgain()
{
var assembly = GeneratedAssembly.Create(
"""
using DependencyModules.Runtime.Attributes;

namespace TestNamespace;

public interface IStore { }

public interface IClock { }

public class Clock : IClock { }

[SingletonService]
public class Store : IStore
{
[ScopedService]
public static IClock CreateClock() => new Clock();
}

[DependencyModule]
public partial class TestModule;
"""
);

Assert.Equal(ServiceLifetime.Singleton, assembly.Descriptor("IStore").Lifetime);
Assert.Equal(ServiceLifetime.Scoped, assembly.Descriptor("IClock").Lifetime);
}
}
Loading