< Summary

Information
Class: AsiBackbone.Analyzers.LocalDevelopmentSigningProductionAnalyzer
Assembly: AsiBackbone.Analyzers
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Analyzers/LocalDevelopmentSigningProductionAnalyzer.cs
Line coverage
83%
Covered lines: 95
Uncovered lines: 19
Coverable lines: 114
Total lines: 256
Line coverage: 83.3%
Branch coverage
67%
Covered branches: 70
Total branches: 104
Branch coverage: 67.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Analyzers/LocalDevelopmentSigningProductionAnalyzer.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 LocalDevelopmentSigningProductionAnalyzer : DiagnosticAnalyzer
 10{
 11    public const string DiagnosticId = "ASIB002";
 12
 13    private const string LocalDevelopmentNamespace = "AsiBackbone.Signing.LocalDevelopment";
 14
 215    private static readonly DiagnosticDescriptor Rule = new(
 216        DiagnosticId,
 217        "Do not wire local-development signing in production branches",
 218        "Local-development signing type '{0}' is used inside a production environment branch; use a host-owned productio
 219        "AsiBackbone.ProductionSafety",
 220        DiagnosticSeverity.Warning,
 221        isEnabledByDefault: true,
 222        description: "LocalDevelopment signing providers generate in-process keys for tests, samples, and local proof pa
 23
 1224    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
 25
 26    public override void Initialize(AnalysisContext context)
 27    {
 1228        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 1229        context.EnableConcurrentExecution();
 1230        context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation);
 1231        context.RegisterOperationAction(AnalyzeObjectCreation, OperationKind.ObjectCreation);
 1232    }
 33
 34    private static void AnalyzeInvocation(OperationAnalysisContext context)
 35    {
 2236        var invocation = (IInvocationOperation)context.Operation;
 37
 2238        if (IsSuppressedByHostMarker(context.ContainingSymbol)
 2239            || !IsInsideProductionBranch(invocation)
 2240            || IsNestedInsideInvocationThatAlreadyReferencesLocalDevelopment(invocation))
 41        {
 1042            return;
 43        }
 44
 1245        ITypeSymbol? localDevelopmentType = FindReferencedLocalDevelopmentType(invocation);
 1246        if (localDevelopmentType is null)
 47        {
 648            return;
 49        }
 50
 651        context.ReportDiagnostic(
 652            Diagnostic.Create(
 653                Rule,
 654                invocation.Syntax.GetLocation(),
 655                localDevelopmentType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
 656    }
 57
 58    private static void AnalyzeObjectCreation(OperationAnalysisContext context)
 59    {
 2860        var objectCreation = (IObjectCreationOperation)context.Operation;
 61
 2862        if (IsSuppressedByHostMarker(context.ContainingSymbol) || !IsInsideProductionBranch(objectCreation))
 63        {
 2664            return;
 65        }
 66
 267        ITypeSymbol? localDevelopmentType = FindLocalDevelopmentType(objectCreation.Type);
 268        if (localDevelopmentType is null)
 69        {
 070            return;
 71        }
 72
 273        context.ReportDiagnostic(
 274            Diagnostic.Create(
 275                Rule,
 276                objectCreation.Syntax.GetLocation(),
 277                localDevelopmentType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
 278    }
 79
 80    private static bool IsNestedInsideInvocationThatAlreadyReferencesLocalDevelopment(IInvocationOperation invocation)
 81    {
 1482        return invocation.Parent is IArgumentOperation { Parent: IInvocationOperation parentInvocation }
 1483            && FindReferencedLocalDevelopmentType(parentInvocation) is not null;
 84    }
 85
 86    private static ITypeSymbol? FindReferencedLocalDevelopmentType(IInvocationOperation invocation)
 87    {
 1488        ITypeSymbol? containingType = FindLocalDevelopmentType(invocation.TargetMethod.ContainingType);
 1489        if (containingType is not null)
 90        {
 091            return containingType;
 92        }
 93
 3694        foreach (ITypeSymbol typeArgument in invocation.TargetMethod.TypeArguments)
 95        {
 896            ITypeSymbol? localDevelopmentType = FindLocalDevelopmentType(typeArgument);
 897            if (localDevelopmentType is not null)
 98            {
 899                return localDevelopmentType;
 100            }
 101        }
 102
 24103        foreach (IArgumentOperation argument in invocation.Arguments)
 104        {
 6105            ITypeSymbol? localDevelopmentType = FindLocalDevelopmentType(argument.Value.Type);
 6106            if (localDevelopmentType is not null)
 107            {
 0108                return localDevelopmentType;
 109            }
 110        }
 111
 6112        return FindLocalDevelopmentType(invocation.Type);
 113    }
 114
 115    private static ITypeSymbol? FindLocalDevelopmentType(ITypeSymbol? type)
 116    {
 36117        if (type is null)
 118        {
 0119            return null;
 120        }
 121
 36122        if (type is INamedTypeSymbol namedType && namedType.IsGenericType)
 123        {
 0124            foreach (ITypeSymbol typeArgument in namedType.TypeArguments)
 125            {
 0126                ITypeSymbol? localDevelopmentType = FindLocalDevelopmentType(typeArgument);
 0127                if (localDevelopmentType is not null)
 128                {
 0129                    return localDevelopmentType;
 130                }
 131            }
 132        }
 133
 36134        string namespaceName = type.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) ?
 36135        return namespaceName.Equals(LocalDevelopmentNamespace, StringComparison.Ordinal)
 36136            ? type
 36137            : null;
 138    }
 139
 140    private static bool IsInsideProductionBranch(IOperation operation)
 141    {
 292142        for (IOperation? current = operation.Parent; current is not null; current = current.Parent)
 143        {
 118144            if (current is IConditionalOperation conditionalOperation
 118145                && IsProductionLikeCondition(conditionalOperation.Condition))
 146            {
 16147                return true;
 148            }
 149        }
 150
 28151        return false;
 152    }
 153
 154    private static bool IsProductionLikeCondition(IOperation operation)
 155    {
 20156        operation = Unwrap(operation);
 157
 20158        return operation switch
 20159        {
 18160            IInvocationOperation invocationOperation => IsProductionInvocation(invocationOperation)
 18161                || InvocationComparesEnvironmentNameToProduction(invocationOperation),
 2162            IBinaryOperation binaryOperation => BinaryComparesEnvironmentNameToProduction(binaryOperation),
 0163            _ => false
 20164        };
 165    }
 166
 167    private static bool IsProductionInvocation(IInvocationOperation invocation)
 168    {
 18169        IMethodSymbol method = invocation.TargetMethod.ReducedFrom ?? invocation.TargetMethod;
 18170        string namespaceName = method.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
 171
 18172        return method.Name.Equals("IsProduction", StringComparison.Ordinal)
 18173            && namespaceName.Equals("Microsoft.Extensions.Hosting", StringComparison.Ordinal);
 174    }
 175
 176    private static bool InvocationComparesEnvironmentNameToProduction(IInvocationOperation invocation)
 177    {
 4178        if (!invocation.TargetMethod.Name.Equals("Equals", StringComparison.Ordinal))
 179        {
 4180            return false;
 181        }
 182
 0183        bool hasEnvironmentName = IsEnvironmentNameReference(invocation.Instance);
 0184        bool hasProductionConstant = false;
 185
 0186        foreach (IArgumentOperation argument in invocation.Arguments)
 187        {
 0188            IOperation value = Unwrap(argument.Value);
 0189            hasEnvironmentName = hasEnvironmentName || IsEnvironmentNameReference(value);
 0190            hasProductionConstant = hasProductionConstant || IsProductionConstant(value);
 191        }
 192
 0193        return hasEnvironmentName && hasProductionConstant;
 194    }
 195
 196    private static bool BinaryComparesEnvironmentNameToProduction(IBinaryOperation binaryOperation)
 197    {
 2198        if (binaryOperation.OperatorKind != BinaryOperatorKind.Equals)
 199        {
 0200            return false;
 201        }
 202
 2203        IOperation left = Unwrap(binaryOperation.LeftOperand);
 2204        IOperation right = Unwrap(binaryOperation.RightOperand);
 205
 2206        return (IsEnvironmentNameReference(left) && IsProductionConstant(right))
 2207            || (IsEnvironmentNameReference(right) && IsProductionConstant(left));
 208    }
 209
 210    private static bool IsEnvironmentNameReference(IOperation? operation)
 211    {
 2212        operation = operation is null ? null : Unwrap(operation);
 213
 2214        return operation is IPropertyReferenceOperation propertyReference
 2215            && propertyReference.Property.Name.Equals("EnvironmentName", StringComparison.Ordinal);
 216    }
 217
 218    private static bool IsProductionConstant(IOperation operation)
 219    {
 2220        return operation.ConstantValue.HasValue
 2221            && operation.ConstantValue.Value is string value
 2222            && value.Equals("Production", StringComparison.OrdinalIgnoreCase);
 223    }
 224
 225    private static IOperation Unwrap(IOperation operation)
 226    {
 26227        while (operation is IConversionOperation conversionOperation)
 228        {
 0229            operation = conversionOperation.Operand;
 0230        }
 231
 26232        return operation;
 233    }
 234
 235    private static bool IsSuppressedByHostMarker(ISymbol? symbol)
 236    {
 164237        for (ISymbol? current = symbol; current is not null; current = current.ContainingSymbol)
 238        {
 194239            foreach (AttributeData attribute in current.GetAttributes())
 240            {
 18241                string? attributeName = attribute.AttributeClass?.Name;
 18242                if (attributeName is "AsiBackboneProductionConfigurationReviewed" or "AsiBackboneProductionConfiguration
 243                {
 6244                    return true;
 245                }
 246            }
 247
 76248            if (current is INamedTypeSymbol)
 249            {
 250                break;
 251            }
 252        }
 253
 44254        return false;
 255    }
 256}