< Summary

Information
Class: NexusLabs.Needlr.Generators.TypedConstantRenderer
Assembly: NexusLabs.Needlr.Generators
File(s): /_work/nick-alienware-needlr-1-1784953859-3e8997/needlr/needlr/src/NexusLabs.Needlr.Generators/TypedConstantRenderer.cs
Line coverage
92%
Covered lines: 47
Uncovered lines: 4
Coverable lines: 51
Total lines: 180
Line coverage: 92.1%
Branch coverage
89%
Covered branches: 34
Total branches: 38
Branch coverage: 89.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
TryRender(...)100%66100%
TryRenderPrimitive(...)95.83%2424100%
TryRenderEnum(...)66.66%6684.61%
TryRenderType(...)50%2260%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Globalization;
 3using System.Linq;
 4
 5using Microsoft.CodeAnalysis;
 6using Microsoft.CodeAnalysis.CSharp;
 7
 8namespace NexusLabs.Needlr.Generators;
 9
 10/// <summary>
 11/// Renders a Roslyn <see cref="TypedConstant"/> -- an attribute constructor argument
 12/// value already validated by the compiler -- into the exact C# literal or expression
 13/// source text needed to forward it as a positional argument on a generated guard
 14/// method call.
 15/// </summary>
 16/// <remarks>
 17/// <para>
 18/// Only scalar constant shapes that can be rendered unambiguously and safely are
 19/// supported: <see langword="null"/>, <see langword="bool"/>, every signed and
 20/// unsigned integral primitive (with the C# suffix required for the literal to
 21/// round-trip to the same runtime type, e.g. <c>u</c>/<c>L</c>/<c>UL</c>),
 22/// <see langword="char"/>, <see langword="string"/>, enum members (rendered as a
 23/// fully qualified member reference, or a fully qualified cast expression when no
 24/// declared member matches the value exactly -- e.g. a combined <c>[Flags]</c> value),
 25/// and <see cref="System.Type"/> (rendered as <c>typeof(global::...)</c>, including
 26/// open generic type definitions such as <c>typeof(List&lt;&gt;)</c>).
 27/// </para>
 28/// <para>
 29/// Arrays/params and floating-point (<see langword="float"/>/<see langword="double"/>)
 30/// constants are deliberately unsupported in this slice: rendering an array positional
 31/// argument as a single forwarded literal is inherently ambiguous, and floating-point
 32/// literals can silently lose round-trip precision. A dedicated analyzer is
 33/// responsible for diagnosing an alias attribute usage whose positional arguments
 34/// include one of these unsupported shapes; this renderer only ever reports the
 35/// unsupported result explicitly via its <see langword="bool"/> return value -- it
 36/// never invents a success-shaped fallback string for a shape it cannot safely render.
 37/// </para>
 38/// <para>
 39/// This type never accepts or echoes raw source text: every rendered literal is
 40/// derived solely from the compiler-validated <see cref="TypedConstant"/> value, never
 41/// from unparsed attribute-argument syntax.
 42/// </para>
 43/// </remarks>
 44internal static class TypedConstantRenderer
 45{
 46    /// <summary>
 47    /// Attempts to render <paramref name="constant"/> as a C# literal or expression
 48    /// suitable for splicing directly into generated source. Returns
 49    /// <see langword="false"/> -- with <paramref name="renderedLiteral"/> set to
 50    /// <see cref="string.Empty"/> -- for any constant shape this renderer does not
 51    /// support (arrays/params, floating-point primitives, or any other unrecognized
 52    /// shape), rather than falling back to a best-effort rendering that could produce
 53    /// unsafe or ambiguous source.
 54    /// </summary>
 55    internal static bool TryRender(TypedConstant constant, out string renderedLiteral)
 56    {
 8057        if (constant.IsNull)
 58        {
 459            renderedLiteral = "null";
 460            return true;
 61        }
 62
 7663        switch (constant.Kind)
 64        {
 65            case TypedConstantKind.Primitive:
 6266                return TryRenderPrimitive(constant.Value, out renderedLiteral);
 67
 68            case TypedConstantKind.Enum:
 569                return TryRenderEnum(constant, out renderedLiteral);
 70
 71            case TypedConstantKind.Type:
 572                return TryRenderType(constant, out renderedLiteral);
 73
 74            default:
 75                // TypedConstantKind.Array and TypedConstantKind.Error (and any future
 76                // kind this renderer doesn't yet know about) are unsupported. Note
 77                // that TypedConstant.Value throws for an array constant, so Kind must
 78                // be checked -- and rejected -- before any Value access is attempted.
 479                renderedLiteral = string.Empty;
 480                return false;
 81        }
 82    }
 83
 84    private static bool TryRenderPrimitive(object? value, out string renderedLiteral)
 85    {
 86        switch (value)
 87        {
 88            case bool boolValue:
 189                renderedLiteral = boolValue ? "true" : "false";
 190                return true;
 91
 92            case char charValue:
 393                renderedLiteral = SymbolDisplay.FormatLiteral(charValue, quote: true);
 394                return true;
 95
 96            case string stringValue:
 997                renderedLiteral = SymbolDisplay.FormatLiteral(stringValue, quote: true);
 998                return true;
 99
 100            case sbyte sbyteValue:
 1101                renderedLiteral = sbyteValue.ToString(CultureInfo.InvariantCulture);
 1102                return true;
 103
 104            case byte byteValue:
 1105                renderedLiteral = byteValue.ToString(CultureInfo.InvariantCulture);
 1106                return true;
 107
 108            case short shortValue:
 1109                renderedLiteral = shortValue.ToString(CultureInfo.InvariantCulture);
 1110                return true;
 111
 112            case ushort ushortValue:
 1113                renderedLiteral = ushortValue.ToString(CultureInfo.InvariantCulture);
 1114                return true;
 115
 116            case int intValue:
 31117                renderedLiteral = intValue.ToString(CultureInfo.InvariantCulture);
 31118                return true;
 119
 120            case uint uintValue:
 3121                renderedLiteral = uintValue.ToString(CultureInfo.InvariantCulture) + "u";
 3122                return true;
 123
 124            case long longValue:
 3125                renderedLiteral = longValue.ToString(CultureInfo.InvariantCulture) + "L";
 3126                return true;
 127
 128            case ulong ulongValue:
 3129                renderedLiteral = ulongValue.ToString(CultureInfo.InvariantCulture) + "UL";
 3130                return true;
 131
 132            default:
 133                // float, double (explicitly excluded from this slice), and anything
 134                // else unrecognized (e.g. decimal, which is not a legal attribute
 135                // constructor parameter type in the first place).
 5136                renderedLiteral = string.Empty;
 5137                return false;
 138        }
 139    }
 140
 141    private static bool TryRenderEnum(TypedConstant constant, out string renderedLiteral)
 142    {
 5143        if (constant.Type is not INamedTypeSymbol enumType)
 144        {
 0145            renderedLiteral = string.Empty;
 0146            return false;
 147        }
 148
 5149        var qualifiedEnumTypeName = enumType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 150
 5151        var matchingMember = enumType.GetMembers()
 5152            .OfType<IFieldSymbol>()
 20153            .FirstOrDefault(member => member.IsConst && Equals(member.ConstantValue, constant.Value));
 154
 5155        if (matchingMember is not null)
 156        {
 4157            renderedLiteral = $"{qualifiedEnumTypeName}.{matchingMember.Name}";
 4158            return true;
 159        }
 160
 161        // No single declared member matches the value exactly (for example, a
 162        // combined [Flags] value with no dedicated named member). A fully qualified
 163        // cast of the exact underlying value is still a safe, unambiguous rendering.
 1164        var underlyingValueLiteral = Convert.ToString(constant.Value, CultureInfo.InvariantCulture);
 1165        renderedLiteral = $"({qualifiedEnumTypeName}){underlyingValueLiteral}";
 1166        return true;
 167    }
 168
 169    private static bool TryRenderType(TypedConstant constant, out string renderedLiteral)
 170    {
 5171        if (constant.Value is not ITypeSymbol typeSymbol)
 172        {
 0173            renderedLiteral = string.Empty;
 0174            return false;
 175        }
 176
 5177        renderedLiteral = $"typeof({typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)})";
 5178        return true;
 179    }
 180}