< Summary

Information
Class: NexusLabs.Needlr.Avalonia.AvaloniaDesignTimeConstructorGenerator
Assembly: NexusLabs.Needlr.Avalonia
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Avalonia/AvaloniaDesignTimeConstructorGenerator.cs
Line coverage
97%
Covered lines: 135
Uncovered lines: 3
Coverable lines: 138
Total lines: 204
Line coverage: 97.8%
Branch coverage
72%
Covered branches: 29
Total branches: 40
Branch coverage: 72.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Initialize(...)90%101098.27%
GetClassInfo(...)64.28%282897.05%
GenerateSource(...)100%22100%
.ctor(...)100%11100%
get_ClassName()100%11100%
get_NamespaceName()100%11100%
get_Modifiers()100%210%
get_IsPartial()100%11100%
get_HasPrimaryConstructor()100%11100%
get_HasParameterlessCtor()100%11100%
get_HasParameterizedCtor()100%11100%
get_Location()100%11100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Avalonia/AvaloniaDesignTimeConstructorGenerator.cs

#LineLine coverage
 1using System.Collections.Immutable;
 2using System.Linq;
 3using System.Text;
 4
 5using Microsoft.CodeAnalysis;
 6using Microsoft.CodeAnalysis.CSharp;
 7using Microsoft.CodeAnalysis.CSharp.Syntax;
 8using Microsoft.CodeAnalysis.Text;
 9
 10using NexusLabs.Needlr.Avalonia.Diagnostics;
 11
 12namespace NexusLabs.Needlr.Avalonia;
 13
 14/// <summary>
 15/// Source generator that emits design-time parameterless constructors for Avalonia
 16/// controls decorated with <c>[GenerateAvaloniaDesignTimeConstructor]</c>.
 17/// </summary>
 18[Generator(LanguageNames.CSharp)]
 19public sealed class AvaloniaDesignTimeConstructorGenerator : IIncrementalGenerator
 20{
 21    private const string AttributeFullName =
 22        "NexusLabs.Needlr.Avalonia.GenerateAvaloniaDesignTimeConstructorAttribute";
 23
 24    public void Initialize(IncrementalGeneratorInitializationContext context)
 25    {
 1126        var candidates = context.SyntaxProvider.ForAttributeWithMetadataName(
 1127            AttributeFullName,
 1128            predicate: static (node, _) => node is ClassDeclarationSyntax,
 2229            transform: static (ctx, _) => GetClassInfo(ctx));
 30
 1131        context.RegisterSourceOutput(candidates, static (spc, info) =>
 1132        {
 1133            if (info == null)
 034                return;
 1135
 1136            var classInfo = info.Value;
 1137
 1138            // Validate: must be partial
 1139            if (!classInfo.IsPartial)
 1140            {
 241                spc.ReportDiagnostic(Diagnostic.Create(
 242                    AvaloniaDiagnosticDescriptors.ClassMustBePartial,
 243                    classInfo.Location,
 244                    classInfo.ClassName));
 245                return;
 1146            }
 1147
 1148            // Validate: must not use a primary constructor
 949            if (classInfo.HasPrimaryConstructor)
 1150            {
 251                spc.ReportDiagnostic(Diagnostic.Create(
 252                    AvaloniaDiagnosticDescriptors.PrimaryConstructorNotSupported,
 253                    classInfo.Location,
 254                    classInfo.ClassName));
 255                return;
 1156            }
 1157
 1158            // Validate: must not already have a parameterless constructor
 759            if (classInfo.HasParameterlessCtor)
 1160            {
 161                spc.ReportDiagnostic(Diagnostic.Create(
 162                    AvaloniaDiagnosticDescriptors.AlreadyHasParameterlessCtor,
 163                    classInfo.Location,
 164                    classInfo.ClassName));
 165                return;
 1166            }
 1167
 1168            // Warn: should have at least one constructor with parameters
 669            if (!classInfo.HasParameterizedCtor)
 1170            {
 171                spc.ReportDiagnostic(Diagnostic.Create(
 172                    AvaloniaDiagnosticDescriptors.NoParameterizedCtor,
 173                    classInfo.Location,
 174                    classInfo.ClassName));
 175                return;
 1176            }
 1177
 1178            // Emit the design-time constructor
 579            var source = GenerateSource(classInfo);
 580            spc.AddSource(
 581                $"{classInfo.ClassName}.DesignTimeCtor.g.cs",
 582                SourceText.From(source, Encoding.UTF8));
 1683        });
 1184    }
 85
 86    private static DesignTimeCtorClassInfo? GetClassInfo(
 87        GeneratorAttributeSyntaxContext ctx)
 88    {
 1189        if (ctx.TargetSymbol is not INamedTypeSymbol typeSymbol)
 090            return null;
 91
 1192        var classDecl = ctx.TargetNode as ClassDeclarationSyntax;
 1193        var isPartial = classDecl?.Modifiers.Any(SyntaxKind.PartialKeyword) ?? false;
 94
 1195        var hasPrimaryConstructor = classDecl?.ParameterList != null;
 96
 1197        var hasParameterlessCtor = typeSymbol.InstanceConstructors
 2298            .Any(c => !c.IsStatic &&
 2299                      c.DeclaredAccessibility == Accessibility.Public &&
 22100                      c.Parameters.Length == 0 &&
 22101                      !c.IsImplicitlyDeclared);
 102
 11103        var hasParameterizedCtor = typeSymbol.InstanceConstructors
 23104            .Any(c => !c.IsStatic &&
 23105                      c.DeclaredAccessibility == Accessibility.Public &&
 23106                      c.Parameters.Length > 0);
 107
 11108        var namespaceName = typeSymbol.ContainingNamespace?.IsGlobalNamespace == true
 11109            ? null
 11110            : typeSymbol.ContainingNamespace?.ToDisplayString();
 111
 112        // Build the class declaration modifiers (sealed, internal, etc.)
 11113        var modifiers = "";
 11114        if (classDecl != null)
 115        {
 11116            var mods = classDecl.Modifiers
 20117                .Where(m => m.IsKind(SyntaxKind.SealedKeyword) ||
 20118                            m.IsKind(SyntaxKind.InternalKeyword) ||
 20119                            m.IsKind(SyntaxKind.PublicKeyword))
 22120                .Select(m => m.Text);
 11121            modifiers = string.Join(" ", mods);
 122        }
 123
 11124        return new DesignTimeCtorClassInfo(
 11125            className: typeSymbol.Name,
 11126            namespaceName: namespaceName,
 11127            modifiers: modifiers,
 11128            isPartial: isPartial,
 11129            hasPrimaryConstructor: hasPrimaryConstructor,
 11130            hasParameterlessCtor: hasParameterlessCtor,
 11131            hasParameterizedCtor: hasParameterizedCtor,
 11132            location: ctx.TargetNode.GetLocation());
 133    }
 134
 135    private static string GenerateSource(DesignTimeCtorClassInfo info)
 136    {
 5137        var sb = new StringBuilder();
 5138        sb.AppendLine("// <auto-generated/>");
 5139        sb.AppendLine("#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting c
 5140        sb.AppendLine();
 141
 5142        if (info.NamespaceName != null)
 143        {
 5144            sb.AppendLine($"namespace {info.NamespaceName};");
 5145            sb.AppendLine();
 146        }
 147
 5148        sb.AppendLine($"partial class {info.ClassName}");
 5149        sb.AppendLine("{");
 5150        sb.AppendLine("    /// <summary>");
 5151        sb.AppendLine("    /// Design-time constructor for Avalonia's XAML previewer.");
 5152        sb.AppendLine("    /// At runtime, the DI container uses the richer constructor.");
 5153        sb.AppendLine("    /// </summary>");
 5154        sb.AppendLine($"    public {info.ClassName}()");
 5155        sb.AppendLine("    {");
 5156        sb.AppendLine("        if (!global::Avalonia.Controls.Design.IsDesignMode)");
 5157        sb.AppendLine("        {");
 5158        sb.AppendLine($"            throw new global::System.InvalidOperationException(");
 5159        sb.AppendLine($"                \"The parameterless constructor on '{info.ClassName}' exists only for \" +");
 5160        sb.AppendLine($"                \"Avalonia design-time support. At runtime, resolve this type from \" +");
 5161        sb.AppendLine($"                \"the DI container to use the constructor with dependencies.\");");
 5162        sb.AppendLine("        }");
 5163        sb.AppendLine();
 5164        sb.AppendLine("        InitializeComponent();");
 5165        sb.AppendLine("    }");
 5166        sb.AppendLine("}");
 5167        sb.AppendLine();
 5168        sb.AppendLine("#pragma warning restore CS8618");
 169
 5170        return sb.ToString();
 171    }
 172
 173    private readonly struct DesignTimeCtorClassInfo
 174    {
 175        public DesignTimeCtorClassInfo(
 176            string className,
 177            string? namespaceName,
 178            string modifiers,
 179            bool isPartial,
 180            bool hasPrimaryConstructor,
 181            bool hasParameterlessCtor,
 182            bool hasParameterizedCtor,
 183            Location location)
 184        {
 11185            ClassName = className;
 11186            NamespaceName = namespaceName;
 11187            Modifiers = modifiers;
 11188            IsPartial = isPartial;
 11189            HasPrimaryConstructor = hasPrimaryConstructor;
 11190            HasParameterlessCtor = hasParameterlessCtor;
 11191            HasParameterizedCtor = hasParameterizedCtor;
 11192            Location = location;
 11193        }
 194
 26195        public string ClassName { get; }
 10196        public string? NamespaceName { get; }
 0197        public string Modifiers { get; }
 11198        public bool IsPartial { get; }
 9199        public bool HasPrimaryConstructor { get; }
 7200        public bool HasParameterlessCtor { get; }
 6201        public bool HasParameterizedCtor { get; }
 6202        public Location Location { get; }
 203    }
 204}