< Summary

Information
Class: NexusLabs.Needlr.Generators.RecordConstructorOverloadAnalyzer
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/RecordConstructorOverloadAnalyzer.cs
Line coverage
98%
Covered lines: 172
Uncovered lines: 3
Coverable lines: 175
Total lines: 271
Line coverage: 98.2%
Branch coverage
79%
Covered branches: 43
Total branches: 54
Branch coverage: 79.6%
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(...)73.52%343497.84%
ReportNonparticipatingPropertyGuards(...)100%88100%
AnalyzeParticipatingPropertyGuards(...)100%88100%
IsConstructorGuardOrAlias(...)50%4490%

File(s)

/home/runner/work/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 =>
 143226        ImmutableArray.Create(
 143227            DiagnosticDescriptors.RecordConstructorOverloadRequiresPartialType,
 143228            DiagnosticDescriptors.RecordConstructorOverloadUnsupportedTypeShape,
 143229            DiagnosticDescriptors.RecordConstructorOverloadPropertyUnsupported,
 143230            DiagnosticDescriptors.ConstructorGuardOnNonparticipatingProperty,
 143231            DiagnosticDescriptors.RecordConstructorOverloadConflictsWithGeneratedConstructor,
 143232            DiagnosticDescriptors.RecordConstructorOverloadSignatureCollision,
 143233            DiagnosticDescriptors.InvalidConstructorGuardEnumValue,
 143234            DiagnosticDescriptors.ConstructorGuardIncompatibleWithFieldType,
 143235            DiagnosticDescriptors.ConstructorGuardTypeInvalid,
 143236            DiagnosticDescriptors.ConstructorGuardMethodNameInvalid,
 143237            DiagnosticDescriptors.ConstructorGuardMethodInvalid,
 143238            DiagnosticDescriptors.ConstructorGuardMethodAmbiguous,
 143239            DiagnosticDescriptors.ConstructorGuardAliasUsageArgumentUnsupported,
 143240            DiagnosticDescriptors.ConstructorGuardForwardedArgumentIncompatible);
 41
 42    public override void Initialize(AnalysisContext context)
 43    {
 10744        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 10745        context.EnableConcurrentExecution();
 10746        context.RegisterSyntaxNodeAction(
 10747            AnalyzeTypeDeclaration,
 10748            SyntaxKind.ClassDeclaration,
 10749            SyntaxKind.StructDeclaration,
 10750            SyntaxKind.InterfaceDeclaration,
 10751            SyntaxKind.RecordDeclaration,
 10752            SyntaxKind.RecordStructDeclaration);
 10753    }
 54
 55    private static void AnalyzeTypeDeclaration(
 56        SyntaxNodeAnalysisContext context)
 57    {
 132258        var typeDeclaration = (TypeDeclarationSyntax)context.Node;
 132259        if (context.SemanticModel.GetDeclaredSymbol(typeDeclaration) is not
 132260            INamedTypeSymbol typeSymbol ||
 132261            !GeneratedConstructorEligibility.IsCanonicalDeclaration(
 132262                typeSymbol,
 132263                typeDeclaration))
 64        {
 065            return;
 66        }
 67
 132268        var properties = typeSymbol.GetMembers()
 132269            .OfType<IPropertySymbol>()
 132270            .ToArray();
 132271        ReportNonparticipatingPropertyGuards(context, typeSymbol, properties);
 72
 132273        var markedProperties = properties
 132274            .Where(
 132275                RecordConstructorOverloadDiscoveryHelper.HasMarker)
 132276            .OrderBy(
 132277                property =>
 378                    property.Locations.FirstOrDefault()?.SourceTree?.FilePath ??
 379                    string.Empty,
 132280                System.StringComparer.Ordinal)
 132281            .ThenBy(
 132282                property =>
 383                    property.Locations.FirstOrDefault()?.SourceSpan.Start ?? 0)
 132284            .ToArray();
 132285        if (markedProperties.Length == 0)
 125686            return;
 87
 6688        var typeLocation = typeDeclaration.Identifier.GetLocation();
 6689        if (RecordConstructorOverloadDiscoveryHelper
 6690            .HasFieldBasedGeneratedConstructorTrigger(typeSymbol))
 91        {
 492            context.ReportDiagnostic(Diagnostic.Create(
 493                DiagnosticDescriptors.RecordConstructorOverloadConflictsWithGeneratedConstructor,
 494                typeLocation,
 495                typeSymbol.Name));
 496            return;
 97        }
 98
 6299        var typeReason =
 62100            RecordConstructorOverloadDiscoveryHelper
 62101                .GetTypeIneligibilityReason(typeSymbol);
 62102        if (typeReason is not null)
 103        {
 14104            context.ReportDiagnostic(Diagnostic.Create(
 14105                DiagnosticDescriptors.RecordConstructorOverloadUnsupportedTypeShape,
 14106                typeLocation,
 14107                typeSymbol.Name,
 14108                typeReason));
 14109            return;
 110        }
 111
 48112        if (!GeneratedConstructorEligibility.IsDeclaredPartial(typeSymbol))
 113        {
 2114            context.ReportDiagnostic(Diagnostic.Create(
 2115                DiagnosticDescriptors.RecordConstructorOverloadRequiresPartialType,
 2116                typeLocation,
 2117                typeSymbol.Name));
 2118            return;
 119        }
 120
 46121        var primaryDeclaration =
 46122            RecordConstructorOverloadDiscoveryHelper
 46123                .GetPrimaryRecordDeclaration(typeSymbol);
 46124        if (primaryDeclaration is null)
 0125            return;
 126
 46127        var hasInvalidProperty = false;
 188128        foreach (var property in markedProperties)
 129        {
 48130            var propertyReason =
 48131                RecordConstructorOverloadDiscoveryHelper
 48132                    .GetPropertyIneligibilityReason(
 48133                        typeSymbol,
 48134                        property,
 48135                        primaryDeclaration);
 48136            if (propertyReason is not null)
 137            {
 24138                var marker =
 24139                    RecordConstructorOverloadDiscoveryHelper
 24140                        .GetMarkerAttribute(property);
 24141                context.ReportDiagnostic(Diagnostic.Create(
 24142                    DiagnosticDescriptors.RecordConstructorOverloadPropertyUnsupported,
 24143                    marker is null
 24144                        ? property.Locations.FirstOrDefault() ?? Location.None
 24145                        : ConstructorGuardAnalysisHelper.GetAttributeLocation(
 24146                            context,
 24147                            marker),
 24148                    property.Name,
 24149                    propertyReason));
 24150                hasInvalidProperty = true;
 24151                continue;
 152            }
 153
 24154            AnalyzeParticipatingPropertyGuards(
 24155                context,
 24156                typeSymbol,
 24157                property);
 158        }
 159
 46160        if (hasInvalidProperty)
 24161            return;
 162
 22163        if (RecordConstructorOverloadDiscoveryHelper.TryGetSignatureCollision(
 22164            typeSymbol,
 22165            context.Compilation,
 22166            out var collisionDisplay))
 167        {
 10168            context.ReportDiagnostic(Diagnostic.Create(
 10169                DiagnosticDescriptors.RecordConstructorOverloadSignatureCollision,
 10170                typeLocation,
 10171                typeSymbol.Name,
 10172                collisionDisplay));
 173        }
 22174    }
 175
 176    private static void ReportNonparticipatingPropertyGuards(
 177        SyntaxNodeAnalysisContext context,
 178        INamedTypeSymbol typeSymbol,
 179        IReadOnlyList<IPropertySymbol> properties)
 180    {
 6024181        foreach (var property in properties)
 182        {
 1690183            if (RecordConstructorOverloadDiscoveryHelper.HasMarker(property))
 184                continue;
 185
 3248186            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        }
 1322199    }
 200
 201    private static void AnalyzeParticipatingPropertyGuards(
 202        SyntaxNodeAnalysisContext context,
 203        INamedTypeSymbol containingType,
 204        IPropertySymbol property)
 205    {
 104206        foreach (var attribute in property.GetAttributes())
 207        {
 28208            if (attribute.AttributeClass is not { } attributeClass)
 209                continue;
 210
 211            ConstructorGuardOccurrence occurrence;
 28212            if (GeneratedConstructorEligibility.IsNeedlrGeneratorsAttribute(
 28213                attributeClass,
 28214                ConstructorGuardAttributeName))
 215            {
 3216                occurrence =
 3217                    ConstructorGuardAnalysisHelper.BuildDirectGuardOccurrence(
 3218                        property,
 3219                        property.Type,
 3220                        "property",
 3221                        attribute,
 3222                        null);
 223            }
 25224            else if (ConstructorGuardAnalysisHelper.TryGetGuardDefinition(
 25225                attributeClass,
 25226                out var guardType,
 25227                out var methodName,
 25228                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        }
 24255    }
 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}