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
9 changes: 7 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Both generators follow the **Incremental Generator** pattern (IIncrementalGenera

**Key Features:**
- Auto-detects all implemented interfaces (excluding System.* and Microsoft.* namespaces)
- **Abstract base class support** - Register services against abstract base classes (e.g., `AuthenticationStateProvider`, `DelegatingHandler`)
- **Generic interface registration** - Full support for open generic types like `IRepository<T>` and `IHandler<TRequest, TResponse>`
- **Keyed service registration** - Multiple implementations of the same interface with different keys (.NET 8+)
- **Factory method registration** - Custom initialization logic via static factory methods
Expand All @@ -123,6 +124,10 @@ Both generators follow the **Incremental Generator** pattern (IIncrementalGenera
// Input: [Registration] public class UserService : IUserService { }
// Output: services.AddSingleton<IUserService, UserService>();

// Abstract Base Class Input: [Registration(Lifetime.Scoped, As = typeof(AuthenticationStateProvider))]
// public class ServerAuthenticationStateProvider : AuthenticationStateProvider { }
// Abstract Base Class Output: services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();

// Generic Input: [Registration(Lifetime.Scoped)] public class Repository<T> : IRepository<T> where T : class { }
// Generic Output: services.AddScoped(typeof(IRepository<>), typeof(Repository<>));

Expand Down Expand Up @@ -277,8 +282,8 @@ services.AddDependencyRegistrationsFromDomain(
- Runtime (method parameters): Flexible per application, allows different apps to exclude different services

**Diagnostics:**
- `ATCDIR001` - Service 'As' type must be an interface (Error)
- `ATCDIR002` - Class does not implement specified interface (Error)
- `ATCDIR001` - Service 'As' type must be an interface or abstract class (Error)
- `ATCDIR002` - Class does not implement specified interface or inherit from abstract class (Error)
- `ATCDIR003` - Duplicate registration with different lifetimes (Warning)
- `ATCDIR004` - Hosted services must use Singleton lifetime (Error)
- `ATCDIR005` - Factory method not found (Error)
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<PackageReference Include="Atc.Analyzer" Version="0.1.11" PrivateAssets="All" />
<PackageReference Include="AsyncFixer" Version="1.6.0" PrivateAssets="All" />
<PackageReference Include="Asyncify" Version="0.9.7" PrivateAssets="All" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.253" PrivateAssets="All" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.254" PrivateAssets="All" />
<PackageReference Include="SecurityCodeScan.VS2019" Version="5.6.7" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.507" PrivateAssets="All" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.15.0.120848" PrivateAssets="All" />
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,8 @@ Get errors at compile time, not runtime:

| ID | Description |
|----|-------------|
| ATCDIR001 | `As` parameter must be an interface type |
| ATCDIR002 | Class must implement the specified interface |
| ATCDIR001 | `As` parameter must be an interface or abstract class type |
| ATCDIR002 | Class must implement the specified interface or inherit from abstract class |
| ATCDIR003 | Duplicate registration with different lifetimes |
| ATCDIR004 | Hosted services must use Singleton lifetime |
| ATCDIR005 | Factory method not found |
Expand Down
38 changes: 29 additions & 9 deletions docs/DependencyRegistrationGenerators.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ services.AddScoped<IUserService, UserService>();
- [⚙️ RegistrationAttribute Parameters](#️-registrationattribute-parameters)
- [📝 Examples](#-examples)
- [🛡️ Diagnostics](#️-diagnostics)
- [❌ ATCDIR001: As Type Must Be Interface](#-atcdir001-as-type-must-be-interface)
- [❌ ATCDIR002: Class Does Not Implement Interface](#-atcdir002-class-does-not-implement-interface)
- [❌ ATCDIR001: As Type Must Be Interface or Abstract Class](#-atcdir001-as-type-must-be-interface-or-abstract-class)
- [❌ ATCDIR002: Class Does Not Implement Interface or Inherit Abstract Class](#-atcdir002-class-does-not-implement-interface-or-inherit-abstract-class)
- [⚠️ ATCDIR003: Duplicate Registration with Different Lifetime](#️-atcdir003-duplicate-registration-with-different-lifetime)
- [❌ ATCDIR004: Hosted Services Must Use Singleton Lifetime](#-atcdir004-hosted-services-must-use-singleton-lifetime)
- [🔷 Generic Interface Registration](#-generic-interface-registration)
Expand Down Expand Up @@ -1233,35 +1233,55 @@ var app = builder.Build();

The generator provides compile-time diagnostics to catch common errors:

### ❌ ATCDIR001: As Type Must Be Interface
### ❌ ATCDIR001: As Type Must Be Interface or Abstract Class

**Severity:** Error

**Description:** The type specified in `As` parameter must be an interface.
**Description:** The type specified in `As` parameter must be an interface or abstract class.

```csharp
// ❌ Error: BaseService is a class, not an interface
// ❌ Error: BaseService is a concrete class
public class BaseService { }

[Registration(As = typeof(BaseService))]
public class UserService : BaseService { }

// ✅ OK: Abstract base class
public abstract class AbstractBaseService { }

[Registration(As = typeof(AbstractBaseService))]
public class UserService : AbstractBaseService { }

// ✅ OK: Interface
public interface IUserService { }

[Registration(As = typeof(IUserService))]
public class UserService : IUserService { }
```

**Fix:** Use an interface type or remove the `As` parameter.
**Fix:** Use an interface, abstract class, or remove the `As` parameter.

### ❌ ATCDIR002: Class Does Not Implement Interface
### ❌ ATCDIR002: Class Does Not Implement Interface or Inherit Abstract Class

**Severity:** Error

**Description:** Class does not implement the interface specified in `As` parameter.
**Description:** Class does not implement the interface or inherit from the abstract class specified in `As` parameter.

```csharp
public interface IUserService { }

// ❌ Error: UserService doesn't implement IUserService
[Registration(As = typeof(IUserService))]
public class UserService { }

public abstract class AuthenticationStateProvider { }

// ❌ Error: UserService doesn't inherit from AuthenticationStateProvider
[Registration(As = typeof(AuthenticationStateProvider))]
public class UserService { }
```

**Fix:** Implement the interface or remove the `As` parameter.
**Fix:** Implement the interface, inherit from the abstract class, or remove the `As` parameter.

### ⚠️ ATCDIR003: Duplicate Registration with Different Lifetime

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageReference Include="Scalar.AspNetCore" Version="2.10.3" />
<PackageReference Include="Scalar.AspNetCore" Version="2.11.0" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion sample/PetStore.Api/PetStore.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageReference Include="Scalar.AspNetCore" Version="2.10.3" />
<PackageReference Include="Scalar.AspNetCore" Version="2.11.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public RegistrationAttribute(Lifetime lifetime = Lifetime.Singleton)
public Lifetime Lifetime { get; }

/// <summary>
/// Gets or sets the service type to register against (typically an interface).
/// Gets or sets the service type to register against (typically an interface or abstract class).
/// If not specified, the service will be registered as its concrete type.
/// </summary>
public global::System.Type? As { get; set; }
Expand Down
7 changes: 5 additions & 2 deletions src/Atc.SourceGenerators/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
ATCDIR001 | DependencyInjection | Error | Service 'As' type must be an interface
ATCDIR002 | DependencyInjection | Error | Class does not implement specified interface
ATCDIR001 | DependencyInjection | Error | Service 'As' type must be an interface or abstract class
ATCDIR002 | DependencyInjection | Error | Class does not implement specified interface or inherit from abstract class
ATCDIR003 | DependencyInjection | Warning | Duplicate service registration with different lifetime
ATCDIR004 | DependencyInjection | Error | Hosted services must use Singleton lifetime
ATCDIR005 | DependencyInjection | Error | Factory method not found
Expand All @@ -30,6 +30,9 @@ ATCOPT010 | OptionsBinding | Error | PostConfigure callback method has invalid s
ATCOPT011 | OptionsBinding | Error | ConfigureAll requires multiple named options
ATCOPT012 | OptionsBinding | Error | ConfigureAll callback method not found
ATCOPT013 | OptionsBinding | Error | ConfigureAll callback method has invalid signature
ATCOPT014 | OptionsBinding | Error | ChildSections cannot be used with Name property
ATCOPT015 | OptionsBinding | Error | ChildSections requires at least 2 items
ATCOPT016 | OptionsBinding | Error | ChildSections items cannot be null or empty
ATCMAP001 | ObjectMapping | Error | Mapping class must be partial
ATCMAP002 | ObjectMapping | Error | Target type must be a class or struct
ATCMAP003 | ObjectMapping | Error | MapProperty target property not found
Expand Down
3 changes: 0 additions & 3 deletions src/Atc.SourceGenerators/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,3 @@

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
ATCOPT014 | OptionsBinding | Error | ChildSections cannot be used with Name property
ATCOPT015 | OptionsBinding | Error | ChildSections requires at least 2 items
ATCOPT016 | OptionsBinding | Error | ChildSections items cannot be null or empty
2 changes: 1 addition & 1 deletion src/Atc.SourceGenerators/Atc.SourceGenerators.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
</ItemGroup>

Expand Down
12 changes: 12 additions & 0 deletions src/Atc.SourceGenerators/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Atc.SourceGenerators;

/// <summary>
/// Common constants used across source generators.
/// </summary>
public static class Constants
{
/// <summary>
/// Unix-style line feed character used for consistent line endings in generated code.
/// </summary>
public const char LineFeed = '\n';
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static StringBuilder AppendLineLf(
builder.Append(value);
}

builder.Append('\n');
builder.Append(Constants.LineFeed);
return builder;
}
}
Loading