Skip to content
Open
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 @@ -29,11 +29,18 @@ private static void OnCompilationStart(CompilationStartAnalysisContext context)
private static void OnTypeAnalysis(SymbolAnalysisContext context, INamedTypeSymbol carterModuleType)
{
var typeSymbol = (INamedTypeSymbol)context.Symbol;
if (!typeSymbol.Interfaces.Contains(carterModuleType, SymbolEqualityComparer.Default))
if (typeSymbol.IsAbstract
|| !typeSymbol.AllInterfaces.Contains(carterModuleType, SymbolEqualityComparer.Default)
|| typeSymbol.DeclaredAccessibility is not (Accessibility.Public or Accessibility.Internal))
{
return;
}

var isDirectImplementation = typeSymbol.Interfaces.Contains(carterModuleType, SymbolEqualityComparer.Default);
var descriptor = isDirectImplementation
? DiagnosticDescriptors.CarterModuleShouldNotHaveDependencies
: DiagnosticDescriptors.CarterDerivedModuleShouldNotHaveDependencies;

foreach (var constructor in typeSymbol.Constructors)
{
if (constructor.DeclaredAccessibility == Accessibility.Private || constructor.Parameters.Length == 0)
Expand All @@ -58,7 +65,7 @@ private static void OnTypeAnalysis(SymbolAnalysisContext context, INamedTypeSymb
}

var diagnostic = Diagnostic.Create(
DiagnosticDescriptors.CarterModuleShouldNotHaveDependencies,
descriptor,
identifier.Value.GetLocation(),
identifier.Value.Text
);
Expand All @@ -67,5 +74,9 @@ private static void OnTypeAnalysis(SymbolAnalysisContext context, INamedTypeSymb
}
}

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.CarterModuleShouldNotHaveDependencies);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
[
DiagnosticDescriptors.CarterModuleShouldNotHaveDependencies,
DiagnosticDescriptors.CarterDerivedModuleShouldNotHaveDependencies,
];
}
10 changes: 10 additions & 0 deletions src/Carter.Analyzers/DiagnosticDescriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,14 @@ internal static class DiagnosticDescriptors
true,
"When a class implements ICarterModule, it should not have any dependencies. This is because Carter uses minimal APIs and dependencies should be declared in the request delegate."
);

public static readonly DiagnosticDescriptor CarterDerivedModuleShouldNotHaveDependencies = new(
"CARTER2",
"Derived Carter module should not have dependencies",
"'{0}' inherits ICarterModule and should not have dependencies",
"Usage",
DiagnosticSeverity.Warning,
true,
"Carter registers all non-abstract types assignable to ICarterModule as singletons, including types that inherit ICarterModule through a base class. Dependencies should be declared in the request delegate."
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
namespace Carter.Tests.Analyzers;

using System.Threading.Tasks;
using Carter.Analyzers;
using Microsoft.CodeAnalysis.Testing;
using Xunit;

public sealed class CarterDerivedModuleShouldNotHaveDependenciesTests
{
[Theory]
[InlineData("class")]
[InlineData("record")]
public Task CarterSubModuleWithConstructorDependencies_Diagnostic(string type)
{
var code = $$"""
using Carter;
using Microsoft.AspNetCore.Routing;

{{type}} MyCarterModule : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app) {}
}

{{type}} MySubCarterModule : MyCarterModule
{
public {|#0:MySubCarterModule|}(string s) {}
}
""";

var diagnosticResult = new DiagnosticResult(DiagnosticDescriptors.CarterDerivedModuleShouldNotHaveDependencies)
.WithLocation(0)
.WithArguments("MySubCarterModule");

return VerifyAsync(code, diagnosticResult);
}

[Theory]
[InlineData("class")]
[InlineData("record")]
public Task ConcreteTypeDerivedFromAbstractModuleWithDependencies_Diagnostic(string type)
{
var code = $$"""
using Carter;
using Microsoft.AspNetCore.Routing;

abstract {{type}} MyAbstractCarterModule : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app) {}
}

{{type}} MyCarterModule : MyAbstractCarterModule
{
public {|#0:MyCarterModule|}(string s) {}
}
""";

var diagnosticResult = new DiagnosticResult(DiagnosticDescriptors.CarterDerivedModuleShouldNotHaveDependencies)
.WithLocation(0)
.WithArguments("MyCarterModule");

return VerifyAsync(code, diagnosticResult);
}

[Theory]
[InlineData("class")]
[InlineData("record")]
public Task DerivedModuleWithPrivateConstructor_NoDiagnostic(string type)
{
var code = $$"""
using Carter;
using Microsoft.AspNetCore.Routing;

{{type}} MyCarterModule : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app) {}
}

{{type}} MySubCarterModule : MyCarterModule
{
private MySubCarterModule(string s) {}
}
""";

return VerifyAsync(code);
}

[Theory]
[InlineData("class")]
[InlineData("record")]
public Task AbstractDerivedModuleWithDependencies_NoDiagnostic(string type)
{
var code = $$"""
using Carter;
using Microsoft.AspNetCore.Routing;

{{type}} MyCarterModule : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app) {}
}

abstract {{type}} MyAbstractSubCarterModule : MyCarterModule
{
public MyAbstractSubCarterModule(string s) {}
}
""";

return VerifyAsync(code);
}

[Theory]
[InlineData("class")]
[InlineData("record")]
public Task DerivedModuleWithoutConstructor_NoDiagnostic(string type)
{
var code = $$"""
using Carter;
using Microsoft.AspNetCore.Routing;

{{type}} MyCarterModule : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app) {}
}

{{type}} MySubCarterModule : MyCarterModule
{
}
""";

return VerifyAsync(code);
}

[Theory]
[InlineData("class")]
[InlineData("record")]
public Task ThreeLevelInheritanceWithDependencies_Diagnostic(string type)
{
var code = $$"""
using Carter;
using Microsoft.AspNetCore.Routing;

{{type}} MyCarterModule : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app) {}
}

{{type}} MySubCarterModule : MyCarterModule
{
}

{{type}} MySubSubCarterModule : MySubCarterModule
{
public {|#0:MySubSubCarterModule|}(string s) {}
}
""";

var diagnosticResult = new DiagnosticResult(DiagnosticDescriptors.CarterDerivedModuleShouldNotHaveDependencies)
.WithLocation(0)
.WithArguments("MySubSubCarterModule");

return VerifyAsync(code, diagnosticResult);
}

private static Task VerifyAsync(string code, DiagnosticResult? diagnosticResult = null)
{
AnalyzerTest<DefaultVerifier> test = new CSharpPreviewAnalyzerTest<CarterModuleShouldNotHaveDependenciesAnalyzer>(code);
if (diagnosticResult.HasValue)
{
test.ExpectedDiagnostics.Add(diagnosticResult.Value);
}

return test.RunAsync();
}
}
Loading