Skip to content

Commit

Permalink
Add Roslyn Analyzer project (#349)
Browse files Browse the repository at this point in the history
  • Loading branch information
CollinAlpert authored May 24, 2024
1 parent f419ab6 commit 8456dd0
Show file tree
Hide file tree
Showing 7 changed files with 426 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
namespace Carter.Analyzers;

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CarterModuleShouldNotHaveDependenciesAnalyzer : DiagnosticAnalyzer
{
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(OnCompilationStart);
}

private static void OnCompilationStart(CompilationStartAnalysisContext context)
{
var carterModuleType = context.Compilation.GetTypeByMetadataName("Carter.ICarterModule");
if (carterModuleType is null)
{
return;
}

context.RegisterSymbolAction(ctx => OnTypeAnalysis(ctx, carterModuleType), SymbolKind.NamedType);
}

private static void OnTypeAnalysis(SymbolAnalysisContext context, INamedTypeSymbol carterModuleType)
{
var typeSymbol = (INamedTypeSymbol)context.Symbol;
if (!typeSymbol.Interfaces.Contains(carterModuleType, SymbolEqualityComparer.Default))
{
return;
}

foreach (var constructor in typeSymbol.Constructors)
{
if (constructor.DeclaredAccessibility == Accessibility.Private || constructor.Parameters.Length == 0)
{
continue;
}

foreach (var syntaxReference in constructor.DeclaringSyntaxReferences)
{
var node = syntaxReference.GetSyntax();
SyntaxToken identifier;
if (node is ConstructorDeclarationSyntax constructorDeclaration)
{
identifier = constructorDeclaration.Identifier;
} else if (node is RecordDeclarationSyntax recordDeclaration)
{
identifier = recordDeclaration.Identifier;
}
else
{
continue;
}

var diagnostic = Diagnostic.Create(
DiagnosticDescriptors.CarterModuleShouldNotHaveDependencies,
identifier.GetLocation(),
identifier.Text
);
context.ReportDiagnostic(diagnostic);
}
}
}

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [DiagnosticDescriptors.CarterModuleShouldNotHaveDependencies];
}
16 changes: 16 additions & 0 deletions src/Carter/Analyzers/DiagnosticDescriptors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.CodeAnalysis;

namespace Carter.Analyzers;

internal static class DiagnosticDescriptors
{
public static readonly DiagnosticDescriptor CarterModuleShouldNotHaveDependencies = new(
"CARTER1",
"Carter module should not have dependencies",
"'{0}' should not have dependencies",
"Usage",
DiagnosticSeverity.Warning,
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."
);
}
8 changes: 7 additions & 1 deletion src/Carter/Carter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,27 @@
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
<PackageReadmeFile>README.md</PackageReadmeFile>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<NoWarn>RS2008</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\media\carterlogo.png" Pack="true" PackagePath="\" />
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" PrivateAssets="compile" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="all" />
<PackageReference Include="MinVer" Version="2.5.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="$(AssemblyName).Tests" />
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions src/Carter/DependencyContextAssemblyCatalog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ private static Assembly SafeLoadAssembly(AssemblyName assemblyName)
{
try
{
#pragma warning disable RS1035
return Assembly.Load(assemblyName);
#pragma warning restore RS1035
}
catch (Exception)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Carter/ModelBinding/BindExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,14 @@ public static async Task BindAndSaveFile(this HttpRequest request, string saveLo

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

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

using (var fileToSave = File.Create(Path.Combine(saveLocation, fileName)))
await file.CopyToAsync(fileToSave);
#pragma warning restore RS1035
}
}
Loading

0 comments on commit 8456dd0

Please sign in to comment.