Skip to content
Draft
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
@@ -0,0 +1,8 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Architecture.CodeAnalysis.Specs.for_ConceptAsMustHaveNotSetSentinelRule.given;

public class a_conceptasmusthavenotsetssentinelrule : Cratis.Architecture.CodeAnalysis.Specs.given.an_architecture_analyzer
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Architecture.CodeAnalysis.Specs.for_ConceptAsMustHaveNotSetSentinelRule.when_analyzing_concept_records;

public class and_concept_has_empty_sentinel : given.a_conceptasmusthavenotsetssentinelrule
{
async Task Because() =>
_diagnostics = await analyze(
"""
public record ConceptAs<T>(T Value);

public record AuthorName(string Value) : ConceptAs<string>(Value)
{
public static readonly AuthorName Empty = new(string.Empty);
}
""");

[Fact] void should_not_report_diagnostic() =>
_diagnostics.Any(_ => _.Id == "CRARCH0027").ShouldBeFalse();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Architecture.CodeAnalysis.Specs.for_ConceptAsMustHaveNotSetSentinelRule.when_analyzing_concept_records;

public class and_concept_has_notset_sentinel : given.a_conceptasmusthavenotsetssentinelrule
{
async Task Because() =>
_diagnostics = await analyze(
"""
using System;

public record ConceptAs<T>(T Value);

public record AuthorId(Guid Value) : ConceptAs<Guid>(Value)
{
public static readonly AuthorId NotSet = new(Guid.Empty);
}
""");

[Fact] void should_not_report_diagnostic() =>
_diagnostics.Any(_ => _.Id == "CRARCH0027").ShouldBeFalse();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Architecture.CodeAnalysis.Specs.for_ConceptAsMustHaveNotSetSentinelRule.when_analyzing_concept_records;

public class and_concept_missing_notset_sentinel : given.a_conceptasmusthavenotsetssentinelrule
{
async Task Because() =>
_diagnostics = await analyze(
"""
using System;

public record ConceptAs<T>(T Value);

public record AuthorId(Guid Value) : ConceptAs<Guid>(Value)
{
}
""");

[Fact] void should_report_diagnostic() =>
_diagnostics.Any(_ => _.Id == "CRARCH0027").ShouldBeTrue();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Architecture.CodeAnalysis.Specs.for_ConceptAsMustHaveNotSetSentinelRule.when_analyzing_concept_records;

public class and_regular_record_without_notset : given.a_conceptasmusthavenotsetssentinelrule
{
async Task Because() =>
_diagnostics = await analyze(
"""
public record Author(string Name)
{
}
""");

[Fact] void should_not_report_diagnostic() =>
_diagnostics.Any(_ => _.Id == "CRARCH0027").ShouldBeFalse();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Architecture.CodeAnalysis.Specs.for_GuidConceptMustHaveNewFactoryRule.given;

public class a_guidconceptmusthavenwfactoryrule : Cratis.Architecture.CodeAnalysis.Specs.given.an_architecture_analyzer
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Architecture.CodeAnalysis.Specs.for_GuidConceptMustHaveNewFactoryRule.when_analyzing_guid_concepts;

public class and_guid_concept_has_new_factory : given.a_guidconceptmusthavenwfactoryrule
{
async Task Because() =>
_diagnostics = await analyze(
"""
using System;

public record ConceptAs<T>(T Value);

public record AuthorId(Guid Value) : ConceptAs<Guid>(Value)
{
public static AuthorId New() => new(Guid.NewGuid());
}
""");

[Fact] void should_not_report_diagnostic() =>
_diagnostics.Any(_ => _.Id == "CRARCH0028").ShouldBeFalse();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Architecture.CodeAnalysis.Specs.for_GuidConceptMustHaveNewFactoryRule.when_analyzing_guid_concepts;

public class and_guid_concept_missing_new_factory : given.a_guidconceptmusthavenwfactoryrule
{
async Task Because() =>
_diagnostics = await analyze(
"""
using System;

public record ConceptAs<T>(T Value);

public record AuthorId(Guid Value) : ConceptAs<Guid>(Value)
{
}
""");

[Fact] void should_report_diagnostic() =>
_diagnostics.Any(_ => _.Id == "CRARCH0028").ShouldBeTrue();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Architecture.CodeAnalysis.Specs.for_GuidConceptMustHaveNewFactoryRule.when_analyzing_guid_concepts;

public class and_string_concept_without_new_factory : given.a_guidconceptmusthavenwfactoryrule
{
async Task Because() =>
_diagnostics = await analyze(
"""
public record ConceptAs<T>(T Value);

public record AuthorName(string Value) : ConceptAs<string>(Value)
{
}
""");

[Fact] void should_not_report_diagnostic() =>
_diagnostics.Any(_ => _.Id == "CRARCH0028").ShouldBeFalse();
}
8 changes: 8 additions & 0 deletions Source/CodeAnalysis/Analyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public partial class ArchitectureAnalyzer : DiagnosticAnalyzer
LoggerMessageContainerConventionsRule.Descriptor,
UseCratisFundamentalsTracesRule.Descriptor,
UseCratisFundamentalsMetricsRule.Descriptor,
ConceptAsMustHaveNotSetSentinelRule.Descriptor,
GuidConceptMustHaveNewFactoryRule.Descriptor,
// TODO: CRARCH0029 - Uncomment when the rule is fully implemented
// AvoidPrimitiveTypesRule.Descriptor,
];

/// <inheritdoc/>
Expand All @@ -61,6 +65,10 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeIdentifierTypeUse, SyntaxKind.IdentifierName);
context.RegisterSyntaxNodeAction(AnalyzeMemberAccess, SyntaxKind.SimpleMemberAccessExpression);
// TODO: CRARCH0029 - Uncomment these when the rule is fully implemented
// context.RegisterSyntaxNodeAction(AnalyzePropertyDeclaration, SyntaxKind.PropertyDeclaration);
// context.RegisterSyntaxNodeAction(AnalyzeFieldDeclaration, SyntaxKind.FieldDeclaration);
// context.RegisterSyntaxNodeAction(AnalyzeParameter, SyntaxKind.Parameter);
context.RegisterSyntaxNodeAction(
AnalyzePrivateModifier,
SyntaxKind.ClassDeclaration,
Expand Down
2 changes: 2 additions & 0 deletions Source/CodeAnalysis/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ CRARCH0023 | Architecture | Warning | Use typed logger category
CRARCH0024 | Architecture | Warning | LoggerMessage container conventions
CRARCH0025 | Architecture | Warning | Use Cratis Fundamentals traces
CRARCH0026 | Architecture | Warning | Use Cratis Fundamentals metrics
CRARCH0027 | Architecture | Warning | ConceptAs must have a static readonly NotSet sentinel
CRARCH0028 | Architecture | Warning | Guid-backed identity concept must have a static New() factory
2 changes: 2 additions & 0 deletions Source/CodeAnalysis/NamedTypeAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ static void AnalyzeNamedType(SymbolAnalysisContext context)
ExceptionTypeNamingRule.Analyze(context, type);
NoPostfixesOnClassNamesRule.Analyze(context, type);
StaticClassNamingConventionRule.Analyze(context, type);
ConceptAsMustHaveNotSetSentinelRule.Analyze(context, type);
GuidConceptMustHaveNewFactoryRule.Analyze(context, type);

if (type.TypeKind != TypeKind.Class)
{
Expand Down
Loading