Skip to content

Commit 8456dd0

Browse files
authored
Add Roslyn Analyzer project (#349)
1 parent f419ab6 commit 8456dd0

File tree

7 files changed

+426
-1
lines changed

7 files changed

+426
-1
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
namespace Carter.Analyzers;
2+
3+
using System.Collections.Immutable;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
using Microsoft.CodeAnalysis.Diagnostics;
7+
8+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
9+
internal sealed class CarterModuleShouldNotHaveDependenciesAnalyzer : DiagnosticAnalyzer
10+
{
11+
public override void Initialize(AnalysisContext context)
12+
{
13+
context.EnableConcurrentExecution();
14+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
15+
context.RegisterCompilationStartAction(OnCompilationStart);
16+
}
17+
18+
private static void OnCompilationStart(CompilationStartAnalysisContext context)
19+
{
20+
var carterModuleType = context.Compilation.GetTypeByMetadataName("Carter.ICarterModule");
21+
if (carterModuleType is null)
22+
{
23+
return;
24+
}
25+
26+
context.RegisterSymbolAction(ctx => OnTypeAnalysis(ctx, carterModuleType), SymbolKind.NamedType);
27+
}
28+
29+
private static void OnTypeAnalysis(SymbolAnalysisContext context, INamedTypeSymbol carterModuleType)
30+
{
31+
var typeSymbol = (INamedTypeSymbol)context.Symbol;
32+
if (!typeSymbol.Interfaces.Contains(carterModuleType, SymbolEqualityComparer.Default))
33+
{
34+
return;
35+
}
36+
37+
foreach (var constructor in typeSymbol.Constructors)
38+
{
39+
if (constructor.DeclaredAccessibility == Accessibility.Private || constructor.Parameters.Length == 0)
40+
{
41+
continue;
42+
}
43+
44+
foreach (var syntaxReference in constructor.DeclaringSyntaxReferences)
45+
{
46+
var node = syntaxReference.GetSyntax();
47+
SyntaxToken identifier;
48+
if (node is ConstructorDeclarationSyntax constructorDeclaration)
49+
{
50+
identifier = constructorDeclaration.Identifier;
51+
} else if (node is RecordDeclarationSyntax recordDeclaration)
52+
{
53+
identifier = recordDeclaration.Identifier;
54+
}
55+
else
56+
{
57+
continue;
58+
}
59+
60+
var diagnostic = Diagnostic.Create(
61+
DiagnosticDescriptors.CarterModuleShouldNotHaveDependencies,
62+
identifier.GetLocation(),
63+
identifier.Text
64+
);
65+
context.ReportDiagnostic(diagnostic);
66+
}
67+
}
68+
}
69+
70+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [DiagnosticDescriptors.CarterModuleShouldNotHaveDependencies];
71+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace Carter.Analyzers;
4+
5+
internal static class DiagnosticDescriptors
6+
{
7+
public static readonly DiagnosticDescriptor CarterModuleShouldNotHaveDependencies = new(
8+
"CARTER1",
9+
"Carter module should not have dependencies",
10+
"'{0}' should not have dependencies",
11+
"Usage",
12+
DiagnosticSeverity.Warning,
13+
true,
14+
"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."
15+
);
16+
}

src/Carter/Carter.csproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,27 @@
1313
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
1414
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
1515
<PackageReadmeFile>README.md</PackageReadmeFile>
16+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
17+
<NoWarn>RS2008</NoWarn>
1618
</PropertyGroup>
1719
<ItemGroup>
1820
<None Include="..\..\media\carterlogo.png" Pack="true" PackagePath="\" />
1921
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
22+
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
2023
</ItemGroup>
2124
<ItemGroup>
2225
<FrameworkReference Include="Microsoft.AspNetCore.App" />
2326
</ItemGroup>
2427
<ItemGroup>
2528
<PackageReference Include="FluentValidation" Version="11.8.0" />
29+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" PrivateAssets="compile" />
2630
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.0" />
2731
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="all" />
2832
<PackageReference Include="MinVer" Version="2.5.0">
2933
<PrivateAssets>all</PrivateAssets>
3034
</PackageReference>
31-
35+
</ItemGroup>
36+
<ItemGroup>
37+
<InternalsVisibleTo Include="$(AssemblyName).Tests" />
3238
</ItemGroup>
3339
</Project>

src/Carter/DependencyContextAssemblyCatalog.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ private static Assembly SafeLoadAssembly(AssemblyName assemblyName)
8989
{
9090
try
9191
{
92+
#pragma warning disable RS1035
9293
return Assembly.Load(assemblyName);
94+
#pragma warning restore RS1035
9395
}
9496
catch (Exception)
9597
{

src/Carter/ModelBinding/BindExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,14 @@ public static async Task BindAndSaveFile(this HttpRequest request, string saveLo
8888

8989
private static async Task SaveFileInternal(IFormFile file, string saveLocation, string fileName = "")
9090
{
91+
#pragma warning disable RS1035
9192
if (!Directory.Exists(saveLocation))
9293
Directory.CreateDirectory(saveLocation);
9394

9495
fileName = !string.IsNullOrWhiteSpace(fileName) ? fileName : file.FileName;
9596

9697
using (var fileToSave = File.Create(Path.Combine(saveLocation, fileName)))
9798
await file.CopyToAsync(fileToSave);
99+
#pragma warning restore RS1035
98100
}
99101
}

0 commit comments

Comments
 (0)