diff --git a/src/DependencyModules.SourceGenerator.Impl/BaseSourceGenerator.cs b/src/DependencyModules.SourceGenerator.Impl/BaseSourceGenerator.cs index bfb4dd5..c8507b0 100644 --- a/src/DependencyModules.SourceGenerator.Impl/BaseSourceGenerator.cs +++ b/src/DependencyModules.SourceGenerator.Impl/BaseSourceGenerator.cs @@ -480,27 +480,39 @@ private DependencyFlags GetDependencyFlags(GeneratorSyntaxContext context) switch (name) { case "OnlyRealm": - onlyRealm = argumentSyntax.Expression.ToString() == "true"; + onlyRealm = + ConstantArgumentReader.ReadBool(context, argumentSyntax.Expression) + ?? false; break; case "Using": - registrationType = GetRegistrationType( - argumentSyntax.Expression.ToString() + registrationType = ConstantArgumentReader.ReadRegistrationType( + context, + argumentSyntax.Expression ); break; case "GenerateAttribute": - generateAttribute = - argumentSyntax.Expression.ToString().Trim('"') == "true"; + generateAttribute = ConstantArgumentReader.ReadBool( + context, + argumentSyntax.Expression + ); break; case "RegisterJsonSerializers": - registerGenerator = - argumentSyntax.Expression.ToString().Trim('"') == "true"; + registerGenerator = ConstantArgumentReader.ReadBool( + context, + argumentSyntax.Expression + ); break; case "GenerateUseMethod": - useMethod = argumentSyntax.Expression.ToString().Trim('"'); + useMethod = ConstantArgumentReader.ReadString( + context, + argumentSyntax.Expression + ); break; case "GenerateFactories": - generateFactories = - argumentSyntax.Expression.ToString().Trim('"') == "true"; + generateFactories = ConstantArgumentReader.ReadBool( + context, + argumentSyntax.Expression + ); break; } } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/ConstantArgumentReader.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/ConstantArgumentReader.cs new file mode 100644 index 0000000..3391a89 --- /dev/null +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/ConstantArgumentReader.cs @@ -0,0 +1,65 @@ +using DependencyModules.SourceGenerator.Impl.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace DependencyModules.SourceGenerator.Impl.Utilities; + +/// +/// Reads the value of an argument from the semantic model, not from its source text. +/// +/// +/// A constant declared elsewhere, a digit separator and a fully qualified enum member all read as +/// the value they evaluate to. The generator does not reference the runtime assembly, so an enum +/// argument arrives as its underlying number. Each method returns null for an argument that has no +/// constant value. +/// +public static class ConstantArgumentReader +{ + public static int? ReadInt(SyntaxTransformContext context, ExpressionSyntax expression) => + context.SemanticModel.GetConstantValue(expression).Value is int value ? value : null; + + public static bool? ReadBool(SyntaxTransformContext context, ExpressionSyntax expression) => + context.SemanticModel.GetConstantValue(expression).Value is bool value ? value : null; + + public static string? ReadString(SyntaxTransformContext context, ExpressionSyntax expression) => + context.SemanticModel.GetConstantValue(expression).Value as string; + + /// + /// The generator's has the member order of the runtime's. + /// + public static RegistrationType? ReadRegistrationType( + SyntaxTransformContext context, + ExpressionSyntax expression + ) => + ReadInt(context, expression) is { } value && Enum.IsDefined(typeof(RegistrationType), value) + ? (RegistrationType)value + : null; + + /// + /// ServiceLifetime numbers its members Singleton, Scoped, Transient, which is the reverse + /// of . + /// + public static ServiceLifestyle? ReadLifetime( + SyntaxTransformContext context, + ExpressionSyntax expression + ) => + ReadInt(context, expression) switch + { + 0 => ServiceLifestyle.Singleton, + 1 => ServiceLifestyle.Scoped, + 2 => ServiceLifestyle.Transient, + _ => null, + }; + + /// + /// has the flag values of the runtime's + /// InterceptedMembers. + /// + public static InterceptedMemberKinds? ReadMemberKinds( + SyntaxTransformContext context, + ExpressionSyntax expression + ) => + ReadInt(context, expression) is { } value + ? (InterceptedMemberKinds)value & InterceptedMemberKinds.All + : null; +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorModelUtility.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorModelUtility.cs index 728a2f6..f872ff0 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorModelUtility.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorModelUtility.cs @@ -48,10 +48,7 @@ CancellationToken cancellationToken switch (argument.NameEquals?.Name.ToString()) { case "Order": - if (int.TryParse(argument.Expression.ToString(), out var parsed)) - { - order = parsed; - } + order = ConstantArgumentReader.ReadInt(context, argument.Expression) ?? 0; break; case "Service": explicitService = GetTypeOfArgument(argument, context); diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptorModelUtility.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptorModelUtility.cs index 57337c3..2d9bca5 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptorModelUtility.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptorModelUtility.cs @@ -84,7 +84,7 @@ is not INamedTypeSymbol implementationSymbol ref realm ); - memberKinds &= ReadMemberKinds(attribute); + memberKinds &= ReadMemberKinds(attribute, context); } if (interceptorSymbols.Count == 0) @@ -245,13 +245,10 @@ CancellationToken cancellationToken /// /// The kinds of member [Intercept] names, as flags. Everything when it names none. /// - /// - /// Read as written rather than resolved, for the reason the lifetime is: the alternative is - /// referencing an enum this generator does not. Splitting on the or-operator and taking the last - /// segment of each part handles `Methods`, `InterceptedMembers.Methods` and any qualification of - /// either. - /// - private static InterceptedMemberKinds ReadMemberKinds(AttributeSyntax attribute) + private static InterceptedMemberKinds ReadMemberKinds( + AttributeSyntax attribute, + SyntaxTransformContext context + ) { foreach ( var argument in attribute.ArgumentList?.Arguments @@ -263,33 +260,11 @@ var argument in attribute.ArgumentList?.Arguments continue; } - var kinds = InterceptedMemberKinds.None; - - foreach (var part in argument.Expression.ToString().Split('|')) - { - var member = part.Substring(part.LastIndexOf('.') + 1).Trim(); + var kinds = ConstantArgumentReader.ReadMemberKinds(context, argument.Expression); - switch (member) - { - case "Methods": - kinds |= InterceptedMemberKinds.Methods; - break; - case "Properties": - kinds |= InterceptedMemberKinds.Properties; - break; - case "Indexers": - kinds |= InterceptedMemberKinds.Indexers; - break; - case "Events": - kinds |= InterceptedMemberKinds.Events; - break; - case "All": - kinds |= InterceptedMemberKinds.All; - break; - } - } - - return kinds == InterceptedMemberKinds.None ? InterceptedMemberKinds.All : kinds; + return kinds is null or InterceptedMemberKinds.None + ? InterceptedMemberKinds.All + : kinds.Value; } return InterceptedMemberKinds.All; @@ -446,7 +421,7 @@ ref INamedTypeSymbol? realm // Read first, applied below. Lifetime is a named argument and may be written after the // interceptors it applies to, so collecting them in one pass would attach whatever the // lifetime happened to be at that point in the argument list. - var lifestyle = ReadLifetime(attribute); + var lifestyle = ReadLifetime(attribute, context); foreach (var argument in attribute.ArgumentList.Arguments) { @@ -470,10 +445,7 @@ ref INamedTypeSymbol? realm switch (name) { case "Order": - if (int.TryParse(argument.Expression.ToString(), out var parsed)) - { - order = parsed; - } + order = ConstantArgumentReader.ReadInt(context, argument.Expression) ?? 0; break; case "Service": if (argument.Expression is TypeOfExpressionSyntax serviceTypeOf) @@ -495,35 +467,20 @@ ref INamedTypeSymbol? realm /// The lifetime one [Intercept] names for the interceptors it lists, Singleton when it names /// none. /// - /// - /// Read from the written name rather than resolved, because the alternative is a constant of an - /// enum this generator does not reference. Every legal spelling ends in the member name, so the - /// last segment is the answer for `Scoped`, `ServiceLifetime.Scoped` and any qualification of - /// it alike. - /// - private static ServiceLifestyle ReadLifetime(AttributeSyntax attribute) + private static ServiceLifestyle ReadLifetime( + AttributeSyntax attribute, + SyntaxTransformContext context + ) { foreach ( var argument in attribute.ArgumentList?.Arguments ?? default(SeparatedSyntaxList) ) { - if (argument.NameEquals?.Name.ToString() != "Lifetime") - { - continue; - } - - var written = argument.Expression.ToString(); - var member = written.Substring(written.LastIndexOf('.') + 1).Trim(); - - switch (member) + if (argument.NameEquals?.Name.ToString() == "Lifetime") { - case "Scoped": - return ServiceLifestyle.Scoped; - case "Transient": - return ServiceLifestyle.Transient; - case "Singleton": - return ServiceLifestyle.Singleton; + return ConstantArgumentReader.ReadLifetime(context, argument.Expression) + ?? ServiceLifestyle.Singleton; } } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/ServiceModelUtility.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/ServiceModelUtility.cs index 4dcc8ff..f6344db 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/ServiceModelUtility.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/ServiceModelUtility.cs @@ -602,13 +602,18 @@ is MemberAccessExpressionSyntax accessExpressionSyntax break; case "Using": - registrationType = BaseSourceGenerator.GetRegistrationType( - argumentSyntax.Expression.ToString() + registrationType = ConstantArgumentReader.ReadRegistrationType( + context, + argumentSyntax.Expression ); break; case "Lifetime": - lifestyle = GetLifestyle(argumentSyntax.Expression.ToString()); + lifestyle = + ConstantArgumentReader.ReadLifetime( + context, + argumentSyntax.Expression + ) ?? ServiceLifestyle.Singleton; break; case "Realm": @@ -619,15 +624,9 @@ is MemberAccessExpressionSyntax accessExpressionSyntax break; case "Order": - if ( - int.TryParse( - argumentSyntax.Expression.ToString(), - out var parsedOrder - ) - ) - { - order = parsedOrder; - } + order = + ConstantArgumentReader.ReadInt(context, argumentSyntax.Expression) + ?? 0; break; } } @@ -657,24 +656,6 @@ out var parsedOrder } } - private static ServiceLifestyle GetLifestyle(string toString) - { - // The value arrives as written in source, normally qualified: "ServiceLifetime.Scoped". - // Parsing that whole string fails, and the silent fallback below then registered every - // cross-wired service as a singleton regardless of the lifetime the developer asked for. - var separatorIndex = toString.LastIndexOf('.'); - - var value = - separatorIndex >= 0 ? toString.Substring(separatorIndex + 1).Trim() : toString.Trim(); - - if (Enum.TryParse(value, out ServiceLifestyle lifestyle)) - { - return lifestyle; - } - - return ServiceLifestyle.Singleton; - } - private static ServiceRegistrationModel GetServiceRegistration( SyntaxTransformContext context, AttributeSyntax attributeSyntax, @@ -724,8 +705,9 @@ is MemberAccessExpressionSyntax accessExpressionSyntax } break; case "Using": - registrationType = BaseSourceGenerator.GetRegistrationType( - argumentSyntax.Expression.ToString() + registrationType = ConstantArgumentReader.ReadRegistrationType( + context, + argumentSyntax.Expression ); break; @@ -753,15 +735,9 @@ argumentSyntax.Expression is TypeOfExpressionSyntax typeOfExpression break; case "Order": - if ( - int.TryParse( - argumentSyntax.Expression.ToString(), - out var parsedOrder - ) - ) - { - order = parsedOrder; - } + order = + ConstantArgumentReader.ReadInt(context, argumentSyntax.Expression) + ?? 0; break; } } diff --git a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionModelUtility.cs b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionModelUtility.cs index 0b9dfdd..4880f5e 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionModelUtility.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionModelUtility.cs @@ -371,9 +371,7 @@ out string? reason registrationType = argument == null ? null - : SourceGenerator.Impl.BaseSourceGenerator.GetRegistrationType( - argument.ToString() - ); + : ConstantArgumentReader.ReadRegistrationType(context, argument); if (registrationType == null) { @@ -449,7 +447,14 @@ out string? reason if (name is WithNameCall or WithoutNameCall) { - var patterns = ReadPatterns(context, call); + var patterns = ReadPatterns(context, call, out var unreadable); + + if (unreadable != null) + { + reason = NotConstant(name, unreadable); + + return null; + } if (patterns.Count == 0) { @@ -473,7 +478,14 @@ out string? reason // Read as literals for the same reason every other filter is: the declaration is // parsed, never executed, so anything the build cannot see is refused rather than // quietly dropped. - var arguments = ReadPatterns(context, call); + var arguments = ReadPatterns(context, call, out var unreadable); + + if (unreadable != null) + { + reason = NotConstant(name, unreadable); + + return null; + } if (arguments.Count == 0) { @@ -619,26 +631,41 @@ out string? reason /// /// /// GetConstantValue rather than the literal text, so a const declared elsewhere - /// reads as the string it evaluates to. + /// reads as the string it evaluates to. One argument that is not a constant refuses the whole + /// call, as the namespace calls do. Dropping it would read IfEnvironmentValue(key, "on") + /// as a test for the key on. /// private static IReadOnlyList ReadPatterns( SyntaxTransformContext context, - InvocationExpressionSyntax call + InvocationExpressionSyntax call, + out ArgumentSyntax? unreadable ) { var values = new List(); foreach (var argument in call.ArgumentList.Arguments) { - if (context.SemanticModel.GetConstantValue(argument.Expression).Value is string value) + if ( + context.SemanticModel.GetConstantValue(argument.Expression).Value + is not string value + ) { - values.Add(value); + unreadable = argument; + + return values; } + + values.Add(value); } + unreadable = null; + return values; } + private static string NotConstant(string callName, ArgumentSyntax argument) => + $"'{argument}' in '{callName}' is not a compile-time constant"; + /// /// The name of the assembly the call's marker type lives in. /// diff --git a/tests/DependencyModules.Tests/GeneratorTests/ConstantArgumentTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ConstantArgumentTests.cs new file mode 100644 index 0000000..a419cd1 --- /dev/null +++ b/tests/DependencyModules.Tests/GeneratorTests/ConstantArgumentTests.cs @@ -0,0 +1,337 @@ +using DependencyModules.Tests.Infrastructure; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace DependencyModules.Tests.GeneratorTests; + +/// +/// An attribute or convention argument is read as the value it evaluates to. A constant declared +/// elsewhere, a digit separator and a fully qualified enum member all have to give the same result +/// as the literal. +/// +public class ConstantArgumentTests +{ + private const string Preamble = """ + using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Conventions; + using DependencyModules.Runtime.Interception; + using Microsoft.Extensions.DependencyInjection; + + namespace TestNamespace; + + public static class Orders + { + public const int Late = 2000; + } + + public static class Choices + { + public const RegistrationType TryIt = RegistrationType.Try; + public const InterceptedMembers MethodsOnly = InterceptedMembers.Methods; + public const ServiceLifetime PerScope = ServiceLifetime.Scoped; + public const bool Yes = true; + } + + """; + + private static GeneratorResult Run(string source) => + GeneratorTestHarness.Run(Preamble + source); + + private static GeneratedAssembly Compile(string source) => + GeneratedAssembly.Create(Preamble + source); + + [Fact] + public void ServiceOrder_IsReadAsItsValue() + { + var assembly = Compile( + """ + public interface IRule { } + + [SingletonService(Order = Orders.Late)] + public class ConstantOrderRule : IRule { } + + [SingletonService(Order = 1_000)] + public class SeparatorOrderRule : IRule { } + + [SingletonService(Order = 5)] + public class LiteralOrderRule : IRule { } + + [DependencyModule] + public partial class TestModule; + """ + ); + + Assert.Equal( + new[] { "LiteralOrderRule", "SeparatorOrderRule", "ConstantOrderRule" }, + assembly.Descriptors("IRule").Select(d => d.ImplementationType!.Name) + ); + } + + [Theory] + [InlineData("DependencyModules.Runtime.Attributes.RegistrationType.Try")] + [InlineData("Choices.TryIt")] + public void ServiceUsing_IsReadAsItsValue(string registrationType) + { + var assembly = Compile( + $$""" + public interface IClock { } + + [SingletonService(Using = {{registrationType}})] + public class FirstClock : IClock { } + + [SingletonService(Using = {{registrationType}})] + public class SecondClock : IClock { } + + [DependencyModule] + public partial class TestModule; + """ + ); + + Assert.Single(assembly.Descriptors("IClock")); + } + + [Fact] + public void ModuleArguments_AreReadAsTheirValues() + { + var result = Run( + """ + public interface IClock { } + + [SingletonService] + public class FirstClock : IClock { } + + [SingletonService] + public class SecondClock : IClock { } + + [DependencyModule(Using = Choices.TryIt, GenerateFactories = Choices.Yes)] + public partial class TestModule; + """ + ); + + result.AssertNoErrors(); + + var registrations = result.SourceContaining("Dependencies"); + + Assert.Contains("services.TryAddSingleton(", registrations); + Assert.DoesNotContain("services.AddSingleton(", registrations); + Assert.Contains("new global::TestNamespace.FirstClock()", registrations); + } + + [Fact] + public void CrossWireLifetime_IsReadAsItsValue() + { + var assembly = Compile( + """ + public interface IReader { } + + [CrossWireService(Lifetime = Choices.PerScope)] + public class Store : IReader { } + + [DependencyModule] + public partial class TestModule; + """ + ); + + Assert.Equal(ServiceLifetime.Scoped, assembly.Descriptor("Store").Lifetime); + Assert.Equal(ServiceLifetime.Scoped, assembly.Descriptor("IReader").Lifetime); + } + + [Fact] + public void DecoratorOrder_IsReadAsItsValue() + { + var assembly = Compile( + """ + public interface IGreeter + { + string Greet(); + } + + [SingletonService] + public class Greeter : IGreeter + { + public string Greet() => "hello"; + } + + [Decorator(Order = Orders.Late)] + public class OuterGreeter(IGreeter inner) : IGreeter + { + public string Greet() => $"outer({inner.Greet()})"; + } + + [Decorator(Order = 1_000)] + public class InnerGreeter(IGreeter inner) : IGreeter + { + public string Greet() => $"inner({inner.Greet()})"; + } + + [DependencyModule] + public partial class TestModule; + """ + ); + + var greeter = assembly.ResolveRequired("IGreeter"); + + Assert.Equal( + "outer(inner(hello))", + greeter.GetType().GetMethod("Greet")!.Invoke(greeter, null) + ); + } + + [Fact] + public void InterceptArguments_AreReadAsTheirValues() + { + var assembly = Compile( + """ + public interface IGreeter + { + string Hello(); + + string Name { get; } + } + + public class CountingInterceptor : IInterceptor + { + public static int Calls; + + public TResult Intercept(InvocationContext context) + { + Calls++; + return context.Proceed(); + } + } + + [SingletonService] + [Intercept( + typeof(CountingInterceptor), + Order = Orders.Late, + Members = Choices.MethodsOnly, + Lifetime = Choices.PerScope + )] + public class Greeter : IGreeter + { + public string Hello() => "hello"; + + public string Name => "greeter"; + } + + [Decorator(Order = 1_000)] + public class LoggingGreeter(IGreeter inner) : IGreeter + { + public string Hello() => inner.Hello(); + + public string Name => inner.Name; + } + + [DependencyModule] + public partial class TestModule; + """ + ); + + Assert.Equal(ServiceLifetime.Scoped, assembly.Descriptor("CountingInterceptor").Lifetime); + + using var scope = assembly.BuildProvider().CreateScope(); + var greeterType = assembly.Type("IGreeter"); + var greeter = scope.ServiceProvider.GetRequiredService(greeterType); + + // Order 2000 puts the interception outside the decorator at 1000. + Assert.Equal("Greeter_Intercepted", greeter.GetType().Name); + + greeterType.GetMethod("Hello")!.Invoke(greeter, null); + greeterType.GetProperty("Name")!.GetValue(greeter); + + Assert.Equal(1, assembly.Type("CountingInterceptor").GetField("Calls")!.GetValue(null)); + } + + [Fact] + public void ConventionUsing_IsReadAsItsValue() + { + var result = Run( + """ + public interface IHandler { } + + public class OrderHandler : IHandler { } + + [DependencyModule] + public partial class TestModule : IConventionModule + { + public void Conventions(IConventionDefinitions conventions) + { + conventions.RegisterAll().Using(Choices.TryIt).AsSingleton(); + } + } + """ + ); + + result.AssertNoErrors(); + + Assert.Contains( + "services.TryAddSingleton(", + result.SourceContaining("ConventionDependencies") + ); + } + + [Theory] + [InlineData("IfEnvironmentValue(Keys.Feature, \"on\")", "'Keys.Feature'")] + [InlineData("WithName(\"Order*\", Keys.InvoicePattern)", "'Keys.InvoicePattern'")] + [InlineData("Using(Keys.Using)", "RegistrationType")] + public void AConventionArgumentThatIsNotAConstant_IsReported(string call, string named) + { + var result = Run( + $$""" + public static class Keys + { + public static readonly string Feature = "FEATURE_X"; + public static readonly string InvoicePattern = "Invoice*"; + public static readonly RegistrationType Using = RegistrationType.Try; + } + + public interface IHandler { } + + public class OrderHandler : IHandler { } + + [DependencyModule] + public partial class TestModule : IConventionModule + { + public void Conventions(IConventionDefinitions conventions) + { + conventions.RegisterAll().{{call}}.AsSingleton(); + } + } + """ + ); + + var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0009"); + Assert.Contains(named, diagnostic.GetMessage()); + } + + [Fact] + public void AConstantConventionArgument_IsRead() + { + var result = Run( + """ + public static class Keys + { + public const string Feature = "FEATURE_X"; + } + + public interface IHandler { } + + public class OrderHandler : IHandler { } + + [DependencyModule] + public partial class TestModule : IConventionModule + { + public void Conventions(IConventionDefinitions conventions) + { + conventions.RegisterAll().IfEnvironmentValue(Keys.Feature, "on").AsSingleton(); + } + } + """ + ); + + result.AssertNoErrors(); + + Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0009"); + Assert.Contains("\"FEATURE_X\"", result.SourceContaining("ConventionDependencies")); + } +} diff --git a/tests/DependencyModules.Tests/Snapshots/PublicApiTests.SourceGeneratorApi.verified.txt b/tests/DependencyModules.Tests/Snapshots/PublicApiTests.SourceGeneratorApi.verified.txt index faa65b9..79c0103 100644 --- a/tests/DependencyModules.Tests/Snapshots/PublicApiTests.SourceGeneratorApi.verified.txt +++ b/tests/DependencyModules.Tests/Snapshots/PublicApiTests.SourceGeneratorApi.verified.txt @@ -1851,6 +1851,15 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities protected abstract bool TestForTypes(Microsoft.CodeAnalysis.SyntaxNode node, System.Threading.CancellationToken token); public bool Where(Microsoft.CodeAnalysis.SyntaxNode node, System.Threading.CancellationToken token) { } } + public static class ConstantArgumentReader + { + public static bool? ReadBool(DependencyModules.SourceGenerator.Impl.Utilities.SyntaxTransformContext context, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) { } + public static int? ReadInt(DependencyModules.SourceGenerator.Impl.Utilities.SyntaxTransformContext context, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) { } + public static DependencyModules.SourceGenerator.Impl.Models.ServiceLifestyle? ReadLifetime(DependencyModules.SourceGenerator.Impl.Utilities.SyntaxTransformContext context, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) { } + public static DependencyModules.SourceGenerator.Impl.Models.InterceptedMemberKinds? ReadMemberKinds(DependencyModules.SourceGenerator.Impl.Utilities.SyntaxTransformContext context, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) { } + public static DependencyModules.SourceGenerator.Impl.Models.RegistrationType? ReadRegistrationType(DependencyModules.SourceGenerator.Impl.Utilities.SyntaxTransformContext context, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) { } + public static string? ReadString(DependencyModules.SourceGenerator.Impl.Utilities.SyntaxTransformContext context, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) { } + } public static class ConstructorArgumentWriter { public static object[] Arguments(CSharpAuthor.ParameterDefinition serviceProvider, System.Collections.Generic.IReadOnlyList parameters) { }