< Summary

Information
Class: AsiBackbone.Analyzers.GovernanceArtifactPersistenceAnalyzer
Assembly: AsiBackbone.Analyzers
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Analyzers/GovernanceArtifactPersistenceAnalyzer.cs
Line coverage
94%
Covered lines: 87
Uncovered lines: 5
Coverable lines: 92
Total lines: 174
Line coverage: 94.5%
Branch coverage
77%
Covered branches: 59
Total branches: 76
Branch coverage: 77.6%
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(...)100%88100%
AnalyzeDiscardAssignment(...)62.5%8892.3%
UnwrapAwait(...)100%22100%
IsArtifactProducerOperation(...)83.33%201882.35%
GetGovernanceArtifactType(...)75%4480%
UnwrapKnownWrapper(...)70%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
 4435    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
 36
 37    public override void Initialize(AnalysisContext context)
 38    {
 4439        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 4440        context.EnableConcurrentExecution();
 4441        context.RegisterOperationAction(AnalyzeExpressionStatement, OperationKind.ExpressionStatement);
 4442        context.RegisterOperationAction(AnalyzeDiscardAssignment, OperationKind.SimpleAssignment);
 4443    }
 44
 45    private static void AnalyzeExpressionStatement(OperationAnalysisContext context)
 46    {
 4247        var expressionStatement = (IExpressionStatementOperation)context.Operation;
 48
 4249        if (IsSuppressedByHostMarker(context.ContainingSymbol) || expressionStatement.Operation is ISimpleAssignmentOper
 50        {
 2051            return;
 52        }
 53
 2254        IOperation expression = UnwrapAwait(expressionStatement.Operation);
 2255        ITypeSymbol? artifactType = GetGovernanceArtifactType(expression.Type);
 2256        if (artifactType is null || !IsArtifactProducerOperation(expression))
 57        {
 458            return;
 59        }
 60
 1861        context.ReportDiagnostic(
 1862            Diagnostic.Create(
 1863                Rule,
 1864                expression.Syntax.GetLocation(),
 1865                artifactType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
 1866    }
 67
 68    private static void AnalyzeDiscardAssignment(OperationAnalysisContext context)
 69    {
 1870        var assignment = (ISimpleAssignmentOperation)context.Operation;
 71
 1872        if (IsSuppressedByHostMarker(context.ContainingSymbol) || assignment.Target is not IDiscardOperation)
 73        {
 074            return;
 75        }
 76
 1877        IOperation value = UnwrapAwait(assignment.Value);
 1878        ITypeSymbol? artifactType = GetGovernanceArtifactType(value.Type);
 1879        if (artifactType is null || !IsArtifactProducerOperation(value))
 80        {
 881            return;
 82        }
 83
 1084        context.ReportDiagnostic(
 1085            Diagnostic.Create(
 1086                Rule,
 1087                assignment.Syntax.GetLocation(),
 1088                artifactType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
 1089    }
 90
 91    private static IOperation UnwrapAwait(IOperation operation)
 92    {
 9093        return operation is IAwaitOperation awaitOperation
 9094            ? awaitOperation.Operation
 9095            : operation;
 96    }
 97
 98    private static bool IsArtifactProducerOperation(IOperation? operation)
 99    {
 50100        if (operation is null)
 101        {
 0102            return false;
 103        }
 104
 50105        operation = UnwrapAwait(operation);
 106
 50107        while (operation is IConversionOperation conversionOperation)
 108        {
 0109            operation = conversionOperation.Operand;
 0110        }
 111
 50112        return operation switch
 50113        {
 24114            IInvocationOperation => true,
 2115            IObjectCreationOperation => true,
 2116            IPropertyReferenceOperation => true,
 6117            IConditionalOperation conditionalOperation => IsArtifactProducerOperation(conditionalOperation.WhenTrue)
 6118                || IsArtifactProducerOperation(conditionalOperation.WhenFalse),
 2119            ICoalesceOperation coalesceOperation => IsArtifactProducerOperation(coalesceOperation.Value)
 2120                || IsArtifactProducerOperation(coalesceOperation.WhenNull),
 14121            _ => false
 50122        };
 123    }
 124
 125    private static ITypeSymbol? GetGovernanceArtifactType(ITypeSymbol? type)
 126    {
 40127        ITypeSymbol? candidate = UnwrapKnownWrapper(type);
 40128        if (candidate is null)
 129        {
 0130            return null;
 131        }
 132
 40133        string candidateName = candidate.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
 40134        return GovernanceArtifactTypeNames.Contains(candidateName) ? candidate : null;
 135    }
 136
 137    private static ITypeSymbol? UnwrapKnownWrapper(ITypeSymbol? type)
 138    {
 40139        if (type is not INamedTypeSymbol namedType || !namedType.IsGenericType || namedType.TypeArguments.Length != 1)
 140        {
 34141            return type;
 142        }
 143
 6144        string namespaceName = namedType.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageForm
 145
 6146        return namespaceName == "System.Threading.Tasks" && (namedType.Name == "Task" || namedType.Name == "ValueTask")
 6147            ? namedType.TypeArguments[0]
 6148            : namespaceName == "AsiBackbone.Core.Results" && namedType.Name == "OperationResult"
 6149            ? namedType.TypeArguments[0]
 6150            : type;
 151    }
 152
 153    private static bool IsSuppressedByHostMarker(ISymbol? symbol)
 154    {
 236155        for (ISymbol? current = symbol; current is not null; current = current.ContainingSymbol)
 156        {
 238157            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
 116166            if (current is INamedTypeSymbol)
 167            {
 168                break;
 169            }
 170        }
 171
 58172        return false;
 173    }
 174}