Skip to content

Commit

Permalink
Merge pull request #647 from SteveDunn/throw-analyzer
Browse files Browse the repository at this point in the history
Add analyzer to check for for user throw keywords in NormalizeInput and Validate
  • Loading branch information
SteveDunn authored Jul 26, 2024
2 parents fc6d240 + a559f29 commit 9e5b8e0
Show file tree
Hide file tree
Showing 5 changed files with 406 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,46 +33,54 @@ public class MongoScenario : IScenario
{
public async Task Run()
{
string runnerOs = Environment.GetEnvironmentVariable("RUNNER_OS");

try
{
string runnerOs = Environment.GetEnvironmentVariable("RUNNER_OS");

bool isLocalOrLinuxOnGitHub = string.IsNullOrEmpty(runnerOs) || runnerOs == "Linux";
bool isLocalOrLinuxOnGitHub = string.IsNullOrEmpty(runnerOs) || runnerOs == "Linux";

if (!isLocalOrLinuxOnGitHub)
{
Console.WriteLine("Skipping because not running locally or on Linux on a GitHub action.");
return;
}
if (!isLocalOrLinuxOnGitHub)
{
Console.WriteLine("Skipping because not running locally or on Linux on a GitHub action.");
return;
}

MongoDbContainer container = new MongoDbBuilder().WithImage("mongo:latest").Build();
MongoDbContainer container = new MongoDbBuilder().WithImage("mongo:latest").Build();

await container.StartAsync();
await container.StartAsync();

var client = new MongoClient(container.GetConnectionString());
var client = new MongoClient(container.GetConnectionString());

var database = client.GetDatabase("testDatabase");
var collection = database.GetCollection<Person>("peopleCollection");
var database = client.GetDatabase("testDatabase");
var collection = database.GetCollection<Person>("peopleCollection");

BsonSerializer.RegisterSerializer(new NameBsonSerializer());
BsonSerializer.RegisterSerializer(new AgeBsonSerializer());
BsonSerializer.RegisterSerializer(new NameBsonSerializer());
BsonSerializer.RegisterSerializer(new AgeBsonSerializer());

// or, use the generated one for all value objects...
// BsonSerializationRegisterForVogen_Examples.TryRegister();
// or, use the generated one for all value objects...
// BsonSerializationRegisterForVogen_Examples.TryRegister();

var personFaker = new Faker<Person>()
.RuleFor(p => p.Name, f => Name.From(f.Name.FirstName()))
.RuleFor(p => p.Age, f => Age.From(DateTime.Now.Year - f.Person.DateOfBirth.Year));
var personFaker = new Faker<Person>()
.RuleFor(p => p.Name, f => Name.From(f.Name.FirstName()))
.RuleFor(p => p.Age, f => Age.From(DateTime.Now.Year - f.Person.DateOfBirth.Year));

foreach (Person eachPerson in personFaker.Generate(10))
{
await collection.InsertOneAsync(eachPerson);
}
foreach (Person eachPerson in personFaker.Generate(10))
{
await collection.InsertOneAsync(eachPerson);
}

Console.WriteLine("Inserted people... Now finding them...");
Console.WriteLine("Inserted people... Now finding them...");

IAsyncCursor<Person> people = await collection.FindAsync("{}");
await people.ForEachAsync((person) => Console.WriteLine($"{person.Name} is {person.Age}"));
IAsyncCursor<Person> people = await collection.FindAsync("{}");
await people.ForEachAsync((person) => Console.WriteLine($"{person.Name} is {person.Age}"));

await container.DisposeAsync();
await container.DisposeAsync();
}
catch (Exception e) when (e.Message.StartsWith("Docker is either not running"))
{
Console.WriteLine("Docker is not running - skipping this scenario");
}
}
}

Expand Down
10 changes: 9 additions & 1 deletion src/Vogen/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,12 @@ VOG028 | Usage | Info | NormalizeInputMethodAnalyzer

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
VOG029 | Usage | Error | SpecifyTypeExplicitlyInValueObjectAttributeAnalzyer
VOG029 | Usage | Error | SpecifyTypeExplicitlyInValueObjectAttributeAnalzyer

## Release 4.0.16

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
VOG032 | Usage | Warning | DoNotThrowFromUserCodeAnalyzer
1 change: 1 addition & 0 deletions src/Vogen/Diagnostics/RuleIdentifiers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ public static class RuleIdentifiers
public const string ExplicitlySpecifyTypeInValueObjectAttribute = "VOG029";
public const string EfCoreTargetMustExplicitlySpecifyItsPrimitive = "VOG030";
public const string EfCoreTargetMustBeAVo = "VOG031";
public const string DoNotThrowFromUserCode = "VOG032";
}
60 changes: 60 additions & 0 deletions src/Vogen/Rules/DoNotThrowFromUserCodeAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Vogen.Diagnostics;

namespace Vogen.Rules;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class DoNotThrowFromUserCodeAnalyzer : DiagnosticAnalyzer
{
// ReSharper disable once ArrangeObjectCreationWhenTypeEvident - current bug in Roslyn analyzer means it won't pick this up when implied
private static readonly DiagnosticDescriptor _rule = new DiagnosticDescriptor(
RuleIdentifiers.DoNotThrowFromUserCode,
"Value objects should not throw exceptions",
"Type '{0}' throws an exception which can cause surprising side effects, for instance, in implicit conversions",
RuleCategories.Usage,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "Value objects can contain user code; methods such as NormalizeInput and Validate. These should not throw exceptions, because that can cause surprising side effects such as when doing implicit conversions (which should not throw). The only place to throw, and which is handle by the generated code, is an exception related to validation.");

// ReSharper disable once UseCollectionExpression
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(_rule);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ThrowStatement, SyntaxKind.ThrowExpression);
}

private static void Analyze(SyntaxNodeAnalysisContext ctx)
{
var throwStatement = ctx.Node;

var containingType = throwStatement.AncestorsAndSelf().OfType<TypeDeclarationSyntax>().FirstOrDefault();
if (containingType is null)
{
return;
}

if (!VoFilter.IsTarget(containingType))
{
return;
}

var containingMethod = throwStatement.AncestorsAndSelf().OfType<MethodDeclarationSyntax>().FirstOrDefault();

if (containingMethod?.Identifier.Text is not ("Validate" or "NormalizeInput"))
{
return;
}

var diagnostic = DiagnosticsCatalogue.BuildDiagnostic(_rule, containingType.Identifier.Text, throwStatement.GetLocation());
ctx.ReportDiagnostic(diagnostic);
}
}
Loading

0 comments on commit 9e5b8e0

Please sign in to comment.