< 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: 102
Uncovered lines: 6
Coverable lines: 108
Total lines: 210
Line coverage: 94.4%
Branch coverage
77%
Covered branches: 77
Total branches: 100
Branch coverage: 77%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()50%44100%
get_SupportedDiagnostics()100%11100%
Initialize(...)100%11100%
AnalyzeExpressionStatement(...)100%88100%
AnalyzeDiscardAssignment(...)70%101092.86%
UnwrapAwait(...)100%22100%
IsArtifactProducerOperation(...)83.33%201882.35%
IsPersistenceBoundaryInvocation(...)83.33%66100%
IsPersistenceBoundaryType(...)100%11100%
GetGovernanceArtifactType(...)75%4480%
UnwrapKnownWrapper(...)85%2020100%
IsKnownAsyncWrapper(...)50%171475%
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
 113    private static readonly DiagnosticDescriptor Rule = new(
 114        DiagnosticId,
 115        "Persist or continue AsiBackbone governance artifact",
 116        "AsiBackbone governance artifact '{0}' is created or returned and then discarded; persist audit/outbox residue o
 117        "AsiBackbone.GovernanceSafety",
 118        DiagnosticSeverity.Warning,
 119        isEnabledByDefault: true,
 120        description: "Governance decisions, decision receipt, capability grants, handshake outcomes, and outbox artifact
 21
 122    private static readonly ImmutableHashSet<string> GovernanceArtifactTypeNames = ImmutableHashSet.Create(
 123        StringComparer.Ordinal,
 124        "AsiBackbone.Core.Audit.AuditLedgerRecord",
 125        "AsiBackbone.Core.Audit.DecisionReceipt",
 126        "AsiBackbone.Core.CapabilityTokens.CapabilityGrantUseResult",
 127        "AsiBackbone.Core.CapabilityTokens.CapabilityGrantValidationResult",
 128        "AsiBackbone.Core.CapabilityTokens.CapabilityTokenGrant",
 129        "AsiBackbone.Core.Decisions.GovernanceDecision",
 130        "AsiBackbone.Core.Emissions.GovernanceEmissionEnvelope",
 131        "AsiBackbone.Core.Emissions.GovernanceEmissionResult",
 132        "AsiBackbone.Core.Handshakes.LiabilityHandshakeAcknowledgment",
 133        "AsiBackbone.Core.Outbox.GovernanceOutboxEntry");
 34
 135    private static readonly ImmutableHashSet<string> PersistenceBoundaryTypeNames = ImmutableHashSet.Create(
 136        StringComparer.Ordinal,
 137        "AsiBackbone.Core.Audit.IGovernanceAuditLedgerStore",
 138        "AsiBackbone.Core.Audit.IDecisionReceiptLifecycleStore",
 139        "AsiBackbone.Core.Audit.IDecisionReceiptSink",
 140        "AsiBackbone.Core.CapabilityTokens.ICapabilityGrantUseStore",
 141        "AsiBackbone.Core.Outbox.IGovernanceOutboxStore");
 42
 2443    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
 44
 45    public override void Initialize(AnalysisContext context)
 46    {
 2447        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 2448        context.EnableConcurrentExecution();
 2449        context.RegisterOperationAction(AnalyzeExpressionStatement, OperationKind.ExpressionStatement);
 2450        context.RegisterOperationAction(AnalyzeDiscardAssignment, OperationKind.SimpleAssignment);
 2451    }
 52
 53    private static void AnalyzeExpressionStatement(OperationAnalysisContext context)
 54    {
 2355        var expressionStatement = (IExpressionStatementOperation)context.Operation;
 56
 2357        if (IsSuppressedByHostMarker(context.ContainingSymbol) || expressionStatement.Operation is ISimpleAssignmentOper
 58        {
 1159            return;
 60        }
 61
 1262        IOperation expression = UnwrapAwait(expressionStatement.Operation);
 1263        ITypeSymbol? artifactType = GetGovernanceArtifactType(expression.Type);
 1264        if (artifactType is null || !IsArtifactProducerOperation(expression))
 65        {
 366            return;
 67        }
 68
 969        context.ReportDiagnostic(
 970            Diagnostic.Create(
 971                Rule,
 972                expression.Syntax.GetLocation(),
 973                artifactType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
 974    }
 75
 76    private static void AnalyzeDiscardAssignment(OperationAnalysisContext context)
 77    {
 1078        var assignment = (ISimpleAssignmentOperation)context.Operation;
 79
 1080        if (IsSuppressedByHostMarker(context.ContainingSymbol)
 1081            || (assignment.Target is not IDiscardOperation && !IsKnownAsyncWrapper(assignment.Value.Type)))
 82        {
 083            return;
 84        }
 85
 1086        IOperation value = UnwrapAwait(assignment.Value);
 1087        ITypeSymbol? artifactType = GetGovernanceArtifactType(value.Type);
 1088        if (artifactType is null || !IsArtifactProducerOperation(value))
 89        {
 490            return;
 91        }
 92
 693        context.ReportDiagnostic(
 694            Diagnostic.Create(
 695                Rule,
 696                assignment.Syntax.GetLocation(),
 697                artifactType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
 698    }
 99
 100    private static IOperation UnwrapAwait(IOperation operation)
 101    {
 49102        return operation is IAwaitOperation awaitOperation
 49103            ? awaitOperation.Operation
 49104            : operation;
 105    }
 106
 107    private static bool IsArtifactProducerOperation(IOperation? operation)
 108    {
 27109        if (operation is null)
 110        {
 0111            return false;
 112        }
 113
 27114        operation = UnwrapAwait(operation);
 115
 27116        while (operation is IConversionOperation conversionOperation)
 117        {
 0118            operation = conversionOperation.Operand;
 0119        }
 120
 27121        return operation switch
 27122        {
 14123            IInvocationOperation invocationOperation => !IsPersistenceBoundaryInvocation(invocationOperation),
 1124            IObjectCreationOperation => true,
 1125            IPropertyReferenceOperation => true,
 3126            IConditionalOperation conditionalOperation => IsArtifactProducerOperation(conditionalOperation.WhenTrue)
 3127                || IsArtifactProducerOperation(conditionalOperation.WhenFalse),
 1128            ICoalesceOperation coalesceOperation => IsArtifactProducerOperation(coalesceOperation.Value)
 1129                || IsArtifactProducerOperation(coalesceOperation.WhenNull),
 7130            _ => false
 27131        };
 132    }
 133
 134    private static bool IsPersistenceBoundaryInvocation(IInvocationOperation invocation)
 135    {
 14136        return invocation.Instance?.Type is INamedTypeSymbol receiverType
 14137            && (IsPersistenceBoundaryType(receiverType)
 14138                || receiverType.AllInterfaces.Any(IsPersistenceBoundaryType));
 139    }
 140
 141    private static bool IsPersistenceBoundaryType(INamedTypeSymbol type)
 142    {
 3143        string typeName = type.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
 3144        return PersistenceBoundaryTypeNames.Contains(typeName);
 145    }
 146
 147    private static ITypeSymbol? GetGovernanceArtifactType(ITypeSymbol? type)
 148    {
 22149        ITypeSymbol? candidate = UnwrapKnownWrapper(type);
 22150        if (candidate is null)
 151        {
 0152            return null;
 153        }
 154
 22155        string candidateName = candidate.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
 22156        return GovernanceArtifactTypeNames.Contains(candidateName) ? candidate : null;
 157    }
 158
 159    private static ITypeSymbol? UnwrapKnownWrapper(ITypeSymbol? type)
 160    {
 28161        while (type is INamedTypeSymbol { IsGenericType: true, TypeArguments.Length: 1 } namedType)
 162        {
 6163            string namespaceName = namedType.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessage
 164
 6165            if ((namespaceName == "System.Threading.Tasks" && (namedType.Name == "Task" || namedType.Name == "ValueTask"
 6166                || (namespaceName == "AsiBackbone.Core.Results" && namedType.Name == "OperationResult"))
 167            {
 6168                type = namedType.TypeArguments[0];
 6169                continue;
 170            }
 171
 172            break;
 173        }
 174
 22175        return type;
 176    }
 177
 178    private static bool IsKnownAsyncWrapper(ITypeSymbol? type)
 179    {
 1180        if (type is not INamedTypeSymbol { IsGenericType: true, TypeArguments.Length: 1 } namedType)
 181        {
 0182            return false;
 183        }
 184
 1185        string namespaceName = namedType.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageForm
 1186        return namespaceName == "System.Threading.Tasks" && (namedType.Name == "Task" || namedType.Name == "ValueTask");
 187    }
 188
 189    private static bool IsSuppressedByHostMarker(ISymbol? symbol)
 190    {
 130191        for (ISymbol? current = symbol; current is not null; current = current.ContainingSymbol)
 192        {
 131193            foreach (AttributeData attribute in current.GetAttributes())
 194            {
 1195                string? attributeName = attribute.AttributeClass?.Name;
 1196                if (attributeName is "AsiBackbonePersistenceHandled" or "AsiBackbonePersistenceHandledAttribute")
 197                {
 1198                    return true;
 199                }
 200            }
 201
 64202            if (current is INamedTypeSymbol)
 203            {
 204                break;
 205            }
 206        }
 207
 32208        return false;
 209    }
 210}