< Summary

Information
Class: NexusLabs.Needlr.Generators.RecordConstructorOverloadAnalyzer
Assembly: NexusLabs.Needlr.Generators
File(s): /_work/nick-alienware-needlr-1-1784953859-3e8997/needlr/needlr/src/NexusLabs.Needlr.Generators/RecordConstructorOverloadAnalyzer.cs
Line coverage
96%
Covered lines: 169
Uncovered lines: 6
Coverable lines: 175
Total lines: 271
Line coverage: 96.5%
Branch coverage
72%
Covered branches: 39
Total branches: 54
Branch coverage: 72.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_SupportedDiagnostics()100%11100%
Initialize(...)100%11100%
AnalyzeTypeDeclaration(...)61.76%343494.62%
ReportNonparticipatingPropertyGuards(...)100%88100%
AnalyzeParticipatingPropertyGuards(...)100%88100%
IsConstructorGuardOrAlias(...)50%4490%

File(s)

/_work/nick-alienware-needlr-1-1784953859-3e8997/needlr/needlr/src/NexusLabs.Needlr.Generators/RecordConstructorOverloadAnalyzer.cs

#LineLine coverage
 1using System.Collections.Generic;
 2using System.Collections.Immutable;
 3using System.Linq;
 4
 5using Microsoft.CodeAnalysis;
 6using Microsoft.CodeAnalysis.CSharp;
 7using Microsoft.CodeAnalysis.CSharp.Syntax;
 8using Microsoft.CodeAnalysis.Diagnostics;
 9
 10using NexusLabs.Needlr.Generators.Models;
 11using NexusLabs.Needlr.Roslyn.Shared;
 12
 13namespace NexusLabs.Needlr.Generators;
 14
 15/// <summary>
 16/// Validates record constructor-overload markers, participating properties,
 17/// constructor guards, and proposed constructor signatures.
 18/// </summary>
 19[DiagnosticAnalyzer(LanguageNames.CSharp)]
 20public sealed class RecordConstructorOverloadAnalyzer : DiagnosticAnalyzer
 21{
 22    private const string ConstructorGuardAttributeName =
 23        "ConstructorGuardAttribute";
 24
 25    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
 97226        ImmutableArray.Create(
 97227            DiagnosticDescriptors.RecordConstructorOverloadRequiresPartialType,
 97228            DiagnosticDescriptors.RecordConstructorOverloadUnsupportedTypeShape,
 97229            DiagnosticDescriptors.RecordConstructorOverloadPropertyUnsupported,
 97230            DiagnosticDescriptors.ConstructorGuardOnNonparticipatingProperty,
 97231            DiagnosticDescriptors.RecordConstructorOverloadConflictsWithGeneratedConstructor,
 97232            DiagnosticDescriptors.RecordConstructorOverloadSignatureCollision,
 97233            DiagnosticDescriptors.InvalidConstructorGuardEnumValue,
 97234            DiagnosticDescriptors.ConstructorGuardIncompatibleWithFieldType,
 97235            DiagnosticDescriptors.ConstructorGuardTypeInvalid,
 97236            DiagnosticDescriptors.ConstructorGuardMethodNameInvalid,
 97237            DiagnosticDescriptors.ConstructorGuardMethodInvalid,
 97238            DiagnosticDescriptors.ConstructorGuardMethodAmbiguous,
 97239            DiagnosticDescriptors.ConstructorGuardAliasUsageArgumentUnsupported,
 97240            DiagnosticDescriptors.ConstructorGuardForwardedArgumentIncompatible);
 41
 42    public override void Initialize(AnalysisContext context)
 43    {
 6644        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 6645        context.EnableConcurrentExecution();
 6646        context.RegisterSyntaxNodeAction(
 6647            AnalyzeTypeDeclaration,
 6648            SyntaxKind.ClassDeclaration,
 6649            SyntaxKind.StructDeclaration,
 6650            SyntaxKind.InterfaceDeclaration,
 6651            SyntaxKind.RecordDeclaration,
 6652            SyntaxKind.RecordStructDeclaration);
 6653    }
 54
 55    private static void AnalyzeTypeDeclaration(
 56        SyntaxNodeAnalysisContext context)
 57    {
 83258        var typeDeclaration = (TypeDeclarationSyntax)context.Node;
 83259        if (context.SemanticModel.GetDeclaredSymbol(typeDeclaration) is not
 83260            INamedTypeSymbol typeSymbol ||
 83261            !GeneratedConstructorEligibility.IsCanonicalDeclaration(
 83262                typeSymbol,
 83263                typeDeclaration))
 64        {
 065            return;
 66        }
 67
 83268        var properties = typeSymbol.GetMembers()
 83269            .OfType<IPropertySymbol>()
 83270            .ToArray();
 83271        ReportNonparticipatingPropertyGuards(context, typeSymbol, properties);
 72
 83273        var markedProperties = properties
 83274            .Where(
 83275                RecordConstructorOverloadDiscoveryHelper.HasMarker)
 83276            .OrderBy(
 83277                property =>
 078                    property.Locations.FirstOrDefault()?.SourceTree?.FilePath ??
 079                    string.Empty,
 83280                System.StringComparer.Ordinal)
 83281            .ThenBy(
 83282                property =>
 083                    property.Locations.FirstOrDefault()?.SourceSpan.Start ?? 0)
 83284            .ToArray();
 83285        if (markedProperties.Length == 0)
 79186            return;
 87
 4188        var typeLocation = typeDeclaration.Identifier.GetLocation();
 4189        if (RecordConstructorOverloadDiscoveryHelper
 4190            .HasFieldBasedGeneratedConstructorTrigger(typeSymbol))
 91        {
 492            context.ReportDiagnostic(Diagnostic.Create(
 493                DiagnosticDescriptors.RecordConstructorOverloadConflictsWithGeneratedConstructor,
 494                typeLocation,
 495                typeSymbol.Name));
 496            return;
 97        }
 98
 3799        var typeReason =
 37100            RecordConstructorOverloadDiscoveryHelper
 37101                .GetTypeIneligibilityReason(typeSymbol);
 37102        if (typeReason is not null)
 103        {
 12104            context.ReportDiagnostic(Diagnostic.Create(
 12105                DiagnosticDescriptors.RecordConstructorOverloadUnsupportedTypeShape,
 12106                typeLocation,
 12107                typeSymbol.Name,
 12108                typeReason));
 12109            return;
 110        }
 111
 25112        if (!GeneratedConstructorEligibility.IsDeclaredPartial(typeSymbol))
 113        {
 2114            context.ReportDiagnostic(Diagnostic.Create(
 2115                DiagnosticDescriptors.RecordConstructorOverloadRequiresPartialType,
 2116                typeLocation,
 2117                typeSymbol.Name));
 2118            return;
 119        }
 120
 23121        var primaryDeclaration =
 23122            RecordConstructorOverloadDiscoveryHelper
 23123                .GetPrimaryRecordDeclaration(typeSymbol);
 23124        if (primaryDeclaration is null)
 0125            return;
 126
 23127        var hasInvalidProperty = false;
 92128        foreach (var property in markedProperties)
 129        {
 23130            var propertyReason =
 23131                RecordConstructorOverloadDiscoveryHelper
 23132                    .GetPropertyIneligibilityReason(
 23133                        typeSymbol,
 23134                        property,
 23135                        primaryDeclaration);
 23136            if (propertyReason is not null)
 137            {
 12138                var marker =
 12139                    RecordConstructorOverloadDiscoveryHelper
 12140                        .GetMarkerAttribute(property);
 12141                context.ReportDiagnostic(Diagnostic.Create(
 12142                    DiagnosticDescriptors.RecordConstructorOverloadPropertyUnsupported,
 12143                    marker is null
 12144                        ? property.Locations.FirstOrDefault() ?? Location.None
 12145                        : ConstructorGuardAnalysisHelper.GetAttributeLocation(
 12146                            context,
 12147                            marker),
 12148                    property.Name,
 12149                    propertyReason));
 12150                hasInvalidProperty = true;
 12151                continue;
 152            }
 153
 11154            AnalyzeParticipatingPropertyGuards(
 11155                context,
 11156                typeSymbol,
 11157                property);
 158        }
 159
 23160        if (hasInvalidProperty)
 12161            return;
 162
 11163        if (RecordConstructorOverloadDiscoveryHelper.TryGetSignatureCollision(
 11164            typeSymbol,
 11165            context.Compilation,
 11166            out var collisionDisplay))
 167        {
 6168            context.ReportDiagnostic(Diagnostic.Create(
 6169                DiagnosticDescriptors.RecordConstructorOverloadSignatureCollision,
 6170                typeLocation,
 6171                typeSymbol.Name,
 6172                collisionDisplay));
 173        }
 11174    }
 175
 176    private static void ReportNonparticipatingPropertyGuards(
 177        SyntaxNodeAnalysisContext context,
 178        INamedTypeSymbol typeSymbol,
 179        IReadOnlyList<IPropertySymbol> properties)
 180    {
 3790181        foreach (var property in properties)
 182        {
 1063183            if (RecordConstructorOverloadDiscoveryHelper.HasMarker(property))
 184                continue;
 185
 2048186            foreach (var attribute in property.GetAttributes())
 187            {
 2188                if (!IsConstructorGuardOrAlias(attribute))
 189                    continue;
 190
 2191                context.ReportDiagnostic(Diagnostic.Create(
 2192                    DiagnosticDescriptors.ConstructorGuardOnNonparticipatingProperty,
 2193                    ConstructorGuardAnalysisHelper.GetAttributeLocation(
 2194                        context,
 2195                        attribute),
 2196                    property.Name));
 197            }
 198        }
 832199    }
 200
 201    private static void AnalyzeParticipatingPropertyGuards(
 202        SyntaxNodeAnalysisContext context,
 203        INamedTypeSymbol containingType,
 204        IPropertySymbol property)
 205    {
 52206        foreach (var attribute in property.GetAttributes())
 207        {
 15208            if (attribute.AttributeClass is not { } attributeClass)
 209                continue;
 210
 211            ConstructorGuardOccurrence occurrence;
 15212            if (GeneratedConstructorEligibility.IsNeedlrGeneratorsAttribute(
 15213                attributeClass,
 15214                ConstructorGuardAttributeName))
 215            {
 3216                occurrence =
 3217                    ConstructorGuardAnalysisHelper.BuildDirectGuardOccurrence(
 3218                        property,
 3219                        property.Type,
 3220                        "property",
 3221                        attribute,
 3222                        null);
 223            }
 12224            else if (ConstructorGuardAnalysisHelper.TryGetGuardDefinition(
 12225                attributeClass,
 12226                out var guardType,
 12227                out var methodName,
 12228                out var methodNameExplicit))
 229            {
 1230                occurrence =
 1231                    ConstructorGuardAnalysisHelper.BuildAliasOccurrence(
 1232                        property,
 1233                        property.Type,
 1234                        "property",
 1235                        attribute,
 1236                        null,
 1237                        guardType,
 1238                        methodName,
 1239                        methodNameExplicit,
 1240                        attributeClass.DeclaringSyntaxReferences.Length > 0);
 241            }
 242            else
 243            {
 244                continue;
 245            }
 246
 4247            ConstructorGuardAnalysisHelper.AnalyzePositiveGuardOccurrence(
 4248                context,
 4249                containingType,
 4250                occurrence,
 4251                ConstructorGuardAnalysisHelper.GetAttributeLocation(
 4252                    context,
 4253                    attribute));
 254        }
 11255    }
 256
 257    private static bool IsConstructorGuardOrAlias(AttributeData attribute)
 258    {
 2259        if (attribute.AttributeClass is not { } attributeClass)
 0260            return false;
 261
 2262        return GeneratedConstructorEligibility.IsNeedlrGeneratorsAttribute(
 2263                attributeClass,
 2264                ConstructorGuardAttributeName) ||
 2265            ConstructorGuardAnalysisHelper.TryGetGuardDefinition(
 2266                attributeClass,
 2267                out _,
 2268                out _,
 2269                out _);
 270    }
 271}