< Summary

Information
Class: AsiBackbone.Analyzers.GovernanceArtifactPersistenceAnalyzer
Assembly: AsiBackbone.Analyzers
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Analyzers/GovernanceArtifactPersistenceAnalyzer.cs
Line coverage
86%
Covered lines: 80
Uncovered lines: 12
Coverable lines: 92
Total lines: 174
Line coverage: 86.9%
Branch coverage
63%
Covered branches: 48
Total branches: 76
Branch coverage: 63.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()50%22100%
get_SupportedDiagnostics()100%11100%
Initialize(...)100%11100%
AnalyzeExpressionStatement(...)75%8892.3%
AnalyzeDiscardAssignment(...)62.5%8892.3%
UnwrapAwait(...)100%22100%
IsArtifactProducerOperation(...)44.44%661847.05%
GetGovernanceArtifactType(...)50%4480%
UnwrapKnownWrapper(...)65%2020100%
IsSuppressedByHostMarker(...)78.57%1414100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Analyzers/GovernanceArtifactPersistenceAnalyzer.cs

#LineLine coverage
 1using System.Collections.Immutable;
 2using Microsoft.CodeAnalysis;
 3using Microsoft.CodeAnalysis.Diagnostics;
 4using Microsoft.CodeAnalysis.Operations;
 5
 6namespace AsiBackbone.Analyzers;
 7
 8[DiagnosticAnalyzer(LanguageNames.CSharp)]
 9public sealed class GovernanceArtifactPersistenceAnalyzer : DiagnosticAnalyzer
 10{
 11    public const string DiagnosticId = "ASIB001";
 12
 213    private static readonly DiagnosticDescriptor Rule = new(
 214        DiagnosticId,
 215        "Persist or continue AsiBackbone governance artifact",
 216        "AsiBackbone governance artifact '{0}' is created or returned and then discarded; persist audit/outbox residue o
 217        "AsiBackbone.GovernanceSafety",
 218        DiagnosticSeverity.Warning,
 219        isEnabledByDefault: true,
 220        description: "Governance decisions, audit residue, capability grants, handshake outcomes, and outbox artifacts s
 21
 222    private static readonly ImmutableHashSet<string> GovernanceArtifactTypeNames = ImmutableHashSet.Create(
 223        StringComparer.Ordinal,
 224        "AsiBackbone.Core.Audit.AuditLedgerRecord",
 225        "AsiBackbone.Core.Audit.AuditResidue",
 226        "AsiBackbone.Core.CapabilityTokens.CapabilityGrantUseResult",
 227        "AsiBackbone.Core.CapabilityTokens.CapabilityGrantValidationResult",
 228        "AsiBackbone.Core.CapabilityTokens.CapabilityTokenGrant",
 229        "AsiBackbone.Core.Decisions.GovernanceDecision",
 230        "AsiBackbone.Core.Emissions.GovernanceEmissionEnvelope",
 231        "AsiBackbone.Core.Emissions.GovernanceEmissionResult",
 232        "AsiBackbone.Core.Handshakes.LiabilityHandshakeAcknowledgment",
 233        "AsiBackbone.Core.Outbox.GovernanceOutboxEntry");
 34
 1435    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
 36
 37    public override void Initialize(AnalysisContext context)
 38    {
 1439        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 1440        context.EnableConcurrentExecution();
 1441        context.RegisterOperationAction(AnalyzeExpressionStatement, OperationKind.ExpressionStatement);
 1442        context.RegisterOperationAction(AnalyzeDiscardAssignment, OperationKind.SimpleAssignment);
 1443    }
 44
 45    private static void AnalyzeExpressionStatement(OperationAnalysisContext context)
 46    {
 1247        var expressionStatement = (IExpressionStatementOperation)context.Operation;
 48
 1249        if (IsSuppressedByHostMarker(context.ContainingSymbol) || expressionStatement.Operation is ISimpleAssignmentOper
 50        {
 651            return;
 52        }
 53
 654        IOperation expression = UnwrapAwait(expressionStatement.Operation);
 655        ITypeSymbol? artifactType = GetGovernanceArtifactType(expression.Type);
 656        if (artifactType is null || !IsArtifactProducerOperation(expression))
 57        {
 058            return;
 59        }
 60
 661        context.ReportDiagnostic(
 662            Diagnostic.Create(
 663                Rule,
 664                expression.Syntax.GetLocation(),
 665                artifactType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
 666    }
 67
 68    private static void AnalyzeDiscardAssignment(OperationAnalysisContext context)
 69    {
 470        var assignment = (ISimpleAssignmentOperation)context.Operation;
 71
 472        if (IsSuppressedByHostMarker(context.ContainingSymbol) || assignment.Target is not IDiscardOperation)
 73        {
 074            return;
 75        }
 76
 477        IOperation value = UnwrapAwait(assignment.Value);
 478        ITypeSymbol? artifactType = GetGovernanceArtifactType(value.Type);
 479        if (artifactType is null || !IsArtifactProducerOperation(value))
 80        {
 281            return;
 82        }
 83
 284        context.ReportDiagnostic(
 285            Diagnostic.Create(
 286                Rule,
 287                assignment.Syntax.GetLocation(),
 288                artifactType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
 289    }
 90
 91    private static IOperation UnwrapAwait(IOperation operation)
 92    {
 2093        return operation is IAwaitOperation awaitOperation
 2094            ? awaitOperation.Operation
 2095            : operation;
 96    }
 97
 98    private static bool IsArtifactProducerOperation(IOperation? operation)
 99    {
 10100        if (operation is null)
 101        {
 0102            return false;
 103        }
 104
 10105        operation = UnwrapAwait(operation);
 106
 10107        while (operation is IConversionOperation conversionOperation)
 108        {
 0109            operation = conversionOperation.Operand;
 0110        }
 111
 10112        return operation switch
 10113        {
 8114            IInvocationOperation => true,
 0115            IObjectCreationOperation => true,
 0116            IPropertyReferenceOperation => true,
 0117            IConditionalOperation conditionalOperation => IsArtifactProducerOperation(conditionalOperation.WhenTrue)
 0118                || IsArtifactProducerOperation(conditionalOperation.WhenFalse),
 0119            ICoalesceOperation coalesceOperation => IsArtifactProducerOperation(coalesceOperation.Value)
 0120                || IsArtifactProducerOperation(coalesceOperation.WhenNull),
 2121            _ => false
 10122        };
 123    }
 124
 125    private static ITypeSymbol? GetGovernanceArtifactType(ITypeSymbol? type)
 126    {
 10127        ITypeSymbol? candidate = UnwrapKnownWrapper(type);
 10128        if (candidate is null)
 129        {
 0130            return null;
 131        }
 132
 10133        string candidateName = candidate.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
 10134        return GovernanceArtifactTypeNames.Contains(candidateName) ? candidate : null;
 135    }
 136
 137    private static ITypeSymbol? UnwrapKnownWrapper(ITypeSymbol? type)
 138    {
 10139        if (type is not INamedTypeSymbol namedType || !namedType.IsGenericType || namedType.TypeArguments.Length != 1)
 140        {
 6141            return type;
 142        }
 143
 4144        string namespaceName = namedType.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageForm
 145
 4146        return namespaceName == "System.Threading.Tasks" && (namedType.Name == "Task" || namedType.Name == "ValueTask")
 4147            ? namedType.TypeArguments[0]
 4148            : namespaceName == "AsiBackbone.Core.Results" && namedType.Name == "OperationResult"
 4149            ? namedType.TypeArguments[0]
 4150            : type;
 151    }
 152
 153    private static bool IsSuppressedByHostMarker(ISymbol? symbol)
 154    {
 60155        for (ISymbol? current = symbol; current is not null; current = current.ContainingSymbol)
 156        {
 62157            foreach (AttributeData attribute in current.GetAttributes())
 158            {
 2159                string? attributeName = attribute.AttributeClass?.Name;
 2160                if (attributeName is "AsiBackbonePersistenceHandled" or "AsiBackbonePersistenceHandledAttribute")
 161                {
 2162                    return true;
 163                }
 164            }
 165
 28166            if (current is INamedTypeSymbol)
 167            {
 168                break;
 169            }
 170        }
 171
 14172        return false;
 173    }
 174}