diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..6e2e244 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "csharpier": { + "version": "1.3.0", + "commands": [ + "csharpier" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/.csharpierignore b/.csharpierignore new file mode 100644 index 0000000..8da2be7 --- /dev/null +++ b/.csharpierignore @@ -0,0 +1,9 @@ +# CSharpier reindents MSBuild XML but leaves the interior of multi-line comments at its old +# indentation, so every commented csproj in this repo comes out with hanging comment bodies. +# The C# formatting is what is wanted here; the project files stay hand-maintained. +*.csproj +*.props +*.targets + +# Build output. Nothing here is a source file. +artifacts/ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..41aceaf --- /dev/null +++ b/.editorconfig @@ -0,0 +1,53 @@ +root = true + +# CSharpier is what enforces the layout below; this file exists so an IDE reaches the same answer +# while you type, rather than reformatting to its own defaults and losing the change at the next +# commit. CSharpier reads indent_style, indent_size, max_line_length and end_of_line from here, so +# those four are shared settings rather than a second opinion. Everything else describes what +# CSharpier already does and is not configurable in it. +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.{json,yaml,yml,md}] +indent_size = 2 + +[*.md] +# Two trailing spaces is a line break in Markdown. +trim_trailing_whitespace = false + +[*.{csproj,props,targets}] +# Not formatted by CSharpier — see .csharpierignore. +indent_size = 4 + +[*.cs] +max_line_length = 100 + +# Allman. This is the one CSharpier does not offer a choice about, and the reason the repo moved +# off K&R: matching the formatter is cheaper than arguing with it. +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after + +csharp_preserve_single_line_statements = false +csharp_preserve_single_line_blocks = true diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..31210aa --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,6 @@ +# Revisions that only moved whitespace. GitHub honours this file automatically; for local blame: +# +# git config blame.ignoreRevsFile .git-blame-ignore-revs + +# Reformat every C# file with CSharpier +79a2881a3fd722dd95f8aa589567003acb896678 diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..0429329 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,45 @@ +#!/bin/sh +# +# Rejects a commit whose C# is not CSharpier-formatted. +# +# Enable it once per clone: +# +# git config core.hooksPath .githooks +# +# A hook is local and skippable with --no-verify, so it is the fast answer rather than the +# guarantee; build-package.yaml runs the same check on every pull request. + +set -eu + +staged=$(git diff --cached --name-only --diff-filter=ACMR -- '*.cs') + +if [ -z "$staged" ]; then + exit 0 +fi + +if ! command -v dotnet >/dev/null 2>&1; then + echo "pre-commit: dotnet is not on PATH, skipping the format check." >&2 + exit 0 +fi + +# The manifest pins the version, so every clone and CI agree on what formatted means. +if ! dotnet tool run csharpier --version >/dev/null 2>&1; then + dotnet tool restore >/dev/null +fi + +# Split on newlines only, so a path with spaces stays one argument, and with globbing off so one +# with a bracket in it is not expanded. Named paths skip CSharpier's cache and cost about 50ms +# each, which is worth paying to check the files being committed rather than the whole tree. +IFS=' +' +set -f +set -- $staged +set +f + +# The working tree is what gets checked, not the staged content. They differ only when a file is +# staged in part, which is rare enough not to pay for a temporary checkout on every commit. +if ! dotnet csharpier check "$@"; then + echo >&2 + echo "pre-commit: run 'dotnet csharpier format .' and stage the result." >&2 + exit 1 +fi diff --git a/.github/workflows/build-package.yaml b/.github/workflows/build-package.yaml index 6d0bbeb..2c8632c 100644 --- a/.github/workflows/build-package.yaml +++ b/.github/workflows/build-package.yaml @@ -43,6 +43,14 @@ jobs: - run: dotnet restore DependencyModules.sln + # The pre-commit hook in .githooks runs this too, but a hook is local and skippable. This is + # what actually holds the formatting. It runs before the build so a style-only failure costs + # seconds rather than a full compile and test cycle. + - name: Check formatting + run: | + dotnet tool restore + dotnet csharpier check . + - run: dotnet build DependencyModules.sln --no-restore --configuration Release # Runs every test suite with coverage collection and merges the results. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6784ad7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# Contributing + +## Setup + +```sh +git config core.hooksPath .githooks +git config blame.ignoreRevsFile .git-blame-ignore-revs +dotnet tool restore +``` + +The first line turns on the pre-commit hook, which rejects a commit whose C# is not formatted. Git +does not carry hooks across a clone, so this is the one step that cannot be automated for you. + +The second keeps the CSharpier reformat out of `git blame`, which otherwise reports it as the last +change to nearly every line in the repo. GitHub already reads that file without being asked. + +## Formatting + +C# layout is [CSharpier](https://csharpier.com)'s, and the version is pinned in +`.config/dotnet-tools.json` so every clone and CI agree on what formatted means. Braces are Allman. +Nothing about the style is up for discussion in review — run the formatter: + +```sh +dotnet csharpier format . +``` + +`.editorconfig` describes the same layout for your IDE, so typing and formatting do not disagree. +Project files are excluded (see `.csharpierignore`); CSharpier reindents MSBuild XML but leaves the +interior of multi-line comments where it was, which this repo has a lot of. + +`build-package.yaml` runs `dotnet csharpier check .` on every pull request. The hook is the fast +answer, that check is the guarantee. + +## Build and test + +```sh +dotnet build DependencyModules.sln +dotnet test DependencyModules.sln +``` + +Both target frameworks are built, so running the tests needs the .NET 8 runtime alongside the .NET +10 SDK that `global.json` selects. + +`./scripts/coverage.sh 85` runs every suite with coverage and fails under the threshold, the same +way CI does. `./scripts/verify-packages.sh` packs the libraries and consumes them from a real +package reference, which is the only thing that catches a packaging fault. diff --git a/README.md b/README.md index 4aede94..ef76acf 100644 --- a/README.md +++ b/README.md @@ -106,8 +106,10 @@ time: ```csharp [DependencyModule] -public partial class HandlerModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class HandlerModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll(typeof(IRequestHandler<,>)).AsScoped(); conventions.RegisterAll(typeof(IValidator<>)) @@ -172,12 +174,13 @@ Tests receive their dependencies as method parameters, against the real registra [assembly: ApplicationModule] [assembly: NSubstituteSupport] -public class OrderTests { +public class OrderTests +{ [ModuleTest] public async Task PlaceOrder_PricesThroughTheChannel( IRequestHandler handler, - [Mock] IBookRepository books) { - + [Mock] IBookRepository books) + { books.Find("isbn-1", Arg.Any()) .Returns(new Book("isbn-1", 20m)); diff --git a/benchmarks/DependencyModules.Benchmarks/Program.cs b/benchmarks/DependencyModules.Benchmarks/Program.cs index 9c2b6bb..537c8c6 100644 --- a/benchmarks/DependencyModules.Benchmarks/Program.cs +++ b/benchmarks/DependencyModules.Benchmarks/Program.cs @@ -44,15 +44,18 @@ namespace DependencyModules.Benchmarks; /// /// /// -public static class Program { - +public static class Program +{ private const int Runs = 15; - public static void Main() { + public static void Main() + { Console.WriteLine( - $"TieredCompilation={Environment.GetEnvironmentVariable("DOTNET_TieredCompilation") ?? "(default, results will drift)"}"); + $"TieredCompilation={Environment.GetEnvironmentVariable("DOTNET_TieredCompilation") ?? "(default, results will drift)"}" + ); - for (var i = 0; i < 20; i++) { + for (var i = 0; i < 20; i++) + { ColdRun(BuildSources(400, 100)); } @@ -60,12 +63,16 @@ public static void Main() { Console.WriteLine("classes implementing cold ms after one edit ms"); Console.WriteLine("---------------------------------------------------"); - foreach (var total in new[] { 500, 2000 }) { - foreach (var implementing in new[] { 0, total / 4, total }) { + foreach (var total in new[] { 500, 2000 }) + { + foreach (var implementing in new[] { 0, total / 4, total }) + { var cold = Median(() => ColdRun(BuildSources(total, implementing))); var incremental = Median(() => IncrementalRun(total, implementing)); - Console.WriteLine($"{total,7} {implementing,12} {cold,8:F1} {incremental,18:F1}"); + Console.WriteLine( + $"{total, 7} {implementing, 12} {cold, 8:F1} {incremental, 18:F1}" + ); } } @@ -74,10 +81,12 @@ public static void Main() { FrameworkStack(2000); } - private static double Median(Func measure) { + private static double Median(Func measure) + { var timings = new List(Runs); - for (var run = 0; run < Runs; run++) { + for (var run = 0; run < Runs; run++) + { timings.Add(measure()); } @@ -95,19 +104,21 @@ private static double Median(Func measure) { /// what a consuming project loads is this one plus one per framework. Measured rather than /// assumed, because it is the cost every such framework imposes on every build. /// - private class ExtensionShapedGenerator(string attributeName) : BaseSourceGenerator { - + private class ExtensionShapedGenerator(string attributeName) : BaseSourceGenerator + { protected override ITypeDefinition[] ModuleAttributeTypes() => new[] { TypeDefinition.Get("Bench.Framework", attributeName) }; - protected override IEnumerable AttributeSourceGenerators() { + protected override IEnumerable AttributeSourceGenerators() + { yield return new FrameworkAttributeGenerator(); } } - private class FrameworkAttributeGenerator : IDependencyModuleSourceGenerator { - - private static readonly ITypeDefinition[] _attributes = { + private class FrameworkAttributeGenerator : IDependencyModuleSourceGenerator + { + private static readonly ITypeDefinition[] _attributes = + { TypeDefinition.Get("Bench.Framework", "EndpointAttribute"), TypeDefinition.Get("Bench.Framework", "HandlerAttribute"), TypeDefinition.Get("Bench.Framework", "JobAttribute"), @@ -115,55 +126,71 @@ private class FrameworkAttributeGenerator : IDependencyModuleSourceGenerator { public void SetupGenerator( IncrementalGeneratorInitializationContext context, - IncrementalValuesProvider<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> incrementalValueProvider) { - + IncrementalValuesProvider<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> incrementalValueProvider + ) + { var models = AttributeModelCollector.Collect( context, _attributes, static (syntaxContext, cancellation) => - ServiceModelUtility.GetServiceModel(syntaxContext, cancellation) ?? ServiceModel.Ignore, + ServiceModelUtility.GetServiceModel(syntaxContext, cancellation) + ?? ServiceModel.Ignore, new ServiceModelComparer(), - ServiceModel.Ignore); + ServiceModel.Ignore + ); context.RegisterSourceOutput( incrementalValueProvider.Collect().Combine(models), - static (productionContext, data) => { }); + static (productionContext, data) => { } + ); } } - private static void FrameworkStack(int total) { + private static void FrameworkStack(int total) + { Console.WriteLine(); Console.WriteLine("analyzers loaded cold ms after one edit ms"); Console.WriteLine("--------------------------------------------------------------------"); - for (var frameworks = 0; frameworks <= 3; frameworks++) { + for (var frameworks = 0; frameworks <= 3; frameworks++) + { var count = frameworks; var cold = Median(() => Run(BuildSources(total, total / 4), count, cold: true)); var incremental = Median(() => Run(BuildSources(total, total / 4), count, cold: false)); - var label = count == 0 - ? "DependencyModules only" - : $"DependencyModules + {count} framework generator(s)"; + var label = + count == 0 + ? "DependencyModules only" + : $"DependencyModules + {count} framework generator(s)"; - Console.WriteLine($"{label,-40} {cold,8:F1} {incremental,18:F1}"); + Console.WriteLine($"{label, -40} {cold, 8:F1} {incremental, 18:F1}"); } } - private static double Run(string[] sources, int frameworks, bool cold) { + private static double Run(string[] sources, int frameworks, bool cold) + { var compilation = Compile(sources); - var generators = new List { - new SourceGenerator.SourceGenerator().AsSourceGenerator() + var generators = new List + { + new SourceGenerator.SourceGenerator().AsSourceGenerator(), }; - for (var i = 0; i < frameworks; i++) { - generators.Add(new ExtensionShapedGenerator($"Framework{i}Attribute").AsSourceGenerator()); + for (var i = 0; i < frameworks; i++) + { + generators.Add( + new ExtensionShapedGenerator($"Framework{i}Attribute").AsSourceGenerator() + ); } GeneratorDriver driver = CSharpGeneratorDriver.Create(generators); - if (cold) { + if (cold) + { return Time(() => driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _)); } @@ -173,17 +200,20 @@ private static double Run(string[] sources, int frameworks, bool cold) { var edited = compilation.ReplaceSyntaxTree( compilation.SyntaxTrees.ElementAt(1), - CSharpSyntaxTree.ParseText(sources[1].Replace("_seed = 0", "_seed = 42"), options)); + CSharpSyntaxTree.ParseText(sources[1].Replace("_seed = 0", "_seed = 42"), options) + ); return Time(() => driver.RunGeneratorsAndUpdateCompilation(edited, out _, out _)); } - private static double ColdRun(string[] sources) { + private static double ColdRun(string[] sources) + { var compilation = Compile(sources); // A fresh driver each run: reusing one would measure the incremental cache instead. var driver = CSharpGeneratorDriver.Create( - new SourceGenerator.SourceGenerator().AsSourceGenerator()); + new SourceGenerator.SourceGenerator().AsSourceGenerator() + ); return Time(() => driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _)); } @@ -191,25 +221,29 @@ private static double ColdRun(string[] sources) { /// /// The second run of one driver, after editing a method body in a single file. /// - private static double IncrementalRun(int total, int implementing) { + private static double IncrementalRun(int total, int implementing) + { var sources = BuildSources(total, implementing); var options = new CSharpParseOptions(LanguageVersion.Latest); var compilation = Compile(sources); GeneratorDriver driver = CSharpGeneratorDriver.Create( - new SourceGenerator.SourceGenerator().AsSourceGenerator()); + new SourceGenerator.SourceGenerator().AsSourceGenerator() + ); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); // Only the edited tree is replaced; the other N-1 keep their identity and should stay cached. var edited = compilation.ReplaceSyntaxTree( compilation.SyntaxTrees.ElementAt(1), - CSharpSyntaxTree.ParseText(sources[1].Replace("_seed = 0", "_seed = 42"), options)); + CSharpSyntaxTree.ParseText(sources[1].Replace("_seed = 0", "_seed = 42"), options) + ); return Time(() => driver.RunGeneratorsAndUpdateCompilation(edited, out _, out _)); } - private static double Time(Action action) { + private static double Time(Action action) + { GC.Collect(); GC.WaitForPendingFinalizers(); @@ -220,25 +254,30 @@ private static double Time(Action action) { return (Stopwatch.GetTimestamp() - start) * 1000.0 / Stopwatch.Frequency; } - private static CSharpCompilation Compile(string[] sources) { + private static CSharpCompilation Compile(string[] sources) + { var options = new CSharpParseOptions(LanguageVersion.Latest); return CSharpCompilation.Create( "BenchAssembly", sources.Select(source => CSharpSyntaxTree.ParseText(source, options)), References, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + ); } - private static string[] BuildSources(int total, int implementing) { + private static string[] BuildSources(int total, int implementing) + { var trees = new string[total + 2]; trees[0] = "namespace BenchNamespace; public interface IMarker { }"; - for (var i = 0; i < total; i++) { - trees[i + 1] = i < implementing - ? $"namespace BenchNamespace; public class Implementing{i} : IMarker {{{Body(i)}}}" - : $"namespace BenchNamespace; public class Plain{i} {{{Body(i)}}}"; + for (var i = 0; i < total; i++) + { + trees[i + 1] = + i < implementing + ? $"namespace BenchNamespace; public class Implementing{i} : IMarker {{{Body(i)}}}" + : $"namespace BenchNamespace; public class Plain{i} {{{Body(i)}}}"; } var builder = new StringBuilder(); @@ -248,7 +287,9 @@ private static string[] BuildSources(int total, int implementing) { builder.AppendLine("namespace BenchNamespace;"); builder.AppendLine("[DependencyModule]"); builder.AppendLine("public partial class BenchModule : IConventionModule {"); - builder.AppendLine(" void IConventionModule.Conventions(IConventionDefinitions conventions) {"); + builder.AppendLine( + " void IConventionModule.Conventions(IConventionDefinitions conventions) {" + ); builder.AppendLine(" conventions.RegisterAll().AsScoped();"); builder.AppendLine(" }"); builder.AppendLine("}"); @@ -258,7 +299,8 @@ private static string[] BuildSources(int total, int implementing) { return trees; } - private static string Body(int i) => $$""" + private static string Body(int i) => + $$""" private readonly int _seed = {{i}}; public int Value => _seed; @@ -270,14 +312,18 @@ private static string Body(int i) => $$""" private static readonly MetadataReference[] References = BuildReferences(); - private static MetadataReference[] BuildReferences() { + private static MetadataReference[] BuildReferences() + { // Touched so the runtime and DI assemblies are loaded before the sweep below. _ = typeof(IServiceCollection); _ = typeof(Runtime.ModuleEnvironment); - return AppDomain.CurrentDomain.GetAssemblies() + return AppDomain + .CurrentDomain.GetAssemblies() .Where(assembly => !assembly.IsDynamic && !string.IsNullOrEmpty(assembly.Location)) - .Select(assembly => (MetadataReference)MetadataReference.CreateFromFile(assembly.Location)) + .Select(assembly => + (MetadataReference)MetadataReference.CreateFromFile(assembly.Location) + ) .ToArray(); } } diff --git a/docs/design/aot-decorators-and-convention-cost.md b/docs/design/aot-decorators-and-convention-cost.md index 61dcc85..5151fdc 100644 --- a/docs/design/aot-decorators-and-convention-cost.md +++ b/docs/design/aot-decorators-and-convention-cost.md @@ -695,7 +695,8 @@ has to learn from `P`, and who owns the knowledge of how to build `P`'s decorato `P`'s generator emits, next to the decorator, a generic static method: ```csharp -public static class LoggingBehaviorRegistration { +public static class LoggingBehaviorRegistration +{ public static void ApplyTo(IServiceCollection services) => DecoratorHelper.Decorate(services, typeof(IRequestHandler), (provider, inner) => new LoggingBehavior( diff --git a/docs/design/convention-registration-and-decorators.md b/docs/design/convention-registration-and-decorators.md index 3c2c797..ad684eb 100644 --- a/docs/design/convention-registration-and-decorators.md +++ b/docs/design/convention-registration-and-decorators.md @@ -116,8 +116,10 @@ attributes: ```csharp [DependencyModule] -public partial class DataModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class DataModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsScoped(); } } @@ -254,7 +256,8 @@ real DLL, then a **second compilation referencing it and containing no handlers was run through an `IIncrementalGenerator`, which emitted: ```csharp -private static void ModuleDependencies(IServiceCollection services) { +private static void ModuleDependencies(IServiceCollection services) +{ services.AddScoped(typeof(global::TheLibrary.IHandler), typeof(global::TheLibrary.CreateOrderHandler)); services.AddScoped(typeof(global::TheLibrary.IHandler), typeof(global::TheLibrary.RenameOrderHandler)); } @@ -567,7 +570,8 @@ public interface IRepository { Item Get(int id); } public class Repository : IRepository { ... } [Decorator(Order = 1)] -public class CachingRepository(IRepository inner, IMemoryCache cache) : IRepository { +public class CachingRepository(IRepository inner, IMemoryCache cache) : IRepository +{ public Item Get(int id) => cache.GetOrCreate(id, _ => inner.Get(id))!; } ``` @@ -603,8 +607,10 @@ Step 4 is two lines and fixes a public API that is currently a no-op, independen Descriptor rewrite, the same approach Scrutor takes: ```csharp -private static void ModuleDecorators(IServiceCollection services) { - for (var i = services.Count - 1; i >= 0; i--) { +private static void ModuleDecorators(IServiceCollection services) +{ + for (var i = services.Count - 1; i >= 0; i--) + { var descriptor = services[i]; if (descriptor.ServiceType != typeof(global::App.IRepository)) continue; @@ -693,7 +699,8 @@ Removes the boilerplate of writing a decorator that overrides one member and for ```csharp [Decorator] -public partial class CachingRepository(IRepository inner, IMemoryCache cache) : IRepository { +public partial class CachingRepository(IRepository inner, IMemoryCache cache) : IRepository +{ public Item Get(int id) => cache.GetOrCreate(id, _ => inner.Get(id))!; // every other IRepository member is generated, forwarding to inner } @@ -786,7 +793,8 @@ interceptor does. That is a stronger differentiator than Scrutor parity. **Tier 1, lifecycle hooks.** No boxing, no argument array. ```csharp -public interface IInterceptor { +public interface IInterceptor +{ void OnEnter(string member); void OnExit(string member); void OnError(string member, Exception exception); @@ -801,7 +809,8 @@ signature. **Tier 2, full invocation.** Opt-in, for caching, retry and authorisation. ```csharp -public interface IInvocationInterceptor { +public interface IInvocationInterceptor +{ ValueTask InterceptAsync(IInvocation invocation); // arguments, ProceedAsync, short-circuit } ``` diff --git a/docs/design/runtime-module-graph.md b/docs/design/runtime-module-graph.md index 597fe27..85c9619 100644 --- a/docs/design/runtime-module-graph.md +++ b/docs/design/runtime-module-graph.md @@ -29,7 +29,8 @@ and the dependency methods name each registration. The smallest useful thing: `LoadModules` registers what it loaded. ```csharp -public interface IModuleGraph { +public interface IModuleGraph +{ IReadOnlyList Modules { get; } bool Contains() where TModule : IDependencyModule; } @@ -44,7 +45,8 @@ two questions with the sharpest failure modes today. The generator emits, per module, the service types it registers, and the runtime assembles them: ```csharp -public interface IModuleGraph { +public interface IModuleGraph +{ IReadOnlyList Modules { get; } } diff --git a/integ-tests/ConsoleTestProject/Program.cs b/integ-tests/ConsoleTestProject/Program.cs index 6aec88c..5e020c6 100644 --- a/integ-tests/ConsoleTestProject/Program.cs +++ b/integ-tests/ConsoleTestProject/Program.cs @@ -16,4 +16,3 @@ container.GetRequiredService(); SutProject.SutModule.Run(); - diff --git a/integ-tests/ConsoleTestProject/TestExport.cs b/integ-tests/ConsoleTestProject/TestExport.cs index 5ae08a8..e1c78f2 100644 --- a/integ-tests/ConsoleTestProject/TestExport.cs +++ b/integ-tests/ConsoleTestProject/TestExport.cs @@ -3,5 +3,4 @@ namespace ConsoleTestProject; [SingletonService] -public class TestExport { -} \ No newline at end of file +public class TestExport { } diff --git a/integ-tests/SecondarySutProject/BetterDependencyOne.cs b/integ-tests/SecondarySutProject/BetterDependencyOne.cs index d5c4d9e..9892a18 100644 --- a/integ-tests/SecondarySutProject/BetterDependencyOne.cs +++ b/integ-tests/SecondarySutProject/BetterDependencyOne.cs @@ -4,18 +4,15 @@ namespace SecondarySutProject; [TransientService] -public class BetterDependencyOne : IDependencyOne { - - public BetterDependencyOne(ISingletonService singletonService, IScopedService scopedService) { +public class BetterDependencyOne : IDependencyOne +{ + public BetterDependencyOne(ISingletonService singletonService, IScopedService scopedService) + { SingletonService = singletonService; ScopedService = scopedService; } - public ISingletonService SingletonService { - get; - } + public ISingletonService SingletonService { get; } - public IScopedService ScopedService { - get; - } -} \ No newline at end of file + public IScopedService ScopedService { get; } +} diff --git a/integ-tests/SecondarySutProject/CircularReferenceModules/ModuleA.cs b/integ-tests/SecondarySutProject/CircularReferenceModules/ModuleA.cs index 9e9b2ee..90f4b9e 100644 --- a/integ-tests/SecondarySutProject/CircularReferenceModules/ModuleA.cs +++ b/integ-tests/SecondarySutProject/CircularReferenceModules/ModuleA.cs @@ -4,4 +4,4 @@ namespace SecondarySutProject.CircularReferenceModules; [DependencyModule] [ModuleB] -public partial class ModuleA { } \ No newline at end of file +public partial class ModuleA { } diff --git a/integ-tests/SecondarySutProject/CircularReferenceModules/ModuleB.cs b/integ-tests/SecondarySutProject/CircularReferenceModules/ModuleB.cs index 73a4651..e98ce0d 100644 --- a/integ-tests/SecondarySutProject/CircularReferenceModules/ModuleB.cs +++ b/integ-tests/SecondarySutProject/CircularReferenceModules/ModuleB.cs @@ -4,5 +4,4 @@ namespace SecondarySutProject.CircularReferenceModules; [DependencyModule] [ModuleA] -public partial class ModuleB { -} \ No newline at end of file +public partial class ModuleB { } diff --git a/integ-tests/SecondarySutProject/CircularReferenceModules/ServiceA.cs b/integ-tests/SecondarySutProject/CircularReferenceModules/ServiceA.cs index 53454f5..451eb8a 100644 --- a/integ-tests/SecondarySutProject/CircularReferenceModules/ServiceA.cs +++ b/integ-tests/SecondarySutProject/CircularReferenceModules/ServiceA.cs @@ -3,4 +3,4 @@ namespace SecondarySutProject.CircularReferenceModules; [TransientService(Realm = typeof(ModuleA))] -public class ServiceA { } \ No newline at end of file +public class ServiceA { } diff --git a/integ-tests/SecondarySutProject/CircularReferenceModules/ServiceB.cs b/integ-tests/SecondarySutProject/CircularReferenceModules/ServiceB.cs index 75633ad..b115a7a 100644 --- a/integ-tests/SecondarySutProject/CircularReferenceModules/ServiceB.cs +++ b/integ-tests/SecondarySutProject/CircularReferenceModules/ServiceB.cs @@ -3,4 +3,4 @@ namespace SecondarySutProject.CircularReferenceModules; [TransientService(Realm = typeof(ModuleB))] -public class ServiceB { } \ No newline at end of file +public class ServiceB { } diff --git a/integ-tests/SecondarySutProject/PackagePolicies.cs b/integ-tests/SecondarySutProject/PackagePolicies.cs index b466c7a..607df53 100644 --- a/integ-tests/SecondarySutProject/PackagePolicies.cs +++ b/integ-tests/SecondarySutProject/PackagePolicies.cs @@ -6,25 +6,29 @@ namespace SecondarySutProject; // This project does not reference the conventions analyzer, so nothing here registers itself. /// A policy contract a consumer might scan for. -public interface IPackagePolicy { +public interface IPackagePolicy +{ /// Identifies the policy in assertions. string Name { get; } } /// A public policy, visible across the assembly boundary. -public class FirstPackagePolicy : IPackagePolicy { +public class FirstPackagePolicy : IPackagePolicy +{ /// public string Name => "first"; } /// A second public policy, so the scan has more than one match. -public class SecondPackagePolicy : IPackagePolicy { +public class SecondPackagePolicy : IPackagePolicy +{ /// public string Name => "second"; } // Internal, so it is invisible across the boundary — the difference between scanning metadata and // scanning the compilation being built. -internal class HiddenPackagePolicy : IPackagePolicy { +internal class HiddenPackagePolicy : IPackagePolicy +{ public string Name => "hidden"; } diff --git a/integ-tests/SecondarySutProject/ParameterizedModules/ParameterizedModule.cs b/integ-tests/SecondarySutProject/ParameterizedModules/ParameterizedModule.cs index 3cd4932..6bebcd2 100644 --- a/integ-tests/SecondarySutProject/ParameterizedModules/ParameterizedModule.cs +++ b/integ-tests/SecondarySutProject/ParameterizedModules/ParameterizedModule.cs @@ -6,18 +6,21 @@ namespace SecondarySutProject.ParameterizedModules; [DependencyModule(OnlyRealm = true)] -public partial class ParameterizedModule : IServiceCollectionConfiguration { +public partial class ParameterizedModule : IServiceCollectionConfiguration +{ private readonly string _a; private readonly int _b; - public ParameterizedModule(string a, int b) { + public ParameterizedModule(string a, int b) + { _a = a; _b = b; } public string? C { get; set; } - public void ConfigureServices(IServiceCollection services) { + public void ConfigureServices(IServiceCollection services) + { services.AddTransient(_ => new SomeRuntimeDependency(_a, _b, C!)); } @@ -27,4 +30,4 @@ public override bool Equals(object? obj) => obj is ParameterizedModule other && other._a == _a && other._b == _b && other.C == C; public override int GetHashCode() => HashCode.Combine(_a, _b, C); -} \ No newline at end of file +} diff --git a/integ-tests/SecondarySutProject/ParameterizedModules/SecondaryParameterizedModule.cs b/integ-tests/SecondarySutProject/ParameterizedModules/SecondaryParameterizedModule.cs index dd7c803..6aff520 100644 --- a/integ-tests/SecondarySutProject/ParameterizedModules/SecondaryParameterizedModule.cs +++ b/integ-tests/SecondarySutProject/ParameterizedModules/SecondaryParameterizedModule.cs @@ -4,4 +4,4 @@ namespace SecondarySutProject.ParameterizedModules; [DependencyModule(OnlyRealm = true)] [ParameterizedModule("test-string", 10)] -public partial class SecondaryParameterizedModule { } \ No newline at end of file +public partial class SecondaryParameterizedModule { } diff --git a/integ-tests/SecondarySutProject/ParameterizedModules/SomeRuntimeDependency.cs b/integ-tests/SecondarySutProject/ParameterizedModules/SomeRuntimeDependency.cs index 27eaaa9..955605c 100644 --- a/integ-tests/SecondarySutProject/ParameterizedModules/SomeRuntimeDependency.cs +++ b/integ-tests/SecondarySutProject/ParameterizedModules/SomeRuntimeDependency.cs @@ -1,8 +1,9 @@ namespace SecondarySutProject.ParameterizedModules; -public class SomeRuntimeDependency { - - public SomeRuntimeDependency(string someDependency, int intDependency, string cValue) { +public class SomeRuntimeDependency +{ + public SomeRuntimeDependency(string someDependency, int intDependency, string cValue) + { SomeDependency = someDependency; IntDependency = intDependency; CValue = cValue; @@ -13,4 +14,4 @@ public SomeRuntimeDependency(string someDependency, int intDependency, string cV public int IntDependency { get; } public string? CValue { get; } -} \ No newline at end of file +} diff --git a/integ-tests/SecondarySutProject/SecondarySutModule.cs b/integ-tests/SecondarySutProject/SecondarySutModule.cs index f99aa0e..8c86b04 100644 --- a/integ-tests/SecondarySutProject/SecondarySutModule.cs +++ b/integ-tests/SecondarySutProject/SecondarySutModule.cs @@ -5,4 +5,4 @@ namespace SecondarySutProject; [DependencyModule] [SutModule] -public partial class SecondarySutModule { } \ No newline at end of file +public partial class SecondarySutModule { } diff --git a/integ-tests/SutProject.NUnitTests/DataRowTests.cs b/integ-tests/SutProject.NUnitTests/DataRowTests.cs index aed1dea..1610ab5 100644 --- a/integ-tests/SutProject.NUnitTests/DataRowTests.cs +++ b/integ-tests/SutProject.NUnitTests/DataRowTests.cs @@ -12,14 +12,17 @@ namespace SutProject.NUnitTests; /// cannot express a row that covers the first parameters while the container covers the rest, which /// is what a module test with data is. [ModuleTestCase] is the same idea without that rule. /// -public class DataRowTests { - +public class DataRowTests +{ [ModuleTest(typeof(SutModule))] [ModuleTestCase(1)] [ModuleTestCase(2)] [ModuleTestCase(3)] public void RowSuppliesTheLeadingParameterAndTheContainerTheRest( - int number, ISingletonService singletonService) { + int number, + ISingletonService singletonService + ) + { Assert.That(number, Is.InRange(1, 3)); Assert.That(singletonService, Is.Not.Null, "resolved from the container, not from the row"); @@ -32,7 +35,11 @@ public void RowSuppliesTheLeadingParameterAndTheContainerTheRest( [ModuleTestCase("first", 1)] [ModuleTestCase("second", 2)] public void SeveralLeadingParametersComeFromTheRow( - string word, int number, ISingletonService singletonService) { + string word, + int number, + ISingletonService singletonService + ) + { Assert.That(word, Is.AnyOf("first", "second")); Assert.That(number, Is.AnyOf(1, 2)); Assert.That(singletonService, Is.Not.Null); @@ -41,7 +48,8 @@ public void SeveralLeadingParametersComeFromTheRow( /// A row can fill every parameter, leaving nothing for the container. [ModuleTest(typeof(SutModule))] [ModuleTestCase(4, 5)] - public void ARowMayCoverEveryParameter(int first, int second) { + public void ARowMayCoverEveryParameter(int first, int second) + { Assert.That(first + second, Is.EqualTo(9)); } @@ -50,7 +58,10 @@ public void ARowMayCoverEveryParameter(int first, int second) { [ModuleTestCase(10)] [ModuleTestCase(20)] public void RowsComposeWithInjectedValues( - int number, [InjectValues("supplied")] NeedsAValue needsAValue) { + int number, + [InjectValues("supplied")] NeedsAValue needsAValue + ) + { Assert.That(number, Is.AnyOf(10, 20)); Assert.That(needsAValue.Text, Is.EqualTo("supplied")); Assert.That(needsAValue.SingletonService, Is.Not.Null); @@ -58,11 +69,13 @@ public void RowsComposeWithInjectedValues( [ModuleTest(typeof(SutModule))] [ModuleTestCase(1, TestName = "a row can name itself")] - public void NamedRow(int number) { + public void NamedRow(int number) + { Assert.That(number, Is.EqualTo(1)); } - public class NeedsAValue(ISingletonService singletonService, string text) { + public class NeedsAValue(ISingletonService singletonService, string text) + { public ISingletonService SingletonService { get; } = singletonService; public string Text { get; } = text; @@ -72,10 +85,11 @@ public class NeedsAValue(ISingletonService singletonService, string text) { /// /// Each row is its own test case, so each gets its own container — the same rule repetitions follow. /// -public class DDataRowReport { - +public class DDataRowReport +{ [Test] - public void EveryRowRanExactlyOnce() { + public void EveryRowRanExactlyOnce() + { Assert.That(DataRowTests.SeenNumbers, Is.EquivalentTo(new[] { 1, 2, 3 })); } } diff --git a/integ-tests/SutProject.NUnitTests/EnvironmentSeedingTests.cs b/integ-tests/SutProject.NUnitTests/EnvironmentSeedingTests.cs index 80a8cb3..f555c4e 100644 --- a/integ-tests/SutProject.NUnitTests/EnvironmentSeedingTests.cs +++ b/integ-tests/SutProject.NUnitTests/EnvironmentSeedingTests.cs @@ -13,7 +13,8 @@ namespace SutProject.NUnitTests; /// conditions through the real runner, which applies modules before the service-setup pass. /// [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] -public class SeededEnvironmentAttribute(string name) : Attribute, IModuleEnvironmentProvider { +public class SeededEnvironmentAttribute(string name) : Attribute, IModuleEnvironmentProvider +{ public IModuleEnvironment? ProvideEnvironment(MethodInfo testMethod) => new ModuleEnvironment(false, name); } @@ -27,11 +28,12 @@ public class GatedByEnvironment : IGatedByEnvironment { } [DependencyModule(OnlyRealm = true)] public partial class SeededEnvironmentModule { } -public class EnvironmentSeedingTests { - +public class EnvironmentSeedingTests +{ [ModuleTest(typeof(SeededEnvironmentModule))] [SeededEnvironment("seeded-environment")] - public void AGatedRegistrationAppliesUnderTheSeededEnvironment(IServiceProvider provider) { + public void AGatedRegistrationAppliesUnderTheSeededEnvironment(IServiceProvider provider) + { Assert.That(provider.GetService(), Is.Not.Null); } @@ -40,7 +42,8 @@ public void AGatedRegistrationAppliesUnderTheSeededEnvironment(IServiceProvider /// condition was never compiled in. /// [ModuleTest(typeof(SeededEnvironmentModule))] - public void TheSameRegistrationIsAbsentWithoutASeed(IServiceProvider provider) { + public void TheSameRegistrationIsAbsentWithoutASeed(IServiceProvider provider) + { Assert.That(provider.GetService(), Is.Null); } } diff --git a/integ-tests/SutProject.NUnitTests/FakeItEasy/FakeItEasyTests.cs b/integ-tests/SutProject.NUnitTests/FakeItEasy/FakeItEasyTests.cs index dcaffc7..92f090f 100644 --- a/integ-tests/SutProject.NUnitTests/FakeItEasy/FakeItEasyTests.cs +++ b/integ-tests/SutProject.NUnitTests/FakeItEasy/FakeItEasyTests.cs @@ -10,14 +10,16 @@ namespace SutProject.NUnitTests.FakeItEasy; /// The FakeItEasy package, unchanged, against NUnit. /// [FakeItEasySupport] -public class FakeItEasyTests { - +public class FakeItEasyTests +{ [ModuleTest] [SutModule] public void MockTest( [Mock] IDependencyOne dependencyOne, [Mock] IScopedService scopedService, - ISingletonService singletonService) { + ISingletonService singletonService + ) + { A.CallTo(() => dependencyOne.SingletonService).Returns(singletonService); A.CallTo(() => dependencyOne.ScopedService).Returns(scopedService); @@ -28,7 +30,8 @@ public void MockTest( /// The injected fake is the thing you configure, unlike Moq — no unwrapping step. [ModuleTest] [SutModule] - public void TheInjectedInstanceIsTheFake([Mock] IDependencyOne dependencyOne) { + public void TheInjectedInstanceIsTheFake([Mock] IDependencyOne dependencyOne) + { Assert.That(Fake.GetFakeManager(dependencyOne), Is.Not.Null); } } diff --git a/integ-tests/SutProject.NUnitTests/IterationLifetimeTests.cs b/integ-tests/SutProject.NUnitTests/IterationLifetimeTests.cs index 88d2a94..a4f666a 100644 --- a/integ-tests/SutProject.NUnitTests/IterationLifetimeTests.cs +++ b/integ-tests/SutProject.NUnitTests/IterationLifetimeTests.cs @@ -12,23 +12,22 @@ public partial class LifetimeModule { } /// than inferred. /// [ScopedService(Realm = typeof(LifetimeModule))] -public class TrackedService : IDisposable { - +public class TrackedService : IDisposable +{ private static int _next; public static readonly List Constructed = []; public static readonly List Disposed = []; - public TrackedService() { + public TrackedService() + { Id = Interlocked.Increment(ref _next); Constructed.Add(Id); } - public int Id { - get; - } + public int Id { get; } public void Dispose() => Disposed.Add(Id); } @@ -43,8 +42,8 @@ public int Id { /// because the report at the end reads what they recorded; NUnit runs fixtures within an assembly /// in alphabetical order. /// -public class ARepeatedModuleTests { - +public class ARepeatedModuleTests +{ public static readonly List Log = []; public static readonly List ServiceIds = []; @@ -57,42 +56,54 @@ public class ARepeatedModuleTests { [ModuleTest(typeof(LifetimeModule))] [Repeat(3)] - public void EachRepetitionGetsItsOwnContainer(TrackedService trackedService) { + public void EachRepetitionGetsItsOwnContainer(TrackedService trackedService) + { Log.Add($"test:{trackedService.Id}"); ServiceIds.Add(trackedService.Id); } } -public class BRetriedModuleTests { - +public class BRetriedModuleTests +{ private static int _attempts; public static readonly List ServiceIds = []; [ModuleTest(typeof(LifetimeModule))] [Retry(3)] - public void EachRetryAttemptGetsItsOwnContainer(TrackedService trackedService) { + public void EachRetryAttemptGetsItsOwnContainer(TrackedService trackedService) + { ServiceIds.Add(trackedService.Id); _attempts++; - Assert.That(_attempts, Is.EqualTo(3), "fails the first two attempts on purpose, passes the third"); + Assert.That( + _attempts, + Is.EqualTo(3), + "fails the first two attempts on purpose, passes the third" + ); } } -public class CLifetimeReport { - +public class CLifetimeReport +{ /// /// The container has to outlive setup and teardown, not sit between them. Wrapping only the test /// method would order this setup, open, test, close, teardown — leaving [SetUp] running /// before the container exists and [TearDown] after it is gone. /// [Test] - public void SetUpAndTearDownRunInsideTheContainersLifetime() { - Assert.That(ARepeatedModuleTests.Log, Has.Count.EqualTo(9), "three iterations of setup, test, teardown"); - - for (var i = 0; i < 3; i++) { + public void SetUpAndTearDownRunInsideTheContainersLifetime() + { + Assert.That( + ARepeatedModuleTests.Log, + Has.Count.EqualTo(9), + "three iterations of setup, test, teardown" + ); + + for (var i = 0; i < 3; i++) + { Assert.That(ARepeatedModuleTests.Log[i * 3], Is.EqualTo("setup")); Assert.That(ARepeatedModuleTests.Log[i * 3 + 1], Does.StartWith("test:")); Assert.That(ARepeatedModuleTests.Log[i * 3 + 2], Is.EqualTo("teardown")); @@ -100,22 +111,30 @@ public void SetUpAndTearDownRunInsideTheContainersLifetime() { } [Test] - public void NoServiceInstanceIsSharedBetweenIterations() { + public void NoServiceInstanceIsSharedBetweenIterations() + { var repeated = ARepeatedModuleTests.ServiceIds; var retried = BRetriedModuleTests.ServiceIds; Assert.That(repeated, Has.Count.EqualTo(3)); Assert.That(retried, Has.Count.EqualTo(3)); - Assert.That(repeated.Concat(retried).Distinct().Count(), Is.EqualTo(6), - "three repetitions and three retry attempts, six containers, six instances"); + Assert.That( + repeated.Concat(retried).Distinct().Count(), + Is.EqualTo(6), + "three repetitions and three retry attempts, six containers, six instances" + ); } [Test] - public void EveryIterationsServicesWereDisposedWithItsContainer() { + public void EveryIterationsServicesWereDisposedWithItsContainer() + { var iterationIds = ARepeatedModuleTests.ServiceIds.Concat(BRetriedModuleTests.ServiceIds); - Assert.That(TrackedService.Disposed, Is.SupersetOf(iterationIds), - "the container is torn down at the end of the iteration, not left to the fixture"); + Assert.That( + TrackedService.Disposed, + Is.SupersetOf(iterationIds), + "the container is torn down at the end of the iteration, not left to the fixture" + ); } } diff --git a/integ-tests/SutProject.NUnitTests/ModuleLoadingTests.cs b/integ-tests/SutProject.NUnitTests/ModuleLoadingTests.cs index 83f4559..c4ca047 100644 --- a/integ-tests/SutProject.NUnitTests/ModuleLoadingTests.cs +++ b/integ-tests/SutProject.NUnitTests/ModuleLoadingTests.cs @@ -20,11 +20,12 @@ public class ExtraService { } /// [Test] does, so a module test fixture needs no class-level attribute — the same as the /// xUnit integration. /// -public class ModuleLoadingTests { - +public class ModuleLoadingTests +{ /// Modules named on the attribute itself. [ModuleTest(typeof(SutModule))] - public void LoadsAModuleNamedByType(ISingletonService singletonService) { + public void LoadsAModuleNamedByType(ISingletonService singletonService) + { Assert.That(singletonService, Is.Not.Null); Assert.That(singletonService.GetName(), Is.EqualTo(nameof(SingletonService))); } @@ -32,25 +33,29 @@ public void LoadsAModuleNamedByType(ISingletonService singletonService) { /// The generated module attribute, which reaches the same loading by another route. [ModuleTest] [SutModule] - public void LoadsAModuleNamedByItsGeneratedAttribute(IDependencyOne dependencyOne) { + public void LoadsAModuleNamedByItsGeneratedAttribute(IDependencyOne dependencyOne) + { Assert.That(dependencyOne.SingletonService, Is.Not.Null); Assert.That(dependencyOne.ScopedService, Is.Not.Null); } [ModuleTest(typeof(SutModule), typeof(ExtraModule))] - public void LoadsSeveralModules(ISingletonService singletonService, ExtraService extraService) { + public void LoadsSeveralModules(ISingletonService singletonService, ExtraService extraService) + { Assert.That(singletonService, Is.Not.Null); Assert.That(extraService, Is.Not.Null); } [ModuleTest] - public void TakesNoModulesAtAll() { + public void TakesNoModulesAtAll() + { Assert.Pass("a module test need not name a module"); } /// The container itself, which cannot be resolved from itself. [ModuleTest(typeof(SutModule))] - public void InjectsTheServiceProvider(IServiceProvider serviceProvider) { + public void InjectsTheServiceProvider(IServiceProvider serviceProvider) + { Assert.That(serviceProvider.GetService(), Is.Not.Null); } @@ -59,18 +64,24 @@ public void InjectsTheServiceProvider(IServiceProvider serviceProvider) { /// class under test without registering it. /// [ModuleTest(typeof(SutModule))] - public void ConstructsAnUnregisteredConcreteType(NeedsASingleton needsASingleton) { + public void ConstructsAnUnregisteredConcreteType(NeedsASingleton needsASingleton) + { Assert.That(needsASingleton.SingletonService, Is.Not.Null); } [ModuleTest(typeof(SutModule))] - public void PublishesTheTestCaseInfo(ITestCaseInfo testCaseInfo, ISingletonService singletonService) { + public void PublishesTheTestCaseInfo( + ITestCaseInfo testCaseInfo, + ISingletonService singletonService + ) + { Assert.That(testCaseInfo.TestMethod.Name, Is.EqualTo(nameof(PublishesTheTestCaseInfo))); Assert.That(testCaseInfo.TestMethodArguments, Has.Count.EqualTo(2)); Assert.That(testCaseInfo.TestMethodArguments[1], Is.SameAs(singletonService)); } - public class NeedsASingleton(ISingletonService singletonService) { + public class NeedsASingleton(ISingletonService singletonService) + { public ISingletonService SingletonService { get; } = singletonService; } } diff --git a/integ-tests/SutProject.NUnitTests/Moq/MoqTests.cs b/integ-tests/SutProject.NUnitTests/Moq/MoqTests.cs index e5ff3b3..a6fb00d 100644 --- a/integ-tests/SutProject.NUnitTests/Moq/MoqTests.cs +++ b/integ-tests/SutProject.NUnitTests/Moq/MoqTests.cs @@ -10,12 +10,15 @@ namespace SutProject.NUnitTests.Moq; /// The Moq package, unchanged, against NUnit. /// [MoqSupport] -public class MoqTests { - +public class MoqTests +{ [ModuleTest] [SutModule] public void MockTest( - [Mock] Mock dependencyOne, ISingletonService singletonService) { + [Mock] Mock dependencyOne, + ISingletonService singletonService + ) + { dependencyOne.Setup(mock => mock.SingletonService).Returns(singletonService); Assert.That(dependencyOne.Object.SingletonService, Is.SameAs(singletonService)); @@ -30,7 +33,8 @@ public void MockTest( [ModuleTest] [SutModule] [TestExport(typeof(ISingletonService), Implementation = typeof(ExportedSingletonService))] - public void TestExportBeatsAMockOfTheSameService(ISingletonService singletonService) { + public void TestExportBeatsAMockOfTheSameService(ISingletonService singletonService) + { Assert.That(singletonService, Is.TypeOf()); } } diff --git a/integ-tests/SutProject.NUnitTests/NSubstitute/NSubstituteTests.cs b/integ-tests/SutProject.NUnitTests/NSubstitute/NSubstituteTests.cs index c70916b..42c4af9 100644 --- a/integ-tests/SutProject.NUnitTests/NSubstitute/NSubstituteTests.cs +++ b/integ-tests/SutProject.NUnitTests/NSubstitute/NSubstituteTests.cs @@ -17,14 +17,16 @@ namespace SutProject.NUnitTests.NSubstitute; /// each other. /// [NSubstituteSupport] -public class NSubstituteTests { - +public class NSubstituteTests +{ [ModuleTest] [SutModule] public void MockTest( [Mock] IDependencyOne dependencyOne, [Mock] IScopedService scopedService, - ISingletonService singletonService) { + ISingletonService singletonService + ) + { dependencyOne.SingletonService.Returns(singletonService); dependencyOne.ScopedService.Returns(scopedService); @@ -36,7 +38,10 @@ public void MockTest( [ModuleTest] [SutModule] public void AMockReplacesTheRegistrationForTheWholeContainer( - [Mock] IScopedService scopedService, IDependencyOne dependencyOne) { + [Mock] IScopedService scopedService, + IDependencyOne dependencyOne + ) + { Assert.That(dependencyOne.ScopedService, Is.SameAs(scopedService)); } @@ -47,8 +52,13 @@ public void AMockReplacesTheRegistrationForTheWholeContainer( [ModuleTest] [SutModule] [Repeat(3)] - public void EachIterationGetsAFreshMock([Mock] IScopedService scopedService) { - Assert.That(Seen.Add(scopedService), Is.True, "a mock instance is never reused across iterations"); + public void EachIterationGetsAFreshMock([Mock] IScopedService scopedService) + { + Assert.That( + Seen.Add(scopedService), + Is.True, + "a mock instance is never reused across iterations" + ); } private static readonly HashSet Seen = []; diff --git a/integ-tests/SutProject.NUnitTests/ServiceProviderBuilderPrecedenceTests.cs b/integ-tests/SutProject.NUnitTests/ServiceProviderBuilderPrecedenceTests.cs index 170cf89..13949a2 100644 --- a/integ-tests/SutProject.NUnitTests/ServiceProviderBuilderPrecedenceTests.cs +++ b/integ-tests/SutProject.NUnitTests/ServiceProviderBuilderPrecedenceTests.cs @@ -8,20 +8,28 @@ namespace SutProject.NUnitTests; /// /// Records which actually built the container. /// -public interface IProviderBuiltBy { +public interface IProviderBuiltBy +{ string Scope { get; } } -public class ProviderBuiltBy(string scope) : IProviderBuiltBy { +public class ProviderBuiltBy(string scope) : IProviderBuiltBy +{ public string Scope => scope; } /// /// A builder that stamps the container with the scope it was declared at. /// -public class ScopeStampingProviderAttribute(string scope) : Attribute, IServiceProviderBuilderAttribute { +public class ScopeStampingProviderAttribute(string scope) + : Attribute, + IServiceProviderBuilderAttribute +{ public IServiceProvider BuildServiceProvider( - ITestMethodContext testMethod, IServiceCollection serviceCollection) { + ITestMethodContext testMethod, + IServiceCollection serviceCollection + ) + { serviceCollection.AddSingleton(new ProviderBuiltBy(scope)); return serviceCollection.BuildServiceProvider(); @@ -34,16 +42,18 @@ public IServiceProvider BuildServiceProvider( /// overridden by a broader default. /// [ScopeStampingProvider("class")] -public class ServiceProviderBuilderPrecedenceTests { - +public class ServiceProviderBuilderPrecedenceTests +{ [ModuleTest] [ScopeStampingProvider("method")] - public void MethodBeatsClass(IProviderBuiltBy builtBy) { + public void MethodBeatsClass(IProviderBuiltBy builtBy) + { Assert.That(builtBy.Scope, Is.EqualTo("method")); } [ModuleTest] - public void ClassAppliesWhenTheMethodDeclaresNone(IProviderBuiltBy builtBy) { + public void ClassAppliesWhenTheMethodDeclaresNone(IProviderBuiltBy builtBy) + { Assert.That(builtBy.Scope, Is.EqualTo("class")); } } diff --git a/integ-tests/SutProject.NUnitTests/TestExportTests.cs b/integ-tests/SutProject.NUnitTests/TestExportTests.cs index 24f5afb..de32e51 100644 --- a/integ-tests/SutProject.NUnitTests/TestExportTests.cs +++ b/integ-tests/SutProject.NUnitTests/TestExportTests.cs @@ -8,7 +8,8 @@ namespace SutProject.NUnitTests; /// /// Stands in for the real singleton, so a test can tell which of two registrations survived. /// -public class ExportedSingletonService : ISingletonService { +public class ExportedSingletonService : ISingletonService +{ public string GetName() => nameof(ExportedSingletonService); } @@ -20,12 +21,13 @@ public class ExportedSingletonService : ISingletonService { /// is why it is available here at all — it registers through ITestServiceSetupAttribute and /// never needed a test framework. /// -public class TestExportTests { - +public class TestExportTests +{ [ModuleTest] [SutModule] [TestExport(typeof(ISingletonService), Implementation = typeof(ExportedSingletonService))] - public void OverridesARegistrationForOneTest(ISingletonService singletonService) { + public void OverridesARegistrationForOneTest(ISingletonService singletonService) + { Assert.That(singletonService, Is.TypeOf()); } @@ -35,15 +37,23 @@ public void OverridesARegistrationForOneTest(ISingletonService singletonService) /// [ModuleTest] [SutModule] - public void TheOverrideDoesNotLeakIntoTheNextTest(ISingletonService singletonService) { + public void TheOverrideDoesNotLeakIntoTheNextTest(ISingletonService singletonService) + { Assert.That(singletonService, Is.TypeOf()); } [ModuleTest] [SutModule] - [TestExport(typeof(ISingletonService), Implementation = typeof(ExportedSingletonService), - Lifetime = ServiceLifetime.Singleton)] - public void HonoursTheLifetimeItIsGiven(ISingletonService first, IServiceProvider serviceProvider) { + [TestExport( + typeof(ISingletonService), + Implementation = typeof(ExportedSingletonService), + Lifetime = ServiceLifetime.Singleton + )] + public void HonoursTheLifetimeItIsGiven( + ISingletonService first, + IServiceProvider serviceProvider + ) + { Assert.That(serviceProvider.GetRequiredService(), Is.SameAs(first)); } } diff --git a/integ-tests/SutProject.Tests/CircularReferenceModules/CircularModuleTests.cs b/integ-tests/SutProject.Tests/CircularReferenceModules/CircularModuleTests.cs index 8cda3a8..0aa354a 100644 --- a/integ-tests/SutProject.Tests/CircularReferenceModules/CircularModuleTests.cs +++ b/integ-tests/SutProject.Tests/CircularReferenceModules/CircularModuleTests.cs @@ -4,29 +4,30 @@ namespace SutProject.Tests.CircularReferenceModules; -public class CircularModuleTests { - +public class CircularModuleTests +{ [ModuleTest] [ModuleA] - public void LoadModuleATest(ServiceA serviceA, ServiceB serviceB) { + public void LoadModuleATest(ServiceA serviceA, ServiceB serviceB) + { Assert.NotNull(serviceA); Assert.NotNull(serviceB); } - [ModuleTest] [ModuleB] - public void LoadModuleBTest(ServiceA serviceA, ServiceB serviceB) { + public void LoadModuleBTest(ServiceA serviceA, ServiceB serviceB) + { Assert.NotNull(serviceA); Assert.NotNull(serviceB); } - [ModuleTest] [ModuleA] [ModuleB] - public void LoadModuleBothTest(ServiceA serviceA, ServiceB serviceB) { + public void LoadModuleBothTest(ServiceA serviceA, ServiceB serviceB) + { Assert.NotNull(serviceA); Assert.NotNull(serviceB); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/ConventionTests/ConventionEdgeModules.cs b/integ-tests/SutProject.Tests/ConventionTests/ConventionEdgeModules.cs index 7006567..6399b26 100644 --- a/integ-tests/SutProject.Tests/ConventionTests/ConventionEdgeModules.cs +++ b/integ-tests/SutProject.Tests/ConventionTests/ConventionEdgeModules.cs @@ -1,5 +1,5 @@ -using DependencyModules.Runtime.Conventions; using DependencyModules.Runtime.Attributes; +using DependencyModules.Runtime.Conventions; using SecondarySutProject; using SutProject.Tests.ConventionTests.Nested; @@ -17,23 +17,28 @@ namespace SutProject.Tests.ConventionTests; // to use when the decorator or the service comes from an assembly you do not control. // --------------------------------------------------------------------------- -public interface IModuleDecorated { +public interface IModuleDecorated +{ string Describe(); } -public class ModuleDecoratedCore : IModuleDecorated { +public class ModuleDecoratedCore : IModuleDecorated +{ public string Describe() => "core"; } /// Carries no [Decorator]; the module names it instead. -public class ModuleDecoratedWrapper(IModuleDecorated inner) : IModuleDecorated { +public class ModuleDecoratedWrapper(IModuleDecorated inner) : IModuleDecorated +{ public string Describe() => $"wrapped({inner.Describe()})"; } [DependencyModule] [Decorate(typeof(IModuleDecorated), typeof(ModuleDecoratedWrapper))] -public partial class ConventionModuleDecorateModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionModuleDecorateModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { // The wrapper implements the interface too, so it would match. Excluding it by name is the // cost of declaring decoration on the module rather than on the class. conventions.RegisterAll().WithoutName("*Wrapper").AsSingleton(); @@ -44,28 +49,35 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // Two modules scanning one interface, composed into the same application. // --------------------------------------------------------------------------- -public interface IShared { +public interface IShared +{ string Name { get; } } -public class SharedFirst : IShared { +public class SharedFirst : IShared +{ public string Name => "first"; } -public class SharedSecond : IShared { +public class SharedSecond : IShared +{ public string Name => "second"; } [DependencyModule] -public partial class ConventionSharedFirstModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionSharedFirstModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().WithName("SharedFirst").AsSingleton(); } } [DependencyModule] -public partial class ConventionSharedSecondModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionSharedSecondModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().WithName("SharedSecond").AsSingleton(); conventions.RegisterAll().WithoutName("SharedFirst").AsSingleton(); } @@ -75,35 +87,48 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // Exact versus prefix namespaces, and the negative form. // --------------------------------------------------------------------------- -public interface INamespaceScanned { +public interface INamespaceScanned +{ string Name { get; } } -public class RootLevelScanned : INamespaceScanned { +public class RootLevelScanned : INamespaceScanned +{ public string Name => "root"; } /// Prefix filters reach into nested namespaces; exact ones do not. [DependencyModule] -public partial class ConventionPrefixNamespaceModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().InNamespaceOf().AsSingleton(); +public partial class ConventionPrefixNamespaceModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { + conventions + .RegisterAll() + .InNamespaceOf() + .AsSingleton(); } } [DependencyModule] -public partial class ConventionExactNamespaceModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll() +public partial class ConventionExactNamespaceModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { + conventions + .RegisterAll() .InExactNamespaces("SutProject.Tests.ConventionTests") .AsSingleton(); } } [DependencyModule] -public partial class ConventionExcludedNamespaceModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll() +public partial class ConventionExcludedNamespaceModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { + conventions + .RegisterAll() .NotInNamespaceOf() .AsSingleton(); } @@ -113,18 +138,22 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // An open generic implementation, resolved at several closings. // --------------------------------------------------------------------------- -public interface IOpenCache { +public interface IOpenCache +{ string Describe(); } /// Closes nothing, so it registers as the open generic. -public class OpenPassThroughCache : IOpenCache { +public class OpenPassThroughCache : IOpenCache +{ public string Describe() => "cache:" + typeof(T).Name; } [DependencyModule] -public partial class ConventionOpenGenericModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionOpenGenericModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll(typeof(IOpenCache<>)).AsSingleton(); } } @@ -133,36 +162,44 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // Lifetime, disposal, and an internal candidate. // --------------------------------------------------------------------------- -public interface IScopedByConvention { +public interface IScopedByConvention +{ Guid Id { get; } } -public class ScopedByConvention : IScopedByConvention { +public class ScopedByConvention : IScopedByConvention +{ public Guid Id { get; } = Guid.NewGuid(); } -public interface IDisposableByConvention { +public interface IDisposableByConvention +{ bool Disposed { get; } } -public class DisposableByConvention : IDisposableByConvention, IDisposable { +public class DisposableByConvention : IDisposableByConvention, IDisposable +{ public bool Disposed { get; private set; } public void Dispose() => Disposed = true; } /// Internal, and still a candidate — the compilation being built sees its own internals. -public interface IInternallyImplemented { +public interface IInternallyImplemented +{ string Name { get; } } -internal class InternalCandidate : IInternallyImplemented { +internal class InternalCandidate : IInternallyImplemented +{ public string Name => "internal"; } [DependencyModule] -public partial class ConventionLifetimeModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionLifetimeModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsScoped(); conventions.RegisterAll().AsSingleton(); conventions.RegisterAll().AsSingleton(); @@ -174,32 +211,38 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // ActivatorUtilities, so everything but the inner instance is resolved from the container. // --------------------------------------------------------------------------- -public interface IDecoratorDependency { +public interface IDecoratorDependency +{ string Value { get; } } -public class DecoratorDependency : IDecoratorDependency { +public class DecoratorDependency : IDecoratorDependency +{ public string Value => "dep"; } -public interface IDependentlyDecorated { +public interface IDependentlyDecorated +{ string Describe(); } -public class DependentlyDecoratedCore : IDependentlyDecorated { +public class DependentlyDecoratedCore : IDependentlyDecorated +{ public string Describe() => "core"; } [Decorator] public class DependentlyDecorating(IDependentlyDecorated inner, IDecoratorDependency dependency) - : IDependentlyDecorated { - + : IDependentlyDecorated +{ public string Describe() => $"{dependency.Value}({inner.Describe()})"; } [DependencyModule] -public partial class ConventionDecoratorDependencyModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionDecoratorDependencyModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSingleton(); conventions.RegisterAll().AsSingleton(); } @@ -210,9 +253,12 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // --------------------------------------------------------------------------- [DependencyModule] -public partial class ConventionFilteredScanModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll() +public partial class ConventionFilteredScanModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { + conventions + .RegisterAll() .InAssemblyOf() .WithName("First*") .AsSelf() diff --git a/integ-tests/SutProject.Tests/ConventionTests/ConventionEdgeTests.cs b/integ-tests/SutProject.Tests/ConventionTests/ConventionEdgeTests.cs index 453b3ce..f421c4c 100644 --- a/integ-tests/SutProject.Tests/ConventionTests/ConventionEdgeTests.cs +++ b/integ-tests/SutProject.Tests/ConventionTests/ConventionEdgeTests.cs @@ -9,9 +9,10 @@ namespace SutProject.Tests.ConventionTests; /// /// The corners of convention registration. /// -public class ConventionEdgeTests { - - private static ServiceProvider Provider(params IDependencyModule[] modules) { +public class ConventionEdgeTests +{ + private static ServiceProvider Provider(params IDependencyModule[] modules) + { var collection = new ServiceCollection(); collection.AddModules(modules); @@ -24,7 +25,8 @@ private static ServiceProvider Provider(params IDependencyModule[] modules) { /// decorator or the service comes from an assembly you do not control. /// [Fact] - public void ModuleLevelDecorateWrapsAConventionRegistration() { + public void ModuleLevelDecorateWrapsAConventionRegistration() + { var provider = Provider(new ConventionModuleDecorateModule()); Assert.Equal("wrapped(core)", provider.GetRequiredService().Describe()); @@ -36,7 +38,8 @@ public void ModuleLevelDecorateWrapsAConventionRegistration() { /// other. /// [Fact] - public void TwoModulesScanningOneInterfaceBothContribute() { + public void TwoModulesScanningOneInterfaceBothContribute() + { var names = Provider(new ConventionSharedFirstModule(), new ConventionSharedSecondModule()) .GetServices() .Select(shared => shared.Name) @@ -51,7 +54,8 @@ public void TwoModulesScanningOneInterfaceBothContribute() { /// "MyApp.Order.Handlers". /// [Fact] - public void InNamespaceOfReachesNestedNamespaces() { + public void InNamespaceOfReachesNestedNamespaces() + { var names = Provider(new ConventionPrefixNamespaceModule()) .GetServices() .Select(scanned => scanned.Name) @@ -63,7 +67,8 @@ public void InNamespaceOfReachesNestedNamespaces() { /// And InExactNamespaces is how you say you meant only that one. [Fact] - public void InExactNamespacesExcludesNestedNamespaces() { + public void InExactNamespacesExcludesNestedNamespaces() + { var names = Provider(new ConventionExactNamespaceModule()) .GetServices() .Select(scanned => scanned.Name) @@ -73,7 +78,8 @@ public void InExactNamespacesExcludesNestedNamespaces() { } [Fact] - public void NotInNamespaceOfExcludesThatNamespace() { + public void NotInNamespaceOfExcludesThatNamespace() + { var names = Provider(new ConventionExcludedNamespaceModule()) .GetServices() .Select(scanned => scanned.Name) @@ -87,7 +93,8 @@ public void NotInNamespaceOfExcludesThatNamespace() { /// closes it per request. /// [Fact] - public void AnOpenGenericRegistrationResolvesAtEveryClosing() { + public void AnOpenGenericRegistrationResolvesAtEveryClosing() + { var provider = Provider(new ConventionOpenGenericModule()); Assert.Equal("cache:String", provider.GetRequiredService>().Describe()); @@ -95,7 +102,8 @@ public void AnOpenGenericRegistrationResolvesAtEveryClosing() { } [Fact] - public void ADeclaredScopedLifetimeActuallyScopes() { + public void ADeclaredScopedLifetimeActuallyScopes() + { var provider = Provider(new ConventionLifetimeModule()); using var first = provider.CreateScope(); @@ -111,7 +119,8 @@ public void ADeclaredScopedLifetimeActuallyScopes() { /// The container owns what it constructed, however the registration was declared. /// [Fact] - public void AConventionRegisteredSingletonIsDisposedWithTheProvider() { + public void AConventionRegisteredSingletonIsDisposedWithTheProvider() + { var provider = Provider(new ConventionLifetimeModule()); var disposable = provider.GetRequiredService(); @@ -128,7 +137,8 @@ public void AConventionRegisteredSingletonIsDisposedWithTheProvider() { /// nothing can report, since it cannot see what it cannot see. /// [Fact] - public void AnInternalImplementationIsACandidateInThisCompilation() { + public void AnInternalImplementationIsACandidateInThisCompilation() + { var provider = Provider(new ConventionLifetimeModule()); Assert.Equal("internal", provider.GetRequiredService().Name); @@ -139,7 +149,8 @@ public void AnInternalImplementationIsACandidateInThisCompilation() { /// container, so a decorator's own dependencies can be convention-registered too. /// [Fact] - public void ADecoratorResolvesItsOwnConventionRegisteredDependencies() { + public void ADecoratorResolvesItsOwnConventionRegisteredDependencies() + { var provider = Provider(new ConventionDecoratorDependencyModule()); Assert.Equal("dep(core)", provider.GetRequiredService().Describe()); @@ -149,7 +160,8 @@ public void ADecoratorResolvesItsOwnConventionRegisteredDependencies() { /// Filters and shapes apply to a metadata scan the same way they apply to local types. /// [Fact] - public void AFilteredMetadataScanRegistersTheConcreteType() { + public void AFilteredMetadataScanRegistersTheConcreteType() + { var provider = Provider(new ConventionFilteredScanModule()); Assert.Equal("first", provider.GetRequiredService().Name); diff --git a/integ-tests/SutProject.Tests/ConventionTests/ConventionFeatureModules.cs b/integ-tests/SutProject.Tests/ConventionTests/ConventionFeatureModules.cs index 1146c6a..7eac1c4 100644 --- a/integ-tests/SutProject.Tests/ConventionTests/ConventionFeatureModules.cs +++ b/integ-tests/SutProject.Tests/ConventionTests/ConventionFeatureModules.cs @@ -1,5 +1,5 @@ -using DependencyModules.Runtime.Conventions; using DependencyModules.Runtime.Attributes; +using DependencyModules.Runtime.Conventions; using SecondarySutProject; namespace SutProject.Tests.ConventionTests; @@ -14,17 +14,20 @@ namespace SutProject.Tests.ConventionTests; // Shape: AsSelf, AsSelfWithInterfaces, AlsoAsSelf. // --------------------------------------------------------------------------- -public interface IShapeService { +public interface IShapeService +{ string Name { get; } } public interface IAlsoShaped { } -public class SelfShaped : IShapeService { +public class SelfShaped : IShapeService +{ public string Name => "self"; } -public class CrossWiredShape : IShapeService, IAlsoShaped, IDisposable { +public class CrossWiredShape : IShapeService, IAlsoShaped, IDisposable +{ public string Name => "crosswired"; public void Dispose() { } @@ -33,14 +36,17 @@ public void Dispose() { } /// The only implementor of its interface, so AlsoAsSelf has one match to resolve. public interface IAlsoSelfService { } -public class AlsoSelfShape : IShapeService, IAlsoSelfService { +public class AlsoSelfShape : IShapeService, IAlsoSelfService +{ public string Name => "alsoself"; } /// Registers the concrete type instead of the interface. [DependencyModule] -public partial class ConventionAsSelfModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionAsSelfModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSelf().AsSingleton(); } } @@ -50,16 +56,20 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// which is reachable but is never what "as its interfaces" means. /// [DependencyModule] -public partial class ConventionCrossWireModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionCrossWireModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSelfWithInterfaces().AsSingleton(); } } /// Registers the matched interface and the concrete type, sharing one instance. [DependencyModule] -public partial class ConventionAlsoAsSelfModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionAlsoAsSelfModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AlsoAsSelf().AsSingleton(); } } @@ -72,44 +82,55 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { [AttributeUsage(AttributeTargets.Class)] public class PolicyAttribute : Attribute { } -public interface IFiltered { +public interface IFiltered +{ string Name { get; } } [Policy] -public class MarkedRepository : IFiltered { +public class MarkedRepository : IFiltered +{ public string Name => "marked"; } -public class UnmarkedRepository : IFiltered { +public class UnmarkedRepository : IFiltered +{ public string Name => "unmarked"; } -public class MarkedHelper : IFiltered { +public class MarkedHelper : IFiltered +{ public string Name => "helper"; } /// Only the type carrying the attribute. [DependencyModule] -public partial class ConventionAttributeFilterModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionAttributeFilterModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().WithAttribute().AsSingleton(); } } /// Only the names matching the glob. [DependencyModule] -public partial class ConventionNameFilterModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionNameFilterModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().WithName("*Repository").AsSingleton(); } } /// Selected by namespace alone, with no interface to match on. [DependencyModule] -public partial class ConventionNamespaceModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll() +public partial class ConventionNamespaceModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { + conventions + .RegisterAll() .InNamespaceOf() .WithName("NamespaceOnly*") .AsSelf() @@ -139,16 +160,20 @@ public class ExplicitlyRegistered : IExplicitSource, IExplicitTarget { } /// Registers each match as the interface named after it. [DependencyModule] -public partial class ConventionMatchingInterfaceModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionMatchingInterfaceModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsMatchingInterface().AsSingleton(); } } /// Registers every match as one named service type. [DependencyModule] -public partial class ConventionAsModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionAsModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().As().AsSingleton(); } } @@ -157,38 +182,47 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // Registration type and service key. // --------------------------------------------------------------------------- -public interface IKeyedService { +public interface IKeyedService +{ string Name { get; } } -public class KeyedOne : IKeyedService { +public class KeyedOne : IKeyedService +{ public string Name => "keyed-one"; } /// Registered under a service key. [DependencyModule] -public partial class ConventionKeyModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionKeyModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().WithKey("primary").AsSingleton(); } } -public interface ITriedService { +public interface ITriedService +{ string Name { get; } } -public class TriedOne : ITriedService { +public class TriedOne : ITriedService +{ public string Name => "one"; } -public class TriedTwo : ITriedService { +public class TriedTwo : ITriedService +{ public string Name => "two"; } /// Try registers the service type once and skips the second match. [DependencyModule] -public partial class ConventionUsingModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionUsingModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().Using(RegistrationType.Try).AsSingleton(); } } @@ -204,8 +238,10 @@ public interface ISecondRole { } public class TwoRoles : IFirstRole, ISecondRole { } [DependencyModule] -public partial class ConventionTwoRolesModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionTwoRolesModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSingleton(); conventions.RegisterAll().AsScoped(); } @@ -220,8 +256,10 @@ public class OrderShipped { } public class OrderEvents : INotification, INotification { } [DependencyModule] -public partial class ConventionClosingsModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionClosingsModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll(typeof(INotification<>)).AsTransient(); } } @@ -230,7 +268,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // Conventions and decorators together — the MediatR shape. // --------------------------------------------------------------------------- -public interface IRequestHandler { +public interface IRequestHandler +{ TResponse Handle(TRequest request); } @@ -238,21 +277,25 @@ public class CreateThing { } public class RenameThing { } -public class ThingResult { +public class ThingResult +{ public string Value { get; set; } = ""; } -public class CreateThingHandler : IRequestHandler { +public class CreateThingHandler : IRequestHandler +{ public ThingResult Handle(CreateThing request) => new() { Value = "created" }; } -public class RenameThingHandler : IRequestHandler { +public class RenameThingHandler : IRequestHandler +{ public ThingResult Handle(RenameThing request) => new() { Value = "renamed" }; } /// Records what the decorator saw, so a test can prove it ran. [SingletonService] -public class HandlerLog { +public class HandlerLog +{ public List Lines { get; } = new(); } @@ -262,10 +305,12 @@ public class HandlerLog { /// [Decorator] public class LoggingRequestHandler( - IRequestHandler inner, HandlerLog log) - : IRequestHandler { - - public TResponse Handle(TRequest request) { + IRequestHandler inner, + HandlerLog log +) : IRequestHandler +{ + public TResponse Handle(TRequest request) + { log.Lines.Add("handling " + typeof(TRequest).Name); return inner.Handle(request); @@ -273,8 +318,10 @@ public TResponse Handle(TRequest request) { } [DependencyModule] -public partial class ConventionDecoratedHandlerModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionDecoratedHandlerModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll(typeof(IRequestHandler<,>)).AsScoped(); } } @@ -284,10 +331,10 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // --------------------------------------------------------------------------- [DependencyModule] -public partial class ConventionAssemblyScanModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll() - .InAssemblyOf() - .AsSingleton(); +public partial class ConventionAssemblyScanModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { + conventions.RegisterAll().InAssemblyOf().AsSingleton(); } } diff --git a/integ-tests/SutProject.Tests/ConventionTests/ConventionFeatureTests.cs b/integ-tests/SutProject.Tests/ConventionTests/ConventionFeatureTests.cs index d739bc4..bfae912 100644 --- a/integ-tests/SutProject.Tests/ConventionTests/ConventionFeatureTests.cs +++ b/integ-tests/SutProject.Tests/ConventionTests/ConventionFeatureTests.cs @@ -14,9 +14,12 @@ namespace SutProject.Tests.ConventionTests; /// colliding, or — for InAssemblyOf — that a genuine compile-time assembly reference is what /// gets scanned. That is what this file is for. /// -public class ConventionFeatureTests { - - private static ServiceProvider Provider(params DependencyModules.Runtime.Interfaces.IDependencyModule[] modules) { +public class ConventionFeatureTests +{ + private static ServiceProvider Provider( + params DependencyModules.Runtime.Interfaces.IDependencyModule[] modules + ) + { var collection = new ServiceCollection(); collection.AddModules(modules); @@ -25,8 +28,9 @@ private static ServiceProvider Provider(params DependencyModules.Runtime.Interfa } private static IServiceCollection Collection( - params DependencyModules.Runtime.Interfaces.IDependencyModule[] modules) { - + params DependencyModules.Runtime.Interfaces.IDependencyModule[] modules + ) + { var collection = new ServiceCollection(); collection.AddModules(modules); @@ -35,7 +39,8 @@ private static IServiceCollection Collection( } [Fact] - public void AsSelfRegistersTheConcreteType() { + public void AsSelfRegistersTheConcreteType() + { var provider = Provider(new ConventionAsSelfModule()); Assert.NotNull(provider.GetService()); @@ -43,7 +48,8 @@ public void AsSelfRegistersTheConcreteType() { } [Fact] - public void AsSelfWithInterfacesSharesOneInstanceAndSkipsSystemInterfaces() { + public void AsSelfWithInterfacesSharesOneInstanceAndSkipsSystemInterfaces() + { var provider = Provider(new ConventionCrossWireModule()); var asConcrete = provider.GetRequiredService(); @@ -56,7 +62,8 @@ public void AsSelfWithInterfacesSharesOneInstanceAndSkipsSystemInterfaces() { } [Fact] - public void AlsoAsSelfRegistersBothAndSharesOneInstance() { + public void AlsoAsSelfRegistersBothAndSharesOneInstance() + { var provider = Provider(new ConventionAlsoAsSelfModule()); var asInterface = provider.GetRequiredService(); @@ -69,7 +76,8 @@ public void AlsoAsSelfRegistersBothAndSharesOneInstance() { } [Fact] - public void WithAttributeSelectsOnlyMarkedTypes() { + public void WithAttributeSelectsOnlyMarkedTypes() + { var services = Provider(new ConventionAttributeFilterModule()) .GetServices() .Select(service => service.Name) @@ -79,7 +87,8 @@ public void WithAttributeSelectsOnlyMarkedTypes() { } [Fact] - public void WithNameSelectsOnTheGlob() { + public void WithNameSelectsOnTheGlob() + { var services = Provider(new ConventionNameFilterModule()) .GetServices() .Select(service => service.Name) @@ -93,7 +102,8 @@ public void WithNameSelectsOnTheGlob() { /// A concrete type with no interface, selected by namespace and name alone. /// [Fact] - public void RegisterAllWithFiltersRegistersTypesThatImplementNothing() { + public void RegisterAllWithFiltersRegistersTypesThatImplementNothing() + { var provider = Provider(new ConventionNamespaceModule()); Assert.NotNull(provider.GetService()); @@ -101,7 +111,8 @@ public void RegisterAllWithFiltersRegistersTypesThatImplementNothing() { } [Fact] - public void AsMatchingInterfaceRegistersRenamerAsIRenamer() { + public void AsMatchingInterfaceRegistersRenamerAsIRenamer() + { var provider = Provider(new ConventionMatchingInterfaceModule()); Assert.IsType(provider.GetRequiredService()); @@ -109,7 +120,8 @@ public void AsMatchingInterfaceRegistersRenamerAsIRenamer() { } [Fact] - public void AsRegistersUnderTheNamedServiceType() { + public void AsRegistersUnderTheNamedServiceType() + { var provider = Provider(new ConventionAsModule()); Assert.IsType(provider.GetRequiredService()); @@ -117,7 +129,8 @@ public void AsRegistersUnderTheNamedServiceType() { } [Fact] - public void WithKeyRegistersUnderAServiceKey() { + public void WithKeyRegistersUnderAServiceKey() + { var provider = Provider(new ConventionKeyModule()); Assert.IsType(provider.GetRequiredKeyedService("primary")); @@ -125,26 +138,31 @@ public void WithKeyRegistersUnderAServiceKey() { } [Fact] - public void UsingTryRegistersTheServiceTypeOnce() { + public void UsingTryRegistersTheServiceTypeOnce() + { Assert.Single( Collection(new ConventionUsingModule()), - descriptor => descriptor.ServiceType == typeof(ITriedService)); + descriptor => descriptor.ServiceType == typeof(ITriedService) + ); } /// /// A type filling two roles registers as both, each with its own lifetime. /// [Fact] - public void ATypeMatchedThroughTwoInterfacesRegistersAsBoth() { + public void ATypeMatchedThroughTwoInterfacesRegistersAsBoth() + { var collection = Collection(new ConventionTwoRolesModule()); Assert.Equal( ServiceLifetime.Singleton, - Assert.Single(collection, d => d.ServiceType == typeof(IFirstRole)).Lifetime); + Assert.Single(collection, d => d.ServiceType == typeof(IFirstRole)).Lifetime + ); Assert.Equal( ServiceLifetime.Scoped, - Assert.Single(collection, d => d.ServiceType == typeof(ISecondRole)).Lifetime); + Assert.Single(collection, d => d.ServiceType == typeof(ISecondRole)).Lifetime + ); } /// @@ -152,7 +170,8 @@ public void ATypeMatchedThroughTwoInterfacesRegistersAsBoth() { /// left the second silently unregistered. /// [Fact] - public void OneConventionRegistersEveryClosing() { + public void OneConventionRegistersEveryClosing() + { var provider = Provider(new ConventionClosingsModule()); Assert.IsType(provider.GetRequiredService>()); @@ -163,7 +182,8 @@ public void OneConventionRegistersEveryClosing() { /// One open generic decorator over every handler a convention registered — the MediatR shape. /// [Fact] - public void ADecoratorWrapsEveryConventionRegisteredHandler() { + public void ADecoratorWrapsEveryConventionRegisteredHandler() + { var provider = Provider(new ConventionDecoratedHandlerModule()); var log = provider.GetRequiredService(); @@ -185,23 +205,30 @@ public void ADecoratorWrapsEveryConventionRegisteredHandler() { /// nothing, it would register as the open generic and make the whole module undecoratable. /// [Fact] - public void ADecoratorIsNotRegisteredAsAService() { + public void ADecoratorIsNotRegisteredAsAService() + { var collection = Collection(new ConventionDecoratedHandlerModule()); - Assert.DoesNotContain(collection, descriptor => descriptor.ServiceType.IsGenericTypeDefinition); + Assert.DoesNotContain( + collection, + descriptor => descriptor.ServiceType.IsGenericTypeDefinition + ); Assert.Equal( 2, collection.Count(descriptor => - descriptor.ServiceType.IsGenericType && - descriptor.ServiceType.GetGenericTypeDefinition() == typeof(IRequestHandler<,>))); + descriptor.ServiceType.IsGenericType + && descriptor.ServiceType.GetGenericTypeDefinition() == typeof(IRequestHandler<,>) + ) + ); } /// /// Scanning a genuinely referenced assembly, where there is no syntax to read. /// [Fact] - public void InAssemblyOfRegistersPublicTypesFromTheReferencedAssembly() { + public void InAssemblyOfRegistersPublicTypesFromTheReferencedAssembly() + { var policies = Provider(new ConventionAssemblyScanModule()) .GetServices() .Select(policy => policy.Name) diff --git a/integ-tests/SutProject.Tests/ConventionTests/ConventionInteractionModules.cs b/integ-tests/SutProject.Tests/ConventionTests/ConventionInteractionModules.cs index 56374ff..9591563 100644 --- a/integ-tests/SutProject.Tests/ConventionTests/ConventionInteractionModules.cs +++ b/integ-tests/SutProject.Tests/ConventionTests/ConventionInteractionModules.cs @@ -1,5 +1,5 @@ -using DependencyModules.Runtime.Conventions; using DependencyModules.Runtime.Attributes; +using DependencyModules.Runtime.Conventions; using DependencyModules.Runtime.Interception; namespace SutProject.Tests.ConventionTests; @@ -18,31 +18,38 @@ namespace SutProject.Tests.ConventionTests; /// Records what the interceptor saw. [SingletonService] -public class InterceptLog { +public class InterceptLog +{ public List Lines { get; } = new(); } [SingletonService] -public class RecordingInterceptor(InterceptLog log) : IInterceptor { - public TResult Intercept(InvocationContext context) { +public class RecordingInterceptor(InterceptLog log) : IInterceptor +{ + public TResult Intercept(InvocationContext context) + { log.Lines.Add("intercepted " + context.Caller.MemberName); return context.Proceed(); } } -public interface IInterceptedByConvention { +public interface IInterceptedByConvention +{ string Work(); } [Intercept(typeof(RecordingInterceptor))] -public class InterceptedByConvention : IInterceptedByConvention { +public class InterceptedByConvention : IInterceptedByConvention +{ public string Work() => "worked"; } [DependencyModule] -public partial class ConventionInterceptModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionInterceptModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSingleton(); } } @@ -51,27 +58,33 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // Two decorators with an order, over convention-registered services. // --------------------------------------------------------------------------- -public interface IOrdered { +public interface IOrdered +{ string Describe(); } -public class OrderedCore : IOrdered { +public class OrderedCore : IOrdered +{ public string Describe() => "core"; } [Decorator(Order = 10)] -public class InnerOrdered(IOrdered inner) : IOrdered { +public class InnerOrdered(IOrdered inner) : IOrdered +{ public string Describe() => $"inner({inner.Describe()})"; } [Decorator(Order = 20)] -public class OuterOrdered(IOrdered inner) : IOrdered { +public class OuterOrdered(IOrdered inner) : IOrdered +{ public string Describe() => $"outer({inner.Describe()})"; } [DependencyModule] -public partial class ConventionOrderedDecoratorModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionOrderedDecoratorModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSingleton(); } } @@ -80,22 +93,27 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // A keyed convention registration, decorated. // --------------------------------------------------------------------------- -public interface IKeyedAndDecorated { +public interface IKeyedAndDecorated +{ string Describe(); } -public class KeyedCore : IKeyedAndDecorated { +public class KeyedCore : IKeyedAndDecorated +{ public string Describe() => "core"; } [Decorator] -public class KeyedWrapper(IKeyedAndDecorated inner) : IKeyedAndDecorated { +public class KeyedWrapper(IKeyedAndDecorated inner) : IKeyedAndDecorated +{ public string Describe() => $"wrapped({inner.Describe()})"; } [DependencyModule] -public partial class ConventionKeyedDecoratedModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionKeyedDecoratedModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().WithKey("main").AsSingleton(); } } @@ -104,38 +122,47 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // Type shapes that are not plain classes: records, nested types, primary constructors. // --------------------------------------------------------------------------- -public interface IShaped { +public interface IShaped +{ string Name { get; } } -public record ShapedRecord : IShaped { +public record ShapedRecord : IShaped +{ public string Name => "record"; } public record struct NotACandidate; -public class Outer { - public class NestedShaped : IShaped { +public class Outer +{ + public class NestedShaped : IShaped + { public string Name => "nested"; } } -public interface IShapedDependency { +public interface IShapedDependency +{ string Value { get; } } -public class ShapedDependency : IShapedDependency { +public class ShapedDependency : IShapedDependency +{ public string Value => "dep"; } /// Primary constructor, injected from another convention registration. -public class PrimaryConstructorShaped(IShapedDependency dependency) : IShaped { +public class PrimaryConstructorShaped(IShapedDependency dependency) : IShaped +{ public string Name => "primary-" + dependency.Value; } [DependencyModule] -public partial class ConventionShapesModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionShapesModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSingleton(); conventions.RegisterAll().AsSingleton(); } @@ -145,17 +172,21 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // A realm module. OnlyRealm means it takes nothing that did not name it. // --------------------------------------------------------------------------- -public interface IRealmScoped { +public interface IRealmScoped +{ string Name { get; } } -public class RealmScoped : IRealmScoped { +public class RealmScoped : IRealmScoped +{ public string Name => "realm"; } [DependencyModule(OnlyRealm = true)] -public partial class ConventionRealmModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionRealmModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSingleton(); } } @@ -164,17 +195,21 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // A module composed from another. The conventions of a dependency come along with it. // --------------------------------------------------------------------------- -public interface IComposedService { +public interface IComposedService +{ string Name { get; } } -public class ComposedService : IComposedService { +public class ComposedService : IComposedService +{ public string Name => "composed"; } [DependencyModule] -public partial class ConventionDependencyModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionDependencyModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSingleton(); } } @@ -188,22 +223,27 @@ public partial class ConventionCompositionModule; // Environment conditions on convention candidates. // --------------------------------------------------------------------------- -public interface IConditionalByConvention { +public interface IConditionalByConvention +{ string Name { get; } } -public class AlwaysConditional : IConditionalByConvention { +public class AlwaysConditional : IConditionalByConvention +{ public string Name => "always"; } [IfEnvironment("Development")] -public class DevelopmentOnlyConditional : IConditionalByConvention { +public class DevelopmentOnlyConditional : IConditionalByConvention +{ public string Name => "development"; } [DependencyModule] -public partial class ConventionConditionalModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionConditionalModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSingleton(); } } diff --git a/integ-tests/SutProject.Tests/ConventionTests/ConventionInteractionTests.cs b/integ-tests/SutProject.Tests/ConventionTests/ConventionInteractionTests.cs index 7ef4612..6b3987f 100644 --- a/integ-tests/SutProject.Tests/ConventionTests/ConventionInteractionTests.cs +++ b/integ-tests/SutProject.Tests/ConventionTests/ConventionInteractionTests.cs @@ -13,11 +13,13 @@ namespace SutProject.Tests.ConventionTests; /// decoration do not know a service was registered by convention, conventions do not know a /// candidate is intercepted, and the type shapes people write are not all plain classes. /// -public class ConventionInteractionTests { - +public class ConventionInteractionTests +{ private static ServiceProvider Provider( - IModuleEnvironment? environment, params IDependencyModule[] modules) { - + IModuleEnvironment? environment, + params IDependencyModule[] modules + ) + { var collection = new ServiceCollection(); collection.AddModules(environment, modules); @@ -34,7 +36,8 @@ private static ServiceProvider Provider(params IDependencyModule[] modules) => /// ends up registered. /// [Fact] - public void AConventionRegisteredServiceIsIntercepted() { + public void AConventionRegisteredServiceIsIntercepted() + { var provider = Provider(new ConventionInterceptModule()); var service = provider.GetRequiredService(); @@ -48,7 +51,8 @@ public void AConventionRegisteredServiceIsIntercepted() { /// Two decorators nest by declared order, lower closest to the implementation. /// [Fact] - public void DecoratorsNestByOrderOverAConventionRegistration() { + public void DecoratorsNestByOrderOverAConventionRegistration() + { var provider = Provider(new ConventionOrderedDecoratorModule()); Assert.Equal("outer(inner(core))", provider.GetRequiredService().Describe()); @@ -58,12 +62,14 @@ public void DecoratorsNestByOrderOverAConventionRegistration() { /// Decoration rewrites a keyed registration in place, keeping the key. /// [Fact] - public void AKeyedConventionRegistrationIsDecorated() { + public void AKeyedConventionRegistrationIsDecorated() + { var provider = Provider(new ConventionKeyedDecoratedModule()); Assert.Equal( "wrapped(core)", - provider.GetRequiredKeyedService("main").Describe()); + provider.GetRequiredKeyedService("main").Describe() + ); } /// @@ -71,7 +77,8 @@ public void AKeyedConventionRegistrationIsDecorated() { /// convention registration can be injected into another. /// [Fact] - public void RecordsNestedTypesAndPrimaryConstructorsAreCandidates() { + public void RecordsNestedTypesAndPrimaryConstructorsAreCandidates() + { var names = Provider(new ConventionShapesModule()) .GetServices() .Select(shaped => shaped.Name) @@ -85,7 +92,8 @@ public void RecordsNestedTypesAndPrimaryConstructorsAreCandidates() { /// An OnlyRealm module takes its own convention registrations, which name it as their realm. /// [Fact] - public void ARealmModuleTakesItsOwnConventionRegistrations() { + public void ARealmModuleTakesItsOwnConventionRegistrations() + { var provider = Provider(new ConventionRealmModule()); Assert.Equal("realm", provider.GetRequiredService().Name); @@ -95,7 +103,8 @@ public void ARealmModuleTakesItsOwnConventionRegistrations() { /// Composing a module brings its conventions with it, the same as its attribute registrations. /// [Fact] - public void ComposingAModuleBringsItsConventions() { + public void ComposingAModuleBringsItsConventions() + { var provider = Provider(new ConventionCompositionModule()); Assert.Equal("composed", provider.GetRequiredService().Name); @@ -110,9 +119,14 @@ public void ComposingAModuleBringsItsConventions() { [InlineData("Development", new[] { "always", "development" })] [InlineData("Production", new[] { "always" })] public void EnvironmentConditionsApplyToConventionCandidates( - string environmentName, string[] expected) { - - var names = Provider(new ModuleEnvironment(environmentName), new ConventionConditionalModule()) + string environmentName, + string[] expected + ) + { + var names = Provider( + new ModuleEnvironment(environmentName), + new ConventionConditionalModule() + ) .GetServices() .Select(service => service.Name) .OrderBy(name => name) @@ -126,7 +140,8 @@ public void EnvironmentConditionsApplyToConventionCandidates( /// declaring module, so two modules scanning the same interface do not leak into each other. /// [Fact] - public void ConventionRegistrationsDoNotLeakBetweenModules() { + public void ConventionRegistrationsDoNotLeakBetweenModules() + { var onlyShapes = Provider(new ConventionShapesModule()); Assert.Empty(onlyShapes.GetServices()); diff --git a/integ-tests/SutProject.Tests/ConventionTests/ConventionModules.cs b/integ-tests/SutProject.Tests/ConventionTests/ConventionModules.cs index 5285351..b570e0c 100644 --- a/integ-tests/SutProject.Tests/ConventionTests/ConventionModules.cs +++ b/integ-tests/SutProject.Tests/ConventionTests/ConventionModules.cs @@ -1,5 +1,5 @@ -using DependencyModules.Runtime.Conventions; using DependencyModules.Runtime.Attributes; +using DependencyModules.Runtime.Conventions; namespace SutProject.Tests.ConventionTests; @@ -10,18 +10,21 @@ namespace SutProject.Tests.ConventionTests; // Direct declaration, and reach through interface inheritance. // --------------------------------------------------------------------------- -public interface IConventionService { +public interface IConventionService +{ string Name { get; } } /// Extends the scanned interface, so implementing it is a declared match. public interface IAuditedConventionService : IConventionService { } -public class DirectService : IConventionService { +public class DirectService : IConventionService +{ public string Name => "direct"; } -public class InheritedService : IAuditedConventionService { +public class InheritedService : IAuditedConventionService +{ public string Name => "inherited"; } @@ -31,19 +34,23 @@ public class InheritedService : IAuditedConventionService { // through a base class and matches only with IncludeBaseClasses(). // --------------------------------------------------------------------------- -public interface IBaseClassReachService { +public interface IBaseClassReachService +{ string Name { get; } } -public class DirectReachService : IBaseClassReachService { +public class DirectReachService : IBaseClassReachService +{ public string Name => "direct-reach"; } -public abstract class ReachServiceBase : IBaseClassReachService { +public abstract class ReachServiceBase : IBaseClassReachService +{ public abstract string Name { get; } } -public class ThroughBaseClass : ReachServiceBase { +public class ThroughBaseClass : ReachServiceBase +{ public override string Name => "through-base"; } @@ -51,7 +58,8 @@ public class ThroughBaseClass : ReachServiceBase { // Open generic scanned against concrete closings. // --------------------------------------------------------------------------- -public interface IConventionHandler { +public interface IConventionHandler +{ TOut Handle(TIn input); } @@ -59,15 +67,18 @@ public class CreateOrder { } public class RenameOrder { } -public class OrderId { +public class OrderId +{ public int Value { get; set; } } -public class CreateOrderHandler : IConventionHandler { +public class CreateOrderHandler : IConventionHandler +{ public OrderId Handle(CreateOrder input) => new() { Value = 1 }; } -public class RenameOrderHandler : IConventionHandler { +public class RenameOrderHandler : IConventionHandler +{ public OrderId Handle(RenameOrder input) => new() { Value = 2 }; } @@ -75,11 +86,13 @@ public class RenameOrderHandler : IConventionHandler { // A generic implementation passing its own parameter through, which registers open. // --------------------------------------------------------------------------- -public interface IConventionCache { +public interface IConventionCache +{ string Describe(); } -public class PassThroughCache : IConventionCache { +public class PassThroughCache : IConventionCache +{ public string Describe() => "open:" + typeof(T).Name; } @@ -88,13 +101,15 @@ public class PassThroughCache : IConventionCache { // closed construction the type actually implements. // --------------------------------------------------------------------------- -public interface IConventionStore { +public interface IConventionStore +{ string Describe(); } public interface IAuditedStore : IConventionStore { } -public class StringStore : IAuditedStore { +public class StringStore : IAuditedStore +{ public string Describe() => "audited:string"; } @@ -102,16 +117,19 @@ public class StringStore : IAuditedStore { // An explicit attribute always wins; the convention picks up only the unattributed type. // --------------------------------------------------------------------------- -public interface IAttributeWinsService { +public interface IAttributeWinsService +{ string Name { get; } } [SingletonService] -public class AttributedService : IAttributeWinsService { +public class AttributedService : IAttributeWinsService +{ public string Name => "attributed"; } -public class ByConventionService : IAttributeWinsService { +public class ByConventionService : IAttributeWinsService +{ public string Name => "by-convention"; } @@ -124,8 +142,10 @@ public class ByConventionService : IAttributeWinsService { /// extends it. /// [DependencyModule] -public partial class ConventionSutModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionSutModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSingleton(); conventions.RegisterAll(typeof(IConventionHandler<,>)).AsTransient(); conventions.RegisterAll(typeof(IConventionCache<>)).AsScoped(); @@ -136,16 +156,20 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// Base-class hop off, which is the default. Only the direct implementation matches. [DependencyModule] -public partial class ConventionNoBaseClassModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionNoBaseClassModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSingleton(); } } /// The same scan with the base-class hop opted in. [DependencyModule] -public partial class ConventionBaseClassModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class ConventionBaseClassModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsSingleton().IncludeBaseClasses(); } } diff --git a/integ-tests/SutProject.Tests/ConventionTests/ConventionRegistrationTests.cs b/integ-tests/SutProject.Tests/ConventionTests/ConventionRegistrationTests.cs index 3c7c2ab..a51f364 100644 --- a/integ-tests/SutProject.Tests/ConventionTests/ConventionRegistrationTests.cs +++ b/integ-tests/SutProject.Tests/ConventionTests/ConventionRegistrationTests.cs @@ -15,28 +15,34 @@ namespace SutProject.Tests.ConventionTests; /// colliding. That is what this file is for. /// [ConventionSutModule] -public class ConventionRegistrationTests { - +public class ConventionRegistrationTests +{ [ModuleTest] - public void RegistersTypeDeclaringTheInterfaceDirectly(IEnumerable services) { + public void RegistersTypeDeclaringTheInterfaceDirectly(IEnumerable services) + { Assert.Contains(services, service => service.Name == "direct"); } [ModuleTest] public void RegistersTypeReachingTheInterfaceThroughInterfaceInheritance( - IEnumerable services) { + IEnumerable services + ) + { Assert.Contains(services, service => service.Name == "inherited"); } [ModuleTest] - public void RegistersExactlyTheTwoMatchingTypes(IEnumerable services) { + public void RegistersExactlyTheTwoMatchingTypes(IEnumerable services) + { Assert.Equal( new[] { "direct", "inherited" }, - services.Select(service => service.Name).OrderBy(name => name).ToArray()); + services.Select(service => service.Name).OrderBy(name => name).ToArray() + ); } [ModuleTest] - public void RegistersTheDeclaredLifetime(IConventionService first, IConventionService second) { + public void RegistersTheDeclaredLifetime(IConventionService first, IConventionService second) + { // Declared AsSingleton, so one instance serves both parameters. Assert.Same(first, second); } @@ -52,8 +58,9 @@ public void RegistersTheDeclaredLifetime(IConventionService first, IConventionSe [ModuleTest] public void ClosesAnOpenGenericAgainstEachImplementation( IConventionHandler create, - IConventionHandler rename) { - + IConventionHandler rename + ) + { Assert.IsType(create); Assert.IsType(rename); @@ -67,8 +74,10 @@ public void ClosesAnOpenGenericAgainstEachImplementation( /// [ModuleTest] public void RegistersAGenericImplementationAsAnOpenGeneric( - IConventionCache ints, IConventionCache strings) { - + IConventionCache ints, + IConventionCache strings + ) + { Assert.IsType>(ints); Assert.IsType>(strings); @@ -81,14 +90,18 @@ public void RegistersAGenericImplementationAsAnOpenGeneric( /// construction: StringStore declares IAuditedStore<string>, never IConventionStore<string>. /// [ModuleTest] - public void ClosesAnOpenGenericReachedThroughInterfaceInheritance(IConventionStore store) { + public void ClosesAnOpenGenericReachedThroughInterfaceInheritance( + IConventionStore store + ) + { Assert.IsType(store); Assert.Equal("audited:string", store.Describe()); } /// An open generic convention registers nothing for a construction nobody implements. [ModuleTest] - public void DoesNotRegisterAnUnimplementedConstruction(IServiceProvider provider) { + public void DoesNotRegisterAnUnimplementedConstruction(IServiceProvider provider) + { Assert.Null(provider.GetService>()); Assert.Null(provider.GetService>()); } @@ -99,8 +112,9 @@ public void DoesNotRegisterAnUnimplementedConstruction(IServiceProvider provider [ModuleTest] public void AnExplicitAttributeStillRegistersAlongsideTheConvention( - IEnumerable services) { - + IEnumerable services + ) + { var names = services.Select(service => service.Name).OrderBy(name => name).ToArray(); Assert.Equal(new[] { "attributed", "by-convention" }, names); @@ -108,8 +122,10 @@ public void AnExplicitAttributeStillRegistersAlongsideTheConvention( [ModuleTest] public void TheAttributedTypeKeepsItsOwnLifetime( - IEnumerable first, IEnumerable second) { - + IEnumerable first, + IEnumerable second + ) + { // The attribute declared Singleton and the convention declared Transient. The attributed // type is registered once, by its attribute, so it survives across resolutions while the // convention-registered one does not. @@ -128,21 +144,25 @@ public void TheAttributedTypeKeepsItsOwnLifetime( /// /// The base-class hop, proven from both sides against the same interface. /// -public class ConventionBaseClassReachTests { - +public class ConventionBaseClassReachTests +{ [ModuleTest] [ConventionNoBaseClassModule] - public void ABaseClassHopIsNotMatchedByDefault(IEnumerable services) { + public void ABaseClassHopIsNotMatchedByDefault(IEnumerable services) + { Assert.Equal( new[] { "direct-reach" }, - services.Select(service => service.Name).OrderBy(name => name).ToArray()); + services.Select(service => service.Name).OrderBy(name => name).ToArray() + ); } [ModuleTest] [ConventionBaseClassModule] - public void ABaseClassHopIsMatchedWhenOptedIn(IEnumerable services) { + public void ABaseClassHopIsMatchedWhenOptedIn(IEnumerable services) + { Assert.Equal( new[] { "direct-reach", "through-base" }, - services.Select(service => service.Name).OrderBy(name => name).ToArray()); + services.Select(service => service.Name).OrderBy(name => name).ToArray() + ); } } diff --git a/integ-tests/SutProject.Tests/ConventionTests/NestedNamespaceTypes.cs b/integ-tests/SutProject.Tests/ConventionTests/NestedNamespaceTypes.cs index 52c8fd5..d9a32f7 100644 --- a/integ-tests/SutProject.Tests/ConventionTests/NestedNamespaceTypes.cs +++ b/integ-tests/SutProject.Tests/ConventionTests/NestedNamespaceTypes.cs @@ -6,7 +6,8 @@ namespace SutProject.Tests.ConventionTests.Nested; /// Lives one namespace below the conventions, so a prefix filter reaches it and an exact one does /// not. That difference is the whole point of InExactNamespaces. /// -public class NestedScanned : INamespaceScanned { +public class NestedScanned : INamespaceScanned +{ /// public string Name => "nested"; } diff --git a/integ-tests/SutProject.Tests/CrossWire/CrossWireTests.cs b/integ-tests/SutProject.Tests/CrossWire/CrossWireTests.cs index 8cd3264..aefad6a 100644 --- a/integ-tests/SutProject.Tests/CrossWire/CrossWireTests.cs +++ b/integ-tests/SutProject.Tests/CrossWire/CrossWireTests.cs @@ -7,32 +7,20 @@ namespace SutProject.Tests.CrossWire; [DependencyModule(OnlyRealm = true)] -public partial class CrossWireModule { - -} +public partial class CrossWireModule { } [DependencyModule(OnlyRealm = true)] -public partial class CrossWireScopedModule { - -} - -public interface IInterface1 { - -} +public partial class CrossWireScopedModule { } -public interface IInterface2 { +public interface IInterface1 { } -} +public interface IInterface2 { } [CrossWireService(Realm = typeof(CrossWireModule))] -public class CrossWireService : IInterface1, IInterface2 { - -} +public class CrossWireService : IInterface1, IInterface2 { } [CrossWireService(Lifetime = ServiceLifetime.Scoped, Realm = typeof(CrossWireScopedModule))] -public class ScopedCrossWireService : IInterface1, IInterface2 { - -} +public class ScopedCrossWireService : IInterface1, IInterface2 { } /// /// The contract of [CrossWireService] is that one instance is reachable through the implementation @@ -40,11 +28,15 @@ public class ScopedCrossWireService : IInterface1, IInterface2 { /// would pass no matter what the generator emitted, so each test here compares the instances /// obtained through different service types. /// -public class CrossWireTests { - +public class CrossWireTests +{ [ModuleTest] [CrossWireModule] - public void CrossWire_SharesOneInstanceAcrossItsInterfaces(IInterface1 interface1, IInterface2 interface2) { + public void CrossWire_SharesOneInstanceAcrossItsInterfaces( + IInterface1 interface1, + IInterface2 interface2 + ) + { Assert.NotNull(interface1); Assert.NotNull(interface2); Assert.Same(interface1, interface2); @@ -53,26 +45,31 @@ public void CrossWire_SharesOneInstanceAcrossItsInterfaces(IInterface1 interface [ModuleTest] [CrossWireModule] public void CrossWire_ResolvesTheImplementationType( - IInterface1 interface1, CrossWireService implementation) { - + IInterface1 interface1, + CrossWireService implementation + ) + { Assert.NotNull(implementation); Assert.Same(interface1, implementation); } [ModuleTest] [CrossWireModule] - public void CrossWire_DefaultsToASingleInstanceAcrossScopes(IServiceProvider provider) { + public void CrossWire_DefaultsToASingleInstanceAcrossScopes(IServiceProvider provider) + { using var first = provider.CreateScope(); using var second = provider.CreateScope(); Assert.Same( first.ServiceProvider.GetService(), - second.ServiceProvider.GetService()); + second.ServiceProvider.GetService() + ); } [ModuleTest] [CrossWireScopedModule] - public void ScopedCrossWire_SharesOneInstanceWithinAScope(IServiceProvider provider) { + public void ScopedCrossWire_SharesOneInstanceWithinAScope(IServiceProvider provider) + { using var scope = provider.CreateScope(); var asInterface1 = scope.ServiceProvider.GetService(); @@ -87,12 +84,14 @@ public void ScopedCrossWire_SharesOneInstanceWithinAScope(IServiceProvider provi /// [ModuleTest] [CrossWireScopedModule] - public void ScopedCrossWire_DiffersBetweenScopes(IServiceProvider provider) { + public void ScopedCrossWire_DiffersBetweenScopes(IServiceProvider provider) + { using var first = provider.CreateScope(); using var second = provider.CreateScope(); Assert.NotSame( first.ServiceProvider.GetService(), - second.ServiceProvider.GetService()); + second.ServiceProvider.GetService() + ); } } diff --git a/integ-tests/SutProject.Tests/Customization/CustomDependencyTestCase.cs b/integ-tests/SutProject.Tests/Customization/CustomDependencyTestCase.cs index cb42dd2..de1a249 100644 --- a/integ-tests/SutProject.Tests/Customization/CustomDependencyTestCase.cs +++ b/integ-tests/SutProject.Tests/Customization/CustomDependencyTestCase.cs @@ -3,10 +3,12 @@ namespace SutProject.Tests.Customization; -public class CustomDependencyTestCase { +public class CustomDependencyTestCase +{ [ModuleTest] [CustomServiceProvider] - public void TestCase(ICustomTestDependency dependency) { + public void TestCase(ICustomTestDependency dependency) + { Assert.NotNull(dependency); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/Customization/CustomServiceProviderAttribute.cs b/integ-tests/SutProject.Tests/Customization/CustomServiceProviderAttribute.cs index 7566d7f..216fb8b 100644 --- a/integ-tests/SutProject.Tests/Customization/CustomServiceProviderAttribute.cs +++ b/integ-tests/SutProject.Tests/Customization/CustomServiceProviderAttribute.cs @@ -3,10 +3,13 @@ namespace SutProject.Tests.Customization; -public class CustomServiceProviderAttribute : Attribute, IServiceProviderBuilderAttribute { - +public class CustomServiceProviderAttribute : Attribute, IServiceProviderBuilderAttribute +{ public IServiceProvider BuildServiceProvider( - ITestMethodContext testMethod, IServiceCollection serviceCollection) { + ITestMethodContext testMethod, + IServiceCollection serviceCollection + ) + { serviceCollection.AddSingleton(); return serviceCollection.BuildServiceProvider(); } diff --git a/integ-tests/SutProject.Tests/Customization/CustomTestDependency.cs b/integ-tests/SutProject.Tests/Customization/CustomTestDependency.cs index d79407e..693a1a4 100644 --- a/integ-tests/SutProject.Tests/Customization/CustomTestDependency.cs +++ b/integ-tests/SutProject.Tests/Customization/CustomTestDependency.cs @@ -1,9 +1,5 @@ namespace SutProject.Tests.Customization; -public interface ICustomTestDependency { - -} +public interface ICustomTestDependency { } -public class CustomTestDependency : ICustomTestDependency { - -} \ No newline at end of file +public class CustomTestDependency : ICustomTestDependency { } diff --git a/integ-tests/SutProject.Tests/Customization/ServiceProviderBuilderPrecedenceTests.cs b/integ-tests/SutProject.Tests/Customization/ServiceProviderBuilderPrecedenceTests.cs index 07c0ea8..3af77e2 100644 --- a/integ-tests/SutProject.Tests/Customization/ServiceProviderBuilderPrecedenceTests.cs +++ b/integ-tests/SutProject.Tests/Customization/ServiceProviderBuilderPrecedenceTests.cs @@ -8,22 +8,30 @@ namespace SutProject.Tests.Customization; /// /// Records which actually built the container. /// -public interface IProviderBuiltBy { +public interface IProviderBuiltBy +{ string Scope { get; } } -public class ProviderBuiltBy(string scope) : IProviderBuiltBy { +public class ProviderBuiltBy(string scope) : IProviderBuiltBy +{ public string Scope => scope; } /// /// A builder that stamps the container with the scope it was declared at. /// -public class ScopeStampingProviderAttribute(string scope) : Attribute, IServiceProviderBuilderAttribute { +public class ScopeStampingProviderAttribute(string scope) + : Attribute, + IServiceProviderBuilderAttribute +{ public string Scope => scope; public IServiceProvider BuildServiceProvider( - ITestMethodContext testMethod, IServiceCollection serviceCollection) { + ITestMethodContext testMethod, + IServiceCollection serviceCollection + ) + { serviceCollection.AddSingleton(new ProviderBuiltBy(scope)); return serviceCollection.BuildServiceProvider(); @@ -36,16 +44,18 @@ public IServiceProvider BuildServiceProvider( /// overridden by a broader default. /// [ScopeStampingProvider("class")] -public class ServiceProviderBuilderPrecedenceTests { - +public class ServiceProviderBuilderPrecedenceTests +{ [ModuleTest] [ScopeStampingProvider("method")] - public void MethodBeatsClass(IProviderBuiltBy builtBy) { + public void MethodBeatsClass(IProviderBuiltBy builtBy) + { Assert.Equal("method", builtBy.Scope); } [ModuleTest] - public void ClassAppliesWhenTheMethodDeclaresNone(IProviderBuiltBy builtBy) { + public void ClassAppliesWhenTheMethodDeclaresNone(IProviderBuiltBy builtBy) + { Assert.Equal("class", builtBy.Scope); } } diff --git a/integ-tests/SutProject.Tests/DataTests/InlineDataTests.cs b/integ-tests/SutProject.Tests/DataTests/InlineDataTests.cs index fa39250..11ce908 100644 --- a/integ-tests/SutProject.Tests/DataTests/InlineDataTests.cs +++ b/integ-tests/SutProject.Tests/DataTests/InlineDataTests.cs @@ -3,25 +3,27 @@ namespace SutProject.Tests.DataTests; -public class InlineDataTests { - +public class InlineDataTests +{ [ModuleTest] [InlineData("Hello World")] [SutModule] - public void SimpleValueTests(string value, IDependencyOne one) { + public void SimpleValueTests(string value, IDependencyOne one) + { Assert.Equal("Hello World", value); Assert.NotNull(one); } } -public class MultiRowDataTests { - +public class MultiRowDataTests +{ [ModuleTest] [InlineData("one")] [InlineData("two")] [InlineData("three")] [SutModule] - public void MultipleRows(string value, IDependencyOne one) { + public void MultipleRows(string value, IDependencyOne one) + { Assert.NotNull(value); Assert.NotNull(one); } diff --git a/integ-tests/SutProject.Tests/DataTests/MemberDataTests.cs b/integ-tests/SutProject.Tests/DataTests/MemberDataTests.cs index a860cbf..207f43a 100644 --- a/integ-tests/SutProject.Tests/DataTests/MemberDataTests.cs +++ b/integ-tests/SutProject.Tests/DataTests/MemberDataTests.cs @@ -13,16 +13,18 @@ namespace SutProject.Tests.DataTests; /// this file needs to exist. A regression here is only visible as the suite getting smaller, so /// the unit-level counterpart in ModuleTestCaseDataTests asserts on the number of cases created. /// -public class MemberDataTests { - +public class MemberDataTests +{ public static TheoryData Rows => new("one", "two"); - public static IEnumerable RawRows() { + public static IEnumerable RawRows() + { yield return ["one"]; yield return ["two"]; } - public static IEnumerable> TypedRows() { + public static IEnumerable> TypedRows() + { yield return new TheoryDataRow("one"); yield return new TheoryDataRow("two"); } @@ -34,7 +36,8 @@ public static IEnumerable> TypedRows() { [ModuleTest] [MemberData(nameof(Rows))] [SutModule] - public void TheoryDataRowsAreSupplied(string value, IDependencyOne one) { + public void TheoryDataRowsAreSupplied(string value, IDependencyOne one) + { Assert.NotNull(value); Assert.NotNull(one); } @@ -42,7 +45,8 @@ public void TheoryDataRowsAreSupplied(string value, IDependencyOne one) { [ModuleTest] [MemberData(nameof(RawRows))] [SutModule] - public void ObjectArrayRowsAreSupplied(string value, IDependencyOne one) { + public void ObjectArrayRowsAreSupplied(string value, IDependencyOne one) + { Assert.NotNull(value); Assert.NotNull(one); } @@ -50,7 +54,8 @@ public void ObjectArrayRowsAreSupplied(string value, IDependencyOne one) { [ModuleTest] [MemberData(nameof(TypedRows))] [SutModule] - public void TheoryDataRowRowsAreSupplied(string value, IDependencyOne one) { + public void TheoryDataRowRowsAreSupplied(string value, IDependencyOne one) + { Assert.NotNull(value); Assert.NotNull(one); } @@ -61,7 +66,8 @@ public void TheoryDataRowRowsAreSupplied(string value, IDependencyOne one) { [ModuleTest] [MemberData(nameof(Rows), MemberType = typeof(MemberDataTests))] [SutModule] - public void ExplicitMemberTypeRowsAreSupplied(string value, IDependencyOne one) { + public void ExplicitMemberTypeRowsAreSupplied(string value, IDependencyOne one) + { Assert.NotNull(value); Assert.NotNull(one); } @@ -69,7 +75,8 @@ public void ExplicitMemberTypeRowsAreSupplied(string value, IDependencyOne one) [ModuleTest] [ClassData(typeof(ClassRows))] [SutModule] - public void ClassDataRowsAreSupplied(string value, IDependencyOne one) { + public void ClassDataRowsAreSupplied(string value, IDependencyOne one) + { Assert.NotNull(value); Assert.NotNull(one); } @@ -77,8 +84,10 @@ public void ClassDataRowsAreSupplied(string value, IDependencyOne one) { #pragma warning restore xUnit1037 } -public class ClassRows : TheoryData { - public ClassRows() { +public class ClassRows : TheoryData +{ + public ClassRows() + { Add("one"); Add("two"); } diff --git a/integ-tests/SutProject.Tests/DuplicateNames/FirstDuplicateNameTests.cs b/integ-tests/SutProject.Tests/DuplicateNames/FirstDuplicateNameTests.cs index 9622beb..d5e3bdd 100644 --- a/integ-tests/SutProject.Tests/DuplicateNames/FirstDuplicateNameTests.cs +++ b/integ-tests/SutProject.Tests/DuplicateNames/FirstDuplicateNameTests.cs @@ -8,10 +8,12 @@ namespace SutProject.Tests.DuplicateNames; /// same name. A test case unique ID built from the bare method name collides across classes, and /// xUnit silently drops the duplicate, so both of these must still run. /// -public class FirstDuplicateNameTests { +public class FirstDuplicateNameTests +{ [ModuleTest] [SutModule] - public void SharedMethodName(IDependencyOne dependency) { + public void SharedMethodName(IDependencyOne dependency) + { Assert.NotNull(dependency); } } diff --git a/integ-tests/SutProject.Tests/DuplicateNames/SecondDuplicateNameTests.cs b/integ-tests/SutProject.Tests/DuplicateNames/SecondDuplicateNameTests.cs index 1b70b04..23a56dd 100644 --- a/integ-tests/SutProject.Tests/DuplicateNames/SecondDuplicateNameTests.cs +++ b/integ-tests/SutProject.Tests/DuplicateNames/SecondDuplicateNameTests.cs @@ -6,10 +6,12 @@ namespace SutProject.Tests.DuplicateNames; /// /// See . /// -public class SecondDuplicateNameTests { +public class SecondDuplicateNameTests +{ [ModuleTest] [SutModule] - public void SharedMethodName(IDependencyOne dependency) { + public void SharedMethodName(IDependencyOne dependency) + { Assert.NotNull(dependency); } } diff --git a/integ-tests/SutProject.Tests/EnvironmentTests/EnvironmentConfigurationTests.cs b/integ-tests/SutProject.Tests/EnvironmentTests/EnvironmentConfigurationTests.cs index f4ca0ff..809dc86 100644 --- a/integ-tests/SutProject.Tests/EnvironmentTests/EnvironmentConfigurationTests.cs +++ b/integ-tests/SutProject.Tests/EnvironmentTests/EnvironmentConfigurationTests.cs @@ -6,56 +6,71 @@ namespace SutProject.Tests.EnvironmentTests; -public class TestEnvironment : IModuleEnvironment { +public class TestEnvironment : IModuleEnvironment +{ public string EnvironmentName { get; } private readonly Dictionary _values; - public TestEnvironment(string environmentName, Dictionary? values = null) { + public TestEnvironment(string environmentName, Dictionary? values = null) + { EnvironmentName = environmentName; _values = values ?? new Dictionary(); } - public string? Value(string name) { + public string? Value(string name) + { return _values.TryGetValue(name, out var value) ? value : null; } } -public interface IEnvironmentDependency { +public interface IEnvironmentDependency +{ string EnvironmentName { get; } } -public class EnvironmentDependency(string environmentName) : IEnvironmentDependency { +public class EnvironmentDependency(string environmentName) : IEnvironmentDependency +{ public string EnvironmentName { get; } = environmentName; } [DependencyModule] -public partial class EnvironmentAwareModule : IEnvironmentServiceCollectionConfiguration { - public void ConfigureServices(IServiceCollection services, IModuleEnvironment environment) { +public partial class EnvironmentAwareModule : IEnvironmentServiceCollectionConfiguration +{ + public void ConfigureServices(IServiceCollection services, IModuleEnvironment environment) + { var envName = environment.EnvironmentName; services.AddSingleton(new EnvironmentDependency(envName)); } } [DependencyModule] -public partial class DualConfigModule : IServiceCollectionConfiguration, IEnvironmentServiceCollectionConfiguration { - public void ConfigureServices(IServiceCollection services) { +public partial class DualConfigModule + : IServiceCollectionConfiguration, + IEnvironmentServiceCollectionConfiguration +{ + public void ConfigureServices(IServiceCollection services) + { services.AddSingleton(new StringMarker("from-configure")); } - public void ConfigureServices(IServiceCollection services, IModuleEnvironment environment) { + public void ConfigureServices(IServiceCollection services, IModuleEnvironment environment) + { var envName = environment.EnvironmentName; services.AddSingleton(new EnvironmentDependency(envName)); } } -public class StringMarker(string value) { +public class StringMarker(string value) +{ public string Value { get; } = value; } -public class EnvironmentConfigurationTests { +public class EnvironmentConfigurationTests +{ [Fact] - public void EnvironmentPassedToModule_WhenRegistered() { + public void EnvironmentPassedToModule_WhenRegistered() + { var serviceCollection = new ServiceCollection(); var environment = new TestEnvironment("Production"); @@ -71,7 +86,8 @@ public void EnvironmentPassedToModule_WhenRegistered() { /// No environment supplied means the process default rather than null. /// [Fact] - public void ProcessEnvironment_WhenNotRegistered() { + public void ProcessEnvironment_WhenNotRegistered() + { var serviceCollection = new ServiceCollection(); serviceCollection.AddModules(new EnvironmentAwareModule()); @@ -87,7 +103,8 @@ public void ProcessEnvironment_WhenNotRegistered() { /// null to branch on. /// [Fact] - public void ModuleEnvironmentNone_HasNoNameAndNoValues() { + public void ModuleEnvironmentNone_HasNoNameAndNoValues() + { var serviceCollection = new ServiceCollection(); serviceCollection.AddModules(ModuleEnvironment.None, new EnvironmentAwareModule()); @@ -99,7 +116,8 @@ public void ModuleEnvironmentNone_HasNoNameAndNoValues() { } [Fact] - public void EnvironmentRegisteredAsSingleton_WhenProvided() { + public void EnvironmentRegisteredAsSingleton_WhenProvided() + { var serviceCollection = new ServiceCollection(); var environment = new TestEnvironment("Development"); @@ -112,11 +130,13 @@ public void EnvironmentRegisteredAsSingleton_WhenProvided() { } [Fact] - public void EnvironmentValues_AccessibleInModule() { + public void EnvironmentValues_AccessibleInModule() + { var serviceCollection = new ServiceCollection(); - var values = new Dictionary { + var values = new Dictionary + { { "Region", "us-east-1" }, - { "Feature.NewUI", "true" } + { "Feature.NewUI", "true" }, }; var environment = new TestEnvironment("Staging", values); @@ -131,7 +151,8 @@ public void EnvironmentValues_AccessibleInModule() { } [Fact] - public void BothConfigurationInterfaces_CalledCorrectly() { + public void BothConfigurationInterfaces_CalledCorrectly() + { var serviceCollection = new ServiceCollection(); var environment = new TestEnvironment("Test"); @@ -150,7 +171,8 @@ public void BothConfigurationInterfaces_CalledCorrectly() { /// registrations is the same one that resolves. /// [Fact] - public void NullEnvironmentParameter_RegistersTheProcessDefault() { + public void NullEnvironmentParameter_RegistersTheProcessDefault() + { var serviceCollection = new ServiceCollection(); serviceCollection.AddModules((IModuleEnvironment?)null, new EnvironmentAwareModule()); @@ -159,22 +181,28 @@ public void NullEnvironmentParameter_RegistersTheProcessDefault() { // fresh environment per call, so the invariant is that these are the same object — not that // either matches something asked for later. var registered = Assert.Single( - serviceCollection, descriptor => descriptor.ServiceType == typeof(IModuleEnvironment)); + serviceCollection, + descriptor => descriptor.ServiceType == typeof(IModuleEnvironment) + ); var serviceProvider = serviceCollection.BuildServiceProvider(); Assert.Same( - registered.ImplementationInstance, serviceProvider.GetRequiredService()); + registered.ImplementationInstance, + serviceProvider.GetRequiredService() + ); Assert.Equal( ModuleEnvironment.CreateDefault().EnvironmentName, - serviceProvider.GetRequiredService().EnvironmentName); + serviceProvider.GetRequiredService().EnvironmentName + ); } /// /// An environment the application supplied is never displaced by the default. /// [Fact] - public void SuppliedEnvironmentIsNotReplacedByTheDefault() { + public void SuppliedEnvironmentIsNotReplacedByTheDefault() + { var serviceCollection = new ServiceCollection(); var environment = new TestEnvironment("Staging"); diff --git a/integ-tests/SutProject.Tests/EnvironmentTests/EnvironmentSeedingTests.cs b/integ-tests/SutProject.Tests/EnvironmentTests/EnvironmentSeedingTests.cs index 4f77114..d8a3971 100644 --- a/integ-tests/SutProject.Tests/EnvironmentTests/EnvironmentSeedingTests.cs +++ b/integ-tests/SutProject.Tests/EnvironmentTests/EnvironmentSeedingTests.cs @@ -14,7 +14,8 @@ namespace SutProject.Tests.EnvironmentTests; /// tests. /// [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] -public class SeededEnvironmentAttribute(string name) : Attribute, IModuleEnvironmentProvider { +public class SeededEnvironmentAttribute(string name) : Attribute, IModuleEnvironmentProvider +{ public IModuleEnvironment? ProvideEnvironment(MethodInfo testMethod) => new ModuleEnvironment(false, name); } @@ -38,12 +39,13 @@ public partial class SeededEnvironmentModule { } /// service-setup pass, so every condition had been decided against the process default before an /// attribute could register anything. /// -public class EnvironmentSeedingTests { - +public class EnvironmentSeedingTests +{ [ModuleTest] [SeededEnvironmentModule] [SeededEnvironment("seeded-environment")] - public void AGatedRegistrationAppliesUnderTheSeededEnvironment(IServiceProvider provider) { + public void AGatedRegistrationAppliesUnderTheSeededEnvironment(IServiceProvider provider) + { Assert.NotNull(provider.GetService()); } @@ -53,7 +55,8 @@ public void AGatedRegistrationAppliesUnderTheSeededEnvironment(IServiceProvider /// [ModuleTest] [SeededEnvironmentModule] - public void TheSameRegistrationIsAbsentWithoutASeed(IServiceProvider provider) { + public void TheSameRegistrationIsAbsentWithoutASeed(IServiceProvider provider) + { Assert.Null(provider.GetService()); } @@ -61,31 +64,35 @@ public void TheSameRegistrationIsAbsentWithoutASeed(IServiceProvider provider) { [ModuleTest] [EnvironmentAwareModule] [SeededEnvironment("seeded-environment")] - public void AModuleReadingTheEnvironmentSeesTheSeededOne(IEnvironmentDependency dependency) { + public void AModuleReadingTheEnvironmentSeesTheSeededOne(IEnvironmentDependency dependency) + { Assert.Equal("seeded-environment", dependency.EnvironmentName); } [ModuleTest] [EnvironmentAwareModule] - public void WithoutASeedTheProcessDefaultApplies(IEnvironmentDependency dependency) { + public void WithoutASeedTheProcessDefaultApplies(IEnvironmentDependency dependency) + { Assert.Equal(ModuleEnvironment.CreateDefault().EnvironmentName, dependency.EnvironmentName); } } /// Narrowest scope wins, matching how every other attribute here resolves. [SeededEnvironment("outer-environment")] -public class EnvironmentSeedingPrecedenceTests { - +public class EnvironmentSeedingPrecedenceTests +{ [ModuleTest] [EnvironmentAwareModule] - public void AClassLevelSeedApplies(IEnvironmentDependency dependency) { + public void AClassLevelSeedApplies(IEnvironmentDependency dependency) + { Assert.Equal("outer-environment", dependency.EnvironmentName); } [ModuleTest] [EnvironmentAwareModule] [SeededEnvironment("inner-environment")] - public void TheMethodsSeedBeatsTheClasses(IEnvironmentDependency dependency) { + public void TheMethodsSeedBeatsTheClasses(IEnvironmentDependency dependency) + { Assert.Equal("inner-environment", dependency.EnvironmentName); } } diff --git a/integ-tests/SutProject.Tests/FactoryTests/SimpleFactoryTests.cs b/integ-tests/SutProject.Tests/FactoryTests/SimpleFactoryTests.cs index 134e613..5cdd6a5 100644 --- a/integ-tests/SutProject.Tests/FactoryTests/SimpleFactoryTests.cs +++ b/integ-tests/SutProject.Tests/FactoryTests/SimpleFactoryTests.cs @@ -7,33 +7,38 @@ namespace SutProject.Tests.FactoryTests; [DependencyModule(OnlyRealm = true)] -public partial class FactoryModule { - -} - -public static class FactoryClass { +public partial class FactoryModule { } +public static class FactoryClass +{ [SingletonService(Realm = typeof(FactoryModule))] public static IDependencyOne FactoryService( - ISingletonService singletonService, IScopedService scopedService) { + ISingletonService singletonService, + IScopedService scopedService + ) + { return new DependencyOne(singletonService, scopedService); } [SingletonService(Realm = typeof(FactoryModule))] - public static ISingletonService SingletonService(IServiceProvider serviceProvider) { + public static ISingletonService SingletonService(IServiceProvider serviceProvider) + { return new SingletonService(); } [ScopedService(Realm = typeof(FactoryModule))] - public static IScopedService ScopedService() { + public static IScopedService ScopedService() + { return new ScopedService(); } } -public class SimpleFactoryTests { +public class SimpleFactoryTests +{ [ModuleTest] [FactoryModule] - public void FactoryTest(IDependencyOne dependencyOne) { + public void FactoryTest(IDependencyOne dependencyOne) + { Assert.NotNull(dependencyOne); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/FakeItEasy/FakeItEasyAttributeTests.cs b/integ-tests/SutProject.Tests/FakeItEasy/FakeItEasyAttributeTests.cs index cc8e16d..340ba83 100644 --- a/integ-tests/SutProject.Tests/FakeItEasy/FakeItEasyAttributeTests.cs +++ b/integ-tests/SutProject.Tests/FakeItEasy/FakeItEasyAttributeTests.cs @@ -10,12 +10,16 @@ namespace SutProject.Tests.FakeItEasy; /// The same scenario as the NSubstitute and Moq tests, so the three can be read against each other. /// [FakeItEasySupport] -public class FakeItEasyAttributeTests { - +public class FakeItEasyAttributeTests +{ [ModuleTest] [SutModule] - public void MockTest([Mock] IDependencyOne dependencyOne, - [Mock] IScopedService scopedService, ISingletonService singletonService) { + public void MockTest( + [Mock] IDependencyOne dependencyOne, + [Mock] IScopedService scopedService, + ISingletonService singletonService + ) + { A.CallTo(() => dependencyOne.SingletonService).Returns(singletonService); A.CallTo(() => dependencyOne.ScopedService).Returns(scopedService); @@ -28,7 +32,8 @@ public void MockTest([Mock] IDependencyOne dependencyOne, /// [ModuleTest] [SutModule] - public void TheInjectedInstanceIsTheFake([Mock] IDependencyOne dependencyOne) { + public void TheInjectedInstanceIsTheFake([Mock] IDependencyOne dependencyOne) + { Assert.True(Fake.GetFakeManager(dependencyOne) is not null); } } diff --git a/integ-tests/SutProject.Tests/Features/FeatureModules.cs b/integ-tests/SutProject.Tests/Features/FeatureModules.cs index bf87703..b29ce32 100644 --- a/integ-tests/SutProject.Tests/Features/FeatureModules.cs +++ b/integ-tests/SutProject.Tests/Features/FeatureModules.cs @@ -4,38 +4,45 @@ namespace SutProject.Tests.Features; -public interface IModuleFeatureValue { +public interface IModuleFeatureValue +{ string Value { get; } } [DependencyModule(OnlyRealm = true)] -public partial class FeatureModuleA : IModuleFeatureValue { - +public partial class FeatureModuleA : IModuleFeatureValue +{ public string Value => "A"; } [DependencyModule(OnlyRealm = true)] -public partial class FeatureModuleB : IModuleFeatureValue { - +public partial class FeatureModuleB : IModuleFeatureValue +{ public string Value => "B"; } [DependencyModule(OnlyRealm = true)] -public partial class FeatureModuleC : IModuleFeatureValue { - +public partial class FeatureModuleC : IModuleFeatureValue +{ public string Value => "C"; } -public class DependencyValue(string value) { +public class DependencyValue(string value) +{ public string Value { get; } = value; } [DependencyModule(OnlyRealm = true)] -public partial class FeatureModuleHandler : IDependencyModuleFeature { - - public void HandleFeature(IServiceCollection collection, IEnumerable feature) { - foreach (var featureValue in feature) { +public partial class FeatureModuleHandler : IDependencyModuleFeature +{ + public void HandleFeature( + IServiceCollection collection, + IEnumerable feature + ) + { + foreach (var featureValue in feature) + { collection.AddSingleton(_ => new DependencyValue(featureValue.Value)); } } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/Features/FeatureTests.cs b/integ-tests/SutProject.Tests/Features/FeatureTests.cs index 91bbfca..1f779d8 100644 --- a/integ-tests/SutProject.Tests/Features/FeatureTests.cs +++ b/integ-tests/SutProject.Tests/Features/FeatureTests.cs @@ -3,18 +3,20 @@ namespace SutProject.Tests.Features; -public class FeatureTests { +public class FeatureTests +{ [ModuleTest] [FeatureModuleHandler] [FeatureModuleA] [FeatureModuleB] [FeatureModuleC] - public void FeatureTest(IEnumerable values) { + public void FeatureTest(IEnumerable values) + { var valuesArray = values.ToArray(); - + Assert.Equal(3, valuesArray.Length); Assert.Single(valuesArray, v => v.Value == "A"); Assert.Single(valuesArray, v => v.Value == "B"); Assert.Single(valuesArray, v => v.Value == "C"); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/Features/OrderFeatureTests.cs b/integ-tests/SutProject.Tests/Features/OrderFeatureTests.cs index d17839a..2e60414 100644 --- a/integ-tests/SutProject.Tests/Features/OrderFeatureTests.cs +++ b/integ-tests/SutProject.Tests/Features/OrderFeatureTests.cs @@ -3,17 +3,19 @@ namespace SutProject.Tests.Features; -public class OrderFeatureTests { +public class OrderFeatureTests +{ [ModuleTest] [FirstFeatureHandler] [SecondFeatureHandler] [ThirdFeatureHandler] - public void OrderTest(IEnumerable values) { + public void OrderTest(IEnumerable values) + { var valuesList = values as List ?? values.ToList(); - + Assert.Equal(3, valuesList.Count); Assert.Equal("1", valuesList[0].Value); Assert.Equal("2", valuesList[1].Value); Assert.Equal("3", valuesList[2].Value); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/Features/OrderedFeatureModules.cs b/integ-tests/SutProject.Tests/Features/OrderedFeatureModules.cs index e73f9e1..be350ba 100644 --- a/integ-tests/SutProject.Tests/Features/OrderedFeatureModules.cs +++ b/integ-tests/SutProject.Tests/Features/OrderedFeatureModules.cs @@ -4,39 +4,37 @@ namespace SutProject.Tests.Features; -public interface IOrderedFeature { - -} - +public interface IOrderedFeature { } [DependencyModule(OnlyRealm = true)] -public partial class FirstFeatureHandler : IDependencyModuleFeature { - +public partial class FirstFeatureHandler : IDependencyModuleFeature +{ public int Order => 1; - public void HandleFeature(IServiceCollection collection, IEnumerable feature) { - collection.AddSingleton(_ => new DependencyValue("1")); + public void HandleFeature(IServiceCollection collection, IEnumerable feature) + { + collection.AddSingleton(_ => new DependencyValue("1")); } } [DependencyModule(OnlyRealm = true)] -public partial class SecondFeatureHandler : IDependencyModuleFeature { - +public partial class SecondFeatureHandler : IDependencyModuleFeature +{ public int Order => 2; - public void HandleFeature(IServiceCollection collection, IEnumerable feature) { + public void HandleFeature(IServiceCollection collection, IEnumerable feature) + { collection.AddSingleton(_ => new DependencyValue("2")); } } [DependencyModule(OnlyRealm = true)] -public partial class ThirdFeatureHandler : IDependencyModuleFeature { - +public partial class ThirdFeatureHandler : IDependencyModuleFeature +{ public int Order => 3; - public void HandleFeature(IServiceCollection collection, IEnumerable feature) { + public void HandleFeature(IServiceCollection collection, IEnumerable feature) + { collection.AddSingleton(_ => new DependencyValue("3")); } } - - diff --git a/integ-tests/SutProject.Tests/GenerateAttributeTests/ModuleAttributeTests.cs b/integ-tests/SutProject.Tests/GenerateAttributeTests/ModuleAttributeTests.cs index 1d0f3f4..c4caaf8 100644 --- a/integ-tests/SutProject.Tests/GenerateAttributeTests/ModuleAttributeTests.cs +++ b/integ-tests/SutProject.Tests/GenerateAttributeTests/ModuleAttributeTests.cs @@ -3,17 +3,23 @@ namespace SutProject.Tests.GenerateAttributeTests; -public class ModuleAttributeTests { +public class ModuleAttributeTests +{ [Fact] - public void AssertGenerateAttribute() { + public void AssertGenerateAttribute() + { var assembly = GetType().Assembly; - var withAttributeType = assembly.GetType(typeof(ModuleWithAttribute).FullName + "Attribute"); - + var withAttributeType = assembly.GetType( + typeof(ModuleWithAttribute).FullName + "Attribute" + ); + Assert.NotNull(withAttributeType); - - var withoutAttributeType = assembly.GetType(typeof(ModuleWithoutAttribute).FullName + "Attribute"); - + + var withoutAttributeType = assembly.GetType( + typeof(ModuleWithoutAttribute).FullName + "Attribute" + ); + Assert.Null(withoutAttributeType); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/GenerateAttributeTests/ModuleWithAttribute.cs b/integ-tests/SutProject.Tests/GenerateAttributeTests/ModuleWithAttribute.cs index 74ebcee..a8f76e8 100644 --- a/integ-tests/SutProject.Tests/GenerateAttributeTests/ModuleWithAttribute.cs +++ b/integ-tests/SutProject.Tests/GenerateAttributeTests/ModuleWithAttribute.cs @@ -3,6 +3,4 @@ namespace SutProject.Tests.GenerateAttributeTests; [DependencyModule] -public partial class ModuleWithAttribute { - -} \ No newline at end of file +public partial class ModuleWithAttribute { } diff --git a/integ-tests/SutProject.Tests/GenerateAttributeTests/ModuleWithoutAttribute.cs b/integ-tests/SutProject.Tests/GenerateAttributeTests/ModuleWithoutAttribute.cs index 99ee40d..248d75e 100644 --- a/integ-tests/SutProject.Tests/GenerateAttributeTests/ModuleWithoutAttribute.cs +++ b/integ-tests/SutProject.Tests/GenerateAttributeTests/ModuleWithoutAttribute.cs @@ -3,6 +3,4 @@ namespace SutProject.Tests.GenerateAttributeTests; [DependencyModule(GenerateAttribute = false)] -public partial class ModuleWithoutAttribute { - -} \ No newline at end of file +public partial class ModuleWithoutAttribute { } diff --git a/integ-tests/SutProject.Tests/GenerateFactories/GenerateFactoryModule.cs b/integ-tests/SutProject.Tests/GenerateFactories/GenerateFactoryModule.cs index 8eb3e8b..bdf100c 100644 --- a/integ-tests/SutProject.Tests/GenerateFactories/GenerateFactoryModule.cs +++ b/integ-tests/SutProject.Tests/GenerateFactories/GenerateFactoryModule.cs @@ -9,35 +9,29 @@ public partial class GenerateFactoryModule; [SingletonService(Realm = typeof(GenerateFactoryModule))] public class FactoryDepOne( - ISingletonService? singletonService = null, - IScopedService? scopedService = null) : IDependencyOne { + ISingletonService? singletonService = null, + IScopedService? scopedService = null +) : IDependencyOne +{ + public ISingletonService SingletonService { get; } = singletonService!; - public ISingletonService SingletonService { - get; - } = singletonService!; - - public IScopedService ScopedService { - get; - } = scopedService!; + public IScopedService ScopedService { get; } = scopedService!; } [SingletonService(Realm = typeof(GenerateFactoryModule), Key = "Keyed")] -public class GenerateKeyed() : IKeyedRegistration { - public string Key { - get; - } = "Keyed"; +public class GenerateKeyed() : IKeyedRegistration +{ + public string Key { get; } = "Keyed"; } [SingletonService(Realm = typeof(GenerateFactoryModule))] -public class KeyedDependency([FromKeyedServices("Keyed")] IKeyedRegistration registration) { - public IKeyedRegistration Registration { - get; - } = registration; +public class KeyedDependency([FromKeyedServices("Keyed")] IKeyedRegistration registration) +{ + public IKeyedRegistration Registration { get; } = registration; } [SingletonService(Realm = typeof(GenerateFactoryModule))] -public class StandardConstructor { - public StandardConstructor(IDependencyOne dependencyOne) { - - } -} \ No newline at end of file +public class StandardConstructor +{ + public StandardConstructor(IDependencyOne dependencyOne) { } +} diff --git a/integ-tests/SutProject.Tests/GenerateFactories/GenerateFactoryTests.cs b/integ-tests/SutProject.Tests/GenerateFactories/GenerateFactoryTests.cs index 5be349c..558bc2c 100644 --- a/integ-tests/SutProject.Tests/GenerateFactories/GenerateFactoryTests.cs +++ b/integ-tests/SutProject.Tests/GenerateFactories/GenerateFactoryTests.cs @@ -3,11 +3,12 @@ namespace SutProject.Tests.GenerateFactories; -public class GenerateFactoryTests { +public class GenerateFactoryTests +{ [ModuleTest] [GenerateFactoryModule] - public void ConstructGeneratedFactories( - KeyedDependency keyedDependency, IDependencyOne dep) { + public void ConstructGeneratedFactories(KeyedDependency keyedDependency, IDependencyOne dep) + { Assert.NotNull(keyedDependency); Assert.NotNull(dep); @@ -16,4 +17,4 @@ public void ConstructGeneratedFactories( Assert.Equal("Keyed", keyedDependency.Registration.Key); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/GenericTests/ResolveGenericTypeTests.cs b/integ-tests/SutProject.Tests/GenericTests/ResolveGenericTypeTests.cs index 69d4bb2..21bf4e1 100644 --- a/integ-tests/SutProject.Tests/GenericTests/ResolveGenericTypeTests.cs +++ b/integ-tests/SutProject.Tests/GenericTests/ResolveGenericTypeTests.cs @@ -6,25 +6,28 @@ namespace SutProject.Tests.GenericTests; - [DependencyModule] -public partial class GenericListModule : IServiceCollectionConfiguration { - - public void ConfigureServices(IServiceCollection services) { +public partial class GenericListModule : IServiceCollectionConfiguration +{ + public void ConfigureServices(IServiceCollection services) + { services.AddTransient(typeof(IReadOnlyList<>), typeof(List<>)); } } [SutModule] -public class ResolveGenericTypeTests { +public class ResolveGenericTypeTests +{ [ModuleTest] - public void ResolveGenericType(IGenericInterface genericInterface) { + public void ResolveGenericType(IGenericInterface genericInterface) + { Assert.NotNull(genericInterface); Assert.NotNull(genericInterface.Value); } [ModuleTest] - public void ResolveClosedGeneric(IGenericInterface genericInterface) { + public void ResolveClosedGeneric(IGenericInterface genericInterface) + { Assert.NotNull(genericInterface); Assert.IsType(genericInterface); } @@ -32,7 +35,8 @@ public void ResolveClosedGeneric(IGenericInterface genericInterface) { [ModuleTest] [SutModule] [GenericListModule] - public void ResolveList(IReadOnlyList genericList) { + public void ResolveList(IReadOnlyList genericList) + { Assert.NotNull(genericList); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/KeyedTests/KeyedModule.cs b/integ-tests/SutProject.Tests/KeyedTests/KeyedModule.cs index b65b4d3..2ec2e33 100644 --- a/integ-tests/SutProject.Tests/KeyedTests/KeyedModule.cs +++ b/integ-tests/SutProject.Tests/KeyedTests/KeyedModule.cs @@ -3,6 +3,4 @@ namespace SutProject.Tests.KeyedTests; [DependencyModule] -public partial class KeyedModule { - -} \ No newline at end of file +public partial class KeyedModule { } diff --git a/integ-tests/SutProject.Tests/KeyedTests/KeyedRegistrationModels.cs b/integ-tests/SutProject.Tests/KeyedTests/KeyedRegistrationModels.cs index 0917ccb..e76c21b 100644 --- a/integ-tests/SutProject.Tests/KeyedTests/KeyedRegistrationModels.cs +++ b/integ-tests/SutProject.Tests/KeyedTests/KeyedRegistrationModels.cs @@ -3,39 +3,44 @@ namespace SutProject.Tests.KeyedTests; -public interface IKeyedRegistration { +public interface IKeyedRegistration +{ string Key { get; } } [SingletonService(Key = "A", Realm = typeof(KeyedModule))] -public class AKeyedRegistration : KeyedRegistration { - public AKeyedRegistration() : base("A") { } +public class AKeyedRegistration : KeyedRegistration +{ + public AKeyedRegistration() + : base("A") { } } [SingletonService(Key = "B", Realm = typeof(KeyedModule))] -public class BKeyedRegistration : KeyedRegistration { - public BKeyedRegistration() : base("B") { - } +public class BKeyedRegistration : KeyedRegistration +{ + public BKeyedRegistration() + : base("B") { } } [SingletonService(Key = "C", Realm = typeof(KeyedModule))] -public class CKeyedRegistration : KeyedRegistration { - public CKeyedRegistration() : base("C") { - } +public class CKeyedRegistration : KeyedRegistration +{ + public CKeyedRegistration() + : base("C") { } } [SingletonService(Realm = typeof(KeyedModule))] -public class CKeyedDependency([FromKeyedServices("C")] IKeyedRegistration registration) { - public IKeyedRegistration Registration { - get; - } = registration; - +public class CKeyedDependency([FromKeyedServices("C")] IKeyedRegistration registration) +{ + public IKeyedRegistration Registration { get; } = registration; } -public abstract class KeyedRegistration : IKeyedRegistration { - protected KeyedRegistration(string key) { +public abstract class KeyedRegistration : IKeyedRegistration +{ + protected KeyedRegistration(string key) + { Key = key; } public string Key { get; } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/KeyedTests/KeyedRegistrationTests.cs b/integ-tests/SutProject.Tests/KeyedTests/KeyedRegistrationTests.cs index f48d12b..8c7246d 100644 --- a/integ-tests/SutProject.Tests/KeyedTests/KeyedRegistrationTests.cs +++ b/integ-tests/SutProject.Tests/KeyedTests/KeyedRegistrationTests.cs @@ -4,40 +4,46 @@ namespace SutProject.Tests.KeyedTests; -public class KeyedRegistrationTests { +public class KeyedRegistrationTests +{ [ModuleTest] [KeyedModule] - public void AKeyTest(IServiceProvider serviceProvider) { + public void AKeyTest(IServiceProvider serviceProvider) + { var aService = serviceProvider.GetKeyedService("A"); - + Assert.NotNull(aService); Assert.Equal("A", aService.Key); } - + [ModuleTest] [KeyedModule] - public void BKeyTest(IServiceProvider serviceProvider) { + public void BKeyTest(IServiceProvider serviceProvider) + { var aService = serviceProvider.GetKeyedService("B"); - + Assert.NotNull(aService); Assert.Equal("B", aService.Key); } - + [ModuleTest] [KeyedModule] - public void CKeyTest(IServiceProvider serviceProvider) { + public void CKeyTest(IServiceProvider serviceProvider) + { var aService = serviceProvider.GetKeyedService("C"); - + Assert.NotNull(aService); Assert.Equal("C", aService.Key); } - + [ModuleTest] [KeyedModule] public void AllKeyTest( - [FromKeyedServices("A")] IKeyedRegistration aService, - [FromKeyedServices("B")] IKeyedRegistration bService, - [FromKeyedServices("C")] IKeyedRegistration cService) { + [FromKeyedServices("A")] IKeyedRegistration aService, + [FromKeyedServices("B")] IKeyedRegistration bService, + [FromKeyedServices("C")] IKeyedRegistration cService + ) + { Assert.NotNull(aService); Assert.NotNull(bService); Assert.NotNull(cService); @@ -45,4 +51,4 @@ public void AllKeyTest( Assert.Equal("B", bService.Key); Assert.Equal("C", cService.Key); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/Moq/MoqAttributeTests.cs b/integ-tests/SutProject.Tests/Moq/MoqAttributeTests.cs index dec82ac..fd01653 100644 --- a/integ-tests/SutProject.Tests/Moq/MoqAttributeTests.cs +++ b/integ-tests/SutProject.Tests/Moq/MoqAttributeTests.cs @@ -12,12 +12,16 @@ namespace SutProject.Tests.Moq; /// object, so a test can name either — and the two have to agree about which mock they mean. /// [MoqSupport] -public class MoqAttributeTests { - +public class MoqAttributeTests +{ [ModuleTest] [SutModule] - public void MockTest([Mock] IDependencyOne dependencyOne, - [Mock] IScopedService scopedService, ISingletonService singletonService) { + public void MockTest( + [Mock] IDependencyOne dependencyOne, + [Mock] IScopedService scopedService, + ISingletonService singletonService + ) + { Mock.Get(dependencyOne).Setup(x => x.SingletonService).Returns(singletonService); Mock.Get(dependencyOne).Setup(x => x.ScopedService).Returns(scopedService); @@ -30,7 +34,8 @@ public void MockTest([Mock] IDependencyOne dependencyOne, /// [ModuleTest] [SutModule] - public void UnconfiguredMembersAreLoose([Mock] IDependencyOne dependencyOne) { + public void UnconfiguredMembersAreLoose([Mock] IDependencyOne dependencyOne) + { Assert.Null(dependencyOne.ScopedService); } @@ -47,7 +52,10 @@ public void UnconfiguredMembersAreLoose([Mock] IDependencyOne dependencyOne) { [ModuleTest] [SutModule] public void MockOfTIsInjectedDirectly( - Mock mock, ISingletonService singletonService) { + Mock mock, + ISingletonService singletonService + ) + { mock.Setup(x => x.GetName()).Returns("mocked"); Assert.Same(mock.Object, singletonService); @@ -61,7 +69,10 @@ public void MockOfTIsInjectedDirectly( [ModuleTest] [SutModule] public void MockOfTIsInjectedWithTheAttributeToo( - [Mock] Mock mock, ISingletonService singletonService) { + [Mock] Mock mock, + ISingletonService singletonService + ) + { mock.Setup(x => x.GetName()).Returns("mocked"); Assert.Same(mock.Object, singletonService); @@ -76,7 +87,10 @@ public void MockOfTIsInjectedWithTheAttributeToo( [ModuleTest] [SutModule] public void ServiceUnderTestIsBuiltAgainstTheMock( - IDependencyOne dependencyOne, Mock singletonService) { + IDependencyOne dependencyOne, + Mock singletonService + ) + { singletonService.Setup(x => x.GetName()).Returns("mocked"); Assert.Same(singletonService.Object, dependencyOne.SingletonService); @@ -91,7 +105,10 @@ public void ServiceUnderTestIsBuiltAgainstTheMock( [ModuleTest] [SutModule] public void TheMockAndTheInstanceAreOnePair( - [Mock] ISingletonService instance, Mock mock) { + [Mock] ISingletonService instance, + Mock mock + ) + { mock.Setup(x => x.GetName()).Returns("mocked"); Assert.Same(mock.Object, instance); @@ -105,7 +122,10 @@ public void TheMockAndTheInstanceAreOnePair( [ModuleTest] [SutModule] public void RepeatedMockParametersShareOneMock( - Mock first, Mock second) { + Mock first, + Mock second + ) + { first.Setup(x => x.GetName()).Returns("mocked"); Assert.Same(first, second); @@ -122,7 +142,9 @@ public void RepeatedMockParametersShareOneMock( public void DifferentServicesGetDifferentMocks( Mock singletonService, Mock scopedService, - IDependencyOne dependencyOne) { + IDependencyOne dependencyOne + ) + { Assert.Same(singletonService.Object, dependencyOne.SingletonService); Assert.Same(scopedService.Object, dependencyOne.ScopedService); } @@ -136,7 +158,10 @@ public void DifferentServicesGetDifferentMocks( [SutModule] [TestExport(typeof(ISingletonService), Implementation = typeof(ExportedSingletonService))] public void TestExportStillWinsOverAMock( - ISingletonService instance, Mock mock) { + ISingletonService instance, + Mock mock + ) + { Assert.IsType(instance); Assert.NotSame(mock.Object, instance); } @@ -154,7 +179,8 @@ public void TestExportStillWinsOverAMock( [ModuleTest] [SutModule] [TestExport(typeof(ISingletonService), Implementation = typeof(ExportedSingletonService))] - public void AMockOnAParameterBeatsATestExportOnTheMethod([Mock] ISingletonService service) { + public void AMockOnAParameterBeatsATestExportOnTheMethod([Mock] ISingletonService service) + { Assert.IsNotType(service); global::Moq.Mock.Get(service).Setup(x => x.GetName()).Returns("mocked"); @@ -163,7 +189,8 @@ public void AMockOnAParameterBeatsATestExportOnTheMethod([Mock] ISingletonServic } #pragma warning restore DM0021 - public class ExportedSingletonService : ISingletonService { + public class ExportedSingletonService : ISingletonService + { public string GetName() => "exported"; } } @@ -176,13 +203,17 @@ public class ExportedSingletonService : ISingletonService { /// putting it on the fixture above would have changed every test there, which is the point of it. /// [MoqSupport] -[TestExport(typeof(IScopedService), Implementation = typeof(ClassLevelTestExportTests.ExportedScopedService))] -public class ClassLevelTestExportTests { - +[TestExport( + typeof(IScopedService), + Implementation = typeof(ClassLevelTestExportTests.ExportedScopedService) +)] +public class ClassLevelTestExportTests +{ /// The default, taken by any test that does not say otherwise. [ModuleTest] [SutModule] - public void WithoutAMockTheClassDefaultApplies(IScopedService service) { + public void WithoutAMockTheClassDefaultApplies(IScopedService service) + { Assert.IsType(service); } @@ -192,7 +223,8 @@ public void WithoutAMockTheClassDefaultApplies(IScopedService service) { /// [ModuleTest] [SutModule] - public void AMockOnAParameterBeatsIt([Mock] IScopedService service) { + public void AMockOnAParameterBeatsIt([Mock] IScopedService service) + { Assert.IsNotType(service); // Reachable as a mock, which is the point - the parameter is the double, not the export. diff --git a/integ-tests/SutProject.Tests/Moq/MoqSetupOrderingTests.cs b/integ-tests/SutProject.Tests/Moq/MoqSetupOrderingTests.cs index b4c52f8..bb95d11 100644 --- a/integ-tests/SutProject.Tests/Moq/MoqSetupOrderingTests.cs +++ b/integ-tests/SutProject.Tests/Moq/MoqSetupOrderingTests.cs @@ -13,7 +13,8 @@ namespace SutProject.Tests.Moq; /// Declared outside the fixture because the attribute naming it sits on the fixture itself, and /// attribute arguments there resolve in the enclosing scope rather than the class's own. /// -public class ExportedSingletonService : ISingletonService { +public class ExportedSingletonService : ISingletonService +{ public string GetName() => "exported"; } @@ -37,13 +38,16 @@ public class ExportedSingletonService : ISingletonService { /// that test still passes; this one does not. /// [TestExport(typeof(ISingletonService), Implementation = typeof(ExportedSingletonService))] -public class MoqSetupOrderingTests { - +public class MoqSetupOrderingTests +{ [ModuleTest] [SutModule] [MoqSupport] public void ExplicitRegistrationBeatsAMockDeclaredNearerTheMethod( - ISingletonService instance, Mock mock) { + ISingletonService instance, + Mock mock + ) + { Assert.IsType(instance); Assert.Equal("exported", instance.GetName()); diff --git a/integ-tests/SutProject.Tests/NSubstitute/NSubstituteAttributeTests.cs b/integ-tests/SutProject.Tests/NSubstitute/NSubstituteAttributeTests.cs index 34a2b4c..96d12bf 100644 --- a/integ-tests/SutProject.Tests/NSubstitute/NSubstituteAttributeTests.cs +++ b/integ-tests/SutProject.Tests/NSubstitute/NSubstituteAttributeTests.cs @@ -1,22 +1,26 @@ +using DependencyModules.NSubstitute; using DependencyModules.Testing.Attributes; using DependencyModules.xUnit.Attributes; -using DependencyModules.NSubstitute; using NSubstitute; using Xunit; namespace SutProject.Tests.NSubstitute; [NSubstituteSupport] -public class NSubstituteAttributeTests { - +public class NSubstituteAttributeTests +{ [ModuleTest] [SutModule] - public void MockTest([Mock] IDependencyOne dependencyOne, - [Mock] IScopedService scopedService, ISingletonService singletonService) { + public void MockTest( + [Mock] IDependencyOne dependencyOne, + [Mock] IScopedService scopedService, + ISingletonService singletonService + ) + { dependencyOne.SingletonService.Returns(singletonService); dependencyOne.ScopedService.Returns(scopedService); Assert.Same(dependencyOne.SingletonService, singletonService); Assert.Same(dependencyOne.ScopedService, scopedService); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/ParameterizedModuleTests/ArrayParameterModule.cs b/integ-tests/SutProject.Tests/ParameterizedModuleTests/ArrayParameterModule.cs index cfe6677..2297986 100644 --- a/integ-tests/SutProject.Tests/ParameterizedModuleTests/ArrayParameterModule.cs +++ b/integ-tests/SutProject.Tests/ParameterizedModuleTests/ArrayParameterModule.cs @@ -3,9 +3,10 @@ namespace SutProject.Tests.ParameterizedModuleTests; [DependencyModule] -public partial class ArrayParameterModule { +public partial class ArrayParameterModule +{ public string[]? ArrayParameter { get; set; } = []; - + public Type? TypeValue { get; set; } // The other answer DM0018 accepts: this fixture is composed once, so type-only identity is @@ -15,9 +16,6 @@ public partial class ArrayParameterModule { public override int GetHashCode() => typeof(ArrayParameterModule).GetHashCode(); } - [DependencyModule] [ArrayParameterModule(ArrayParameter = ["A", "B"], TypeValue = typeof(int))] -public partial class AnotherModule { - -} \ No newline at end of file +public partial class AnotherModule { } diff --git a/integ-tests/SutProject.Tests/ParameterizedModuleTests/DefaultedParameterModule.cs b/integ-tests/SutProject.Tests/ParameterizedModuleTests/DefaultedParameterModule.cs index 02913d9..38e9639 100644 --- a/integ-tests/SutProject.Tests/ParameterizedModuleTests/DefaultedParameterModule.cs +++ b/integ-tests/SutProject.Tests/ParameterizedModuleTests/DefaultedParameterModule.cs @@ -4,7 +4,8 @@ namespace SutProject.Tests.ParameterizedModuleTests; -public class DefaultedParameterValues(string label, int size) { +public class DefaultedParameterValues(string label, int size) +{ public string Label { get; } = label; public int Size { get; } = size; @@ -25,12 +26,14 @@ public class DefaultedParameterValues(string label, int size) { /// behaviour. /// [DependencyModule] -public partial class DefaultedParameterModule : IServiceCollectionConfiguration { +public partial class DefaultedParameterModule : IServiceCollectionConfiguration +{ public string Label { get; set; } = "default-label"; public int Size { get; set; } = 42; - public void ConfigureServices(IServiceCollection services) { + public void ConfigureServices(IServiceCollection services) + { services.AddSingleton(new DefaultedParameterValues(Label, Size)); } diff --git a/integ-tests/SutProject.Tests/ParameterizedModuleTests/DefaultedParameterModuleTests.cs b/integ-tests/SutProject.Tests/ParameterizedModuleTests/DefaultedParameterModuleTests.cs index 362c215..0acdf09 100644 --- a/integ-tests/SutProject.Tests/ParameterizedModuleTests/DefaultedParameterModuleTests.cs +++ b/integ-tests/SutProject.Tests/ParameterizedModuleTests/DefaultedParameterModuleTests.cs @@ -3,13 +3,14 @@ namespace SutProject.Tests.ParameterizedModuleTests; -public class DefaultedParameterModuleTests { - +public class DefaultedParameterModuleTests +{ /// /// A composition that names no parameters leaves the module's own initialiser in place. /// [ModuleTest(typeof(DefaultedParameterComposer))] - public void AnUnnamedReferenceParameter_KeepsItsDefault(DefaultedParameterValues values) { + public void AnUnnamedReferenceParameter_KeepsItsDefault(DefaultedParameterValues values) + { Assert.Equal("default-label", values.Label); } @@ -19,12 +20,14 @@ public void AnUnnamedReferenceParameter_KeepsItsDefault(DefaultedParameterValues /// module parameter's initialiser does not survive composition by attribute. /// [ModuleTest(typeof(DefaultedParameterComposer))] - public void AnUnnamedValueParameter_DoesNotKeepItsDefault(DefaultedParameterValues values) { + public void AnUnnamedValueParameter_DoesNotKeepItsDefault(DefaultedParameterValues values) + { Assert.Equal(0, values.Size); } [ModuleTest(typeof(NamedParameterComposer))] - public void NamedParameters_AreCarriedAcross(DefaultedParameterValues values) { + public void NamedParameters_AreCarriedAcross(DefaultedParameterValues values) + { Assert.Equal("named-label", values.Label); Assert.Equal(7, values.Size); } diff --git a/integ-tests/SutProject.Tests/ParameterizedModuleTests/LocalParameterizedModule.cs b/integ-tests/SutProject.Tests/ParameterizedModuleTests/LocalParameterizedModule.cs index 9d76157..6f7e408 100644 --- a/integ-tests/SutProject.Tests/ParameterizedModuleTests/LocalParameterizedModule.cs +++ b/integ-tests/SutProject.Tests/ParameterizedModuleTests/LocalParameterizedModule.cs @@ -7,8 +7,7 @@ namespace SutProject.Tests.ParameterizedModuleTests; [DependencyModule] [ParameterizedModule("local-string", 20, C = "CValue")] -public partial class LocalParameterizedModule : IServiceCollectionConfiguration { - public void ConfigureServices(IServiceCollection services) { - - } -} \ No newline at end of file +public partial class LocalParameterizedModule : IServiceCollectionConfiguration +{ + public void ConfigureServices(IServiceCollection services) { } +} diff --git a/integ-tests/SutProject.Tests/ParameterizedModuleTests/LocalParameterizedModuleTests.cs b/integ-tests/SutProject.Tests/ParameterizedModuleTests/LocalParameterizedModuleTests.cs index 53eb977..6e8da08 100644 --- a/integ-tests/SutProject.Tests/ParameterizedModuleTests/LocalParameterizedModuleTests.cs +++ b/integ-tests/SutProject.Tests/ParameterizedModuleTests/LocalParameterizedModuleTests.cs @@ -4,11 +4,13 @@ namespace SutProject.Tests.ParameterizedModuleTests; -public class LocalParameterizedModuleTests { +public class LocalParameterizedModuleTests +{ [ModuleTest(typeof(LocalParameterizedModule))] - public void ParameterTest(SomeRuntimeDependency someRuntimeDependency) { + public void ParameterTest(SomeRuntimeDependency someRuntimeDependency) + { Assert.Equal("local-string", someRuntimeDependency.SomeDependency); Assert.Equal(20, someRuntimeDependency.IntDependency); Assert.Equal("CValue", someRuntimeDependency.CValue); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/RealmTests/FirstRealmModule.cs b/integ-tests/SutProject.Tests/RealmTests/FirstRealmModule.cs index a9cad53..74f9168 100644 --- a/integ-tests/SutProject.Tests/RealmTests/FirstRealmModule.cs +++ b/integ-tests/SutProject.Tests/RealmTests/FirstRealmModule.cs @@ -3,4 +3,4 @@ namespace SutProject.Tests.RealmTests; [DependencyModule(OnlyRealm = true)] -public partial class FirstRealmModule { } \ No newline at end of file +public partial class FirstRealmModule { } diff --git a/integ-tests/SutProject.Tests/RealmTests/RealmDependencyOne.cs b/integ-tests/SutProject.Tests/RealmTests/RealmDependencyOne.cs index 169031c..175e36c 100644 --- a/integ-tests/SutProject.Tests/RealmTests/RealmDependencyOne.cs +++ b/integ-tests/SutProject.Tests/RealmTests/RealmDependencyOne.cs @@ -2,18 +2,20 @@ namespace SutProject.Tests.RealmTests; -[TransientService(As = typeof(IDependencyOne), Realm = typeof(FirstRealmModule), Using = RegistrationType.Add)] -public class RealmDependencyOne : IDependencyOne { - public RealmDependencyOne(ISingletonService singletonService, IScopedService scopedService) { +[TransientService( + As = typeof(IDependencyOne), + Realm = typeof(FirstRealmModule), + Using = RegistrationType.Add +)] +public class RealmDependencyOne : IDependencyOne +{ + public RealmDependencyOne(ISingletonService singletonService, IScopedService scopedService) + { SingletonService = singletonService; ScopedService = scopedService; } - public ISingletonService SingletonService { - get; - } + public ISingletonService SingletonService { get; } - public IScopedService ScopedService { - get; - } -} \ No newline at end of file + public IScopedService ScopedService { get; } +} diff --git a/integ-tests/SutProject.Tests/RealmTests/RealmIsolationTests.cs b/integ-tests/SutProject.Tests/RealmTests/RealmIsolationTests.cs index 6f85a9c..af8fd4f 100644 --- a/integ-tests/SutProject.Tests/RealmTests/RealmIsolationTests.cs +++ b/integ-tests/SutProject.Tests/RealmTests/RealmIsolationTests.cs @@ -4,11 +4,13 @@ namespace SutProject.Tests.RealmTests; -public class RealmIsolationTests { +public class RealmIsolationTests +{ [ModuleTest] [FirstRealmModule] [SecondarySutModule] - public void OverrideDependencyWithRealm(IDependencyOne dependencyOne) { + public void OverrideDependencyWithRealm(IDependencyOne dependencyOne) + { Assert.IsType(dependencyOne); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/RegistrationTypeTests/ReplaceTests.cs b/integ-tests/SutProject.Tests/RegistrationTypeTests/ReplaceTests.cs index df2c9c2..2d3e376 100644 --- a/integ-tests/SutProject.Tests/RegistrationTypeTests/ReplaceTests.cs +++ b/integ-tests/SutProject.Tests/RegistrationTypeTests/ReplaceTests.cs @@ -6,33 +6,30 @@ namespace SutProject.Tests.RegistrationTypeTests; [DependencyModule(OnlyRealm = true)] [SutModule] -public partial class ReplaceModule { - -} +public partial class ReplaceModule { } [SingletonService(Using = RegistrationType.Replace, Realm = typeof(ReplaceModule))] -public class ReplaceDependency : IDependencyOne { - - public ReplaceDependency(ISingletonService singletonService, IScopedService scopedService) { +public class ReplaceDependency : IDependencyOne +{ + public ReplaceDependency(ISingletonService singletonService, IScopedService scopedService) + { SingletonService = singletonService; ScopedService = scopedService; } - public ISingletonService SingletonService { - get; - } + public ISingletonService SingletonService { get; } - public IScopedService ScopedService { - get; - } + public IScopedService ScopedService { get; } } -public class ReplaceTests { +public class ReplaceTests +{ [ModuleTest] [ReplaceModule] - public void ReplaceTest(IEnumerable dependencies) { + public void ReplaceTest(IEnumerable dependencies) + { var dependenciesList = dependencies.ToList(); Assert.Single(dependenciesList); Assert.IsType(dependenciesList[0]); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/RegistrationTypeTests/TryRegistrationTests.cs b/integ-tests/SutProject.Tests/RegistrationTypeTests/TryRegistrationTests.cs index 9b87438..201c3ea 100644 --- a/integ-tests/SutProject.Tests/RegistrationTypeTests/TryRegistrationTests.cs +++ b/integ-tests/SutProject.Tests/RegistrationTypeTests/TryRegistrationTests.cs @@ -17,37 +17,36 @@ public partial class TryAtModuleLevelWithSutModule; [SingletonService(Using = RegistrationType.Try, Realm = typeof(TryWithSutModule))] [SingletonService(Using = RegistrationType.Try, Realm = typeof(TryWithoutSutModule))] -#pragma warning disable CS8618 -public class TryDependency : IDependencyOne { +#pragma warning disable CS8618 +public class TryDependency : IDependencyOne +{ + public ISingletonService SingletonService { get; } - public ISingletonService SingletonService { - get; - } - - public IScopedService ScopedService { - get; - } + public IScopedService ScopedService { get; } } - #pragma warning restore CS8618 -public class TryRegistrationTests { +public class TryRegistrationTests +{ [ModuleTest] [TryWithSutModule] - public void TryWithSut(IDependencyOne service) { + public void TryWithSut(IDependencyOne service) + { Assert.IsType(service); } [ModuleTest] [TryWithoutSutModule] - public void TryWithoutSut(IDependencyOne service) { + public void TryWithoutSut(IDependencyOne service) + { Assert.IsType(service); } [ModuleTest] [TryAtModuleLevelWithSutModule] - public void TryModuleLevelSut(IDependencyOne service) { + public void TryModuleLevelSut(IDependencyOne service) + { Assert.IsType(service); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/StandardTests/AttributeTest.cs b/integ-tests/SutProject.Tests/StandardTests/AttributeTest.cs index c651c13..07b755f 100644 --- a/integ-tests/SutProject.Tests/StandardTests/AttributeTest.cs +++ b/integ-tests/SutProject.Tests/StandardTests/AttributeTest.cs @@ -4,33 +4,27 @@ namespace SutProject.Tests.StandardTests; -public interface ICustomAttributeInterface { - -} +public interface ICustomAttributeInterface { } -public partial class AttributeTestModuleAttribute : ICustomAttributeInterface{ - -} +public partial class AttributeTestModuleAttribute : ICustomAttributeInterface { } [DependencyModule(OnlyRealm = true)] -public partial class AttributeTestModule { - -} +public partial class AttributeTestModule { } [DependencyModule] [AttributeTestModule] -public partial class SomeModule { - -} +public partial class SomeModule { } -public class AttributeTest { +public class AttributeTest +{ [Fact] - public void AttributePartialTest() { + public void AttributePartialTest() + { var attributeType = typeof(AttributeTestModuleAttribute); var interfaces = attributeType.GetInterfaces(); - + Assert.Contains(interfaces, i => i == typeof(ICustomAttributeInterface)); Assert.Contains(interfaces, i => i == typeof(IDependencyModuleProvider)); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/StandardTests/AutoRegistrationTest.cs b/integ-tests/SutProject.Tests/StandardTests/AutoRegistrationTest.cs index 69f24a4..b3b0681 100644 --- a/integ-tests/SutProject.Tests/StandardTests/AutoRegistrationTest.cs +++ b/integ-tests/SutProject.Tests/StandardTests/AutoRegistrationTest.cs @@ -6,21 +6,19 @@ namespace SutProject.Tests.StandardTests; [DependencyModule(OnlyRealm = true)] [SutModule] -public partial class AutoRegisterModule { - -} +public partial class AutoRegisterModule { } [SingletonService(Realm = typeof(AutoRegisterModule))] -public class InheritDependencyOne - (ISingletonService singletonService, IScopedService scopedService) - : DependencyOne(singletonService, scopedService), IDependencyOne { - -} +public class InheritDependencyOne(ISingletonService singletonService, IScopedService scopedService) + : DependencyOne(singletonService, scopedService), + IDependencyOne { } -public class AutoRegistrationTest { +public class AutoRegistrationTest +{ [ModuleTest] [AutoRegisterModule] - public void AutoRegisterClassWithInheritance(IDependencyOne dependencyOne) { + public void AutoRegisterClassWithInheritance(IDependencyOne dependencyOne) + { Assert.IsType(dependencyOne); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/StandardTests/DuplicateModules.cs b/integ-tests/SutProject.Tests/StandardTests/DuplicateModules.cs index ab1451c..89aff0b 100644 --- a/integ-tests/SutProject.Tests/StandardTests/DuplicateModules.cs +++ b/integ-tests/SutProject.Tests/StandardTests/DuplicateModules.cs @@ -20,4 +20,4 @@ public partial class CombinedModule; [TestModule] [TestModule2] [TestModule2] -public partial class DuplicateModule; \ No newline at end of file +public partial class DuplicateModule; diff --git a/integ-tests/SutProject.Tests/StandardTests/LifetimeSemanticsTests.cs b/integ-tests/SutProject.Tests/StandardTests/LifetimeSemanticsTests.cs index 02b1958..8f81d6e 100644 --- a/integ-tests/SutProject.Tests/StandardTests/LifetimeSemanticsTests.cs +++ b/integ-tests/SutProject.Tests/StandardTests/LifetimeSemanticsTests.cs @@ -14,11 +14,12 @@ namespace SutProject.Tests.StandardTests; /// Assert.Same(a, b) passes either way. Distinguishing them requires crossing a scope /// boundary, which is what these tests do. /// -public class LifetimeSemanticsTests { - +public class LifetimeSemanticsTests +{ [ModuleTest] [SutModule] - public void Singleton_IsTheSameInstanceAcrossScopes(IServiceProvider provider) { + public void Singleton_IsTheSameInstanceAcrossScopes(IServiceProvider provider) + { using var first = provider.CreateScope(); using var second = provider.CreateScope(); @@ -31,7 +32,8 @@ public void Singleton_IsTheSameInstanceAcrossScopes(IServiceProvider provider) { [ModuleTest] [SutModule] - public void Singleton_IsTheSameInstanceAsTheRootProviders(IServiceProvider provider) { + public void Singleton_IsTheSameInstanceAsTheRootProviders(IServiceProvider provider) + { var fromRoot = provider.GetService(); using var scope = provider.CreateScope(); @@ -41,7 +43,8 @@ public void Singleton_IsTheSameInstanceAsTheRootProviders(IServiceProvider provi [ModuleTest] [SutModule] - public void Scoped_IsSharedWithinAScope(IServiceProvider provider) { + public void Scoped_IsSharedWithinAScope(IServiceProvider provider) + { using var scope = provider.CreateScope(); var first = scope.ServiceProvider.GetService(); @@ -57,7 +60,8 @@ public void Scoped_IsSharedWithinAScope(IServiceProvider provider) { /// [ModuleTest] [SutModule] - public void Scoped_DiffersBetweenScopes(IServiceProvider provider) { + public void Scoped_DiffersBetweenScopes(IServiceProvider provider) + { using var first = provider.CreateScope(); using var second = provider.CreateScope(); @@ -74,7 +78,8 @@ public void Scoped_DiffersBetweenScopes(IServiceProvider provider) { /// [ModuleTest] [SutModule] - public void Transient_IsANewInstanceEveryResolution(IServiceProvider provider) { + public void Transient_IsANewInstanceEveryResolution(IServiceProvider provider) + { var first = provider.GetService(); var second = provider.GetService(); @@ -85,7 +90,8 @@ public void Transient_IsANewInstanceEveryResolution(IServiceProvider provider) { [ModuleTest] [SutModule] - public void Transient_IsANewInstanceWithinASingleScope(IServiceProvider provider) { + public void Transient_IsANewInstanceWithinASingleScope(IServiceProvider provider) + { using var scope = provider.CreateScope(); var first = scope.ServiceProvider.GetService(); @@ -96,21 +102,25 @@ public void Transient_IsANewInstanceWithinASingleScope(IServiceProvider provider [ModuleTest] [SutModule] - public void RegisteredLifetimes_MatchTheirAttributes(IServiceProvider provider) { + public void RegisteredLifetimes_MatchTheirAttributes(IServiceProvider provider) + { // Resolving proves the wiring; the descriptors prove the lifetime the generator chose. var collection = new ServiceCollection(); collection.AddModule(); Assert.Equal( ServiceLifetime.Singleton, - Assert.Single(collection, d => d.ServiceType == typeof(ISingletonService)).Lifetime); + Assert.Single(collection, d => d.ServiceType == typeof(ISingletonService)).Lifetime + ); Assert.Equal( ServiceLifetime.Scoped, - Assert.Single(collection, d => d.ServiceType == typeof(IScopedService)).Lifetime); + Assert.Single(collection, d => d.ServiceType == typeof(IScopedService)).Lifetime + ); Assert.Equal( ServiceLifetime.Transient, - Assert.Single(collection, d => d.ServiceType == typeof(IDependencyOne)).Lifetime); + Assert.Single(collection, d => d.ServiceType == typeof(IDependencyOne)).Lifetime + ); } } diff --git a/integ-tests/SutProject.Tests/StandardTests/ModuleDuplicationTests.cs b/integ-tests/SutProject.Tests/StandardTests/ModuleDuplicationTests.cs index e4b14a1..c7f17c7 100644 --- a/integ-tests/SutProject.Tests/StandardTests/ModuleDuplicationTests.cs +++ b/integ-tests/SutProject.Tests/StandardTests/ModuleDuplicationTests.cs @@ -5,49 +5,55 @@ namespace SutProject.Tests.StandardTests; -public class ModuleDuplicationTests { +public class ModuleDuplicationTests +{ [ModuleTest] [CombinedModule] - public void CombinedModuleTest(IEnumerable dependencies) { + public void CombinedModuleTest(IEnumerable dependencies) + { Assert.Single(dependencies); } - + [ModuleTest] [DuplicateModule] - public void DuplicateModuleTest(IEnumerable dependencies) { + public void DuplicateModuleTest(IEnumerable dependencies) + { Assert.Single(dependencies); } [ModuleTest] [CombinedModule] [DuplicateModule] - public void CombinedAndDuplicateModuleTest(IEnumerable dependencies) { + public void CombinedAndDuplicateModuleTest(IEnumerable dependencies) + { Assert.Single(dependencies); } [Fact] - public void CombinedModuleAddModules() { + public void CombinedModuleAddModules() + { var serviceCollection = new ServiceCollection(); serviceCollection.AddModules(new CombinedModule()); - + var serviceProvider = serviceCollection.BuildServiceProvider(); - + var dependencies = serviceProvider.GetServices(); - + Assert.Single(dependencies); } [Fact] - public void MultipleDuplicatesAddModules() { + public void MultipleDuplicatesAddModules() + { var serviceCollection = new ServiceCollection(); serviceCollection.AddModules(new CombinedModule(), new DuplicateModule()); - + var serviceProvider = serviceCollection.BuildServiceProvider(); - + var dependencies = serviceProvider.GetServices(); - + Assert.Single(dependencies); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/StandardTests/MultipleModulesTests.cs b/integ-tests/SutProject.Tests/StandardTests/MultipleModulesTests.cs index d76a7df..f8e65ec 100644 --- a/integ-tests/SutProject.Tests/StandardTests/MultipleModulesTests.cs +++ b/integ-tests/SutProject.Tests/StandardTests/MultipleModulesTests.cs @@ -6,51 +6,61 @@ namespace SutProject.Tests.StandardTests; -public class StringWrapper(string value) { +public class StringWrapper(string value) +{ public string Value { get; } = value; } [DependencyModule] -public partial class UniqueModule : IServiceCollectionConfiguration{ +public partial class UniqueModule : IServiceCollectionConfiguration +{ private readonly string _value; - public UniqueModule(string value) { + public UniqueModule(string value) + { _value = value; } - - public override bool Equals(object? obj) { - if (obj is UniqueModule other) { + + public override bool Equals(object? obj) + { + if (obj is UniqueModule other) + { return _value == other._value; } return false; } - - public override int GetHashCode() { + + public override int GetHashCode() + { return _value.GetHashCode(); } - public void ConfigureServices(IServiceCollection services) { + public void ConfigureServices(IServiceCollection services) + { services.AddTransient(_ => new StringWrapper(_value)); } } -public class MultipleModulesTests { +public class MultipleModulesTests +{ [ModuleTest] [UniqueModule("test-value")] [UniqueModule("test-value-2")] - public void MultipleModuleTest(IEnumerable wrappers) { + public void MultipleModuleTest(IEnumerable wrappers) + { var wrapperList = wrappers.ToList(); Assert.Equal(2, wrapperList.Count); Assert.Contains(wrapperList, wrapper => wrapper.Value == "test-value"); Assert.Contains(wrapperList, wrapper => wrapper.Value == "test-value-2"); } - + [ModuleTest] [UniqueModule("test-value")] [UniqueModule("test-value")] - public void SameMultipleModuleTest(IEnumerable wrappers) { + public void SameMultipleModuleTest(IEnumerable wrappers) + { var wrapperList = wrappers.ToList(); Assert.Single(wrapperList); Assert.Contains(wrapperList, wrapper => wrapper.Value == "test-value"); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/StandardTests/NoNamespaceTests.cs b/integ-tests/SutProject.Tests/StandardTests/NoNamespaceTests.cs index b823f35..411a5a0 100644 --- a/integ-tests/SutProject.Tests/StandardTests/NoNamespaceTests.cs +++ b/integ-tests/SutProject.Tests/StandardTests/NoNamespaceTests.cs @@ -4,33 +4,28 @@ using Xunit; [DependencyModule] -public partial class TestModule { - -} +public partial class TestModule { } [DependencyModule(OnlyRealm = true)] -public partial class NoNamespaceTestModule { - -} +public partial class NoNamespaceTestModule { } #pragma warning disable CS8618 [SingletonService(Realm = typeof(NoNamespaceTestModule))] -public class SomeDependency : IDependencyOne { - public ISingletonService SingletonService { - get; - } +public class SomeDependency : IDependencyOne +{ + public ISingletonService SingletonService { get; } - public IScopedService ScopedService { - get; - } + public IScopedService ScopedService { get; } } -#pragma warning restore CS8618 +#pragma warning restore CS8618 -public class NoNamespaceTests { +public class NoNamespaceTests +{ [ModuleTest] [NoNamespaceTestModule] - public void NoNamespaceTest(IDependencyOne dependency) { + public void NoNamespaceTest(IDependencyOne dependency) + { Assert.NotNull(dependency); Assert.IsType(dependency); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/StandardTests/RecordModuleModels.cs b/integ-tests/SutProject.Tests/StandardTests/RecordModuleModels.cs index fb6f455..2cf4b42 100644 --- a/integ-tests/SutProject.Tests/StandardTests/RecordModuleModels.cs +++ b/integ-tests/SutProject.Tests/StandardTests/RecordModuleModels.cs @@ -2,12 +2,14 @@ namespace SutProject.Tests.StandardTests; -public interface IRecordModuleService { +public interface IRecordModuleService +{ string Value { get; } } [SingletonService(Realm = typeof(RecordModule))] -public class RecordModuleService : IRecordModuleService { +public class RecordModuleService : IRecordModuleService +{ public string Value => "FromRecordModule"; } diff --git a/integ-tests/SutProject.Tests/StandardTests/RecordTests.cs b/integ-tests/SutProject.Tests/StandardTests/RecordTests.cs index 1e4577f..d51b70e 100644 --- a/integ-tests/SutProject.Tests/StandardTests/RecordTests.cs +++ b/integ-tests/SutProject.Tests/StandardTests/RecordTests.cs @@ -3,25 +3,30 @@ namespace SutProject.Tests.StandardTests; -public class RecordServiceTests { +public class RecordServiceTests +{ [ModuleTest] [SutModule] - public void ResolveRecordService(IRecordService recordService) { + public void ResolveRecordService(IRecordService recordService) + { Assert.NotNull(recordService); Assert.Equal("RecordService", recordService.GetName()); } [ModuleTest] [SutModule] - public void RecordServiceIsSingleton(IRecordService first, IRecordService second) { + public void RecordServiceIsSingleton(IRecordService first, IRecordService second) + { Assert.Same(first, second); } } -public class RecordModuleTests { +public class RecordModuleTests +{ [ModuleTest] [RecordModule] - public void ResolveServiceFromRecordModule(IRecordModuleService service) { + public void ResolveServiceFromRecordModule(IRecordModuleService service) + { Assert.NotNull(service); Assert.Equal("FromRecordModule", service.Value); } diff --git a/integ-tests/SutProject.Tests/StandardTests/SecondarySutProjectTests.cs b/integ-tests/SutProject.Tests/StandardTests/SecondarySutProjectTests.cs index fdc9d82..d5d48cb 100644 --- a/integ-tests/SutProject.Tests/StandardTests/SecondarySutProjectTests.cs +++ b/integ-tests/SutProject.Tests/StandardTests/SecondarySutProjectTests.cs @@ -4,10 +4,12 @@ namespace SutProject.Tests.StandardTests; -public class SecondarySutProjectTests { +public class SecondarySutProjectTests +{ [ModuleTest] [SecondarySutModule] - public void OverrideDependency(IDependencyOne dependencyOne) { + public void OverrideDependency(IDependencyOne dependencyOne) + { Assert.IsType(dependencyOne); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/StandardTests/SerializerTests.cs b/integ-tests/SutProject.Tests/StandardTests/SerializerTests.cs index b81429c..ef78070 100644 --- a/integ-tests/SutProject.Tests/StandardTests/SerializerTests.cs +++ b/integ-tests/SutProject.Tests/StandardTests/SerializerTests.cs @@ -5,12 +5,14 @@ namespace SutProject.Tests.StandardTests; -public class SerializerTests { +public class SerializerTests +{ [ModuleTest] [SerializerClasses] - public void LoadSerializer(IEnumerable resolvers) { + public void LoadSerializer(IEnumerable resolvers) + { var resolverList = resolvers.ToList(); - + Assert.Single(resolverList); Assert.IsType(resolverList.First()); } @@ -19,11 +21,11 @@ public void LoadSerializer(IEnumerable resolvers) { [SerializerClasses] [InlineData("A")] [InlineData("B")] - public void LoadKeyedASerializer(string key, IServiceProvider serviceProvider) { - var resolverList = - serviceProvider.GetKeyedServices(key).ToList(); - + public void LoadKeyedASerializer(string key, IServiceProvider serviceProvider) + { + var resolverList = serviceProvider.GetKeyedServices(key).ToList(); + Assert.Single(resolverList); Assert.EndsWith(key, resolverList[0].GetType().Name); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/StandardTests/SimpleResolveTests.cs b/integ-tests/SutProject.Tests/StandardTests/SimpleResolveTests.cs index 6774990..bc380d2 100644 --- a/integ-tests/SutProject.Tests/StandardTests/SimpleResolveTests.cs +++ b/integ-tests/SutProject.Tests/StandardTests/SimpleResolveTests.cs @@ -3,10 +3,12 @@ namespace SutProject.Tests.StandardTests; -public class SimpleResolveTests { +public class SimpleResolveTests +{ [ModuleTest] [SutModule] - public void SimpleTest(IDependencyOne dependencyOne) { + public void SimpleTest(IDependencyOne dependencyOne) + { Assert.NotNull(dependencyOne); Assert.NotNull(dependencyOne.SingletonService); Assert.NotNull(dependencyOne.ScopedService); @@ -14,8 +16,12 @@ public void SimpleTest(IDependencyOne dependencyOne) { [ModuleTest] [SutModule] - public void ResolveServiceProvider(IDependencyOne dependencyOne, IServiceProvider serviceProvider) { + public void ResolveServiceProvider( + IDependencyOne dependencyOne, + IServiceProvider serviceProvider + ) + { Assert.NotNull(dependencyOne); Assert.NotNull(serviceProvider); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/StandardTests/SingletonTests.cs b/integ-tests/SutProject.Tests/StandardTests/SingletonTests.cs index 9382c52..8c86473 100644 --- a/integ-tests/SutProject.Tests/StandardTests/SingletonTests.cs +++ b/integ-tests/SutProject.Tests/StandardTests/SingletonTests.cs @@ -3,12 +3,17 @@ namespace SutProject.Tests.StandardTests; -public class SingletonTests { +public class SingletonTests +{ [ModuleTest] [SutModule] - public void ResolveSingleton(ISingletonService singletonService, ISingletonService otherSingletonService) { + public void ResolveSingleton( + ISingletonService singletonService, + ISingletonService otherSingletonService + ) + { Assert.NotNull(singletonService); Assert.NotNull(otherSingletonService); Assert.Same(singletonService, otherSingletonService); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/TestFramework/ContainerLifetimeTests.cs b/integ-tests/SutProject.Tests/TestFramework/ContainerLifetimeTests.cs index f022f05..bc59209 100644 --- a/integ-tests/SutProject.Tests/TestFramework/ContainerLifetimeTests.cs +++ b/integ-tests/SutProject.Tests/TestFramework/ContainerLifetimeTests.cs @@ -8,22 +8,23 @@ namespace SutProject.Tests.TestFramework; public partial class LifetimeModule { } [ScopedService(Realm = typeof(LifetimeModule))] -public class TrackedService : IDisposable { - +public class TrackedService : IDisposable +{ private static int _next; public static readonly List Disposed = []; - public TrackedService() { + public TrackedService() + { Id = Interlocked.Increment(ref _next); } - public int Id { - get; - } + public int Id { get; } - public void Dispose() { - lock (Disposed) { + public void Dispose() + { + lock (Disposed) + { Disposed.Add(Id); } } @@ -46,21 +47,25 @@ public void Dispose() { /// are released together when the last row has run. /// /// -public class ContainerLifetimeTests { - +public class ContainerLifetimeTests +{ private static readonly object Sync = new(); private static readonly List Seen = []; [ModuleTest(typeof(LifetimeModule))] - public void TheContainerOfATestThatHasRunIsDisposed(TrackedService service) => AssertEarlierDisposed(service); + public void TheContainerOfATestThatHasRunIsDisposed(TrackedService service) => + AssertEarlierDisposed(service); [ModuleTest(typeof(LifetimeModule))] public void WhicheverOfTheTwoRanFirst(TrackedService service) => AssertEarlierDisposed(service); - private static void AssertEarlierDisposed(TrackedService current) { - lock (Sync) { - foreach (var earlier in Seen) { + private static void AssertEarlierDisposed(TrackedService current) + { + lock (Sync) + { + foreach (var earlier in Seen) + { Assert.Contains(earlier, TrackedService.Disposed); } diff --git a/integ-tests/SutProject.Tests/TestFramework/InjectValueTests.cs b/integ-tests/SutProject.Tests/TestFramework/InjectValueTests.cs index ea8a58c..d537f1e 100644 --- a/integ-tests/SutProject.Tests/TestFramework/InjectValueTests.cs +++ b/integ-tests/SutProject.Tests/TestFramework/InjectValueTests.cs @@ -6,13 +6,14 @@ namespace SutProject.Tests.TestFramework; public record InjectModel(IDependencyOne DependencyOne, string StringValue); -public class InjectValueTests { +public class InjectValueTests +{ [ModuleTest] [SutModule] - public void InjectTestValue( - [InjectValues("Hello World!")]InjectModel injectModel) { + public void InjectTestValue([InjectValues("Hello World!")] InjectModel injectModel) + { Assert.NotNull(injectModel); Assert.NotNull(injectModel.DependencyOne); Assert.Equal("Hello World!", injectModel.StringValue); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/TestFramework/ModuleTestCaseTests.cs b/integ-tests/SutProject.Tests/TestFramework/ModuleTestCaseTests.cs index 48a73c2..4804c5b 100644 --- a/integ-tests/SutProject.Tests/TestFramework/ModuleTestCaseTests.cs +++ b/integ-tests/SutProject.Tests/TestFramework/ModuleTestCaseTests.cs @@ -8,26 +8,32 @@ namespace SutProject.Tests.TestFramework; -public class AssemblyTestCaseTests { +public class AssemblyTestCaseTests +{ [ModuleTest] - public void AssemblyTest(ITestRealmService service) { + public void AssemblyTest(ITestRealmService service) + { Assert.IsType(service); } } [ClassLevelModule] -public class ClassTestCaseTests { +public class ClassTestCaseTests +{ [ModuleTest] - public void ClassTest(ITestRealmService service) { + public void ClassTest(ITestRealmService service) + { Assert.IsType(service); } } [ClassLevelModule] -public class MethodTestCaseTests { +public class MethodTestCaseTests +{ [ModuleTest] [MethodLevelModule] - public void MethodTest(ITestRealmService service) { + public void MethodTest(ITestRealmService service) + { Assert.IsType(service); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject.Tests/TestFramework/TestCaseModels.cs b/integ-tests/SutProject.Tests/TestFramework/TestCaseModels.cs index b7759ae..5d90fa0 100644 --- a/integ-tests/SutProject.Tests/TestFramework/TestCaseModels.cs +++ b/integ-tests/SutProject.Tests/TestFramework/TestCaseModels.cs @@ -2,18 +2,11 @@ namespace SutProject.Tests.TestFramework; - [SingletonService(Realm = typeof(AssemblyLevelModule))] -public class AssemblyTestCaseService : ITestRealmService { - -} +public class AssemblyTestCaseService : ITestRealmService { } [SingletonService(Realm = typeof(ClassLevelModule))] -public class ClassTestCaseService : ITestRealmService { - -} +public class ClassTestCaseService : ITestRealmService { } [SingletonService(Realm = typeof(MethodLevelModule))] -public class MethodTestCaseService : ITestRealmService { - -} +public class MethodTestCaseService : ITestRealmService { } diff --git a/integ-tests/SutProject.Tests/TestFramework/TestModules.cs b/integ-tests/SutProject.Tests/TestFramework/TestModules.cs index 1aa0a2e..9eca3fa 100644 --- a/integ-tests/SutProject.Tests/TestFramework/TestModules.cs +++ b/integ-tests/SutProject.Tests/TestFramework/TestModules.cs @@ -5,11 +5,8 @@ namespace SutProject.Tests.TestFramework; [DependencyModule(OnlyRealm = true)] public partial class AssemblyLevelModule { } - [DependencyModule(OnlyRealm = true)] -public partial class ClassLevelModule { - -} +public partial class ClassLevelModule { } [DependencyModule(OnlyRealm = true)] -public partial class MethodLevelModule { } \ No newline at end of file +public partial class MethodLevelModule { } diff --git a/integ-tests/SutProject.Tests/UseMethod/UseMethodTests.cs b/integ-tests/SutProject.Tests/UseMethod/UseMethodTests.cs index 790e0c1..5323aa9 100644 --- a/integ-tests/SutProject.Tests/UseMethod/UseMethodTests.cs +++ b/integ-tests/SutProject.Tests/UseMethod/UseMethodTests.cs @@ -5,22 +5,25 @@ namespace SutProject.Tests.UseMethod; [DependencyModule(GenerateUseMethod = "UseMethodModule", OnlyRealm = true)] -public partial class UseMethodModule(string name) { +public partial class UseMethodModule(string name) +{ public string Name => name; } [SingletonService(Realm = typeof(UseMethodModule))] public class SomeImplementation; -public class UseMethodTests { +public class UseMethodTests +{ [Fact] - public void UseMethodTest() { + public void UseMethodTest() + { var serviceCollection = new ServiceCollection(); serviceCollection.UseMethodModule("testMethod"); - + var serviceProvider = serviceCollection.BuildServiceProvider(); - + var instance = serviceProvider.GetService(); Assert.NotNull(instance); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject/DependencyOne.cs b/integ-tests/SutProject/DependencyOne.cs index d75c8b4..8381ce0 100644 --- a/integ-tests/SutProject/DependencyOne.cs +++ b/integ-tests/SutProject/DependencyOne.cs @@ -2,16 +2,16 @@ namespace SutProject; -public interface IDependencyOne { +public interface IDependencyOne +{ ISingletonService SingletonService { get; } IScopedService ScopedService { get; } } [TransientService] -public class DependencyOne( - ISingletonService singletonService, - IScopedService scopedService) : IDependencyOne { - +public class DependencyOne(ISingletonService singletonService, IScopedService scopedService) + : IDependencyOne +{ public ISingletonService SingletonService { get; } = singletonService; public IScopedService ScopedService { get; } = scopedService; -} \ No newline at end of file +} diff --git a/integ-tests/SutProject/GenericClass.cs b/integ-tests/SutProject/GenericClass.cs index 31dbaef..13d5f63 100644 --- a/integ-tests/SutProject/GenericClass.cs +++ b/integ-tests/SutProject/GenericClass.cs @@ -2,19 +2,19 @@ namespace SutProject; -public interface IGenericInterface { +public interface IGenericInterface +{ T Value { get; } } [SingletonService] -public class GenericClass(T value) : IGenericInterface { - public T Value { - get; - } = value; +public class GenericClass(T value) : IGenericInterface +{ + public T Value { get; } = value; } [SingletonService] -public class StringGeneric : IGenericInterface { - +public class StringGeneric : IGenericInterface +{ public string Value => "Hello World"; -} \ No newline at end of file +} diff --git a/integ-tests/SutProject/KeyedService.cs b/integ-tests/SutProject/KeyedService.cs index 29bb734..1a97b80 100644 --- a/integ-tests/SutProject/KeyedService.cs +++ b/integ-tests/SutProject/KeyedService.cs @@ -4,6 +4,4 @@ namespace SutProject; [TransientService(Key = Constants.StringValue)] -public class KeyedService { - -} \ No newline at end of file +public class KeyedService { } diff --git a/integ-tests/SutProject/RealmRegistrationService.cs b/integ-tests/SutProject/RealmRegistrationService.cs index fa004b4..3227afd 100644 --- a/integ-tests/SutProject/RealmRegistrationService.cs +++ b/integ-tests/SutProject/RealmRegistrationService.cs @@ -3,4 +3,4 @@ namespace SutProject; [SingletonService(Realm = typeof(SutRealmModule))] -public class RealmRegistrationService { } \ No newline at end of file +public class RealmRegistrationService { } diff --git a/integ-tests/SutProject/RecordService.cs b/integ-tests/SutProject/RecordService.cs index d7982d9..2bd2511 100644 --- a/integ-tests/SutProject/RecordService.cs +++ b/integ-tests/SutProject/RecordService.cs @@ -2,13 +2,16 @@ namespace SutProject; -public interface IRecordService { +public interface IRecordService +{ string GetName(); } [SingletonService] -public record RecordService : IRecordService { - public string GetName() { +public record RecordService : IRecordService +{ + public string GetName() + { return nameof(RecordService); } } diff --git a/integ-tests/SutProject/ScopedService.cs b/integ-tests/SutProject/ScopedService.cs index 567de8c..cea835f 100644 --- a/integ-tests/SutProject/ScopedService.cs +++ b/integ-tests/SutProject/ScopedService.cs @@ -5,4 +5,4 @@ namespace SutProject; public interface IScopedService { } [ScopedService] -public class ScopedService : IScopedService { } \ No newline at end of file +public class ScopedService : IScopedService { } diff --git a/integ-tests/SutProject/SerializerClasses.cs b/integ-tests/SutProject/SerializerClasses.cs index 70cfa94..4300e31 100644 --- a/integ-tests/SutProject/SerializerClasses.cs +++ b/integ-tests/SutProject/SerializerClasses.cs @@ -4,9 +4,7 @@ namespace SutProject; [DependencyModule(RegisterJsonSerializers = true)] -public partial class SerializerClasses { - -} +public partial class SerializerClasses { } public record SerialA(string A, string B); @@ -17,14 +15,12 @@ public record SerialB(string A, string B); [JsonSerializable(typeof(SerialB))] public partial class SerializerContext : JsonSerializerContext; - [JsonSourceGenerationOptions] [JsonSerializable(typeof(SerialA))] [TransientService(Key = "A", Realm = typeof(SerializerClasses))] public partial class SerializerContextA : JsonSerializerContext; - [JsonSourceGenerationOptions] [JsonSerializable(typeof(SerialA))] [TransientService(Key = "B", Realm = typeof(SerializerClasses))] -public partial class SerializerContextB : JsonSerializerContext; \ No newline at end of file +public partial class SerializerContextB : JsonSerializerContext; diff --git a/integ-tests/SutProject/SingletonService.cs b/integ-tests/SutProject/SingletonService.cs index 6d556e7..995dbd4 100644 --- a/integ-tests/SutProject/SingletonService.cs +++ b/integ-tests/SutProject/SingletonService.cs @@ -2,14 +2,16 @@ namespace SutProject; -public interface ISingletonService { +public interface ISingletonService +{ string GetName(); } [SingletonService] -public class SingletonService : ISingletonService { - - public string GetName() { +public class SingletonService : ISingletonService +{ + public string GetName() + { return nameof(SingletonService); } -} \ No newline at end of file +} diff --git a/integ-tests/SutProject/SubDirectory/Constants.cs b/integ-tests/SutProject/SubDirectory/Constants.cs index 0b1d404..8d45d4d 100644 --- a/integ-tests/SutProject/SubDirectory/Constants.cs +++ b/integ-tests/SutProject/SubDirectory/Constants.cs @@ -1,5 +1,6 @@ namespace SutProject.SubDirectory; -public class Constants { +public class Constants +{ public const string StringValue = "StringValue"; -} \ No newline at end of file +} diff --git a/integ-tests/SutProject/SutModule.cs b/integ-tests/SutProject/SutModule.cs index 654bc15..cc90532 100644 --- a/integ-tests/SutProject/SutModule.cs +++ b/integ-tests/SutProject/SutModule.cs @@ -3,8 +3,7 @@ namespace SutProject; [DependencyModule] -public partial class SutModule { - public static void Run() { - - } -} \ No newline at end of file +public partial class SutModule +{ + public static void Run() { } +} diff --git a/integ-tests/SutProject/SutRealmModule.cs b/integ-tests/SutProject/SutRealmModule.cs index 3f6fa45..45d2129 100644 --- a/integ-tests/SutProject/SutRealmModule.cs +++ b/integ-tests/SutProject/SutRealmModule.cs @@ -3,5 +3,4 @@ namespace SutProject; [DependencyModule(OnlyRealm = true)] -public partial class SutRealmModule { -} \ No newline at end of file +public partial class SutRealmModule { } diff --git a/integ-tests/SutProject/TestRealmModule.cs b/integ-tests/SutProject/TestRealmModule.cs index a96a9ec..dcf2cb7 100644 --- a/integ-tests/SutProject/TestRealmModule.cs +++ b/integ-tests/SutProject/TestRealmModule.cs @@ -5,9 +5,7 @@ namespace SutProject; [DependencyModule(OnlyRealm = true)] public partial class TestRealmModule { } -public interface ITestRealmService { - -} +public interface ITestRealmService { } [SingletonService(Realm = typeof(TestRealmModule))] -public class TestRealmService : ITestRealmService { } \ No newline at end of file +public class TestRealmService : ITestRealmService { } diff --git a/integ-tests/web/WebApiApp.Tests/Bootstrap.cs b/integ-tests/web/WebApiApp.Tests/Bootstrap.cs index ba4dff6..2ef066d 100644 --- a/integ-tests/web/WebApiApp.Tests/Bootstrap.cs +++ b/integ-tests/web/WebApiApp.Tests/Bootstrap.cs @@ -2,4 +2,4 @@ using WebApiApp; [assembly: NSubstituteSupport] -[assembly: ApplicationModule] \ No newline at end of file +[assembly: ApplicationModule] diff --git a/integ-tests/web/WebApiApp.Tests/WeatherTests.cs b/integ-tests/web/WebApiApp.Tests/WeatherTests.cs index 5ff7970..e306456 100644 --- a/integ-tests/web/WebApiApp.Tests/WeatherTests.cs +++ b/integ-tests/web/WebApiApp.Tests/WeatherTests.cs @@ -5,28 +5,33 @@ namespace WebApiApp.Tests; -public class WeatherTests { +public class WeatherTests +{ [ModuleTest] - public void GetForecast(Weather weather) { + public void GetForecast(Weather weather) + { var response = weather.GetWeatherForecast().ToArray(); - + Assert.Equal(5, response.Length); } [ModuleTest] public void GetStaticForecast( - Weather weather, + Weather weather, [Mock] ITemperatureProvider temperatureProvider, - [Mock] IAiSummaryProvider aiSummaryProvider) { + [Mock] IAiSummaryProvider aiSummaryProvider + ) + { temperatureProvider.GetTemperature().Returns(38); aiSummaryProvider.GetSummary().Returns("Sunny"); - + var response = weather.GetWeatherForecast().ToArray(); Assert.Equal(5, response.Length); - foreach (var weatherForecast in response) { + foreach (var weatherForecast in response) + { Assert.Equal(38, weatherForecast.TemperatureC); Assert.Equal("Sunny", weatherForecast.Summary); } } -} \ No newline at end of file +} diff --git a/integ-tests/web/WebApiApp/AiSummaryProvider.cs b/integ-tests/web/WebApiApp/AiSummaryProvider.cs index 26099ff..32233dc 100644 --- a/integ-tests/web/WebApiApp/AiSummaryProvider.cs +++ b/integ-tests/web/WebApiApp/AiSummaryProvider.cs @@ -2,18 +2,30 @@ namespace WebApiApp; -public interface IAiSummaryProvider { +public interface IAiSummaryProvider +{ string GetSummary(); } [SingletonService] -public class AiSummaryProvider : IAiSummaryProvider { - - private static string[] summaries = new[] { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +public class AiSummaryProvider : IAiSummaryProvider +{ + private static string[] summaries = new[] + { + "Freezing", + "Bracing", + "Chilly", + "Cool", + "Mild", + "Warm", + "Balmy", + "Hot", + "Sweltering", + "Scorching", }; - - public string GetSummary() { + + public string GetSummary() + { return summaries[Random.Shared.Next(summaries.Length)]; } -} \ No newline at end of file +} diff --git a/integ-tests/web/WebApiApp/Program.cs b/integ-tests/web/WebApiApp/Program.cs index 6f7abf7..012b0ed 100644 --- a/integ-tests/web/WebApiApp/Program.cs +++ b/integ-tests/web/WebApiApp/Program.cs @@ -15,4 +15,3 @@ .WithName("GetWeatherForecast"); app.Run(); - diff --git a/integ-tests/web/WebApiApp/SummaryProvider.cs b/integ-tests/web/WebApiApp/SummaryProvider.cs index b8291a8..4e993cd 100644 --- a/integ-tests/web/WebApiApp/SummaryProvider.cs +++ b/integ-tests/web/WebApiApp/SummaryProvider.cs @@ -2,14 +2,16 @@ namespace WebApiApp; -public interface ISummaryProvider { +public interface ISummaryProvider +{ string GetSummary(); } [SingletonService] -public class SummaryProvider(IAiSummaryProvider aiSummaryProvider) : ISummaryProvider { - - public string GetSummary() { +public class SummaryProvider(IAiSummaryProvider aiSummaryProvider) : ISummaryProvider +{ + public string GetSummary() + { return aiSummaryProvider.GetSummary(); } -} \ No newline at end of file +} diff --git a/integ-tests/web/WebApiApp/TemperatureProvider.cs b/integ-tests/web/WebApiApp/TemperatureProvider.cs index a7a3691..94a72ee 100644 --- a/integ-tests/web/WebApiApp/TemperatureProvider.cs +++ b/integ-tests/web/WebApiApp/TemperatureProvider.cs @@ -2,14 +2,16 @@ namespace WebApiApp; -public interface ITemperatureProvider { +public interface ITemperatureProvider +{ int GetTemperature(); -} +} [SingletonService] -public class TemperatureProvider : ITemperatureProvider { - - public int GetTemperature() { +public class TemperatureProvider : ITemperatureProvider +{ + public int GetTemperature() + { return Random.Shared.Next(-20, 55); } -} \ No newline at end of file +} diff --git a/integ-tests/web/WebApiApp/Weather.cs b/integ-tests/web/WebApiApp/Weather.cs index f5acd57..9476eba 100644 --- a/integ-tests/web/WebApiApp/Weather.cs +++ b/integ-tests/web/WebApiApp/Weather.cs @@ -3,19 +3,18 @@ namespace WebApiApp; [SingletonService] -public class Weather( - ISummaryProvider summaryProvider, - ITemperatureProvider temperatureProvider) { - - public IEnumerable GetWeatherForecast() { - var forecast = Enumerable.Range(1, 5).Select(index => - new WeatherForecast - ( - DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - temperatureProvider.GetTemperature(), - summaryProvider.GetSummary() - )) +public class Weather(ISummaryProvider summaryProvider, ITemperatureProvider temperatureProvider) +{ + public IEnumerable GetWeatherForecast() + { + var forecast = Enumerable + .Range(1, 5) + .Select(index => new WeatherForecast( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + temperatureProvider.GetTemperature(), + summaryProvider.GetSummary() + )) .ToArray(); return forecast; } -} \ No newline at end of file +} diff --git a/integ-tests/web/WebApiApp/WeatherForecast.cs b/integ-tests/web/WebApiApp/WeatherForecast.cs index 162deb2..c9d828d 100644 --- a/integ-tests/web/WebApiApp/WeatherForecast.cs +++ b/integ-tests/web/WebApiApp/WeatherForecast.cs @@ -1,5 +1,6 @@ namespace WebApiApp; -public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) { +public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); -} \ No newline at end of file +} diff --git a/src/DependencyModules.FakeItEasy/FakeItEasySupportAttribute.cs b/src/DependencyModules.FakeItEasy/FakeItEasySupportAttribute.cs index 70d466f..8b9720a 100644 --- a/src/DependencyModules.FakeItEasy/FakeItEasySupportAttribute.cs +++ b/src/DependencyModules.FakeItEasy/FakeItEasySupportAttribute.cs @@ -16,14 +16,15 @@ namespace DependencyModules.FakeItEasy; /// /// [ModuleTest] /// [FakeItEasySupport] -/// public void SendsTheMail(IEmailSender sender, [Mock] IAuditLog log) { +/// public void SendsTheMail(IEmailSender sender, [Mock] IAuditLog log) +/// { /// A.CallTo(() => log.Write(A<string>._)).MustHaveHappened(); /// } /// /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] -public class FakeItEasySupportAttribute : Attribute, IMockSupportAttribute { - +public class FakeItEasySupportAttribute : Attribute, IMockSupportAttribute +{ /// /// Provides a fake of the specified type. /// @@ -33,7 +34,8 @@ public class FakeItEasySupportAttribute : Attribute, IMockSupportAttribute { /// /// The type to fake. /// A fake implementing . - public object ProvideMock(Type type) { + public object ProvideMock(Type type) + { return global::FakeItEasy.Sdk.Create.Fake(type); } } diff --git a/src/DependencyModules.Moq/MoqSupportAttribute.cs b/src/DependencyModules.Moq/MoqSupportAttribute.cs index cd0584f..b22295e 100644 --- a/src/DependencyModules.Moq/MoqSupportAttribute.cs +++ b/src/DependencyModules.Moq/MoqSupportAttribute.cs @@ -25,14 +25,15 @@ namespace DependencyModules.Moq; /// /// [ModuleTest] /// [MoqSupport] -/// public void SendsTheMail(IEmailSender sender, Mock<IAuditLog> log) { +/// public void SendsTheMail(IEmailSender sender, Mock<IAuditLog> log) +/// { /// log.Verify(x => x.Write(It.IsAny<string>())); /// } /// /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] -public class MoqSupportAttribute : Attribute, IMockSupportAttribute, ITestServiceSetupAttribute { - +public class MoqSupportAttribute : Attribute, IMockSupportAttribute, ITestServiceSetupAttribute +{ /// /// Registers a mock for every Mock<T> the test asked for, alongside the object that /// mock produces. @@ -54,14 +55,22 @@ public class MoqSupportAttribute : Attribute, IMockSupportAttribute, ITestServic /// /// The test the container is being built for. /// The collection backing the test's container. - public void SetupServiceCollection(ITestMethodContext testMethod, IServiceCollection serviceCollection) { + public void SetupServiceCollection( + ITestMethodContext testMethod, + IServiceCollection serviceCollection + ) + { var mocked = new HashSet(); - foreach (var parameter in testMethod.Method.GetParameters()) { + foreach (var parameter in testMethod.Method.GetParameters()) + { // Add returns false for a type already handled: two parameters naming the same // Mock are one mock, so configuring either is configuring what the test was given. - if (!TryGetMockedType(parameter.ParameterType, out var mockedType) || - !mocked.Add(mockedType)) { + if ( + !TryGetMockedType(parameter.ParameterType, out var mockedType) + || !mocked.Add(mockedType) + ) + { continue; } @@ -89,8 +98,10 @@ public void SetupServiceCollection(ITestMethodContext testMethod, IServiceCollec /// The mocked instance — Mock<T>.Object — or a Mock<T> when that is /// what was asked for. /// - public object ProvideMock(Type type) { - if (TryGetMockedType(type, out var mockedType)) { + public object ProvideMock(Type type) + { + if (TryGetMockedType(type, out var mockedType)) + { return CreateMock(mockedType); } @@ -107,13 +118,17 @@ public object ProvideMock(Type type) { /// [TestExport] naming the same service — and without this it would also beat the pairing here, /// leaving the test configuring one mock while the container handed out another. /// - public bool RegistersService(ITestMethodContext testMethod, Type serviceType) { - foreach (var parameter in testMethod.Method.GetParameters()) { - if (!TryGetMockedType(parameter.ParameterType, out var mockedType)) { + public bool RegistersService(ITestMethodContext testMethod, Type serviceType) + { + foreach (var parameter in testMethod.Method.GetParameters()) + { + if (!TryGetMockedType(parameter.ParameterType, out var mockedType)) + { continue; } - if (parameter.ParameterType == serviceType || mockedType == serviceType) { + if (parameter.ParameterType == serviceType || mockedType == serviceType) + { return true; } } @@ -127,9 +142,16 @@ private static MoqLib.Mock CreateMock(Type type) => /// /// Reads IFoo out of a Mock<IFoo>, and reports anything else as not ours. /// - private static bool TryGetMockedType(Type parameterType, [NotNullWhen(true)] out Type? mockedType) { - if (parameterType.IsGenericType && - parameterType.GetGenericTypeDefinition() == typeof(MoqLib.Mock<>)) { + private static bool TryGetMockedType( + Type parameterType, + [NotNullWhen(true)] out Type? mockedType + ) + { + if ( + parameterType.IsGenericType + && parameterType.GetGenericTypeDefinition() == typeof(MoqLib.Mock<>) + ) + { mockedType = parameterType.GetGenericArguments()[0]; return true; } diff --git a/src/DependencyModules.NSubstitute/NSubstituteSupportAttribute.cs b/src/DependencyModules.NSubstitute/NSubstituteSupportAttribute.cs index a3fea15..19c05c9 100644 --- a/src/DependencyModules.NSubstitute/NSubstituteSupportAttribute.cs +++ b/src/DependencyModules.NSubstitute/NSubstituteSupportAttribute.cs @@ -17,21 +17,23 @@ namespace DependencyModules.NSubstitute; /// /// [ModuleTest] /// [NSubstituteSupport] -/// public void SendsTheMail(IEmailSender sender, [Mock] IAuditLog log) { +/// public void SendsTheMail(IEmailSender sender, [Mock] IAuditLog log) +/// { /// log.Received().Write(Arg.Any<string>()); /// } /// /// /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] -public class NSubstituteSupportAttribute : Attribute, IMockSupportAttribute { - +public class NSubstituteSupportAttribute : Attribute, IMockSupportAttribute +{ /// /// Provides a substitute for the specified type. /// /// The type to substitute for. /// A substitute implementing . - public object ProvideMock(Type type) { + public object ProvideMock(Type type) + { return NSub.Substitute.For([type], []); } } diff --git a/src/DependencyModules.NUnit/Attributes/ModuleTestAttribute.cs b/src/DependencyModules.NUnit/Attributes/ModuleTestAttribute.cs index b441246..2462e61 100644 --- a/src/DependencyModules.NUnit/Attributes/ModuleTestAttribute.cs +++ b/src/DependencyModules.NUnit/Attributes/ModuleTestAttribute.cs @@ -29,14 +29,20 @@ namespace DependencyModules.NUnit.Attributes; /// /// /// [ModuleTest(typeof(MyModule))] -/// public void ResolvesTheService(IMyService service) { +/// public void ResolvesTheService(IMyService service) +/// { /// Assert.That(service, Is.Not.Null); /// } /// /// [AttributeUsage(AttributeTargets.Method)] -public class ModuleTestAttribute : Attribute, ITestBuilder, IWrapSetUpTearDown, IImplyFixture, IModuleTestAttribute { - +public class ModuleTestAttribute + : Attribute, + ITestBuilder, + IWrapSetUpTearDown, + IImplyFixture, + IModuleTestAttribute +{ /// /// Where a row's arguments are stashed between building the test case and executing it. /// @@ -57,14 +63,13 @@ public class ModuleTestAttribute : Attribute, ITestBuilder, IWrapSetUpTearDown, /// parameters after a params array. NUnit takes navigation from the assembly's symbols instead, /// so nothing is lost by taking the params form here. /// - public ModuleTestAttribute(params Type[] modules) { + public ModuleTestAttribute(params Type[] modules) + { ModuleTypes = modules; } /// - public Type[] ModuleTypes { - get; - } + public Type[] ModuleTypes { get; } /// /// Builds one test case per data row, or a single case when the method has no rows. @@ -75,30 +80,42 @@ public Type[] ModuleTypes { /// this has to satisfy is NUnit's arity check, which an array of the right length does; the real /// arguments are written into that array at execution time, once there is a container. /// - public IEnumerable BuildFrom(IMethodInfo method, Test? suite) { + public IEnumerable BuildFrom(IMethodInfo method, Test? suite) + { var parameterCount = method.GetParameters().Length; - var rows = method.MethodInfo.GetCustomAttributes(false) + var rows = method + .MethodInfo.GetCustomAttributes(false) .OfType() .SelectMany(dataAttribute => dataAttribute.GetRows(method.MethodInfo)) .ToArray(); - if (rows.Length == 0) { - yield return BuildTestMethod(method, suite, new object?[parameterCount], null, method.Name); + if (rows.Length == 0) + { + yield return BuildTestMethod( + method, + suite, + new object?[parameterCount], + null, + method.Name + ); yield break; } - var names = method.MethodInfo.GetCustomAttributes(false) + var names = method + .MethodInfo.GetCustomAttributes(false) .OfType() .Select(attribute => attribute.TestName) .ToArray(); - for (var i = 0; i < rows.Length; i++) { + for (var i = 0; i < rows.Length; i++) + { var row = rows[i]; var arguments = new object?[parameterCount]; - if (row.Length <= parameterCount) { + if (row.Length <= parameterCount) + { Array.Copy(row, arguments, row.Length); } @@ -106,15 +123,17 @@ public IEnumerable BuildFrom(IMethodInfo method, Test? suite) { var testMethod = BuildTestMethod(method, suite, arguments, row, testName); - if (row.Length > parameterCount) { + if (row.Length > parameterCount) + { // Reported as a failing test rather than thrown, so one bad row names itself instead // of taking down discovery for the whole fixture. testMethod.RunState = RunState.NotRunnable; testMethod.Properties.Set( PropertyNames.SkipReason, - $"[ModuleTestCase] supplied {row.Length} arguments to a method taking " + - $"{parameterCount}. A row may supply fewer than the method takes — the remaining " + - "parameters are resolved from the container — but not more."); + $"[ModuleTestCase] supplied {row.Length} arguments to a method taking " + + $"{parameterCount}. A row may supply fewer than the method takes — the remaining " + + "parameters are resolved from the container — but not more." + ); } yield return testMethod; @@ -125,12 +144,19 @@ public IEnumerable BuildFrom(IMethodInfo method, Test? suite) { public TestCommand Wrap(TestCommand command) => new ModuleTestCommand(command); private static TestMethod BuildTestMethod( - IMethodInfo method, Test? suite, object?[] arguments, object?[]? row, string testName) { + IMethodInfo method, + Test? suite, + object?[] arguments, + object?[]? row, + string testName + ) + { var parameters = new TestCaseParameters(arguments) { TestName = testName }; var testMethod = new NUnitTestCaseBuilder().BuildTestMethod(method, suite, parameters); - if (row != null) { + if (row != null) + { testMethod.Properties.Set(RowPropertyName, row); } @@ -153,11 +179,12 @@ private static string DisplayName(string methodName, object?[] row) => /// keeps a name that a test explorer filters on from changing with the machine's locale. /// private static string FormatArgument(object? argument) => - argument switch { + argument switch + { null => "null", string text => $"\"{text}\"", char character => $"'{character}'", IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture), - _ => argument.ToString() ?? string.Empty + _ => argument.ToString() ?? string.Empty, }; } diff --git a/src/DependencyModules.NUnit/Attributes/ModuleTestCaseAttribute.cs b/src/DependencyModules.NUnit/Attributes/ModuleTestCaseAttribute.cs index c6831c8..1c2141f 100644 --- a/src/DependencyModules.NUnit/Attributes/ModuleTestCaseAttribute.cs +++ b/src/DependencyModules.NUnit/Attributes/ModuleTestCaseAttribute.cs @@ -10,8 +10,8 @@ namespace DependencyModules.NUnit.Attributes; /// row returned here, so a source of rows — a member, a file, a generator — only has to implement /// this to become usable. /// -public interface IModuleTestDataAttribute { - +public interface IModuleTestDataAttribute +{ /// /// The rows to build test cases from. A row covers the leading parameters of the method; the /// rest are resolved from the test's container. @@ -40,29 +40,27 @@ public interface IModuleTestDataAttribute { /// [ModuleTest(typeof(MyModule))] /// [ModuleTestCase(1, "one")] /// [ModuleTestCase(2, "two")] -/// public void Converts(int number, string word, INumberFormatter formatter) { +/// public void Converts(int number, string word, INumberFormatter formatter) +/// { /// Assert.That(formatter.Spell(number), Is.EqualTo(word)); /// } /// /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] -public class ModuleTestCaseAttribute(params object?[] arguments) : Attribute, IModuleTestDataAttribute { - +public class ModuleTestCaseAttribute(params object?[] arguments) + : Attribute, + IModuleTestDataAttribute +{ /// /// The arguments for this row, covering the method's leading parameters in order. /// - public object?[] Arguments { - get; - } = arguments; + public object?[] Arguments { get; } = arguments; /// /// Overrides the name this row is reported under. Defaults to the method name followed by the /// row's arguments, which is what tells one row from another in a test explorer. /// - public string? TestName { - get; - set; - } + public string? TestName { get; set; } /// public IEnumerable GetRows(MethodInfo method) => [Arguments]; diff --git a/src/DependencyModules.NUnit/Impl/ModuleTestCommand.cs b/src/DependencyModules.NUnit/Impl/ModuleTestCommand.cs index c60c9ae..69b92e8 100644 --- a/src/DependencyModules.NUnit/Impl/ModuleTestCommand.cs +++ b/src/DependencyModules.NUnit/Impl/ModuleTestCommand.cs @@ -22,10 +22,11 @@ namespace DependencyModules.NUnit.Impl; /// teardown, timeouts, expected exceptions and the rest working normally — this only has to make /// sure the arguments are in place before delegating. /// -public class ModuleTestCommand(TestCommand innerCommand) : DelegatingTestCommand(innerCommand) { - +public class ModuleTestCommand(TestCommand innerCommand) : DelegatingTestCommand(innerCommand) +{ /// - public override TestResult Execute(TestExecutionContext context) { + public override TestResult Execute(TestExecutionContext context) + { var testMethod = (TestMethod)Test; var method = testMethod.Method!.MethodInfo; @@ -58,13 +59,18 @@ public override TestResult Execute(TestExecutionContext context) { // beats a [TestExport] naming the same service. resolver.SetupServiceCollection(serviceCollection); - var serviceProvider = BuildServiceProvider(moduleContext, serviceCollection, knownAttributes); + var serviceProvider = BuildServiceProvider( + moduleContext, + serviceCollection, + knownAttributes + ); // Every container the case built, the first and any the source was asked for, disposed // together in the finally below. var providers = new List { serviceProvider }; - try { + try + { Start(moduleContext, knownAttributes, serviceProvider); // Named throughout: three of these are delegates of shapes that would happily bind to @@ -74,24 +80,30 @@ public override TestResult Execute(TestExecutionContext context) { pinned: serviceProvider, pinnedServices: SharedRegistrations.Collect(method, knownAttributes), build: services => BuildServiceProvider(moduleContext, services, knownAttributes), - start: built => { + start: built => + { Start(moduleContext, knownAttributes, built); return ValueTask.CompletedTask; }, - track: providers.Add); + track: providers.Add + ); var arguments = resolver .ResolveArgumentsAsync(serviceProvider, RowArguments(testMethod)) - .GetAwaiter().GetResult(); + .GetAwaiter() + .GetResult(); PublishArguments(testMethod, serviceProvider, arguments); return innerCommand.Execute(context); - } finally { + } + finally + { // Backwards, so a container the source built goes before the one holding the instances // it was handed. - for (var i = providers.Count - 1; i >= 0; i--) { + for (var i = providers.Count - 1; i >= 0; i--) + { DisposeProvider(providers[i]); } } @@ -108,8 +120,13 @@ public override TestResult Execute(TestExecutionContext context) { /// hook is awaited here rather than up the stack. /// private static void Start( - ITestMethodContext context, Attribute[] knownAttributes, IServiceProvider provider) { - foreach (var startupAttribute in knownAttributes.OfType()) { + ITestMethodContext context, + Attribute[] knownAttributes, + IServiceProvider provider + ) + { + foreach (var startupAttribute in knownAttributes.OfType()) + { startupAttribute.StartupAsync(context, provider).GetAwaiter().GetResult(); } } @@ -131,7 +148,11 @@ private static void Start( /// NUnit's setup and teardown handling. /// private static void PublishArguments( - TestMethod testMethod, IServiceProvider serviceProvider, object?[] arguments) { + TestMethod testMethod, + IServiceProvider serviceProvider, + object?[] arguments + ) + { var target = testMethod.Arguments; Array.Copy(arguments, target, arguments.Length); @@ -140,12 +161,19 @@ private static void PublishArguments( } private static void SetupTestCaseInfo( - IServiceCollection serviceCollection, TestMethod testMethod, Attribute[] knownAttributes) { - serviceCollection.AddSingleton(provider => provider.GetRequiredService()); + IServiceCollection serviceCollection, + TestMethod testMethod, + Attribute[] knownAttributes + ) + { + serviceCollection.AddSingleton(provider => + provider.GetRequiredService() + ); serviceCollection.AddSingleton(_ => new TestCaseInfo( testMethod, ArraySegment.Empty, - knownAttributes)); + knownAttributes + )); } /// @@ -156,11 +184,17 @@ private static void SetupTestCaseInfo( /// which is the reverse of how every other attribute here resolves. /// private static IServiceProvider BuildServiceProvider( - ITestMethodContext context, IServiceCollection serviceCollection, Attribute[] knownAttributes) { - var serviceProviderBuilderAttribute = - knownAttributes.OfType().LastOrDefault(); - - if (serviceProviderBuilderAttribute != null) { + ITestMethodContext context, + IServiceCollection serviceCollection, + Attribute[] knownAttributes + ) + { + var serviceProviderBuilderAttribute = knownAttributes + .OfType() + .LastOrDefault(); + + if (serviceProviderBuilderAttribute != null) + { return serviceProviderBuilderAttribute.BuildServiceProvider(context, serviceCollection); } @@ -177,12 +211,17 @@ private static IServiceProvider BuildServiceProvider( /// which relying on attribute order alone would not guarantee. /// private static void SetupServiceSetupAttributes( - ITestMethodContext context, IServiceCollection serviceCollection, Attribute[] knownAttributes) { + ITestMethodContext context, + IServiceCollection serviceCollection, + Attribute[] knownAttributes + ) + { var setupAttributes = knownAttributes .OfType() .OrderBy(attribute => attribute is IMockSupportAttribute ? 0 : 1); - foreach (var setupAttribute in setupAttributes) { + foreach (var setupAttribute in setupAttributes) + { setupAttribute.SetupServiceCollection(context, serviceCollection); } } @@ -194,14 +233,20 @@ private static void SetupServiceSetupAttributes( /// service-setup pass runs too late to supply it. /// private static void SeedEnvironment( - IServiceCollection serviceCollection, MethodInfo method, Attribute[] knownAttributes) { + IServiceCollection serviceCollection, + MethodInfo method, + Attribute[] knownAttributes + ) + { IModuleEnvironment? environment = null; - foreach (var provider in knownAttributes.OfType()) { + foreach (var provider in knownAttributes.OfType()) + { environment = provider.ProvideEnvironment(method) ?? environment; } - if (environment != null) { + if (environment != null) + { serviceCollection.Add(new ServiceDescriptor(typeof(IModuleEnvironment), environment)); } } @@ -214,19 +259,27 @@ private static void SeedEnvironment( /// because it does not drag the runtime in behind it. /// private static void SetupModules( - IServiceCollection serviceCollection, MethodInfo method, IEnumerable knownAttributes) { + IServiceCollection serviceCollection, + MethodInfo method, + IEnumerable knownAttributes + ) + { var modules = new List(); - foreach (var loadModuleAttribute in knownAttributes.OfType()) { + foreach (var loadModuleAttribute in knownAttributes.OfType()) + { modules.Add(loadModuleAttribute.GetModule()); } var testAttribute = method.GetTestAttribute(); - if (testAttribute != null) { + if (testAttribute != null) + { var count = 0; - foreach (var moduleType in testAttribute.ModuleTypes) { - if (Activator.CreateInstance(moduleType, []) is IDependencyModule moduleInstance) { + foreach (var moduleType in testAttribute.ModuleTypes) + { + if (Activator.CreateInstance(moduleType, []) is IDependencyModule moduleInstance) + { modules.Insert(count++, moduleInstance); } } @@ -242,8 +295,10 @@ private static void SetupModules( /// implements makes ServiceProvider.Dispose throw rather /// than fall back. /// - private static void DisposeProvider(IServiceProvider serviceProvider) { - switch (serviceProvider) { + private static void DisposeProvider(IServiceProvider serviceProvider) + { + switch (serviceProvider) + { case IAsyncDisposable asyncDisposable: asyncDisposable.DisposeAsync().AsTask().GetAwaiter().GetResult(); break; diff --git a/src/DependencyModules.NUnit/Impl/NUnitTestMethodContext.cs b/src/DependencyModules.NUnit/Impl/NUnitTestMethodContext.cs index 3cb8bb3..268671c 100644 --- a/src/DependencyModules.NUnit/Impl/NUnitTestMethodContext.cs +++ b/src/DependencyModules.NUnit/Impl/NUnitTestMethodContext.cs @@ -14,14 +14,12 @@ namespace DependencyModules.NUnit.Impl; /// if (testMethod is INUnitTestMethodContext nunit) reaches NUnit's own model — the test's /// name and id, its properties, and the fixture it belongs to. /// -public interface INUnitTestMethodContext : ITestMethodContext { - +public interface INUnitTestMethodContext : ITestMethodContext +{ /// /// NUnit's own model of the test method being executed. /// - TestMethod NUnitTestMethod { - get; - } + TestMethod NUnitTestMethod { get; } } /// @@ -34,15 +32,12 @@ TestMethod NUnitTestMethod { /// internal sealed class NUnitTestMethodContext( TestMethod testMethod, - IReadOnlyList attributes) : INUnitTestMethodContext { - - public TestMethod NUnitTestMethod { - get; - } = testMethod; + IReadOnlyList attributes +) : INUnitTestMethodContext +{ + public TestMethod NUnitTestMethod { get; } = testMethod; public MethodInfo Method => NUnitTestMethod.Method!.MethodInfo; - public IReadOnlyList Attributes { - get; - } = attributes; + public IReadOnlyList Attributes { get; } = attributes; } diff --git a/src/DependencyModules.NUnit/Impl/TestCaseInfo.cs b/src/DependencyModules.NUnit/Impl/TestCaseInfo.cs index 405d939..96c7403 100644 --- a/src/DependencyModules.NUnit/Impl/TestCaseInfo.cs +++ b/src/DependencyModules.NUnit/Impl/TestCaseInfo.cs @@ -8,15 +8,13 @@ namespace DependencyModules.NUnit.Impl; /// /// Registered in every test's container, so a service can be told what it is being built for. /// -public interface ITestCaseInfo { - +public interface ITestCaseInfo +{ /// /// NUnit's model of the test method being executed, including the arguments the case was /// built with, its name and its properties. /// - TestMethod TestMethod { - get; - } + TestMethod TestMethod { get; } /// /// Gets the arguments passed to the test method for a specific test case. @@ -26,18 +24,13 @@ TestMethod TestMethod { /// registration itself is made — a service reading this in its constructor would be reading it /// too early. Read it from a method the test calls, not from a constructor. /// - IReadOnlyList TestMethodArguments { - get; - set; - } + IReadOnlyList TestMethodArguments { get; set; } /// /// Gets the collection of attributes associated with the test method of a specific test case, /// widest scope first: assembly, then declaring type, then the method. /// - IReadOnlyList TestMethodAttributes { - get; - } + IReadOnlyList TestMethodAttributes { get; } } /// @@ -46,21 +39,15 @@ IReadOnlyList TestMethodAttributes { public class TestCaseInfo( TestMethod testMethod, IReadOnlyList testMethodArguments, - IReadOnlyList testMethodAttributes) : ITestCaseInfo { - + IReadOnlyList testMethodAttributes +) : ITestCaseInfo +{ /// - public TestMethod TestMethod { - get; - } = testMethod; + public TestMethod TestMethod { get; } = testMethod; /// - public IReadOnlyList TestMethodArguments { - get; - set; - } = testMethodArguments; + public IReadOnlyList TestMethodArguments { get; set; } = testMethodArguments; /// - public IReadOnlyList TestMethodAttributes { - get; - } = testMethodAttributes; + public IReadOnlyList TestMethodAttributes { get; } = testMethodAttributes; } diff --git a/src/DependencyModules.Runtime/Attributes/BaseServiceAttribute.cs b/src/DependencyModules.Runtime/Attributes/BaseServiceAttribute.cs index ffbda9b..5f35565 100644 --- a/src/DependencyModules.Runtime/Attributes/BaseServiceAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/BaseServiceAttribute.cs @@ -6,7 +6,8 @@ namespace DependencyModules.Runtime.Attributes; /// /// Defines the method of registration for services in a dependency injection container. /// -public enum RegistrationType { +public enum RegistrationType +{ /// /// Registers the service unconditionally, adding a new registration even if another service of the same type exists. /// @@ -28,7 +29,7 @@ public enum RegistrationType { /// Replaces an existing service registration with the new one. /// Typically used to override default implementations in the dependency injection container. /// - Replace + Replace, } /// @@ -57,24 +58,27 @@ public enum RegistrationType { /// a registration consists of. /// /// -public interface IServiceRegistrationAttribute { +public interface IServiceRegistrationAttribute +{ /// /// Gets or sets a key used for service registration, /// typically to distinguish between multiple registrations /// of the same service type or to categorize services. /// - object? Key { + object? Key + { get => null; - set {} + set { } } /// /// Gets or sets the type that the service should be registered as in the dependency injection container. /// Typically used to specify an interface or a base type that the implementation will be registered and resolved as. /// - Type? As { + Type? As + { get => null; - set { } + set { } } /// @@ -82,7 +86,8 @@ public interface IServiceRegistrationAttribute { /// determining the duration for which the service instance is retained. /// Common lifetimes include Transient, Scoped, and Singleton. /// - ServiceLifetime Lifetime { + ServiceLifetime Lifetime + { get => ServiceLifetime.Transient; set { } } @@ -93,7 +98,8 @@ ServiceLifetime Lifetime { /// service if it doesn't already exist, adding a service to an enumerable, or replacing /// an existing service. /// - RegistrationType Using { + RegistrationType Using + { get => RegistrationType.Add; set { } } @@ -105,18 +111,17 @@ RegistrationType Using { /// registration attributes, such as specifying the service type, registration type, and /// associated service lifetime. /// -public abstract class BaseServiceAttribute : Attribute, IServiceRegistrationAttribute { - +public abstract class BaseServiceAttribute : Attribute, IServiceRegistrationAttribute +{ /// public object? Key { get; set; } - + /// public Type? As { get; set; } - + /// public RegistrationType Using { get; set; } = RegistrationType.Add; - /// /// Gets or sets the module or scope under which the service should be registered. /// This property allows organizing or segregating service registrations across different logical groups @@ -146,10 +151,11 @@ public abstract class BaseServiceAttribute : Attribute, IServiceRegistrationAttr /// /// public int Order { get; set; } - + /// [Browsable(false)] - ServiceLifetime IServiceRegistrationAttribute.Lifetime { + ServiceLifetime IServiceRegistrationAttribute.Lifetime + { get => Lifetime; set => throw new Exception("Setting lifetime is not supported"); } @@ -161,4 +167,4 @@ ServiceLifetime IServiceRegistrationAttribute.Lifetime { /// or singleton lifetime. /// protected abstract ServiceLifetime Lifetime { get; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Attributes/CrossWireServiceAttribute.cs b/src/DependencyModules.Runtime/Attributes/CrossWireServiceAttribute.cs index 41b7e23..163eb25 100644 --- a/src/DependencyModules.Runtime/Attributes/CrossWireServiceAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/CrossWireServiceAttribute.cs @@ -8,35 +8,30 @@ namespace DependencyModules.Runtime.Attributes; /// will be registered pointing to the implementation registration /// allowing for the same instance to be returned for multiple interfaces /// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = false)] -public class CrossWireServiceAttribute : Attribute, IServiceRegistrationAttribute { - +[AttributeUsage( + AttributeTargets.Class | AttributeTargets.Method, + AllowMultiple = true, + Inherited = false +)] +public class CrossWireServiceAttribute : Attribute, IServiceRegistrationAttribute +{ /// - public object? Key { - get; - set; - } - + public object? Key { get; set; } + /// [Browsable(false)] - Type? IServiceRegistrationAttribute.As { - get; - set; - } - + Type? IServiceRegistrationAttribute.As { get; set; } + /// - public ServiceLifetime Lifetime { - get; - set; - } - + public ServiceLifetime Lifetime { get; set; } + /// - /// Which method type to use, + /// Which method type to use, /// public RegistrationType Using { get; set; } = RegistrationType.Add; - + /// /// DependencyModule realm that this type should be associated with /// public Type? Realm { get; set; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Attributes/DecorateAttribute.cs b/src/DependencyModules.Runtime/Attributes/DecorateAttribute.cs index 6d4313f..4573611 100644 --- a/src/DependencyModules.Runtime/Attributes/DecorateAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/DecorateAttribute.cs @@ -21,7 +21,8 @@ namespace DependencyModules.Runtime.Attributes; /// /// The decorator, which must implement . [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] -public class DecorateAttribute(Type service, Type decorator) : Attribute { +public class DecorateAttribute(Type service, Type decorator) : Attribute +{ /// /// The service being decorated. /// diff --git a/src/DependencyModules.Runtime/Attributes/DecoratorAttribute.cs b/src/DependencyModules.Runtime/Attributes/DecoratorAttribute.cs index 17b005e..599e97b 100644 --- a/src/DependencyModules.Runtime/Attributes/DecoratorAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/DecoratorAttribute.cs @@ -11,13 +11,15 @@ namespace DependencyModules.Runtime.Attributes; /// /// /// [Decorator(Order = 100)] -/// public class CachingRepository(IRepository inner, IMemoryCache cache) : IRepository { +/// public class CachingRepository(IRepository inner, IMemoryCache cache) : IRepository +/// { /// public Item Get(int id) => cache.GetOrCreate(id, _ => inner.Get(id))!; /// } /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] -public class DecoratorAttribute : Attribute { +public class DecoratorAttribute : Attribute +{ /// /// Controls how decorators nest. Lower values are applied first and therefore sit closer to the /// implementation; higher values wrap them. diff --git a/src/DependencyModules.Runtime/Attributes/DependencyModuleAttribute.cs b/src/DependencyModules.Runtime/Attributes/DependencyModuleAttribute.cs index f417ef6..c3c5cf9 100644 --- a/src/DependencyModules.Runtime/Attributes/DependencyModuleAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/DependencyModuleAttribute.cs @@ -3,8 +3,9 @@ namespace DependencyModules.Runtime.Attributes; /// /// Applied to partial classes to denote a module entry point /// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly , Inherited = false)] -public class DependencyModuleAttribute : Attribute { +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly, Inherited = false)] +public class DependencyModuleAttribute : Attribute +{ /// /// Restrict registration to types that are registered for this realm (Type) /// @@ -24,16 +25,16 @@ public class DependencyModuleAttribute : Attribute { /// Register JsonSourceGenerationOptions classes /// public bool RegisterJsonSerializers { get; set; } = false; - + /// /// Generate a IServiceCollection extension method /// Attributes are usually preferred over UseXXX methods /// public string? GenerateUseMethod { get; set; } - + /// /// Setting this to true will generate registration using factories /// instead of allowing the container to construct the type /// public bool GenerateFactories { get; set; } = false; -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Attributes/IfEnvironmentAttribute.cs b/src/DependencyModules.Runtime/Attributes/IfEnvironmentAttribute.cs index 2cdee98..2062c8e 100644 --- a/src/DependencyModules.Runtime/Attributes/IfEnvironmentAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/IfEnvironmentAttribute.cs @@ -23,12 +23,14 @@ namespace DependencyModules.Runtime.Attributes; /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] -public class IfEnvironmentAttribute : Attribute { +public class IfEnvironmentAttribute : Attribute +{ /// /// Registers the service only in the given environments. /// /// The environment names to register in. - public IfEnvironmentAttribute(params string[] environmentNames) { + public IfEnvironmentAttribute(params string[] environmentNames) + { EnvironmentNames = environmentNames; } diff --git a/src/DependencyModules.Runtime/Attributes/IfEnvironmentValueAttribute.cs b/src/DependencyModules.Runtime/Attributes/IfEnvironmentValueAttribute.cs index bc981a6..e121b2a 100644 --- a/src/DependencyModules.Runtime/Attributes/IfEnvironmentValueAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/IfEnvironmentValueAttribute.cs @@ -22,12 +22,14 @@ namespace DependencyModules.Runtime.Attributes; /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] -public class IfEnvironmentValueAttribute : Attribute { +public class IfEnvironmentValueAttribute : Attribute +{ /// /// Registers the service only when the environment has any value for . /// /// The key that has to be present. - public IfEnvironmentValueAttribute(string key) { + public IfEnvironmentValueAttribute(string key) + { Key = key; } @@ -37,7 +39,8 @@ public IfEnvironmentValueAttribute(string key) { /// /// The key to read. /// The value it has to equal. - public IfEnvironmentValueAttribute(string key, string value) { + public IfEnvironmentValueAttribute(string key, string value) + { Key = key; Value = value; } diff --git a/src/DependencyModules.Runtime/Attributes/IfNotEnvironmentAttribute.cs b/src/DependencyModules.Runtime/Attributes/IfNotEnvironmentAttribute.cs index 5157e3b..8c588c4 100644 --- a/src/DependencyModules.Runtime/Attributes/IfNotEnvironmentAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/IfNotEnvironmentAttribute.cs @@ -15,12 +15,14 @@ namespace DependencyModules.Runtime.Attributes; /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] -public class IfNotEnvironmentAttribute : Attribute { +public class IfNotEnvironmentAttribute : Attribute +{ /// /// Registers the service except in the given environments. /// /// The environment names to exclude. - public IfNotEnvironmentAttribute(params string[] environmentNames) { + public IfNotEnvironmentAttribute(params string[] environmentNames) + { EnvironmentNames = environmentNames; } diff --git a/src/DependencyModules.Runtime/Attributes/IfNotEnvironmentValueAttribute.cs b/src/DependencyModules.Runtime/Attributes/IfNotEnvironmentValueAttribute.cs index 39eefa7..e71436b 100644 --- a/src/DependencyModules.Runtime/Attributes/IfNotEnvironmentValueAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/IfNotEnvironmentValueAttribute.cs @@ -15,13 +15,15 @@ namespace DependencyModules.Runtime.Attributes; /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] -public class IfNotEnvironmentValueAttribute : Attribute { +public class IfNotEnvironmentValueAttribute : Attribute +{ /// /// Registers the service except when the environment has any value for /// . /// /// The key whose presence skips the registration. - public IfNotEnvironmentValueAttribute(string key) { + public IfNotEnvironmentValueAttribute(string key) + { Key = key; } @@ -31,7 +33,8 @@ public IfNotEnvironmentValueAttribute(string key) { /// /// The key to read. /// The value that skips the registration. - public IfNotEnvironmentValueAttribute(string key, string value) { + public IfNotEnvironmentValueAttribute(string key, string value) + { Key = key; Value = value; } diff --git a/src/DependencyModules.Runtime/Attributes/InterceptAttribute.cs b/src/DependencyModules.Runtime/Attributes/InterceptAttribute.cs index e70a848..0e866bb 100644 --- a/src/DependencyModules.Runtime/Attributes/InterceptAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/InterceptAttribute.cs @@ -20,7 +20,8 @@ namespace DependencyModules.Runtime.Attributes; /// The interceptor types to apply, in order. Each is resolved from the container. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] -public class InterceptAttribute(params Type[] interceptors) : Attribute { +public class InterceptAttribute(params Type[] interceptors) : Attribute +{ /// /// The interceptors applied to the service. /// diff --git a/src/DependencyModules.Runtime/Attributes/InterceptedMembers.cs b/src/DependencyModules.Runtime/Attributes/InterceptedMembers.cs index c89b6e0..9790683 100644 --- a/src/DependencyModules.Runtime/Attributes/InterceptedMembers.cs +++ b/src/DependencyModules.Runtime/Attributes/InterceptedMembers.cs @@ -21,7 +21,8 @@ namespace DependencyModules.Runtime.Attributes; /// /// [Flags] -public enum InterceptedMembers { +public enum InterceptedMembers +{ /// Ordinary methods. Methods = 1, @@ -35,5 +36,5 @@ public enum InterceptedMembers { Events = 8, /// Everything the interface declares. The default. - All = Methods | Properties | Indexers | Events + All = Methods | Properties | Indexers | Events, } diff --git a/src/DependencyModules.Runtime/Attributes/ScopedServiceAttribute.cs b/src/DependencyModules.Runtime/Attributes/ScopedServiceAttribute.cs index ce3ab27..5bd8b63 100644 --- a/src/DependencyModules.Runtime/Attributes/ScopedServiceAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/ScopedServiceAttribute.cs @@ -5,9 +5,13 @@ namespace DependencyModules.Runtime.Attributes; /// /// Register service or factory as scoped /// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = false)] -public class ScopedServiceAttribute : BaseServiceAttribute { - +[AttributeUsage( + AttributeTargets.Class | AttributeTargets.Method, + AllowMultiple = true, + Inherited = false +)] +public class ScopedServiceAttribute : BaseServiceAttribute +{ /// protected override ServiceLifetime Lifetime => ServiceLifetime.Scoped; -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Attributes/SingletonServiceAttribute.cs b/src/DependencyModules.Runtime/Attributes/SingletonServiceAttribute.cs index a113f97..dc21fee 100644 --- a/src/DependencyModules.Runtime/Attributes/SingletonServiceAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/SingletonServiceAttribute.cs @@ -5,8 +5,13 @@ namespace DependencyModules.Runtime.Attributes; /// /// Register service or factory as singleton /// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = false)] -public class SingletonServiceAttribute : BaseServiceAttribute { +[AttributeUsage( + AttributeTargets.Class | AttributeTargets.Method, + AllowMultiple = true, + Inherited = false +)] +public class SingletonServiceAttribute : BaseServiceAttribute +{ /// protected override ServiceLifetime Lifetime => ServiceLifetime.Singleton; -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Attributes/TransientServiceAttribute.cs b/src/DependencyModules.Runtime/Attributes/TransientServiceAttribute.cs index edb139a..e475230 100644 --- a/src/DependencyModules.Runtime/Attributes/TransientServiceAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/TransientServiceAttribute.cs @@ -5,9 +5,13 @@ namespace DependencyModules.Runtime.Attributes; /// /// Register service or factory as Transient /// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = false)] -public class TransientServiceAttribute : BaseServiceAttribute { - +[AttributeUsage( + AttributeTargets.Class | AttributeTargets.Method, + AllowMultiple = true, + Inherited = false +)] +public class TransientServiceAttribute : BaseServiceAttribute +{ /// protected override ServiceLifetime Lifetime => ServiceLifetime.Transient; -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Conventions/ConventionContracts.cs b/src/DependencyModules.Runtime/Conventions/ConventionContracts.cs index 4ae19f2..1b7bcda 100644 --- a/src/DependencyModules.Runtime/Conventions/ConventionContracts.cs +++ b/src/DependencyModules.Runtime/Conventions/ConventionContracts.cs @@ -16,8 +16,8 @@ // Nothing here has behaviour or is ever executed. The generator reads the chain out of the method // body at compile time and emits ordinary registrations; the body itself is never called. -namespace DependencyModules.Runtime.Conventions { - +namespace DependencyModules.Runtime.Conventions +{ /// /// Implement this on a [DependencyModule] class to register services by convention /// instead of attributing each one. @@ -46,8 +46,10 @@ namespace DependencyModules.Runtime.Conventions { /// /// /// [DependencyModule] - /// public partial class DataModule : IConventionModule { - /// void IConventionModule.Conventions(IConventionDefinitions conventions) { + /// public partial class DataModule : IConventionModule + /// { + /// void IConventionModule.Conventions(IConventionDefinitions conventions) + /// { /// conventions.RegisterAll<IRepository>().AsScoped(); /// conventions.RegisterAll(typeof(IRequestHandler<,>)).AsTransient(); /// } @@ -55,8 +57,8 @@ namespace DependencyModules.Runtime.Conventions { /// /// /// - public interface IConventionModule { - + public interface IConventionModule + { /// /// Declares this module's conventions. Read at compile time; never invoked. /// @@ -70,8 +72,8 @@ public interface IConventionModule { /// /// Nothing implements this. The calls made on it are read from source at compile time. /// - public interface IConventionDefinitions { - + public interface IConventionDefinitions + { /// /// Registers every type in this compilation that implements /// , as . @@ -135,8 +137,8 @@ public interface IConventionDefinitions { /// A lifetime is required. There is no default, because a lifetime nobody wrote down is /// the most expensive thing for a registration to get wrong; omitting one is DM0009. /// - public interface IConventionRegistration { - + public interface IConventionRegistration + { /// Registers the matches as singletons. IConventionRegistration AsSingleton(); @@ -255,7 +257,8 @@ public interface IConventionRegistration { /// /// How to add the registration. IConventionRegistration Using( - global::DependencyModules.Runtime.Attributes.RegistrationType registrationType); + global::DependencyModules.Runtime.Attributes.RegistrationType registrationType + ); /// /// Registers every match under a service key. diff --git a/src/DependencyModules.Runtime/Features/FeatureApplicator.cs b/src/DependencyModules.Runtime/Features/FeatureApplicator.cs index c984b4e..83578c6 100644 --- a/src/DependencyModules.Runtime/Features/FeatureApplicator.cs +++ b/src/DependencyModules.Runtime/Features/FeatureApplicator.cs @@ -12,7 +12,9 @@ namespace DependencyModules.Runtime.Features; /// The type of the feature being applied, which must adhere to the constraints defined /// by the corresponding feature handler. /// -public class FeatureApplicator(IDependencyModuleFeature handler) : IFeatureApplicator { +public class FeatureApplicator(IDependencyModuleFeature handler) + : IFeatureApplicator +{ /// /// Gets the order of the feature applicator execution. /// The order determines the sequence in which feature applicators @@ -29,7 +31,11 @@ public class FeatureApplicator(IDependencyModuleFeature hand /// /// The list of dependency modules containing features to be applied. /// - public void Apply(IServiceCollection serviceCollection, IReadOnlyList modules) { + public void Apply( + IServiceCollection serviceCollection, + IReadOnlyList modules + ) + { handler.HandleFeature(serviceCollection, modules.OfType()); } -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Features/IDependencyModuleApplicatorProvider.cs b/src/DependencyModules.Runtime/Features/IDependencyModuleApplicatorProvider.cs index 9b8f085..887a0a9 100644 --- a/src/DependencyModules.Runtime/Features/IDependencyModuleApplicatorProvider.cs +++ b/src/DependencyModules.Runtime/Features/IDependencyModuleApplicatorProvider.cs @@ -6,8 +6,8 @@ namespace DependencyModules.Runtime.Features; /// Provides a mechanism to retrieve a collection of feature applicators used for handling /// and applying specific features in the dependency module system. /// -public interface IDependencyModuleApplicatorProvider { - +public interface IDependencyModuleApplicatorProvider +{ /// /// Retrieves a collection of feature applicators responsible for handling and applying /// specific features in the dependency module system. @@ -16,7 +16,8 @@ public interface IDependencyModuleApplicatorProvider { /// An enumerable collection of objects implementing the interface. /// If no feature applicators are available, an empty collection is returned. /// - IEnumerable FeatureApplicators() { + IEnumerable FeatureApplicators() + { return ArraySegment.Empty; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Features/IDependencyModuleFeature.cs b/src/DependencyModules.Runtime/Features/IDependencyModuleFeature.cs index 420b681..0da4561 100644 --- a/src/DependencyModules.Runtime/Features/IDependencyModuleFeature.cs +++ b/src/DependencyModules.Runtime/Features/IDependencyModuleFeature.cs @@ -11,7 +11,8 @@ namespace DependencyModules.Runtime.Features; /// The feature type to be handled by the module. Represents objects or details that can be utilized /// to configure or apply specific functionality during service registration. /// -public interface IDependencyModuleFeature { +public interface IDependencyModuleFeature +{ /// /// Gets the order in which the dependency module feature should be applied. /// Features with a lower order value are handled earlier during the service collection @@ -34,4 +35,4 @@ public interface IDependencyModuleFeature { /// Each feature provides specific information to configure related services. /// void HandleFeature(IServiceCollection collection, IEnumerable feature); -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Features/IFeatureApplicator.cs b/src/DependencyModules.Runtime/Features/IFeatureApplicator.cs index e2bb537..0a44913 100644 --- a/src/DependencyModules.Runtime/Features/IFeatureApplicator.cs +++ b/src/DependencyModules.Runtime/Features/IFeatureApplicator.cs @@ -7,7 +7,8 @@ namespace DependencyModules.Runtime.Features; /// Defines the contract for a feature applicator responsible for applying /// specific features within a dependency management system. /// -public interface IFeatureApplicator { +public interface IFeatureApplicator +{ /// /// Represents the order of execution for feature applicators when applying features /// to a service collection. This property determines the sequence in which @@ -25,4 +26,4 @@ public interface IFeatureApplicator { /// A read-only list of dependency modules containing the features to be applied. /// void Apply(IServiceCollection serviceCollection, IReadOnlyList modules); -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Helpers/DecoratorHelper.cs b/src/DependencyModules.Runtime/Helpers/DecoratorHelper.cs index 01976c8..fdfa6ea 100644 --- a/src/DependencyModules.Runtime/Helpers/DecoratorHelper.cs +++ b/src/DependencyModules.Runtime/Helpers/DecoratorHelper.cs @@ -24,8 +24,8 @@ namespace DependencyModules.Runtime.Helpers; /// decorator is constructed by a literal new and the code exists in the assembly. /// /// -public static class DecoratorHelper { - +public static class DecoratorHelper +{ /// /// Swaps an open generic registration for a generated wrapper that implements the same /// open generic service. @@ -63,25 +63,38 @@ public static void InterceptOpenGeneric( IServiceCollection services, Type serviceType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] - Type implementationType, + Type implementationType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] - Type wrapperType) { - + Type wrapperType + ) + { // Snapshotted: the loop appends the implementation's own registration, and re-reading Count // would walk into what it just added. var count = services.Count; - for (var i = 0; i < count; i++) { + for (var i = 0; i < count; i++) + { var descriptor = services[i]; - if (descriptor.ServiceType != serviceType || ImplementationOf(descriptor) != implementationType) { + if ( + descriptor.ServiceType != serviceType + || ImplementationOf(descriptor) != implementationType + ) + { continue; } - services.Add(new ServiceDescriptor(implementationType, implementationType, descriptor.Lifetime)); + services.Add( + new ServiceDescriptor(implementationType, implementationType, descriptor.Lifetime) + ); services[i] = descriptor.IsKeyedService - ? new ServiceDescriptor(serviceType, descriptor.ServiceKey, wrapperType, descriptor.Lifetime) + ? new ServiceDescriptor( + serviceType, + descriptor.ServiceKey, + wrapperType, + descriptor.Lifetime + ) : new ServiceDescriptor(serviceType, wrapperType, descriptor.Lifetime); } } @@ -94,7 +107,9 @@ public static void InterceptOpenGeneric( /// cannot be read through one property. /// private static Type? ImplementationOf(ServiceDescriptor descriptor) => - descriptor.IsKeyedService ? descriptor.KeyedImplementationType : descriptor.ImplementationType; + descriptor.IsKeyedService + ? descriptor.KeyedImplementationType + : descriptor.ImplementationType; /// /// Wraps every registration of using . @@ -114,8 +129,9 @@ public static void InterceptOpenGeneric( public static void Decorate( IServiceCollection services, Type serviceType, - Func decoratorFactory) { - + Func decoratorFactory + ) + { Decorate(services, serviceType, decoratorFactory, null, null); } @@ -142,8 +158,10 @@ public static void Decorate( public static void Decorate( IServiceCollection services, Type decoratorIdentity, - Func decoratorFactory) where TService : class { - + Func decoratorFactory + ) + where TService : class + { Decorate(services, decoratorIdentity, decoratorFactory, null); } @@ -182,15 +200,18 @@ public static void Decorate( IServiceCollection services, Type decoratorIdentity, Func decoratorFactory, - Type? implementationType) where TService : class { - + Type? implementationType + ) + where TService : class + { Decorate( services, typeof(TService), (provider, inner) => decoratorFactory(provider, (TService)inner), null, decoratorIdentity, - implementationType); + implementationType + ); } /// @@ -217,14 +238,18 @@ public static void Decorate( private static readonly ConditionalWeakTable> Applied = new(); private static bool AlreadyApplied(ServiceDescriptor descriptor, Type? decoratorIdentity) => - decoratorIdentity != null && - Applied.TryGetValue(descriptor, out var applied) && - applied.Contains(decoratorIdentity); + decoratorIdentity != null + && Applied.TryGetValue(descriptor, out var applied) + && applied.Contains(decoratorIdentity); private static void RecordApplied( - ServiceDescriptor original, ServiceDescriptor replacement, Type? decoratorIdentity) { - - if (decoratorIdentity == null) { + ServiceDescriptor original, + ServiceDescriptor replacement, + Type? decoratorIdentity + ) + { + if (decoratorIdentity == null) + { return; } @@ -232,7 +257,8 @@ private static void RecordApplied( // Stacked decorators each rewrite the slot, so the set has to follow the descriptor that // now occupies it rather than staying with the one that was replaced. - if (Applied.TryGetValue(original, out var existing)) { + if (Applied.TryGetValue(original, out var existing)) + { applied.UnionWith(existing); } @@ -251,7 +277,8 @@ private static void RecordApplied( /// then be unable to recognise its own registration, so the origin is carried across the /// replacement the same way is. /// - private static readonly ConditionalWeakTable OriginImplementation = new(); + private static readonly ConditionalWeakTable OriginImplementation = + new(); /// /// The implementation behind a descriptor, or null when it cannot be known — a registration made @@ -259,10 +286,14 @@ private static void RecordApplied( /// DependencyModules_GenerateFactories emits. /// private static Type? OriginImplementationOf(ServiceDescriptor descriptor) => - OriginImplementation.TryGetValue(descriptor, out var origin) ? origin : ImplementationOf(descriptor); - - private static void RecordOrigin(ServiceDescriptor original, ServiceDescriptor replacement) { - if (OriginImplementationOf(original) is not { } origin) { + OriginImplementation.TryGetValue(descriptor, out var origin) + ? origin + : ImplementationOf(descriptor); + + private static void RecordOrigin(ServiceDescriptor original, ServiceDescriptor replacement) + { + if (OriginImplementationOf(original) is not { } origin) + { return; } @@ -276,25 +307,30 @@ private static void Decorate( Func decoratorFactory, Type? decoratorType, Type? decoratorIdentity, - Type? implementationType = null) { - + Type? implementationType = null + ) + { var ordinal = 0; - for (var i = services.Count - 1; i >= 0; i--) { + for (var i = services.Count - 1; i >= 0; i--) + { var descriptor = services[i]; - if (!Matches(descriptor.ServiceType, serviceType)) { + if (!Matches(descriptor.ServiceType, serviceType)) + { continue; } // A registration this method displaced on an earlier pass is machinery, not a service. // Decorating it would wrap the implementation a second time, one layer further in. - if (descriptor.ServiceKey is DisplacedImplementationKey) { + if (descriptor.ServiceKey is DisplacedImplementationKey) + { continue; } // Emitted from two places for the same registration; the first one wins. - if (AlreadyApplied(descriptor, decoratorIdentity)) { + if (AlreadyApplied(descriptor, decoratorIdentity)) + { continue; } @@ -303,9 +339,12 @@ private static void Decorate( // registration made from an instance or a factory cannot be attributed to an // implementation, and refusing to wrap it there would silently stop intercepting a // service that had asked for it — a worse failure than the one this filter prevents. - if (implementationType != null && - OriginImplementationOf(descriptor) is { } origin && - origin != implementationType) { + if ( + implementationType != null + && OriginImplementationOf(descriptor) is { } origin + && origin != implementationType + ) + { continue; } @@ -321,12 +360,14 @@ private static void Decorate( descriptor.ServiceType, descriptor.ServiceKey, (provider, key) => decoratorFactory(provider, innerFactory(provider, key)), - descriptor.Lifetime) + descriptor.Lifetime + ) : new ServiceDescriptor( descriptor.ServiceType, provider => decoratorFactory(provider, innerFactory(provider, null)), // The decorator must not change how long the service lives. - descriptor.Lifetime); + descriptor.Lifetime + ); RecordApplied(descriptor, replacement, decoratorIdentity); RecordOrigin(descriptor, replacement); @@ -344,27 +385,31 @@ private static void Decorate( /// ordinal only separates descriptors that are otherwise identical, which is legal — /// AddSingleton<IFoo, Foo>() twice registers two services and must stay two. /// - private sealed class DisplacedImplementationKey : IEquatable { + private sealed class DisplacedImplementationKey : IEquatable + { private readonly Type _serviceType; private readonly Type _implementationType; private readonly int _ordinal; - public DisplacedImplementationKey(Type serviceType, Type implementationType, int ordinal) { + public DisplacedImplementationKey(Type serviceType, Type implementationType, int ordinal) + { _serviceType = serviceType; _implementationType = implementationType; _ordinal = ordinal; } public bool Equals(DisplacedImplementationKey? other) => - other is not null && - _serviceType == other._serviceType && - _implementationType == other._implementationType && - _ordinal == other._ordinal; + other is not null + && _serviceType == other._serviceType + && _implementationType == other._implementationType + && _ordinal == other._ordinal; public override bool Equals(object? obj) => Equals(obj as DisplacedImplementationKey); - public override int GetHashCode() { - unchecked { + public override int GetHashCode() + { + unchecked + { var hash = _serviceType.GetHashCode(); hash = hash * 31 + _implementationType.GetHashCode(); return hash * 31 + _ordinal; @@ -393,47 +438,70 @@ public override string ToString() => /// /// private static Func CaptureInner( - IServiceCollection services, ServiceDescriptor descriptor, int ordinal) { - - if (descriptor.IsKeyedService) { - if (descriptor.KeyedImplementationInstance is { } keyedInstance) { + IServiceCollection services, + ServiceDescriptor descriptor, + int ordinal + ) + { + if (descriptor.IsKeyedService) + { + if (descriptor.KeyedImplementationInstance is { } keyedInstance) + { return (_, _) => keyedInstance; } - if (descriptor.KeyedImplementationFactory is { } keyedFactory) { + if (descriptor.KeyedImplementationFactory is { } keyedFactory) + { return (provider, key) => keyedFactory(provider, key); } - if (descriptor.KeyedImplementationType is { } keyedImplementationType) { + if (descriptor.KeyedImplementationType is { } keyedImplementationType) + { var keyedInnerKey = Displace( - services, descriptor.ServiceType, keyedImplementationType, ordinal, descriptor.Lifetime); + services, + descriptor.ServiceType, + keyedImplementationType, + ordinal, + descriptor.Lifetime + ); - return (provider, _) => provider.GetRequiredKeyedService(keyedImplementationType, keyedInnerKey); + return (provider, _) => + provider.GetRequiredKeyedService(keyedImplementationType, keyedInnerKey); } throw new InvalidOperationException( - $"The keyed registration for '{descriptor.ServiceType}' has no implementation type, factory, " + - "or instance, so there is nothing to decorate."); + $"The keyed registration for '{descriptor.ServiceType}' has no implementation type, factory, " + + "or instance, so there is nothing to decorate." + ); } - if (descriptor.ImplementationInstance is { } instance) { + if (descriptor.ImplementationInstance is { } instance) + { return (_, _) => instance; } - if (descriptor.ImplementationFactory is { } factory) { + if (descriptor.ImplementationFactory is { } factory) + { return (provider, _) => factory(provider); } - if (descriptor.ImplementationType is { } implementationType) { + if (descriptor.ImplementationType is { } implementationType) + { var innerKey = Displace( - services, descriptor.ServiceType, implementationType, ordinal, descriptor.Lifetime); + services, + descriptor.ServiceType, + implementationType, + ordinal, + descriptor.Lifetime + ); return (provider, _) => provider.GetRequiredKeyedService(implementationType, innerKey); } throw new InvalidOperationException( - $"The registration for '{descriptor.ServiceType}' has no implementation type, factory, or " + - "instance, so there is nothing to decorate."); + $"The registration for '{descriptor.ServiceType}' has no implementation type, factory, or " + + "instance, so there is nothing to decorate." + ); } /// @@ -443,10 +511,11 @@ private static DisplacedImplementationKey Displace( IServiceCollection services, Type serviceType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] - Type implementationType, + Type implementationType, int ordinal, - ServiceLifetime lifetime) { - + ServiceLifetime lifetime + ) + { var key = new DisplacedImplementationKey(serviceType, implementationType, ordinal); // Appended while the caller iterates backwards, so it is never revisited on this pass. @@ -472,32 +541,39 @@ private static DisplacedImplementationKey Displace( /// Closed registrations of a generic service are decorated normally, so declaring a closed /// construction is the way through. /// - private static void GuardOpenGenericRegistration(ServiceDescriptor descriptor, Type? decoratorType) { - if (!descriptor.ServiceType.IsGenericTypeDefinition) { + private static void GuardOpenGenericRegistration( + ServiceDescriptor descriptor, + Type? decoratorType + ) + { + if (!descriptor.ServiceType.IsGenericTypeDefinition) + { return; } var by = decoratorType == null ? "" : $" by '{decoratorType}'"; throw new InvalidOperationException( - $"'{descriptor.ServiceType}' is registered as an open generic and cannot be decorated{by}. " + - "Decorating replaces a registration with a factory, which the container does not allow " + - "for an open generic service type. Register closed constructions of the service instead, " + - "such as a class deriving from the generic implementation."); + $"'{descriptor.ServiceType}' is registered as an open generic and cannot be decorated{by}. " + + "Decorating replaces a registration with a factory, which the container does not allow " + + "for an open generic service type. Register closed constructions of the service instead, " + + "such as a class deriving from the generic implementation." + ); } /// /// True when a registered service type is the one being decorated, including a closed /// construction of a decorated open generic. /// - private static bool Matches(Type registeredServiceType, Type decoratedServiceType) { - if (registeredServiceType == decoratedServiceType) { + private static bool Matches(Type registeredServiceType, Type decoratedServiceType) + { + if (registeredServiceType == decoratedServiceType) + { return true; } - return decoratedServiceType.IsGenericTypeDefinition && - registeredServiceType.IsGenericType && - registeredServiceType.GetGenericTypeDefinition() == decoratedServiceType; + return decoratedServiceType.IsGenericTypeDefinition + && registeredServiceType.IsGenericType + && registeredServiceType.GetGenericTypeDefinition() == decoratedServiceType; } - } diff --git a/src/DependencyModules.Runtime/Helpers/DecoratorRegistration.cs b/src/DependencyModules.Runtime/Helpers/DecoratorRegistration.cs index c6fc7a9..dc2f346 100644 --- a/src/DependencyModules.Runtime/Helpers/DecoratorRegistration.cs +++ b/src/DependencyModules.Runtime/Helpers/DecoratorRegistration.cs @@ -19,7 +19,8 @@ namespace DependencyModules.Runtime.Helpers; /// The function that rewrites registrations in the collection, receiving the environment any /// condition on the decorator is evaluated against. /// -public sealed class DecoratorRegistration(int order, EnvironmentRegistryFunc registryFunc) { +public sealed class DecoratorRegistration(int order, EnvironmentRegistryFunc registryFunc) +{ /// /// A decorator with no environment condition. /// diff --git a/src/DependencyModules.Runtime/Helpers/DependencyRegistry.cs b/src/DependencyModules.Runtime/Helpers/DependencyRegistry.cs index 46e3811..b390c36 100644 --- a/src/DependencyModules.Runtime/Helpers/DependencyRegistry.cs +++ b/src/DependencyModules.Runtime/Helpers/DependencyRegistry.cs @@ -26,7 +26,9 @@ namespace DependencyModules.Runtime.Helpers; /// The IServiceCollection to which dependencies will be added. /// The environment conditions are evaluated against. Never null. public delegate void EnvironmentRegistryFunc( - IServiceCollection serviceCollection, IModuleEnvironment environment); + IServiceCollection serviceCollection, + IModuleEnvironment environment +); /// /// Static class used to store dependency registration functions @@ -34,7 +36,8 @@ public delegate void EnvironmentRegistryFunc( /// /// // ReSharper disable once ClassNeverInstantiated.Global -public class DependencyRegistry { +public class DependencyRegistry +{ // ReSharper disable StaticMemberInGenericType private static readonly object SyncLock = new(); private static List? RegistryFuncs; @@ -46,8 +49,10 @@ public class DependencyRegistry { /// /// /// - public static int Add(RegistryFunc registryFunc) { - lock (SyncLock) { + public static int Add(RegistryFunc registryFunc) + { + lock (SyncLock) + { (RegistryFuncs ??= []).Add((serviceCollection, _) => registryFunc(serviceCollection)); } @@ -59,8 +64,10 @@ public static int Add(RegistryFunc registryFunc) { /// /// /// - public static int Add(EnvironmentRegistryFunc registryFunc) { - lock (SyncLock) { + public static int Add(EnvironmentRegistryFunc registryFunc) + { + lock (SyncLock) + { (RegistryFuncs ??= []).Add(registryFunc); } @@ -76,15 +83,16 @@ public static int Add(EnvironmentRegistryFunc registryFunc) { /// public static int Add( Func provider, - ServiceLifetime lifetime = ServiceLifetime.Transient) where TInstance : class { - lock (SyncLock) { + ServiceLifetime lifetime = ServiceLifetime.Transient + ) + where TInstance : class + { + lock (SyncLock) + { (RegistryFuncs ??= []).Add( - (registry, _) => registry.Add( - new ServiceDescriptor( - typeof(TInstance), - provider, - lifetime - ))); + (registry, _) => + registry.Add(new ServiceDescriptor(typeof(TInstance), provider, lifetime)) + ); } return 1; } @@ -104,18 +112,25 @@ public static int Add( /// public static int Add( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] - Type implementationType, + Type implementationType, ServiceLifetime lifetime = ServiceLifetime.Transient, - object? serviceKey = null) where TInstance : class { - lock (SyncLock) { + object? serviceKey = null + ) + where TInstance : class + { + lock (SyncLock) + { (RegistryFuncs ??= []).Add( - (registry, _) => registry.Add( - new ServiceDescriptor( - typeof(TInstance), - serviceKey, - implementationType, - lifetime - ))); + (registry, _) => + registry.Add( + new ServiceDescriptor( + typeof(TInstance), + serviceKey, + implementationType, + lifetime + ) + ) + ); } return 1; } @@ -133,8 +148,10 @@ public static int Add( /// that an application's decorators wrap those contributed by the libraries it consumes. /// /// - public static int AddDecorator(RegistryFunc registryFunc, int order = 0) { - lock (SyncLock) { + public static int AddDecorator(RegistryFunc registryFunc, int order = 0) + { + lock (SyncLock) + { (Decorators ??= []).Add(new DecoratorRegistration(order, registryFunc)); } @@ -152,8 +169,10 @@ public static int AddDecorator(RegistryFunc registryFunc, int order = 0) { /// Function that decorates registrations already in the collection. /// See the other overload; ordering is unaffected by the condition. /// - public static int AddDecorator(EnvironmentRegistryFunc registryFunc, int order = 0) { - lock (SyncLock) { + public static int AddDecorator(EnvironmentRegistryFunc registryFunc, int order = 0) + { + lock (SyncLock) + { (Decorators ??= []).Add(new DecoratorRegistration(order, registryFunc)); } @@ -165,8 +184,10 @@ public static int AddDecorator(EnvironmentRegistryFunc registryFunc, int order = /// /// /// - public static int AddModule(params IDependencyModule[] modules) { - lock (SyncLock) { + public static int AddModule(params IDependencyModule[] modules) + { + lock (SyncLock) + { (Modules ??= []).AddRange(modules); } @@ -178,21 +199,26 @@ public static int AddModule(params IDependencyModule[] modules) { /// /// /// - public static void LoadModules(IServiceCollection serviceCollection, params IDependencyModule[] dependencyModules) { + public static void LoadModules( + IServiceCollection serviceCollection, + params IDependencyModule[] dependencyModules + ) + { var modules = GetAllModules(dependencyModules); - + ApplyFeatures(serviceCollection, modules); ApplyServices(serviceCollection, modules); - + ApplyDecorators(serviceCollection, modules); } - + /// /// Apply all registration for a given type to the service collection /// /// - public static void ApplyServices(IServiceCollection serviceCollection) { + public static void ApplyServices(IServiceCollection serviceCollection) + { ApplyServices(serviceCollection, FindOrCreateEnvironment(serviceCollection)); } @@ -202,16 +228,23 @@ public static void ApplyServices(IServiceCollection serviceCollection) { /// /// /// - public static void ApplyServices(IServiceCollection serviceCollection, IModuleEnvironment environment) { + public static void ApplyServices( + IServiceCollection serviceCollection, + IModuleEnvironment environment + ) + { EnvironmentRegistryFunc[] snapshot; - lock (SyncLock) { - if (RegistryFuncs == null) { + lock (SyncLock) + { + if (RegistryFuncs == null) + { return; } snapshot = RegistryFuncs.ToArray(); } - foreach (var registryFunc in snapshot) { + foreach (var registryFunc in snapshot) + { registryFunc(serviceCollection, environment); } } @@ -220,7 +253,8 @@ public static void ApplyServices(IServiceCollection serviceCollection, IModuleEn /// Apply all decorators /// /// - public static void ApplyDecorators(IServiceCollection serviceCollection) { + public static void ApplyDecorators(IServiceCollection serviceCollection) + { ApplyDecorators(serviceCollection, FindOrCreateEnvironment(serviceCollection)); } @@ -234,7 +268,8 @@ public static void ApplyDecorators(IServiceCollection serviceCollection) { /// where two calls disagreeing would actually matter — two process defaults read the same /// variables and give the same answers. /// - private static IModuleEnvironment FindOrCreateEnvironment(IServiceCollection serviceCollection) { + private static IModuleEnvironment FindOrCreateEnvironment(IServiceCollection serviceCollection) + { var environment = FindModuleEnvironment(serviceCollection); return environment ?? ModuleEnvironment.CreateDefault(); @@ -244,12 +279,15 @@ private static IModuleEnvironment FindOrCreateEnvironment(IServiceCollection ser /// Stable insertion sort by Order. Replaces OrderBy so that no LINQ ordering machinery is /// instantiated for DecoratorRegistration at startup. /// - private static void SortByOrder(List list) { - for (var i = 1; i < list.Count; i++) { + private static void SortByOrder(List list) + { + for (var i = 1; i < list.Count; i++) + { var item = list[i]; var j = i - 1; - while (j >= 0 && list[j].Order > item.Order) { + while (j >= 0 && list[j].Order > item.Order) + { list[j + 1] = list[j]; j--; } @@ -268,10 +306,12 @@ private static void SortByOrder(List list) { /// what lets decoration find the same instance the registrations were decided against, which /// matters now that CreateDefault builds a fresh one per call. /// - private static IModuleEnvironment ResolveEnvironment(IServiceCollection serviceCollection) { + private static IModuleEnvironment ResolveEnvironment(IServiceCollection serviceCollection) + { var environment = FindModuleEnvironment(serviceCollection); - if (environment != null) { + if (environment != null) + { return environment; } @@ -290,10 +330,15 @@ private static IModuleEnvironment ResolveEnvironment(IServiceCollection serviceC /// /// /// - public static void ApplyDecorators(IServiceCollection serviceCollection, IModuleEnvironment environment) { + public static void ApplyDecorators( + IServiceCollection serviceCollection, + IModuleEnvironment environment + ) + { var list = new List(GetDecorators()); SortByOrder(list); - for (var i = 0; i < list.Count; i++) { + for (var i = 0; i < list.Count; i++) + { list[i].RegistryFunc(serviceCollection, environment); } } @@ -306,8 +351,10 @@ public static void ApplyDecorators(IServiceCollection serviceCollection, IModule /// that decorators from every module can be sorted together. Applying each module's decorators /// separately would make module discovery order outrank the declared order. /// - public static IReadOnlyList GetDecorators() { - lock (SyncLock) { + public static IReadOnlyList GetDecorators() + { + lock (SyncLock) + { return Decorators == null ? Array.Empty() : Decorators.ToArray(); } } @@ -317,9 +364,12 @@ public static IReadOnlyList GetDecorators() { /// /// /// - public static IEnumerable GetModules(params object[] modules) { - lock (SyncLock) { - if (Modules == null || Modules.Count == 0) { + public static IEnumerable GetModules(params object[] modules) + { + lock (SyncLock) + { + if (Modules == null || Modules.Count == 0) + { return modules; } @@ -338,35 +388,45 @@ public static IEnumerable GetModules(params object[] modules) { /// [MethodImpl(MethodImplOptions.NoInlining)] private static IEnumerable CombineWithAddedModules( - List registered, object[] modules) { - + List registered, + object[] modules + ) + { var snapshot = registered.ToList(); return modules.Length == 0 ? snapshot : snapshot.Concat(modules); } - private static void ApplyDecorators(IServiceCollection serviceCollection, IReadOnlyList modules) { + private static void ApplyDecorators( + IServiceCollection serviceCollection, + IReadOnlyList modules + ) + { // Gathered from every module and sorted together, the same way ApplyFeatures collects and // sorts feature applicators. Applying each module's decorators in turn would let module // discovery order outrank the declared order, which breaks a pipeline assembled from more // than one package. List? decorators = null; - for (var i = 0; i < modules.Count; i++) { + for (var i = 0; i < modules.Count; i++) + { var registrations = modules[i].InternalGetDecorators(); // Tested before enumerating. A module with no decorators is the common case, and asking // it for an enumerator to immediately find it empty built one per module per startup. - if (registrations is ICollection { Count: 0 }) { + if (registrations is ICollection { Count: 0 }) + { continue; } - foreach (var registration in registrations) { + foreach (var registration in registrations) + { (decorators ??= []).Add(registration); } } - if (decorators != null) { + if (decorators != null) + { // The same environment the registrations were decided against. ApplyServices runs first // and registers one when nothing supplied it, so this finds that instance rather than // building a second answer to "what environment is this" — a decorator gated on @@ -377,12 +437,14 @@ private static void ApplyDecorators(IServiceCollection serviceCollection, IReadO SortByOrder(decorators); - for (var i = 0; i < decorators.Count; i++) { + for (var i = 0; i < decorators.Count; i++) + { decorators[i].RegistryFunc(serviceCollection, environment); } } - for (var i = 0; i < modules.Count; i++) { + for (var i = 0; i < modules.Count; i++) + { var module = modules[i]; // Retained for hand-written modules that decorate directly. Generated modules use @@ -391,13 +453,18 @@ private static void ApplyDecorators(IServiceCollection serviceCollection, IReadO // Mirrors how ApplyServices invokes ConfigureServices. Runs last, so the manual escape // hatch sees every declared decorator already in place. - if (module is IServiceCollectionConfiguration serviceCollectionConfigure) { + if (module is IServiceCollectionConfiguration serviceCollectionConfigure) + { serviceCollectionConfigure.ConfigureDecorators(serviceCollection); } } } - private static void ApplyServices(IServiceCollection serviceCollection, IReadOnlyList modules) { + private static void ApplyServices( + IServiceCollection serviceCollection, + IReadOnlyList modules + ) + { // Always looked for now. Attribute conditions live on generated modules, which do not // implement IEnvironmentServiceCollectionConfiguration, so the old "only if some module // asked for it" gate would have missed them. It costs one scan of the collection per @@ -409,7 +476,8 @@ private static void ApplyServices(IServiceCollection serviceCollection, IReadOnl // environment at all. An application with no environment says so with ModuleEnvironment.None. // Nothing to apply means nothing to decide, so the collection is left exactly as it was // rather than picking up an environment nobody asked for. - if (modules.Count == 0) { + if (modules.Count == 0) + { return; } @@ -419,15 +487,18 @@ private static void ApplyServices(IServiceCollection serviceCollection, IReadOnl // never displaced — and registering it is what lets ApplyDecorators find the same instance. var environment = ResolveEnvironment(serviceCollection); - for (var i = 0; i < modules.Count; i++) { + for (var i = 0; i < modules.Count; i++) + { var module = modules[i]; module.InternalApplyServices(serviceCollection, environment); - if (module is IServiceCollectionConfiguration serviceCollectionConfigure) { + if (module is IServiceCollectionConfiguration serviceCollectionConfigure) + { serviceCollectionConfigure.ConfigureServices(serviceCollection); } - if (module is IEnvironmentServiceCollectionConfiguration environmentConfigure) { + if (module is IEnvironmentServiceCollectionConfiguration environmentConfigure) + { environmentConfigure.ConfigureServices(serviceCollection, environment); } } @@ -444,15 +515,18 @@ private static void ApplyServices(IServiceCollection serviceCollection, IReadOnl /// the registration that was ignored got shadowed by the one added in its place — a service /// gated on "Development" quietly took its production branch. /// - private static void RefuseUnusableEnvironment(ServiceDescriptor descriptor) { - if (descriptor.ServiceType == typeof(IModuleEnvironment)) { + private static void RefuseUnusableEnvironment(ServiceDescriptor descriptor) + { + if (descriptor.ServiceType == typeof(IModuleEnvironment)) + { throw new InvalidOperationException( - "An IModuleEnvironment is registered, but not as a singleton instance, so it cannot " + - "be used. The environment decides which services are registered, which happens " + - "while the service collection is being populated and before any provider exists to " + - "construct it from. Register the instance directly with " + - "AddSingleton(new MyEnvironment()), or pass it to " + - "AddModules(environment, modules)."); + "An IModuleEnvironment is registered, but not as a singleton instance, so it cannot " + + "be used. The environment decides which services are registered, which happens " + + "while the service collection is being populated and before any provider exists to " + + "construct it from. Register the instance directly with " + + "AddSingleton(new MyEnvironment()), or pass it to " + + "AddModules(environment, modules)." + ); } } @@ -467,18 +541,25 @@ private static void RefuseUnusableEnvironment(ServiceDescriptor descriptor) { /// The last matching descriptor decides, because that is the one the container would resolve. /// Anything earlier is shadowed and cannot be what the application meant. /// - private static IModuleEnvironment? FindModuleEnvironment(IServiceCollection serviceCollection) { - for (var i = serviceCollection.Count - 1; i >= 0; i--) { + private static IModuleEnvironment? FindModuleEnvironment(IServiceCollection serviceCollection) + { + for (var i = serviceCollection.Count - 1; i >= 0; i--) + { var descriptor = serviceCollection[i]; - if (descriptor.ServiceType != typeof(IModuleEnvironment)) { + if (descriptor.ServiceType != typeof(IModuleEnvironment)) + { continue; } - if (descriptor is { + if ( + descriptor is + { Lifetime: ServiceLifetime.Singleton, ImplementationInstance: IModuleEnvironment environment - }) { + } + ) + { return environment; } @@ -488,44 +569,59 @@ private static void RefuseUnusableEnvironment(ServiceDescriptor descriptor) { return null; } - private static void ApplyFeatures(IServiceCollection serviceCollection, IReadOnlyList modules) { + private static void ApplyFeatures( + IServiceCollection serviceCollection, + IReadOnlyList modules + ) + { List? features = null; - for (var i = 0; i < modules.Count; i++) { + for (var i = 0; i < modules.Count; i++) + { var module = modules[i]; - - if (module is IDependencyModuleApplicatorProvider provider) { - foreach (var featureApplicator in provider.FeatureApplicators()) { + + if (module is IDependencyModuleApplicatorProvider provider) + { + foreach (var featureApplicator in provider.FeatureApplicators()) + { (features ??= []).Add(featureApplicator); } } } - - if (features != null) { + + if (features != null) + { features.Sort((x, y) => x.Order.CompareTo(y.Order)); - for (var i = 0; i < features.Count; i++) { + for (var i = 0; i < features.Count; i++) + { var feature = features[i]; feature.Apply(serviceCollection, modules); } } } - - private static IReadOnlyList GetAllModules(IDependencyModule[] dependencyModules) { + private static IReadOnlyList GetAllModules( + IDependencyModule[] dependencyModules + ) + { var list = new List(); - foreach (var dependencyModule in dependencyModules) { + foreach (var dependencyModule in dependencyModules) + { InternalGetModules(dependencyModule, list); } return list; } - - private static void InternalGetModules(IDependencyModule dependencyModule, List allDependencyModules) { - if (!dependencyModule.LoadModule || - AlreadySeen(allDependencyModules, dependencyModule)) { + private static void InternalGetModules( + IDependencyModule dependencyModule, + List allDependencyModules + ) + { + if (!dependencyModule.LoadModule || AlreadySeen(allDependencyModules, dependencyModule)) + { return; } @@ -533,13 +629,17 @@ private static void InternalGetModules(IDependencyModule dependencyModule, List< var declared = dependencyModule.InternalGetModules(); - if (declared is not ICollection { Count: 0 }) { - foreach (var dependencyObject in declared) { - if (dependencyObject is IDependencyModuleProvider moduleProvider) { + if (declared is not ICollection { Count: 0 }) + { + foreach (var dependencyObject in declared) + { + if (dependencyObject is IDependencyModuleProvider moduleProvider) + { var dep = moduleProvider.GetModule(); InternalGetModules(dep, allDependencyModules); } - else if (dependencyObject is IDependencyModule module) { + else if (dependencyObject is IDependencyModule module) + { InternalGetModules(module, allDependencyModules); } } @@ -550,11 +650,13 @@ private static void InternalGetModules(IDependencyModule dependencyModule, List< // Both lists are empty for the overwhelming majority of modules, and both are reached // through an interface. Testing for an empty collection first avoids building an enumerator // for each of them on every module of every startup. - if (overridden is ICollection { Count: 0 }) { + if (overridden is ICollection { Count: 0 }) + { return; } - foreach (var module in overridden) { + foreach (var module in overridden) + { InternalGetModules(module, allDependencyModules); } } @@ -568,15 +670,18 @@ private static void InternalGetModules(IDependencyModule dependencyModule, List< /// for an interface is a runtime type-construction step - it showed up as the single most /// expensive thing in module discovery, to compare a list that usually holds one item. /// - private static bool AlreadySeen(List modules, IDependencyModule candidate) { - for (var i = 0; i < modules.Count; i++) { + private static bool AlreadySeen(List modules, IDependencyModule candidate) + { + for (var i = 0; i < modules.Count; i++) + { // Argument order matches EqualityComparer.Default, which asks the element rather // than the candidate, so a hand-written asymmetric Equals behaves as it always did. - if (modules[i].Equals(candidate)) { + if (modules[i].Equals(candidate)) + { return true; } } return false; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Helpers/EnvironmentConditions.cs b/src/DependencyModules.Runtime/Helpers/EnvironmentConditions.cs index a6ac683..4eedfa2 100644 --- a/src/DependencyModules.Runtime/Helpers/EnvironmentConditions.cs +++ b/src/DependencyModules.Runtime/Helpers/EnvironmentConditions.cs @@ -12,8 +12,8 @@ namespace DependencyModules.Runtime.Helpers; /// The negative forms of the attributes emit a ! around these rather than adding methods. /// [EditorBrowsable(EditorBrowsableState.Never)] -public static class EnvironmentConditions { - +public static class EnvironmentConditions +{ /// /// True when the environment name matches any of . /// @@ -25,13 +25,23 @@ public static class EnvironmentConditions { /// The environment to test. /// The names to accept. /// True when any name matches. - public static bool NameIs(IModuleEnvironment environment, params string[] names) { - if (environment == null) { + public static bool NameIs(IModuleEnvironment environment, params string[] names) + { + if (environment == null) + { return false; } - for (var i = 0; i < names.Length; i++) { - if (string.Equals(environment.EnvironmentName, names[i], StringComparison.OrdinalIgnoreCase)) { + for (var i = 0; i < names.Length; i++) + { + if ( + string.Equals( + environment.EnvironmentName, + names[i], + StringComparison.OrdinalIgnoreCase + ) + ) + { return true; } } diff --git a/src/DependencyModules.Runtime/Interception/CallerInfo.cs b/src/DependencyModules.Runtime/Interception/CallerInfo.cs index e7c2a76..fed6bac 100644 --- a/src/DependencyModules.Runtime/Interception/CallerInfo.cs +++ b/src/DependencyModules.Runtime/Interception/CallerInfo.cs @@ -14,7 +14,8 @@ namespace DependencyModules.Runtime.Interception; /// /// The interface being intercepted. /// The member being invoked. -public readonly struct CallerInfo(Type serviceType, string memberName) { +public readonly struct CallerInfo(Type serviceType, string memberName) +{ /// /// The intercepted interface. /// diff --git a/src/DependencyModules.Runtime/Interception/IArguments.cs b/src/DependencyModules.Runtime/Interception/IArguments.cs index 8451bf3..9633250 100644 --- a/src/DependencyModules.Runtime/Interception/IArguments.cs +++ b/src/DependencyModules.Runtime/Interception/IArguments.cs @@ -9,7 +9,8 @@ namespace DependencyModules.Runtime.Interception; /// writing one replaces the value the implementation will receive, and an interceptor that ignores /// them pays for neither. /// -public interface IArguments { +public interface IArguments +{ /// /// The number of arguments the intercepted member declares. /// diff --git a/src/DependencyModules.Runtime/Interception/IInterceptor.cs b/src/DependencyModules.Runtime/Interception/IInterceptor.cs index 8c8ba1f..46bf3e3 100644 --- a/src/DependencyModules.Runtime/Interception/IInterceptor.cs +++ b/src/DependencyModules.Runtime/Interception/IInterceptor.cs @@ -17,18 +17,23 @@ namespace DependencyModules.Runtime.Interception; /// cannot serve rather than skipping them silently. /// /// -/// public TResult Intercept<TResult>(InvocationContext<TResult> context) { +/// public TResult Intercept<TResult>(InvocationContext<TResult> context) +/// { /// var stopwatch = Stopwatch.StartNew(); /// -/// try { +/// try +/// { /// return context.Proceed(); -/// } finally { +/// } +/// finally +/// { /// _log.Record(context.Caller, stopwatch.Elapsed); /// } /// } /// /// -public interface IInterceptor { +public interface IInterceptor +{ /// /// Wraps one call. Call to run the rest of the /// pipeline, more than once to retry, or not at all to return without reaching the @@ -51,14 +56,16 @@ public interface IInterceptor { /// body, state spanning the call is an ordinary local and a scope may be held across it. /// /// -/// public async ValueTask<TResult> InterceptAsync<TResult>(AsyncInvocationContext<TResult> context) { +/// public async ValueTask<TResult> InterceptAsync<TResult>(AsyncInvocationContext<TResult> context) +/// { /// using var scope = _tracer.StartSpan(context.Caller.MemberName); /// /// return await context.ProceedAsync(); /// } /// /// -public interface IAsyncInterceptor { +public interface IAsyncInterceptor +{ /// /// Wraps one call. Call to run the /// rest of the pipeline, more than once to retry, or not at all to return without reaching the @@ -80,10 +87,12 @@ public interface IAsyncInterceptor { /// An interceptor here enumerates the stream, and so observes each item as it is produced. /// /// -/// public async IAsyncEnumerable<TItem> InterceptStream<TItem>(StreamInvocationContext<TItem> context) { +/// public async IAsyncEnumerable<TItem> InterceptStream<TItem>(StreamInvocationContext<TItem> context) +/// { /// var count = 0; /// -/// await foreach (var item in context.Proceed()) { +/// await foreach (var item in context.Proceed()) +/// { /// count++; /// yield return item; /// } @@ -92,7 +101,8 @@ public interface IAsyncInterceptor { /// } /// /// -public interface IAsyncEnumerableInterceptor { +public interface IAsyncEnumerableInterceptor +{ /// /// Wraps one call. Enumerate to yield the /// implementation's items, or yield something else to replace them. diff --git a/src/DependencyModules.Runtime/Interception/InvocationContext.cs b/src/DependencyModules.Runtime/Interception/InvocationContext.cs index 59af76d..574a261 100644 --- a/src/DependencyModules.Runtime/Interception/InvocationContext.cs +++ b/src/DependencyModules.Runtime/Interception/InvocationContext.cs @@ -13,7 +13,8 @@ namespace DependencyModules.Runtime.Interception; /// /// The member's return type, or when it returns void. /// -public readonly struct InvocationContext { +public readonly struct InvocationContext +{ private readonly InvocationState _state; private readonly int _stage; @@ -22,7 +23,8 @@ public readonly struct InvocationContext { /// /// The state for this call. /// The position of the interceptor receiving this context. - public InvocationContext(InvocationState state, int stage) { + public InvocationContext(InvocationState state, int stage) + { _state = state; _stage = stage; } @@ -54,7 +56,8 @@ public InvocationContext(InvocationState state, int stage) { /// /// The type the task produces, or for a task with no result. /// -public readonly struct AsyncInvocationContext { +public readonly struct AsyncInvocationContext +{ private readonly AsyncInvocationState _state; private readonly int _stage; @@ -63,7 +66,8 @@ public readonly struct AsyncInvocationContext { /// /// The state for this call. /// The position of the interceptor receiving this context. - public AsyncInvocationContext(AsyncInvocationState state, int stage) { + public AsyncInvocationContext(AsyncInvocationState state, int stage) + { _state = state; _stage = stage; } @@ -93,7 +97,8 @@ public AsyncInvocationContext(AsyncInvocationState state, int stage) { /// observes each item as it is produced rather than only the call that produced the stream. /// /// The type the stream yields. -public readonly struct StreamInvocationContext { +public readonly struct StreamInvocationContext +{ private readonly StreamInvocationState _state; private readonly int _stage; @@ -102,7 +107,8 @@ public readonly struct StreamInvocationContext { /// /// The state for this call. /// The position of the interceptor receiving this context. - public StreamInvocationContext(StreamInvocationState state, int stage) { + public StreamInvocationContext(StreamInvocationState state, int stage) + { _state = state; _stage = stage; } diff --git a/src/DependencyModules.Runtime/Interception/InvocationState.cs b/src/DependencyModules.Runtime/Interception/InvocationState.cs index 34d1748..0fac998 100644 --- a/src/DependencyModules.Runtime/Interception/InvocationState.cs +++ b/src/DependencyModules.Runtime/Interception/InvocationState.cs @@ -12,7 +12,8 @@ namespace DependencyModules.Runtime.Interception; /// is a struct pointing at one of these plus a stage index, /// so proceeding is a virtual call rather than a closure allocation and a Func per interceptor. /// -public abstract class InvocationState : IArguments { +public abstract class InvocationState : IArguments +{ /// /// The member being invoked. /// @@ -34,7 +35,8 @@ public abstract class InvocationState : IArguments { /// /// The member's return type, or when it returns void. /// -public abstract class InvocationState : InvocationState { +public abstract class InvocationState : InvocationState +{ /// /// Runs the pipeline from onwards. /// @@ -51,7 +53,8 @@ public abstract class InvocationState : InvocationState { /// /// The type the task produces, or for a task with no result. /// -public abstract class AsyncInvocationState : InvocationState { +public abstract class AsyncInvocationState : InvocationState +{ /// /// Runs the pipeline from onwards. /// @@ -66,7 +69,8 @@ public abstract class AsyncInvocationState : InvocationState { /// Invocation state for a member returning an async stream. /// /// The type the stream yields. -public abstract class StreamInvocationState : InvocationState { +public abstract class StreamInvocationState : InvocationState +{ /// /// Runs the pipeline from onwards. /// diff --git a/src/DependencyModules.Runtime/Interception/NoResult.cs b/src/DependencyModules.Runtime/Interception/NoResult.cs index ac95a40..9f897ee 100644 --- a/src/DependencyModules.Runtime/Interception/NoResult.cs +++ b/src/DependencyModules.Runtime/Interception/NoResult.cs @@ -9,7 +9,8 @@ namespace DependencyModules.Runtime.Interception; /// as everything else, rather than each interceptor having to implement an overload it does not care /// about. An interceptor written generically never names this type. /// -public readonly struct NoResult { +public readonly struct NoResult +{ /// /// Renders as void, so an interceptor logging a result does not print a type name. /// diff --git a/src/DependencyModules.Runtime/Interfaces/IDependencyModule.cs b/src/DependencyModules.Runtime/Interfaces/IDependencyModule.cs index 1cdbff2..02f843e 100644 --- a/src/DependencyModules.Runtime/Interfaces/IDependencyModule.cs +++ b/src/DependencyModules.Runtime/Interfaces/IDependencyModule.cs @@ -8,12 +8,13 @@ namespace DependencyModules.Runtime.Interfaces; /// /// Internal interface not intended to be consumed by developers /// -public interface IDependencyModule { +public interface IDependencyModule +{ /// /// Flag to disable loading module and dependencies. /// bool LoadModule => true; - + /// /// Populate a service collection with registrations /// @@ -24,16 +25,18 @@ public interface IDependencyModule { /// Intended for developers to override and provide their own IDependencyModules /// /// - IEnumerable GetModules() { + IEnumerable GetModules() + { return Array.Empty(); } - + /// /// Internal method not intended to be called by general developers /// /// [Browsable(false)] - IEnumerable InternalGetModules() { + IEnumerable InternalGetModules() + { // Array.Empty() rather than an array of the interface, so the runtime's empty check // is a plain ICollection test rather than one relying on array covariance. return Array.Empty(); @@ -57,11 +60,11 @@ void InternalApplyServices(IServiceCollection serviceCollection) { } /// /// Never null; see ModuleEnvironment.Default. [Browsable(false)] - void InternalApplyServices(IServiceCollection serviceCollection, IModuleEnvironment environment) { + void InternalApplyServices(IServiceCollection serviceCollection, IModuleEnvironment environment) + { InternalApplyServices(serviceCollection); } - /// /// Internal method not intended to be called by general developers /// @@ -78,7 +81,8 @@ void InternalApplyDecorators(IServiceCollection serviceCollection) { } /// order the developer declared. /// [Browsable(false)] - IEnumerable InternalGetDecorators() { + IEnumerable InternalGetDecorators() + { return Array.Empty(); } -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Interfaces/IDependencyModuleProvider.cs b/src/DependencyModules.Runtime/Interfaces/IDependencyModuleProvider.cs index 61f6ba7..99ca435 100644 --- a/src/DependencyModules.Runtime/Interfaces/IDependencyModuleProvider.cs +++ b/src/DependencyModules.Runtime/Interfaces/IDependencyModuleProvider.cs @@ -3,7 +3,8 @@ namespace DependencyModules.Runtime.Interfaces; /// /// Internal interface not intended to be consumed by developers /// -public interface IDependencyModuleProvider { +public interface IDependencyModuleProvider +{ /// /// Retrieves an instance of a dependency module. /// @@ -11,4 +12,4 @@ public interface IDependencyModuleProvider { /// An instance of the IDependencyModule. /// IDependencyModule GetModule(); -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/Interfaces/IModuleEnvironment.cs b/src/DependencyModules.Runtime/Interfaces/IModuleEnvironment.cs index 62b5014..2535d4c 100644 --- a/src/DependencyModules.Runtime/Interfaces/IModuleEnvironment.cs +++ b/src/DependencyModules.Runtime/Interfaces/IModuleEnvironment.cs @@ -3,7 +3,8 @@ namespace DependencyModules.Runtime.Interfaces; /// /// Minimal environment interface for conditional service registration. /// -public interface IModuleEnvironment { +public interface IModuleEnvironment +{ /// /// The name of the current environment (e.g. "Development", "Production"). /// diff --git a/src/DependencyModules.Runtime/Interfaces/IModuleEnvironmentProvider.cs b/src/DependencyModules.Runtime/Interfaces/IModuleEnvironmentProvider.cs index a6ac21a..f5b79ca 100644 --- a/src/DependencyModules.Runtime/Interfaces/IModuleEnvironmentProvider.cs +++ b/src/DependencyModules.Runtime/Interfaces/IModuleEnvironmentProvider.cs @@ -27,8 +27,8 @@ namespace DependencyModules.Runtime.Interfaces; /// narrowest one that answers decides, matching how every other attribute resolves. /// /// -public interface IModuleEnvironmentProvider { - +public interface IModuleEnvironmentProvider +{ /// /// The environment module conditions are evaluated against, or null to leave the decision /// to a wider scope. diff --git a/src/DependencyModules.Runtime/Interfaces/IServiceCollectionConfiguration.cs b/src/DependencyModules.Runtime/Interfaces/IServiceCollectionConfiguration.cs index e8083d0..f29f2cd 100644 --- a/src/DependencyModules.Runtime/Interfaces/IServiceCollectionConfiguration.cs +++ b/src/DependencyModules.Runtime/Interfaces/IServiceCollectionConfiguration.cs @@ -5,7 +5,8 @@ namespace DependencyModules.Runtime.Interfaces; /// /// DependencyModules that want to do programmatic registration should implement this interface. /// -public interface IServiceCollectionConfiguration { +public interface IServiceCollectionConfiguration +{ /// /// Configure service in IServiceCollection /// @@ -22,7 +23,8 @@ void ConfigureDecorators(IServiceCollection services) { } /// /// DependencyModules that need access to the environment during registration should implement this interface. /// -public interface IEnvironmentServiceCollectionConfiguration { +public interface IEnvironmentServiceCollectionConfiguration +{ /// /// Configure services with access to the module environment. /// @@ -36,4 +38,4 @@ public interface IEnvironmentServiceCollectionConfiguration { /// /// The environment for this AddModules call. Never null. void ConfigureServices(IServiceCollection services, IModuleEnvironment environment); -} \ No newline at end of file +} diff --git a/src/DependencyModules.Runtime/ModuleEnvironment.cs b/src/DependencyModules.Runtime/ModuleEnvironment.cs index 234dd6e..b74e371 100644 --- a/src/DependencyModules.Runtime/ModuleEnvironment.cs +++ b/src/DependencyModules.Runtime/ModuleEnvironment.cs @@ -15,14 +15,16 @@ namespace DependencyModules.Runtime; /// Values can be supplied inline, since this is a collection of them: /// /// services.AddModules( -/// new ModuleEnvironment("Development") { +/// new ModuleEnvironment("Development") +/// { /// { "FEATURE_PROFILING", "on" }, /// { "REGION", "eu" } /// }, /// new ApplicationModule()); /// /// -public class ModuleEnvironment : IModuleEnvironment, IEnumerable> { +public class ModuleEnvironment : IModuleEnvironment, IEnumerable> +{ private readonly Dictionary _values; private readonly bool _fallBackToEnvironmentVariables; @@ -40,7 +42,10 @@ public class ModuleEnvironment : IModuleEnvironment, IEnumerable /// The environment name conditions compare against. /// Values reachable through ; null means none. - public ModuleEnvironment(string environmentName, IReadOnlyDictionary? values = null) + public ModuleEnvironment( + string environmentName, + IReadOnlyDictionary? values = null + ) : this(true, environmentName, values) { } /// @@ -63,19 +68,25 @@ public ModuleEnvironment(string environmentName, IReadOnlyDictionary? values = null) { - EnvironmentName = environmentName ?? throw new ArgumentNullException(nameof(environmentName)); + IReadOnlyDictionary? values = null + ) + { + EnvironmentName = + environmentName ?? throw new ArgumentNullException(nameof(environmentName)); _fallBackToEnvironmentVariables = fallBackToEnvironmentVariables; // Copied rather than held by reference. Add writes to this dictionary, and writing into one // the caller still holds would be a side effect they did not ask for. A caller who supplied // a comparer picked it deliberately — most often OrdinalIgnoreCase, matching how Windows // treats variable names — so it is carried over instead of being reset to ordinal. - _values = values switch { - Dictionary dictionary => - new Dictionary(dictionary, dictionary.Comparer), + _values = values switch + { + Dictionary dictionary => new Dictionary( + dictionary, + dictionary.Comparer + ), not null => new Dictionary(values), - null => new Dictionary() + null => new Dictionary(), }; } @@ -95,12 +106,15 @@ public ModuleEnvironment( /// no longer seeing a variable changed mid-process, which nothing should be relying on; ask /// for a fresh view if you need one. /// - public string? Value(string name) { - if (_values.TryGetValue(name, out var value)) { + public string? Value(string name) + { + if (_values.TryGetValue(name, out var value)) + { return value; } - if (!_fallBackToEnvironmentVariables) { + if (!_fallBackToEnvironmentVariables) + { return null; } @@ -184,27 +198,37 @@ public ModuleEnvironment( /// collections and allocating its lock and bucket arrays to serve a cache most applications /// never touch. /// - private static class ProcessValueCache { - public static string? Read(ref Dictionary? cache, string name) { + private static class ProcessValueCache + { + public static string? Read(ref Dictionary? cache, string name) + { var map = Volatile.Read(ref cache); - if (map != null) { - lock (map) { - if (map.TryGetValue(name, out var cached)) { + if (map != null) + { + lock (map) + { + if (map.TryGetValue(name, out var cached)) + { return cached; } } } - else { + else + { // Whoever gets there first owns the cache; a loser simply fills the winner's. - map = Interlocked.CompareExchange( - ref cache, new Dictionary(StringComparer.Ordinal), null) - ?? Volatile.Read(ref cache)!; + map = + Interlocked.CompareExchange( + ref cache, + new Dictionary(StringComparer.Ordinal), + null + ) ?? Volatile.Read(ref cache)!; } var value = Environment.GetEnvironmentVariable(name); - lock (map) { + lock (map) + { map[name] = value; } @@ -212,20 +236,22 @@ private static class ProcessValueCache { } } - private sealed class ProcessModuleEnvironment : IModuleEnvironment { + private sealed class ProcessModuleEnvironment : IModuleEnvironment + { private Dictionary? _values; // Not cached. It is read once per AddModules call rather than per service, and a fresh // instance is what CreateDefault hands out anyway. public string EnvironmentName => - Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? - Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? - "Production"; + Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") + ?? Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") + ?? "Production"; public string? Value(string name) => ProcessValueCache.Read(ref _values, name); } - private sealed class EmptyModuleEnvironment : IModuleEnvironment { + private sealed class EmptyModuleEnvironment : IModuleEnvironment + { public string EnvironmentName => ""; public string? Value(string name) => null; diff --git a/src/DependencyModules.Runtime/ServiceCollectionExtensions.cs b/src/DependencyModules.Runtime/ServiceCollectionExtensions.cs index c975bb6..2ef56b6 100644 --- a/src/DependencyModules.Runtime/ServiceCollectionExtensions.cs +++ b/src/DependencyModules.Runtime/ServiceCollectionExtensions.cs @@ -8,7 +8,8 @@ namespace DependencyModules.Runtime; /// /// Provides extension methods for adding dependency modules to the IServiceCollection. /// -public static class ServiceCollectionExtensions { +public static class ServiceCollectionExtensions +{ /// /// Add dependency module to service collection /// @@ -16,7 +17,8 @@ public static class ServiceCollectionExtensions { /// /// public static IServiceCollection AddModule(this IServiceCollection services) - where T : IDependencyModule, new() { + where T : IDependencyModule, new() + { return AddModule(services, new T()); } @@ -27,19 +29,27 @@ public static IServiceCollection AddModule(this IServiceCollection services) /// /// // ReSharper disable once MemberCanBePrivate.Global - public static IServiceCollection AddModule(this IServiceCollection services, IDependencyModule module) { + public static IServiceCollection AddModule( + this IServiceCollection services, + IDependencyModule module + ) + { module.PopulateServiceCollection(services); return services; } - + /// /// Add dependency modules to service collection /// /// /// /// - public static IServiceCollection AddModules(this IServiceCollection services, params IDependencyModule[] modules) { + public static IServiceCollection AddModules( + this IServiceCollection services, + params IDependencyModule[] modules + ) + { DependencyRegistry.LoadModules(services, modules); return services; @@ -60,8 +70,14 @@ public static IServiceCollection AddModules(this IServiceCollection services, pa /// written down. To layer one on another, read the existing one and combine before calling this; /// the collection is right there. /// - public static IServiceCollection AddModules(this IServiceCollection services, IModuleEnvironment? environment, params IDependencyModule[] modules) { - if (environment != null) { + public static IServiceCollection AddModules( + this IServiceCollection services, + IModuleEnvironment? environment, + params IDependencyModule[] modules + ) + { + if (environment != null) + { services.RemoveAll(); services.AddSingleton(environment); } @@ -70,4 +86,4 @@ public static IServiceCollection AddModules(this IServiceCollection services, IM return services; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/BaseAttributeSourceGenerator.cs b/src/DependencyModules.SourceGenerator.Impl/BaseAttributeSourceGenerator.cs index 485ac56..8432aaa 100644 --- a/src/DependencyModules.SourceGenerator.Impl/BaseAttributeSourceGenerator.cs +++ b/src/DependencyModules.SourceGenerator.Impl/BaseAttributeSourceGenerator.cs @@ -7,25 +7,39 @@ namespace DependencyModules.SourceGenerator.Impl; -public interface IDependencyModuleSourceGenerator { - void SetupGenerator(IncrementalGeneratorInitializationContext context, - IncrementalValuesProvider<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> incrementalValueProvider); +public interface IDependencyModuleSourceGenerator +{ + void SetupGenerator( + IncrementalGeneratorInitializationContext context, + IncrementalValuesProvider<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> incrementalValueProvider + ); } -public abstract class BaseAttributeSourceGenerator : IDependencyModuleSourceGenerator { - - public void SetupGenerator(IncrementalGeneratorInitializationContext context, - IncrementalValuesProvider<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> incrementalValueProvider) { - +public abstract class BaseAttributeSourceGenerator : IDependencyModuleSourceGenerator +{ + public void SetupGenerator( + IncrementalGeneratorInitializationContext context, + IncrementalValuesProvider<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> incrementalValueProvider + ) + { var attributeTypes = AttributeTypes().ToArray(); - if (attributeTypes.Length == 0) { + if (attributeTypes.Length == 0) + { return; } // One provider, two outputs. Sharing it means the transform runs once; registering // CollectModels twice would do the same discovery work twice over. - var models = incrementalValueProvider.Collect().Combine(CollectModels(context, attributeTypes)); + var models = incrementalValueProvider + .Collect() + .Combine(CollectModels(context, attributeTypes)); context.RegisterSourceOutput(models, WrapGenerateSourceOutput); @@ -35,22 +49,42 @@ public void SetupGenerator(IncrementalGeneratorInitializationContext context, // into the emitting output instead would re-emit every file on every keystroke, since the // compilation changes with each one; this output emits nothing, so re-running it is a walk // over models that are already cached. - context.RegisterSourceOutput(models.Combine(context.CompilationProvider), WrapReportDiagnostics); + context.RegisterSourceOutput( + models.Combine(context.CompilationProvider), + WrapReportDiagnostics + ); } /// /// One provider per attribute, merged. See . /// private IncrementalValueProvider> CollectModels( - IncrementalGeneratorInitializationContext context, ITypeDefinition[] attributeTypes) => + IncrementalGeneratorInitializationContext context, + ITypeDefinition[] attributeTypes + ) => AttributeModelCollector.Collect( - context, attributeTypes, GenerateAttributeModel, GetComparer(), IgnoredModel); - - private void WrapGenerateSourceOutput(SourceProductionContext context, - (ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Left, ImmutableArray Right) data) { + context, + attributeTypes, + GenerateAttributeModel, + GetComparer(), + IgnoredModel + ); + + private void WrapGenerateSourceOutput( + SourceProductionContext context, + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Left, + ImmutableArray Right + ) data + ) + { var config = data.Left.FirstOrDefault().Right; - if (config != null) { + if (config != null) + { FileLogger.Wrap( LoggerName, config, @@ -58,29 +92,50 @@ private void WrapGenerateSourceOutput(SourceProductionContext context, // Surfaced as a build error rather than discarded. A generator that fails quietly // produces a green build with no registrations, which is far harder to diagnose // than a failed one. - exception => context.ReportDiagnostic( - Diagnostic.Create( - DependencyModuleDiagnostics.GeneratorFailure, - Location.None, - $"{exception.GetType().Name}: {exception.Message}"))); + exception => + context.ReportDiagnostic( + Diagnostic.Create( + DependencyModuleDiagnostics.GeneratorFailure, + Location.None, + $"{exception.GetType().Name}: {exception.Message}" + ) + ) + ); } } - private void WrapReportDiagnostics(SourceProductionContext context, - ((ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Left, ImmutableArray Right) Left, - Compilation Right) data) { + private void WrapReportDiagnostics( + SourceProductionContext context, + ( + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Left, + ImmutableArray Right + ) Left, + Compilation Right + ) data + ) + { var config = data.Left.Left.FirstOrDefault().Right; - if (config != null) { + if (config != null) + { FileLogger.Wrap( LoggerName, config, - logger => ReportDiagnostics(context, data.Left, new SyntaxTreeLookup(data.Right), logger), - exception => context.ReportDiagnostic( - Diagnostic.Create( - DependencyModuleDiagnostics.GeneratorFailure, - Location.None, - $"{exception.GetType().Name}: {exception.Message}"))); + logger => + ReportDiagnostics(context, data.Left, new SyntaxTreeLookup(data.Right), logger), + exception => + context.ReportDiagnostic( + Diagnostic.Create( + DependencyModuleDiagnostics.GeneratorFailure, + Location.None, + $"{exception.GetType().Name}: {exception.Message}" + ) + ) + ); } } @@ -92,26 +147,45 @@ private void WrapReportDiagnostics(SourceProductionContext context, /// compilation and the emission does not have to. A generator with nothing to report leaves it /// alone; the conditions belong beside the ones emission uses to skip a model, not duplicated. /// - protected virtual void ReportDiagnostics(SourceProductionContext context, - (ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Left, ImmutableArray Right) data, + protected virtual void ReportDiagnostics( + SourceProductionContext context, + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Left, + ImmutableArray Right + ) data, SyntaxTreeLookup lookup, - FileLogger logger) { } + FileLogger logger + ) { } protected virtual string LoggerName => GetType().Name; protected abstract IEnumerable AttributeTypes(); - protected abstract void GenerateSourceOutput(SourceProductionContext arg1, - (ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Left, ImmutableArray Right) valueTuple, - FileLogger logger); + protected abstract void GenerateSourceOutput( + SourceProductionContext arg1, + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Left, + ImmutableArray Right + ) valueTuple, + FileLogger logger + ); protected abstract IEqualityComparer GetComparer(); - protected abstract T GenerateAttributeModel(GeneratorAttributeSyntaxContext arg1, CancellationToken arg2); + protected abstract T GenerateAttributeModel( + GeneratorAttributeSyntaxContext arg1, + CancellationToken arg2 + ); /// /// The sentinel this generator emits for a declaration it does not own. Every model type already /// has one, and the writers already skip it. /// protected abstract T IgnoredModel { get; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/BaseAttributeWithConfigSourceGenerator.cs b/src/DependencyModules.SourceGenerator.Impl/BaseAttributeWithConfigSourceGenerator.cs index 0c03959..d1d63bd 100644 --- a/src/DependencyModules.SourceGenerator.Impl/BaseAttributeWithConfigSourceGenerator.cs +++ b/src/DependencyModules.SourceGenerator.Impl/BaseAttributeWithConfigSourceGenerator.cs @@ -8,41 +8,60 @@ namespace DependencyModules.SourceGenerator.Impl; -public abstract class BaseAttributeWithConfigSourceGenerator : IDependencyModuleSourceGenerator { - - public void SetupGenerator(IncrementalGeneratorInitializationContext context, - IncrementalValuesProvider<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> incrementalValueProvider) { - var classSelector = new SyntaxSelector(AttributeTypes().ToArray()); +public abstract class BaseAttributeWithConfigSourceGenerator + : IDependencyModuleSourceGenerator +{ + public void SetupGenerator( + IncrementalGeneratorInitializationContext context, + IncrementalValuesProvider<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> incrementalValueProvider + ) + { + var classSelector = new SyntaxSelector( + AttributeTypes().ToArray() + ); - var serviceModelProvider = context.SyntaxProvider.CreateSyntaxProvider( - classSelector.Where, - GenerateAttributeModel - ).WithComparer(GetComparer()); + var serviceModelProvider = context + .SyntaxProvider.CreateSyntaxProvider(classSelector.Where, GenerateAttributeModel) + .WithComparer(GetComparer()); - var collection = - serviceModelProvider.Collect(); + var collection = serviceModelProvider.Collect(); - var config = - context.AnalyzerConfigOptionsProvider.Select(GenerateConfigAttributeModel).WithComparer(GetConfigComparer()); + var config = context + .AnalyzerConfigOptionsProvider.Select(GenerateConfigAttributeModel) + .WithComparer(GetConfigComparer()); var valuesProvider = incrementalValueProvider.Combine(config); - context.RegisterSourceOutput( - valuesProvider.Combine(collection), - GenerateSourceOutput - ); + context.RegisterSourceOutput(valuesProvider.Combine(collection), GenerateSourceOutput); } protected abstract IEnumerable AttributeTypes(); - protected abstract void GenerateSourceOutput(SourceProductionContext arg1, - (((ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right) Left, TConfig Right) Left, ImmutableArray Right) arg2); + protected abstract void GenerateSourceOutput( + SourceProductionContext arg1, + ( + ( + (ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right) Left, + TConfig Right + ) Left, + ImmutableArray Right + ) arg2 + ); - protected abstract TConfig GenerateConfigAttributeModel(AnalyzerConfigOptionsProvider arg1, CancellationToken arg2); + protected abstract TConfig GenerateConfigAttributeModel( + AnalyzerConfigOptionsProvider arg1, + CancellationToken arg2 + ); protected abstract IEqualityComparer GetComparer(); - protected abstract TModel GenerateAttributeModel(GeneratorSyntaxContext arg1, CancellationToken arg2); + protected abstract TModel GenerateAttributeModel( + GeneratorSyntaxContext arg1, + CancellationToken arg2 + ); protected abstract IEqualityComparer GetConfigComparer(); -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/BaseSourceGenerator.cs b/src/DependencyModules.SourceGenerator.Impl/BaseSourceGenerator.cs index 6cbc849..4cd3e72 100644 --- a/src/DependencyModules.SourceGenerator.Impl/BaseSourceGenerator.cs +++ b/src/DependencyModules.SourceGenerator.Impl/BaseSourceGenerator.cs @@ -3,21 +3,23 @@ using DependencyModules.SourceGenerator.Impl.Models; using DependencyModules.SourceGenerator.Impl.Utilities; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; namespace DependencyModules.SourceGenerator.Impl; -public abstract class BaseSourceGenerator : IIncrementalGenerator { - - public void Initialize(IncrementalGeneratorInitializationContext context) { +public abstract class BaseSourceGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { var incrementalValueProvider = CreateSourceValueProvider(context); var dependencyConfigurationProvider = CreateConfigurationValueProvider(context); var valuesProvider = incrementalValueProvider.Combine(dependencyConfigurationProvider); - foreach (var attributeSourceGenerator in AttributeSourceGenerators()) { + foreach (var attributeSourceGenerator in AttributeSourceGenerators()) + { attributeSourceGenerator.SetupGenerator(context, valuesProvider); } @@ -30,72 +32,138 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { /// Returns the attribute types that trigger module source generation. /// Override to support custom trigger attributes (e.g. for framework-specific module attributes). /// - protected virtual ITypeDefinition[] ModuleAttributeTypes() { + protected virtual ITypeDefinition[] ModuleAttributeTypes() + { return new[] { KnownTypes.DependencyModules.Attributes.DependencyModuleAttribute }; } - private IncrementalValueProvider CreateConfigurationValueProvider(IncrementalGeneratorInitializationContext context) { - return context.AnalyzerConfigOptionsProvider.Select((options, _) => { - RegistrationType defaultRegistrationType = RegistrationType.Add; - bool registerSourceGenerator = false; - bool autoGenerateEntry = true; - bool generateFactories = false; - var rootNamespace = ""; - var projectDirectory = ""; - var logOutputFolder = ""; - - if (options.GlobalOptions.TryGetValue( - "build_property.DependencyModules_RegistrationType", out var value)) { - defaultRegistrationType = GetRegistrationType(value); - } - - if (options.GlobalOptions.TryGetValue( - "build_property.DependencyModules_LogOutputDirectory", out var logOutputFolderValue)) { - logOutputFolder = logOutputFolderValue; - } - - if (TryGetBoolean(options, "DependencyModules_RegisterGenerator", out var generator)) { - registerSourceGenerator = generator; - } + private IncrementalValueProvider CreateConfigurationValueProvider( + IncrementalGeneratorInitializationContext context + ) + { + return context + .AnalyzerConfigOptionsProvider.Select( + (options, _) => + { + RegistrationType defaultRegistrationType = RegistrationType.Add; + bool registerSourceGenerator = false; + bool autoGenerateEntry = true; + bool generateFactories = false; + var rootNamespace = ""; + var projectDirectory = ""; + var logOutputFolder = ""; + + if ( + options.GlobalOptions.TryGetValue( + "build_property.DependencyModules_RegistrationType", + out var value + ) + ) + { + defaultRegistrationType = GetRegistrationType(value); + } - if (options.GlobalOptions.TryGetValue("build_property.RootNamespace", out var rootNamespaceString)) { - rootNamespace = rootNamespaceString; - } + if ( + options.GlobalOptions.TryGetValue( + "build_property.DependencyModules_LogOutputDirectory", + out var logOutputFolderValue + ) + ) + { + logOutputFolder = logOutputFolderValue; + } - if (options.GlobalOptions.TryGetValue("build_property.ProjectDir", out var projectDirString)) { - projectDirectory = projectDirString; - } - - if (TryGetBoolean(options, "DependencyModules_AutoGenerateModule", out var autoGenerateEntryValue)) { - autoGenerateEntry = autoGenerateEntryValue; - } - - if (TryGetBoolean(options, "DependencyModules_GenerateFactories", out var generateFactoriesValue)) { - generateFactories = generateFactoriesValue; - } + if ( + TryGetBoolean( + options, + "DependencyModules_RegisterGenerator", + out var generator + ) + ) + { + registerSourceGenerator = generator; + } - var excludeGeneratedCodeFromCoverage = true; - if (TryGetBoolean(options, "ExcludeGeneratedCodeFromCoverage", out var excludeCoverageValue)) { - excludeGeneratedCodeFromCoverage = excludeCoverageValue; - } + if ( + options.GlobalOptions.TryGetValue( + "build_property.RootNamespace", + out var rootNamespaceString + ) + ) + { + rootNamespace = rootNamespaceString; + } - var codeStyle = BraceStyle.Allman; - if (options.GlobalOptions.TryGetValue("build_property.GeneratedCodeStyle", out var codeStyleValue)) { - codeStyle = GetCodeStyle(codeStyleValue); - } + if ( + options.GlobalOptions.TryGetValue( + "build_property.ProjectDir", + out var projectDirString + ) + ) + { + projectDirectory = projectDirString; + } + + if ( + TryGetBoolean( + options, + "DependencyModules_AutoGenerateModule", + out var autoGenerateEntryValue + ) + ) + { + autoGenerateEntry = autoGenerateEntryValue; + } - return new DependencyModuleConfigurationModel( - defaultRegistrationType, - registerSourceGenerator, - rootNamespace, - projectDirectory, - autoGenerateEntry, - logOutputFolder, - LogOutputLevel.Debug, - generateFactories, - excludeGeneratedCodeFromCoverage, - codeStyle); - }).WithComparer(new DependencyModuleConfigurationModelComparer()); + if ( + TryGetBoolean( + options, + "DependencyModules_GenerateFactories", + out var generateFactoriesValue + ) + ) + { + generateFactories = generateFactoriesValue; + } + + var excludeGeneratedCodeFromCoverage = true; + if ( + TryGetBoolean( + options, + "ExcludeGeneratedCodeFromCoverage", + out var excludeCoverageValue + ) + ) + { + excludeGeneratedCodeFromCoverage = excludeCoverageValue; + } + + var codeStyle = BraceStyle.Allman; + if ( + options.GlobalOptions.TryGetValue( + "build_property.GeneratedCodeStyle", + out var codeStyleValue + ) + ) + { + codeStyle = GetCodeStyle(codeStyleValue); + } + + return new DependencyModuleConfigurationModel( + defaultRegistrationType, + registerSourceGenerator, + rootNamespace, + projectDirectory, + autoGenerateEntry, + logOutputFolder, + LogOutputLevel.Debug, + generateFactories, + excludeGeneratedCodeFromCoverage, + codeStyle + ); + } + ) + .WithComparer(new DependencyModuleConfigurationModelComparer()); } /// @@ -106,11 +174,19 @@ private IncrementalValueProvider CreateConfi /// the developer has not set it. Without this check every boolean default would be overwritten /// with false the moment the property was made visible. /// - private static bool TryGetBoolean(AnalyzerConfigOptionsProvider options, string propertyName, out bool value) { + private static bool TryGetBoolean( + AnalyzerConfigOptionsProvider options, + string propertyName, + out bool value + ) + { value = false; - if (!options.GlobalOptions.TryGetValue("build_property." + propertyName, out var raw) || - string.IsNullOrWhiteSpace(raw)) { + if ( + !options.GlobalOptions.TryGetValue("build_property." + propertyName, out var raw) + || string.IsNullOrWhiteSpace(raw) + ) + { return false; } @@ -142,10 +218,15 @@ private static bool TryGetBoolean(AnalyzerConfigOptionsProvider options, string /// contributing only providers — overrides this with an empty body. /// /// - protected virtual void SetupRootGenerator(IncrementalGeneratorInitializationContext context, - IncrementalValueProvider> valuesProvider) { - - if (TriggersOnDefaultModuleAttribute()) { + protected virtual void SetupRootGenerator( + IncrementalGeneratorInitializationContext context, + IncrementalValueProvider< + ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> + > valuesProvider + ) + { + if (TriggersOnDefaultModuleAttribute()) + { return; } @@ -171,79 +252,111 @@ protected virtual void SetupRootGenerator(IncrementalGeneratorInitializationCont /// Whether this generator reads the module attribute this package declares, rather than one of /// its own. It decides who writes a module, and who is contributing to someone else's. /// - private bool TriggersOnDefaultModuleAttribute() { + private bool TriggersOnDefaultModuleAttribute() + { var moduleAttributes = ModuleAttributeTypes(); - return moduleAttributes.Length == 1 && - moduleAttributes[0].Equals(KnownTypes.DependencyModules.Attributes.DependencyModuleAttribute); + return moduleAttributes.Length == 1 + && moduleAttributes[0] + .Equals(KnownTypes.DependencyModules.Attributes.DependencyModuleAttribute); } - private IncrementalValuesProvider CreateSourceValueProvider(IncrementalGeneratorInitializationContext context) { - var classSelector = new SyntaxSelector( - ModuleAttributeTypes()) { + private IncrementalValuesProvider CreateSourceValueProvider( + IncrementalGeneratorInitializationContext context + ) + { + var classSelector = new SyntaxSelector< + ClassDeclarationSyntax, + RecordDeclarationSyntax, + CompilationUnitSyntax + >(ModuleAttributeTypes()) + { AutoApproveCompilationUnit = ShouldAutoApproveCompilationUnit, ApproveFilter = "Program.cs", }; - return context.SyntaxProvider.CreateSyntaxProvider( - classSelector.Where, - GenerateEntryPointModel - ).WithComparer(new ModuleEntryPointModelComparer()); + return context + .SyntaxProvider.CreateSyntaxProvider(classSelector.Where, GenerateEntryPointModel) + .WithComparer(new ModuleEntryPointModelComparer()); } - protected virtual ModuleEntryPointModel GenerateEntryPointModel(GeneratorSyntaxContext context, CancellationToken cancellation) { + protected virtual ModuleEntryPointModel GenerateEntryPointModel( + GeneratorSyntaxContext context, + CancellationToken cancellation + ) + { cancellation.ThrowIfCancellationRequested(); - if (context.Node is TypeDeclarationSyntax typeDeclarationSyntax) { + if (context.Node is TypeDeclarationSyntax typeDeclarationSyntax) + { return GetClassEntryPointModel(context, cancellation, typeDeclarationSyntax); } return GetCompilationUnitSyntaxEntry(context, cancellation); } - private ModuleEntryPointModel GetClassEntryPointModel(GeneratorSyntaxContext context, CancellationToken cancellation, TypeDeclarationSyntax typeDeclarationSyntax) { + private ModuleEntryPointModel GetClassEntryPointModel( + GeneratorSyntaxContext context, + CancellationToken cancellation, + TypeDeclarationSyntax typeDeclarationSyntax + ) + { var featureTypes = new List(); ModuleEntryPointFeatures features = ModuleEntryPointFeatures.None; List? attributes = AttributeModelHelper .GetAttributes(context, typeDeclarationSyntax.AttributeLists, cancellation) .ToList(); - if (typeDeclarationSyntax.BaseList != null) { - foreach (var baseType in typeDeclarationSyntax.BaseList.Types) { + if (typeDeclarationSyntax.BaseList != null) + { + foreach (var baseType in typeDeclarationSyntax.BaseList.Types) + { var typeDefinition = baseType.Type.GetTypeDefinition(context); - if (typeDefinition is GenericTypeDefinition { TypeDefinitionEnum: TypeDefinitionEnum.InterfaceDefinition, Name: "IDependencyModuleFeature" } genericTypeDefinition) { + if ( + typeDefinition is GenericTypeDefinition + { + TypeDefinitionEnum: TypeDefinitionEnum.InterfaceDefinition, + Name: "IDependencyModuleFeature" + } genericTypeDefinition + ) + { featureTypes.Add(genericTypeDefinition.TypeArguments.First()); } } } - + var dependencyFlags = GetDependencyFlags(context); var implementsEqualsFlag = GetEqualsFlag(context); var modelInfo = AttributeModelHelper.GetAttributeClassInfo(context, cancellation); - if (dependencyFlags.OnlyRealm) { + if (dependencyFlags.OnlyRealm) + { features |= ModuleEntryPointFeatures.OnlyRealm; } - if (typeDeclarationSyntax is RecordDeclarationSyntax) { + if (typeDeclarationSyntax is RecordDeclarationSyntax) + { features |= ModuleEntryPointFeatures.IsRecord; } - else if (!implementsEqualsFlag) { + else if (!implementsEqualsFlag) + { features |= ModuleEntryPointFeatures.ShouldImplementEquals; } - if (!typeDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) { + if (!typeDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) + { features |= ModuleEntryPointFeatures.NotPartial; } // A module nested inside another type cannot be completed where it was written: the // generated half is emitted at namespace level, so it becomes a second, unrelated type and // the nested declaration never implements IDependencyModule. - if (typeDeclarationSyntax.Parent is TypeDeclarationSyntax) { + if (typeDeclarationSyntax.Parent is TypeDeclarationSyntax) + { features |= ModuleEntryPointFeatures.NestedInType; } - + return new ModuleEntryPointModel( features, context.Node.SyntaxTree?.FilePath ?? "", @@ -262,31 +375,52 @@ private ModuleEntryPointModel GetClassEntryPointModel(GeneratorSyntaxContext con ); } - private ModuleEntryPointModel GetCompilationUnitSyntaxEntry(GeneratorSyntaxContext context, CancellationToken cancellation) { + private ModuleEntryPointModel GetCompilationUnitSyntaxEntry( + GeneratorSyntaxContext context, + CancellationToken cancellation + ) + { var compilationUnitSyntax = (CompilationUnitSyntax)context.Node; var attributes = AttributeModelHelper .GetAttributes(context, compilationUnitSyntax.AttributeLists, cancellation) .ToList(); var additionalModules = new List(); - - foreach (var syntax in compilationUnitSyntax.Members) { - if (syntax is GlobalStatementSyntax { Statement: ExpressionStatementSyntax { Expression: InvocationExpressionSyntax invocationExpressionSyntax } expressionStatementSyntax }) { - if (context.SemanticModel.GetSymbolInfo(expressionStatementSyntax.Expression).Symbol - is IMethodSymbol { IsStatic: true } methodSymbol) { - + foreach (var syntax in compilationUnitSyntax.Members) + { + if ( + syntax is GlobalStatementSyntax + { + Statement: ExpressionStatementSyntax + { + Expression: InvocationExpressionSyntax invocationExpressionSyntax + } expressionStatementSyntax + } + ) + { + if ( + context.SemanticModel.GetSymbolInfo(expressionStatementSyntax.Expression).Symbol + is IMethodSymbol { IsStatic: true } methodSymbol + ) + { var typeSymbol = methodSymbol.ContainingSymbol as ITypeSymbol; var declaringType = methodSymbol.ContainingType; - var moduleInterface = typeSymbol?.AllInterfaces.Any(x => x.GetTypeDefinition().Equals(KnownTypes.DependencyModules.Interfaces.IDependencyModule)); - - if (moduleInterface.GetValueOrDefault(false) && - declaringType.Constructors.Any(c => c.Parameters.Length == 0)) { + var moduleInterface = typeSymbol?.AllInterfaces.Any(x => + x.GetTypeDefinition() + .Equals(KnownTypes.DependencyModules.Interfaces.IDependencyModule) + ); + + if ( + moduleInterface.GetValueOrDefault(false) + && declaringType.Constructors.Any(c => c.Parameters.Length == 0) + ) + { additionalModules.Add(declaringType.GetTypeDefinition()); } } } } - + return new ModuleEntryPointModel( ModuleEntryPointFeatures.AutoGenerateModule, context.Node.SyntaxTree?.FilePath ?? "", @@ -305,67 +439,92 @@ private ModuleEntryPointModel GetCompilationUnitSyntaxEntry(GeneratorSyntaxConte ); } - private bool GetEqualsFlag(GeneratorSyntaxContext context) { - return context.Node.DescendantNodes().OfType().Any(m => m.Identifier.ToString().Equals("Equals")); + private bool GetEqualsFlag(GeneratorSyntaxContext context) + { + return context + .Node.DescendantNodes() + .OfType() + .Any(m => m.Identifier.ToString().Equals("Equals")); } - private record DependencyFlags - (bool OnlyRealm, RegistrationType? RegistrationType, bool? GenerateAttribute, bool? GenerateFactories, bool? RegisterGenerator, string? UseMethod); - - private DependencyFlags - GetDependencyFlags(GeneratorSyntaxContext context) { + private record DependencyFlags( + bool OnlyRealm, + RegistrationType? RegistrationType, + bool? GenerateAttribute, + bool? GenerateFactories, + bool? RegisterGenerator, + string? UseMethod + ); + + private DependencyFlags GetDependencyFlags(GeneratorSyntaxContext context) + { var onlyRealm = false; RegistrationType? registrationType = null; bool? generateAttribute = null; bool? registerGenerator = null; bool? generateFactories = null; string? useMethod = null; - if (context.Node is TypeDeclarationSyntax typeDeclarationSyntax) { - var module = typeDeclarationSyntax.DescendantNodes().OfType().FirstOrDefault(attr => attr.Name.ToString().StartsWith("DependencyModule")); - - if (module is { ArgumentList: not null }) { - foreach (var argumentSyntax in module.ArgumentList.Arguments) { + if (context.Node is TypeDeclarationSyntax typeDeclarationSyntax) + { + var module = typeDeclarationSyntax + .DescendantNodes() + .OfType() + .FirstOrDefault(attr => attr.Name.ToString().StartsWith("DependencyModule")); + + if (module is { ArgumentList: not null }) + { + foreach (var argumentSyntax in module.ArgumentList.Arguments) + { var name = argumentSyntax.NameEquals?.Name.ToString(); - switch (name) { + switch (name) + { case "OnlyRealm": onlyRealm = argumentSyntax.Expression.ToString() == "true"; break; case "Using": - registrationType = GetRegistrationType(argumentSyntax.Expression.ToString()); + registrationType = GetRegistrationType( + argumentSyntax.Expression.ToString() + ); break; case "GenerateAttribute": - generateAttribute = argumentSyntax.Expression.ToString().Trim('"') == "true"; + generateAttribute = + argumentSyntax.Expression.ToString().Trim('"') == "true"; break; case "RegisterJsonSerializers": - registerGenerator = argumentSyntax.Expression.ToString().Trim('"') == "true"; + registerGenerator = + argumentSyntax.Expression.ToString().Trim('"') == "true"; break; case "GenerateUseMethod": useMethod = argumentSyntax.Expression.ToString().Trim('"'); break; case "GenerateFactories": - generateFactories = argumentSyntax.Expression.ToString().Trim('"') == "true"; + generateFactories = + argumentSyntax.Expression.ToString().Trim('"') == "true"; break; } } } } - + return new DependencyFlags( onlyRealm, - registrationType, + registrationType, generateAttribute, generateFactories, - registerGenerator, - useMethod); + registerGenerator, + useMethod + ); } - + /// /// Parses the GeneratedCodeStyle build property. The name carries no framework prefix on /// purpose: it is shared with other source generators, so one csproj line styles all of them. /// - public static BraceStyle GetCodeStyle(string value) { - switch (value.Trim().ToLowerInvariant()) { + public static BraceStyle GetCodeStyle(string value) + { + switch (value.Trim().ToLowerInvariant()) + { case "kandr": case "k&r": return BraceStyle.KAndR; @@ -374,24 +533,27 @@ public static BraceStyle GetCodeStyle(string value) { } } - public static RegistrationType GetRegistrationType(string toString) { + public static RegistrationType GetRegistrationType(string toString) + { var typeString = toString.Replace("RegistrationType.", ""); - if (string.IsNullOrEmpty(typeString)) { + if (string.IsNullOrEmpty(typeString)) + { return RegistrationType.Add; } - - switch (typeString) { + + switch (typeString) + { case "Add": return RegistrationType.Add; case "Try": return RegistrationType.Try; case "TryEnumerable": - return RegistrationType.TryEnumerable; + return RegistrationType.TryEnumerable; case "Replace": return RegistrationType.Replace; default: return RegistrationType.Add; } } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/DecoratorFileWriter.cs b/src/DependencyModules.SourceGenerator.Impl/DecoratorFileWriter.cs index 65a4143..6bbc533 100644 --- a/src/DependencyModules.SourceGenerator.Impl/DecoratorFileWriter.cs +++ b/src/DependencyModules.SourceGenerator.Impl/DecoratorFileWriter.cs @@ -13,8 +13,8 @@ namespace DependencyModules.SourceGenerator.Impl; /// The bodies are a single call into DecoratorHelper; the rewrite itself is deliberately not /// generated. /// -public class DecoratorFileWriter { - +public class DecoratorFileWriter +{ /// /// Distinguishes the methods and fields this file declares from those another file declares on /// the same partial class. The attribute path and the convention path each emit decorations for @@ -24,8 +24,9 @@ public string Write( ModuleEntryPointModel entryPointModel, DependencyModuleConfigurationModel configurationModel, IReadOnlyList decorators, - string uniqueId = "") { - + string uniqueId = "" + ) + { var csharpFile = new CSharpFileDefinition(entryPointModel.EntryPointType.Namespace); var classDefinition = csharpFile.AddClass(entryPointModel.EntryPointType.Name); @@ -40,61 +41,93 @@ public string Write( // Anything that cannot be constructed by generated code has already been reported and // dropped. There is no reflective shape left to fall back to, so reaching the writer means // the decoration can be emitted. - for (var i = 0; i < decorators.Count; i++) { - WriteDecorator(entryPointModel, classDefinition, decorators[i], i, configurationModel, uniqueId); + for (var i = 0; i < decorators.Count; i++) + { + WriteDecorator( + entryPointModel, + classDefinition, + decorators[i], + i, + configurationModel, + uniqueId + ); } - var outputContext = new OutputContext(new OutputContextOptions { - TypeOutputMode = TypeOutputMode.Global, - BraceStyle = configurationModel.GeneratedCodeStyle - }); + var outputContext = new OutputContext( + new OutputContextOptions + { + TypeOutputMode = TypeOutputMode.Global, + BraceStyle = configurationModel.GeneratedCodeStyle, + } + ); csharpFile.WriteOutput(outputContext); return EntryModelUtil.ApplyRecordDeclaration(outputContext.Output(), entryPointModel); } - private static void WriteDecorator( ModuleEntryPointModel entryPointModel, ClassDefinition classDefinition, DecoratorModel decorator, int index, DependencyModuleConfigurationModel configurationModel, - string uniqueId) { - + string uniqueId + ) + { var methodName = $"Apply{uniqueId}Decorator{index}"; var method = classDefinition.AddMethod(methodName); method.Modifiers |= ComponentModifier.Private | ComponentModifier.Static; - if (configurationModel.ExcludeGeneratedCodeFromCoverage) { - method.AddAttribute(TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "ExcludeFromCodeCoverage")); + if (configurationModel.ExcludeGeneratedCodeFromCoverage) + { + method.AddAttribute( + TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "ExcludeFromCodeCoverage") + ); } var services = method.AddParameter( - KnownTypes.Microsoft.DependencyInjection.IServiceCollection, "services"); + KnownTypes.Microsoft.DependencyInjection.IServiceCollection, + "services" + ); // The parameter only appears when something tests it, so an unconditional decorator keeps // the RegistryFunc shape and the AddDecorator overload it always used. var hasConditions = decorator.Conditions is { Count: > 0 }; var environment = hasConditions - ? method.AddParameter(KnownTypes.DependencyModules.Interfaces.IModuleEnvironment, "environment") + ? method.AddParameter( + KnownTypes.DependencyModules.Interfaces.IModuleEnvironment, + "environment" + ) : null; - var decorate = Decoration(decorator, services.Name, decorator.ServiceType, decorator.DecoratorType); + var decorate = Decoration( + decorator, + services.Name, + decorator.ServiceType, + decorator.DecoratorType + ); - if (environment != null) { + if (environment != null) + { // Guarding the call rather than the registration: a decorator that does not apply is // simply not run, so the service resolves undecorated instead of being wrapped by // something that re-tests the environment on every call. var block = method.If( CodeOutputComponent.Get( - EnvironmentConditionWriter.BuildCondition(decorator.Conditions!, environment.Name))); + EnvironmentConditionWriter.BuildCondition( + decorator.Conditions!, + environment.Name + ) + ) + ); block.AddIndentedStatement(decorate); - } else { + } + else + { method.AddIndentedStatement(decorate); } @@ -124,30 +157,36 @@ private static IOutputComponent Decoration( DecoratorModel decorator, string servicesName, ITypeDefinition serviceType, - ITypeDefinition decoratorType) { - + ITypeDefinition decoratorType + ) + { // Shared with the service writer rather than reimplemented. Resolving every parameter with // GetRequiredService looked right and quietly ignored what the parameter declared: a // [FromKeyedServices] dependency resolved the unkeyed registration, and a nullable one threw // instead of resolving to null. var arguments = ConstructorArgumentWriter.Arguments( new ParameterDefinition( - KnownTypes.Microsoft.DependencyInjection.IServiceProvider, ProviderParameterName), + KnownTypes.Microsoft.DependencyInjection.IServiceProvider, + ProviderParameterName + ), decorator.Constructor!.Parameters, decorator.InnerParameterIndex, - CodeOutputComponent.Get(InnerParameterName)); + CodeOutputComponent.Get(InnerParameterName) + ); var construct = New(decoratorType, arguments); var lambda = new WrapStatement( CodeOutputComponent.Get(" => "), CodeOutputComponent.Get($"({ProviderParameterName}, {InnerParameterName})"), - construct); + construct + ); // The four-argument overload when an implementation is named, which is the one interception // already uses: it skips a descriptor whose origin is a different type, so the decorator // reaches one registration rather than every registration of the service. - if (decorator.Implementation != null) { + if (decorator.Implementation != null) + { return SyntaxHelpers.InvokeGeneric( KnownTypes.DependencyModules.Helpers.DecoratorHelper, "Decorate", @@ -155,7 +194,8 @@ private static IOutputComponent Decoration( CodeOutputComponent.Get(servicesName), TypeOf(decoratorType), lambda, - TypeOf(decorator.Implementation)); + TypeOf(decorator.Implementation) + ); } return SyntaxHelpers.InvokeGeneric( @@ -164,7 +204,8 @@ private static IOutputComponent Decoration( new[] { serviceType }, CodeOutputComponent.Get(servicesName), TypeOf(decoratorType), - lambda); + lambda + ); } private static string ToCamel(string value) => @@ -180,30 +221,39 @@ private static void WriteRegistration( DecoratorModel decorator, int index, string methodName, - string uniqueId) { - + string uniqueId + ) + { // A field initializer registers the method, matching how service registrations are hooked up. // DynamicDependency keeps the trimmer from removing a method only referenced this way. - var field = classDefinition.AddField(typeof(int), $"{ToCamel(uniqueId)}decoratorField{index}"); + var field = classDefinition.AddField( + typeof(int), + $"{ToCamel(uniqueId)}decoratorField{index}" + ); field.Modifiers |= ComponentModifier.Private | ComponentModifier.Static; field.AddAttribute( TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "DynamicDependency"), - $"nameof({methodName})"); + $"nameof({methodName})" + ); var registryType = new GenericTypeDefinition( TypeDefinitionEnum.ClassDefinition, KnownTypes.DependencyModules.Helpers.Namespace, "DependencyRegistry", - new[] { entryPointModel.EntryPointType }); + new[] { entryPointModel.EntryPointType } + ); field.InitializeValue = new StaticInvokeStatement( registryType, "AddDecorator", - new List { + new List + { CodeOutputComponent.Get(methodName), - CodeOutputComponent.Get(decorator.Order.ToString()) - }) { - Indented = false + CodeOutputComponent.Get(decorator.Order.ToString()), + } + ) + { + Indented = false, }; } } diff --git a/src/DependencyModules.SourceGenerator.Impl/DependencyFileWriter.cs b/src/DependencyModules.SourceGenerator.Impl/DependencyFileWriter.cs index a17d1a6..3d27b9b 100644 --- a/src/DependencyModules.SourceGenerator.Impl/DependencyFileWriter.cs +++ b/src/DependencyModules.SourceGenerator.Impl/DependencyFileWriter.cs @@ -8,7 +8,8 @@ namespace DependencyModules.SourceGenerator.Impl; -public class DependencyFileWriter { +public class DependencyFileWriter +{ private readonly FileLogger _logger; private readonly bool _coverageAttributeOnMethod; @@ -21,7 +22,8 @@ public class DependencyFileWriter { /// own the class-level attribute; every other file contributing to the same partial has to apply /// it per member, which is what DecoratorFileWriter already does. /// - public DependencyFileWriter(FileLogger logger, bool coverageAttributeOnMethod = false) { + public DependencyFileWriter(FileLogger logger, bool coverageAttributeOnMethod = false) + { _logger = logger; _coverageAttributeOnMethod = coverageAttributeOnMethod; } @@ -30,28 +32,40 @@ public string Write( ModuleEntryPointModel entryPointModel, DependencyModuleConfigurationModel configurationModel, IEnumerable serviceModels, - string uniqueId) { - - if (entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule) && - string.IsNullOrEmpty(entryPointModel.EntryPointType.Namespace)) { - entryPointModel = entryPointModel with { - EntryPointType = TypeDefinition.Get(configurationModel.RootNamespace, entryPointModel.EntryPointType.Name) + string uniqueId + ) + { + if ( + entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule) + && string.IsNullOrEmpty(entryPointModel.EntryPointType.Namespace) + ) + { + entryPointModel = entryPointModel with + { + EntryPointType = TypeDefinition.Get( + configurationModel.RootNamespace, + entryPointModel.EntryPointType.Name + ), }; } _interceptedServiceTypes = InterceptedServiceTypes(serviceModels); - _logger.Info($"Generating Dependencies for {entryPointModel.EntryPointType.Namespace}.{entryPointModel.EntryPointType.Namespace}"); + _logger.Info( + $"Generating Dependencies for {entryPointModel.EntryPointType.Namespace}.{entryPointModel.EntryPointType.Namespace}" + ); var csharpFile = new CSharpFileDefinition(entryPointModel.EntryPointType.Namespace); GenerateClass(entryPointModel, configurationModel, serviceModels, csharpFile, uniqueId); var output = new OutputContext( - new OutputContextOptions { + new OutputContextOptions + { TypeOutputMode = TypeOutputMode.Global, - BraceStyle = configurationModel.GeneratedCodeStyle - }); + BraceStyle = configurationModel.GeneratedCodeStyle, + } + ); csharpFile.WriteOutput(output); @@ -62,12 +76,14 @@ public string Write( return result; } - private void GenerateClass(ModuleEntryPointModel entryPointModel, + private void GenerateClass( + ModuleEntryPointModel entryPointModel, DependencyModuleConfigurationModel configurationModel, IEnumerable serviceModels, CSharpFileDefinition csharpFile, - string uniqueId) { - + string uniqueId + ) + { var classDefinition = csharpFile.AddClass(entryPointModel.EntryPointType.Name); // Asked for by name because qualification cannot replace it: the registrations call @@ -86,45 +102,68 @@ private void GenerateClass(ModuleEntryPointModel entryPointModel, // writers already do this; the registrations file is where the annotations actually land. classDefinition.EnableNullable(); - if (configurationModel.ExcludeGeneratedCodeFromCoverage && !_coverageAttributeOnMethod) { + if (configurationModel.ExcludeGeneratedCodeFromCoverage && !_coverageAttributeOnMethod) + { classDefinition.AddAttribute( - TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "ExcludeFromCodeCoverage")); + TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "ExcludeFromCodeCoverage") + ); } - var methodName = - GenerateDependencyMethod(entryPointModel, configurationModel, serviceModels, classDefinition, uniqueId); + var methodName = GenerateDependencyMethod( + entryPointModel, + configurationModel, + serviceModels, + classDefinition, + uniqueId + ); CreateInvokeStatement(entryPointModel, methodName, classDefinition, uniqueId); } - private void CreateInvokeStatement(ModuleEntryPointModel entryPointModel, string methodName, ClassDefinition classDefinition, string uniqueId) { + private void CreateInvokeStatement( + ModuleEntryPointModel entryPointModel, + string methodName, + ClassDefinition classDefinition, + string uniqueId + ) + { var lowerName = uniqueId.ToLower() + "Field"; var field = classDefinition.AddField(typeof(int), lowerName); field.Modifiers |= ComponentModifier.Private | ComponentModifier.Static; - field.AddAttribute(TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "DynamicDependency"), $"nameof({methodName})"); + field.AddAttribute( + TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "DynamicDependency"), + $"nameof({methodName})" + ); var closedType = new GenericTypeDefinition( - TypeDefinitionEnum.ClassDefinition, KnownTypes.DependencyModules.Helpers.Namespace, "DependencyRegistry", new[] { - entryPointModel.EntryPointType - }); - - var invokeStatement = new StaticInvokeStatement(closedType, "Add", new List { - CodeOutputComponent.Get(methodName) - }) { - Indented = false + TypeDefinitionEnum.ClassDefinition, + KnownTypes.DependencyModules.Helpers.Namespace, + "DependencyRegistry", + new[] { entryPointModel.EntryPointType } + ); + + var invokeStatement = new StaticInvokeStatement( + closedType, + "Add", + new List { CodeOutputComponent.Get(methodName) } + ) + { + Indented = false, }; field.InitializeValue = invokeStatement; } - private string GenerateDependencyMethod(ModuleEntryPointModel entryPointModel, + private string GenerateDependencyMethod( + ModuleEntryPointModel entryPointModel, DependencyModuleConfigurationModel configurationModel, IEnumerable serviceModels, ClassDefinition classDefinition, - string uniqueId) { - + string uniqueId + ) + { classDefinition.AddUsingNamespace("Microsoft.Extensions.DependencyInjection.Extensions"); var method = classDefinition.AddMethod(uniqueId + "Dependencies"); @@ -134,11 +173,16 @@ private string GenerateDependencyMethod(ModuleEntryPointModel entryPointModel, // The glue factories GenerateGlueFactory may add are not covered here. They exist only for // [Factory] registrations, which the attribute path produces and the convention path — the // only caller that sets this — cannot. - if (configurationModel.ExcludeGeneratedCodeFromCoverage && _coverageAttributeOnMethod) { + if (configurationModel.ExcludeGeneratedCodeFromCoverage && _coverageAttributeOnMethod) + { method.AddAttribute( - TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "ExcludeFromCodeCoverage")); + TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "ExcludeFromCodeCoverage") + ); } - var services = method.AddParameter(KnownTypes.Microsoft.DependencyInjection.IServiceCollection, "services"); + var services = method.AddParameter( + KnownTypes.Microsoft.DependencyInjection.IServiceCollection, + "services" + ); var stringBuilder = new StringBuilder(); @@ -150,51 +194,75 @@ private string GenerateDependencyMethod(ModuleEntryPointModel entryPointModel, // without conditions generates exactly the method it always has and its Add call still // binds to the RegistryFunc overload. var environment = sortedServiceModels.Any(model => model.Conditions is { Count: > 0 }) - ? method.AddParameter(KnownTypes.DependencyModules.Interfaces.IModuleEnvironment, "environment") + ? method.AddParameter( + KnownTypes.DependencyModules.Interfaces.IModuleEnvironment, + "environment" + ) : null; - foreach (var serviceModel in sortedServiceModels) { - if (serviceModel.Equals(ServiceModel.Ignore)) { + foreach (var serviceModel in sortedServiceModels) + { + if (serviceModel.Equals(ServiceModel.Ignore)) + { continue; } - if ((serviceModel.Features & RegistrationFeature.AutoRegisterSourceGenerator) == - RegistrationFeature.AutoRegisterSourceGenerator && !autoRegisterGenerators) { + if ( + (serviceModel.Features & RegistrationFeature.AutoRegisterSourceGenerator) + == RegistrationFeature.AutoRegisterSourceGenerator + && !autoRegisterGenerators + ) + { continue; } // One guard around everything the service registers. The attributes are declared on the // class, so every registration it produces shares them. - var block = environment != null && serviceModel.Conditions is { Count: > 0 } conditions - ? method.If(CodeOutputComponent.Get(BuildCondition(conditions, environment.Name))) - : (BaseBlockDefinition)method; + var block = + environment != null && serviceModel.Conditions is { Count: > 0 } conditions + ? method.If( + CodeOutputComponent.Get(BuildCondition(conditions, environment.Name)) + ) + : (BaseBlockDefinition)method; var crossWire = false; - foreach (var registrationModel in serviceModel.Registrations) { + foreach (var registrationModel in serviceModel.Registrations) + { // skip registrations not for this realm - if (registrationModel.Realm != null) { - if (!registrationModel.Realm.Equals(entryPointModel.EntryPointType)) { + if (registrationModel.Realm != null) + { + if (!registrationModel.Realm.Equals(entryPointModel.EntryPointType)) + { continue; } } else if ( - (entryPointModel.ModuleFeatures & ModuleEntryPointFeatures.OnlyRealm) == - ModuleEntryPointFeatures.OnlyRealm) { + (entryPointModel.ModuleFeatures & ModuleEntryPointFeatures.OnlyRealm) + == ModuleEntryPointFeatures.OnlyRealm + ) + { continue; } - if (registrationModel.Namespaces != null) { - foreach (var namespaceString in registrationModel.Namespaces) { + if (registrationModel.Namespaces != null) + { + foreach (var namespaceString in registrationModel.Namespaces) + { classDefinition.AddUsingNamespace(namespaceString); } } crossWire |= registrationModel.CrossWire.GetValueOrDefault(false); - var registrationType = GetRegistrationType(entryPointModel, configurationModel, registrationModel); + var registrationType = GetRegistrationType( + entryPointModel, + configurationModel, + registrationModel + ); - switch (registrationType) { + switch (registrationType) + { case RegistrationType.Add: case RegistrationType.Try: HandleTryAndAddRegistrationTypes( @@ -207,7 +275,8 @@ private string GenerateDependencyMethod(ModuleEntryPointModel entryPointModel, serviceModel, block, services, - uniqueId); + uniqueId + ); break; case RegistrationType.Replace: @@ -221,13 +290,15 @@ private string GenerateDependencyMethod(ModuleEntryPointModel entryPointModel, serviceModel, block, services, - uniqueId); + uniqueId + ); break; } } - if (crossWire) { + if (crossWire) + { CrossWireRegisterImplementation( configurationModel, entryPointModel, @@ -235,7 +306,8 @@ private string GenerateDependencyMethod(ModuleEntryPointModel entryPointModel, block, services, serviceModel, - uniqueId); + uniqueId + ); } } @@ -245,8 +317,9 @@ private string GenerateDependencyMethod(ModuleEntryPointModel entryPointModel, // Shared with DecoratorFileWriter through EnvironmentConditionWriter, so a service and a // decorator carrying the same attributes cannot end up testing them differently. private static string BuildCondition( - IReadOnlyList conditions, string environmentParameter) => - EnvironmentConditionWriter.BuildCondition(conditions, environmentParameter); + IReadOnlyList conditions, + string environmentParameter + ) => EnvironmentConditionWriter.BuildCondition(conditions, environmentParameter); private void CrossWireRegisterImplementation( DependencyModuleConfigurationModel configurationModel, @@ -255,12 +328,16 @@ private void CrossWireRegisterImplementation( BaseBlockDefinition block, ParameterDefinition services, ServiceModel serviceModel, - string uniqueId) { - var registrationModel = - serviceModel.Registrations.First(r => r.CrossWire.GetValueOrDefault(false)); + string uniqueId + ) + { + var registrationModel = serviceModel.Registrations.First(r => + r.CrossWire.GetValueOrDefault(false) + ); var invokeMethod = ""; - switch (registrationModel.RegistrationType.GetValueOrDefault(RegistrationType.Add)) { + switch (registrationModel.RegistrationType.GetValueOrDefault(RegistrationType.Add)) + { case RegistrationType.Add: invokeMethod = "Add"; break; @@ -275,31 +352,39 @@ private void CrossWireRegisterImplementation( break; } - var parameters = new List { - TypeOf(serviceModel.ImplementationType) - }; + var parameters = new List { TypeOf(serviceModel.ImplementationType) }; - if (registrationModel.Key != null) { + if (registrationModel.Key != null) + { parameters.Add(registrationModel.Key); } - if (serviceModel.Factory == null) { - if (serviceModel.FactoryOutput != null) { + if (serviceModel.Factory == null) + { + if (serviceModel.FactoryOutput != null) + { parameters.Add(serviceModel.FactoryOutput); } - else if (serviceModel is { Constructor: not null, ImplementationType: not GenericTypeDefinition } && - ShouldGenerateFactory(serviceModel, entryPointModel, configurationModel)) { + else if ( + serviceModel + is { Constructor: not null, ImplementationType: not GenericTypeDefinition } + && ShouldGenerateFactory(serviceModel, entryPointModel, configurationModel) + ) + { parameters.Add(GenerateNewFactory(serviceModel, registrationModel)); } - else { + else + { parameters.Add(TypeOf(serviceModel.ImplementationType)); } } - else { + else + { AddFactoryParameter(serviceModel, classDefinition, parameters, uniqueId); } - switch (registrationModel.Lifestyle) { + switch (registrationModel.Lifestyle) + { case ServiceLifestyle.Transient: parameters.Add(CodeOutputComponent.Get("ServiceLifetime.Transient")); break; @@ -313,16 +398,12 @@ private void CrossWireRegisterImplementation( throw new ArgumentOutOfRangeException(); } - var serviceDescriptor = - New( - KnownTypes.Microsoft.DependencyInjection.ServiceDescriptor, - parameters.ToArray()); + var serviceDescriptor = New( + KnownTypes.Microsoft.DependencyInjection.ServiceDescriptor, + parameters.ToArray() + ); - block.AddIndentedStatement( - services.Invoke( - invokeMethod, - serviceDescriptor - )); + block.AddIndentedStatement(services.Invoke(invokeMethod, serviceDescriptor)); } /// @@ -344,9 +425,12 @@ private void CrossWireRegisterImplementation( private bool ShouldGenerateFactory( ServiceModel serviceModel, ModuleEntryPointModel entryPointModel, - DependencyModuleConfigurationModel configurationModel) => - !IsInterceptedServiceType(serviceModel) && - entryPointModel.GenerateFactories.GetValueOrDefault(configurationModel.GenerateFactories); + DependencyModuleConfigurationModel configurationModel + ) => + !IsInterceptedServiceType(serviceModel) + && entryPointModel.GenerateFactories.GetValueOrDefault( + configurationModel.GenerateFactories + ); /// /// Whether any registration of this service is one interception has to be able to pick out. @@ -359,18 +443,25 @@ private bool ShouldGenerateFactory( /// from the registration being wrapped, and it came back wearing another class's wrapper. /// private bool IsInterceptedServiceType(ServiceModel serviceModel) => - serviceModel.Registrations.Any( - registration => _interceptedServiceTypes.Contains(registration.ServiceType)); - - private static HashSet InterceptedServiceTypes(IEnumerable serviceModels) { + serviceModel.Registrations.Any(registration => + _interceptedServiceTypes.Contains(registration.ServiceType) + ); + + private static HashSet InterceptedServiceTypes( + IEnumerable serviceModels + ) + { var types = new HashSet(); - foreach (var serviceModel in serviceModels) { - if (!serviceModel.Features.HasFlag(RegistrationFeature.Intercepted)) { + foreach (var serviceModel in serviceModels) + { + if (!serviceModel.Features.HasFlag(RegistrationFeature.Intercepted)) + { continue; } - foreach (var registration in serviceModel.Registrations) { + foreach (var registration in serviceModel.Registrations) + { types.Add(registration.ServiceType); } } @@ -380,59 +471,85 @@ private static HashSet InterceptedServiceTypes(IEnumerable _interceptedServiceTypes = new(); - private static object GenerateNewFactory(ServiceModel serviceModel, ServiceRegistrationModel registrationModel) { - var parameter = - new ParameterDefinition(KnownTypes.Microsoft.DependencyInjection.IServiceProvider, "provider"); - - var providerParameters = registrationModel.Key == null ? "provider => " : "(provider, _) => "; + private static object GenerateNewFactory( + ServiceModel serviceModel, + ServiceRegistrationModel registrationModel + ) + { + var parameter = new ParameterDefinition( + KnownTypes.Microsoft.DependencyInjection.IServiceProvider, + "provider" + ); + + var providerParameters = + registrationModel.Key == null ? "provider => " : "(provider, _) => "; var provider = CodeOutputComponent.Get(providerParameters); var newStatement = New( serviceModel.ImplementationType, - GetArgumentsForParameterList(parameter, serviceModel.Constructor!.Parameters)); + GetArgumentsForParameterList(parameter, serviceModel.Constructor!.Parameters) + ); return new WrapStatement(newStatement, provider, null); } - private void HandleTryEnumerableAndReplaceRegistrationType(DependencyModuleConfigurationModel configurationModel, ModuleEntryPointModel entryPointModel, ClassDefinition classDefinition, + private void HandleTryEnumerableAndReplaceRegistrationType( + DependencyModuleConfigurationModel configurationModel, + ModuleEntryPointModel entryPointModel, + ClassDefinition classDefinition, RegistrationType registrationType, ServiceRegistrationModel registrationModel, ServiceModel serviceModel, BaseBlockDefinition block, - ParameterDefinition services, string uniqueId) { + ParameterDefinition services, + string uniqueId + ) + { var invokeMethod = registrationType == RegistrationType.Replace ? "Replace" : "TryAddEnumerable"; - var parameters = new List { - TypeOf(registrationModel.ServiceType) - }; + var parameters = new List { TypeOf(registrationModel.ServiceType) }; - if (registrationModel.Key != null) { + if (registrationModel.Key != null) + { parameters.Add(registrationModel.Key); } - if (registrationModel.CrossWire == true) { + if (registrationModel.CrossWire == true) + { AddCrossWireParameter(serviceModel, registrationModel, parameters); } - else if (serviceModel.Factory == null) { - if (serviceModel.FactoryOutput != null) { - var factoryOutput = serviceModel.FactoryOutput?.Invoke(serviceModel, registrationModel); + else if (serviceModel.Factory == null) + { + if (serviceModel.FactoryOutput != null) + { + var factoryOutput = serviceModel.FactoryOutput?.Invoke( + serviceModel, + registrationModel + ); parameters.Add(factoryOutput ?? TypeOf(serviceModel.ImplementationType)); } - else if (serviceModel is { Constructor: not null, ImplementationType: not GenericTypeDefinition } && - ShouldGenerateFactory(serviceModel, entryPointModel, configurationModel)) { + else if ( + serviceModel + is { Constructor: not null, ImplementationType: not GenericTypeDefinition } + && ShouldGenerateFactory(serviceModel, entryPointModel, configurationModel) + ) + { parameters.Add(GenerateNewFactory(serviceModel, registrationModel)); } - else { + else + { parameters.Add(TypeOf(serviceModel.ImplementationType)); } } - else { + else + { AddFactoryParameter(serviceModel, classDefinition, parameters, uniqueId); } - switch (registrationModel.Lifestyle) { + switch (registrationModel.Lifestyle) + { case ServiceLifestyle.Transient: parameters.Add(CodeOutputComponent.Get("ServiceLifetime.Transient")); break; @@ -446,38 +563,43 @@ private void HandleTryEnumerableAndReplaceRegistrationType(DependencyModuleConfi throw new ArgumentOutOfRangeException(); } - var serviceDescriptor = - New( - KnownTypes.Microsoft.DependencyInjection.ServiceDescriptor, - parameters.ToArray()); + var serviceDescriptor = New( + KnownTypes.Microsoft.DependencyInjection.ServiceDescriptor, + parameters.ToArray() + ); - block.AddIndentedStatement( - services.Invoke( - invokeMethod, - serviceDescriptor - )); + block.AddIndentedStatement(services.Invoke(invokeMethod, serviceDescriptor)); } - private void HandleTryAndAddRegistrationTypes(DependencyModuleConfigurationModel configurationModel, ModuleEntryPointModel entryPointModel, ClassDefinition classDefinition, StringBuilder stringBuilder, + private void HandleTryAndAddRegistrationTypes( + DependencyModuleConfigurationModel configurationModel, + ModuleEntryPointModel entryPointModel, + ClassDefinition classDefinition, + StringBuilder stringBuilder, RegistrationType registrationType, ServiceRegistrationModel registrationModel, ServiceModel serviceModel, BaseBlockDefinition block, ParameterDefinition services, - string uniqueId) { + string uniqueId + ) + { stringBuilder.Length = 0; - if (registrationType == RegistrationType.Try) { + if (registrationType == RegistrationType.Try) + { stringBuilder.Append("Try"); } stringBuilder.Append("Add"); - if (registrationModel.Key != null) { + if (registrationModel.Key != null) + { stringBuilder.Append("Keyed"); } - switch (registrationModel.Lifestyle) { + switch (registrationModel.Lifestyle) + { case ServiceLifestyle.Transient: stringBuilder.Append("Transient"); break; @@ -493,68 +615,81 @@ private void HandleTryAndAddRegistrationTypes(DependencyModuleConfigurationModel parameters.Add(TypeOf(registrationModel.ServiceType)); - if (registrationModel.Key != null) { + if (registrationModel.Key != null) + { parameters.Add(registrationModel.Key); } - if (registrationModel.CrossWire == true) { - AddCrossWireParameter( - serviceModel, registrationModel, parameters); + if (registrationModel.CrossWire == true) + { + AddCrossWireParameter(serviceModel, registrationModel, parameters); } - else if (serviceModel.Factory == null) { - if (serviceModel.FactoryOutput != null) { - var factoryOutput = serviceModel.FactoryOutput?.Invoke(serviceModel, registrationModel); + else if (serviceModel.Factory == null) + { + if (serviceModel.FactoryOutput != null) + { + var factoryOutput = serviceModel.FactoryOutput?.Invoke( + serviceModel, + registrationModel + ); parameters.Add(factoryOutput ?? TypeOf(serviceModel.ImplementationType)); } - else if (serviceModel is { Constructor: not null, ImplementationType: not GenericTypeDefinition } && - ShouldGenerateFactory(serviceModel, entryPointModel, configurationModel)) { + else if ( + serviceModel + is { Constructor: not null, ImplementationType: not GenericTypeDefinition } + && ShouldGenerateFactory(serviceModel, entryPointModel, configurationModel) + ) + { parameters.Add(GenerateNewFactory(serviceModel, registrationModel)); } - else { + else + { parameters.Add(TypeOf(serviceModel.ImplementationType)); } } - else { + else + { AddFactoryParameter(serviceModel, classDefinition, parameters, uniqueId); } - block.AddIndentedStatement( - services.Invoke( - stringBuilder.ToString(), - parameters.ToArray() - )); + block.AddIndentedStatement(services.Invoke(stringBuilder.ToString(), parameters.ToArray())); } private static void AddCrossWireParameter( ServiceModel serviceModel, ServiceRegistrationModel registrationModel, - List parameters) { + List parameters + ) + { IOutputComponent invoke; - var serviceProvider = - new ParameterDefinition(KnownTypes.Microsoft.DependencyInjection.IServiceProvider, "s"); + var serviceProvider = new ParameterDefinition( + KnownTypes.Microsoft.DependencyInjection.IServiceProvider, + "s" + ); - if (registrationModel.Key != null) { + if (registrationModel.Key != null) + { var key = registrationModel.Key; - if (key is string stringValue) { + if (key is string stringValue) + { key = QuoteString(stringValue); } - invoke = - serviceProvider.InvokeGeneric( - "GetRequiredKeyedServices", - new[] { - serviceModel.ImplementationType - }, - key); + invoke = serviceProvider.InvokeGeneric( + "GetRequiredKeyedServices", + new[] { serviceModel.ImplementationType }, + key + ); } - else { - invoke = - serviceProvider.InvokeGeneric("GetRequiredService", new[] { - serviceModel.ImplementationType - }); + else + { + invoke = serviceProvider.InvokeGeneric( + "GetRequiredService", + new[] { serviceModel.ImplementationType } + ); } var wrapper = new WrapStatement(CodeOutputComponent.Get(" => "), serviceProvider, invoke); @@ -562,20 +697,39 @@ private static void AddCrossWireParameter( parameters.Add(wrapper); } - private static void AddFactoryParameter(ServiceModel serviceModel, ClassDefinition classDefinition, List parameters, string uniqueId) { + private static void AddFactoryParameter( + ServiceModel serviceModel, + ClassDefinition classDefinition, + List parameters, + string uniqueId + ) + { var factory = serviceModel.Factory; - if (factory == null) { + if (factory == null) + { return; } - if (factory.Parameters.Count == 1 && factory.Parameters.Any(m => - m.ParameterType.Equals(KnownTypes.Microsoft.DependencyInjection.IServiceProvider))) { - parameters.Add(CodeOutputComponent.Get( - factory.TypeDefinition.Namespace + "." + factory.TypeDefinition.Name + "." + factory.MethodName)); + if ( + factory.Parameters.Count == 1 + && factory.Parameters.Any(m => + m.ParameterType.Equals(KnownTypes.Microsoft.DependencyInjection.IServiceProvider) + ) + ) + { + parameters.Add( + CodeOutputComponent.Get( + factory.TypeDefinition.Namespace + + "." + + factory.TypeDefinition.Name + + "." + + factory.MethodName + ) + ); } - else { - var glueFactory = GenerateGlueFactory( - serviceModel, factory, classDefinition, uniqueId); + else + { + var glueFactory = GenerateGlueFactory(serviceModel, factory, classDefinition, uniqueId); parameters.Add(CodeOutputComponent.Get(glueFactory.Name)); } @@ -585,7 +739,9 @@ private static MethodDefinition GenerateGlueFactory( ServiceModel serviceModel, ServiceFactoryModel factory, ClassDefinition classDefinition, - string uniqueId) { + string uniqueId + ) + { var glueFactoryName = uniqueId + "GlueFactory" + classDefinition.Methods.Count; var method = classDefinition.AddMethod(glueFactoryName); @@ -593,7 +749,9 @@ private static MethodDefinition GenerateGlueFactory( method.SetReturnType(serviceModel.ImplementationType); var serviceProvider = method.AddParameter( - KnownTypes.Microsoft.DependencyInjection.IServiceProvider, "serviceProvider"); + KnownTypes.Microsoft.DependencyInjection.IServiceProvider, + "serviceProvider" + ); var parameterList = GetArgumentsForParameterList(serviceProvider, factory.Parameters); @@ -603,15 +761,23 @@ private static MethodDefinition GenerateGlueFactory( } private static object[] GetArgumentsForParameterList( - ParameterDefinition serviceProvider, IReadOnlyList parameterList) => - ConstructorArgumentWriter.Arguments(serviceProvider, parameterList); + ParameterDefinition serviceProvider, + IReadOnlyList parameterList + ) => ConstructorArgumentWriter.Arguments(serviceProvider, parameterList); - private static RegistrationType GetRegistrationType(ModuleEntryPointModel entryPointModel, DependencyModuleConfigurationModel configurationModel, ServiceRegistrationModel registrationModel) { - if (registrationModel.RegistrationType.HasValue) { + private static RegistrationType GetRegistrationType( + ModuleEntryPointModel entryPointModel, + DependencyModuleConfigurationModel configurationModel, + ServiceRegistrationModel registrationModel + ) + { + if (registrationModel.RegistrationType.HasValue) + { return registrationModel.RegistrationType.Value; } - if (entryPointModel.RegistrationType.HasValue) { + if (entryPointModel.RegistrationType.HasValue) + { return entryPointModel.RegistrationType.Value; } @@ -649,35 +815,46 @@ private static RegistrationType GetRegistrationType(ModuleEntryPointModel entryP /// /// private List GetSortedServiceModels( - IEnumerable serviceModels, DependencyModuleConfigurationModel configurationModel) { - + IEnumerable serviceModels, + DependencyModuleConfigurationModel configurationModel + ) + { var list = new List(serviceModels); - list.Sort((x, y) => { - var byCondition = IsConditional(x).CompareTo(IsConditional(y)); - - if (byCondition != 0) { - return byCondition; - } + list.Sort( + (x, y) => + { + var byCondition = IsConditional(x).CompareTo(IsConditional(y)); - var byStrategy = ActsOnExistingRegistration(x, configurationModel) - .CompareTo(ActsOnExistingRegistration(y, configurationModel)); + if (byCondition != 0) + { + return byCondition; + } - if (byStrategy != 0) { - return byStrategy; - } + var byStrategy = ActsOnExistingRegistration(x, configurationModel) + .CompareTo(ActsOnExistingRegistration(y, configurationModel)); - // Order last of the deciding keys, so naming one cannot move a registration ahead of a - // condition or ahead of the Replace it depends on - both of which are about whether a - // registration works at all, where Order is only about where it lands among its peers. - var byOrder = OrderOf(x).CompareTo(OrderOf(y)); + if (byStrategy != 0) + { + return byStrategy; + } - // Name is the tie-break rather than the only key, so the order stays total and the - // output stays deterministic under List.Sort, which is not stable. - return byOrder != 0 - ? byOrder - : string.Compare(x.ImplementationType.Name, y.ImplementationType.Name, StringComparison.Ordinal); - }); + // Order last of the deciding keys, so naming one cannot move a registration ahead of a + // condition or ahead of the Replace it depends on - both of which are about whether a + // registration works at all, where Order is only about where it lands among its peers. + var byOrder = OrderOf(x).CompareTo(OrderOf(y)); + + // Name is the tie-break rather than the only key, so the order stays total and the + // output stays deterministic under List.Sort, which is not stable. + return byOrder != 0 + ? byOrder + : string.Compare( + x.ImplementationType.Name, + y.ImplementationType.Name, + StringComparison.Ordinal + ); + } + ); return list; } @@ -690,12 +867,15 @@ private List GetSortedServiceModels( /// to name its own order. They are emitted together, so the model needs one number, and the /// lowest is the one that matches what naming an order means — "put this early". /// - private static int OrderOf(ServiceModel serviceModel) { + private static int OrderOf(ServiceModel serviceModel) + { var order = 0; var first = true; - foreach (var registration in serviceModel.Registrations) { - if (first || registration.Order < order) { + foreach (var registration in serviceModel.Registrations) + { + if (first || registration.Order < order) + { order = registration.Order; first = false; } @@ -716,18 +896,23 @@ private static bool IsConditional(ServiceModel serviceModel) => /// they arrive in, and deferring it would change nothing. /// private static bool ActsOnExistingRegistration( - ServiceModel serviceModel, DependencyModuleConfigurationModel configurationModel) { - - foreach (var registration in serviceModel.Registrations) { + ServiceModel serviceModel, + DependencyModuleConfigurationModel configurationModel + ) + { + foreach (var registration in serviceModel.Registrations) + { // Null means the registration took the project-wide default, which is what // DependencyModules_RegistrationType sets. - var registrationType = registration.RegistrationType ?? configurationModel.RegistrationType; + var registrationType = + registration.RegistrationType ?? configurationModel.RegistrationType; - if (registrationType is RegistrationType.Try or RegistrationType.Replace) { + if (registrationType is RegistrationType.Try or RegistrationType.Replace) + { return true; } } return false; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/DependencyModuleDiagnostics.cs b/src/DependencyModules.SourceGenerator.Impl/DependencyModuleDiagnostics.cs index b300087..38b4f4d 100644 --- a/src/DependencyModules.SourceGenerator.Impl/DependencyModuleDiagnostics.cs +++ b/src/DependencyModules.SourceGenerator.Impl/DependencyModuleDiagnostics.cs @@ -9,7 +9,8 @@ namespace DependencyModules.SourceGenerator.Impl; /// that succeeds and an application that misbehaves at run time. Everything here exists to move a /// failure from run time to build time, or at minimum to make it visible. /// -public static class DependencyModuleDiagnostics { +public static class DependencyModuleDiagnostics +{ private const string Category = "DependencyModules"; /// @@ -20,13 +21,13 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor GeneratorFailure = new( id: "DM0001", title: "DependencyModules generator failed", - messageFormat: - "The DependencyModules generator failed and registrations may be missing or incomplete: {0}. " + - "Set DependencyModules_LogOutputDirectory to capture a log, and report the issue at " + - "https://github.com/ipjohnson/DependencyModules/issues with that log attached.", + messageFormat: "The DependencyModules generator failed and registrations may be missing or incomplete: {0}. " + + "Set DependencyModules_LogOutputDirectory to capture a log, and report the issue at " + + "https://github.com/ipjohnson/DependencyModules/issues with that log attached.", category: Category, defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for a service the container could never construct. Without this the generator emits a @@ -35,12 +36,12 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor ServiceCannotBeConstructed = new( id: "DM0002", title: "Service type cannot be constructed", - messageFormat: - "'{0}' is {1} and cannot be instantiated, so it was not registered. " + - "Apply the service attribute to a concrete class, or register it with a static factory method.", + messageFormat: "'{0}' is {1} and cannot be instantiated, so it was not registered. " + + "Apply the service attribute to a concrete class, or register it with a static factory method.", category: Category, defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for a module that is not partial. The compiler also reports CS0260 once the generated @@ -49,12 +50,12 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor ModuleMustBePartial = new( id: "DM0003", title: "Dependency module must be partial", - messageFormat: - "'{0}' is marked with [DependencyModule] but is not declared partial. " + - "The generator completes the type with a second partial declaration, so add the partial modifier.", + messageFormat: "'{0}' is marked with [DependencyModule] but is not declared partial. " + + "The generator completes the type with a second partial declaration, so add the partial modifier.", category: Category, defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for a module declared inside another type. @@ -69,13 +70,13 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor ModuleCannotBeNested = new( id: "DM0017", title: "Dependency module cannot be nested inside another type", - messageFormat: - "'{0}' is marked with [DependencyModule] but is declared inside another type. " + - "The generator completes a module at namespace level, so this would produce a second, " + - "unrelated type and register nothing. Move it out to the namespace.", + messageFormat: "'{0}' is marked with [DependencyModule] but is declared inside another type. " + + "The generator completes a module at namespace level, so this would produce a second, " + + "unrelated type and register nothing. Move it out to the namespace.", category: Category, defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for an assembly-level module attribute in a file that is not the entry point. @@ -110,14 +111,14 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor DecoratorImplementationNeedsTypeRegistration = new( id: "DM0022", title: "Decorator names an implementation while factories are generated", - messageFormat: - "'{0}' decorates only '{1}', but DependencyModules_GenerateFactories is on for this project " + - "and a factory registration cannot say what implementation it built — so the decorator would " + - "wrap every registration of '{2}' instead of one. Turn the property off for this project, or " + - "drop Implementation and decorate them all.", + messageFormat: "'{0}' decorates only '{1}', but DependencyModules_GenerateFactories is on for this project " + + "and a factory registration cannot say what implementation it built — so the decorator would " + + "wrap every registration of '{2}' instead of one. Turn the property off for this project, or " + + "drop Implementation and decorate them all.", category: Category, defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for a [Mock] parameter and a [TestExport] on the same method, both @@ -136,14 +137,14 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor MockAndTestExportOnOneMethod = new( id: "DM0021", title: "[Mock] and [TestExport] name one service on the same method", - messageFormat: - "'{0}' carries [TestExport] for '{1}' and a [Mock] parameter naming the same service. The " + - "parameter wins, so the [TestExport] does nothing. Move the [TestExport] to the class or " + - "the assembly if it is the default this test is overriding, or drop whichever of the two " + - "was not meant.", + messageFormat: "'{0}' carries [TestExport] for '{1}' and a [Mock] parameter naming the same service. The " + + "parameter wins, so the [TestExport] does nothing. Move the [TestExport] to the class or " + + "the assembly if it is the default this test is overriding, or drop whichever of the two " + + "was not meant.", category: Category, defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for an interception no module applies, so it can never run. @@ -162,25 +163,25 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor InterceptionAppliedByNoModule = new( id: "DM0020", title: "Interception is applied by no module", - messageFormat: - "'{0}' is marked for interception, but no module in this compilation applies it, so the " + - "interceptors never run. This happens when the registration and the interception land in " + - "different realms — a realm-only module registering '{0}' by convention, for instance, while " + - "the interception names no realm. Name the module on [Intercept(Realm = typeof(...))].", + messageFormat: "'{0}' is marked for interception, but no module in this compilation applies it, so the " + + "interceptors never run. This happens when the registration and the interception land in " + + "different realms — a realm-only module registering '{0}' by convention, for instance, while " + + "the interception names no realm. Name the module on [Intercept(Realm = typeof(...))].", category: Category, defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); + isEnabledByDefault: true + ); public static readonly DiagnosticDescriptor AssemblyModuleAttributeNotComposed = new( id: "DM0019", title: "Assembly-level module attribute is not composed", - messageFormat: - "'{0}' is applied at the assembly level in this file, but the generated ApplicationModule is " + - "built from '{1}', so this composition is ignored and the module's services are not " + - "registered. Move the attribute to '{1}', or load the module explicitly with AddModule.", + messageFormat: "'{0}' is applied at the assembly level in this file, but the generated ApplicationModule is " + + "built from '{1}', so this composition is ignored and the module's services are not " + + "registered. Move the attribute to '{1}', or load the module explicitly with AddModule.", category: Category, defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for a module carrying settable properties while relying on the generated @@ -196,11 +197,10 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor ModuleWithPropertiesShouldImplementEquals = new( id: "DM0018", title: "Module with properties relies on generated equality", - messageFormat: - "'{0}' has settable properties but does not declare Equals, so the generated equality " + - "compares by type alone. Two instances carrying different values count as the same module " + - "and the first one reached wins. Declare Equals and GetHashCode on '{0}' to say which " + - "instances are the same.", + messageFormat: "'{0}' has settable properties but does not declare Equals, so the generated equality " + + "compares by type alone. Two instances carrying different values count as the same module " + + "and the first one reached wins. Declare Equals and GetHashCode on '{0}' to say which " + + "instances are the same.", category: Category, // A warning rather than informational. The identity of a module with parameters is genuinely // ambiguous, and the generator picks type-only on the developer's behalf — so the choice is @@ -212,7 +212,8 @@ public static class DependencyModuleDiagnostics { // Silencing still works per project, through NoWarn or .editorconfig, for a codebase whose // parameterised modules are each composed once. defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised when two conventions in one module register a type as the same service type. @@ -232,12 +233,12 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor AmbiguousConventionMatch = new( id: "DM0004", title: "Convention match is ambiguous", - messageFormat: - "'{0}' is matched by two conventions in '{1}' that both register it as '{2}'. {3} " + - "Narrow one of them, or move it to another module.", + messageFormat: "'{0}' is matched by two conventions in '{1}' that both register it as '{2}'. {3} " + + "Narrow one of them, or move it to another module.", category: Category, defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for a convention that matched nothing. A convention naming a service type no type in @@ -254,7 +255,8 @@ public static class DependencyModuleDiagnostics { messageFormat: "The convention registering '{0}' in '{1}' matched no types. {2}.", category: Category, defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for a convention match the container could not construct. The abstract and static @@ -264,12 +266,12 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor ConventionMatchNotConstructable = new( id: "DM0006", title: "Convention matched a type that cannot be constructed", - messageFormat: - "'{0}' matches the convention registering '{1}' in '{2}', but has no accessible constructor, " + - "so it was not registered", + messageFormat: "'{0}' matches the convention registering '{1}' in '{2}', but has no accessible constructor, " + + "so it was not registered", category: Category, defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised when two decorators of one service share an order. Applying them in an arbitrary order @@ -278,12 +280,12 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor AmbiguousDecoratorOrder = new( id: "DM0007", title: "Decorator order is ambiguous", - messageFormat: - "'{0}' and '{1}' both decorate '{2}' with order {3}, so the order they nest in is undefined. " + - "Give them distinct Order values.", + messageFormat: "'{0}' and '{1}' both decorate '{2}' with order {3}, so the order they nest in is undefined. " + + "Give them distinct Order values.", category: Category, defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised when a class marked for interception cannot be wrapped. Interception works through an @@ -299,13 +301,13 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor CannotIntercept = new( id: "DM0008", title: "Service cannot be intercepted", - messageFormat: - "This service cannot be intercepted, so no wrapper was generated and none of its members are " + - "intercepted: {0}. Other members may be unsupported for the same reason. Write a decorator " + - "instead, or move the member to an interface that is not intercepted.", + messageFormat: "This service cannot be intercepted, so no wrapper was generated and none of its members are " + + "intercepted: {0}. Other members may be unsupported for the same reason. Write a decorator " + + "instead, or move the member to an interface that is not intercepted.", category: Category, defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for something in a Conventions method body the generator could not read. @@ -321,7 +323,8 @@ public static class DependencyModuleDiagnostics { messageFormat: "This convention declaration could not be read, because {0}: {1}", category: Category, defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Reports, on the class itself, that a convention registered it. @@ -342,7 +345,8 @@ public static class DependencyModuleDiagnostics { messageFormat: "Exposed as {0}", category: Category, defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Reports, on the class itself, that its registration is conditional and on what. @@ -362,7 +366,8 @@ public static class DependencyModuleDiagnostics { messageFormat: "Registered only when {0}", category: Category, defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for a condition that names nothing to test. @@ -379,7 +384,8 @@ public static class DependencyModuleDiagnostics { messageFormat: "{0} names no {1} to test, so it does not depend on the environment", category: Category, defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for an interceptor that cannot serve some of the members it was applied to. @@ -401,12 +407,12 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor InterceptorCannotServeMembers = new( id: "DM0015", title: "Interceptor does not apply to every member", - messageFormat: - "'{0}' does not implement '{1}', so it is not applied to {2} on '{3}': {4}. Those members run " + - "without it. Implement '{1}' on the interceptor, or apply it to a service that has no such member.", + messageFormat: "'{0}' does not implement '{1}', so it is not applied to {2} on '{3}': {4}. Those members run " + + "without it. Implement '{1}' on the interceptor, or apply it to a service that has no such member.", category: Category, defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for a decorator whose service is registered as an open generic. @@ -431,14 +437,14 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor OpenGenericCannotBeDecorated = new( id: "DM0013", title: "Open generic registration cannot be decorated", - messageFormat: - "'{0}' is registered as an open generic, so '{1}' cannot decorate it. Decoration replaces a " + - "registration with a factory, and the container does not allow one for an open generic " + - "service type. Register closed constructions of '{0}' instead — a convention over the open " + - "generic registers one per implementation, and a generic decorator is then expanded across them.", + messageFormat: "'{0}' is registered as an open generic, so '{1}' cannot decorate it. Decoration replaces a " + + "registration with a factory, and the container does not allow one for an open generic " + + "service type. Register closed constructions of '{0}' instead — a convention over the open " + + "generic registers one per implementation, and a generic decorator is then expanded across them.", category: Category, defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for [CrossWireService] on a generic type. @@ -459,14 +465,14 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor CrossWireCannotBeGeneric = new( id: "DM0014", title: "Generic type cannot be cross-wired", - messageFormat: - "'{0}' is generic, so [CrossWireService] cannot register it. Cross-wiring shares one instance " + - "across every service type, which needs a factory, and the container does not allow one for an " + - "open generic registration. Use [SingletonService], [ScopedService] or [TransientService] to " + - "register it, applying one per interface if it needs to answer to more than one.", + messageFormat: "'{0}' is generic, so [CrossWireService] cannot register it. Cross-wiring shares one instance " + + "across every service type, which needs a factory, and the container does not allow one for an " + + "open generic registration. Use [SingletonService], [ScopedService] or [TransientService] to " + + "register it, applying one per interface if it needs to answer to more than one.", category: Category, defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); + isEnabledByDefault: true + ); /// /// Raised for an assembly-level module attribute whose namespace the file does not import. @@ -489,12 +495,11 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor ModuleAttributeNamespaceNotImported = new( id: "DM0016", title: "Assembly-level module attribute needs its namespace imported", - messageFormat: - "'{0}' is declared in '{1}', and an assembly-level attribute has no namespace context, so " + - "this does not compile. Add 'using {1};' to this file, or write it qualified as " + - "'[assembly: {1}.{0}]'.", + messageFormat: "'{0}' is declared in '{1}', and an assembly-level attribute has no namespace context, so " + + "this does not compile. Add 'using {1};' to this file, or write it qualified as " + + "'[assembly: {1}.{0}]'.", category: Category, defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); - + isEnabledByDefault: true + ); } diff --git a/src/DependencyModules.SourceGenerator.Impl/DependencyModuleWriter.cs b/src/DependencyModules.SourceGenerator.Impl/DependencyModuleWriter.cs index 6c4f56a..8cc7cad 100644 --- a/src/DependencyModules.SourceGenerator.Impl/DependencyModuleWriter.cs +++ b/src/DependencyModules.SourceGenerator.Impl/DependencyModuleWriter.cs @@ -8,11 +8,12 @@ namespace DependencyModules.SourceGenerator.Impl; - -public class DependencyModuleWriter { +public class DependencyModuleWriter +{ private readonly bool _generateAttribute; - public DependencyModuleWriter(bool generateAttribute) { + public DependencyModuleWriter(bool generateAttribute) + { _generateAttribute = generateAttribute; } @@ -28,34 +29,49 @@ public DependencyModuleWriter(bool generateAttribute) { /// public static void Register( IncrementalGeneratorInitializationContext context, - IncrementalValueProvider> valuesProvider, - bool generateAttribute) { - + IncrementalValueProvider< + ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> + > valuesProvider, + bool generateAttribute + ) + { context.RegisterSourceOutput( - valuesProvider, new DependencyModuleWriter(generateAttribute).GenerateSource); + valuesProvider, + new DependencyModuleWriter(generateAttribute).GenerateSource + ); context.RegisterSourceOutput( valuesProvider.Combine(context.CompilationProvider), - ModuleEntryPointDiagnostics.Report); + ModuleEntryPointDiagnostics.Report + ); } - public void GenerateSource(SourceProductionContext context, - ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> allEntryPoints) { - - if (allEntryPoints.Length == 0) { + public void GenerateSource( + SourceProductionContext context, + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> allEntryPoints + ) + { + if (allEntryPoints.Length == 0) + { return; } - - var (entryPointList, configurationModel) = - EntryModelUtil.ConsolidateEntryPointModels(allEntryPoints); - foreach (var entryPointModel in entryPointList) { + var (entryPointList, configurationModel) = EntryModelUtil.ConsolidateEntryPointModels( + allEntryPoints + ); + + foreach (var entryPointModel in entryPointList) + { context.CancellationToken.ThrowIfCancellationRequested(); ProcessEntryPoint( context, WithDelegateTarget(entryPointModel, entryPointList), - configurationModel); + configurationModel + ); } } @@ -69,52 +85,66 @@ public void GenerateSource(SourceProductionContext context, /// the emitted module changes, and a module with nothing to defer to is returned untouched. /// private static ModuleEntryPointModel WithDelegateTarget( - ModuleEntryPointModel entryPointModel, IList entryPointList) { - + ModuleEntryPointModel entryPointModel, + IList entryPointList + ) + { var target = EntryModelUtil.DelegateTargetFor(entryPointModel, entryPointList); - if (target == null) { + if (target == null) + { return entryPointModel; } var modules = new List(entryPointModel.AdditionalModules); - if (!modules.Contains(target)) { + if (!modules.Contains(target)) + { modules.Add(target); } - return entryPointModel with { AdditionalModules = modules }; + return entryPointModel with + { + AdditionalModules = modules, + }; } private void ProcessEntryPoint( - SourceProductionContext context, - ModuleEntryPointModel entryPointModel, - DependencyModuleConfigurationModel configurationModel) { - + SourceProductionContext context, + ModuleEntryPointModel entryPointModel, + DependencyModuleConfigurationModel configurationModel + ) + { // Both of these are reported by ModuleEntryPointDiagnostics, which owns the conditions. // Generating anyway is what makes them worth stopping for: a non-partial module produces // CS0260 against the developer's own declaration, and a nested one emits a same-named type // at namespace level that compiles and registers nothing. Either way the actionable message // would arrive buried under errors describing the symptom. - if (ModuleEntryPointDiagnostics.IsNotPartial(entryPointModel) || - ModuleEntryPointDiagnostics.IsNestedInType(entryPointModel)) { + if ( + ModuleEntryPointDiagnostics.IsNotPartial(entryPointModel) + || ModuleEntryPointDiagnostics.IsNestedInType(entryPointModel) + ) + { return; } - entryPointModel = EntryModelUtil.EnsureNamespace(entryPointModel,configurationModel); + entryPointModel = EntryModelUtil.EnsureNamespace(entryPointModel, configurationModel); var csharpFile = new CSharpFileDefinition(entryPointModel.EntryPointType.Namespace); GenerateModuleClass(entryPointModel, csharpFile); GenerateUseMethod(entryPointModel, configurationModel, csharpFile); - + GenerateAttribute(entryPointModel, csharpFile); - var outputContext = new OutputContext(new OutputContextOptions { - TypeOutputMode = TypeOutputMode.Global, - BraceStyle = configurationModel.GeneratedCodeStyle - }); + var outputContext = new OutputContext( + new OutputContextOptions + { + TypeOutputMode = TypeOutputMode.Global, + BraceStyle = configurationModel.GeneratedCodeStyle, + } + ); csharpFile.WriteOutput(outputContext); @@ -124,14 +154,21 @@ private void ProcessEntryPoint( context.AddSource( entryPointModel.EntryPointType.GetFileNameHint( - configurationModel.RootNamespace, "Module"), - source); + configurationModel.RootNamespace, + "Module" + ), + source + ); } - private void GenerateAttribute(ModuleEntryPointModel moduleEntryPoint, - CSharpFileDefinition csharpFile) { + private void GenerateAttribute( + ModuleEntryPointModel moduleEntryPoint, + CSharpFileDefinition csharpFile + ) + { var model = moduleEntryPoint; - if (_generateAttribute && model.GenerateAttribute != false) { + if (_generateAttribute && model.GenerateAttribute != false) + { var attributeGenerator = new ModuleAttributeWriter(); attributeGenerator.CreateAttributeClass(csharpFile, model); @@ -139,45 +176,58 @@ private void GenerateAttribute(ModuleEntryPointModel moduleEntryPoint, } private void GenerateUseMethod( - ModuleEntryPointModel entryPointModel, - DependencyModuleConfigurationModel configurationModel, - CSharpFileDefinition csharpFile) { - - if (string.IsNullOrEmpty(entryPointModel.UseMethod)) { + ModuleEntryPointModel entryPointModel, + DependencyModuleConfigurationModel configurationModel, + CSharpFileDefinition csharpFile + ) + { + if (string.IsNullOrEmpty(entryPointModel.UseMethod)) + { return; } - var extensionMethod = csharpFile.AddClass($"{entryPointModel.EntryPointType.Name}Extensions"); + var extensionMethod = csharpFile.AddClass( + $"{entryPointModel.EntryPointType.Name}Extensions" + ); + + extensionMethod.Modifiers = + ComponentModifier.Public | ComponentModifier.Static | ComponentModifier.Partial; - extensionMethod.Modifiers = ComponentModifier.Public | ComponentModifier.Static | ComponentModifier.Partial; - var method = extensionMethod.AddMethod(entryPointModel.UseMethod!); method.Modifiers = ComponentModifier.Public | ComponentModifier.Static; method.SetReturnType(KnownTypes.Microsoft.DependencyInjection.IServiceCollection); - var serviceProvider = method.AddParameter(KnownTypes.Microsoft.DependencyInjection.IServiceCollection, "serviceCollection"); + var serviceProvider = method.AddParameter( + KnownTypes.Microsoft.DependencyInjection.IServiceCollection, + "serviceCollection" + ); serviceProvider.This = true; - + var parameters = new List(); - - foreach (var parameterInfoModel in entryPointModel.Parameters) { - var param = method.AddParameter(parameterInfoModel.ParameterType, parameterInfoModel.ParameterName); - + + foreach (var parameterInfoModel in entryPointModel.Parameters) + { + var param = method.AddParameter( + parameterInfoModel.ParameterType, + parameterInfoModel.ParameterName + ); + parameters.Add(param); } - + var newStatement = New(entryPointModel.EntryPointType, parameters.ToArray()); - + method.Return(serviceProvider.Invoke("AddModules", newStatement)); method.AddUsingNamespace("DependencyModules.Runtime"); } - - private void GenerateModuleClass(ModuleEntryPointModel model, CSharpFileDefinition csharpFile) { + + private void GenerateModuleClass(ModuleEntryPointModel model, CSharpFileDefinition csharpFile) + { var classDefinition = csharpFile.AddClass(model.EntryPointType.Name); classDefinition.EnableNullable(); classDefinition.Modifiers |= ComponentModifier.Partial; - + SetupStaticConstructor(classDefinition); PopulateServiceCollectionMethod(classDefinition, model); @@ -190,46 +240,66 @@ private void GenerateModuleClass(ModuleEntryPointModel model, CSharpFileDefiniti FeatureMethod(classDefinition, model); - if ((model.ModuleFeatures & ModuleEntryPointFeatures.ShouldImplementEquals) == - ModuleEntryPointFeatures.ShouldImplementEquals) { + if ( + (model.ModuleFeatures & ModuleEntryPointFeatures.ShouldImplementEquals) + == ModuleEntryPointFeatures.ShouldImplementEquals + ) + { EqualMethod(classDefinition, model); HashMethod(classDefinition, model); } } - private void FeatureMethod(ClassDefinition classDefinition, ModuleEntryPointModel model) { - if (model.Features.Count == 0) { + private void FeatureMethod(ClassDefinition classDefinition, ModuleEntryPointModel model) + { + if (model.Features.Count == 0) + { return; } - classDefinition.AddBaseType(KnownTypes.DependencyModules.Features.IDependencyModuleApplicatorProvider); + classDefinition.AddBaseType( + KnownTypes.DependencyModules.Features.IDependencyModuleApplicatorProvider + ); var method = classDefinition.AddMethod("FeatureApplicators"); method.SetReturnType( - new GenericTypeDefinition(typeof(IEnumerable<>), new []{KnownTypes.DependencyModules.Features.IFeatureApplicator})); + new GenericTypeDefinition( + typeof(IEnumerable<>), + new[] { KnownTypes.DependencyModules.Features.IFeatureApplicator } + ) + ); method.Modifiers |= ComponentModifier.Virtual | ComponentModifier.Public; method.AddLeadingTrait(CodeOutputComponent.Get("[Browsable(false)]", true)); method.AddUsingNamespace("System.ComponentModel"); - - method.InterfaceImplementation = KnownTypes.DependencyModules.Features.IDependencyModuleApplicatorProvider; - - foreach (var typeDefinition in model.Features) { + + method.InterfaceImplementation = KnownTypes + .DependencyModules + .Features + .IDependencyModuleApplicatorProvider; + + foreach (var typeDefinition in model.Features) + { method.AddIndentedStatement( YieldReturn( New( - new GenericTypeDefinition(TypeDefinitionEnum.ClassDefinition, - KnownTypes.DependencyModules.Features.FeatureApplicator.Namespace, - KnownTypes.DependencyModules.Features.FeatureApplicator.Name, - new []{typeDefinition}), "this") - )); + new GenericTypeDefinition( + TypeDefinitionEnum.ClassDefinition, + KnownTypes.DependencyModules.Features.FeatureApplicator.Namespace, + KnownTypes.DependencyModules.Features.FeatureApplicator.Name, + new[] { typeDefinition } + ), + "this" + ) + ) + ); } } - private void HashMethod( - ClassDefinition classDefinition, ModuleEntryPointModel model) { + private void HashMethod(ClassDefinition classDefinition, ModuleEntryPointModel model) + { var hashMethod = classDefinition.AddMethod("GetHashCode"); hashMethod.Modifiers |= ComponentModifier.Override; @@ -243,14 +313,18 @@ private void HashMethod( hashMethod.Return(stableHash.ToString()); } - private static int GetStableHashCode(string str) { - unchecked { + private static int GetStableHashCode(string str) + { + unchecked + { var hash1 = 5381; var hash2 = hash1; - for (var i = 0; i < str.Length && str[i] != '\0'; i += 2) { + for (var i = 0; i < str.Length && str[i] != '\0'; i += 2) + { hash1 = ((hash1 << 5) + hash1) ^ str[i]; - if (i == str.Length - 1) { + if (i == str.Length - 1) + { break; } @@ -261,7 +335,8 @@ private static int GetStableHashCode(string str) { } } - private void EqualMethod(ClassDefinition classDefinition, ModuleEntryPointModel model) { + private void EqualMethod(ClassDefinition classDefinition, ModuleEntryPointModel model) + { var equalMethod = classDefinition.AddMethod("Equals"); equalMethod.Modifiers |= ComponentModifier.Override; @@ -277,7 +352,11 @@ private void EqualMethod(ClassDefinition classDefinition, ModuleEntryPointModel /// every module can be sorted together. Emitted unconditionally: a module may gain decorators /// from a source the registrations file never saw, and returning an empty list costs nothing. /// - private void InternalGetDecoratorsMethod(ClassDefinition classDefinition, ModuleEntryPointModel model) { + private void InternalGetDecoratorsMethod( + ClassDefinition classDefinition, + ModuleEntryPointModel model + ) + { var method = classDefinition.AddMethod("InternalGetDecorators"); method.AddLeadingTrait(CodeOutputComponent.Get("[Browsable(false)]", true)); @@ -287,76 +366,101 @@ private void InternalGetDecoratorsMethod(ClassDefinition classDefinition, Module method.SetReturnType( new GenericTypeDefinition( typeof(IEnumerable<>), - new[] { KnownTypes.DependencyModules.Helpers.DecoratorRegistration })); + new[] { KnownTypes.DependencyModules.Helpers.DecoratorRegistration } + ) + ); var closedType = new GenericTypeDefinition( - TypeDefinitionEnum.ClassDefinition, KnownTypes.DependencyModules.Helpers.Namespace, "DependencyRegistry", new[] { - model.EntryPointType - }); + TypeDefinitionEnum.ClassDefinition, + KnownTypes.DependencyModules.Helpers.Namespace, + "DependencyRegistry", + new[] { model.EntryPointType } + ); - method.Return(new StaticInvokeStatement(closedType, "GetDecorators", new List()) { - Indented = false - }); + method.Return( + new StaticInvokeStatement(closedType, "GetDecorators", new List()) + { + Indented = false, + } + ); } - private void InternalGetModulesMethod(ClassDefinition classDefinition, ModuleEntryPointModel model) { + private void InternalGetModulesMethod( + ClassDefinition classDefinition, + ModuleEntryPointModel model + ) + { var attributeModels = FilterAttributes(model.AttributeModels); - + var getModulesMethod = classDefinition.AddMethod("InternalGetModules"); getModulesMethod.AddLeadingTrait(CodeOutputComponent.Get("[Browsable(false)]", true)); getModulesMethod.AddUsingNamespace("System.ComponentModel"); - - getModulesMethod.InterfaceImplementation = KnownTypes.DependencyModules.Interfaces.IDependencyModule; + + getModulesMethod.InterfaceImplementation = KnownTypes + .DependencyModules + .Interfaces + .IDependencyModule; getModulesMethod.SetReturnType(TypeDefinition.Get(typeof(IEnumerable))); - + var parametersList = new List(); - - foreach (var additionalModule in model.AdditionalModules) { - if (additionalModule != null) { + + foreach (var additionalModule in model.AdditionalModules) + { + if (additionalModule != null) + { var newStatement = New(additionalModule); parametersList.Add(newStatement); } } - - foreach (var modelAttributeModel in attributeModels) { + + foreach (var modelAttributeModel in attributeModels) + { var newStatement = New( modelAttributeModel.TypeDefinition, - modelAttributeModel.GetArguments().Select(o => (object)o).ToArray()); - - foreach (var propertyValue in modelAttributeModel.PropertyValues()) { + modelAttributeModel.GetArguments().Select(o => (object)o).ToArray() + ); + + foreach (var propertyValue in modelAttributeModel.PropertyValues()) + { newStatement.AddInitValue(propertyValue); } - + parametersList.Add(newStatement); } - + var closedType = new GenericTypeDefinition( - TypeDefinitionEnum.ClassDefinition, KnownTypes.DependencyModules.Helpers.Namespace, "DependencyRegistry", new[] { - model.EntryPointType - }); - + TypeDefinitionEnum.ClassDefinition, + KnownTypes.DependencyModules.Helpers.Namespace, + "DependencyRegistry", + new[] { model.EntryPointType } + ); + getModulesMethod.Return( - new StaticInvokeStatement( - closedType, - "GetModules", - parametersList.ToArray()) { - Indented = false - }); + new StaticInvokeStatement(closedType, "GetModules", parametersList.ToArray()) + { + Indented = false, + } + ); } - private List FilterAttributes(IReadOnlyList modelAttributeModels) { + private List FilterAttributes( + IReadOnlyList modelAttributeModels + ) + { var attributeModels = new List(); - foreach (var modelAttributeModel in modelAttributeModels) { - if (modelAttributeModel.TypeDefinition.Name == "DependencyModuleAttribute") { + foreach (var modelAttributeModel in modelAttributeModels) + { + if (modelAttributeModel.TypeDefinition.Name == "DependencyModuleAttribute") + { continue; } attributeModels.Add(modelAttributeModel); } - + return attributeModels; } @@ -379,77 +483,98 @@ private List FilterAttributes(IReadOnlyList mode /// private void InternalApplyServicesMethod( ClassDefinition classDefinition, - ModuleEntryPointModel model) { - + ModuleEntryPointModel model + ) + { var closedType = new GenericTypeDefinition( - TypeDefinitionEnum.ClassDefinition, KnownTypes.DependencyModules.Helpers.Namespace, "DependencyRegistry", new[] { - model.EntryPointType - }); + TypeDefinitionEnum.ClassDefinition, + KnownTypes.DependencyModules.Helpers.Namespace, + "DependencyRegistry", + new[] { model.EntryPointType } + ); ApplyServicesOverload(classDefinition, closedType, withEnvironment: false); ApplyServicesOverload(classDefinition, closedType, withEnvironment: true); } private static void ApplyServicesOverload( - ClassDefinition classDefinition, ITypeDefinition closedType, bool withEnvironment) { - + ClassDefinition classDefinition, + ITypeDefinition closedType, + bool withEnvironment + ) + { var loadDependenciesMethod = classDefinition.AddMethod("InternalApplyServices"); loadDependenciesMethod.AddLeadingTrait(CodeOutputComponent.Get("[Browsable(false)]", true)); loadDependenciesMethod.AddUsingNamespace("System.ComponentModel"); - loadDependenciesMethod.InterfaceImplementation = - KnownTypes.DependencyModules.Interfaces.IDependencyModule; + loadDependenciesMethod.InterfaceImplementation = KnownTypes + .DependencyModules + .Interfaces + .IDependencyModule; - var arguments = new List { + var arguments = new List + { loadDependenciesMethod.AddParameter( - KnownTypes.Microsoft.DependencyInjection.IServiceCollection, "services") + KnownTypes.Microsoft.DependencyInjection.IServiceCollection, + "services" + ), }; - if (withEnvironment) { + if (withEnvironment) + { arguments.Add( loadDependenciesMethod.AddParameter( - KnownTypes.DependencyModules.Interfaces.IModuleEnvironment, "environment")); + KnownTypes.DependencyModules.Interfaces.IModuleEnvironment, + "environment" + ) + ); } loadDependenciesMethod.AddIndentedStatement( - new StaticInvokeStatement(closedType, "ApplyServices", arguments) { - Indented = false - }); + new StaticInvokeStatement(closedType, "ApplyServices", arguments) { Indented = false } + ); } - private void PopulateServiceCollectionMethod(ClassDefinition classDefinition, ModuleEntryPointModel model) { + private void PopulateServiceCollectionMethod( + ClassDefinition classDefinition, + ModuleEntryPointModel model + ) + { classDefinition.AddBaseType(KnownTypes.DependencyModules.Interfaces.IDependencyModule); var loadDependenciesMethod = classDefinition.AddMethod("PopulateServiceCollection"); - var parameter = - loadDependenciesMethod.AddParameter( - KnownTypes.Microsoft.DependencyInjection.IServiceCollection, "services"); + var parameter = loadDependenciesMethod.AddParameter( + KnownTypes.Microsoft.DependencyInjection.IServiceCollection, + "services" + ); var closedType = new GenericTypeDefinition( TypeDefinitionEnum.ClassDefinition, KnownTypes.DependencyModules.Helpers.Namespace, "DependencyRegistry", - new[] { - model.EntryPointType - }); + new[] { model.EntryPointType } + ); loadDependenciesMethod.AddIndentedStatement( new StaticInvokeStatement( closedType, "LoadModules", - new IOutputComponent[] { + new IOutputComponent[] + { parameter, - new CodeOutputComponent("this") { - Indented = false - } - }) { - Indented = false - }); + new CodeOutputComponent("this") { Indented = false }, + } + ) + { + Indented = false, + } + ); } - private void SetupStaticConstructor(ClassDefinition classDefinition) { + private void SetupStaticConstructor(ClassDefinition classDefinition) + { classDefinition.AddConstructor().Modifiers = ComponentModifier.Static; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/InterceptorFileWriter.cs b/src/DependencyModules.SourceGenerator.Impl/InterceptorFileWriter.cs index 2bc1294..58148b8 100644 --- a/src/DependencyModules.SourceGenerator.Impl/InterceptorFileWriter.cs +++ b/src/DependencyModules.SourceGenerator.Impl/InterceptorFileWriter.cs @@ -23,12 +23,17 @@ namespace DependencyModules.SourceGenerator.Impl; /// Every generated name carries a _dm prefix. The wrapper implements a user's interface, and /// a member of that interface named _inner would otherwise collide with the field. /// -public class InterceptorFileWriter { - +public class InterceptorFileWriter +{ private const string InnerField = "_dmInner"; - public string Write(InterceptorModel model, string wrapperName, string namespaceName, - DependencyModuleConfigurationModel configurationModel) { + public string Write( + InterceptorModel model, + string wrapperName, + string namespaceName, + DependencyModuleConfigurationModel configurationModel + ) + { var csharpFile = new CSharpFileDefinition(namespaceName); var wrapper = csharpFile.AddClass(wrapperName); @@ -39,8 +44,10 @@ public string Write(InterceptorModel model, string wrapperName, string namespace // what lets the container register it as an open generic implementation. Only constraint-free // parameters reach here; a constrained one is refused upstream, because the wrapper would have // to repeat the constraint and there is no way to emit one. - if (model.IsOpenGeneric) { - foreach (var typeParameter in model.TypeParameters!) { + if (model.IsOpenGeneric) + { + foreach (var typeParameter in model.TypeParameters!) + { wrapper.AddGenericParameter(typeParameter.Name); WriteConstraint(wrapper.AddConstraint(typeParameter.Name), typeParameter); @@ -48,17 +55,22 @@ public string Write(InterceptorModel model, string wrapperName, string namespace } wrapper.AddBaseType(model.ServiceType); - wrapper.AddAttribute(TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "ExcludeFromCodeCoverage")); + wrapper.AddAttribute( + TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "ExcludeFromCodeCoverage") + ); WriteFields(wrapper, model); WriteConstructor(wrapper, model); - foreach (var declaration in model.Declarations) { + foreach (var declaration in model.Declarations) + { WriteDeclaration(wrapper, model, declaration); } - for (var index = 0; index < model.Members.Count; index++) { - if (IsIntercepted(model, model.Members[index])) { + for (var index = 0; index < model.Members.Count; index++) + { + if (IsIntercepted(model, model.Members[index])) + { WriteState(wrapper, model, model.Members[index], index, wrapperName, namespaceName); } } @@ -67,10 +79,12 @@ public string Write(InterceptorModel model, string wrapperName, string namespace csharpFile.EnableNullable(); var output = new OutputContext( - new OutputContextOptions { + new OutputContextOptions + { TypeOutputMode = TypeOutputMode.Global, - BraceStyle = configurationModel.GeneratedCodeStyle - }); + BraceStyle = configurationModel.GeneratedCodeStyle, + } + ); csharpFile.WriteOutput(output); @@ -90,25 +104,34 @@ public string Write(InterceptorModel model, string wrapperName, string namespace /// DecoratorHelper.InterceptOpenGeneric registers alongside it. /// private static ITypeDefinition InnerType(InterceptorModel model) => - model.IsOpenGeneric ? Closed(model.ImplementationType, model.TypeParameters!) : model.ServiceType; + model.IsOpenGeneric + ? Closed(model.ImplementationType, model.TypeParameters!) + : model.ServiceType; /// /// A type closed over the wrapper's own type parameters — Repository becomes /// Repository<T>. /// private static ITypeDefinition Closed( - ITypeDefinition type, IReadOnlyList typeParameters) { - + ITypeDefinition type, + IReadOnlyList typeParameters + ) + { var arguments = new ITypeDefinition[typeParameters.Count]; - for (var i = 0; i < arguments.Length; i++) { + for (var i = 0; i < arguments.Length; i++) + { // A TypeParameterDefinition, not TypeDefinition.Get("", name): an empty namespace now // means the global namespace, which Global mode qualifies - and global::T is not a type. arguments[i] = new TypeParameterDefinition(typeParameters[i].Name); } return new GenericTypeDefinition( - TypeDefinitionEnum.ClassDefinition, type.Namespace, type.Name, arguments); + TypeDefinitionEnum.ClassDefinition, + type.Namespace, + type.Name, + arguments + ); } /// @@ -119,7 +142,11 @@ private static ITypeDefinition Closed( /// Repository_Intercepted<T> the name is Repository_Intercepted<T>, and /// the bare name is CS0305. /// - private static ITypeDefinition SelfType(InterceptorModel model, string wrapperName, string namespaceName) => + private static ITypeDefinition SelfType( + InterceptorModel model, + string wrapperName, + string namespaceName + ) => model.IsOpenGeneric ? Closed(TypeDefinition.Get(namespaceName, wrapperName), model.TypeParameters!) : TypeDefinition.Get(namespaceName, wrapperName); @@ -129,9 +156,12 @@ private static ITypeDefinition SelfType(InterceptorModel model, string wrapperNa /// to repeat or the call they forward will not satisfy them. /// private static void WriteConstraints( - InterceptedMemberModel member, Func addConstraint) { - - foreach (var typeParameter in member.TypeParameters) { + InterceptedMemberModel member, + Func addConstraint + ) + { + foreach (var typeParameter in member.TypeParameters) + { WriteConstraint(addConstraint(typeParameter.Name), typeParameter); } } @@ -143,8 +173,13 @@ private static void WriteConstraints( /// The parts go in as the symbol reported them and come out in the order C# requires, which is /// ConstraintDefinition's job rather than this writer's. /// - private static void WriteConstraint(ConstraintDefinition constraint, TypeParameterModel typeParameter) { - switch (typeParameter.Primary) { + private static void WriteConstraint( + ConstraintDefinition constraint, + TypeParameterModel typeParameter + ) + { + switch (typeParameter.Primary) + { case "class": constraint.Class(); break; @@ -162,50 +197,62 @@ private static void WriteConstraint(ConstraintDefinition constraint, TypeParamet break; } - foreach (var constraintType in typeParameter.ConstraintTypes) { + foreach (var constraintType in typeParameter.ConstraintTypes) + { constraint.Implements(constraintType); } - if (typeParameter.DefaultConstructor) { + if (typeParameter.DefaultConstructor) + { constraint.DefaultConstructor(); } } - private static void WriteFields(ClassDefinition wrapper, InterceptorModel model) { + private static void WriteFields(ClassDefinition wrapper, InterceptorModel model) + { var inner = wrapper.AddField(InnerType(model), InnerField); inner.Modifiers |= ComponentModifier.Private | ComponentModifier.Readonly; - for (var index = 0; index < model.Interceptors.Count; index++) { - var interceptor = wrapper.AddField(model.Interceptors[index].Type, InterceptorField(index)); + for (var index = 0; index < model.Interceptors.Count; index++) + { + var interceptor = wrapper.AddField( + model.Interceptors[index].Type, + InterceptorField(index) + ); interceptor.Modifiers |= ComponentModifier.Private | ComponentModifier.Readonly; } // Everything identifying a member is known now, so one caller is built per member and shared // by every call rather than constructed per invocation. A member nothing intercepts has no // caller, because it has no pipeline to report itself to. - for (var index = 0; index < model.Members.Count; index++) { - if (!IsIntercepted(model, model.Members[index])) { + for (var index = 0; index < model.Members.Count; index++) + { + if (!IsIntercepted(model, model.Members[index])) + { continue; } - var caller = wrapper.AddField( - Interception.CallerInfo, CallerField(index)); + var caller = wrapper.AddField(Interception.CallerInfo, CallerField(index)); - caller.Modifiers |= ComponentModifier.Private | ComponentModifier.Static | ComponentModifier.Readonly; + caller.Modifiers |= + ComponentModifier.Private | ComponentModifier.Static | ComponentModifier.Readonly; caller.InitializeValue = New( Interception.CallerInfo, TypeOf(model.ServiceType), - QuoteString(model.Members[index].Name)); + QuoteString(model.Members[index].Name) + ); } } - private static void WriteConstructor(ClassDefinition wrapper, InterceptorModel model) { + private static void WriteConstructor(ClassDefinition wrapper, InterceptorModel model) + { var constructor = wrapper.AddConstructor(); constructor.AddParameter(InnerType(model), "inner"); constructor.AddIndentedStatement($"{InnerField} = inner"); - for (var index = 0; index < model.Interceptors.Count; index++) { + for (var index = 0; index < model.Interceptors.Count; index++) + { constructor.AddParameter(model.Interceptors[index].Type, $"interceptor{index}"); constructor.AddIndentedStatement($"{InterceptorField(index)} = interceptor{index}"); } @@ -215,11 +262,20 @@ private static void WriteConstructor(ClassDefinition wrapper, InterceptorModel m /// One member as the interface declares it, with each accessor forwarding into the pipeline. /// private static void WriteDeclaration( - ClassDefinition wrapper, InterceptorModel model, InterceptedDeclarationModel declaration) { - - switch (declaration.Kind) { + ClassDefinition wrapper, + InterceptorModel model, + InterceptedDeclarationModel declaration + ) + { + switch (declaration.Kind) + { case DeclarationKind.Method: - WriteForwardingMethod(wrapper, model, model.Members[declaration.First], declaration.First); + WriteForwardingMethod( + wrapper, + model, + model.Members[declaration.First], + declaration.First + ); break; case DeclarationKind.Property: @@ -237,56 +293,99 @@ private static void WriteDeclaration( } private static void WriteProperty( - ClassDefinition wrapper, InterceptorModel model, InterceptedDeclarationModel declaration) { - + ClassDefinition wrapper, + InterceptorModel model, + InterceptedDeclarationModel declaration + ) + { var property = wrapper.AddProperty(declaration.Type!, declaration.Identifier); property.Modifiers |= ComponentModifier.Public; - if (declaration.First >= 0) { - WriteAccessorBody(property.Get, model, model.Members[declaration.First], declaration.First); + if (declaration.First >= 0) + { + WriteAccessorBody( + property.Get, + model, + model.Members[declaration.First], + declaration.First + ); } - if (declaration.Second < 0) { + if (declaration.Second < 0) + { // A get-only property. Leaving the setter in place would declare one the interface does // not have, and PropertyDefinition writes an empty pair as an auto-property. property.Set = null; - } else { - WriteAccessorBody(property.Set!, model, model.Members[declaration.Second], declaration.Second); + } + else + { + WriteAccessorBody( + property.Set!, + model, + model.Members[declaration.Second], + declaration.Second + ); } } private static void WriteIndexer( - ClassDefinition wrapper, InterceptorModel model, InterceptedDeclarationModel declaration) { - + ClassDefinition wrapper, + InterceptorModel model, + InterceptedDeclarationModel declaration + ) + { var indexer = wrapper.AddProperty(declaration.Type!, "this"); indexer.Modifiers |= ComponentModifier.Public; - foreach (var index in declaration.Indices) { + foreach (var index in declaration.Indices) + { indexer.AddIndexParameter(index.Type, index.Identifier); } - if (declaration.First >= 0) { - WriteAccessorBody(indexer.Get, model, model.Members[declaration.First], declaration.First); + if (declaration.First >= 0) + { + WriteAccessorBody( + indexer.Get, + model, + model.Members[declaration.First], + declaration.First + ); } - if (declaration.Second < 0) { + if (declaration.Second < 0) + { indexer.Set = null; - } else { - WriteAccessorBody(indexer.Set!, model, model.Members[declaration.Second], declaration.Second); + } + else + { + WriteAccessorBody( + indexer.Set!, + model, + model.Members[declaration.Second], + declaration.Second + ); } } private static void WriteEvent( - ClassDefinition wrapper, InterceptorModel model, InterceptedDeclarationModel declaration) { - + ClassDefinition wrapper, + InterceptorModel model, + InterceptedDeclarationModel declaration + ) + { var declared = wrapper.AddEvent(declaration.Type!, declaration.Identifier); declared.Modifiers |= ComponentModifier.Public; WriteAccessorBody(declared.Add, model, model.Members[declaration.First], declaration.First); - WriteAccessorBody(declared.Remove, model, model.Members[declaration.Second], declaration.Second); + WriteAccessorBody( + declared.Remove, + model, + model.Members[declaration.Second], + declaration.Second + ); } /// @@ -294,9 +393,14 @@ private static void WriteEvent( /// order the CLR gives an accessor: any indices, then the assigned value. /// private static void WriteAccessorBody( - PropertyMethodDefinition accessor, InterceptorModel model, InterceptedMemberModel member, int index) { - - if (!IsIntercepted(model, member)) { + PropertyMethodDefinition accessor, + InterceptorModel model, + InterceptedMemberModel member, + int index + ) + { + if (!IsIntercepted(model, member)) + { WritePassThrough(accessor, member); return; @@ -307,13 +411,17 @@ private static void WriteAccessorBody( arguments.AddRange(member.Parameters.Select(parameter => parameter.Identifier)); accessor.AddIndentedStatement( - $"var state = new {ClosedStateName(member, index)}({string.Join(", ", arguments)})"); + $"var state = new {ClosedStateName(member, index)}({string.Join(", ", arguments)})" + ); accessor.NewLine(); - if (member.ReturnShape == ReturnShape.Void) { + if (member.ReturnShape == ReturnShape.Void) + { accessor.AddIndentedStatement("state.Invoke(0)"); - } else { + } + else + { accessor.Return("state.Invoke(0)"); } } @@ -327,12 +435,16 @@ private static void WriteAccessorBody( /// it can serve and has nothing to say about the rest. Those members build no state and /// allocate nothing. /// - private static void WritePassThrough(BaseBlockDefinition block, InterceptedMemberModel member) { + private static void WritePassThrough(BaseBlockDefinition block, InterceptedMemberModel member) + { var call = InnerCall(member, InnerField); - if (member.ReturnShape == ReturnShape.Void) { + if (member.ReturnShape == ReturnShape.Void) + { block.AddIndentedStatement(call); - } else { + } + else + { block.Return(call); } } @@ -341,17 +453,23 @@ private static void WritePassThrough(BaseBlockDefinition block, InterceptedMembe /// The method as the interface declares it, forwarding into the pipeline. /// private static void WriteForwardingMethod( - ClassDefinition wrapper, InterceptorModel model, InterceptedMemberModel member, int index) { - + ClassDefinition wrapper, + InterceptorModel model, + InterceptedMemberModel member, + int index + ) + { var method = wrapper.AddMethod(member.Identifier); method.Modifiers |= ComponentModifier.Public; - if (member.ReturnType != null) { + if (member.ReturnType != null) + { method.SetReturnType(member.ReturnType); } - foreach (var typeParameter in member.TypeParameters) { + foreach (var typeParameter in member.TypeParameters) + { method.AddGenericParameter(new TypeParameterDefinition(typeParameter.Name)); } @@ -359,21 +477,27 @@ private static void WriteForwardingMethod( var arguments = new List { "this" }; - foreach (var parameter in member.Parameters) { + foreach (var parameter in member.Parameters) + { var declared = method.AddParameter(parameter.Type, parameter.Identifier); // Dropping params does not merely lose sugar: an optional parameter ahead of it becomes // an optional parameter followed by a required one, which the compiler refuses. declared.IsParams = parameter.IsParams; - if (parameter.DefaultValue != null) { - declared.DefaultValue = new CodeOutputComponent(parameter.DefaultValue) { Indented = false }; + if (parameter.DefaultValue != null) + { + declared.DefaultValue = new CodeOutputComponent(parameter.DefaultValue) + { + Indented = false, + }; } arguments.Add(parameter.Identifier); } - if (!IsIntercepted(model, member)) { + if (!IsIntercepted(model, member)) + { WritePassThrough(method, member); return; @@ -381,16 +505,19 @@ private static void WriteForwardingMethod( // A ValueTask cannot be built from the pipeline's ValueTask without either an await // or an allocation, so this one shape is written as an async method. - if (member.ReturnShape == ReturnShape.ValueTask) { + if (member.ReturnShape == ReturnShape.ValueTask) + { method.Modifiers |= ComponentModifier.Async; } method.AddIndentedStatement( - $"var state = new {ClosedStateName(member, index)}({string.Join(", ", arguments)})"); + $"var state = new {ClosedStateName(member, index)}({string.Join(", ", arguments)})" + ); method.NewLine(); - switch (member.ReturnShape) { + switch (member.ReturnShape) + { case ReturnShape.Void: method.AddIndentedStatement("state.Invoke(0)"); break; @@ -424,12 +551,14 @@ private static void WriteState( InterceptedMemberModel member, int index, string wrapperName, - string namespaceName) { - - var baseType = member.Kind switch { + string namespaceName + ) + { + var baseType = member.Kind switch + { InterceptorKind.Async => Interception.AsyncInvocationState(member.ResultType), InterceptorKind.Stream => Interception.StreamInvocationState(member.ResultType), - _ => Interception.InvocationState(member.ResultType) + _ => Interception.InvocationState(member.ResultType), }; var state = wrapper.AddClass(StateName(index)); @@ -437,7 +566,8 @@ private static void WriteState( state.Modifiers |= ComponentModifier.Private | ComponentModifier.Sealed; state.AddBaseType(baseType); - foreach (var typeParameter in member.TypeParameters) { + foreach (var typeParameter in member.TypeParameters) + { state.AddGenericParameter(typeParameter.Name); } @@ -454,12 +584,16 @@ private static void WriteState( } private static void WriteStateFields( - ClassDefinition state, InterceptedMemberModel member, ITypeDefinition selfType) { - + ClassDefinition state, + InterceptedMemberModel member, + ITypeDefinition selfType + ) + { var self = state.AddField(selfType, "_self"); self.Modifiers |= ComponentModifier.Private | ComponentModifier.Readonly; - for (var index = 0; index < member.Parameters.Count; index++) { + for (var index = 0; index < member.Parameters.Count; index++) + { var argument = state.AddField(member.Parameters[index].Type, ArgumentField(index)); argument.Modifiers |= ComponentModifier.Private; } @@ -470,20 +604,30 @@ private static void WriteStateFields( /// non-nullable reference is definitely assigned and the wrapper needs no nullable suppression. /// private static void WriteStateConstructor( - ClassDefinition state, InterceptedMemberModel member, int index, ITypeDefinition selfType) { - + ClassDefinition state, + InterceptedMemberModel member, + int index, + ITypeDefinition selfType + ) + { var constructor = state.AddConstructor(); constructor.AddParameter(selfType, "self"); constructor.AddIndentedStatement("_self = self"); - for (var argument = 0; argument < member.Parameters.Count; argument++) { + for (var argument = 0; argument < member.Parameters.Count; argument++) + { constructor.AddParameter(member.Parameters[argument].Type, $"arg{argument}"); constructor.AddIndentedStatement($"{ArgumentField(argument)} = arg{argument}"); } } - private static void WriteCallerAndCount(ClassDefinition state, InterceptedMemberModel member, int index) { + private static void WriteCallerAndCount( + ClassDefinition state, + InterceptedMemberModel member, + int index + ) + { var caller = state.AddProperty(Interception.CallerInfo, "Caller"); caller.Modifiers |= ComponentModifier.Public | ComponentModifier.Override; @@ -503,7 +647,8 @@ private static void WriteCallerAndCount(ClassDefinition state, InterceptedMember /// Reading boxes, and writing replaces the field the last stage passes on, so an interceptor /// that ignores the arguments pays for neither. /// - private static void WriteArgumentsIndexer(ClassDefinition state, InterceptedMemberModel member) { + private static void WriteArgumentsIndexer(ClassDefinition state, InterceptedMemberModel member) + { var indexer = state.AddProperty(TypeDefinition.Get(typeof(object)).MakeNullable(), "this"); indexer.Modifiers |= ComponentModifier.Public | ComponentModifier.Override; @@ -512,7 +657,8 @@ private static void WriteArgumentsIndexer(ClassDefinition state, InterceptedMemb var get = indexer.Get.Switch("index"); - for (var index = 0; index < member.Parameters.Count; index++) { + for (var index = 0; index < member.Parameters.Count; index++) + { get.AddCase(index).Return(ArgumentField(index)); } @@ -520,10 +666,12 @@ private static void WriteArgumentsIndexer(ClassDefinition state, InterceptedMemb var set = indexer.Set!.Switch("index"); - for (var index = 0; index < member.Parameters.Count; index++) { + for (var index = 0; index < member.Parameters.Count; index++) + { var block = set.AddCase(index); - block.Assign(Bang(StaticCast(member.Parameters[index].Type, "value"))) + block + .Assign(Bang(StaticCast(member.Parameters[index].Type, "value"))) .To(ArgumentField(index)); block.Break(); } @@ -531,7 +679,8 @@ private static void WriteArgumentsIndexer(ClassDefinition state, InterceptedMemb set.AddDefault().Throw(SystemTypes.ArgumentOutOfRangeException, "nameof(index)"); } - private static void WriteNameAt(ClassDefinition state, InterceptedMemberModel member) { + private static void WriteNameAt(ClassDefinition state, InterceptedMemberModel member) + { var nameAt = state.AddMethod("NameAt"); nameAt.Modifiers |= ComponentModifier.Public | ComponentModifier.Override; @@ -540,7 +689,8 @@ private static void WriteNameAt(ClassDefinition state, InterceptedMemberModel me var switchBlock = nameAt.Switch("index"); - for (var index = 0; index < member.Parameters.Count; index++) { + for (var index = 0; index < member.Parameters.Count; index++) + { switchBlock.AddCase(index).Return(QuoteString(member.Parameters[index].Name)); } @@ -552,21 +702,29 @@ private static void WriteNameAt(ClassDefinition state, InterceptedMemberModel me /// rather than being held here, which is what lets an interceptor proceed more than once. /// private static void WriteInvoke( - ClassDefinition state, InterceptorModel model, InterceptedMemberModel member, string wrapperName) { - - var (returnType, contextType, interceptMethod) = member.Kind switch { + ClassDefinition state, + InterceptorModel model, + InterceptedMemberModel member, + string wrapperName + ) + { + var (returnType, contextType, interceptMethod) = member.Kind switch + { InterceptorKind.Async => ( SystemTypes.ValueTask(member.ResultType), Interception.AsyncInvocationContext(member.ResultType), - "InterceptAsync"), + "InterceptAsync" + ), InterceptorKind.Stream => ( SystemTypes.AsyncEnumerable(member.ResultType), Interception.StreamInvocationContext(member.ResultType), - "InterceptStream"), + "InterceptStream" + ), _ => ( member.ResultType, Interception.InvocationContext(member.ResultType), - "Intercept") + "Intercept" + ), }; var invoke = state.AddMethod("Invoke"); @@ -582,21 +740,28 @@ private static void WriteInvoke( // it. Proceed() walks stage + 1, so the numbering has to be contiguous. var stage = 0; - for (var index = 0; index < model.Interceptors.Count; index++) { - if (!model.Interceptors[index].CanServe(member.Kind)) { + for (var index = 0; index < model.Interceptors.Count; index++) + { + if (!model.Interceptors[index].CanServe(member.Kind)) + { continue; } - switchBlock.AddCase(stage).Return( - CodeOutputComponent.Get($"_self.{InterceptorField(index)}") - .Invoke(interceptMethod, New(contextType, "this", stage))); + switchBlock + .AddCase(stage) + .Return( + CodeOutputComponent + .Get($"_self.{InterceptorField(index)}") + .Invoke(interceptMethod, New(contextType, "this", stage)) + ); stage++; } var last = switchBlock.AddDefault(); - switch (member.ReturnShape) { + switch (member.ReturnShape) + { case ReturnShape.Void: last.AddIndentedStatement(InnerCall(member)); last.NewLine(); @@ -620,7 +785,8 @@ private static void WriteInvoke( // A task with no result has to be awaited to be turned into a NoResult, and an await needs a // method to sit in. Left un-configured so the interceptor's continuation resumes on the // context it started on, the way a hand-written decorator would. - if (member.ReturnShape is ReturnShape.Task or ReturnShape.ValueTask) { + if (member.ReturnShape is ReturnShape.Task or ReturnShape.ValueTask) + { var inner = state.AddMethod("DmInvokeInner"); inner.Modifiers |= ComponentModifier.Private | ComponentModifier.Async; @@ -650,24 +816,28 @@ private static void WriteInvoke( /// The last stage of a pipeline reads the arguments off the state; a member nothing intercepts /// has no state and passes on the parameters it was handed. /// - private static string InnerCall(InterceptedMemberModel member, string? passThroughTarget = null) { + private static string InnerCall(InterceptedMemberModel member, string? passThroughTarget = null) + { var target = passThroughTarget ?? $"_self.{InnerField}"; var last = member.Parameters.Count - 1; string Argument(int index) => passThroughTarget == null ? ArgumentField(index) : member.Parameters[index].Identifier; - string Arguments(int start, int end) { + string Arguments(int start, int end) + { var arguments = new List(); - for (var index = start; index < end; index++) { + for (var index = start; index < end; index++) + { arguments.Add(Argument(index)); } return string.Join(", ", arguments); } - switch (member.Form) { + switch (member.Form) + { case AccessorForm.PropertyGet: return $"{target}.{member.Identifier}"; @@ -688,26 +858,36 @@ string Arguments(int start, int end) { return $"{target}.{member.Identifier} -= {Argument(0)}"; default: - var typeArguments = member.TypeParameters.Count == 0 - ? "" - : "<" + string.Join(", ", member.TypeParameters.Select(parameter => parameter.Name)) + ">"; - - return $"{target}.{member.Identifier}{typeArguments}" + - $"({Arguments(0, member.Parameters.Count)})"; + var typeArguments = + member.TypeParameters.Count == 0 + ? "" + : "<" + + string.Join( + ", ", + member.TypeParameters.Select(parameter => parameter.Name) + ) + + ">"; + + return $"{target}.{member.Identifier}{typeArguments}" + + $"({Arguments(0, member.Parameters.Count)})"; } } /// /// Whether any of the service's interceptors can be placed around this member. /// - private static bool IsIntercepted(InterceptorModel model, InterceptedMemberModel member) { + private static bool IsIntercepted(InterceptorModel model, InterceptedMemberModel member) + { // Left out by [Intercept].Members. Still forwarded below, just not through the chain. - if (member.Excluded) { + if (member.Excluded) + { return false; } - foreach (var interceptor in model.Interceptors) { - if (interceptor.CanServe(member.Kind)) { + foreach (var interceptor in model.Interceptors) + { + if (interceptor.CanServe(member.Kind)) + { return true; } } @@ -721,17 +901,18 @@ private static bool IsIntercepted(InterceptorModel model, InterceptedMemberModel /// The state class is constructed closed over the member's type parameters, since a nested type /// cannot close over a method's. /// - private static string ClosedStateName(InterceptedMemberModel member, int index) { - if (member.TypeParameters.Count == 0) { + private static string ClosedStateName(InterceptedMemberModel member, int index) + { + if (member.TypeParameters.Count == 0) + { return StateName(index); } - return $"{StateName(index)}<" + - string.Join(", ", member.TypeParameters.Select(parameter => parameter.Name)) + - ">"; + return $"{StateName(index)}<" + + string.Join(", ", member.TypeParameters.Select(parameter => parameter.Name)) + + ">"; } - private static string InterceptorField(int index) => $"_dmInterceptor{index}"; private static string CallerField(int index) => $"_dmCaller{index}"; diff --git a/src/DependencyModules.SourceGenerator.Impl/InterceptorRegistrationWriter.cs b/src/DependencyModules.SourceGenerator.Impl/InterceptorRegistrationWriter.cs index 94c6ac0..b4d8c96 100644 --- a/src/DependencyModules.SourceGenerator.Impl/InterceptorRegistrationWriter.cs +++ b/src/DependencyModules.SourceGenerator.Impl/InterceptorRegistrationWriter.cs @@ -1,6 +1,6 @@ -using DependencyModules.SourceGenerator.Impl.Utilities; using CSharpAuthor; using DependencyModules.SourceGenerator.Impl.Models; +using DependencyModules.SourceGenerator.Impl.Utilities; using static CSharpAuthor.SyntaxHelpers; namespace DependencyModules.SourceGenerator.Impl; @@ -9,13 +9,14 @@ namespace DependencyModules.SourceGenerator.Impl; /// Registers each generated wrapper as a decorator of the service it intercepts, and registers the /// interceptors themselves so the wrapper can be constructed. /// -public class InterceptorRegistrationWriter { - +public class InterceptorRegistrationWriter +{ public string Write( ModuleEntryPointModel entryPointModel, DependencyModuleConfigurationModel configurationModel, - IReadOnlyList models) { - + IReadOnlyList models + ) + { var csharpFile = new CSharpFileDefinition(entryPointModel.EntryPointType.Namespace); var classDefinition = csharpFile.AddClass(entryPointModel.EntryPointType.Name); @@ -25,14 +26,18 @@ public string Write( // else, so the namespace is asked for by name; Global mode derives no usings on its own. classDefinition.AddUsingNamespace("Microsoft.Extensions.DependencyInjection"); - for (var i = 0; i < models.Count; i++) { + for (var i = 0; i < models.Count; i++) + { WriteInterceptor(entryPointModel, classDefinition, models[i], i, configurationModel); } - var outputContext = new OutputContext(new OutputContextOptions { - TypeOutputMode = TypeOutputMode.Global, - BraceStyle = configurationModel.GeneratedCodeStyle - }); + var outputContext = new OutputContext( + new OutputContextOptions + { + TypeOutputMode = TypeOutputMode.Global, + BraceStyle = configurationModel.GeneratedCodeStyle, + } + ); csharpFile.WriteOutput(outputContext); @@ -48,15 +53,21 @@ public string Write( /// parameter names instead is CS0246, because no T is in scope at the registration. /// Blank-named arguments are how this codebase represents unbound throughout. /// - private static ITypeDefinition Unbound(ITypeDefinition type, int arity) { + private static ITypeDefinition Unbound(ITypeDefinition type, int arity) + { var arguments = new ITypeDefinition[arity]; - for (var i = 0; i < arity; i++) { + for (var i = 0; i < arity; i++) + { arguments[i] = TypeDefinition.Get("", ""); } return new GenericTypeDefinition( - TypeDefinitionEnum.ClassDefinition, type.Namespace, type.Name, arguments); + TypeDefinitionEnum.ClassDefinition, + type.Namespace, + type.Name, + arguments + ); } private static void WriteInterceptor( @@ -64,19 +75,25 @@ private static void WriteInterceptor( ClassDefinition classDefinition, InterceptorModel model, int index, - DependencyModuleConfigurationModel configurationModel) { - + DependencyModuleConfigurationModel configurationModel + ) + { var methodName = $"ApplyInterceptor{index}"; var method = classDefinition.AddMethod(methodName); method.Modifiers |= ComponentModifier.Private | ComponentModifier.Static; - if (configurationModel.ExcludeGeneratedCodeFromCoverage) { - method.AddAttribute(TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "ExcludeFromCodeCoverage")); + if (configurationModel.ExcludeGeneratedCodeFromCoverage) + { + method.AddAttribute( + TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "ExcludeFromCodeCoverage") + ); } var services = method.AddParameter( - KnownTypes.Microsoft.DependencyInjection.IServiceCollection, "services"); + KnownTypes.Microsoft.DependencyInjection.IServiceCollection, + "services" + ); // Each interceptor is registered as itself, not as IInterceptor. Registering the shared // interface instead made every interceptor visible to every wrapper, and two services with @@ -87,8 +104,10 @@ private static void WriteInterceptor( // theirs is the one already in the collection. var registered = new HashSet(); - foreach (var interceptor in model.Interceptors) { - if (!registered.Add(interceptor.Type)) { + foreach (var interceptor in model.Interceptors) + { + if (!registered.Add(interceptor.Type)) + { continue; } @@ -96,10 +115,13 @@ private static void WriteInterceptor( new StaticInvokeStatement( KnownTypes.Microsoft.DependencyInjection.ServiceCollectionDescriptorExtensions, TryAddMethodFor(interceptor.Lifestyle), - new List { + new List + { CodeOutputComponent.Get(services.Name), - TypeOf(interceptor.Type) - })); + TypeOf(interceptor.Type), + } + ) + ); } var wrapperName = $"{model.ImplementationType.Name.Replace(".", "_")}_Intercepted"; @@ -107,7 +129,8 @@ private static void WriteInterceptor( method.NewLine(); - if (model.IsOpenGeneric) { + if (model.IsOpenGeneric) + { // An open generic service cannot be decorated: decoration rewrites the registration into // a factory, and the container refuses a factory for one. It does accept an open generic // implementation type, and the wrapper is one — so the registration is swapped for the @@ -120,23 +143,33 @@ private static void WriteInterceptor( new StaticInvokeStatement( KnownTypes.DependencyModules.Helpers.DecoratorHelper, "InterceptOpenGeneric", - new List { + new List + { CodeOutputComponent.Get(services.Name), TypeOf(Unbound(model.ServiceType, model.TypeParameters!.Count)), TypeOf(Unbound(model.ImplementationType, model.TypeParameters!.Count)), - TypeOf(Unbound(wrapperType, model.TypeParameters!.Count)) - })); - } else { + TypeOf(Unbound(wrapperType, model.TypeParameters!.Count)), + } + ) + ); + } + else + { // The wrapper is generated right here, so its constructor is known exactly: the // intercepted instance, then one parameter per interceptor. Emitting the `new` rather than // handing the type to ActivatorUtilities is what keeps interception working in a published // Native AOT application — the same reason decorators are emitted closed. var arguments = new List { CodeOutputComponent.Get("inner") }; - for (var i = 0; i < model.Interceptors.Count; i++) { + for (var i = 0; i < model.Interceptors.Count; i++) + { arguments.Add( new InvokeGenericDefinition( - "provider", "GetRequiredService", new[] { model.Interceptors[i].Type })); + "provider", + "GetRequiredService", + new[] { model.Interceptors[i].Type } + ) + ); } // The implementation is named so the rewrite lands only on the registration this @@ -154,8 +187,11 @@ private static void WriteInterceptor( new WrapStatement( CodeOutputComponent.Get(" => "), CodeOutputComponent.Get("(provider, inner)"), - New(wrapperType, arguments.ToArray())), - TypeOf(model.ImplementationType))); + New(wrapperType, arguments.ToArray()) + ), + TypeOf(model.ImplementationType) + ) + ); } // A field initializer registers the method, matching how decorator registrations are hooked @@ -164,22 +200,27 @@ private static void WriteInterceptor( field.Modifiers |= ComponentModifier.Private | ComponentModifier.Static; field.AddAttribute( TypeDefinition.Get("System.Diagnostics.CodeAnalysis", "DynamicDependency"), - $"nameof({methodName})"); + $"nameof({methodName})" + ); var registryType = new GenericTypeDefinition( TypeDefinitionEnum.ClassDefinition, KnownTypes.DependencyModules.Helpers.Namespace, "DependencyRegistry", - new[] { entryPointModel.EntryPointType }); + new[] { entryPointModel.EntryPointType } + ); field.InitializeValue = new StaticInvokeStatement( registryType, "AddDecorator", - new List { + new List + { CodeOutputComponent.Get(methodName), - CodeOutputComponent.Get(model.Order.ToString()) - }) { - Indented = false + CodeOutputComponent.Get(model.Order.ToString()), + } + ) + { + Indented = false, }; } @@ -192,9 +233,10 @@ private static void WriteInterceptor( /// registration is already in the collection by the time this runs. /// private static string TryAddMethodFor(ServiceLifestyle lifestyle) => - lifestyle switch { + lifestyle switch + { ServiceLifestyle.Scoped => "TryAddScoped", ServiceLifestyle.Transient => "TryAddTransient", - _ => "TryAddSingleton" + _ => "TryAddSingleton", }; } diff --git a/src/DependencyModules.SourceGenerator.Impl/KnownTypes.cs b/src/DependencyModules.SourceGenerator.Impl/KnownTypes.cs index 1267e7f..e536fe7 100644 --- a/src/DependencyModules.SourceGenerator.Impl/KnownTypes.cs +++ b/src/DependencyModules.SourceGenerator.Impl/KnownTypes.cs @@ -3,23 +3,37 @@ namespace DependencyModules.SourceGenerator.Impl; // ReSharper disable InconsistentNaming -public static class KnownTypes { - public static class Microsoft { - public static class DependencyInjection { - +public static class KnownTypes +{ + public static class Microsoft + { + public static class DependencyInjection + { public const string Namespace = "Microsoft.Extensions.DependencyInjection"; - public static readonly ITypeDefinition IServiceCollection = - TypeDefinition.Get(TypeDefinitionEnum.InterfaceDefinition, Namespace, "IServiceCollection"); - - public static readonly ITypeDefinition IServiceProvider = - TypeDefinition.Get(TypeDefinitionEnum.InterfaceDefinition, "System", "IServiceProvider"); - - public static readonly ITypeDefinition ServiceDescriptor = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "ServiceDescriptor"); - - public static readonly ITypeDefinition FromKeyedServicesAttribute = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "FromKeyedServicesAttribute"); + public static readonly ITypeDefinition IServiceCollection = TypeDefinition.Get( + TypeDefinitionEnum.InterfaceDefinition, + Namespace, + "IServiceCollection" + ); + + public static readonly ITypeDefinition IServiceProvider = TypeDefinition.Get( + TypeDefinitionEnum.InterfaceDefinition, + "System", + "IServiceProvider" + ); + + public static readonly ITypeDefinition ServiceDescriptor = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "ServiceDescriptor" + ); + + public static readonly ITypeDefinition FromKeyedServicesAttribute = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "FromKeyedServicesAttribute" + ); /// /// Home of the TryAdd family. Invoked statically so the generated file needs no using. @@ -28,48 +42,82 @@ public static class DependencyInjection { TypeDefinition.Get( TypeDefinitionEnum.ClassDefinition, Namespace + ".Extensions", - "ServiceCollectionDescriptorExtensions"); + "ServiceCollectionDescriptorExtensions" + ); } - - public static class TextJson { + + public static class TextJson + { public const string Namespace = "System.Text.Json.Serialization"; - + public static readonly ITypeDefinition JsonSourceGenerationOptionsAttribute = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "JsonSourceGenerationOptionsAttribute"); - - public static readonly ITypeDefinition IJsonTypeInfoResolver = - TypeDefinition.Get(TypeDefinitionEnum.InterfaceDefinition, Namespace + ".Metadata", "IJsonTypeInfoResolver"); + TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "JsonSourceGenerationOptionsAttribute" + ); + + public static readonly ITypeDefinition IJsonTypeInfoResolver = TypeDefinition.Get( + TypeDefinitionEnum.InterfaceDefinition, + Namespace + ".Metadata", + "IJsonTypeInfoResolver" + ); } } - public static class DependencyModules { - public static class Attributes { + public static class DependencyModules + { + public static class Attributes + { public const string Namespace = "DependencyModules.Runtime.Attributes"; - public static readonly ITypeDefinition DependencyModuleAttribute = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "DependencyModuleAttribute"); - - public static readonly ITypeDefinition TransientServiceAttribute = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "TransientServiceAttribute"); - - public static readonly ITypeDefinition ScopedServiceAttribute = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "ScopedServiceAttribute"); - - public static readonly ITypeDefinition SingletonServiceAttribute = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "SingletonServiceAttribute"); - - public static readonly ITypeDefinition DecoratorAttribute = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "DecoratorAttribute"); - - public static readonly ITypeDefinition DecorateAttribute = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "DecorateAttribute"); - - public static readonly ITypeDefinition InterceptAttribute = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "InterceptAttribute"); - - public static readonly ITypeDefinition CrossWireServiceAttribute = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "CrossWireServiceAttribute"); - + public static readonly ITypeDefinition DependencyModuleAttribute = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "DependencyModuleAttribute" + ); + + public static readonly ITypeDefinition TransientServiceAttribute = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "TransientServiceAttribute" + ); + + public static readonly ITypeDefinition ScopedServiceAttribute = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "ScopedServiceAttribute" + ); + + public static readonly ITypeDefinition SingletonServiceAttribute = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "SingletonServiceAttribute" + ); + + public static readonly ITypeDefinition DecoratorAttribute = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "DecoratorAttribute" + ); + + public static readonly ITypeDefinition DecorateAttribute = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "DecorateAttribute" + ); + + public static readonly ITypeDefinition InterceptAttribute = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "InterceptAttribute" + ); + + public static readonly ITypeDefinition CrossWireServiceAttribute = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "CrossWireServiceAttribute" + ); } /// @@ -77,66 +125,101 @@ public static class Attributes { /// these at run time — but DM0021 is about two of them written together, and that is a /// question only a compiler can answer before the test runs. /// - public static class Testing { + public static class Testing + { public const string Namespace = "DependencyModules.Testing.Attributes"; - public static readonly ITypeDefinition MockAttribute = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "MockAttribute"); - - public static readonly ITypeDefinition TestExportAttribute = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "TestExportAttribute"); + public static readonly ITypeDefinition MockAttribute = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "MockAttribute" + ); + + public static readonly ITypeDefinition TestExportAttribute = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "TestExportAttribute" + ); } - public static class Interfaces { + public static class Interfaces + { public const string Namespace = "DependencyModules.Runtime.Interfaces"; // ReSharper disable once InconsistentNaming - public static readonly ITypeDefinition IDependencyModule = - TypeDefinition.Get(TypeDefinitionEnum.InterfaceDefinition, Namespace, "IDependencyModule"); + public static readonly ITypeDefinition IDependencyModule = TypeDefinition.Get( + TypeDefinitionEnum.InterfaceDefinition, + Namespace, + "IDependencyModule" + ); // ReSharper disable once InconsistentNaming - public static readonly ITypeDefinition IDependencyModuleProvider = - TypeDefinition.Get(TypeDefinitionEnum.InterfaceDefinition, Namespace, "IDependencyModuleProvider"); + public static readonly ITypeDefinition IDependencyModuleProvider = TypeDefinition.Get( + TypeDefinitionEnum.InterfaceDefinition, + Namespace, + "IDependencyModuleProvider" + ); // ReSharper disable once InconsistentNaming - public static readonly ITypeDefinition IModuleEnvironment = - TypeDefinition.Get(TypeDefinitionEnum.InterfaceDefinition, Namespace, "IModuleEnvironment"); + public static readonly ITypeDefinition IModuleEnvironment = TypeDefinition.Get( + TypeDefinitionEnum.InterfaceDefinition, + Namespace, + "IModuleEnvironment" + ); } - public static class Features { - + public static class Features + { public const string Namespace = "DependencyModules.Runtime.Features"; // ReSharper disable once InconsistentNaming - public static readonly ITypeDefinition IFeatureApplicator = - TypeDefinition.Get(TypeDefinitionEnum.InterfaceDefinition, Namespace, "IFeatureApplicator"); + public static readonly ITypeDefinition IFeatureApplicator = TypeDefinition.Get( + TypeDefinitionEnum.InterfaceDefinition, + Namespace, + "IFeatureApplicator" + ); // ReSharper disable once InconsistentNaming public static readonly ITypeDefinition IDependencyModuleApplicatorProvider = - TypeDefinition.Get(TypeDefinitionEnum.InterfaceDefinition, Namespace, "IDependencyModuleApplicatorProvider"); - - // ReSharper disable once InconsistentNaming - public static readonly ITypeDefinition FeatureApplicator = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "FeatureApplicator"); + TypeDefinition.Get( + TypeDefinitionEnum.InterfaceDefinition, + Namespace, + "IDependencyModuleApplicatorProvider" + ); + // ReSharper disable once InconsistentNaming + public static readonly ITypeDefinition FeatureApplicator = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "FeatureApplicator" + ); } - - public static class Helpers { + public static class Helpers + { public const string Namespace = "DependencyModules.Runtime.Helpers"; - public static readonly ITypeDefinition DecoratorHelper = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "DecoratorHelper"); + public static readonly ITypeDefinition DecoratorHelper = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "DecoratorHelper" + ); - public static readonly ITypeDefinition DecoratorRegistration = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "DecoratorRegistration"); + public static readonly ITypeDefinition DecoratorRegistration = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "DecoratorRegistration" + ); /// /// The tests a generated environment condition calls. Invoked statically, so the /// generated file needs no using. /// - public static readonly ITypeDefinition EnvironmentConditions = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "EnvironmentConditions"); + public static readonly ITypeDefinition EnvironmentConditions = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "EnvironmentConditions" + ); } /// @@ -146,14 +229,21 @@ public static class Helpers { /// The state and context types are generic over the member's result, so they are built per /// member rather than being constants. /// - public static class Interception { + public static class Interception + { public const string Namespace = "DependencyModules.Runtime.Interception"; - public static readonly ITypeDefinition CallerInfo = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "CallerInfo"); + public static readonly ITypeDefinition CallerInfo = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "CallerInfo" + ); - public static readonly ITypeDefinition NoResult = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, Namespace, "NoResult"); + public static readonly ITypeDefinition NoResult = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + Namespace, + "NoResult" + ); public static ITypeDefinition InvocationState(ITypeDefinition result) => Close("InvocationState", result); @@ -175,20 +265,36 @@ public static ITypeDefinition StreamInvocationContext(ITypeDefinition item) => private static ITypeDefinition Close(string name, ITypeDefinition argument) => new GenericTypeDefinition( - TypeDefinitionEnum.ClassDefinition, Namespace, name, new[] { argument }); + TypeDefinitionEnum.ClassDefinition, + Namespace, + name, + new[] { argument } + ); } } - public static class System { - public static readonly ITypeDefinition ArgumentOutOfRangeException = - TypeDefinition.Get(TypeDefinitionEnum.ClassDefinition, "System", "ArgumentOutOfRangeException"); + public static class System + { + public static readonly ITypeDefinition ArgumentOutOfRangeException = TypeDefinition.Get( + TypeDefinitionEnum.ClassDefinition, + "System", + "ArgumentOutOfRangeException" + ); public static ITypeDefinition ValueTask(ITypeDefinition result) => new GenericTypeDefinition( - TypeDefinitionEnum.ClassDefinition, "System.Threading.Tasks", "ValueTask", new[] { result }); + TypeDefinitionEnum.ClassDefinition, + "System.Threading.Tasks", + "ValueTask", + new[] { result } + ); public static ITypeDefinition AsyncEnumerable(ITypeDefinition item) => new GenericTypeDefinition( - TypeDefinitionEnum.InterfaceDefinition, "System.Collections.Generic", "IAsyncEnumerable", new[] { item }); + TypeDefinitionEnum.InterfaceDefinition, + "System.Collections.Generic", + "IAsyncEnumerable", + new[] { item } + ); } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/AttributeModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/AttributeModel.cs index c649bfb..731806e 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/AttributeModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/AttributeModel.cs @@ -4,16 +4,17 @@ namespace DependencyModules.SourceGenerator.Impl.Models; -public record AttributeArgumentValue(string Name, object? Value) { +public record AttributeArgumentValue(string Name, object? Value) +{ // Value arrives as object and may be an array, which the compiler-generated record equality // would compare by reference. See ModelEquality for why that breaks incremental caching. public virtual bool Equals(AttributeArgumentValue? other) => - other is not null && - Name == other.Name && - ModelEquality.ValueEquals(Value, other.Value); + other is not null && Name == other.Name && ModelEquality.ValueEquals(Value, other.Value); - public override int GetHashCode() { - unchecked { + public override int GetHashCode() + { + unchecked + { return Name.GetHashCode() * 31 + ModelEquality.ValueHashCode(Value); } } @@ -23,18 +24,21 @@ public record AttributeModel( ITypeDefinition TypeDefinition, IReadOnlyList Arguments, IReadOnlyList Properties, - IReadOnlyList ImplementedInterfaces) { - + IReadOnlyList ImplementedInterfaces +) +{ // Structural equality over the list members; see ModelEquality. public virtual bool Equals(AttributeModel? other) => - other is not null && - TypeDefinition.Equals(other.TypeDefinition) && - ModelEquality.ListEquals(Arguments, other.Arguments) && - ModelEquality.ListEquals(Properties, other.Properties) && - ModelEquality.ListEquals(ImplementedInterfaces, other.ImplementedInterfaces); - - public override int GetHashCode() { - unchecked { + other is not null + && TypeDefinition.Equals(other.TypeDefinition) + && ModelEquality.ListEquals(Arguments, other.Arguments) + && ModelEquality.ListEquals(Properties, other.Properties) + && ModelEquality.ListEquals(ImplementedInterfaces, other.ImplementedInterfaces); + + public override int GetHashCode() + { + unchecked + { var hash = TypeDefinition.GetHashCode(); hash = hash * 31 + ModelEquality.ListHashCode(Arguments); hash = hash * 31 + ModelEquality.ListHashCode(Properties); @@ -43,103 +47,123 @@ public override int GetHashCode() { } } - - public IList GetArguments() { + public IList GetArguments() + { var list = new List(); - foreach (var argument in Arguments) { + foreach (var argument in Arguments) + { IOutputComponent? outputComponent = null; - if (argument.Value is IOutputComponent component) { + if (argument.Value is IOutputComponent component) + { outputComponent = component; } - else if (argument.Value is Array arrayValue) { + else if (argument.Value is Array arrayValue) + { var collectionSyntax = new CollectionSyntaxDeclaration(); - foreach (var objectValue in arrayValue) { - if (objectValue is string stringValue) { + foreach (var objectValue in arrayValue) + { + if (objectValue is string stringValue) + { // Raw: CollectionSyntaxDeclaration quotes strings itself. Quoting here too // produced ""a"" under 1.x's naive QuoteString and "\"a\"" under 2.0's // escaping one - both wrong, only the second visibly so. collectionSyntax.Add(stringValue); } - else if (argument.Value is ITypeDefinition typeDefinition) { + else if (argument.Value is ITypeDefinition typeDefinition) + { outputComponent = SyntaxHelpers.TypeOf(typeDefinition); collectionSyntax.Add(outputComponent); } - else if (objectValue is not null) { + else if (objectValue is not null) + { collectionSyntax.Add(CodeOutputComponent.Get(objectValue)); } } - + outputComponent = collectionSyntax; } - else if (argument.Value is string stringValue) { - outputComponent = CodeOutputComponent.Get( - SyntaxHelpers.QuoteString(stringValue) - ); - } - else if (argument.Value is ITypeDefinition typeDefinition) { + else if (argument.Value is string stringValue) + { + outputComponent = CodeOutputComponent.Get(SyntaxHelpers.QuoteString(stringValue)); + } + else if (argument.Value is ITypeDefinition typeDefinition) + { outputComponent = SyntaxHelpers.TypeOf(typeDefinition); } - else if (argument.Value is not null) { + else if (argument.Value is not null) + { outputComponent = CodeOutputComponent.Get(argument.Value); } - if (outputComponent != null) { + if (outputComponent != null) + { list.Add(outputComponent); } } return list; } - - public IList PropertyValues() { + public IList PropertyValues() + { var list = new List(); - foreach (var argument in Properties) { - + foreach (var argument in Properties) + { IOutputComponent? outputComponent = null; - if (argument.Value is IOutputComponent component) { + if (argument.Value is IOutputComponent component) + { outputComponent = component; } - else if (argument.Value is Array arrayValue) { + else if (argument.Value is Array arrayValue) + { var collectionSyntax = new CollectionSyntaxDeclaration(); - foreach (var objectValue in arrayValue) { - if (objectValue is string stringValue) { + foreach (var objectValue in arrayValue) + { + if (objectValue is string stringValue) + { collectionSyntax.Add(stringValue); } - else if (argument.Value is ITypeDefinition typeDefinition) { + else if (argument.Value is ITypeDefinition typeDefinition) + { outputComponent = SyntaxHelpers.TypeOf(typeDefinition); collectionSyntax.Add(outputComponent); } - else if (objectValue is not null) { + else if (objectValue is not null) + { collectionSyntax.Add(CodeOutputComponent.Get(objectValue)); } } - - outputComponent = collectionSyntax; + + outputComponent = collectionSyntax; } - else if (argument.Value is string stringValue) { + else if (argument.Value is string stringValue) + { outputComponent = CodeOutputComponent.Get( SyntaxHelpers.QuoteString(stringValue.Trim('"')) ); } - else if (argument.Value is ITypeDefinition typeDefinition) { + else if (argument.Value is ITypeDefinition typeDefinition) + { outputComponent = SyntaxHelpers.TypeOf(typeDefinition); } - else if (argument.Value is not null) { - + else if (argument.Value is not null) + { outputComponent = CodeOutputComponent.Get(argument.Value); } - - if (outputComponent != null) { + + if (outputComponent != null) + { list.Add( new WrapStatement( CodeOutputComponent.Get(" = "), CodeOutputComponent.Get(argument.Name), - outputComponent)); + outputComponent + ) + ); } } @@ -149,4 +173,5 @@ public IList PropertyValues() { public record AttributeClassInfo( ConstructorInfoModel ConstructorInfo, - IReadOnlyList Properties); \ No newline at end of file + IReadOnlyList Properties +); diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/ConstructorInfoModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/ConstructorInfoModel.cs index 4ab6086..dd1f4be 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/ConstructorInfoModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/ConstructorInfoModel.cs @@ -1,10 +1,10 @@ namespace DependencyModules.SourceGenerator.Impl.Models; -public record ConstructorInfoModel(IReadOnlyList Parameters) { - +public record ConstructorInfoModel(IReadOnlyList Parameters) +{ // Structural equality over Parameters; see ModelEquality. public virtual bool Equals(ConstructorInfoModel? other) => other is not null && ModelEquality.ListEquals(Parameters, other.Parameters); public override int GetHashCode() => ModelEquality.ListHashCode(Parameters); -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/DecoratorModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/DecoratorModel.cs index 642937d..7913802 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/DecoratorModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/DecoratorModel.cs @@ -43,20 +43,19 @@ public record DecoratorModel( ConstructorInfoModel? Constructor = null, int InnerParameterIndex = -1, bool TypeParametersMatchService = true, - /// /// The one implementation this decorator wraps, or null to wrap every registration of the /// service — which is the default and what a decorator declared against an interface means. /// ITypeDefinition? Implementation = null, - /// /// Where the decorator was declared, so DM0007 and DM0013 can point at it rather than at the /// project. Null for a decorator declared through [Decorate] on a module, which names two types /// and has no declaration of its own to point at. /// - LocationModel? Location = null) { - + LocationModel? Location = null +) +{ /// /// Whether the decorator can be constructed by generated code. /// @@ -87,8 +86,8 @@ public record DecoratorModel( /// mode this generator is built never to produce. /// public bool HasUnboundServiceType => - ServiceType is GenericTypeDefinition generic && - generic.TypeArguments.Any(argument => string.IsNullOrEmpty(argument.Name)); + ServiceType is GenericTypeDefinition generic + && generic.TypeArguments.Any(argument => string.IsNullOrEmpty(argument.Name)); /// /// Sentinel for a syntax node that carried the attribute but produced no usable model, matching @@ -98,7 +97,8 @@ ServiceType is GenericTypeDefinition generic && TypeDefinition.Get("", "Ignore"), TypeDefinition.Get("", "Ignore"), 0, - null); + null + ); public bool IsIgnored => ReferenceEquals(this, Ignore); } @@ -107,39 +107,45 @@ ServiceType is GenericTypeDefinition generic && /// Equality for the incremental pipeline. Every field affects generated output, so all of them are /// compared; missing one would serve stale output after an edit to it. /// -public class DecoratorModelComparer : IEqualityComparer { - - public bool Equals(DecoratorModel? x, DecoratorModel? y) { - if (ReferenceEquals(x, y)) { +public class DecoratorModelComparer : IEqualityComparer +{ + public bool Equals(DecoratorModel? x, DecoratorModel? y) + { + if (ReferenceEquals(x, y)) + { return true; } - if (x is null || y is null) { + if (x is null || y is null) + { return false; } - return x.Order == y.Order && - x.ServiceType.Equals(y.ServiceType) && - x.DecoratorType.Equals(y.DecoratorType) && - Equals(x.Realm, y.Realm) && - // Decides which registration is wrapped, so leaving it out would serve the previous - // emission when only Implementation changed. - Equals(x.Implementation, y.Implementation) && - x.InnerParameterIndex == y.InnerParameterIndex && - x.TypeParametersMatchService == y.TypeParametersMatchService && - Equals(x.Constructor, y.Constructor) && - ConditionsEqual(x.Conditions, y.Conditions); + return x.Order == y.Order + && x.ServiceType.Equals(y.ServiceType) + && x.DecoratorType.Equals(y.DecoratorType) + && Equals(x.Realm, y.Realm) + && + // Decides which registration is wrapped, so leaving it out would serve the previous + // emission when only Implementation changed. + Equals(x.Implementation, y.Implementation) + && x.InnerParameterIndex == y.InnerParameterIndex + && x.TypeParametersMatchService == y.TypeParametersMatchService + && Equals(x.Constructor, y.Constructor) + && ConditionsEqual(x.Conditions, y.Conditions); } // Structural rather than by reference: two runs build separate lists, so comparing references // would miss the cache on every keystroke and re-emit every decorator. private static bool ConditionsEqual( IReadOnlyList? x, - IReadOnlyList? y) => - (x?.Count ?? 0) == 0 && (y?.Count ?? 0) == 0 || ModelEquality.ListEquals(x, y); + IReadOnlyList? y + ) => (x?.Count ?? 0) == 0 && (y?.Count ?? 0) == 0 || ModelEquality.ListEquals(x, y); - public int GetHashCode(DecoratorModel obj) { - unchecked { + public int GetHashCode(DecoratorModel obj) + { + unchecked + { var hash = obj.ServiceType.GetHashCode(); hash = hash * 31 + obj.DecoratorType.GetHashCode(); hash = hash * 31 + obj.Order; diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/DependencyModuleConfigurationModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/DependencyModuleConfigurationModel.cs index fb4327e..351da7f 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/DependencyModuleConfigurationModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/DependencyModuleConfigurationModel.cs @@ -2,7 +2,8 @@ namespace DependencyModules.SourceGenerator.Impl.Models; -public enum LogOutputLevel { +public enum LogOutputLevel +{ Debug = 1, Info = 2, Warning = 3, @@ -27,27 +28,33 @@ public record DependencyModuleConfigurationModel( BraceStyle GeneratedCodeStyle = BraceStyle.Allman ); -public class DependencyModuleConfigurationModelComparer : - IEqualityComparer { - - public bool Equals(DependencyModuleConfigurationModel? x, DependencyModuleConfigurationModel? y) { - if (ReferenceEquals(x, y)) return true; - if (x is null) return false; - if (y is null) return false; - if (x.GetType() != y.GetType()) return false; - return x.RegistrationType == y.RegistrationType && - x.RegisterSourceGenerator == y.RegisterSourceGenerator && - x.RootNamespace == y.RootNamespace && - x.ProjectDir == y.ProjectDir && - x.LogOutputFolder == y.LogOutputFolder && - x.AutoGenerateEntry == y.AutoGenerateEntry && - x.LogOutputLevel == y.LogOutputLevel && - x.GenerateFactories == y.GenerateFactories && - x.ExcludeGeneratedCodeFromCoverage == y.ExcludeGeneratedCodeFromCoverage && - x.GeneratedCodeStyle == y.GeneratedCodeStyle; +public class DependencyModuleConfigurationModelComparer + : IEqualityComparer +{ + public bool Equals(DependencyModuleConfigurationModel? x, DependencyModuleConfigurationModel? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null) + return false; + if (y is null) + return false; + if (x.GetType() != y.GetType()) + return false; + return x.RegistrationType == y.RegistrationType + && x.RegisterSourceGenerator == y.RegisterSourceGenerator + && x.RootNamespace == y.RootNamespace + && x.ProjectDir == y.ProjectDir + && x.LogOutputFolder == y.LogOutputFolder + && x.AutoGenerateEntry == y.AutoGenerateEntry + && x.LogOutputLevel == y.LogOutputLevel + && x.GenerateFactories == y.GenerateFactories + && x.ExcludeGeneratedCodeFromCoverage == y.ExcludeGeneratedCodeFromCoverage + && x.GeneratedCodeStyle == y.GeneratedCodeStyle; } - public int GetHashCode(DependencyModuleConfigurationModel obj) { + public int GetHashCode(DependencyModuleConfigurationModel obj) + { return obj.GetHashCode(); } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/DiagnosticReporter.cs b/src/DependencyModules.SourceGenerator.Impl/Models/DiagnosticReporter.cs index ffb8c70..5d83e3f 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/DiagnosticReporter.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/DiagnosticReporter.cs @@ -21,14 +21,16 @@ namespace DependencyModules.SourceGenerator.Impl.Models; /// first lets a caller skip building a message it is about to discard. /// /// -public sealed class DiagnosticReporter { +public sealed class DiagnosticReporter +{ private readonly Action? _sink; private readonly SyntaxTreeLookup _lookup; /// A reporter that discards everything. public static readonly DiagnosticReporter Silent = new(null, SyntaxTreeLookup.None); - public DiagnosticReporter(Action? sink, SyntaxTreeLookup lookup) { + public DiagnosticReporter(Action? sink, SyntaxTreeLookup lookup) + { _sink = sink; _lookup = lookup; } @@ -36,22 +38,36 @@ public DiagnosticReporter(Action? sink, SyntaxTreeLookup lookup) { /// True when nothing is listening, so there is no point composing a message. public bool IsSilent => _sink == null; - public void Report(DiagnosticDescriptor descriptor, LocationModel? location, params object?[] messageArgs) { - if (_sink == null) { + public void Report( + DiagnosticDescriptor descriptor, + LocationModel? location, + params object?[] messageArgs + ) + { + if (_sink == null) + { return; } - _sink(Diagnostic.Create( - descriptor, - location?.ToLocationOrNone(_lookup) ?? Location.None, - messageArgs)); + _sink( + Diagnostic.Create( + descriptor, + location?.ToLocationOrNone(_lookup) ?? Location.None, + messageArgs + ) + ); } /// /// Reports at a location that is already resolved — one read straight from syntax rather than /// carried through a model. /// - public void Report(DiagnosticDescriptor descriptor, Location location, params object?[] messageArgs) { + public void Report( + DiagnosticDescriptor descriptor, + Location location, + params object?[] messageArgs + ) + { _sink?.Invoke(Diagnostic.Create(descriptor, location, messageArgs)); } diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/EnvironmentConditionModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/EnvironmentConditionModel.cs index 386b8d8..5aa92c5 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/EnvironmentConditionModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/EnvironmentConditionModel.cs @@ -3,7 +3,8 @@ namespace DependencyModules.SourceGenerator.Impl.Models; /// /// What an environment condition tests. /// -public enum EnvironmentConditionKind { +public enum EnvironmentConditionKind +{ /// /// The environment's name, against one or more accepted names. /// @@ -41,18 +42,21 @@ public record EnvironmentConditionModel( EnvironmentConditionKind Kind, bool Negate, string? Key, - IReadOnlyList Values) { - + IReadOnlyList Values +) +{ // Structural equality over Values; see ModelEquality. public virtual bool Equals(EnvironmentConditionModel? other) => - other is not null && - Kind == other.Kind && - Negate == other.Negate && - Key == other.Key && - ModelEquality.ListEquals(Values, other.Values); + other is not null + && Kind == other.Kind + && Negate == other.Negate + && Key == other.Key + && ModelEquality.ListEquals(Values, other.Values); - public override int GetHashCode() { - unchecked { + public override int GetHashCode() + { + unchecked + { var hash = (int)Kind; hash = hash * 31 + Negate.GetHashCode(); hash = hash * 31 + (Key?.GetHashCode() ?? 0); diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/IClassModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/IClassModel.cs index 23d226e..cdc601d 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/IClassModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/IClassModel.cs @@ -2,10 +2,11 @@ namespace DependencyModules.SourceGenerator.Impl.Models; -public interface IClassModel { +public interface IClassModel +{ ITypeDefinition ClassType { get; } - + IReadOnlyList Parameters { get; } IReadOnlyList PropertyInfoModels { get; } IReadOnlyList AttributeModels { get; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/InterceptedMemberKinds.cs b/src/DependencyModules.SourceGenerator.Impl/Models/InterceptedMemberKinds.cs index 7b088a3..700d5e1 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/InterceptedMemberKinds.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/InterceptedMemberKinds.cs @@ -9,11 +9,12 @@ namespace DependencyModules.SourceGenerator.Impl.Models; /// namespace rather than as a type. /// [Flags] -public enum InterceptedMemberKinds { +public enum InterceptedMemberKinds +{ None = 0, Methods = 1, Properties = 2, Indexers = 4, Events = 8, - All = Methods | Properties | Indexers | Events + All = Methods | Properties | Indexers | Events, } diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/InterceptorModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/InterceptorModel.cs index c1a0de1..ad2aff8 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/InterceptorModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/InterceptorModel.cs @@ -11,14 +11,15 @@ namespace DependencyModules.SourceGenerator.Impl.Models; /// The return type is known at compile time, so the right shape is emitted per member instead of /// being sniffed at run time. /// -public enum ReturnShape { +public enum ReturnShape +{ Void, Value, Task, TaskOfValue, ValueTask, ValueTaskOfValue, - AsyncEnumerable + AsyncEnumerable, } /// @@ -29,10 +30,11 @@ public enum ReturnShape { /// one has nowhere to await inside a sync member. A stream is not awaitable at all, and wrapping it /// as a plain value would time the construction of the iterator rather than the work. /// -public enum InterceptorKind { +public enum InterceptorKind +{ Sync, Async, - Stream + Stream, } /// @@ -48,14 +50,15 @@ public record InterceptionRefusal(string Message); /// An accessor is invoked by its syntax, not by its name: the CLR calls it get_Count and /// reports it that way, but the call has to be written as Count. /// -public enum AccessorForm { +public enum AccessorForm +{ Method, PropertyGet, PropertySet, IndexerGet, IndexerSet, EventAdd, - EventRemove + EventRemove, } /// @@ -83,7 +86,8 @@ public record InterceptedParameterModel( string Identifier, ITypeDefinition Type, string? DefaultValue, - bool IsParams = false); + bool IsParams = false +); /// /// One type parameter of an intercepted member, with the constraints the wrapper has to repeat. @@ -114,22 +118,25 @@ public record TypeParameterModel( string Name, string? Primary, IReadOnlyList ConstraintTypes, - bool DefaultConstructor) { - + bool DefaultConstructor +) +{ /// /// Structural equality over the constraint types, which the compiler-generated version compares /// by reference — two identical models built on consecutive runs would never match, and the /// incremental cache would miss on every keystroke. /// public virtual bool Equals(TypeParameterModel? other) => - other is not null && - Name == other.Name && - Primary == other.Primary && - DefaultConstructor == other.DefaultConstructor && - ModelEquality.ListEquals(ConstraintTypes, other.ConstraintTypes); - - public override int GetHashCode() { - unchecked { + other is not null + && Name == other.Name + && Primary == other.Primary + && DefaultConstructor == other.DefaultConstructor + && ModelEquality.ListEquals(ConstraintTypes, other.ConstraintTypes); + + public override int GetHashCode() + { + unchecked + { var hash = Name.GetHashCode(); hash = hash * 31 + (Primary?.GetHashCode() ?? 0); @@ -157,17 +164,19 @@ public record InterceptorTypeModel( /// /// The lifetime this interceptor is registered with, from the [Intercept] that named it. /// - ServiceLifestyle Lifestyle = ServiceLifestyle.Singleton) { - + ServiceLifestyle Lifestyle = ServiceLifestyle.Singleton +) +{ /// /// Whether this interceptor can be placed around a member of the given kind. /// public bool CanServe(InterceptorKind kind) => - kind switch { + kind switch + { InterceptorKind.Sync => Sync, InterceptorKind.Async => Async, InterceptorKind.Stream => Stream, - _ => false + _ => false, }; } @@ -204,49 +213,57 @@ public record InterceptedMemberModel( IReadOnlyList Parameters, IReadOnlyList TypeParameters, ReturnShape ReturnShape, - /// /// The declaration left out by [Intercept].Members. Still forwarded — the wrapper /// implements the whole interface — but not through the interceptor chain. /// - bool Excluded = false) { - + bool Excluded = false +) +{ /// /// The interceptor interface this member has to be routed through. /// public InterceptorKind Kind => - ReturnShape switch { - ReturnShape.Task or ReturnShape.TaskOfValue or - ReturnShape.ValueTask or ReturnShape.ValueTaskOfValue => InterceptorKind.Async, + ReturnShape switch + { + ReturnShape.Task + or ReturnShape.TaskOfValue + or ReturnShape.ValueTask + or ReturnShape.ValueTaskOfValue => InterceptorKind.Async, ReturnShape.AsyncEnumerable => InterceptorKind.Stream, - _ => InterceptorKind.Sync + _ => InterceptorKind.Sync, }; /// /// Structural equality, because the compiler-generated version compares the two lists by /// reference and two identical models built on consecutive runs would never match. /// - public virtual bool Equals(InterceptedMemberModel? other) { - if (ReferenceEquals(this, other)) { + public virtual bool Equals(InterceptedMemberModel? other) + { + if (ReferenceEquals(this, other)) + { return true; } - if (other is null) { + if (other is null) + { return false; } - return Name == other.Name && - Identifier == other.Identifier && - Form == other.Form && - Equals(ReturnType, other.ReturnType) && - ResultType.Equals(other.ResultType) && - ReturnShape == other.ReturnShape && - ModelEquality.ListEquals(Parameters, other.Parameters) && - ModelEquality.ListEquals(TypeParameters, other.TypeParameters); + return Name == other.Name + && Identifier == other.Identifier + && Form == other.Form + && Equals(ReturnType, other.ReturnType) + && ResultType.Equals(other.ResultType) + && ReturnShape == other.ReturnShape + && ModelEquality.ListEquals(Parameters, other.Parameters) + && ModelEquality.ListEquals(TypeParameters, other.TypeParameters); } - public override int GetHashCode() { - unchecked { + public override int GetHashCode() + { + unchecked + { var hash = Name.GetHashCode(); hash = hash * 31 + Identifier.GetHashCode(); @@ -269,11 +286,12 @@ public override int GetHashCode() { /// Separate from the pipeline units because they do not line up: a property is one declaration and /// up to two of them, each with its own state class and its own caller. /// -public enum DeclarationKind { +public enum DeclarationKind +{ Method, Property, Indexer, - Event + Event, } /// @@ -301,27 +319,33 @@ public record InterceptedDeclarationModel( ITypeDefinition? Type, IReadOnlyList Indices, int First, - int Second) { - - public virtual bool Equals(InterceptedDeclarationModel? other) { - if (ReferenceEquals(this, other)) { + int Second +) +{ + public virtual bool Equals(InterceptedDeclarationModel? other) + { + if (ReferenceEquals(this, other)) + { return true; } - if (other is null) { + if (other is null) + { return false; } - return Kind == other.Kind && - Identifier == other.Identifier && - Equals(Type, other.Type) && - First == other.First && - Second == other.Second && - ModelEquality.ListEquals(Indices, other.Indices); + return Kind == other.Kind + && Identifier == other.Identifier + && Equals(Type, other.Type) + && First == other.First + && Second == other.Second + && ModelEquality.ListEquals(Indices, other.Indices); } - public override int GetHashCode() { - unchecked { + public override int GetHashCode() + { + unchecked + { var hash = (int)Kind; hash = hash * 31 + Identifier.GetHashCode(); @@ -360,13 +384,13 @@ public record InterceptorModel( InterceptionRefusal? Refusal = null, IReadOnlyList? TypeParameters = null, ITypeDefinition? Realm = null, - /// /// Where the intercepted class was declared, so DM0008 and DM0015 can point at it rather than /// at the project. /// - LocationModel? Location = null) { - + LocationModel? Location = null +) +{ /// /// Whether the intercepted service is an open generic, and so registers as an implementation type /// rather than through a factory. @@ -383,14 +407,19 @@ public record InterceptorModel( Array.Empty(), Array.Empty(), Array.Empty(), - 0); + 0 + ); /// /// A model that generates nothing and explains why, so an unsupported shape produces a /// diagnostic rather than a wrapper that does not compile. /// public static InterceptorModel Refused(string message, LocationModel? location = null) => - Ignore with { Refusal = new InterceptionRefusal(message), Location = location }; + Ignore with + { + Refusal = new InterceptionRefusal(message), + Location = location, + }; public bool IsIgnored => ReferenceEquals(this, Ignore); } @@ -398,34 +427,40 @@ public static InterceptorModel Refused(string message, LocationModel? location = /// /// Equality for the incremental pipeline. Every field affects the generated wrapper. /// -public class InterceptorModelComparer : IEqualityComparer { - - public bool Equals(InterceptorModel? x, InterceptorModel? y) { - if (ReferenceEquals(x, y)) { +public class InterceptorModelComparer : IEqualityComparer +{ + public bool Equals(InterceptorModel? x, InterceptorModel? y) + { + if (ReferenceEquals(x, y)) + { return true; } - if (x is null || y is null) { + if (x is null || y is null) + { return false; } - return x.Order == y.Order && - x.ServiceType.Equals(y.ServiceType) && - x.ImplementationType.Equals(y.ImplementationType) && - // Realm decides which module emits the applicator, so leaving it out meant editing - // only `Realm = typeof(X)` compared equal to the model before the edit, hit the - // cache and re-emitted nothing. DecoratorModelComparer and ServiceModelComparer both - // compare theirs; this was the odd one out. - Equals(x.Realm, y.Realm) && - Equals(x.Refusal, y.Refusal) && - ModelEquality.ListEquals(x.Interceptors, y.Interceptors) && - ModelEquality.ListEquals(x.Members, y.Members) && - ModelEquality.ListEquals(x.Declarations, y.Declarations) && - ModelEquality.ListEquals(x.TypeParameters, y.TypeParameters); + return x.Order == y.Order + && x.ServiceType.Equals(y.ServiceType) + && x.ImplementationType.Equals(y.ImplementationType) + && + // Realm decides which module emits the applicator, so leaving it out meant editing + // only `Realm = typeof(X)` compared equal to the model before the edit, hit the + // cache and re-emitted nothing. DecoratorModelComparer and ServiceModelComparer both + // compare theirs; this was the odd one out. + Equals(x.Realm, y.Realm) + && Equals(x.Refusal, y.Refusal) + && ModelEquality.ListEquals(x.Interceptors, y.Interceptors) + && ModelEquality.ListEquals(x.Members, y.Members) + && ModelEquality.ListEquals(x.Declarations, y.Declarations) + && ModelEquality.ListEquals(x.TypeParameters, y.TypeParameters); } - public int GetHashCode(InterceptorModel obj) { - unchecked { + public int GetHashCode(InterceptorModel obj) + { + unchecked + { var hash = obj.ServiceType.GetHashCode(); hash = hash * 31 + obj.ImplementationType.GetHashCode(); diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/LocationModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/LocationModel.cs index 06d7649..3651cf2 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/LocationModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/LocationModel.cs @@ -21,8 +21,9 @@ public record LocationModel( int StartLine, int StartCharacter, int EndLine, - int EndCharacter) { - + int EndCharacter +) +{ /// /// Rebuilds a reportable location. Safe to call only outside the incremental pipeline. /// @@ -39,7 +40,9 @@ public Location ToLocation() => new TextSpan(SpanStart, SpanLength), new LinePositionSpan( new LinePosition(StartLine, StartCharacter), - new LinePosition(EndLine, EndCharacter))); + new LinePosition(EndLine, EndCharacter) + ) + ); /// /// Rebuilds a reportable location against the syntax tree it came from, so that @@ -53,10 +56,12 @@ public Location ToLocation() => /// a replayed model can carry a span from a previous version of the file. Out of bounds, /// Location.Create throws, and a generator that throws reports nothing at all. /// - public Location ToLocation(SyntaxTreeLookup lookup) { + public Location ToLocation(SyntaxTreeLookup lookup) + { var tree = lookup.Find(FilePath); - if (tree == null) { + if (tree == null) + { return ToLocation(); } @@ -85,13 +90,15 @@ public Location ToLocation(SyntaxTreeLookup lookup) { /// /// private static SyntaxNodeOrToken NarrowToName(SyntaxNode node) => - node switch { + node switch + { TypeDeclarationSyntax type => type.Identifier, MethodDeclarationSyntax method => method.Identifier, - _ => node + _ => node, }; - private static LocationModel From(SyntaxNodeOrToken nodeOrToken) { + private static LocationModel From(SyntaxNodeOrToken nodeOrToken) + { var span = nodeOrToken.GetLocation()!.GetLineSpan(); return new LocationModel( @@ -101,7 +108,8 @@ private static LocationModel From(SyntaxNodeOrToken nodeOrToken) { span.StartLinePosition.Line, span.StartLinePosition.Character, span.EndLinePosition.Line, - span.EndLinePosition.Character); + span.EndLinePosition.Character + ); } /// diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/MethodInfoModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/MethodInfoModel.cs index 1c72a17..58087ca 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/MethodInfoModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/MethodInfoModel.cs @@ -2,7 +2,8 @@ namespace DependencyModules.SourceGenerator.Impl.Models; -public enum AccessModifier { +public enum AccessModifier +{ PublicModifier, PrivateModifier, ProtectedModifier, @@ -14,4 +15,5 @@ public record MethodInfoModel( string MethodName, ITypeDefinition ReturnType, IReadOnlyList Parameters, - IReadOnlyList GenericArguments); \ No newline at end of file + IReadOnlyList GenericArguments +); diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/ModelEquality.cs b/src/DependencyModules.SourceGenerator.Impl/Models/ModelEquality.cs index b28a732..a8ae207 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/ModelEquality.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/ModelEquality.cs @@ -11,19 +11,24 @@ namespace DependencyModules.SourceGenerator.Impl.Models; /// structurally identical models built on consecutive runs would never compare equal, and the /// generator would regenerate everything on every keystroke. /// -internal static class ModelEquality { - - public static bool ListEquals(IReadOnlyList? x, IReadOnlyList? y) { - if (ReferenceEquals(x, y)) { +internal static class ModelEquality +{ + public static bool ListEquals(IReadOnlyList? x, IReadOnlyList? y) + { + if (ReferenceEquals(x, y)) + { return true; } - if (x is null || y is null || x.Count != y.Count) { + if (x is null || y is null || x.Count != y.Count) + { return false; } - for (var i = 0; i < x.Count; i++) { - if (!EqualityComparer.Default.Equals(x[i], y[i])) { + for (var i = 0; i < x.Count; i++) + { + if (!EqualityComparer.Default.Equals(x[i], y[i])) + { return false; } } @@ -31,15 +36,18 @@ public static bool ListEquals(IReadOnlyList? x, IReadOnlyList? y) { return true; } - public static int ListHashCode(IReadOnlyList? list) { - if (list is null) { + public static int ListHashCode(IReadOnlyList? list) + { + if (list is null) + { return 0; } - - unchecked { + unchecked + { var hash = 19; - for (var i = 0; i < list.Count; i++) { + for (var i = 0; i < list.Count; i++) + { hash = hash * 31 + (list[i]?.GetHashCode() ?? 0); } @@ -50,32 +58,40 @@ public static int ListHashCode(IReadOnlyList? list) { /// /// Compares attribute argument values, which arrive as object and may be arrays. /// - public static bool ValueEquals(object? x, object? y) { - if (Equals(x, y)) { + public static bool ValueEquals(object? x, object? y) + { + if (Equals(x, y)) + { return true; } - if (x is string || y is string) { + if (x is string || y is string) + { return false; } - if (x is IEnumerable xs && y is IEnumerable ys) { + if (x is IEnumerable xs && y is IEnumerable ys) + { var xEnumerator = xs.GetEnumerator(); var yEnumerator = ys.GetEnumerator(); - while (true) { + while (true) + { var xMoved = xEnumerator.MoveNext(); var yMoved = yEnumerator.MoveNext(); - if (xMoved != yMoved) { + if (xMoved != yMoved) + { return false; } - if (!xMoved) { + if (!xMoved) + { return true; } - if (!ValueEquals(xEnumerator.Current, yEnumerator.Current)) { + if (!ValueEquals(xEnumerator.Current, yEnumerator.Current)) + { return false; } } @@ -84,20 +100,26 @@ public static bool ValueEquals(object? x, object? y) { return false; } - public static int ValueHashCode(object? value) { - if (value is null) { + public static int ValueHashCode(object? value) + { + if (value is null) + { return 0; } - if (value is string) { + if (value is string) + { return value.GetHashCode(); } - if (value is IEnumerable enumerable) { - unchecked { + if (value is IEnumerable enumerable) + { + unchecked + { var hash = 19; - foreach (var item in enumerable) { + foreach (var item in enumerable) + { hash = hash * 31 + ValueHashCode(item); } diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/ModuleEntryPointModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/ModuleEntryPointModel.cs index 01afeb2..1ee7cb1 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/ModuleEntryPointModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/ModuleEntryPointModel.cs @@ -3,7 +3,8 @@ namespace DependencyModules.SourceGenerator.Impl.Models; [Flags] -public enum ModuleEntryPointFeatures { +public enum ModuleEntryPointFeatures +{ None, AutoGenerateModule = 1, OnlyRealm = 2, @@ -40,15 +41,20 @@ public record ModuleEntryPointModel( IReadOnlyList PropertyInfoModels, IReadOnlyList AttributeModels, IReadOnlyList AdditionalModules, - IReadOnlyList Features) : IClassModel { + IReadOnlyList Features +) : IClassModel +{ public ITypeDefinition ClassType => EntryPointType; } -public class ModuleEntryPointModelComparer : IEqualityComparer { - - public bool Equals(ModuleEntryPointModel? x, ModuleEntryPointModel? y) { - if (x is null && y is null) return true; - if (x is null || y is null) return false; +public class ModuleEntryPointModelComparer : IEqualityComparer +{ + public bool Equals(ModuleEntryPointModel? x, ModuleEntryPointModel? y) + { + if (x is null && y is null) + return true; + if (x is null || y is null) + return false; // Location is deliberately absent. It is carried so diagnostics can point at the // declaration, but it shifts whenever anything above the module is edited — including a @@ -56,29 +62,33 @@ public bool Equals(ModuleEntryPointModel? x, ModuleEntryPointModel? y) { // module on a keystroke that changed nothing (IncrementalGenerationTests covers exactly // that). The cost is that a diagnostic replayed from cache can sit a line or two off until // the next semantic edit, which is the cheaper of the two mistakes. - return x.FileLocation == y.FileLocation && - x.EntryPointType.Equals(y.EntryPointType) && - x.ModuleFeatures == y.ModuleFeatures && - x.UseMethod == y.UseMethod && - x.RegistrationType == y.RegistrationType && - x.GenerateAttribute == y.GenerateAttribute && - x.RegisterJsonSerializers == y.RegisterJsonSerializers && - x.GenerateFactories == y.GenerateFactories && - x.Parameters.SequenceEqual(y.Parameters) && - x.PropertyInfoModels.SequenceEqual(y.PropertyInfoModels) && - x.Features.SequenceEqual(y.Features) && - x.AttributeModels.SequenceEqual(y.AttributeModels) && - x.AdditionalModules.SequenceEqual(y.AdditionalModules); + return x.FileLocation == y.FileLocation + && x.EntryPointType.Equals(y.EntryPointType) + && x.ModuleFeatures == y.ModuleFeatures + && x.UseMethod == y.UseMethod + && x.RegistrationType == y.RegistrationType + && x.GenerateAttribute == y.GenerateAttribute + && x.RegisterJsonSerializers == y.RegisterJsonSerializers + && x.GenerateFactories == y.GenerateFactories + && x.Parameters.SequenceEqual(y.Parameters) + && x.PropertyInfoModels.SequenceEqual(y.PropertyInfoModels) + && x.Features.SequenceEqual(y.Features) + && x.AttributeModels.SequenceEqual(y.AttributeModels) + && x.AdditionalModules.SequenceEqual(y.AdditionalModules); } - public int GetHashCode(ModuleEntryPointModel obj) { - unchecked { + public int GetHashCode(ModuleEntryPointModel obj) + { + unchecked + { var hash = 17; hash = hash * 31 + obj.EntryPointType.GetHashCode(); - if (obj.RegistrationType.HasValue) { + if (obj.RegistrationType.HasValue) + { hash = hash * 31 + obj.RegistrationType.Value.GetHashCode(); } - if (obj.GenerateAttribute.HasValue) { + if (obj.GenerateAttribute.HasValue) + { hash = hash * 31 + obj.GenerateAttribute.Value.GetHashCode(); } hash = hash * 31 + obj.FileLocation.GetHashCode(); @@ -90,15 +100,18 @@ public int GetHashCode(ModuleEntryPointModel obj) { hash = GetListHashCode(obj.PropertyInfoModels, hash); hash = GetListHashCode(obj.AttributeModels, hash); hash = GetListHashCode(obj.Features, hash); - + return hash; } } - private int GetListHashCode(IEnumerable list, int hashSeed) { + private int GetListHashCode(IEnumerable list, int hashSeed) + { int hash = hashSeed; - unchecked { - foreach (var obj in list) { + unchecked + { + foreach (var obj in list) + { hash = hash * 31 + (obj?.GetHashCode() ?? 1); } } @@ -106,14 +119,17 @@ private int GetListHashCode(IEnumerable list, int hashSeed) { } } -public static class ModuleEntryPointModelExtensions { - public static string UniqueId(this ModuleEntryPointModel model) { +public static class ModuleEntryPointModelExtensions +{ + public static string UniqueId(this ModuleEntryPointModel model) + { var count = 0; - foreach (var charValue in model.EntryPointType.Namespace + "." + model.EntryPointType.Name) { + foreach (var charValue in model.EntryPointType.Namespace + "." + model.EntryPointType.Name) + { count += charValue; } - + return count.ToString(); } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/ParameterInfoModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/ParameterInfoModel.cs index 9504e4c..911bf39 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/ParameterInfoModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/ParameterInfoModel.cs @@ -6,18 +6,21 @@ public record ParameterInfoModel( string ParameterName, ITypeDefinition ParameterType, object? DefaultValue, - IReadOnlyList Attributes) { - + IReadOnlyList Attributes +) +{ // Structural equality over Attributes; see ModelEquality. public virtual bool Equals(ParameterInfoModel? other) => - other is not null && - ParameterName == other.ParameterName && - ParameterType.Equals(other.ParameterType) && - Equals(DefaultValue, other.DefaultValue) && - ModelEquality.ListEquals(Attributes, other.Attributes); + other is not null + && ParameterName == other.ParameterName + && ParameterType.Equals(other.ParameterType) + && Equals(DefaultValue, other.DefaultValue) + && ModelEquality.ListEquals(Attributes, other.Attributes); - public override int GetHashCode() { - unchecked { + public override int GetHashCode() + { + unchecked + { var hash = ParameterName.GetHashCode(); hash = hash * 31 + ParameterType.GetHashCode(); hash = hash * 31 + (DefaultValue?.GetHashCode() ?? 0); @@ -25,4 +28,4 @@ public override int GetHashCode() { return hash; } } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/PropertyInfoModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/PropertyInfoModel.cs index 6695036..2ff94b2 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/PropertyInfoModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/PropertyInfoModel.cs @@ -17,8 +17,9 @@ public record PropertyInfoModel( string PropertyName, bool IsReadOnly, bool IsStatic, - bool IsVisibleToAttribute) { - + bool IsVisibleToAttribute +) +{ /// /// Whether this property is carried across to the generated attribute as a module parameter. /// diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/ServiceModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/ServiceModel.cs index c03f3dc..5b91888 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/ServiceModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/ServiceModel.cs @@ -2,22 +2,25 @@ namespace DependencyModules.SourceGenerator.Impl.Models; -public enum ServiceLifestyle { +public enum ServiceLifestyle +{ Transient, Scoped, - Singleton + Singleton, } -public enum RegistrationType { +public enum RegistrationType +{ Add, Try, TryEnumerable, - Replace + Replace, } [Flags] -public enum RegistrationFeature { - None= 0, +public enum RegistrationFeature +{ + None = 0, AutoRegisterSourceGenerator = 1, /// @@ -54,17 +57,20 @@ public enum RegistrationFeature { public record ServiceFactoryModel( ITypeDefinition TypeDefinition, string MethodName, - IReadOnlyList Parameters) { - + IReadOnlyList Parameters +) +{ // Structural equality over Parameters; see ModelEquality. public virtual bool Equals(ServiceFactoryModel? other) => - other is not null && - TypeDefinition.Equals(other.TypeDefinition) && - MethodName == other.MethodName && - ModelEquality.ListEquals(Parameters, other.Parameters); + other is not null + && TypeDefinition.Equals(other.TypeDefinition) + && MethodName == other.MethodName + && ModelEquality.ListEquals(Parameters, other.Parameters); - public override int GetHashCode() { - unchecked { + public override int GetHashCode() + { + unchecked + { var hash = TypeDefinition.GetHashCode(); hash = hash * 31 + MethodName.GetHashCode(); hash = hash * 31 + ModelEquality.ListHashCode(Parameters); @@ -81,16 +87,18 @@ public record ServiceRegistrationModel( object? Key = null, bool? CrossWire = false, IReadOnlyList? Namespaces = null, - /// /// Where this registration sits among the others for the same service, lowest first. Decides the /// sequence an IEnumerable<T> dependency sees, and therefore which one a single /// resolve returns. /// - int Order = 0); + int Order = 0 +); public delegate IOutputComponent? FactoryOutputDelegate( - ServiceModel serviceModel, ServiceRegistrationModel registrationModel); + ServiceModel serviceModel, + ServiceRegistrationModel registrationModel +); public record ServiceModel( ITypeDefinition ImplementationType, @@ -106,14 +114,15 @@ public record ServiceModel( /// writer emits one guard around the lot. /// IReadOnlyList? Conditions = null, - /// /// Where the implementation was declared, so a diagnostic about it can point at the class /// rather than at the project. Deliberately absent from /// — it is not part of what makes two models the same /// registration, and including it would miss the incremental cache on an edit above the class. /// - LocationModel? Location = null) { + LocationModel? Location = null +) +{ public static ServiceModel Ignore = new ServiceModel( TypeDefinition.Get("", "Ignore"), null, @@ -121,35 +130,51 @@ public record ServiceModel( null, Array.Empty(), RegistrationFeature.None - ); + ); } -public class ServiceModelComparer : IEqualityComparer { - - public bool Equals(ServiceModel? x, ServiceModel? y) { - if (ReferenceEquals(x, y)) return true; - if (x is null) return false; - if (y is null) return false; - if (x.GetType() != y.GetType()) return false; - return - x.Features == y.Features && - x.ImplementationType.Equals(y.ImplementationType) && - CompareConstructor(x.Constructor, y.Constructor) && - CompareRegistrations(x.Registrations, y.Registrations) && - CompareFactory(x.Factory, y.Factory) && - CompareFactoryOutput(x.FactoryOutput, y.FactoryOutput) && - CompareConditions(x.Conditions, y.Conditions); +public class ServiceModelComparer : IEqualityComparer +{ + public bool Equals(ServiceModel? x, ServiceModel? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null) + return false; + if (y is null) + return false; + if (x.GetType() != y.GetType()) + return false; + return x.Features == y.Features + && x.ImplementationType.Equals(y.ImplementationType) + && CompareConstructor(x.Constructor, y.Constructor) + && CompareRegistrations(x.Registrations, y.Registrations) + && CompareFactory(x.Factory, y.Factory) + && CompareFactoryOutput(x.FactoryOutput, y.FactoryOutput) + && CompareConditions(x.Conditions, y.Conditions); } - private bool CompareConstructor(ConstructorInfoModel? xConstructor, ConstructorInfoModel? yConstructor) { - if (xConstructor is null && yConstructor is null) return true; - if (xConstructor is null || yConstructor is null) return false; + private bool CompareConstructor( + ConstructorInfoModel? xConstructor, + ConstructorInfoModel? yConstructor + ) + { + if (xConstructor is null && yConstructor is null) + return true; + if (xConstructor is null || yConstructor is null) + return false; return xConstructor.Parameters.SequenceEqual(yConstructor.Parameters); } - private bool CompareFactoryOutput(FactoryOutputDelegate? xFactoryOutput, FactoryOutputDelegate? yFactoryOutput) { - if (xFactoryOutput is null && yFactoryOutput is null) return true; - if (xFactoryOutput is null || yFactoryOutput is null) return false; + private bool CompareFactoryOutput( + FactoryOutputDelegate? xFactoryOutput, + FactoryOutputDelegate? yFactoryOutput + ) + { + if (xFactoryOutput is null && yFactoryOutput is null) + return true; + if (xFactoryOutput is null || yFactoryOutput is null) + return false; return true; } @@ -159,35 +184,50 @@ private bool CompareFactoryOutput(FactoryOutputDelegate? xFactoryOutput, Factory /// private bool CompareConditions( IReadOnlyList? xConditions, - IReadOnlyList? yConditions) { - if ((xConditions?.Count ?? 0) == 0 && (yConditions?.Count ?? 0) == 0) { + IReadOnlyList? yConditions + ) + { + if ((xConditions?.Count ?? 0) == 0 && (yConditions?.Count ?? 0) == 0) + { return true; } return ModelEquality.ListEquals(xConditions, yConditions); } - private bool CompareFactory(ServiceFactoryModel? xFactory, ServiceFactoryModel? yFactory) { - if (xFactory is null && yFactory is null) return true; - if (xFactory is null) return false; - if (yFactory is null) return false; + private bool CompareFactory(ServiceFactoryModel? xFactory, ServiceFactoryModel? yFactory) + { + if (xFactory is null && yFactory is null) + return true; + if (xFactory is null) + return false; + if (yFactory is null) + return false; return xFactory.Equals(yFactory); } - public int GetHashCode(ServiceModel obj) { + public int GetHashCode(ServiceModel obj) + { return obj.ImplementationType.GetHashCode(); } - private bool CompareRegistrations(IReadOnlyList xRegistrations, IReadOnlyList yRegistrations) { - if (xRegistrations.Count != yRegistrations.Count) { + private bool CompareRegistrations( + IReadOnlyList xRegistrations, + IReadOnlyList yRegistrations + ) + { + if (xRegistrations.Count != yRegistrations.Count) + { return false; } - for (var i = 0; i < xRegistrations.Count; i++) { + for (var i = 0; i < xRegistrations.Count; i++) + { var x = xRegistrations[i]; var y = yRegistrations[i]; - if (!CompareRegistration(x, y)) { + if (!CompareRegistration(x, y)) + { return false; } } @@ -195,19 +235,27 @@ private bool CompareRegistrations(IReadOnlyList xRegis return true; } - private bool CompareRegistration(ServiceRegistrationModel x, ServiceRegistrationModel y) { - return x.ServiceType.Equals(y.ServiceType) && - x.Lifestyle == y.Lifestyle && - x.RegistrationType == y.RegistrationType && - CompareNamespaces(x.Namespaces, y.Namespaces) && - Equals(x.Realm, y.Realm) && - Equals(x.Key, y.Key); + private bool CompareRegistration(ServiceRegistrationModel x, ServiceRegistrationModel y) + { + return x.ServiceType.Equals(y.ServiceType) + && x.Lifestyle == y.Lifestyle + && x.RegistrationType == y.RegistrationType + && CompareNamespaces(x.Namespaces, y.Namespaces) + && Equals(x.Realm, y.Realm) + && Equals(x.Key, y.Key); } - private bool CompareNamespaces(IReadOnlyList? xNamespaces, IReadOnlyList? yNamespaces) { - if (xNamespaces is null && yNamespaces is null) return true; - if (xNamespaces is null || yNamespaces is null) return false; - if (xNamespaces.Count != yNamespaces.Count) return false; + private bool CompareNamespaces( + IReadOnlyList? xNamespaces, + IReadOnlyList? yNamespaces + ) + { + if (xNamespaces is null && yNamespaces is null) + return true; + if (xNamespaces is null || yNamespaces is null) + return false; + if (xNamespaces.Count != yNamespaces.Count) + return false; return xNamespaces.SequenceEqual(yNamespaces); } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/SyntaxTreeLookup.cs b/src/DependencyModules.SourceGenerator.Impl/Models/SyntaxTreeLookup.cs index 4728364..bfeccc6 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/SyntaxTreeLookup.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/SyntaxTreeLookup.cs @@ -15,29 +15,36 @@ namespace DependencyModules.SourceGenerator.Impl.Models; /// with nothing. The map is built on first use, so a run that reports no diagnostics — the ordinary /// case — never walks the trees at all. /// -public sealed class SyntaxTreeLookup { +public sealed class SyntaxTreeLookup +{ private readonly Compilation? _compilation; private Dictionary? _byPath; /// A lookup that finds nothing, for callers with no compilation to hand. public static readonly SyntaxTreeLookup None = new(null); - public SyntaxTreeLookup(Compilation? compilation) { + public SyntaxTreeLookup(Compilation? compilation) + { _compilation = compilation; } - public SyntaxTree? Find(string filePath) { - if (_compilation == null || string.IsNullOrEmpty(filePath)) { + public SyntaxTree? Find(string filePath) + { + if (_compilation == null || string.IsNullOrEmpty(filePath)) + { return null; } - if (_byPath == null) { + if (_byPath == null) + { _byPath = new Dictionary(); - foreach (var tree in _compilation.SyntaxTrees) { + foreach (var tree in _compilation.SyntaxTrees) + { // First wins. Two trees can share a path — a linked file compiled into more than // one target — and either answers the question a location asks. - if (!string.IsNullOrEmpty(tree.FilePath) && !_byPath.ContainsKey(tree.FilePath)) { + if (!string.IsNullOrEmpty(tree.FilePath) && !_byPath.ContainsKey(tree.FilePath)) + { _byPath.Add(tree.FilePath, tree); } } diff --git a/src/DependencyModules.SourceGenerator.Impl/ModuleAttributeWriter.cs b/src/DependencyModules.SourceGenerator.Impl/ModuleAttributeWriter.cs index 9623f61..9efb1ed 100644 --- a/src/DependencyModules.SourceGenerator.Impl/ModuleAttributeWriter.cs +++ b/src/DependencyModules.SourceGenerator.Impl/ModuleAttributeWriter.cs @@ -6,23 +6,34 @@ namespace DependencyModules.SourceGenerator.Impl; -public class ModuleAttributeWriter : BaseAttributeWriter { - - protected override void CustomImplementation(IConstructContainer container, ClassDefinition attributeClass, ModuleEntryPointModel model) { +public class ModuleAttributeWriter : BaseAttributeWriter +{ + protected override void CustomImplementation( + IConstructContainer container, + ClassDefinition attributeClass, + ModuleEntryPointModel model + ) + { var method = attributeClass.AddMethod("GetModule"); method.SetReturnType(KnownTypes.DependencyModules.Interfaces.IDependencyModule); - var newModule = - method.Assign( - New(model.EntryPointType, - attributeClass.Fields.Select(f => f.Instance).OfType().ToArray())).ToVar("newModule"); - - foreach (var propertyInfoModel in model.PropertyInfoModels) { - if (!propertyInfoModel.IsModuleParameter) { + var newModule = method + .Assign( + New( + model.EntryPointType, + attributeClass.Fields.Select(f => f.Instance).OfType().ToArray() + ) + ) + .ToVar("newModule"); + + foreach (var propertyInfoModel in model.PropertyInfoModels) + { + if (!propertyInfoModel.IsModuleParameter) + { continue; } - + // Guarded whatever the declared nullability. An attribute property is null until // somebody assigns it, and `?` is an annotation rather than a runtime fact — so gating // the guard on it meant `public string Label { get; set; } = "default";` had its @@ -35,9 +46,11 @@ protected override void CustomImplementation(IConstructContainer container, Clas // BaseAttributeWriter already wraps the class in a pragma for it. var block = method.If(NotEquals(propertyInfoModel.PropertyName, Null())); - block.Assign(propertyInfoModel.PropertyName).To(newModule.Property(propertyInfoModel.PropertyName)); + block + .Assign(propertyInfoModel.PropertyName) + .To(newModule.Property(propertyInfoModel.PropertyName)); } method.Return(newModule); } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/ModuleEntryPointDiagnostics.cs b/src/DependencyModules.SourceGenerator.Impl/ModuleEntryPointDiagnostics.cs index a2c5427..b24495d 100644 --- a/src/DependencyModules.SourceGenerator.Impl/ModuleEntryPointDiagnostics.cs +++ b/src/DependencyModules.SourceGenerator.Impl/ModuleEntryPointDiagnostics.cs @@ -30,8 +30,8 @@ namespace DependencyModules.SourceGenerator.Impl; /// rather than repeating them. /// /// -public static class ModuleEntryPointDiagnostics { - +public static class ModuleEntryPointDiagnostics +{ /// /// Generating into a non-partial type produces CS0260 against the developer's own declaration, /// which describes the symptom rather than the fix. @@ -56,15 +56,22 @@ public static bool IsNestedInType(ModuleEntryPointModel model) => /// has already answered the question this asks about. /// public static bool ReliesOnGeneratedEquality(ModuleEntryPointModel model) => - model.PropertyInfoModels.Any(p => p.IsModuleParameter) && - model.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.ShouldImplementEquals); + model.PropertyInfoModels.Any(p => p.IsModuleParameter) + && model.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.ShouldImplementEquals); public static void Report( SourceProductionContext context, - (ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Models, - Compilation Compilation) input) { - - if (input.Models.Length == 0) { + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Models, + Compilation Compilation + ) input + ) + { + if (input.Models.Length == 0) + { return; } @@ -74,22 +81,40 @@ public static void Report( var lookup = new SyntaxTreeLookup(input.Compilation); - foreach (var entryPointModel in entryPointList) { + foreach (var entryPointModel in entryPointList) + { context.CancellationToken.ThrowIfCancellationRequested(); - if (IsNotPartial(entryPointModel)) { - Report(context, DependencyModuleDiagnostics.ModuleMustBePartial, entryPointModel, lookup); + if (IsNotPartial(entryPointModel)) + { + Report( + context, + DependencyModuleDiagnostics.ModuleMustBePartial, + entryPointModel, + lookup + ); continue; } - if (IsNestedInType(entryPointModel)) { - Report(context, DependencyModuleDiagnostics.ModuleCannotBeNested, entryPointModel, lookup); + if (IsNestedInType(entryPointModel)) + { + Report( + context, + DependencyModuleDiagnostics.ModuleCannotBeNested, + entryPointModel, + lookup + ); continue; } - if (ReliesOnGeneratedEquality(entryPointModel)) { - Report(context, DependencyModuleDiagnostics.ModuleWithPropertiesShouldImplementEquals, - entryPointModel, lookup); + if (ReliesOnGeneratedEquality(entryPointModel)) + { + Report( + context, + DependencyModuleDiagnostics.ModuleWithPropertiesShouldImplementEquals, + entryPointModel, + lookup + ); } } } @@ -98,10 +123,13 @@ private static void Report( SourceProductionContext context, DiagnosticDescriptor descriptor, ModuleEntryPointModel entryPointModel, - SyntaxTreeLookup lookup) => + SyntaxTreeLookup lookup + ) => context.ReportDiagnostic( Diagnostic.Create( descriptor, entryPointModel.Location.ToLocationOrNone(lookup), - entryPointModel.EntryPointType.Name)); + entryPointModel.EntryPointType.Name + ) + ); } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/AttributeModelCollector.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/AttributeModelCollector.cs index 64919c3..5d54489 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/AttributeModelCollector.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/AttributeModelCollector.cs @@ -21,8 +21,8 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// compilation — against 33 ms for the one visit of every syntax node this shape replaced. /// /// -public static class AttributeModelCollector { - +public static class AttributeModelCollector +{ /// /// Collects one model per declaration carrying any of . /// @@ -36,27 +36,34 @@ public static IncrementalValueProvider> Collect( ITypeDefinition[] attributeTypes, Func generate, IEqualityComparer comparer, - TModel ignored) { - + TModel ignored + ) + { IncrementalValueProvider>? merged = null; // ForAttributeWithMetadataName takes a single name, so an attribute set needs one provider // each. They share the index, so several indexed lookups still cost far less than one visit // of every syntax node. - foreach (var attributeType in attributeTypes) { + foreach (var attributeType in attributeTypes) + { var owner = attributeType; - var provider = context.SyntaxProvider.ForAttributeWithMetadataName( + var provider = context + .SyntaxProvider.ForAttributeWithMetadataName( MetadataName(owner), static (node, _) => node is MemberDeclarationSyntax, (syntaxContext, cancellation) => - Owned(syntaxContext, cancellation, attributeTypes, owner, generate, ignored)) + Owned(syntaxContext, cancellation, attributeTypes, owner, generate, ignored) + ) .WithComparer(comparer) .Collect(); - merged = merged == null - ? provider - : merged.Value.Combine(provider).Select(static (pair, _) => pair.Left.AddRange(pair.Right)); + merged = + merged == null + ? provider + : merged + .Value.Combine(provider) + .Select(static (pair, _) => pair.Left.AddRange(pair.Right)); } return merged!.Value; @@ -78,12 +85,15 @@ private static TModel Owned( ITypeDefinition[] attributeTypes, ITypeDefinition owner, Func generate, - TModel ignored) { - + TModel ignored + ) + { var present = context.TargetSymbol.GetAttributes(); - foreach (var candidate in attributeTypes) { - if (!IsPresent(present, candidate)) { + foreach (var candidate in attributeTypes) + { + if (!IsPresent(present, candidate)) + { continue; } @@ -93,11 +103,16 @@ private static TModel Owned( return generate(context, cancellation); } - private static bool IsPresent(ImmutableArray present, ITypeDefinition candidate) { - foreach (var attribute in present) { - if (attribute.AttributeClass is { } attributeClass && - attributeClass.Name == candidate.Name && - NamespaceOf(attributeClass) == candidate.Namespace) { + private static bool IsPresent(ImmutableArray present, ITypeDefinition candidate) + { + foreach (var attribute in present) + { + if ( + attribute.AttributeClass is { } attributeClass + && attributeClass.Name == candidate.Name + && NamespaceOf(attributeClass) == candidate.Namespace + ) + { return true; } } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/AttributeModelHelper.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/AttributeModelHelper.cs index fe2a0f1..5da88ef 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/AttributeModelHelper.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/AttributeModelHelper.cs @@ -6,28 +6,29 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; -public static class AttributeModelHelper { - +public static class AttributeModelHelper +{ public static IReadOnlyList GetAttributeModels( SyntaxTransformContext context, SyntaxNode node, CancellationToken cancellationToken, - Func? filter = null) { + Func? filter = null + ) + { SyntaxList? attributeLists = null; - if (node is BaseParameterSyntax parameterSyntax) { + if (node is BaseParameterSyntax parameterSyntax) + { attributeLists = parameterSyntax.AttributeLists; } - else if (node is MemberDeclarationSyntax memberDeclarationSyntax) { + else if (node is MemberDeclarationSyntax memberDeclarationSyntax) + { attributeLists = memberDeclarationSyntax.AttributeLists; } - if (attributeLists != null) { - var results = GetAttributes( - context, - attributeLists.Value, - cancellationToken, - filter); + if (attributeLists != null) + { + var results = GetAttributes(context, attributeLists.Value, cancellationToken, filter); return results.ToList(); } @@ -37,36 +38,45 @@ public static IReadOnlyList GetAttributeModels( public static AttributeClassInfo GetAttributeClassInfo( SyntaxTransformContext context, - CancellationToken cancellationToken) { + CancellationToken cancellationToken + ) + { var propertyList = new List(); - foreach (var syntax in - context.Node.DescendantNodes()) { + foreach (var syntax in context.Node.DescendantNodes()) + { cancellationToken.ThrowIfCancellationRequested(); - if (syntax is PropertyDeclarationSyntax propertyDeclarationSyntax) { - var setter = - propertyDeclarationSyntax.AccessorList?.Accessors.FirstOrDefault( - x => x.IsKind(SyntaxKind.SetAccessorDeclaration)); - - var propertyType = - propertyDeclarationSyntax.Type.GetTypeDefinition(context); - - if (propertyType != null) { - propertyList.Add(new PropertyInfoModel(propertyType, - propertyDeclarationSyntax.Identifier.ToString(), - setter == null, - propertyDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword)), - IsVisibleToAttribute(propertyDeclarationSyntax.Modifiers) - )); + if (syntax is PropertyDeclarationSyntax propertyDeclarationSyntax) + { + var setter = propertyDeclarationSyntax.AccessorList?.Accessors.FirstOrDefault(x => + x.IsKind(SyntaxKind.SetAccessorDeclaration) + ); + + var propertyType = propertyDeclarationSyntax.Type.GetTypeDefinition(context); + + if (propertyType != null) + { + propertyList.Add( + new PropertyInfoModel( + propertyType, + propertyDeclarationSyntax.Identifier.ToString(), + setter == null, + propertyDeclarationSyntax.Modifiers.Any(m => + m.IsKind(SyntaxKind.StaticKeyword) + ), + IsVisibleToAttribute(propertyDeclarationSyntax.Modifiers) + ) + ); } } } return new AttributeClassInfo( - ServiceModelUtility.GetConstructorInfo(context, context.Node, cancellationToken) ?? - new ConstructorInfoModel(Array.Empty()), - propertyList); + ServiceModelUtility.GetConstructorInfo(context, context.Node, cancellationToken) + ?? new ConstructorInfoModel(Array.Empty()), + propertyList + ); } /// @@ -81,17 +91,20 @@ public static AttributeClassInfo GetAttributeClassInfo( /// access modifier at all is private by default, which is the case most likely to be written by /// accident and was the one that produced generated code that would not compile. /// - private static bool IsVisibleToAttribute(SyntaxTokenList modifiers) { + private static bool IsVisibleToAttribute(SyntaxTokenList modifiers) + { var isPrivate = modifiers.Any(m => m.IsKind(SyntaxKind.PrivateKeyword)); var isProtected = modifiers.Any(m => m.IsKind(SyntaxKind.ProtectedKeyword)); var isInternal = modifiers.Any(m => m.IsKind(SyntaxKind.InternalKeyword)); var isPublic = modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)); - if (isPrivate) { + if (isPrivate) + { return false; } - if (isProtected) { + if (isProtected) + { return isInternal; } @@ -102,15 +115,21 @@ public static IEnumerable GetAttributes( SyntaxTransformContext context, SyntaxList attributeListSyntax, CancellationToken cancellationToken, - Func? filter = null) { - foreach (var attributeList in attributeListSyntax) { - foreach (var attribute in attributeList.Attributes) { + Func? filter = null + ) + { + foreach (var attributeList in attributeListSyntax) + { + foreach (var attribute in attributeList.Attributes) + { cancellationToken.ThrowIfCancellationRequested(); var operation = ModelExtensions.GetTypeInfo(context.SemanticModel, attribute); - if (filter?.Invoke(attribute) ?? true) { - if (operation.Type != null) { + if (filter?.Invoke(attribute) ?? true) + { + if (operation.Type != null) + { yield return InternalAttributeModel(context, attribute, operation); } } @@ -118,89 +137,107 @@ public static IEnumerable GetAttributes( } } - public static AttributeModel? GetAttribute(SyntaxTransformContext context, AttributeSyntax attribute) { + public static AttributeModel? GetAttribute( + SyntaxTransformContext context, + AttributeSyntax attribute + ) + { var operation = ModelExtensions.GetTypeInfo(context.SemanticModel, attribute); - return operation.Type != null ? InternalAttributeModel(context, attribute, operation) : null; + return operation.Type != null + ? InternalAttributeModel(context, attribute, operation) + : null; } private static AttributeModel InternalAttributeModel( - SyntaxTransformContext context, AttributeSyntax attribute, TypeInfo operation) { + SyntaxTransformContext context, + AttributeSyntax attribute, + TypeInfo operation + ) + { var arguments = new List(); var properties = new List(); - if (attribute.ArgumentList != null) { - foreach (var attributeArgumentSyntax in - attribute.ArgumentList.Arguments) { - var operationValue = context.SemanticModel.GetOperation(attributeArgumentSyntax.Expression); + if (attribute.ArgumentList != null) + { + foreach (var attributeArgumentSyntax in attribute.ArgumentList.Arguments) + { + var operationValue = context.SemanticModel.GetOperation( + attributeArgumentSyntax.Expression + ); - if (operationValue == null) { + if (operationValue == null) + { continue; } var constantValue = GetOperationValue(context, operationValue); - if (attributeArgumentSyntax.NameColon != null) { + if (attributeArgumentSyntax.NameColon != null) + { arguments.Add( new AttributeArgumentValue( attributeArgumentSyntax.NameColon.ToString(), constantValue - )); + ) + ); } - else if (attributeArgumentSyntax.NameEquals != null) { - var name = - attributeArgumentSyntax.NameEquals.Name.ToString().Replace("=", "").Trim(); - - properties.Add( - new AttributeArgumentValue( - name, - constantValue - )); + else if (attributeArgumentSyntax.NameEquals != null) + { + var name = attributeArgumentSyntax + .NameEquals.Name.ToString() + .Replace("=", "") + .Trim(); + + properties.Add(new AttributeArgumentValue(name, constantValue)); } - else { - - arguments.Add( - new AttributeArgumentValue( - "", - constantValue - )); + else + { + arguments.Add(new AttributeArgumentValue("", constantValue)); } } } - if (operation.Type == null) { + if (operation.Type == null) + { throw new ArgumentNullException("operation.Type", "The type argument cannot be null."); } var type = operation.Type.GetTypeDefinition(); - if (!type.Name.EndsWith("Attribute")) { + if (!type.Name.EndsWith("Attribute")) + { type = TypeDefinition.Get(type.Namespace, type.Name + "Attribute"); } - return new AttributeModel(type, - arguments, - properties, - GetInterfaces(context, attribute)); + return new AttributeModel(type, arguments, properties, GetInterfaces(context, attribute)); } - private static object? GetOperationValue(SyntaxTransformContext context, IOperation operationValue) { - if (operationValue.ConstantValue.HasValue) { + private static object? GetOperationValue( + SyntaxTransformContext context, + IOperation operationValue + ) + { + if (operationValue.ConstantValue.HasValue) + { return operationValue.ConstantValue.Value; } return GetOperationValue(context, operationValue.Syntax); } - private static object GetOperationValue(SyntaxTransformContext context, SyntaxNode syntaxNode) { - - if (syntaxNode is CollectionExpressionSyntax collectionExpressionSyntax) { + private static object GetOperationValue(SyntaxTransformContext context, SyntaxNode syntaxNode) + { + if (syntaxNode is CollectionExpressionSyntax collectionExpressionSyntax) + { var collection = new List(); - foreach (var elementSyntax in collectionExpressionSyntax.Elements) { + foreach (var elementSyntax in collectionExpressionSyntax.Elements) + { var dec = elementSyntax.DescendantNodes().FirstOrDefault(); - if (dec != null) { + if (dec != null) + { collection.Add(GetOperationValue(context, dec)); } } @@ -208,14 +245,17 @@ private static object GetOperationValue(SyntaxTransformContext context, SyntaxNo return collection.ToArray(); } - if (syntaxNode is LiteralExpressionSyntax literalExpressionSyntax) { + if (syntaxNode is LiteralExpressionSyntax literalExpressionSyntax) + { return literalExpressionSyntax.Token.Value ?? "null"; } - if (syntaxNode is TypeOfExpressionSyntax typeOf) { + if (syntaxNode is TypeOfExpressionSyntax typeOf) + { var type = typeOf.Type.GetTypeDefinition(context); - if (type != null) { + if (type != null) + { // typeof(IRepo<>) binds to the unbound symbol, which carries the declaration's type // parameters as its arguments. Re-emitting that verbatim writes typeof(IRepo) // into the generated module, where T is not in scope — CS0246, in generated code, @@ -228,26 +268,32 @@ private static object GetOperationValue(SyntaxTransformContext context, SyntaxNo } private static IReadOnlyList GetInterfaces( - SyntaxTransformContext context, AttributeSyntax attribute) { + SyntaxTransformContext context, + AttributeSyntax attribute + ) + { var interfaces = new List(); - var symbol = - context.SemanticModel.GetTypeInfo(attribute); + var symbol = context.SemanticModel.GetTypeInfo(attribute); - if (symbol.Type is INamedTypeSymbol namespaceOrTypeSymbol) { - foreach (var interfaceSymbol in namespaceOrTypeSymbol.AllInterfaces) { + if (symbol.Type is INamedTypeSymbol namespaceOrTypeSymbol) + { + foreach (var interfaceSymbol in namespaceOrTypeSymbol.AllInterfaces) + { interfaces.Add(interfaceSymbol.GetTypeDefinition()); } } return interfaces; } + /// /// Whether the type was written as Foo<> rather than closed over anything. /// private static bool IsUnboundGeneric(TypeSyntax type) => - type is GenericNameSyntax generic && - generic.TypeArgumentList.Arguments.Count > 0 && - generic.TypeArgumentList.Arguments.All(argument => argument is OmittedTypeArgumentSyntax); - -} \ No newline at end of file + type is GenericNameSyntax generic + && generic.TypeArgumentList.Arguments.Count > 0 + && generic.TypeArgumentList.Arguments.All(argument => + argument is OmittedTypeArgumentSyntax + ); +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/AttributeTypeMatcher.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/AttributeTypeMatcher.cs index 14e50d9..6a6e214 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/AttributeTypeMatcher.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/AttributeTypeMatcher.cs @@ -26,8 +26,8 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// node in the compilation must not do. /// /// -public static class AttributeTypeMatcher { - +public static class AttributeTypeMatcher +{ /// /// Whether resolves to . /// @@ -40,11 +40,13 @@ public static bool Matches( SemanticModel semanticModel, AttributeSyntax attributeSyntax, ITypeDefinition attributeType, - CancellationToken cancellationToken) { - + CancellationToken cancellationToken + ) + { var symbol = Resolve(semanticModel, attributeSyntax, cancellationToken); - if (symbol == null) { + if (symbol == null) + { return MatchesAsWritten(attributeSyntax, attributeType); } @@ -60,30 +62,43 @@ public static bool Matches( /// argument list that does not match any overload still names the attribute unambiguously. /// private static INamedTypeSymbol? Resolve( - SemanticModel semanticModel, AttributeSyntax attributeSyntax, CancellationToken cancellationToken) { - + SemanticModel semanticModel, + AttributeSyntax attributeSyntax, + CancellationToken cancellationToken + ) + { var symbolInfo = semanticModel.GetSymbolInfo(attributeSyntax, cancellationToken); - if (symbolInfo.Symbol?.ContainingType is { } containingType) { + if (symbolInfo.Symbol?.ContainingType is { } containingType) + { return containingType; } - if (symbolInfo.CandidateSymbols.Length > 0 && - symbolInfo.CandidateSymbols[0].ContainingType is { } candidateType) { + if ( + symbolInfo.CandidateSymbols.Length > 0 + && symbolInfo.CandidateSymbols[0].ContainingType is { } candidateType + ) + { return candidateType; } - return semanticModel.GetTypeInfo(attributeSyntax, cancellationToken).Type as INamedTypeSymbol; + return semanticModel.GetTypeInfo(attributeSyntax, cancellationToken).Type + as INamedTypeSymbol; } /// /// The old comparison, kept only for the unresolvable case. /// - private static bool MatchesAsWritten(AttributeSyntax attributeSyntax, ITypeDefinition attributeType) { + private static bool MatchesAsWritten( + AttributeSyntax attributeSyntax, + ITypeDefinition attributeType + ) + { var written = attributeSyntax.Name.ToString(); var lastDot = written.LastIndexOf('.'); - if (lastDot >= 0) { + if (lastDot >= 0) + { written = written.Substring(lastDot + 1); } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/BaseAttributeWriter.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/BaseAttributeWriter.cs index 625eeb2..235e573 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/BaseAttributeWriter.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/BaseAttributeWriter.cs @@ -4,8 +4,11 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; -public abstract class BaseAttributeWriter where T : IClassModel { - public void CreateAttributeClass(IConstructContainer container, T model) { +public abstract class BaseAttributeWriter + where T : IClassModel +{ + public void CreateAttributeClass(IConstructContainer container, T model) + { var attributeClass = ConstructClassDefinition(container, model); AddClassTraits(attributeClass); @@ -13,73 +16,108 @@ public void CreateAttributeClass(IConstructContainer container, T model) { CreateConstructor(container, attributeClass, model); CreateProperties(container, attributeClass, model); - + CustomImplementation(container, attributeClass, model); } - private static void AddClassTraits(ClassDefinition attributeClass) { + private static void AddClassTraits(ClassDefinition attributeClass) + { attributeClass.EnableNullable(); attributeClass.WrapInPragma("CS0472"); // Fully qualified: generated code must compile regardless of the consumer's using // directives, and nothing else in this file causes a "using System;" to be emitted. attributeClass.AddLeadingTrait( new UsageAttributeComponent( - "[global::System.AttributeUsage(" + - "global::System.AttributeTargets.Class | " + - "global::System.AttributeTargets.Assembly | " + - "global::System.AttributeTargets.Method | " + - "global::System.AttributeTargets.Parameter, AllowMultiple = true)]")); + "[global::System.AttributeUsage(" + + "global::System.AttributeTargets.Class | " + + "global::System.AttributeTargets.Assembly | " + + "global::System.AttributeTargets.Method | " + + "global::System.AttributeTargets.Parameter, AllowMultiple = true)]" + ) + ); } - protected virtual void CustomImplementation(IConstructContainer container, ClassDefinition attributeClass, T model) { - - } + protected virtual void CustomImplementation( + IConstructContainer container, + ClassDefinition attributeClass, + T model + ) { } + + private static ClassDefinition ConstructClassDefinition(IConstructContainer container, T model) + { + var attributeClass = container.AddClass(model.ClassType.Name + "Attribute"); - private static ClassDefinition ConstructClassDefinition(IConstructContainer container, T model) { - var attributeClass = container.AddClass( model.ClassType.Name + "Attribute"); - attributeClass.Modifiers |= ComponentModifier.Public | ComponentModifier.Partial; - attributeClass.AddBaseType(TypeDefinition.Get("System","Attribute")); - attributeClass.AddBaseType(KnownTypes.DependencyModules.Interfaces.IDependencyModuleProvider); - + attributeClass.AddBaseType(TypeDefinition.Get("System", "Attribute")); + attributeClass.AddBaseType( + KnownTypes.DependencyModules.Interfaces.IDependencyModuleProvider + ); + return attributeClass; } - protected virtual void CreateProperties(IConstructContainer container, ClassDefinition attributeClass, T model) { - foreach (var propertyInfoModel in model.PropertyInfoModels) { - if (!propertyInfoModel.IsModuleParameter) { + protected virtual void CreateProperties( + IConstructContainer container, + ClassDefinition attributeClass, + T model + ) + { + foreach (var propertyInfoModel in model.PropertyInfoModels) + { + if (!propertyInfoModel.IsModuleParameter) + { continue; } var propertyType = propertyInfoModel.PropertyType; - if (propertyType.IsNullable) { - propertyType = TypeDefinition.Get(propertyType.TypeDefinitionEnum, propertyType.Namespace, propertyType.Name, propertyType.IsArray); + if (propertyType.IsNullable) + { + propertyType = TypeDefinition.Get( + propertyType.TypeDefinitionEnum, + propertyType.Namespace, + propertyType.Name, + propertyType.IsArray + ); } var property = attributeClass.AddProperty(propertyType, propertyInfoModel.PropertyName); var stringBuilder = new StringBuilder(); propertyType.WriteTypeName(stringBuilder, TypeOutputMode.Global); - - property.DefaultValue = - new WrapStatement(CodeOutputComponent.Get(stringBuilder.ToString()), "default(", ")!"); + + property.DefaultValue = new WrapStatement( + CodeOutputComponent.Get(stringBuilder.ToString()), + "default(", + ")!" + ); } } - protected virtual void CreateConstructor(IConstructContainer container, ClassDefinition attributeClass, T model) { - if (model.Parameters.Count > 0) { + protected virtual void CreateConstructor( + IConstructContainer container, + ClassDefinition attributeClass, + T model + ) + { + if (model.Parameters.Count > 0) + { var constructor = attributeClass.AddConstructor(); - foreach (var constructorParameter in model.Parameters) { + foreach (var constructorParameter in model.Parameters) + { var field = attributeClass.AddField( - constructorParameter.ParameterType, constructorParameter.ParameterName + "Field"); + constructorParameter.ParameterType, + constructorParameter.ParameterName + "Field" + ); - var parameter = - constructor.AddParameter(constructorParameter.ParameterType, constructorParameter.ParameterName); + var parameter = constructor.AddParameter( + constructorParameter.ParameterType, + constructorParameter.ParameterName + ); constructor.Assign(parameter).To(field.Name); } } } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/BaseMethodHelper.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/BaseMethodHelper.cs index a451b4a..115b52b 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/BaseMethodHelper.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/BaseMethodHelper.cs @@ -5,34 +5,50 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; -public static class BaseMethodHelper { +public static class BaseMethodHelper +{ public static IReadOnlyList GetMethodParameters( this BaseMethodDeclarationSyntax methodDeclarationSyntax, SyntaxTransformContext context, CancellationToken cancellationToken - ) { + ) + { var parameterList = methodDeclarationSyntax.ParameterList; return GetParameters(parameterList, context, cancellationToken); } - public static IReadOnlyList GetParameters(this ParameterListSyntax? parameterList, SyntaxTransformContext context, CancellationToken cancellationToken) { - if (parameterList == null) { + public static IReadOnlyList GetParameters( + this ParameterListSyntax? parameterList, + SyntaxTransformContext context, + CancellationToken cancellationToken + ) + { + if (parameterList == null) + { return Array.Empty(); } - + var list = new List(); - foreach (var parameterSyntax in parameterList.Parameters) { + foreach (var parameterSyntax in parameterList.Parameters) + { cancellationToken.ThrowIfCancellationRequested(); - list.Add(new ParameterInfoModel( - parameterSyntax.Identifier.ToString(), - parameterSyntax.Type?.GetTypeDefinition(context) ?? TypeDefinition.Get(typeof(object)), - null, - AttributeModelHelper.GetAttributeModels(context, parameterSyntax, cancellationToken) - )); + list.Add( + new ParameterInfoModel( + parameterSyntax.Identifier.ToString(), + parameterSyntax.Type?.GetTypeDefinition(context) + ?? TypeDefinition.Get(typeof(object)), + null, + AttributeModelHelper.GetAttributeModels( + context, + parameterSyntax, + cancellationToken + ) + ) + ); } return list; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/CollectionSyntaxDeclaration.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/CollectionSyntaxDeclaration.cs index 17de9db..403e59d 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/CollectionSyntaxDeclaration.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/CollectionSyntaxDeclaration.cs @@ -2,32 +2,41 @@ namespace DependencyModules.SourceGenerator.Impl; -public class CollectionSyntaxDeclaration : BaseOutputComponent { +public class CollectionSyntaxDeclaration : BaseOutputComponent +{ private List _items = new(); - public void Add(object item) { + public void Add(object item) + { _items.Add(item); } - protected override void WriteComponentOutput(IOutputContext outputContext) { + protected override void WriteComponentOutput(IOutputContext outputContext) + { outputContext.Write("["); var first = true; - foreach (var value in _items) { - if (first == false) { + foreach (var value in _items) + { + if (first == false) + { outputContext.Write(", "); } - else { + else + { first = false; } - - if (value is IOutputComponent component) { + + if (value is IOutputComponent component) + { component.WriteOutput(outputContext); } - else if (value is string str) { + else if (value is string str) + { outputContext.Write(SyntaxHelpers.QuoteString(str)); } - else { + else + { outputContext.Write(value.ToString()); } } @@ -35,14 +44,19 @@ protected override void WriteComponentOutput(IOutputContext outputContext) { outputContext.Write("]"); } - public override bool Equals(object? obj) { - if (obj is CollectionSyntaxDeclaration other) { - if (other._items.Count != _items.Count) { + public override bool Equals(object? obj) + { + if (obj is CollectionSyntaxDeclaration other) + { + if (other._items.Count != _items.Count) + { return false; } - for (var i = 0; i < _items.Count; i++) { - if (other._items[i].Equals(_items[i]) == false) { + for (var i = 0; i < _items.Count; i++) + { + if (other._items[i].Equals(_items[i]) == false) + { return false; } } @@ -53,7 +67,8 @@ public override bool Equals(object? obj) { return false; } - public override int GetHashCode() { + public override int GetHashCode() + { return base.GetHashCode(); } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/CompilerService.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/CompilerService.cs index dcde369..ed1a03e 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/CompilerService.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/CompilerService.cs @@ -1,3 +1,3 @@ namespace System.Runtime.CompilerServices; -internal static class IsExternalInit { } \ No newline at end of file +internal static class IsExternalInit { } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/ConstructorArgumentWriter.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/ConstructorArgumentWriter.cs index 9d778b1..218e45f 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/ConstructorArgumentWriter.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/ConstructorArgumentWriter.cs @@ -23,14 +23,15 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// The decorator writer duplicated this and got the second one wrong, which is why it now lives in /// one place. /// -public static class ConstructorArgumentWriter { - +public static class ConstructorArgumentWriter +{ /// /// Arguments for every parameter. /// public static object[] Arguments( - ParameterDefinition serviceProvider, IReadOnlyList parameters) => - Arguments(serviceProvider, parameters, -1, null); + ParameterDefinition serviceProvider, + IReadOnlyList parameters + ) => Arguments(serviceProvider, parameters, -1, null); /// /// Arguments for every parameter, with one supplied rather than resolved. @@ -44,12 +45,15 @@ public static object[] Arguments( ParameterDefinition serviceProvider, IReadOnlyList parameters, int suppliedIndex, - object? supplied) { - + object? supplied + ) + { var arguments = new List(parameters.Count); - for (var i = 0; i < parameters.Count; i++) { - if (i == suppliedIndex && supplied != null) { + for (var i = 0; i < parameters.Count; i++) + { + if (i == suppliedIndex && supplied != null) + { arguments.Add(supplied); continue; @@ -61,28 +65,42 @@ public static object[] Arguments( return arguments.ToArray(); } - private static object Argument(ParameterDefinition serviceProvider, ParameterInfoModel parameter) { - if (parameter.ParameterType.Equals(KnownTypes.Microsoft.DependencyInjection.IServiceProvider)) { + private static object Argument( + ParameterDefinition serviceProvider, + ParameterInfoModel parameter + ) + { + if ( + parameter.ParameterType.Equals( + KnownTypes.Microsoft.DependencyInjection.IServiceProvider + ) + ) + { return serviceProvider; } - var keyed = parameter.Attributes.FirstOrDefault( - attribute => attribute.TypeDefinition.Equals( - KnownTypes.Microsoft.DependencyInjection.FromKeyedServicesAttribute)); + var keyed = parameter.Attributes.FirstOrDefault(attribute => + attribute.TypeDefinition.Equals( + KnownTypes.Microsoft.DependencyInjection.FromKeyedServicesAttribute + ) + ); var name = "Get"; var arguments = new List(); - if (!parameter.ParameterType.IsNullable) { + if (!parameter.ParameterType.IsNullable) + { name += "Required"; } - if (keyed != null) { + if (keyed != null) + { name += "Keyed"; var key = keyed.Arguments.First().Value!; - if (key is string text) { + if (key is string text) + { key = QuoteString(text); } @@ -94,6 +112,7 @@ private static object Argument(ParameterDefinition serviceProvider, ParameterInf return serviceProvider.InvokeGeneric( name, new[] { parameter.ParameterType.MakeNullable(false) }, - arguments.ToArray()); + arguments.ToArray() + ); } } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorConstraintChecker.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorConstraintChecker.cs index 9bfd641..b58083a 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorConstraintChecker.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorConstraintChecker.cs @@ -21,21 +21,30 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// which is better than a decoration going missing for a reason nothing reports. /// /// -public static class DecoratorConstraintChecker { - +public static class DecoratorConstraintChecker +{ public static bool CanClose( - Compilation compilation, ITypeDefinition decoratorType, GenericTypeDefinition closedService) { - + Compilation compilation, + ITypeDefinition decoratorType, + GenericTypeDefinition closedService + ) + { var decorator = Resolve(compilation, decoratorType); - if (decorator == null || decorator.TypeParameters.Length != closedService.TypeArguments.Count) { + if ( + decorator == null + || decorator.TypeParameters.Length != closedService.TypeArguments.Count + ) + { return true; } - for (var i = 0; i < decorator.TypeParameters.Length; i++) { + for (var i = 0; i < decorator.TypeParameters.Length; i++) + { var argument = Resolve(compilation, closedService.TypeArguments[i]); - if (argument != null && !Satisfies(decorator.TypeParameters[i], argument)) { + if (argument != null && !Satisfies(decorator.TypeParameters[i], argument)) + { return false; } } @@ -43,24 +52,33 @@ public static bool CanClose( return true; } - private static bool Satisfies(ITypeParameterSymbol parameter, INamedTypeSymbol argument) { - if (parameter.HasReferenceTypeConstraint && !argument.IsReferenceType) { + private static bool Satisfies(ITypeParameterSymbol parameter, INamedTypeSymbol argument) + { + if (parameter.HasReferenceTypeConstraint && !argument.IsReferenceType) + { return false; } - if (parameter.HasValueTypeConstraint && !argument.IsValueType) { + if (parameter.HasValueTypeConstraint && !argument.IsValueType) + { return false; } - if (parameter.HasConstructorConstraint && - !argument.InstanceConstructors.Any( - constructor => constructor.Parameters.Length == 0 && - constructor.DeclaredAccessibility == Accessibility.Public)) { + if ( + parameter.HasConstructorConstraint + && !argument.InstanceConstructors.Any(constructor => + constructor.Parameters.Length == 0 + && constructor.DeclaredAccessibility == Accessibility.Public + ) + ) + { return false; } - foreach (var constraint in parameter.ConstraintTypes) { - if (!Implements(argument, constraint)) { + foreach (var constraint in parameter.ConstraintTypes) + { + if (!Implements(argument, constraint)) + { return false; } } @@ -68,25 +86,42 @@ private static bool Satisfies(ITypeParameterSymbol parameter, INamedTypeSymbol a return true; } - private static bool Implements(INamedTypeSymbol argument, ITypeSymbol constraint) { + private static bool Implements(INamedTypeSymbol argument, ITypeSymbol constraint) + { // A constraint naming another type parameter cannot be checked without the whole // substitution, and the service's own constraints already cover the usual case. - if (constraint is ITypeParameterSymbol) { + if (constraint is ITypeParameterSymbol) + { return true; } - if (SymbolEqualityComparer.Default.Equals(argument, constraint)) { + if (SymbolEqualityComparer.Default.Equals(argument, constraint)) + { return true; } - foreach (var implemented in argument.AllInterfaces) { - if (SymbolEqualityComparer.Default.Equals(implemented.OriginalDefinition, constraint.OriginalDefinition)) { + foreach (var implemented in argument.AllInterfaces) + { + if ( + SymbolEqualityComparer.Default.Equals( + implemented.OriginalDefinition, + constraint.OriginalDefinition + ) + ) + { return true; } } - for (var baseType = argument.BaseType; baseType != null; baseType = baseType.BaseType) { - if (SymbolEqualityComparer.Default.Equals(baseType.OriginalDefinition, constraint.OriginalDefinition)) { + for (var baseType = argument.BaseType; baseType != null; baseType = baseType.BaseType) + { + if ( + SymbolEqualityComparer.Default.Equals( + baseType.OriginalDefinition, + constraint.OriginalDefinition + ) + ) + { return true; } } @@ -102,23 +137,40 @@ private static bool Implements(INamedTypeSymbol argument, ITypeSymbol constraint /// GetTypeByMetadataName("int") finds nothing — so a value type would look unresolvable /// and be allowed through, which is exactly the case this class exists to catch. /// - private static readonly Dictionary Aliases = new() { - ["bool"] = "System.Boolean", ["byte"] = "System.Byte", ["sbyte"] = "System.SByte", - ["char"] = "System.Char", ["decimal"] = "System.Decimal", ["double"] = "System.Double", - ["float"] = "System.Single", ["int"] = "System.Int32", ["uint"] = "System.UInt32", - ["long"] = "System.Int64", ["ulong"] = "System.UInt64", ["short"] = "System.Int16", - ["ushort"] = "System.UInt16", ["nint"] = "System.IntPtr", ["nuint"] = "System.UIntPtr", - ["object"] = "System.Object", ["string"] = "System.String", + private static readonly Dictionary Aliases = new() + { + ["bool"] = "System.Boolean", + ["byte"] = "System.Byte", + ["sbyte"] = "System.SByte", + ["char"] = "System.Char", + ["decimal"] = "System.Decimal", + ["double"] = "System.Double", + ["float"] = "System.Single", + ["int"] = "System.Int32", + ["uint"] = "System.UInt32", + ["long"] = "System.Int64", + ["ulong"] = "System.UInt64", + ["short"] = "System.Int16", + ["ushort"] = "System.UInt16", + ["nint"] = "System.IntPtr", + ["nuint"] = "System.UIntPtr", + ["object"] = "System.Object", + ["string"] = "System.String", }; - private static INamedTypeSymbol? Resolve(Compilation compilation, ITypeDefinition type) { - var name = string.IsNullOrEmpty(type.Namespace) ? type.Name : type.Namespace + "." + type.Name; + private static INamedTypeSymbol? Resolve(Compilation compilation, ITypeDefinition type) + { + var name = string.IsNullOrEmpty(type.Namespace) + ? type.Name + : type.Namespace + "." + type.Name; - if (Aliases.TryGetValue(name, out var metadataName)) { + if (Aliases.TryGetValue(name, out var metadataName)) + { name = metadataName; } - if (type is GenericTypeDefinition { TypeArguments.Count: > 0 } generic) { + if (type is GenericTypeDefinition { TypeArguments.Count: > 0 } generic) + { name += "`" + generic.TypeArguments.Count; } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorExpansion.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorExpansion.cs index c9f1a75..f27a829 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorExpansion.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorExpansion.cs @@ -20,8 +20,8 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// name the same closed service. /// /// -public static class DecoratorExpansion { - +public static class DecoratorExpansion +{ /// /// Decorators that name a service the compilation registers as an open generic. Nothing can be /// emitted for those — see — and the caller reports @@ -32,23 +32,28 @@ public static IReadOnlyList Expand( IReadOnlyList registeredServiceTypes, out IReadOnlyList refusedForOpenGenericRegistration, bool includeNonGeneric = true, - Func? canClose = null) { - + Func? canClose = null + ) + { var expanded = new List(decorators.Count); List? refused = null; - foreach (var decorator in decorators) { - if (decorator.IsIgnored) { + foreach (var decorator in decorators) + { + if (decorator.IsIgnored) + { continue; } - if (!decorator.IsOpenGeneric) { + if (!decorator.IsOpenGeneric) + { // An unbound service type has no legal emission at all: Decorate> is // CS7003. A generic decorator reaches this state only when nothing closed it, and is // handled below; a non-generic one never had an expansion step to catch it, so this // is where it stops. Refused whatever is registered, because the emission is invalid // on its own terms. - if (decorator.HasUnboundServiceType) { + if (decorator.HasUnboundServiceType) + { (refused ??= new List()).Add(decorator); continue; @@ -59,7 +64,8 @@ public static IReadOnlyList Expand( // by generated code is dropped: generated code builds the decorator with a literal // new, and the reflective overload that used to stand in for this is gone because // it never worked in a published application. - if (includeNonGeneric && decorator.CanMonomorphise) { + if (includeNonGeneric && decorator.CanMonomorphise) + { expanded.Add(decorator); } @@ -68,8 +74,10 @@ public static IReadOnlyList Expand( var closedCount = 0; - foreach (var serviceType in registeredServiceTypes) { - if (!ClosesTheSameGeneric(serviceType, decorator.ServiceType)) { + foreach (var serviceType in registeredServiceTypes) + { + if (!ClosesTheSameGeneric(serviceType, decorator.ServiceType)) + { continue; } @@ -77,13 +85,15 @@ public static IReadOnlyList Expand( // A decorator may constrain its type parameters more tightly than the service does. // Closing it over an argument that violates one emits code that does not compile. - if (canClose != null && !canClose(decorator.DecoratorType, closedService)) { + if (canClose != null && !canClose(decorator.DecoratorType, closedService)) + { continue; } var closed = DecoratorTypeUtility.Close(decorator, closedService); - if (closed == null) { + if (closed == null) + { continue; } @@ -96,12 +106,17 @@ public static IReadOnlyList Expand( // reporting, while a compilation that registers nothing at all is the ordinary // cross-assembly case — [Decorate] exists to name a service someone else registers, so // reporting that would fire on the feature's primary use. - if (closedCount == 0 && NamesAnOpenGenericRegistration(decorator, registeredServiceTypes)) { + if ( + closedCount == 0 + && NamesAnOpenGenericRegistration(decorator, registeredServiceTypes) + ) + { (refused ??= new List()).Add(decorator); } } - refusedForOpenGenericRegistration = (IReadOnlyList?)refused ?? Array.Empty(); + refusedForOpenGenericRegistration = + (IReadOnlyList?)refused ?? Array.Empty(); return expanded; } @@ -114,19 +129,25 @@ public static IReadOnlyList Expand( /// services.AddSingleton(typeof(IStore<>), typeof(Store<>)) produces. /// private static bool NamesAnOpenGenericRegistration( - DecoratorModel decorator, IReadOnlyList registeredServiceTypes) { - - if (decorator.ServiceType is not GenericTypeDefinition decorated) { + DecoratorModel decorator, + IReadOnlyList registeredServiceTypes + ) + { + if (decorator.ServiceType is not GenericTypeDefinition decorated) + { return false; } - foreach (var registered in registeredServiceTypes) { - if (registered is GenericTypeDefinition open && - open.TypeArguments.Count == decorated.TypeArguments.Count && - open.Name == decorated.Name && - open.Namespace == decorated.Namespace && - open.TypeArguments.All(argument => string.IsNullOrEmpty(argument.Name))) { - + foreach (var registered in registeredServiceTypes) + { + if ( + registered is GenericTypeDefinition open + && open.TypeArguments.Count == decorated.TypeArguments.Count + && open.Name == decorated.Name + && open.Namespace == decorated.Namespace + && open.TypeArguments.All(argument => string.IsNullOrEmpty(argument.Name)) + ) + { return true; } } @@ -137,12 +158,16 @@ private static bool NamesAnOpenGenericRegistration( /// /// Whether a registered service type is a closed construction of the decorated open generic. /// - private static bool ClosesTheSameGeneric(ITypeDefinition registered, ITypeDefinition decorated) => - registered is GenericTypeDefinition closed && - decorated is GenericTypeDefinition open && - closed.TypeArguments.Count == open.TypeArguments.Count && - closed.Name == open.Name && - closed.Namespace == open.Namespace && + private static bool ClosesTheSameGeneric( + ITypeDefinition registered, + ITypeDefinition decorated + ) => + registered is GenericTypeDefinition closed + && decorated is GenericTypeDefinition open + && closed.TypeArguments.Count == open.TypeArguments.Count + && closed.Name == open.Name + && closed.Namespace == open.Namespace + && // The decorated form has its arguments blanked; a registration that also has them blanked is // an open generic registration, which cannot be decorated at all. closed.TypeArguments.Any(argument => !string.IsNullOrEmpty(argument.Name)); diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorModelUtility.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorModelUtility.cs index 0bda5a8..728a2f6 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorModelUtility.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorModelUtility.cs @@ -9,21 +9,27 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// Builds instances from the two declaration surfaces: /// [Decorator] on the decorator class, and [Decorate] on a module. /// -public static class DecoratorModelUtility { - +public static class DecoratorModelUtility +{ /// /// Reads a [Decorator] class declaration. /// - public static DecoratorModel? GetDecoratorModel(SyntaxTransformContext context, CancellationToken cancellationToken) { + public static DecoratorModel? GetDecoratorModel( + SyntaxTransformContext context, + CancellationToken cancellationToken + ) + { cancellationToken.ThrowIfCancellationRequested(); - if (context.Node is not TypeDeclarationSyntax typeDeclarationSyntax) { + if (context.Node is not TypeDeclarationSyntax typeDeclarationSyntax) + { return null; } var attribute = FindAttribute(typeDeclarationSyntax, "Decorator"); - if (attribute == null) { + if (attribute == null) + { return null; } @@ -35,11 +41,15 @@ public static class DecoratorModelUtility { ITypeDefinition? explicitService = null; ITypeDefinition? implementation = null; - if (attribute.ArgumentList != null) { - foreach (var argument in attribute.ArgumentList.Arguments) { - switch (argument.NameEquals?.Name.ToString()) { + if (attribute.ArgumentList != null) + { + foreach (var argument in attribute.ArgumentList.Arguments) + { + switch (argument.NameEquals?.Name.ToString()) + { case "Order": - if (int.TryParse(argument.Expression.ToString(), out var parsed)) { + if (int.TryParse(argument.Expression.ToString(), out var parsed)) + { order = parsed; } break; @@ -56,9 +66,11 @@ public static class DecoratorModelUtility { } } - var written = explicitService ?? InferDecoratedService(typeDeclarationSyntax, context, implemented); + var written = + explicitService ?? InferDecoratedService(typeDeclarationSyntax, context, implemented); - if (written == null) { + if (written == null) + { return null; } @@ -67,17 +79,24 @@ public static class DecoratorModelUtility { // A generic decorator decorates the open service. Its base list names the service closed over // its own type parameters, as IHandler; the unbound IHandler<> is the form the model // carries, and the closed constructions to emit against are worked out from the registrations. - if (decoratorType is GenericTypeDefinition { TypeArguments.Count: > 0 }) { + if (decoratorType is GenericTypeDefinition { TypeArguments.Count: > 0 }) + { serviceType = ToUnboundGeneric(written); } // Read from the decorator class, exactly as they are for a service. A decorator is a // registration like any other, and one gated on Development has no other way to say so. var conditions = EnvironmentConditionUtility.GetConditions( - context, typeDeclarationSyntax, cancellationToken); + context, + typeDeclarationSyntax, + cancellationToken + ); var constructor = ServiceModelUtility.GetConstructorInfo( - context, typeDeclarationSyntax, cancellationToken); + context, + typeDeclarationSyntax, + cancellationToken + ); return new DecoratorModel( serviceType, @@ -89,7 +108,8 @@ public static class DecoratorModelUtility { IndexOfInnerParameter(constructor, written), TypeParametersMatchService(typeDeclarationSyntax, written), implementation, - LocationModel.From(typeDeclarationSyntax)); + LocationModel.From(typeDeclarationSyntax) + ); } /// @@ -102,15 +122,22 @@ public static class DecoratorModelUtility { /// carries an annotation the service type does not, and comparing them as written finds no /// parameter at all — which drops the decoration with nothing said. /// - private static int IndexOfInnerParameter(ConstructorInfoModel? constructor, ITypeDefinition serviceType) { - if (constructor == null) { + private static int IndexOfInnerParameter( + ConstructorInfoModel? constructor, + ITypeDefinition serviceType + ) + { + if (constructor == null) + { return -1; } var wanted = serviceType.MakeNullable(false); - for (var i = 0; i < constructor.Parameters.Count; i++) { - if (constructor.Parameters[i].ParameterType.MakeNullable(false).Equals(wanted)) { + for (var i = 0; i < constructor.Parameters.Count; i++) + { + if (constructor.Parameters[i].ParameterType.MakeNullable(false).Equals(wanted)) + { return i; } } @@ -129,21 +156,29 @@ private static int IndexOfInnerParameter(ConstructorInfoModel? constructor, ITyp /// emitted for them instead. /// private static bool TypeParametersMatchService( - TypeDeclarationSyntax typeDeclarationSyntax, ITypeDefinition serviceType) { - + TypeDeclarationSyntax typeDeclarationSyntax, + ITypeDefinition serviceType + ) + { var declared = typeDeclarationSyntax.TypeParameterList?.Parameters; - if (declared is not { Count: > 0 }) { + if (declared is not { Count: > 0 }) + { return true; } - if (serviceType is not GenericTypeDefinition generic || - generic.TypeArguments.Count != declared.Value.Count) { + if ( + serviceType is not GenericTypeDefinition generic + || generic.TypeArguments.Count != declared.Value.Count + ) + { return false; } - for (var i = 0; i < declared.Value.Count; i++) { - if (generic.TypeArguments[i].Name != declared.Value[i].Identifier.Text) { + for (var i = 0; i < declared.Value.Count; i++) + { + if (generic.TypeArguments[i].Name != declared.Value[i].Identifier.Text) + { return false; } } @@ -154,28 +189,46 @@ private static bool TypeParametersMatchService( /// /// Reads the [Decorate(service, decorator)] attributes declared on a module. /// - public static IEnumerable GetModuleDeclaredDecorators(ModuleEntryPointModel entryPointModel) { - foreach (var attribute in entryPointModel.AttributeModels) { - if (attribute.TypeDefinition.Name is not ("DecorateAttribute" or "Decorate")) { + public static IEnumerable GetModuleDeclaredDecorators( + ModuleEntryPointModel entryPointModel + ) + { + foreach (var attribute in entryPointModel.AttributeModels) + { + if (attribute.TypeDefinition.Name is not ("DecorateAttribute" or "Decorate")) + { continue; } - if (attribute.Arguments.Count < 2 || - attribute.Arguments[0].Value is not ITypeDefinition service || - attribute.Arguments[1].Value is not ITypeDefinition decorator) { + if ( + attribute.Arguments.Count < 2 + || attribute.Arguments[0].Value is not ITypeDefinition service + || attribute.Arguments[1].Value is not ITypeDefinition decorator + ) + { continue; } var order = 0; - foreach (var property in attribute.Properties) { - if (property.Name == "Order" && property.Value != null && - int.TryParse(property.Value.ToString(), out var parsed)) { + foreach (var property in attribute.Properties) + { + if ( + property.Name == "Order" + && property.Value != null + && int.TryParse(property.Value.ToString(), out var parsed) + ) + { order = parsed; } } - yield return new DecoratorModel(service, decorator, order, entryPointModel.EntryPointType); + yield return new DecoratorModel( + service, + decorator, + order, + entryPointModel.EntryPointType + ); } } @@ -187,20 +240,25 @@ attribute.Arguments[0].Value is not ITypeDefinition service || private static ITypeDefinition? InferDecoratedService( TypeDeclarationSyntax typeDeclarationSyntax, SyntaxTransformContext context, - IReadOnlyList implemented) { - - if (implemented.Count == 0) { + IReadOnlyList implemented + ) + { + if (implemented.Count == 0) + { return null; } - foreach (var parameterType in GetConstructorParameterTypes(typeDeclarationSyntax, context)) { + foreach (var parameterType in GetConstructorParameterTypes(typeDeclarationSyntax, context)) + { // Normalised, because `IGreeter? inner` is legal and its parameter type carries an // annotation the implemented interface does not. Compared as written, no parameter looks // like the service and the class stops being a decorator at all — silently. var declared = parameterType.MakeNullable(false); - foreach (var candidate in implemented) { - if (candidate.MakeNullable(false).Equals(declared)) { + foreach (var candidate in implemented) + { + if (candidate.MakeNullable(false).Equals(declared)) + { return candidate; } } @@ -210,23 +268,33 @@ attribute.Arguments[0].Value is not ITypeDefinition service || } private static IEnumerable GetConstructorParameterTypes( - TypeDeclarationSyntax typeDeclarationSyntax, SyntaxTransformContext context) { - - if (typeDeclarationSyntax.ParameterList != null) { - foreach (var parameter in typeDeclarationSyntax.ParameterList.Parameters) { + TypeDeclarationSyntax typeDeclarationSyntax, + SyntaxTransformContext context + ) + { + if (typeDeclarationSyntax.ParameterList != null) + { + foreach (var parameter in typeDeclarationSyntax.ParameterList.Parameters) + { var type = parameter.Type?.GetTypeDefinition(context); - if (type != null) { + if (type != null) + { yield return type; } } } - foreach (var constructor in typeDeclarationSyntax.Members.OfType()) { - foreach (var parameter in constructor.ParameterList.Parameters) { + foreach ( + var constructor in typeDeclarationSyntax.Members.OfType() + ) + { + foreach (var parameter in constructor.ParameterList.Parameters) + { var type = parameter.Type?.GetTypeDefinition(context); - if (type != null) { + if (type != null) + { yield return type; } } @@ -234,18 +302,23 @@ private static IEnumerable GetConstructorParameterTypes( } private static IReadOnlyList GetImplementedInterfaces( - TypeDeclarationSyntax typeDeclarationSyntax, SyntaxTransformContext context) { - + TypeDeclarationSyntax typeDeclarationSyntax, + SyntaxTransformContext context + ) + { var interfaces = new List(); - if (typeDeclarationSyntax.BaseList == null) { + if (typeDeclarationSyntax.BaseList == null) + { return interfaces; } - foreach (var baseType in typeDeclarationSyntax.BaseList.Types) { + foreach (var baseType in typeDeclarationSyntax.BaseList.Types) + { var type = baseType.Type.GetTypeDefinition(context); - if (type != null) { + if (type != null) + { interfaces.Add(type); } } @@ -256,8 +329,10 @@ private static IReadOnlyList GetImplementedInterfaces( /// /// Rewrites a generic type so its arguments render as the unbound <> form. /// - private static ITypeDefinition ToUnboundGeneric(ITypeDefinition type) { - if (type is not GenericTypeDefinition { TypeArguments.Count: > 0 } generic) { + private static ITypeDefinition ToUnboundGeneric(ITypeDefinition type) + { + if (type is not GenericTypeDefinition { TypeArguments.Count: > 0 } generic) + { return type; } @@ -265,41 +340,62 @@ private static ITypeDefinition ToUnboundGeneric(ITypeDefinition type) { generic.TypeDefinitionEnum, generic.Namespace, generic.Name, - generic.TypeArguments.Select(_ => (ITypeDefinition)TypeDefinition.Get("", "")).ToArray()); + generic.TypeArguments.Select(_ => (ITypeDefinition)TypeDefinition.Get("", "")).ToArray() + ); } - private static ITypeDefinition GetDeclaredType(TypeDeclarationSyntax typeDeclarationSyntax, SyntaxTransformContext context) { + private static ITypeDefinition GetDeclaredType( + TypeDeclarationSyntax typeDeclarationSyntax, + SyntaxTransformContext context + ) + { var name = typeDeclarationSyntax.Identifier.ToString(); - foreach (var containing in typeDeclarationSyntax.Ancestors().OfType()) { + foreach ( + var containing in typeDeclarationSyntax.Ancestors().OfType() + ) + { name = containing.Identifier + "." + name; } var namespaceName = typeDeclarationSyntax.GetNamespace(); - if (typeDeclarationSyntax.TypeParameterList is { Parameters.Count: > 0 } parameters) { + if (typeDeclarationSyntax.TypeParameterList is { Parameters.Count: > 0 } parameters) + { return new GenericTypeDefinition( TypeDefinitionEnum.ClassDefinition, namespaceName, name, - parameters.Parameters.Select(_ => TypeDefinition.Get("", "")).ToArray()); + parameters.Parameters.Select(_ => TypeDefinition.Get("", "")).ToArray() + ); } return TypeDefinition.Get(namespaceName, name); } - private static ITypeDefinition? GetTypeOfArgument(AttributeArgumentSyntax argument, SyntaxTransformContext context) { + private static ITypeDefinition? GetTypeOfArgument( + AttributeArgumentSyntax argument, + SyntaxTransformContext context + ) + { return argument.Expression is TypeOfExpressionSyntax typeOf ? typeOf.Type.GetTypeDefinition(context) : null; } - private static AttributeSyntax? FindAttribute(TypeDeclarationSyntax typeDeclarationSyntax, string name) { - foreach (var attributeList in typeDeclarationSyntax.AttributeLists) { - foreach (var attribute in attributeList.Attributes) { + private static AttributeSyntax? FindAttribute( + TypeDeclarationSyntax typeDeclarationSyntax, + string name + ) + { + foreach (var attributeList in typeDeclarationSyntax.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { var attributeName = attribute.Name.ToString(); - if (attributeName == name || attributeName == name + "Attribute") { + if (attributeName == name || attributeName == name + "Attribute") + { return attribute; } } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorTypeUtility.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorTypeUtility.cs index f26c343..cc9a9c7 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorTypeUtility.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorTypeUtility.cs @@ -14,41 +14,53 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// a decorator taking IValidator<TReq> resolves IValidator<CreateOrder> /// rather than something the compiler would reject. /// -public static class DecoratorTypeUtility { - +public static class DecoratorTypeUtility +{ /// /// The decoration to emit for one closed registration, or null when the decorator cannot be /// closed over it. /// - public static DecoratorModel? Close(DecoratorModel decorator, GenericTypeDefinition closedService) { - if (!decorator.CanMonomorphise) { + public static DecoratorModel? Close( + DecoratorModel decorator, + GenericTypeDefinition closedService + ) + { + if (!decorator.CanMonomorphise) + { return null; } var parameterNames = TypeParameterNames(decorator); - if (parameterNames == null || parameterNames.Count != closedService.TypeArguments.Count) { + if (parameterNames == null || parameterNames.Count != closedService.TypeArguments.Count) + { return null; } var substitutions = new Dictionary(parameterNames.Count); - for (var i = 0; i < parameterNames.Count; i++) { + for (var i = 0; i < parameterNames.Count; i++) + { substitutions[parameterNames[i]] = closedService.TypeArguments[i]; } var parameters = new List(decorator.Constructor!.Parameters.Count); - foreach (var parameter in decorator.Constructor.Parameters) { - parameters.Add(parameter with { - ParameterType = Substitute(parameter.ParameterType, substitutions) - }); + foreach (var parameter in decorator.Constructor.Parameters) + { + parameters.Add( + parameter with + { + ParameterType = Substitute(parameter.ParameterType, substitutions), + } + ); } - return decorator with { + return decorator with + { ServiceType = closedService, DecoratorType = CloseDecorator(decorator.DecoratorType, closedService.TypeArguments), - Constructor = new ConstructorInfoModel(parameters) + Constructor = new ConstructorInfoModel(parameters), }; } @@ -64,17 +76,21 @@ public static class DecoratorTypeUtility { /// Safe only because is true, which is /// what guarantees these names are also the decorator's own parameters, in the same order. /// - private static IReadOnlyList? TypeParameterNames(DecoratorModel decorator) { + private static IReadOnlyList? TypeParameterNames(DecoratorModel decorator) + { var inner = decorator.Constructor!.Parameters[decorator.InnerParameterIndex].ParameterType; - if (inner is not GenericTypeDefinition generic || generic.TypeArguments.Count == 0) { + if (inner is not GenericTypeDefinition generic || generic.TypeArguments.Count == 0) + { return null; } var names = new List(generic.TypeArguments.Count); - foreach (var argument in generic.TypeArguments) { - if (string.IsNullOrEmpty(argument.Name)) { + foreach (var argument in generic.TypeArguments) + { + if (string.IsNullOrEmpty(argument.Name)) + { return null; } @@ -85,32 +101,48 @@ public static class DecoratorTypeUtility { } private static ITypeDefinition CloseDecorator( - ITypeDefinition decoratorType, IReadOnlyList typeArguments) => + ITypeDefinition decoratorType, + IReadOnlyList typeArguments + ) => decoratorType is GenericTypeDefinition generic ? new GenericTypeDefinition( - generic.TypeDefinitionEnum, generic.Namespace, generic.Name, typeArguments.ToArray()) + generic.TypeDefinitionEnum, + generic.Namespace, + generic.Name, + typeArguments.ToArray() + ) : decoratorType; /// /// Replaces type parameters with the arguments the registration closed them over, at any depth. /// private static ITypeDefinition Substitute( - ITypeDefinition type, Dictionary substitutions) { - - if (type is GenericTypeDefinition generic) { + ITypeDefinition type, + Dictionary substitutions + ) + { + if (type is GenericTypeDefinition generic) + { var arguments = new ITypeDefinition[generic.TypeArguments.Count]; - for (var i = 0; i < arguments.Length; i++) { + for (var i = 0; i < arguments.Length; i++) + { arguments[i] = Substitute(generic.TypeArguments[i], substitutions); } return new GenericTypeDefinition( - generic.TypeDefinitionEnum, generic.Namespace, generic.Name, arguments); + generic.TypeDefinitionEnum, + generic.Namespace, + generic.Name, + arguments + ); } // A type parameter has no namespace; anything with one is an ordinary type and is left alone // even if it shares a name with a parameter. - return string.IsNullOrEmpty(type.Namespace) && substitutions.TryGetValue(type.Name, out var closed) + return + string.IsNullOrEmpty(type.Namespace) + && substitutions.TryGetValue(type.Name, out var closed) ? closed : type; } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/EntryModelUtil.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/EntryModelUtil.cs index 9e920ba..46c805b 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/EntryModelUtil.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/EntryModelUtil.cs @@ -1,11 +1,12 @@ using System.Collections.Immutable; -using CSharpAuthor; using System.Text.RegularExpressions; +using CSharpAuthor; using DependencyModules.SourceGenerator.Impl.Models; namespace DependencyModules.SourceGenerator.Impl.Utilities; -public class EntryModelUtil { +public class EntryModelUtil +{ /// /// The declared module an auto-generated ApplicationModule should defer to, or null when /// it has to carry its own registrations. @@ -33,28 +34,41 @@ public class EntryModelUtil { /// /// public static ITypeDefinition? DelegateTargetFor( - ModuleEntryPointModel entryPointModel, IEnumerable allEntryPoints) { - - if (!entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule)) { + ModuleEntryPointModel entryPointModel, + IEnumerable allEntryPoints + ) + { + if (!entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule)) + { return null; } ModuleEntryPointModel? target = null; - foreach (var candidate in allEntryPoints) { - if (candidate.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule) || - candidate.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.OnlyRealm) || - candidate.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.NotPartial)) { + foreach (var candidate in allEntryPoints) + { + if ( + candidate.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule) + || candidate.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.OnlyRealm) + || candidate.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.NotPartial) + ) + { continue; } // A module the caller has to supply arguments for cannot be constructed by the auto // module, which has nothing to pass. - if (candidate.Parameters.Count > 0) { + if (candidate.Parameters.Count > 0) + { continue; } - if (target == null || string.Compare(FullName(candidate), FullName(target), StringComparison.Ordinal) < 0) { + if ( + target == null + || string.Compare(FullName(candidate), FullName(target), StringComparison.Ordinal) + < 0 + ) + { target = candidate; } } @@ -70,11 +84,16 @@ public class EntryModelUtil { /// through this, so an auto-generated module that defers to a declared one is skipped by all of /// them rather than by whichever ones remembered to. /// - public static IList RegistrationTargets(IList entryPoints) { + public static IList RegistrationTargets( + IList entryPoints + ) + { List? filtered = null; - for (var i = 0; i < entryPoints.Count; i++) { - if (DelegateTargetFor(entryPoints[i], entryPoints) == null) { + for (var i = 0; i < entryPoints.Count; i++) + { + if (DelegateTargetFor(entryPoints[i], entryPoints) == null) + { filtered?.Add(entryPoints[i]); continue; } @@ -99,47 +118,77 @@ private static string FullName(ModuleEntryPointModel model) => /// [Decorator] and the first [Intercept] to a project that happened to contain a record module. /// Shared so the next writer cannot get it wrong. /// - public static string ApplyRecordDeclaration(string output, ModuleEntryPointModel entryPointModel) { - if (!entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.IsRecord)) { + public static string ApplyRecordDeclaration( + string output, + ModuleEntryPointModel entryPointModel + ) + { + if (!entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.IsRecord)) + { return output; } return Regex.Replace( output, @"partial class " + Regex.Escape(entryPointModel.EntryPointType.Name) + @"(?!\w)", - $"partial record class {entryPointModel.EntryPointType.Name}"); + $"partial record class {entryPointModel.EntryPointType.Name}" + ); } - public static string GenerateFileName(ModuleEntryPointModel entryPointModel, string uniquePortion) { + public static string GenerateFileName( + ModuleEntryPointModel entryPointModel, + string uniquePortion + ) + { var namespaceName = entryPointModel.EntryPointType.Namespace; - if (string.IsNullOrEmpty(entryPointModel.EntryPointType.Namespace)) { + if (string.IsNullOrEmpty(entryPointModel.EntryPointType.Namespace)) + { namespaceName = "blank-namespace"; } - + return $"{namespaceName}.{entryPointModel.EntryPointType.GetShortName()}.{uniquePortion}.g.cs"; } - - public static ModuleEntryPointModel EnsureNamespace(ModuleEntryPointModel entryPointModel, DependencyModuleConfigurationModel configurationModel) { - - if (entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule) && - string.IsNullOrEmpty(entryPointModel.EntryPointType.Namespace)) { - entryPointModel = entryPointModel with { + + public static ModuleEntryPointModel EnsureNamespace( + ModuleEntryPointModel entryPointModel, + DependencyModuleConfigurationModel configurationModel + ) + { + if ( + entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule) + && string.IsNullOrEmpty(entryPointModel.EntryPointType.Namespace) + ) + { + entryPointModel = entryPointModel with + { EntryPointType = TypeDefinition.Get( - configurationModel.RootNamespace, - entryPointModel.EntryPointType.Name) + configurationModel.RootNamespace, + entryPointModel.EntryPointType.Name + ), }; } return entryPointModel; } - - public static (IList uniqueEntryPoints, DependencyModuleConfigurationModel configurationModel) ConsolidateEntryPointModels( - ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> entryPointList) { + + public static ( + IList uniqueEntryPoints, + DependencyModuleConfigurationModel configurationModel + ) ConsolidateEntryPointModels( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> entryPointList + ) + { var uniqueEntryPoints = new List(); var configurationModel = entryPointList.First().Right; var entryPointModels = entryPointList.Select(m => m.Left); - if (!configurationModel.AutoGenerateEntry) { - entryPointModels = entryPointModels.Where(m => !m.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule)); + if (!configurationModel.AutoGenerateEntry) + { + entryPointModels = entryPointModels.Where(m => + !m.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule) + ); } // Before grouping, not after. A generated module is created with no namespace and given the @@ -152,40 +201,59 @@ public static (IList uniqueEntryPoints, DependencyModuleC // they are one module and keep the declared one. entryPointModels = entryPointModels.Select(m => EnsureNamespace(m, configurationModel)); - var groupingEnumerable = - entryPointModels.GroupBy(m => m.EntryPointType.Namespace + "." + m.EntryPointType.GetShortName()); + var groupingEnumerable = entryPointModels.GroupBy(m => + m.EntryPointType.Namespace + "." + m.EntryPointType.GetShortName() + ); - foreach (var grouping in groupingEnumerable) { - if (grouping.Count() > 1) { + foreach (var grouping in groupingEnumerable) + { + if (grouping.Count() > 1) + { uniqueEntryPoints.Add( - ConsolidateEntryPointModelGrouping(grouping, configurationModel)); - } else { + ConsolidateEntryPointModelGrouping(grouping, configurationModel) + ); + } + else + { var entryPointModel = grouping.First(); - if (entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule)) { + if ( + entryPointModel.ModuleFeatures.HasFlag( + ModuleEntryPointFeatures.AutoGenerateModule + ) + ) + { var path = Path.Combine(configurationModel.ProjectDir, "Program.cs"); - if (entryPointModel.FileLocation == path) { + if (entryPointModel.FileLocation == path) + { uniqueEntryPoints.Add(grouping.First()); } } - else { + else + { uniqueEntryPoints.Add(grouping.First()); } } } - + return (uniqueEntryPoints, configurationModel); } - private static ModuleEntryPointModel ConsolidateEntryPointModelGrouping(IGrouping grouping, DependencyModuleConfigurationModel configurationModel) { - var firstNonAuto = grouping.FirstOrDefault( - m => m.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule) == false); - - if (firstNonAuto != null) { + private static ModuleEntryPointModel ConsolidateEntryPointModelGrouping( + IGrouping grouping, + DependencyModuleConfigurationModel configurationModel + ) + { + var firstNonAuto = grouping.FirstOrDefault(m => + m.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule) == false + ); + + if (firstNonAuto != null) + { return firstNonAuto; } - + return grouping.First(); } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/EnvironmentConditionUtility.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/EnvironmentConditionUtility.cs index a3f4f42..e54751c 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/EnvironmentConditionUtility.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/EnvironmentConditionUtility.cs @@ -19,8 +19,8 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// the incremental cache. /// /// -public static class EnvironmentConditionUtility { - +public static class EnvironmentConditionUtility +{ private const string IfEnvironment = "IfEnvironmentAttribute"; private const string IfNotEnvironment = "IfNotEnvironmentAttribute"; private const string IfEnvironmentValue = "IfEnvironmentValueAttribute"; @@ -34,21 +34,28 @@ public static class EnvironmentConditionUtility { /// has always had and existing snapshots do not move. /// public static IReadOnlyList? GetConditions( - SyntaxTransformContext context, SyntaxNode node, CancellationToken cancellationToken) { - - if (node is not MemberDeclarationSyntax memberDeclaration) { + SyntaxTransformContext context, + SyntaxNode node, + CancellationToken cancellationToken + ) + { + if (node is not MemberDeclarationSyntax memberDeclaration) + { return null; } List? conditions = null; - foreach (var attributeList in memberDeclaration.AttributeLists) { - foreach (var attribute in attributeList.Attributes) { + foreach (var attributeList in memberDeclaration.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { cancellationToken.ThrowIfCancellationRequested(); var condition = ReadCondition(context, attribute); - if (condition == null) { + if (condition == null) + { continue; } @@ -61,29 +68,43 @@ public static class EnvironmentConditionUtility { } private static EnvironmentConditionModel? ReadCondition( - SyntaxTransformContext context, AttributeSyntax attribute) { - - if (ModelExtensions.GetTypeInfo(context.SemanticModel, attribute).Type is not { } attributeType || - attributeType.ContainingNamespace.GetFullName() != KnownTypes.DependencyModules.Attributes.Namespace) { + SyntaxTransformContext context, + AttributeSyntax attribute + ) + { + if ( + ModelExtensions.GetTypeInfo(context.SemanticModel, attribute).Type + is not { } attributeType + || attributeType.ContainingNamespace.GetFullName() + != KnownTypes.DependencyModules.Attributes.Namespace + ) + { return null; } - var kind = attributeType.Name switch { + var kind = attributeType.Name switch + { IfEnvironment or IfNotEnvironment => EnvironmentConditionKind.Name, IfEnvironmentValue or IfNotEnvironmentValue => EnvironmentConditionKind.Value, _ => (EnvironmentConditionKind?)null, }; - if (kind == null) { + if (kind == null) + { return null; } var negate = attributeType.Name is IfNotEnvironment or IfNotEnvironmentValue; var arguments = ReadStringArguments(context, attribute); - if (kind == EnvironmentConditionKind.Name) { + if (kind == EnvironmentConditionKind.Name) + { return new EnvironmentConditionModel( - EnvironmentConditionKind.Name, negate, null, arguments); + EnvironmentConditionKind.Name, + negate, + null, + arguments + ); } // The key is the first argument; a second, when present, is the value it has to equal. @@ -92,8 +113,7 @@ public static class EnvironmentConditionUtility { var key = arguments.Count > 0 ? arguments[0] : ""; var values = arguments.Count > 1 ? new[] { arguments[1] } : Array.Empty(); - return new EnvironmentConditionModel( - EnvironmentConditionKind.Value, negate, key, values); + return new EnvironmentConditionModel(EnvironmentConditionKind.Value, negate, key, values); } /// @@ -101,22 +121,28 @@ public static class EnvironmentConditionUtility { /// attributes has a settable property, so one can only be a mistake. /// private static IReadOnlyList ReadStringArguments( - SyntaxTransformContext context, AttributeSyntax attribute) { - - if (attribute.ArgumentList == null) { + SyntaxTransformContext context, + AttributeSyntax attribute + ) + { + if (attribute.ArgumentList == null) + { return Array.Empty(); } var values = new List(); - foreach (var argument in attribute.ArgumentList.Arguments) { - if (argument.NameEquals != null) { + foreach (var argument in attribute.ArgumentList.Arguments) + { + if (argument.NameEquals != null) + { continue; } // GetConstantValue rather than the literal text, so nameof(...) and a const declared // elsewhere both read as the string they evaluate to. - if (context.SemanticModel.GetConstantValue(argument.Expression).Value is string value) { + if (context.SemanticModel.GetConstantValue(argument.Expression).Value is string value) + { values.Add(value); } } @@ -134,7 +160,8 @@ private static IReadOnlyList ReadStringArguments( /// anything, so it is reported rather than emitted. /// public static bool IsEmpty(EnvironmentConditionModel condition) => - condition.Kind switch { + condition.Kind switch + { EnvironmentConditionKind.Name => condition.Values.Count == 0, EnvironmentConditionKind.Value => string.IsNullOrEmpty(condition.Key), _ => false, @@ -143,10 +170,12 @@ public static bool IsEmpty(EnvironmentConditionModel condition) => /// /// A condition as it would read in a diagnostic message. /// - public static string Describe(EnvironmentConditionModel condition) { + public static string Describe(EnvironmentConditionModel condition) + { var not = condition.Negate ? "not " : ""; - if (condition.Kind == EnvironmentConditionKind.Name) { + if (condition.Kind == EnvironmentConditionKind.Name) + { return $"environment is {not}{string.Join(" or ", condition.Values)}"; } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/EnvironmentConditionWriter.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/EnvironmentConditionWriter.cs index 54659ef..cde7a98 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/EnvironmentConditionWriter.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/EnvironmentConditionWriter.cs @@ -10,8 +10,8 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// Shared by every writer that emits something conditional — service registrations and decorators — /// so the two cannot drift into testing the same attributes differently. /// -public static class EnvironmentConditionWriter { - +public static class EnvironmentConditionWriter +{ private const string ConditionsType = "global::" + KnownTypes.DependencyModules.Helpers.Namespace + ".EnvironmentConditions"; @@ -27,22 +27,27 @@ public static class EnvironmentConditionWriter { /// Conditions to test, combined with and. /// Name of the IModuleEnvironment parameter in scope. public static string BuildCondition( - IReadOnlyList conditions, string environmentParameter) { - + IReadOnlyList conditions, + string environmentParameter + ) + { var parts = new List(conditions.Count); - foreach (var condition in conditions) { + foreach (var condition in conditions) + { // An empty condition tests nothing; it is reported as DM0012 and left out rather than // emitted as a call that is constant either way. - if (EnvironmentConditionUtility.IsEmpty(condition)) { + if (EnvironmentConditionUtility.IsEmpty(condition)) + { continue; } - var call = condition.Kind == EnvironmentConditionKind.Name - ? $"{ConditionsType}.NameIs({environmentParameter}, {QuoteAll(condition.Values)})" + var call = + condition.Kind == EnvironmentConditionKind.Name + ? $"{ConditionsType}.NameIs({environmentParameter}, {QuoteAll(condition.Values)})" : condition.Values.Count > 0 ? $"{ConditionsType}.ValueIs({environmentParameter}, {QuoteString(condition.Key!)}, {QuoteString(condition.Values[0])})" - : $"{ConditionsType}.HasValue({environmentParameter}, {QuoteString(condition.Key!)})"; + : $"{ConditionsType}.HasValue({environmentParameter}, {QuoteString(condition.Key!)})"; parts.Add(condition.Negate ? "!" + call : call); } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/FileLogger.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/FileLogger.cs index ba72b06..326fbbe 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/FileLogger.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/FileLogger.cs @@ -3,7 +3,8 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; -public class FileLogger : IDisposable { +public class FileLogger : IDisposable +{ private readonly string _loggerName; private readonly string _outputFolder; private StringBuilder? _sb; @@ -21,76 +22,94 @@ public static void Wrap( string loggerName, DependencyModuleConfigurationModel configurationModel, Action logger, - Action? reportFailure = null) { - + Action? reportFailure = null + ) + { var fileLogger = new FileLogger(configurationModel, loggerName); - try { + try + { logger(fileLogger); } - catch (Exception e) { + catch (Exception e) + { fileLogger.Error($"{e.Message}\n{e.StackTrace}"); - if (reportFailure == null) { + if (reportFailure == null) + { throw; } reportFailure(e); } - finally { + finally + { fileLogger.Dispose(); } } - - public FileLogger(DependencyModuleConfigurationModel configurationModel, string loggerName) { + + public FileLogger(DependencyModuleConfigurationModel configurationModel, string loggerName) + { _loggerName = loggerName; _outputFolder = configurationModel.LogOutputFolder; - if (!string.IsNullOrEmpty(_outputFolder)) { + if (!string.IsNullOrEmpty(_outputFolder)) + { _sb = new StringBuilder(); } } - public void Info(string message) { + public void Info(string message) + { WriteLog("INFO", message); } - public void Info(string message, object data) { + public void Info(string message, object data) + { WriteLog("INFO", message, data); } - - public void Error(string message) { + + public void Error(string message) + { WriteLog("ERROR", message); } - - public void Error(string message, object data) { + + public void Error(string message, object data) + { WriteLog("ERROR", message, data); } - private void WriteLog(string level, string message, object? data = null) { - if (_sb != null) { + private void WriteLog(string level, string message, object? data = null) + { + if (_sb != null) + { _sb.AppendLine($"{level}: {message}"); - if (data != null) { + if (data != null) + { _sb.AppendLine(data.ToString()); } } } - - public void Dispose() { - if (_sb == null) { + + public void Dispose() + { + if (_sb == null) + { return; } var fileName = $"{_loggerName}.{DateTimeOffset.Now.ToUnixTimeMilliseconds()}.txt"; #pragma warning disable RS1035 - try { + try + { // _sb is only allocated when an output folder was configured, so honour it here rather // than dropping the log into whatever directory the compiler happens to be running in. Directory.CreateDirectory(_outputFolder); File.WriteAllText(Path.Combine(_outputFolder, fileName), _sb.ToString()); } - catch (Exception) { + catch (Exception) + { // Diagnostic logging must never fail a build. } #pragma warning restore RS1035 } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/ITypeDefinitionExtensions.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/ITypeDefinitionExtensions.cs index ae4bec3..218abab 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/ITypeDefinitionExtensions.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/ITypeDefinitionExtensions.cs @@ -2,8 +2,8 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; -public static class ITypeDefinitionExtensions { - +public static class ITypeDefinitionExtensions +{ /// /// Rewrites a generic type's arguments to nothing, so it renders as IRepo<>. /// @@ -19,7 +19,10 @@ public static ITypeDefinition ToUnboundGeneric(this ITypeDefinition type) => generic.TypeDefinitionEnum, generic.Namespace, generic.Name, - generic.TypeArguments.Select(_ => (ITypeDefinition)TypeDefinition.Get("", "")).ToArray()) + generic + .TypeArguments.Select(_ => (ITypeDefinition)TypeDefinition.Get("", "")) + .ToArray() + ) : type; /// @@ -34,22 +37,29 @@ public static ITypeDefinition ToUnboundGeneric(this ITypeDefinition type) => /// a type in the global namespace, which had none to begin with. They are different types and /// need different files, so the global namespace is named rather than left blank. /// - public static string GetFileNameHint(this ITypeDefinition typeDefinition, string rootNamespace, string uniquePart) { + public static string GetFileNameHint( + this ITypeDefinition typeDefinition, + string rootNamespace, + string uniquePart + ) + { var nameString = typeDefinition.Namespace; - if (nameString == rootNamespace || - nameString.StartsWith(rootNamespace + ".")) { + if (nameString == rootNamespace || nameString.StartsWith(rootNamespace + ".")) + { nameString = nameString.Substring(rootNamespace.Length); nameString = nameString.TrimStart('.'); } - else if (string.IsNullOrWhiteSpace(nameString)) { + else if (string.IsNullOrWhiteSpace(nameString)) + { nameString = "global"; } - if (!string.IsNullOrWhiteSpace(nameString)) { + if (!string.IsNullOrWhiteSpace(nameString)) + { nameString += "."; } return $"{nameString}{typeDefinition.Name}.{uniquePart}.g.cs"; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptedMemberReader.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptedMemberReader.cs index 7c96ef3..ca8ef5d 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptedMemberReader.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptedMemberReader.cs @@ -14,8 +14,8 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// equatable across compilations, so holding one would defeat the incremental cache and regenerate /// every wrapper on every keystroke. /// -public static class InterceptedMemberReader { - +public static class InterceptedMemberReader +{ /// /// Everything a wrapper for has to implement, including what it /// inherits from base interfaces. @@ -28,8 +28,9 @@ public static bool Read( INamedTypeSymbol serviceType, out IReadOnlyList members, out IReadOnlyList declarations, - out string? unsupported) { - + out string? unsupported + ) + { unsupported = null; var memberList = new List(); @@ -38,27 +39,34 @@ public static bool Read( members = memberList; declarations = declarationList; - foreach (var candidate in EnumerateMembers(serviceType)) { - if (candidate.IsStatic) { - unsupported = $"'{candidate.Name}' is static, which cannot be forwarded through an instance"; + foreach (var candidate in EnumerateMembers(serviceType)) + { + if (candidate.IsStatic) + { + unsupported = + $"'{candidate.Name}' is static, which cannot be forwarded through an instance"; return false; } - switch (candidate) { + switch (candidate) + { case IMethodSymbol { MethodKind: MethodKind.Ordinary } method: - if (!ReadMethod(method, memberList, declarationList, out unsupported)) { + if (!ReadMethod(method, memberList, declarationList, out unsupported)) + { return false; } break; case IPropertySymbol property: - if (!ReadProperty(property, memberList, declarationList, out unsupported)) { + if (!ReadProperty(property, memberList, declarationList, out unsupported)) + { return false; } break; case IEventSymbol @event: - if (!ReadEvent(@event, memberList, declarationList, out unsupported)) { + if (!ReadEvent(@event, memberList, declarationList, out unsupported)) + { return false; } break; @@ -72,38 +80,47 @@ private static bool ReadMethod( IMethodSymbol method, List members, List declarations, - out string? unsupported) { - - if (method.ReturnsByRef || method.ReturnsByRefReadonly) { + out string? unsupported + ) + { + if (method.ReturnsByRef || method.ReturnsByRefReadonly) + { unsupported = $"'{method.Name}' returns by reference, which cannot be wrapped"; return false; } var parameters = ReadParameters(method.Parameters, method.Name, out unsupported); - if (parameters == null) { + if (parameters == null) + { return false; } var returnShape = GetReturnShape(method.ReturnType); - declarations.Add(new InterceptedDeclarationModel( - DeclarationKind.Method, - EscapeIdentifier(method.Name), - null, - Array.Empty(), - members.Count, - -1)); - - members.Add(new InterceptedMemberModel( - method.Name, - EscapeIdentifier(method.Name), - AccessorForm.Method, - returnShape == ReturnShape.Void ? null : method.ReturnType.GetTypeDefinition(), - GetResultType(method.ReturnType, returnShape), - parameters, - ReadTypeParameters(method), - returnShape)); + declarations.Add( + new InterceptedDeclarationModel( + DeclarationKind.Method, + EscapeIdentifier(method.Name), + null, + Array.Empty(), + members.Count, + -1 + ) + ); + + members.Add( + new InterceptedMemberModel( + method.Name, + EscapeIdentifier(method.Name), + AccessorForm.Method, + returnShape == ReturnShape.Void ? null : method.ReturnType.GetTypeDefinition(), + GetResultType(method.ReturnType, returnShape), + parameters, + ReadTypeParameters(method), + returnShape + ) + ); return true; } @@ -120,14 +137,17 @@ private static bool ReadProperty( IPropertySymbol property, List members, List declarations, - out string? unsupported) { - - if (property.ReturnsByRef || property.ReturnsByRefReadonly) { + out string? unsupported + ) + { + if (property.ReturnsByRef || property.ReturnsByRefReadonly) + { unsupported = $"'{property.Name}' returns by reference, which cannot be wrapped"; return false; } - if (property.SetMethod is { IsInitOnly: true }) { + if (property.SetMethod is { IsInitOnly: true }) + { unsupported = $"'{property.Name}' is init-only, and a wrapper cannot forward to an initializer"; return false; @@ -135,12 +155,15 @@ private static bool ReadProperty( var indices = ReadParameters(property.Parameters, property.Name, out unsupported); - if (indices == null) { + if (indices == null) + { return false; } - if (property.Type.IsRefLikeType) { - unsupported = $"'{property.Name}' is a ref struct and cannot be held for the duration of a call"; + if (property.Type.IsRefLikeType) + { + unsupported = + $"'{property.Name}' is a ref struct and cannot be held for the duration of a call"; return false; } @@ -148,47 +171,59 @@ private static bool ReadProperty( var getter = -1; var setter = -1; - if (property.GetMethod != null) { + if (property.GetMethod != null) + { getter = members.Count; - members.Add(new InterceptedMemberModel( - property.GetMethod.Name, - EscapeIdentifier(property.Name), - property.IsIndexer ? AccessorForm.IndexerGet : AccessorForm.PropertyGet, - type, - type, - indices, - Array.Empty(), - ReturnShape.Value)); + members.Add( + new InterceptedMemberModel( + property.GetMethod.Name, + EscapeIdentifier(property.Name), + property.IsIndexer ? AccessorForm.IndexerGet : AccessorForm.PropertyGet, + type, + type, + indices, + Array.Empty(), + ReturnShape.Value + ) + ); } - if (property.SetMethod != null) { + if (property.SetMethod != null) + { setter = members.Count; // The assigned value is the last argument, after any indices, matching how the CLR names // and orders a setter's parameters. - var arguments = new List(indices) { - new("value", "value", type, null) + var arguments = new List(indices) + { + new("value", "value", type, null), }; - members.Add(new InterceptedMemberModel( - property.SetMethod.Name, - EscapeIdentifier(property.Name), - property.IsIndexer ? AccessorForm.IndexerSet : AccessorForm.PropertySet, - null, - KnownTypes.DependencyModules.Interception.NoResult, - arguments, - Array.Empty(), - ReturnShape.Void)); + members.Add( + new InterceptedMemberModel( + property.SetMethod.Name, + EscapeIdentifier(property.Name), + property.IsIndexer ? AccessorForm.IndexerSet : AccessorForm.PropertySet, + null, + KnownTypes.DependencyModules.Interception.NoResult, + arguments, + Array.Empty(), + ReturnShape.Void + ) + ); } - declarations.Add(new InterceptedDeclarationModel( - property.IsIndexer ? DeclarationKind.Indexer : DeclarationKind.Property, - property.IsIndexer ? "this" : EscapeIdentifier(property.Name), - type, - indices, - getter, - setter)); + declarations.Add( + new InterceptedDeclarationModel( + property.IsIndexer ? DeclarationKind.Indexer : DeclarationKind.Property, + property.IsIndexer ? "this" : EscapeIdentifier(property.Name), + type, + indices, + getter, + setter + ) + ); return true; } @@ -197,11 +232,13 @@ private static bool ReadEvent( IEventSymbol @event, List members, List declarations, - out string? unsupported) { - + out string? unsupported + ) + { unsupported = null; - if (@event.AddMethod == null || @event.RemoveMethod == null) { + if (@event.AddMethod == null || @event.RemoveMethod == null) + { unsupported = $"'{@event.Name}' does not declare both add and remove"; return false; } @@ -213,41 +250,56 @@ private static bool ReadEvent( var remove = members.Count; - members.Add(EventAccessor(@event.RemoveMethod.Name, @event.Name, AccessorForm.EventRemove, type)); + members.Add( + EventAccessor(@event.RemoveMethod.Name, @event.Name, AccessorForm.EventRemove, type) + ); - declarations.Add(new InterceptedDeclarationModel( - DeclarationKind.Event, - EscapeIdentifier(@event.Name), - type, - Array.Empty(), - add, - remove)); + declarations.Add( + new InterceptedDeclarationModel( + DeclarationKind.Event, + EscapeIdentifier(@event.Name), + type, + Array.Empty(), + add, + remove + ) + ); return true; } private static InterceptedMemberModel EventAccessor( - string name, string eventName, AccessorForm form, ITypeDefinition handlerType) => - new(name, + string name, + string eventName, + AccessorForm form, + ITypeDefinition handlerType + ) => + new( + name, EscapeIdentifier(eventName), form, null, KnownTypes.DependencyModules.Interception.NoResult, new InterceptedParameterModel[] { new("value", "value", handlerType, null) }, Array.Empty(), - ReturnShape.Void); + ReturnShape.Void + ); /// /// The interface's own members plus everything it inherits. Skipping base interfaces would /// produce a wrapper that does not satisfy the interface. /// - private static IEnumerable EnumerateMembers(INamedTypeSymbol serviceType) { - foreach (var member in serviceType.GetMembers()) { + private static IEnumerable EnumerateMembers(INamedTypeSymbol serviceType) + { + foreach (var member in serviceType.GetMembers()) + { yield return member; } - foreach (var baseInterface in serviceType.AllInterfaces) { - foreach (var member in baseInterface.GetMembers()) { + foreach (var baseInterface in serviceType.AllInterfaces) + { + foreach (var member in baseInterface.GetMembers()) + { yield return member; } } @@ -262,41 +314,51 @@ private static IEnumerable EnumerateMembers(INamedTypeSymbol serviceTyp /// as code that does not compile. A hand-written decorator remains the answer for those. /// private static IReadOnlyList? ReadParameters( - IReadOnlyList declared, string memberName, out string? unsupported) { - + IReadOnlyList declared, + string memberName, + out string? unsupported + ) + { unsupported = null; var parameters = new List(); - foreach (var parameter in declared) { - if (parameter.RefKind != RefKind.None) { - var keyword = parameter.RefKind switch { + foreach (var parameter in declared) + { + if (parameter.RefKind != RefKind.None) + { + var keyword = parameter.RefKind switch + { RefKind.Ref => "ref", RefKind.Out => "out", - _ => "in" + _ => "in", }; unsupported = - $"'{memberName}' takes '{parameter.Name}' by {keyword}, and an argument passed by " + - "reference cannot be held for the duration of a call"; + $"'{memberName}' takes '{parameter.Name}' by {keyword}, and an argument passed by " + + "reference cannot be held for the duration of a call"; return null; } - if (parameter.Type.IsRefLikeType) { + if (parameter.Type.IsRefLikeType) + { unsupported = - $"'{memberName}' takes '{parameter.Name}', which is a ref struct and cannot be " + - "held for the duration of a call"; + $"'{memberName}' takes '{parameter.Name}', which is a ref struct and cannot be " + + "held for the duration of a call"; return null; } - parameters.Add(new InterceptedParameterModel( - parameter.Name, - EscapeIdentifier(parameter.Name), - parameter.Type.GetTypeDefinition(), - RenderDefaultValue(parameter), - parameter.IsParams)); + parameters.Add( + new InterceptedParameterModel( + parameter.Name, + EscapeIdentifier(parameter.Name), + parameter.Type.GetTypeDefinition(), + RenderDefaultValue(parameter), + parameter.IsParams + ) + ); } return parameters; @@ -306,45 +368,56 @@ private static IEnumerable EnumerateMembers(INamedTypeSymbol serviceTyp /// A parameter's default as it should be written on the wrapper. Dropping it would narrow the /// signature the interface promised. /// - private static string? RenderDefaultValue(IParameterSymbol parameter) { - if (!parameter.HasExplicitDefaultValue) { + private static string? RenderDefaultValue(IParameterSymbol parameter) + { + if (!parameter.HasExplicitDefaultValue) + { return null; } var value = parameter.ExplicitDefaultValue; - if (value == null) { + if (value == null) + { return parameter.Type.IsValueType ? "default" : "null"; } // An enum default arrives as its underlying value, so it is cast back rather than guessed at // by matching the value against the enum's members. - if (parameter.Type.TypeKind == TypeKind.Enum) { + if (parameter.Type.TypeKind == TypeKind.Enum) + { var builder = new StringBuilder("("); parameter.Type.GetTypeDefinition().WriteTypeName(builder, TypeOutputMode.Global); - return builder.Append(')') + return builder + .Append(')') .Append(Convert.ToString(value, CultureInfo.InvariantCulture)) .ToString(); } return Microsoft.CodeAnalysis.CSharp.SymbolDisplay.FormatPrimitive( - value, quoteStrings: true, useHexadecimalNumbers: false); + value, + quoteStrings: true, + useHexadecimalNumbers: false + ); } /// /// The member's type parameters and their constraints. The state class repeats both, or the call /// it forwards will not satisfy the constraints the interface declared. /// - private static IReadOnlyList ReadTypeParameters(IMethodSymbol method) { - if (method.TypeParameters.Length == 0) { + private static IReadOnlyList ReadTypeParameters(IMethodSymbol method) + { + if (method.TypeParameters.Length == 0) + { return Array.Empty(); } var typeParameters = new List(); - foreach (var parameter in method.TypeParameters) { + foreach (var parameter in method.TypeParameters) + { typeParameters.Add(TypeParameterReader.Read(parameter)); } @@ -352,12 +425,15 @@ private static IReadOnlyList ReadTypeParameters(IMethodSymbo } private static string EscapeIdentifier(string name) => - Microsoft.CodeAnalysis.CSharp.SyntaxFacts.GetKeywordKind(name) == Microsoft.CodeAnalysis.CSharp.SyntaxKind.None + Microsoft.CodeAnalysis.CSharp.SyntaxFacts.GetKeywordKind(name) + == Microsoft.CodeAnalysis.CSharp.SyntaxKind.None ? name : "@" + name; - private static ReturnShape GetReturnShape(ITypeSymbol returnType) { - if (returnType.SpecialType == SpecialType.System_Void) { + private static ReturnShape GetReturnShape(ITypeSymbol returnType) + { + if (returnType.SpecialType == SpecialType.System_Void) + { return ReturnShape.Void; } @@ -369,13 +445,14 @@ private static ReturnShape GetReturnShape(ITypeSymbol returnType) { ? containing.ToDisplayString() + "." + definition.MetadataName : definition.MetadataName; - return name switch { + return name switch + { "System.Threading.Tasks.Task" => ReturnShape.Task, "System.Threading.Tasks.Task`1" => ReturnShape.TaskOfValue, "System.Threading.Tasks.ValueTask" => ReturnShape.ValueTask, "System.Threading.Tasks.ValueTask`1" => ReturnShape.ValueTaskOfValue, "System.Collections.Generic.IAsyncEnumerable`1" => ReturnShape.AsyncEnumerable, - _ => ReturnShape.Value + _ => ReturnShape.Value, }; } @@ -384,8 +461,10 @@ private static ReturnShape GetReturnShape(ITypeSymbol returnType) { /// through the same pipeline, standing on NoResult so an interceptor never needs an /// overload for the void case. /// - private static ITypeDefinition GetResultType(ITypeSymbol returnType, ReturnShape shape) { - switch (shape) { + private static ITypeDefinition GetResultType(ITypeSymbol returnType, ReturnShape shape) + { + switch (shape) + { case ReturnShape.Void: case ReturnShape.Task: case ReturnShape.ValueTask: diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptorModelUtility.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptorModelUtility.cs index e90bb21..57337c3 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptorModelUtility.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptorModelUtility.cs @@ -9,8 +9,8 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// /// Builds an from a class carrying [Intercept]. /// -public static class InterceptorModelUtility { - +public static class InterceptorModelUtility +{ private const string InterceptionNamespace = "DependencyModules.Runtime.Interception"; /// @@ -23,22 +23,33 @@ public static class InterceptorModelUtility { /// A diagnostic cannot be raised from here — the transform holds no context that can. /// public static InterceptorModel GetInterceptorModel( - SyntaxTransformContext context, CancellationToken cancellationToken) { - + SyntaxTransformContext context, + CancellationToken cancellationToken + ) + { cancellationToken.ThrowIfCancellationRequested(); - if (context.Node is not TypeDeclarationSyntax typeDeclarationSyntax) { + if (context.Node is not TypeDeclarationSyntax typeDeclarationSyntax) + { return InterceptorModel.Ignore; } - var attributes = FindAttributes(typeDeclarationSyntax, context.SemanticModel, cancellationToken); + var attributes = FindAttributes( + typeDeclarationSyntax, + context.SemanticModel, + cancellationToken + ); - if (attributes.Count == 0) { + if (attributes.Count == 0) + { return InterceptorModel.Ignore; } - if (context.SemanticModel.GetDeclaredSymbol(typeDeclarationSyntax, cancellationToken) - is not INamedTypeSymbol implementationSymbol) { + if ( + context.SemanticModel.GetDeclaredSymbol(typeDeclarationSyntax, cancellationToken) + is not INamedTypeSymbol implementationSymbol + ) + { return InterceptorModel.Ignore; } @@ -61,37 +72,62 @@ public static InterceptorModel GetInterceptorModel( // Every [Intercept], not the first. The attribute is AllowMultiple, so stacking them is a // supported way to write what one attribute can also express as a params list — and reading // only the first dropped every interceptor after it, with nothing to say so. - foreach (var attribute in attributes) { + foreach (var attribute in attributes) + { ReadAttribute( - attribute, context, cancellationToken, interceptorSymbols, ref order, ref explicitService, ref realm); + attribute, + context, + cancellationToken, + interceptorSymbols, + ref order, + ref explicitService, + ref realm + ); memberKinds &= ReadMemberKinds(attribute); } - if (interceptorSymbols.Count == 0) { + if (interceptorSymbols.Count == 0) + { return InterceptorModel.Ignore; } - var serviceSymbol = ResolveServiceInterface(implementationSymbol, explicitService, out var unsupported); + var serviceSymbol = ResolveServiceInterface( + implementationSymbol, + explicitService, + out var unsupported + ); - if (serviceSymbol == null) { + if (serviceSymbol == null) + { return Refuse(unsupported, context); } - if (!InterceptedMemberReader.Read(serviceSymbol, out var members, out var declarations, out unsupported)) { + if ( + !InterceptedMemberReader.Read( + serviceSymbol, + out var members, + out var declarations, + out unsupported + ) + ) + { return Refuse(unsupported, context); } - if (members.Count == 0) { + if (members.Count == 0) + { return InterceptorModel.Refused( - $"'{serviceSymbol.Name}' declares nothing to intercept"); + $"'{serviceSymbol.Name}' declares nothing to intercept" + ); } members = ApplyMemberKinds(members, declarations, memberKinds); var interceptors = new List(); - foreach (var (interceptorSymbol, lifestyle) in interceptorSymbols) { + foreach (var (interceptorSymbol, lifestyle) in interceptorSymbols) + { interceptors.Add(ReadInterceptorType(interceptorSymbol, lifestyle)); } @@ -109,9 +145,12 @@ public static InterceptorModel GetInterceptorModel( declarations, order, TypeParameters: TypeParameterModels(implementationSymbol), - Realm: (realm?.GetTypeDefinition() ?? - RegistrationRealm(typeDeclarationSyntax, context, cancellationToken)), - Location: LocationModel.From(context.Node)); + Realm: ( + realm?.GetTypeDefinition() + ?? RegistrationRealm(typeDeclarationSyntax, context, cancellationToken) + ), + Location: LocationModel.From(context.Node) + ); } /// @@ -139,19 +178,30 @@ public static InterceptorModel GetInterceptorModel( private static ITypeDefinition? RegistrationRealm( TypeDeclarationSyntax typeDeclarationSyntax, SyntaxTransformContext context, - CancellationToken cancellationToken) { - - foreach (var attributeList in typeDeclarationSyntax.AttributeLists) { - foreach (var attribute in attributeList.Attributes) { - if (!IsServiceAttribute(attribute, context, cancellationToken)) { + CancellationToken cancellationToken + ) + { + foreach (var attributeList in typeDeclarationSyntax.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + if (!IsServiceAttribute(attribute, context, cancellationToken)) + { continue; } - foreach (var argument in attribute.ArgumentList?.Arguments ?? - default(SeparatedSyntaxList)) { - if (argument.NameEquals?.Name.ToString() == "Realm" && - argument.Expression is TypeOfExpressionSyntax realmTypeOf) { - return ResolveType(realmTypeOf, context, cancellationToken)?.GetTypeDefinition(); + foreach ( + var argument in attribute.ArgumentList?.Arguments + ?? default(SeparatedSyntaxList) + ) + { + if ( + argument.NameEquals?.Name.ToString() == "Realm" + && argument.Expression is TypeOfExpressionSyntax realmTypeOf + ) + { + return ResolveType(realmTypeOf, context, cancellationToken) + ?.GetTypeDefinition(); } } } @@ -161,11 +211,22 @@ public static InterceptorModel GetInterceptorModel( } private static bool IsServiceAttribute( - AttributeSyntax attribute, SyntaxTransformContext context, CancellationToken cancellationToken) { - - foreach (var serviceAttribute in ServiceAttributeTypes) { - if (AttributeTypeMatcher.Matches( - context.SemanticModel, attribute, serviceAttribute, cancellationToken)) { + AttributeSyntax attribute, + SyntaxTransformContext context, + CancellationToken cancellationToken + ) + { + foreach (var serviceAttribute in ServiceAttributeTypes) + { + if ( + AttributeTypeMatcher.Matches( + context.SemanticModel, + attribute, + serviceAttribute, + cancellationToken + ) + ) + { return true; } } @@ -173,11 +234,12 @@ private static bool IsServiceAttribute( return false; } - private static readonly ITypeDefinition[] ServiceAttributeTypes = { + private static readonly ITypeDefinition[] ServiceAttributeTypes = + { KnownTypes.DependencyModules.Attributes.SingletonServiceAttribute, KnownTypes.DependencyModules.Attributes.ScopedServiceAttribute, KnownTypes.DependencyModules.Attributes.TransientServiceAttribute, - KnownTypes.DependencyModules.Attributes.CrossWireServiceAttribute + KnownTypes.DependencyModules.Attributes.CrossWireServiceAttribute, }; /// @@ -189,24 +251,41 @@ private static bool IsServiceAttribute( /// segment of each part handles `Methods`, `InterceptedMembers.Methods` and any qualification of /// either. /// - private static InterceptedMemberKinds ReadMemberKinds(AttributeSyntax attribute) { - foreach (var argument in attribute.ArgumentList?.Arguments ?? - default(SeparatedSyntaxList)) { - if (argument.NameEquals?.Name.ToString() != "Members") { + private static InterceptedMemberKinds ReadMemberKinds(AttributeSyntax attribute) + { + foreach ( + var argument in attribute.ArgumentList?.Arguments + ?? default(SeparatedSyntaxList) + ) + { + if (argument.NameEquals?.Name.ToString() != "Members") + { continue; } var kinds = InterceptedMemberKinds.None; - foreach (var part in argument.Expression.ToString().Split('|')) { + foreach (var part in argument.Expression.ToString().Split('|')) + { var member = part.Substring(part.LastIndexOf('.') + 1).Trim(); - 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; + 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; } } @@ -228,46 +307,56 @@ private static InterceptedMemberKinds ReadMemberKinds(AttributeSyntax attribute) private static IReadOnlyList ApplyMemberKinds( IReadOnlyList members, IReadOnlyList declarations, - InterceptedMemberKinds kinds) { - - if (kinds == InterceptedMemberKinds.All) { + InterceptedMemberKinds kinds + ) + { + if (kinds == InterceptedMemberKinds.All) + { return members; } var excluded = new HashSet(); - foreach (var declaration in declarations) { - if (KindOf(declaration.Kind) is var kind && (kinds & kind) != 0) { + foreach (var declaration in declarations) + { + if (KindOf(declaration.Kind) is var kind && (kinds & kind) != 0) + { continue; } excluded.Add(declaration.First); - if (declaration.Second >= 0) { + if (declaration.Second >= 0) + { excluded.Add(declaration.Second); } } - if (excluded.Count == 0) { + if (excluded.Count == 0) + { return members; } var result = new List(members.Count); - for (var index = 0; index < members.Count; index++) { - result.Add(excluded.Contains(index) ? members[index] with { Excluded = true } : members[index]); + for (var index = 0; index < members.Count; index++) + { + result.Add( + excluded.Contains(index) ? members[index] with { Excluded = true } : members[index] + ); } return result; } private static InterceptedMemberKinds KindOf(DeclarationKind kind) => - kind switch { + kind switch + { DeclarationKind.Method => InterceptedMemberKinds.Methods, DeclarationKind.Property => InterceptedMemberKinds.Properties, DeclarationKind.Indexer => InterceptedMemberKinds.Indexers, DeclarationKind.Event => InterceptedMemberKinds.Events, - _ => InterceptedMemberKinds.All + _ => InterceptedMemberKinds.All, }; private static InterceptorModel Refuse(string? reason, SyntaxTransformContext context) => @@ -279,17 +368,23 @@ private static InterceptorModel Refuse(string? reason, SyntaxTransformContext co /// The interfaces an interceptor implements, which decide the members it can be placed around. /// private static InterceptorTypeModel ReadInterceptorType( - INamedTypeSymbol symbol, ServiceLifestyle lifestyle) { + INamedTypeSymbol symbol, + ServiceLifestyle lifestyle + ) + { var sync = false; var async = false; var stream = false; - foreach (var implemented in symbol.AllInterfaces) { - if (implemented.ContainingNamespace?.ToDisplayString() != InterceptionNamespace) { + foreach (var implemented in symbol.AllInterfaces) + { + if (implemented.ContainingNamespace?.ToDisplayString() != InterceptionNamespace) + { continue; } - switch (implemented.Name) { + switch (implemented.Name) + { case "IInterceptor": sync = true; break; @@ -315,11 +410,16 @@ private static InterceptorTypeModel ReadInterceptorType( /// when none of them matches at all is there no wrapper worth generating. /// private static bool AnyMemberIsIntercepted( - IReadOnlyList interceptors, IReadOnlyList members) { - - foreach (var member in members) { - foreach (var interceptor in interceptors) { - if (interceptor.CanServe(member.Kind)) { + IReadOnlyList interceptors, + IReadOnlyList members + ) + { + foreach (var member in members) + { + foreach (var interceptor in interceptors) + { + if (interceptor.CanServe(member.Kind)) + { return true; } } @@ -335,9 +435,11 @@ private static void ReadAttribute( List<(INamedTypeSymbol Symbol, ServiceLifestyle Lifestyle)> interceptors, ref int order, ref INamedTypeSymbol? explicitService, - ref INamedTypeSymbol? realm) { - - if (attribute.ArgumentList == null) { + ref INamedTypeSymbol? realm + ) + { + if (attribute.ArgumentList == null) + { return; } @@ -346,14 +448,18 @@ private static void ReadAttribute( // lifetime happened to be at that point in the argument list. var lifestyle = ReadLifetime(attribute); - foreach (var argument in attribute.ArgumentList.Arguments) { + foreach (var argument in attribute.ArgumentList.Arguments) + { var name = argument.NameEquals?.Name.ToString(); - if (name == null) { - if (argument.Expression is TypeOfExpressionSyntax typeOf) { + if (name == null) + { + if (argument.Expression is TypeOfExpressionSyntax typeOf) + { var symbol = ResolveType(typeOf, context, cancellationToken); - if (symbol != null) { + if (symbol != null) + { interceptors.Add((symbol, lifestyle)); } } @@ -361,19 +467,23 @@ private static void ReadAttribute( continue; } - switch (name) { + switch (name) + { case "Order": - if (int.TryParse(argument.Expression.ToString(), out var parsed)) { + if (int.TryParse(argument.Expression.ToString(), out var parsed)) + { order = parsed; } break; case "Service": - if (argument.Expression is TypeOfExpressionSyntax serviceTypeOf) { + if (argument.Expression is TypeOfExpressionSyntax serviceTypeOf) + { explicitService = ResolveType(serviceTypeOf, context, cancellationToken); } break; case "Realm": - if (argument.Expression is TypeOfExpressionSyntax realmTypeOf) { + if (argument.Expression is TypeOfExpressionSyntax realmTypeOf) + { realm = ResolveType(realmTypeOf, context, cancellationToken); } break; @@ -391,17 +501,23 @@ private static void ReadAttribute( /// last segment is the answer for `Scoped`, `ServiceLifetime.Scoped` and any qualification of /// it alike. /// - private static ServiceLifestyle ReadLifetime(AttributeSyntax attribute) { - foreach (var argument in attribute.ArgumentList?.Arguments ?? - default(SeparatedSyntaxList)) { - if (argument.NameEquals?.Name.ToString() != "Lifetime") { + private static ServiceLifestyle ReadLifetime(AttributeSyntax attribute) + { + 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) { + switch (member) + { case "Scoped": return ServiceLifestyle.Scoped; case "Transient": @@ -415,8 +531,10 @@ private static ServiceLifestyle ReadLifetime(AttributeSyntax attribute) { } private static INamedTypeSymbol? ResolveType( - TypeOfExpressionSyntax typeOf, SyntaxTransformContext context, CancellationToken cancellationToken) => - context.SemanticModel.GetTypeInfo(typeOf.Type, cancellationToken).Type as INamedTypeSymbol; + TypeOfExpressionSyntax typeOf, + SyntaxTransformContext context, + CancellationToken cancellationToken + ) => context.SemanticModel.GetTypeInfo(typeOf.Type, cancellationToken).Type as INamedTypeSymbol; /// /// The interface to wrap. Interception works through an interface: a call the implementation @@ -424,13 +542,19 @@ private static ServiceLifestyle ReadLifetime(AttributeSyntax attribute) { /// to wrap. /// private static INamedTypeSymbol? ResolveServiceInterface( - INamedTypeSymbol implementation, INamedTypeSymbol? explicitService, out string? unsupported) { - + INamedTypeSymbol implementation, + INamedTypeSymbol? explicitService, + out string? unsupported + ) + { unsupported = null; - if (explicitService != null) { - foreach (var candidate in implementation.AllInterfaces) { - if (SymbolEqualityComparer.Default.Equals(candidate, explicitService)) { + if (explicitService != null) + { + foreach (var candidate in implementation.AllInterfaces) + { + if (SymbolEqualityComparer.Default.Equals(candidate, explicitService)) + { return candidate; } } @@ -441,12 +565,15 @@ private static ServiceLifestyle ReadLifetime(AttributeSyntax attribute) { var interfaces = DeclaredInterfaces(implementation); - if (interfaces.Length == 0) { - unsupported = $"'{implementation.Name}' implements no interface, so there is nothing to intercept"; + if (interfaces.Length == 0) + { + unsupported = + $"'{implementation.Name}' implements no interface, so there is nothing to intercept"; return null; } - if (interfaces.Length > 1) { + if (interfaces.Length > 1) + { unsupported = $"'{implementation.Name}' implements more than one interface; set Service to choose which to intercept"; return null; @@ -469,9 +596,14 @@ private static ServiceLifestyle ReadLifetime(AttributeSyntax attribute) { /// Not AllInterfaces, which flattens what the interfaces themselves extend and would /// report a plain IDerived as ambiguous with the IBase behind it. /// - private static ImmutableArray DeclaredInterfaces(INamedTypeSymbol implementation) { - for (var type = implementation; type != null; type = type.BaseType) { - if (type.Interfaces.Length > 0) { + private static ImmutableArray DeclaredInterfaces( + INamedTypeSymbol implementation + ) + { + for (var type = implementation; type != null; type = type.BaseType) + { + if (type.Interfaces.Length > 0) + { return type.Interfaces; } } @@ -483,28 +615,37 @@ private static ImmutableArray DeclaredInterfaces(INamedTypeSym /// The implementation's type parameters and their constraints, which the wrapper repeats so its /// own parameters line up with the ones the service and the implementation are closed over. /// - private static IReadOnlyList TypeParameterModels(INamedTypeSymbol symbol) { - if (symbol.TypeParameters.Length == 0) { + private static IReadOnlyList TypeParameterModels(INamedTypeSymbol symbol) + { + if (symbol.TypeParameters.Length == 0) + { return Array.Empty(); } var models = new TypeParameterModel[symbol.TypeParameters.Length]; - for (var i = 0; i < models.Length; i++) { + for (var i = 0; i < models.Length; i++) + { models[i] = TypeParameterReader.Read(symbol.TypeParameters[i]); } return models; } - private static ITypeDefinition ToTypeDefinition(INamedTypeSymbol symbol) { + private static ITypeDefinition ToTypeDefinition(INamedTypeSymbol symbol) + { var namespaceName = symbol.ContainingNamespace.IsGlobalNamespace ? "" : symbol.ContainingNamespace.ToDisplayString(); var name = symbol.Name; - for (var containing = symbol.ContainingType; containing != null; containing = containing.ContainingType) { + for ( + var containing = symbol.ContainingType; + containing != null; + containing = containing.ContainingType + ) + { name = containing.Name + "." + name; } @@ -527,17 +668,24 @@ private static ITypeDefinition ToTypeDefinition(INamedTypeSymbol symbol) { private static List FindAttributes( TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel semanticModel, - CancellationToken cancellationToken) { - + CancellationToken cancellationToken + ) + { var attributes = new List(); - foreach (var attributeList in typeDeclarationSyntax.AttributeLists) { - foreach (var attribute in attributeList.Attributes) { - if (AttributeTypeMatcher.Matches( + foreach (var attributeList in typeDeclarationSyntax.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + if ( + AttributeTypeMatcher.Matches( semanticModel, attribute, KnownTypes.DependencyModules.Attributes.InterceptAttribute, - cancellationToken)) { + cancellationToken + ) + ) + { attributes.Add(attribute); } } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/ModuleDecoratorResolver.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/ModuleDecoratorResolver.cs index ea22b61..88b9b57 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/ModuleDecoratorResolver.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/ModuleDecoratorResolver.cs @@ -18,8 +18,8 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// new for it, and emitting one is what makes the decoration survive publishing. /// /// -public static class ModuleDecoratorResolver { - +public static class ModuleDecoratorResolver +{ /// /// A resolved decorator, or the reason it could not be. /// @@ -35,11 +35,15 @@ public record Resolution(DecoratorModel Model, string? Reason); public static IReadOnlyList Resolve( ModuleEntryPointModel entryPointModel, Compilation compilation, - CancellationToken cancellationToken) { - + CancellationToken cancellationToken + ) + { var resolutions = new List(); - foreach (var decorator in DecoratorModelUtility.GetModuleDeclaredDecorators(entryPointModel)) { + foreach ( + var decorator in DecoratorModelUtility.GetModuleDeclaredDecorators(entryPointModel) + ) + { cancellationToken.ThrowIfCancellationRequested(); resolutions.Add(Resolve(decorator, compilation)); @@ -48,32 +52,44 @@ public static IReadOnlyList Resolve( return resolutions; } - private static Resolution Resolve(DecoratorModel decorator, Compilation compilation) { + private static Resolution Resolve(DecoratorModel decorator, Compilation compilation) + { var symbol = Find(compilation, decorator.DecoratorType); - if (symbol == null) { + if (symbol == null) + { return new Resolution( - decorator, "its type could not be resolved from this compilation or its references"); + decorator, + "its type could not be resolved from this compilation or its references" + ); } var constructor = SymbolConstructorReader.Read(symbol); - if (constructor == null) { + if (constructor == null) + { return new Resolution(decorator, "it has no public constructor"); } var innerIndex = IndexOfInner(constructor, decorator.ServiceType); - if (innerIndex < 0) { + if (innerIndex < 0) + { return new Resolution( decorator, - $"no constructor parameter takes '{decorator.ServiceType.Name}', so there is nowhere " + - "to pass the instance being wrapped"); + $"no constructor parameter takes '{decorator.ServiceType.Name}', so there is nowhere " + + "to pass the instance being wrapped" + ); } return new Resolution( - decorator with { Constructor = constructor, InnerParameterIndex = innerIndex }, - null); + decorator with + { + Constructor = constructor, + InnerParameterIndex = innerIndex, + }, + null + ); } /// @@ -86,13 +102,18 @@ private static Resolution Resolve(DecoratorModel decorator, Compilation compilat /// parameter type keeps its names, because closing the decorator over a registration reads the /// type parameter order back off it. /// - private static int IndexOfInner(ConstructorInfoModel constructor, ITypeDefinition serviceType) { + private static int IndexOfInner(ConstructorInfoModel constructor, ITypeDefinition serviceType) + { var wanted = serviceType.ToUnboundGeneric(); - for (var i = 0; i < constructor.Parameters.Count; i++) { + for (var i = 0; i < constructor.Parameters.Count; i++) + { var parameterType = constructor.Parameters[i].ParameterType.MakeNullable(false); - if (parameterType.Equals(serviceType) || parameterType.ToUnboundGeneric().Equals(wanted)) { + if ( + parameterType.Equals(serviceType) || parameterType.ToUnboundGeneric().Equals(wanted) + ) + { return i; } } @@ -107,10 +128,14 @@ private static int IndexOfInner(ConstructorInfoModel constructor, ITypeDefinitio /// A generic decorator arrives with its arguments blanked, because an unbound generic is what a /// typeof can carry — so the metadata name needs the arity back on it. /// - private static INamedTypeSymbol? Find(Compilation compilation, ITypeDefinition type) { - var name = string.IsNullOrEmpty(type.Namespace) ? type.Name : type.Namespace + "." + type.Name; - - if (type is GenericTypeDefinition { TypeArguments.Count: > 0 } generic) { + private static INamedTypeSymbol? Find(Compilation compilation, ITypeDefinition type) + { + var name = string.IsNullOrEmpty(type.Namespace) + ? type.Name + : type.Namespace + "." + type.Name; + + if (type is GenericTypeDefinition { TypeArguments.Count: > 0 } generic) + { name += "`" + generic.TypeArguments.Count; } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/ServiceModelUtility.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/ServiceModelUtility.cs index 65866b6..39cb958 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/ServiceModelUtility.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/ServiceModelUtility.cs @@ -7,7 +7,8 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; -public class ServiceModelUtility { +public class ServiceModelUtility +{ /// /// Interfaces that describe a capability rather than a role, keyed by namespace and name. /// @@ -41,11 +42,12 @@ public class ServiceModelUtility { /// typeof(IDisposable))] still registers IDisposable. This governs inference only. /// /// - private static readonly HashSet _capabilityInterfaces = new() { + private static readonly HashSet _capabilityInterfaces = new() + { "System.IDisposable", "System.IAsyncDisposable", "System.ICloneable", - "System.IComparable", // covers IComparable, same name + "System.IComparable", // covers IComparable, same name "System.IEquatable", "System.IConvertible", "System.IFormattable", @@ -57,28 +59,44 @@ public class ServiceModelUtility { "System.Runtime.Serialization.ISerializable", "System.ComponentModel.INotifyPropertyChanged", "System.ComponentModel.INotifyPropertyChanging", - "System.Collections.Specialized.INotifyCollectionChanged" + "System.Collections.Specialized.INotifyCollectionChanged", }; - private static readonly ITypeDefinition _crossWireService = - KnownTypes.DependencyModules.Attributes.CrossWireServiceAttribute; - - private static readonly ITypeDefinition _serializerService = - KnownTypes.Microsoft.TextJson.JsonSourceGenerationOptionsAttribute; - - private static readonly ITypeDefinition[] _attributeTypes = { - KnownTypes.DependencyModules.Attributes.TransientServiceAttribute, KnownTypes.DependencyModules.Attributes.ScopedServiceAttribute, KnownTypes.DependencyModules.Attributes.SingletonServiceAttribute, + private static readonly ITypeDefinition _crossWireService = KnownTypes + .DependencyModules + .Attributes + .CrossWireServiceAttribute; + + private static readonly ITypeDefinition _serializerService = KnownTypes + .Microsoft + .TextJson + .JsonSourceGenerationOptionsAttribute; + + private static readonly ITypeDefinition[] _attributeTypes = + { + KnownTypes.DependencyModules.Attributes.TransientServiceAttribute, + KnownTypes.DependencyModules.Attributes.ScopedServiceAttribute, + KnownTypes.DependencyModules.Attributes.SingletonServiceAttribute, }; /// /// The lifetime a service attribute type carries. /// - private static ServiceLifestyle LifestyleOf(ITypeDefinition attributeType) { - if (attributeType.Name == KnownTypes.DependencyModules.Attributes.SingletonServiceAttribute.Name) { + private static ServiceLifestyle LifestyleOf(ITypeDefinition attributeType) + { + if ( + attributeType.Name + == KnownTypes.DependencyModules.Attributes.SingletonServiceAttribute.Name + ) + { return ServiceLifestyle.Singleton; } - if (attributeType.Name == KnownTypes.DependencyModules.Attributes.ScopedServiceAttribute.Name) { + if ( + attributeType.Name + == KnownTypes.DependencyModules.Attributes.ScopedServiceAttribute.Name + ) + { return ServiceLifestyle.Scoped; } @@ -86,53 +104,84 @@ private static ServiceLifestyle LifestyleOf(ITypeDefinition attributeType) { } public static ServiceModel? GetServiceModel( - SyntaxTransformContext context, CancellationToken cancellationToken) { + SyntaxTransformContext context, + CancellationToken cancellationToken + ) + { cancellationToken.ThrowIfCancellationRequested(); - if (context.Node is ClassDeclarationSyntax or RecordDeclarationSyntax) { + if (context.Node is ClassDeclarationSyntax or RecordDeclarationSyntax) + { return GetClassDeclarationServiceModel(context, cancellationToken); } - if (context.Node is MethodDeclarationSyntax methodDeclarationSyntax) { - return MethodDeclarationServiceModel(context, methodDeclarationSyntax, cancellationToken); + if (context.Node is MethodDeclarationSyntax methodDeclarationSyntax) + { + return MethodDeclarationServiceModel( + context, + methodDeclarationSyntax, + cancellationToken + ); } return null; } - private static ServiceModel? MethodDeclarationServiceModel(SyntaxTransformContext context, MethodDeclarationSyntax methodDeclarationSyntax, CancellationToken cancellationToken) { + private static ServiceModel? MethodDeclarationServiceModel( + SyntaxTransformContext context, + MethodDeclarationSyntax methodDeclarationSyntax, + CancellationToken cancellationToken + ) + { // only support public or internal factory methods - if (methodDeclarationSyntax.Modifiers.Any( - m => m.IsKind(SyntaxKind.PrivateKeyword) || m.IsKind(SyntaxKind.ProtectedKeyword))) { + if ( + methodDeclarationSyntax.Modifiers.Any(m => + m.IsKind(SyntaxKind.PrivateKeyword) || m.IsKind(SyntaxKind.ProtectedKeyword) + ) + ) + { return null; } // only support static methods - if (!methodDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) { + if (!methodDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) + { return null; } var returnType = methodDeclarationSyntax.ReturnType.GetTypeDefinition(context); var factoryModel = GetFactoryModel(context, methodDeclarationSyntax, cancellationToken); - if (returnType == null || factoryModel == null) { + if (returnType == null || factoryModel == null) + { return null; } - var models = - AttributeModelHelper.GetAttributeModels(context, context.Node, cancellationToken); + var models = AttributeModelHelper.GetAttributeModels( + context, + context.Node, + cancellationToken + ); return new ServiceModel( returnType, null, - factoryModel, null, + factoryModel, + null, GetRegistrations(context, returnType, models, cancellationToken), RegistrationFeature.None, - Location: LocationModel.From(context.Node)); + Location: LocationModel.From(context.Node) + ); } - private static ServiceFactoryModel? GetFactoryModel(SyntaxTransformContext context, MethodDeclarationSyntax methodDeclarationSyntax, CancellationToken cancellationToken) { + private static ServiceFactoryModel? GetFactoryModel( + SyntaxTransformContext context, + MethodDeclarationSyntax methodDeclarationSyntax, + CancellationToken cancellationToken + ) + { var factoryClass = methodDeclarationSyntax.FirstAncestorOrSelf(); - if (factoryClass == null) { + if (factoryClass == null) + { return null; } @@ -141,7 +190,8 @@ private static ServiceLifestyle LifestyleOf(ITypeDefinition attributeType) { return new ServiceFactoryModel( factoryType, methodDeclarationSyntax.Identifier.ToString().Trim('"'), - methodDeclarationSyntax.GetMethodParameters(context, cancellationToken)); + methodDeclarationSyntax.GetMethodParameters(context, cancellationToken) + ); } /// @@ -149,22 +199,28 @@ private static ServiceLifestyle LifestyleOf(ITypeDefinition attributeType) { /// report it instead of emitting a registration that fails when the provider is built. /// private static RegistrationFeature GetConstructionFeatures( - SyntaxTransformContext context, CancellationToken cancellationToken) { - - if (context.Node is not TypeDeclarationSyntax typeDeclarationSyntax) { + SyntaxTransformContext context, + CancellationToken cancellationToken + ) + { + if (context.Node is not TypeDeclarationSyntax typeDeclarationSyntax) + { return RegistrationFeature.None; } var features = RegistrationFeature.None; - if (typeDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) { + if (typeDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) + { features |= RegistrationFeature.StaticImplementation; } - else if (typeDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.AbstractKeyword))) { + else if (typeDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.AbstractKeyword))) + { features |= RegistrationFeature.AbstractImplementation; } - if (IsIntercepted(typeDeclarationSyntax, context, cancellationToken)) { + if (IsIntercepted(typeDeclarationSyntax, context, cancellationToken)) + { features |= RegistrationFeature.Intercepted; } @@ -183,15 +239,22 @@ private static RegistrationFeature GetConstructionFeatures( private static bool IsIntercepted( TypeDeclarationSyntax typeDeclarationSyntax, SyntaxTransformContext context, - CancellationToken cancellationToken) { - - foreach (var attributeList in typeDeclarationSyntax.AttributeLists) { - foreach (var attribute in attributeList.Attributes) { - if (AttributeTypeMatcher.Matches( + CancellationToken cancellationToken + ) + { + foreach (var attributeList in typeDeclarationSyntax.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + if ( + AttributeTypeMatcher.Matches( context.SemanticModel, attribute, KnownTypes.DependencyModules.Attributes.InterceptAttribute, - cancellationToken)) { + cancellationToken + ) + ) + { return true; } } @@ -200,29 +263,44 @@ private static bool IsIntercepted( return false; } - private static ServiceModel? GetClassDeclarationServiceModel(SyntaxTransformContext context, CancellationToken cancellationToken) { + private static ServiceModel? GetClassDeclarationServiceModel( + SyntaxTransformContext context, + CancellationToken cancellationToken + ) + { var classDefinition = GetClassDefinition(context); - if (classDefinition == null) { + if (classDefinition == null) + { return null; } - var attributes = - AttributeModelHelper.GetAttributeModels(context, context.Node, cancellationToken); + var attributes = AttributeModelHelper.GetAttributeModels( + context, + context.Node, + cancellationToken + ); - var registrations = GetRegistrations(context, classDefinition, attributes, cancellationToken); + var registrations = GetRegistrations( + context, + classDefinition, + attributes, + cancellationToken + ); - if (registrations.Count == 0) { + if (registrations.Count == 0) + { return new ServiceModel( classDefinition, GetConstructorInfo(context, context.Node, cancellationToken), null, FactoryOutput, - new[] { + new[] + { new ServiceRegistrationModel( KnownTypes.Microsoft.TextJson.IJsonTypeInfoResolver, ServiceLifestyle.Transient - ) + ), }, RegistrationFeature.AutoRegisterSourceGenerator, Location: LocationModel.From(context.Node) @@ -231,19 +309,25 @@ private static bool IsIntercepted( FactoryOutputDelegate? factoryOutput = null; - if (registrations.Any( - r => r.ServiceType.Equals(KnownTypes.Microsoft.TextJson.IJsonTypeInfoResolver))) { + if ( + registrations.Any(r => + r.ServiceType.Equals(KnownTypes.Microsoft.TextJson.IJsonTypeInfoResolver) + ) + ) + { factoryOutput = FactoryOutput; } - return new ServiceModel(classDefinition, + return new ServiceModel( + classDefinition, GetConstructorInfo(context, context.Node, cancellationToken), null, factoryOutput, registrations, GetConstructionFeatures(context, cancellationToken), EnvironmentConditionUtility.GetConditions(context, context.Node, cancellationToken), - LocationModel.From(context.Node)); + LocationModel.From(context.Node) + ); } /// @@ -263,92 +347,132 @@ private static bool IsIntercepted( /// type's parameters. /// /// - public static ConstructorInfoModel? GetConstructorInfo(SyntaxTransformContext context, SyntaxNode node, CancellationToken cancellationToken) { + public static ConstructorInfoModel? GetConstructorInfo( + SyntaxTransformContext context, + SyntaxNode node, + CancellationToken cancellationToken + ) + { var constructorList = new List(); var members = node is TypeDeclarationSyntax declaration ? declaration.Members.OfType() : node.DescendantNodes().OfType(); - foreach (var constructor in members) { - if (constructor.Modifiers.Any(m => m.IsKind(SyntaxKind.PrivateKeyword))) { + foreach (var constructor in members) + { + if (constructor.Modifiers.Any(m => m.IsKind(SyntaxKind.PrivateKeyword))) + { continue; } - if (constructor.AttributeLists.Any(attributeList => - attributeList.Attributes.Any( - a => a.Name.ToString() == "ActivatorUtilitiesConstructorAttribute" || - a.Name.ToString() == "ActivatorUtilitiesConstructor"))) { - - return new ConstructorInfoModel(constructor.GetMethodParameters(context, cancellationToken)); + if ( + constructor.AttributeLists.Any(attributeList => + attributeList.Attributes.Any(a => + a.Name.ToString() == "ActivatorUtilitiesConstructorAttribute" + || a.Name.ToString() == "ActivatorUtilitiesConstructor" + ) + ) + ) + { + return new ConstructorInfoModel( + constructor.GetMethodParameters(context, cancellationToken) + ); } constructorList.Add(constructor); } - if (node is TypeDeclarationSyntax { ParameterList.Parameters.Count: > 0 } typeDeclarationSyntax) { + if ( + node is TypeDeclarationSyntax + { + ParameterList.Parameters.Count: > 0 + } typeDeclarationSyntax + ) + { return new ConstructorInfoModel( - typeDeclarationSyntax.ParameterList.GetParameters(context, cancellationToken)); + typeDeclarationSyntax.ParameterList.GetParameters(context, cancellationToken) + ); } - - if (constructorList.Count == 0) { + + if (constructorList.Count == 0) + { return new ConstructorInfoModel(ImmutableArray.Empty); } - if (constructorList.Count == 1) { + if (constructorList.Count == 1) + { var constructor = constructorList[0]; - return new ConstructorInfoModel(constructor.GetMethodParameters(context, cancellationToken)); + return new ConstructorInfoModel( + constructor.GetMethodParameters(context, cancellationToken) + ); } constructorList.Sort( - (a, b) => - a.ParameterList.Parameters.Count.CompareTo(b.ParameterList.Parameters.Count)); + (a, b) => a.ParameterList.Parameters.Count.CompareTo(b.ParameterList.Parameters.Count) + ); return new ConstructorInfoModel( constructorList.Last().GetMethodParameters(context, cancellationToken) ); } - private static IOutputComponent? FactoryOutput(ServiceModel servicemodel, ServiceRegistrationModel registrationmodel) { + private static IOutputComponent? FactoryOutput( + ServiceModel servicemodel, + ServiceRegistrationModel registrationmodel + ) + { var signature = "_ => "; - if (registrationmodel.Key != null) { + if (registrationmodel.Key != null) + { signature = "(_,_) => "; } var component = CodeOutputComponent.Get( - $"{signature}{servicemodel.ImplementationType.Namespace}.{servicemodel.ImplementationType.Name}.Default"); + $"{signature}{servicemodel.ImplementationType.Namespace}.{servicemodel.ImplementationType.Name}.Default" + ); return component; } - private static ITypeDefinition? GetClassDefinition(SyntaxTransformContext context) { + private static ITypeDefinition? GetClassDefinition(SyntaxTransformContext context) + { ITypeDefinition? classTypeDefinition = null; - if (context.Node is TypeDeclarationSyntax typeDeclarationSyntax) { + if (context.Node is TypeDeclarationSyntax typeDeclarationSyntax) + { classTypeDefinition = GetTypeDeclarationDefinition(typeDeclarationSyntax); } return classTypeDefinition; } - private static ITypeDefinition GetTypeDeclarationDefinition(TypeDeclarationSyntax typeDeclarationSyntax) { + private static ITypeDefinition GetTypeDeclarationDefinition( + TypeDeclarationSyntax typeDeclarationSyntax + ) + { ITypeDefinition classTypeDefinition; var declaredName = GetDeclaredName(typeDeclarationSyntax); - if (typeDeclarationSyntax.TypeParameterList is { Parameters.Count: > 0 }) { - classTypeDefinition = - new GenericTypeDefinition( - TypeDefinitionEnum.ClassDefinition, - typeDeclarationSyntax.GetNamespace(), - declaredName, - typeDeclarationSyntax.TypeParameterList.Parameters.Select(_ => TypeDefinition.Get("", "")) - .ToArray() - ); + if (typeDeclarationSyntax.TypeParameterList is { Parameters.Count: > 0 }) + { + classTypeDefinition = new GenericTypeDefinition( + TypeDefinitionEnum.ClassDefinition, + typeDeclarationSyntax.GetNamespace(), + declaredName, + typeDeclarationSyntax + .TypeParameterList.Parameters.Select(_ => TypeDefinition.Get("", "")) + .ToArray() + ); } - else { - classTypeDefinition = TypeDefinition.Get(typeDeclarationSyntax.GetNamespace(), declaredName); + else + { + classTypeDefinition = TypeDefinition.Get( + typeDeclarationSyntax.GetNamespace(), + declaredName + ); } return classTypeDefinition; @@ -358,35 +482,67 @@ private static ITypeDefinition GetTypeDeclarationDefinition(TypeDeclarationSynta /// The type's name qualified by any containing types, so a nested service is referenced as /// Outer.Inner rather than Inner, which would resolve against the namespace and fail to compile. /// - private static string GetDeclaredName(TypeDeclarationSyntax typeDeclarationSyntax) { + private static string GetDeclaredName(TypeDeclarationSyntax typeDeclarationSyntax) + { var name = typeDeclarationSyntax.Identifier.ToString(); - foreach (var containingType in typeDeclarationSyntax.Ancestors().OfType()) { + foreach ( + var containingType in typeDeclarationSyntax.Ancestors().OfType() + ) + { name = containingType.Identifier + "." + name; } return name; } - private static List GetRegistrations(SyntaxTransformContext context, ITypeDefinition classDefinition, IReadOnlyList attributes, CancellationToken cancellationToken) { + private static List GetRegistrations( + SyntaxTransformContext context, + ITypeDefinition classDefinition, + IReadOnlyList attributes, + CancellationToken cancellationToken + ) + { var list = new List(); - foreach (var attributeSyntax in - context.Node.DescendantNodes().OfType()) { - foreach (var typeDefinition in _attributeTypes) { + foreach (var attributeSyntax in context.Node.DescendantNodes().OfType()) + { + foreach (var typeDefinition in _attributeTypes) + { cancellationToken.ThrowIfCancellationRequested(); // Resolved, not compared as written: a namespace-qualified usage, a global:: prefix // and a using alias all name the same attribute, and all of them used to be silently // skipped — leaving the class unregistered with nothing to say so. - if (AttributeTypeMatcher.Matches( - context.SemanticModel, attributeSyntax, typeDefinition, cancellationToken)) { - list.Add(GetServiceRegistration(context, attributeSyntax, classDefinition, typeDefinition)); + if ( + AttributeTypeMatcher.Matches( + context.SemanticModel, + attributeSyntax, + typeDefinition, + cancellationToken + ) + ) + { + list.Add( + GetServiceRegistration( + context, + attributeSyntax, + classDefinition, + typeDefinition + ) + ); } } - if (AttributeTypeMatcher.Matches( - context.SemanticModel, attributeSyntax, _crossWireService, cancellationToken)) { + if ( + AttributeTypeMatcher.Matches( + context.SemanticModel, + attributeSyntax, + _crossWireService, + cancellationToken + ) + ) + { list.AddRange(GetCrossWiredService(context, attributeSyntax, classDefinition)); } } @@ -394,8 +550,12 @@ private static List GetRegistrations(SyntaxTransformCo return list; } - private static IEnumerable GetCrossWiredService(SyntaxTransformContext context, AttributeSyntax attributeSyntax, ITypeDefinition classDefinition) { - + private static IEnumerable GetCrossWiredService( + SyntaxTransformContext context, + AttributeSyntax attributeSyntax, + ITypeDefinition classDefinition + ) + { RegistrationType? registrationType = null; ITypeDefinition? realm = null; object? key = null; @@ -403,23 +563,33 @@ private static IEnumerable GetCrossWiredService(Syntax var order = 0; var namespaces = new List(); - if (attributeSyntax.ArgumentList != null) { - foreach (var argumentSyntax in attributeSyntax.ArgumentList.Arguments) { - if (argumentSyntax.NameEquals != null) { - switch (argumentSyntax.NameEquals.Name.ToString()) { + if (attributeSyntax.ArgumentList != null) + { + foreach (var argumentSyntax in attributeSyntax.ArgumentList.Arguments) + { + if (argumentSyntax.NameEquals != null) + { + switch (argumentSyntax.NameEquals.Name.ToString()) + { case "Key": key = argumentSyntax.Expression.ToString(); - if (argumentSyntax.Expression is MemberAccessExpressionSyntax accessExpressionSyntax) { + if ( + argumentSyntax.Expression + is MemberAccessExpressionSyntax accessExpressionSyntax + ) + { var type = accessExpressionSyntax.GetTypeDefinition(context); - if (type != null) { + if (type != null) + { namespaces.AddRange(type.KnownNamespaces); } } break; case "Using": - registrationType = - BaseSourceGenerator.GetRegistrationType(argumentSyntax.Expression.ToString()); + registrationType = BaseSourceGenerator.GetRegistrationType( + argumentSyntax.Expression.ToString() + ); break; case "Lifetime": @@ -427,13 +597,20 @@ private static IEnumerable GetCrossWiredService(Syntax break; case "Realm": - if (argumentSyntax.Expression is TypeOfExpressionSyntax realmType) { + if (argumentSyntax.Expression is TypeOfExpressionSyntax realmType) + { realm = realmType.Type.GetTypeDefinition(context); } break; case "Order": - if (int.TryParse(argumentSyntax.Expression.ToString(), out var parsedOrder)) { + if ( + int.TryParse( + argumentSyntax.Expression.ToString(), + out var parsedOrder + ) + ) + { order = parsedOrder; } break; @@ -442,11 +619,14 @@ private static IEnumerable GetCrossWiredService(Syntax } } - if (context.Node is TypeDeclarationSyntax { BaseList: not null } typeDeclarationSyntax) { - foreach (var baseTypeSyntax in typeDeclarationSyntax.BaseList.Types) { + if (context.Node is TypeDeclarationSyntax { BaseList: not null } typeDeclarationSyntax) + { + foreach (var baseTypeSyntax in typeDeclarationSyntax.BaseList.Types) + { var type = baseTypeSyntax.Type.GetTypeDefinition(context); - if (type?.TypeDefinitionEnum == TypeDefinitionEnum.InterfaceDefinition) { + if (type?.TypeDefinitionEnum == TypeDefinitionEnum.InterfaceDefinition) + { yield return new ServiceRegistrationModel( type, lifestyle, @@ -462,17 +642,18 @@ private static IEnumerable GetCrossWiredService(Syntax } } - private static ServiceLifestyle GetLifestyle(string toString) { + 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(); + var value = + separatorIndex >= 0 ? toString.Substring(separatorIndex + 1).Trim() : toString.Trim(); - if (Enum.TryParse(value, out ServiceLifestyle lifestyle)) { + if (Enum.TryParse(value, out ServiceLifestyle lifestyle)) + { return lifestyle; } @@ -483,8 +664,9 @@ private static ServiceRegistrationModel GetServiceRegistration( SyntaxTransformContext context, AttributeSyntax attributeSyntax, ITypeDefinition classDefinition, - ITypeDefinition attributeType) { - + ITypeDefinition attributeType + ) + { // The lifetime comes from the attribute type the usage resolved to, not from how the usage // was spelled. Reading it back off `attributeSyntax.Name` meant only a spelling that literally // began with "Singleton" or "Scoped" produced that lifetime: a qualified name, a global:: @@ -503,43 +685,66 @@ private static ServiceRegistrationModel GetServiceRegistration( object? key = null; var namespaces = new List(); - if (attributeSyntax.ArgumentList != null) { - foreach (var argumentSyntax in attributeSyntax.ArgumentList.Arguments) { - if (argumentSyntax.NameEquals != null) { - switch (argumentSyntax.NameEquals.Name.ToString()) { + if (attributeSyntax.ArgumentList != null) + { + foreach (var argumentSyntax in attributeSyntax.ArgumentList.Arguments) + { + if (argumentSyntax.NameEquals != null) + { + switch (argumentSyntax.NameEquals.Name.ToString()) + { case "Key": key = argumentSyntax.Expression.ToString(); - if (argumentSyntax.Expression is MemberAccessExpressionSyntax accessExpressionSyntax) { + if ( + argumentSyntax.Expression + is MemberAccessExpressionSyntax accessExpressionSyntax + ) + { var type = accessExpressionSyntax.GetTypeDefinition(context); - if (type != null) { + if (type != null) + { namespaces.AddRange(type.KnownNamespaces); } } break; case "Using": - registrationType = - BaseSourceGenerator.GetRegistrationType(argumentSyntax.Expression.ToString()); + registrationType = BaseSourceGenerator.GetRegistrationType( + argumentSyntax.Expression.ToString() + ); break; case "As": - if (argumentSyntax.Expression is TypeOfExpressionSyntax typeOfExpression) { + if ( + argumentSyntax.Expression is TypeOfExpressionSyntax typeOfExpression + ) + { registration = typeOfExpression.Type.GetTypeDefinition(context); - if (registration is GenericTypeDefinition) { - registration = ReplaceGenericParametersForRegistration(registration); + if (registration is GenericTypeDefinition) + { + registration = ReplaceGenericParametersForRegistration( + registration + ); } } break; case "Realm": - if (argumentSyntax.Expression is TypeOfExpressionSyntax realmType) { + if (argumentSyntax.Expression is TypeOfExpressionSyntax realmType) + { realm = realmType.Type.GetTypeDefinition(context); } break; case "Order": - if (int.TryParse(argumentSyntax.Expression.ToString(), out var parsedOrder)) { + if ( + int.TryParse( + argumentSyntax.Expression.ToString(), + out var parsedOrder + ) + ) + { order = parsedOrder; } break; @@ -561,7 +766,10 @@ private static ServiceRegistrationModel GetServiceRegistration( } private static ITypeDefinition GetServiceTypeFromClass( - SyntaxTransformContext context, ITypeDefinition classDefinition) { + SyntaxTransformContext context, + ITypeDefinition classDefinition + ) + { return GetBaseTypeRegistration(context) ?? classDefinition; } @@ -570,27 +778,41 @@ private static ITypeDefinition GetServiceTypeFromClass( /// declared interface that is not a capability, else the /// first one a base class provides. /// - private static ITypeDefinition? GetBaseTypeRegistration(SyntaxTransformContext context) { - if (context.Node is TypeDeclarationSyntax { BaseList: not null } typeDeclarationSyntax) { + private static ITypeDefinition? GetBaseTypeRegistration(SyntaxTransformContext context) + { + if (context.Node is TypeDeclarationSyntax { BaseList: not null } typeDeclarationSyntax) + { INamedTypeSymbol? baseClassSymbol = null; - foreach (var baseTypeSyntax in typeDeclarationSyntax.BaseList.Types) { - var symbolInfo = ModelExtensions.GetSymbolInfo(context.SemanticModel, baseTypeSyntax.Type); + foreach (var baseTypeSyntax in typeDeclarationSyntax.BaseList.Types) + { + var symbolInfo = ModelExtensions.GetSymbolInfo( + context.SemanticModel, + baseTypeSyntax.Type + ); - if (symbolInfo.Symbol is INamedTypeSymbol namedTypeSymbol) { - var baseTypeDefinition = - namedTypeSymbol.GetTypeDefinitionFromNamedSymbol(); + if (symbolInfo.Symbol is INamedTypeSymbol namedTypeSymbol) + { + var baseTypeDefinition = namedTypeSymbol.GetTypeDefinitionFromNamedSymbol(); // only auto register interfaces - if (baseTypeDefinition is { TypeDefinitionEnum: TypeDefinitionEnum.InterfaceDefinition }) { + if ( + baseTypeDefinition is + { TypeDefinitionEnum: TypeDefinitionEnum.InterfaceDefinition } + ) + { // Passed over rather than remembered: a skipped interface must not become the // symbol walked below, or IEnumerable would hand back IEnumerable. - if (SkipInterface(baseTypeDefinition)) { + if (SkipInterface(baseTypeDefinition)) + { continue; } - if (baseTypeDefinition is GenericTypeDefinition) { - baseTypeDefinition = ReplaceGenericParametersForRegistration(baseTypeDefinition); + if (baseTypeDefinition is GenericTypeDefinition) + { + baseTypeDefinition = ReplaceGenericParametersForRegistration( + baseTypeDefinition + ); } return baseTypeDefinition; @@ -600,7 +822,8 @@ private static ITypeDefinition GetServiceTypeFromClass( } } - if (baseClassSymbol != null) { + if (baseClassSymbol != null) + { return GetBaseInterface(context, baseClassSymbol); } } @@ -608,26 +831,31 @@ private static ITypeDefinition GetServiceTypeFromClass( return null; } - - private static ITypeDefinition? GetBaseInterface(SyntaxTransformContext context, INamedTypeSymbol baseTypeSymbol) { - foreach (var interfaceSymbol in baseTypeSymbol.Interfaces) { - var interfaceType = - interfaceSymbol.GetTypeDefinitionFromNamedSymbol(); + private static ITypeDefinition? GetBaseInterface( + SyntaxTransformContext context, + INamedTypeSymbol baseTypeSymbol + ) + { + foreach (var interfaceSymbol in baseTypeSymbol.Interfaces) + { + var interfaceType = interfaceSymbol.GetTypeDefinitionFromNamedSymbol(); // only auto register interfaces - if (interfaceType == null || - SkipInterface(interfaceType)) { + if (interfaceType == null || SkipInterface(interfaceType)) + { continue; } - if (interfaceType is GenericTypeDefinition) { + if (interfaceType is GenericTypeDefinition) + { interfaceType = ReplaceGenericParametersForRegistration(interfaceType); } return interfaceType; } - if (baseTypeSymbol.BaseType == null) { + if (baseTypeSymbol.BaseType == null) + { return null; } @@ -645,10 +873,15 @@ private static ITypeDefinition GetServiceTypeFromClass( private static bool SkipInterface(ITypeDefinition interfaceType) => _capabilityInterfaces.Contains($"{interfaceType.Namespace}.{interfaceType.Name}"); - private static ITypeDefinition ReplaceGenericParametersForRegistration(ITypeDefinition registration) { - var argumentTypes = - registration.TypeArguments.Select( - _ => _ is TypeParameterDefinition ? TypeDefinition.Get("", "") : _).ToArray(); + private static ITypeDefinition ReplaceGenericParametersForRegistration( + ITypeDefinition registration + ) + { + var argumentTypes = registration + .TypeArguments.Select(_ => + _ is TypeParameterDefinition ? TypeDefinition.Get("", "") : _ + ) + .ToArray(); registration = new GenericTypeDefinition( registration.TypeDefinitionEnum, @@ -656,7 +889,7 @@ private static ITypeDefinition ReplaceGenericParametersForRegistration(ITypeDefi registration.Name, argumentTypes ); - + return registration; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/SymbolConstructorReader.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/SymbolConstructorReader.cs index ca933b4..f29960c 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/SymbolConstructorReader.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/SymbolConstructorReader.cs @@ -21,8 +21,8 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// every resolution and is the shape a published Native AOT build has no code for. /// /// -public static class SymbolConstructorReader { - +public static class SymbolConstructorReader +{ private const string ActivatorUtilitiesConstructor = "ActivatorUtilitiesConstructorAttribute"; private const string FromKeyedServices = "FromKeyedServicesAttribute"; @@ -35,7 +35,8 @@ public static class SymbolConstructorReader { /// it. Falling back to something reflective would trade a build error for a failure at resolve /// time in a published application. /// - public static ConstructorInfoModel? Read(INamedTypeSymbol type) { + public static ConstructorInfoModel? Read(INamedTypeSymbol type) + { var chosen = Choose(type); return chosen == null ? null : new ConstructorInfoModel(Parameters(chosen)); @@ -49,21 +50,27 @@ public static class SymbolConstructorReader { /// opted into a specific constructor and silently got a different one is the kind of difference /// nobody looks for. /// - private static IMethodSymbol? Choose(INamedTypeSymbol type) { + private static IMethodSymbol? Choose(INamedTypeSymbol type) + { IMethodSymbol? greediest = null; - foreach (var constructor in type.InstanceConstructors) { - if (constructor.DeclaredAccessibility != Accessibility.Public || constructor.IsStatic) { + foreach (var constructor in type.InstanceConstructors) + { + if (constructor.DeclaredAccessibility != Accessibility.Public || constructor.IsStatic) + { continue; } - foreach (var attribute in constructor.GetAttributes()) { - if (attribute.AttributeClass?.Name == ActivatorUtilitiesConstructor) { + foreach (var attribute in constructor.GetAttributes()) + { + if (attribute.AttributeClass?.Name == ActivatorUtilitiesConstructor) + { return constructor; } } - if (greediest == null || constructor.Parameters.Length > greediest.Parameters.Length) { + if (greediest == null || constructor.Parameters.Length > greediest.Parameters.Length) + { greediest = constructor; } } @@ -71,15 +78,20 @@ public static class SymbolConstructorReader { return greediest; } - private static IReadOnlyList Parameters(IMethodSymbol constructor) { + private static IReadOnlyList Parameters(IMethodSymbol constructor) + { var parameters = new List(constructor.Parameters.Length); - foreach (var parameter in constructor.Parameters) { - parameters.Add(new ParameterInfoModel( - parameter.Name, - TypeOf(parameter), - parameter.HasExplicitDefaultValue ? parameter.ExplicitDefaultValue : null, - Attributes(parameter))); + foreach (var parameter in constructor.Parameters) + { + parameters.Add( + new ParameterInfoModel( + parameter.Name, + TypeOf(parameter), + parameter.HasExplicitDefaultValue ? parameter.ExplicitDefaultValue : null, + Attributes(parameter) + ) + ); } return parameters; @@ -93,7 +105,8 @@ private static IReadOnlyList Parameters(IMethodSymbol constr /// GetService or GetRequiredService, so an optional dependency that lost its /// annotation would start throwing when the container simply does not have one. /// - private static ITypeDefinition TypeOf(IParameterSymbol parameter) { + private static ITypeDefinition TypeOf(IParameterSymbol parameter) + { var definition = parameter.Type.GetTypeDefinition(); return parameter.NullableAnnotation == NullableAnnotation.Annotated @@ -110,22 +123,33 @@ private static ITypeDefinition TypeOf(IParameterSymbol parameter) { /// resolves, and getting it wrong returns the right type and the wrong instance with nothing /// reported. /// - private static IReadOnlyList Attributes(IParameterSymbol parameter) { + private static IReadOnlyList Attributes(IParameterSymbol parameter) + { List? attributes = null; - foreach (var attribute in parameter.GetAttributes()) { - if (attribute.AttributeClass?.Name != FromKeyedServices || - attribute.ConstructorArguments.Length == 0) { + foreach (var attribute in parameter.GetAttributes()) + { + if ( + attribute.AttributeClass?.Name != FromKeyedServices + || attribute.ConstructorArguments.Length == 0 + ) + { continue; } attributes ??= new List(1); - attributes.Add(new AttributeModel( - KnownTypes.Microsoft.DependencyInjection.FromKeyedServicesAttribute, - new[] { new AttributeArgumentValue("key", attribute.ConstructorArguments[0].Value) }, - Array.Empty(), - Array.Empty())); + attributes.Add( + new AttributeModel( + KnownTypes.Microsoft.DependencyInjection.FromKeyedServicesAttribute, + new[] + { + new AttributeArgumentValue("key", attribute.ConstructorArguments[0].Value), + }, + Array.Empty(), + Array.Empty() + ) + ); } return (IReadOnlyList?)attributes ?? Array.Empty(); diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/SyntaxNodeExtensions.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/SyntaxNodeExtensions.cs index 84fcd52..d593868 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/SyntaxNodeExtensions.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/SyntaxNodeExtensions.cs @@ -5,28 +5,37 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; -public static class SyntaxNodeExtensions { - public static string GetNamespace(this BaseTypeDeclarationSyntax syntax) { +public static class SyntaxNodeExtensions +{ + public static string GetNamespace(this BaseTypeDeclarationSyntax syntax) + { var parentSyntaxNode = syntax.Parent; - while (parentSyntaxNode != null && - parentSyntaxNode is not NamespaceDeclarationSyntax && - parentSyntaxNode is not FileScopedNamespaceDeclarationSyntax) { + while ( + parentSyntaxNode != null + && parentSyntaxNode is not NamespaceDeclarationSyntax + && parentSyntaxNode is not FileScopedNamespaceDeclarationSyntax + ) + { parentSyntaxNode = parentSyntaxNode.Parent; } - if (parentSyntaxNode is BaseNamespaceDeclarationSyntax namespaceNode) { + if (parentSyntaxNode is BaseNamespaceDeclarationSyntax namespaceNode) + { return WalkNamespaceNodes(namespaceNode); } return ""; } - private static string WalkNamespaceNodes(BaseNamespaceDeclarationSyntax? namespaceNode) { + private static string WalkNamespaceNodes(BaseNamespaceDeclarationSyntax? namespaceNode) + { var stringBuilder = new StringBuilder(); - while (namespaceNode != null) { - if (stringBuilder.Length > 0) { + while (namespaceNode != null) + { + if (stringBuilder.Length > 0) + { stringBuilder.Insert(0, '.'); } @@ -38,51 +47,79 @@ private static string WalkNamespaceNodes(BaseNamespaceDeclarationSyntax? namespa return stringBuilder.ToString(); } - public static ITypeDefinition GetTypeDefinition(this TypeDeclarationSyntax typeDeclarationSyntax) { - var namespaceSyntax = typeDeclarationSyntax.Ancestors().OfType().FirstOrDefault(); - - return TypeDefinition.Get(namespaceSyntax?.Name.ToFullString().TrimEnd() ?? "", - typeDeclarationSyntax.Identifier.Text); + public static ITypeDefinition GetTypeDefinition( + this TypeDeclarationSyntax typeDeclarationSyntax + ) + { + var namespaceSyntax = typeDeclarationSyntax + .Ancestors() + .OfType() + .FirstOrDefault(); + + return TypeDefinition.Get( + namespaceSyntax?.Name.ToFullString().TrimEnd() ?? "", + typeDeclarationSyntax.Identifier.Text + ); } - public static AttributeSyntax? GetAttribute(this SyntaxNode node, string attributeName, string ns = "") { + public static AttributeSyntax? GetAttribute( + this SyntaxNode node, + string attributeName, + string ns = "" + ) + { return node.DescendantNodes() - .OfType().FirstOrDefault( - a => { - var name = a.Name.ToString(); - - return name.Equals(attributeName) || name.Equals(attributeName + "Attribute") || - name.Equals(ns + "." + attributeName) || name.Equals(ns + "." + attributeName + "Attribute"); - }); + .OfType() + .FirstOrDefault(a => + { + var name = a.Name.ToString(); + + return name.Equals(attributeName) + || name.Equals(attributeName + "Attribute") + || name.Equals(ns + "." + attributeName) + || name.Equals(ns + "." + attributeName + "Attribute"); + }); } - public static IEnumerable - GetAttributes(this SyntaxNode node, string attributeName, string ns = "") { + public static IEnumerable GetAttributes( + this SyntaxNode node, + string attributeName, + string ns = "" + ) + { return node.DescendantNodes() - .OfType().Where( - a => { - var name = a.Name.ToString(); - - return name.Equals(attributeName) || name.Equals(attributeName + "Attribute") || - name.Equals(ns + "." + attributeName) || name.Equals(ns + "." + attributeName + "Attribute"); - }); + .OfType() + .Where(a => + { + var name = a.Name.ToString(); + + return name.Equals(attributeName) + || name.Equals(attributeName + "Attribute") + || name.Equals(ns + "." + attributeName) + || name.Equals(ns + "." + attributeName + "Attribute"); + }); } - public static bool IsAttributed(this SyntaxNode node, string attributeName, string ns = "") { + public static bool IsAttributed(this SyntaxNode node, string attributeName, string ns = "") + { return node.DescendantNodes() - .OfType().Any( - a => { - var name = a.Name.ToString(); - - return name.Equals(attributeName) || name.Equals(attributeName + "Attribute") || - name.Equals(ns + "." + attributeName) || name.Equals(ns + "." + attributeName + "Attribute"); - }); + .OfType() + .Any(a => + { + var name = a.Name.ToString(); + + return name.Equals(attributeName) + || name.Equals(attributeName + "Attribute") + || name.Equals(ns + "." + attributeName) + || name.Equals(ns + "." + attributeName + "Attribute"); + }); } - public static bool IsAttributed(this SyntaxNode node, ITypeDefinition typeDefinition) { + public static bool IsAttributed(this SyntaxNode node, ITypeDefinition typeDefinition) + { var ns = typeDefinition.Namespace; var attributeName = typeDefinition.Name.Replace("Attribute", ""); return IsAttributed(node, attributeName, ns); } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/SyntaxSelector.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/SyntaxSelector.cs index a01f5e9..f9cc9ee 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/SyntaxSelector.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/SyntaxSelector.cs @@ -4,15 +4,17 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; -public abstract class BaseSyntaxSelector { - private const string _attributeString = "Attribute"; +public abstract class BaseSyntaxSelector +{ + private const string _attributeString = "Attribute"; private readonly List _names; public bool AutoApproveCompilationUnit { get; set; } = false; - + public string ApproveFilter { get; set; } = ""; - - protected BaseSyntaxSelector(params ITypeDefinition[] attributes) { + + protected BaseSyntaxSelector(params ITypeDefinition[] attributes) + { _names = GetAttributeStrings(attributes); } @@ -27,14 +29,20 @@ protected BaseSyntaxSelector(params ITypeDefinition[] attributes) { /// module: no partial written, no diagnostic, and a CS0311 at the consumer's /// AddModule<T>() naming neither the attribute nor the omission. /// - private List GetAttributeStrings(ITypeDefinition[] attributes) { + private List GetAttributeStrings(ITypeDefinition[] attributes) + { var returnList = new List(); - foreach (var attribute in attributes) { + foreach (var attribute in attributes) + { returnList.Add(attribute.Name); - if (attribute.Name.EndsWith(_attributeString)) { - var simpleName = attribute.Name.Substring(0, attribute.Name.Length - _attributeString.Length); + if (attribute.Name.EndsWith(_attributeString)) + { + var simpleName = attribute.Name.Substring( + 0, + attribute.Name.Length - _attributeString.Length + ); returnList.Add(simpleName); } @@ -56,10 +64,12 @@ private List GetAttributeStrings(ITypeDefinition[] attributes) { /// regardless of which namespace it came from, so a same-named attribute from elsewhere was /// always a candidate and is filtered downstream as it always was. /// - private static string LastSegment(string attributeName) { + private static string LastSegment(string attributeName) + { var lastDot = attributeName.LastIndexOf('.'); - if (lastDot >= 0) { + if (lastDot >= 0) + { return attributeName.Substring(lastDot + 1); } @@ -69,75 +79,95 @@ private static string LastSegment(string attributeName) { } protected abstract bool TestForTypes(SyntaxNode node, CancellationToken token); - - public bool Where(SyntaxNode node, CancellationToken token) { - - if (!TestForTypes(node, token)) { + + public bool Where(SyntaxNode node, CancellationToken token) + { + if (!TestForTypes(node, token)) + { return false; } - if (node is MemberDeclarationSyntax memberDeclarationSyntax) { + if (node is MemberDeclarationSyntax memberDeclarationSyntax) + { return ProcessAttributeList(memberDeclarationSyntax.AttributeLists); } - if (node is CompilationUnitSyntax compilationUnitSyntax) { - return IsAutoApprove(compilationUnitSyntax) || - ProcessAttributeList(compilationUnitSyntax.AttributeLists); + if (node is CompilationUnitSyntax compilationUnitSyntax) + { + return IsAutoApprove(compilationUnitSyntax) + || ProcessAttributeList(compilationUnitSyntax.AttributeLists); } - + var found = node.DescendantNodes() - .OfType().Any(a => _names.Contains(LastSegment(a.Name.ToString()))); - + .OfType() + .Any(a => _names.Contains(LastSegment(a.Name.ToString()))); + return found; } - private bool IsAutoApprove(CompilationUnitSyntax compilationUnitSyntax) { - if (!AutoApproveCompilationUnit) { + private bool IsAutoApprove(CompilationUnitSyntax compilationUnitSyntax) + { + if (!AutoApproveCompilationUnit) + { return false; } - - return ApproveFilter == "" || - compilationUnitSyntax.SyntaxTree.FilePath.EndsWith(ApproveFilter); + + return ApproveFilter == "" + || compilationUnitSyntax.SyntaxTree.FilePath.EndsWith(ApproveFilter); } - private bool ProcessAttributeList(SyntaxList attributeLists) { + private bool ProcessAttributeList(SyntaxList attributeLists) + { var foundAttribute = false; - foreach (var attributeListSyntax in attributeLists) { - foreach (var attributeSyntax in attributeListSyntax.Attributes) { + foreach (var attributeListSyntax in attributeLists) + { + foreach (var attributeSyntax in attributeListSyntax.Attributes) + { foundAttribute = _names.Contains(LastSegment(attributeSyntax.Name.ToString())); - - if (foundAttribute) { + + if (foundAttribute) + { break; } } - if (foundAttribute) { + if (foundAttribute) + { break; } } - + return foundAttribute; } - } -public class SyntaxSelector : BaseSyntaxSelector where T : SyntaxNode { - public SyntaxSelector(params ITypeDefinition[] attributes) : base(attributes) {} - - protected override bool TestForTypes(SyntaxNode node, CancellationToken token) { - if (node is T) { +public class SyntaxSelector : BaseSyntaxSelector + where T : SyntaxNode +{ + public SyntaxSelector(params ITypeDefinition[] attributes) + : base(attributes) { } + + protected override bool TestForTypes(SyntaxNode node, CancellationToken token) + { + if (node is T) + { return true; } - + return false; } } - -public class SyntaxSelector : BaseSyntaxSelector where T1 : SyntaxNode where T2 : SyntaxNode { - public SyntaxSelector(params ITypeDefinition[] attributes) : base(attributes) {} - - protected override bool TestForTypes(SyntaxNode node, CancellationToken token) { - if (node is T1 or T2) { +public class SyntaxSelector : BaseSyntaxSelector + where T1 : SyntaxNode + where T2 : SyntaxNode +{ + public SyntaxSelector(params ITypeDefinition[] attributes) + : base(attributes) { } + + protected override bool TestForTypes(SyntaxNode node, CancellationToken token) + { + if (node is T1 or T2) + { return true; } @@ -145,14 +175,21 @@ protected override bool TestForTypes(SyntaxNode node, CancellationToken token) { } } -public class SyntaxSelector : BaseSyntaxSelector where T1 : SyntaxNode where T2 : SyntaxNode where T3 : SyntaxNode { - public SyntaxSelector(params ITypeDefinition[] attributes) : base(attributes) {} - - protected override bool TestForTypes(SyntaxNode node, CancellationToken token) { - if (node is T1 or T2 or T3) { +public class SyntaxSelector : BaseSyntaxSelector + where T1 : SyntaxNode + where T2 : SyntaxNode + where T3 : SyntaxNode +{ + public SyntaxSelector(params ITypeDefinition[] attributes) + : base(attributes) { } + + protected override bool TestForTypes(SyntaxNode node, CancellationToken token) + { + if (node is T1 or T2 or T3) + { return true; } return false; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/SyntaxTransformContext.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/SyntaxTransformContext.cs index 096d090..92415d7 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/SyntaxTransformContext.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/SyntaxTransformContext.cs @@ -14,9 +14,10 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// Everything below the provider takes this instead, and the implicit conversions keep the call sites /// identical whichever provider they came from. /// -public readonly struct SyntaxTransformContext { - - public SyntaxTransformContext(SyntaxNode node, SemanticModel semanticModel) { +public readonly struct SyntaxTransformContext +{ + public SyntaxTransformContext(SyntaxNode node, SemanticModel semanticModel) + { Node = node; SemanticModel = semanticModel; } @@ -32,6 +33,7 @@ public static implicit operator SyntaxTransformContext(GeneratorSyntaxContext co /// The target node is the declaration the attribute was found on, which is the node a /// CreateSyntaxProvider predicate would have selected for the same attribute. /// - public static implicit operator SyntaxTransformContext(GeneratorAttributeSyntaxContext context) => - new(context.TargetNode, context.SemanticModel); + public static implicit operator SyntaxTransformContext( + GeneratorAttributeSyntaxContext context + ) => new(context.TargetNode, context.SemanticModel); } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/TypeParameterReader.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/TypeParameterReader.cs index 8d9d0fa..66ddacc 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/TypeParameterReader.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/TypeParameterReader.cs @@ -14,31 +14,41 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; /// implies it, and Roslyn reports a constructor constraint for a struct-constrained parameter /// even though repeating new() alongside it is CS0451. /// -public static class TypeParameterReader { - - public static TypeParameterModel Read(ITypeParameterSymbol parameter) { +public static class TypeParameterReader +{ + public static TypeParameterModel Read(ITypeParameterSymbol parameter) + { string? primary = null; - if (parameter.HasUnmanagedTypeConstraint) { + if (parameter.HasUnmanagedTypeConstraint) + { primary = "unmanaged"; - } else if (parameter.HasValueTypeConstraint) { + } + else if (parameter.HasValueTypeConstraint) + { primary = "struct"; - } else if (parameter.HasReferenceTypeConstraint) { - primary = parameter.ReferenceTypeConstraintNullableAnnotation == NullableAnnotation.Annotated - ? "class?" - : "class"; - } else if (parameter.HasNotNullConstraint) { + } + else if (parameter.HasReferenceTypeConstraint) + { + primary = + parameter.ReferenceTypeConstraintNullableAnnotation == NullableAnnotation.Annotated + ? "class?" + : "class"; + } + else if (parameter.HasNotNullConstraint) + { primary = "notnull"; } var constraintTypes = new ITypeDefinition[parameter.ConstraintTypes.Length]; - for (var i = 0; i < constraintTypes.Length; i++) { + for (var i = 0; i < constraintTypes.Length; i++) + { constraintTypes[i] = parameter.ConstraintTypes[i].GetTypeDefinition(); } - var defaultConstructor = parameter.HasConstructorConstraint && - primary is not ("struct" or "unmanaged"); + var defaultConstructor = + parameter.HasConstructorConstraint && primary is not ("struct" or "unmanaged"); return new TypeParameterModel(parameter.Name, primary, constraintTypes, defaultConstructor); } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/TypeSyntaxExtensions.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/TypeSyntaxExtensions.cs index 86f827d..bd214f7 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/TypeSyntaxExtensions.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/TypeSyntaxExtensions.cs @@ -4,53 +4,69 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; -public static class TypeSyntaxExtensions { - public static ITypeDefinition? GetTypeDefinition(this SyntaxNode typeSyntax, - SyntaxTransformContext generatorSyntaxContext) { +public static class TypeSyntaxExtensions +{ + public static ITypeDefinition? GetTypeDefinition( + this SyntaxNode typeSyntax, + SyntaxTransformContext generatorSyntaxContext + ) + { var symbolInfo = generatorSyntaxContext.SemanticModel.GetSymbolInfo(typeSyntax); var type = GetTypeDefinitionFromSymbolInfo(symbolInfo); - if (typeSyntax.ToString().EndsWith("?")) { + if (typeSyntax.ToString().EndsWith("?")) + { return type?.MakeNullable(); } return type; } - public static ITypeDefinition? GetTypeDefinition(this MemberAccessExpressionSyntax syntax, SyntaxTransformContext generatorSyntaxContext) { + public static ITypeDefinition? GetTypeDefinition( + this MemberAccessExpressionSyntax syntax, + SyntaxTransformContext generatorSyntaxContext + ) + { var typeInfo = generatorSyntaxContext.SemanticModel.GetSymbolInfo(syntax.Expression); - - if (typeInfo.Symbol is INamedTypeSymbol namedTypeSymbol) { + + if (typeInfo.Symbol is INamedTypeSymbol namedTypeSymbol) + { return GetTypeDefinition(namedTypeSymbol); } - + return null; } - - public static string GetFullName(this INamespaceSymbol? namespaceSymbol) { - if (namespaceSymbol == null) { + + public static string GetFullName(this INamespaceSymbol? namespaceSymbol) + { + if (namespaceSymbol == null) + { return ""; } var baseString = namespaceSymbol.ContainingNamespace?.GetFullName(); - if (string.IsNullOrEmpty(baseString)) { + if (string.IsNullOrEmpty(baseString)) + { return namespaceSymbol.Name; } return baseString + "." + namespaceSymbol.Name; } - public static ITypeDefinition GetTypeDefinition(this ITypeSymbol typeSymbol) { - if (typeSymbol is INamedTypeSymbol namedTypeSymbol) { + public static ITypeDefinition GetTypeDefinition(this ITypeSymbol typeSymbol) + { + if (typeSymbol is INamedTypeSymbol namedTypeSymbol) + { return InternalGetTypeDefinitionFromNamedSymbol(namedTypeSymbol); } // A type parameter has no namespace and no containing type to qualify it with: T is written // as T. Falling through to the qualified path below renders it as the type that declared it, // IWork.T, which names nothing. - if (typeSymbol is ITypeParameterSymbol) { + if (typeSymbol is ITypeParameterSymbol) + { var typeParameter = new TypeParameterDefinition(typeSymbol.Name); return typeSymbol.NullableAnnotation == NullableAnnotation.Annotated @@ -58,60 +74,81 @@ public static ITypeDefinition GetTypeDefinition(this ITypeSymbol typeSymbol) { : typeParameter; } - if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol) { + if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol) + { return arrayTypeSymbol.ElementType.GetTypeDefinition().MakeArray(); } var typeEnum = GetTypeSymbolKind(typeSymbol); - return TypeDefinition.Get(typeEnum, typeSymbol.ContainingNamespace.GetFullName(), GetTypeName(typeSymbol)); + return TypeDefinition.Get( + typeEnum, + typeSymbol.ContainingNamespace.GetFullName(), + GetTypeName(typeSymbol) + ); } - private static TypeDefinitionEnum GetTypeSymbolKind(ITypeSymbol typeSymbol) { + private static TypeDefinitionEnum GetTypeSymbolKind(ITypeSymbol typeSymbol) + { var typeEnum = TypeDefinitionEnum.ClassDefinition; - if (typeSymbol.TypeKind == TypeKind.Enum) { + if (typeSymbol.TypeKind == TypeKind.Enum) + { typeEnum = TypeDefinitionEnum.EnumDefinition; } - else if (typeSymbol.TypeKind == TypeKind.Interface) { + else if (typeSymbol.TypeKind == TypeKind.Interface) + { typeEnum = TypeDefinitionEnum.InterfaceDefinition; } return typeEnum; } - private static string GetTypeName(ITypeSymbol typeSymbol) { - if (typeSymbol.ContainingType != null) { + private static string GetTypeName(ITypeSymbol typeSymbol) + { + if (typeSymbol.ContainingType != null) + { return GetTypeName(typeSymbol.ContainingType) + "." + typeSymbol.Name; } return typeSymbol.Name; } - public static ITypeDefinition? GetTypeDefinitionFromSymbolInfo(SymbolInfo symbolInfo) { - if (symbolInfo.Symbol is INamedTypeSymbol namedTypeSymbol) { + public static ITypeDefinition? GetTypeDefinitionFromSymbolInfo(SymbolInfo symbolInfo) + { + if (symbolInfo.Symbol is INamedTypeSymbol namedTypeSymbol) + { return GetTypeDefinitionFromNamedSymbol(namedTypeSymbol); } - if (symbolInfo.Symbol is IArrayTypeSymbol arrayTypeSymbol) { + if (symbolInfo.Symbol is IArrayTypeSymbol arrayTypeSymbol) + { return GetTypeDefinitionFromType(arrayTypeSymbol.ElementType).MakeArray(); } - + return null; } - - public static ITypeDefinition? GetTypeDefinitionFromNamedSymbol(this INamedTypeSymbol? namedTypeSymbol) { - if (namedTypeSymbol == null) { + + public static ITypeDefinition? GetTypeDefinitionFromNamedSymbol( + this INamedTypeSymbol? namedTypeSymbol + ) + { + if (namedTypeSymbol == null) + { return null; } - + return InternalGetTypeDefinitionFromNamedSymbol(namedTypeSymbol); } - - private static ITypeDefinition InternalGetTypeDefinitionFromNamedSymbol(INamedTypeSymbol namedTypeSymbol) { - if (namedTypeSymbol.IsGenericType) { - if (namedTypeSymbol.Name == "Nullable") { + private static ITypeDefinition InternalGetTypeDefinitionFromNamedSymbol( + INamedTypeSymbol namedTypeSymbol + ) + { + if (namedTypeSymbol.IsGenericType) + { + if (namedTypeSymbol.Name == "Nullable") + { var baseType = namedTypeSymbol.TypeArguments.First(); return GetTypeDefinitionFromType(baseType).MakeNullable(); } @@ -120,7 +157,8 @@ private static ITypeDefinition InternalGetTypeDefinitionFromNamedSymbol(INamedTy var closingTypes = new List(); - foreach (var typeSymbol in closingTypeSymbols) { + foreach (var typeSymbol in closingTypeSymbols) + { var finalType = GetTypeDefinitionFromType(typeSymbol); closingTypes.Add(finalType); @@ -133,7 +171,8 @@ private static ITypeDefinition InternalGetTypeDefinitionFromNamedSymbol(INamedTy closingTypes ); - if (namedTypeSymbol.NullableAnnotation == NullableAnnotation.Annotated) { + if (namedTypeSymbol.NullableAnnotation == NullableAnnotation.Annotated) + { return genericType.MakeNullable(); } @@ -146,17 +185,22 @@ private static ITypeDefinition InternalGetTypeDefinitionFromNamedSymbol(INamedTy var typeDef = TypeDefinition.Get( GetTypeSymbolKind(namedTypeSymbol), - namedTypeSymbol.ContainingNamespace.GetFullName(), GetTypeName(namedTypeSymbol)); + namedTypeSymbol.ContainingNamespace.GetFullName(), + GetTypeName(namedTypeSymbol) + ); - if (namedTypeSymbol.NullableAnnotation == NullableAnnotation.Annotated) { + if (namedTypeSymbol.NullableAnnotation == NullableAnnotation.Annotated) + { return typeDef.MakeNullable(); } return typeDef; } - private static ITypeDefinition GetTypeDefinitionFromType(ITypeSymbol typeSymbol) { - switch (typeSymbol.SpecialType) { + private static ITypeDefinition GetTypeDefinitionFromType(ITypeSymbol typeSymbol) + { + switch (typeSymbol.SpecialType) + { case SpecialType.System_Int16: return TypeDefinition.Get(typeof(short)); @@ -178,23 +222,27 @@ private static ITypeDefinition GetTypeDefinitionFromType(ITypeSymbol typeSymbol) case SpecialType.System_String: return TypeDefinition.Get(typeof(string)); } - - if (typeSymbol is ITypeParameterSymbol || typeSymbol is IErrorTypeSymbol) { + + if (typeSymbol is ITypeParameterSymbol || typeSymbol is IErrorTypeSymbol) + { return new TypeParameterDefinition(typeSymbol.Name); } - if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol) { + if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol) + { return GetTypeDefinitionFromType(arrayTypeSymbol.ElementType).MakeArray(); } - if (typeSymbol is INamedTypeSymbol namedTypeSymbol) { + if (typeSymbol is INamedTypeSymbol namedTypeSymbol) + { return GetTypeDefinitionFromNamedSymbol(namedTypeSymbol)!; } return TypeDefinition.Get(typeSymbol.ContainingNamespace.GetFullName(), typeSymbol.Name); } - private static bool IsKnownType(string name) { + private static bool IsKnownType(string name) + { return false; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/UsageAttributeComponent.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/UsageAttributeComponent.cs index 355aa7f..0bb0e42 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/UsageAttributeComponent.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/UsageAttributeComponent.cs @@ -2,12 +2,12 @@ namespace DependencyModules.SourceGenerator.Impl.Utilities; - public class UsageAttributeComponent : BaseOutputComponent { private readonly string _usage; - public UsageAttributeComponent(string usage) { + public UsageAttributeComponent(string usage) + { _usage = usage; } diff --git a/src/DependencyModules.SourceGenerator/AssemblyModuleAttributeDiagnostics.cs b/src/DependencyModules.SourceGenerator/AssemblyModuleAttributeDiagnostics.cs index 5908459..b908830 100644 --- a/src/DependencyModules.SourceGenerator/AssemblyModuleAttributeDiagnostics.cs +++ b/src/DependencyModules.SourceGenerator/AssemblyModuleAttributeDiagnostics.cs @@ -24,18 +24,20 @@ namespace DependencyModules.SourceGenerator; /// usage already says where to look. /// /// -internal static class AssemblyModuleAttributeDiagnostics { - +internal static class AssemblyModuleAttributeDiagnostics +{ /// /// One compilation unit's assembly attributes and the namespaces in scope for them. /// - internal sealed class UnitModel : IEquatable { - + internal sealed class UnitModel : IEquatable + { public UnitModel( string filePath, ImmutableArray usages, ImmutableArray fileUsings, - ImmutableArray globalUsings) { + ImmutableArray globalUsings + ) + { FilePath = filePath; Usages = usages; FileUsings = fileUsings; @@ -54,20 +56,21 @@ public UnitModel( public ImmutableArray GlobalUsings { get; } public bool Equals(UnitModel? other) => - other != null && - FilePath == other.FilePath && - Usages.SequenceEqual(other.Usages) && - FileUsings.SequenceEqual(other.FileUsings) && - GlobalUsings.SequenceEqual(other.GlobalUsings); + other != null + && FilePath == other.FilePath + && Usages.SequenceEqual(other.Usages) + && FileUsings.SequenceEqual(other.FileUsings) + && GlobalUsings.SequenceEqual(other.GlobalUsings); public override bool Equals(object? obj) => Equals(obj as UnitModel); public override int GetHashCode() => Usages.Length * 397 ^ FileUsings.Length; } - internal sealed class Usage : IEquatable { - - public Usage(string name, Location location) { + internal sealed class Usage : IEquatable + { + public Usage(string name, Location location) + { Name = name; Location = location; } @@ -86,14 +89,18 @@ public bool Equals(Usage? other) => } internal static IncrementalValueProvider> Collect( - IncrementalGeneratorInitializationContext context) => - context.SyntaxProvider.CreateSyntaxProvider( + IncrementalGeneratorInitializationContext context + ) => + context + .SyntaxProvider.CreateSyntaxProvider( static (node, _) => node is CompilationUnitSyntax, - static (syntaxContext, cancellation) => Read(syntaxContext, cancellation)) + static (syntaxContext, cancellation) => Read(syntaxContext, cancellation) + ) .Where(static model => !model.Usages.IsEmpty || !model.GlobalUsings.IsEmpty) .Collect(); - private static UnitModel Read(GeneratorSyntaxContext context, CancellationToken cancellation) { + private static UnitModel Read(GeneratorSyntaxContext context, CancellationToken cancellation) + { cancellation.ThrowIfCancellationRequested(); var unit = (CompilationUnitSyntax)context.Node; @@ -102,10 +109,12 @@ private static UnitModel Read(GeneratorSyntaxContext context, CancellationToken var fileUsings = ImmutableArray.CreateBuilder(); var globalUsings = ImmutableArray.CreateBuilder(); - foreach (var usingDirective in unit.Usings) { + foreach (var usingDirective in unit.Usings) + { // An alias imports one name rather than a namespace, so it cannot bring a module // attribute into scope under the name this checks for. - if (usingDirective.Alias != null || usingDirective.Name == null) { + if (usingDirective.Alias != null || usingDirective.Name == null) + { continue; } @@ -116,14 +125,18 @@ private static UnitModel Read(GeneratorSyntaxContext context, CancellationToken target.Add(usingDirective.Name.ToString()); } - foreach (var attributeList in unit.AttributeLists) { - if (!attributeList.Target?.Identifier.IsKind(SyntaxKind.AssemblyKeyword) ?? true) { + foreach (var attributeList in unit.AttributeLists) + { + if (!attributeList.Target?.Identifier.IsKind(SyntaxKind.AssemblyKeyword) ?? true) + { continue; } - foreach (var attribute in attributeList.Attributes) { + foreach (var attribute in attributeList.Attributes) + { // A qualified usage already names where the attribute lives. - if (attribute.Name is not SimpleNameSyntax simpleName) { + if (attribute.Name is not SimpleNameSyntax simpleName) + { continue; } @@ -135,17 +148,28 @@ private static UnitModel Read(GeneratorSyntaxContext context, CancellationToken unit.SyntaxTree.FilePath, usages.ToImmutable(), fileUsings.ToImmutable(), - globalUsings.ToImmutable()); + globalUsings.ToImmutable() + ); } internal static void Report( SourceProductionContext context, - ((ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Modules, - ImmutableArray Units) Left, Compilation Right) data) { - + ( + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Modules, + ImmutableArray Units + ) Left, + Compilation Right + ) data + ) + { var input = data.Left; - if (input.Units.IsDefaultOrEmpty || input.Modules.IsDefaultOrEmpty) { + if (input.Units.IsDefaultOrEmpty || input.Modules.IsDefaultOrEmpty) + { return; } @@ -157,11 +181,15 @@ internal static void Report( // is never written by hand at the assembly level, so neither can produce this mistake. var modulesByName = new Dictionary(StringComparer.Ordinal); - foreach (var (module, _) in input.Modules) { + foreach (var (module, _) in input.Modules) + { var moduleNamespace = module.EntryPointType.Namespace; - if (string.IsNullOrEmpty(moduleNamespace) || - module.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule)) { + if ( + string.IsNullOrEmpty(moduleNamespace) + || module.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule) + ) + { continue; } @@ -174,8 +202,10 @@ internal static void Report( // by the test integration instead and are perfectly at home in any file. string? entryPointFile = null; - foreach (var (module, _) in input.Modules) { - if (module.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule)) { + foreach (var (module, _) in input.Modules) + { + if (module.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.AutoGenerateModule)) + { entryPointFile = module.FileLocation; break; } @@ -183,14 +213,18 @@ internal static void Report( var globalUsings = new HashSet(StringComparer.Ordinal); - foreach (var unit in input.Units) { - foreach (var globalUsing in unit.GlobalUsings) { + foreach (var unit in input.Units) + { + foreach (var globalUsing in unit.GlobalUsings) + { globalUsings.Add(globalUsing); } } - foreach (var unit in input.Units) { - foreach (var usage in unit.Usages) { + foreach (var unit in input.Units) + { + foreach (var usage in unit.Usages) + { context.CancellationToken.ThrowIfCancellationRequested(); // Written as [assembly: Foo] or [assembly: FooAttribute]; both name module Foo. @@ -201,49 +235,71 @@ internal static void Report( // takes; the exhaustive walk is only worth doing for a name that resolved to // nothing, which is a build that is already red. moduleNamespace ??= referenced.FindImported( - usage.Name, unit.FileUsings.Concat(globalUsings)); + usage.Name, + unit.FileUsings.Concat(globalUsings) + ); moduleNamespace ??= referenced.FindAnywhere(usage.Name); - if (moduleNamespace == null) { + if (moduleNamespace == null) + { continue; } - if (!unit.FileUsings.Contains(moduleNamespace) && !globalUsings.Contains(moduleNamespace)) { + if ( + !unit.FileUsings.Contains(moduleNamespace) + && !globalUsings.Contains(moduleNamespace) + ) + { context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.ModuleAttributeNamespaceNotImported, usage.Location, usage.Name, - moduleNamespace)); + moduleNamespace + ) + ); // It does not compile yet, so which file it belongs in is a later question. continue; } - if (entryPointFile != null && - !string.IsNullOrEmpty(unit.FilePath) && - !string.Equals(unit.FilePath, entryPointFile, StringComparison.Ordinal)) { + if ( + entryPointFile != null + && !string.IsNullOrEmpty(unit.FilePath) + && !string.Equals(unit.FilePath, entryPointFile, StringComparison.Ordinal) + ) + { context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.AssemblyModuleAttributeNotComposed, usage.Location, usage.Name, - System.IO.Path.GetFileName(entryPointFile))); + System.IO.Path.GetFileName(entryPointFile) + ) + ); } } } } /// The namespace of a module declared in this compilation, or null. - private static string? LocalModuleNamespace(Dictionary modulesByName, string name) { - if (modulesByName.TryGetValue(name, out var found)) { + private static string? LocalModuleNamespace( + Dictionary modulesByName, + string name + ) + { + if (modulesByName.TryGetValue(name, out var found)) + { return found; } - return name.EndsWith("Attribute", StringComparison.Ordinal) && - modulesByName.TryGetValue( - name.Substring(0, name.Length - "Attribute".Length), out found) + return + name.EndsWith("Attribute", StringComparison.Ordinal) + && modulesByName.TryGetValue( + name.Substring(0, name.Length - "Attribute".Length), + out found + ) ? found : null; } diff --git a/src/DependencyModules.SourceGenerator/Conventions/ConventionContractSource.cs b/src/DependencyModules.SourceGenerator/Conventions/ConventionContractSource.cs index ca901d9..1e27897 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/ConventionContractSource.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/ConventionContractSource.cs @@ -16,8 +16,8 @@ namespace DependencyModules.Conventions; /// a rename on either side would stop every convention matching, silently. /// /// -public static class ConventionContractSource { - +public static class ConventionContractSource +{ /// /// The namespace the contracts are declared in, and the metadata prefix the generator matches /// declarations against. Deliberately not this assembly's own namespace: the contracts ship in @@ -35,5 +35,4 @@ public static class ConventionContractSource { /// The method the generator reads. Implemented explicitly, so the name is fixed. /// public const string ConventionMethod = "Conventions"; - } diff --git a/src/DependencyModules.SourceGenerator/Conventions/ConventionGenerator.cs b/src/DependencyModules.SourceGenerator/Conventions/ConventionGenerator.cs index f963a02..0fa1603 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/ConventionGenerator.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/ConventionGenerator.cs @@ -1,6 +1,6 @@ -using CSharpAuthor; using System.Collections.Immutable; using System.Text; +using CSharpAuthor; using DependencyModules.Conventions.Models; using DependencyModules.Conventions.Utilities; using DependencyModules.SourceGenerator.Impl; @@ -15,7 +15,10 @@ namespace DependencyModules.Conventions; /// A module-level [Decorate] after its decorator's constructor has been looked up. /// public record ResolvedModuleDecorator( - ITypeDefinition ModuleType, DecoratorModel Model, string? Reason); + ITypeDefinition ModuleType, + DecoratorModel Model, + string? Reason +); /// /// The module-level decorations, and the compilation they were resolved from. @@ -31,8 +34,10 @@ public record ResolvedModuleDecorator( /// only symbols can answer. /// public record ModuleDecorators( - EquatableList Resolved, Compilation Compilation) { - + EquatableList Resolved, + Compilation Compilation +) +{ public virtual bool Equals(ModuleDecorators? other) => other is not null && Resolved.Equals(other.Resolved); @@ -55,14 +60,18 @@ public virtual bool Equals(ModuleDecorators? other) => /// stamp of everything that can change what a name binds to, which took the per-keystroke cost from /// 11–39 ms at 2,000 classes to a flat ~11 ms, and the convention half of that to ~2.4 ms. /// -public class ConventionGenerator : IDependencyModuleSourceGenerator { - +public class ConventionGenerator : IDependencyModuleSourceGenerator +{ private const string LoggerName = "ConventionSourceGenerator"; public void SetupGenerator( IncrementalGeneratorInitializationContext context, - IncrementalValuesProvider<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> incrementalValueProvider) { - + IncrementalValuesProvider<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> incrementalValueProvider + ) + { // The contracts used to be emitted here through RegisterPostInitializationOutput. They now // live in DependencyModules.Runtime, which is what lets them be public — and public is what // retires the explicit implementation requirement and the CS0436 between two assemblies that @@ -75,11 +84,12 @@ public void SetupGenerator( // Lambdas rather than method groups: SyntaxTransformContext converts implicitly from // GeneratorSyntaxContext, but a method group conversion will not apply a user-defined // conversion to a parameter. - var conventionModules = context.SyntaxProvider - .CreateSyntaxProvider( + var conventionModules = context + .SyntaxProvider.CreateSyntaxProvider( ConventionModelUtility.IsConventionModuleCandidate, (syntaxContext, cancellation) => - ConventionModelUtility.GetConventionModuleModel(syntaxContext, cancellation)) + ConventionModelUtility.GetConventionModuleModel(syntaxContext, cancellation) + ) .Where(model => !model.IsIgnored) .Collect(); @@ -93,9 +103,11 @@ public void SetupGenerator( context, new[] { KnownTypes.DependencyModules.Attributes.DecoratorAttribute }, static (syntaxContext, cancellation) => - DecoratorModelUtility.GetDecoratorModel(syntaxContext, cancellation) ?? DecoratorModel.Ignore, + DecoratorModelUtility.GetDecoratorModel(syntaxContext, cancellation) + ?? DecoratorModel.Ignore, new DecoratorModelComparer(), - DecoratorModel.Ignore); + DecoratorModel.Ignore + ); // The registrations the *attributes* made. Decoration needs them and the convention ones in // the same place: a generic decorator is closed over the type arguments a registration used, @@ -103,44 +115,66 @@ public void SetupGenerator( // up emitted from two stages that could not see each other. var attributeServices = AttributeModelCollector.Collect( context, - new[] { + new[] + { KnownTypes.DependencyModules.Attributes.TransientServiceAttribute, KnownTypes.DependencyModules.Attributes.ScopedServiceAttribute, KnownTypes.DependencyModules.Attributes.SingletonServiceAttribute, - KnownTypes.DependencyModules.Attributes.CrossWireServiceAttribute + KnownTypes.DependencyModules.Attributes.CrossWireServiceAttribute, }, static (syntaxContext, cancellation) => - ServiceModelUtility.GetServiceModel(syntaxContext, cancellation) ?? ServiceModel.Ignore, + ServiceModelUtility.GetServiceModel(syntaxContext, cancellation) + ?? ServiceModel.Ignore, new ServiceModelComparer(), - ServiceModel.Ignore); + ServiceModel.Ignore + ); // [Decorate] on a module names its decorator by typeof(), so the constructor has to be // looked up from the compilation. Resolved here rather than in the output stage: the // compilation changes on every keystroke, and combining it into the output would re-emit // everything every time. The result is compared by value, so an unchanged lookup propagates // nothing — the same shape the metadata scan below uses, and for the same reason. - var moduleDecorators = incrementalValueProvider.Collect() + var moduleDecorators = incrementalValueProvider + .Collect() .Combine(context.CompilationProvider) - .Select((pair, cancellation) => { - var resolved = new List(); - - foreach (var (entryPoint, _) in pair.Left) { - foreach (var resolution in - ModuleDecoratorResolver.Resolve(entryPoint, pair.Right, cancellation)) { - resolved.Add(new ResolvedModuleDecorator( - entryPoint.EntryPointType, resolution.Model, resolution.Reason)); + .Select( + (pair, cancellation) => + { + var resolved = new List(); + + foreach (var (entryPoint, _) in pair.Left) + { + foreach ( + var resolution in ModuleDecoratorResolver.Resolve( + entryPoint, + pair.Right, + cancellation + ) + ) + { + resolved.Add( + new ResolvedModuleDecorator( + entryPoint.EntryPointType, + resolution.Model, + resolution.Reason + ) + ); + } } - } - return new ModuleDecorators( - new EquatableList(resolved), pair.Right); - }); + return new ModuleDecorators( + new EquatableList(resolved), + pair.Right + ); + } + ); - var candidates = context.SyntaxProvider - .CreateSyntaxProvider( + var candidates = context + .SyntaxProvider.CreateSyntaxProvider( ConventionCandidateUtility.IsCandidate, (syntaxContext, cancellation) => - ConventionCandidateCache.GetOrAdd(syntaxContext, cancellation)) + ConventionCandidateCache.GetOrAdd(syntaxContext, cancellation) + ) .Where(model => !model.IsIgnored) .Collect(); @@ -152,11 +186,15 @@ public void SetupGenerator( // case and costs nothing. var metadataCandidates = conventionModules .Combine(context.CompilationProvider) - .Select((pair, cancellation) => - new EquatableList( - MetadataCandidateUtility.Collect(pair.Left, pair.Right, cancellation))); - - var everything = incrementalValueProvider.Collect() + .Select( + (pair, cancellation) => + new EquatableList( + MetadataCandidateUtility.Collect(pair.Left, pair.Right, cancellation) + ) + ); + + var everything = incrementalValueProvider + .Collect() .Combine(conventionModules) .Combine(candidates) .Combine(metadataCandidates) @@ -180,19 +218,38 @@ public void SetupGenerator( // The cost is that matching runs twice, once per output. The emitting pass is silent, so // nothing composes a diagnostic message it is about to discard, and this pass writes no // source. - context.RegisterSourceOutput(everything.Combine(context.CompilationProvider), ReportDiagnostics); + context.RegisterSourceOutput( + everything.Combine(context.CompilationProvider), + ReportDiagnostics + ); } private void GenerateSourceOutput( SourceProductionContext context, - ((((((ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Left, - ImmutableArray Right) Left, - ImmutableArray Right) Left, - EquatableList Right) Left, - ImmutableArray Right) Left, - ImmutableArray Right) Left, - ModuleDecorators Right) data) { - + ( + ( + ( + ( + ( + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Left, + ImmutableArray Right + ) Left, + ImmutableArray Right + ) Left, + EquatableList Right + ) Left, + ImmutableArray Right + ) Left, + ImmutableArray Right + ) Left, + ModuleDecorators Right + ) data + ) + { var entryPoints = data.Left.Left.Left.Left.Left.Left; var conventionModules = data.Left.Left.Left.Left.Left.Right; var decorators = data.Left.Left.Right; @@ -201,13 +258,15 @@ private void GenerateSourceOutput( // In-compilation candidates and metadata candidates travel together; a convention sees one // source or the other, decided by whether it named an assembly. - var candidates = data.Left.Left.Left.Left.Right.Length == 0 - ? (IReadOnlyList)data.Left.Left.Left.Right - : data.Left.Left.Left.Left.Right.Concat(data.Left.Left.Left.Right).ToArray(); + var candidates = + data.Left.Left.Left.Left.Right.Length == 0 + ? (IReadOnlyList)data.Left.Left.Left.Right + : data.Left.Left.Left.Left.Right.Concat(data.Left.Left.Left.Right).ToArray(); // Decoration runs whether or not anything declares a convention, so the early-out is on // entry points alone. - if (entryPoints.Length == 0) { + if (entryPoints.Length == 0) + { return; } @@ -216,16 +275,30 @@ private void GenerateSourceOutput( FileLogger.Wrap( LoggerName, configuration, - logger => Generate( - context, entryPoints, conventionModules, candidates, decorators, attributeServices, - moduleDecorators, DiagnosticReporter.Silent, emit: true, logger), + logger => + Generate( + context, + entryPoints, + conventionModules, + candidates, + decorators, + attributeServices, + moduleDecorators, + DiagnosticReporter.Silent, + emit: true, + logger + ), // Surfaced as a build error rather than discarded, matching the attribute generators. A // generator that fails quietly produces a green build with no registrations. - exception => context.ReportDiagnostic( - Diagnostic.Create( - DependencyModuleDiagnostics.GeneratorFailure, - Location.None, - $"{exception.GetType().Name}: {exception.Message}"))); + exception => + context.ReportDiagnostic( + Diagnostic.Create( + DependencyModuleDiagnostics.GeneratorFailure, + Location.None, + $"{exception.GetType().Name}: {exception.Message}" + ) + ) + ); } /// @@ -233,46 +306,85 @@ private void GenerateSourceOutput( /// private void ReportDiagnostics( SourceProductionContext context, - (((((((ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Left, - ImmutableArray Right) Left, - ImmutableArray Right) Left, - EquatableList Right) Left, - ImmutableArray Right) Left, - ImmutableArray Right) Left, - ModuleDecorators Right) Left, - Compilation Right) data) { - + ( + ( + ( + ( + ( + ( + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Left, + ImmutableArray Right + ) Left, + ImmutableArray Right + ) Left, + EquatableList Right + ) Left, + ImmutableArray Right + ) Left, + ImmutableArray Right + ) Left, + ModuleDecorators Right + ) Left, + Compilation Right + ) data + ) + { var models = data.Left; var entryPoints = models.Left.Left.Left.Left.Left.Left; - if (entryPoints.Length == 0) { + if (entryPoints.Length == 0) + { return; } - var candidates = models.Left.Left.Left.Left.Right.Length == 0 - ? (IReadOnlyList)models.Left.Left.Left.Right - : models.Left.Left.Left.Left.Right.Concat(models.Left.Left.Left.Right).ToArray(); + var candidates = + models.Left.Left.Left.Left.Right.Length == 0 + ? (IReadOnlyList)models.Left.Left.Left.Right + : models.Left.Left.Left.Left.Right.Concat(models.Left.Left.Left.Right).ToArray(); var configuration = entryPoints.First().Right; - var report = new DiagnosticReporter(context.ReportDiagnostic, new SyntaxTreeLookup(data.Right)); + var report = new DiagnosticReporter( + context.ReportDiagnostic, + new SyntaxTreeLookup(data.Right) + ); FileLogger.Wrap( LoggerName, configuration, - logger => Generate( - context, entryPoints, models.Left.Left.Left.Left.Left.Right, candidates, - models.Left.Left.Right, models.Left.Right, models.Right, - report, emit: false, logger), - exception => context.ReportDiagnostic( - Diagnostic.Create( - DependencyModuleDiagnostics.GeneratorFailure, - Location.None, - $"{exception.GetType().Name}: {exception.Message}"))); + logger => + Generate( + context, + entryPoints, + models.Left.Left.Left.Left.Left.Right, + candidates, + models.Left.Left.Right, + models.Left.Right, + models.Right, + report, + emit: false, + logger + ), + exception => + context.ReportDiagnostic( + Diagnostic.Create( + DependencyModuleDiagnostics.GeneratorFailure, + Location.None, + $"{exception.GetType().Name}: {exception.Message}" + ) + ) + ); } private void Generate( SourceProductionContext context, - ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> entryPoints, + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> entryPoints, ImmutableArray conventionModules, IReadOnlyList candidates, ImmutableArray decorators, @@ -280,13 +392,17 @@ private void Generate( ModuleDecorators moduleDecorators, DiagnosticReporter report, bool emit, - FileLogger logger) { - - var (entryPointList, configurationModel) = EntryModelUtil.ConsolidateEntryPointModels(entryPoints); + FileLogger logger + ) + { + var (entryPointList, configurationModel) = EntryModelUtil.ConsolidateEntryPointModels( + entryPoints + ); logger.Info( - $"Discovered {conventionModules.Length} convention module(s) and " + - $"{candidates.Count} candidate type(s)."); + $"Discovered {conventionModules.Length} convention module(s) and " + + $"{candidates.Count} candidate type(s)." + ); var claimed = new HashSet(); @@ -294,19 +410,32 @@ private void Generate( // and convention registrations are emitted once rather than alongside an identical copy on // the module it defers to. It can never be a convention module itself - IConventionModule is // implemented by hand - so nothing here goes unclaimed as a result. - foreach (var entryPointModel in EntryModelUtil.RegistrationTargets(entryPointList)) { + foreach (var entryPointModel in EntryModelUtil.RegistrationTargets(entryPointList)) + { context.CancellationToken.ThrowIfCancellationRequested(); - var conventionModule = conventionModules.FirstOrDefault( - module => module.ModuleType.Equals(entryPointModel.EntryPointType)); + var conventionModule = conventionModules.FirstOrDefault(module => + module.ModuleType.Equals(entryPointModel.EntryPointType) + ); - if (conventionModule != null) { + if (conventionModule != null) + { claimed.Add(conventionModule); } GenerateForModule( - context, entryPointModel, configurationModel, conventionModule, candidates, decorators, - attributeServices, moduleDecorators, report, emit, logger); + context, + entryPointModel, + configurationModel, + conventionModule, + candidates, + decorators, + attributeServices, + moduleDecorators, + report, + emit, + logger + ); } ReportUnclaimedModules(report, conventionModules, claimed, logger); @@ -323,23 +452,38 @@ private void GenerateForModule( ModuleDecorators moduleDecorators, DiagnosticReporter report, bool emit, - FileLogger logger) { - + FileLogger logger + ) + { var withNamespace = EntryModelUtil.EnsureNamespace(entryPointModel, configurationModel); - var serviceModels = conventionModule == null - ? Array.Empty() - : ConventionMatcher.Match( - withNamespace, conventionModule, candidates, report, logger); + var serviceModels = + conventionModule == null + ? Array.Empty() + : ConventionMatcher.Match( + withNamespace, + conventionModule, + candidates, + report, + logger + ); // Every registration this compilation makes, however it was declared. This is the whole // point of the single stage: a generic decorator is expanded once, against all of them. WriteDecorators( - context, withNamespace, configurationModel, - ServiceTypes(attributeServices, serviceModels), decorators, moduleDecorators, - report, emit, logger); - - if (serviceModels.Count == 0 || !emit) { + context, + withNamespace, + configurationModel, + ServiceTypes(attributeServices, serviceModels), + decorators, + moduleDecorators, + report, + emit, + logger + ); + + if (serviceModels.Count == 0 || !emit) + { return; } @@ -352,27 +496,37 @@ private void GenerateForModule( context.AddSource( withNamespace.EntryPointType.GetFileNameHint( - configurationModel.RootNamespace, "ConventionDependencies"), - output); + configurationModel.RootNamespace, + "ConventionDependencies" + ), + output + ); } /// /// Every service type the compilation registers, in the closed form it registers it as. /// private static IReadOnlyList ServiceTypes( - ImmutableArray attributeServices, IReadOnlyList conventionServices) { - + ImmutableArray attributeServices, + IReadOnlyList conventionServices + ) + { var seen = new HashSet(); var ordered = new List(); - void Add(IEnumerable models) { - foreach (var model in models) { - if (model.Equals(ServiceModel.Ignore)) { + void Add(IEnumerable models) + { + foreach (var model in models) + { + if (model.Equals(ServiceModel.Ignore)) + { continue; } - foreach (var registration in model.Registrations) { - if (seen.Add(registration.ServiceType)) { + foreach (var registration in model.Registrations) + { + if (seen.Add(registration.ServiceType)) + { ordered.Add(registration.ServiceType); } } @@ -409,11 +563,19 @@ private static void WriteDecorators( ModuleDecorators moduleDecorators, DiagnosticReporter report, bool emit, - FileLogger logger) { - - var decorators = CollectDecorators(report, entryPointModel, declared, moduleDecorators, logger); - - if (decorators.Count == 0) { + FileLogger logger + ) + { + var decorators = CollectDecorators( + report, + entryPointModel, + declared, + moduleDecorators, + logger + ); + + if (decorators.Count == 0) + { return; } @@ -423,14 +585,24 @@ private static void WriteDecorators( out var refusedForOpenGenericRegistration, canClose: (decoratorType, closedService) => DecoratorConstraintChecker.CanClose( - moduleDecorators.Compilation, decoratorType, closedService)); + moduleDecorators.Compilation, + decoratorType, + closedService + ) + ); ReportOpenGenericDecoration(report, refusedForOpenGenericRegistration, logger); ReportImplementationUnderFactories( - report, decorators, entryPointModel, configurationModel, logger); + report, + decorators, + entryPointModel, + configurationModel, + logger + ); - if (expanded.Count == 0 || !emit) { + if (expanded.Count == 0 || !emit) + { return; } @@ -440,8 +612,11 @@ private static void WriteDecorators( context.AddSource( entryPointModel.EntryPointType.GetFileNameHint( - configurationModel.RootNamespace, "Decorators"), - output); + configurationModel.RootNamespace, + "Decorators" + ), + output + ); } /// @@ -453,26 +628,32 @@ private static IReadOnlyList CollectDecorators( ModuleEntryPointModel entryPointModel, ImmutableArray declared, ModuleDecorators moduleDecorators, - FileLogger logger) { - + FileLogger logger + ) + { var decorators = new List(); - foreach (var decorator in declared) { - if (decorator.IsIgnored) { + foreach (var decorator in declared) + { + if (decorator.IsIgnored) + { continue; } // A realm-scoped decorator belongs only to its realm. An unscoped one belongs to every // module that is not realm-only, matching how service registrations behave. - if (decorator.Realm != null) { - if (decorator.Realm.Equals(entryPointModel.EntryPointType)) { + if (decorator.Realm != null) + { + if (decorator.Realm.Equals(entryPointModel.EntryPointType)) + { decorators.Add(decorator); } continue; } - if (!entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.OnlyRealm)) { + if (!entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.OnlyRealm)) + { decorators.Add(decorator); } } @@ -480,15 +661,19 @@ private static IReadOnlyList CollectDecorators( // [Decorate] carries two type names and nothing else, so its decorator's constructor is // looked up rather than read from a declaration — the only route for one declared in a // referenced assembly, which is the case the module-level form exists for. - foreach (var resolution in moduleDecorators.Resolved) { - if (!resolution.ModuleType.Equals(entryPointModel.EntryPointType)) { + foreach (var resolution in moduleDecorators.Resolved) + { + if (!resolution.ModuleType.Equals(entryPointModel.EntryPointType)) + { continue; } - if (resolution.Reason != null) { + if (resolution.Reason != null) + { logger.Error( - $"'{resolution.Model.DecoratorType.Name}' cannot be constructed by generated " + - $"code: {resolution.Reason}."); + $"'{resolution.Model.DecoratorType.Name}' cannot be constructed by generated " + + $"code: {resolution.Reason}." + ); } decorators.Add(resolution.Model); @@ -514,27 +699,37 @@ private static void ReportImplementationUnderFactories( IReadOnlyList decorators, ModuleEntryPointModel entryPointModel, DependencyModuleConfigurationModel configurationModel, - FileLogger logger) { - - if (!entryPointModel.GenerateFactories.GetValueOrDefault(configurationModel.GenerateFactories)) { + FileLogger logger + ) + { + if ( + !entryPointModel.GenerateFactories.GetValueOrDefault( + configurationModel.GenerateFactories + ) + ) + { return; } - foreach (var decorator in decorators) { - if (decorator.Implementation == null) { + foreach (var decorator in decorators) + { + if (decorator.Implementation == null) + { continue; } logger.Error( - $"'{decorator.DecoratorType.Name}' names an implementation, which generated " + - "factories cannot be told apart by."); + $"'{decorator.DecoratorType.Name}' names an implementation, which generated " + + "factories cannot be told apart by." + ); report.Report( DependencyModuleDiagnostics.DecoratorImplementationNeedsTypeRegistration, decorator.Location, decorator.DecoratorType.Name, decorator.Implementation.Name, - decorator.ServiceType.Name); + decorator.ServiceType.Name + ); } } @@ -550,21 +745,25 @@ private static void ReportImplementationUnderFactories( private static void ReportOpenGenericDecoration( DiagnosticReporter report, IReadOnlyList refused, - FileLogger logger) { - - foreach (var decorator in refused) { + FileLogger logger + ) + { + foreach (var decorator in refused) + { var serviceName = decorator.ServiceType.Name; var decoratorName = decorator.DecoratorType.Name; logger.Error( - $"'{decoratorName}' cannot decorate '{serviceName}' because it is registered as an " + - "open generic."); + $"'{decoratorName}' cannot decorate '{serviceName}' because it is registered as an " + + "open generic." + ); report.Report( DependencyModuleDiagnostics.OpenGenericCannotBeDecorated, decorator.Location, serviceName, - decoratorName); + decoratorName + ); } } @@ -573,18 +772,27 @@ private static void ReportOpenGenericDecoration( /// reported rather than resolved arbitrarily. /// private static void ReportAmbiguousOrdering( - DiagnosticReporter report, IReadOnlyList decorators, FileLogger logger) { - - for (var i = 0; i < decorators.Count; i++) { - for (var j = i + 1; j < decorators.Count; j++) { - if (decorators[i].Order != decorators[j].Order || - !decorators[i].ServiceType.Equals(decorators[j].ServiceType)) { + DiagnosticReporter report, + IReadOnlyList decorators, + FileLogger logger + ) + { + for (var i = 0; i < decorators.Count; i++) + { + for (var j = i + 1; j < decorators.Count; j++) + { + if ( + decorators[i].Order != decorators[j].Order + || !decorators[i].ServiceType.Equals(decorators[j].ServiceType) + ) + { continue; } logger.Error( - $"'{decorators[i].DecoratorType.Name}' and '{decorators[j].DecoratorType.Name}' both " + - $"decorate '{decorators[i].ServiceType.Name}' with order {decorators[i].Order}."); + $"'{decorators[i].DecoratorType.Name}' and '{decorators[j].DecoratorType.Name}' both " + + $"decorate '{decorators[i].ServiceType.Name}' with order {decorators[i].Order}." + ); report.Report( DependencyModuleDiagnostics.AmbiguousDecoratorOrder, @@ -592,7 +800,8 @@ private static void ReportAmbiguousOrdering( decorators[i].DecoratorType.Name, decorators[j].DecoratorType.Name, decorators[i].ServiceType.Name, - decorators[i].Order); + decorators[i].Order + ); } } } @@ -608,10 +817,13 @@ private static void ReportUnclaimedModules( DiagnosticReporter report, ImmutableArray conventionModules, HashSet claimed, - FileLogger logger) { - - foreach (var conventionModule in conventionModules) { - if (claimed.Contains(conventionModule)) { + FileLogger logger + ) + { + foreach (var conventionModule in conventionModules) + { + if (claimed.Contains(conventionModule)) + { continue; } @@ -623,7 +835,8 @@ private static void ReportUnclaimedModules( DependencyModuleDiagnostics.ConventionCannotBeRead, conventionModule.Location, "the declaring type is not marked with [DependencyModule], so it registers nothing", - name); + name + ); } } } diff --git a/src/DependencyModules.SourceGenerator/Conventions/Models/ConventionCandidateModel.cs b/src/DependencyModules.SourceGenerator/Conventions/Models/ConventionCandidateModel.cs index 865b07f..6a93e5d 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Models/ConventionCandidateModel.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Models/ConventionCandidateModel.cs @@ -22,7 +22,8 @@ namespace DependencyModules.Conventions.Models; public record ImplementedInterfaceModel( ITypeDefinition InterfaceType, string DefinitionKey, - string? ViaTypeName); + string? ViaTypeName +); /// /// A class or record a convention could match. @@ -71,17 +72,20 @@ public record ConventionCandidateModel( /// should not silently pick up a type from a package, and a scan of a package should not pick up /// a local one. /// - string? AssemblyName = null) { - + string? AssemblyName = null +) +{ public static readonly ConventionCandidateModel Ignore = new( TypeDefinition.Get("", "Ignore"), Array.Empty(), Array.Empty(), null, true, - LocationModel.None); + LocationModel.None + ); - public bool IsIgnored => ReferenceEquals(this, Ignore) || ImplementationType.Equals(Ignore.ImplementationType); + public bool IsIgnored => + ReferenceEquals(this, Ignore) || ImplementationType.Equals(Ignore.ImplementationType); /// /// The interfaces visible to a convention with the given reach. @@ -90,30 +94,40 @@ public IEnumerable InterfacesInReach(bool includeBase includeBaseClasses ? DeclaredInterfaces.Concat(BaseClassInterfaces) : DeclaredInterfaces; public virtual bool Equals(ConventionCandidateModel? other) => - other is not null && - ImplementationType.Equals(other.ImplementationType) && - HasAccessibleConstructor == other.HasAccessibleConstructor && - Location == other.Location && - ModelEquality.ListEquals(DeclaredInterfaces, other.DeclaredInterfaces) && - ModelEquality.ListEquals(BaseClassInterfaces, other.BaseClassInterfaces) && - CompareConstructor(Constructor, other.Constructor) && + other is not null + && ImplementationType.Equals(other.ImplementationType) + && HasAccessibleConstructor == other.HasAccessibleConstructor + && Location == other.Location + && ModelEquality.ListEquals(DeclaredInterfaces, other.DeclaredInterfaces) + && ModelEquality.ListEquals(BaseClassInterfaces, other.BaseClassInterfaces) + && CompareConstructor(Constructor, other.Constructor) + && // Null and empty both mean unconditional and have to compare equal, or an edit elsewhere // in the file would miss the incremental cache. - ((Conditions?.Count ?? 0) == 0 && (other.Conditions?.Count ?? 0) == 0 || - ModelEquality.ListEquals(Conditions, other.Conditions)) && - ((AttributeTypeKeys?.Count ?? 0) == 0 && (other.AttributeTypeKeys?.Count ?? 0) == 0 || - ModelEquality.ListEquals(AttributeTypeKeys, other.AttributeTypeKeys)) && - AssemblyName == other.AssemblyName; + ( + (Conditions?.Count ?? 0) == 0 && (other.Conditions?.Count ?? 0) == 0 + || ModelEquality.ListEquals(Conditions, other.Conditions) + ) + && ( + (AttributeTypeKeys?.Count ?? 0) == 0 && (other.AttributeTypeKeys?.Count ?? 0) == 0 + || ModelEquality.ListEquals(AttributeTypeKeys, other.AttributeTypeKeys) + ) + && AssemblyName == other.AssemblyName; - private static bool CompareConstructor(ConstructorInfoModel? x, ConstructorInfoModel? y) { - if (x is null && y is null) return true; - if (x is null || y is null) return false; + private static bool CompareConstructor(ConstructorInfoModel? x, ConstructorInfoModel? y) + { + if (x is null && y is null) + return true; + if (x is null || y is null) + return false; return ModelEquality.ListEquals(x.Parameters, y.Parameters); } - public override int GetHashCode() { - unchecked { + public override int GetHashCode() + { + unchecked + { var hash = ImplementationType.GetHashCode(); hash = hash * 31 + HasAccessibleConstructor.GetHashCode(); hash = hash * 31 + ModelEquality.ListHashCode(DeclaredInterfaces); diff --git a/src/DependencyModules.SourceGenerator/Conventions/Models/ConventionModel.cs b/src/DependencyModules.SourceGenerator/Conventions/Models/ConventionModel.cs index df23c2c..31af506 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Models/ConventionModel.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Models/ConventionModel.cs @@ -13,11 +13,14 @@ namespace DependencyModules.Conventions.Models; /// — structurally different, same service. A key built from namespace, name and arity is equal for /// both, and is a string, so it costs nothing to keep in an incremental model. /// -public static class ConventionTypeKey { - - public static string For(ITypeDefinition type) { +public static class ConventionTypeKey +{ + public static string For(ITypeDefinition type) + { var arity = type.TypeArguments?.Count ?? 0; - var name = string.IsNullOrEmpty(type.Namespace) ? type.Name : type.Namespace + "." + type.Name; + var name = string.IsNullOrEmpty(type.Namespace) + ? type.Name + : type.Namespace + "." + type.Name; return arity == 0 ? name : name + "`" + arity; } @@ -26,7 +29,8 @@ public static string For(ITypeDefinition type) { /// /// What a convention registers each match as. /// -public enum ConventionRegisterAs { +public enum ConventionRegisterAs +{ /// /// As the service type the convention matched through. The default. /// @@ -70,8 +74,8 @@ public enum ConventionRegisterAs { /// once per pattern rather than once per candidate. /// /// True for the Without form. -public record NameFilterModel(string Pattern, bool Exclude) { - +public record NameFilterModel(string Pattern, bool Exclude) +{ /// /// True when the pattern is matched against the full name rather than the bare type name. /// @@ -87,25 +91,29 @@ public record NameFilterModel(string Pattern, bool Exclude) { /// InNamespaceOf means and what people expect of a namespace filter. /// /// True for the NotIn forms. -public record NamespaceFilterModel(string Namespace, bool Exact, bool Exclude) { - +public record NamespaceFilterModel(string Namespace, bool Exact, bool Exclude) +{ /// /// Whether a type's namespace falls inside this filter, ignoring whether it includes or /// excludes. /// - public bool Covers(string? candidateNamespace) { + public bool Covers(string? candidateNamespace) + { var value = candidateNamespace ?? ""; - if (Exact) { + if (Exact) + { return string.Equals(value, Namespace, StringComparison.Ordinal); } // A prefix match has to stop at a namespace separator, or "MyApp.Order" would swallow // "MyApp.Ordering". - return value.Equals(Namespace, StringComparison.Ordinal) || - (value.Length > Namespace.Length && - value[Namespace.Length] == '.' && - value.StartsWith(Namespace, StringComparison.Ordinal)); + return value.Equals(Namespace, StringComparison.Ordinal) + || ( + value.Length > Namespace.Length + && value[Namespace.Length] == '.' + && value.StartsWith(Namespace, StringComparison.Ordinal) + ); } } @@ -175,8 +183,9 @@ public record ConventionModel( IReadOnlyList? NameFilters = null, ITypeDefinition? ExplicitServiceType = null, string? AssemblyName = null, - IReadOnlyList? Conditions = null) { - + IReadOnlyList? Conditions = null +) +{ /// /// Whether the attributes a candidate carries pass the filters. /// @@ -184,15 +193,20 @@ public record ConventionModel( /// Requirements combine with and: a type has to carry every attribute asked for and none /// of the excluded ones. Alternatives would need an or, which no Scrutor overload offers either. /// - public bool AttributesMatch(IReadOnlyList? candidateAttributes) { - if (AttributeFilters == null) { + public bool AttributesMatch(IReadOnlyList? candidateAttributes) + { + if (AttributeFilters == null) + { return true; } - foreach (var filter in AttributeFilters) { - var carried = candidateAttributes != null && candidateAttributes.Contains(filter.TypeKey); + foreach (var filter in AttributeFilters) + { + var carried = + candidateAttributes != null && candidateAttributes.Contains(filter.TypeKey); - if (carried == filter.Exclude) { + if (carried == filter.Exclude) + { return false; } } @@ -208,17 +222,22 @@ public bool AttributesMatch(IReadOnlyList? candidateAttributes) { /// /// Whether a candidate's namespace passes the filters. /// - public bool NamespaceMatches(string? candidateNamespace) { - if (NamespaceFilters == null) { + public bool NamespaceMatches(string? candidateNamespace) + { + if (NamespaceFilters == null) + { return true; } var included = false; var anyInclusion = false; - foreach (var filter in NamespaceFilters) { - if (filter.Exclude) { - if (filter.Covers(candidateNamespace)) { + foreach (var filter in NamespaceFilters) + { + if (filter.Exclude) + { + if (filter.Covers(candidateNamespace)) + { return false; } @@ -235,26 +254,28 @@ public bool NamespaceMatches(string? candidateNamespace) { // Structural equality over the filter list; a positional record would compare it by reference // and never hit the incremental cache. See ModelEquality. public virtual bool Equals(ConventionModel? other) => - other is not null && - Equals(ServiceType, other.ServiceType) && - DefinitionKey == other.DefinitionKey && - IsOpenGeneric == other.IsOpenGeneric && - Lifestyle == other.Lifestyle && - IncludeBaseClasses == other.IncludeBaseClasses && - Location == other.Location && - RegisterAs == other.RegisterAs && - RegistrationType == other.RegistrationType && - Equals(Key, other.Key) && - ModelEquality.ListEquals(NamespaceFilters, other.NamespaceFilters) && - ModelEquality.ListEquals(KeyNamespaces, other.KeyNamespaces) && - ModelEquality.ListEquals(AttributeFilters, other.AttributeFilters) && - ModelEquality.ListEquals(NameFilters, other.NameFilters) && - Equals(ExplicitServiceType, other.ExplicitServiceType) && - AssemblyName == other.AssemblyName && - ModelEquality.ListEquals(Conditions, other.Conditions); - - public override int GetHashCode() { - unchecked { + other is not null + && Equals(ServiceType, other.ServiceType) + && DefinitionKey == other.DefinitionKey + && IsOpenGeneric == other.IsOpenGeneric + && Lifestyle == other.Lifestyle + && IncludeBaseClasses == other.IncludeBaseClasses + && Location == other.Location + && RegisterAs == other.RegisterAs + && RegistrationType == other.RegistrationType + && Equals(Key, other.Key) + && ModelEquality.ListEquals(NamespaceFilters, other.NamespaceFilters) + && ModelEquality.ListEquals(KeyNamespaces, other.KeyNamespaces) + && ModelEquality.ListEquals(AttributeFilters, other.AttributeFilters) + && ModelEquality.ListEquals(NameFilters, other.NameFilters) + && Equals(ExplicitServiceType, other.ExplicitServiceType) + && AssemblyName == other.AssemblyName + && ModelEquality.ListEquals(Conditions, other.Conditions); + + public override int GetHashCode() + { + unchecked + { var hash = ServiceType?.GetHashCode() ?? 0; hash = hash * 31 + (DefinitionKey?.GetHashCode() ?? 0); hash = hash * 31 + IsOpenGeneric.GetHashCode(); @@ -292,12 +313,12 @@ public record ConventionModuleModel( ITypeDefinition ModuleType, IReadOnlyList Conventions, IReadOnlyList Unreadable, - /// /// Where the declaring type sits, so a diagnostic about the module itself can point at it. /// - LocationModel? Location = null) { - + LocationModel? Location = null +) +{ /// /// The sentinel for a declaration this generator does not own, matching how every other model /// in this codebase signals "nothing to do". @@ -305,20 +326,23 @@ public record ConventionModuleModel( public static readonly ConventionModuleModel Ignore = new( TypeDefinition.Get("", "Ignore"), Array.Empty(), - Array.Empty()); + Array.Empty() + ); public bool IsIgnored => ReferenceEquals(this, Ignore) || ModuleType.Equals(Ignore.ModuleType); // Structural equality over the lists; a positional record would compare them by reference and // never hit the incremental cache. See ModelEquality. public virtual bool Equals(ConventionModuleModel? other) => - other is not null && - ModuleType.Equals(other.ModuleType) && - ModelEquality.ListEquals(Conventions, other.Conventions) && - ModelEquality.ListEquals(Unreadable, other.Unreadable); - - public override int GetHashCode() { - unchecked { + other is not null + && ModuleType.Equals(other.ModuleType) + && ModelEquality.ListEquals(Conventions, other.Conventions) + && ModelEquality.ListEquals(Unreadable, other.Unreadable); + + public override int GetHashCode() + { + unchecked + { var hash = ModuleType.GetHashCode(); hash = hash * 31 + ModelEquality.ListHashCode(Conventions); hash = hash * 31 + ModelEquality.ListHashCode(Unreadable); diff --git a/src/DependencyModules.SourceGenerator/Conventions/Models/EquatableList.cs b/src/DependencyModules.SourceGenerator/Conventions/Models/EquatableList.cs index 7d1ebac..fd77b4f 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Models/EquatableList.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Models/EquatableList.cs @@ -11,10 +11,12 @@ namespace DependencyModules.Conventions.Models; /// is measurable rather than theoretical: the metadata scan re-runs on every keystroke by /// construction, so without this the emission would too. /// -public sealed class EquatableList : IReadOnlyList { +public sealed class EquatableList : IReadOnlyList +{ private readonly IReadOnlyList _items; - public EquatableList(IReadOnlyList items) { + public EquatableList(IReadOnlyList items) + { _items = items; } diff --git a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionCandidateCache.cs b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionCandidateCache.cs index 55b243a..9864b59 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionCandidateCache.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionCandidateCache.cs @@ -1,8 +1,8 @@ using System.Runtime.CompilerServices; using DependencyModules.Conventions.Models; +using DependencyModules.SourceGenerator.Impl.Models; using DependencyModules.SourceGenerator.Impl.Utilities; using Microsoft.CodeAnalysis; -using DependencyModules.SourceGenerator.Impl.Models; namespace DependencyModules.Conventions.Utilities; @@ -24,12 +24,14 @@ namespace DependencyModules.Conventions.Utilities; /// entirely on being complete. /// /// -public static class ConventionCandidateCache { - +public static class ConventionCandidateCache +{ private static readonly ConditionalWeakTable Entries = new(); - private sealed class Entry { - public Entry(long stamp, ConventionCandidateModel model) { + private sealed class Entry + { + public Entry(long stamp, ConventionCandidateModel model) + { Stamp = stamp; Model = model; } @@ -40,11 +42,14 @@ public Entry(long stamp, ConventionCandidateModel model) { } public static ConventionCandidateModel GetOrAdd( - SyntaxTransformContext context, CancellationToken cancellationToken) { - + SyntaxTransformContext context, + CancellationToken cancellationToken + ) + { var stamp = DeclarationStamp.Of(context.SemanticModel.Compilation); - if (Entries.TryGetValue(context.Node, out var entry) && entry.Stamp == stamp) { + if (Entries.TryGetValue(context.Node, out var entry) && entry.Stamp == stamp) + { return entry.Model; } diff --git a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionCandidateUtility.cs b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionCandidateUtility.cs index 2130f15..61a97c5 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionCandidateUtility.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionCandidateUtility.cs @@ -27,8 +27,8 @@ namespace DependencyModules.Conventions.Utilities; /// convention decides whether they count rather than the type graph deciding for it. /// /// -public static class ConventionCandidateUtility { - +public static class ConventionCandidateUtility +{ /// /// Attributes that take a type out of convention matching. /// @@ -42,12 +42,18 @@ public static class ConventionCandidateUtility { /// decorated. One open generic decorator over convention-registered handlers — the ordinary /// MediatR and FluentValidation shape — failed at the composition root because of it. /// - private static readonly string[] ServiceAttributeNames = { - "SingletonService", "SingletonServiceAttribute", - "ScopedService", "ScopedServiceAttribute", - "TransientService", "TransientServiceAttribute", - "CrossWireService", "CrossWireServiceAttribute", - "Decorator", "DecoratorAttribute", + private static readonly string[] ServiceAttributeNames = + { + "SingletonService", + "SingletonServiceAttribute", + "ScopedService", + "ScopedServiceAttribute", + "TransientService", + "TransientServiceAttribute", + "CrossWireService", + "CrossWireServiceAttribute", + "Decorator", + "DecoratorAttribute", }; /// @@ -70,20 +76,26 @@ public static class ConventionCandidateUtility { /// base list there is no interface walk. /// /// - public static bool IsCandidate(SyntaxNode node, CancellationToken cancellationToken) { + public static bool IsCandidate(SyntaxNode node, CancellationToken cancellationToken) + { cancellationToken.ThrowIfCancellationRequested(); - if (node is not ClassDeclarationSyntax and not RecordDeclarationSyntax) { + if (node is not ClassDeclarationSyntax and not RecordDeclarationSyntax) + { return false; } var typeDeclaration = (TypeDeclarationSyntax)node; - foreach (var modifier in typeDeclaration.Modifiers) { - if (modifier.IsKind(SyntaxKind.StaticKeyword) || - modifier.IsKind(SyntaxKind.AbstractKeyword) || - modifier.IsKind(SyntaxKind.PrivateKeyword) || - modifier.IsKind(SyntaxKind.ProtectedKeyword)) { + foreach (var modifier in typeDeclaration.Modifiers) + { + if ( + modifier.IsKind(SyntaxKind.StaticKeyword) + || modifier.IsKind(SyntaxKind.AbstractKeyword) + || modifier.IsKind(SyntaxKind.PrivateKeyword) + || modifier.IsKind(SyntaxKind.ProtectedKeyword) + ) + { return false; } } @@ -99,13 +111,17 @@ public static bool IsCandidate(SyntaxNode node, CancellationToken cancellationTo /// static factory method marked [SingletonService] would otherwise disqualify itself as a /// candidate for reasons that have nothing to do with the class. /// - private static bool HasServiceAttribute(TypeDeclarationSyntax typeDeclaration) { - foreach (var attributeList in typeDeclaration.AttributeLists) { - foreach (var attribute in attributeList.Attributes) { + private static bool HasServiceAttribute(TypeDeclarationSyntax typeDeclaration) + { + foreach (var attributeList in typeDeclaration.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { var name = attribute.Name.ToString(); var simpleName = name.Substring(name.LastIndexOf('.') + 1); - if (Array.IndexOf(ServiceAttributeNames, simpleName) >= 0) { + if (Array.IndexOf(ServiceAttributeNames, simpleName) >= 0) + { return true; } } @@ -115,11 +131,14 @@ private static bool HasServiceAttribute(TypeDeclarationSyntax typeDeclaration) { } public static ConventionCandidateModel GetCandidateModel( - SyntaxTransformContext context, CancellationToken cancellationToken) { - + SyntaxTransformContext context, + CancellationToken cancellationToken + ) + { cancellationToken.ThrowIfCancellationRequested(); - if (context.Node is not TypeDeclarationSyntax typeDeclaration) { + if (context.Node is not TypeDeclarationSyntax typeDeclaration) + { return ConventionCandidateModel.Ignore; } @@ -127,7 +146,8 @@ public static ConventionCandidateModel GetCandidateModel( // semantic question. Binding a symbol for every such type — which is most types in most // projects — was the largest remaining cost of admitting them: measured on 2,000 classes, // the second run after an edit took 40 ms with the symbol and 12 ms without. - if (typeDeclaration.BaseList is not { Types.Count: > 0 }) { + if (typeDeclaration.BaseList is not { Types.Count: > 0 }) + { return new ConventionCandidateModel( typeDeclaration.GetTypeDefinition(), Array.Empty(), @@ -135,18 +155,31 @@ public static ConventionCandidateModel GetCandidateModel( ServiceModelUtility.GetConstructorInfo(context, typeDeclaration, cancellationToken), DeclaresAccessibleConstructor(typeDeclaration), LocationModel.From(typeDeclaration), - EnvironmentConditionUtility.GetConditions(context, typeDeclaration, cancellationToken), - CollectAttributeKeys(context, typeDeclaration, cancellationToken)); + EnvironmentConditionUtility.GetConditions( + context, + typeDeclaration, + cancellationToken + ), + CollectAttributeKeys(context, typeDeclaration, cancellationToken) + ); } - if (context.SemanticModel.GetDeclaredSymbol(typeDeclaration) is not INamedTypeSymbol symbol) { + if (context.SemanticModel.GetDeclaredSymbol(typeDeclaration) is not INamedTypeSymbol symbol) + { return ConventionCandidateModel.Ignore; } var declared = new List(); var viaBaseClass = new List(); - CollectInterfaces(symbol, typeDeclaration, context, declared, viaBaseClass, cancellationToken); + CollectInterfaces( + symbol, + typeDeclaration, + context, + declared, + viaBaseClass, + cancellationToken + ); return new ConventionCandidateModel( ImplementationTypeOf(symbol), @@ -156,7 +189,8 @@ public static ConventionCandidateModel GetCandidateModel( HasAccessibleConstructor(symbol), LocationModel.From(typeDeclaration), EnvironmentConditionUtility.GetConditions(context, typeDeclaration, cancellationToken), - CollectAttributeKeys(context, typeDeclaration, cancellationToken)); + CollectAttributeKeys(context, typeDeclaration, cancellationToken) + ); } /// @@ -169,16 +203,24 @@ public static ConventionCandidateModel GetCandidateModel( /// of admitting every class as a candidate. /// private static IReadOnlyList? CollectAttributeKeys( - SyntaxTransformContext context, TypeDeclarationSyntax typeDeclaration, - CancellationToken cancellationToken) { - + SyntaxTransformContext context, + TypeDeclarationSyntax typeDeclaration, + CancellationToken cancellationToken + ) + { List? keys = null; - foreach (var attributeList in typeDeclaration.AttributeLists) { - foreach (var attribute in attributeList.Attributes) { + foreach (var attributeList in typeDeclaration.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { cancellationToken.ThrowIfCancellationRequested(); - if (ModelExtensions.GetTypeInfo(context.SemanticModel, attribute).Type is not { } type) { + if ( + ModelExtensions.GetTypeInfo(context.SemanticModel, attribute).Type + is not { } type + ) + { continue; } @@ -199,32 +241,41 @@ private static void CollectInterfaces( SyntaxTransformContext context, List declared, List viaBaseClass, - CancellationToken cancellationToken) { - + CancellationToken cancellationToken + ) + { // Deduped on the type definition rather than on the arity key, which is deliberately equal // for every closing of one generic — IHandler and IHandler are distinct services. var seen = new HashSet(); - foreach (var baseTypeSyntax in typeDeclaration.BaseList!.Types) { + foreach (var baseTypeSyntax in typeDeclaration.BaseList!.Types) + { cancellationToken.ThrowIfCancellationRequested(); - if (context.SemanticModel.GetSymbolInfo(baseTypeSyntax.Type).Symbol - is not INamedTypeSymbol baseSymbol) { + if ( + context.SemanticModel.GetSymbolInfo(baseTypeSyntax.Type).Symbol + is not INamedTypeSymbol baseSymbol + ) + { continue; } - if (baseSymbol.TypeKind == TypeKind.Interface) { + if (baseSymbol.TypeKind == TypeKind.Interface) + { // Written on the declaration, plus everything that interface extends. An interface // declaring that it extends another is a deliberate statement of substitutability, // so a convention naming the base interface matches this type by declaration. Add(declared, seen, symbol, baseSymbol, null); - foreach (var inherited in baseSymbol.AllInterfaces) { + foreach (var inherited in baseSymbol.AllInterfaces) + { Add(declared, seen, symbol, inherited, baseSymbol.Name); } } - else if (baseSymbol.TypeKind == TypeKind.Class) { - foreach (var inherited in baseSymbol.AllInterfaces) { + else if (baseSymbol.TypeKind == TypeKind.Class) + { + foreach (var inherited in baseSymbol.AllInterfaces) + { Add(viaBaseClass, seen, symbol, inherited, baseSymbol.Name); } } @@ -236,22 +287,30 @@ private static void Add( HashSet seen, INamedTypeSymbol implementation, INamedTypeSymbol interfaceSymbol, - string? viaTypeName) { - + string? viaTypeName + ) + { var interfaceType = RegistrationFormOf(interfaceSymbol, implementation); - if (interfaceType == null) { + if (interfaceType == null) + { return; } // Deduped across both lists: an interface reached by declaration is a declared match even if // a base class also brings it, and listing it twice would register the same service twice. - if (!seen.Add(interfaceType)) { + if (!seen.Add(interfaceType)) + { return; } - target.Add(new ImplementedInterfaceModel( - interfaceType, ConventionTypeKey.For(interfaceType), viaTypeName)); + target.Add( + new ImplementedInterfaceModel( + interfaceType, + ConventionTypeKey.For(interfaceType), + viaTypeName + ) + ); } /// @@ -266,27 +325,39 @@ private static void Add( /// built. /// private static ITypeDefinition? RegistrationFormOf( - INamedTypeSymbol interfaceSymbol, INamedTypeSymbol implementation) { - + INamedTypeSymbol interfaceSymbol, + INamedTypeSymbol implementation + ) + { var definition = interfaceSymbol.GetTypeDefinition(); - if (!ContainsTypeParameter(interfaceSymbol)) { + if (!ContainsTypeParameter(interfaceSymbol)) + { return definition; } - if (!implementation.IsGenericType) { + if (!implementation.IsGenericType) + { return null; } // Every argument is one of the implementation's own parameters, used once, in order. var arguments = interfaceSymbol.TypeArguments; - if (arguments.Length != implementation.TypeParameters.Length) { + if (arguments.Length != implementation.TypeParameters.Length) + { return null; } - for (var i = 0; i < arguments.Length; i++) { - if (!SymbolEqualityComparer.Default.Equals(arguments[i], implementation.TypeParameters[i])) { + for (var i = 0; i < arguments.Length; i++) + { + if ( + !SymbolEqualityComparer.Default.Equals( + arguments[i], + implementation.TypeParameters[i] + ) + ) + { return null; } } @@ -294,13 +365,17 @@ private static void Add( return OpenFormOf(definition); } - private static bool ContainsTypeParameter(INamedTypeSymbol symbol) { - foreach (var argument in symbol.TypeArguments) { - if (argument is ITypeParameterSymbol) { + private static bool ContainsTypeParameter(INamedTypeSymbol symbol) + { + foreach (var argument in symbol.TypeArguments) + { + if (argument is ITypeParameterSymbol) + { return true; } - if (argument is INamedTypeSymbol nested && ContainsTypeParameter(nested)) { + if (argument is INamedTypeSymbol nested && ContainsTypeParameter(nested)) + { return true; } } @@ -317,9 +392,11 @@ private static ITypeDefinition OpenFormOf(ITypeDefinition definition) => definition.TypeDefinitionEnum, definition.Namespace, definition.Name, - definition.TypeArguments.Select(_ => TypeDefinition.Get("", "")).ToArray()); + definition.TypeArguments.Select(_ => TypeDefinition.Get("", "")).ToArray() + ); - private static ITypeDefinition ImplementationTypeOf(INamedTypeSymbol symbol) { + private static ITypeDefinition ImplementationTypeOf(INamedTypeSymbol symbol) + { var definition = symbol.GetTypeDefinition(); return symbol.IsGenericType ? OpenFormOf(definition) : definition; @@ -341,24 +418,30 @@ private static ITypeDefinition ImplementationTypeOf(INamedTypeSymbol symbol) { /// A type declaring no constructor has the implicit public parameterless one; a primary /// constructor is public; otherwise any constructor not marked private or protected will do. /// - private static bool DeclaresAccessibleConstructor(TypeDeclarationSyntax typeDeclaration) { - if (typeDeclaration.ParameterList is { Parameters.Count: >= 0 }) { + private static bool DeclaresAccessibleConstructor(TypeDeclarationSyntax typeDeclaration) + { + if (typeDeclaration.ParameterList is { Parameters.Count: >= 0 }) + { return true; } var declaredAny = false; - foreach (var constructor in typeDeclaration.Members.OfType()) { - if (constructor.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) { + foreach (var constructor in typeDeclaration.Members.OfType()) + { + if (constructor.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) + { continue; } declaredAny = true; - var hidden = constructor.Modifiers.Any( - m => m.IsKind(SyntaxKind.PrivateKeyword) || m.IsKind(SyntaxKind.ProtectedKeyword)); + var hidden = constructor.Modifiers.Any(m => + m.IsKind(SyntaxKind.PrivateKeyword) || m.IsKind(SyntaxKind.ProtectedKeyword) + ); - if (!hidden) { + if (!hidden) + { return true; } } @@ -366,14 +449,22 @@ private static bool DeclaresAccessibleConstructor(TypeDeclarationSyntax typeDecl return !declaredAny; } - private static bool HasAccessibleConstructor(INamedTypeSymbol symbol) { - if (symbol.InstanceConstructors.Length == 0) { + private static bool HasAccessibleConstructor(INamedTypeSymbol symbol) + { + if (symbol.InstanceConstructors.Length == 0) + { return true; } - foreach (var constructor in symbol.InstanceConstructors) { - if (constructor.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal - or Accessibility.ProtectedOrInternal) { + foreach (var constructor in symbol.InstanceConstructors) + { + if ( + constructor.DeclaredAccessibility + is Accessibility.Public + or Accessibility.Internal + or Accessibility.ProtectedOrInternal + ) + { return true; } } diff --git a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionMatcher.cs b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionMatcher.cs index 4269bbd..3b83325 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionMatcher.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionMatcher.cs @@ -1,9 +1,9 @@ +using System.Text.RegularExpressions; using CSharpAuthor; using DependencyModules.Conventions.Models; using DependencyModules.SourceGenerator.Impl; using DependencyModules.SourceGenerator.Impl.Models; using DependencyModules.SourceGenerator.Impl.Utilities; -using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; namespace DependencyModules.Conventions.Utilities; @@ -18,7 +18,8 @@ namespace DependencyModules.Conventions.Utilities; public record ConventionRegistrationMatch( ConventionModel Convention, ConventionCandidateModel Candidate, - ImplementedInterfaceModel? Interface); + ImplementedInterfaceModel? Interface +); /// /// One registration a match produces, before the ambiguity check has run. @@ -31,7 +32,8 @@ public record ConventionRegistrationMatch( /// internal record PendingRegistration( ConventionRegistrationMatch Match, - ServiceRegistrationModel Registration); + ServiceRegistrationModel Registration +); /// /// Matches a module's conventions against the candidates in the compilation. @@ -41,32 +43,36 @@ internal record PendingRegistration( /// have to be cacheable. Everything it works from is already rendered to strings and /// s, so no symbol is touched here. /// -public static class ConventionMatcher { - +public static class ConventionMatcher +{ public static IReadOnlyList Match( ModuleEntryPointModel entryPointModel, ConventionModuleModel conventionModule, IReadOnlyList candidates, DiagnosticReporter report, - FileLogger logger) { - + FileLogger logger + ) + { var moduleName = entryPointModel.EntryPointType.Name; - foreach (var unreadable in conventionModule.Unreadable) { + foreach (var unreadable in conventionModule.Unreadable) + { logger.Error($"{moduleName}: refused '{unreadable.Text}' — {unreadable.Reason}."); report.Report( DependencyModuleDiagnostics.ConventionCannotBeRead, unreadable.Location, unreadable.Reason, - unreadable.Text); + unreadable.Text + ); } var merged = MergePartialDeclarations(candidates); var matches = new List(); - foreach (var convention in conventionModule.Conventions) { + foreach (var convention in conventionModule.Conventions) + { CollectMatches(convention, merged, moduleName, matches, report, logger); } @@ -76,8 +82,10 @@ public static IReadOnlyList Match( // drag a perfectly good interface registration down with a duplicated self one. var pending = new List(); - foreach (var match in matches) { - foreach (var registration in BuildRegistrations(match, entryPointModel)) { + foreach (var match in matches) + { + foreach (var registration in BuildRegistrations(match, entryPointModel)) + { pending.Add(new PendingRegistration(match, registration)); } } @@ -95,18 +103,23 @@ private static void CollectMatches( string moduleName, List matches, DiagnosticReporter report, - FileLogger logger) { - + FileLogger logger + ) + { var serviceName = convention.DisplayName; - if (convention.Lifestyle == null) { - logger.Error($"{moduleName}: the convention registering '{serviceName}' declared no lifetime."); + if (convention.Lifestyle == null) + { + logger.Error( + $"{moduleName}: the convention registering '{serviceName}' declared no lifetime." + ); report.Report( DependencyModuleDiagnostics.ConventionCannotBeRead, convention.Location, "no lifetime was declared; call AsSingleton(), AsScoped() or AsTransient()", - $"RegisterAll({serviceName})"); + $"RegisterAll({serviceName})" + ); return; } @@ -117,20 +130,26 @@ private static void CollectMatches( // the model keeps patterns as strings. var nameFilters = CompileNameFilters(convention); - foreach (var candidate in candidates) { - if (candidate.IsIgnored) { + foreach (var candidate in candidates) + { + if (candidate.IsIgnored) + { continue; } // One source or the other. A scan of the project being built must not pick up a type // from a package, and a scan of a package must not pick up a local one. - if (candidate.AssemblyName != convention.AssemblyName) { + if (candidate.AssemblyName != convention.AssemblyName) + { continue; } - if (!convention.NamespaceMatches(candidate.ImplementationType.Namespace) || - !convention.AttributesMatch(candidate.AttributeTypeKeys) || - !NameMatches(nameFilters, candidate.ImplementationType)) { + if ( + !convention.NamespaceMatches(candidate.ImplementationType.Namespace) + || !convention.AttributesMatch(candidate.AttributeTypeKeys) + || !NameMatches(nameFilters, candidate.ImplementationType) + ) + { continue; } @@ -138,21 +157,25 @@ private static void CollectMatches( // as itself — one match, no interface. List matched; - if (convention.ServiceType == null) { + if (convention.ServiceType == null) + { matched = new List { null }; } - else { + else + { var reachable = AllMatchingInterfaces(convention, candidate); - if (reachable.Count == 0) { + if (reachable.Count == 0) + { continue; } // AsSelf and AsSelfWithInterfaces name the implementation, so several matching // closings still produce one registration rather than one per closing. The default // and AlsoAsSelf register each matched closing. - matched = convention.RegisterAs is ConventionRegisterAs.Interfaces - or ConventionRegisterAs.AlsoSelf + matched = convention.RegisterAs + is ConventionRegisterAs.Interfaces + or ConventionRegisterAs.AlsoSelf ? reachable.Cast().ToList() : new List { reachable[0] }; } @@ -161,35 +184,47 @@ or ConventionRegisterAs.AlsoSelf // Reported rather than registered: a registration the container cannot construct throws // when the provider is built, a long way from the convention responsible. - if (!candidate.HasAccessibleConstructor) { + if (!candidate.HasAccessibleConstructor) + { logger.Error( - $"{moduleName}: '{candidate.ImplementationType.Name}' matched '{serviceName}' " + - "but has no accessible constructor."); + $"{moduleName}: '{candidate.ImplementationType.Name}' matched '{serviceName}' " + + "but has no accessible constructor." + ); report.Report( DependencyModuleDiagnostics.ConventionMatchNotConstructable, - candidate.Location == LocationModel.None ? convention.Location : candidate.Location, + candidate.Location == LocationModel.None + ? convention.Location + : candidate.Location, candidate.ImplementationType.Name, serviceName, - moduleName); + moduleName + ); continue; } - foreach (var candidateInterface in matched) { - matches.Add(new ConventionRegistrationMatch(convention, candidate, candidateInterface)); + foreach (var candidateInterface in matched) + { + matches.Add( + new ConventionRegistrationMatch(convention, candidate, candidateInterface) + ); } } - if (found == 0) { - logger.Info($"{moduleName}: the convention registering '{serviceName}' matched nothing."); + if (found == 0) + { + logger.Info( + $"{moduleName}: the convention registering '{serviceName}' matched nothing." + ); report.Report( DependencyModuleDiagnostics.ConventionMatchedNothing, convention.Location, serviceName, moduleName, - AdviceForEmptyMatch(convention, serviceName)); + AdviceForEmptyMatch(convention, serviceName) + ); } } @@ -202,24 +237,24 @@ or ConventionRegisterAs.AlsoSelf /// the shape every MVVM project starts from — class FooViewModel : ViewModelBase — so /// saying "call IncludeBaseClasses()" there sent readers looking for a mistake they had not made. /// - private static string AdviceForEmptyMatch(ConventionModel convention, string serviceName) { - if (convention.ServiceType is { TypeDefinitionEnum: TypeDefinitionEnum.ClassDefinition }) { - return - $"Conventions match the interfaces a type declares, and '{serviceName}' is a class, " + - "so no type can match it. Register an interface the types declare — a marker " + - "interface on the base class is enough — or register them by attribute instead"; + private static string AdviceForEmptyMatch(ConventionModel convention, string serviceName) + { + if (convention.ServiceType is { TypeDefinitionEnum: TypeDefinitionEnum.ClassDefinition }) + { + return $"Conventions match the interfaces a type declares, and '{serviceName}' is a class, " + + "so no type can match it. Register an interface the types declare — a marker " + + "interface on the base class is enough — or register them by attribute instead"; } - if (convention.IncludeBaseClasses) { - return - "Conventions match a type that declares the service type, or declares an interface " + - "extending it. IncludeBaseClasses() is already applied, so check the name, namespace " + - "and assembly filters on this convention"; + if (convention.IncludeBaseClasses) + { + return "Conventions match a type that declares the service type, or declares an interface " + + "extending it. IncludeBaseClasses() is already applied, so check the name, namespace " + + "and assembly filters on this convention"; } - return - "Conventions match a type that declares the service type, or declares an interface " + - "extending it; call IncludeBaseClasses() to also match types that reach it through a base class"; + return "Conventions match a type that declares the service type, or declares an interface " + + "extending it; call IncludeBaseClasses() to also match types that reach it through a base class"; } /// @@ -230,31 +265,41 @@ private static string AdviceForEmptyMatch(ConventionModel convention, string ser /// one. Everything else is escaped, so a pattern cannot smuggle in a regex. /// private static List<(Regex Pattern, bool Qualified, bool Exclude)>? CompileNameFilters( - ConventionModel convention) { - - if (convention.NameFilters == null) { + ConventionModel convention + ) + { + if (convention.NameFilters == null) + { return null; } var compiled = new List<(Regex, bool, bool)>(convention.NameFilters.Count); - foreach (var filter in convention.NameFilters) { - var expression = "^" + - Regex.Escape(filter.Pattern).Replace("\\*", ".*").Replace("\\?", ".") + - "$"; + foreach (var filter in convention.NameFilters) + { + var expression = + "^" + Regex.Escape(filter.Pattern).Replace("\\*", ".*").Replace("\\?", ".") + "$"; // Ordinal and case-sensitive, consistent with C# identifiers. - compiled.Add((new Regex(expression, RegexOptions.CultureInvariant), filter.IsQualified, - filter.Exclude)); + compiled.Add( + ( + new Regex(expression, RegexOptions.CultureInvariant), + filter.IsQualified, + filter.Exclude + ) + ); } return compiled; } private static bool NameMatches( - List<(Regex Pattern, bool Qualified, bool Exclude)>? filters, ITypeDefinition implementation) { - - if (filters == null) { + List<(Regex Pattern, bool Qualified, bool Exclude)>? filters, + ITypeDefinition implementation + ) + { + if (filters == null) + { return true; } @@ -267,11 +312,14 @@ private static bool NameMatches( var included = false; var anyInclusion = false; - foreach (var (pattern, qualified, exclude) in filters) { + foreach (var (pattern, qualified, exclude) in filters) + { var subject = qualified ? qualifiedName : bareName; - if (exclude) { - if (pattern.IsMatch(subject)) { + if (exclude) + { + if (pattern.IsMatch(subject)) + { return false; } @@ -296,11 +344,16 @@ private static bool NameMatches( /// implementation appearing twice. /// private static List AllMatchingInterfaces( - ConventionModel convention, ConventionCandidateModel candidate) { - + ConventionModel convention, + ConventionCandidateModel candidate + ) + { var matched = new List(); - foreach (var candidateInterface in candidate.InterfacesInReach(convention.IncludeBaseClasses)) { + foreach ( + var candidateInterface in candidate.InterfacesInReach(convention.IncludeBaseClasses) + ) + { // An open convention matches any closing, and the candidate is registered against the // construction it actually implements. A closed one has to match exactly, or // RegisterAll>() would pick up every other closing as well. @@ -308,7 +361,8 @@ private static List AllMatchingInterfaces( ? convention.DefinitionKey == candidateInterface.DefinitionKey : convention.ServiceType!.Equals(candidateInterface.InterfaceType); - if (isMatch) { + if (isMatch) + { matched.Add(candidateInterface); } } @@ -335,32 +389,38 @@ private static List AllMatchingInterfaces( /// /// private static IReadOnlyList MergePartialDeclarations( - IReadOnlyList candidates) { - + IReadOnlyList candidates + ) + { var byType = new Dictionary>(); var order = new List(); var anyPartial = false; - foreach (var candidate in candidates) { - if (!byType.TryGetValue(candidate.ImplementationType, out var parts)) { + foreach (var candidate in candidates) + { + if (!byType.TryGetValue(candidate.ImplementationType, out var parts)) + { parts = new List(); byType[candidate.ImplementationType] = parts; order.Add(candidate.ImplementationType); } - else { + else + { anyPartial = true; } parts.Add(candidate); } - if (!anyPartial) { + if (!anyPartial) + { return candidates; } var merged = new List(order.Count); - foreach (var implementationType in order) { + foreach (var implementationType in order) + { var parts = byType[implementationType]; merged.Add(parts.Count == 1 ? parts[0] : MergeParts(parts)); @@ -369,7 +429,8 @@ private static IReadOnlyList MergePartialDeclarations( return merged; } - private static ConventionCandidateModel MergeParts(List parts) { + private static ConventionCandidateModel MergeParts(List parts) + { var declared = new List(); var viaBaseClass = new List(); var seen = new HashSet(); @@ -377,17 +438,23 @@ private static ConventionCandidateModel MergeParts(List(); // Attributes on partial parts combine, so the conditions do too. - foreach (var part in parts) { - if (part.Conditions != null) { + foreach (var part in parts) + { + if (part.Conditions != null) + { conditions.AddRange(part.Conditions); } } @@ -405,17 +474,22 @@ private static ConventionCandidateModel MergeParts(List(); // Attributes on partial parts combine, so the keys do too. - foreach (var part in parts) { - if (part.AttributeTypeKeys != null) { - foreach (var key in part.AttributeTypeKeys) { - if (!attributeKeys.Contains(key)) { + foreach (var part in parts) + { + if (part.AttributeTypeKeys != null) + { + foreach (var key in part.AttributeTypeKeys) + { + if (!attributeKeys.Contains(key)) + { attributeKeys.Add(key); } } } } - return parts[0] with { + return parts[0] with + { AttributeTypeKeys = attributeKeys.Count > 0 ? attributeKeys : null, DeclaredInterfaces = declared, BaseClassInterfaces = viaBaseClass, @@ -427,15 +501,19 @@ private static ConventionCandidateModel MergeParts(List parts) { + private static ConstructorInfoModel? GreediestConstructor(List parts) + { ConstructorInfoModel? greediest = null; - foreach (var part in parts) { - if (part.Constructor == null) { + foreach (var part in parts) + { + if (part.Constructor == null) + { continue; } - if (greediest == null || part.Constructor.Parameters.Count > greediest.Parameters.Count) { + if (greediest == null || part.Constructor.Parameters.Count > greediest.Parameters.Count) + { greediest = part.Constructor; } } @@ -454,21 +532,26 @@ private static List RemoveAmbiguous( List pending, string moduleName, DiagnosticReporter report, - FileLogger logger) { - + FileLogger logger + ) + { // Keyed on what actually reaches the container: this implementation, under this service // type. A type filling two roles registers twice; one service type claimed twice is the // ambiguity. var byRegistration = - new Dictionary<(ITypeDefinition Implementation, ITypeDefinition Service), - List>(); + new Dictionary< + (ITypeDefinition Implementation, ITypeDefinition Service), + List + >(); var order = new List<(ITypeDefinition Implementation, ITypeDefinition Service)>(); - foreach (var entry in pending) { + foreach (var entry in pending) + { var key = (entry.Match.Candidate.ImplementationType, entry.Registration.ServiceType); - if (!byRegistration.TryGetValue(key, out var list)) { + if (!byRegistration.TryGetValue(key, out var list)) + { list = new List(); byRegistration[key] = list; order.Add(key); @@ -481,10 +564,12 @@ private static List RemoveAmbiguous( // Insertion order rather than dictionary order: the emitted registration order feeds the // module snapshots, and a hash order would move them for unrelated reasons. - foreach (var key in order) { + foreach (var key in order) + { var group = byRegistration[key]; - if (group.Count == 1) { + if (group.Count == 1) + { usable.Add(group[0]); continue; @@ -494,14 +579,16 @@ private static List RemoveAmbiguous( var second = group[1]; var serviceName = key.Service.Name; - var difference = first.Match.Convention.Lifestyle == second.Match.Convention.Lifestyle - ? "The declaration is duplicated." - : $"They declare different lifetimes ({first.Match.Convention.Lifestyle} and " + - $"{second.Match.Convention.Lifestyle})."; + var difference = + first.Match.Convention.Lifestyle == second.Match.Convention.Lifestyle + ? "The declaration is duplicated." + : $"They declare different lifetimes ({first.Match.Convention.Lifestyle} and " + + $"{second.Match.Convention.Lifestyle})."; logger.Error( - $"{moduleName}: '{key.Implementation.Name}' is registered as " + - $"'{serviceName}' by two conventions. {difference}"); + $"{moduleName}: '{key.Implementation.Name}' is registered as " + + $"'{serviceName}' by two conventions. {difference}" + ); report.Report( DependencyModuleDiagnostics.AmbiguousConventionMatch, @@ -509,7 +596,8 @@ private static List RemoveAmbiguous( key.Implementation.Name, moduleName, serviceName, - difference); + difference + ); } return usable; @@ -533,16 +621,25 @@ private static List RemoveAmbiguous( /// /// private static (ITypeDefinition Implementation, ITypeDefinition Service) RegistrationKey( - ConventionRegistrationMatch match) { - + ConventionRegistrationMatch match + ) + { var implementation = match.Candidate.ImplementationType; - return match.Convention.RegisterAs switch { - ConventionRegisterAs.Explicit => (implementation, match.Convention.ExplicitServiceType!), - ConventionRegisterAs.MatchingInterface => - (implementation, MatchingInterfaceOf(match) ?? implementation), - ConventionRegisterAs.Interfaces when match.Interface != null => - (implementation, match.Interface.InterfaceType), + return match.Convention.RegisterAs switch + { + ConventionRegisterAs.Explicit => ( + implementation, + match.Convention.ExplicitServiceType! + ), + ConventionRegisterAs.MatchingInterface => ( + implementation, + MatchingInterfaceOf(match) ?? implementation + ), + ConventionRegisterAs.Interfaces when match.Interface != null => ( + implementation, + match.Interface.InterfaceType + ), _ => (implementation, implementation), }; } @@ -585,22 +682,38 @@ private static LocationModel LocationOf(ConventionRegistrationMatch match) => /// so that line does not hold the way this one does. /// /// - private static bool IsFrameworkInterface(ITypeDefinition interfaceType) { + private static bool IsFrameworkInterface(ITypeDefinition interfaceType) + { var namespaceName = interfaceType.Namespace; - return namespaceName == "System" || - (namespaceName != null && namespaceName.StartsWith("System.", StringComparison.Ordinal)); + return namespaceName == "System" + || ( + namespaceName != null + && namespaceName.StartsWith("System.", StringComparison.Ordinal) + ); } /// /// The interface named after the implementation — Foo as IFoo. /// - private static ITypeDefinition? MatchingInterfaceOf(ConventionRegistrationMatch match) { + private static ITypeDefinition? MatchingInterfaceOf(ConventionRegistrationMatch match) + { var wanted = "I" + match.Candidate.ImplementationType.Name; - foreach (var candidateInterface in - match.Candidate.InterfacesInReach(match.Convention.IncludeBaseClasses)) { - if (string.Equals(candidateInterface.InterfaceType.Name, wanted, StringComparison.Ordinal)) { + foreach ( + var candidateInterface in match.Candidate.InterfacesInReach( + match.Convention.IncludeBaseClasses + ) + ) + { + if ( + string.Equals( + candidateInterface.InterfaceType.Name, + wanted, + StringComparison.Ordinal + ) + ) + { return candidateInterface.InterfaceType; } } @@ -616,23 +729,29 @@ private static string ServiceTypeNameOf(ConventionRegistrationMatch match) => /// match was not direct. /// private static void ReportExposure( - IReadOnlyList usable, string moduleName, DiagnosticReporter report) { - - foreach (var entry in usable) { + IReadOnlyList usable, + string moduleName, + DiagnosticReporter report + ) + { + foreach (var entry in usable) + { var serviceType = entry.Registration.ServiceType; // The interface a convention matched through explains a match that was not direct, and // only applies when this registration is that interface. - var via = entry.Match.Interface != null && - entry.Match.Interface.InterfaceType.Equals(serviceType) && - entry.Match.Interface.ViaTypeName != null - ? $" (via {entry.Match.Interface.ViaTypeName})" - : ""; + var via = + entry.Match.Interface != null + && entry.Match.Interface.InterfaceType.Equals(serviceType) + && entry.Match.Interface.ViaTypeName != null + ? $" (via {entry.Match.Interface.ViaTypeName})" + : ""; report.Report( DependencyModuleDiagnostics.ExposedByConvention, LocationOf(entry.Match), - $"{serviceType.Name} in {moduleName}{via}"); + $"{serviceType.Name} in {moduleName}{via}" + ); } } @@ -640,8 +759,10 @@ private static void ReportExposure( /// Produces the same models the attribute path produces, so emission needs no special case. /// private static IReadOnlyList BuildServiceModels( - IReadOnlyList usable, FileLogger logger) { - + IReadOnlyList usable, + FileLogger logger + ) + { // One model per implementation, carrying every registration it produces — the shape // ServiceModelUtility builds for the attribute path. Two models with the same // ImplementationType would duplicate the per-implementation state the writer reads: @@ -653,11 +774,13 @@ private static IReadOnlyList BuildServiceModels( var byGroup = new Dictionary>(); var order = new List(); - foreach (var entry in usable) { + foreach (var entry in usable) + { var conditions = MergeConditions(entry.Match); var key = GroupKey(entry.Match.Candidate.ImplementationType, conditions); - if (!byGroup.TryGetValue(key, out var list)) { + if (!byGroup.TryGetValue(key, out var list)) + { list = new List(); byGroup[key] = list; order.Add(new ConventionModelGroup(entry.Match, conditions, key)); @@ -668,27 +791,32 @@ private static IReadOnlyList BuildServiceModels( var models = new List(order.Count); - foreach (var group in order) { + foreach (var group in order) + { var registrations = byGroup[group.Key]; logger.Info( - $" {group.Match.Candidate.ImplementationType.Name} -> " + - $"{string.Join(", ", registrations.Select(r => r.ServiceType.Name))} " + - "(by convention)"); - - models.Add(new ServiceModel( - group.Match.Candidate.ImplementationType, - group.Match.Candidate.Constructor, - null, - null, - registrations, - // Carried across for the same reason the attribute path sets it: interception picks - // its registration out by asking each descriptor what implementation it was built - // from, and a factory descriptor cannot say. A convention-registered class carrying - // [Intercept] needs the exemption as much as an attribute-registered one, and - // reaches this writer by a different route. - InterceptionFeature(group.Match.Candidate), - group.Conditions)); + $" {group.Match.Candidate.ImplementationType.Name} -> " + + $"{string.Join(", ", registrations.Select(r => r.ServiceType.Name))} " + + "(by convention)" + ); + + models.Add( + new ServiceModel( + group.Match.Candidate.ImplementationType, + group.Match.Candidate.Constructor, + null, + null, + registrations, + // Carried across for the same reason the attribute path sets it: interception picks + // its registration out by asking each descriptor what implementation it was built + // from, and a factory descriptor cannot say. A convention-registered class carrying + // [Intercept] needs the exemption as much as an attribute-registered one, and + // reaches this writer by a different route. + InterceptionFeature(group.Match.Candidate), + group.Conditions + ) + ); } return models; @@ -707,8 +835,9 @@ private static RegistrationFeature InterceptionFeature(ConventionCandidateModel ? RegistrationFeature.Intercepted : RegistrationFeature.None; - private static readonly string InterceptAttributeKey = - ConventionTypeKey.For(KnownTypes.DependencyModules.Attributes.InterceptAttribute); + private static readonly string InterceptAttributeKey = ConventionTypeKey.For( + KnownTypes.DependencyModules.Attributes.InterceptAttribute + ); /// /// One ServiceModel's worth of matches: the same implementation under the same conditions. @@ -716,7 +845,8 @@ private static RegistrationFeature InterceptionFeature(ConventionCandidateModel private record ConventionModelGroup( ConventionRegistrationMatch Match, IReadOnlyList? Conditions, - string Key); + string Key + ); /// /// The conditions in force for one match: the convention's and the class's, combined. @@ -727,20 +857,25 @@ private record ConventionModelGroup( /// written in the other, which is the kind of thing nobody finds until production. /// private static IReadOnlyList? MergeConditions( - ConventionRegistrationMatch match) { - + ConventionRegistrationMatch match + ) + { var fromConvention = match.Convention.Conditions; var fromCandidate = match.Candidate.Conditions; - if ((fromConvention?.Count ?? 0) == 0) { + if ((fromConvention?.Count ?? 0) == 0) + { return fromCandidate; } - if ((fromCandidate?.Count ?? 0) == 0) { + if ((fromCandidate?.Count ?? 0) == 0) + { return fromConvention; } - var merged = new List(fromConvention!.Count + fromCandidate!.Count); + var merged = new List( + fromConvention!.Count + fromCandidate!.Count + ); merged.AddRange(fromConvention); merged.AddRange(fromCandidate); @@ -751,14 +886,18 @@ private record ConventionModelGroup( /// A stable key for grouping, so equal condition sets share a model and different ones do not. /// private static string GroupKey( - ITypeDefinition implementation, IReadOnlyList? conditions) { - - if ((conditions?.Count ?? 0) == 0) { + ITypeDefinition implementation, + IReadOnlyList? conditions + ) + { + if ((conditions?.Count ?? 0) == 0) + { return implementation.ToString(); } var parts = conditions!.Select(condition => - $"{condition.Kind}|{condition.Negate}|{condition.Key}|{string.Join(",", condition.Values)}"); + $"{condition.Kind}|{condition.Negate}|{condition.Key}|{string.Join(",", condition.Values)}" + ); return implementation + "::" + string.Join(";", parts); } @@ -774,8 +913,10 @@ private static string GroupKey( /// instance per service type instead of the shared instance the contract promises. /// private static IReadOnlyList BuildRegistrations( - ConventionRegistrationMatch match, ModuleEntryPointModel entryPointModel) { - + ConventionRegistrationMatch match, + ModuleEntryPointModel entryPointModel + ) + { var convention = match.Convention; var lifestyle = convention.Lifestyle!.Value; @@ -784,16 +925,22 @@ private static IReadOnlyList BuildRegistrations( // module declares one. var realm = entryPointModel.EntryPointType; - ServiceRegistrationModel Registration(ITypeDefinition serviceType, bool crossWire = false) => - new(serviceType, + ServiceRegistrationModel Registration( + ITypeDefinition serviceType, + bool crossWire = false + ) => + new( + serviceType, lifestyle, convention.RegistrationType, realm, convention.Key, crossWire, - convention.KeyNamespaces); + convention.KeyNamespaces + ); - switch (convention.RegisterAs) { + switch (convention.RegisterAs) + { case ConventionRegisterAs.Self: return new[] { Registration(match.Candidate.ImplementationType) }; @@ -821,9 +968,14 @@ ServiceRegistrationModel Registration(ITypeDefinition serviceType, bool crossWir case ConventionRegisterAs.SelfAndInterfaces: var crossWired = new List(); - foreach (var reachable in - match.Candidate.InterfacesInReach(convention.IncludeBaseClasses)) { - if (IsFrameworkInterface(reachable.InterfaceType)) { + foreach ( + var reachable in match.Candidate.InterfacesInReach( + convention.IncludeBaseClasses + ) + ) + { + if (IsFrameworkInterface(reachable.InterfaceType)) + { continue; } @@ -832,7 +984,8 @@ ServiceRegistrationModel Registration(ITypeDefinition serviceType, bool crossWir // Nothing left to expand to still registers the type itself, so filtering everything // away degrades to AsSelf() rather than to nothing. - if (crossWired.Count == 0) { + if (crossWired.Count == 0) + { crossWired.Add(Registration(match.Candidate.ImplementationType)); } diff --git a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionModelUtility.cs b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionModelUtility.cs index 7a32e00..0b9dfdd 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionModelUtility.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionModelUtility.cs @@ -26,8 +26,8 @@ namespace DependencyModules.Conventions.Utilities; /// breaks the cache. /// /// -public static class ConventionModelUtility { - +public static class ConventionModelUtility +{ private const string RegisterAll = "RegisterAll"; private const string IncludeBaseClasses = "IncludeBaseClasses"; private const string UsingCall = "Using"; @@ -47,20 +47,26 @@ public static class ConventionModelUtility { /// Named after the attributes rather than something fluent-sounding like OnlyIn, so the /// two ways of saying the same thing read the same and one is discoverable from the other. /// - private static readonly Dictionary ConditionCalls = new() { + private static readonly Dictionary< + string, + (EnvironmentConditionKind Kind, bool Negate) + > ConditionCalls = new() + { ["IfEnvironment"] = (EnvironmentConditionKind.Name, false), ["IfNotEnvironment"] = (EnvironmentConditionKind.Name, true), ["IfEnvironmentValue"] = (EnvironmentConditionKind.Value, false), ["IfNotEnvironmentValue"] = (EnvironmentConditionKind.Value, true), }; - private static readonly Dictionary LifetimeCalls = new() { + private static readonly Dictionary LifetimeCalls = new() + { ["AsSingleton"] = ServiceLifestyle.Singleton, ["AsScoped"] = ServiceLifestyle.Scoped, ["AsTransient"] = ServiceLifestyle.Transient, }; - private static readonly Dictionary RegisterAsCalls = new() { + private static readonly Dictionary RegisterAsCalls = new() + { ["AsSelf"] = ConventionRegisterAs.Self, ["AsSelfWithInterfaces"] = ConventionRegisterAs.SelfAndInterfaces, ["AlsoAsSelf"] = ConventionRegisterAs.AlsoSelf, @@ -69,7 +75,8 @@ public static class ConventionModelUtility { /// /// The namespace filter calls, and the shape of filter each produces. /// - private static readonly Dictionary NamespaceCalls = new() { + private static readonly Dictionary NamespaceCalls = new() + { ["InNamespaceOf"] = (false, false), ["InNamespaces"] = (false, false), ["InExactNamespaces"] = (true, false), @@ -86,15 +93,22 @@ public static class ConventionModelUtility { /// the semantic model; the transform confirms it is the right IConventionModule before /// reading anything. /// - public static bool IsConventionModuleCandidate(SyntaxNode node, CancellationToken cancellationToken) { + public static bool IsConventionModuleCandidate( + SyntaxNode node, + CancellationToken cancellationToken + ) + { cancellationToken.ThrowIfCancellationRequested(); - if (node is not TypeDeclarationSyntax { BaseList: not null } typeDeclaration) { + if (node is not TypeDeclarationSyntax { BaseList: not null } typeDeclaration) + { return false; } - foreach (var baseType in typeDeclaration.BaseList.Types) { - if (SimpleNameOf(baseType.Type) == ConventionContractSource.ConventionModule) { + foreach (var baseType in typeDeclaration.BaseList.Types) + { + if (SimpleNameOf(baseType.Type) == ConventionContractSource.ConventionModule) + { return true; } } @@ -103,51 +117,65 @@ public static bool IsConventionModuleCandidate(SyntaxNode node, CancellationToke } public static ConventionModuleModel GetConventionModuleModel( - SyntaxTransformContext context, CancellationToken cancellationToken) { - + SyntaxTransformContext context, + CancellationToken cancellationToken + ) + { cancellationToken.ThrowIfCancellationRequested(); - if (context.Node is not TypeDeclarationSyntax typeDeclaration) { + if (context.Node is not TypeDeclarationSyntax typeDeclaration) + { return ConventionModuleModel.Ignore; } - if (!ImplementsConventionModule(context, typeDeclaration)) { + if (!ImplementsConventionModule(context, typeDeclaration)) + { return ConventionModuleModel.Ignore; } var method = FindConventionsMethod(typeDeclaration); - if (method?.Body == null) { + if (method?.Body == null) + { // An expression-bodied or abstract declaration has nothing to read. Reported rather than // ignored: the module said it had conventions and produced none. return new ConventionModuleModel( typeDeclaration.GetTypeDefinition(), Array.Empty(), - new[] { + new[] + { new UnreadableStatementModel( method?.ToString() ?? ConventionContractSource.ConventionMethod, "the Conventions method needs a statement body containing RegisterAll calls", - LocationModel.From((SyntaxNode?)method ?? typeDeclaration)) - }); + LocationModel.From((SyntaxNode?)method ?? typeDeclaration) + ), + } + ); } var parameterName = method.ParameterList.Parameters.FirstOrDefault()?.Identifier.Text; - if (string.IsNullOrEmpty(parameterName)) { + if (string.IsNullOrEmpty(parameterName)) + { return ConventionModuleModel.Ignore; } var conventions = new List(); var unreadable = new List(); - foreach (var statement in method.Body.Statements) { + foreach (var statement in method.Body.Statements) + { cancellationToken.ThrowIfCancellationRequested(); ReadStatement(context, statement, parameterName!, conventions, unreadable); } return new ConventionModuleModel( - typeDeclaration.GetTypeDefinition(), conventions, unreadable, LocationModel.From(typeDeclaration)); + typeDeclaration.GetTypeDefinition(), + conventions, + unreadable, + LocationModel.From(typeDeclaration) + ); } private static void ReadStatement( @@ -155,30 +183,38 @@ private static void ReadStatement( StatementSyntax statement, string parameterName, List conventions, - List unreadable) { - - if (statement is not ExpressionStatementSyntax { Expression: InvocationExpressionSyntax invocation }) { - unreadable.Add(Refuse( - statement, - "only RegisterAll chains can appear here, because this body is read at compile time " + - "rather than executed")); + List unreadable + ) + { + if ( + statement + is not ExpressionStatementSyntax { Expression: InvocationExpressionSyntax invocation } + ) + { + unreadable.Add( + Refuse( + statement, + "only RegisterAll chains can appear here, because this body is read at compile time " + + "rather than executed" + ) + ); return; } var chain = UnwrapChain(invocation, parameterName); - if (chain == null) { - unreadable.Add(Refuse( - statement, - $"expected a chain of calls on '{parameterName}'")); + if (chain == null) + { + unreadable.Add(Refuse(statement, $"expected a chain of calls on '{parameterName}'")); return; } var convention = BuildConvention(context, statement, chain, out var reason); - if (convention == null) { + if (convention == null) + { unreadable.Add(Refuse(statement, reason!)); return; @@ -197,13 +233,17 @@ private static void ReadStatement( /// a helper call or an unrelated statement being read as a convention. /// private static List? UnwrapChain( - InvocationExpressionSyntax invocation, string parameterName) { - + InvocationExpressionSyntax invocation, + string parameterName + ) + { var calls = new List(); ExpressionSyntax current = invocation; - while (current is InvocationExpressionSyntax candidate) { - if (candidate.Expression is not MemberAccessExpressionSyntax access) { + while (current is InvocationExpressionSyntax candidate) + { + if (candidate.Expression is not MemberAccessExpressionSyntax access) + { return null; } @@ -211,8 +251,11 @@ private static void ReadStatement( current = access.Expression; } - if (current is not IdentifierNameSyntax identifier || - identifier.Identifier.Text != parameterName) { + if ( + current is not IdentifierNameSyntax identifier + || identifier.Identifier.Text != parameterName + ) + { return null; } @@ -225,14 +268,16 @@ private static void ReadStatement( SyntaxTransformContext context, StatementSyntax statement, List chain, - out string? reason) { - + out string? reason + ) + { reason = null; var head = chain[0]; var headName = MethodNameOf(head); - if (headName != RegisterAll) { + if (headName != RegisterAll) + { reason = $"a convention has to start with {RegisterAll}, not '{headName}'"; return null; @@ -241,19 +286,21 @@ private static void ReadStatement( // No type argument and no argument at all is the filter-selected form, which is valid and // has no service type. Anything else that fails to resolve is a mistake. var selectsByFilter = - head.Expression is MemberAccessExpressionSyntax { Name: not GenericNameSyntax } && - head.ArgumentList.Arguments.Count == 0; + head.Expression is MemberAccessExpressionSyntax { Name: not GenericNameSyntax } + && head.ArgumentList.Arguments.Count == 0; ITypeDefinition? serviceType = null; var isOpenGeneric = false; - if (!selectsByFilter) { + if (!selectsByFilter) + { serviceType = ReadServiceType(context, head, out isOpenGeneric); - if (serviceType == null) { + if (serviceType == null) + { reason = - $"could not resolve the service type; write {RegisterAll}(), " + - $"{RegisterAll}(typeof(IService<>)) or {RegisterAll}() with a filter"; + $"could not resolve the service type; write {RegisterAll}(), " + + $"{RegisterAll}(typeof(IService<>)) or {RegisterAll}() with a filter"; return null; } @@ -272,13 +319,17 @@ private static void ReadStatement( ITypeDefinition? explicitServiceType = null; string? assemblyName = null; - for (var i = 1; i < chain.Count; i++) { + for (var i = 1; i < chain.Count; i++) + { var call = chain[i]; var name = MethodNameOf(call); - if (LifetimeCalls.TryGetValue(name, out var candidateLifestyle)) { - if (lifestyle != null) { - reason = "a convention declares one lifetime, and this one declares more than one"; + if (LifetimeCalls.TryGetValue(name, out var candidateLifestyle)) + { + if (lifestyle != null) + { + reason = + "a convention declares one lifetime, and this one declares more than one"; return null; } @@ -288,15 +339,22 @@ private static void ReadStatement( continue; } - if (name == IncludeBaseClasses) { + if (name == IncludeBaseClasses) + { includeBaseClasses = true; continue; } - if (RegisterAsCalls.TryGetValue(name, out var candidateRegisterAs)) { - if (registerAs != ConventionRegisterAs.Interfaces && registerAs != candidateRegisterAs) { - reason = "a convention registers matches one way, and this one says more than one"; + if (RegisterAsCalls.TryGetValue(name, out var candidateRegisterAs)) + { + if ( + registerAs != ConventionRegisterAs.Interfaces + && registerAs != candidateRegisterAs + ) + { + reason = + "a convention registers matches one way, and this one says more than one"; return null; } @@ -306,14 +364,19 @@ private static void ReadStatement( continue; } - if (name == UsingCall) { + if (name == UsingCall) + { var argument = call.ArgumentList.Arguments.FirstOrDefault()?.Expression; - registrationType = argument == null - ? null - : SourceGenerator.Impl.BaseSourceGenerator.GetRegistrationType(argument.ToString()); + registrationType = + argument == null + ? null + : SourceGenerator.Impl.BaseSourceGenerator.GetRegistrationType( + argument.ToString() + ); - if (registrationType == null) { + if (registrationType == null) + { reason = $"'{name}' needs a RegistrationType it can read at compile time"; return null; @@ -322,10 +385,12 @@ private static void ReadStatement( continue; } - if (name == WithKeyCall) { + if (name == WithKeyCall) + { var argument = call.ArgumentList.Arguments.FirstOrDefault()?.Expression; - if (argument == null) { + if (argument == null) + { reason = $"'{name}' needs a key"; return null; @@ -335,17 +400,22 @@ private static void ReadStatement( // and an enum member all reach the emitted registration unchanged. key = argument.ToString(); - if (argument is MemberAccessExpressionSyntax memberAccess) { - keyNamespaces = memberAccess.GetTypeDefinition(context)?.KnownNamespaces.ToArray(); + if (argument is MemberAccessExpressionSyntax memberAccess) + { + keyNamespaces = memberAccess + .GetTypeDefinition(context) + ?.KnownNamespaces.ToArray(); } continue; } - if (name == InAssemblyOfCall) { + if (name == InAssemblyOfCall) + { assemblyName = MarkerAssemblyNameOf(context, call); - if (assemblyName == null) { + if (assemblyName == null) + { reason = $"'{name}' needs a type argument from the assembly to scan"; return null; @@ -354,16 +424,19 @@ private static void ReadStatement( continue; } - if (name == AsMatchingInterfaceCall) { + if (name == AsMatchingInterfaceCall) + { registerAs = ConventionRegisterAs.MatchingInterface; continue; } - if (name == AsCall) { + if (name == AsCall) + { explicitServiceType = SingleTypeArgumentOf(context, call); - if (explicitServiceType == null) { + if (explicitServiceType == null) + { reason = $"'{name}' needs a service type argument"; return null; @@ -374,10 +447,12 @@ private static void ReadStatement( continue; } - if (name is WithNameCall or WithoutNameCall) { + if (name is WithNameCall or WithoutNameCall) + { var patterns = ReadPatterns(context, call); - if (patterns.Count == 0) { + if (patterns.Count == 0) + { reason = $"'{name}' needs at least one pattern it can read at compile time"; return null; @@ -385,71 +460,96 @@ private static void ReadStatement( nameFilters ??= new List(); - foreach (var pattern in patterns) { + foreach (var pattern in patterns) + { nameFilters.Add(new NameFilterModel(pattern, name == WithoutNameCall)); } continue; } - if (ConditionCalls.TryGetValue(name, out var conditionCall)) { + if (ConditionCalls.TryGetValue(name, out var conditionCall)) + { // 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); - if (arguments.Count == 0) { - reason = conditionCall.Kind == EnvironmentConditionKind.Name - ? $"'{name}' needs at least one environment name it can read at compile time" - : $"'{name}' needs an environment key it can read at compile time"; + if (arguments.Count == 0) + { + reason = + conditionCall.Kind == EnvironmentConditionKind.Name + ? $"'{name}' needs at least one environment name it can read at compile time" + : $"'{name}' needs an environment key it can read at compile time"; return null; } conditions ??= new List(); - if (conditionCall.Kind == EnvironmentConditionKind.Name) { - conditions.Add(new EnvironmentConditionModel( - EnvironmentConditionKind.Name, conditionCall.Negate, null, arguments)); - } else { + if (conditionCall.Kind == EnvironmentConditionKind.Name) + { + conditions.Add( + new EnvironmentConditionModel( + EnvironmentConditionKind.Name, + conditionCall.Negate, + null, + arguments + ) + ); + } + else + { // (key) tests presence, (key, value) tests equality. More than two would be a // call that does not exist on the interface. - if (arguments.Count > 2) { + if (arguments.Count > 2) + { reason = $"'{name}' takes a key and an optional value"; return null; } - conditions.Add(new EnvironmentConditionModel( - EnvironmentConditionKind.Value, - conditionCall.Negate, - arguments[0], - arguments.Count > 1 ? new[] { arguments[1] } : Array.Empty())); + conditions.Add( + new EnvironmentConditionModel( + EnvironmentConditionKind.Value, + conditionCall.Negate, + arguments[0], + arguments.Count > 1 ? new[] { arguments[1] } : Array.Empty() + ) + ); } continue; } - if (name is WithAttributeCall or WithoutAttributeCall) { + if (name is WithAttributeCall or WithoutAttributeCall) + { var attributeType = SingleTypeArgumentOf(context, call); - if (attributeType == null) { + if (attributeType == null) + { reason = $"'{name}' needs an attribute type argument"; return null; } attributeFilters ??= new List(); - attributeFilters.Add(new AttributeFilterModel( - ConventionTypeKey.For(attributeType), name == WithoutAttributeCall)); + attributeFilters.Add( + new AttributeFilterModel( + ConventionTypeKey.For(attributeType), + name == WithoutAttributeCall + ) + ); continue; } - if (NamespaceCalls.TryGetValue(name, out var namespaceCall)) { + if (NamespaceCalls.TryGetValue(name, out var namespaceCall)) + { var read = ReadNamespaceFilters(context, call, namespaceCall); - if (read == null) { + if (read == null) + { reason = $"'{name}' needs a namespace it can read at compile time"; return null; @@ -466,24 +566,27 @@ private static void ReadStatement( return null; } - if (selectsByFilter) { - if (registerAs == ConventionRegisterAs.Interfaces) { + if (selectsByFilter) + { + if (registerAs == ConventionRegisterAs.Interfaces) + { reason = - $"{RegisterAll}() names no service type, so there is nothing to register the " + - "matches as; call AsSelf() or AsSelfWithInterfaces()"; + $"{RegisterAll}() names no service type, so there is nothing to register the " + + "matches as; call AsSelf() or AsSelfWithInterfaces()"; return null; } var hasInclusion = - namespaceFilters?.Any(filter => !filter.Exclude) == true || - nameFilters?.Any(filter => !filter.Exclude) == true || - attributeFilters?.Any(filter => !filter.Exclude) == true; + namespaceFilters?.Any(filter => !filter.Exclude) == true + || nameFilters?.Any(filter => !filter.Exclude) == true + || attributeFilters?.Any(filter => !filter.Exclude) == true; - if (!hasInclusion) { + if (!hasInclusion) + { reason = - $"{RegisterAll}() with no filter matches every class in the compilation; " + - "narrow it with InNamespaceOf() or InNamespaces(...)"; + $"{RegisterAll}() with no filter matches every class in the compilation; " + + "narrow it with InNamespaceOf() or InNamespaces(...)"; return null; } @@ -507,7 +610,8 @@ private static void ReadStatement( nameFilters, explicitServiceType, assemblyName, - conditions); + conditions + ); } /// @@ -518,12 +622,16 @@ private static void ReadStatement( /// reads as the string it evaluates to. /// private static IReadOnlyList ReadPatterns( - SyntaxTransformContext context, InvocationExpressionSyntax call) { - + SyntaxTransformContext context, + InvocationExpressionSyntax call + ) + { var values = new List(); - foreach (var argument in call.ArgumentList.Arguments) { - if (context.SemanticModel.GetConstantValue(argument.Expression).Value is string value) { + foreach (var argument in call.ArgumentList.Arguments) + { + if (context.SemanticModel.GetConstantValue(argument.Expression).Value is string value) + { values.Add(value); } } @@ -540,14 +648,21 @@ private static IReadOnlyList ReadPatterns( /// is what keeps a metadata scan affordable. /// private static string? MarkerAssemblyNameOf( - SyntaxTransformContext context, InvocationExpressionSyntax call) { - - if (call.Expression is not MemberAccessExpressionSyntax { Name: GenericNameSyntax generic } || - generic.TypeArgumentList.Arguments.Count != 1) { + SyntaxTransformContext context, + InvocationExpressionSyntax call + ) + { + if ( + call.Expression is not MemberAccessExpressionSyntax { Name: GenericNameSyntax generic } + || generic.TypeArgumentList.Arguments.Count != 1 + ) + { return null; } - var symbol = context.SemanticModel.GetSymbolInfo(generic.TypeArgumentList.Arguments[0]).Symbol; + var symbol = context + .SemanticModel.GetSymbolInfo(generic.TypeArgumentList.Arguments[0]) + .Symbol; return symbol?.ContainingAssembly?.Name; } @@ -556,9 +671,11 @@ private static IReadOnlyList ReadPatterns( /// The single type argument of a generic filter call, resolved. /// private static ITypeDefinition? SingleTypeArgumentOf( - SyntaxTransformContext context, InvocationExpressionSyntax call) => - call.Expression is MemberAccessExpressionSyntax { Name: GenericNameSyntax generic } && - generic.TypeArgumentList.Arguments.Count == 1 + SyntaxTransformContext context, + InvocationExpressionSyntax call + ) => + call.Expression is MemberAccessExpressionSyntax { Name: GenericNameSyntax generic } + && generic.TypeArgumentList.Arguments.Count == 1 ? generic.TypeArgumentList.Arguments[0].GetTypeDefinition(context) : null; @@ -567,16 +684,23 @@ private static IReadOnlyList ReadPatterns( /// literals. /// private static List? ReadNamespaceFilters( - SyntaxTransformContext context, InvocationExpressionSyntax call, (bool Exact, bool Exclude) form) { - + SyntaxTransformContext context, + InvocationExpressionSyntax call, + (bool Exact, bool Exclude) form + ) + { var filters = new List(); // InNamespaceOf() — the namespace is wherever the marker type lives. - if (call.Expression is MemberAccessExpressionSyntax { Name: GenericNameSyntax generic } && - generic.TypeArgumentList.Arguments.Count == 1) { + if ( + call.Expression is MemberAccessExpressionSyntax { Name: GenericNameSyntax generic } + && generic.TypeArgumentList.Arguments.Count == 1 + ) + { var marker = generic.TypeArgumentList.Arguments[0].GetTypeDefinition(context); - if (marker == null) { + if (marker == null) + { return null; } @@ -585,8 +709,13 @@ private static IReadOnlyList ReadPatterns( return filters; } - foreach (var argument in call.ArgumentList.Arguments) { - if (context.SemanticModel.GetConstantValue(argument.Expression).Value is not string value) { + foreach (var argument in call.ArgumentList.Arguments) + { + if ( + context.SemanticModel.GetConstantValue(argument.Expression).Value + is not string value + ) + { return null; } @@ -601,19 +730,30 @@ private static IReadOnlyList ReadPatterns( /// RegisterAll(typeof(T)). /// private static ITypeDefinition? ReadServiceType( - SyntaxTransformContext context, InvocationExpressionSyntax invocation, out bool isOpenGeneric) { - + SyntaxTransformContext context, + InvocationExpressionSyntax invocation, + out bool isOpenGeneric + ) + { isOpenGeneric = false; - if (invocation.Expression is MemberAccessExpressionSyntax { Name: GenericNameSyntax { TypeArgumentList.Arguments.Count: 1 } generic }) { + if ( + invocation.Expression is MemberAccessExpressionSyntax + { + Name: GenericNameSyntax { TypeArgumentList.Arguments.Count: 1 } generic + } + ) + { return generic.TypeArgumentList.Arguments[0].GetTypeDefinition(context); } - var argument = invocation.ArgumentList.Arguments.Count == 1 - ? invocation.ArgumentList.Arguments[0].Expression - : null; + var argument = + invocation.ArgumentList.Arguments.Count == 1 + ? invocation.ArgumentList.Arguments[0].Expression + : null; - if (argument is not TypeOfExpressionSyntax typeOf) { + if (argument is not TypeOfExpressionSyntax typeOf) + { return null; } @@ -625,12 +765,15 @@ private static IReadOnlyList ReadPatterns( /// /// True for typeof(IHandler<,>), where the type arguments are omitted. /// - private static bool IsUnboundGeneric(TypeSyntax type) { - var generic = type as GenericNameSyntax ?? - (type as QualifiedNameSyntax)?.Right as GenericNameSyntax; - - return generic != null && - generic.TypeArgumentList.Arguments.Any(argument => argument is OmittedTypeArgumentSyntax); + private static bool IsUnboundGeneric(TypeSyntax type) + { + var generic = + type as GenericNameSyntax ?? (type as QualifiedNameSyntax)?.Right as GenericNameSyntax; + + return generic != null + && generic.TypeArgumentList.Arguments.Any(argument => + argument is OmittedTypeArgumentSyntax + ); } private static UnreadableStatementModel Refuse(StatementSyntax statement, string reason) => @@ -640,22 +783,31 @@ private static UnreadableStatementModel Refuse(StatementSyntax statement, string /// A single line of the refused statement, so the diagnostic message stays readable when what /// was refused is a loop or a block. /// - private static string Summarise(StatementSyntax statement) { + private static string Summarise(StatementSyntax statement) + { var text = statement.ToString().Replace("\r", " ").Replace("\n", " ").Trim(); - while (text.Contains(" ")) { + while (text.Contains(" ")) + { text = text.Replace(" ", " "); } return text.Length <= 80 ? text : text.Substring(0, 77) + "..."; } - private static MethodDeclarationSyntax? FindConventionsMethod(TypeDeclarationSyntax typeDeclaration) { + private static MethodDeclarationSyntax? FindConventionsMethod( + TypeDeclarationSyntax typeDeclaration + ) + { MethodDeclarationSyntax? fallback = null; - foreach (var method in typeDeclaration.Members.OfType()) { - if (method.Identifier.Text != ConventionContractSource.ConventionMethod || - method.ParameterList.Parameters.Count != 1) { + foreach (var method in typeDeclaration.Members.OfType()) + { + if ( + method.Identifier.Text != ConventionContractSource.ConventionMethod + || method.ParameterList.Parameters.Count != 1 + ) + { continue; } @@ -665,7 +817,8 @@ private static string Summarise(StatementSyntax statement) { // against an internal parameter type, which is why explicit implementation was the only // form that compiled. The explicit one is still preferred when a type carries both, // because that is the one the interface is actually satisfied by. - if (method.ExplicitInterfaceSpecifier != null) { + if (method.ExplicitInterfaceSpecifier != null) + { return method; } @@ -676,21 +829,29 @@ private static string Summarise(StatementSyntax statement) { } private static bool ImplementsConventionModule( - SyntaxTransformContext context, TypeDeclarationSyntax typeDeclaration) { - - if (typeDeclaration.BaseList == null) { + SyntaxTransformContext context, + TypeDeclarationSyntax typeDeclaration + ) + { + if (typeDeclaration.BaseList == null) + { return false; } - foreach (var baseType in typeDeclaration.BaseList.Types) { - if (SimpleNameOf(baseType.Type) != ConventionContractSource.ConventionModule) { + foreach (var baseType in typeDeclaration.BaseList.Types) + { + if (SimpleNameOf(baseType.Type) != ConventionContractSource.ConventionModule) + { continue; } // Confirms it is the emitted contract rather than a same-named interface of the // developer's own, which the syntactic predicate cannot tell apart. - if (context.SemanticModel.GetSymbolInfo(baseType.Type).Symbol is INamedTypeSymbol symbol && - symbol.ContainingNamespace.GetFullName() == ConventionContractSource.Namespace) { + if ( + context.SemanticModel.GetSymbolInfo(baseType.Type).Symbol is INamedTypeSymbol symbol + && symbol.ContainingNamespace.GetFullName() == ConventionContractSource.Namespace + ) + { return true; } } @@ -707,7 +868,8 @@ invocation.Expression is MemberAccessExpressionSyntax access /// The unqualified name of a written type, without namespace or type arguments. /// private static string SimpleNameOf(TypeSyntax type) => - type switch { + type switch + { SimpleNameSyntax simple => simple.Identifier.Text, QualifiedNameSyntax qualified => qualified.Right.Identifier.Text, AliasQualifiedNameSyntax alias => alias.Name.Identifier.Text, diff --git a/src/DependencyModules.SourceGenerator/Conventions/Utilities/DeclarationStamp.cs b/src/DependencyModules.SourceGenerator/Conventions/Utilities/DeclarationStamp.cs index df660fd..d2a1cdb 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Utilities/DeclarationStamp.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Utilities/DeclarationStamp.cs @@ -32,12 +32,13 @@ namespace DependencyModules.Conventions.Utilities; /// from it needs an argument. /// /// -public static class DeclarationStamp { - +public static class DeclarationStamp +{ private static readonly ConditionalWeakTable PerTree = new(); private static readonly ConditionalWeakTable PerCompilation = new(); - private sealed class StampBox { + private sealed class StampBox + { public StampBox(long value) => Value = value; public long Value { get; } @@ -47,8 +48,10 @@ private sealed class StampBox { /// The stamp for a whole compilation. Memoised on the compilation, and on each tree beneath it, /// so an edit re-hashes one tree and re-combines the rest. /// - public static long Of(Compilation compilation) { - if (PerCompilation.TryGetValue(compilation, out var cached)) { + public static long Of(Compilation compilation) + { + if (PerCompilation.TryGetValue(compilation, out var cached)) + { return cached.Value; } @@ -56,11 +59,13 @@ public static long Of(Compilation compilation) { // a stale semantic model is served, so the width is a correctness property. var hash = 14695981039346656037UL; - foreach (var tree in compilation.SyntaxTrees) { + foreach (var tree in compilation.SyntaxTrees) + { hash = Mix(hash, (ulong)TreeStamp(tree)); } - foreach (var reference in compilation.References) { + foreach (var reference in compilation.References) + { hash = Mix(hash, (ulong)(reference.Display?.GetHashCode() ?? 0)); } @@ -75,8 +80,10 @@ public static long Of(Compilation compilation) { /// One tree's contribution. Cached on the tree, which is immutable, so only an edited tree is /// ever re-hashed. /// - private static long TreeStamp(SyntaxTree tree) { - if (PerTree.TryGetValue(tree, out var cached)) { + private static long TreeStamp(SyntaxTree tree) + { + if (PerTree.TryGetValue(tree, out var cached)) + { return cached.Value; } @@ -84,10 +91,18 @@ private static long TreeStamp(SyntaxTree tree) { // Descends into containers only. A method body is not a container of anything that can // change a binding, and skipping them is what makes the common keystroke free. - foreach (var node in tree.GetRoot().DescendantNodes(descendIntoChildren: n => - n is CompilationUnitSyntax or BaseNamespaceDeclarationSyntax or TypeDeclarationSyntax)) { - - switch (node) { + foreach ( + var node in tree.GetRoot() + .DescendantNodes(descendIntoChildren: n => + n + is CompilationUnitSyntax + or BaseNamespaceDeclarationSyntax + or TypeDeclarationSyntax + ) + ) + { + switch (node) + { case UsingDirectiveSyntax usingDirective: hash = Mix(hash, Hash(usingDirective.ToString())); break; @@ -111,7 +126,8 @@ private static long TreeStamp(SyntaxTree tree) { // Signatures, not bodies. A constructor added to one part of a partial changes // what another part's symbol reports about itself. - foreach (var member in type.Members) { + foreach (var member in type.Members) + { hash = Mix(hash, MemberSignature(member)); } @@ -127,31 +143,44 @@ private static long TreeStamp(SyntaxTree tree) { } private static ulong MemberSignature(MemberDeclarationSyntax member) => - member switch { - ConstructorDeclarationSyntax constructor => - Hash(constructor.Modifiers + constructor.ParameterList.ToString()), - MethodDeclarationSyntax method => - Hash(method.Modifiers + method.ReturnType.ToString() + method.Identifier.Text + - method.TypeParameterList + method.ParameterList), - PropertyDeclarationSyntax property => - Hash(property.Modifiers + property.Type.ToString() + property.Identifier.Text), - FieldDeclarationSyntax field => - Hash(field.Modifiers + field.Declaration.Type.ToString() + - string.Join(",", field.Declaration.Variables.Select(v => v.Identifier.Text))), - EventDeclarationSyntax @event => - Hash(@event.Modifiers + @event.Type.ToString() + @event.Identifier.Text), + member switch + { + ConstructorDeclarationSyntax constructor => Hash( + constructor.Modifiers + constructor.ParameterList.ToString() + ), + MethodDeclarationSyntax method => Hash( + method.Modifiers + + method.ReturnType.ToString() + + method.Identifier.Text + + method.TypeParameterList + + method.ParameterList + ), + PropertyDeclarationSyntax property => Hash( + property.Modifiers + property.Type.ToString() + property.Identifier.Text + ), + FieldDeclarationSyntax field => Hash( + field.Modifiers + + field.Declaration.Type.ToString() + + string.Join(",", field.Declaration.Variables.Select(v => v.Identifier.Text)) + ), + EventDeclarationSyntax @event => Hash( + @event.Modifiers + @event.Type.ToString() + @event.Identifier.Text + ), // Nested types are reached by the walk above, so they need nothing here. - _ => Hash(member.Kind().ToString()) + _ => Hash(member.Kind().ToString()), }; - private static ulong Hash(string? text) { - if (text == null) { + private static ulong Hash(string? text) + { + if (text == null) + { return 0; } var hash = 14695981039346656037UL; - foreach (var c in text) { + foreach (var c in text) + { hash = (hash ^ c) * 1099511628211UL; } diff --git a/src/DependencyModules.SourceGenerator/Conventions/Utilities/MetadataCandidateUtility.cs b/src/DependencyModules.SourceGenerator/Conventions/Utilities/MetadataCandidateUtility.cs index 4532730..582214d 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Utilities/MetadataCandidateUtility.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Utilities/MetadataCandidateUtility.cs @@ -28,8 +28,8 @@ namespace DependencyModules.Conventions.Utilities; /// documented rather than diagnosed. /// /// -public static class MetadataCandidateUtility { - +public static class MetadataCandidateUtility +{ /// /// Candidates from every assembly the given conventions name. /// @@ -40,33 +40,47 @@ public static class MetadataCandidateUtility { public static IReadOnlyList Collect( IReadOnlyList conventionModules, Compilation compilation, - CancellationToken cancellationToken) { - + CancellationToken cancellationToken + ) + { var wanted = new HashSet(StringComparer.Ordinal); - foreach (var module in conventionModules) { - foreach (var convention in module.Conventions) { - if (convention.AssemblyName != null) { + foreach (var module in conventionModules) + { + foreach (var convention in module.Conventions) + { + if (convention.AssemblyName != null) + { wanted.Add(convention.AssemblyName); } } } - if (wanted.Count == 0) { + if (wanted.Count == 0) + { return Array.Empty(); } var candidates = new List(); - foreach (var reference in compilation.References) { + foreach (var reference in compilation.References) + { cancellationToken.ThrowIfCancellationRequested(); - if (compilation.GetAssemblyOrModuleSymbol(reference) is not IAssemblySymbol assembly || - !wanted.Contains(assembly.Name)) { + if ( + compilation.GetAssemblyOrModuleSymbol(reference) is not IAssemblySymbol assembly + || !wanted.Contains(assembly.Name) + ) + { continue; } - CollectFromNamespace(assembly.GlobalNamespace, assembly.Name, candidates, cancellationToken); + CollectFromNamespace( + assembly.GlobalNamespace, + assembly.Name, + candidates, + cancellationToken + ); } return candidates; @@ -76,12 +90,15 @@ private static void CollectFromNamespace( INamespaceSymbol namespaceSymbol, string assemblyName, List candidates, - CancellationToken cancellationToken) { - - foreach (var member in namespaceSymbol.GetMembers()) { + CancellationToken cancellationToken + ) + { + foreach (var member in namespaceSymbol.GetMembers()) + { cancellationToken.ThrowIfCancellationRequested(); - switch (member) { + switch (member) + { case INamespaceSymbol nested: CollectFromNamespace(nested, assemblyName, candidates, cancellationToken); break; @@ -106,17 +123,20 @@ private static void CollectFromNamespace( /// scanning that interface would match the decorator and register it as a service. /// private static bool IsCandidate(INamedTypeSymbol type) => - type.TypeKind == TypeKind.Class && - !type.IsAbstract && - !type.IsStatic && - type.DeclaredAccessibility == Accessibility.Public && - !DeclaresRegistration(type); - - private static bool DeclaresRegistration(INamedTypeSymbol type) { - foreach (var attribute in type.GetAttributes()) { + type.TypeKind == TypeKind.Class + && !type.IsAbstract + && !type.IsStatic + && type.DeclaredAccessibility == Accessibility.Public + && !DeclaresRegistration(type); + + private static bool DeclaresRegistration(INamedTypeSymbol type) + { + foreach (var attribute in type.GetAttributes()) + { var name = attribute.AttributeClass?.Name; - if (name != null && Array.IndexOf(ExcludedAttributeNames, name) >= 0) { + if (name != null && Array.IndexOf(ExcludedAttributeNames, name) >= 0) + { return true; } } @@ -124,7 +144,8 @@ private static bool DeclaresRegistration(INamedTypeSymbol type) { return false; } - private static readonly string[] ExcludedAttributeNames = { + private static readonly string[] ExcludedAttributeNames = + { "SingletonServiceAttribute", "ScopedServiceAttribute", "TransientServiceAttribute", @@ -132,7 +153,11 @@ private static bool DeclaresRegistration(INamedTypeSymbol type) { "DecoratorAttribute", }; - private static ConventionCandidateModel BuildCandidate(INamedTypeSymbol type, string assemblyName) { + private static ConventionCandidateModel BuildCandidate( + INamedTypeSymbol type, + string assemblyName + ) + { var declared = new List(); var viaBaseClass = new List(); var seen = new HashSet(); @@ -140,15 +165,18 @@ private static ConventionCandidateModel BuildCandidate(INamedTypeSymbol type, st // Directly implemented interfaces, and what those extend, are the metadata equivalent of // "written on the declaration". Anything else in AllInterfaces arrived through a base class, // which is the distinction IncludeBaseClasses turns on. - foreach (var interfaceSymbol in type.Interfaces) { + foreach (var interfaceSymbol in type.Interfaces) + { Add(declared, seen, interfaceSymbol, null); - foreach (var inherited in interfaceSymbol.AllInterfaces) { + foreach (var inherited in interfaceSymbol.AllInterfaces) + { Add(declared, seen, inherited, interfaceSymbol.Name); } } - foreach (var interfaceSymbol in type.AllInterfaces) { + foreach (var interfaceSymbol in type.AllInterfaces) + { Add(viaBaseClass, seen, interfaceSymbol, type.BaseType?.Name); } @@ -161,23 +189,31 @@ private static ConventionCandidateModel BuildCandidate(INamedTypeSymbol type, st LocationModel.None, null, AttributeKeysOf(type), - assemblyName); + assemblyName + ); } private static void Add( List target, HashSet seen, INamedTypeSymbol interfaceSymbol, - string? viaTypeName) { - + string? viaTypeName + ) + { var definition = interfaceSymbol.GetTypeDefinition(); - if (!seen.Add(definition)) { + if (!seen.Add(definition)) + { return; } - target.Add(new ImplementedInterfaceModel( - definition, ConventionTypeKey.For(definition), viaTypeName)); + target.Add( + new ImplementedInterfaceModel( + definition, + ConventionTypeKey.For(definition), + viaTypeName + ) + ); } /// @@ -192,17 +228,21 @@ private static void Add( private static ConstructorInfoModel? GreediestConstructor(INamedTypeSymbol type) => SymbolConstructorReader.Read(type); - private static IReadOnlyList? AttributeKeysOf(INamedTypeSymbol type) { + private static IReadOnlyList? AttributeKeysOf(INamedTypeSymbol type) + { var attributes = type.GetAttributes(); - if (attributes.Length == 0) { + if (attributes.Length == 0) + { return null; } var keys = new List(attributes.Length); - foreach (var attribute in attributes) { - if (attribute.AttributeClass is { } attributeClass) { + foreach (var attribute in attributes) + { + if (attribute.AttributeClass is { } attributeClass) + { keys.Add(ConventionTypeKey.For(attributeClass.GetTypeDefinition())); } } diff --git a/src/DependencyModules.SourceGenerator/InterceptorSourceGenerator.cs b/src/DependencyModules.SourceGenerator/InterceptorSourceGenerator.cs index 0da8c3d..ae47cc3 100644 --- a/src/DependencyModules.SourceGenerator/InterceptorSourceGenerator.cs +++ b/src/DependencyModules.SourceGenerator/InterceptorSourceGenerator.cs @@ -16,26 +16,34 @@ namespace DependencyModules.SourceGenerator; /// generated rather than written. Everything that path already handles — lifetime preservation, the /// three descriptor shapes, global ordering — applies without change. /// -public class InterceptorSourceGenerator : BaseAttributeSourceGenerator { +public class InterceptorSourceGenerator : BaseAttributeSourceGenerator +{ private readonly IEqualityComparer _comparer = new InterceptorModelComparer(); - private static readonly ITypeDefinition[] _attributeTypes = { - KnownTypes.DependencyModules.Attributes.InterceptAttribute + private static readonly ITypeDefinition[] _attributeTypes = + { + KnownTypes.DependencyModules.Attributes.InterceptAttribute, }; protected override string LoggerName => "InterceptorSourceGenerator"; - protected override IEnumerable AttributeTypes() { + protected override IEnumerable AttributeTypes() + { return _attributeTypes; } protected override InterceptorModel IgnoredModel => InterceptorModel.Ignore; - protected override IEqualityComparer GetComparer() { + protected override IEqualityComparer GetComparer() + { return _comparer; } - protected override InterceptorModel GenerateAttributeModel(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { + protected override InterceptorModel GenerateAttributeModel( + GeneratorAttributeSyntaxContext context, + CancellationToken cancellationToken + ) + { // A refusal travels on the model so the output stage, which owns the diagnostic context, can // report it. Reporting from the transform is not possible. return InterceptorModelUtility.GetInterceptorModel(context, cancellationToken); @@ -43,36 +51,55 @@ protected override InterceptorModel GenerateAttributeModel(GeneratorAttributeSyn protected override void GenerateSourceOutput( SourceProductionContext context, - (ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Left, - ImmutableArray Right) inputData, - FileLogger logger) { - - if (inputData.Left.Length == 0 || inputData.Right.Length == 0) { + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Left, + ImmutableArray Right + ) inputData, + FileLogger logger + ) + { + if (inputData.Left.Length == 0 || inputData.Right.Length == 0) + { return; } - var (entryPointList, configurationModel) = EntryModelUtil.ConsolidateEntryPointModels(inputData.Left); + var (entryPointList, configurationModel) = EntryModelUtil.ConsolidateEntryPointModels( + inputData.Left + ); // Filtering only; ReportUnsupported's explanations live in ReportDiagnostics, which shares // the same predicate. var usable = Usable(inputData.Right); - if (usable.Count == 0) { + if (usable.Count == 0) + { return; } var writer = new InterceptorFileWriter(); - foreach (var model in usable) { + foreach (var model in usable) + { context.CancellationToken.ThrowIfCancellationRequested(); var wrapperName = $"{model.ImplementationType.Name.Replace(".", "_")}_Intercepted"; - logger.Info($"Generating '{wrapperName}' for '{model.ServiceType}' with {model.Members.Count} member(s)."); + logger.Info( + $"Generating '{wrapperName}' for '{model.ServiceType}' with {model.Members.Count} member(s)." + ); context.AddSource( $"{wrapperName}.g.cs", - writer.Write(model, wrapperName, model.ImplementationType.Namespace, configurationModel)); + writer.Write( + model, + wrapperName, + model.ImplementationType.Namespace, + configurationModel + ) + ); } // One registration file per module, carrying the interceptions that belong to that module. @@ -80,16 +107,23 @@ protected override void GenerateSourceOutput( // applicators for interceptions that named no realm and had nothing to do with it, which // either wrapped an unrelated service or — when the leaked interceptor needed a dependency // the isolated container did not have — threw while building the provider. - foreach (var entryPointModel in EntryModelUtil.RegistrationTargets(entryPointList)) { + foreach (var entryPointModel in EntryModelUtil.RegistrationTargets(entryPointList)) + { var registrationWriter = new InterceptorRegistrationWriter(); context.AddSource( - EntryModelUtil.EnsureNamespace(entryPointModel, configurationModel) - .EntryPointType.GetFileNameHint(configurationModel.RootNamespace, "Interceptors"), + EntryModelUtil + .EnsureNamespace(entryPointModel, configurationModel) + .EntryPointType.GetFileNameHint( + configurationModel.RootNamespace, + "Interceptors" + ), registrationWriter.Write( EntryModelUtil.EnsureNamespace(entryPointModel, configurationModel), configurationModel, - ForModule(usable, entryPointModel))); + ForModule(usable, entryPointModel) + ) + ); } } @@ -102,21 +136,27 @@ protected override void GenerateSourceOutput( /// realm-only. /// private static IReadOnlyList ForModule( - IReadOnlyList models, ModuleEntryPointModel entryPointModel) { - + IReadOnlyList models, + ModuleEntryPointModel entryPointModel + ) + { var onlyRealm = entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.OnlyRealm); var selected = new List(); - foreach (var model in models) { - if (model.Realm != null) { - if (model.Realm.Equals(entryPointModel.EntryPointType)) { + foreach (var model in models) + { + if (model.Realm != null) + { + if (model.Realm.Equals(entryPointModel.EntryPointType)) + { selected.Add(model); } continue; } - if (!onlyRealm) { + if (!onlyRealm) + { selected.Add(model); } } @@ -132,15 +172,19 @@ private static IReadOnlyList ForModule( /// interceptor able to serve it — a wrapper for the second would forward every call untouched. /// Both are explained by ; this only decides what to write. /// - private static IReadOnlyList Usable(ImmutableArray models) { + private static IReadOnlyList Usable(ImmutableArray models) + { var usable = new List(); - foreach (var model in models) { - if (model.Refusal != null || model.IsIgnored || model.Members.Count == 0) { + foreach (var model in models) + { + if (model.Refusal != null || model.IsIgnored || model.Members.Count == 0) + { continue; } - if (!ServesAnyMember(model)) { + if (!ServesAnyMember(model)) + { continue; } @@ -153,8 +197,9 @@ private static IReadOnlyList Usable(ImmutableArrayWhether any member has an interceptor that can serve it. private static bool ServesAnyMember(InterceptorModel model) => model.Members.Any(member => - !member.Excluded && - model.Interceptors.Any(interceptor => interceptor.CanServe(member.Kind))); + !member.Excluded + && model.Interceptors.Any(interceptor => interceptor.CanServe(member.Kind)) + ); /// /// Why an interception was refused, or is quietly absent from members it was applied to. @@ -163,32 +208,45 @@ private static bool ServesAnyMember(InterceptorModel model) => /// Reported apart from emission so the locations carry their syntax tree, which is what lets /// one of these be silenced where it is written rather than only across the whole project. /// - protected override void ReportDiagnostics(SourceProductionContext context, - (ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Left, - ImmutableArray Right) data, + protected override void ReportDiagnostics( + SourceProductionContext context, + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Left, + ImmutableArray Right + ) data, SyntaxTreeLookup lookup, - FileLogger logger) { - - if (data.Left.Length == 0 || data.Right.Length == 0) { + FileLogger logger + ) + { + if (data.Left.Length == 0 || data.Right.Length == 0) + { return; } - foreach (var model in data.Right) { + foreach (var model in data.Right) + { context.CancellationToken.ThrowIfCancellationRequested(); - if (model.Refusal != null) { + if (model.Refusal != null) + { logger.Error($"Cannot intercept: {model.Refusal.Message}"); context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.CannotIntercept, model.Location?.ToLocationOrNone(lookup) ?? Location.None, - model.Refusal.Message)); + model.Refusal.Message + ) + ); continue; } - if (model.IsIgnored || model.Members.Count == 0) { + if (model.IsIgnored || model.Members.Count == 0) + { continue; } @@ -217,46 +275,59 @@ protected override void ReportDiagnostics(SourceProductionContext context, /// private static void ReportUnappliedInterceptions( SourceProductionContext context, - ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> entryPoints, + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> entryPoints, IReadOnlyList usable, SyntaxTreeLookup lookup, - FileLogger logger) { - - if (usable.Count == 0) { + FileLogger logger + ) + { + if (usable.Count == 0) + { return; } var (entryPointList, _) = EntryModelUtil.ConsolidateEntryPointModels(entryPoints); var targets = EntryModelUtil.RegistrationTargets(entryPointList).ToArray(); - if (targets.Length == 0) { + if (targets.Length == 0) + { return; } var applied = new HashSet(); - foreach (var entryPointModel in targets) { - foreach (var model in ForModule(usable, entryPointModel)) { + foreach (var entryPointModel in targets) + { + foreach (var model in ForModule(usable, entryPointModel)) + { applied.Add(model.ImplementationType); } } - foreach (var model in usable) { + foreach (var model in usable) + { context.CancellationToken.ThrowIfCancellationRequested(); - if (applied.Contains(model.ImplementationType)) { + if (applied.Contains(model.ImplementationType)) + { continue; } logger.Error( - $"No module applies the interception on '{model.ImplementationType.Name}', " + - "so its interceptors never run."); + $"No module applies the interception on '{model.ImplementationType.Name}', " + + "so its interceptors never run." + ); context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.InterceptionAppliedByNoModule, model.Location?.ToLocationOrNone(lookup) ?? Location.None, - model.ImplementationType.Name)); + model.ImplementationType.Name + ) + ); } } @@ -273,19 +344,30 @@ private static void ReportUnappliedInterceptions( /// than one per member. /// private static void ReportUnservedMembers( - SourceProductionContext context, InterceptorModel model, SyntaxTreeLookup lookup, - FileLogger logger) { - - foreach (var interceptor in model.Interceptors) { - foreach (var kind in new[] { - InterceptorKind.Sync, InterceptorKind.Async, InterceptorKind.Stream - }) { - - if (interceptor.CanServe(kind)) { + SourceProductionContext context, + InterceptorModel model, + SyntaxTreeLookup lookup, + FileLogger logger + ) + { + foreach (var interceptor in model.Interceptors) + { + foreach ( + var kind in new[] + { + InterceptorKind.Sync, + InterceptorKind.Async, + InterceptorKind.Stream, + } + ) + { + if (interceptor.CanServe(kind)) + { continue; } - var unserved = model.Members + var unserved = model + .Members // An excluded member was never meant to be served, so an interceptor not // covering it is not the omission DM0015 is about. .Where(member => !member.Excluded && member.Kind == kind) @@ -294,13 +376,15 @@ private static void ReportUnservedMembers( .OrderBy(name => name, StringComparer.Ordinal) .ToArray(); - if (unserved.Length == 0) { + if (unserved.Length == 0) + { continue; } logger.Error( - $"'{interceptor.Type.Name}' does not implement {InterfaceFor(kind)}, so it is not " + - $"applied to {string.Join(", ", unserved)} on '{model.ServiceType.Name}'."); + $"'{interceptor.Type.Name}' does not implement {InterfaceFor(kind)}, so it is not " + + $"applied to {string.Join(", ", unserved)} on '{model.ServiceType.Name}'." + ); context.ReportDiagnostic( Diagnostic.Create( @@ -310,22 +394,26 @@ private static void ReportUnservedMembers( InterfaceFor(kind), DescriptionFor(kind), model.ServiceType.Name, - string.Join(", ", unserved))); + string.Join(", ", unserved) + ) + ); } } } private static string InterfaceFor(InterceptorKind kind) => - kind switch { + kind switch + { InterceptorKind.Async => "IAsyncInterceptor", InterceptorKind.Stream => "IAsyncEnumerableInterceptor", - _ => "IInterceptor" + _ => "IInterceptor", }; private static string DescriptionFor(InterceptorKind kind) => - kind switch { + kind switch + { InterceptorKind.Async => "the members returning a task", InterceptorKind.Stream => "the members returning an async stream", - _ => "the members returning a value directly" + _ => "the members returning a value directly", }; } diff --git a/src/DependencyModules.SourceGenerator/ReferencedModuleLookup.cs b/src/DependencyModules.SourceGenerator/ReferencedModuleLookup.cs index 96b27a6..db0e54f 100644 --- a/src/DependencyModules.SourceGenerator/ReferencedModuleLookup.cs +++ b/src/DependencyModules.SourceGenerator/ReferencedModuleLookup.cs @@ -23,14 +23,17 @@ namespace DependencyModules.SourceGenerator; /// point of DM0016. /// /// -internal sealed class ReferencedModuleLookup { - private const string ProviderInterface = "DependencyModules.Runtime.Interfaces.IDependencyModuleProvider"; +internal sealed class ReferencedModuleLookup +{ + private const string ProviderInterface = + "DependencyModules.Runtime.Interfaces.IDependencyModuleProvider"; private readonly Compilation? _compilation; private readonly INamedTypeSymbol? _providerInterface; private Dictionary? _byName; - public ReferencedModuleLookup(Compilation? compilation) { + public ReferencedModuleLookup(Compilation? compilation) + { _compilation = compilation; _providerInterface = compilation?.GetTypeByMetadataName(ProviderInterface); } @@ -39,20 +42,26 @@ public ReferencedModuleLookup(Compilation? compilation) { /// The namespace of a module whose attribute is and which one of /// brings into scope, or null. /// - public string? FindImported(string name, IEnumerable candidateNamespaces) { - if (_compilation == null || _providerInterface == null) { + public string? FindImported(string name, IEnumerable candidateNamespaces) + { + if (_compilation == null || _providerInterface == null) + { return null; } - foreach (var candidate in candidateNamespaces) { - if (string.IsNullOrEmpty(candidate)) { + foreach (var candidate in candidateNamespaces) + { + if (string.IsNullOrEmpty(candidate)) + { continue; } - foreach (var typeName in AttributeNames(name)) { + foreach (var typeName in AttributeNames(name)) + { var symbol = _compilation.GetTypeByMetadataName($"{candidate}.{typeName}"); - if (symbol != null && IsModuleAttribute(symbol)) { + if (symbol != null && IsModuleAttribute(symbol)) + { return candidate; } } @@ -68,15 +77,19 @@ public ReferencedModuleLookup(Compilation? compilation) { /// Walks the referenced assemblies. Reached only for a usage that resolved to nothing, so the /// compilation it walks is one that is already failing to build. /// - public string? FindAnywhere(string name) { - if (_compilation == null || _providerInterface == null) { + public string? FindAnywhere(string name) + { + if (_compilation == null || _providerInterface == null) + { return null; } _byName ??= BuildIndex(); - foreach (var typeName in AttributeNames(name)) { - if (_byName.TryGetValue(typeName, out var moduleNamespace)) { + foreach (var typeName in AttributeNames(name)) + { + if (_byName.TryGetValue(typeName, out var moduleNamespace)) + { return moduleNamespace; } } @@ -85,10 +98,12 @@ public ReferencedModuleLookup(Compilation? compilation) { } /// Written as [assembly: Foo] or [assembly: FooAttribute]. - private static IEnumerable AttributeNames(string name) { + private static IEnumerable AttributeNames(string name) + { yield return name; - if (!name.EndsWith("Attribute", System.StringComparison.Ordinal)) { + if (!name.EndsWith("Attribute", System.StringComparison.Ordinal)) + { yield return name + "Attribute"; } } @@ -96,11 +111,14 @@ private static IEnumerable AttributeNames(string name) { private bool IsModuleAttribute(INamedTypeSymbol symbol) => symbol.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, _providerInterface)); - private Dictionary BuildIndex() { + private Dictionary BuildIndex() + { var index = new Dictionary(System.StringComparer.Ordinal); - foreach (var reference in _compilation!.References) { - if (_compilation.GetAssemblyOrModuleSymbol(reference) is not IAssemblySymbol assembly) { + foreach (var reference in _compilation!.References) + { + if (_compilation.GetAssemblyOrModuleSymbol(reference) is not IAssemblySymbol assembly) + { continue; } @@ -110,18 +128,23 @@ private Dictionary BuildIndex() { return index; } - private void Walk(INamespaceSymbol namespaceSymbol, Dictionary index) { - foreach (var type in namespaceSymbol.GetTypeMembers()) { - if (type.DeclaredAccessibility == Accessibility.Public && IsModuleAttribute(type)) { + private void Walk(INamespaceSymbol namespaceSymbol, Dictionary index) + { + foreach (var type in namespaceSymbol.GetTypeMembers()) + { + if (type.DeclaredAccessibility == Accessibility.Public && IsModuleAttribute(type)) + { // First wins. Two packages can ship a same-named module, and naming one of them is // more useful than naming neither. - if (!index.ContainsKey(type.Name)) { + if (!index.ContainsKey(type.Name)) + { index.Add(type.Name, namespaceSymbol.ToDisplayString()); } } } - foreach (var nested in namespaceSymbol.GetNamespaceMembers()) { + foreach (var nested in namespaceSymbol.GetNamespaceMembers()) + { Walk(nested, index); } } diff --git a/src/DependencyModules.SourceGenerator/ServiceSourceGenerator.cs b/src/DependencyModules.SourceGenerator/ServiceSourceGenerator.cs index 58553e6..81c145a 100644 --- a/src/DependencyModules.SourceGenerator/ServiceSourceGenerator.cs +++ b/src/DependencyModules.SourceGenerator/ServiceSourceGenerator.cs @@ -10,29 +10,47 @@ namespace DependencyModules.SourceGenerator; -public class ServiceSourceGenerator : BaseAttributeSourceGenerator { - private static ITypeDefinition[] _skipTypes = new [] { TypeDefinition.Get(typeof(INotifyPropertyChanged))}; - private static readonly ITypeDefinition[] _attributeTypes = { - KnownTypes.DependencyModules.Attributes.TransientServiceAttribute, - KnownTypes.DependencyModules.Attributes.ScopedServiceAttribute, +public class ServiceSourceGenerator : BaseAttributeSourceGenerator +{ + private static ITypeDefinition[] _skipTypes = new[] + { + TypeDefinition.Get(typeof(INotifyPropertyChanged)), + }; + private static readonly ITypeDefinition[] _attributeTypes = + { + KnownTypes.DependencyModules.Attributes.TransientServiceAttribute, + KnownTypes.DependencyModules.Attributes.ScopedServiceAttribute, KnownTypes.DependencyModules.Attributes.SingletonServiceAttribute, KnownTypes.DependencyModules.Attributes.CrossWireServiceAttribute, - KnownTypes.Microsoft.TextJson.JsonSourceGenerationOptionsAttribute + KnownTypes.Microsoft.TextJson.JsonSourceGenerationOptionsAttribute, }; - private readonly IEqualityComparer _serviceEqualityComparer = new ServiceModelComparer(); + private readonly IEqualityComparer _serviceEqualityComparer = + new ServiceModelComparer(); - protected override IEnumerable AttributeTypes() { + protected override IEnumerable AttributeTypes() + { return _attributeTypes; } - protected override void GenerateSourceOutput(SourceProductionContext context, - (ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Left, ImmutableArray Right) inputData, - FileLogger logger) { - if (inputData.Left.Length == 0 || inputData.Right.Length == 0) { + protected override void GenerateSourceOutput( + SourceProductionContext context, + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Left, + ImmutableArray Right + ) inputData, + FileLogger logger + ) + { + if (inputData.Left.Length == 0 || inputData.Right.Length == 0) + { logger.Info( - $"Nothing to generate: {inputData.Left.Length} module(s) and " + - $"{inputData.Right.Length} service(s) were discovered."); + $"Nothing to generate: {inputData.Left.Length} module(s) and " + + $"{inputData.Right.Length} service(s) were discovered." + ); return; } @@ -43,38 +61,55 @@ protected override void GenerateSourceOutput(SourceProductionContext context, // generic that cannot be cross-wired, is dropped here and explained there. var serviceModels = Registerable(inputData.Right); - if (serviceModels.Length == 0) { + if (serviceModels.Length == 0) + { return; } - var (entryPointList, configurationModel) = - EntryModelUtil.ConsolidateEntryPointModels(inputData.Left); + var (entryPointList, configurationModel) = EntryModelUtil.ConsolidateEntryPointModels( + inputData.Left + ); - foreach (var entryPointModel in EntryModelUtil.RegistrationTargets(entryPointList)) { + foreach (var entryPointModel in EntryModelUtil.RegistrationTargets(entryPointList)) + { context.CancellationToken.ThrowIfCancellationRequested(); - GenerateSourceOutput(context, entryPointModel, configurationModel, serviceModels, logger); + GenerateSourceOutput( + context, + entryPointModel, + configurationModel, + serviceModels, + logger + ); } } - protected void GenerateSourceOutput(SourceProductionContext context, + protected void GenerateSourceOutput( + SourceProductionContext context, ModuleEntryPointModel entryPointModel, DependencyModuleConfigurationModel configurationModel, - ImmutableArray serviceModels, FileLogger logger) { - + ImmutableArray serviceModels, + FileLogger logger + ) + { // don't generate empty dependency registrations - if (serviceModels.Length == 0) { + if (serviceModels.Length == 0) + { return; } - - entryPointModel = EntryModelUtil.EnsureNamespace(entryPointModel,configurationModel); - + + entryPointModel = EntryModelUtil.EnsureNamespace(entryPointModel, configurationModel); + var writer = new DependencyFileWriter(logger); var output = writer.Write(entryPointModel, configurationModel, serviceModels, "Module"); - + context.AddSource( entryPointModel.EntryPointType.GetFileNameHint( - configurationModel.RootNamespace,"Dependencies"), output); + configurationModel.RootNamespace, + "Dependencies" + ), + output + ); } /// @@ -86,38 +121,54 @@ protected void GenerateSourceOutput(SourceProductionContext context, /// what configuration was in effect — none of which is visible from the generated output alone. /// private static void LogDiscovery( - (ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Left, - ImmutableArray Right) inputData, - FileLogger logger) { - + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Left, + ImmutableArray Right + ) inputData, + FileLogger logger + ) + { var configuration = inputData.Left.First().Right; logger.Info( - "Configuration: " + - $"RootNamespace='{configuration.RootNamespace}', " + - $"ProjectDir='{configuration.ProjectDir}', " + - $"RegistrationType={configuration.RegistrationType}, " + - $"AutoGenerateModule={configuration.AutoGenerateEntry}, " + - $"GenerateFactories={configuration.GenerateFactories}, " + - $"ExcludeGeneratedCodeFromCoverage={configuration.ExcludeGeneratedCodeFromCoverage}"); + "Configuration: " + + $"RootNamespace='{configuration.RootNamespace}', " + + $"ProjectDir='{configuration.ProjectDir}', " + + $"RegistrationType={configuration.RegistrationType}, " + + $"AutoGenerateModule={configuration.AutoGenerateEntry}, " + + $"GenerateFactories={configuration.GenerateFactories}, " + + $"ExcludeGeneratedCodeFromCoverage={configuration.ExcludeGeneratedCodeFromCoverage}" + ); logger.Info($"Discovered {inputData.Left.Length} module(s):"); - foreach (var (entryPoint, _) in inputData.Left) { + foreach (var (entryPoint, _) in inputData.Left) + { logger.Info( - $" {entryPoint.EntryPointType.Namespace}.{entryPoint.EntryPointType.Name} " + - $"[{entryPoint.ModuleFeatures}] from '{entryPoint.FileLocation}'"); + $" {entryPoint.EntryPointType.Namespace}.{entryPoint.EntryPointType.Name} " + + $"[{entryPoint.ModuleFeatures}] from '{entryPoint.FileLocation}'" + ); } logger.Info($"Discovered {inputData.Right.Length} service(s):"); - foreach (var serviceModel in inputData.Right) { - foreach (var registration in serviceModel.Registrations) { + foreach (var serviceModel in inputData.Right) + { + foreach (var registration in serviceModel.Registrations) + { logger.Info( - $" {serviceModel.ImplementationType.Name} -> {registration.ServiceType.Name} " + - $"({registration.Lifestyle}" + - (registration.Key != null ? $", key={registration.Key}" : "") + - (registration.Realm != null ? $", realm={registration.Realm.Name}" : "") + - (registration.RegistrationType != null ? $", using={registration.RegistrationType}" : "") + - ")"); + $" {serviceModel.ImplementationType.Name} -> {registration.ServiceType.Name} " + + $"({registration.Lifestyle}" + + (registration.Key != null ? $", key={registration.Key}" : "") + + (registration.Realm != null ? $", realm={registration.Realm.Name}" : "") + + ( + registration.RegistrationType != null + ? $", using={registration.RegistrationType}" + : "" + ) + + ")" + ); } } } @@ -131,15 +182,21 @@ private static void LogDiscovery( /// build for an abstract or static type, and at compile time for a cross-wired generic. Each is /// explained by a diagnostic; this only decides what not to write. /// - private static ImmutableArray Registerable(ImmutableArray serviceModels) { - if (!serviceModels.Any(m => IsUnconstructable(m) || IsCrossWiredGeneric(m))) { + private static ImmutableArray Registerable( + ImmutableArray serviceModels + ) + { + if (!serviceModels.Any(m => IsUnconstructable(m) || IsCrossWiredGeneric(m))) + { return serviceModels; } var builder = ImmutableArray.CreateBuilder(serviceModels.Length); - foreach (var serviceModel in serviceModels) { - if (!IsUnconstructable(serviceModel) && !IsCrossWiredGeneric(serviceModel)) { + foreach (var serviceModel in serviceModels) + { + if (!IsUnconstructable(serviceModel) && !IsCrossWiredGeneric(serviceModel)) + { builder.Add(serviceModel); } } @@ -172,8 +229,8 @@ private static string UnconstructableReason(ServiceModel serviceModel) => /// A cross-wired registration on an implementation that is itself generic. /// private static bool IsCrossWiredGeneric(ServiceModel serviceModel) => - serviceModel.ImplementationType is GenericTypeDefinition { TypeArguments.Count: > 0 } && - serviceModel.Registrations.Any(registration => registration.CrossWire == true); + serviceModel.ImplementationType is GenericTypeDefinition { TypeArguments.Count: > 0 } + && serviceModel.Registrations.Any(registration => registration.CrossWire == true); /// /// Everything this generator has to say about the services it found. @@ -184,20 +241,30 @@ private static bool IsCrossWiredGeneric(ServiceModel serviceModel) => /// project. The two halves share their predicates: what drops is /// exactly what the first two loops here explain. /// - protected override void ReportDiagnostics(SourceProductionContext context, - (ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> Left, - ImmutableArray Right) data, + protected override void ReportDiagnostics( + SourceProductionContext context, + ( + ImmutableArray<( + ModuleEntryPointModel Left, + DependencyModuleConfigurationModel Right + )> Left, + ImmutableArray Right + ) data, SyntaxTreeLookup lookup, - FileLogger logger) { - - if (data.Left.Length == 0 || data.Right.Length == 0) { + FileLogger logger + ) + { + if (data.Left.Length == 0 || data.Right.Length == 0) + { return; } - foreach (var serviceModel in data.Right) { + foreach (var serviceModel in data.Right) + { context.CancellationToken.ThrowIfCancellationRequested(); - if (!IsUnconstructable(serviceModel)) { + if (!IsUnconstructable(serviceModel)) + { continue; } @@ -211,13 +278,17 @@ protected override void ReportDiagnostics(SourceProductionContext context, DependencyModuleDiagnostics.ServiceCannotBeConstructed, serviceModel.Location?.ToLocationOrNone(lookup) ?? Location.None, typeName, - reason)); + reason + ) + ); } - foreach (var serviceModel in data.Right) { + foreach (var serviceModel in data.Right) + { context.CancellationToken.ThrowIfCancellationRequested(); - if (!IsCrossWiredGeneric(serviceModel)) { + if (!IsCrossWiredGeneric(serviceModel)) + { continue; } @@ -229,12 +300,15 @@ protected override void ReportDiagnostics(SourceProductionContext context, Diagnostic.Create( DependencyModuleDiagnostics.CrossWireCannotBeGeneric, serviceModel.Location?.ToLocationOrNone(lookup) ?? Location.None, - typeName)); + typeName + ) + ); } var registerable = Registerable(data.Right); - if (registerable.Length > 0) { + if (registerable.Length > 0) + { ReportEnvironmentConditions(context, registerable, lookup, logger); } } @@ -250,22 +324,30 @@ protected override void ReportDiagnostics(SourceProductionContext context, /// meant to say. /// private static void ReportEnvironmentConditions( - SourceProductionContext context, ImmutableArray serviceModels, - SyntaxTreeLookup lookup, FileLogger logger) { - - foreach (var serviceModel in serviceModels) { - if (serviceModel.Conditions is not { Count: > 0 } conditions) { + SourceProductionContext context, + ImmutableArray serviceModels, + SyntaxTreeLookup lookup, + FileLogger logger + ) + { + foreach (var serviceModel in serviceModels) + { + if (serviceModel.Conditions is not { Count: > 0 } conditions) + { continue; } var typeName = serviceModel.ImplementationType.Name; - foreach (var condition in conditions) { - if (!EnvironmentConditionUtility.IsEmpty(condition)) { + foreach (var condition in conditions) + { + if (!EnvironmentConditionUtility.IsEmpty(condition)) + { continue; } - var kind = condition.Kind == EnvironmentConditionKind.Name ? "environment name" : "key"; + var kind = + condition.Kind == EnvironmentConditionKind.Name ? "environment name" : "key"; logger.Error($"'{typeName}' has an environment condition that names no {kind}."); @@ -274,7 +356,9 @@ private static void ReportEnvironmentConditions( DependencyModuleDiagnostics.EmptyEnvironmentCondition, serviceModel.Location?.ToLocationOrNone(lookup) ?? Location.None, typeName, - kind)); + kind + ) + ); } var described = conditions @@ -282,7 +366,8 @@ private static void ReportEnvironmentConditions( .Select(EnvironmentConditionUtility.Describe) .ToArray(); - if (described.Length == 0) { + if (described.Length == 0) + { continue; } @@ -294,25 +379,34 @@ private static void ReportEnvironmentConditions( Diagnostic.Create( DependencyModuleDiagnostics.RegisteredConditionally, serviceModel.Location?.ToLocationOrNone(lookup) ?? Location.None, - summary)); + summary + ) + ); } } private static bool IsUnconstructable(ServiceModel serviceModel) => // A factory supplies the instance, so the declaring type never has to be constructed. - serviceModel.Factory == null && - (serviceModel.Features.HasFlag(RegistrationFeature.AbstractImplementation) || - serviceModel.Features.HasFlag(RegistrationFeature.StaticImplementation)); + serviceModel.Factory == null + && ( + serviceModel.Features.HasFlag(RegistrationFeature.AbstractImplementation) + || serviceModel.Features.HasFlag(RegistrationFeature.StaticImplementation) + ); protected override ServiceModel IgnoredModel => ServiceModel.Ignore; - protected override IEqualityComparer GetComparer() { + protected override IEqualityComparer GetComparer() + { return _serviceEqualityComparer; } - protected override ServiceModel GenerateAttributeModel(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { + protected override ServiceModel GenerateAttributeModel( + GeneratorAttributeSyntaxContext context, + CancellationToken cancellationToken + ) + { var serviceModel = ServiceModelUtility.GetServiceModel(context, cancellationToken); - + return serviceModel ?? ServiceModel.Ignore; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator/SourceGenerator.cs b/src/DependencyModules.SourceGenerator/SourceGenerator.cs index 230a717..25b7731 100644 --- a/src/DependencyModules.SourceGenerator/SourceGenerator.cs +++ b/src/DependencyModules.SourceGenerator/SourceGenerator.cs @@ -10,9 +10,10 @@ namespace DependencyModules.SourceGenerator; /// module partial and a generator built on the same base class does not. /// [Generator] -public class SourceGenerator : BaseSourceGenerator { - - protected override IEnumerable AttributeSourceGenerators() { +public class SourceGenerator : BaseSourceGenerator +{ + protected override IEnumerable AttributeSourceGenerators() + { yield return new ServiceSourceGenerator(); yield return new InterceptorSourceGenerator(); yield return new global::DependencyModules.Conventions.ConventionGenerator(); @@ -23,9 +24,13 @@ protected override IEnumerable AttributeSource /// default, so that a third party building on it contributes to these modules rather than /// declaring every one of them a second time; this is the generator that claim belongs to. /// - protected override void SetupRootGenerator(IncrementalGeneratorInitializationContext context, - IncrementalValueProvider> valuesProvider) { - + protected override void SetupRootGenerator( + IncrementalGeneratorInitializationContext context, + IncrementalValueProvider< + ImmutableArray<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> + > valuesProvider + ) + { DependencyModuleWriter.Register(context, valuesProvider, generateAttribute: true); // DM0021. Registered here for the same reason DM0016 is: a framework generator loaded @@ -35,8 +40,10 @@ protected override void SetupRootGenerator(IncrementalGeneratorInitializationCon // DM0016. Registered here rather than on the base class so that a framework generator loaded // alongside this one does not report the same usage twice. context.RegisterSourceOutput( - valuesProvider.Combine(AssemblyModuleAttributeDiagnostics.Collect(context)) + valuesProvider + .Combine(AssemblyModuleAttributeDiagnostics.Collect(context)) .Combine(context.CompilationProvider), - AssemblyModuleAttributeDiagnostics.Report); + AssemblyModuleAttributeDiagnostics.Report + ); } -} \ No newline at end of file +} diff --git a/src/DependencyModules.SourceGenerator/TestAttributeDiagnostics.cs b/src/DependencyModules.SourceGenerator/TestAttributeDiagnostics.cs index eeae4d6..ca257b9 100644 --- a/src/DependencyModules.SourceGenerator/TestAttributeDiagnostics.cs +++ b/src/DependencyModules.SourceGenerator/TestAttributeDiagnostics.cs @@ -26,14 +26,21 @@ namespace DependencyModules.SourceGenerator; /// for — reporting that would be reporting the feature. /// /// -internal static class TestAttributeDiagnostics { - - internal static void Setup(IncrementalGeneratorInitializationContext context) { - var methods = context.SyntaxProvider - .CreateSyntaxProvider( +internal static class TestAttributeDiagnostics +{ + internal static void Setup(IncrementalGeneratorInitializationContext context) + { + var methods = context + .SyntaxProvider.CreateSyntaxProvider( static (node, _) => - node is MethodDeclarationSyntax { AttributeLists.Count: > 0, ParameterList.Parameters.Count: > 0 }, - Read) + node + is MethodDeclarationSyntax + { + AttributeLists.Count: > 0, + ParameterList.Parameters.Count: > 0 + }, + Read + ) .Where(static finding => finding != null) .Collect(); @@ -53,35 +60,46 @@ internal static void Setup(IncrementalGeneratorInitializationContext context) { /// private record Finding(string MethodName, string ServiceName, LocationModel Location); - private static Finding? Read(GeneratorSyntaxContext syntaxContext, System.Threading.CancellationToken cancellationToken) { + private static Finding? Read( + GeneratorSyntaxContext syntaxContext, + System.Threading.CancellationToken cancellationToken + ) + { var context = (SyntaxTransformContext)syntaxContext; var method = (MethodDeclarationSyntax)context.Node; var exported = ExportedServices(method, context, cancellationToken); - if (exported.Count == 0) { + if (exported.Count == 0) + { return null; } - foreach (var parameter in method.ParameterList.Parameters) { + foreach (var parameter in method.ParameterList.Parameters) + { cancellationToken.ThrowIfCancellationRequested(); - if (!CarriesMock(parameter, context, cancellationToken)) { + if (!CarriesMock(parameter, context, cancellationToken)) + { continue; } var parameterType = parameter.Type?.GetTypeDefinition(context); - if (parameterType == null) { + if (parameterType == null) + { continue; } - foreach (var service in exported) { - if (service.Equals(parameterType)) { + foreach (var service in exported) + { + if (service.Equals(parameterType)) + { return new Finding( method.Identifier.ToString(), service.Name, - LocationModel.From(parameter)); + LocationModel.From(parameter) + ); } } } @@ -95,26 +113,37 @@ private record Finding(string MethodName, string ServiceName, LocationModel Loca private static List ExportedServices( MethodDeclarationSyntax method, SyntaxTransformContext context, - System.Threading.CancellationToken cancellationToken) { - + System.Threading.CancellationToken cancellationToken + ) + { var services = new List(); - foreach (var attributeList in method.AttributeLists) { - foreach (var attribute in attributeList.Attributes) { - if (!AttributeTypeMatcher.Matches( + foreach (var attributeList in method.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + if ( + !AttributeTypeMatcher.Matches( context.SemanticModel, attribute, KnownTypes.DependencyModules.Testing.TestExportAttribute, - cancellationToken)) { + cancellationToken + ) + ) + { continue; } // The service is the first positional argument: [TestExport(typeof(IFoo), ...)]. - var first = attribute.ArgumentList?.Arguments.FirstOrDefault( - argument => argument.NameEquals == null); - - if (first?.Expression is TypeOfExpressionSyntax typeOf && - typeOf.Type.GetTypeDefinition(context) is { } service) { + var first = attribute.ArgumentList?.Arguments.FirstOrDefault(argument => + argument.NameEquals == null + ); + + if ( + first?.Expression is TypeOfExpressionSyntax typeOf + && typeOf.Type.GetTypeDefinition(context) is { } service + ) + { services.Add(service); } } @@ -126,15 +155,22 @@ private static List ExportedServices( private static bool CarriesMock( ParameterSyntax parameter, SyntaxTransformContext context, - System.Threading.CancellationToken cancellationToken) { - - foreach (var attributeList in parameter.AttributeLists) { - foreach (var attribute in attributeList.Attributes) { - if (AttributeTypeMatcher.Matches( + System.Threading.CancellationToken cancellationToken + ) + { + foreach (var attributeList in parameter.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + if ( + AttributeTypeMatcher.Matches( context.SemanticModel, attribute, KnownTypes.DependencyModules.Testing.MockAttribute, - cancellationToken)) { + cancellationToken + ) + ) + { return true; } } @@ -145,12 +181,18 @@ private static bool CarriesMock( private static void Report( SourceProductionContext context, - (System.Collections.Immutable.ImmutableArray Findings, Compilation Compilation) data) { - + ( + System.Collections.Immutable.ImmutableArray Findings, + Compilation Compilation + ) data + ) + { var lookup = new SyntaxTreeLookup(data.Compilation); - foreach (var finding in data.Findings) { - if (finding == null) { + foreach (var finding in data.Findings) + { + if (finding == null) + { continue; } @@ -161,7 +203,9 @@ private static void Report( DependencyModuleDiagnostics.MockAndTestExportOnOneMethod, finding.Location.ToLocationOrNone(lookup), finding.MethodName, - finding.ServiceName)); + finding.ServiceName + ) + ); } } } diff --git a/src/DependencyModules.Testing/Attributes/InjectValuesAttribute.cs b/src/DependencyModules.Testing/Attributes/InjectValuesAttribute.cs index a6a4a2b..ed0dd82 100644 --- a/src/DependencyModules.Testing/Attributes/InjectValuesAttribute.cs +++ b/src/DependencyModules.Testing/Attributes/InjectValuesAttribute.cs @@ -28,8 +28,10 @@ namespace DependencyModules.Testing.Attributes; /// [AttributeUsage(AttributeTargets.Parameter)] public class InjectValuesAttribute(params object[] value) - : Attribute, IInjectValueAttribute, ISharedTestRegistration { - + : Attribute, + IInjectValueAttribute, + ISharedTestRegistration +{ /// /// Provides the specified values for a method parameter during dependency /// injection and test execution. This method is part of the @@ -48,7 +50,8 @@ public class InjectValuesAttribute(params object[] value) /// An array of objects representing the values to be injected into the specified /// method parameter. /// - public object[] ProvideValue(IServiceProvider serviceProvider, ParameterInfo parameter) { + public object[] ProvideValue(IServiceProvider serviceProvider, ParameterInfo parameter) + { return value; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.Testing/Attributes/Interfaces/IInjectValueAttribute.cs b/src/DependencyModules.Testing/Attributes/Interfaces/IInjectValueAttribute.cs index ae4d711..babf97f 100644 --- a/src/DependencyModules.Testing/Attributes/Interfaces/IInjectValueAttribute.cs +++ b/src/DependencyModules.Testing/Attributes/Interfaces/IInjectValueAttribute.cs @@ -7,7 +7,8 @@ namespace DependencyModules.Testing.Attributes.Interfaces; /// This attribute is only applicable when the type is not registered with DI /// rather it's instantiated using ActivatorUtilities.CreateInstance /// -public interface IInjectValueAttribute { +public interface IInjectValueAttribute +{ /// /// Provides predefined values to method parameters during runtime. This method /// is part of the dependency injection process and retrieves values based on @@ -26,4 +27,4 @@ public interface IInjectValueAttribute { /// respective method parameters. /// object[] ProvideValue(IServiceProvider serviceProvider, ParameterInfo parameter); -} \ No newline at end of file +} diff --git a/src/DependencyModules.Testing/Attributes/Interfaces/IMockSupportAttribute.cs b/src/DependencyModules.Testing/Attributes/Interfaces/IMockSupportAttribute.cs index 6595acc..c726c86 100644 --- a/src/DependencyModules.Testing/Attributes/Interfaces/IMockSupportAttribute.cs +++ b/src/DependencyModules.Testing/Attributes/Interfaces/IMockSupportAttribute.cs @@ -8,7 +8,8 @@ namespace DependencyModules.Testing.Attributes.Interfaces; /// for specified types. It is typically used in conjunction with dependency injection /// to enable mocking capabilities in testing frameworks. /// -public interface IMockSupportAttribute { +public interface IMockSupportAttribute +{ /// /// Provides a mock object instance for the specified type. /// @@ -36,4 +37,4 @@ public interface IMockSupportAttribute { /// The test the container is being built for. /// The service a [Mock] parameter is about to register. bool RegistersService(ITestMethodContext testMethod, Type serviceType) => false; -} \ No newline at end of file +} diff --git a/src/DependencyModules.Testing/Attributes/Interfaces/IModuleTestAttribute.cs b/src/DependencyModules.Testing/Attributes/Interfaces/IModuleTestAttribute.cs index 3a82b0b..852a3f4 100644 --- a/src/DependencyModules.Testing/Attributes/Interfaces/IModuleTestAttribute.cs +++ b/src/DependencyModules.Testing/Attributes/Interfaces/IModuleTestAttribute.cs @@ -12,12 +12,10 @@ namespace DependencyModules.Testing.Attributes.Interfaces; /// Implemented by the integrations, not by test authors. A test names its modules through the /// [ModuleTest] attribute of whichever framework it is written against. /// -public interface IModuleTestAttribute { - +public interface IModuleTestAttribute +{ /// /// The module types to load, in declaration order. Empty when a test names none. /// - Type[] ModuleTypes { - get; - } + Type[] ModuleTypes { get; } } diff --git a/src/DependencyModules.Testing/Attributes/Interfaces/IOrderedAttribute.cs b/src/DependencyModules.Testing/Attributes/Interfaces/IOrderedAttribute.cs index 260d3bd..f16dd9c 100644 --- a/src/DependencyModules.Testing/Attributes/Interfaces/IOrderedAttribute.cs +++ b/src/DependencyModules.Testing/Attributes/Interfaces/IOrderedAttribute.cs @@ -10,7 +10,8 @@ namespace DependencyModules.Testing.Attributes.Interfaces; /// their precedence. This can be used in frameworks, test cases, or other /// systems requiring deterministic or prioritized execution flows. /// -public interface IOrderedAttribute { +public interface IOrderedAttribute +{ /// /// Represents the execution or processing order assigned to an object or component. /// This property is used to specify the precedence or priority of the object @@ -20,4 +21,4 @@ public interface IOrderedAttribute { /// The default value of the property is 10 if not explicitly overridden by the implementer. /// int Order => 10; -} \ No newline at end of file +} diff --git a/src/DependencyModules.Testing/Attributes/Interfaces/IServiceProviderBuilderAttribute.cs b/src/DependencyModules.Testing/Attributes/Interfaces/IServiceProviderBuilderAttribute.cs index a2bdd4d..a4913fd 100644 --- a/src/DependencyModules.Testing/Attributes/Interfaces/IServiceProviderBuilderAttribute.cs +++ b/src/DependencyModules.Testing/Attributes/Interfaces/IServiceProviderBuilderAttribute.cs @@ -15,13 +15,16 @@ namespace DependencyModules.Testing.Attributes.Interfaces; /// it would not otherwise get, such as scope validation. It runs last, after every other hook has /// contributed, so it is also the final chance to inspect or amend the collection. /// -public interface IServiceProviderBuilderAttribute { - +public interface IServiceProviderBuilderAttribute +{ /// /// Builds the container for the test. /// /// The test the container is being built for. /// The fully populated collection. /// The container the test resolves its parameters and services from. - IServiceProvider BuildServiceProvider(ITestMethodContext testMethod, IServiceCollection serviceCollection); + IServiceProvider BuildServiceProvider( + ITestMethodContext testMethod, + IServiceCollection serviceCollection + ); } diff --git a/src/DependencyModules.Testing/Attributes/Interfaces/ISharedTestRegistration.cs b/src/DependencyModules.Testing/Attributes/Interfaces/ISharedTestRegistration.cs index bb99c00..748e335 100644 --- a/src/DependencyModules.Testing/Attributes/Interfaces/ISharedTestRegistration.cs +++ b/src/DependencyModules.Testing/Attributes/Interfaces/ISharedTestRegistration.cs @@ -1,4 +1,5 @@ using System.Reflection; + namespace DependencyModules.Testing.Attributes.Interfaces; /// @@ -33,8 +34,8 @@ namespace DependencyModules.Testing.Attributes.Interfaces; /// attribute to work is a rule most tests will not get. /// /// -public interface ISharedTestRegistration { - +public interface ISharedTestRegistration +{ /// /// Whether what this attribute registered is pinned. /// diff --git a/src/DependencyModules.Testing/Attributes/Interfaces/ITestContainerSource.cs b/src/DependencyModules.Testing/Attributes/Interfaces/ITestContainerSource.cs index f2d7fcd..7cd3453 100644 --- a/src/DependencyModules.Testing/Attributes/Interfaces/ITestContainerSource.cs +++ b/src/DependencyModules.Testing/Attributes/Interfaces/ITestContainerSource.cs @@ -23,8 +23,8 @@ namespace DependencyModules.Testing.Attributes.Interfaces; /// request through a chain that was never assembled. /// /// -public interface ITestContainerSource { - +public interface ITestContainerSource +{ /// /// A container built from the test's composition, started, and owned by the runner. /// diff --git a/src/DependencyModules.Testing/Attributes/Interfaces/ITestMethodContext.cs b/src/DependencyModules.Testing/Attributes/Interfaces/ITestMethodContext.cs index 11c0ff1..3b043ad 100644 --- a/src/DependencyModules.Testing/Attributes/Interfaces/ITestMethodContext.cs +++ b/src/DependencyModules.Testing/Attributes/Interfaces/ITestMethodContext.cs @@ -12,8 +12,8 @@ namespace DependencyModules.Testing.Attributes.Interfaces; /// dependency on it would bind every mocking package to a single test framework. This carries the part /// that is common; a framework integration supplies its own implementation over its own model. /// -public interface ITestMethodContext { - +public interface ITestMethodContext +{ /// /// The method under test. /// @@ -22,9 +22,7 @@ public interface ITestMethodContext { /// AttributeUtility hang off this, and it is the same instance the framework integration /// reads parameters from, so a hook sees exactly the signature the test will be invoked with. /// - MethodInfo Method { - get; - } + MethodInfo Method { get; } /// /// Every attribute in scope for the method, widest scope first: assembly, then declaring type, @@ -38,7 +36,5 @@ MethodInfo Method { /// "the most specific one wins" and so looks at the method first. This list is in the order things /// are applied, where the most specific runs last and therefore wins. /// - IReadOnlyList Attributes { - get; - } + IReadOnlyList Attributes { get; } } diff --git a/src/DependencyModules.Testing/Attributes/Interfaces/ITestParameterValueProvider.cs b/src/DependencyModules.Testing/Attributes/Interfaces/ITestParameterValueProvider.cs index 3c2248a..4450b92 100644 --- a/src/DependencyModules.Testing/Attributes/Interfaces/ITestParameterValueProvider.cs +++ b/src/DependencyModules.Testing/Attributes/Interfaces/ITestParameterValueProvider.cs @@ -15,8 +15,8 @@ namespace DependencyModules.Testing.Attributes.Interfaces; /// runs before the container is built, so a parameter can change what the service under test is /// constructed with, not merely what the test itself ends up holding. /// -public interface ITestParameterValueProvider { - +public interface ITestParameterValueProvider +{ /// /// Adds whatever services are needed to supply this parameter. /// @@ -24,7 +24,10 @@ public interface ITestParameterValueProvider { /// The collection backing the test's container. /// The parameter being supplied. void SetupServiceCollection( - ITestMethodContext testMethod, IServiceCollection serviceCollection, ParameterInfo parameter); + ITestMethodContext testMethod, + IServiceCollection serviceCollection, + ParameterInfo parameter + ); /// /// Produces the value to pass for this parameter. @@ -37,5 +40,8 @@ void SetupServiceCollection( /// that the parameter is resolved from the container like any other. /// Task GetParameterValueAsync( - ITestMethodContext testMethod, IServiceProvider serviceProvider, ParameterInfo parameter); + ITestMethodContext testMethod, + IServiceProvider serviceProvider, + ParameterInfo parameter + ); } diff --git a/src/DependencyModules.Testing/Attributes/Interfaces/ITestServiceSetupAttribute.cs b/src/DependencyModules.Testing/Attributes/Interfaces/ITestServiceSetupAttribute.cs index db2cdb0..6f34721 100644 --- a/src/DependencyModules.Testing/Attributes/Interfaces/ITestServiceSetupAttribute.cs +++ b/src/DependencyModules.Testing/Attributes/Interfaces/ITestServiceSetupAttribute.cs @@ -17,12 +17,15 @@ namespace DependencyModules.Testing.Attributes.Interfaces; /// Runs before the container is built, in widest-scope-first order. Registrations are last-one-wins, /// so an attribute on the method overrides the same service registered from the assembly. /// -public interface ITestServiceSetupAttribute { - +public interface ITestServiceSetupAttribute +{ /// /// Adds services for the given test. /// /// The test the container is being built for. /// The collection backing the test's container. - void SetupServiceCollection(ITestMethodContext testMethod, IServiceCollection serviceCollection); + void SetupServiceCollection( + ITestMethodContext testMethod, + IServiceCollection serviceCollection + ); } diff --git a/src/DependencyModules.Testing/Attributes/Interfaces/ITestStartupAttribute.cs b/src/DependencyModules.Testing/Attributes/Interfaces/ITestStartupAttribute.cs index ff2ef4b..a989db0 100644 --- a/src/DependencyModules.Testing/Attributes/Interfaces/ITestStartupAttribute.cs +++ b/src/DependencyModules.Testing/Attributes/Interfaces/ITestStartupAttribute.cs @@ -11,8 +11,8 @@ namespace DependencyModules.Testing.Attributes.Interfaces; /// /// Applies to a method, a class or an assembly, and is found by walking that chain. /// -public interface ITestStartupAttribute { - +public interface ITestStartupAttribute +{ /// /// Performs whatever asynchronous setup the test needs — seeding a store, opening a connection, /// priming a cache. diff --git a/src/DependencyModules.Testing/Attributes/MockAttribute.cs b/src/DependencyModules.Testing/Attributes/MockAttribute.cs index c3bff27..ed812f6 100644 --- a/src/DependencyModules.Testing/Attributes/MockAttribute.cs +++ b/src/DependencyModules.Testing/Attributes/MockAttribute.cs @@ -32,11 +32,9 @@ namespace DependencyModules.Testing.Attributes; /// the whole bar for declaring an attribute shared - not that isolating it would be unusual, but that /// it would have no coherent reading. /// -[AttributeUsage( - AttributeTargets.Parameter, - AllowMultiple = true)] -public class MockAttribute : Attribute, ITestParameterValueProvider, ISharedTestRegistration { - +[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)] +public class MockAttribute : Attribute, ITestParameterValueProvider, ISharedTestRegistration +{ /// /// Registers the double in place of the parameter's service. /// @@ -53,17 +51,25 @@ public class MockAttribute : Attribute, ITestParameterValueProvider, ISharedTest /// Thrown when a required mock library is not found, indicating that the type or assembly is not correctly attributed. /// public void SetupServiceCollection( - ITestMethodContext testMethod, IServiceCollection serviceCollection, ParameterInfo parameter) { + ITestMethodContext testMethod, + IServiceCollection serviceCollection, + ParameterInfo parameter + ) + { var mockAttribute = testMethod.Method.GetTestAttribute(); - if (mockAttribute == null) { - throw new Exception("Mock library not found, please ensure the Type or Assembly is attributed correctly."); + if (mockAttribute == null) + { + throw new Exception( + "Mock library not found, please ensure the Type or Assembly is attributed correctly." + ); } // The mock library owns this type for this test - a Moq test naming both Mock and // [Mock] IFoo wants one mock seen two ways, and registering a second one here would leave // the test configuring one while the container handed out another. - if (mockAttribute.RegistersService(testMethod, parameter.ParameterType)) { + if (mockAttribute.RegistersService(testMethod, parameter.ParameterType)) + { return; } @@ -74,10 +80,17 @@ public void SetupServiceCollection( // the keyed registration — the one the consumer actually injects — untouched, so the service // under test kept the real implementation while the test held a double it believed was wired // in. The arrangement ran, the double recorded nothing, and the assertion failed elsewhere. - if (key == null) { + if (key == null) + { serviceCollection.AddSingleton(parameter.ParameterType, _ => mockedValue); - } else { - serviceCollection.AddKeyedSingleton(parameter.ParameterType, key, (_, _) => mockedValue); + } + else + { + serviceCollection.AddKeyedSingleton( + parameter.ParameterType, + key, + (_, _) => mockedValue + ); } } @@ -98,11 +111,18 @@ public void SetupServiceCollection( /// or null if the parameter could not be resolved. /// public Task GetParameterValueAsync( - ITestMethodContext testMethod, IServiceProvider serviceProvider, ParameterInfo parameter) { + ITestMethodContext testMethod, + IServiceProvider serviceProvider, + ParameterInfo parameter + ) + { var key = ServiceKeyOf(parameter); - if (key != null && serviceProvider is IKeyedServiceProvider keyedServiceProvider) { - return Task.FromResult(keyedServiceProvider.GetKeyedService(parameter.ParameterType, key)); + if (key != null && serviceProvider is IKeyedServiceProvider keyedServiceProvider) + { + return Task.FromResult( + keyedServiceProvider.GetKeyedService(parameter.ParameterType, key) + ); } return Task.FromResult(serviceProvider.GetService(parameter.ParameterType)); diff --git a/src/DependencyModules.Testing/Attributes/TestExportAttribute.cs b/src/DependencyModules.Testing/Attributes/TestExportAttribute.cs index b745b27..4122e53 100644 --- a/src/DependencyModules.Testing/Attributes/TestExportAttribute.cs +++ b/src/DependencyModules.Testing/Attributes/TestExportAttribute.cs @@ -29,11 +29,11 @@ namespace DependencyModules.Testing.Attributes; /// /// [AttributeUsage( - AttributeTargets.Assembly | - AttributeTargets.Class | - AttributeTargets.Method, - AllowMultiple = true)] -public class TestExportAttribute : Attribute, ITestServiceSetupAttribute, ISharedTestRegistration { + AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method, + AllowMultiple = true +)] +public class TestExportAttribute : Attribute, ITestServiceSetupAttribute, ISharedTestRegistration +{ /// /// An attribute that configures and exports services to the dependency injection container /// for test scenarios. This supports customized service registrations with specific lifetimes @@ -49,7 +49,8 @@ public class TestExportAttribute : Attribute, ITestServiceSetupAttribute, IShare /// This attribute is not guaranteed to be thread-safe and should be used carefully when /// dealing with parallel or concurrent test execution. /// - public TestExportAttribute(Type service) { + public TestExportAttribute(Type service) + { Service = service; } @@ -57,27 +58,19 @@ public TestExportAttribute(Type service) { /// Gets the service type to be registered in the service collection. /// The specified type represents the service interface or base type for dependency injection. /// - public Type Service { - get; - } + public Type Service { get; } /// /// Gets or sets the implementation type to be registered for the associated service in the service collection. /// If no value is provided, the service type will be used as the implementation type by default. /// - public Type? Implementation { - get; - set; - } + public Type? Implementation { get; set; } /// /// Gets or sets the lifetime of the service in the dependency injection container. /// Determines whether the service is registered as singleton, scoped, or transient. /// - public ServiceLifetime Lifetime { - get; - set; - } = ServiceLifetime.Transient; + public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Transient; /// /// Whether this export is kept across every container the test builds, rather than registered @@ -106,10 +99,7 @@ public ServiceLifetime Lifetime { /// what was asked for. /// /// - public bool Shared { - get; - set; - } + public bool Shared { get; set; } /// /// The exported service, which is what pinning applies to when is set. @@ -120,7 +110,6 @@ public bool Shared { /// IReadOnlyList ISharedTestRegistration.SharedServices => [Service]; - /// /// Configures the service collection for a test method by adding services with specified lifetimes. /// This method enables dynamic service registration during test execution, supporting dependency injection setup. @@ -133,10 +122,15 @@ public bool Shared { /// The service collection to which services are added. This collection is used to configure /// the dependency injection container for the test's execution environment. /// - public void SetupServiceCollection(ITestMethodContext testMethod, IServiceCollection serviceCollection) { + public void SetupServiceCollection( + ITestMethodContext testMethod, + IServiceCollection serviceCollection + ) + { var implementation = Implementation ?? Service; - switch (Lifetime) { + switch (Lifetime) + { case ServiceLifetime.Singleton: serviceCollection.AddSingleton(Service, implementation); break; @@ -148,4 +142,4 @@ public void SetupServiceCollection(ITestMethodContext testMethod, IServiceCollec break; } } -} \ No newline at end of file +} diff --git a/src/DependencyModules.Testing/Impl/AttributeUtility.cs b/src/DependencyModules.Testing/Impl/AttributeUtility.cs index c2253ec..4411569 100644 --- a/src/DependencyModules.Testing/Impl/AttributeUtility.cs +++ b/src/DependencyModules.Testing/Impl/AttributeUtility.cs @@ -6,8 +6,8 @@ namespace DependencyModules.Testing.Impl; /// /// Provides utility methods for retrieving attributes from methods, parameters, classes, or assemblies. /// -public static class AttributeUtility { - +public static class AttributeUtility +{ /// /// Retrieves an attribute of the specified type from a method. /// Searches on the method, then its declaring type, and finally its assembly. @@ -15,12 +15,19 @@ public static class AttributeUtility { /// The type of the attribute to retrieve. /// The method from which to retrieve the attribute. /// The first matching attribute of the specified type, or null if no attribute is found. - public static T? GetTestAttribute(this MethodInfo methodInfo) where T : class { - var returnAttribute = methodInfo.GetOrderedCustomAttributes().FirstOrDefault(a => a is T) ?? - methodInfo.DeclaringType?.GetTypeInfo().GetOrderedCustomAttributes() - .FirstOrDefault(a => a is T) ?? - methodInfo.DeclaringType?.GetTypeInfo().Assembly.GetOrderedCustomAttributes() - .FirstOrDefault(a => a is T); + public static T? GetTestAttribute(this MethodInfo methodInfo) + where T : class + { + var returnAttribute = + methodInfo.GetOrderedCustomAttributes().FirstOrDefault(a => a is T) + ?? methodInfo + .DeclaringType?.GetTypeInfo() + .GetOrderedCustomAttributes() + .FirstOrDefault(a => a is T) + ?? methodInfo + .DeclaringType?.GetTypeInfo() + .Assembly.GetOrderedCustomAttributes() + .FirstOrDefault(a => a is T); return returnAttribute as T; } @@ -32,25 +39,32 @@ public static class AttributeUtility { /// The type of the attribute to retrieve. /// The parameter from which to retrieve the attribute. /// The first matching attribute of the specified type, or null if no attribute is found. - public static T? GetTestAttribute(this ParameterInfo parameterInfo) where T : class { + public static T? GetTestAttribute(this ParameterInfo parameterInfo) + where T : class + { var attribute = parameterInfo.GetOrderedCustomAttributes().FirstOrDefault(a => a is T); - if (attribute != null) { + if (attribute != null) + { return attribute as T; } var methodInfo = parameterInfo.Member; - var returnAttribute = methodInfo.GetOrderedCustomAttributes().FirstOrDefault(a => a is T) ?? - methodInfo.DeclaringType?.GetTypeInfo().GetOrderedCustomAttributes() - .FirstOrDefault(a => a is T) ?? - methodInfo.DeclaringType?.GetTypeInfo().Assembly.GetOrderedCustomAttributes() - .FirstOrDefault(a => a is T); + var returnAttribute = + methodInfo.GetOrderedCustomAttributes().FirstOrDefault(a => a is T) + ?? methodInfo + .DeclaringType?.GetTypeInfo() + .GetOrderedCustomAttributes() + .FirstOrDefault(a => a is T) + ?? methodInfo + .DeclaringType?.GetTypeInfo() + .Assembly.GetOrderedCustomAttributes() + .FirstOrDefault(a => a is T); return returnAttribute as T; } - /// /// Retrieves all attributes of the specified type from a method. /// Searches the method, its declaring type, and its assembly in order to accumulate matching attributes. @@ -58,13 +72,23 @@ public static class AttributeUtility { /// The type of the attributes to retrieve. /// The method from which to retrieve the attributes. /// An enumerable collection of attributes of the specified type. - public static IEnumerable GetTestAttributes(this MethodInfo methodInfo) where T : class { + public static IEnumerable GetTestAttributes(this MethodInfo methodInfo) + where T : class + { var returnList = new List(); - if (methodInfo.DeclaringType != null) { - returnList.AddRange(methodInfo.DeclaringType.GetTypeInfo().Assembly.GetOrderedCustomAttributes().OfType()); - - returnList.AddRange(methodInfo.DeclaringType.GetTypeInfo().GetOrderedCustomAttributes().OfType()); + if (methodInfo.DeclaringType != null) + { + returnList.AddRange( + methodInfo + .DeclaringType.GetTypeInfo() + .Assembly.GetOrderedCustomAttributes() + .OfType() + ); + + returnList.AddRange( + methodInfo.DeclaringType.GetTypeInfo().GetOrderedCustomAttributes().OfType() + ); } returnList.AddRange(methodInfo.GetOrderedCustomAttributes().OfType()); @@ -72,7 +96,6 @@ public static IEnumerable GetTestAttributes(this MethodInfo methodInfo) wh return returnList; } - /// /// Retrieves all attributes of the specified type from a parameterInfo. /// Searches on the method, its declaring type, and its assembly in order. @@ -80,15 +103,25 @@ public static IEnumerable GetTestAttributes(this MethodInfo methodInfo) wh /// The type of the attributes to retrieve. /// The parameter from which to retrieve the attributes. /// A collection of matching attributes of the specified type, or an empty collection if no attributes are found. - public static IEnumerable GetTestAttributes(this ParameterInfo parameterInfo) where T : class { + public static IEnumerable GetTestAttributes(this ParameterInfo parameterInfo) + where T : class + { var returnList = new List(); var methodInfo = parameterInfo.Member; - if (methodInfo.DeclaringType != null) { - returnList.AddRange(methodInfo.DeclaringType.GetTypeInfo().Assembly.GetOrderedCustomAttributes().OfType()); - - returnList.AddRange(methodInfo.DeclaringType.GetTypeInfo().GetOrderedCustomAttributes().OfType()); + if (methodInfo.DeclaringType != null) + { + returnList.AddRange( + methodInfo + .DeclaringType.GetTypeInfo() + .Assembly.GetOrderedCustomAttributes() + .OfType() + ); + + returnList.AddRange( + methodInfo.DeclaringType.GetTypeInfo().GetOrderedCustomAttributes().OfType() + ); } returnList.AddRange(methodInfo.GetOrderedCustomAttributes().OfType()); @@ -98,51 +131,63 @@ public static IEnumerable GetTestAttributes(this ParameterInfo parameterIn return returnList; } - private static IEnumerable GetOrderedCustomAttributes(this Assembly assembly) { - return assembly.GetCustomAttributes() - .Order(new SortCustomAttribute(assembly)); + private static IEnumerable GetOrderedCustomAttributes(this Assembly assembly) + { + return assembly.GetCustomAttributes().Order(new SortCustomAttribute(assembly)); } - - private static IEnumerable GetOrderedCustomAttributes(this Type type) { - return type.GetCustomAttributes() - .Order(new SortCustomAttribute(type.Assembly)); + + private static IEnumerable GetOrderedCustomAttributes(this Type type) + { + return type.GetCustomAttributes().Order(new SortCustomAttribute(type.Assembly)); } - - private static IEnumerable GetOrderedCustomAttributes(this MemberInfo? memberInfo) { - if (memberInfo?.DeclaringType == null) { + + private static IEnumerable GetOrderedCustomAttributes(this MemberInfo? memberInfo) + { + if (memberInfo?.DeclaringType == null) + { return ArraySegment.Empty; } - return memberInfo.GetCustomAttributes() + return memberInfo + .GetCustomAttributes() .Order(new SortCustomAttribute(memberInfo.DeclaringType.Assembly)); } - - private static IEnumerable GetOrderedCustomAttributes(this ParameterInfo? parameterInfo) { - if (parameterInfo?.Member.DeclaringType == null) { + + private static IEnumerable GetOrderedCustomAttributes( + this ParameterInfo? parameterInfo + ) + { + if (parameterInfo?.Member.DeclaringType == null) + { return ArraySegment.Empty; } - return parameterInfo.GetCustomAttributes() + return parameterInfo + .GetCustomAttributes() .Order(new SortCustomAttribute(parameterInfo.Member.DeclaringType.Assembly)); } /// /// Provides a custom attribute sorting mechanism based on the association of attributes with a specified assembly. /// - private class SortCustomAttribute(Assembly testAssembly) : IComparer { - - public int Compare(Attribute? x, Attribute? y) { - if (testAssembly.Equals(x?.GetType().Assembly)) { - if (testAssembly.Equals(y?.GetType().Assembly)) { + private class SortCustomAttribute(Assembly testAssembly) : IComparer + { + public int Compare(Attribute? x, Attribute? y) + { + if (testAssembly.Equals(x?.GetType().Assembly)) + { + if (testAssembly.Equals(y?.GetType().Assembly)) + { return 0; } return 1; } - else if (testAssembly.Equals(y?.GetType().Assembly)) { + else if (testAssembly.Equals(y?.GetType().Assembly)) + { return -1; } - + return 0; } } -} \ No newline at end of file +} diff --git a/src/DependencyModules.Testing/Impl/SharedRegistrations.cs b/src/DependencyModules.Testing/Impl/SharedRegistrations.cs index ff21654..91de293 100644 --- a/src/DependencyModules.Testing/Impl/SharedRegistrations.cs +++ b/src/DependencyModules.Testing/Impl/SharedRegistrations.cs @@ -10,8 +10,8 @@ namespace DependencyModules.Testing.Impl; /// Shared by every integration, because the rule is the same one wherever the test runs and only the /// discovery around it differs. /// -public static class SharedRegistrations { - +public static class SharedRegistrations +{ /// /// Types the harness itself can never pin, whatever anything else says. /// @@ -56,21 +56,30 @@ public static class SharedRegistrations { /// /// The attributes in scope for the test, widest first, as the runner collected them. /// - public static IReadOnlyCollection Collect(MethodInfo method, IEnumerable knownAttributes) { - var attributes = knownAttributes as IReadOnlyCollection ?? knownAttributes.ToArray(); + public static IReadOnlyCollection Collect( + MethodInfo method, + IEnumerable knownAttributes + ) + { + var attributes = + knownAttributes as IReadOnlyCollection ?? knownAttributes.ToArray(); var isolated = new HashSet(NeverPinned); - foreach (var registration in attributes.OfType()) { - foreach (var service in registration.IsolatedServices(method)) { + foreach (var registration in attributes.OfType()) + { + foreach (var service in registration.IsolatedServices(method)) + { isolated.Add(service); } } var pinned = new HashSet(); - foreach (var parameter in method.GetParameters()) { - var declarations = parameter.GetCustomAttributes() + foreach (var parameter in method.GetParameters()) + { + var declarations = parameter + .GetCustomAttributes() .OfType() .ToArray(); @@ -78,25 +87,31 @@ public static IReadOnlyCollection Collect(MethodInfo method, IEnumerable !declaration.Shared)) { + if (declarations.Any(declaration => !declaration.Shared)) + { continue; } pinned.Add(parameter.ParameterType); - foreach (var declaration in declarations) { - foreach (var service in declaration.SharedServices) { + foreach (var declaration in declarations) + { + foreach (var service in declaration.SharedServices) + { pinned.Add(service); } } } - foreach (var registration in attributes.OfType()) { - if (!registration.Shared) { + foreach (var registration in attributes.OfType()) + { + if (!registration.Shared) + { continue; } - foreach (var service in registration.SharedServices) { + foreach (var service in registration.SharedServices) + { pinned.Add(service); } } diff --git a/src/DependencyModules.Testing/Impl/TestContainerSource.cs b/src/DependencyModules.Testing/Impl/TestContainerSource.cs index 147917a..8a929eb 100644 --- a/src/DependencyModules.Testing/Impl/TestContainerSource.cs +++ b/src/DependencyModules.Testing/Impl/TestContainerSource.cs @@ -22,7 +22,8 @@ namespace DependencyModules.Testing.Impl; /// them - pays for none of this. /// /// -public sealed class TestContainerSource : ITestContainerSource { +public sealed class TestContainerSource : ITestContainerSource +{ private readonly object _gate = new(); private Composition? _composition; @@ -43,17 +44,22 @@ public void Initialize( IReadOnlyCollection pinnedServices, Func build, Func start, - Action track) { + Action track + ) + { _composition = new Composition(services, pinned, pinnedServices, build, start, track); } /// - public async ValueTask CreateAsync() { - var composition = _composition - ?? throw new InvalidOperationException( - $"This {nameof(TestContainerSource)} was never initialized, so there is " + - "nothing to build a container from. The runner does that once the test's " + - "own container exists."); + public async ValueTask CreateAsync() + { + var composition = + _composition + ?? throw new InvalidOperationException( + $"This {nameof(TestContainerSource)} was never initialized, so there is " + + "nothing to build a container from. The runner does that once the test's " + + "own container exists." + ); var provider = composition.Build(Template(composition)); @@ -69,12 +75,15 @@ public async ValueTask CreateAsync() { /// twice would take a second set of pinned instances out of the first container - leaving two /// objects where the whole point is one. /// - private IServiceCollection Template(Composition composition) { - if (_template != null) { + private IServiceCollection Template(Composition composition) + { + if (_template != null) + { return _template; } - lock (_gate) { + lock (_gate) + { return _template ??= BuildTemplate(composition); } } @@ -103,27 +112,34 @@ private IServiceCollection Template(Composition composition) { /// without anyone asking. An open generic is left alone too, having no closed type to resolve. /// /// - private static IServiceCollection BuildTemplate(Composition composition) { + private static IServiceCollection BuildTemplate(Composition composition) + { var instances = Resolve(composition); IServiceCollection template = new ServiceCollection(); var taken = new HashSet(); - foreach (var descriptor in composition.Services) { + foreach (var descriptor in composition.Services) + { var serviceType = descriptor.ServiceType; - if (!instances.TryGetValue(serviceType, out var pinned) || - descriptor.ImplementationInstance != null) { + if ( + !instances.TryGetValue(serviceType, out var pinned) + || descriptor.ImplementationInstance != null + ) + { template.Add(descriptor); continue; } - if (!taken.Add(serviceType)) { + if (!taken.Add(serviceType)) + { continue; } - foreach (var instance in pinned) { + foreach (var instance in pinned) + { template.Add(new ServiceDescriptor(serviceType, instance)); } } @@ -160,28 +176,35 @@ private static IServiceCollection BuildTemplate(Composition composition) { /// An open generic is skipped, having no closed type to resolve. /// /// - private static Dictionary Resolve(Composition composition) { + private static Dictionary Resolve(Composition composition) + { var instances = new Dictionary(); - foreach (var serviceType in composition.PinnedServices) { - if (serviceType.IsGenericTypeDefinition || serviceType.IsByRef || serviceType.IsPointer) { + foreach (var serviceType in composition.PinnedServices) + { + if (serviceType.IsGenericTypeDefinition || serviceType.IsByRef || serviceType.IsPointer) + { continue; } object[] resolved; - try { + try + { var sequence = typeof(IEnumerable<>).MakeGenericType(serviceType); resolved = ((IEnumerable)composition.Pinned.GetRequiredService(sequence)) .Cast() .Where(instance => instance != null) .ToArray(); - } catch (Exception) { + } + catch (Exception) + { continue; } - if (resolved.Length > 0) { + if (resolved.Length > 0) + { instances[serviceType] = resolved; } } @@ -195,5 +218,6 @@ private sealed record Composition( IReadOnlyCollection PinnedServices, Func Build, Func Start, - Action Track); + Action Track + ); } diff --git a/src/DependencyModules.Testing/Impl/TestParameterResolver.cs b/src/DependencyModules.Testing/Impl/TestParameterResolver.cs index 3fca5b4..afe69fc 100644 --- a/src/DependencyModules.Testing/Impl/TestParameterResolver.cs +++ b/src/DependencyModules.Testing/Impl/TestParameterResolver.cs @@ -20,16 +20,19 @@ namespace DependencyModules.Testing.Impl; /// once there is a provider to resolve from. One instance belongs /// to one container: a data-driven test that builds a container per row wants a resolver per row too. /// -public sealed class TestParameterResolver { +public sealed class TestParameterResolver +{ private readonly ITestMethodContext _testMethod; - private readonly Dictionary> _valueProviders = new(); + private readonly Dictionary> _valueProviders = + new(); private bool _setupRan; /// /// Creates a resolver for one test method and one container. /// /// The test whose parameters are being supplied. - public TestParameterResolver(ITestMethodContext testMethod) { + public TestParameterResolver(ITestMethodContext testMethod) + { _testMethod = testMethod; } @@ -42,13 +45,19 @@ public TestParameterResolver(ITestMethodContext testMethod) { /// constructed against it — rather than the test being handed a double nothing else can see. /// /// The collection backing the test's container. - public void SetupServiceCollection(IServiceCollection serviceCollection) { - foreach (var parameterInfo in _testMethod.Method.GetParameters()) { - var providers = parameterInfo.GetCustomAttributes().OfType().ToList(); + public void SetupServiceCollection(IServiceCollection serviceCollection) + { + foreach (var parameterInfo in _testMethod.Method.GetParameters()) + { + var providers = parameterInfo + .GetCustomAttributes() + .OfType() + .ToList(); _valueProviders.Add(parameterInfo, providers); - foreach (var valueProvider in providers) { + foreach (var valueProvider in providers) + { valueProvider.SetupServiceCollection(_testMethod, serviceCollection, parameterInfo); } } @@ -76,17 +85,24 @@ public void SetupServiceCollection(IServiceCollection serviceCollection) { /// would quietly skip every parameter attribute, so a [Mock] parameter would hand back the /// real service instead of a substitute. /// - public async Task ResolveArgumentsAsync(IServiceProvider serviceProvider, object?[] data) { - if (!_setupRan) { + public async Task ResolveArgumentsAsync( + IServiceProvider serviceProvider, + object?[] data + ) + { + if (!_setupRan) + { throw new InvalidOperationException( - $"{nameof(SetupServiceCollection)} must be called before {nameof(ResolveArgumentsAsync)}, " + - "while the service collection can still be added to."); + $"{nameof(SetupServiceCollection)} must be called before {nameof(ResolveArgumentsAsync)}, " + + "while the service collection can still be added to." + ); } var parameterList = _testMethod.Method.GetParameters(); var arguments = new List(data); - for (var i = data.Length; i < parameterList.Length; i++) { + for (var i = data.Length; i < parameterList.Length; i++) + { var parameterInfo = parameterList[i]; var value = await ResolveFromParameterProviders(parameterInfo, serviceProvider); @@ -103,15 +119,25 @@ public void SetupServiceCollection(IServiceCollection serviceCollection) { /// because a test asking for the container itself cannot be resolved from it. /// private async Task ResolveFromParameterProviders( - ParameterInfo parameterInfo, IServiceProvider serviceProvider) { - if (parameterInfo.ParameterType == typeof(IServiceProvider)) { + ParameterInfo parameterInfo, + IServiceProvider serviceProvider + ) + { + if (parameterInfo.ParameterType == typeof(IServiceProvider)) + { return serviceProvider; } - foreach (var valueProvider in _valueProviders[parameterInfo]) { - var value = await valueProvider.GetParameterValueAsync(_testMethod, serviceProvider, parameterInfo); + foreach (var valueProvider in _valueProviders[parameterInfo]) + { + var value = await valueProvider.GetParameterValueAsync( + _testMethod, + serviceProvider, + parameterInfo + ); - if (value != null) { + if (value != null) + { return value; } } @@ -119,15 +145,26 @@ public void SetupServiceCollection(IServiceCollection serviceCollection) { return null; } - private object? ResolveFromContainer(ParameterInfo parameterInfo, IServiceProvider serviceProvider) { + private object? ResolveFromContainer( + ParameterInfo parameterInfo, + IServiceProvider serviceProvider + ) + { var keyedServicesAttribute = parameterInfo.GetCustomAttribute(); - if (keyedServicesAttribute != null && serviceProvider is IKeyedServiceProvider keyedServiceProvider) { - return keyedServiceProvider.GetKeyedService(parameterInfo.ParameterType, keyedServicesAttribute.Key); + if ( + keyedServicesAttribute != null + && serviceProvider is IKeyedServiceProvider keyedServiceProvider + ) + { + return keyedServiceProvider.GetKeyedService( + parameterInfo.ParameterType, + keyedServicesAttribute.Key + ); } return serviceProvider.GetService(parameterInfo.ParameterType) - ?? ConstructValueFromType(parameterInfo, serviceProvider); + ?? ConstructValueFromType(parameterInfo, serviceProvider); } /// @@ -138,15 +175,25 @@ public void SetupServiceCollection(IServiceCollection serviceCollection) { /// An on the parameter supplies the constructor arguments the /// container cannot work out for itself. The last one on the parameter wins. /// - private static object? ConstructValueFromType(ParameterInfo parameterInfo, IServiceProvider serviceProvider) { + private static object? ConstructValueFromType( + ParameterInfo parameterInfo, + IServiceProvider serviceProvider + ) + { object[] parameterValues = []; - foreach (var attribute in parameterInfo.GetCustomAttributes()) { - if (attribute is IInjectValueAttribute injectValueAttribute) { + foreach (var attribute in parameterInfo.GetCustomAttributes()) + { + if (attribute is IInjectValueAttribute injectValueAttribute) + { parameterValues = injectValueAttribute.ProvideValue(serviceProvider, parameterInfo); } } - return ActivatorUtilities.CreateInstance(serviceProvider, parameterInfo.ParameterType, parameterValues); + return ActivatorUtilities.CreateInstance( + serviceProvider, + parameterInfo.ParameterType, + parameterValues + ); } } diff --git a/src/DependencyModules.xUnit/Attributes/ModuleTestAttribute.cs b/src/DependencyModules.xUnit/Attributes/ModuleTestAttribute.cs index 9401fda..d76a20c 100644 --- a/src/DependencyModules.xUnit/Attributes/ModuleTestAttribute.cs +++ b/src/DependencyModules.xUnit/Attributes/ModuleTestAttribute.cs @@ -21,8 +21,8 @@ namespace DependencyModules.xUnit.Attributes; /// [XunitTestCaseDiscoverer(typeof(ModuleTestDiscoverer))] [AttributeUsage(AttributeTargets.Method)] -public class ModuleTestAttribute : FactAttribute, IModuleTestAttribute { - +public class ModuleTestAttribute : FactAttribute, IModuleTestAttribute +{ /// /// Marks a test method, taking no modules. /// @@ -34,9 +34,9 @@ public class ModuleTestAttribute : FactAttribute, IModuleTestAttribute { /// public ModuleTestAttribute( [CallerFilePath] string? sourceFilePath = null, - [CallerLineNumber] int sourceLineNumber = -1) - : base(sourceFilePath, sourceLineNumber) => - ModuleTypes = []; + [CallerLineNumber] int sourceLineNumber = -1 + ) + : base(sourceFilePath, sourceLineNumber) => ModuleTypes = []; /// /// Marks a test method and names one module to configure the test's container with. @@ -50,9 +50,9 @@ public ModuleTestAttribute( public ModuleTestAttribute( Type module, [CallerFilePath] string? sourceFilePath = null, - [CallerLineNumber] int sourceLineNumber = -1) - : base(sourceFilePath, sourceLineNumber) => - ModuleTypes = [module]; + [CallerLineNumber] int sourceLineNumber = -1 + ) + : base(sourceFilePath, sourceLineNumber) => ModuleTypes = [module]; /// /// Marks a test method and names several modules to configure the test's container with. @@ -63,8 +63,7 @@ public ModuleTestAttribute( /// still runs and still reports correctly; only navigation from a test explorer back to the /// source is unavailable. Naming one module, or none, takes an overload that does capture it. /// - public ModuleTestAttribute(params Type[] modules) => - ModuleTypes = modules; + public ModuleTestAttribute(params Type[] modules) => ModuleTypes = modules; /// /// Gets an array of module types associated with the test method decorated with @@ -78,7 +77,5 @@ public ModuleTestAttribute(params Type[] modules) => /// Declared by , so the module loading itself is shared with /// every other test framework integration rather than reading this attribute by name. /// - public Type[] ModuleTypes { - get; - } -} \ No newline at end of file + public Type[] ModuleTypes { get; } +} diff --git a/src/DependencyModules.xUnit/Impl/ModuleTestCase.cs b/src/DependencyModules.xUnit/Impl/ModuleTestCase.cs index 9319605..ee64158 100644 --- a/src/DependencyModules.xUnit/Impl/ModuleTestCase.cs +++ b/src/DependencyModules.xUnit/Impl/ModuleTestCase.cs @@ -22,8 +22,8 @@ namespace DependencyModules.xUnit.Impl; /// ModuleTestCommand has always disposed in a finally around the test; this is the /// same lifetime for xUnit. /// -public class ModuleTestCase : XunitTestCase, ISelfExecutingXunitTestCase { - +public class ModuleTestCase : XunitTestCase, ISelfExecutingXunitTestCase +{ /// /// One per container this case built: one for a plain test, one per row for a data-driven /// one. Runtime state only, never serialized with the case. @@ -56,26 +56,29 @@ public ModuleTestCase( object?[]? testMethodArguments = null, string? sourceFilePath = null, int? sourceLineNumber = null, - int? timeout = null) : base( - // Named rather than positional throughout. XunitTestCase's constructor takes thirteen - // parameters, eleven of them optional, and a version that inserts one mid-list rebinds - // every argument after it — silently where the types happen to line up, and as a wall of - // unrelated-looking conversion errors where they do not. Named arguments make an insertion - // either invisible or a single precise error. - testMethod: testMethod, - testCaseDisplayName: testCaseDisplayName, - uniqueID: uniqueID, - @explicit: @explicit, - skipExceptions: skipExceptions, - skipReason: skipReason, - skipType: skipType, - skipUnless: skipUnless, - skipWhen: skipWhen, - traits: traits, - testMethodArguments: testMethodArguments, - sourceFilePath: sourceFilePath, - sourceLineNumber: sourceLineNumber, - timeout: timeout) { } + int? timeout = null + ) + : base( + // Named rather than positional throughout. XunitTestCase's constructor takes thirteen + // parameters, eleven of them optional, and a version that inserts one mid-list rebinds + // every argument after it — silently where the types happen to line up, and as a wall of + // unrelated-looking conversion errors where they do not. Named arguments make an insertion + // either invisible or a single precise error. + testMethod: testMethod, + testCaseDisplayName: testCaseDisplayName, + uniqueID: uniqueID, + @explicit: @explicit, + skipExceptions: skipExceptions, + skipReason: skipReason, + skipType: skipType, + skipUnless: skipUnless, + skipWhen: skipWhen, + traits: traits, + testMethodArguments: testMethodArguments, + sourceFilePath: sourceFilePath, + sourceLineNumber: sourceLineNumber, + timeout: timeout + ) { } /// /// Executes logic before the invocation of the test method associated with the current test case. @@ -83,11 +86,10 @@ public ModuleTestCase( /// public override void PreInvoke() { } - private record StartupValues( - IServiceProvider ServiceProvider, - TestParameterResolver Resolver); + private record StartupValues(IServiceProvider ServiceProvider, TestParameterResolver Resolver); - private async Task SetupServiceCollection() { + private async Task SetupServiceCollection() + { var serviceCollection = new ServiceCollection(); var knownAttributes = TestMethod.Method.GetTestAttributes().ToArray(); @@ -135,7 +137,8 @@ private async Task SetupServiceCollection() { pinnedServices: SharedRegistrations.Collect(TestMethod.Method, knownAttributes), build: services => BuildServiceProvider(context, services, knownAttributes), start: built => StartAsync(context, knownAttributes, built), - track: _providers.Add); + track: _providers.Add + ); return new StartupValues(provider, resolver); } @@ -149,20 +152,27 @@ private async Task SetupServiceCollection() { /// that looks composed and is not. /// private static async ValueTask StartAsync( - ITestMethodContext context, Attribute[] knownAttributes, IServiceProvider provider) { - foreach (var startupAttribute in knownAttributes.OfType()) { + ITestMethodContext context, + Attribute[] knownAttributes, + IServiceProvider provider + ) + { + foreach (var startupAttribute in knownAttributes.OfType()) + { await startupAttribute.StartupAsync(context, provider); } } - private void SetupTestCaseInfo(ServiceCollection serviceCollection, Attribute[] knownAttributes) { - - serviceCollection.AddSingleton(provider => provider.GetRequiredService()); + private void SetupTestCaseInfo(ServiceCollection serviceCollection, Attribute[] knownAttributes) + { + serviceCollection.AddSingleton(provider => + provider.GetRequiredService() + ); serviceCollection.AddSingleton(_ => new TestCaseInfo( TestMethod, - ArraySegment.Empty, + ArraySegment.Empty, knownAttributes - )); + )); } /// @@ -173,11 +183,17 @@ private void SetupTestCaseInfo(ServiceCollection serviceCollection, Attribute[] /// which is the reverse of how every other attribute here resolves. /// private IServiceProvider BuildServiceProvider( - ITestMethodContext context, IServiceCollection serviceCollection, Attribute[] knownAttributes) { - var serviceProviderBuilderAttribute = - knownAttributes.OfType().LastOrDefault(); - - if (serviceProviderBuilderAttribute != null) { + ITestMethodContext context, + IServiceCollection serviceCollection, + Attribute[] knownAttributes + ) + { + var serviceProviderBuilderAttribute = knownAttributes + .OfType() + .LastOrDefault(); + + if (serviceProviderBuilderAttribute != null) + { return serviceProviderBuilderAttribute.BuildServiceProvider(context, serviceCollection); } @@ -199,12 +215,17 @@ private IServiceProvider BuildServiceProvider( /// mocked, which is what [Mock] is for. /// private void SetupServiceSetupAttributes( - ITestMethodContext context, IServiceCollection serviceCollection, Attribute[] knownAttributes) { + ITestMethodContext context, + IServiceCollection serviceCollection, + Attribute[] knownAttributes + ) + { var setupAttributes = knownAttributes .OfType() .OrderBy(attribute => attribute is IMockSupportAttribute ? 0 : 1); - foreach (var setupAttribute in setupAttributes) { + foreach (var setupAttribute in setupAttributes) + { setupAttribute.SetupServiceCollection(context, serviceCollection); } } @@ -221,42 +242,52 @@ private void SetupServiceSetupAttributes( /// had been decided against the default. Widest scope first, so the narrowest attribute that /// answers decides, matching how every other attribute here resolves. /// - private void SeedEnvironment(IServiceCollection serviceCollection, Attribute[] knownAttributes) { + private void SeedEnvironment(IServiceCollection serviceCollection, Attribute[] knownAttributes) + { IModuleEnvironment? environment = null; - foreach (var provider in knownAttributes.OfType()) { + foreach (var provider in knownAttributes.OfType()) + { environment = provider.ProvideEnvironment(TestMethod.Method) ?? environment; } - if (environment != null) { + if (environment != null) + { serviceCollection.Add(new ServiceDescriptor(typeof(IModuleEnvironment), environment)); } } - private void SetupModules(ServiceCollection serviceCollection, IEnumerable knownAttributes) { + private void SetupModules( + ServiceCollection serviceCollection, + IEnumerable knownAttributes + ) + { var modules = new List(); - foreach (var loadModuleAttribute in knownAttributes.OfType()) { - + foreach (var loadModuleAttribute in knownAttributes.OfType()) + { var moduleTypes = loadModuleAttribute.GetModule(); - + modules.Add(moduleTypes); } // The interface rather than ModuleTestAttribute, so this reads the same for any integration. var testAttribute = TestMethod.Method.GetTestAttribute(); - if (testAttribute != null) { + if (testAttribute != null) + { var count = 0; - foreach (var moduleType in testAttribute.ModuleTypes) { - if (Activator.CreateInstance(moduleType, []) is IDependencyModule moduleInstance) { + foreach (var moduleType in testAttribute.ModuleTypes) + { + if (Activator.CreateInstance(moduleType, []) is IDependencyModule moduleInstance) + { modules.Insert(count++, moduleInstance); } } } modules.Reverse(); - + DependencyRegistry.LoadModules(serviceCollection, modules.ToArray()); } @@ -277,23 +308,36 @@ public async ValueTask Run( IMessageBus messageBus, object?[] constructorArguments, ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) { - try { + CancellationTokenSource cancellationTokenSource + ) + { + try + { return await XunitRunnerHelper.RunXunitTestCase( - this, messageBus, cancellationTokenSource, aggregator, explicitOption, constructorArguments); + this, + messageBus, + cancellationTokenSource, + aggregator, + explicitOption, + constructorArguments + ); } - finally { + finally + { await DisposeProviders(); } } - private async ValueTask DisposeProviders() { + private async ValueTask DisposeProviders() + { var providers = _providers.ToArray(); _providers.Clear(); - foreach (var provider in providers) { - switch (provider) { + foreach (var provider in providers) + { + switch (provider) + { case IAsyncDisposable asyncDisposable: await asyncDisposable.DisposeAsync(); break; @@ -310,11 +354,12 @@ private async ValueTask DisposeProviders() { /// tests that are associated with this test case. /// /// - public override async ValueTask> CreateTests() { - var dataAttributes = - TestMethod.Method.GetTestAttributes().ToArray(); + public override async ValueTask> CreateTests() + { + var dataAttributes = TestMethod.Method.GetTestAttributes().ToArray(); - if (dataAttributes.Length == 0) { + if (dataAttributes.Length == 0) + { return await UnitTestWithNoDataAttributes(); } @@ -341,26 +386,33 @@ public override async ValueTask> CreateTests() { /// Conditional, as the interface requires: an explicit MemberType is the author's answer and is /// never overwritten. /// - private void SupplyReflectedType(IDataAttribute[] dataAttributes) { + private void SupplyReflectedType(IDataAttribute[] dataAttributes) + { var reflectedType = TestMethod.Method.ReflectedType; - if (reflectedType == null) { + if (reflectedType == null) + { return; } - foreach (var typeAware in dataAttributes.OfType()) { + foreach (var typeAware in dataAttributes.OfType()) + { typeAware.MemberType ??= reflectedType; } } - private async Task> UnitTestFromDataAttributes(IDataAttribute[] dataAttributes) { + private async Task> UnitTestFromDataAttributes( + IDataAttribute[] dataAttributes + ) + { var unitTests = new List(); - foreach (var dataAttribute in dataAttributes) { - var dataRowCollection = - await dataAttribute.GetData(TestMethod.Method, DisposalTracker); + foreach (var dataAttribute in dataAttributes) + { + var dataRowCollection = await dataAttribute.GetData(TestMethod.Method, DisposalTracker); - foreach (var theoryDataRow in dataRowCollection) { + foreach (var theoryDataRow in dataRowCollection) + { var data = theoryDataRow.GetData(); var startupValues = await SetupServiceCollection(); @@ -383,7 +435,8 @@ private async Task> UnitTestFromDataAttributes(I skipWhen: theoryDataRow.SkipWhen ?? SkipWhen, testDisplayName: GetRowDisplayName(theoryDataRow, data), testIndex: unitTests.Count, - traits: theoryDataRow.Traits?.ToReadOnlyTraits() ?? Traits.ToReadOnlyTraits(), + traits: theoryDataRow.Traits?.ToReadOnlyTraits() + ?? Traits.ToReadOnlyTraits(), timeout: theoryDataRow.Timeout ?? Timeout, testMethodArguments: await ResolveArguments(data, startupValues) ) @@ -391,7 +444,8 @@ private async Task> UnitTestFromDataAttributes(I } } - if (unitTests.Count == 0) { + if (unitTests.Count == 0) + { // Failing rather than returning nothing, which is what xUnit's own delay-enumerated // theory does for a theory without data. Returning an empty collection here is reported // as a pass, so a row source that stopped producing rows — for any reason, not only the @@ -401,16 +455,18 @@ private async Task> UnitTestFromDataAttributes(I // Exceptions thrown from CreateTests are caught and converted into a test case failure, // which is the documented way to surface this. throw new InvalidOperationException( - $"No data was found for '{TestMethod.TestClass.TestClassName}.{TestMethod.MethodName}'. " + - $"It carries {DescribeAttributes(dataAttributes)}, and every one of them returned no rows. " + - "A data-driven test with no rows runs nothing, so it is reported as a failure rather " + - "than as a pass."); + $"No data was found for '{TestMethod.TestClass.TestClassName}.{TestMethod.MethodName}'. " + + $"It carries {DescribeAttributes(dataAttributes)}, and every one of them returned no rows. " + + "A data-driven test with no rows runs nothing, so it is reported as a failure rather " + + "than as a pass." + ); } return unitTests; } - private static string DescribeAttributes(IDataAttribute[] dataAttributes) { + private static string DescribeAttributes(IDataAttribute[] dataAttributes) + { var names = dataAttributes .Select(attribute => "[" + TrimAttributeSuffix(attribute.GetType().Name) + "]") .ToArray(); @@ -433,7 +489,8 @@ private static string TrimAttributeSuffix(string name) => /// at execution time, and naming a test after a service instance would be neither readable /// nor stable between runs. /// - private string GetRowDisplayName(Xunit.ITheoryDataRow theoryDataRow, object?[] data) { + private string GetRowDisplayName(Xunit.ITheoryDataRow theoryDataRow, object?[] data) + { var baseDisplayName = theoryDataRow.TestDisplayName ?? TestCaseDisplayName; return TestMethod.GetDisplayName( @@ -442,13 +499,16 @@ private string GetRowDisplayName(Xunit.ITheoryDataRow theoryDataRow, object?[] d // for [Theory]; passing null would compile and quietly drop it for module tests. label: theoryDataRow.Label, testMethodArguments: data, - methodGenericTypes: null); + methodGenericTypes: null + ); } - private async Task> UnitTestWithNoDataAttributes() { + private async Task> UnitTestWithNoDataAttributes() + { var startupValues = await SetupServiceCollection(); - return [ + return + [ new XunitTest( testCase: this, testMethod: TestMethod, @@ -462,7 +522,7 @@ private async Task> UnitTestWithNoDataAttributes traits: Traits.ToReadOnlyTraits(), timeout: Timeout, testMethodArguments: await ResolveArguments([], startupValues) - ) + ), ]; } @@ -470,11 +530,19 @@ private async Task> UnitTestWithNoDataAttributes /// The arguments are published on so a test can read what it was /// invoked with. That is xUnit's own object, which is why this is not part of the shared resolver. /// - private static async Task ResolveArguments(object?[] data, StartupValues startupValues) { - var arguments = await startupValues.Resolver.ResolveArgumentsAsync(startupValues.ServiceProvider, data); - - startupValues.ServiceProvider.GetRequiredService().TestMethodArguments = arguments; + private static async Task ResolveArguments( + object?[] data, + StartupValues startupValues + ) + { + var arguments = await startupValues.Resolver.ResolveArgumentsAsync( + startupValues.ServiceProvider, + data + ); + + startupValues.ServiceProvider.GetRequiredService().TestMethodArguments = + arguments; return arguments; } -} \ No newline at end of file +} diff --git a/src/DependencyModules.xUnit/Impl/ModuleTestDiscoverer.cs b/src/DependencyModules.xUnit/Impl/ModuleTestDiscoverer.cs index c8d0cb6..f49adad 100644 --- a/src/DependencyModules.xUnit/Impl/ModuleTestDiscoverer.cs +++ b/src/DependencyModules.xUnit/Impl/ModuleTestDiscoverer.cs @@ -14,8 +14,8 @@ namespace DependencyModules.xUnit.Impl; /// /// /// -public class ModuleTestDiscoverer : IXunitTestCaseDiscoverer { - +public class ModuleTestDiscoverer : IXunitTestCaseDiscoverer +{ /// /// Discovers test cases for the provided method using the xUnit framework /// and returns a collection of test cases to be executed. @@ -33,8 +33,11 @@ public class ModuleTestDiscoverer : IXunitTestCaseDiscoverer { /// A task that, when completed, contains a read-only collection of discovered test cases specific to the provided method. /// public ValueTask> Discover( - ITestFrameworkDiscoveryOptions discoveryOptions, IXunitTestMethod testMethod, IFactAttribute factAttribute) { - + ITestFrameworkDiscoveryOptions discoveryOptions, + IXunitTestMethod testMethod, + IFactAttribute factAttribute + ) + { // Delegate to xUnit's own introspection rather than deriving these by hand. The bare method // name is not unique across test classes, and xUnit silently drops a test case whose ID // collides with one already discovered. This also picks up display name formatting, the @@ -46,10 +49,15 @@ public ValueTask> Discover( // Naming a parameter only the newer one declares resolves it. A module test has no label, // which is what null says. var details = TestIntrospectionHelper.GetTestCaseDetails( - discoveryOptions, testMethod, factAttribute, label: null); + discoveryOptions, + testMethod, + factAttribute, + label: null + ); return new ValueTask>( - new[] { + new[] + { new ModuleTestCase( testMethod: details.ResolvedTestMethod, testCaseDisplayName: details.TestCaseDisplayName, @@ -70,8 +78,8 @@ public ValueTask> Discover( sourceFilePath: details.SourceFilePath, sourceLineNumber: details.SourceLineNumber, timeout: details.Timeout - ) + ), } ); } -} \ No newline at end of file +} diff --git a/src/DependencyModules.xUnit/Impl/TestCaseInfo.cs b/src/DependencyModules.xUnit/Impl/TestCaseInfo.cs index d9c009b..e419055 100644 --- a/src/DependencyModules.xUnit/Impl/TestCaseInfo.cs +++ b/src/DependencyModules.xUnit/Impl/TestCaseInfo.cs @@ -5,8 +5,8 @@ namespace DependencyModules.xUnit.Impl; /// /// Defines the contract for retrieving information about a specific test case. /// -public interface ITestCaseInfo { - +public interface ITestCaseInfo +{ /// /// Gets the test method associated with a specific test case. /// @@ -16,9 +16,7 @@ public interface ITestCaseInfo { /// useful when retrieving metadata or executing logic related to /// the underlying test method in the context of xUnit.net testing framework. /// - IXunitTestMethod TestMethod { - get; - } + IXunitTestMethod TestMethod { get; } /// /// Gets or sets the arguments passed to the test method for a specific test case. @@ -29,10 +27,7 @@ IXunitTestMethod TestMethod { /// is particularly useful in scenarios where the arguments need to be examined /// or manipulated, such as parameterized test cases within the xUnit.net testing framework. /// - IReadOnlyList TestMethodArguments { - get; - set; - } + IReadOnlyList TestMethodArguments { get; set; } /// /// Gets the collection of attributes associated with the test method of a specific test case. @@ -42,9 +37,7 @@ IXunitTestMethod TestMethod { /// applied to the test method. This property can be utilized to retrieve additional /// behavioral or descriptive information tied to the associated test method. /// - IReadOnlyList TestMethodAttributes { - get; - } + IReadOnlyList TestMethodAttributes { get; } } /// @@ -53,8 +46,9 @@ IReadOnlyList TestMethodAttributes { public class TestCaseInfo( IXunitTestMethod testMethod, IReadOnlyList testMethodArguments, - IReadOnlyList testMethodAttributes) : ITestCaseInfo { - + IReadOnlyList testMethodAttributes +) : ITestCaseInfo +{ /// /// Gets the test method associated with the test case. /// @@ -62,9 +56,7 @@ public class TestCaseInfo( /// The TestMethod property provides access to the underlying test method for a given test case. /// It can be utilized to retrieve metadata or invoke specific logic related to the corresponding xUnit test method. /// - public IXunitTestMethod TestMethod { - get; - } = testMethod; + public IXunitTestMethod TestMethod { get; } = testMethod; /// /// Gets or sets the arguments used for invoking the test method associated with the test case. @@ -74,10 +66,7 @@ public IXunitTestMethod TestMethod { /// the test method during execution. This is particularly useful when preparing customized or dynamically /// resolved arguments for parameterized test cases. /// - public IReadOnlyList TestMethodArguments { - get; - set; - } = testMethodArguments; + public IReadOnlyList TestMethodArguments { get; set; } = testMethodArguments; /// /// Gets the collection of attributes applied to the test method associated with a test case. @@ -88,7 +77,5 @@ public IReadOnlyList TestMethodArguments { /// This property is particularly helpful when injecting behaviors or inspecting the attributes for /// parameterized or decorated test methods. /// - public IReadOnlyList TestMethodAttributes { - get; - } = testMethodAttributes; -} \ No newline at end of file + public IReadOnlyList TestMethodAttributes { get; } = testMethodAttributes; +} diff --git a/src/DependencyModules.xUnit/Impl/TraitDictionaryExtensions.cs b/src/DependencyModules.xUnit/Impl/TraitDictionaryExtensions.cs index c72ff9f..cd87bf7 100644 --- a/src/DependencyModules.xUnit/Impl/TraitDictionaryExtensions.cs +++ b/src/DependencyModules.xUnit/Impl/TraitDictionaryExtensions.cs @@ -16,8 +16,8 @@ namespace DependencyModules.xUnit.Impl; /// ToReadOnlyTraits also avoids colliding with the AsReadOnly that /// supplies for dictionaries. /// -internal static class TraitDictionaryExtensions { - +internal static class TraitDictionaryExtensions +{ /// /// Widens the mutable form xUnit stores traits in to the read-only form its constructors take. /// @@ -30,11 +30,13 @@ internal static class TraitDictionaryExtensions { /// equivalent under today's xUnit. /// public static IReadOnlyDictionary> ToReadOnlyTraits( - this Dictionary> traits) => + this Dictionary> traits + ) => traits.ToDictionary( pair => pair.Key, pair => (IReadOnlyCollection)pair.Value, - traits.Comparer); + traits.Comparer + ); /// /// Copies the read-only form into the mutable one, under the supplied key comparer. @@ -50,11 +52,13 @@ public static IReadOnlyDictionary> ToReadOnl /// public static Dictionary> ToWritableTraits( this IReadOnlyDictionary> traits, - IEqualityComparer comparer) { - + IEqualityComparer comparer + ) + { var result = new Dictionary>(comparer); - foreach (var pair in traits) { + foreach (var pair in traits) + { result[pair.Key] = new HashSet(pair.Value); } diff --git a/src/DependencyModules.xUnit/Impl/XunitTestMethodContext.cs b/src/DependencyModules.xUnit/Impl/XunitTestMethodContext.cs index 15168b4..fed9239 100644 --- a/src/DependencyModules.xUnit/Impl/XunitTestMethodContext.cs +++ b/src/DependencyModules.xUnit/Impl/XunitTestMethodContext.cs @@ -14,14 +14,12 @@ namespace DependencyModules.xUnit.Impl; /// if (testMethod is IXunitTestMethodContext xunit) reaches the full model — unique ID, merged /// traits, generic resolution, the test class and its collection. /// -public interface IXunitTestMethodContext : ITestMethodContext { - +public interface IXunitTestMethodContext : ITestMethodContext +{ /// /// xUnit's own model of the test method. /// - IXunitTestMethod XunitTestMethod { - get; - } + IXunitTestMethod XunitTestMethod { get; } } /// @@ -34,15 +32,12 @@ IXunitTestMethod XunitTestMethod { /// internal sealed class XunitTestMethodContext( IXunitTestMethod testMethod, - IReadOnlyList attributes) : IXunitTestMethodContext { - - public IXunitTestMethod XunitTestMethod { - get; - } = testMethod; + IReadOnlyList attributes +) : IXunitTestMethodContext +{ + public IXunitTestMethod XunitTestMethod { get; } = testMethod; public MethodInfo Method => XunitTestMethod.Method; - public IReadOnlyList Attributes { - get; - } = attributes; + public IReadOnlyList Attributes { get; } = attributes; } diff --git a/tests/DependencyModules.Tests/ApiTests/PublicApiTests.cs b/tests/DependencyModules.Tests/ApiTests/PublicApiTests.cs index 976d85c..e28ad66 100644 --- a/tests/DependencyModules.Tests/ApiTests/PublicApiTests.cs +++ b/tests/DependencyModules.Tests/ApiTests/PublicApiTests.cs @@ -19,15 +19,17 @@ namespace DependencyModules.Tests.ApiTests; /// UPDATE_SNAPSHOTS=1 dotnet test tests/DependencyModules.Tests /// then read the diff carefully before committing it. /// -public class PublicApiTests { - +public class PublicApiTests +{ [Fact] - public void RuntimeApi() { + public void RuntimeApi() + { Snapshot.Match(ApiOf(typeof(DependencyModuleAttribute))); } [Fact] - public void XUnitApi() { + public void XUnitApi() + { Snapshot.Match(ApiOf(typeof(ModuleTestAttribute))); } @@ -37,8 +39,11 @@ public void XUnitApi() { /// IModuleTestAttribute is common to both. /// [Fact] - public void NUnitApi() { - Snapshot.Match(ApiOf(typeof(global::DependencyModules.NUnit.Attributes.ModuleTestAttribute))); + public void NUnitApi() + { + Snapshot.Match( + ApiOf(typeof(global::DependencyModules.NUnit.Attributes.ModuleTestAttribute)) + ); } /// @@ -46,23 +51,31 @@ public void NUnitApi() { /// test framework dependency, which is the point of it — a change here reaches all of them. /// [Fact] - public void TestingApi() { + public void TestingApi() + { Snapshot.Match(ApiOf(typeof(Testing.Attributes.Interfaces.IMockSupportAttribute))); } [Fact] - public void NSubstituteApi() { - Snapshot.Match(ApiOf(typeof(global::DependencyModules.NSubstitute.NSubstituteSupportAttribute))); + public void NSubstituteApi() + { + Snapshot.Match( + ApiOf(typeof(global::DependencyModules.NSubstitute.NSubstituteSupportAttribute)) + ); } [Fact] - public void MoqApi() { + public void MoqApi() + { Snapshot.Match(ApiOf(typeof(global::DependencyModules.Moq.MoqSupportAttribute))); } [Fact] - public void FakeItEasyApi() { - Snapshot.Match(ApiOf(typeof(global::DependencyModules.FakeItEasy.FakeItEasySupportAttribute))); + public void FakeItEasyApi() + { + Snapshot.Match( + ApiOf(typeof(global::DependencyModules.FakeItEasy.FakeItEasySupportAttribute)) + ); } /// @@ -72,21 +85,25 @@ public void FakeItEasyApi() { /// generators on top of these base classes. /// [Fact] - public void SourceGeneratorApi() { + public void SourceGeneratorApi() + { Snapshot.Match(ApiOf(typeof(SourceGenerator.SourceGenerator))); } private static string ApiOf(Type typeFromAssembly) => typeFromAssembly.Assembly.GeneratePublicApi( - new ApiGeneratorOptions { + new ApiGeneratorOptions + { // Assembly-level attributes are build metadata, not API, and several of them // (SourceLink, InternalsVisibleTo, TFM) change with build configuration. - ExcludeAttributes = [ + ExcludeAttributes = + [ "System.Runtime.Versioning.TargetFrameworkAttribute", "System.Reflection.AssemblyMetadataAttribute", "System.Runtime.CompilerServices.InternalsVisibleToAttribute", "System.Diagnostics.DebuggableAttribute", - "System.Runtime.CompilerServices.CompilationRelaxationsAttribute" - ] - }); + "System.Runtime.CompilerServices.CompilationRelaxationsAttribute", + ], + } + ); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/ApplicationModuleCollisionTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ApplicationModuleCollisionTests.cs index c84c289..62b5b33 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ApplicationModuleCollisionTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ApplicationModuleCollisionTests.cs @@ -26,10 +26,11 @@ namespace DependencyModules.Tests.GeneratorTests; /// The variants below are kept as a set on purpose: every one of them was reported as the trigger /// at some point, and only the last is. /// -public class ApplicationModuleCollisionTests { - +public class ApplicationModuleCollisionTests +{ [Fact] - public void DeclaringApplicationModuleInTheRootNamespace_DoesNotCollide() { + public void DeclaringApplicationModuleInTheRootNamespace_DoesNotCollide() + { var result = Run(DeclaredModule("ConfiguredRoot")); Assert.Empty(result.DuplicateHintNames); @@ -41,12 +42,14 @@ public void DeclaringApplicationModuleInTheRootNamespace_DoesNotCollide() { /// the reason this shipped. Asserted separately from AssertNoErrors for that reason. /// [Fact] - public void DeclaringApplicationModuleInTheRootNamespace_DoesNotWarn() { + public void DeclaringApplicationModuleInTheRootNamespace_DoesNotWarn() + { var result = Run(DeclaredModule("ConfiguredRoot")); Assert.DoesNotContain( result.CompilationDiagnostics.Concat(result.GeneratorDiagnostics), - diagnostic => diagnostic.Id == "CS8785"); + diagnostic => diagnostic.Id == "CS8785" + ); } /// @@ -55,7 +58,8 @@ public void DeclaringApplicationModuleInTheRootNamespace_DoesNotWarn() { /// recognised the two as the same — this only ever failed to recognise them. /// [Fact] - public void TheDeclaredModuleIsTheOneGenerated() { + public void TheDeclaredModuleIsTheOneGenerated() + { var result = Run(DeclaredModule("ConfiguredRoot")); var module = result.SourceContaining("ApplicationModule.Module"); @@ -68,7 +72,8 @@ public void TheDeclaredModuleIsTheOneGenerated() { /// surviving one carries what the project asked for. /// [Fact] - public void TheSurvivingModuleStillRegistersTheProjectsServices() { + public void TheSurvivingModuleStillRegistersTheProjectsServices() + { var result = Run(DeclaredModule("ConfiguredRoot")); Assert.Contains("Thing", result.SourceContaining("ApplicationModule.Dependencies")); @@ -80,23 +85,33 @@ public void TheSurvivingModuleStillRegistersTheProjectsServices() { /// [Theory] // A different name never collides with the generated ApplicationModule. - [InlineData("different name", """ - namespace ConfiguredRoot; - [DependencyModules.Runtime.Attributes.DependencyModule] - public partial class CompositionModule; - """)] + [InlineData( + "different name", + """ + namespace ConfiguredRoot; + [DependencyModules.Runtime.Attributes.DependencyModule] + public partial class CompositionModule; + """ + )] // A namespace other than RootNamespace produces a different hint name. - [InlineData("different namespace", """ - namespace SomewhereElse; - [DependencyModules.Runtime.Attributes.DependencyModule] - public partial class ApplicationModule; - """)] + [InlineData( + "different namespace", + """ + namespace SomewhereElse; + [DependencyModules.Runtime.Attributes.DependencyModule] + public partial class ApplicationModule; + """ + )] // The global namespace, likewise. - [InlineData("global namespace", """ - [DependencyModules.Runtime.Attributes.DependencyModule] - public partial class ApplicationModule; - """)] - public void AShapeThatNeverCollided_StillBuildsCleanly(string variant, string declaration) { + [InlineData( + "global namespace", + """ + [DependencyModules.Runtime.Attributes.DependencyModule] + public partial class ApplicationModule; + """ + )] + public void AShapeThatNeverCollided_StillBuildsCleanly(string variant, string declaration) + { var result = Run(declaration); Assert.Empty(result.DuplicateHintNames); @@ -115,22 +130,24 @@ public void AShapeThatNeverCollided_StillBuildsCleanly(string variant, string de /// lose it, and this fix makes the shape work either way. /// [Fact] - public void AnExplicitMainWithADeclaredApplicationModule_BuildsCleanly() { + public void AnExplicitMainWithADeclaredApplicationModule_BuildsCleanly() + { var result = GeneratorTestHarness.Run( - new Dictionary { - ["Program.cs"] = - """ - namespace ConfiguredRoot; - - public static class Program { - public static void Main() => System.Console.WriteLine("hello"); - } - """, + new Dictionary + { + ["Program.cs"] = """ + namespace ConfiguredRoot; + + public static class Program { + public static void Main() => System.Console.WriteLine("hello"); + } + """, ["Composition.cs"] = DeclaredModule("ConfiguredRoot"), - ["Services.cs"] = Services + ["Services.cs"] = Services, }, new Dictionary { ["RootNamespace"] = "ConfiguredRoot" }, - OutputKind.ConsoleApplication); + OutputKind.ConsoleApplication + ); Assert.Empty(result.DuplicateHintNames); result.AssertNoErrors(); @@ -138,14 +155,13 @@ public static class Program { private static string DeclaredModule(string namespaceName) => $$""" - namespace {{namespaceName}}; + namespace {{namespaceName}}; - [DependencyModules.Runtime.Attributes.DependencyModule] - public partial class ApplicationModule; - """; + [DependencyModules.Runtime.Attributes.DependencyModule] + public partial class ApplicationModule; + """; - private const string Services = - """ + private const string Services = """ namespace ConfiguredRoot; public interface IThing; @@ -156,13 +172,15 @@ public class Thing : IThing; private static GeneratorResult Run(string declaration) => GeneratorTestHarness.Run( - new Dictionary { + new Dictionary + { // Top-level statements: this is what makes the generator emit its own // ApplicationModule into RootNamespace. ["Program.cs"] = """System.Console.WriteLine("hello");""", ["Composition.cs"] = declaration, - ["Services.cs"] = Services + ["Services.cs"] = Services, }, new Dictionary { ["RootNamespace"] = "ConfiguredRoot" }, - OutputKind.ConsoleApplication); + OutputKind.ConsoleApplication + ); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/AssemblyModuleAttributeDiagnosticsTests.cs b/tests/DependencyModules.Tests/GeneratorTests/AssemblyModuleAttributeDiagnosticsTests.cs index 2ba099a..5d1f2e2 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/AssemblyModuleAttributeDiagnosticsTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/AssemblyModuleAttributeDiagnosticsTests.cs @@ -12,10 +12,9 @@ namespace DependencyModules.Tests.GeneratorTests; /// running, so it does not exist in the compilation being examined and nothing about it resolves. /// That makes the false positives the interesting cases, and most of these tests are one. /// -public class AssemblyModuleAttributeDiagnosticsTests { - - private const string ModuleInNamespace = - """ +public class AssemblyModuleAttributeDiagnosticsTests +{ + private const string ModuleInNamespace = """ namespace MyApp.Composition; [DependencyModules.Runtime.Attributes.DependencyModule] @@ -23,7 +22,8 @@ public partial class ApplicationModule; """; [Fact] - public void MissingUsing_IsReported() { + public void MissingUsing_IsReported() + { var result = Run("[assembly: ApplicationModule]"); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0016"); @@ -34,20 +34,23 @@ public void MissingUsing_IsReported() { /// The suffixed spelling names the same module. [Fact] - public void MissingUsing_IsReported_ForTheAttributeSuffixedSpelling() { + public void MissingUsing_IsReported_ForTheAttributeSuffixedSpelling() + { var result = Run("[assembly: ApplicationModuleAttribute]"); Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0016"); } [Fact] - public void TheUsingBeingPresent_IsSilent() { + public void TheUsingBeingPresent_IsSilent() + { var result = Run( """ using MyApp.Composition; [assembly: ApplicationModule] - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0016"); } @@ -57,19 +60,23 @@ public void TheUsingBeingPresent_IsSilent() { /// attribute sits in would report a build that is already correct. /// [Fact] - public void AGlobalUsingInAnotherFile_IsSilent() { + public void AGlobalUsingInAnotherFile_IsSilent() + { var result = GeneratorTestHarness.Run( - new Dictionary { + new Dictionary + { ["Module.cs"] = ModuleInNamespace, ["GlobalUsings.cs"] = "global using MyApp.Composition;", - ["Bootstrap.cs"] = "[assembly: ApplicationModule]" - }); + ["Bootstrap.cs"] = "[assembly: ApplicationModule]", + } + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0016"); } [Fact] - public void AQualifiedUsage_IsSilent() { + public void AQualifiedUsage_IsSilent() + { var result = Run("[assembly: MyApp.Composition.ApplicationModule]"); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0016"); @@ -77,7 +84,8 @@ public void AQualifiedUsage_IsSilent() { /// An attribute this compilation declares no module for belongs to somebody else. [Fact] - public void AnUnrelatedAssemblyAttribute_IsSilent() { + public void AnUnrelatedAssemblyAttribute_IsSilent() + { var result = Run("[assembly: System.Reflection.AssemblyMetadata(\"key\", \"value\")]"); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0016"); @@ -85,16 +93,18 @@ public void AnUnrelatedAssemblyAttribute_IsSilent() { /// A module in the global namespace has no namespace to import. [Fact] - public void AModuleInTheGlobalNamespace_IsSilent() { + public void AModuleInTheGlobalNamespace_IsSilent() + { var result = GeneratorTestHarness.Run( - new Dictionary { - ["Module.cs"] = - """ - [DependencyModules.Runtime.Attributes.DependencyModule] - public partial class ApplicationModule; - """, - ["Bootstrap.cs"] = "[assembly: ApplicationModule]" - }); + new Dictionary + { + ["Module.cs"] = """ + [DependencyModules.Runtime.Attributes.DependencyModule] + public partial class ApplicationModule; + """, + ["Bootstrap.cs"] = "[assembly: ApplicationModule]", + } + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0016"); } @@ -104,13 +114,15 @@ public partial class ApplicationModule; /// into scope under the name written here and the report still stands. /// [Fact] - public void AUsingAlias_DoesNotCountAsTheImport() { + public void AUsingAlias_DoesNotCountAsTheImport() + { var result = Run( """ using Composition = MyApp.Composition; [assembly: ApplicationModule] - """); + """ + ); Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0016"); } @@ -122,15 +134,16 @@ public void AUsingAlias_DoesNotCountAsTheImport() { /// resolve. /// [Fact] - public void AnAssemblyAttributeOutsideTheEntryPointFile_IsReported() { + public void AnAssemblyAttributeOutsideTheEntryPointFile_IsReported() + { var result = RunWithEntryPoint( - bootstrap: - """ + bootstrap: """ using MyApp.Composition; [assembly: ApplicationModule] """, - program: "System.Console.WriteLine();"); + program: "System.Console.WriteLine();" + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0019"); @@ -139,17 +152,18 @@ public void AnAssemblyAttributeOutsideTheEntryPointFile_IsReported() { } [Fact] - public void AnAssemblyAttributeInTheEntryPointFile_IsSilent() { + public void AnAssemblyAttributeInTheEntryPointFile_IsSilent() + { var result = RunWithEntryPoint( bootstrap: "", - program: - """ + program: """ using MyApp.Composition; [assembly: ApplicationModule] System.Console.WriteLine(); - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0019"); } @@ -161,29 +175,35 @@ public void AnAssemblyAttributeInTheEntryPointFile_IsSilent() { /// what the testing guide shows. /// [Fact] - public void WithNoGeneratedApplicationModule_IsSilent() { + public void WithNoGeneratedApplicationModule_IsSilent() + { var result = Run( """ using MyApp.Composition; [assembly: ApplicationModule] - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0019"); } private static GeneratorResult Run(string bootstrap) => GeneratorTestHarness.Run( - new Dictionary { + new Dictionary + { ["Module.cs"] = ModuleInNamespace, - ["Bootstrap.cs"] = bootstrap - }); + ["Bootstrap.cs"] = bootstrap, + } + ); private static GeneratorResult RunWithEntryPoint(string bootstrap, string program) => GeneratorTestHarness.Run( - new Dictionary { + new Dictionary + { ["Module.cs"] = ModuleInNamespace, ["Bootstrap.cs"] = bootstrap, - ["Program.cs"] = program - }); + ["Program.cs"] = program, + } + ); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/AttributeModelOutputTests.cs b/tests/DependencyModules.Tests/GeneratorTests/AttributeModelOutputTests.cs index 9c04975..757ce82 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/AttributeModelOutputTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/AttributeModelOutputTests.cs @@ -9,53 +9,72 @@ namespace DependencyModules.Tests.GeneratorTests; /// AttributeModel turns the values captured from a source attribute back into C# for the generated /// module attribute. Each supported value shape has to round-trip into code that compiles. /// -public class AttributeModelOutputTests { - +public class AttributeModelOutputTests +{ private static readonly ITypeDefinition SomeType = TypeDefinition.Get("Ns", "SomeType"); [Fact] - public void GetArguments_WithNoArguments_ReturnsNothing() { + public void GetArguments_WithNoArguments_ReturnsNothing() + { Assert.Empty(Attribute().GetArguments()); } [Fact] - public void GetArguments_QuotesStringValues() { - var rendered = Render(Attribute(arguments: [new AttributeArgumentValue("name", "hello")]).GetArguments()); + public void GetArguments_QuotesStringValues() + { + var rendered = Render( + Attribute(arguments: [new AttributeArgumentValue("name", "hello")]).GetArguments() + ); Assert.Contains("\"hello\"", rendered); } [Fact] - public void GetArguments_WritesTypeValuesAsTypeof() { - var rendered = Render(Attribute(arguments: [new AttributeArgumentValue("type", SomeType)]).GetArguments()); + public void GetArguments_WritesTypeValuesAsTypeof() + { + var rendered = Render( + Attribute(arguments: [new AttributeArgumentValue("type", SomeType)]).GetArguments() + ); Assert.Contains("typeof(", rendered); Assert.Contains("SomeType", rendered); } [Fact] - public void GetArguments_WritesPrimitiveValuesVerbatim() { - var rendered = Render(Attribute(arguments: [new AttributeArgumentValue("count", 42)]).GetArguments()); + public void GetArguments_WritesPrimitiveValuesVerbatim() + { + var rendered = Render( + Attribute(arguments: [new AttributeArgumentValue("count", 42)]).GetArguments() + ); Assert.Contains("42", rendered); } [Fact] - public void GetArguments_WritesBooleanValues() { - var rendered = Render(Attribute(arguments: [new AttributeArgumentValue("flag", true)]).GetArguments()); + public void GetArguments_WritesBooleanValues() + { + var rendered = Render( + Attribute(arguments: [new AttributeArgumentValue("flag", true)]).GetArguments() + ); Assert.Contains("True", rendered, StringComparison.OrdinalIgnoreCase); } [Fact] - public void GetArguments_SkipsNullValues() { - Assert.Empty(Attribute(arguments: [new AttributeArgumentValue("nothing", null)]).GetArguments()); + public void GetArguments_SkipsNullValues() + { + Assert.Empty( + Attribute(arguments: [new AttributeArgumentValue("nothing", null)]).GetArguments() + ); } [Fact] - public void GetArguments_WritesStringArraysAsACollection() { - var rendered = Render(Attribute( - arguments: [new AttributeArgumentValue("names", new[] { "a", "b" })]).GetArguments()); + public void GetArguments_WritesStringArraysAsACollection() + { + var rendered = Render( + Attribute(arguments: [new AttributeArgumentValue("names", new[] { "a", "b" })]) + .GetArguments() + ); Assert.StartsWith("[", rendered); Assert.EndsWith("]", rendered); @@ -65,34 +84,50 @@ public void GetArguments_WritesStringArraysAsACollection() { } [Fact] - public void GetArguments_PassesThroughOutputComponents() { + public void GetArguments_PassesThroughOutputComponents() + { var component = CodeOutputComponent.Get("SomeExpression"); - var rendered = Render(Attribute(arguments: [new AttributeArgumentValue("value", component)]).GetArguments()); + var rendered = Render( + Attribute(arguments: [new AttributeArgumentValue("value", component)]).GetArguments() + ); Assert.Contains("SomeExpression", rendered); } [Fact] - public void GetArguments_PreservesArgumentOrder() { - var rendered = Render(Attribute(arguments: [ - new AttributeArgumentValue("first", "one"), - new AttributeArgumentValue("second", "two") - ]).GetArguments()); - - Assert.True(rendered.IndexOf("one", StringComparison.Ordinal) < rendered.IndexOf("two", StringComparison.Ordinal), - $"Arguments came out in the wrong order: {rendered}"); + public void GetArguments_PreservesArgumentOrder() + { + var rendered = Render( + Attribute( + arguments: + [ + new AttributeArgumentValue("first", "one"), + new AttributeArgumentValue("second", "two"), + ] + ) + .GetArguments() + ); + + Assert.True( + rendered.IndexOf("one", StringComparison.Ordinal) + < rendered.IndexOf("two", StringComparison.Ordinal), + $"Arguments came out in the wrong order: {rendered}" + ); } [Fact] - public void PropertyValues_WithNoProperties_ReturnsNothing() { + public void PropertyValues_WithNoProperties_ReturnsNothing() + { Assert.Empty(Attribute().PropertyValues()); } [Fact] - public void PropertyValues_WritesNamedAssignments() { - var rendered = Render(Attribute( - properties: [new AttributeArgumentValue("Name", "value")]).PropertyValues()); + public void PropertyValues_WritesNamedAssignments() + { + var rendered = Render( + Attribute(properties: [new AttributeArgumentValue("Name", "value")]).PropertyValues() + ); Assert.Contains("Name", rendered); Assert.Contains("=", rendered); @@ -104,34 +139,44 @@ public void PropertyValues_WritesNamedAssignments() { /// a doubly-quoted literal. /// [Fact] - public void PropertyValues_DoesNotDoubleQuoteAnAlreadyQuotedString() { - var rendered = Render(Attribute( - properties: [new AttributeArgumentValue("Name", "\"value\"")]).PropertyValues()); + public void PropertyValues_DoesNotDoubleQuoteAnAlreadyQuotedString() + { + var rendered = Render( + Attribute(properties: [new AttributeArgumentValue("Name", "\"value\"")]) + .PropertyValues() + ); Assert.DoesNotContain("\"\"", rendered); Assert.Contains("\"value\"", rendered); } [Fact] - public void PropertyValues_WritesTypeValuesAsTypeof() { - var rendered = Render(Attribute( - properties: [new AttributeArgumentValue("As", SomeType)]).PropertyValues()); + public void PropertyValues_WritesTypeValuesAsTypeof() + { + var rendered = Render( + Attribute(properties: [new AttributeArgumentValue("As", SomeType)]).PropertyValues() + ); Assert.Contains("typeof(", rendered); } [Fact] - public void PropertyValues_SkipsNullValues() { - Assert.Empty(Attribute(properties: [new AttributeArgumentValue("Name", null)]).PropertyValues()); + public void PropertyValues_SkipsNullValues() + { + Assert.Empty( + Attribute(properties: [new AttributeArgumentValue("Name", null)]).PropertyValues() + ); } [Fact] - public void CollectionSyntax_WithNoItems_WritesEmptyBrackets() { + public void CollectionSyntax_WithNoItems_WritesEmptyBrackets() + { Assert.Equal("[]", Render(new CollectionSyntaxDeclaration())); } [Fact] - public void CollectionSyntax_QuotesStringItems() { + public void CollectionSyntax_QuotesStringItems() + { var collection = new CollectionSyntaxDeclaration(); collection.Add("value"); @@ -139,7 +184,8 @@ public void CollectionSyntax_QuotesStringItems() { } [Fact] - public void CollectionSyntax_SeparatesItemsWithCommas() { + public void CollectionSyntax_SeparatesItemsWithCommas() + { var collection = new CollectionSyntaxDeclaration(); collection.Add("one"); collection.Add("two"); @@ -148,7 +194,8 @@ public void CollectionSyntax_SeparatesItemsWithCommas() { } [Fact] - public void CollectionSyntax_WritesNonStringItemsVerbatim() { + public void CollectionSyntax_WritesNonStringItemsVerbatim() + { var collection = new CollectionSyntaxDeclaration(); collection.Add(1); collection.Add(2); @@ -157,7 +204,8 @@ public void CollectionSyntax_WritesNonStringItemsVerbatim() { } [Fact] - public void CollectionSyntax_WithTheSameItems_IsEqual() { + public void CollectionSyntax_WithTheSameItems_IsEqual() + { var first = new CollectionSyntaxDeclaration(); first.Add("a"); @@ -168,7 +216,8 @@ public void CollectionSyntax_WithTheSameItems_IsEqual() { } [Fact] - public void CollectionSyntax_WithDifferentItems_IsNotEqual() { + public void CollectionSyntax_WithDifferentItems_IsNotEqual() + { var first = new CollectionSyntaxDeclaration(); first.Add("a"); @@ -179,7 +228,8 @@ public void CollectionSyntax_WithDifferentItems_IsNotEqual() { } [Fact] - public void CollectionSyntax_WithDifferentCounts_IsNotEqual() { + public void CollectionSyntax_WithDifferentCounts_IsNotEqual() + { var first = new CollectionSyntaxDeclaration(); first.Add("a"); @@ -187,19 +237,24 @@ public void CollectionSyntax_WithDifferentCounts_IsNotEqual() { } [Fact] - public void CollectionSyntax_IsNotEqualToOtherTypes() { + public void CollectionSyntax_IsNotEqualToOtherTypes() + { Assert.False(new CollectionSyntaxDeclaration().Equals("not a collection")); } private static AttributeModel Attribute( IReadOnlyList? arguments = null, - IReadOnlyList? properties = null) => - new(SomeType, arguments ?? [], properties ?? [], []); + IReadOnlyList? properties = null + ) => new(SomeType, arguments ?? [], properties ?? [], []); - private static string Render(IEnumerable components) { - var context = new OutputContext(new OutputContextOptions { TypeOutputMode = TypeOutputMode.Global }); + private static string Render(IEnumerable components) + { + var context = new OutputContext( + new OutputContextOptions { TypeOutputMode = TypeOutputMode.Global } + ); - foreach (var component in components) { + foreach (var component in components) + { component.WriteOutput(context); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/AutoModuleDelegationTests.cs b/tests/DependencyModules.Tests/GeneratorTests/AutoModuleDelegationTests.cs index 74b35c6..2e55b68 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/AutoModuleDelegationTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/AutoModuleDelegationTests.cs @@ -19,45 +19,62 @@ namespace DependencyModules.Tests.GeneratorTests; /// and the runtime loads it. These tests pin both halves - that the duplicate is gone, and that /// AddModule<ApplicationModule>() still registers exactly what it did before. /// -public class AutoModuleDelegationTests { - +public class AutoModuleDelegationTests +{ [Fact] - public void ApplicationModule_DoesNotRepeatTheRegistrationsOfADeclaredModule() { - var result = Run(TopLevelProgramWith( - """ - public interface IThing; + public void ApplicationModule_DoesNotRepeatTheRegistrationsOfADeclaredModule() + { + var result = Run( + TopLevelProgramWith( + """ + public interface IThing; - [SingletonService] - public class Thing : IThing; + [SingletonService] + public class Thing : IThing; - [DependencyModule] - public partial class TestModule; - """)); + [DependencyModule] + public partial class TestModule; + """ + ) + ); result.AssertNoErrors(); - Assert.Contains(result.GeneratedSources.Keys, key => key.Contains("TestModule.Dependencies")); - Assert.DoesNotContain(result.GeneratedSources.Keys, key => key.Contains("ApplicationModule.Dependencies")); + Assert.Contains( + result.GeneratedSources.Keys, + key => key.Contains("TestModule.Dependencies") + ); + Assert.DoesNotContain( + result.GeneratedSources.Keys, + key => key.Contains("ApplicationModule.Dependencies") + ); } /// /// The class is still generated, and still reachable - only its registrations moved. /// [Fact] - public void ApplicationModule_NamesTheModuleItDefersTo() { - var result = Run(TopLevelProgramWith( - """ - public interface IThing; + public void ApplicationModule_NamesTheModuleItDefersTo() + { + var result = Run( + TopLevelProgramWith( + """ + public interface IThing; - [SingletonService] - public class Thing : IThing; + [SingletonService] + public class Thing : IThing; - [DependencyModule] - public partial class TestModule; - """)); + [DependencyModule] + public partial class TestModule; + """ + ) + ); result.AssertNoErrors(); - Assert.Contains("new global::TestNamespace.TestModule()", result.SourceContaining("ApplicationModule.Module")); + Assert.Contains( + "new global::TestNamespace.TestModule()", + result.SourceContaining("ApplicationModule.Module") + ); } /// @@ -65,42 +82,56 @@ public partial class TestModule; /// same way and have to stop being duplicated the same way. /// [Fact] - public void ApplicationModule_DoesNotRepeatDecorationsEither() { - var result = Run(TopLevelProgramWith( - """ - public interface IThing; + public void ApplicationModule_DoesNotRepeatDecorationsEither() + { + var result = Run( + TopLevelProgramWith( + """ + public interface IThing; - [SingletonService] - public class Thing : IThing; + [SingletonService] + public class Thing : IThing; - [Decorator] - public class ThingDecorator(IThing inner) : IThing; + [Decorator] + public class ThingDecorator(IThing inner) : IThing; - [DependencyModule] - public partial class TestModule; - """)); + [DependencyModule] + public partial class TestModule; + """ + ) + ); result.AssertNoErrors(); Assert.Contains(result.GeneratedSources.Keys, key => key.Contains("TestModule.Decorators")); - Assert.DoesNotContain(result.GeneratedSources.Keys, key => key.Contains("ApplicationModule.Decorators")); + Assert.DoesNotContain( + result.GeneratedSources.Keys, + key => key.Contains("ApplicationModule.Decorators") + ); } /// /// With nothing to defer to, the auto module carries its own registrations exactly as before. /// [Fact] - public void ApplicationModule_KeepsItsRegistrationsWhenNoModuleIsDeclared() { - var result = Run(TopLevelProgramWith( - """ - public interface IThing; + public void ApplicationModule_KeepsItsRegistrationsWhenNoModuleIsDeclared() + { + var result = Run( + TopLevelProgramWith( + """ + public interface IThing; - [SingletonService] - public class Thing : IThing; - """)); + [SingletonService] + public class Thing : IThing; + """ + ) + ); result.AssertNoErrors(); - Assert.Contains(result.GeneratedSources.Keys, key => key.Contains("ApplicationModule.Dependencies")); + Assert.Contains( + result.GeneratedSources.Keys, + key => key.Contains("ApplicationModule.Dependencies") + ); } /// @@ -108,46 +139,55 @@ public class Thing : IThing; /// drop everything else. The auto module keeps its own registrations in that case. /// [Fact] - public void ApplicationModule_KeepsItsRegistrationsWhenTheOnlyModuleIsRealmRestricted() { - var result = Run(TopLevelProgramWith( - """ - public interface IThing; + public void ApplicationModule_KeepsItsRegistrationsWhenTheOnlyModuleIsRealmRestricted() + { + var result = Run( + TopLevelProgramWith( + """ + public interface IThing; - [SingletonService] - public class Thing : IThing; + [SingletonService] + public class Thing : IThing; - [DependencyModule(OnlyRealm = true)] - public partial class RealmModule; - """)); + [DependencyModule(OnlyRealm = true)] + public partial class RealmModule; + """ + ) + ); result.AssertNoErrors(); - Assert.Contains(result.GeneratedSources.Keys, key => key.Contains("ApplicationModule.Dependencies")); + Assert.Contains( + result.GeneratedSources.Keys, + key => key.Contains("ApplicationModule.Dependencies") + ); } /// /// The point of the whole exercise: what reaches the service collection is unchanged. /// [Fact] - public void ApplicationModule_RegistersTheSameServicesAsTheModuleItDefersTo() { - var assembly = Compile(TopLevelProgramWith( - """ - public interface IThing; + public void ApplicationModule_RegistersTheSameServicesAsTheModuleItDefersTo() + { + var assembly = Compile( + TopLevelProgramWith( + """ + public interface IThing; - [SingletonService] - public class Thing : IThing; + [SingletonService] + public class Thing : IThing; - [DependencyModule] - public partial class TestModule; - """)); + [DependencyModule] + public partial class TestModule; + """ + ) + ); var viaApplicationModule = Apply(assembly, "TestNamespace.ApplicationModule"); var viaDeclaredModule = Apply(assembly, "TestNamespace.TestModule"); var thing = assembly.GetType("TestNamespace.IThing")!; - Assert.Equal( - Describe(viaDeclaredModule, thing), - Describe(viaApplicationModule, thing)); + Assert.Equal(Describe(viaDeclaredModule, thing), Describe(viaApplicationModule, thing)); Assert.NotNull(viaApplicationModule.BuildServiceProvider().GetService(thing)); } @@ -158,20 +198,27 @@ public partial class TestModule; /// deduplication sees them as one. /// [Fact] - public void LoadingBothModules_RegistersEachServiceOnce() { - var assembly = Compile(TopLevelProgramWith( - """ - public interface IThing; + public void LoadingBothModules_RegistersEachServiceOnce() + { + var assembly = Compile( + TopLevelProgramWith( + """ + public interface IThing; - [SingletonService] - public class Thing : IThing; + [SingletonService] + public class Thing : IThing; - [DependencyModule] - public partial class TestModule; - """)); + [DependencyModule] + public partial class TestModule; + """ + ) + ); var both = new ServiceCollection(); - both.AddModules(Module(assembly, "TestNamespace.ApplicationModule"), Module(assembly, "TestNamespace.TestModule")); + both.AddModules( + Module(assembly, "TestNamespace.ApplicationModule"), + Module(assembly, "TestNamespace.TestModule") + ); var thing = assembly.GetType("TestNamespace.IThing")!; @@ -183,9 +230,13 @@ private static string Describe(IServiceCollection services, Type serviceType) => ", ", services .Where(descriptor => descriptor.ServiceType == serviceType) - .Select(descriptor => $"{descriptor.Lifetime}:{descriptor.ImplementationType?.FullName}")); + .Select(descriptor => + $"{descriptor.Lifetime}:{descriptor.ImplementationType?.FullName}" + ) + ); - private static IServiceCollection Apply(Assembly assembly, string moduleName) { + private static IServiceCollection Apply(Assembly assembly, string moduleName) + { var services = new ServiceCollection(); services.AddModules(Module(assembly, moduleName)); @@ -193,21 +244,26 @@ private static IServiceCollection Apply(Assembly assembly, string moduleName) { return services; } - private static IDependencyModule Module(Assembly assembly, string moduleName) { - var type = assembly.GetType(moduleName) - ?? throw new InvalidOperationException( - $"No type '{moduleName}'. Present: " + - string.Join(", ", assembly.GetTypes().Select(t => t.FullName))); + private static IDependencyModule Module(Assembly assembly, string moduleName) + { + var type = + assembly.GetType(moduleName) + ?? throw new InvalidOperationException( + $"No type '{moduleName}'. Present: " + + string.Join(", ", assembly.GetTypes().Select(t => t.FullName)) + ); return (IDependencyModule)Activator.CreateInstance(type)!; } - private static Assembly Compile(IReadOnlyDictionary sources) { + private static Assembly Compile(IReadOnlyDictionary sources) + { var result = GeneratorTestHarness.Run( sources, null, OutputKind.ConsoleApplication, - assemblyName: "AutoModuleDelegation" + Interlocked.Increment(ref _counter)); + assemblyName: "AutoModuleDelegation" + Interlocked.Increment(ref _counter) + ); result.AssertNoErrors(); @@ -218,9 +274,13 @@ private static Assembly Compile(IReadOnlyDictionary sources) { emitted.Success, string.Join( Environment.NewLine, - emitted.Diagnostics - .Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) - .Select(diagnostic => $" {diagnostic.Id} {diagnostic.GetMessage()}"))); + emitted + .Diagnostics.Where(diagnostic => + diagnostic.Severity == DiagnosticSeverity.Error + ) + .Select(diagnostic => $" {diagnostic.Id} {diagnostic.GetMessage()}") + ) + ); return Assembly.Load(stream.ToArray()); } @@ -231,18 +291,17 @@ private static GeneratorResult Run(IReadOnlyDictionary sources) GeneratorTestHarness.Run(sources, null, OutputKind.ConsoleApplication); private static Dictionary TopLevelProgramWith(string services) => - new() { - ["Program.cs"] = - """ + new() + { + ["Program.cs"] = """ System.Console.WriteLine("hello"); """, - ["Services.cs"] = - $$""" - using DependencyModules.Runtime.Attributes; + ["Services.cs"] = $$""" + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - {{services}} - """ + {{services}} + """, }; } diff --git a/tests/DependencyModules.Tests/GeneratorTests/ConfigurationTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ConfigurationTests.cs index 36b2fb5..baee62a 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ConfigurationTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ConfigurationTests.cs @@ -10,10 +10,11 @@ namespace DependencyModules.Tests.GeneratorTests; /// The generator is configured through MSBuild properties surfaced as CompilerVisibleProperty in /// the packaged build/*.targets. These tests pin the mapping from property value to generated code. /// -public class ConfigurationTests { - +public class ConfigurationTests +{ [Fact] - public void ExcludeGeneratedCodeFromCoverage_DefaultsToOn() { + public void ExcludeGeneratedCodeFromCoverage_DefaultsToOn() + { var result = GeneratorTestHarness.Run(ModuleWithService); result.AssertNoErrors(); @@ -21,20 +22,24 @@ public void ExcludeGeneratedCodeFromCoverage_DefaultsToOn() { } [Fact] - public void ExcludeGeneratedCodeFromCoverage_False_OmitsTheAttribute() { + public void ExcludeGeneratedCodeFromCoverage_False_OmitsTheAttribute() + { var result = GeneratorTestHarness.Run( ModuleWithService, - new Dictionary { ["ExcludeGeneratedCodeFromCoverage"] = "false" }); + new Dictionary { ["ExcludeGeneratedCodeFromCoverage"] = "false" } + ); result.AssertNoErrors(); Assert.DoesNotContain("ExcludeFromCodeCoverage", result.SourceContaining("Dependencies")); } [Fact] - public void ExcludeGeneratedCodeFromCoverage_IsCaseInsensitive() { + public void ExcludeGeneratedCodeFromCoverage_IsCaseInsensitive() + { var result = GeneratorTestHarness.Run( ModuleWithService, - new Dictionary { ["ExcludeGeneratedCodeFromCoverage"] = "FALSE" }); + new Dictionary { ["ExcludeGeneratedCodeFromCoverage"] = "FALSE" } + ); result.AssertNoErrors(); Assert.DoesNotContain("ExcludeFromCodeCoverage", result.SourceContaining("Dependencies")); @@ -45,60 +50,68 @@ public void ExcludeGeneratedCodeFromCoverage_IsCaseInsensitive() { /// statement Program.cs; explicitly declared modules keep whatever namespace they are written in. /// [Fact] - public void RootNamespace_NamesTheAutoGeneratedApplicationModule() { + public void RootNamespace_NamesTheAutoGeneratedApplicationModule() + { var result = GeneratorTestHarness.Run( - new Dictionary { - ["Program.cs"] = - """ - using DependencyModules.Runtime.Attributes; - - System.Console.WriteLine("hello"); - """, - ["Services.cs"] = - """ - namespace TestNamespace; - - public interface IThing; - - [DependencyModules.Runtime.Attributes.SingletonService] - public class Thing : IThing; - """ + new Dictionary + { + ["Program.cs"] = """ + using DependencyModules.Runtime.Attributes; + + System.Console.WriteLine("hello"); + """, + ["Services.cs"] = """ + namespace TestNamespace; + + public interface IThing; + + [DependencyModules.Runtime.Attributes.SingletonService] + public class Thing : IThing; + """, }, new Dictionary { ["RootNamespace"] = "ConfiguredRoot" }, - OutputKind.ConsoleApplication); + OutputKind.ConsoleApplication + ); result.AssertNoErrors(); - Assert.Contains("namespace ConfiguredRoot", result.SourceContaining("ApplicationModule.Module")); + Assert.Contains( + "namespace ConfiguredRoot", + result.SourceContaining("ApplicationModule.Module") + ); } [Fact] - public void AutoGenerateModule_False_SuppressesTheApplicationModule() { + public void AutoGenerateModule_False_SuppressesTheApplicationModule() + { var result = GeneratorTestHarness.Run( - new Dictionary { - ["Program.cs"] = - """ - System.Console.WriteLine("hello"); - """, - ["Services.cs"] = - """ - using DependencyModules.Runtime.Attributes; + new Dictionary + { + ["Program.cs"] = """ + System.Console.WriteLine("hello"); + """, + ["Services.cs"] = """ + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - public interface IThing; + public interface IThing; - [SingletonService] - public class Thing : IThing; + [SingletonService] + public class Thing : IThing; - [DependencyModule] - public partial class TestModule; - """ + [DependencyModule] + public partial class TestModule; + """, }, new Dictionary { ["DependencyModules_AutoGenerateModule"] = "false" }, - OutputKind.ConsoleApplication); + OutputKind.ConsoleApplication + ); result.AssertNoErrors(); - Assert.DoesNotContain(result.GeneratedSources.Keys, key => key.Contains("ApplicationModule")); + Assert.DoesNotContain( + result.GeneratedSources.Keys, + key => key.Contains("ApplicationModule") + ); } [Theory] @@ -109,22 +122,29 @@ public partial class TestModule; [InlineData("RegistrationType.Try", RegistrationType.Try)] [InlineData("", RegistrationType.Add)] [InlineData("nonsense", RegistrationType.Add)] - public void GetRegistrationType_ParsesKnownValuesAndFallsBackToAdd(string input, RegistrationType expected) { + public void GetRegistrationType_ParsesKnownValuesAndFallsBackToAdd( + string input, + RegistrationType expected + ) + { Assert.Equal(expected, BaseSourceGenerator.GetRegistrationType(input)); } [Fact] - public void DependencyModulesRegistrationType_ChangesTheDefaultRegistrationMethod() { + public void DependencyModulesRegistrationType_ChangesTheDefaultRegistrationMethod() + { var result = GeneratorTestHarness.Run( ModuleWithService, - new Dictionary { ["DependencyModules_RegistrationType"] = "Try" }); + new Dictionary { ["DependencyModules_RegistrationType"] = "Try" } + ); result.AssertNoErrors(); Assert.Contains("TryAdd", result.SourceContaining("Dependencies")); } [Fact] - public void GeneratedCodeStyle_DefaultsToAllman() { + public void GeneratedCodeStyle_DefaultsToAllman() + { var result = GeneratorTestHarness.Run(ModuleWithService); result.AssertNoErrors(); @@ -132,21 +152,28 @@ public void GeneratedCodeStyle_DefaultsToAllman() { } [Fact] - public void GeneratedCodeStyle_KAndR_PutsTheBraceOnTheOpeningLine() { + public void GeneratedCodeStyle_KAndR_PutsTheBraceOnTheOpeningLine() + { var result = GeneratorTestHarness.Run( ModuleWithService, - new Dictionary { ["GeneratedCodeStyle"] = "KAndR" }); + new Dictionary { ["GeneratedCodeStyle"] = "KAndR" } + ); result.AssertNoErrors(); Assert.Contains("services) {", result.SourceContaining("Dependencies")); - Assert.Contains("public partial class TestModule {", result.SourceContaining("Dependencies")); + Assert.Contains( + "public partial class TestModule {", + result.SourceContaining("Dependencies") + ); } [Fact] - public void GeneratedCodeStyle_IsCaseInsensitive() { + public void GeneratedCodeStyle_IsCaseInsensitive() + { var result = GeneratorTestHarness.Run( ModuleWithService, - new Dictionary { ["GeneratedCodeStyle"] = "kandr" }); + new Dictionary { ["GeneratedCodeStyle"] = "kandr" } + ); result.AssertNoErrors(); Assert.Contains("services) {", result.SourceContaining("Dependencies")); @@ -157,17 +184,18 @@ public void GeneratedCodeStyle_IsCaseInsensitive() { /// stance DependencyModules_RegistrationType takes. /// [Fact] - public void GeneratedCodeStyle_UnknownValue_FallsBackToAllman() { + public void GeneratedCodeStyle_UnknownValue_FallsBackToAllman() + { var result = GeneratorTestHarness.Run( ModuleWithService, - new Dictionary { ["GeneratedCodeStyle"] = "whitesmiths" }); + new Dictionary { ["GeneratedCodeStyle"] = "whitesmiths" } + ); result.AssertNoErrors(); Assert.Contains("services)\n {", result.SourceContaining("Dependencies")); } - private const string ModuleWithService = - """ + private const string ModuleWithService = """ using DependencyModules.Runtime.Attributes; namespace TestNamespace; diff --git a/tests/DependencyModules.Tests/GeneratorTests/ConventionContractTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ConventionContractTests.cs index 3e0c57b..297113b 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ConventionContractTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ConventionContractTests.cs @@ -3,7 +3,6 @@ using DependencyModules.Tests.Infrastructure; using Microsoft.Extensions.DependencyInjection; using Xunit; - using GeneratorNames = DependencyModules.Conventions.ConventionContractSource; namespace DependencyModules.Tests.GeneratorTests; @@ -23,20 +22,23 @@ namespace DependencyModules.Tests.GeneratorTests; /// registers nothing, which is the failure mode this generator exists to prevent everywhere else. /// /// -public class ConventionContractTests { - +public class ConventionContractTests +{ [Fact] - public void TheGeneratorLooksForTheNamespaceTheContractsAreDeclaredIn() { + public void TheGeneratorLooksForTheNamespaceTheContractsAreDeclaredIn() + { Assert.Equal(GeneratorNames.Namespace, typeof(IConventionModule).Namespace); } [Fact] - public void TheGeneratorLooksForTheInterfaceTheContractsDeclare() { + public void TheGeneratorLooksForTheInterfaceTheContractsDeclare() + { Assert.Equal(GeneratorNames.ConventionModule, nameof(IConventionModule)); } [Fact] - public void TheGeneratorLooksForTheMethodTheInterfaceDeclares() { + public void TheGeneratorLooksForTheMethodTheInterfaceDeclares() + { var method = Assert.Single(typeof(IConventionModule).GetMethods()); Assert.Equal(GeneratorNames.ConventionMethod, method.Name); @@ -51,8 +53,10 @@ public void TheGeneratorLooksForTheMethodTheInterfaceDeclares() { /// hurried addition gets wrong and nothing else would catch. /// [Fact] - public void EveryRegistrationVerbContinuesTheChain() { - var breaks = typeof(IConventionRegistration).GetMethods() + public void EveryRegistrationVerbContinuesTheChain() + { + var breaks = typeof(IConventionRegistration) + .GetMethods() .Where(method => method.ReturnType != typeof(IConventionRegistration)) .Select(method => method.Name) .ToArray(); @@ -64,8 +68,10 @@ public void EveryRegistrationVerbContinuesTheChain() { /// Every entry point produces a registration to continue from. /// [Fact] - public void EveryRegisterAllOverloadStartsTheChain() { - var breaks = typeof(IConventionDefinitions).GetMethods() + public void EveryRegisterAllOverloadStartsTheChain() + { + var breaks = typeof(IConventionDefinitions) + .GetMethods() .Where(method => method.ReturnType != typeof(IConventionRegistration)) .Select(method => method.Name) .ToArray(); @@ -73,8 +79,7 @@ public void EveryRegisterAllOverloadStartsTheChain() { Assert.Empty(breaks); } - private const string Preamble = - """ + private const string Preamble = """ using DependencyModules.Runtime.Attributes; using DependencyModules.Runtime.Conventions; @@ -98,41 +103,51 @@ public class Greeter : IGreeter { public string Greet() => "hello"; } /// that it stays retired. /// [Fact] - public void AnImplicitPublicImplementationDeclaresConventions() { + public void AnImplicitPublicImplementationDeclaresConventions() + { var assembly = GeneratedAssembly.Create( - Preamble + - """ - [DependencyModule] - public partial class TestModule : IConventionModule { - public void Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().AsSingleton(); + Preamble + + """ + [DependencyModule] + public partial class TestModule : IConventionModule { + public void Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().AsSingleton(); + } } - } - """); + """ + ); var provider = assembly.BuildProvider(); - Assert.Equal("hello", ((dynamic)provider.GetRequiredService(assembly.Type("IGreeter"))).Greet()); + Assert.Equal( + "hello", + ((dynamic)provider.GetRequiredService(assembly.Type("IGreeter"))).Greet() + ); } /// /// The explicit form still compiles and still registers, so nobody has to rewrite anything. /// [Fact] - public void TheExplicitImplementationStillDeclaresConventions() { + public void TheExplicitImplementationStillDeclaresConventions() + { var assembly = GeneratedAssembly.Create( - Preamble + - """ - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().AsSingleton(); + Preamble + + """ + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().AsSingleton(); + } } - } - """); + """ + ); var provider = assembly.BuildProvider(); - Assert.Equal("hello", ((dynamic)provider.GetRequiredService(assembly.Type("IGreeter"))).Greet()); + Assert.Equal( + "hello", + ((dynamic)provider.GetRequiredService(assembly.Type("IGreeter"))).Greet() + ); } } diff --git a/tests/DependencyModules.Tests/GeneratorTests/ConventionDecoratorTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ConventionDecoratorTests.cs index 184c03a..678838a 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ConventionDecoratorTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ConventionDecoratorTests.cs @@ -14,10 +14,9 @@ namespace DependencyModules.Tests.GeneratorTests; /// because an open generic registration cannot be decorated. The error blamed the open generic /// limitation, which is a long way from the cause. /// -public class ConventionDecoratorTests { - - private const string Preamble = - """ +public class ConventionDecoratorTests +{ + private const string Preamble = """ using System.Collections.Generic; using DependencyModules.Runtime.Attributes; using DependencyModules.Runtime.Conventions; @@ -62,7 +61,8 @@ private static GeneratedAssembly Compile(string module) => /// shape, and the reason this combination has to work. /// [Fact] - public void OneDecoratorWrapsEveryConventionRegisteredHandler() { + public void OneDecoratorWrapsEveryConventionRegisteredHandler() + { var assembly = Compile( """ [DependencyModule] @@ -71,7 +71,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll(typeof(IRequestHandler<,>)).AsScoped(); } } - """); + """ + ); var provider = assembly.BuildProvider(); var log = provider.GetRequiredService(assembly.Type("Log")); @@ -80,15 +81,20 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { var handler = assembly.Type("IRequestHandler`2"); var orderId = assembly.Type("OrderId"); - foreach (var request in new[] { "CreateOrder", "RenameOrder" }) { + foreach (var request in new[] { "CreateOrder", "RenameOrder" }) + { var requestType = assembly.Type(request); - var service = provider.GetRequiredService(handler.MakeGenericType(requestType, orderId)); + var service = provider.GetRequiredService( + handler.MakeGenericType(requestType, orderId) + ); // Every handler resolves as the decorator, not as the implementation. Assert.Equal("LoggingHandler`2", service.GetType().Name); - service.GetType().GetMethod("Handle")!.Invoke( - service, new[] { Activator.CreateInstance(requestType) }); + service + .GetType() + .GetMethod("Handle")! + .Invoke(service, new[] { Activator.CreateInstance(requestType) }); } Assert.Equal(["handling CreateOrder", "handling RenameOrder"], lines); @@ -98,7 +104,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// The decorator itself is not registered as a service. /// [Fact] - public void ADecoratorIsNotAConventionCandidate() { + public void ADecoratorIsNotAConventionCandidate() + { var assembly = Compile( """ [DependencyModule] @@ -107,7 +114,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll(typeof(IRequestHandler<,>)).AsScoped(); } } - """); + """ + ); // Two handlers, two registrations. The decorator rewrote them in place rather than adding // a third, and never registered itself as an open generic. @@ -120,7 +128,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// The exclusion is on the declaration, so it holds however the convention selects. /// [Fact] - public void ADecoratorIsExcludedWhenSelectedByFilterRatherThanInterface() { + public void ADecoratorIsExcludedWhenSelectedByFilterRatherThanInterface() + { var assembly = Compile( """ [DependencyModule] @@ -129,10 +138,17 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().WithName("*Handler").AsSelf().AsScoped(); } } - """); - - Assert.Contains(assembly.Services, d => d.ServiceType == assembly.Type("CreateOrderHandler")); - Assert.Contains(assembly.Services, d => d.ServiceType == assembly.Type("RenameOrderHandler")); + """ + ); + + Assert.Contains( + assembly.Services, + d => d.ServiceType == assembly.Type("CreateOrderHandler") + ); + Assert.Contains( + assembly.Services, + d => d.ServiceType == assembly.Type("RenameOrderHandler") + ); // LoggingHandler ends in "Handler" and would otherwise match. Assert.DoesNotContain(assembly.Services, d => d.ServiceType.Name == "LoggingHandler`2"); diff --git a/tests/DependencyModules.Tests/GeneratorTests/ConventionRegistrationTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ConventionRegistrationTests.cs index 91dfd86..0a0f137 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ConventionRegistrationTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ConventionRegistrationTests.cs @@ -14,10 +14,9 @@ namespace DependencyModules.Tests.GeneratorTests; /// source passes happily while the wrong service type is registered, which is exactly the class of /// mistake convention matching is most likely to make. /// -public class ConventionRegistrationTests { - - private const string Preamble = - """ +public class ConventionRegistrationTests +{ + private const string Preamble = """ using System; using DependencyModules.Runtime.Attributes; using DependencyModules.Runtime.Conventions; @@ -44,9 +43,9 @@ private static GeneratorResult Run(string source) => [Theory] [InlineData("Development", 2)] [InlineData("Production", 1)] - public void ConventionsHonourEnvironmentConditions(string environmentName, int expected) { - const string source = - """ + public void ConventionsHonourEnvironmentConditions(string environmentName, int expected) + { + const string source = """ public interface IFoo { } public class AlwaysFoo : IFoo { } @@ -64,7 +63,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { var assembly = GeneratedAssembly.Create( Preamble + source, - environment: new ModuleEnvironment(environmentName)); + environment: new ModuleEnvironment(environmentName) + ); Assert.Equal(expected, assembly.Descriptors("IFoo").Count); } @@ -76,9 +76,9 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { [Theory] [InlineData("Development", 2)] [InlineData("Production", 0)] - public void ConventionsCarryTheirOwnEnvironmentCondition(string environmentName, int expected) { - const string source = - """ + public void ConventionsCarryTheirOwnEnvironmentCondition(string environmentName, int expected) + { + const string source = """ public interface IFoo { } public class OneFoo : IFoo { } @@ -94,7 +94,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { var assembly = GeneratedAssembly.Create( Preamble + source, - environment: new ModuleEnvironment(environmentName)); + environment: new ModuleEnvironment(environmentName) + ); Assert.Equal(expected, assembly.Descriptors("IFoo").Count); } @@ -112,10 +113,12 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { [InlineData("Development", "us", 1)] [InlineData("Production", "eu", 0)] public void ConventionAndClassConditionsCombineWithAnd( - string environmentName, string region, int expected) { - - const string source = - """ + string environmentName, + string region, + int expected + ) + { + const string source = """ public interface IFoo { } public class PlainFoo : IFoo { } @@ -133,7 +136,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { var assembly = GeneratedAssembly.Create( Preamble + source, - environment: new ModuleEnvironment(false, environmentName) { { "REGION", region } }); + environment: new ModuleEnvironment(false, environmentName) { { "REGION", region } } + ); Assert.Equal(expected, assembly.Descriptors("IFoo").Count); } @@ -141,9 +145,9 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { [Theory] [InlineData("on", 1)] [InlineData("off", 0)] - public void ConventionsCarryValueConditions(string flag, int expected) { - const string source = - """ + public void ConventionsCarryValueConditions(string flag, int expected) + { + const string source = """ public interface IFoo { } public class OneFoo : IFoo { } @@ -158,7 +162,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { var assembly = GeneratedAssembly.Create( Preamble + source, - environment: new ModuleEnvironment(false, "Development") { { "FLAG", flag } }); + environment: new ModuleEnvironment(false, "Development") { { "FLAG", flag } } + ); Assert.Equal(expected, assembly.Descriptors("IFoo").Count); } @@ -166,9 +171,9 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { [Theory] [InlineData("Development", 0)] [InlineData("Production", 1)] - public void ConventionsCarryNegatedConditions(string environmentName, int expected) { - const string source = - """ + public void ConventionsCarryNegatedConditions(string environmentName, int expected) + { + const string source = """ public interface IFoo { } public class OneFoo : IFoo { } @@ -183,7 +188,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { var assembly = GeneratedAssembly.Create( Preamble + source, - environment: new ModuleEnvironment(environmentName)); + environment: new ModuleEnvironment(environmentName) + ); Assert.Equal(expected, assembly.Descriptors("IFoo").Count); } @@ -200,10 +206,12 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { [InlineData("Development", 1, 1)] [InlineData("Production", 0, 1)] public void ConventionsWithDifferentConditionsDoNotShareAGuard( - string environmentName, int expectedFoo, int expectedBar) { - - const string source = - """ + string environmentName, + int expectedFoo, + int expectedBar + ) + { + const string source = """ public interface IFoo { } public interface IBar { } @@ -220,7 +228,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { var assembly = GeneratedAssembly.Create( Preamble + source, - environment: new ModuleEnvironment(environmentName)); + environment: new ModuleEnvironment(environmentName) + ); Assert.Equal(expectedFoo, assembly.Descriptors("IFoo").Count); Assert.Equal(expectedBar, assembly.Descriptors("IBar").Count); @@ -236,9 +245,9 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// and registered nothing. One convention, named twice. /// [Fact] - public void APartialClassReachingTheServiceFromTwoPartsIsNotAmbiguous() { - const string source = - """ + public void APartialClassReachingTheServiceFromTwoPartsIsNotAmbiguous() + { + const string source = """ public interface IFoo { } public abstract class FooBase : IFoo { } @@ -265,9 +274,9 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// Two parts declaring different interfaces that both reach the scanned one is still one class. /// [Fact] - public void APartialClassDeclaringTheServiceTwiceIsNotAmbiguous() { - const string source = - """ + public void APartialClassDeclaringTheServiceTwiceIsNotAmbiguous() + { + const string source = """ public interface IFoo { } public interface IFooPrime : IFoo { } @@ -292,9 +301,9 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// needed even when another part reaches the same interface through a base class. /// [Fact] - public void APartDeclaringTheServiceMakesItADeclaredMatch() { - const string source = - """ + public void APartDeclaringTheServiceMakesItADeclaredMatch() + { + const string source = """ public interface IFoo { } public abstract class FooBase : IFoo { } @@ -322,9 +331,9 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// have, which is a CS error inside generated code. /// [Fact] - public void TheConstructorMayBeDeclaredInAnotherPart() { - const string source = - """ + public void TheConstructorMayBeDeclaredInAnotherPart() + { + const string source = """ public interface IDep { } [SingletonService] @@ -359,9 +368,9 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// and the two registrations are independently predictable from reading the module. /// [Fact] - public void ATypeMatchedThroughDifferentInterfacesRegistersAsBoth() { - const string source = - """ + public void ATypeMatchedThroughDifferentInterfacesRegistersAsBoth() + { + const string source = """ public interface IFoo { } public interface IBar { } @@ -391,7 +400,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// Sharing one instance is AsSelfWithInterfaces, and it is opt-in. /// [Fact] - public void EachRoleKeepsItsOwnLifetimeAndItsOwnInstance() { + public void EachRoleKeepsItsOwnLifetimeAndItsOwnInstance() + { var assembly = Compile( """ public interface IFoo { } @@ -406,7 +416,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsScoped(); } } - """); + """ + ); Assert.Equal(ServiceLifetime.Singleton, assembly.Descriptor("IFoo").Lifetime); Assert.Equal(ServiceLifetime.Scoped, assembly.Descriptor("IBar").Lifetime); @@ -415,7 +426,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { Assert.NotSame( provider.GetService(assembly.Type("IFoo")), - provider.GetService(assembly.Type("IBar"))); + provider.GetService(assembly.Type("IBar")) + ); } /// @@ -426,7 +438,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// green build, no diagnostic, and an event that never fires. /// [Fact] - public void OneConventionRegistersEveryClosingACandidateImplements() { + public void OneConventionRegistersEveryClosingACandidateImplements() + { var assembly = Compile( """ public interface INotificationHandler { } @@ -442,7 +455,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll(typeof(INotificationHandler<>)).AsTransient(); } } - """); + """ + ); var handlerType = assembly.Type("INotificationHandler`1"); @@ -460,7 +474,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// implementations — the shape the attribute path produces and the writer relies on. /// [Fact] - public void SeveralClosingsProduceOneImplementationWithSeveralRegistrations() { + public void SeveralClosingsProduceOneImplementationWithSeveralRegistrations() + { var assembly = Compile( """ public interface IHandler { } @@ -476,17 +491,17 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll(typeof(IHandler<>)).AsTransient(); } } - """); + """ + ); - var registered = assembly.Services - .Where(d => d.ImplementationType == assembly.Type("Both")) + var registered = assembly + .Services.Where(d => d.ImplementationType == assembly.Type("Both")) .ToArray(); Assert.Equal(2, registered.Length); } - private const string Marker = - """ + private const string Marker = """ public class HandlerAttribute : System.Attribute { } public class LegacyAttribute : System.Attribute { } @@ -503,20 +518,25 @@ public class MarkedLegacy : IFoo { } """; [Fact] - public void WithAttributeLimitsMatchesToTypesCarryingIt() { + public void WithAttributeLimitsMatchesToTypesCarryingIt() + { var assembly = Compile( - Marker + - """ - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().WithAttribute().AsSingleton(); + Marker + + """ + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().WithAttribute().AsSingleton(); + } } - } - """); + """ + ); - var implementations = assembly.Descriptors("IFoo").Select(d => d.ImplementationType).ToArray(); + var implementations = assembly + .Descriptors("IFoo") + .Select(d => d.ImplementationType) + .ToArray(); Assert.Equal(2, implementations.Length); Assert.Contains(assembly.Type("Marked"), implementations); @@ -525,41 +545,48 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { } [Fact] - public void WithoutAttributeExcludesTypesCarryingIt() { + public void WithoutAttributeExcludesTypesCarryingIt() + { var assembly = Compile( - Marker + - """ - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().WithoutAttribute().AsSingleton(); + Marker + + """ + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().WithoutAttribute().AsSingleton(); + } } - } - """); + """ + ); - var implementations = assembly.Descriptors("IFoo").Select(d => d.ImplementationType).ToArray(); + var implementations = assembly + .Descriptors("IFoo") + .Select(d => d.ImplementationType) + .ToArray(); Assert.Equal(2, implementations.Length); Assert.DoesNotContain(assembly.Type("MarkedLegacy"), implementations); } [Fact] - public void AttributeFiltersCombineWithAnd() { + public void AttributeFiltersCombineWithAnd() + { var assembly = Compile( - Marker + - """ - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll() - .WithAttribute() - .WithoutAttribute() - .AsSingleton(); + Marker + + """ + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll() + .WithAttribute() + .WithoutAttribute() + .AsSingleton(); + } } - } - """); + """ + ); Assert.Equal(assembly.Type("Marked"), assembly.Descriptor("IFoo").ImplementationType); } @@ -568,20 +595,22 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// Resolved rather than matched on how it was written, so the qualified form counts. /// [Fact] - public void ANamespaceQualifiedAttributeStillMatches() { + public void ANamespaceQualifiedAttributeStillMatches() + { var assembly = Compile( - Marker + - """ - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll() - .WithAttribute() - .AsSingleton(); + Marker + + """ + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll() + .WithAttribute() + .AsSingleton(); + } } - } - """); + """ + ); Assert.Equal(2, assembly.Descriptors("IFoo").Count); } @@ -592,48 +621,56 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { [InlineData("Order?epository", 1)] [InlineData("TestNamespace.*Repository", 2)] [InlineData("*repository", 0)] - public void WithNameMatchesTheGlob(string pattern, int expected) { + public void WithNameMatchesTheGlob(string pattern, int expected) + { var result = Run( $$""" - public class OrderRepository { } - public class UserRepository { } - public class OrderService { } - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().WithName("{{pattern}}").AsSelf().AsScoped(); - } - } - """); + public class OrderRepository { } + public class UserRepository { } + public class OrderService { } + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().WithName("{{pattern}}").AsSelf().AsScoped(); + } + } + """ + ); // A pattern matching nothing is DM0005 rather than silence. - if (expected == 0) { + if (expected == 0) + { Assert.Contains(result.GeneratorDiagnostics, d => d.Id == "DM0005"); return; } var assembly = GeneratedAssembly.Create( - Preamble + - $$""" - public class OrderRepository { } - public class UserRepository { } - public class OrderService { } - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().WithName("{{pattern}}").AsSelf().AsScoped(); - } - } - """); - - Assert.Equal(expected, assembly.Services.Count(d => d.ImplementationType?.Namespace == "TestNamespace")); + Preamble + + $$""" + public class OrderRepository { } + public class UserRepository { } + public class OrderService { } + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().WithName("{{pattern}}").AsSelf().AsScoped(); + } + } + """ + ); + + Assert.Equal( + expected, + assembly.Services.Count(d => d.ImplementationType?.Namespace == "TestNamespace") + ); } [Fact] - public void AsRegistersEveryMatchAsOneNamedService() { + public void AsRegistersEveryMatchAsOneNamedService() + { var assembly = Compile( """ public interface IFoo { } @@ -647,14 +684,16 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().As().AsSingleton(); } } - """); + """ + ); Assert.Empty(assembly.Descriptors("IFoo")); Assert.Equal(assembly.Type("Foo"), assembly.Descriptor("IMarker").ImplementationType); } [Fact] - public void AsMatchingInterfaceRegistersFooAsIFoo() { + public void AsMatchingInterfaceRegistersFooAsIFoo() + { var assembly = Compile( """ public interface IMarker { } @@ -670,7 +709,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsMatchingInterface().AsSingleton(); } } - """); + """ + ); Assert.Equal(assembly.Type("Foo"), assembly.Descriptor("IFoo").ImplementationType); Assert.Equal(assembly.Type("Bar"), assembly.Descriptor("IBar").ImplementationType); @@ -685,7 +725,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// which is never what "register this as its interfaces" means. /// [Fact] - public void AsSelfWithInterfacesSkipsSystemInterfaces() { + public void AsSelfWithInterfacesSkipsSystemInterfaces() + { var assembly = Compile( """ public interface IMarker { } @@ -702,7 +743,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSelfWithInterfaces().AsSingleton().IncludeBaseClasses(); } } - """); + """ + ); Assert.Single(assembly.Descriptors("IMarker")); @@ -710,7 +752,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { } [Fact] - public void AsSelfWithInterfacesSkipsGenericSystemInterfaces() { + public void AsSelfWithInterfacesSkipsGenericSystemInterfaces() + { var assembly = Compile( """ public interface IRule { } @@ -731,12 +774,16 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll(typeof(IValidator<>)).AsSelfWithInterfaces().AsScoped().IncludeBaseClasses(); } } - """); + """ + ); // The validator interface survives; the enumerable ones do not. Assert.Single(assembly.Services, d => d.ServiceType.Name == "IValidator`1"); - Assert.DoesNotContain(assembly.Services, d => d.ServiceType.Namespace?.StartsWith("System") == true); + Assert.DoesNotContain( + assembly.Services, + d => d.ServiceType.Namespace?.StartsWith("System") == true + ); } /// @@ -744,7 +791,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// whatever namespace it lives in — refusing that is a different kind of wrong. /// [Fact] - public void ANamedSystemServiceTypeIsStillRegistered() { + public void ANamedSystemServiceTypeIsStillRegistered() + { var assembly = Compile( """ public class Closeable : System.IDisposable { @@ -757,7 +805,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton(); } } - """); + """ + ); Assert.Single(assembly.Services, d => d.ServiceType == typeof(IDisposable)); } @@ -766,7 +815,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// Filtering everything away degrades to AsSelf rather than to nothing. /// [Fact] - public void ATypeReachingOnlySystemInterfacesRegistersItself() { + public void ATypeReachingOnlySystemInterfacesRegistersItself() + { var assembly = Compile( """ public interface IMarker { } @@ -781,15 +831,15 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSelf().AsSingleton().IncludeBaseClasses(); } } - """); + """ + ); // Nothing to assert beyond the module compiling and registering something; the shape that // matters is covered by AsSelfWithInterfacesSkipsSystemInterfaces. Assert.NotEmpty(assembly.Services); } - private const string Validators = - """ + private const string Validators = """ public interface IRule { } public interface IValidator { } public interface IValidator : IValidator { } @@ -808,18 +858,20 @@ public class FooValidator : AbstractValidator { } /// The FluentValidation shape: registered as the matched interface and as the concrete type. /// [Fact] - public void AlsoAsSelfRegistersTheInterfaceAndTheType() { + public void AlsoAsSelfRegistersTheInterfaceAndTheType() + { var assembly = Compile( - Validators + - """ - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll(typeof(IValidator<>)).IncludeBaseClasses().AlsoAsSelf().AsScoped(); + Validators + + """ + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll(typeof(IValidator<>)).IncludeBaseClasses().AlsoAsSelf().AsScoped(); + } } - } - """); + """ + ); Assert.Contains(assembly.Services, d => d.ServiceType.Name == "IValidator`1"); Assert.Contains(assembly.Services, d => d.ServiceType == assembly.Type("FooValidator")); @@ -830,54 +882,64 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// independently and hands you two; this is a deliberate difference. /// [Fact] - public void AlsoAsSelfSharesOneInstance() { + public void AlsoAsSelfSharesOneInstance() + { var assembly = Compile( - Validators + - """ - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll(typeof(IValidator<>)).IncludeBaseClasses().AlsoAsSelf().AsScoped(); + Validators + + """ + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll(typeof(IValidator<>)).IncludeBaseClasses().AlsoAsSelf().AsScoped(); + } } - } - """); + """ + ); var provider = assembly.BuildProvider(); using var scope = provider.CreateScope(); - var validatorInterface = assembly.Services - .First(d => d.ServiceType.Name == "IValidator`1").ServiceType; + var validatorInterface = assembly + .Services.First(d => d.ServiceType.Name == "IValidator`1") + .ServiceType; Assert.Same( scope.ServiceProvider.GetService(validatorInterface), - scope.ServiceProvider.GetService(assembly.Type("FooValidator"))); + scope.ServiceProvider.GetService(assembly.Type("FooValidator")) + ); } /// /// Only the interfaces the convention matched, not everything the type can reach. /// [Fact] - public void AlsoAsSelfDoesNotPullInUnmatchedInterfaces() { + public void AlsoAsSelfDoesNotPullInUnmatchedInterfaces() + { var assembly = Compile( - Validators + - """ - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll(typeof(IValidator<>)).IncludeBaseClasses().AlsoAsSelf().AsScoped(); + Validators + + """ + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll(typeof(IValidator<>)).IncludeBaseClasses().AlsoAsSelf().AsScoped(); + } } - } - """); + """ + ); // IValidator and the enumerable interfaces are reachable but were not matched. Assert.DoesNotContain(assembly.Services, d => d.ServiceType == assembly.Type("IValidator")); - Assert.DoesNotContain(assembly.Services, d => d.ServiceType.Namespace?.StartsWith("System") == true); + Assert.DoesNotContain( + assembly.Services, + d => d.ServiceType.Namespace?.StartsWith("System") == true + ); } [Fact] - public void AsSelfAndAlsoAsSelfTogetherIsRefused() { + public void AsSelfAndAlsoAsSelfTogetherIsRefused() + { var result = Run( """ public interface IFoo { } @@ -889,7 +951,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSelf().AlsoAsSelf().AsSingleton(); } } - """); + """ + ); Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0009"); } @@ -899,7 +962,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// convention repeating itself is not an ambiguity. /// [Fact] - public void AlsoAsSelfRegistersTheTypeOnceAcrossSeveralClosings() { + public void AlsoAsSelfRegistersTheTypeOnceAcrossSeveralClosings() + { var result = Run( """ public interface IHandler { } @@ -915,7 +979,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll(typeof(IHandler<>)).AlsoAsSelf().AsScoped(); } } - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0004"); @@ -934,14 +999,16 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll(typeof(IHandler<>)).AlsoAsSelf().AsScoped(); } } - """); + """ + ); Assert.Single(assembly.Services, d => d.ServiceType == assembly.Type("Both")); Assert.Equal(2, assembly.Services.Count(d => d.ServiceType.Name == "IHandler`1")); } [Fact] - public void UsingChoosesHowTheRegistrationIsAdded() { + public void UsingChoosesHowTheRegistrationIsAdded() + { var assembly = Compile( """ public interface IFoo { } @@ -954,14 +1021,16 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton().Using(RegistrationType.Try); } } - """); + """ + ); // Try registers the service type once and skips the second match. Assert.Single(assembly.Descriptors("IFoo")); } [Fact] - public void WithKeyRegistersUnderAServiceKey() { + public void WithKeyRegistersUnderAServiceKey() + { var assembly = Compile( """ public interface IFoo { } @@ -973,7 +1042,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton().WithKey("primary"); } } - """); + """ + ); var descriptor = assembly.Descriptor("IFoo"); @@ -982,7 +1052,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { Assert.IsType( assembly.Type("Foo"), - assembly.BuildProvider().GetRequiredKeyedService(assembly.Type("IFoo"), "primary")); + assembly.BuildProvider().GetRequiredKeyedService(assembly.Type("IFoo"), "primary") + ); } /// @@ -990,7 +1061,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// the source does not say which. /// [Fact] - public void TwoConventionsRegisteringOneServiceTypeIsAmbiguous() { + public void TwoConventionsRegisteringOneServiceTypeIsAmbiguous() + { var result = Run( """ public interface IFoo { } @@ -1005,7 +1077,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton(); } } - """); + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0004"); @@ -1018,7 +1091,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// redundant and collapsing it silently is the failure mode this codebase avoids. /// [Fact] - public void ADuplicatedConventionIsAmbiguousEvenWithEqualLifetimes() { + public void ADuplicatedConventionIsAmbiguousEvenWithEqualLifetimes() + { var result = Run( """ public interface IFoo { } @@ -1032,7 +1106,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton(); } } - """); + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0004"); @@ -1040,7 +1115,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { } [Fact] - public void AsSelfRegistersTheConcreteTypeRatherThanTheService() { + public void AsSelfRegistersTheConcreteTypeRatherThanTheService() + { var assembly = Compile( """ public interface IFoo { } @@ -1052,7 +1128,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSelf().AsSingleton(); } } - """); + """ + ); Assert.Empty(assembly.Descriptors("IFoo")); Assert.Single(assembly.Descriptors("Foo")); @@ -1063,7 +1140,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// which is why it emits the cross-wire shape rather than two independent registrations. /// [Fact] - public void AsSelfWithInterfacesSharesOneInstance() { + public void AsSelfWithInterfacesSharesOneInstance() + { var assembly = Compile( """ public interface IFoo { } @@ -1076,7 +1154,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSelfWithInterfaces().AsSingleton(); } } - """); + """ + ); var provider = assembly.BuildProvider(); @@ -1093,7 +1172,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// The hole this closes: a concrete class implementing nothing, selected by namespace. /// [Fact] - public void AConcreteTypeWithNoInterfaceRegistersByNamespace() { + public void AConcreteTypeWithNoInterfaceRegistersByNamespace() + { var assembly = Compile( """ public class OrderCalculator { } @@ -1105,14 +1185,16 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().InNamespaceOf().AsSelf().AsScoped(); } } - """); + """ + ); Assert.Single(assembly.Descriptors("OrderCalculator")); Assert.Single(assembly.Descriptors("OrderValidator")); } [Fact] - public void NamespaceFiltersNarrowAnAssignabilityConvention() { + public void NamespaceFiltersNarrowAnAssignabilityConvention() + { var result = Run( """ public interface IFoo { } @@ -1124,14 +1206,16 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().InNamespaces("SomewhereElse").AsSingleton(); } } - """); + """ + ); // Filtered out entirely, which is DM0005 rather than silence. Assert.Contains(result.GeneratorDiagnostics, d => d.Id == "DM0005"); } [Fact] - public void NotInNamespacesExcludesAfterInclusions() { + public void NotInNamespacesExcludesAfterInclusions() + { var assembly = Compile( """ public interface IFoo { } @@ -1143,13 +1227,15 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().NotInNamespaces("TestNamespace").AsSingleton(); } } - """); + """ + ); Assert.Empty(assembly.Descriptors("IFoo")); } [Fact] - public void RegisterAllWithNoServiceTypeNeedsAShape() { + public void RegisterAllWithNoServiceTypeNeedsAShape() + { var result = Run( """ public class Thing { } @@ -1160,7 +1246,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().InNamespaceOf().AsScoped(); } } - """); + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0009"); @@ -1171,7 +1258,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// Without a filter it would match every class in the compilation, so it is refused. /// [Fact] - public void RegisterAllWithNoServiceTypeAndNoFilterIsRefused() { + public void RegisterAllWithNoServiceTypeAndNoFilterIsRefused() + { var result = Run( """ public class Thing { } @@ -1182,7 +1270,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSelf().AsScoped(); } } - """); + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0009"); @@ -1190,7 +1279,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { } [Fact] - public void RegistersEveryTypeDeclaringTheServiceInterface() { + public void RegistersEveryTypeDeclaringTheServiceInterface() + { var assembly = Compile( """ public interface IFoo { } @@ -1205,7 +1295,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton(); } } - """); + """ + ); var descriptors = assembly.Descriptors("IFoo"); @@ -1216,7 +1307,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { } [Fact] - public void RegisteredServiceResolves() { + public void RegisteredServiceResolves() + { var assembly = Compile( """ public interface IFoo { } @@ -1228,7 +1320,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsScoped(); } } - """); + """ + ); Assert.IsType(assembly.Type("Foo"), assembly.ResolveRequired("IFoo")); Assert.Equal(ServiceLifetime.Scoped, assembly.Descriptor("IFoo").Lifetime); @@ -1239,7 +1332,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// so a convention naming the base interface matches by declaration. /// [Fact] - public void MatchesThroughInterfaceInheritance() { + public void MatchesThroughInterfaceInheritance() + { var assembly = Compile( """ public interface IFoo { } @@ -1253,7 +1347,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton(); } } - """); + """ + ); Assert.Equal(assembly.Type("Thing"), assembly.Descriptor("IFoo").ImplementationType); } @@ -1264,7 +1359,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// takes an explicit opt-in. /// [Fact] - public void DoesNotMatchThroughABaseClassByDefault() { + public void DoesNotMatchThroughABaseClassByDefault() + { var result = Run( """ public interface IFoo { } @@ -1277,14 +1373,16 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton(); } } - """); + """ + ); // Nothing matched, which is DM0005 rather than silence. Assert.Contains(result.GeneratorDiagnostics, d => d.Id == "DM0005"); } [Fact] - public void MatchesThroughABaseClassWhenAskedTo() { + public void MatchesThroughABaseClassWhenAskedTo() + { var assembly = Compile( """ public interface IFoo { } @@ -1297,7 +1395,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton().IncludeBaseClasses(); } } - """); + """ + ); Assert.Equal(assembly.Type("Thing"), assembly.Descriptor("IFoo").ImplementationType); } @@ -1307,7 +1406,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// actually implements, not the open definition. /// [Fact] - public void ClosesAnOpenGenericAgainstEachImplementation() { + public void ClosesAnOpenGenericAgainstEachImplementation() + { var assembly = Compile( """ public interface IHandler { } @@ -1325,11 +1425,15 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll(typeof(IHandler<,>)).AsTransient(); } } - """); + """ + ); var handler = assembly.Type("IHandler`2"); - var createOrder = handler.MakeGenericType(assembly.Type("CreateOrder"), assembly.Type("OrderId")); + var createOrder = handler.MakeGenericType( + assembly.Type("CreateOrder"), + assembly.Type("OrderId") + ); var rename = handler.MakeGenericType(assembly.Type("Rename"), assembly.Type("OrderId")); var provider = assembly.BuildProvider(); @@ -1343,7 +1447,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// RegisterAll<IHandler<A,B>>() would pick up every other closing too. /// [Fact] - public void AClosedGenericConventionMatchesOnlyThatConstruction() { + public void AClosedGenericConventionMatchesOnlyThatConstruction() + { var assembly = Compile( """ public interface IRepo { } @@ -1356,20 +1461,23 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll>().AsSingleton(); } } - """); + """ + ); var repo = assembly.Type("IRepo`1"); var provider = assembly.BuildProvider(); Assert.IsType( assembly.Type("IntRepo"), - provider.GetService(repo.MakeGenericType(typeof(int)))); + provider.GetService(repo.MakeGenericType(typeof(int))) + ); Assert.Null(provider.GetService(repo.MakeGenericType(typeof(string)))); } [Fact] - public void AnExplicitServiceAttributeBeatsTheConvention() { + public void AnExplicitServiceAttributeBeatsTheConvention() + { var assembly = Compile( """ public interface IFoo { } @@ -1385,25 +1493,31 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsTransient(); } } - """); + """ + ); var descriptors = assembly.Descriptors("IFoo"); // The attributed type is registered once, by its attribute, at the lifetime the attribute // declared — not a second time at the convention's lifetime. - var attributed = descriptors.Where(d => d.ImplementationType == assembly.Type("Attributed")).ToArray(); + var attributed = descriptors + .Where(d => d.ImplementationType == assembly.Type("Attributed")) + .ToArray(); Assert.Single(attributed); Assert.Equal(ServiceLifetime.Singleton, attributed[0].Lifetime); - var byConvention = descriptors.Where(d => d.ImplementationType == assembly.Type("ByConvention")).ToArray(); + var byConvention = descriptors + .Where(d => d.ImplementationType == assembly.Type("ByConvention")) + .ToArray(); Assert.Single(byConvention); Assert.Equal(ServiceLifetime.Transient, byConvention[0].Lifetime); } [Fact] - public void OmittingTheLifetimeIsRefused() { + public void OmittingTheLifetimeIsRefused() + { var result = Run( """ public interface IFoo { } @@ -1415,7 +1529,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll(); } } - """); + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0009"); @@ -1428,26 +1543,30 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// build stayed green. /// [Theory] - [InlineData("foreach (var t in new Type[0]) { conventions.RegisterAll().AsSingleton(); }")] + [InlineData( + "foreach (var t in new Type[0]) { conventions.RegisterAll().AsSingleton(); }" + )] [InlineData("if (DateTime.Now.Day > 1) { conventions.RegisterAll().AsSingleton(); }")] [InlineData("var x = 5;")] [InlineData("Helper(conventions);")] [InlineData("conventions.RegisterAll().AsSingleton().AsScoped();")] - public void UnreadableStatementsAreRefused(string statement) { + public void UnreadableStatementsAreRefused(string statement) + { var result = Run( $$""" - public interface IFoo { } - public class Foo : IFoo { } + public interface IFoo { } + public class Foo : IFoo { } - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - {{statement}} - } + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + {{statement}} + } - private static void Helper(IConventionDefinitions c) { } - } - """); + private static void Helper(IConventionDefinitions c) { } + } + """ + ); Assert.Contains(result.GeneratorDiagnostics, d => d.Id == "DM0009"); } @@ -1459,7 +1578,8 @@ private static void Helper(IConventionDefinitions c) { } // keeps DM0004 honest for the case it exists for. [Fact] - public void AConcreteTypeWithNoAccessibleConstructorIsReported() { + public void AConcreteTypeWithNoAccessibleConstructorIsReported() + { var result = Run( """ public interface IFoo { } @@ -1474,7 +1594,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton(); } } - """); + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0006"); @@ -1486,7 +1607,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// declaration that explains why it is in the container. /// [Fact] - public void ReportsWhatEachClassIsExposedAs() { + public void ReportsWhatEachClassIsExposedAs() + { var result = Run( """ public interface IFoo { } @@ -1501,7 +1623,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton(); } } - """); + """ + ); var exposures = result.GeneratorDiagnostics.Where(d => d.Id == "DM0010").ToArray(); @@ -1510,7 +1633,10 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { // The indirect match names the hop, which is what keeps it from reading as luck. Assert.Contains(exposures, d => d.GetMessage() == "Exposed as IFoo in TestModule"); - Assert.Contains(exposures, d => d.GetMessage() == "Exposed as IFoo in TestModule (via IFooPrime)"); + Assert.Contains( + exposures, + d => d.GetMessage() == "Exposed as IFoo in TestModule (via IFooPrime)" + ); } /// @@ -1519,9 +1645,9 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// primitives at output. This asserts the rebuild lands on the right line. /// [Fact] - public void ExposureIsReportedOnTheClassItself() { - const string body = - """ + public void ExposureIsReportedOnTheClassItself() + { + const string body = """ public interface IFoo { } public class Foo : IFoo { } @@ -1551,7 +1677,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// reported rather than left to fail silently. /// [Fact] - public void ConventionsOnANonModuleAreReported() { + public void ConventionsOnANonModuleAreReported() + { var result = Run( """ public interface IFoo { } @@ -1565,7 +1692,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton(); } } - """); + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0009"); @@ -1573,9 +1701,9 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { } [Fact] - public void EditingAnUnrelatedMethodBodyReusesTheCachedOutput() { - const string template = - """ + public void EditingAnUnrelatedMethodBodyReusesTheCachedOutput() + { + const string template = """ using DependencyModules.Runtime.Attributes; using DependencyModules.Runtime.Conventions; @@ -1597,10 +1725,13 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { var result = GeneratorTestHarness.RunIncremental( new Dictionary { ["Test.cs"] = template.Replace("VALUE", "1") }, - new Dictionary { ["Test.cs"] = template.Replace("VALUE", "2") }); + new Dictionary { ["Test.cs"] = template.Replace("VALUE", "2") } + ); Assert.Equal(result.FirstRun, result.SecondRun); - Assert.True(result.AllOutputsCached, - "editing a method body cannot change any registration, so every output should be cached"); + Assert.True( + result.AllOutputsCached, + "editing a method body cannot change any registration, so every output should be cached" + ); } } diff --git a/tests/DependencyModules.Tests/GeneratorTests/DecoratorGenerationTests.cs b/tests/DependencyModules.Tests/GeneratorTests/DecoratorGenerationTests.cs index 5ce0169..6ba19ed 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/DecoratorGenerationTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/DecoratorGenerationTests.cs @@ -10,30 +10,38 @@ namespace DependencyModules.Tests.GeneratorTests; /// Decoration verified by resolving from a real container built from generated code, rather than by /// matching the generated text. The registrations are the point; their shape is not. /// -public class DecoratorGenerationTests { - +public class DecoratorGenerationTests +{ [Fact] - public void Decorator_WrapsTheRegisteredImplementation() { - var generated = GeneratedAssembly.Create(Module( - """ - [Decorator] - public class LoudGreeter(IGreeter inner) : IGreeter { - public string Greet() => inner.Greet().ToUpperInvariant(); - } - """)); + public void Decorator_WrapsTheRegisteredImplementation() + { + var generated = GeneratedAssembly.Create( + Module( + """ + [Decorator] + public class LoudGreeter(IGreeter inner) : IGreeter { + public string Greet() => inner.Greet().ToUpperInvariant(); + } + """ + ) + ); Assert.Equal("HELLO", Greet(generated)); } [Fact] - public void Decorator_PreservesTheServiceLifetime() { - var generated = GeneratedAssembly.Create(Module( - """ - [Decorator] - public class LoudGreeter(IGreeter inner) : IGreeter { - public string Greet() => inner.Greet(); - } - """)); + public void Decorator_PreservesTheServiceLifetime() + { + var generated = GeneratedAssembly.Create( + Module( + """ + [Decorator] + public class LoudGreeter(IGreeter inner) : IGreeter { + public string Greet() => inner.Greet(); + } + """ + ) + ); Assert.Equal(ServiceLifetime.Singleton, generated.Descriptor("IGreeter").Lifetime); } @@ -42,43 +50,52 @@ public class LoudGreeter(IGreeter inner) : IGreeter { /// Lower order sits closer to the implementation, so the higher-order decorator is outermost. /// [Fact] - public void Decorators_NestByAscendingOrder() { - var generated = GeneratedAssembly.Create(Module( - """ - [Decorator(Order = 10)] - public class InnerGreeter(IGreeter inner) : IGreeter { - public string Greet() => $"inner({inner.Greet()})"; - } + public void Decorators_NestByAscendingOrder() + { + var generated = GeneratedAssembly.Create( + Module( + """ + [Decorator(Order = 10)] + public class InnerGreeter(IGreeter inner) : IGreeter { + public string Greet() => $"inner({inner.Greet()})"; + } - [Decorator(Order = 20)] - public class OuterGreeter(IGreeter inner) : IGreeter { - public string Greet() => $"outer({inner.Greet()})"; - } - """)); + [Decorator(Order = 20)] + public class OuterGreeter(IGreeter inner) : IGreeter { + public string Greet() => $"outer({inner.Greet()})"; + } + """ + ) + ); Assert.Equal("outer(inner(hello))", Greet(generated)); } [Fact] - public void Decorator_ReceivesItsOwnDependencies() { - var generated = GeneratedAssembly.Create(Module( - """ - public interface IPrefix { string Value { get; } } + public void Decorator_ReceivesItsOwnDependencies() + { + var generated = GeneratedAssembly.Create( + Module( + """ + public interface IPrefix { string Value { get; } } - [SingletonService] - public class Prefix : IPrefix { public string Value => "pre"; } + [SingletonService] + public class Prefix : IPrefix { public string Value => "pre"; } - [Decorator] - public class PrefixedGreeter(IGreeter inner, IPrefix prefix) : IGreeter { - public string Greet() => $"{prefix.Value}-{inner.Greet()}"; - } - """)); + [Decorator] + public class PrefixedGreeter(IGreeter inner, IPrefix prefix) : IGreeter { + public string Greet() => $"{prefix.Value}-{inner.Greet()}"; + } + """ + ) + ); Assert.Equal("pre-hello", Greet(generated)); } [Fact] - public void OpenGenericDecorator_WrapsEveryClosedRegistration() { + public void OpenGenericDecorator_WrapsEveryClosedRegistration() + { var generated = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -100,15 +117,20 @@ public class ValidatingHandler(IHandler inner) : IHandler { [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = generated.BuildProvider(); var handler = generated.Type("IHandler`1"); - Assert.Equal("validated(string)", - Invoke(provider.GetService(handler.MakeGenericType(typeof(string)))!, "Handle")); - Assert.Equal("validated(int)", - Invoke(provider.GetService(handler.MakeGenericType(typeof(int)))!, "Handle")); + Assert.Equal( + "validated(string)", + Invoke(provider.GetService(handler.MakeGenericType(typeof(string)))!, "Handle") + ); + Assert.Equal( + "validated(int)", + Invoke(provider.GetService(handler.MakeGenericType(typeof(int)))!, "Handle") + ); } /// @@ -126,7 +148,8 @@ public partial class TestModule; /// /// [Fact] - public void OpenGenericRegistration_IsNotDecorated() { + public void OpenGenericRegistration_IsNotDecorated() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -145,7 +168,8 @@ public class LoggingRepo(IRepo inner) : IRepo { [DependencyModule] public partial class TestModule; - """); + """ + ); // Nothing is emitted for it, so the registration stands undecorated rather than the // provider throwing when it is built. @@ -153,14 +177,16 @@ public partial class TestModule; Assert.DoesNotContain( result.GeneratedSources, - source => source.Key.Contains("Decorators") && source.Value.Contains("LoggingRepo")); + source => source.Key.Contains("Decorators") && source.Value.Contains("LoggingRepo") + ); } /// /// The way through, end to end: a closed construction of the generic service is decorated. /// [Fact] - public void ClosedConstructionOfAGenericService_IsDecorated() { + public void ClosedConstructionOfAGenericService_IsDecorated() + { var generated = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -181,16 +207,20 @@ public class LoggingRepo(IRepo inner) : IRepo { [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = generated.BuildProvider(); - var resolved = provider.GetService(generated.Type("IRepo`1").MakeGenericType(typeof(string)))!; + var resolved = provider.GetService( + generated.Type("IRepo`1").MakeGenericType(typeof(string)) + )!; Assert.Equal("logged(repo)", Invoke(resolved, "Name")); } [Fact] - public void ModuleLevelDecorate_WrapsAServiceTheModuleDoesNotDeclare() { + public void ModuleLevelDecorate_WrapsAServiceTheModuleDoesNotDeclare() + { var generated = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -209,26 +239,32 @@ public class LoudGreeter(IGreeter inner) : IGreeter { [DependencyModule] [Decorate(typeof(IGreeter), typeof(LoudGreeter))] public partial class TestModule; - """); + """ + ); Assert.Equal("HELLO", Greet(generated)); } [Fact] - public void Decorator_WithExplicitService_UsesIt() { - var generated = GeneratedAssembly.Create(Module( - """ - [Decorator(Service = typeof(IGreeter))] - public class LoudGreeter(IGreeter inner) : IGreeter { - public string Greet() => inner.Greet().ToUpperInvariant(); - } - """)); + public void Decorator_WithExplicitService_UsesIt() + { + var generated = GeneratedAssembly.Create( + Module( + """ + [Decorator(Service = typeof(IGreeter))] + public class LoudGreeter(IGreeter inner) : IGreeter { + public string Greet() => inner.Greet().ToUpperInvariant(); + } + """ + ) + ); Assert.Equal("HELLO", Greet(generated)); } [Fact] - public void NoDecorator_LeavesTheServiceAlone() { + public void NoDecorator_LeavesTheServiceAlone() + { var generated = GeneratedAssembly.Create(Module("")); Assert.Equal("hello", Greet(generated)); @@ -238,19 +274,23 @@ public void NoDecorator_LeavesTheServiceAlone() { /// Two decorators of one service with the same order would nest in an order nobody declared. /// [Fact] - public void DecoratorsSharingAnOrder_ReportDM0007() { - var result = GeneratorTestHarness.Run(Module( - """ - [Decorator(Order = 5)] - public class FirstGreeter(IGreeter inner) : IGreeter { - public string Greet() => inner.Greet(); - } + public void DecoratorsSharingAnOrder_ReportDM0007() + { + var result = GeneratorTestHarness.Run( + Module( + """ + [Decorator(Order = 5)] + public class FirstGreeter(IGreeter inner) : IGreeter { + public string Greet() => inner.Greet(); + } - [Decorator(Order = 5)] - public class SecondGreeter(IGreeter inner) : IGreeter { - public string Greet() => inner.Greet(); - } - """)); + [Decorator(Order = 5)] + public class SecondGreeter(IGreeter inner) : IGreeter { + public string Greet() => inner.Greet(); + } + """ + ) + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0007"); @@ -259,19 +299,23 @@ public class SecondGreeter(IGreeter inner) : IGreeter { } [Fact] - public void DecoratorsWithDistinctOrders_ReportNothing() { - var result = GeneratorTestHarness.Run(Module( - """ - [Decorator(Order = 1)] - public class FirstGreeter(IGreeter inner) : IGreeter { - public string Greet() => inner.Greet(); - } + public void DecoratorsWithDistinctOrders_ReportNothing() + { + var result = GeneratorTestHarness.Run( + Module( + """ + [Decorator(Order = 1)] + public class FirstGreeter(IGreeter inner) : IGreeter { + public string Greet() => inner.Greet(); + } - [Decorator(Order = 2)] - public class SecondGreeter(IGreeter inner) : IGreeter { - public string Greet() => inner.Greet(); - } - """)); + [Decorator(Order = 2)] + public class SecondGreeter(IGreeter inner) : IGreeter { + public string Greet() => inner.Greet(); + } + """ + ) + ); result.AssertNoErrors(); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0007"); @@ -288,8 +332,10 @@ public class SecondGreeter(IGreeter inner) : IGreeter { [InlineData("Development", "HELLO")] [InlineData("Production", "hello")] public void Decorator_AppliesOnlyWhenItsEnvironmentConditionHolds( - string environmentName, string expected) { - + string environmentName, + string expected + ) + { var generated = GeneratedAssembly.Create( Module( """ @@ -298,8 +344,10 @@ public void Decorator_AppliesOnlyWhenItsEnvironmentConditionHolds( public class LoudGreeter(IGreeter inner) : IGreeter { public string Greet() => inner.Greet().ToUpperInvariant(); } - """), - environment: new ModuleEnvironment(environmentName)); + """ + ), + environment: new ModuleEnvironment(environmentName) + ); Assert.Equal(expected, Greet(generated)); } @@ -307,7 +355,8 @@ public class LoudGreeter(IGreeter inner) : IGreeter { [Theory] [InlineData("on", "HELLO")] [InlineData("off", "hello")] - public void Decorator_HonoursValueConditions(string flag, string expected) { + public void Decorator_HonoursValueConditions(string flag, string expected) + { var generated = GeneratedAssembly.Create( Module( """ @@ -316,8 +365,10 @@ public void Decorator_HonoursValueConditions(string flag, string expected) { public class LoudGreeter(IGreeter inner) : IGreeter { public string Greet() => inner.Greet().ToUpperInvariant(); } - """), - environment: new ModuleEnvironment(false, "Development") { { "LOUD", flag } }); + """ + ), + environment: new ModuleEnvironment(false, "Development") { { "LOUD", flag } } + ); Assert.Equal(expected, Greet(generated)); } @@ -326,7 +377,8 @@ public class LoudGreeter(IGreeter inner) : IGreeter { /// A condition changes whether a decorator applies, never where it sits in the nesting. /// [Fact] - public void Decorator_ConditionDoesNotDisturbOrdering() { + public void Decorator_ConditionDoesNotDisturbOrdering() + { var generated = GeneratedAssembly.Create( Module( """ @@ -340,8 +392,10 @@ public class Inner(IGreeter inner) : IGreeter { public class Outer(IGreeter inner) : IGreeter { public string Greet() => "outer(" + inner.Greet() + ")"; } - """), - environment: new ModuleEnvironment("Development")); + """ + ), + environment: new ModuleEnvironment("Development") + ); Assert.Equal("outer(inner(hello))", Greet(generated)); } @@ -351,7 +405,8 @@ public class Outer(IGreeter inner) : IGreeter { /// the whole chain going with it. /// [Fact] - public void Decorator_UnconditionalOneSurvivesWhenAConditionalOneDoesNot() { + public void Decorator_UnconditionalOneSurvivesWhenAConditionalOneDoesNot() + { var generated = GeneratedAssembly.Create( Module( """ @@ -365,8 +420,10 @@ public class Inner(IGreeter inner) : IGreeter { public class Outer(IGreeter inner) : IGreeter { public string Greet() => "outer(" + inner.Greet() + ")"; } - """), - environment: new ModuleEnvironment("Production")); + """ + ), + environment: new ModuleEnvironment("Production") + ); Assert.Equal("outer(hello)", Greet(generated)); } @@ -376,28 +433,30 @@ private static string Greet(GeneratedAssembly generated) => /// Calls Handle on a resolved handler with a fresh request. private static void Handle(object handler, GeneratedAssembly assembly) => - handler.GetType().GetMethod("Handle")!.Invoke( - handler, new[] { System.Activator.CreateInstance(assembly.Type("Create")) }); + handler + .GetType() + .GetMethod("Handle")! + .Invoke(handler, new[] { System.Activator.CreateInstance(assembly.Type("Create")) }); private static string Invoke(object target, string method) => (string)target.GetType().GetMethod(method)!.Invoke(target, null)!; private static string Module(string body) => $$""" - using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - public interface IGreeter { string Greet(); } + public interface IGreeter { string Greet(); } - [SingletonService] - public class Greeter : IGreeter { public string Greet() => "hello"; } + [SingletonService] + public class Greeter : IGreeter { public string Greet() => "hello"; } - {{body}} + {{body}} - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; /// /// A decorator's own dependencies are resolved on the terms each parameter declares. @@ -416,7 +475,8 @@ public partial class TestModule; /// /// [Fact] - public void Decorator_ResolvesAKeyedDependencyFromTheKeyItDeclares() { + public void Decorator_ResolvesAKeyedDependencyFromTheKeyItDeclares() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -448,7 +508,8 @@ public class StampedGreeter( [DependencyModule] public partial class TestModule; - """); + """ + ); var greeter = assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")); @@ -460,7 +521,8 @@ public partial class TestModule; /// A nullable dependency the container does not have resolves to null rather than throwing. /// [Fact] - public void Decorator_ResolvesAnOptionalDependencyToNull() { + public void Decorator_ResolvesAnOptionalDependencyToNull() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -481,7 +543,8 @@ public class AuditedGreeter(IGreeter inner, IAudit? audit) : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); var greeter = assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")); @@ -502,7 +565,8 @@ public partial class TestModule; /// parameter was resolved on the terms it declares. /// [Fact] - public void ModuleLevelDecorate_ConstructsTheDecoratorFromItsResolvedConstructor() { + public void ModuleLevelDecorate_ConstructsTheDecoratorFromItsResolvedConstructor() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -533,7 +597,8 @@ public class StampedGreeter( [DependencyModule] [Decorate(typeof(IGreeter), typeof(StampedGreeter))] public partial class TestModule; - """); + """ + ); var greeter = assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")); @@ -549,7 +614,8 @@ public partial class TestModule; /// published application. /// [Fact] - public void ModuleLevelDecorate_WithNoPublicConstructor_IsNotDecorated() { + public void ModuleLevelDecorate_WithNoPublicConstructor_IsNotDecorated() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -570,7 +636,8 @@ public class HiddenGreeter : IGreeter { [DependencyModule] [Decorate(typeof(IGreeter), typeof(HiddenGreeter))] public partial class TestModule; - """); + """ + ); // Generated code constructs the decorator, so a private constructor means there is nothing // to emit. The build stays green and the service resolves undecorated. @@ -578,7 +645,8 @@ public partial class TestModule; Assert.DoesNotContain( result.GeneratedSources, - source => source.Value.Contains("new global::TestNamespace.HiddenGreeter")); + source => source.Value.Contains("new global::TestNamespace.HiddenGreeter") + ); } // ------------------------------------------------------------------------------------------ @@ -587,8 +655,7 @@ public partial class TestModule; // exercises DecoratorTypeUtility.Close as much as it does the emission. // ------------------------------------------------------------------------------------------ - private const string HandlerPreamble = - """ + private const string HandlerPreamble = """ using DependencyModules.Runtime.Attributes; using Microsoft.Extensions.DependencyInjection; @@ -621,41 +688,46 @@ public class CountHandler : IHandler { /// decorator resolves the unkeyed registration, which is the right type and the wrong instance. /// [Fact] - public void GenericDecorator_KeyedDependencySurvivesTypeSubstitution() { + public void GenericDecorator_KeyedDependencySurvivesTypeSubstitution() + { var assembly = GeneratedAssembly.Create( - HandlerPreamble + - """ - public interface IStamp { string Value { get; } } + HandlerPreamble + + """ + public interface IStamp { string Value { get; } } - [SingletonService] - public class DefaultStamp : IStamp { public string Value => "?"; } + [SingletonService] + public class DefaultStamp : IStamp { public string Value => "?"; } - [SingletonService(Key = "quiet")] - public class QuietStamp : IStamp { public string Value => "."; } - - [Decorator] - public class StampedHandler( - IHandler inner, - [FromKeyedServices("quiet")] IStamp stamp) : IHandler { + [SingletonService(Key = "quiet")] + public class QuietStamp : IStamp { public string Value => "."; } - public TResponse Handle(TRequest request) { - Log.Lines.Add(stamp.Value); - return inner.Handle(request); + [Decorator] + public class StampedHandler( + IHandler inner, + [FromKeyedServices("quiet")] IStamp stamp) : IHandler { + + public TResponse Handle(TRequest request) { + Log.Lines.Add(stamp.Value); + return inner.Handle(request); + } } - } - public static class Log { public static System.Collections.Generic.List Lines = new(); } + public static class Log { public static System.Collections.Generic.List Lines = new(); } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); var provider = assembly.BuildProvider(); var handler = assembly.Type("IHandler`2"); Handle( - provider.GetRequiredService(handler.MakeGenericType(assembly.Type("Create"), assembly.Type("Id")))!, - assembly); + provider.GetRequiredService( + handler.MakeGenericType(assembly.Type("Create"), assembly.Type("Id")) + )!, + assembly + ); var lines = (System.Collections.Generic.List) assembly.Type("Log").GetField("Lines")!.GetValue(null)!; @@ -667,29 +739,34 @@ public partial class TestModule; /// A generic decorator's optional dependency resolves to null rather than throwing. /// [Fact] - public void GenericDecorator_OptionalDependencyResolvesToNull() { + public void GenericDecorator_OptionalDependencyResolvesToNull() + { var assembly = GeneratedAssembly.Create( - HandlerPreamble + - """ - public interface IAudit { } + HandlerPreamble + + """ + public interface IAudit { } - [Decorator] - public class AuditedHandler( - IHandler inner, IAudit? audit) : IHandler { + [Decorator] + public class AuditedHandler( + IHandler inner, IAudit? audit) : IHandler { - public bool Audited => audit != null; + public bool Audited => audit != null; - public TResponse Handle(TRequest request) => inner.Handle(request); - } + public TResponse Handle(TRequest request) => inner.Handle(request); + } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); var handler = assembly.Type("IHandler`2"); - var resolved = assembly.BuildProvider().GetRequiredService( - handler.MakeGenericType(assembly.Type("Create"), assembly.Type("Id"))); + var resolved = assembly + .BuildProvider() + .GetRequiredService( + handler.MakeGenericType(assembly.Type("Create"), assembly.Type("Id")) + ); Assert.False((bool)resolved.GetType().GetProperty("Audited")!.GetValue(resolved)!); } @@ -703,33 +780,38 @@ public partial class TestModule; /// compile, which is the failure this pins. /// [Fact] - public void GenericDecorator_ClosesADependencyOverItsOwnTypeParameters() { + public void GenericDecorator_ClosesADependencyOverItsOwnTypeParameters() + { var assembly = GeneratedAssembly.Create( - HandlerPreamble + - """ - public interface IValidator { string Name { get; } } + HandlerPreamble + + """ + public interface IValidator { string Name { get; } } - [SingletonService] - public class CreateValidator : IValidator { public string Name => "create"; } + [SingletonService] + public class CreateValidator : IValidator { public string Name => "create"; } - [Decorator] - public class ValidatedHandler( - IHandler inner, - IValidator validator) : IHandler { + [Decorator] + public class ValidatedHandler( + IHandler inner, + IValidator validator) : IHandler { - public string ValidatorName => validator.Name; + public string ValidatorName => validator.Name; - public TResponse Handle(TRequest request) => inner.Handle(request); - } + public TResponse Handle(TRequest request) => inner.Handle(request); + } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); var handler = assembly.Type("IHandler`2"); - var resolved = assembly.BuildProvider().GetRequiredService( - handler.MakeGenericType(assembly.Type("Create"), assembly.Type("Id"))); + var resolved = assembly + .BuildProvider() + .GetRequiredService( + handler.MakeGenericType(assembly.Type("Create"), assembly.Type("Id")) + ); Assert.Equal("create", resolved.GetType().GetProperty("ValidatorName")!.GetValue(resolved)); } @@ -743,32 +825,36 @@ public partial class TestModule; /// shape rather than the outcome: the emitted call must name the closed decorator. /// [Fact] - public void GenericDecorator_ClosesOverAValueTypeArgument() { + public void GenericDecorator_ClosesOverAValueTypeArgument() + { var result = GeneratorTestHarness.Run( - HandlerPreamble + - """ - [Decorator] - public class LoggingHandler( - IHandler inner) : IHandler { + HandlerPreamble + + """ + [Decorator] + public class LoggingHandler( + IHandler inner) : IHandler { - public TResponse Handle(TRequest request) => inner.Handle(request); - } + public TResponse Handle(TRequest request) => inner.Handle(request); + } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); Assert.Empty(result.Errors); - var decorators = Assert.Single( - result.GeneratedSources, source => source.Key.Contains("Decorators")).Value; + var decorators = Assert + .Single(result.GeneratedSources, source => source.Key.Contains("Decorators")) + .Value; // One closed call per registration, each naming the decorator closed over the same // arguments — including the value-type one, which is the instantiation Native AOT cannot // produce at run time and the reason the open-generic call had to go. Assert.True( System.Text.RegularExpressions.Regex.Matches(decorators, "Decorate<").Count == 2, - "expected one closed Decorate call per registration, got:\n" + decorators); + "expected one closed Decorate call per registration, got:\n" + decorators + ); // Nothing is closed at run time any more. Assert.DoesNotContain("IHandler<,>", decorators); @@ -778,34 +864,39 @@ public partial class TestModule; /// Two generic decorators over one service nest in their declared order. /// [Fact] - public void GenericDecorators_StackInOrder() { + public void GenericDecorators_StackInOrder() + { var assembly = GeneratedAssembly.Create( - HandlerPreamble + - """ - public static class Log { public static System.Collections.Generic.List Lines = new(); } - - [Decorator(Order = 1)] - public class InnerMost( - IHandler inner) : IHandler { - public TResponse Handle(TRequest r) { Log.Lines.Add("inner"); return inner.Handle(r); } - } + HandlerPreamble + + """ + public static class Log { public static System.Collections.Generic.List Lines = new(); } + + [Decorator(Order = 1)] + public class InnerMost( + IHandler inner) : IHandler { + public TResponse Handle(TRequest r) { Log.Lines.Add("inner"); return inner.Handle(r); } + } - [Decorator(Order = 2)] - public class OuterMost( - IHandler inner) : IHandler { - public TResponse Handle(TRequest r) { Log.Lines.Add("outer"); return inner.Handle(r); } - } + [Decorator(Order = 2)] + public class OuterMost( + IHandler inner) : IHandler { + public TResponse Handle(TRequest r) { Log.Lines.Add("outer"); return inner.Handle(r); } + } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); var provider = assembly.BuildProvider(); var handler = assembly.Type("IHandler`2"); Handle( - provider.GetRequiredService(handler.MakeGenericType(assembly.Type("Create"), assembly.Type("Id")))!, - assembly); + provider.GetRequiredService( + handler.MakeGenericType(assembly.Type("Create"), assembly.Type("Id")) + )!, + assembly + ); var lines = (System.Collections.Generic.List) assembly.Type("Log").GetField("Lines")!.GetValue(null)!; @@ -825,7 +916,8 @@ public partial class TestModule; /// the build. /// [Fact] - public void GenericDecorator_RegisteredByBothPaths_IsAppliedOnce() { + public void GenericDecorator_RegisteredByBothPaths_IsAppliedOnce() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -864,13 +956,16 @@ public void Conventions(IConventionDefinitions conventions) { conventions.RegisterAll(typeof(IHandler<,>)).AsSingleton(); } } - """); + """ + ); var provider = assembly.BuildProvider(); - var handler = assembly.Type("IHandler`2") + var handler = assembly + .Type("IHandler`2") .MakeGenericType(assembly.Type("Create"), assembly.Type("Id")); - foreach (var service in (System.Collections.IEnumerable)provider.GetServices(handler)) { + foreach (var service in (System.Collections.IEnumerable)provider.GetServices(handler)) + { Handle(service!, assembly); } @@ -896,7 +991,8 @@ public void Conventions(IConventionDefinitions conventions) { /// different way than the author asked for, which nothing would report. /// [Fact] - public void Decorator_HonoursTheConstructorItMarked() { + public void Decorator_HonoursTheConstructorItMarked() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -927,10 +1023,13 @@ public class PickyGreeter : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("hello:marked", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "hello:marked", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// @@ -941,7 +1040,8 @@ public partial class TestModule; /// Resolving it would work by accident on Microsoft's container and is wrong in principle. /// [Fact] - public void Decorator_TakingTheProviderGetsTheProvider() { + public void Decorator_TakingTheProviderGetsTheProvider() + { var assembly = GeneratedAssembly.Create( """ using System; @@ -966,10 +1066,13 @@ public string Greet() => [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("hello!", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "hello!", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// @@ -981,7 +1084,8 @@ public partial class TestModule; /// working, which looks like the service moved rather than broke. /// [Fact] - public void Decorator_KeyedRegistrationKeepsItsKey() { + public void Decorator_KeyedRegistrationKeepsItsKey() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1001,12 +1105,16 @@ public class LoudGreeter(IGreeter inner) : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = assembly.BuildProvider(); var greeter = assembly.Type("IGreeter"); - Assert.Equal("GOOD DAY", Invoke(provider.GetRequiredKeyedService(greeter, "formal"), "Greet")); + Assert.Equal( + "GOOD DAY", + Invoke(provider.GetRequiredKeyedService(greeter, "formal"), "Greet") + ); Assert.Null(provider.GetService(greeter)); } @@ -1019,7 +1127,8 @@ public partial class TestModule; /// that does not compile, which is the failure mode this pins. /// [Fact] - public void GenericDecorator_ClosesOverANestedTypeArgument() { + public void GenericDecorator_ClosesOverANestedTypeArgument() + { var assembly = GeneratedAssembly.Create( """ using System.Collections.Generic; @@ -1046,10 +1155,15 @@ public class LoggingHandler( [DependencyModule] public partial class TestModule; - """); + """ + ); - var handler = assembly.Type("IHandler`2").MakeGenericType( - typeof(List<>).MakeGenericType(assembly.Type("Create")), assembly.Type("Id")); + var handler = assembly + .Type("IHandler`2") + .MakeGenericType( + typeof(List<>).MakeGenericType(assembly.Type("Create")), + assembly.Type("Id") + ); var resolved = assembly.BuildProvider().GetRequiredService(handler); @@ -1064,7 +1178,8 @@ public partial class TestModule; /// registration. Emitting for a construction nothing registers would be dead code at best. /// [Fact] - public void GenericDecorator_DecoratesOnlyTheRegisteredClosings() { + public void GenericDecorator_DecoratesOnlyTheRegisteredClosings() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -1088,12 +1203,14 @@ public class LoggingHandler(IHandler inner) : IHandler { [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.Empty(result.Errors); - var decorators = Assert.Single( - result.GeneratedSources, source => source.Key.Contains("Decorators")).Value; + var decorators = Assert + .Single(result.GeneratedSources, source => source.Key.Contains("Decorators")) + .Value; Assert.Contains("Registered", decorators); Assert.DoesNotContain("NeverRegistered", decorators); @@ -1103,7 +1220,8 @@ public partial class TestModule; /// A generic decorator keeps the lifetime each registration declared. /// [Fact] - public void GenericDecorator_PreservesEachRegistrationsLifetime() { + public void GenericDecorator_PreservesEachRegistrationsLifetime() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1128,17 +1246,20 @@ public class LoggingHandler(IHandler inner) : IHandler { [DependencyModule] public partial class TestModule; - """); + """ + ); var handler = assembly.Type("IHandler`1"); var singleton = Assert.Single( assembly.Services, - d => d.ServiceType == handler.MakeGenericType(assembly.Type("A"))); + d => d.ServiceType == handler.MakeGenericType(assembly.Type("A")) + ); var transient = Assert.Single( assembly.Services, - d => d.ServiceType == handler.MakeGenericType(assembly.Type("B"))); + d => d.ServiceType == handler.MakeGenericType(assembly.Type("B")) + ); Assert.Equal(ServiceLifetime.Singleton, singleton.Lifetime); Assert.Equal(ServiceLifetime.Transient, transient.Lifetime); @@ -1153,7 +1274,8 @@ public partial class TestModule; /// produced. /// [Fact] - public void GenericDecorator_LeavesTheInnerOwnedByTheContainer() { + public void GenericDecorator_LeavesTheInnerOwnedByTheContainer() + { var assembly = GeneratedAssembly.Create( """ using System; @@ -1180,12 +1302,14 @@ public class LoggingHandler(IHandler inner) : IHandler { [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = assembly.BuildProvider(); var handler = assembly.Type("IHandler`1").MakeGenericType(assembly.Type("A")); - using (var scope = provider.CreateScope()) { + using (var scope = provider.CreateScope()) + { Assert.Equal("a", Invoke(scope.ServiceProvider.GetRequiredService(handler), "Handle")); } @@ -1196,7 +1320,8 @@ public partial class TestModule; /// A decorator scoped to a realm decorates only that module's registrations. /// [Fact] - public void Decorator_ScopedToARealm_DecoratesOnlyThatModule() { + public void Decorator_ScopedToARealm_DecoratesOnlyThatModule() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1219,10 +1344,13 @@ public partial class DecoratedModule; [DependencyModule(OnlyRealm = true)] public partial class PlainModule; """, - moduleName: "DecoratedModule"); + moduleName: "DecoratedModule" + ); - Assert.Equal("HELLO", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "HELLO", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// @@ -1237,8 +1365,10 @@ public partial class PlainModule; [InlineData("Development", "HELLO")] [InlineData("Production", "hello")] public void Decorator_WithAnEnvironmentCondition_AppliesOnlyWhenItHolds( - string environment, string expected) { - + string environment, + string expected + ) + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1259,17 +1389,21 @@ public class LoudGreeter(IGreeter inner) : IGreeter { [DependencyModule] public partial class TestModule; """, - environment: new ModuleEnvironment(environment)); + environment: new ModuleEnvironment(environment) + ); - Assert.Equal(expected, Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + expected, + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// /// Every implementation behind one service is decorated, not just the last registered. /// [Fact] - public void Decorator_WrapsEveryImplementationOfTheService() { + public void Decorator_WrapsEveryImplementationOfTheService() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1291,10 +1425,13 @@ public class LoudGreeter(IGreeter inner) : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); - var all = ((System.Collections.IEnumerable)assembly.BuildProvider() - .GetServices(assembly.Type("IGreeter"))) + var all = ( + (System.Collections.IEnumerable) + assembly.BuildProvider().GetServices(assembly.Type("IGreeter")) + ) .Cast() .Select(service => Invoke(service, "Greet")) .ToArray(); @@ -1311,7 +1448,8 @@ public partial class TestModule; /// asserts that the second sees what the first produced. /// [Fact] - public void Decorator_AndInterceptor_BothWrapTheService() { + public void Decorator_AndInterceptor_BothWrapTheService() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1342,10 +1480,13 @@ public class LoudGreeter(IGreeter inner) : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); var greeted = Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet"); + assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), + "Greet" + ); var lines = (System.Collections.Generic.List) assembly.Type("Log").GetField("Lines")!.GetValue(null)!; @@ -1364,7 +1505,8 @@ public partial class TestModule; /// service type is the implementation. /// [Fact] - public void Decorator_OverAConventionRegisteredAsSelf() { + public void Decorator_OverAConventionRegisteredAsSelf() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1387,10 +1529,13 @@ public void Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSelf().AsSingleton(); } } - """); + """ + ); - Assert.Equal("WORK", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("Worker")), "Work")); + Assert.Equal( + "WORK", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("Worker")), "Work") + ); } /// @@ -1410,7 +1555,8 @@ public void Conventions(IConventionDefinitions conventions) { /// /// [Fact] - public void ModuleLevelDecorate_CanNameAGenericDecorator() { + public void ModuleLevelDecorate_CanNameAGenericDecorator() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1436,15 +1582,26 @@ public class LoudHandler(IHandler inner) : IHandler { [DependencyModule] [Decorate(typeof(IHandler<>), typeof(LoudHandler<>))] public partial class TestModule; - """); + """ + ); var provider = assembly.BuildProvider(); var handler = assembly.Type("IHandler`1"); - Assert.Equal("A", Invoke( - provider.GetRequiredService(handler.MakeGenericType(assembly.Type("A"))), "Handle")); - Assert.Equal("B", Invoke( - provider.GetRequiredService(handler.MakeGenericType(assembly.Type("B"))), "Handle")); + Assert.Equal( + "A", + Invoke( + provider.GetRequiredService(handler.MakeGenericType(assembly.Type("A"))), + "Handle" + ) + ); + Assert.Equal( + "B", + Invoke( + provider.GetRequiredService(handler.MakeGenericType(assembly.Type("B"))), + "Handle" + ) + ); } // ------------------------------------------------------------------------------------------ @@ -1453,7 +1610,8 @@ public partial class TestModule; /// The wrapped service does not have to be the first constructor parameter. [Fact] - public void Decorator_InnerParameterNeedNotComeFirst() { + public void Decorator_InnerParameterNeedNotComeFirst() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1475,15 +1633,19 @@ public class LoudGreeter(Suffix suffix, IGreeter inner) : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("hello!", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "hello!", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// A record decorator is constructed through its primary constructor. [Fact] - public void Decorator_DeclaredAsARecord() { + public void Decorator_DeclaredAsARecord() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1502,15 +1664,19 @@ public record LoudGreeter(IGreeter Inner) : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("HELLO", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "HELLO", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// A decorator nested inside another type is named correctly in the emitted new. [Fact] - public void Decorator_NestedInsideAnotherType() { + public void Decorator_NestedInsideAnotherType() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1531,10 +1697,13 @@ public class LoudGreeter(IGreeter inner) : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("HELLO", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "HELLO", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// A decorator whose inner parameter is nullable still finds it. @@ -1544,7 +1713,8 @@ public partial class TestModule; /// the decoration is dropped with nothing said. /// [Fact] - public void Decorator_WithANullableInnerParameter() { + public void Decorator_WithANullableInnerParameter() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1563,10 +1733,13 @@ public class LoudGreeter(IGreeter? inner) : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("HELLO", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "HELLO", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// @@ -1579,7 +1752,8 @@ public partial class TestModule; /// wrong way round, which compiles whenever the two types happen to be compatible. /// [Fact] - public void GenericDecorator_WithReorderedTypeParameters_IsNotEmitted() { + public void GenericDecorator_WithReorderedTypeParameters_IsNotEmitted() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -1605,20 +1779,23 @@ public class Swapped( [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.Empty(result.Errors); Assert.DoesNotContain( result.GeneratedSources, - source => source.Value.Contains("new global::TestNamespace.Swapped")); + source => source.Value.Contains("new global::TestNamespace.Swapped") + ); } /// /// A generic decorator with fewer type parameters than the service has arguments is not emitted. /// [Fact] - public void GenericDecorator_WithMismatchedArity_IsNotEmitted() { + public void GenericDecorator_WithMismatchedArity_IsNotEmitted() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -1641,14 +1818,16 @@ public class Same(IHandler inner) : IHandler { [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.Empty(result.Errors); } /// A cross-wired registration is a factory descriptor, and decorates like one. [Fact] - public void Decorator_OverACrossWiredRegistration() { + public void Decorator_OverACrossWiredRegistration() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1667,14 +1846,21 @@ public class LoudGreeter(IGreeter inner) : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = assembly.BuildProvider(); - Assert.Equal("HELLO", Invoke(provider.GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "HELLO", + Invoke(provider.GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); // The implementation stays resolvable as itself, undecorated — that is what cross-wiring is. - Assert.Equal("hello", Invoke(provider.GetRequiredService(assembly.Type("Greeter")), "Greet")); + Assert.Equal( + "hello", + Invoke(provider.GetRequiredService(assembly.Type("Greeter")), "Greet") + ); } /// @@ -1682,7 +1868,8 @@ public partial class TestModule; /// convention. /// [Fact] - public void GenericDecorator_OverConventionRegistrations_WithAGenericDependency() { + public void GenericDecorator_OverConventionRegistrations_WithAGenericDependency() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1712,11 +1899,15 @@ public void Conventions(IConventionDefinitions conventions) { conventions.RegisterAll(typeof(IHandler<>)).AsSingleton(); } } - """); + """ + ); var handler = assembly.Type("IHandler`1").MakeGenericType(assembly.Type("A")); - Assert.Equal("[A]a", Invoke(assembly.BuildProvider().GetRequiredService(handler), "Handle")); + Assert.Equal( + "[A]a", + Invoke(assembly.BuildProvider().GetRequiredService(handler), "Handle") + ); } /// @@ -1729,7 +1920,8 @@ public void Conventions(IConventionDefinitions conventions) { /// decorator's side effects happening twice per call and nothing else. /// [Fact] - public void Decorator_WithTwoModulesInTheCompilation_IsAppliedOnce() { + public void Decorator_WithTwoModulesInTheCompilation_IsAppliedOnce() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1753,7 +1945,8 @@ public partial class TestModule; [DependencyModule] public partial class OtherModule; - """); + """ + ); Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet"); @@ -1766,7 +1959,8 @@ public partial class OtherModule; /// between two decorators and a nesting order nobody declared. /// [Fact] - public void Decorators_SharingAnOrder_AreReported() { + public void Decorators_SharingAnOrder_AreReported() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -1786,7 +1980,8 @@ public class Second(IGreeter inner) : IGreeter { public string Greet() => inner. [DependencyModule] public partial class TestModule; - """); + """ + ); var reported = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0007"); @@ -1802,7 +1997,8 @@ public partial class TestModule; /// through — so this pins that the ordering applies across both rather than within each. /// [Fact] - public void GenericAndNonGenericDecorators_NestByOrder() { + public void GenericAndNonGenericDecorators_NestByOrder() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1830,7 +2026,8 @@ public class SpecificOuter(IHandler inner) : IHandler { [DependencyModule] public partial class TestModule; - """); + """ + ); var handler = assembly.Type("IHandler`1").MakeGenericType(assembly.Type("A")); @@ -1844,7 +2041,8 @@ public partial class TestModule; /// An unscoped decorator does not reach a realm-only module. [Fact] - public void Decorator_Unscoped_DoesNotReachARealmOnlyModule() { + public void Decorator_Unscoped_DoesNotReachARealmOnlyModule() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1864,15 +2062,19 @@ public class LoudGreeter(IGreeter inner) : IGreeter { [DependencyModule(OnlyRealm = true)] public partial class RealmModule; """, - moduleName: "RealmModule"); + moduleName: "RealmModule" + ); - Assert.Equal("hello", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "hello", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// A convention registering matches under a key is decorated under that key. [Fact] - public void Decorator_OverAKeyedConventionRegistration() { + public void Decorator_OverAKeyedConventionRegistration() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1896,7 +2098,8 @@ public void Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().WithKey("loud").AsSingleton(); } } - """); + """ + ); var provider = assembly.BuildProvider(); var greeter = assembly.Type("IGreeter"); @@ -1914,7 +2117,8 @@ public void Conventions(IConventionDefinitions conventions) { /// promises has to survive the rewrite. /// [Fact] - public void Decorator_OverAConventionRegisteredAsSelfWithInterfaces() { + public void Decorator_OverAConventionRegisteredAsSelfWithInterfaces() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1937,19 +2141,27 @@ public void Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSelfWithInterfaces().AsSingleton(); } } - """); + """ + ); var provider = assembly.BuildProvider(); - Assert.Equal("HELLO", Invoke(provider.GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "HELLO", + Invoke(provider.GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); // The implementation itself stays undecorated, which is what cross-wiring means. - Assert.Equal("hello", Invoke(provider.GetRequiredService(assembly.Type("Greeter")), "Greet")); + Assert.Equal( + "hello", + Invoke(provider.GetRequiredService(assembly.Type("Greeter")), "Greet") + ); } /// A decorator with no matching registration emits nothing and breaks nothing. [Fact] - public void Decorator_WithNothingToDecorate_EmitsNothing() { + public void Decorator_WithNothingToDecorate_EmitsNothing() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -1965,7 +2177,8 @@ public class LoudGreeter(IGreeter inner) : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.Empty(result.Errors); } @@ -1980,7 +2193,8 @@ public partial class TestModule; /// for two declarations that are each perfectly legal. /// [Fact] - public void GenericDecorator_ConstrainedToReferenceTypes_SkipsValueTypeClosings() { + public void GenericDecorator_ConstrainedToReferenceTypes_SkipsValueTypeClosings() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -2004,7 +2218,8 @@ public class Logging(IHandler inner) : IHandler where T : class { [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.Empty(result.Errors); } @@ -2013,7 +2228,8 @@ public partial class TestModule; /// A constraint the closing does satisfy still emits. /// [Fact] - public void GenericDecorator_ConstrainedToAnInterface_EmitsForSatisfyingClosings() { + public void GenericDecorator_ConstrainedToAnInterface_EmitsForSatisfyingClosings() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -2036,11 +2252,15 @@ public class Logging(IHandler inner) : IHandler where T : IRequest { [DependencyModule] public partial class TestModule; - """); + """ + ); var handler = assembly.Type("IHandler`1").MakeGenericType(assembly.Type("Thing")); - Assert.Equal("THING", Invoke(assembly.BuildProvider().GetRequiredService(handler), "Handle")); + Assert.Equal( + "THING", + Invoke(assembly.BuildProvider().GetRequiredService(handler), "Handle") + ); } /// @@ -2052,7 +2272,8 @@ public partial class TestModule; /// it wins over inference rather than being one more candidate. /// [Fact] - public void Decorator_WithAnExplicitService_DecoratesThatOne() { + public void Decorator_WithAnExplicitService_DecoratesThatOne() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -2076,19 +2297,24 @@ public class Loud(IGreeter greeter, IFarewell farewell) : IGreeter, IFarewell { [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = assembly.BuildProvider(); Assert.Equal("BYE", Invoke(provider.GetRequiredService(assembly.Type("IFarewell")), "Bye")); // The other interface it implements is not decorated. - Assert.Equal("hello", Invoke(provider.GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "hello", + Invoke(provider.GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// A registration declared with Try is still decorated. [Fact] - public void Decorator_OverATryRegistration() { + public void Decorator_OverATryRegistration() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -2107,10 +2333,13 @@ public class LoudGreeter(IGreeter inner) : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("HELLO", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "HELLO", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// A decorator reaching the service through a base class is still a decorator. @@ -2119,7 +2348,8 @@ public partial class TestModule; /// directly-written interfaces count, this stops being recognised and is silently not applied. /// [Fact] - public void Decorator_ImplementingTheServiceThroughABaseClass() { + public void Decorator_ImplementingTheServiceThroughABaseClass() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -2140,7 +2370,8 @@ public class LoudGreeter(IGreeter inner) : GreeterBase { [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.Empty(result.Errors); } @@ -2150,8 +2381,10 @@ public partial class TestModule; [InlineData("Development", "A")] [InlineData("Production", "a")] public void GenericDecorator_WithAnEnvironmentCondition_GuardsEachClosing( - string environment, string expected) { - + string environment, + string expected + ) + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -2178,11 +2411,15 @@ public class Loud(IHandler inner) : IHandler { [DependencyModule] public partial class TestModule; """, - environment: new ModuleEnvironment(environment)); + environment: new ModuleEnvironment(environment) + ); var handler = assembly.Type("IHandler`1").MakeGenericType(assembly.Type("A")); - Assert.Equal(expected, Invoke(assembly.BuildProvider().GetRequiredService(handler), "Handle")); + Assert.Equal( + expected, + Invoke(assembly.BuildProvider().GetRequiredService(handler), "Handle") + ); } /// Two closings of one generic service each get their own decoration. @@ -2191,7 +2428,8 @@ public partial class TestModule; /// seen first. /// [Fact] - public void GenericDecorator_OverTwoClosingsOfOneService() { + public void GenericDecorator_OverTwoClosingsOfOneService() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -2216,20 +2454,32 @@ public class Loud(IHandler inner) : IHandler { [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = assembly.BuildProvider(); var handler = assembly.Type("IHandler`1"); - Assert.Equal("MULTI", Invoke( - provider.GetRequiredService(handler.MakeGenericType(assembly.Type("A"))), "Handle")); - Assert.Equal("MULTI", Invoke( - provider.GetRequiredService(handler.MakeGenericType(assembly.Type("B"))), "Handle")); + Assert.Equal( + "MULTI", + Invoke( + provider.GetRequiredService(handler.MakeGenericType(assembly.Type("A"))), + "Handle" + ) + ); + Assert.Equal( + "MULTI", + Invoke( + provider.GetRequiredService(handler.MakeGenericType(assembly.Type("B"))), + "Handle" + ) + ); } /// A deeply nested type argument is substituted at every level. [Fact] - public void GenericDecorator_ClosesOverADeeplyNestedTypeArgument() { + public void GenericDecorator_ClosesOverADeeplyNestedTypeArgument() + { var assembly = GeneratedAssembly.Create( """ using System.Collections.Generic; @@ -2253,18 +2503,27 @@ public class Loud(IHandler inner) : IHandler { [DependencyModule] public partial class TestModule; - """); + """ + ); - var handler = assembly.Type("IHandler`1").MakeGenericType( - typeof(IReadOnlyList<>).MakeGenericType( - typeof(Dictionary<,>).MakeGenericType(typeof(string), assembly.Type("Create")))); + var handler = assembly + .Type("IHandler`1") + .MakeGenericType( + typeof(IReadOnlyList<>).MakeGenericType( + typeof(Dictionary<,>).MakeGenericType(typeof(string), assembly.Type("Create")) + ) + ); - Assert.Equal("DEEP", Invoke(assembly.BuildProvider().GetRequiredService(handler), "Handle")); + Assert.Equal( + "DEEP", + Invoke(assembly.BuildProvider().GetRequiredService(handler), "Handle") + ); } /// Three type parameters are substituted in order. [Fact] - public void GenericDecorator_WithThreeTypeParameters() { + public void GenericDecorator_WithThreeTypeParameters() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -2287,17 +2546,20 @@ public class Loud(IPipe inner) : IPipeA keyed registration with a keyed dependency on the decorator. [Fact] - public void Decorator_KeyedRegistrationAndKeyedDependency() { + public void Decorator_KeyedRegistrationAndKeyedDependency() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -2327,15 +2589,24 @@ public class StampedGreeter( [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("good day.", Invoke( - assembly.BuildProvider().GetRequiredKeyedService(assembly.Type("IGreeter"), "formal"), "Greet")); + Assert.Equal( + "good day.", + Invoke( + assembly + .BuildProvider() + .GetRequiredKeyedService(assembly.Type("IGreeter"), "formal"), + "Greet" + ) + ); } /// A decorator can depend on the module environment. [Fact] - public void Decorator_DependingOnTheModuleEnvironment() { + public void Decorator_DependingOnTheModuleEnvironment() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime; @@ -2357,15 +2628,19 @@ public class NamedGreeter(IGreeter inner, IModuleEnvironment environment) : IGre [DependencyModule] public partial class TestModule; """, - environment: new ModuleEnvironment("Staging")); + environment: new ModuleEnvironment("Staging") + ); - Assert.Equal("hello:env", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "hello:env", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// A TryEnumerable registration is decorated. [Fact] - public void Decorator_OverATryEnumerableRegistration() { + public void Decorator_OverATryEnumerableRegistration() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -2384,15 +2659,19 @@ public class LoudGreeter(IGreeter inner) : IGreeter { [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("HELLO", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "HELLO", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// A convention reaching the interface through a base class is decorated. [Fact] - public void Decorator_OverAConventionUsingIncludeBaseClasses() { + public void Decorator_OverAConventionUsingIncludeBaseClasses() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -2417,15 +2696,19 @@ public void Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().IncludeBaseClasses().AsSingleton(); } } - """); + """ + ); - Assert.Equal("HELLO", Invoke( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "HELLO", + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// Interception over a closed construction of a generic service. [Fact] - public void Interceptor_OverAClosedGenericService() { + public void Interceptor_OverAClosedGenericService() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -2453,7 +2736,8 @@ public TResult Intercept(InvocationContext context) { [DependencyModule] public partial class TestModule; - """); + """ + ); var handler = assembly.Type("IHandler`1").MakeGenericType(assembly.Type("A")); diff --git a/tests/DependencyModules.Tests/GeneratorTests/DecoratorImplementationTests.cs b/tests/DependencyModules.Tests/GeneratorTests/DecoratorImplementationTests.cs index 7ca798b..c01de68 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/DecoratorImplementationTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/DecoratorImplementationTests.cs @@ -19,21 +19,23 @@ namespace DependencyModules.Tests.GeneratorTests; /// implementation type and skips descriptors built from anything else, which is the overload /// interception uses. Only the attribute could not say so. /// -public class DecoratorImplementationTests { - +public class DecoratorImplementationTests +{ /// /// The default, unchanged: no Implementation named, so every registration of the service is /// wrapped. /// [Fact] - public void WithNoImplementationNamed_EveryRegistrationIsWrapped() { + public void WithNoImplementationNamed_EveryRegistrationIsWrapped() + { var resolved = Resolve("[Decorator]"); Assert.Equal(["Logged", "Logged"], resolved.Select(Outer)); } [Fact] - public void NamingAnImplementation_WrapsOnlyThatOne() { + public void NamingAnImplementation_WrapsOnlyThatOne() + { var resolved = Resolve("[Decorator(Implementation = typeof(Loud))]"); Assert.Equal(["Logged", "Quiet"], resolved.Select(Outer)); @@ -44,7 +46,8 @@ public void NamingAnImplementation_WrapsOnlyThatOne() { /// implementation, not whichever registration happened to be first. /// [Fact] - public void TheWrappedInstance_IsTheNamedImplementation() { + public void TheWrappedInstance_IsTheNamedImplementation() + { var resolved = Resolve("[Decorator(Implementation = typeof(Loud))]"); var logged = Assert.Single(resolved, greeter => Outer(greeter) == "Logged"); @@ -53,7 +56,8 @@ public void TheWrappedInstance_IsTheNamedImplementation() { } [Fact] - public void TheUnnamedImplementation_IsUntouched() { + public void TheUnnamedImplementation_IsUntouched() + { var resolved = Resolve("[Decorator(Implementation = typeof(Loud))]"); var quiet = Assert.Single(resolved, greeter => Outer(greeter) == "Quiet"); @@ -67,7 +71,8 @@ public void TheUnnamedImplementation_IsUntouched() { /// default. /// [Fact] - public void NamingAnImplementationThatIsNotRegistered_WrapsNothing() { + public void NamingAnImplementationThatIsNotRegistered_WrapsNothing() + { var resolved = Resolve("[Decorator(Implementation = typeof(Unregistered))]"); Assert.Equal(["Loud", "Quiet"], resolved.Select(Outer)); @@ -84,8 +89,12 @@ public void NamingAnImplementationThatIsNotRegistered_WrapsNothing() { /// learns about it. Reported rather than silently doing the wrong thing. /// [Fact] - public void NamingAnImplementationUnderGenerateFactories_ReportsDM0022() { - var result = Generate("[Decorator(Implementation = typeof(Loud))]", generateFactories: true); + public void NamingAnImplementationUnderGenerateFactories_ReportsDM0022() + { + var result = Generate( + "[Decorator(Implementation = typeof(Loud))]", + generateFactories: true + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0022"); @@ -94,15 +103,20 @@ public void NamingAnImplementationUnderGenerateFactories_ReportsDM0022() { } [Fact] - public void NamingNoImplementationUnderGenerateFactories_IsNotReported() { + public void NamingNoImplementationUnderGenerateFactories_IsNotReported() + { var result = Generate("[Decorator]", generateFactories: true); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0022"); } [Fact] - public void NamingAnImplementationWithoutGenerateFactories_IsNotReported() { - var result = Generate("[Decorator(Implementation = typeof(Loud))]", generateFactories: false); + public void NamingAnImplementationWithoutGenerateFactories_IsNotReported() + { + var result = Generate( + "[Decorator(Implementation = typeof(Loud))]", + generateFactories: false + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0022"); } @@ -115,48 +129,57 @@ private static string Greet(object greeter) => private static GeneratorResult Generate(string decoratorAttribute, bool generateFactories) => GeneratorTestHarness.Run( Source(decoratorAttribute), - new Dictionary { - ["DependencyModules_GenerateFactories"] = generateFactories ? "true" : "false" - }); - - private static object[] Resolve(string decoratorAttribute, bool generateFactories = false) { + new Dictionary + { + ["DependencyModules_GenerateFactories"] = generateFactories ? "true" : "false", + } + ); + + private static object[] Resolve(string decoratorAttribute, bool generateFactories = false) + { var generated = GeneratedAssembly.Create( Source(decoratorAttribute), - buildProperties: new Dictionary { - ["DependencyModules_GenerateFactories"] = generateFactories ? "true" : "false" - }); + buildProperties: new Dictionary + { + ["DependencyModules_GenerateFactories"] = generateFactories ? "true" : "false", + } + ); var provider = generated.BuildProvider(); - return ((System.Collections.IEnumerable)provider - .GetService(typeof(IEnumerable<>).MakeGenericType(generated.Type("IGreeter")))!) + return ( + (System.Collections.IEnumerable) + provider.GetService( + typeof(IEnumerable<>).MakeGenericType(generated.Type("IGreeter")) + )! + ) .Cast() .OrderBy(greeter => greeter.GetType().Name, System.StringComparer.Ordinal) .ToArray(); } private static string Source(string decoratorAttribute) => - $$""" - using DependencyModules.Runtime.Attributes; + $$""" + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - public interface IGreeter { string Greet(); } + public interface IGreeter { string Greet(); } - [SingletonService] - public class Loud : IGreeter { public string Greet() => "loud"; } + [SingletonService] + public class Loud : IGreeter { public string Greet() => "loud"; } - [SingletonService] - public class Quiet : IGreeter { public string Greet() => "quiet"; } + [SingletonService] + public class Quiet : IGreeter { public string Greet() => "quiet"; } - public class Unregistered : IGreeter { public string Greet() => "nowhere"; } + public class Unregistered : IGreeter { public string Greet() => "nowhere"; } - {{decoratorAttribute}} - public class Logged(IGreeter inner) : IGreeter { - public string Greet() => inner.Greet(); - } + {{decoratorAttribute}} + public class Logged(IGreeter inner) : IGreeter { + public string Greet() => inner.Greet(); + } - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; } diff --git a/tests/DependencyModules.Tests/GeneratorTests/DiagnosticSuppressionTests.cs b/tests/DependencyModules.Tests/GeneratorTests/DiagnosticSuppressionTests.cs index f4c04a2..e639df6 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/DiagnosticSuppressionTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/DiagnosticSuppressionTests.cs @@ -28,23 +28,28 @@ namespace DependencyModules.Tests.GeneratorTests; /// because a generator that failed has nothing to point at. Adding a code without adding it here /// means shipping one more diagnostic nobody can turn off where they wrote it. /// -public class DiagnosticSuppressionTests { - +public class DiagnosticSuppressionTests +{ [Theory] [MemberData(nameof(Triggers))] - public void ADiagnostic_IsReportedAgainstASyntaxTree(string code, string source) { + public void ADiagnostic_IsReportedAgainstASyntaxTree(string code, string source) + { var result = GeneratorTestHarness.Run(source); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == code); - Assert.True(diagnostic.Location.SourceTree != null, - $"{code} was reported at {diagnostic.Location.Kind} with no syntax tree, so neither " + - ".editorconfig nor #pragma can silence it. Location: " + diagnostic.Location); + Assert.True( + diagnostic.Location.SourceTree != null, + $"{code} was reported at {diagnostic.Location.Kind} with no syntax tree, so neither " + + ".editorconfig nor #pragma can silence it. Location: " + + diagnostic.Location + ); } [Theory] [MemberData(nameof(Triggers))] - public void ADiagnostic_IsReportedInTheFileThatCausedIt(string code, string source) { + public void ADiagnostic_IsReportedInTheFileThatCausedIt(string code, string source) + { var result = GeneratorTestHarness.Run(source); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == code); @@ -58,282 +63,383 @@ public void ADiagnostic_IsReportedInTheFileThatCausedIt(string code, string sour /// because they are the two that were never moved and already carry a real location — they are /// covered by AssemblyModuleAttributeDiagnosticsTests. /// - public static IEnumerable Triggers() { - yield return ["DM0002", Module(""" - public interface IThing; - [SingletonService] - public abstract class Thing : IThing; - """)]; - - yield return ["DM0003", """ - using DependencyModules.Runtime.Attributes; - namespace TestNamespace; - [DependencyModule] - public class NotPartialModule; - """]; - - yield return ["DM0012", Module(""" - public interface IThing; - [SingletonService] - [IfEnvironment] - public class Thing : IThing; - """)]; - - yield return ["DM0014", Module(""" - public interface IThing; - [CrossWireService] - public class Thing : IThing; - """)]; - - yield return ["DM0017", """ - using DependencyModules.Runtime.Attributes; - namespace TestNamespace; - public static class Outer { - [DependencyModule] - public partial class NestedModule; - } - """]; - - yield return ["DM0004", Convention(""" - public interface IFoo { } - public class Foo : IFoo { } - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().AsSingleton(); - conventions.RegisterAll().AsScoped(); - } - } - """)]; - - yield return ["DM0005", Convention(""" - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().WithName("NothingMatchesThis").AsSelf().AsScoped(); - } - } - """)]; - - yield return ["DM0006", Convention(""" - public interface IFoo { } - public class Foo : IFoo { private Foo() { } } - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().AsSingleton(); - } - } - """)]; - - yield return ["DM0009", Convention(""" - public interface IFoo { } - public class Foo : IFoo { } - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().AsSelf().AlsoAsSelf().AsSingleton(); - } - } - """)]; - - yield return ["DM0022", """ - using DependencyModules.Runtime.Attributes; - - namespace TestNamespace; - - public interface IGreeter { string Greet(); } - - [SingletonService] - public class Loud : IGreeter { public string Greet() => "loud"; } - - [Decorator(Implementation = typeof(Loud))] - public class Logged(IGreeter inner) : IGreeter { - public string Greet() => inner.Greet(); - } - - [DependencyModule(GenerateFactories = true)] - public partial class TestModule; - """]; - - yield return ["DM0021", """ - using DependencyModules.Runtime.Attributes; - using DependencyModules.Testing.Attributes; - - namespace TestNamespace; - - public interface IThing; - - public class RealThing : IThing; - - public class Fixture { - [TestExport(typeof(IThing), Implementation = typeof(RealThing))] - public void Conflicting([Mock] IThing thing) { } - } - """]; - - yield return ["DM0020", Convention(""" - using DependencyModules.Runtime.Interception; - - public interface IGreeter { string Greet(); } - - public sealed class CountingInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) => context.Proceed(); - } - - [Intercept(typeof(CountingInterceptor))] - public sealed class Greeter : IGreeter { public string Greet() => "hi"; } - - [DependencyModule(OnlyRealm = true)] - public partial class ConventionModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().AsSingleton(); - } - } - """)]; - - yield return ["DM0010", Convention(""" - public interface IFoo { } - public class Foo : IFoo { } - - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().AsSingleton(); - } - } - """)]; - - yield return ["DM0011", Module(""" - public interface IThing; - [SingletonService] - [IfEnvironment("Development")] - public class Thing : IThing; - """)]; - - yield return ["DM0007", """ - using DependencyModules.Runtime.Attributes; - - namespace TestNamespace; - - public interface IThing { string Read(); } - - [SingletonService] - public class Thing : IThing { - public string Read() => ""; - } - - [Decorator(Order = 1)] - public class FirstDecorator(IThing inner) : IThing { - public string Read() => inner.Read(); - } - - [Decorator(Order = 1)] - public class SecondDecorator(IThing inner) : IThing { - public string Read() => inner.Read(); - } - - [DependencyModule] - public partial class TestModule; - """]; - - yield return ["DM0013", """ - using DependencyModules.Runtime.Attributes; - - namespace TestNamespace; - - public interface IStore { string Read(T key); } - - [SingletonService] - public class Store : IStore { - public string Read(T key) => ""; - } - - [Decorator] - public class LoggingStore(IStore inner) : IStore { - public string Read(T key) => inner.Read(key); - } - - [DependencyModule] - public partial class TestModule; - """]; - - yield return ["DM0008", Intercepted(""" - public interface IAwkward { - bool TryGet(string key, out string value); - } - - [SingletonService] - [Intercept(typeof(SyncOnlyInterceptor))] - public class Awkward : IAwkward { - public bool TryGet(string key, out string value) { value = key; return true; } - } - """)]; - - yield return ["DM0015", Intercepted(""" - public interface IAsyncOnly { - Task GetAsync(string key); - } - - [SingletonService] - [Intercept(typeof(SyncOnlyInterceptor))] - public class AsyncOnly : IAsyncOnly { - public Task GetAsync(string key) => Task.FromResult(key); - } - """)]; - - yield return ["DM0018", """ - using DependencyModules.Runtime.Attributes; - namespace TestNamespace; - [DependencyModule] - public partial class TestModule { - public int SizeLimit { get; set; } - } - """]; + public static IEnumerable Triggers() + { + yield return + [ + "DM0002", + Module( + """ + public interface IThing; + [SingletonService] + public abstract class Thing : IThing; + """ + ), + ]; + + yield return + [ + "DM0003", + """ + using DependencyModules.Runtime.Attributes; + namespace TestNamespace; + [DependencyModule] + public class NotPartialModule; + """, + ]; + + yield return + [ + "DM0012", + Module( + """ + public interface IThing; + [SingletonService] + [IfEnvironment] + public class Thing : IThing; + """ + ), + ]; + + yield return + [ + "DM0014", + Module( + """ + public interface IThing; + [CrossWireService] + public class Thing : IThing; + """ + ), + ]; + + yield return + [ + "DM0017", + """ + using DependencyModules.Runtime.Attributes; + namespace TestNamespace; + public static class Outer { + [DependencyModule] + public partial class NestedModule; + } + """, + ]; + + yield return + [ + "DM0004", + Convention( + """ + public interface IFoo { } + public class Foo : IFoo { } + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().AsSingleton(); + conventions.RegisterAll().AsScoped(); + } + } + """ + ), + ]; + + yield return + [ + "DM0005", + Convention( + """ + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().WithName("NothingMatchesThis").AsSelf().AsScoped(); + } + } + """ + ), + ]; + + yield return + [ + "DM0006", + Convention( + """ + public interface IFoo { } + public class Foo : IFoo { private Foo() { } } + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().AsSingleton(); + } + } + """ + ), + ]; + + yield return + [ + "DM0009", + Convention( + """ + public interface IFoo { } + public class Foo : IFoo { } + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().AsSelf().AlsoAsSelf().AsSingleton(); + } + } + """ + ), + ]; + + yield return + [ + "DM0022", + """ + using DependencyModules.Runtime.Attributes; + + namespace TestNamespace; + + public interface IGreeter { string Greet(); } + + [SingletonService] + public class Loud : IGreeter { public string Greet() => "loud"; } + + [Decorator(Implementation = typeof(Loud))] + public class Logged(IGreeter inner) : IGreeter { + public string Greet() => inner.Greet(); + } + + [DependencyModule(GenerateFactories = true)] + public partial class TestModule; + """, + ]; + + yield return + [ + "DM0021", + """ + using DependencyModules.Runtime.Attributes; + using DependencyModules.Testing.Attributes; + + namespace TestNamespace; + + public interface IThing; + + public class RealThing : IThing; + + public class Fixture { + [TestExport(typeof(IThing), Implementation = typeof(RealThing))] + public void Conflicting([Mock] IThing thing) { } + } + """, + ]; + + yield return + [ + "DM0020", + Convention( + """ + using DependencyModules.Runtime.Interception; + + public interface IGreeter { string Greet(); } + + public sealed class CountingInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) => context.Proceed(); + } + + [Intercept(typeof(CountingInterceptor))] + public sealed class Greeter : IGreeter { public string Greet() => "hi"; } + + [DependencyModule(OnlyRealm = true)] + public partial class ConventionModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().AsSingleton(); + } + } + """ + ), + ]; + + yield return + [ + "DM0010", + Convention( + """ + public interface IFoo { } + public class Foo : IFoo { } + + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().AsSingleton(); + } + } + """ + ), + ]; + + yield return + [ + "DM0011", + Module( + """ + public interface IThing; + [SingletonService] + [IfEnvironment("Development")] + public class Thing : IThing; + """ + ), + ]; + + yield return + [ + "DM0007", + """ + using DependencyModules.Runtime.Attributes; + + namespace TestNamespace; + + public interface IThing { string Read(); } + + [SingletonService] + public class Thing : IThing { + public string Read() => ""; + } + + [Decorator(Order = 1)] + public class FirstDecorator(IThing inner) : IThing { + public string Read() => inner.Read(); + } + + [Decorator(Order = 1)] + public class SecondDecorator(IThing inner) : IThing { + public string Read() => inner.Read(); + } + + [DependencyModule] + public partial class TestModule; + """, + ]; + + yield return + [ + "DM0013", + """ + using DependencyModules.Runtime.Attributes; + + namespace TestNamespace; + + public interface IStore { string Read(T key); } + + [SingletonService] + public class Store : IStore { + public string Read(T key) => ""; + } + + [Decorator] + public class LoggingStore(IStore inner) : IStore { + public string Read(T key) => inner.Read(key); + } + + [DependencyModule] + public partial class TestModule; + """, + ]; + + yield return + [ + "DM0008", + Intercepted( + """ + public interface IAwkward { + bool TryGet(string key, out string value); + } + + [SingletonService] + [Intercept(typeof(SyncOnlyInterceptor))] + public class Awkward : IAwkward { + public bool TryGet(string key, out string value) { value = key; return true; } + } + """ + ), + ]; + + yield return + [ + "DM0015", + Intercepted( + """ + public interface IAsyncOnly { + Task GetAsync(string key); + } + + [SingletonService] + [Intercept(typeof(SyncOnlyInterceptor))] + public class AsyncOnly : IAsyncOnly { + public Task GetAsync(string key) => Task.FromResult(key); + } + """ + ), + ]; + + yield return + [ + "DM0018", + """ + using DependencyModules.Runtime.Attributes; + namespace TestNamespace; + [DependencyModule] + public partial class TestModule { + public int SizeLimit { get; set; } + } + """, + ]; } private static string Convention(string body) => $$""" - using System; - using DependencyModules.Runtime.Attributes; - using DependencyModules.Runtime.Conventions; + using System; + using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Conventions; - namespace TestNamespace; + namespace TestNamespace; - {{body}} - """; + {{body}} + """; private static string Intercepted(string body) => $$""" - using System.Threading.Tasks; - using DependencyModules.Runtime.Attributes; - using DependencyModules.Runtime.Interception; + using System.Threading.Tasks; + using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Interception; - namespace TestNamespace; + namespace TestNamespace; - [SingletonService] - public class SyncOnlyInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) => context.Proceed(); - } + [SingletonService] + public class SyncOnlyInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) => context.Proceed(); + } - {{body}} + {{body}} - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; private static string Module(string body) => $$""" - using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - [DependencyModule] - public partial class TestModule; + [DependencyModule] + public partial class TestModule; - {{body}} - """; + {{body}} + """; } diff --git a/tests/DependencyModules.Tests/GeneratorTests/DiagnosticsTests.cs b/tests/DependencyModules.Tests/GeneratorTests/DiagnosticsTests.cs index d4947b8..354a5d5 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/DiagnosticsTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/DiagnosticsTests.cs @@ -9,16 +9,19 @@ namespace DependencyModules.Tests.GeneratorTests; /// mistake that previously produced either a crash when the container was built or, worse, a /// successful build that quietly registered nothing. /// -public class DiagnosticsTests { - +public class DiagnosticsTests +{ /// /// An abstract implementation used to be registered anyway, and the resulting /// AddSingleton(typeof(IThing), typeof(AbstractThing)) threw when the provider was built, /// a long way from the declaration responsible. /// [Fact] - public void AbstractService_ReportsDM0002() { - var result = GeneratorTestHarness.Run(Module("[SingletonService] public abstract class Thing : IThing;")); + public void AbstractService_ReportsDM0002() + { + var result = GeneratorTestHarness.Run( + Module("[SingletonService] public abstract class Thing : IThing;") + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0002"); @@ -28,14 +31,18 @@ public void AbstractService_ReportsDM0002() { } [Fact] - public void AbstractService_IsNotRegistered() { - var result = GeneratorTestHarness.Run(Module("[SingletonService] public abstract class Thing : IThing;")); + public void AbstractService_IsNotRegistered() + { + var result = GeneratorTestHarness.Run( + Module("[SingletonService] public abstract class Thing : IThing;") + ); Assert.DoesNotContain(result.GeneratedSources.Keys, key => key.Contains("Dependencies")); } [Fact] - public void StaticService_ReportsDM0002() { + public void StaticService_ReportsDM0002() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -47,7 +54,8 @@ public static class StaticThing; [DependencyModule] public partial class TestModule; - """); + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0002"); @@ -59,7 +67,8 @@ public partial class TestModule; /// bad one should not discard the good ones. /// [Fact] - public void ConcreteServices_AreStillRegisteredAlongsideARejectedOne() { + public void ConcreteServices_AreStillRegisteredAlongsideARejectedOne() + { var generated = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -75,7 +84,8 @@ public interface IAbstract; [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = generated.BuildProvider(); @@ -88,7 +98,8 @@ public partial class TestModule; /// DM0003 names the fix, and generation is skipped so it is the only error shown. /// [Fact] - public void NonPartialModule_ReportsDM0003() { + public void NonPartialModule_ReportsDM0003() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -101,7 +112,8 @@ public interface IThing; [DependencyModule] public class NotPartialModule; - """); + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0003"); @@ -111,7 +123,8 @@ public class NotPartialModule; } [Fact] - public void NonPartialModule_DoesNotGenerateAConflictingDeclaration() { + public void NonPartialModule_DoesNotGenerateAConflictingDeclaration() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -120,23 +133,31 @@ namespace TestNamespace; [DependencyModule] public class NotPartialModule; - """); + """ + ); // Emitting the module half would add CS0260 on top of DM0003 and point at the wrong thing. - Assert.DoesNotContain(result.GeneratedSources.Keys, key => key.Contains("NotPartialModule.Module")); + Assert.DoesNotContain( + result.GeneratedSources.Keys, + key => key.Contains("NotPartialModule.Module") + ); Assert.DoesNotContain(result.CompilationDiagnostics, d => d.Id == "CS0260"); } [Fact] - public void PartialModule_ReportsNothing() { - var result = GeneratorTestHarness.Run(Module("[SingletonService] public class Thing : IThing;")); + public void PartialModule_ReportsNothing() + { + var result = GeneratorTestHarness.Run( + Module("[SingletonService] public class Thing : IThing;") + ); result.AssertNoErrors(); Assert.Empty(result.GeneratorDiagnostics); } [Fact] - public void AbstractFactoryHost_IsAllowedBecauseTheFactorySuppliesTheInstance() { + public void AbstractFactoryHost_IsAllowedBecauseTheFactorySuppliesTheInstance() + { // The declaring type is never constructed, so an abstract host is legitimate here. var result = GeneratorTestHarness.Run( """ @@ -153,7 +174,8 @@ public abstract class Factories { [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0002"); } @@ -164,7 +186,8 @@ public partial class TestModule; /// a green build — a decorator in the source that never ran. /// [Fact] - public void GenericDecoratorOverOpenGenericRegistration_ReportsDM0013() { + public void GenericDecoratorOverOpenGenericRegistration_ReportsDM0013() + { var result = GeneratorTestHarness.Run( OpenGenericStore( """ @@ -172,7 +195,9 @@ public void GenericDecoratorOverOpenGenericRegistration_ReportsDM0013() { public class LoggingStore(IStore inner) : IStore { public string Read(T key) => inner.Read(key); } - """)); + """ + ) + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0013"); @@ -186,7 +211,8 @@ public class LoggingStore(IStore inner) : IStore { /// expansion, so both have to report. /// [Fact] - public void ModuleDeclaredDecoratorOverOpenGenericRegistration_ReportsDM0013() { + public void ModuleDeclaredDecoratorOverOpenGenericRegistration_ReportsDM0013() + { var result = GeneratorTestHarness.Run( OpenGenericStore( """ @@ -194,7 +220,9 @@ public class LoggingStore(IStore inner) : IStore { public string Read(T key) => inner.Read(key); } """, - moduleAttributes: "[Decorate(typeof(IStore<>), typeof(LoggingStore<>))]")); + moduleAttributes: "[Decorate(typeof(IStore<>), typeof(LoggingStore<>))]" + ) + ); Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0013"); } @@ -205,7 +233,8 @@ public class LoggingStore(IStore inner) : IStore { /// which is CS7003 in generated code. /// [Fact] - public void NonGenericDecoratorOverOpenGenericRegistration_ReportsDM0013() { + public void NonGenericDecoratorOverOpenGenericRegistration_ReportsDM0013() + { var result = GeneratorTestHarness.Run( OpenGenericStore( """ @@ -213,7 +242,9 @@ public class StringStoreDecorator(IStore inner) : IStore { public string Read(string key) => inner.Read(key); } """, - moduleAttributes: "[Decorate(typeof(IStore<>), typeof(StringStoreDecorator))]")); + moduleAttributes: "[Decorate(typeof(IStore<>), typeof(StringStoreDecorator))]" + ) + ); Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0013"); } @@ -223,7 +254,8 @@ public class StringStoreDecorator(IStore inner) : IStore { /// asserts the compilation is clean and emits. /// [Fact] - public void NonGenericDecoratorOverOpenGenericRegistration_StillCompiles() { + public void NonGenericDecoratorOverOpenGenericRegistration_StillCompiles() + { var generated = GeneratedAssembly.Create( OpenGenericStore( """ @@ -231,7 +263,9 @@ public class StringStoreDecorator(IStore inner) : IStore { public string Read(string key) => inner.Read(key); } """, - moduleAttributes: "[Decorate(typeof(IStore<>), typeof(StringStoreDecorator))]")); + moduleAttributes: "[Decorate(typeof(IStore<>), typeof(StringStoreDecorator))]" + ) + ); Assert.Contains(generated.Services, d => d.ServiceType == generated.Type("IStore`1")); } @@ -241,7 +275,8 @@ public class StringStoreDecorator(IStore inner) : IStore { /// across. This is the shape a MediatR-style pipeline is built from. /// [Fact] - public void GenericDecoratorOverClosedRegistrations_DoesNotReportDM0013() { + public void GenericDecoratorOverClosedRegistrations_DoesNotReportDM0013() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -260,7 +295,8 @@ public class LoggingStore(IStore inner) : IStore { [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0013"); } @@ -271,7 +307,8 @@ public partial class TestModule; /// on the feature's primary use. /// [Fact] - public void DecoratorForAServiceThisCompilationDoesNotRegister_DoesNotReportDM0013() { + public void DecoratorForAServiceThisCompilationDoesNotRegister_DoesNotReportDM0013() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -287,7 +324,8 @@ public class LoggingElsewhere(IElsewhere inner) : IElsewhere { [DependencyModule] [Decorate(typeof(IElsewhere<>), typeof(LoggingElsewhere<>))] public partial class TestModule; - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0013"); } @@ -299,8 +337,11 @@ public partial class TestModule; /// GetRequiredService<Ledger<>>(). /// [Fact] - public void CrossWiredGenericType_ReportsDM0014() { - var result = GeneratorTestHarness.Run(CrossWiredLedger("public class Ledger : ILedger, IAudit;")); + public void CrossWiredGenericType_ReportsDM0014() + { + var result = GeneratorTestHarness.Run( + CrossWiredLedger("public class Ledger : ILedger, IAudit;") + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0014"); @@ -309,8 +350,11 @@ public void CrossWiredGenericType_ReportsDM0014() { } [Fact] - public void CrossWiredGenericType_IsNotRegistered() { - var result = GeneratorTestHarness.Run(CrossWiredLedger("public class Ledger : ILedger, IAudit;")); + public void CrossWiredGenericType_IsNotRegistered() + { + var result = GeneratorTestHarness.Run( + CrossWiredLedger("public class Ledger : ILedger, IAudit;") + ); Assert.DoesNotContain(result.GeneratedSources.Keys, key => key.Contains("Dependencies")); } @@ -320,15 +364,19 @@ public void CrossWiredGenericType_IsNotRegistered() { /// interfaces. /// [Fact] - public void CrossWiredNonGenericType_StillRegisters() { - var generated = GeneratedAssembly.Create(CrossWiredLedger("public class Ledger : ILedger, IAudit;")); + public void CrossWiredNonGenericType_StillRegisters() + { + var generated = GeneratedAssembly.Create( + CrossWiredLedger("public class Ledger : ILedger, IAudit;") + ); var provider = generated.BuildProvider(); // The point of cross-wiring: both interfaces answer with the one instance. Assert.Same( provider.GetService(generated.Type("ILedger`1").MakeGenericType(typeof(int))), - provider.GetService(generated.Type("IAudit`1").MakeGenericType(typeof(int)))); + provider.GetService(generated.Type("IAudit`1").MakeGenericType(typeof(int))) + ); } /// @@ -337,7 +385,8 @@ public void CrossWiredNonGenericType_StillRegisters() { /// anything could report on it. /// [Fact] - public void InterceptorThatServesNoMember_ReportsDM0015() { + public void InterceptorThatServesNoMember_ReportsDM0015() + { var result = GeneratorTestHarness.Run( Intercepted( """ @@ -350,7 +399,9 @@ public interface IAsyncOnly { public class AsyncOnly : IAsyncOnly { public Task GetAsync(string key) => Task.FromResult(key); } - """)); + """ + ) + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0015"); @@ -365,7 +416,8 @@ public class AsyncOnly : IAsyncOnly { /// one, which is how an argument-rewriting interceptor stops rewriting halfway through a service. /// [Fact] - public void InterceptorThatServesSomeMembers_ReportsDM0015ForTheRest() { + public void InterceptorThatServesSomeMembers_ReportsDM0015ForTheRest() + { var result = GeneratorTestHarness.Run( Intercepted( """ @@ -380,7 +432,9 @@ public class Mixed : IMixed { public int Count(string key) => key.Length; public Task CountAsync(string key) => Task.FromResult(key.Length); } - """)); + """ + ) + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0015"); @@ -392,7 +446,8 @@ public class Mixed : IMixed { /// An interceptor covering every shape the service uses says nothing. /// [Fact] - public void InterceptorThatServesEveryMember_DoesNotReportDM0015() { + public void InterceptorThatServesEveryMember_DoesNotReportDM0015() + { var result = GeneratorTestHarness.Run( Intercepted( """ @@ -405,7 +460,9 @@ public interface ISyncOnly { public class SyncOnly : ISyncOnly { public int Count(string key) => key.Length; } - """)); + """ + ) + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0015"); } @@ -415,7 +472,8 @@ public class SyncOnly : ISyncOnly { /// the guide read as though the other members were still intercepted. /// [Fact] - public void UnsupportedMember_ReportsThatNoMemberIsIntercepted() { + public void UnsupportedMember_ReportsThatNoMemberIsIntercepted() + { var result = GeneratorTestHarness.Run( Intercepted( """ @@ -430,7 +488,9 @@ public class Awkward : IAwkward { public bool TryGet(string key, out string value) { value = key; return true; } public int Fine(string key) => key.Length; } - """)); + """ + ) + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0008"); @@ -440,68 +500,68 @@ public class Awkward : IAwkward { private static string Intercepted(string body) => $$""" - using System.Threading.Tasks; - using DependencyModules.Runtime.Attributes; - using DependencyModules.Runtime.Interception; + using System.Threading.Tasks; + using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Interception; - namespace TestNamespace; + namespace TestNamespace; - [SingletonService] - public class SyncOnlyInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) => context.Proceed(); - } + [SingletonService] + public class SyncOnlyInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) => context.Proceed(); + } - {{body}} + {{body}} - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; private static string OpenGenericStore(string body, string moduleAttributes = "") => $$""" - using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - public interface IStore { string Read(T key); } + public interface IStore { string Read(T key); } - [SingletonService] - public class Store : IStore { public string Read(T key) => "store"; } + [SingletonService] + public class Store : IStore { public string Read(T key) => "store"; } - {{body}} + {{body}} - [DependencyModule] - {{moduleAttributes}} - public partial class TestModule; - """; + [DependencyModule] + {{moduleAttributes}} + public partial class TestModule; + """; private static string CrossWiredLedger(string implementation) => $$""" - using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - public interface ILedger; - public interface IAudit; + public interface ILedger; + public interface IAudit; - [CrossWireService] - {{implementation}} + [CrossWireService] + {{implementation}} - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; private static string Module(string body) => $$""" - using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - public interface IThing; + public interface IThing; - {{body}} + {{body}} - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; } diff --git a/tests/DependencyModules.Tests/GeneratorTests/EntryModelUtilTests.cs b/tests/DependencyModules.Tests/GeneratorTests/EntryModelUtilTests.cs index 74dea2c..876b13b 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/EntryModelUtilTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/EntryModelUtilTests.cs @@ -11,55 +11,82 @@ namespace DependencyModules.Tests.GeneratorTests; /// EntryModelUtil decides the generated file names and which entry points survive consolidation. /// Getting consolidation wrong either drops a module's registrations or emits duplicate files. /// -public class EntryModelUtilTests { - +public class EntryModelUtilTests +{ [Fact] - public void GenerateFileName_CombinesNamespaceTypeAndSuffix() { - var model = ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("My.Namespace", "MyModule")); + public void GenerateFileName_CombinesNamespaceTypeAndSuffix() + { + var model = ModelFactory.EntryPoint( + entryPointType: TypeDefinition.Get("My.Namespace", "MyModule") + ); - Assert.Equal("My.Namespace.MyModule.Dependencies.g.cs", EntryModelUtil.GenerateFileName(model, "Dependencies")); + Assert.Equal( + "My.Namespace.MyModule.Dependencies.g.cs", + EntryModelUtil.GenerateFileName(model, "Dependencies") + ); } [Fact] - public void GenerateFileName_ForTheGlobalNamespace_UsesAPlaceholder() { + public void GenerateFileName_ForTheGlobalNamespace_UsesAPlaceholder() + { var model = ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("", "MyModule")); - Assert.Equal("blank-namespace.MyModule.Module.g.cs", EntryModelUtil.GenerateFileName(model, "Module")); + Assert.Equal( + "blank-namespace.MyModule.Module.g.cs", + EntryModelUtil.GenerateFileName(model, "Module") + ); } [Fact] - public void EnsureNamespace_GivesAutoGeneratedModulesTheRootNamespace() { + public void EnsureNamespace_GivesAutoGeneratedModulesTheRootNamespace() + { var model = ModelFactory.EntryPoint( features: ModuleEntryPointFeatures.AutoGenerateModule, - entryPointType: TypeDefinition.Get("", "ApplicationModule")); + entryPointType: TypeDefinition.Get("", "ApplicationModule") + ); - var result = EntryModelUtil.EnsureNamespace(model, ModelFactory.Configuration(rootNamespace: "ConfiguredRoot")); + var result = EntryModelUtil.EnsureNamespace( + model, + ModelFactory.Configuration(rootNamespace: "ConfiguredRoot") + ); Assert.Equal("ConfiguredRoot", result.EntryPointType.Namespace); } [Fact] - public void EnsureNamespace_LeavesAutoGeneratedModulesThatAlreadyHaveANamespace() { + public void EnsureNamespace_LeavesAutoGeneratedModulesThatAlreadyHaveANamespace() + { var model = ModelFactory.EntryPoint( features: ModuleEntryPointFeatures.AutoGenerateModule, - entryPointType: TypeDefinition.Get("Existing", "ApplicationModule")); + entryPointType: TypeDefinition.Get("Existing", "ApplicationModule") + ); - var result = EntryModelUtil.EnsureNamespace(model, ModelFactory.Configuration(rootNamespace: "ConfiguredRoot")); + var result = EntryModelUtil.EnsureNamespace( + model, + ModelFactory.Configuration(rootNamespace: "ConfiguredRoot") + ); Assert.Equal("Existing", result.EntryPointType.Namespace); } [Fact] - public void EnsureNamespace_LeavesExplicitlyDeclaredModulesAlone() { - var model = ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("", "DeclaredModule")); + public void EnsureNamespace_LeavesExplicitlyDeclaredModulesAlone() + { + var model = ModelFactory.EntryPoint( + entryPointType: TypeDefinition.Get("", "DeclaredModule") + ); - var result = EntryModelUtil.EnsureNamespace(model, ModelFactory.Configuration(rootNamespace: "ConfiguredRoot")); + var result = EntryModelUtil.EnsureNamespace( + model, + ModelFactory.Configuration(rootNamespace: "ConfiguredRoot") + ); Assert.Equal("", result.EntryPointType.Namespace); } [Fact] - public void Consolidate_KeepsADeclaredModule() { + public void Consolidate_KeepsADeclaredModule() + { var model = ModelFactory.EntryPoint(); var (entryPoints, _) = EntryModelUtil.ConsolidateEntryPointModels(Input(model)); @@ -68,10 +95,12 @@ public void Consolidate_KeepsADeclaredModule() { } [Fact] - public void Consolidate_KeepsAnAutoGeneratedModuleDeclaredInTheProjectsProgramFile() { + public void Consolidate_KeepsAnAutoGeneratedModuleDeclaredInTheProjectsProgramFile() + { var model = ModelFactory.EntryPoint( features: ModuleEntryPointFeatures.AutoGenerateModule, - fileLocation: Path.Combine("/project/", "Program.cs")); + fileLocation: Path.Combine("/project/", "Program.cs") + ); var (entryPoints, _) = EntryModelUtil.ConsolidateEntryPointModels(Input(model)); @@ -82,10 +111,12 @@ public void Consolidate_KeepsAnAutoGeneratedModuleDeclaredInTheProjectsProgramFi /// A Program.cs belonging to a referenced project must not generate an ApplicationModule here. /// [Fact] - public void Consolidate_DropsAnAutoGeneratedModuleFromAnotherProject() { + public void Consolidate_DropsAnAutoGeneratedModuleFromAnotherProject() + { var model = ModelFactory.EntryPoint( features: ModuleEntryPointFeatures.AutoGenerateModule, - fileLocation: "/somewhere-else/Program.cs"); + fileLocation: "/somewhere-else/Program.cs" + ); var (entryPoints, _) = EntryModelUtil.ConsolidateEntryPointModels(Input(model)); @@ -93,29 +124,35 @@ public void Consolidate_DropsAnAutoGeneratedModuleFromAnotherProject() { } [Fact] - public void Consolidate_WithAutoGenerateDisabled_DropsAutoGeneratedModules() { + public void Consolidate_WithAutoGenerateDisabled_DropsAutoGeneratedModules() + { var model = ModelFactory.EntryPoint( features: ModuleEntryPointFeatures.AutoGenerateModule, - fileLocation: Path.Combine("/project/", "Program.cs")); + fileLocation: Path.Combine("/project/", "Program.cs") + ); var (entryPoints, _) = EntryModelUtil.ConsolidateEntryPointModels( - Input(model, ModelFactory.Configuration(autoGenerateEntry: false))); + Input(model, ModelFactory.Configuration(autoGenerateEntry: false)) + ); Assert.Empty(entryPoints); } [Fact] - public void Consolidate_WithAutoGenerateDisabled_KeepsDeclaredModules() { + public void Consolidate_WithAutoGenerateDisabled_KeepsDeclaredModules() + { var model = ModelFactory.EntryPoint(); var (entryPoints, _) = EntryModelUtil.ConsolidateEntryPointModels( - Input(model, ModelFactory.Configuration(autoGenerateEntry: false))); + Input(model, ModelFactory.Configuration(autoGenerateEntry: false)) + ); Assert.Single(entryPoints); } [Fact] - public void Consolidate_GroupsModelsSharingANamespaceAndName() { + public void Consolidate_GroupsModelsSharingANamespaceAndName() + { var first = ModelFactory.EntryPoint(fileLocation: "/project/A.cs"); var second = ModelFactory.EntryPoint(fileLocation: "/project/B.cs"); @@ -129,13 +166,16 @@ public void Consolidate_GroupsModelsSharingANamespaceAndName() { /// carries the configuration. /// [Fact] - public void Consolidate_PrefersTheDeclaredModuleOverTheAutoGeneratedOne() { + public void Consolidate_PrefersTheDeclaredModuleOverTheAutoGeneratedOne() + { var auto = ModelFactory.EntryPoint( features: ModuleEntryPointFeatures.AutoGenerateModule, - entryPointType: TypeDefinition.Get("TestNamespace", "SharedName")); + entryPointType: TypeDefinition.Get("TestNamespace", "SharedName") + ); var declared = ModelFactory.EntryPoint( entryPointType: TypeDefinition.Get("TestNamespace", "SharedName"), - useMethod: "UseIt"); + useMethod: "UseIt" + ); var (entryPoints, _) = EntryModelUtil.ConsolidateEntryPointModels(Input(auto, declared)); @@ -143,9 +183,14 @@ public void Consolidate_PrefersTheDeclaredModuleOverTheAutoGeneratedOne() { } [Fact] - public void Consolidate_KeepsDistinctModulesSeparate() { - var first = ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("TestNamespace", "First")); - var second = ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("TestNamespace", "Second")); + public void Consolidate_KeepsDistinctModulesSeparate() + { + var first = ModelFactory.EntryPoint( + entryPointType: TypeDefinition.Get("TestNamespace", "First") + ); + var second = ModelFactory.EntryPoint( + entryPointType: TypeDefinition.Get("TestNamespace", "Second") + ); var (entryPoints, _) = EntryModelUtil.ConsolidateEntryPointModels(Input(first, second)); @@ -153,17 +198,20 @@ public void Consolidate_KeepsDistinctModulesSeparate() { } [Fact] - public void Consolidate_ReturnsTheConfigurationItWasGiven() { + public void Consolidate_ReturnsTheConfigurationItWasGiven() + { var configuration = ModelFactory.Configuration(rootNamespace: "SomeRoot"); var (_, returned) = EntryModelUtil.ConsolidateEntryPointModels( - Input(ModelFactory.EntryPoint(), configuration)); + Input(ModelFactory.EntryPoint(), configuration) + ); Assert.Same(configuration, returned); } [Fact] - public void UniqueId_IsStableForTheSameType() { + public void UniqueId_IsStableForTheSameType() + { var first = ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("A.B", "Module")); var second = ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("A.B", "Module")); @@ -171,22 +219,32 @@ public void UniqueId_IsStableForTheSameType() { } [Fact] - public void UniqueId_DiffersBetweenTypes() { + public void UniqueId_DiffersBetweenTypes() + { var first = ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("A.B", "ModuleOne")); - var second = ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("A.B", "ModuleTwo")); + var second = ModelFactory.EntryPoint( + entryPointType: TypeDefinition.Get("A.B", "ModuleTwo") + ); Assert.NotEqual(first.UniqueId(), second.UniqueId()); } - private static ImmutableArray<(ModuleEntryPointModel, DependencyModuleConfigurationModel)> Input( - params ModuleEntryPointModel[] models) => - Input(ModelFactory.Configuration(), models); + private static ImmutableArray<( + ModuleEntryPointModel, + DependencyModuleConfigurationModel + )> Input(params ModuleEntryPointModel[] models) => Input(ModelFactory.Configuration(), models); - private static ImmutableArray<(ModuleEntryPointModel, DependencyModuleConfigurationModel)> Input( - ModuleEntryPointModel model, DependencyModuleConfigurationModel configuration) => + private static ImmutableArray<( + ModuleEntryPointModel, + DependencyModuleConfigurationModel + )> Input(ModuleEntryPointModel model, DependencyModuleConfigurationModel configuration) => Input(configuration, model); - private static ImmutableArray<(ModuleEntryPointModel, DependencyModuleConfigurationModel)> Input( - DependencyModuleConfigurationModel configuration, params ModuleEntryPointModel[] models) => - models.Select(model => (model, configuration)).ToImmutableArray(); + private static ImmutableArray<( + ModuleEntryPointModel, + DependencyModuleConfigurationModel + )> Input( + DependencyModuleConfigurationModel configuration, + params ModuleEntryPointModel[] models + ) => models.Select(model => (model, configuration)).ToImmutableArray(); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/EnvironmentConditionTests.cs b/tests/DependencyModules.Tests/GeneratorTests/EnvironmentConditionTests.cs index dc85d7f..8170870 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/EnvironmentConditionTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/EnvironmentConditionTests.cs @@ -13,10 +13,9 @@ namespace DependencyModules.Tests.GeneratorTests; /// actually in the collection. A condition emitted into the wrong branch still produces plausible /// generated text, so text assertions would pass while the service registered in production. /// -public class EnvironmentConditionTests { - - private const string Preamble = - """ +public class EnvironmentConditionTests +{ + private const string Preamble = """ using System; using DependencyModules.Runtime.Attributes; @@ -34,11 +33,12 @@ private static GeneratedAssembly Compile(string source, IModuleEnvironment? envi private static IModuleEnvironment Env(string name) => Env(name, []); /// - private static IModuleEnvironment Env(string name, params (string Key, string? Value)[] values) => - new ModuleEnvironment(false, name, values.ToDictionary(v => v.Key, v => v.Value)); + private static IModuleEnvironment Env( + string name, + params (string Key, string? Value)[] values + ) => new ModuleEnvironment(false, name, values.ToDictionary(v => v.Key, v => v.Value)); - private const string NameGated = - """ + private const string NameGated = """ public interface IEmailSender { } [SingletonService] @@ -46,8 +46,7 @@ public interface IEmailSender { } public class FakeEmailSender : IEmailSender { } """; - private const string Module = - """ + private const string Module = """ [DependencyModule] public partial class TestModule; @@ -59,16 +58,20 @@ public partial class TestModule; [InlineData("development", true)] [InlineData("Production", false)] [InlineData("", false)] - public void IfEnvironmentRegistersOnlyInTheNamedEnvironments(string environmentName, bool expected) { + public void IfEnvironmentRegistersOnlyInTheNamedEnvironments( + string environmentName, + bool expected + ) + { var assembly = Compile(NameGated + Module, Env(environmentName)); Assert.Equal(expected, assembly.Descriptors("IEmailSender").Count == 1); } [Fact] - public void IfNotEnvironmentRegistersEverywhereElse() { - const string source = - """ + public void IfNotEnvironmentRegistersEverywhereElse() + { + const string source = """ public interface IProfiler { } [SingletonService] @@ -81,9 +84,9 @@ public class RequestProfiler : IProfiler { } } [Fact] - public void IfEnvironmentValueTestsPresenceWhenGivenOnlyAKey() { - const string source = - """ + public void IfEnvironmentValueTestsPresenceWhenGivenOnlyAKey() + { + const string source = """ public interface IBilling { } [SingletonService] @@ -91,18 +94,23 @@ public interface IBilling { } public class Billing : IBilling { } """; - Assert.Single(Compile(source + Module, Env("Any", ("FEATURE_BILLING", "anything"))).Descriptors("IBilling")); + Assert.Single( + Compile(source + Module, Env("Any", ("FEATURE_BILLING", "anything"))) + .Descriptors("IBilling") + ); // Set to empty is still set; only absence is absence. - Assert.Single(Compile(source + Module, Env("Any", ("FEATURE_BILLING", ""))).Descriptors("IBilling")); + Assert.Single( + Compile(source + Module, Env("Any", ("FEATURE_BILLING", ""))).Descriptors("IBilling") + ); Assert.Empty(Compile(source + Module, Env("Any")).Descriptors("IBilling")); } [Fact] - public void IfEnvironmentValueComparesTheValueExactly() { - const string source = - """ + public void IfEnvironmentValueComparesTheValueExactly() + { + const string source = """ public interface IBilling { } [SingletonService] @@ -118,9 +126,9 @@ public class Billing : IBilling { } } [Fact] - public void ConditionsOfDifferentKindsCombineWithAnd() { - const string source = - """ + public void ConditionsOfDifferentKindsCombineWithAnd() + { + const string source = """ public interface IThing { } [SingletonService] @@ -129,15 +137,21 @@ public interface IThing { } public class Thing : IThing { } """; - Assert.Single(Compile(source + Module, Env("Development", ("MODE", "on"))).Descriptors("IThing")); - Assert.Empty(Compile(source + Module, Env("Development", ("MODE", "off"))).Descriptors("IThing")); - Assert.Empty(Compile(source + Module, Env("Production", ("MODE", "on"))).Descriptors("IThing")); + Assert.Single( + Compile(source + Module, Env("Development", ("MODE", "on"))).Descriptors("IThing") + ); + Assert.Empty( + Compile(source + Module, Env("Development", ("MODE", "off"))).Descriptors("IThing") + ); + Assert.Empty( + Compile(source + Module, Env("Production", ("MODE", "on"))).Descriptors("IThing") + ); } [Fact] - public void SeveralValueConditionsAllHaveToHold() { - const string source = - """ + public void SeveralValueConditionsAllHaveToHold() + { + const string source = """ public interface IThing { } [SingletonService] @@ -146,7 +160,9 @@ public interface IThing { } public class Thing : IThing { } """; - Assert.Single(Compile(source + Module, Env("Any", ("A", "1"), ("B", "2"))).Descriptors("IThing")); + Assert.Single( + Compile(source + Module, Env("Any", ("A", "1"), ("B", "2"))).Descriptors("IThing") + ); Assert.Empty(Compile(source + Module, Env("Any", ("A", "1"))).Descriptors("IThing")); } @@ -163,9 +179,9 @@ public class Thing : IThing { } /// "Smtp" and the default would land last and win in every environment. /// [Fact] - public void AConditionalRegistrationOverridesAnUnconditionalDefault() { - const string source = - """ + public void AConditionalRegistrationOverridesAnUnconditionalDefault() + { + const string source = """ public interface IEmailSender { } [SingletonService] @@ -181,20 +197,22 @@ public class FakeEmailSender : IEmailSender { } Assert.Equal( development.Type("FakeEmailSender"), - development.BuildProvider().GetService(development.Type("IEmailSender"))!.GetType()); + development.BuildProvider().GetService(development.Type("IEmailSender"))!.GetType() + ); Assert.Equal( production.Type("SmtpEmailSender"), - production.BuildProvider().GetService(production.Type("IEmailSender"))!.GetType()); + production.BuildProvider().GetService(production.Type("IEmailSender"))!.GetType() + ); } /// /// The motivating case: one service type, two implementations, exactly one registered. /// [Fact] - public void TwoImplementationsOfOneServiceSelectByEnvironment() { - const string source = - """ + public void TwoImplementationsOfOneServiceSelectByEnvironment() + { + const string source = """ public interface IEmailSender { string Name { get; } } [SingletonService] @@ -209,14 +227,20 @@ public class SmtpEmailSender : IEmailSender { public string Name => "smtp"; } var development = Compile(source + Module, Env("Development")); var production = Compile(source + Module, Env("Production")); - Assert.Equal(development.Type("FakeEmailSender"), development.Descriptor("IEmailSender").ImplementationType); - Assert.Equal(production.Type("SmtpEmailSender"), production.Descriptor("IEmailSender").ImplementationType); + Assert.Equal( + development.Type("FakeEmailSender"), + development.Descriptor("IEmailSender").ImplementationType + ); + Assert.Equal( + production.Type("SmtpEmailSender"), + production.Descriptor("IEmailSender").ImplementationType + ); } [Fact] - public void UnconditionalServicesInTheSameModuleAreUnaffected() { - const string source = - """ + public void UnconditionalServicesInTheSameModuleAreUnaffected() + { + const string source = """ public interface IAlways { } public interface ISometimes { } @@ -235,7 +259,8 @@ public class Sometimes : ISometimes { } } [Fact] - public void ModuleEnvironmentNoneRegistersNothingConditional() { + public void ModuleEnvironmentNoneRegistersNothingConditional() + { var assembly = Compile(NameGated + Module, ModuleEnvironment.None); Assert.Empty(assembly.Descriptors("IEmailSender")); @@ -250,13 +275,15 @@ public void ModuleEnvironmentNoneRegistersNothingConditional() { /// ModuleEnvironmentDefaultNameTests moves on purpose. /// [Fact] - public void NoEnvironmentSuppliedUsesTheProcessEnvironment() { + public void NoEnvironmentSuppliedUsesTheProcessEnvironment() + { var supplied = Compile(NameGated + Module, ModuleEnvironment.CreateDefault()); var omitted = Compile(NameGated + Module, environment: null); Assert.Equal( supplied.Descriptors("IEmailSender").Count, - omitted.Descriptors("IEmailSender").Count); + omitted.Descriptors("IEmailSender").Count + ); } /// @@ -276,9 +303,11 @@ public void NoEnvironmentSuppliedUsesTheProcessEnvironment() { [Theory] [InlineData("Development")] [InlineData("Production")] - public void AReferencedModuleConditionalDoesNotOverrideTheReferencingModule(string environmentName) { - const string source = - """ + public void AReferencedModuleConditionalDoesNotOverrideTheReferencingModule( + string environmentName + ) + { + const string source = """ public interface IEmailSender { } [SingletonService(Realm = typeof(LibraryModule))] @@ -300,7 +329,8 @@ public partial class TestModule; Assert.Equal( assembly.Type("ApplicationOwn"), - assembly.BuildProvider().GetService(assembly.Type("IEmailSender"))!.GetType()); + assembly.BuildProvider().GetService(assembly.Type("IEmailSender"))!.GetType() + ); } /// @@ -311,10 +341,11 @@ public partial class TestModule; [InlineData("Development", "ApplicationFake")] [InlineData("Production", "LibrarySmtp")] public void AnApplicationConditionalOverridesAReferencedModuleDefault( - string environmentName, string expected) { - - const string source = - """ + string environmentName, + string expected + ) + { + const string source = """ public interface IEmailSender { } [SingletonService(Realm = typeof(LibraryModule))] @@ -336,7 +367,8 @@ public partial class TestModule; Assert.Equal( assembly.Type(expected), - assembly.BuildProvider().GetService(assembly.Type("IEmailSender"))!.GetType()); + assembly.BuildProvider().GetService(assembly.Type("IEmailSender"))!.GetType() + ); } /// @@ -344,9 +376,9 @@ public partial class TestModule; /// both are still in the enumerable. /// [Fact] - public void AConditionalOverrideLeavesTheDefaultInTheEnumerable() { - const string source = - """ + public void AConditionalOverrideLeavesTheDefaultInTheEnumerable() + { + const string source = """ public interface IEmailSender { } [SingletonService] @@ -367,9 +399,9 @@ public class FakeEmailSender : IEmailSender { } /// kind of registration it is, and the two compose. /// [Fact] - public void AConditionCombinesWithReplace() { - const string source = - """ + public void AConditionCombinesWithReplace() + { + const string source = """ public interface IEmailSender { } [SingletonService] @@ -384,14 +416,21 @@ public class FakeEmailSender : IEmailSender { } var production = Compile(source + Module, Env("Production")); Assert.Single(development.Descriptors("IEmailSender")); - Assert.Equal(development.Type("FakeEmailSender"), development.Descriptor("IEmailSender").ImplementationType); + Assert.Equal( + development.Type("FakeEmailSender"), + development.Descriptor("IEmailSender").ImplementationType + ); Assert.Single(production.Descriptors("IEmailSender")); - Assert.Equal(production.Type("SmtpEmailSender"), production.Descriptor("IEmailSender").ImplementationType); + Assert.Equal( + production.Type("SmtpEmailSender"), + production.Descriptor("IEmailSender").ImplementationType + ); } [Fact] - public void ConditionsAreReportedAtBuildTime() { + public void ConditionsAreReportedAtBuildTime() + { var result = GeneratorTestHarness.Run(Preamble + NameGated + Module); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0011"); @@ -400,9 +439,9 @@ public void ConditionsAreReportedAtBuildTime() { } [Fact] - public void UnconditionalServicesReportNothing() { - const string source = - """ + public void UnconditionalServicesReportNothing() + { + const string source = """ public interface IThing { } [SingletonService] @@ -418,15 +457,15 @@ public class Thing : IThing { } [InlineData("[IfEnvironment]", "environment name")] [InlineData("[IfNotEnvironment]", "environment name")] [InlineData("[IfEnvironmentValue(\"\")]", "key")] - public void AConditionThatTestsNothingIsRefused(string attribute, string expectedKind) { - var source = - $$""" - public interface IThing { } + public void AConditionThatTestsNothingIsRefused(string attribute, string expectedKind) + { + var source = $$""" + public interface IThing { } - [SingletonService] - {{attribute}} - public class Thing : IThing { } - """; + [SingletonService] + {{attribute}} + public class Thing : IThing { } + """; var result = GeneratorTestHarness.Run(Preamble + source + Module); @@ -436,8 +475,10 @@ public class Thing : IThing { } // Reported, not silently dropped: the service still registers rather than vanishing. Assert.Single( - GeneratedAssembly.Create(Preamble + source + Module, environment: ModuleEnvironment.None) - .Descriptors("IThing")); + GeneratedAssembly + .Create(Preamble + source + Module, environment: ModuleEnvironment.None) + .Descriptors("IThing") + ); } /// @@ -445,9 +486,9 @@ public class Thing : IThing { } /// keeps its environment names in one place would silently never match. /// [Fact] - public void ConditionArgumentsMayBeConstants() { - const string source = - """ + public void ConditionArgumentsMayBeConstants() + { + const string source = """ public static class Environments { public const string Development = "Development"; } @@ -469,9 +510,9 @@ public class Thing : IThing { } /// already, and here it would register a development service in production. /// [Fact] - public void ANamespaceQualifiedConditionStillApplies() { - const string source = - """ + public void ANamespaceQualifiedConditionStillApplies() + { + const string source = """ public interface IThing { } [SingletonService] diff --git a/tests/DependencyModules.Tests/GeneratorTests/ExtensionSeamTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ExtensionSeamTests.cs index 0d36ad3..ccdbe25 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ExtensionSeamTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ExtensionSeamTests.cs @@ -11,18 +11,16 @@ namespace DependencyModules.Tests.GeneratorTests; /// its own attribute generators through AttributeSourceGenerators(). Both of these pin /// behaviour a framework only finds out about in a consuming application, which is too late. /// -public class ExtensionSeamTests { - - private const string FrameworkAttribute = - """ +public class ExtensionSeamTests +{ + private const string FrameworkAttribute = """ namespace Test.Framework; [System.AttributeUsage(System.AttributeTargets.Class)] public class FrameworkModuleAttribute : System.Attribute; """; - private const string ModuleAndService = - """ + private const string ModuleAndService = """ using DependencyModules.Runtime; using DependencyModules.Runtime.Attributes; using Microsoft.Extensions.DependencyInjection; @@ -55,13 +53,19 @@ public static IServiceCollection Compose() => /// SetupRootGenerator was empty by default and easy to miss. /// [Fact] - public void FrameworkGenerator_EmitsTheModule_WithoutOverridingSetupRootGenerator() { + public void FrameworkGenerator_EmitsTheModule_WithoutOverridingSetupRootGenerator() + { var result = GeneratorTestHarness.Run( - new Dictionary { + new Dictionary + { ["Framework.cs"] = FrameworkAttribute, - ["App.cs"] = ModuleAndService + ["App.cs"] = ModuleAndService, }, - generators: new ISourceGenerator[] { new FrameworkShapedGenerator().AsSourceGenerator() }); + generators: new ISourceGenerator[] + { + new FrameworkShapedGenerator().AsSourceGenerator(), + } + ); result.AssertNoErrors(); Assert.Contains("IDependencyModule", result.SourceContaining("AppModule.Module")); @@ -71,15 +75,21 @@ public void FrameworkGenerator_EmitsTheModule_WithoutOverridingSetupRootGenerato /// A generator that only contributes providers opts out, and then nothing declares the module. /// [Fact] - public void FrameworkGenerator_OptingOut_EmitsNoModule() { + public void FrameworkGenerator_OptingOut_EmitsNoModule() + { var result = GeneratorTestHarness.Run( - new Dictionary { + new Dictionary + { ["Framework.cs"] = FrameworkAttribute, - ["App.cs"] = ModuleAndService + ["App.cs"] = ModuleAndService, }, - generators: new ISourceGenerator[] { new ProvidersOnlyGenerator().AsSourceGenerator() }); + generators: new ISourceGenerator[] { new ProvidersOnlyGenerator().AsSourceGenerator() } + ); - Assert.DoesNotContain(result.GeneratedSources.Keys, key => key.Contains("AppModule.Module")); + Assert.DoesNotContain( + result.GeneratedSources.Keys, + key => key.Contains("AppModule.Module") + ); } /// @@ -93,26 +103,32 @@ public void FrameworkGenerator_OptingOut_EmitsNoModule() { /// have to work together. /// [Fact] - public void StackedGenerators_OverAConsoleApplication_EmitOneApplicationModule() { + public void StackedGenerators_OverAConsoleApplication_EmitOneApplicationModule() + { var result = GeneratorTestHarness.Run( - new Dictionary { + new Dictionary + { ["Framework.cs"] = FrameworkAttribute, - ["Program.cs"] = - """ - System.Console.WriteLine("hello"); - """, - ["App.cs"] = ModuleAndService + ["Program.cs"] = """ + System.Console.WriteLine("hello"); + """, + ["App.cs"] = ModuleAndService, }, outputKind: OutputKind.ConsoleApplication, - generators: new ISourceGenerator[] { + generators: new ISourceGenerator[] + { new SourceGenerator.SourceGenerator().AsSourceGenerator(), - new FrameworkShapedGenerator().AsSourceGenerator() - }); + new FrameworkShapedGenerator().AsSourceGenerator(), + } + ); result.AssertNoErrors(); Assert.Empty(result.DuplicateHintNames); - Assert.Single(result.GeneratedSources.Keys, key => key.Contains("ApplicationModule.Module")); + Assert.Single( + result.GeneratedSources.Keys, + key => key.Contains("ApplicationModule.Module") + ); } /// @@ -121,9 +137,9 @@ public void StackedGenerators_OverAConsoleApplication_EmitOneApplicationModule() /// this package ships, and writing them from both declares every module twice. /// [Fact] - public void ThirdPartyGenerator_OnTheDefaultModuleAttribute_WritesNoModuleOfItsOwn() { - var source = - """ + public void ThirdPartyGenerator_OnTheDefaultModuleAttribute_WritesNoModuleOfItsOwn() + { + var source = """ using DependencyModules.Runtime; using DependencyModules.Runtime.Attributes; using Microsoft.Extensions.DependencyInjection; @@ -146,10 +162,12 @@ public static IServiceCollection Compose() => var result = GeneratorTestHarness.Run( new Dictionary { ["App.cs"] = source }, - generators: new ISourceGenerator[] { + generators: new ISourceGenerator[] + { new SourceGenerator.SourceGenerator().AsSourceGenerator(), - new ThirdPartyGenerator().AsSourceGenerator() - }); + new ThirdPartyGenerator().AsSourceGenerator(), + } + ); result.AssertNoErrors(); @@ -160,12 +178,13 @@ public static IServiceCollection Compose() => /// /// What a framework declares: its module attribute, and the generators that read its own. /// - private class FrameworkShapedGenerator : BaseSourceGenerator { - + private class FrameworkShapedGenerator : BaseSourceGenerator + { protected override ITypeDefinition[] ModuleAttributeTypes() => new[] { TypeDefinition.Get("Test.Framework", "FrameworkModuleAttribute") }; - protected override IEnumerable AttributeSourceGenerators() { + protected override IEnumerable AttributeSourceGenerators() + { yield return new global::DependencyModules.SourceGenerator.ServiceSourceGenerator(); } } @@ -174,19 +193,22 @@ protected override IEnumerable AttributeSource /// A generator taking the base class defaults, triggering on [DependencyModule]: the /// shape the extension guide documents. /// - private class ThirdPartyGenerator : BaseSourceGenerator { - - protected override IEnumerable AttributeSourceGenerators() { + private class ThirdPartyGenerator : BaseSourceGenerator + { + protected override IEnumerable AttributeSourceGenerators() + { yield break; } } - private class ProvidersOnlyGenerator : FrameworkShapedGenerator { - + private class ProvidersOnlyGenerator : FrameworkShapedGenerator + { protected override void SetupRootGenerator( IncrementalGeneratorInitializationContext context, IncrementalValueProvider> valuesProvider) { } + SourceGenerator.Impl.Models.DependencyModuleConfigurationModel Right + )>> valuesProvider + ) { } } } diff --git a/tests/DependencyModules.Tests/GeneratorTests/FileLoggerTests.cs b/tests/DependencyModules.Tests/GeneratorTests/FileLoggerTests.cs index 86b270e..c38011c 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/FileLoggerTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/FileLoggerTests.cs @@ -8,16 +8,22 @@ namespace DependencyModules.Tests.GeneratorTests; /// FileLogger backs the DependencyModules_LogOutputDirectory build property. It runs inside the /// compiler, so it must be inert unless explicitly switched on and must never fail a build. /// -public class FileLoggerTests : IDisposable { - private readonly string _outputFolder = - Path.Combine(Path.GetTempPath(), "DependencyModulesLoggerTests", Guid.NewGuid().ToString("n")); +public class FileLoggerTests : IDisposable +{ + private readonly string _outputFolder = Path.Combine( + Path.GetTempPath(), + "DependencyModulesLoggerTests", + Guid.NewGuid().ToString("n") + ); [Fact] - public void WithNoOutputFolder_WritesNothing() { + public void WithNoOutputFolder_WritesNothing() + { var before = Directory.GetCurrentDirectory(); var filesBefore = Directory.GetFiles(before); - using (var logger = new FileLogger(Configuration(logOutputFolder: ""), "test")) { + using (var logger = new FileLogger(Configuration(logOutputFolder: ""), "test")) + { logger.Info("a message"); logger.Error("a problem"); } @@ -30,8 +36,10 @@ public void WithNoOutputFolder_WritesNothing() { /// configured folder entirely. /// [Fact] - public void WithAnOutputFolder_WritesIntoThatFolder() { - using (var logger = new FileLogger(Configuration(_outputFolder), "generator")) { + public void WithAnOutputFolder_WritesIntoThatFolder() + { + using (var logger = new FileLogger(Configuration(_outputFolder), "generator")) + { logger.Info("a message"); } @@ -43,10 +51,12 @@ public void WithAnOutputFolder_WritesIntoThatFolder() { } [Fact] - public void WithAnOutputFolder_CreatesTheFolderIfMissing() { + public void WithAnOutputFolder_CreatesTheFolderIfMissing() + { Assert.False(Directory.Exists(_outputFolder)); - using (var logger = new FileLogger(Configuration(_outputFolder), "generator")) { + using (var logger = new FileLogger(Configuration(_outputFolder), "generator")) + { logger.Info("a message"); } @@ -54,8 +64,10 @@ public void WithAnOutputFolder_CreatesTheFolderIfMissing() { } [Fact] - public void RecordsLevelsAndMessages() { - using (var logger = new FileLogger(Configuration(_outputFolder), "generator")) { + public void RecordsLevelsAndMessages() + { + using (var logger = new FileLogger(Configuration(_outputFolder), "generator")) + { logger.Info("an info message"); logger.Error("an error message"); logger.Info("with data", "the data"); @@ -73,37 +85,44 @@ public void RecordsLevelsAndMessages() { /// Swallowing it produced a successful build with no registrations and no message at all. /// [Fact] - public void Wrap_WithoutAReporter_RethrowsSoTheFailureIsVisible() { - var exception = Assert.Throws( - () => FileLogger.Wrap( + public void Wrap_WithoutAReporter_RethrowsSoTheFailureIsVisible() + { + var exception = Assert.Throws(() => + FileLogger.Wrap( "generator", Configuration(_outputFolder), - _ => throw new InvalidOperationException("generator blew up"))); + _ => throw new InvalidOperationException("generator blew up") + ) + ); Assert.Equal("generator blew up", exception.Message); } [Fact] - public void Wrap_WithAReporter_HandsItTheExceptionInsteadOfPropagating() { + public void Wrap_WithAReporter_HandsItTheExceptionInsteadOfPropagating() + { Exception? reported = null; FileLogger.Wrap( "generator", Configuration(_outputFolder), _ => throw new InvalidOperationException("generator blew up"), - exception => reported = exception); + exception => reported = exception + ); Assert.NotNull(reported); Assert.Equal("generator blew up", reported!.Message); } [Fact] - public void Wrap_RecordsTheExceptionInTheLog() { + public void Wrap_RecordsTheExceptionInTheLog() + { FileLogger.Wrap( "generator", Configuration(_outputFolder), _ => throw new InvalidOperationException("generator blew up"), - _ => { }); + _ => { } + ); var content = File.ReadAllText(Directory.GetFiles(_outputFolder).Single()); @@ -112,25 +131,33 @@ public void Wrap_RecordsTheExceptionInTheLog() { } [Fact] - public void Wrap_RunsTheCallbackAndDisposesTheLogger() { + public void Wrap_RunsTheCallbackAndDisposesTheLogger() + { var ran = false; - FileLogger.Wrap("generator", Configuration(_outputFolder), logger => { - ran = true; - logger.Info("inside"); - }); + FileLogger.Wrap( + "generator", + Configuration(_outputFolder), + logger => + { + ran = true; + logger.Info("inside"); + } + ); Assert.True(ran); Assert.Contains("inside", File.ReadAllText(Directory.GetFiles(_outputFolder).Single())); } [Fact] - public void AnUnwritableOutputFolder_DoesNotThrow() { + public void AnUnwritableOutputFolder_DoesNotThrow() + { // A path whose parent is a file cannot be created; logging must still not fail the build. var file = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("n")); File.WriteAllText(file, "not a directory"); - try { + try + { var logger = new FileLogger(Configuration(Path.Combine(file, "nested")), "generator"); logger.Info("a message"); @@ -138,7 +165,8 @@ public void AnUnwritableOutputFolder_DoesNotThrow() { Assert.Null(exception); } - finally { + finally + { File.Delete(file); } } @@ -152,10 +180,13 @@ private static DependencyModuleConfigurationModel Configuration(string logOutput AutoGenerateEntry: true, LogOutputFolder: logOutputFolder, LogOutputLevel.Debug, - GenerateFactories: false); + GenerateFactories: false + ); - public void Dispose() { - if (Directory.Exists(_outputFolder)) { + public void Dispose() + { + if (Directory.Exists(_outputFolder)) + { Directory.Delete(_outputFolder, recursive: true); } } diff --git a/tests/DependencyModules.Tests/GeneratorTests/GenerateFactoriesInterceptionTests.cs b/tests/DependencyModules.Tests/GeneratorTests/GenerateFactoriesInterceptionTests.cs index 4d265ff..b68cc4b 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/GenerateFactoriesInterceptionTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/GenerateFactoriesInterceptionTests.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.Extensions.DependencyInjection; using DependencyModules.Tests.Infrastructure; +using Microsoft.Extensions.DependencyInjection; using Xunit; namespace DependencyModules.Tests.GeneratorTests; @@ -24,10 +24,9 @@ namespace DependencyModules.Tests.GeneratorTests; /// property's whole contract — it is meant to change how a service is constructed, not what is /// registered or what wraps it. /// -public class GenerateFactoriesInterceptionTests { - - private const string Interceptor = - """ +public class GenerateFactoriesInterceptionTests +{ + private const string Interceptor = """ public sealed class CountingInterceptor : IInterceptor { public TResult Intercept(InvocationContext context) => context.Proceed(); } @@ -39,7 +38,8 @@ public sealed class CountingInterceptor : IInterceptor { [Theory] [InlineData(false)] [InlineData(true)] - public void AnUnmarkedSibling_IsNotWrapped(bool generateFactories) { + public void AnUnmarkedSibling_IsNotWrapped(bool generateFactories) + { var resolved = Resolve( """ [SingletonService] [Intercept(typeof(CountingInterceptor))] @@ -48,7 +48,8 @@ public sealed class Loud : IGreeter { public string Greet() => "loud"; } [SingletonService] public sealed class Quiet : IGreeter { public string Greet() => "quiet"; } """, - generateFactories); + generateFactories + ); Assert.Equal(["Loud_Intercepted", "Quiet"], resolved); } @@ -60,7 +61,8 @@ public sealed class Quiet : IGreeter { public string Greet() => "quiet"; } [Theory] [InlineData(false)] [InlineData(true)] - public void TwoMarkedImplementations_EachGetTheirOwnWrapper(bool generateFactories) { + public void TwoMarkedImplementations_EachGetTheirOwnWrapper(bool generateFactories) + { var resolved = Resolve( """ [SingletonService] [Intercept(typeof(CountingInterceptor))] @@ -69,7 +71,8 @@ public sealed class Loud : IGreeter { public string Greet() => "loud"; } [SingletonService] [Intercept(typeof(CountingInterceptor))] public sealed class Quiet : IGreeter { public string Greet() => "quiet"; } """, - generateFactories); + generateFactories + ); Assert.Equal(["Loud_Intercepted", "Quiet_Intercepted"], resolved); } @@ -81,7 +84,8 @@ public sealed class Quiet : IGreeter { public string Greet() => "quiet"; } [Theory] [InlineData(false)] [InlineData(true)] - public void AKeyedSibling_IsNotWrapped(bool generateFactories) { + public void AKeyedSibling_IsNotWrapped(bool generateFactories) + { var generated = Build( """ [SingletonService(Key = "loud")] [Intercept(typeof(CountingInterceptor))] @@ -90,7 +94,8 @@ public sealed class Loud : IGreeter { public string Greet() => "loud"; } [SingletonService(Key = "quiet")] public sealed class Quiet : IGreeter { public string Greet() => "quiet"; } """, - generateFactories); + generateFactories + ); var provider = generated.BuildProvider(); var serviceType = generated.Type("IGreeter"); @@ -109,39 +114,46 @@ public sealed class Quiet : IGreeter { public string Greet() => "quiet"; } [Theory] [InlineData(false)] [InlineData(true)] - public void AConventionRegisteredSibling_IsNotWrapped(bool generateFactories) { + public void AConventionRegisteredSibling_IsNotWrapped(bool generateFactories) + { var generated = GeneratedAssembly.Create( $$""" - using DependencyModules.Runtime.Attributes; - using DependencyModules.Runtime.Conventions; - using DependencyModules.Runtime.Interception; + using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Conventions; + using DependencyModules.Runtime.Interception; - namespace TestNamespace; + namespace TestNamespace; - public interface IGreeter { string Greet(); } + public interface IGreeter { string Greet(); } - {{Interceptor}} + {{Interceptor}} - [Intercept(typeof(CountingInterceptor))] - public sealed class Loud : IGreeter { public string Greet() => "loud"; } + [Intercept(typeof(CountingInterceptor))] + public sealed class Loud : IGreeter { public string Greet() => "loud"; } - public sealed class Quiet : IGreeter { public string Greet() => "quiet"; } + public sealed class Quiet : IGreeter { public string Greet() => "quiet"; } - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().AsSingleton(); - } - } - """, - buildProperties: new Dictionary { - ["DependencyModules_GenerateFactories"] = generateFactories ? "true" : "false" - }); + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().AsSingleton(); + } + } + """, + buildProperties: new Dictionary + { + ["DependencyModules_GenerateFactories"] = generateFactories ? "true" : "false", + } + ); var provider = generated.BuildProvider(); - var resolved = ((System.Collections.IEnumerable)provider - .GetService(typeof(IEnumerable<>).MakeGenericType(generated.Type("IGreeter")))!) + var resolved = ( + (System.Collections.IEnumerable) + provider.GetService( + typeof(IEnumerable<>).MakeGenericType(generated.Type("IGreeter")) + )! + ) .Cast() .Select(greeter => greeter.GetType().Name) .OrderBy(name => name, System.StringComparer.Ordinal) @@ -150,12 +162,17 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { Assert.Equal(["Loud_Intercepted", "Quiet"], resolved); } - private static string[] Resolve(string body, bool generateFactories) { + private static string[] Resolve(string body, bool generateFactories) + { var generated = Build(body, generateFactories); var provider = generated.BuildProvider(); - return ((System.Collections.IEnumerable)provider - .GetService(typeof(IEnumerable<>).MakeGenericType(generated.Type("IGreeter")))!) + return ( + (System.Collections.IEnumerable) + provider.GetService( + typeof(IEnumerable<>).MakeGenericType(generated.Type("IGreeter")) + )! + ) .Cast() .Select(greeter => greeter.GetType().Name) .OrderBy(name => name, System.StringComparer.Ordinal) @@ -165,21 +182,23 @@ private static string[] Resolve(string body, bool generateFactories) { private static GeneratedAssembly Build(string body, bool generateFactories) => GeneratedAssembly.Create( $$""" - using DependencyModules.Runtime.Attributes; - using DependencyModules.Runtime.Interception; + using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Interception; - namespace TestNamespace; + namespace TestNamespace; - public interface IGreeter { string Greet(); } + public interface IGreeter { string Greet(); } - {{Interceptor}} + {{Interceptor}} - {{body}} + {{body}} - [DependencyModule] - public partial class TestModule; - """, - buildProperties: new Dictionary { - ["DependencyModules_GenerateFactories"] = generateFactories ? "true" : "false" - }); + [DependencyModule] + public partial class TestModule; + """, + buildProperties: new Dictionary + { + ["DependencyModules_GenerateFactories"] = generateFactories ? "true" : "false", + } + ); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/GeneratedBehaviourTests.cs b/tests/DependencyModules.Tests/GeneratorTests/GeneratedBehaviourTests.cs index 0ea87a3..bb164a5 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/GeneratedBehaviourTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/GeneratedBehaviourTests.cs @@ -12,11 +12,14 @@ namespace DependencyModules.Tests.GeneratorTests; /// These are the tests that would fail if the generator emitted well-formed code that registered /// the wrong thing. Asserting on the shape of generated text cannot catch that. /// -public class GeneratedBehaviourTests { - +public class GeneratedBehaviourTests +{ [Fact] - public void SingletonService_ResolvesTheImplementation() { - var generated = GeneratedAssembly.Create(Module("[SingletonService] public class Thing : IThing;")); + public void SingletonService_ResolvesTheImplementation() + { + var generated = GeneratedAssembly.Create( + Module("[SingletonService] public class Thing : IThing;") + ); var resolved = generated.ResolveRequired("IThing"); @@ -24,8 +27,11 @@ public void SingletonService_ResolvesTheImplementation() { } [Fact] - public void SingletonService_ReturnsTheSameInstanceEveryTime() { - var generated = GeneratedAssembly.Create(Module("[SingletonService] public class Thing : IThing;")); + public void SingletonService_ReturnsTheSameInstanceEveryTime() + { + var generated = GeneratedAssembly.Create( + Module("[SingletonService] public class Thing : IThing;") + ); var provider = generated.BuildProvider(); var serviceType = generated.Type("IThing"); @@ -33,8 +39,11 @@ public void SingletonService_ReturnsTheSameInstanceEveryTime() { } [Fact] - public void TransientService_ReturnsANewInstanceEveryTime() { - var generated = GeneratedAssembly.Create(Module("[TransientService] public class Thing : IThing;")); + public void TransientService_ReturnsANewInstanceEveryTime() + { + var generated = GeneratedAssembly.Create( + Module("[TransientService] public class Thing : IThing;") + ); var provider = generated.BuildProvider(); var serviceType = generated.Type("IThing"); @@ -42,8 +51,11 @@ public void TransientService_ReturnsANewInstanceEveryTime() { } [Fact] - public void ScopedService_IsSharedWithinAScopeAndDiffersAcrossScopes() { - var generated = GeneratedAssembly.Create(Module("[ScopedService] public class Thing : IThing;")); + public void ScopedService_IsSharedWithinAScopeAndDiffersAcrossScopes() + { + var generated = GeneratedAssembly.Create( + Module("[ScopedService] public class Thing : IThing;") + ); var provider = generated.BuildProvider(); var serviceType = generated.Type("IThing"); @@ -62,19 +74,29 @@ public void ScopedService_IsSharedWithinAScopeAndDiffersAcrossScopes() { [InlineData("SingletonService", ServiceLifetime.Singleton)] [InlineData("ScopedService", ServiceLifetime.Scoped)] [InlineData("TransientService", ServiceLifetime.Transient)] - public void ServiceAttribute_RegistersTheMatchingLifetime(string attribute, ServiceLifetime expected) { - var generated = GeneratedAssembly.Create(Module($"[{attribute}] public class Thing : IThing;")); + public void ServiceAttribute_RegistersTheMatchingLifetime( + string attribute, + ServiceLifetime expected + ) + { + var generated = GeneratedAssembly.Create( + Module($"[{attribute}] public class Thing : IThing;") + ); Assert.Equal(expected, generated.Descriptor("IThing").Lifetime); } [Fact] - public void AsProperty_ResolvesUnderTheRequestedServiceTypeOnly() { - var generated = GeneratedAssembly.Create(Module( - """ - public interface IOther; - [SingletonService(As = typeof(IOther))] public class Thing : IThing, IOther; - """)); + public void AsProperty_ResolvesUnderTheRequestedServiceTypeOnly() + { + var generated = GeneratedAssembly.Create( + Module( + """ + public interface IOther; + [SingletonService(As = typeof(IOther))] public class Thing : IThing, IOther; + """ + ) + ); var provider = generated.BuildProvider(); @@ -83,9 +105,11 @@ public interface IOther; } [Fact] - public void KeyedService_ResolvesOnlyThroughItsKey() { - var generated = GeneratedAssembly.Create(Module( - """[SingletonService(Key = "the-key")] public class Thing : IThing;""")); + public void KeyedService_ResolvesOnlyThroughItsKey() + { + var generated = GeneratedAssembly.Create( + Module("""[SingletonService(Key = "the-key")] public class Thing : IThing;""") + ); var provider = generated.BuildProvider(); var serviceType = generated.Type("IThing"); @@ -95,18 +119,28 @@ public void KeyedService_ResolvesOnlyThroughItsKey() { } [Fact] - public void KeyedServices_WithDifferentKeysResolveDifferentImplementations() { - var generated = GeneratedAssembly.Create(Module( - """ - [SingletonService(Key = "first")] public class FirstThing : IThing; - [SingletonService(Key = "second")] public class SecondThing : IThing; - """)); + public void KeyedServices_WithDifferentKeysResolveDifferentImplementations() + { + var generated = GeneratedAssembly.Create( + Module( + """ + [SingletonService(Key = "first")] public class FirstThing : IThing; + [SingletonService(Key = "second")] public class SecondThing : IThing; + """ + ) + ); var provider = generated.BuildProvider(); var serviceType = generated.Type("IThing"); - Assert.Equal(generated.Type("FirstThing"), provider.GetKeyedService(serviceType, "first")!.GetType()); - Assert.Equal(generated.Type("SecondThing"), provider.GetKeyedService(serviceType, "second")!.GetType()); + Assert.Equal( + generated.Type("FirstThing"), + provider.GetKeyedService(serviceType, "first")!.GetType() + ); + Assert.Equal( + generated.Type("SecondThing"), + provider.GetKeyedService(serviceType, "second")!.GetType() + ); } /// @@ -114,14 +148,18 @@ public void KeyedServices_WithDifferentKeysResolveDifferentImplementations() { /// and through every interface it implements. /// [Fact] - public void CrossWireService_SharesOneInstanceAcrossAllItsInterfaces() { - var generated = GeneratedAssembly.Create(Module( - """ - public interface IOther; - [CrossWireService(Lifetime = ServiceLifetime.Singleton)] - public class Thing : IThing, IOther; - """, - extraUsings: "using Microsoft.Extensions.DependencyInjection;")); + public void CrossWireService_SharesOneInstanceAcrossAllItsInterfaces() + { + var generated = GeneratedAssembly.Create( + Module( + """ + public interface IOther; + [CrossWireService(Lifetime = ServiceLifetime.Singleton)] + public class Thing : IThing, IOther; + """, + extraUsings: "using Microsoft.Extensions.DependencyInjection;" + ) + ); var provider = generated.BuildProvider(); @@ -135,18 +173,24 @@ public class Thing : IThing, IOther; } [Fact] - public void TryRegistration_DoesNotReplaceAnExistingRegistration() { - var generated = GeneratedAssembly.Create(Module( - "[SingletonService(Using = RegistrationType.Try)] public class Thing : IThing;")); + public void TryRegistration_DoesNotReplaceAnExistingRegistration() + { + var generated = GeneratedAssembly.Create( + Module("[SingletonService(Using = RegistrationType.Try)] public class Thing : IThing;") + ); Assert.Equal(ServiceLifetime.Singleton, generated.Descriptor("IThing").Lifetime); Assert.Equal(generated.Type("Thing"), generated.ResolveRequired("IThing").GetType()); } [Fact] - public void ReplaceRegistration_LeavesASingleRegistration() { - var generated = GeneratedAssembly.Create(Module( - "[SingletonService(Using = RegistrationType.Replace)] public class Thing : IThing;")); + public void ReplaceRegistration_LeavesASingleRegistration() + { + var generated = GeneratedAssembly.Create( + Module( + "[SingletonService(Using = RegistrationType.Replace)] public class Thing : IThing;" + ) + ); Assert.Single(generated.Descriptors("IThing")); } @@ -159,43 +203,55 @@ public void ReplaceRegistration_LeavesASingleRegistration() { /// won. Renaming the class fixed it, and nothing said so. /// [Fact] - public void ReplaceRegistration_WinsEvenWhenItsTypeNameSortsFirst() { - var generated = GeneratedAssembly.Create(Module( - """ - [SingletonService(Using = RegistrationType.Replace)] public class AaaThing : IThing; - [SingletonService] public class ZzzThing : IThing; - """)); + public void ReplaceRegistration_WinsEvenWhenItsTypeNameSortsFirst() + { + var generated = GeneratedAssembly.Create( + Module( + """ + [SingletonService(Using = RegistrationType.Replace)] public class AaaThing : IThing; + [SingletonService] public class ZzzThing : IThing; + """ + ) + ); Assert.Single(generated.Descriptors("IThing")); Assert.Equal(generated.Type("AaaThing"), generated.ResolveRequired("IThing").GetType()); } [Fact] - public void TryRegistration_DeclinesEvenWhenItsTypeNameSortsFirst() { - var generated = GeneratedAssembly.Create(Module( - """ - [SingletonService(Using = RegistrationType.Try)] public class AaaThing : IThing; - [SingletonService] public class ZzzThing : IThing; - """)); + public void TryRegistration_DeclinesEvenWhenItsTypeNameSortsFirst() + { + var generated = GeneratedAssembly.Create( + Module( + """ + [SingletonService(Using = RegistrationType.Try)] public class AaaThing : IThing; + [SingletonService] public class ZzzThing : IThing; + """ + ) + ); Assert.Single(generated.Descriptors("IThing")); Assert.Equal(generated.Type("ZzzThing"), generated.ResolveRequired("IThing").GetType()); } [Fact] - public void ConstructorDependencies_AreInjectedFromTheContainer() { - var generated = GeneratedAssembly.Create(Module( - """ - public interface IDependency; + public void ConstructorDependencies_AreInjectedFromTheContainer() + { + var generated = GeneratedAssembly.Create( + Module( + """ + public interface IDependency; - [SingletonService] public class Dependency : IDependency; + [SingletonService] public class Dependency : IDependency; - [SingletonService] - public class Thing : IThing { - public IDependency Injected { get; } - public Thing(IDependency dependency) => Injected = dependency; - } - """)); + [SingletonService] + public class Thing : IThing { + public IDependency Injected { get; } + public Thing(IDependency dependency) => Injected = dependency; + } + """ + ) + ); var provider = generated.BuildProvider(); var thing = provider.GetService(generated.Type("IThing"))!; @@ -207,16 +263,20 @@ public class Thing : IThing { } [Fact] - public void StaticFactory_IsInvokedToCreateTheService() { - var generated = GeneratedAssembly.Create(Module( - """ - public class Thing : IThing { - public string Origin { get; private set; } = "constructor"; + public void StaticFactory_IsInvokedToCreateTheService() + { + var generated = GeneratedAssembly.Create( + Module( + """ + public class Thing : IThing { + public string Origin { get; private set; } = "constructor"; - [SingletonService] - public static IThing Create() => new Thing { Origin = "factory" }; - } - """)); + [SingletonService] + public static IThing Create() => new Thing { Origin = "factory" }; + } + """ + ) + ); var thing = generated.ResolveRequired("IThing"); @@ -224,31 +284,37 @@ public class Thing : IThing { } [Fact] - public void StaticFactory_ReceivesItsDependenciesFromTheContainer() { - var generated = GeneratedAssembly.Create(Module( - """ - public interface IDependency; + public void StaticFactory_ReceivesItsDependenciesFromTheContainer() + { + var generated = GeneratedAssembly.Create( + Module( + """ + public interface IDependency; - [SingletonService] public class Dependency : IDependency; + [SingletonService] public class Dependency : IDependency; - public class Thing : IThing { - public IDependency? Injected { get; private set; } + public class Thing : IThing { + public IDependency? Injected { get; private set; } - [SingletonService] - public static IThing Create(IDependency dependency) => new Thing { Injected = dependency }; - } - """)); + [SingletonService] + public static IThing Create(IDependency dependency) => new Thing { Injected = dependency }; + } + """ + ) + ); var provider = generated.BuildProvider(); var thing = provider.GetService(generated.Type("IThing"))!; Assert.Same( provider.GetService(generated.Type("IDependency")), - thing.GetType().GetProperty("Injected")!.GetValue(thing)); + thing.GetType().GetProperty("Injected")!.GetValue(thing) + ); } [Fact] - public void OpenGenericService_ResolvesForAnyTypeArgument() { + public void OpenGenericService_ResolvesForAnyTypeArgument() + { var generated = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -262,7 +328,8 @@ public class GenericThing : IGeneric; [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = generated.BuildProvider(); var closed = generated.Type("IGeneric`1").MakeGenericType(typeof(string)); @@ -271,7 +338,8 @@ public partial class TestModule; } [Fact] - public void ClosedGenericService_ResolvesForItsOwnArgumentOnly() { + public void ClosedGenericService_ResolvesForItsOwnArgumentOnly() + { var generated = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -285,7 +353,8 @@ public class StringGeneric : IGeneric; [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = generated.BuildProvider(); var generic = generated.Type("IGeneric`1"); @@ -295,27 +364,35 @@ public partial class TestModule; } [Fact] - public void NestedService_ResolvesThroughItsContainingType() { - var generated = GeneratedAssembly.Create(Module( - """ - public static class Outer { - [SingletonService] - public class Inner : IThing; - } - """)); + public void NestedService_ResolvesThroughItsContainingType() + { + var generated = GeneratedAssembly.Create( + Module( + """ + public static class Outer { + [SingletonService] + public class Inner : IThing; + } + """ + ) + ); Assert.Equal("Inner", generated.ResolveRequired("IThing").GetType().Name); } [Fact] - public void RecordService_Resolves() { - var generated = GeneratedAssembly.Create(Module("[SingletonService] public record ThingRecord : IThing;")); + public void RecordService_Resolves() + { + var generated = GeneratedAssembly.Create( + Module("[SingletonService] public record ThingRecord : IThing;") + ); Assert.Equal(generated.Type("ThingRecord"), generated.ResolveRequired("IThing").GetType()); } [Fact] - public void ModuleConfiguration_RunsAlongsideGeneratedRegistrations() { + public void ModuleConfiguration_RunsAlongsideGeneratedRegistrations() + { var generated = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -337,7 +414,8 @@ public void ConfigureServices(IServiceCollection services) { services.AddSingleton(); } } - """); + """ + ); var provider = generated.BuildProvider(); @@ -346,7 +424,8 @@ public void ConfigureServices(IServiceCollection services) { } [Fact] - public void OnlyRealm_RegistersOnlyServicesInThatRealm() { + public void OnlyRealm_RegistersOnlyServicesInThatRealm() + { var generated = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -365,7 +444,8 @@ public class InRealm : IInRealm; [SingletonService] public class OutsideRealm : IOutsideRealm; """, - "RealmModule"); + "RealmModule" + ); var provider = generated.BuildProvider(); @@ -374,7 +454,8 @@ public class OutsideRealm : IOutsideRealm; } [Fact] - public void ComposedModule_AppliesTheModulesItReferences() { + public void ComposedModule_AppliesTheModulesItReferences() + { var generated = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -393,13 +474,15 @@ public class FromBase : IFromBase; [BaseModule] public partial class ComposedModule; """, - "ComposedModule"); + "ComposedModule" + ); Assert.NotNull(generated.BuildProvider().GetService(generated.Type("IFromBase"))); } [Fact] - public void ServiceWithNoInterface_ResolvesAsItself() { + public void ServiceWithNoInterface_ResolvesAsItself() + { var generated = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -411,23 +494,24 @@ public class Standalone; [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.NotNull(generated.BuildProvider().GetService(generated.Type("Standalone"))); } private static string Module(string body, string extraUsings = "") => $$""" - using DependencyModules.Runtime.Attributes; - {{extraUsings}} + using DependencyModules.Runtime.Attributes; + {{extraUsings}} - namespace TestNamespace; + namespace TestNamespace; - public interface IThing; + public interface IThing; - {{body}} + {{body}} - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; } diff --git a/tests/DependencyModules.Tests/GeneratorTests/GeneratedCodeRobustnessTests.cs b/tests/DependencyModules.Tests/GeneratorTests/GeneratedCodeRobustnessTests.cs index d3e3398..988af8c 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/GeneratedCodeRobustnessTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/GeneratedCodeRobustnessTests.cs @@ -9,8 +9,8 @@ namespace DependencyModules.Tests.GeneratorTests; /// configured. The harness compiles without implicit usings and without any project-level using /// directives, which is the strictest environment a consumer can present. /// -public class GeneratedCodeRobustnessTests { - +public class GeneratedCodeRobustnessTests +{ /// /// Regression test: the generated module attribute used to be emitted as a bare /// [AttributeUsage(AttributeTargets...)], which only compiled because the consuming @@ -18,7 +18,8 @@ public class GeneratedCodeRobustnessTests { /// failed with CS0246/CS0103 on every generated module. /// [Fact] - public void GeneratedCode_CompilesWithoutImplicitUsings() { + public void GeneratedCode_CompilesWithoutImplicitUsings() + { var result = GeneratorTestHarness.Run( """ namespace TestNamespace; @@ -30,13 +31,15 @@ public class Thing : IThing; [DependencyModules.Runtime.Attributes.DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); } [Fact] - public void GeneratedModuleAttribute_UsesFullyQualifiedAttributeUsage() { + public void GeneratedModuleAttribute_UsesFullyQualifiedAttributeUsage() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -45,7 +48,8 @@ namespace TestNamespace; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); var generated = result.SourceContaining(".Module.g.cs"); @@ -58,7 +62,8 @@ public partial class TestModule; /// A consumer that treats warnings as errors should not be broken by generated code. /// [Fact] - public void GeneratedCode_ProducesNoWarnings() { + public void GeneratedCode_ProducesNoWarnings() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -72,23 +77,36 @@ public class Thing : IThing; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); - var generatedTreePaths = result.Compilation.SyntaxTrees - .Where(tree => tree.FilePath.EndsWith(".g.cs", StringComparison.Ordinal)) + var generatedTreePaths = result + .Compilation.SyntaxTrees.Where(tree => + tree.FilePath.EndsWith(".g.cs", StringComparison.Ordinal) + ) .Select(tree => tree.FilePath) .ToHashSet(StringComparer.Ordinal); - var warnings = result.CompilationDiagnostics - .Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Warning) - .Where(diagnostic => generatedTreePaths.Contains(diagnostic.Location.SourceTree?.FilePath ?? "")) + var warnings = result + .CompilationDiagnostics.Where(diagnostic => + diagnostic.Severity == DiagnosticSeverity.Warning + ) + .Where(diagnostic => + generatedTreePaths.Contains(diagnostic.Location.SourceTree?.FilePath ?? "") + ) .ToArray(); - Assert.True(warnings.Length == 0, - "Generated code produced warnings:" + Environment.NewLine + - string.Join(Environment.NewLine, warnings.Select(w => $" {w.Id} {w.GetMessage()}"))); + Assert.True( + warnings.Length == 0, + "Generated code produced warnings:" + + Environment.NewLine + + string.Join( + Environment.NewLine, + warnings.Select(w => $" {w.Id} {w.GetMessage()}") + ) + ); } /// @@ -105,58 +123,77 @@ public partial class TestModule; [Theory] [InlineData("attribute", "[SingletonService]")] [InlineData("convention", "")] - public void GeneratedCode_ProducesNoWarnings_ForANullableTypeArgument(string _, string attribute) { + public void GeneratedCode_ProducesNoWarnings_ForANullableTypeArgument( + string _, + string attribute + ) + { var result = GeneratorTestHarness.Run( $$""" - using DependencyModules.Runtime.Attributes; - using DependencyModules.Runtime.Conventions; + using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Conventions; - namespace TestNamespace; + namespace TestNamespace; - public class Book; + public class Book; - public interface IHandler { - TResult Handle(TQuery query); - } + public interface IHandler { + TResult Handle(TQuery query); + } - public record GetBook(string Isbn); + public record GetBook(string Isbn); - {{attribute}} - public class GetBookHandler : IHandler { - public Book? Handle(GetBook query) => null; - } + {{attribute}} + public class GetBookHandler : IHandler { + public Book? Handle(GetBook query) => null; + } - [DependencyModule] - public partial class TestModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll(typeof(IHandler<,>)).AsScoped(); - } - } - """); + [DependencyModule] + public partial class TestModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll(typeof(IHandler<,>)).AsScoped(); + } + } + """ + ); result.AssertNoErrors(); AssertNoWarningsFromGeneratedCode(result); } - private static void AssertNoWarningsFromGeneratedCode(GeneratorResult result) { - var generatedTreePaths = result.Compilation.SyntaxTrees - .Where(tree => tree.FilePath.EndsWith(".g.cs", StringComparison.Ordinal)) + private static void AssertNoWarningsFromGeneratedCode(GeneratorResult result) + { + var generatedTreePaths = result + .Compilation.SyntaxTrees.Where(tree => + tree.FilePath.EndsWith(".g.cs", StringComparison.Ordinal) + ) .Select(tree => tree.FilePath) .ToHashSet(StringComparer.Ordinal); - var warnings = result.CompilationDiagnostics - .Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Warning) - .Where(diagnostic => generatedTreePaths.Contains(diagnostic.Location.SourceTree?.FilePath ?? "")) + var warnings = result + .CompilationDiagnostics.Where(diagnostic => + diagnostic.Severity == DiagnosticSeverity.Warning + ) + .Where(diagnostic => + generatedTreePaths.Contains(diagnostic.Location.SourceTree?.FilePath ?? "") + ) .ToArray(); - Assert.True(warnings.Length == 0, - "Generated code produced warnings:" + Environment.NewLine + - string.Join(Environment.NewLine, warnings.Select(w => $" {w.Id} {w.GetMessage()}"))); + Assert.True( + warnings.Length == 0, + "Generated code produced warnings:" + + Environment.NewLine + + string.Join( + Environment.NewLine, + warnings.Select(w => $" {w.Id} {w.GetMessage()}") + ) + ); } [Fact] - public void GeneratedCode_QualifiesReferencesToTheRuntime() { + public void GeneratedCode_QualifiesReferencesToTheRuntime() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -170,17 +207,20 @@ public class Thing : IThing; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); Assert.Contains( "global::DependencyModules.Runtime.Helpers.DependencyRegistry", - result.SourceContaining(".Module.g.cs")); + result.SourceContaining(".Module.g.cs") + ); } [Fact] - public void GeneratorRun_ReportsNoDiagnosticsForValidInput() { + public void GeneratorRun_ReportsNoDiagnosticsForValidInput() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -194,7 +234,8 @@ public class Thing : IThing; [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.Empty(result.GeneratorDiagnostics); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/GeneratorFeatureTests.cs b/tests/DependencyModules.Tests/GeneratorTests/GeneratorFeatureTests.cs index 359d590..3d3eab9 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/GeneratorFeatureTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/GeneratorFeatureTests.cs @@ -8,10 +8,11 @@ namespace DependencyModules.Tests.GeneratorTests; /// handlers, factories, and module composition. Each asserts that the generator produces output /// that compiles, which is the property most easily broken by a change to the writers. /// -public class GeneratorFeatureTests { - +public class GeneratorFeatureTests +{ [Fact] - public void OnlyRealm_RegistersOnlyServicesMarkedForThatRealm() { + public void OnlyRealm_RegistersOnlyServicesMarkedForThatRealm() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -29,7 +30,8 @@ public class InRealm : IInRealm; [SingletonService] public class OutsideRealm : IOutsideRealm; - """); + """ + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -39,7 +41,8 @@ public class OutsideRealm : IOutsideRealm; } [Fact] - public void ModuleWithoutOnlyRealm_RegistersUnmarkedServices() { + public void ModuleWithoutOnlyRealm_RegistersUnmarkedServices() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -53,14 +56,16 @@ public class Thing : IThing; [DependencyModule] public partial class OpenModule; - """); + """ + ); result.AssertNoErrors(); Assert.Contains("Thing", result.SourceContaining("Dependencies")); } [Fact] - public void GenerateUseMethod_EmitsTheNamedMethod() { + public void GenerateUseMethod_EmitsTheNamedMethod() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -74,14 +79,16 @@ public partial class UseMethodModule(string name) { [SingletonService(Realm = typeof(UseMethodModule))] public class RealmService; - """); + """ + ); result.AssertNoErrors(); Assert.Contains("UseTestModule", result.SourceContaining(".Module.g.cs")); } [Fact] - public void GenerateAttributeFalse_SuppressesTheModuleAttribute() { + public void GenerateAttributeFalse_SuppressesTheModuleAttribute() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -95,14 +102,19 @@ public class Thing : IThing; [DependencyModule(GenerateAttribute = false)] public partial class NoAttributeModule; - """); + """ + ); result.AssertNoErrors(); - Assert.DoesNotContain("class NoAttributeModuleAttribute", result.SourceContaining(".Module.g.cs")); + Assert.DoesNotContain( + "class NoAttributeModuleAttribute", + result.SourceContaining(".Module.g.cs") + ); } [Fact] - public void ModuleImplementingAFeature_EmitsAFeatureApplicator() { + public void ModuleImplementingAFeature_EmitsAFeatureApplicator() + { var result = GeneratorTestHarness.Run( """ using System.Collections.Generic; @@ -121,7 +133,8 @@ public partial class FeatureHandlerModule : IDependencyModuleFeature feature) { } } - """); + """ + ); result.AssertNoErrors(); var generated = result.SourceContaining(".Module.g.cs"); @@ -131,7 +144,8 @@ public void HandleFeature(IServiceCollection collection, IEnumerable [Fact] - public void ServiceImplementingSeveralInterfaces_RegistersTheFirstOne() { + public void ServiceImplementingSeveralInterfaces_RegistersTheFirstOne() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -304,7 +330,8 @@ public class Both : IFirst, ISecond; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -314,7 +341,8 @@ public partial class TestModule; } [Fact] - public void CrossWireService_RegistersTheImplementationAndItsInterfaces() { + public void CrossWireService_RegistersTheImplementationAndItsInterfaces() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -329,7 +357,8 @@ public class Both : IFirst, ISecond; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -340,7 +369,8 @@ public partial class TestModule; } [Fact] - public void ModuleLevelRegistrationType_AppliesToItsServices() { + public void ModuleLevelRegistrationType_AppliesToItsServices() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -354,14 +384,16 @@ public class Thing : IThing; [DependencyModule(Using = RegistrationType.Try)] public partial class TryModule; - """); + """ + ); result.AssertNoErrors(); Assert.Contains("TryAdd", result.SourceContaining("Dependencies")); } [Fact] - public void SeveralModulesInOneCompilation_EachGetTheirOwnFiles() { + public void SeveralModulesInOneCompilation_EachGetTheirOwnFiles() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -378,7 +410,8 @@ public partial class FirstModule; [DependencyModule] public partial class SecondModule; - """); + """ + ); result.AssertNoErrors(); @@ -387,7 +420,8 @@ public partial class SecondModule; } [Fact] - public void ModuleInANestedNamespace_GeneratesIntoThatNamespace() { + public void ModuleInANestedNamespace_GeneratesIntoThatNamespace() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -401,14 +435,16 @@ public class Thing : IThing; [DependencyModule] public partial class NestedModule; - """); + """ + ); result.AssertNoErrors(); Assert.Contains("namespace Outer.Inner", result.SourceContaining(".Module.g.cs")); } [Fact] - public void ServiceWithNoInterfaces_RegistersItselfAsTheServiceType() { + public void ServiceWithNoInterfaces_RegistersItselfAsTheServiceType() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -420,14 +456,16 @@ public class Standalone; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); Assert.Contains("Standalone", result.SourceContaining("Dependencies")); } [Fact] - public void ModuleThatOverridesEquals_KeepsTheDeveloperImplementation() { + public void ModuleThatOverridesEquals_KeepsTheDeveloperImplementation() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -447,14 +485,19 @@ public partial class CustomEqualsModule(string key) { public override int GetHashCode() => Key.GetHashCode(); } - """); + """ + ); result.AssertNoErrors(); - Assert.DoesNotContain("public override bool Equals", result.SourceContaining(".Module.g.cs")); + Assert.DoesNotContain( + "public override bool Equals", + result.SourceContaining(".Module.g.cs") + ); } [Fact] - public void ScopedAndTransientFactories_AreSupported() { + public void ScopedAndTransientFactories_AreSupported() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -474,7 +517,8 @@ public class Factories { [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); diff --git a/tests/DependencyModules.Tests/GeneratorTests/IncrementalGenerationTests.cs b/tests/DependencyModules.Tests/GeneratorTests/IncrementalGenerationTests.cs index 9244438..3f14578 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/IncrementalGenerationTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/IncrementalGenerationTests.cs @@ -9,77 +9,95 @@ namespace DependencyModules.Tests.GeneratorTests; /// and a real change serves stale output. These tests pin that behaviour from the outside, which /// is more durable than asserting on the comparers directly. /// -public class IncrementalGenerationTests { - +public class IncrementalGenerationTests +{ [Fact] - public void RerunningOnUnchangedSource_ReusesCachedOutput() { + public void RerunningOnUnchangedSource_ReusesCachedOutput() + { var result = GeneratorTestHarness.RunIncremental(Sources(Service), Sources(Service)); - Assert.True(result.AllOutputsCached, - "Re-running on identical source recomputed output: " + - string.Join(", ", result.OutputReasons)); + Assert.True( + result.AllOutputsCached, + "Re-running on identical source recomputed output: " + + string.Join(", ", result.OutputReasons) + ); } [Fact] - public void EditingAnUnrelatedMethodBody_ReusesCachedOutput() { + public void EditingAnUnrelatedMethodBody_ReusesCachedOutput() + { var before = Sources( - Service + - """ + Service + + """ - public class Unrelated { - public int Compute() => 1; - } - """); + public class Unrelated { + public int Compute() => 1; + } + """ + ); var after = Sources( - Service + - """ + Service + + """ - public class Unrelated { - public int Compute() => 2; - } - """); + public class Unrelated { + public int Compute() => 2; + } + """ + ); var result = GeneratorTestHarness.RunIncremental(before, after); Assert.Equal(result.FirstRun.Keys.OrderBy(k => k), result.SecondRun.Keys.OrderBy(k => k)); - Assert.True(result.AllOutputsCached, - "Editing an unrelated method body regenerated output: " + - string.Join(", ", result.OutputReasons)); + Assert.True( + result.AllOutputsCached, + "Editing an unrelated method body regenerated output: " + + string.Join(", ", result.OutputReasons) + ); } [Fact] - public void AddingAComment_ReusesCachedOutput() { + public void AddingAComment_ReusesCachedOutput() + { var result = GeneratorTestHarness.RunIncremental( Sources(Service), - Sources("// a new comment\n" + Service)); + Sources("// a new comment\n" + Service) + ); - Assert.True(result.AllOutputsCached, - "Adding a comment regenerated output: " + string.Join(", ", result.OutputReasons)); + Assert.True( + result.AllOutputsCached, + "Adding a comment regenerated output: " + string.Join(", ", result.OutputReasons) + ); } [Fact] - public void AddingAService_RegeneratesAndIncludesIt() { + public void AddingAService_RegeneratesAndIncludesIt() + { var after = Sources( - Service + - """ + Service + + """ - public interface ISecond; + public interface ISecond; - [SingletonService] - public class SecondThing : ISecond; - """); + [SingletonService] + public class SecondThing : ISecond; + """ + ); var result = GeneratorTestHarness.RunIncremental(Sources(Service), after); var dependencies = result.SecondRun.Single(pair => pair.Key.Contains("Dependencies")).Value; Assert.Contains("SecondThing", dependencies); - Assert.DoesNotContain("SecondThing", result.FirstRun.Single(pair => pair.Key.Contains("Dependencies")).Value); + Assert.DoesNotContain( + "SecondThing", + result.FirstRun.Single(pair => pair.Key.Contains("Dependencies")).Value + ); } [Fact] - public void ChangingAServiceLifetime_RegeneratesWithTheNewLifetime() { + public void ChangingAServiceLifetime_RegeneratesWithTheNewLifetime() + { var before = Sources("[SingletonService]\npublic class Thing : IThing;"); var after = Sources("[ScopedService]\npublic class Thing : IThing;"); @@ -94,16 +112,18 @@ public void ChangingAServiceLifetime_RegeneratesWithTheNewLifetime() { } [Fact] - public void RemovingAService_RegeneratesWithoutIt() { + public void RemovingAService_RegeneratesWithoutIt() + { var before = Sources( - Service + - """ + Service + + """ - public interface ISecond; + public interface ISecond; - [SingletonService] - public class SecondThing : ISecond; - """); + [SingletonService] + public class SecondThing : ISecond; + """ + ); var result = GeneratorTestHarness.RunIncremental(before, Sources(Service)); @@ -112,26 +132,25 @@ public class SecondThing : ISecond; Assert.DoesNotContain("SecondThing", second); } - private const string Service = - """ + private const string Service = """ [SingletonService] public class Thing : IThing; """; private static Dictionary Sources(string body) => - new() { - ["Test.cs"] = - $$""" - using DependencyModules.Runtime.Attributes; + new() + { + ["Test.cs"] = $$""" + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - public interface IThing; + public interface IThing; - {{body}} + {{body}} - [DependencyModule] - public partial class TestModule; - """ + [DependencyModule] + public partial class TestModule; + """, }; } diff --git a/tests/DependencyModules.Tests/GeneratorTests/InterceptAttributeSpellingTests.cs b/tests/DependencyModules.Tests/GeneratorTests/InterceptAttributeSpellingTests.cs index d4c54be..8511511 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/InterceptAttributeSpellingTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/InterceptAttributeSpellingTests.cs @@ -17,17 +17,24 @@ namespace DependencyModules.Tests.GeneratorTests; /// Interceptors are audit, authorisation, retry and metrics, which is the worst set of things to /// silently not run. /// -public class InterceptAttributeSpellingTests { - +public class InterceptAttributeSpellingTests +{ [Theory] [InlineData("[Intercept(typeof(LoggingInterceptor))]")] [InlineData("[InterceptAttribute(typeof(LoggingInterceptor))]")] [InlineData("[DependencyModules.Runtime.Attributes.Intercept(typeof(LoggingInterceptor))]")] - [InlineData("[DependencyModules.Runtime.Attributes.InterceptAttribute(typeof(LoggingInterceptor))]")] - [InlineData("[global::DependencyModules.Runtime.Attributes.Intercept(typeof(LoggingInterceptor))]")] - [InlineData("[global::DependencyModules.Runtime.Attributes.InterceptAttribute(typeof(LoggingInterceptor))]")] + [InlineData( + "[DependencyModules.Runtime.Attributes.InterceptAttribute(typeof(LoggingInterceptor))]" + )] + [InlineData( + "[global::DependencyModules.Runtime.Attributes.Intercept(typeof(LoggingInterceptor))]" + )] + [InlineData( + "[global::DependencyModules.Runtime.Attributes.InterceptAttribute(typeof(LoggingInterceptor))]" + )] [InlineData("[Wrap(typeof(LoggingInterceptor))]")] - public void EverySpelling_GeneratesTheWrapper(string attribute) { + public void EverySpelling_GeneratesTheWrapper(string attribute) + { var result = Run(attribute).AssertNoErrors(); Assert.Contains("Thing_Intercepted", string.Join(", ", result.GeneratedSources.Keys)); @@ -36,9 +43,12 @@ public void EverySpelling_GeneratesTheWrapper(string attribute) { [Theory] [InlineData("[Intercept(typeof(LoggingInterceptor))]")] [InlineData("[DependencyModules.Runtime.Attributes.Intercept(typeof(LoggingInterceptor))]")] - [InlineData("[global::DependencyModules.Runtime.Attributes.InterceptAttribute(typeof(LoggingInterceptor))]")] + [InlineData( + "[global::DependencyModules.Runtime.Attributes.InterceptAttribute(typeof(LoggingInterceptor))]" + )] [InlineData("[Wrap(typeof(LoggingInterceptor))]")] - public void EverySpelling_AppliesTheInterception(string attribute) { + public void EverySpelling_AppliesTheInterception(string attribute) + { var interceptors = Run(attribute).SourceContaining("Interceptors"); Assert.Contains("Thing_Intercepted", interceptors); @@ -47,28 +57,29 @@ public void EverySpelling_AppliesTheInterception(string attribute) { private static GeneratorResult Run(string attribute) => GeneratorTestHarness.Run( $$""" - using DependencyModules.Runtime.Attributes; - using DependencyModules.Runtime.Interception; - using Wrap = DependencyModules.Runtime.Attributes.InterceptAttribute; + using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Interception; + using Wrap = DependencyModules.Runtime.Attributes.InterceptAttribute; - namespace TestNamespace; + namespace TestNamespace; - public interface IThing { - string Read(string key); - } + public interface IThing { + string Read(string key); + } - [SingletonService] - public class LoggingInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) => context.Proceed(); - } + [SingletonService] + public class LoggingInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) => context.Proceed(); + } - [SingletonService] - {{attribute}} - public class Thing : IThing { - public string Read(string key) => key; - } + [SingletonService] + {{attribute}} + public class Thing : IThing { + public string Read(string key) => key; + } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/InterceptedMembersTests.cs b/tests/DependencyModules.Tests/GeneratorTests/InterceptedMembersTests.cs index 90d16ac..8bb8209 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/InterceptedMembersTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/InterceptedMembersTests.cs @@ -17,36 +17,43 @@ namespace DependencyModules.Tests.GeneratorTests; /// just does not run through the chain, which is the same path a member no interceptor can serve /// already took. /// -public class InterceptedMembersTests { - +public class InterceptedMembersTests +{ [Fact] - public void WithNoKindsNamed_EveryMemberIsIntercepted() { + public void WithNoKindsNamed_EveryMemberIsIntercepted() + { var calls = Run("[Intercept(typeof(CountingInterceptor))]"); Assert.Equal(["Handle", "get_Name"], calls); } [Fact] - public void NamingMethods_LeavesPropertiesAlone() { + public void NamingMethods_LeavesPropertiesAlone() + { var calls = Run( - "[Intercept(typeof(CountingInterceptor), Members = InterceptedMembers.Methods)]"); + "[Intercept(typeof(CountingInterceptor), Members = InterceptedMembers.Methods)]" + ); Assert.Equal(["Handle"], calls); } [Fact] - public void NamingProperties_LeavesMethodsAlone() { + public void NamingProperties_LeavesMethodsAlone() + { var calls = Run( - "[Intercept(typeof(CountingInterceptor), Members = InterceptedMembers.Properties)]"); + "[Intercept(typeof(CountingInterceptor), Members = InterceptedMembers.Properties)]" + ); Assert.Equal(["get_Name"], calls); } [Fact] - public void KindsCombine() { + public void KindsCombine() + { var calls = Run( - "[Intercept(typeof(CountingInterceptor), " + - "Members = InterceptedMembers.Methods | InterceptedMembers.Properties)]"); + "[Intercept(typeof(CountingInterceptor), " + + "Members = InterceptedMembers.Methods | InterceptedMembers.Properties)]" + ); Assert.Equal(["Handle", "get_Name"], calls); } @@ -56,9 +63,11 @@ public void KindsCombine() { /// implements the interface. /// [Fact] - public void AnExcludedMember_IsStillForwarded() { + public void AnExcludedMember_IsStillForwarded() + { var generated = Build( - "[Intercept(typeof(CountingInterceptor), Members = InterceptedMembers.Methods)]"); + "[Intercept(typeof(CountingInterceptor), Members = InterceptedMembers.Methods)]" + ); var service = generated.BuildProvider().GetService(generated.Type("IHandler"))!; @@ -71,15 +80,20 @@ public void AnExcludedMember_IsStillForwarded() { /// asked to cover it is not — reporting that would report the feature. /// [Fact] - public void AnExcludedMember_IsNotReportedAsUnserved() { + public void AnExcludedMember_IsNotReportedAsUnserved() + { var result = GeneratorTestHarness.Run( - Source("[Intercept(typeof(SyncOnlyInterceptor), Members = InterceptedMembers.Methods)]", - interceptor: SyncOnly)); + Source( + "[Intercept(typeof(SyncOnlyInterceptor), Members = InterceptedMembers.Methods)]", + interceptor: SyncOnly + ) + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0015"); } - private static string[] Run(string attribute) { + private static string[] Run(string attribute) + { var generated = Build(attribute); var provider = generated.BuildProvider(); var service = provider.GetService(generated.Type("IHandler"))!; @@ -98,8 +112,7 @@ private static string[] Run(string attribute) { private static GeneratedAssembly Build(string attribute) => GeneratedAssembly.Create(Source(attribute)); - private const string Counting = - """ + private const string Counting = """ public sealed class CountingInterceptor : IInterceptor { public static readonly System.Collections.Generic.List Calls = new(); @@ -110,8 +123,7 @@ public TResult Intercept(InvocationContext context) { } """; - private const string SyncOnly = - """ + private const string SyncOnly = """ public sealed class SyncOnlyInterceptor : IInterceptor { public TResult Intercept(InvocationContext context) => context.Proceed(); } @@ -119,26 +131,26 @@ public sealed class SyncOnlyInterceptor : IInterceptor { private static string Source(string attribute, string interceptor = Counting) => $$""" - using DependencyModules.Runtime.Attributes; - using DependencyModules.Runtime.Interception; + using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Interception; - namespace TestNamespace; + namespace TestNamespace; - public interface IHandler { - string Name { get; } - string Handle(string input); - } + public interface IHandler { + string Name { get; } + string Handle(string input); + } - {{interceptor}} + {{interceptor}} - [SingletonService] - {{attribute}} - public class Handler : IHandler { - public string Name => "named"; - public string Handle(string input) => input; - } + [SingletonService] + {{attribute}} + public class Handler : IHandler { + public string Name => "named"; + public string Handle(string input) => input; + } - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; } diff --git a/tests/DependencyModules.Tests/GeneratorTests/InterceptionRealmTests.cs b/tests/DependencyModules.Tests/GeneratorTests/InterceptionRealmTests.cs index a941fa6..9877bd3 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/InterceptionRealmTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/InterceptionRealmTests.cs @@ -18,10 +18,9 @@ namespace DependencyModules.Tests.GeneratorTests; /// with a realm-scoped service; agent 08 reached it by accident through a convention, because /// convention registrations are always stamped with their declaring module's realm. /// -public class InterceptionRealmTests { - - private const string Preamble = - """ +public class InterceptionRealmTests +{ + private const string Preamble = """ using DependencyModules.Runtime.Attributes; using DependencyModules.Runtime.Conventions; using DependencyModules.Runtime.Interception; @@ -41,19 +40,22 @@ public sealed class CountingInterceptor : IInterceptor { /// registration, because a per-implementation interception is about that one registration. /// [Fact] - public void ARealmScopedService_WithAnUnrealmedInterception_IsInterceptedInThatRealm() { + public void ARealmScopedService_WithAnUnrealmedInterception_IsInterceptedInThatRealm() + { var result = Run( - """ - [DependencyModule(OnlyRealm = true)] - public partial class RealmModule; + """ + [DependencyModule(OnlyRealm = true)] + public partial class RealmModule; - [SingletonService(Realm = typeof(RealmModule))] - [Intercept(typeof(CountingInterceptor))] - public sealed class Greeter : IGreeter { public string Greet() => "hi"; } + [SingletonService(Realm = typeof(RealmModule))] + [Intercept(typeof(CountingInterceptor))] + public sealed class Greeter : IGreeter { public string Greet() => "hi"; } - [DependencyModule] - public partial class TestModule; - """).AssertNoErrors(); + [DependencyModule] + public partial class TestModule; + """ + ) + .AssertNoErrors(); Assert.Contains("Greeter_Intercepted", result.SourceContaining("RealmModule.Interceptors")); } @@ -63,21 +65,27 @@ public partial class TestModule; /// wrapper to, so an applicator there is dead weight at best. /// [Fact] - public void ARealmScopedService_IsNotInterceptedOutsideItsRealm() { + public void ARealmScopedService_IsNotInterceptedOutsideItsRealm() + { var result = Run( - """ - [DependencyModule(OnlyRealm = true)] - public partial class RealmModule; - - [SingletonService(Realm = typeof(RealmModule))] - [Intercept(typeof(CountingInterceptor))] - public sealed class Greeter : IGreeter { public string Greet() => "hi"; } - - [DependencyModule] - public partial class TestModule; - """).AssertNoErrors(); - - Assert.DoesNotContain("Greeter_Intercepted", result.SourceContaining("TestModule.Interceptors")); + """ + [DependencyModule(OnlyRealm = true)] + public partial class RealmModule; + + [SingletonService(Realm = typeof(RealmModule))] + [Intercept(typeof(CountingInterceptor))] + public sealed class Greeter : IGreeter { public string Greet() => "hi"; } + + [DependencyModule] + public partial class TestModule; + """ + ) + .AssertNoErrors(); + + Assert.DoesNotContain( + "Greeter_Intercepted", + result.SourceContaining("TestModule.Interceptors") + ); } /// @@ -85,22 +93,28 @@ public partial class TestModule; /// changelog documents, and following the registration must not take it away. /// [Fact] - public void AnExplicitInterceptRealm_StillDecides() { + public void AnExplicitInterceptRealm_StillDecides() + { var result = Run( - """ - [DependencyModule(OnlyRealm = true)] - public partial class RealmModule; + """ + [DependencyModule(OnlyRealm = true)] + public partial class RealmModule; - [SingletonService] - [Intercept(typeof(CountingInterceptor), Realm = typeof(RealmModule))] - public sealed class Greeter : IGreeter { public string Greet() => "hi"; } + [SingletonService] + [Intercept(typeof(CountingInterceptor), Realm = typeof(RealmModule))] + public sealed class Greeter : IGreeter { public string Greet() => "hi"; } - [DependencyModule] - public partial class TestModule; - """).AssertNoErrors(); + [DependencyModule] + public partial class TestModule; + """ + ) + .AssertNoErrors(); Assert.Contains("Greeter_Intercepted", result.SourceContaining("RealmModule.Interceptors")); - Assert.DoesNotContain("Greeter_Intercepted", result.SourceContaining("TestModule.Interceptors")); + Assert.DoesNotContain( + "Greeter_Intercepted", + result.SourceContaining("TestModule.Interceptors") + ); } /// @@ -108,16 +122,19 @@ public partial class TestModule; /// module that is not realm-only. /// [Fact] - public void AnUnrealmedService_WithAnUnrealmedInterception_IsInterceptedNormally() { + public void AnUnrealmedService_WithAnUnrealmedInterception_IsInterceptedNormally() + { var result = Run( - """ - [SingletonService] - [Intercept(typeof(CountingInterceptor))] - public sealed class Greeter : IGreeter { public string Greet() => "hi"; } + """ + [SingletonService] + [Intercept(typeof(CountingInterceptor))] + public sealed class Greeter : IGreeter { public string Greet() => "hi"; } - [DependencyModule] - public partial class TestModule; - """).AssertNoErrors(); + [DependencyModule] + public partial class TestModule; + """ + ) + .AssertNoErrors(); Assert.Contains("Greeter_Intercepted", result.SourceContaining("TestModule.Interceptors")); } @@ -139,7 +156,8 @@ public partial class TestModule; /// provider. /// [Fact] - public void AConventionRegisteredClass_InAnOnlyRealmModule_ReportsDM0020() { + public void AConventionRegisteredClass_InAnOnlyRealmModule_ReportsDM0020() + { var result = Run( """ [Intercept(typeof(CountingInterceptor))] @@ -151,7 +169,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton(); } } - """); + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0020"); @@ -164,7 +183,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// can never run, not about every realm arrangement that looks unusual. /// [Fact] - public void AnInterceptionSomeModuleApplies_IsNotReported() { + public void AnInterceptionSomeModuleApplies_IsNotReported() + { var result = Run( """ [SingletonService] @@ -173,7 +193,8 @@ public sealed class Greeter : IGreeter { public string Greet() => "hi"; } [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0020"); } @@ -183,7 +204,8 @@ public partial class TestModule; /// module applies it and there is nothing to report. /// [Fact] - public void ARealmScopedServiceFollowingItsRegistration_IsNotReported() { + public void ARealmScopedServiceFollowingItsRegistration_IsNotReported() + { var result = Run( """ [DependencyModule(OnlyRealm = true)] @@ -195,7 +217,8 @@ public sealed class Greeter : IGreeter { public string Greet() => "hi"; } [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0020"); } @@ -205,21 +228,27 @@ public partial class TestModule; /// so the unrealmed interception landed on it by luck rather than by rule. /// [Fact] - public void AConventionRegisteredClass_InAPlainModule_IsStillIntercepted() { + public void AConventionRegisteredClass_InAPlainModule_IsStillIntercepted() + { var result = Run( - """ - [Intercept(typeof(CountingInterceptor))] - public sealed class Greeter : IGreeter { public string Greet() => "hi"; } - - [DependencyModule] - public partial class ConventionModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().AsSingleton(); + """ + [Intercept(typeof(CountingInterceptor))] + public sealed class Greeter : IGreeter { public string Greet() => "hi"; } + + [DependencyModule] + public partial class ConventionModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().AsSingleton(); + } } - } - """).AssertNoErrors(); - - Assert.Contains("Greeter_Intercepted", result.SourceContaining("ConventionModule.Interceptors")); + """ + ) + .AssertNoErrors(); + + Assert.Contains( + "Greeter_Intercepted", + result.SourceContaining("ConventionModule.Interceptors") + ); } /// @@ -229,23 +258,28 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// about which module the applicator landed on. /// [Fact] - public void AConventionRegisteringAsSelf_StillInterceptsTheInterface() { + public void AConventionRegisteringAsSelf_StillInterceptsTheInterface() + { var result = Run( - """ - [Intercept(typeof(CountingInterceptor))] - public sealed class Greeter : IGreeter { public string Greet() => "hi"; } - - [DependencyModule] - public partial class ConventionModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { - conventions.RegisterAll().AsSelf().AsSingleton(); + """ + [Intercept(typeof(CountingInterceptor))] + public sealed class Greeter : IGreeter { public string Greet() => "hi"; } + + [DependencyModule] + public partial class ConventionModule : IConventionModule { + void IConventionModule.Conventions(IConventionDefinitions conventions) { + conventions.RegisterAll().AsSelf().AsSingleton(); + } } - } - """).AssertNoErrors(); - - Assert.Contains("Greeter_Intercepted", result.SourceContaining("ConventionModule.Interceptors")); + """ + ) + .AssertNoErrors(); + + Assert.Contains( + "Greeter_Intercepted", + result.SourceContaining("ConventionModule.Interceptors") + ); } - private static GeneratorResult Run(string body) => - GeneratorTestHarness.Run(Preamble + body); + private static GeneratorResult Run(string body) => GeneratorTestHarness.Run(Preamble + body); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/InterceptionScopeTests.cs b/tests/DependencyModules.Tests/GeneratorTests/InterceptionScopeTests.cs index 4189126..221c14a 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/InterceptionScopeTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/InterceptionScopeTests.cs @@ -15,10 +15,9 @@ namespace DependencyModules.Tests.GeneratorTests; /// [Intercept] coming back wrapped in another class's wrapper, and, with two implementations /// marked, every interceptor running twice per call. Neither threw. /// -public class InterceptionScopeTests { - - private const string Interceptor = - """ +public class InterceptionScopeTests +{ + private const string Interceptor = """ public sealed class CountingInterceptor : IInterceptor { public static int Calls; public TResult Intercept(InvocationContext context) { @@ -29,7 +28,8 @@ public TResult Intercept(InvocationContext context) { """; [Fact] - public void AnUnmarkedSiblingImplementation_IsNotWrapped() { + public void AnUnmarkedSiblingImplementation_IsNotWrapped() + { var generated = GeneratedAssembly.Create( Source( """ @@ -38,13 +38,20 @@ public sealed class Loud : IGreeter { public string Greet() => "loud"; } [SingletonService] public sealed class Quiet : IGreeter { public string Greet() => "quiet"; } - """)); + """ + ) + ); var provider = generated.BuildProvider(); - var resolved = ((System.Collections.IEnumerable)provider - .GetService(typeof(System.Collections.Generic.IEnumerable<>) - .MakeGenericType(generated.Type("IGreeter")))!) + var resolved = ( + (System.Collections.IEnumerable) + provider.GetService( + typeof(System.Collections.Generic.IEnumerable<>).MakeGenericType( + generated.Type("IGreeter") + ) + )! + ) .Cast() .Select(g => g.GetType().Name) .OrderBy(n => n) @@ -54,7 +61,8 @@ public sealed class Quiet : IGreeter { public string Greet() => "quiet"; } } [Fact] - public void TwoMarkedImplementations_EachGetTheirOwnWrapper() { + public void TwoMarkedImplementations_EachGetTheirOwnWrapper() + { var generated = GeneratedAssembly.Create( Source( """ @@ -63,13 +71,20 @@ public sealed class Loud : IGreeter { public string Greet() => "loud"; } [SingletonService] [Intercept(typeof(CountingInterceptor))] public sealed class Quiet : IGreeter { public string Greet() => "quiet"; } - """)); + """ + ) + ); var provider = generated.BuildProvider(); - var resolved = ((System.Collections.IEnumerable)provider - .GetService(typeof(System.Collections.Generic.IEnumerable<>) - .MakeGenericType(generated.Type("IGreeter")))!) + var resolved = ( + (System.Collections.IEnumerable) + provider.GetService( + typeof(System.Collections.Generic.IEnumerable<>).MakeGenericType( + generated.Type("IGreeter") + ) + )! + ) .Cast() .Select(g => g.GetType().Name) .OrderBy(n => n) @@ -86,7 +101,8 @@ public sealed class Quiet : IGreeter { public string Greet() => "quiet"; } /// service that had asked for it. /// [Fact] - public void AnInterceptorOrderedOutsideADecorator_StillApplies() { + public void AnInterceptorOrderedOutsideADecorator_StillApplies() + { var generated = GeneratedAssembly.Create( Source( """ @@ -97,7 +113,9 @@ public sealed class Core : IGreeter { public string Greet() => "core"; } public sealed class Bracketed(IGreeter inner) : IGreeter { public string Greet() => "[" + inner.Greet() + "]"; } - """)); + """ + ) + ); var resolved = generated.ResolveRequired("IGreeter"); @@ -111,18 +129,18 @@ public sealed class Bracketed(IGreeter inner) : IGreeter { private static string Source(string body) => $$""" - using DependencyModules.Runtime.Attributes; - using DependencyModules.Runtime.Interception; + using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Interception; - namespace TestNamespace; + namespace TestNamespace; - public interface IGreeter { string Greet(); } + public interface IGreeter { string Greet(); } - {{Interceptor}} + {{Interceptor}} - {{body}} + {{body}} - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; } diff --git a/tests/DependencyModules.Tests/GeneratorTests/InterceptorGenerationTests.cs b/tests/DependencyModules.Tests/GeneratorTests/InterceptorGenerationTests.cs index 6431b36..0e4cd5d 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/InterceptorGenerationTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/InterceptorGenerationTests.cs @@ -8,11 +8,14 @@ namespace DependencyModules.Tests.GeneratorTests; /// Interception verified by compiling, loading and calling the generated wrapper, since the point is /// what an interceptor observes and can change rather than what the emitted text looks like. /// -public class InterceptorGenerationTests { - +public class InterceptorGenerationTests +{ [Fact] - public void InterceptedService_ResolvesAsTheGeneratedWrapper() { - var generated = GeneratedAssembly.Create(Source("int Sync(int a);", "public int Sync(int a) => a;")); + public void InterceptedService_ResolvesAsTheGeneratedWrapper() + { + var generated = GeneratedAssembly.Create( + Source("int Sync(int a);", "public int Sync(int a) => a;") + ); var resolved = generated.ResolveRequired("IWork"); @@ -20,8 +23,11 @@ public void InterceptedService_ResolvesAsTheGeneratedWrapper() { } [Fact] - public void SyncMethod_ReturnsTheInnerValueThroughThePipeline() { - var generated = GeneratedAssembly.Create(Source("int Sync(int a);", "public int Sync(int a) => a * 2;")); + public void SyncMethod_ReturnsTheInnerValueThroughThePipeline() + { + var generated = GeneratedAssembly.Create( + Source("int Sync(int a);", "public int Sync(int a) => a * 2;") + ); var work = generated.ResolveRequired("IWork"); @@ -30,7 +36,8 @@ public void SyncMethod_ReturnsTheInnerValueThroughThePipeline() { } [Fact] - public void VoidMethod_RunsThroughThePipeline() { + public void VoidMethod_RunsThroughThePipeline() + { var generated = GeneratedAssembly.Create(Source("void Run();", "public void Run() { }")); Invoke(generated.ResolveRequired("IWork"), "Run"); @@ -45,10 +52,14 @@ public void VoidMethod_RunsThroughThePipeline() { /// call has actually finished. /// [Fact] - public async Task AsyncMethod_ExitsAfterTheWorkCompletes() { - var generated = GeneratedAssembly.Create(Source( - "System.Threading.Tasks.Task Compute(int a);", - "public async System.Threading.Tasks.Task Compute(int a) { await Recorder.Gate.Task; return a * 2; }")); + public async Task AsyncMethod_ExitsAfterTheWorkCompletes() + { + var generated = GeneratedAssembly.Create( + Source( + "System.Threading.Tasks.Task Compute(int a);", + "public async System.Threading.Tasks.Task Compute(int a) { await Recorder.Gate.Task; return a * 2; }" + ) + ); var task = (Task)Invoke(generated.ResolveRequired("IWork"), "Compute", 21)!; @@ -64,10 +75,14 @@ public async Task AsyncMethod_ExitsAfterTheWorkCompletes() { } [Fact] - public async Task AsyncVoidMethod_RunsThroughThePipeline() { - var generated = GeneratedAssembly.Create(Source( - "System.Threading.Tasks.Task Run();", - "public async System.Threading.Tasks.Task Run() { await System.Threading.Tasks.Task.Delay(5); }")); + public async Task AsyncVoidMethod_RunsThroughThePipeline() + { + var generated = GeneratedAssembly.Create( + Source( + "System.Threading.Tasks.Task Run();", + "public async System.Threading.Tasks.Task Run() { await System.Threading.Tasks.Task.Delay(5); }" + ) + ); await (Task)Invoke(generated.ResolveRequired("IWork"), "Run")!; @@ -75,10 +90,14 @@ public async Task AsyncVoidMethod_RunsThroughThePipeline() { } [Fact] - public async Task ValueTaskMethod_ReturnsTheInnerValue() { - var generated = GeneratedAssembly.Create(Source( - "System.Threading.Tasks.ValueTask Fetch();", - "public async System.Threading.Tasks.ValueTask Fetch() { await System.Threading.Tasks.Task.Delay(5); return \"done\"; }")); + public async Task ValueTaskMethod_ReturnsTheInnerValue() + { + var generated = GeneratedAssembly.Create( + Source( + "System.Threading.Tasks.ValueTask Fetch();", + "public async System.Threading.Tasks.ValueTask Fetch() { await System.Threading.Tasks.Task.Delay(5); return \"done\"; }" + ) + ); var result = await (ValueTask)Invoke(generated.ResolveRequired("IWork"), "Fetch")!; @@ -87,10 +106,14 @@ public async Task ValueTaskMethod_ReturnsTheInnerValue() { } [Fact] - public async Task ValueTaskWithNoResult_RunsThroughThePipeline() { - var generated = GeneratedAssembly.Create(Source( - "System.Threading.Tasks.ValueTask Save();", - "public async System.Threading.Tasks.ValueTask Save() { await System.Threading.Tasks.Task.Delay(5); }")); + public async Task ValueTaskWithNoResult_RunsThroughThePipeline() + { + var generated = GeneratedAssembly.Create( + Source( + "System.Threading.Tasks.ValueTask Save();", + "public async System.Threading.Tasks.ValueTask Save() { await System.Threading.Tasks.Task.Delay(5); }" + ) + ); await (ValueTask)Invoke(generated.ResolveRequired("IWork"), "Save")!; @@ -103,18 +126,26 @@ public async Task ValueTaskWithNoResult_RunsThroughThePipeline() { /// item as it is produced. /// [Fact] - public async Task AsyncEnumerableMethod_ObservesEachItem() { - var generated = GeneratedAssembly.Create(Source( - "System.Collections.Generic.IAsyncEnumerable Stream(int count);", - """ - public async System.Collections.Generic.IAsyncEnumerable Stream(int count) { - for (var i = 0; i < count; i++) { await System.Threading.Tasks.Task.Yield(); yield return i; } - } - """)); + public async Task AsyncEnumerableMethod_ObservesEachItem() + { + var generated = GeneratedAssembly.Create( + Source( + "System.Collections.Generic.IAsyncEnumerable Stream(int count);", + """ + public async System.Collections.Generic.IAsyncEnumerable Stream(int count) { + for (var i = 0; i < count; i++) { await System.Threading.Tasks.Task.Yield(); yield return i; } + } + """ + ) + ); var items = new List(); - await foreach (var item in (IAsyncEnumerable)Invoke(generated.ResolveRequired("IWork"), "Stream", 3)!) { + await foreach ( + var item in (IAsyncEnumerable) + Invoke(generated.ResolveRequired("IWork"), "Stream", 3)! + ) + { items.Add(item); } @@ -123,10 +154,14 @@ public async System.Collections.Generic.IAsyncEnumerable Stream(int count) } [Fact] - public void GenericMethod_ForwardsWithItsConstraints() { - var generated = GeneratedAssembly.Create(Source( - "T Pick(T item) where T : class;", - "public T Pick(T item) where T : class => item;")); + public void GenericMethod_ForwardsWithItsConstraints() + { + var generated = GeneratedAssembly.Create( + Source( + "T Pick(T item) where T : class;", + "public T Pick(T item) where T : class => item;" + ) + ); var work = generated.ResolveRequired("IWork"); var method = work.GetType().GetMethod("Pick")!.MakeGenericMethod(typeof(string)); @@ -140,10 +175,11 @@ public void GenericMethod_ForwardsWithItsConstraints() { /// and the name matches what appears in a stack trace. /// [Fact] - public void Property_RoutesBothAccessorsAndReportsThemByTheirClrNames() { - var generated = GeneratedAssembly.Create(Source( - "string Name { get; set; }", - "public string Name { get; set; } = \"initial\";")); + public void Property_RoutesBothAccessorsAndReportsThemByTheirClrNames() + { + var generated = GeneratedAssembly.Create( + Source("string Name { get; set; }", "public string Name { get; set; } = \"initial\";") + ); var work = generated.ResolveRequired("IWork"); var property = work.GetType().GetProperty("Name")!; @@ -151,12 +187,18 @@ public void Property_RoutesBothAccessorsAndReportsThemByTheirClrNames() { property.SetValue(work, "assigned"); Assert.Equal("assigned", property.GetValue(work)); - Assert.Equal(["enter set_Name", "exit set_Name", "enter get_Name", "exit get_Name"], Log(generated)); + Assert.Equal( + ["enter set_Name", "exit set_Name", "enter get_Name", "exit get_Name"], + Log(generated) + ); } [Fact] - public void ReadOnlyProperty_DeclaresNoSetter() { - var generated = GeneratedAssembly.Create(Source("int Count { get; }", "public int Count => 7;")); + public void ReadOnlyProperty_DeclaresNoSetter() + { + var generated = GeneratedAssembly.Create( + Source("int Count { get; }", "public int Count => 7;") + ); var work = generated.ResolveRequired("IWork"); var property = work.GetType().GetProperty("Count")!; @@ -171,10 +213,14 @@ public void ReadOnlyProperty_DeclaresNoSetter() { /// and hands the task itself to the interceptor. /// [Fact] - public async Task PropertyReturningATask_TakesTheSyncPath() { - var generated = GeneratedAssembly.Create(Source( - "System.Threading.Tasks.Task Pending { get; }", - "public System.Threading.Tasks.Task Pending => System.Threading.Tasks.Task.FromResult(3);")); + public async Task PropertyReturningATask_TakesTheSyncPath() + { + var generated = GeneratedAssembly.Create( + Source( + "System.Threading.Tasks.Task Pending { get; }", + "public System.Threading.Tasks.Task Pending => System.Threading.Tasks.Task.FromResult(3);" + ) + ); var work = generated.ResolveRequired("IWork"); var pending = (Task)work.GetType().GetProperty("Pending")!.GetValue(work)!; @@ -184,16 +230,20 @@ public async Task PropertyReturningATask_TakesTheSyncPath() { } [Fact] - public void Indexer_ForwardsItsIndicesAndAssignedValue() { - var generated = GeneratedAssembly.Create(Source( - "int this[int row, int column] { get; set; }", - """ - private readonly System.Collections.Generic.Dictionary _cells = new(); - public int this[int row, int column] { - get => _cells.TryGetValue($"{row},{column}", out var value) ? value : -1; - set => _cells[$"{row},{column}"] = value; - } - """)); + public void Indexer_ForwardsItsIndicesAndAssignedValue() + { + var generated = GeneratedAssembly.Create( + Source( + "int this[int row, int column] { get; set; }", + """ + private readonly System.Collections.Generic.Dictionary _cells = new(); + public int this[int row, int column] { + get => _cells.TryGetValue($"{row},{column}", out var value) ? value : -1; + set => _cells[$"{row},{column}"] = value; + } + """ + ) + ); var work = generated.ResolveRequired("IWork"); var indexer = work.GetType().GetProperty("Item")!; @@ -203,28 +253,40 @@ public void Indexer_ForwardsItsIndicesAndAssignedValue() { Assert.Equal(42, indexer.GetValue(work, [2, 3])); Assert.Equal(-1, indexer.GetValue(work, [9, 9])); Assert.Equal( - ["enter set_Item", "exit set_Item", "enter get_Item", "exit get_Item", "enter get_Item", "exit get_Item"], - Log(generated)); + [ + "enter set_Item", + "exit set_Item", + "enter get_Item", + "exit get_Item", + "enter get_Item", + "exit get_Item", + ], + Log(generated) + ); } [Fact] - public void IndexerSetter_ExposesItsIndicesAndValueAsArguments() { - var generated = GeneratedAssembly.Create(Source( - "int this[int row] { get; set; }", - "public int this[int row] { get => row; set { } }", - """ - public class TracingInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) { - var arguments = context.Arguments; - - for (var i = 0; i < arguments.Count; i++) { - Recorder.Entries.Add($"{arguments.NameAt(i)}={arguments[i]}"); + public void IndexerSetter_ExposesItsIndicesAndValueAsArguments() + { + var generated = GeneratedAssembly.Create( + Source( + "int this[int row] { get; set; }", + "public int this[int row] { get => row; set { } }", + """ + public class TracingInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) { + var arguments = context.Arguments; + + for (var i = 0; i < arguments.Count; i++) { + Recorder.Entries.Add($"{arguments.NameAt(i)}={arguments[i]}"); + } + + return context.Proceed(); } - - return context.Proceed(); } - } - """)); + """ + ) + ); var work = generated.ResolveRequired("IWork"); @@ -234,10 +296,14 @@ public TResult Intercept(InvocationContext context) { } [Fact] - public void Event_RoutesAddAndRemove() { - var generated = GeneratedAssembly.Create(Source( - "event System.EventHandler Changed;", - "public event System.EventHandler Changed { add { } remove { } }")); + public void Event_RoutesAddAndRemove() + { + var generated = GeneratedAssembly.Create( + Source( + "event System.EventHandler Changed;", + "public event System.EventHandler Changed { add { } remove { } }" + ) + ); var work = generated.ResolveRequired("IWork"); var changed = work.GetType().GetEvent("Changed")!; @@ -247,64 +313,82 @@ public void Event_RoutesAddAndRemove() { changed.RemoveEventHandler(work, handler); Assert.Equal( - ["enter add_Changed", "exit add_Changed", "enter remove_Changed", "exit remove_Changed"], - Log(generated)); + [ + "enter add_Changed", + "exit add_Changed", + "enter remove_Changed", + "exit remove_Changed", + ], + Log(generated) + ); } [Fact] - public void ThrowingMethod_PropagatesThroughThePipeline() { - var generated = GeneratedAssembly.Create(Source( - "void Run();", - "public void Run() => throw new System.InvalidOperationException(\"boom\");")); + public void ThrowingMethod_PropagatesThroughThePipeline() + { + var generated = GeneratedAssembly.Create( + Source( + "void Run();", + "public void Run() => throw new System.InvalidOperationException(\"boom\");" + ) + ); - var exception = Assert.Throws( - () => Invoke(generated.ResolveRequired("IWork"), "Run")); + var exception = Assert.Throws(() => + Invoke(generated.ResolveRequired("IWork"), "Run") + ); Assert.IsType(exception.InnerException); Assert.Equal(["enter Run", "exit Run"], Log(generated)); } [Fact] - public void SeveralInterceptors_NestInDeclarationOrder() { + public void SeveralInterceptors_NestInDeclarationOrder() + { var generated = GeneratedAssembly.Create( $$""" - {{Preamble}} + {{Preamble}} - {{Tracing("First", "first")}} + {{Tracing("First", "first")}} - {{Tracing("Second", "second")}} + {{Tracing("Second", "second")}} - public interface IWork { void Run(); } + public interface IWork { void Run(); } - [SingletonService] - [Intercept(typeof(FirstInterceptor), typeof(SecondInterceptor))] - public class Work : IWork { public void Run() { } } + [SingletonService] + [Intercept(typeof(FirstInterceptor), typeof(SecondInterceptor))] + public class Work : IWork { public void Run() { } } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); Invoke(generated.ResolveRequired("IWork"), "Run"); Assert.Equal( ["enter first Run", "enter second Run", "exit second Run", "exit first Run"], - Log(generated)); + Log(generated) + ); } [Fact] - public void Caller_CarriesTheServiceTypeAndMember() { - var generated = GeneratedAssembly.Create(Source( - "void Run();", - "public void Run() { }", - """ - public class TracingInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) { - Recorder.Entries.Add(context.Caller.ToString()); - Recorder.Entries.Add(context.Caller.ServiceType.Name); - return context.Proceed(); + public void Caller_CarriesTheServiceTypeAndMember() + { + var generated = GeneratedAssembly.Create( + Source( + "void Run();", + "public void Run() { }", + """ + public class TracingInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) { + Recorder.Entries.Add(context.Caller.ToString()); + Recorder.Entries.Add(context.Caller.ServiceType.Name); + return context.Proceed(); + } } - } - """)); + """ + ) + ); Invoke(generated.ResolveRequired("IWork"), "Run"); @@ -316,24 +400,28 @@ public TResult Intercept(InvocationContext context) { /// so writing one replaces the value the implementation receives. /// [Fact] - public void Arguments_AreReadableByNameAndReplaceable() { - var generated = GeneratedAssembly.Create(Source( - "int Sync(int a, string b);", - "public int Sync(int a, string b) { Recorder.Entries.Add($\"inner {a} {b}\"); return a; }", - """ - public class TracingInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) { - var arguments = context.Arguments; - - for (var i = 0; i < arguments.Count; i++) { - Recorder.Entries.Add($"{arguments.NameAt(i)}={arguments[i]}"); + public void Arguments_AreReadableByNameAndReplaceable() + { + var generated = GeneratedAssembly.Create( + Source( + "int Sync(int a, string b);", + "public int Sync(int a, string b) { Recorder.Entries.Add($\"inner {a} {b}\"); return a; }", + """ + public class TracingInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) { + var arguments = context.Arguments; + + for (var i = 0; i < arguments.Count; i++) { + Recorder.Entries.Add($"{arguments.NameAt(i)}={arguments[i]}"); + } + + arguments[0] = 99; + return context.Proceed(); } - - arguments[0] = 99; - return context.Proceed(); } - } - """)); + """ + ) + ); var result = Invoke(generated.ResolveRequired("IWork"), "Sync", 5, "text"); @@ -346,18 +434,22 @@ public TResult Intercept(InvocationContext context) { /// second time re-enters the same next stage rather than walking past it. /// [Fact] - public void ProceedingTwice_CallsTheImplementationTwice() { - var generated = GeneratedAssembly.Create(Source( - "int Sync(int a);", - "public int Sync(int a) { Recorder.Entries.Add(\"inner\"); return a; }", - """ - public class TracingInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) { - context.Proceed(); - return context.Proceed(); + public void ProceedingTwice_CallsTheImplementationTwice() + { + var generated = GeneratedAssembly.Create( + Source( + "int Sync(int a);", + "public int Sync(int a) { Recorder.Entries.Add(\"inner\"); return a; }", + """ + public class TracingInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) { + context.Proceed(); + return context.Proceed(); + } } - } - """)); + """ + ) + ); var result = Invoke(generated.ResolveRequired("IWork"), "Sync", 5); @@ -366,15 +458,19 @@ public TResult Intercept(InvocationContext context) { } [Fact] - public void NotProceeding_SkipsTheImplementation() { - var generated = GeneratedAssembly.Create(Source( - "int Sync(int a);", - "public int Sync(int a) { Recorder.Entries.Add(\"inner\"); return a; }", - """ - public class TracingInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) => default!; - } - """)); + public void NotProceeding_SkipsTheImplementation() + { + var generated = GeneratedAssembly.Create( + Source( + "int Sync(int a);", + "public int Sync(int a) { Recorder.Entries.Add(\"inner\"); return a; }", + """ + public class TracingInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) => default!; + } + """ + ) + ); Assert.Equal(0, Invoke(generated.ResolveRequired("IWork"), "Sync", 5)); Assert.Empty(Log(generated)); @@ -386,29 +482,31 @@ public class TracingInterceptor : IInterceptor { /// wrapper holds its interceptors as typed fields now, so a wrapper can only reach its own. /// [Fact] - public void TwoServices_DoNotCrossApplyEachOthersInterceptors() { + public void TwoServices_DoNotCrossApplyEachOthersInterceptors() + { var generated = GeneratedAssembly.Create( $$""" - {{Preamble}} + {{Preamble}} - {{Tracing("Alpha", "alpha")}} + {{Tracing("Alpha", "alpha")}} - {{Tracing("Beta", "beta")}} + {{Tracing("Beta", "beta")}} - public interface IAlpha { void Run(); } - public interface IBeta { void Run(); } + public interface IAlpha { void Run(); } + public interface IBeta { void Run(); } - [SingletonService] - [Intercept(typeof(AlphaInterceptor))] - public class Alpha : IAlpha { public void Run() { } } + [SingletonService] + [Intercept(typeof(AlphaInterceptor))] + public class Alpha : IAlpha { public void Run() { } } - [SingletonService] - [Intercept(typeof(BetaInterceptor))] - public class Beta : IBeta { public void Run() { } } + [SingletonService] + [Intercept(typeof(BetaInterceptor))] + public class Beta : IBeta { public void Run() { } } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); var provider = generated.BuildProvider(); @@ -423,38 +521,40 @@ public partial class TestModule; /// rest, which are forwarded untouched rather than costing the sync members their interception. /// [Fact] - public async Task SyncOnlyInterceptor_ServesSyncMembersAndPassesAsyncOnesThrough() { + public async Task SyncOnlyInterceptor_ServesSyncMembersAndPassesAsyncOnesThrough() + { var generated = GeneratedAssembly.Create( $$""" - {{Preamble}} - - public class SyncOnlyInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) { - Recorder.Entries.Add($"enter {context.Caller.MemberName}"); - - try { - return context.Proceed(); - } finally { - Recorder.Entries.Add($"exit {context.Caller.MemberName}"); - } - } - } - - public interface IWork { - int Sync(int a); - Task Async(int a); - } - - [SingletonService] - [Intercept(typeof(SyncOnlyInterceptor))] - public class Work : IWork { - public int Sync(int a) => a * 2; - public Task Async(int a) => Task.FromResult(a * 3); - } - - [DependencyModule] - public partial class TestModule; - """); + {{Preamble}} + + public class SyncOnlyInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) { + Recorder.Entries.Add($"enter {context.Caller.MemberName}"); + + try { + return context.Proceed(); + } finally { + Recorder.Entries.Add($"exit {context.Caller.MemberName}"); + } + } + } + + public interface IWork { + int Sync(int a); + Task Async(int a); + } + + [SingletonService] + [Intercept(typeof(SyncOnlyInterceptor))] + public class Work : IWork { + public int Sync(int a) => a * 2; + public Task Async(int a) => Task.FromResult(a * 3); + } + + [DependencyModule] + public partial class TestModule; + """ + ); var work = generated.ResolveRequired("IWork"); @@ -469,38 +569,40 @@ public partial class TestModule; /// The mirror of the above, so neither direction is the special case. /// [Fact] - public async Task AsyncOnlyInterceptor_ServesAsyncMembersAndPassesSyncOnesThrough() { + public async Task AsyncOnlyInterceptor_ServesAsyncMembersAndPassesSyncOnesThrough() + { var generated = GeneratedAssembly.Create( $$""" - {{Preamble}} - - public class AsyncOnlyInterceptor : IAsyncInterceptor { - public async ValueTask InterceptAsync(AsyncInvocationContext context) { - Recorder.Entries.Add($"enter {context.Caller.MemberName}"); - - try { - return await context.ProceedAsync(); - } finally { - Recorder.Entries.Add($"exit {context.Caller.MemberName}"); - } - } - } - - public interface IWork { - int Sync(int a); - Task Async(int a); - } - - [SingletonService] - [Intercept(typeof(AsyncOnlyInterceptor))] - public class Work : IWork { - public int Sync(int a) => a * 2; - public Task Async(int a) => Task.FromResult(a * 3); - } - - [DependencyModule] - public partial class TestModule; - """); + {{Preamble}} + + public class AsyncOnlyInterceptor : IAsyncInterceptor { + public async ValueTask InterceptAsync(AsyncInvocationContext context) { + Recorder.Entries.Add($"enter {context.Caller.MemberName}"); + + try { + return await context.ProceedAsync(); + } finally { + Recorder.Entries.Add($"exit {context.Caller.MemberName}"); + } + } + } + + public interface IWork { + int Sync(int a); + Task Async(int a); + } + + [SingletonService] + [Intercept(typeof(AsyncOnlyInterceptor))] + public class Work : IWork { + public int Sync(int a) => a * 2; + public Task Async(int a) => Task.FromResult(a * 3); + } + + [DependencyModule] + public partial class TestModule; + """ + ); var work = generated.ResolveRequired("IWork"); @@ -516,40 +618,43 @@ public partial class TestModule; /// walks to is its position in that pipeline rather than in the attribute. /// [Fact] - public void MixedInterceptors_NestOnlyThoseThatServeTheMember() { + public void MixedInterceptors_NestOnlyThoseThatServeTheMember() + { var generated = GeneratedAssembly.Create( $$""" - {{Preamble}} + {{Preamble}} - public class SyncOnlyInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) { - Recorder.Entries.Add("enter sync-only"); + public class SyncOnlyInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) { + Recorder.Entries.Add("enter sync-only"); - try { - return context.Proceed(); - } finally { - Recorder.Entries.Add("exit sync-only"); - } - } - } + try { + return context.Proceed(); + } finally { + Recorder.Entries.Add("exit sync-only"); + } + } + } - {{Tracing("Both", "both")}} + {{Tracing("Both", "both")}} - public interface IWork { int Sync(int a); } + public interface IWork { int Sync(int a); } - [SingletonService] - [Intercept(typeof(SyncOnlyInterceptor), typeof(BothInterceptor))] - public class Work : IWork { public int Sync(int a) => a; } + [SingletonService] + [Intercept(typeof(SyncOnlyInterceptor), typeof(BothInterceptor))] + public class Work : IWork { public int Sync(int a) => a; } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); Invoke(generated.ResolveRequired("IWork"), "Sync", 1); Assert.Equal( ["enter sync-only", "enter both Sync", "exit both Sync", "exit sync-only"], - Log(generated)); + Log(generated) + ); } /// @@ -557,24 +662,26 @@ public partial class TestModule; /// service resolves as the implementation registered it. /// [Fact] - public void InterceptorThatServesNothing_GeneratesNoWrapper() { + public void InterceptorThatServesNothing_GeneratesNoWrapper() + { var generated = GeneratedAssembly.Create( $$""" - {{Preamble}} + {{Preamble}} - public class SyncOnlyInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) => context.Proceed(); - } + public class SyncOnlyInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) => context.Proceed(); + } - public interface IWork { Task Async(int a); } + public interface IWork { Task Async(int a); } - [SingletonService] - [Intercept(typeof(SyncOnlyInterceptor))] - public class Work : IWork { public Task Async(int a) => Task.FromResult(a); } + [SingletonService] + [Intercept(typeof(SyncOnlyInterceptor))] + public class Work : IWork { public Task Async(int a) => Task.FromResult(a); } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); var resolved = generated.ResolveRequired("IWork"); @@ -588,8 +695,11 @@ public partial class TestModule; /// and an open generic implementation type is what the container does accept. /// [Fact] - public void GenericImplementation_IsIntercepted() { - var result = GeneratorTestHarness.Run(GenericRepo("public class Repo : IRepo { public void Run() { } }")); + public void GenericImplementation_IsIntercepted() + { + var result = GeneratorTestHarness.Run( + GenericRepo("public class Repo : IRepo { public void Run() { } }") + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0008"); Assert.Contains(result.GeneratedSources.Keys, key => key.Contains("Repo_Intercepted")); @@ -600,10 +710,15 @@ public void GenericImplementation_IsIntercepted() { /// generic implementation type and close it per construction. /// [Fact] - public void GenericImplementation_EmitsAGenericWrapper() { - var result = GeneratorTestHarness.Run(GenericRepo("public class Repo : IRepo { public void Run() { } }")); + public void GenericImplementation_EmitsAGenericWrapper() + { + var result = GeneratorTestHarness.Run( + GenericRepo("public class Repo : IRepo { public void Run() { } }") + ); - var wrapper = Assert.Single(result.GeneratedSources, pair => pair.Key.Contains("Repo_Intercepted")).Value; + var wrapper = Assert + .Single(result.GeneratedSources, pair => pair.Key.Contains("Repo_Intercepted")) + .Value; Assert.Contains("class Repo_Intercepted", wrapper); @@ -618,10 +733,15 @@ public void GenericImplementation_EmitsAGenericWrapper() { /// `T` is in scope at the registration. /// [Fact] - public void GenericImplementation_RegistersAsAnOpenGenericImplementation() { - var result = GeneratorTestHarness.Run(GenericRepo("public class Repo : IRepo { public void Run() { } }")); + public void GenericImplementation_RegistersAsAnOpenGenericImplementation() + { + var result = GeneratorTestHarness.Run( + GenericRepo("public class Repo : IRepo { public void Run() { } }") + ); - var registration = Assert.Single(result.GeneratedSources, pair => pair.Key.Contains("Interceptors")).Value; + var registration = Assert + .Single(result.GeneratedSources, pair => pair.Key.Contains("Interceptors")) + .Value; Assert.Contains("InterceptOpenGeneric", registration); Assert.Contains("typeof(global::TestNamespace.IRepo<>)", registration); @@ -633,15 +753,20 @@ public void GenericImplementation_RegistersAsAnOpenGenericImplementation() { /// what it wraps. /// [Fact] - public void ConstrainedGenericImplementation_RepeatsTheConstraints() { + public void ConstrainedGenericImplementation_RepeatsTheConstraints() + { var result = GeneratorTestHarness.Run( GenericRepo( "public class Repo : IRepo where T : class, IMarker, new() { public void Run() { } }", - supporting: "public interface IMarker;")); + supporting: "public interface IMarker;" + ) + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0008"); - var wrapper = Assert.Single(result.GeneratedSources, pair => pair.Key.Contains("Repo_Intercepted")).Value; + var wrapper = Assert + .Single(result.GeneratedSources, pair => pair.Key.Contains("Repo_Intercepted")) + .Value; Assert.Contains("where T : class, global::TestNamespace.IMarker, new()", wrapper); } @@ -652,11 +777,17 @@ public void ConstrainedGenericImplementation_RepeatsTheConstraints() { /// reader has to drop it rather than pass it through. /// [Fact] - public void StructConstrainedGeneric_DoesNotRepeatTheDefaultConstructor() { + public void StructConstrainedGeneric_DoesNotRepeatTheDefaultConstructor() + { var result = GeneratorTestHarness.Run( - GenericRepo("public class Repo : IRepo where T : struct { public void Run() { } }")); + GenericRepo( + "public class Repo : IRepo where T : struct { public void Run() { } }" + ) + ); - var wrapper = Assert.Single(result.GeneratedSources, pair => pair.Key.Contains("Repo_Intercepted")).Value; + var wrapper = Assert + .Single(result.GeneratedSources, pair => pair.Key.Contains("Repo_Intercepted")) + .Value; Assert.Contains("where T : struct", wrapper); Assert.DoesNotContain("new()", wrapper); @@ -666,15 +797,18 @@ public void StructConstrainedGeneric_DoesNotRepeatTheDefaultConstructor() { /// And the constrained wrapper is not merely well-formed text: it compiles, loads and runs. /// [Fact] - public void ConstrainedGenericImplementation_ResolvesAndIntercepts() { + public void ConstrainedGenericImplementation_ResolvesAndIntercepts() + { var generated = GeneratedAssembly.Create( GenericRepo( "public class Repo : IRepo where T : class, IMarker, new() { public void Run() { } }", supporting: """ - public interface IMarker; + public interface IMarker; - public class Marked : IMarker; - """)); + public class Marked : IMarker; + """ + ) + ); var closed = generated.Type("IRepo`1").MakeGenericType(generated.Type("Marked")); var resolved = generated.BuildProvider().GetService(closed); @@ -689,21 +823,21 @@ public class Marked : IMarker; /// private static string GenericRepo(string implementation, string supporting = "") => $$""" - {{Preamble}} + {{Preamble}} - {{Tracing("Tracing", "tracing")}} + {{Tracing("Tracing", "tracing")}} - public interface IRepo { void Run(); } + public interface IRepo { void Run(); } - {{supporting}} + {{supporting}} - [SingletonService] - [Intercept(typeof(TracingInterceptor))] - {{implementation}} + [SingletonService] + [Intercept(typeof(TracingInterceptor))] + {{implementation}} - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; /// /// A closed construction of a generic service, which is the answer to the refusal above. The @@ -711,27 +845,31 @@ public partial class TestModule; /// it that way too — interception used to disagree and report that the class implemented none. /// [Fact] - public void ClosedConstructionOfAGenericService_IsIntercepted() { + public void ClosedConstructionOfAGenericService_IsIntercepted() + { var generated = GeneratedAssembly.Create( $$""" - {{Preamble}} + {{Preamble}} - {{Tracing("Tracing", "tracing")}} + {{Tracing("Tracing", "tracing")}} - public interface IRepo { string Name(); } + public interface IRepo { string Name(); } - public class Repo : IRepo { public string Name() => "repo"; } + public class Repo : IRepo { public string Name() => "repo"; } - [SingletonService] - [Intercept(typeof(TracingInterceptor))] - public class StringRepo : Repo { } + [SingletonService] + [Intercept(typeof(TracingInterceptor))] + public class StringRepo : Repo { } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); var provider = generated.BuildProvider(); - var resolved = provider.GetService(generated.Type("IRepo`1").MakeGenericType(typeof(string)))!; + var resolved = provider.GetService( + generated.Type("IRepo`1").MakeGenericType(typeof(string)) + )!; Assert.EndsWith("_Intercepted", resolved.GetType().Name); Assert.Equal("repo", resolved.GetType().GetMethod("Name")!.Invoke(resolved, null)); @@ -739,22 +877,24 @@ public partial class TestModule; } [Fact] - public void RefParameter_ReportsDM0008() { + public void RefParameter_ReportsDM0008() + { var result = GeneratorTestHarness.Run( $$""" - {{Preamble}} + {{Preamble}} - {{Tracing("Tracing", "tracing")}} + {{Tracing("Tracing", "tracing")}} - public interface IWork { void Run(ref int a); } + public interface IWork { void Run(ref int a); } - [SingletonService] - [Intercept(typeof(TracingInterceptor))] - public class Work : IWork { public void Run(ref int a) { } } + [SingletonService] + [Intercept(typeof(TracingInterceptor))] + public class Work : IWork { public void Run(ref int a) { } } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0008"); @@ -762,20 +902,22 @@ public partial class TestModule; } [Fact] - public void ServiceWithNoInterface_ReportsDM0008() { + public void ServiceWithNoInterface_ReportsDM0008() + { var result = GeneratorTestHarness.Run( $$""" - {{Preamble}} + {{Preamble}} - {{Tracing("Tracing", "tracing")}} + {{Tracing("Tracing", "tracing")}} - [SingletonService] - [Intercept(typeof(TracingInterceptor))] - public class Standalone { public void Run() { } } + [SingletonService] + [Intercept(typeof(TracingInterceptor))] + public class Standalone { public void Run() { } } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0008"); @@ -784,23 +926,25 @@ public partial class TestModule; } [Fact] - public void ServiceWithSeveralInterfaces_ReportsDM0008() { + public void ServiceWithSeveralInterfaces_ReportsDM0008() + { var result = GeneratorTestHarness.Run( $$""" - {{Preamble}} + {{Preamble}} - {{Tracing("Tracing", "tracing")}} + {{Tracing("Tracing", "tracing")}} - public interface IOne { void Run(); } - public interface ITwo { void Walk(); } + public interface IOne { void Run(); } + public interface ITwo { void Walk(); } - [SingletonService] - [Intercept(typeof(TracingInterceptor))] - public class Both : IOne, ITwo { public void Run() { } public void Walk() { } } + [SingletonService] + [Intercept(typeof(TracingInterceptor))] + public class Both : IOne, ITwo { public void Run() { } public void Walk() { } } - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0008"); @@ -820,8 +964,7 @@ private static TaskCompletionSource Gate(GeneratedAssembly generated) => private static object? Invoke(object target, string method, params object?[] arguments) => target.GetType().GetMethod(method)!.Invoke(target, arguments); - private const string Preamble = - """ + private const string Preamble = """ using DependencyModules.Runtime.Attributes; using DependencyModules.Runtime.Interception; using System.Collections.Generic; @@ -846,60 +989,65 @@ public static class Recorder { /// private static string Tracing(string prefix, string label) => $$""" - public class {{prefix}}Interceptor : IInterceptor, IAsyncInterceptor, IAsyncEnumerableInterceptor { - public TResult Intercept(InvocationContext context) { - Recorder.Entries.Add($"enter {{label}} {context.Caller.MemberName}"); + public class {{prefix}}Interceptor : IInterceptor, IAsyncInterceptor, IAsyncEnumerableInterceptor { + public TResult Intercept(InvocationContext context) { + Recorder.Entries.Add($"enter {{label}} {context.Caller.MemberName}"); - try { - return context.Proceed(); - } finally { - Recorder.Entries.Add($"exit {{label}} {context.Caller.MemberName}"); - } - } + try { + return context.Proceed(); + } finally { + Recorder.Entries.Add($"exit {{label}} {context.Caller.MemberName}"); + } + } - public async ValueTask InterceptAsync(AsyncInvocationContext context) { - Recorder.Entries.Add($"enter {{label}} {context.Caller.MemberName}"); + public async ValueTask InterceptAsync(AsyncInvocationContext context) { + Recorder.Entries.Add($"enter {{label}} {context.Caller.MemberName}"); - try { - return await context.ProceedAsync(); - } finally { - Recorder.Entries.Add($"exit {{label}} {context.Caller.MemberName}"); - } - } + try { + return await context.ProceedAsync(); + } finally { + Recorder.Entries.Add($"exit {{label}} {context.Caller.MemberName}"); + } + } - public async IAsyncEnumerable InterceptStream(StreamInvocationContext context) { - Recorder.Entries.Add($"enter {{label}} {context.Caller.MemberName}"); + public async IAsyncEnumerable InterceptStream(StreamInvocationContext context) { + Recorder.Entries.Add($"enter {{label}} {context.Caller.MemberName}"); - await foreach (var item in context.Proceed()) { - Recorder.Entries.Add($"item {item}"); + await foreach (var item in context.Proceed()) { + Recorder.Entries.Add($"item {item}"); - yield return item; - } + yield return item; + } - Recorder.Entries.Add($"exit {{label}} {context.Caller.MemberName}"); - } - } - """; + Recorder.Entries.Add($"exit {{label}} {context.Caller.MemberName}"); + } + } + """; /// /// The default interceptor logs without a label, so the single-interceptor tests read as /// "enter Sync" rather than repeating which interceptor produced the entry. /// - private static readonly string DefaultInterceptor = Tracing("Tracing", "").Replace(" {context.Caller", "{context.Caller"); - - private static string Source(string interfaceMember, string implementation, string? interceptor = null) => + private static readonly string DefaultInterceptor = Tracing("Tracing", "") + .Replace(" {context.Caller", "{context.Caller"); + + private static string Source( + string interfaceMember, + string implementation, + string? interceptor = null + ) => $$""" - {{Preamble}} + {{Preamble}} - {{interceptor ?? DefaultInterceptor}} + {{interceptor ?? DefaultInterceptor}} - public interface IWork { {{interfaceMember}} } + public interface IWork { {{interfaceMember}} } - [SingletonService] - [Intercept(typeof(TracingInterceptor))] - public class Work : IWork { {{implementation}} } + [SingletonService] + [Intercept(typeof(TracingInterceptor))] + public class Work : IWork { {{implementation}} } - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; } diff --git a/tests/DependencyModules.Tests/GeneratorTests/InterceptorLifetimeTests.cs b/tests/DependencyModules.Tests/GeneratorTests/InterceptorLifetimeTests.cs index 4fc3eee..f634233 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/InterceptorLifetimeTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/InterceptorLifetimeTests.cs @@ -17,10 +17,11 @@ namespace DependencyModules.Tests.GeneratorTests; /// decorators, but only somebody reading the generated code would ever find it. Naming the lifetime /// where the interception is declared says it out loud. /// -public class InterceptorLifetimeTests { - +public class InterceptorLifetimeTests +{ [Fact] - public void WithNoLifetimeNamed_TheInterceptorIsASingleton() { + public void WithNoLifetimeNamed_TheInterceptorIsASingleton() + { var interceptors = Run("[Intercept(typeof(CountingInterceptor))]"); Assert.Contains("TryAddSingleton", interceptors); @@ -30,9 +31,11 @@ public void WithNoLifetimeNamed_TheInterceptorIsASingleton() { [InlineData("Scoped", "TryAddScoped")] [InlineData("Transient", "TryAddTransient")] [InlineData("Singleton", "TryAddSingleton")] - public void ANamedLifetime_IsTheOneRegistered(string lifetime, string expected) { + public void ANamedLifetime_IsTheOneRegistered(string lifetime, string expected) + { var interceptors = Run( - $"[Intercept(typeof(CountingInterceptor), Lifetime = ServiceLifetime.{lifetime})]"); + $"[Intercept(typeof(CountingInterceptor), Lifetime = ServiceLifetime.{lifetime})]" + ); Assert.Contains(expected, interceptors); } @@ -42,9 +45,11 @@ public void ANamedLifetime_IsTheOneRegistered(string lifetime, string expected) /// not to add a second registration the container resolves ahead of the first. /// [Fact] - public void AScopedInterceptor_IsNotAlsoRegisteredAsASingleton() { + public void AScopedInterceptor_IsNotAlsoRegisteredAsASingleton() + { var interceptors = Run( - "[Intercept(typeof(CountingInterceptor), Lifetime = ServiceLifetime.Scoped)]"); + "[Intercept(typeof(CountingInterceptor), Lifetime = ServiceLifetime.Scoped)]" + ); Assert.DoesNotContain("TryAddSingleton", interceptors); } @@ -53,9 +58,11 @@ public void AScopedInterceptor_IsNotAlsoRegisteredAsASingleton() { /// The registration still has to work end to end, not merely be emitted with the right name. /// [Fact] - public void AScopedInterceptor_Runs() { + public void AScopedInterceptor_Runs() + { var generated = GeneratedAssembly.Create( - Source("[Intercept(typeof(CountingInterceptor), Lifetime = ServiceLifetime.Scoped)]")); + Source("[Intercept(typeof(CountingInterceptor), Lifetime = ServiceLifetime.Scoped)]") + ); var provider = generated.BuildProvider(); var greeter = provider.GetService(generated.Type("IGreeter"))!; @@ -65,29 +72,30 @@ public void AScopedInterceptor_Runs() { } private static string Run(string attribute) => - GeneratorTestHarness.Run(Source(attribute)) + GeneratorTestHarness + .Run(Source(attribute)) .AssertNoErrors() .SourceContaining("Interceptors"); private static string Source(string attribute) => $$""" - using DependencyModules.Runtime.Attributes; - using DependencyModules.Runtime.Interception; - using Microsoft.Extensions.DependencyInjection; + using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Interception; + using Microsoft.Extensions.DependencyInjection; - namespace TestNamespace; + namespace TestNamespace; - public interface IGreeter { string Greet(); } + public interface IGreeter { string Greet(); } - public sealed class CountingInterceptor : IInterceptor { - public TResult Intercept(InvocationContext context) => context.Proceed(); - } + public sealed class CountingInterceptor : IInterceptor { + public TResult Intercept(InvocationContext context) => context.Proceed(); + } - [SingletonService] - {{attribute}} - public sealed class Greeter : IGreeter { public string Greet() => "hi"; } + [SingletonService] + {{attribute}} + public sealed class Greeter : IGreeter { public string Greet() => "hi"; } - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; } diff --git a/tests/DependencyModules.Tests/GeneratorTests/MockTestExportDiagnosticTests.cs b/tests/DependencyModules.Tests/GeneratorTests/MockTestExportDiagnosticTests.cs index 53172d9..8bf09b2 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/MockTestExportDiagnosticTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/MockTestExportDiagnosticTests.cs @@ -16,15 +16,17 @@ namespace DependencyModules.Tests.GeneratorTests; /// everything under it, and one test overriding it for one argument is exactly what having both /// scopes is for. Reporting that would be reporting the feature. /// -public class MockTestExportDiagnosticTests { - +public class MockTestExportDiagnosticTests +{ [Fact] - public void BothOnOneMethod_ReportsDM0021() { + public void BothOnOneMethod_ReportsDM0021() + { var result = Run( """ [TestExport(typeof(IThing), Implementation = typeof(RealThing))] public void Conflicting([Mock] IThing thing) { } - """); + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0021"); @@ -37,12 +39,14 @@ public void Conflicting([Mock] IThing thing) { } /// to see what actually happens. /// [Fact] - public void ItIsReportedAtTheParameter() { + public void ItIsReportedAtTheParameter() + { var result = Run( """ [TestExport(typeof(IThing), Implementation = typeof(RealThing))] public void Conflicting([Mock] IThing thing) { } - """); + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0021"); @@ -54,32 +58,36 @@ public void Conflicting([Mock] IThing thing) { } /// The class-level default a test opts out of. This is the shape the override exists for. /// [Fact] - public void TestExportOnTheClass_IsNotReported() { + public void TestExportOnTheClass_IsNotReported() + { var result = GeneratorTestHarness.Run( $$""" - {{Preamble}} + {{Preamble}} - [TestExport(typeof(IThing), Implementation = typeof(RealThing))] - public class Fixture { - public void Overriding([Mock] IThing thing) { } - } - """); + [TestExport(typeof(IThing), Implementation = typeof(RealThing))] + public class Fixture { + public void Overriding([Mock] IThing thing) { } + } + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0021"); } [Fact] - public void TestExportOnTheAssembly_IsNotReported() { + public void TestExportOnTheAssembly_IsNotReported() + { var result = GeneratorTestHarness.Run( $$""" - {{Preamble}} + {{Preamble}} - [assembly: TestExport(typeof(IThing), Implementation = typeof(RealThing))] + [assembly: TestExport(typeof(IThing), Implementation = typeof(RealThing))] - public class Fixture { - public void Overriding([Mock] IThing thing) { } - } - """); + public class Fixture { + public void Overriding([Mock] IThing thing) { } + } + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0021"); } @@ -88,30 +96,35 @@ public void Overriding([Mock] IThing thing) { } /// Naming different services is two unrelated declarations, not a disagreement. /// [Fact] - public void BothOnOneMethodNamingDifferentServices_IsNotReported() { + public void BothOnOneMethodNamingDifferentServices_IsNotReported() + { var result = Run( """ [TestExport(typeof(IOther), Implementation = typeof(RealOther))] public void Unrelated([Mock] IThing thing) { } - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0021"); } [Fact] - public void AMockWithNoTestExport_IsNotReported() { + public void AMockWithNoTestExport_IsNotReported() + { var result = Run("public void JustAMock([Mock] IThing thing) { }"); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0021"); } [Fact] - public void ATestExportWithNoMock_IsNotReported() { + public void ATestExportWithNoMock_IsNotReported() + { var result = Run( """ [TestExport(typeof(IThing), Implementation = typeof(RealThing))] public void JustAnExport(IThing thing) { } - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0021"); } @@ -120,18 +133,19 @@ public void JustAnExport(IThing thing) { } /// Resolved rather than string-matched, the way every other attribute this generator reads is. /// [Fact] - public void QualifiedSpellings_AreStillReported() { + public void QualifiedSpellings_AreStillReported() + { var result = Run( """ [DependencyModules.Testing.Attributes.TestExport(typeof(IThing), Implementation = typeof(RealThing))] public void Conflicting([global::DependencyModules.Testing.Attributes.Mock] IThing thing) { } - """); + """ + ); Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0021"); } - private const string Preamble = - """ + private const string Preamble = """ using DependencyModules.Runtime.Attributes; using DependencyModules.Testing.Attributes; @@ -149,10 +163,11 @@ public class RealOther : IOther; private static GeneratorResult Run(string body) => GeneratorTestHarness.Run( $$""" - {{Preamble}} + {{Preamble}} - public class Fixture { - {{body}} - } - """); + public class Fixture { + {{body}} + } + """ + ); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/ModelComparerTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ModelComparerTests.cs index 913e1b6..9ac5d55 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ModelComparerTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ModelComparerTests.cs @@ -10,186 +10,300 @@ namespace DependencyModules.Tests.GeneratorTests; /// Every field that affects generated code must make two models compare unequal, or an edit to /// that field will serve stale output. /// -public class ModelComparerTests { - +public class ModelComparerTests +{ private readonly ModuleEntryPointModelComparer _entryPointComparer = new(); private readonly DependencyModuleConfigurationModelComparer _configurationComparer = new(); [Fact] - public void EntryPoints_BuiltFromTheSameValues_AreEqual() { - Assert.True(_entryPointComparer.Equals(ModelFactory.EntryPoint(), ModelFactory.EntryPoint())); + public void EntryPoints_BuiltFromTheSameValues_AreEqual() + { + Assert.True( + _entryPointComparer.Equals(ModelFactory.EntryPoint(), ModelFactory.EntryPoint()) + ); } [Fact] - public void EqualEntryPoints_ShareAHashCode() { + public void EqualEntryPoints_ShareAHashCode() + { Assert.Equal( _entryPointComparer.GetHashCode(ModelFactory.EntryPoint()), - _entryPointComparer.GetHashCode(ModelFactory.EntryPoint())); + _entryPointComparer.GetHashCode(ModelFactory.EntryPoint()) + ); } [Fact] - public void EntryPoints_BothNull_AreEqual() { + public void EntryPoints_BothNull_AreEqual() + { Assert.True(_entryPointComparer.Equals(null, null)); } [Fact] - public void EntryPoints_OneNull_AreNotEqual() { + public void EntryPoints_OneNull_AreNotEqual() + { Assert.False(_entryPointComparer.Equals(ModelFactory.EntryPoint(), null)); Assert.False(_entryPointComparer.Equals(null, ModelFactory.EntryPoint())); } [Fact] - public void EntryPoints_DifferingByFileLocation_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(fileLocation: "/project/A.cs"), - ModelFactory.EntryPoint(fileLocation: "/project/B.cs"))); + public void EntryPoints_DifferingByFileLocation_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(fileLocation: "/project/A.cs"), + ModelFactory.EntryPoint(fileLocation: "/project/B.cs") + ) + ); } [Fact] - public void EntryPoints_DifferingByType_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("Ns", "One")), - ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("Ns", "Two")))); + public void EntryPoints_DifferingByType_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("Ns", "One")), + ModelFactory.EntryPoint(entryPointType: TypeDefinition.Get("Ns", "Two")) + ) + ); } [Fact] - public void EntryPoints_DifferingByFeatures_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(features: ModuleEntryPointFeatures.None), - ModelFactory.EntryPoint(features: ModuleEntryPointFeatures.OnlyRealm))); + public void EntryPoints_DifferingByFeatures_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(features: ModuleEntryPointFeatures.None), + ModelFactory.EntryPoint(features: ModuleEntryPointFeatures.OnlyRealm) + ) + ); } [Fact] - public void EntryPoints_DifferingByUseMethod_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(useMethod: "UseOne"), - ModelFactory.EntryPoint(useMethod: "UseTwo"))); + public void EntryPoints_DifferingByUseMethod_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(useMethod: "UseOne"), + ModelFactory.EntryPoint(useMethod: "UseTwo") + ) + ); } [Fact] - public void EntryPoints_DifferingByRegistrationType_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(registrationType: RegistrationType.Add), - ModelFactory.EntryPoint(registrationType: RegistrationType.Try))); + public void EntryPoints_DifferingByRegistrationType_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(registrationType: RegistrationType.Add), + ModelFactory.EntryPoint(registrationType: RegistrationType.Try) + ) + ); } [Fact] - public void EntryPoints_DifferingByGenerateAttribute_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(generateAttribute: true), - ModelFactory.EntryPoint(generateAttribute: false))); + public void EntryPoints_DifferingByGenerateAttribute_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(generateAttribute: true), + ModelFactory.EntryPoint(generateAttribute: false) + ) + ); } [Fact] - public void EntryPoints_DifferingByJsonSerializerRegistration_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(registerJsonSerializers: true), - ModelFactory.EntryPoint(registerJsonSerializers: false))); + public void EntryPoints_DifferingByJsonSerializerRegistration_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(registerJsonSerializers: true), + ModelFactory.EntryPoint(registerJsonSerializers: false) + ) + ); } [Fact] - public void EntryPoints_DifferingByFactoryGeneration_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(generateFactories: true), - ModelFactory.EntryPoint(generateFactories: false))); + public void EntryPoints_DifferingByFactoryGeneration_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(generateFactories: true), + ModelFactory.EntryPoint(generateFactories: false) + ) + ); } [Fact] - public void EntryPoints_DifferingByParameters_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(parameters: [Parameter("one")]), - ModelFactory.EntryPoint(parameters: [Parameter("two")]))); + public void EntryPoints_DifferingByParameters_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(parameters: [Parameter("one")]), + ModelFactory.EntryPoint(parameters: [Parameter("two")]) + ) + ); } [Fact] - public void EntryPoints_WithSeparateButEqualParameters_AreEqual() { - Assert.True(_entryPointComparer.Equals( - ModelFactory.EntryPoint(parameters: [Parameter("same")]), - ModelFactory.EntryPoint(parameters: [Parameter("same")]))); + public void EntryPoints_WithSeparateButEqualParameters_AreEqual() + { + Assert.True( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(parameters: [Parameter("same")]), + ModelFactory.EntryPoint(parameters: [Parameter("same")]) + ) + ); } [Fact] - public void EntryPoints_DifferingByProperties_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(properties: [Property("One")]), - ModelFactory.EntryPoint(properties: [Property("Two")]))); + public void EntryPoints_DifferingByProperties_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(properties: [Property("One")]), + ModelFactory.EntryPoint(properties: [Property("Two")]) + ) + ); } [Fact] - public void EntryPoints_DifferingByAttributes_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(attributes: [Attribute("One")]), - ModelFactory.EntryPoint(attributes: [Attribute("Two")]))); + public void EntryPoints_DifferingByAttributes_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(attributes: [Attribute("One")]), + ModelFactory.EntryPoint(attributes: [Attribute("Two")]) + ) + ); } [Fact] - public void EntryPoints_WithSeparateButEqualAttributes_AreEqual() { - Assert.True(_entryPointComparer.Equals( - ModelFactory.EntryPoint(attributes: [Attribute("Same")]), - ModelFactory.EntryPoint(attributes: [Attribute("Same")]))); + public void EntryPoints_WithSeparateButEqualAttributes_AreEqual() + { + Assert.True( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(attributes: [Attribute("Same")]), + ModelFactory.EntryPoint(attributes: [Attribute("Same")]) + ) + ); } [Fact] - public void EntryPoints_DifferingByFeatureTypes_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(featureTypes: [TypeDefinition.Get("Ns", "IOne")]), - ModelFactory.EntryPoint(featureTypes: [TypeDefinition.Get("Ns", "ITwo")]))); + public void EntryPoints_DifferingByFeatureTypes_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(featureTypes: [TypeDefinition.Get("Ns", "IOne")]), + ModelFactory.EntryPoint(featureTypes: [TypeDefinition.Get("Ns", "ITwo")]) + ) + ); } [Fact] - public void EntryPoints_DifferingByAdditionalModules_AreNotEqual() { - Assert.False(_entryPointComparer.Equals( - ModelFactory.EntryPoint(additionalModules: [TypeDefinition.Get("Ns", "One")]), - ModelFactory.EntryPoint(additionalModules: [TypeDefinition.Get("Ns", "Two")]))); + public void EntryPoints_DifferingByAdditionalModules_AreNotEqual() + { + Assert.False( + _entryPointComparer.Equals( + ModelFactory.EntryPoint(additionalModules: [TypeDefinition.Get("Ns", "One")]), + ModelFactory.EntryPoint(additionalModules: [TypeDefinition.Get("Ns", "Two")]) + ) + ); } [Fact] - public void Configurations_BuiltFromTheSameValues_AreEqual() { - Assert.True(_configurationComparer.Equals(ModelFactory.Configuration(), ModelFactory.Configuration())); + public void Configurations_BuiltFromTheSameValues_AreEqual() + { + Assert.True( + _configurationComparer.Equals( + ModelFactory.Configuration(), + ModelFactory.Configuration() + ) + ); } [Fact] - public void SameConfigurationInstance_IsEqualToItself() { + public void SameConfigurationInstance_IsEqualToItself() + { var configuration = ModelFactory.Configuration(); Assert.True(_configurationComparer.Equals(configuration, configuration)); } [Fact] - public void Configurations_OneNull_AreNotEqual() { + public void Configurations_OneNull_AreNotEqual() + { Assert.False(_configurationComparer.Equals(ModelFactory.Configuration(), null)); Assert.False(_configurationComparer.Equals(null, ModelFactory.Configuration())); } [Fact] - public void EqualConfigurations_ShareAHashCode() { + public void EqualConfigurations_ShareAHashCode() + { Assert.Equal( _configurationComparer.GetHashCode(ModelFactory.Configuration()), - _configurationComparer.GetHashCode(ModelFactory.Configuration())); + _configurationComparer.GetHashCode(ModelFactory.Configuration()) + ); } [Theory] [MemberData(nameof(DifferingConfigurations))] public void Configurations_DifferingByAnyField_AreNotEqual( - string field, DependencyModuleConfigurationModel other) { - + string field, + DependencyModuleConfigurationModel other + ) + { Assert.False( _configurationComparer.Equals(ModelFactory.Configuration(), other), - $"Configurations differing by {field} compared equal, so a change to it would serve stale output."); + $"Configurations differing by {field} compared equal, so a change to it would serve stale output." + ); } - public static TheoryData DifferingConfigurations() => - new() { - { nameof(DependencyModuleConfigurationModel.RegistrationType), ModelFactory.Configuration(registrationType: RegistrationType.Try) }, - { nameof(DependencyModuleConfigurationModel.RegisterSourceGenerator), ModelFactory.Configuration(registerSourceGenerator: true) }, - { nameof(DependencyModuleConfigurationModel.RootNamespace), ModelFactory.Configuration(rootNamespace: "Other") }, - { nameof(DependencyModuleConfigurationModel.ProjectDir), ModelFactory.Configuration(projectDir: "/other/") }, - { nameof(DependencyModuleConfigurationModel.AutoGenerateEntry), ModelFactory.Configuration(autoGenerateEntry: false) }, - { nameof(DependencyModuleConfigurationModel.LogOutputFolder), ModelFactory.Configuration(logOutputFolder: "/logs") }, - { nameof(DependencyModuleConfigurationModel.LogOutputLevel), ModelFactory.Configuration(logOutputLevel: LogOutputLevel.Error) }, - { nameof(DependencyModuleConfigurationModel.GenerateFactories), ModelFactory.Configuration(generateFactories: true) }, - { nameof(DependencyModuleConfigurationModel.ExcludeGeneratedCodeFromCoverage), ModelFactory.Configuration(excludeGeneratedCodeFromCoverage: false) }, - { nameof(DependencyModuleConfigurationModel.GeneratedCodeStyle), ModelFactory.Configuration(generatedCodeStyle: BraceStyle.KAndR) } + public static TheoryData< + string, + DependencyModuleConfigurationModel + > DifferingConfigurations() => + new() + { + { + nameof(DependencyModuleConfigurationModel.RegistrationType), + ModelFactory.Configuration(registrationType: RegistrationType.Try) + }, + { + nameof(DependencyModuleConfigurationModel.RegisterSourceGenerator), + ModelFactory.Configuration(registerSourceGenerator: true) + }, + { + nameof(DependencyModuleConfigurationModel.RootNamespace), + ModelFactory.Configuration(rootNamespace: "Other") + }, + { + nameof(DependencyModuleConfigurationModel.ProjectDir), + ModelFactory.Configuration(projectDir: "/other/") + }, + { + nameof(DependencyModuleConfigurationModel.AutoGenerateEntry), + ModelFactory.Configuration(autoGenerateEntry: false) + }, + { + nameof(DependencyModuleConfigurationModel.LogOutputFolder), + ModelFactory.Configuration(logOutputFolder: "/logs") + }, + { + nameof(DependencyModuleConfigurationModel.LogOutputLevel), + ModelFactory.Configuration(logOutputLevel: LogOutputLevel.Error) + }, + { + nameof(DependencyModuleConfigurationModel.GenerateFactories), + ModelFactory.Configuration(generateFactories: true) + }, + { + nameof(DependencyModuleConfigurationModel.ExcludeGeneratedCodeFromCoverage), + ModelFactory.Configuration(excludeGeneratedCodeFromCoverage: false) + }, + { + nameof(DependencyModuleConfigurationModel.GeneratedCodeStyle), + ModelFactory.Configuration(generatedCodeStyle: BraceStyle.KAndR) + }, }; private readonly InterceptorModelComparer _interceptorComparer = new(); @@ -201,51 +315,69 @@ public static TheoryData DifferingCo /// compared theirs. /// [Fact] - public void Interceptors_DifferingByRealm_AreNotEqual() { - Assert.False(_interceptorComparer.Equals( - Interceptor(realm: TypeDefinition.Get("Ns", "OneRealm")), - Interceptor(realm: TypeDefinition.Get("Ns", "OtherRealm")))); + public void Interceptors_DifferingByRealm_AreNotEqual() + { + Assert.False( + _interceptorComparer.Equals( + Interceptor(realm: TypeDefinition.Get("Ns", "OneRealm")), + Interceptor(realm: TypeDefinition.Get("Ns", "OtherRealm")) + ) + ); } [Fact] - public void Interceptors_GainingARealm_AreNotEqual() { - Assert.False(_interceptorComparer.Equals( - Interceptor(), - Interceptor(realm: TypeDefinition.Get("Ns", "OneRealm")))); + public void Interceptors_GainingARealm_AreNotEqual() + { + Assert.False( + _interceptorComparer.Equals( + Interceptor(), + Interceptor(realm: TypeDefinition.Get("Ns", "OneRealm")) + ) + ); } [Fact] - public void Interceptors_BuiltFromTheSameValues_AreEqual() { + public void Interceptors_BuiltFromTheSameValues_AreEqual() + { Assert.True(_interceptorComparer.Equals(Interceptor(), Interceptor())); } [Fact] - public void EqualInterceptors_ShareAHashCode() { + public void EqualInterceptors_ShareAHashCode() + { Assert.Equal( _interceptorComparer.GetHashCode(Interceptor()), - _interceptorComparer.GetHashCode(Interceptor())); + _interceptorComparer.GetHashCode(Interceptor()) + ); } [Fact] - public void Interceptors_DifferingByRealm_DoNotShareAHashCode() { + public void Interceptors_DifferingByRealm_DoNotShareAHashCode() + { Assert.NotEqual( _interceptorComparer.GetHashCode(Interceptor()), - _interceptorComparer.GetHashCode(Interceptor(realm: TypeDefinition.Get("Ns", "OneRealm")))); + _interceptorComparer.GetHashCode( + Interceptor(realm: TypeDefinition.Get("Ns", "OneRealm")) + ) + ); } [Fact] - public void Interceptors_DifferingByOrder_AreNotEqual() { + public void Interceptors_DifferingByOrder_AreNotEqual() + { Assert.False(_interceptorComparer.Equals(Interceptor(order: 1), Interceptor(order: 2))); } private static InterceptorModel Interceptor(ITypeDefinition? realm = null, int order = 0) => - new(TypeDefinition.Get("Ns", "IService"), + new( + TypeDefinition.Get("Ns", "IService"), TypeDefinition.Get("Ns", "Service"), [], [], [], order, - Realm: realm); + Realm: realm + ); private static ParameterInfoModel Parameter(string name) => new(name, TypeDefinition.Get("Ns", "SomeType"), null, []); diff --git a/tests/DependencyModules.Tests/GeneratorTests/ModelEqualityTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ModelEqualityTests.cs index 36650dd..8bfabee 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ModelEqualityTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ModelEqualityTests.cs @@ -10,13 +10,14 @@ namespace DependencyModules.Tests.GeneratorTests; /// IReadOnlyList members by reference, which silently disables caching — these tests pin the /// structural semantics that replaced it. /// -public class ModelEqualityTests { - +public class ModelEqualityTests +{ private static readonly ITypeDefinition SomeType = TypeDefinition.Get("Ns", "SomeType"); private static readonly ITypeDefinition OtherType = TypeDefinition.Get("Ns", "OtherType"); [Fact] - public void AttributeModel_WithSeparateButEqualLists_IsEqual() { + public void AttributeModel_WithSeparateButEqualLists_IsEqual() + { var first = Attribute(arguments: [new AttributeArgumentValue("key", "value")]); var second = Attribute(arguments: [new AttributeArgumentValue("key", "value")]); @@ -25,12 +26,14 @@ public void AttributeModel_WithSeparateButEqualLists_IsEqual() { } [Fact] - public void AttributeModel_WithEmptyLists_IsEqual() { + public void AttributeModel_WithEmptyLists_IsEqual() + { Assert.Equal(Attribute(), Attribute()); } [Fact] - public void AttributeModel_WithDifferentArguments_IsNotEqual() { + public void AttributeModel_WithDifferentArguments_IsNotEqual() + { var first = Attribute(arguments: [new AttributeArgumentValue("key", "one")]); var second = Attribute(arguments: [new AttributeArgumentValue("key", "two")]); @@ -38,7 +41,8 @@ public void AttributeModel_WithDifferentArguments_IsNotEqual() { } [Fact] - public void AttributeModel_WithDifferentArgumentCounts_IsNotEqual() { + public void AttributeModel_WithDifferentArgumentCounts_IsNotEqual() + { var first = Attribute(arguments: [new AttributeArgumentValue("key", "one")]); var second = Attribute(); @@ -46,7 +50,8 @@ public void AttributeModel_WithDifferentArgumentCounts_IsNotEqual() { } [Fact] - public void AttributeModel_WithDifferentProperties_IsNotEqual() { + public void AttributeModel_WithDifferentProperties_IsNotEqual() + { var first = Attribute(properties: [new AttributeArgumentValue("P", 1)]); var second = Attribute(properties: [new AttributeArgumentValue("P", 2)]); @@ -54,7 +59,8 @@ public void AttributeModel_WithDifferentProperties_IsNotEqual() { } [Fact] - public void AttributeModel_WithDifferentImplementedInterfaces_IsNotEqual() { + public void AttributeModel_WithDifferentImplementedInterfaces_IsNotEqual() + { var first = Attribute(interfaces: [SomeType]); var second = Attribute(interfaces: [OtherType]); @@ -62,7 +68,8 @@ public void AttributeModel_WithDifferentImplementedInterfaces_IsNotEqual() { } [Fact] - public void AttributeModel_WithDifferentType_IsNotEqual() { + public void AttributeModel_WithDifferentType_IsNotEqual() + { var first = new AttributeModel(SomeType, [], [], []); var second = new AttributeModel(OtherType, [], [], []); @@ -70,7 +77,8 @@ public void AttributeModel_WithDifferentType_IsNotEqual() { } [Fact] - public void AttributeArgumentValue_WithEqualArrayValues_IsEqual() { + public void AttributeArgumentValue_WithEqualArrayValues_IsEqual() + { var first = new AttributeArgumentValue("names", new[] { "a", "b" }); var second = new AttributeArgumentValue("names", new[] { "a", "b" }); @@ -79,7 +87,8 @@ public void AttributeArgumentValue_WithEqualArrayValues_IsEqual() { } [Fact] - public void AttributeArgumentValue_WithDifferentArrayValues_IsNotEqual() { + public void AttributeArgumentValue_WithDifferentArrayValues_IsNotEqual() + { var first = new AttributeArgumentValue("names", new[] { "a", "b" }); var second = new AttributeArgumentValue("names", new[] { "a", "c" }); @@ -87,7 +96,8 @@ public void AttributeArgumentValue_WithDifferentArrayValues_IsNotEqual() { } [Fact] - public void AttributeArgumentValue_WithDifferentArrayLengths_IsNotEqual() { + public void AttributeArgumentValue_WithDifferentArrayLengths_IsNotEqual() + { var first = new AttributeArgumentValue("names", new[] { "a" }); var second = new AttributeArgumentValue("names", new[] { "a", "b" }); @@ -95,24 +105,30 @@ public void AttributeArgumentValue_WithDifferentArrayLengths_IsNotEqual() { } [Fact] - public void AttributeArgumentValue_WithDifferentNames_IsNotEqual() { + public void AttributeArgumentValue_WithDifferentNames_IsNotEqual() + { Assert.NotEqual( new AttributeArgumentValue("one", "value"), - new AttributeArgumentValue("two", "value")); + new AttributeArgumentValue("two", "value") + ); } [Fact] - public void AttributeArgumentValue_WithNullValues_IsEqual() { + public void AttributeArgumentValue_WithNullValues_IsEqual() + { Assert.Equal( new AttributeArgumentValue("key", null), - new AttributeArgumentValue("key", null)); + new AttributeArgumentValue("key", null) + ); } [Fact] - public void AttributeArgumentValue_NullVersusValue_IsNotEqual() { + public void AttributeArgumentValue_NullVersusValue_IsNotEqual() + { Assert.NotEqual( new AttributeArgumentValue("key", null), - new AttributeArgumentValue("key", "value")); + new AttributeArgumentValue("key", "value") + ); } /// @@ -120,14 +136,17 @@ public void AttributeArgumentValue_NullVersusValue_IsNotEqual() { /// char collection. /// [Fact] - public void AttributeArgumentValue_StringVersusCharArray_IsNotEqual() { + public void AttributeArgumentValue_StringVersusCharArray_IsNotEqual() + { Assert.NotEqual( new AttributeArgumentValue("key", "ab"), - new AttributeArgumentValue("key", new[] { 'a', 'b' })); + new AttributeArgumentValue("key", new[] { 'a', 'b' }) + ); } [Fact] - public void ParameterInfoModel_WithSeparateButEqualAttributes_IsEqual() { + public void ParameterInfoModel_WithSeparateButEqualAttributes_IsEqual() + { var first = new ParameterInfoModel("name", SomeType, null, [Attribute()]); var second = new ParameterInfoModel("name", SomeType, null, [Attribute()]); @@ -136,28 +155,35 @@ public void ParameterInfoModel_WithSeparateButEqualAttributes_IsEqual() { } [Fact] - public void ParameterInfoModel_WithDifferentNames_IsNotEqual() { + public void ParameterInfoModel_WithDifferentNames_IsNotEqual() + { Assert.NotEqual( new ParameterInfoModel("one", SomeType, null, []), - new ParameterInfoModel("two", SomeType, null, [])); + new ParameterInfoModel("two", SomeType, null, []) + ); } [Fact] - public void ParameterInfoModel_WithDifferentTypes_IsNotEqual() { + public void ParameterInfoModel_WithDifferentTypes_IsNotEqual() + { Assert.NotEqual( new ParameterInfoModel("name", SomeType, null, []), - new ParameterInfoModel("name", OtherType, null, [])); + new ParameterInfoModel("name", OtherType, null, []) + ); } [Fact] - public void ParameterInfoModel_WithDifferentDefaultValues_IsNotEqual() { + public void ParameterInfoModel_WithDifferentDefaultValues_IsNotEqual() + { Assert.NotEqual( new ParameterInfoModel("name", SomeType, 1, []), - new ParameterInfoModel("name", SomeType, 2, [])); + new ParameterInfoModel("name", SomeType, 2, []) + ); } [Fact] - public void ConstructorInfoModel_WithSeparateButEqualParameters_IsEqual() { + public void ConstructorInfoModel_WithSeparateButEqualParameters_IsEqual() + { var first = new ConstructorInfoModel([new ParameterInfoModel("a", SomeType, null, [])]); var second = new ConstructorInfoModel([new ParameterInfoModel("a", SomeType, null, [])]); @@ -166,41 +192,59 @@ public void ConstructorInfoModel_WithSeparateButEqualParameters_IsEqual() { } [Fact] - public void ConstructorInfoModel_WithDifferentParameters_IsNotEqual() { + public void ConstructorInfoModel_WithDifferentParameters_IsNotEqual() + { Assert.NotEqual( new ConstructorInfoModel([new ParameterInfoModel("a", SomeType, null, [])]), - new ConstructorInfoModel([new ParameterInfoModel("b", SomeType, null, [])])); + new ConstructorInfoModel([new ParameterInfoModel("b", SomeType, null, [])]) + ); } [Fact] - public void ServiceFactoryModel_WithSeparateButEqualParameters_IsEqual() { - var first = new ServiceFactoryModel(SomeType, "Create", [new ParameterInfoModel("a", SomeType, null, [])]); - var second = new ServiceFactoryModel(SomeType, "Create", [new ParameterInfoModel("a", SomeType, null, [])]); + public void ServiceFactoryModel_WithSeparateButEqualParameters_IsEqual() + { + var first = new ServiceFactoryModel( + SomeType, + "Create", + [new ParameterInfoModel("a", SomeType, null, [])] + ); + var second = new ServiceFactoryModel( + SomeType, + "Create", + [new ParameterInfoModel("a", SomeType, null, [])] + ); Assert.Equal(first, second); Assert.Equal(first.GetHashCode(), second.GetHashCode()); } [Fact] - public void ServiceFactoryModel_WithDifferentMethodNames_IsNotEqual() { + public void ServiceFactoryModel_WithDifferentMethodNames_IsNotEqual() + { Assert.NotEqual( new ServiceFactoryModel(SomeType, "Create", []), - new ServiceFactoryModel(SomeType, "Build", [])); + new ServiceFactoryModel(SomeType, "Build", []) + ); } [Fact] - public void ServiceFactoryModel_WithDifferentDeclaringTypes_IsNotEqual() { + public void ServiceFactoryModel_WithDifferentDeclaringTypes_IsNotEqual() + { Assert.NotEqual( new ServiceFactoryModel(SomeType, "Create", []), - new ServiceFactoryModel(OtherType, "Create", [])); + new ServiceFactoryModel(OtherType, "Create", []) + ); } private static AttributeModel Attribute( IReadOnlyList? arguments = null, IReadOnlyList? properties = null, - IReadOnlyList? interfaces = null) => - new(SomeType, + IReadOnlyList? interfaces = null + ) => + new( + SomeType, arguments ?? new List(), properties ?? new List(), - interfaces ?? new List()); + interfaces ?? new List() + ); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/ModuleEqualityDiagnosticTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ModuleEqualityDiagnosticTests.cs index f318bff..662136e 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ModuleEqualityDiagnosticTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ModuleEqualityDiagnosticTests.cs @@ -12,10 +12,11 @@ namespace DependencyModules.Tests.GeneratorTests; /// The generator has to pick an identity either way. This reports that it picked, so the choice is /// the developer's. /// -public class ModuleEqualityDiagnosticTests { - +public class ModuleEqualityDiagnosticTests +{ [Fact] - public void ASettableProperty_IsReported() { + public void ASettableProperty_IsReported() + { var result = Run("public int SizeLimit { get; set; }"); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0018"); @@ -29,28 +30,32 @@ public void ASettableProperty_IsReported() { /// attribute never assigns it — so there is no identity question to answer and nothing to report. /// [Fact] - public void AnExpressionBodiedProperty_IsNotReported() { + public void AnExpressionBodiedProperty_IsNotReported() + { var result = Run("""public string Value => "A";"""); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0018"); } [Fact] - public void AGetOnlyProperty_IsNotReported() { + public void AGetOnlyProperty_IsNotReported() + { var result = Run("public string Value { get; } = \"A\";"); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0018"); } [Fact] - public void AStaticProperty_IsNotReported() { + public void AStaticProperty_IsNotReported() + { var result = Run("public static int Shared { get; set; }"); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0018"); } [Fact] - public void NoPropertiesAtAll_IsNotReported() { + public void NoPropertiesAtAll_IsNotReported() + { var result = Run(""); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0018"); @@ -61,7 +66,8 @@ public void NoPropertiesAtAll_IsNotReported() { /// The generator already stands aside when a module declares its own Equals. /// [Fact] - public void DeclaringEquals_SilencesIt() { + public void DeclaringEquals_SilencesIt() + { var result = Run( """ public int SizeLimit { get; set; } @@ -70,14 +76,16 @@ public override bool Equals(object? obj) => obj is TestModule other && other.SizeLimit == SizeLimit; public override int GetHashCode() => SizeLimit; - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0018"); } /// A record gets its equality from the language, so it never faces the question. [Fact] - public void ARecordModule_IsNotReported() { + public void ARecordModule_IsNotReported() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -88,7 +96,8 @@ namespace TestNamespace; public partial record TestModule { public int SizeLimit { get; set; } } - """); + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0018"); } @@ -96,13 +105,14 @@ public partial record TestModule { private static GeneratorResult Run(string body) => GeneratorTestHarness.Run( $$""" - using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - [DependencyModule] - public partial class TestModule { - {{body}} - } - """); + [DependencyModule] + public partial class TestModule { + {{body}} + } + """ + ); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/ModuleGenerationSnapshotTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ModuleGenerationSnapshotTests.cs index 1cdd339..23722e6 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ModuleGenerationSnapshotTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ModuleGenerationSnapshotTests.cs @@ -12,10 +12,11 @@ namespace DependencyModules.Tests.GeneratorTests; /// UPDATE_SNAPSHOTS=1 dotnet test tests/DependencyModules.Tests /// then review the diff under tests/DependencyModules.Tests/Snapshots. /// -public class ModuleGenerationSnapshotTests { - +public class ModuleGenerationSnapshotTests +{ [Fact] - public void SimpleModule() { + public void SimpleModule() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -29,7 +30,8 @@ public class Thing : IThing; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); Snapshot.Match(result.ToSnapshot()); @@ -45,7 +47,8 @@ public partial class TestModule; /// descriptor and the conditional registration is the override. /// [Fact] - public void ModuleWithEnvironmentConditions() { + public void ModuleWithEnvironmentConditions() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -73,14 +76,16 @@ public class Billing : IBilling; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); Snapshot.Match(result.ToSnapshot()); } [Fact] - public void ModuleWithAllServiceLifetimes() { + public void ModuleWithAllServiceLifetimes() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -102,14 +107,16 @@ public class TransientThing : ITransient; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); Snapshot.Match(result.ToSnapshot()); } [Fact] - public void ModuleWithConstructorParametersAndProperties() { + public void ModuleWithConstructorParametersAndProperties() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -130,14 +137,16 @@ public partial class TestModule(bool someFlag) : IServiceCollectionConfiguration public void ConfigureServices(IServiceCollection services) { } } - """); + """ + ); result.AssertNoErrors(); Snapshot.Match(result.ToSnapshot()); } [Fact] - public void KeyedAndAsRegistrations() { + public void KeyedAndAsRegistrations() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -155,14 +164,16 @@ public class AsThing : IThing, IOther; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); Snapshot.Match(result.ToSnapshot()); } [Fact] - public void RegistrationTypeVariants() { + public void RegistrationTypeVariants() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -184,14 +195,16 @@ public class ReplaceThing : IReplace; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); Snapshot.Match(result.ToSnapshot()); } [Fact] - public void RecordModule() { + public void RecordModule() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -205,14 +218,16 @@ public class Thing : IThing; [DependencyModule] public partial record TestModule; - """); + """ + ); result.AssertNoErrors(); Snapshot.Match(result.ToSnapshot()); } [Fact] - public void GenericServiceRegistrations() { + public void GenericServiceRegistrations() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -229,14 +244,16 @@ public class ClosedGeneric : IGeneric; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); Snapshot.Match(result.ToSnapshot()); } [Fact] - public void ModuleWithCoverageExclusionDisabled() { + public void ModuleWithCoverageExclusionDisabled() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -251,7 +268,8 @@ public class Thing : IThing; [DependencyModule] public partial class TestModule; """, - new Dictionary { ["ExcludeGeneratedCodeFromCoverage"] = "false" }); + new Dictionary { ["ExcludeGeneratedCodeFromCoverage"] = "false" } + ); result.AssertNoErrors(); Snapshot.Match(result.ToSnapshot()); diff --git a/tests/DependencyModules.Tests/GeneratorTests/ModuleParameterAccessibilityTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ModuleParameterAccessibilityTests.cs index 2f35473..939cffe 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ModuleParameterAccessibilityTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ModuleParameterAccessibilityTests.cs @@ -17,14 +17,15 @@ namespace DependencyModules.Tests.GeneratorTests; /// Accessibility is judged from where the generated attribute sits: same assembly, different type. /// So internal reaches it and private does not, and an unmodified property is private by default. /// -public class ModuleParameterAccessibilityTests { - +public class ModuleParameterAccessibilityTests +{ [Theory] [InlineData("private int SizeLimit { get; set; }")] [InlineData("protected int SizeLimit { get; set; }")] [InlineData("private protected int SizeLimit { get; set; }")] [InlineData("int SizeLimit { get; set; }")] - public void APropertyTheAttributeCannotReach_CompilesCleanly(string property) { + public void APropertyTheAttributeCannotReach_CompilesCleanly(string property) + { Run(property).AssertNoErrors(); } @@ -33,7 +34,8 @@ public void APropertyTheAttributeCannotReach_CompilesCleanly(string property) { [InlineData("protected int SizeLimit { get; set; }")] [InlineData("private protected int SizeLimit { get; set; }")] [InlineData("int SizeLimit { get; set; }")] - public void APropertyTheAttributeCannotReach_IsNotCopiedOntoIt(string property) { + public void APropertyTheAttributeCannotReach_IsNotCopiedOntoIt(string property) + { var attribute = Run(property).SourceContaining("TestModule.Module"); Assert.DoesNotContain("SizeLimit", attribute); @@ -48,7 +50,8 @@ public void APropertyTheAttributeCannotReach_IsNotCopiedOntoIt(string property) [InlineData("protected int SizeLimit { get; set; }")] [InlineData("private protected int SizeLimit { get; set; }")] [InlineData("int SizeLimit { get; set; }")] - public void APropertyTheAttributeCannotReach_IsNotReportedAsDM0018(string property) { + public void APropertyTheAttributeCannotReach_IsNotReportedAsDM0018(string property) + { var result = Run(property); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0018"); @@ -62,7 +65,8 @@ public void APropertyTheAttributeCannotReach_IsNotReportedAsDM0018(string proper [InlineData("public int SizeLimit { get; set; }")] [InlineData("internal int SizeLimit { get; set; }")] [InlineData("protected internal int SizeLimit { get; set; }")] - public void AReachableProperty_IsStillAParameter(string property) { + public void AReachableProperty_IsStillAParameter(string property) + { var result = Run(property).AssertNoErrors(); Assert.Contains("SizeLimit", result.SourceContaining("TestModule.Module")); @@ -72,13 +76,14 @@ public void AReachableProperty_IsStillAParameter(string property) { private static GeneratorResult Run(string body) => GeneratorTestHarness.Run( $$""" - using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - [DependencyModule] - public partial class TestModule { - {{body}} - } - """); + [DependencyModule] + public partial class TestModule { + {{body}} + } + """ + ); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/ReferencedAssemblyModuleAttributeTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ReferencedAssemblyModuleAttributeTests.cs index d4ae220..0c57f77 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ReferencedAssemblyModuleAttributeTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ReferencedAssemblyModuleAttributeTests.cs @@ -16,10 +16,9 @@ namespace DependencyModules.Tests.GeneratorTests; /// its DM0019 example — using MyApp.Library; above [assembly: LibraryModule] — and it /// did not fire, because both codes were built only from the modules declared in this compilation. /// -public class ReferencedAssemblyModuleAttributeTests { - - private const string LibrarySource = - """ +public class ReferencedAssemblyModuleAttributeTests +{ + private const string LibrarySource = """ using DependencyModules.Runtime.Attributes; namespace ThePackage.Composition; @@ -38,13 +37,15 @@ public partial class LibraryModule; /// file the generated ApplicationModule was not built from, so nothing reads it. /// [Fact] - public void AnAssemblyAttributeOutsideTheEntryPointFile_ReportsDM0019() { + public void AnAssemblyAttributeOutsideTheEntryPointFile_ReportsDM0019() + { var result = Run( bootstrap: """ - using ThePackage.Composition; + using ThePackage.Composition; - [assembly: LibraryModule] - """); + [assembly: LibraryModule] + """ + ); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0019"); @@ -53,14 +54,16 @@ public void AnAssemblyAttributeOutsideTheEntryPointFile_ReportsDM0019() { } [Fact] - public void AnAssemblyAttributeInTheEntryPointFile_IsSilent() { + public void AnAssemblyAttributeInTheEntryPointFile_IsSilent() + { var result = Run( bootstrap: null, programExtra: """ - using ThePackage.Composition; + using ThePackage.Composition; - [assembly: LibraryModule] - """); + [assembly: LibraryModule] + """ + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0019"); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0016"); @@ -72,7 +75,8 @@ public void AnAssemblyAttributeInTheEntryPointFile_IsSilent() { /// turns that into the one-line fix. /// [Fact] - public void AnAssemblyAttributeWithoutItsNamespaceImported_ReportsDM0016() { + public void AnAssemblyAttributeWithoutItsNamespaceImported_ReportsDM0016() + { var result = Run(bootstrap: "[assembly: LibraryModule]"); var diagnostic = Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0016"); @@ -86,7 +90,8 @@ public void AnAssemblyAttributeWithoutItsNamespaceImported_ReportsDM0016() { /// the in-compilation path already reports in. /// [Fact] - public void AnAssemblyAttributeWithoutItsNamespaceImported_DoesNotAlsoReportDM0019() { + public void AnAssemblyAttributeWithoutItsNamespaceImported_DoesNotAlsoReportDM0019() + { var result = Run(bootstrap: "[assembly: LibraryModule]"); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0019"); @@ -96,12 +101,15 @@ public void AnAssemblyAttributeWithoutItsNamespaceImported_DoesNotAlsoReportDM00 /// A global using satisfies the import wherever it is written, exactly as for a local module. /// [Fact] - public void AGlobalUsingSatisfiesTheImport() { + public void AGlobalUsingSatisfiesTheImport() + { var result = Run( bootstrap: "[assembly: LibraryModule]", - extraFiles: new Dictionary { - ["Usings.cs"] = "global using ThePackage.Composition;" - }); + extraFiles: new Dictionary + { + ["Usings.cs"] = "global using ThePackage.Composition;", + } + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0016"); Assert.Single(result.GeneratorDiagnostics, d => d.Id == "DM0019"); @@ -113,18 +121,25 @@ public void AGlobalUsingSatisfiesTheImport() { /// the same shape, and that is where these attributes are supposed to live in their own file. /// [Fact] - public void AClassLibrary_IsSilent() { - var library = GeneratorTestHarness.CompileLibrary(LibrarySource, "TheModulePackage", runGenerator: true); + public void AClassLibrary_IsSilent() + { + var library = GeneratorTestHarness.CompileLibrary( + LibrarySource, + "TheModulePackage", + runGenerator: true + ); var result = GeneratorTestHarness.Run( - new Dictionary { + new Dictionary + { ["Bootstrap.cs"] = """ - using ThePackage.Composition; + using ThePackage.Composition; - [assembly: LibraryModule] - """ + [assembly: LibraryModule] + """, }, - additionalReferences: [library.Reference]); + additionalReferences: [library.Reference] + ); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0019"); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0016"); @@ -135,7 +150,8 @@ public void AClassLibrary_IsSilent() { /// what keeps this from reporting on every unrelated assembly-level attribute in the project. /// [Fact] - public void AnAttributeThatNamesNoModule_IsSilent() { + public void AnAttributeThatNamesNoModule_IsSilent() + { var result = Run(bootstrap: "[assembly: System.CLSCompliant(true)]"); Assert.DoesNotContain(result.GeneratorDiagnostics, d => d.Id == "DM0016"); @@ -145,27 +161,36 @@ public void AnAttributeThatNamesNoModule_IsSilent() { private static GeneratorResult Run( string? bootstrap, string? programExtra = null, - IReadOnlyDictionary? extraFiles = null) { - - var library = GeneratorTestHarness.CompileLibrary(LibrarySource, "TheModulePackage", runGenerator: true); - - var sources = new Dictionary { + IReadOnlyDictionary? extraFiles = null + ) + { + var library = GeneratorTestHarness.CompileLibrary( + LibrarySource, + "TheModulePackage", + runGenerator: true + ); + + var sources = new Dictionary + { // Top-level statements, so an ApplicationModule is generated and there is an entry // point file for DM0019 to measure against. - ["Program.cs"] = (programExtra ?? "") + "\nSystem.Console.WriteLine(\"hello\");" + ["Program.cs"] = (programExtra ?? "") + "\nSystem.Console.WriteLine(\"hello\");", }; - if (bootstrap != null) { + if (bootstrap != null) + { sources["Bootstrap.cs"] = bootstrap; } - foreach (var extra in extraFiles ?? new Dictionary()) { + foreach (var extra in extraFiles ?? new Dictionary()) + { sources[extra.Key] = extra.Value; } return GeneratorTestHarness.Run( sources, outputKind: OutputKind.ConsoleApplication, - additionalReferences: [library.Reference]); + additionalReferences: [library.Reference] + ); } } diff --git a/tests/DependencyModules.Tests/GeneratorTests/ReferencedAssemblyScanTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ReferencedAssemblyScanTests.cs index df333c3..42e4497 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ReferencedAssemblyScanTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ReferencedAssemblyScanTests.cs @@ -12,10 +12,9 @@ namespace DependencyModules.Tests.GeneratorTests; /// syntax tree for any of its types. That is the whole point: this path reads symbols out of /// metadata, where the in-compilation path reads declarations. /// -public class ReferencedAssemblyScanTests { - - private const string LibrarySource = - """ +public class ReferencedAssemblyScanTests +{ + private const string LibrarySource = """ namespace ThePackage; public interface IHandler { } @@ -38,8 +37,7 @@ private UnconstructableHandler() { } public class Unrelated { } """; - private const string Preamble = - """ + private const string Preamble = """ using DependencyModules.Runtime.Attributes; using DependencyModules.Runtime.Conventions; using ThePackage; @@ -49,18 +47,20 @@ namespace TestNamespace; """; private static (GeneratorResult Result, GeneratedAssembly? Assembly) Run( - string module, bool compile = true) { - + string module, + bool compile = true + ) + { var library = GeneratorTestHarness.CompileLibrary(LibrarySource, "ThePackage"); var references = new[] { library.Reference }; var result = GeneratorTestHarness.Run( new Dictionary { ["Test.cs"] = Preamble + module }, - additionalReferences: references); + additionalReferences: references + ); var assembly = compile - ? GeneratedAssembly.Create( - Preamble + module, additionalReferences: references) + ? GeneratedAssembly.Create(Preamble + module, additionalReferences: references) : null; return (result, assembly); @@ -71,7 +71,8 @@ private static (GeneratorResult Result, GeneratedAssembly? Assembly) Run( /// the closed construction it actually implements. /// [Fact] - public void RegistersTypesFromAReferencedAssembly() { + public void RegistersTypesFromAReferencedAssembly() + { var (result, assembly) = Run( """ [DependencyModule] @@ -82,16 +83,17 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { .AsScoped(); } } - """); + """ + ); result.AssertNoErrors(); - var handlerType = assembly!.Services - .Select(d => d.ServiceType) + var handlerType = assembly! + .Services.Select(d => d.ServiceType) .First(t => t.Name == "IHandler`2"); - var registered = assembly.Services - .Where(d => d.ServiceType.Name == "IHandler`2") + var registered = assembly + .Services.Where(d => d.ServiceType.Name == "IHandler`2") .Select(d => d.ImplementationType!.Name) .OrderBy(name => name) .ToArray(); @@ -105,7 +107,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// the same way it would be in the compilation being built. /// [Fact] - public void SkipsWhatItCannotSeeOrConstruct() { + public void SkipsWhatItCannotSeeOrConstruct() + { var (result, assembly) = Run( """ [DependencyModule] @@ -116,10 +119,11 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { .AsScoped(); } } - """); + """ + ); - var registered = assembly!.Services - .Where(d => d.ServiceType.Name == "IHandler`2") + var registered = assembly! + .Services.Where(d => d.ServiceType.Name == "IHandler`2") .Select(d => d.ImplementationType!.Name) .ToArray(); @@ -134,7 +138,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// A registered service from the package resolves. /// [Fact] - public void TheRegistrationsResolve() { + public void TheRegistrationsResolve() + { var (_, assembly) = Run( """ [DependencyModule] @@ -145,11 +150,12 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { .AsScoped(); } } - """); + """ + ); - var descriptor = assembly!.Services.First( - d => d.ServiceType.Name == "IHandler`2" && - d.ImplementationType!.Name == "CreateOrderHandler"); + var descriptor = assembly!.Services.First(d => + d.ServiceType.Name == "IHandler`2" && d.ImplementationType!.Name == "CreateOrderHandler" + ); var provider = assembly.BuildProvider(); @@ -161,7 +167,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// one that names none must not reach into the package. /// [Fact] - public void AConventionSeesOneSourceOnly() { + public void AConventionSeesOneSourceOnly() + { var (_, assembly) = Run( """ public class LocalHandler : IHandler { } @@ -174,10 +181,11 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { .AsScoped(); } } - """); + """ + ); - var registered = assembly!.Services - .Where(d => d.ServiceType.Name == "IHandler`2") + var registered = assembly! + .Services.Where(d => d.ServiceType.Name == "IHandler`2") .Select(d => d.ImplementationType!.Name) .ToArray(); @@ -188,7 +196,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// Absent the call, a convention scans the compilation being built — unchanged behaviour. /// [Fact] - public void WithoutTheCallOnlyLocalTypesMatch() { + public void WithoutTheCallOnlyLocalTypesMatch() + { var (_, assembly) = Run( """ public class LocalHandler : IHandler { } @@ -199,10 +208,11 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { conventions.RegisterAll(typeof(IHandler<,>)).AsScoped(); } } - """); + """ + ); - var registered = assembly!.Services - .Where(d => d.ServiceType.Name == "IHandler`2") + var registered = assembly! + .Services.Where(d => d.ServiceType.Name == "IHandler`2") .Select(d => d.ImplementationType!.Name) .ToArray(); @@ -214,7 +224,8 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { /// the convention that asked for it rather than nowhere. /// [Fact] - public void DiagnosticsForMetadataMatchesReportAtTheConvention() { + public void DiagnosticsForMetadataMatchesReportAtTheConvention() + { var (result, _) = Run( """ [DependencyModule] @@ -226,23 +237,29 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { } } """, - compile: false); + compile: false + ); var exposures = result.GeneratorDiagnostics.Where(d => d.Id == "DM0010").ToArray(); Assert.NotEmpty(exposures); - Assert.All(exposures, diagnostic => { - Assert.NotEqual(Location.None, diagnostic.Location); - Assert.Contains("Test.cs", diagnostic.Location.GetLineSpan().Path); - }); + Assert.All( + exposures, + diagnostic => + { + Assert.NotEqual(Location.None, diagnostic.Location); + Assert.Contains("Test.cs", diagnostic.Location.GetLineSpan().Path); + } + ); } /// /// Filters apply to metadata types the same way they apply to local ones. /// [Fact] - public void FiltersApplyToMetadataTypes() { + public void FiltersApplyToMetadataTypes() + { var (_, assembly) = Run( """ [DependencyModule] @@ -254,10 +271,11 @@ void IConventionModule.Conventions(IConventionDefinitions conventions) { .AsScoped(); } } - """); + """ + ); - var registered = assembly!.Services - .Where(d => d.ServiceType.Name == "IHandler`2") + var registered = assembly! + .Services.Where(d => d.ServiceType.Name == "IHandler`2") .Select(d => d.ImplementationType!.Name) .ToArray(); diff --git a/tests/DependencyModules.Tests/GeneratorTests/RobustnessTests.cs b/tests/DependencyModules.Tests/GeneratorTests/RobustnessTests.cs index 864aa0f..0b4c37e 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/RobustnessTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/RobustnessTests.cs @@ -14,8 +14,8 @@ namespace DependencyModules.Tests.GeneratorTests; /// Each of these is something a real application does and something the generator could plausibly /// lose quietly — a registration that never happens reads exactly like one that was never asked for. /// -public class RobustnessTests { - +public class RobustnessTests +{ private static string Call(object target, string method) => (string)target.GetType().GetMethod(method)!.Invoke(target, null)!; @@ -26,7 +26,8 @@ private static string Call(object target, string method) => /// singleton, which is the kind of thing found much later. /// [Fact] - public void Module_AddedTwice_RegistersItsServicesOnce() { + public void Module_AddedTwice_RegistersItsServicesOnce() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -40,15 +41,18 @@ public class Greeter : IGreeter { public string Greet() => "hello"; } [DependencyModule] public partial class TestModule; - """); + """ + ); - var module = (IDependencyModule)System.Activator.CreateInstance(assembly.Type("TestModule"))!; + var module = (IDependencyModule) + System.Activator.CreateInstance(assembly.Type("TestModule"))!; var services = new ServiceCollection(); services.AddModules(module, module); Assert.Single( - services.BuildServiceProvider().GetServices(assembly.Type("IGreeter")).Cast()); + services.BuildServiceProvider().GetServices(assembly.Type("IGreeter")).Cast() + ); } /// Two environment conditions on one service combine with and. @@ -57,8 +61,11 @@ public partial class TestModule; [InlineData("Development", false, 0)] [InlineData("Production", true, 0)] public void Service_WithTwoConditions_RegistersOnlyWhenBothHold( - string environment, bool flag, int expected) { - + string environment, + bool flag, + int expected + ) + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -77,16 +84,22 @@ public partial class TestModule; """, environment: new ModuleEnvironment( environment, - flag ? new Dictionary { ["feature"] = "on" } : new Dictionary())); + flag + ? new Dictionary { ["feature"] = "on" } + : new Dictionary() + ) + ); Assert.Equal( expected, - assembly.BuildProvider().GetServices(assembly.Type("IGreeter")).Cast().Count()); + assembly.BuildProvider().GetServices(assembly.Type("IGreeter")).Cast().Count() + ); } /// An explicit service attribute wins over a convention that also matches. [Fact] - public void Convention_DoesNotAlsoRegisterAnAttributedType() { + public void Convention_DoesNotAlsoRegisterAnAttributedType() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -105,15 +118,18 @@ public void Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton(); } } - """); + """ + ); Assert.Single( - assembly.BuildProvider().GetServices(assembly.Type("IGreeter")).Cast()); + assembly.BuildProvider().GetServices(assembly.Type("IGreeter")).Cast() + ); } /// A keyed and an unkeyed registration of one service coexist. [Fact] - public void Service_KeyedAndUnkeyed_AreBothResolvable() { + public void Service_KeyedAndUnkeyed_AreBothResolvable() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -130,7 +146,8 @@ public class Loud : IGreeter { public string Greet() => "LOUD"; } [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = assembly.BuildProvider(); var greeter = assembly.Type("IGreeter"); @@ -141,7 +158,8 @@ public partial class TestModule; /// A cross-wired generic service shares one instance across its interfaces. [Fact] - public void CrossWire_SharesOneInstanceAcrossServiceTypes() { + public void CrossWire_SharesOneInstanceAcrossServiceTypes() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -160,18 +178,21 @@ public class Store : IReader, IWriter { [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = assembly.BuildProvider(); Assert.Equal( Call(provider.GetRequiredService(assembly.Type("IReader")), "Read"), - Call(provider.GetRequiredService(assembly.Type("IWriter")), "Write")); + Call(provider.GetRequiredService(assembly.Type("IWriter")), "Write") + ); } /// A convention excluding a namespace does not register from it. [Fact] - public void Convention_NotInNamespaces_ExcludesThatNamespace() { + public void Convention_NotInNamespaces_ExcludesThatNamespace() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -197,17 +218,23 @@ public class Kept : TestNamespace.IGreeter { public string Greet() => "kept"; } namespace TestNamespace.Excluded { public class Dropped : TestNamespace.IGreeter { public string Greet() => "dropped"; } } - """); + """ + ); - var all = assembly.BuildProvider().GetServices(assembly.Type("IGreeter")) - .Cast().Select(g => Call(g, "Greet")).ToArray(); + var all = assembly + .BuildProvider() + .GetServices(assembly.Type("IGreeter")) + .Cast() + .Select(g => Call(g, "Greet")) + .ToArray(); Assert.Equal(["kept"], all); } /// An interceptor sees an async method through to its result. [Fact] - public async Task Interceptor_OverAnAsyncMethod() { + public async Task Interceptor_OverAnAsyncMethod() + { var assembly = GeneratedAssembly.Create( """ using System.Threading.Tasks; @@ -238,7 +265,8 @@ public async ValueTask InterceptAsync( [DependencyModule] public partial class TestModule; - """); + """ + ); var fetcher = assembly.BuildProvider().GetRequiredService(assembly.Type("IFetcher")); @@ -250,7 +278,8 @@ public partial class TestModule; /// A service depending on a collection of a service gets every registration. [Fact] - public void Service_DependingOnAnEnumerableOfAService_GetsAllOfThem() { + public void Service_DependingOnAnEnumerableOfAService_GetsAllOfThem() + { var assembly = GeneratedAssembly.Create( """ using System.Collections.Generic; @@ -274,10 +303,13 @@ public class Engine(IEnumerable rules) { [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("first,second", Call( - assembly.BuildProvider().GetRequiredService(assembly.Type("Engine")), "Describe")); + Assert.Equal( + "first,second", + Call(assembly.BuildProvider().GetRequiredService(assembly.Type("Engine")), "Describe") + ); } // ------------------------------------------------------------------------------------------ @@ -287,7 +319,8 @@ public partial class TestModule; /// An explicitly named service type is the one registered. [Fact] - public void Service_WithAnExplicitServiceType_RegistersThatOne() { + public void Service_WithAnExplicitServiceType_RegistersThatOne() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -305,7 +338,8 @@ public class Store : IReader, IWriter { [DependencyModule] public partial class TestModule; - """); + """ + ); var provider = assembly.BuildProvider(); @@ -322,7 +356,8 @@ public partial class TestModule; /// also a service is a policy question; what must not happen is the declared one going missing. /// [Fact] - public void Service_ImplementingADerivedInterface_RegistersTheDeclaredOne() { + public void Service_ImplementingADerivedInterface_RegistersTheDeclaredOne() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -337,15 +372,19 @@ public class Store : IAudited { public string Read() => "read"; } [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("read", Call( - assembly.BuildProvider().GetRequiredService(assembly.Type("IAudited")), "Read")); + Assert.Equal( + "read", + Call(assembly.BuildProvider().GetRequiredService(assembly.Type("IAudited")), "Read") + ); } /// A generic implementation registers as the open generic it closes nothing of. [Fact] - public void Service_GenericImplementation_RegistersAsAnOpenGeneric() { + public void Service_GenericImplementation_RegistersAsAnOpenGeneric() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -359,7 +398,8 @@ public class Repo : IRepo { public string Name() => "repo"; } [DependencyModule] public partial class TestModule; - """); + """ + ); var closed = assembly.Type("IRepo`1").MakeGenericType(typeof(string)); @@ -372,7 +412,8 @@ public partial class TestModule; /// from the declaration responsible. /// [Fact] - public void Service_ThatIsAbstract_IsReported() { + public void Service_ThatIsAbstract_IsReported() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -386,7 +427,8 @@ public abstract class Greeter : IGreeter { public abstract string Greet(); } [DependencyModule] public partial class TestModule; - """); + """ + ); Assert.Empty(result.Errors); Assert.Contains(result.GeneratorDiagnostics, d => d.Id == "DM0002"); @@ -394,7 +436,8 @@ public partial class TestModule; /// A record registers like any other class. [Fact] - public void Service_DeclaredAsARecord() { + public void Service_DeclaredAsARecord() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -408,15 +451,19 @@ public record Greeter : IGreeter { public string Greet() => "hello"; } [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("hello", Call( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "hello", + Call(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// A service nested inside another type registers under its nested name. [Fact] - public void Service_NestedInsideAnotherType() { + public void Service_NestedInsideAnotherType() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -432,15 +479,19 @@ public class Greeter : IGreeter { public string Greet() => "hello"; } [DependencyModule] public partial class TestModule; - """); + """ + ); - Assert.Equal("hello", Call( - assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "hello", + Call(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); } /// Replace leaves one registration standing, not two. [Fact] - public void Service_RegisteredWithReplace_LeavesOne() { + public void Service_RegisteredWithReplace_LeavesOne() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -457,17 +508,23 @@ public class Second : IGreeter { public string Greet() => "second"; } [DependencyModule] public partial class TestModule; - """); + """ + ); - var all = assembly.BuildProvider().GetServices(assembly.Type("IGreeter")) - .Cast().Select(g => Call(g, "Greet")).ToArray(); + var all = assembly + .BuildProvider() + .GetServices(assembly.Type("IGreeter")) + .Cast() + .Select(g => Call(g, "Greet")) + .ToArray(); Assert.Equal(["second"], all); } /// A service whose only constructor is private is reported rather than registered. [Fact] - public void Service_WithNoAccessibleConstructor_IsReported() { + public void Service_WithNoAccessibleConstructor_IsReported() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -488,7 +545,8 @@ public void Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().AsSingleton(); } } - """); + """ + ); Assert.Contains(result.GeneratorDiagnostics, d => d.Id == "DM0006"); } @@ -499,8 +557,7 @@ public void Conventions(IConventionDefinitions conventions) { // refused with a diagnostic rather than mis-generated; these check both halves. // ------------------------------------------------------------------------------------------ - private const string InterceptorPreamble = - """ + private const string InterceptorPreamble = """ using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -522,128 +579,164 @@ public TResult Intercept(InvocationContext context) { """; private static GeneratorResult RunIntercepted(string body) => - GeneratorTestHarness.Run(InterceptorPreamble + body + """ + GeneratorTestHarness.Run( + InterceptorPreamble + + body + + """ - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); /// A void method is forwarded. [Fact] - public void Interceptor_OverAVoidMethod() { - Assert.Empty(RunIntercepted( - """ - public interface IWorker { void Work(); } + public void Interceptor_OverAVoidMethod() + { + Assert.Empty( + RunIntercepted( + """ + public interface IWorker { void Work(); } - [SingletonService] - [Intercept(typeof(CountingInterceptor))] - public class Worker : IWorker { public void Work() { } } - """).Errors); + [SingletonService] + [Intercept(typeof(CountingInterceptor))] + public class Worker : IWorker { public void Work() { } } + """ + ).Errors + ); } /// A property is forwarded. [Fact] - public void Interceptor_OverAProperty() { - Assert.Empty(RunIntercepted( - """ - public interface IWorker { string Name { get; set; } } + public void Interceptor_OverAProperty() + { + Assert.Empty( + RunIntercepted( + """ + public interface IWorker { string Name { get; set; } } - [SingletonService] - [Intercept(typeof(CountingInterceptor))] - public class Worker : IWorker { public string Name { get; set; } = ""; } - """).Errors); + [SingletonService] + [Intercept(typeof(CountingInterceptor))] + public class Worker : IWorker { public string Name { get; set; } = ""; } + """ + ).Errors + ); } /// An indexer is forwarded. [Fact] - public void Interceptor_OverAnIndexer() { - Assert.Empty(RunIntercepted( - """ - public interface IWorker { string this[int index] { get; set; } } + public void Interceptor_OverAnIndexer() + { + Assert.Empty( + RunIntercepted( + """ + public interface IWorker { string this[int index] { get; set; } } - [SingletonService] - [Intercept(typeof(CountingInterceptor))] - public class Worker : IWorker { - public string this[int index] { get => ""; set { } } - } - """).Errors); + [SingletonService] + [Intercept(typeof(CountingInterceptor))] + public class Worker : IWorker { + public string this[int index] { get => ""; set { } } + } + """ + ).Errors + ); } /// An event is forwarded. [Fact] - public void Interceptor_OverAnEvent() { - Assert.Empty(RunIntercepted( - """ - public interface IWorker { event EventHandler? Done; } + public void Interceptor_OverAnEvent() + { + Assert.Empty( + RunIntercepted( + """ + public interface IWorker { event EventHandler? Done; } - [SingletonService] - [Intercept(typeof(CountingInterceptor))] - public class Worker : IWorker { public event EventHandler? Done; } - """).Errors); + [SingletonService] + [Intercept(typeof(CountingInterceptor))] + public class Worker : IWorker { public event EventHandler? Done; } + """ + ).Errors + ); } /// A generic method is forwarded with its type parameters. [Fact] - public void Interceptor_OverAGenericMethod() { - Assert.Empty(RunIntercepted( - """ - public interface IWorker { T Echo(T value); } + public void Interceptor_OverAGenericMethod() + { + Assert.Empty( + RunIntercepted( + """ + public interface IWorker { T Echo(T value); } - [SingletonService] - [Intercept(typeof(CountingInterceptor))] - public class Worker : IWorker { public T Echo(T value) => value; } - """).Errors); + [SingletonService] + [Intercept(typeof(CountingInterceptor))] + public class Worker : IWorker { public T Echo(T value) => value; } + """ + ).Errors + ); } /// Default values and params survive forwarding. [Fact] - public void Interceptor_OverDefaultAndParamsArguments() { - Assert.Empty(RunIntercepted( - """ - public interface IWorker { string Join(string separator = ",", params string[] parts); } + public void Interceptor_OverDefaultAndParamsArguments() + { + Assert.Empty( + RunIntercepted( + """ + public interface IWorker { string Join(string separator = ",", params string[] parts); } - [SingletonService] - [Intercept(typeof(CountingInterceptor))] - public class Worker : IWorker { - public string Join(string separator = ",", params string[] parts) => - string.Join(separator, parts); - } - """).Errors); + [SingletonService] + [Intercept(typeof(CountingInterceptor))] + public class Worker : IWorker { + public string Join(string separator = ",", params string[] parts) => + string.Join(separator, parts); + } + """ + ).Errors + ); } /// Members inherited from a base interface are forwarded too. [Fact] - public void Interceptor_OverAnInheritedInterfaceMember() { - Assert.Empty(RunIntercepted( - """ - public interface IBase { string Read(); } - public interface IWorker : IBase { string Write(); } + public void Interceptor_OverAnInheritedInterfaceMember() + { + Assert.Empty( + RunIntercepted( + """ + public interface IBase { string Read(); } + public interface IWorker : IBase { string Write(); } - [SingletonService] - [Intercept(typeof(CountingInterceptor))] - public class Worker : IWorker { - public string Read() => "read"; - public string Write() => "write"; - } - """).Errors); + [SingletonService] + [Intercept(typeof(CountingInterceptor))] + public class Worker : IWorker { + public string Read() => "read"; + public string Write() => "write"; + } + """ + ).Errors + ); } /// An IAsyncEnumerable member is forwarded. [Fact] - public void Interceptor_OverAnAsyncEnumerable() { - Assert.Empty(RunIntercepted( - """ - public interface IWorker { IAsyncEnumerable StreamAsync(); } + public void Interceptor_OverAnAsyncEnumerable() + { + Assert.Empty( + RunIntercepted( + """ + public interface IWorker { IAsyncEnumerable StreamAsync(); } - [SingletonService] - [Intercept(typeof(CountingInterceptor))] - public class Worker : IWorker { - public async IAsyncEnumerable StreamAsync() { - await Task.Yield(); - yield return "one"; + [SingletonService] + [Intercept(typeof(CountingInterceptor))] + public class Worker : IWorker { + public async IAsyncEnumerable StreamAsync() { + await Task.Yield(); + yield return "one"; + } } - } - """).Errors); + """ + ).Errors + ); } /// @@ -655,7 +748,8 @@ public async IAsyncEnumerable StreamAsync() { /// the generated wrapper is not. /// [Fact] - public void Interceptor_OverRefAndOutParameters_IsForwardedOrRefused() { + public void Interceptor_OverRefAndOutParameters_IsForwardedOrRefused() + { var result = RunIntercepted( """ public interface IWorker { bool TryRead(out string value); } @@ -665,7 +759,8 @@ public interface IWorker { bool TryRead(out string value); } public class Worker : IWorker { public bool TryRead(out string value) { value = "read"; return true; } } - """); + """ + ); Assert.Empty(result.Errors); } @@ -675,8 +770,7 @@ public class Worker : IWorker { // arguments, replacing results, short-circuiting, retrying, and observing failures. // ------------------------------------------------------------------------------------------ - private const string ArgumentPreamble = - """ + private const string ArgumentPreamble = """ using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -693,23 +787,41 @@ public static class Log { """; private static object Resolve(string body, string serviceName) => - GeneratedAssembly.Create(ArgumentPreamble + body + """ - - [DependencyModule] - public partial class TestModule; - """).BuildProvider().GetRequiredService( - GeneratedAssembly.Create(ArgumentPreamble + body + """ - - [DependencyModule] - public partial class TestModule; - """).Type(serviceName)); + GeneratedAssembly + .Create( + ArgumentPreamble + + body + + """ + + [DependencyModule] + public partial class TestModule; + """ + ) + .BuildProvider() + .GetRequiredService( + GeneratedAssembly + .Create( + ArgumentPreamble + + body + + """ + + [DependencyModule] + public partial class TestModule; + """ + ) + .Type(serviceName) + ); private static GeneratedAssembly Build(string body) => - GeneratedAssembly.Create(ArgumentPreamble + body + """ + GeneratedAssembly.Create( + ArgumentPreamble + + body + + """ - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); private static object Invoke(object target, string method, params object?[] arguments) => target.GetType().GetMethod(method)!.Invoke(target, arguments)!; @@ -721,7 +833,8 @@ private static object Invoke(object target, string method, params object?[] argu /// original — with nothing to show for it. /// [Fact] - public void Interceptor_CanReplaceAnArgument() { + public void Interceptor_CanReplaceAnArgument() + { var assembly = Build( """ public interface IGreeter { string Greet(string name); } @@ -737,16 +850,23 @@ public TResult Intercept(InvocationContext context) { return context.Proceed(); } } - """); + """ + ); Assert.Equal( "hello replaced", - Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet", "original")); + Invoke( + assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), + "Greet", + "original" + ) + ); } /// Arguments carry the names they were declared with. [Fact] - public void Interceptor_SeesArgumentNamesAndCount() { + public void Interceptor_SeesArgumentNamesAndCount() + { var assembly = Build( """ public interface IGreeter { string Greet(string name, int times); } @@ -764,18 +884,26 @@ public TResult Intercept(InvocationContext context) { return context.Proceed(); } } - """); + """ + ); - Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet", "ian", 2); + Invoke( + assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), + "Greet", + "ian", + 2 + ); Assert.Equal( ["name=ian", "times=2"], - (List)assembly.Type("Log").GetField("Lines")!.GetValue(null)!); + (List)assembly.Type("Log").GetField("Lines")!.GetValue(null)! + ); } /// An interceptor that never proceeds returns its own result. [Fact] - public void Interceptor_CanShortCircuitWithoutProceeding() { + public void Interceptor_CanShortCircuitWithoutProceeding() + { var assembly = Build( """ public interface IGreeter { string Greet(); } @@ -791,18 +919,21 @@ public class CachingInterceptor : IInterceptor { public TResult Intercept(InvocationContext context) => (TResult)(object)"cached"; } - """); + """ + ); Assert.Equal( "cached", - Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); Assert.Equal(0, (int)assembly.Type("Log").GetField("Calls")!.GetValue(null)!); } /// Proceeding twice runs the implementation twice — the retry shape. [Fact] - public void Interceptor_CanProceedMoreThanOnce() { + public void Interceptor_CanProceedMoreThanOnce() + { var assembly = Build( """ public interface IGreeter { string Greet(); } @@ -820,18 +951,21 @@ public TResult Intercept(InvocationContext context) { return context.Proceed(); } } - """); + """ + ); Assert.Equal( "call2", - Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); Assert.Equal(2, (int)assembly.Type("Log").GetField("Calls")!.GetValue(null)!); } /// An interceptor observes an exception the implementation throws. [Fact] - public void Interceptor_SeesAnExceptionFromTheImplementation() { + public void Interceptor_SeesAnExceptionFromTheImplementation() + { var assembly = Build( """ public interface IGreeter { string Greet(); } @@ -852,18 +986,24 @@ public TResult Intercept(InvocationContext context) { } } } - """); + """ + ); Assert.Equal( "recovered", - Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet") + ); - Assert.Equal(["boom"], (List)assembly.Type("Log").GetField("Lines")!.GetValue(null)!); + Assert.Equal( + ["boom"], + (List)assembly.Type("Log").GetField("Lines")!.GetValue(null)! + ); } /// Two interceptors nest in declaration order, outermost first. [Fact] - public void Interceptors_NestInDeclarationOrder() { + public void Interceptors_NestInDeclarationOrder() + { var assembly = Build( """ public interface IGreeter { string Greet(); } @@ -890,18 +1030,21 @@ public TResult Intercept(InvocationContext context) { return context.Proceed(); } } - """); + """ + ); Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet"); Assert.Equal( ["outer", "inner", "impl"], - (List)assembly.Type("Log").GetField("Lines")!.GetValue(null)!); + (List)assembly.Type("Log").GetField("Lines")!.GetValue(null)! + ); } /// An async interceptor can rewrite an argument and replace the result. [Fact] - public async Task AsyncInterceptor_CanReplaceArgumentsAndResult() { + public async Task AsyncInterceptor_CanReplaceArgumentsAndResult() + { var assembly = Build( """ public interface IFetcher { Task FetchAsync(string key); } @@ -925,7 +1068,8 @@ public async ValueTask InterceptAsync( return (TResult)(object)((string)(object)result! + ":seen"); } } - """); + """ + ); var fetcher = assembly.BuildProvider().GetRequiredService(assembly.Type("IFetcher")); var task = (Task)Invoke(fetcher, "FetchAsync", "original"); @@ -935,7 +1079,8 @@ public async ValueTask InterceptAsync( /// An async interceptor can short-circuit without awaiting the implementation. [Fact] - public async Task AsyncInterceptor_CanShortCircuit() { + public async Task AsyncInterceptor_CanShortCircuit() + { var assembly = Build( """ public interface IFetcher { Task FetchAsync(); } @@ -956,7 +1101,8 @@ public ValueTask InterceptAsync( AsyncInvocationContext context) => new ValueTask((TResult)(object)"cached"); } - """); + """ + ); var fetcher = assembly.BuildProvider().GetRequiredService(assembly.Type("IFetcher")); @@ -966,7 +1112,8 @@ public ValueTask InterceptAsync( /// A stream interceptor can replace the items the implementation yields. [Fact] - public async Task StreamInterceptor_CanReplaceTheYieldedItems() { + public async Task StreamInterceptor_CanReplaceTheYieldedItems() + { var assembly = Build( """ public interface IStreamer { IAsyncEnumerable StreamAsync(string prefix); } @@ -994,13 +1141,15 @@ public async IAsyncEnumerable InterceptStream( } } } - """); + """ + ); var streamer = assembly.BuildProvider().GetRequiredService(assembly.Type("IStreamer")); var stream = (IAsyncEnumerable)Invoke(streamer, "StreamAsync", "original"); var seen = new List(); - await foreach (var item in stream) { + await foreach (var item in stream) + { seen.Add(item); } @@ -1014,7 +1163,8 @@ public async IAsyncEnumerable InterceptStream( /// unboxed back to the parameter's type on the way out. /// [Fact] - public void Interceptor_CanReplaceAValueTypeArgument() { + public void Interceptor_CanReplaceAValueTypeArgument() + { var assembly = Build( """ public interface ICounter { int Add(int value); } @@ -1030,11 +1180,13 @@ public TResult Intercept(InvocationContext context) { return context.Proceed(); } } - """); + """ + ); Assert.Equal( 41, - Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("ICounter")), "Add", 4)); + Invoke(assembly.BuildProvider().GetRequiredService(assembly.Type("ICounter")), "Add", 4) + ); } // ------------------------------------------------------------------------------------------ @@ -1048,7 +1200,8 @@ private static object Instance(GeneratedAssembly assembly, string moduleName) => /// Two modules compose, and each contributes its own registrations. [Fact] - public void Modules_ComposedTogether_BothContribute() { + public void Modules_ComposedTogether_BothContribute() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1070,13 +1223,15 @@ public partial class ReadModule; [DependencyModule(OnlyRealm = true)] public partial class WriteModule; """, - moduleName: "ReadModule"); + moduleName: "ReadModule" + ); var services = new ServiceCollection(); services.AddModules( (IDependencyModule)Instance(assembly, "ReadModule"), - (IDependencyModule)Instance(assembly, "WriteModule")); + (IDependencyModule)Instance(assembly, "WriteModule") + ); var provider = services.BuildServiceProvider(); @@ -1092,7 +1247,8 @@ public partial class WriteModule; [Theory] [InlineData(true)] [InlineData(false)] - public void Modules_CrossModuleDependency_ResolvesInEitherOrder(bool readFirst) { + public void Modules_CrossModuleDependency_ResolvesInEitherOrder(bool readFirst) + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1115,7 +1271,8 @@ public partial class ReadModule; [DependencyModule(OnlyRealm = true)] public partial class UseModule; """, - moduleName: "ReadModule"); + moduleName: "ReadModule" + ); var read = (IDependencyModule)Instance(assembly, "ReadModule"); var use = (IDependencyModule)Instance(assembly, "UseModule"); @@ -1123,8 +1280,13 @@ public partial class UseModule; var services = new ServiceCollection(); services.AddModules(readFirst ? new[] { read, use } : new[] { use, read }); - Assert.Equal("using:read", Call( - services.BuildServiceProvider().GetRequiredService(assembly.Type("Consumer")), "Describe")); + Assert.Equal( + "using:read", + Call( + services.BuildServiceProvider().GetRequiredService(assembly.Type("Consumer")), + "Describe" + ) + ); } /// Two equal instances of one module register its services once. @@ -1133,7 +1295,8 @@ public partial class UseModule; /// arriving twice is normal. Registering twice gives two instances behind one singleton. /// [Fact] - public void Module_ArrivingTwiceAsSeparateInstances_RegistersOnce() { + public void Module_ArrivingTwiceAsSeparateInstances_RegistersOnce() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1147,21 +1310,25 @@ public class Greeter : IGreeter { public string Greet() => "hello"; } [DependencyModule] public partial class TestModule; - """); + """ + ); var services = new ServiceCollection(); services.AddModules( (IDependencyModule)Instance(assembly, "TestModule"), - (IDependencyModule)Instance(assembly, "TestModule")); + (IDependencyModule)Instance(assembly, "TestModule") + ); Assert.Single( - services.BuildServiceProvider().GetServices(assembly.Type("IGreeter")).Cast()); + services.BuildServiceProvider().GetServices(assembly.Type("IGreeter")).Cast() + ); } /// A realm-only module contributes nothing to a composition it is not part of. [Fact] - public void Module_RealmOnly_DoesNotLeakIntoAnotherComposition() { + public void Module_RealmOnly_DoesNotLeakIntoAnotherComposition() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1179,13 +1346,15 @@ public partial class HiddenModule; [DependencyModule(OnlyRealm = true)] public partial class PlainModule; """, - moduleName: "PlainModule"); + moduleName: "PlainModule" + ); var services = new ServiceCollection(); services.AddModules((IDependencyModule)Instance(assembly, "PlainModule")); Assert.Empty( - services.BuildServiceProvider().GetServices(assembly.Type("IGreeter")).Cast()); + services.BuildServiceProvider().GetServices(assembly.Type("IGreeter")).Cast() + ); } /// A decorator in one module wraps a service another module registered. @@ -1195,7 +1364,8 @@ public partial class PlainModule; /// open, that phase ordering still has to hold. /// [Fact] - public void Module_DecoratesAServiceAnotherModuleRegistered() { + public void Module_DecoratesAServiceAnotherModuleRegistered() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1218,16 +1388,23 @@ public partial class ServiceModule; [DependencyModule(OnlyRealm = true)] public partial class DecoratorModule; """, - moduleName: "ServiceModule"); + moduleName: "ServiceModule" + ); var services = new ServiceCollection(); services.AddModules( (IDependencyModule)Instance(assembly, "ServiceModule"), - (IDependencyModule)Instance(assembly, "DecoratorModule")); + (IDependencyModule)Instance(assembly, "DecoratorModule") + ); - Assert.Equal("HELLO", Call( - services.BuildServiceProvider().GetRequiredService(assembly.Type("IGreeter")), "Greet")); + Assert.Equal( + "HELLO", + Call( + services.BuildServiceProvider().GetRequiredService(assembly.Type("IGreeter")), + "Greet" + ) + ); } // ------------------------------------------------------------------------------------------ @@ -1245,12 +1422,15 @@ namespace TestNamespace; public interface IGreeter { string Greet(); } - """ + body + """ + """ + + body + + """ - [DependencyModule] - public partial class TestModule; - """, - environment: environment); + [DependencyModule] + public partial class TestModule; + """, + environment: environment + ); private static int Count(GeneratedAssembly assembly) => assembly.BuildProvider().GetServices(assembly.Type("IGreeter")).Cast().Count(); @@ -1259,14 +1439,21 @@ private static int Count(GeneratedAssembly assembly) => [Theory] [InlineData("Production", 0)] [InlineData("Development", 1)] - public void Condition_IfNotEnvironment(string environment, int expected) { - Assert.Equal(expected, Count(WithEnvironment( - """ - [SingletonService] - [IfNotEnvironment("Production")] - public class Greeter : IGreeter { public string Greet() => "hello"; } - """, - new ModuleEnvironment(environment)))); + public void Condition_IfNotEnvironment(string environment, int expected) + { + Assert.Equal( + expected, + Count( + WithEnvironment( + """ + [SingletonService] + [IfNotEnvironment("Production")] + public class Greeter : IGreeter { public string Greet() => "hello"; } + """, + new ModuleEnvironment(environment) + ) + ) + ); } /// One condition listing several names matches any of them. @@ -1274,47 +1461,71 @@ public class Greeter : IGreeter { public string Greet() => "hello"; } [InlineData("Development", 1)] [InlineData("Staging", 1)] [InlineData("Production", 0)] - public void Condition_IfEnvironment_WithSeveralNames(string environment, int expected) { - Assert.Equal(expected, Count(WithEnvironment( - """ - [SingletonService] - [IfEnvironment("Development", "Staging")] - public class Greeter : IGreeter { public string Greet() => "hello"; } - """, - new ModuleEnvironment(environment)))); + public void Condition_IfEnvironment_WithSeveralNames(string environment, int expected) + { + Assert.Equal( + expected, + Count( + WithEnvironment( + """ + [SingletonService] + [IfEnvironment("Development", "Staging")] + public class Greeter : IGreeter { public string Greet() => "hello"; } + """, + new ModuleEnvironment(environment) + ) + ) + ); } /// A value condition with no expected value tests only that the key is present. [Theory] [InlineData(true, 1)] [InlineData(false, 0)] - public void Condition_IfEnvironmentValue_KeyPresence(bool present, int expected) { - Assert.Equal(expected, Count(WithEnvironment( - """ - [SingletonService] - [IfEnvironmentValue("feature")] - public class Greeter : IGreeter { public string Greet() => "hello"; } - """, - new ModuleEnvironment( - "Development", - present - ? new Dictionary { ["feature"] = "anything" } - : new Dictionary())))); + public void Condition_IfEnvironmentValue_KeyPresence(bool present, int expected) + { + Assert.Equal( + expected, + Count( + WithEnvironment( + """ + [SingletonService] + [IfEnvironmentValue("feature")] + public class Greeter : IGreeter { public string Greet() => "hello"; } + """, + new ModuleEnvironment( + "Development", + present + ? new Dictionary { ["feature"] = "anything" } + : new Dictionary() + ) + ) + ) + ); } /// IfNotEnvironmentValue is the negation. [Theory] [InlineData("on", 0)] [InlineData("off", 1)] - public void Condition_IfNotEnvironmentValue(string value, int expected) { - Assert.Equal(expected, Count(WithEnvironment( - """ - [SingletonService] - [IfNotEnvironmentValue("feature", "on")] - public class Greeter : IGreeter { public string Greet() => "hello"; } - """, - new ModuleEnvironment( - "Development", new Dictionary { ["feature"] = value })))); + public void Condition_IfNotEnvironmentValue(string value, int expected) + { + Assert.Equal( + expected, + Count( + WithEnvironment( + """ + [SingletonService] + [IfNotEnvironmentValue("feature", "on")] + public class Greeter : IGreeter { public string Greet() => "hello"; } + """, + new ModuleEnvironment( + "Development", + new Dictionary { ["feature"] = value } + ) + ) + ) + ); } /// A condition on the convention and one on the class combine with and. @@ -1327,8 +1538,11 @@ public class Greeter : IGreeter { public string Greet() => "hello"; } [InlineData("Development", "off", 0)] [InlineData("Production", "on", 0)] public void Condition_OnConventionAndOnClass_BothMustHold( - string environment, string flag, int expected) { - + string environment, + string flag, + int expected + ) + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1349,14 +1563,22 @@ public void Conventions(IConventionDefinitions conventions) { } """, environment: new ModuleEnvironment( - environment, new Dictionary { ["feature"] = flag })); + environment, + new Dictionary { ["feature"] = flag } + ) + ); Assert.Equal(expected, Count(assembly)); } private static string[] Names(GeneratedAssembly assembly) => - assembly.BuildProvider().GetServices(assembly.Type("IGreeter")) - .Cast().Select(g => Call(g, "Greet")).OrderBy(n => n).ToArray(); + assembly + .BuildProvider() + .GetServices(assembly.Type("IGreeter")) + .Cast() + .Select(g => Call(g, "Greet")) + .OrderBy(n => n) + .ToArray(); private static GeneratedAssembly WithConvention(string chain) => GeneratedAssembly.Create( @@ -1378,29 +1600,37 @@ public void Conventions(IConventionDefinitions conventions) { conventions.RegisterAll()CHAIN.AsSingleton(); } } - """.Replace("CHAIN", chain)); + """.Replace("CHAIN", chain) + ); /// A name glob selects by the type's own name. [Fact] - public void Convention_WithName_SelectsByGlob() { + public void Convention_WithName_SelectsByGlob() + { Assert.Equal(["evening", "morning"], Names(WithConvention(""".WithName("*Greeter")"""))); } /// An excluding glob removes what it matches. [Fact] - public void Convention_WithoutName_ExcludesByGlob() { - Assert.Equal(["evening", "salutation"], Names(WithConvention(""".WithoutName("Morning*")"""))); + public void Convention_WithoutName_ExcludesByGlob() + { + Assert.Equal( + ["evening", "salutation"], + Names(WithConvention(""".WithoutName("Morning*")""")) + ); } /// A single-character wildcard matches exactly one character. [Fact] - public void Convention_WithName_SingleCharacterWildcard() { + public void Convention_WithName_SingleCharacterWildcard() + { Assert.Equal(["salutation"], Names(WithConvention(""".WithName("Salutatio?")"""))); } /// An attribute filter selects only what carries it. [Fact] - public void Convention_WithAttribute_SelectsOnlyMarkedTypes() { + public void Convention_WithAttribute_SelectsOnlyMarkedTypes() + { var assembly = GeneratedAssembly.Create( """ using System; @@ -1425,14 +1655,16 @@ public void Conventions(IConventionDefinitions conventions) { conventions.RegisterAll().WithAttribute().AsSingleton(); } } - """); + """ + ); Assert.Equal(["marked"], Names(assembly)); } /// An exact-namespace filter does not match a nested namespace. [Fact] - public void Convention_InExactNamespaces_DoesNotMatchNested() { + public void Convention_InExactNamespaces_DoesNotMatchNested() + { var assembly = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -1458,7 +1690,8 @@ public class Here : TestNamespace.IGreeter { public string Greet() => "here"; } namespace TestNamespace.Direct.Nested { public class Deeper : TestNamespace.IGreeter { public string Greet() => "deeper"; } } - """); + """ + ); Assert.Equal(["here"], Names(assembly)); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/ServiceOrderTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ServiceOrderTests.cs index 18dc3b9..2ed6e2b 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ServiceOrderTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ServiceOrderTests.cs @@ -18,18 +18,21 @@ namespace DependencyModules.Tests.GeneratorTests; /// The default is 0 for everything, and the sort is stable within an order, so a project that names /// no orders sees exactly what it saw before. /// -public class ServiceOrderTests { - +public class ServiceOrderTests +{ [Fact] - public void WithNoOrderNamed_TheExistingOrderIsKept() { + public void WithNoOrderNamed_TheExistingOrderIsKept() + { Assert.Equal(["Alpha", "Beta", "Gamma"], Resolve("", "", "")); } [Fact] - public void OrderDecidesTheSequence() { + public void OrderDecidesTheSequence() + { Assert.Equal( ["Gamma", "Beta", "Alpha"], - Resolve(", Order = 30", ", Order = 20", ", Order = 10")); + Resolve(", Order = 30", ", Order = 20", ", Order = 10") + ); } /// @@ -37,7 +40,8 @@ public void OrderDecidesTheSequence() { /// looks like when the rest of the project has never named an order. /// [Fact] - public void ANegativeOrder_SortsAhead() { + public void ANegativeOrder_SortsAhead() + { Assert.Equal(["Gamma", "Alpha", "Beta"], Resolve("", "", ", Order = -1")); } @@ -46,8 +50,12 @@ public void ANegativeOrder_SortsAhead() { /// not scramble the rest. /// [Fact] - public void WithinOneOrder_TheSortIsStable() { - Assert.Equal(["Alpha", "Beta", "Gamma"], Resolve(", Order = 5", ", Order = 5", ", Order = 5")); + public void WithinOneOrder_TheSortIsStable() + { + Assert.Equal( + ["Alpha", "Beta", "Gamma"], + Resolve(", Order = 5", ", Order = 5", ", Order = 5") + ); } /// @@ -55,7 +63,8 @@ public void WithinOneOrder_TheSortIsStable() { /// too — worth pinning, because it is the half a reader does not think about. /// [Fact] - public void TheLastInOrder_IsWhatASingleResolveReturns() { + public void TheLastInOrder_IsWhatASingleResolveReturns() + { var generated = Build(", Order = 30", ", Order = 20", ", Order = 10"); var resolved = generated.BuildProvider().GetService(generated.Type("IStep"))!; @@ -63,11 +72,16 @@ public void TheLastInOrder_IsWhatASingleResolveReturns() { Assert.Equal("Alpha", resolved.GetType().Name); } - private static string[] Resolve(string alpha, string beta, string gamma) { + private static string[] Resolve(string alpha, string beta, string gamma) + { var generated = Build(alpha, beta, gamma); - return ((System.Collections.IEnumerable)generated.BuildProvider() - .GetService(typeof(IEnumerable<>).MakeGenericType(generated.Type("IStep")))!) + return ( + (System.Collections.IEnumerable) + generated + .BuildProvider() + .GetService(typeof(IEnumerable<>).MakeGenericType(generated.Type("IStep")))! + ) .Cast() .Select(step => step.GetType().Name) .ToArray(); @@ -76,22 +90,23 @@ private static string[] Resolve(string alpha, string beta, string gamma) { private static GeneratedAssembly Build(string alpha, string beta, string gamma) => GeneratedAssembly.Create( $$""" - using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - public interface IStep; + public interface IStep; - [SingletonService(As = typeof(IStep){{alpha}})] - public class Alpha : IStep; + [SingletonService(As = typeof(IStep){{alpha}})] + public class Alpha : IStep; - [SingletonService(As = typeof(IStep){{beta}})] - public class Beta : IStep; + [SingletonService(As = typeof(IStep){{beta}})] + public class Beta : IStep; - [SingletonService(As = typeof(IStep){{gamma}})] - public class Gamma : IStep; + [SingletonService(As = typeof(IStep){{gamma}})] + public class Gamma : IStep; - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); } diff --git a/tests/DependencyModules.Tests/GeneratorTests/ServiceRegistrationTests.cs b/tests/DependencyModules.Tests/GeneratorTests/ServiceRegistrationTests.cs index 39de7e3..ccf0866 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/ServiceRegistrationTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/ServiceRegistrationTests.cs @@ -8,35 +8,47 @@ namespace DependencyModules.Tests.GeneratorTests; /// These assert on the shape of the output rather than an exact snapshot, so they stay readable /// when unrelated parts of the generated file change. /// -public class ServiceRegistrationTests { - +public class ServiceRegistrationTests +{ [Fact] - public void SingletonService_EmitsAddSingleton() { - var result = GeneratorTestHarness.Run(Module("[SingletonService] public class Thing : IThing;")); + public void SingletonService_EmitsAddSingleton() + { + var result = GeneratorTestHarness.Run( + Module("[SingletonService] public class Thing : IThing;") + ); result.AssertNoErrors(); Assert.Contains("AddSingleton", result.SourceContaining("Dependencies")); } [Fact] - public void ScopedService_EmitsAddScoped() { - var result = GeneratorTestHarness.Run(Module("[ScopedService] public class Thing : IThing;")); + public void ScopedService_EmitsAddScoped() + { + var result = GeneratorTestHarness.Run( + Module("[ScopedService] public class Thing : IThing;") + ); result.AssertNoErrors(); Assert.Contains("AddScoped", result.SourceContaining("Dependencies")); } [Fact] - public void TransientService_EmitsAddTransient() { - var result = GeneratorTestHarness.Run(Module("[TransientService] public class Thing : IThing;")); + public void TransientService_EmitsAddTransient() + { + var result = GeneratorTestHarness.Run( + Module("[TransientService] public class Thing : IThing;") + ); result.AssertNoErrors(); Assert.Contains("AddTransient", result.SourceContaining("Dependencies")); } [Fact] - public void Service_RegistersImplementedInterfaceAsServiceType() { - var result = GeneratorTestHarness.Run(Module("[SingletonService] public class Thing : IThing;")); + public void Service_RegistersImplementedInterfaceAsServiceType() + { + var result = GeneratorTestHarness.Run( + Module("[SingletonService] public class Thing : IThing;") + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -46,9 +58,11 @@ public void Service_RegistersImplementedInterfaceAsServiceType() { } [Fact] - public void KeyedService_EmitsKeyedRegistration() { + public void KeyedService_EmitsKeyedRegistration() + { var result = GeneratorTestHarness.Run( - Module("""[SingletonService(Key = "the-key")] public class Thing : IThing;""")); + Module("""[SingletonService(Key = "the-key")] public class Thing : IThing;""") + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -58,12 +72,16 @@ public void KeyedService_EmitsKeyedRegistration() { } [Fact] - public void AsProperty_RegistersTheRequestedServiceType() { - var result = GeneratorTestHarness.Run(Module( - """ - public interface IOther; - [SingletonService(As = typeof(IOther))] public class Thing : IThing, IOther; - """)); + public void AsProperty_RegistersTheRequestedServiceType() + { + var result = GeneratorTestHarness.Run( + Module( + """ + public interface IOther; + [SingletonService(As = typeof(IOther))] public class Thing : IThing, IOther; + """ + ) + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -75,17 +93,25 @@ public interface IOther; [InlineData("RegistrationType.Try", "TryAddSingleton")] [InlineData("RegistrationType.TryEnumerable", "TryAddEnumerable")] [InlineData("RegistrationType.Replace", "Replace")] - public void UsingProperty_ChangesTheRegistrationMethod(string registrationType, string expectedCall) { + public void UsingProperty_ChangesTheRegistrationMethod( + string registrationType, + string expectedCall + ) + { var result = GeneratorTestHarness.Run( - Module($"[SingletonService(Using = {registrationType})] public class Thing : IThing;")); + Module($"[SingletonService(Using = {registrationType})] public class Thing : IThing;") + ); result.AssertNoErrors(); Assert.Contains(expectedCall, result.SourceContaining("Dependencies")); } [Fact] - public void CrossWireService_RegistersImplementationAndInterface() { - var result = GeneratorTestHarness.Run(Module("[CrossWireService] public class Thing : IThing;")); + public void CrossWireService_RegistersImplementationAndInterface() + { + var result = GeneratorTestHarness.Run( + Module("[CrossWireService] public class Thing : IThing;") + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -105,21 +131,23 @@ public void CrossWireService_RegistersImplementationAndInterface() { [Theory] [InlineData("[SingletonService]")] [InlineData("[SingletonServiceAttribute]")] - public void ServiceAttribute_IsMatchedHoweverItIsWritten(string attribute) { + public void ServiceAttribute_IsMatchedHoweverItIsWritten(string attribute) + { var generated = GeneratedAssembly.Create( $$""" - using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - public interface IThing; + public interface IThing; - {{attribute}} - public class Thing : IThing; + {{attribute}} + public class Thing : IThing; - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); Assert.NotNull(generated.ResolveRequired("IThing")); } @@ -128,7 +156,8 @@ public partial class TestModule; /// And an attribute that merely shares a name is not one of ours. /// [Fact] - public void SameNamedAttributeFromAnotherNamespace_IsIgnored() { + public void SameNamedAttributeFromAnotherNamespace_IsIgnored() + { var generated = GeneratedAssembly.Create( """ using DependencyModules.Runtime.Attributes; @@ -146,13 +175,15 @@ public class Thing : IThing; [DependencyModule] public partial class TestModule; } - """); + """ + ); Assert.Empty(generated.Descriptors("IThing")); } [Fact] - public void OpenGenericService_RegistersOpenGenericTypes() { + public void OpenGenericService_RegistersOpenGenericTypes() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -166,7 +197,8 @@ public class GenericThing : IGeneric; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -176,7 +208,8 @@ public partial class TestModule; } [Fact] - public void StaticFactoryMethod_IsRegisteredAsAFactory() { + public void StaticFactoryMethod_IsRegisteredAsAFactory() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -192,7 +225,8 @@ public class Thing : IThing { [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -202,7 +236,8 @@ public partial class TestModule; } [Fact] - public void ModuleWithNoServices_DoesNotEmitADependenciesFile() { + public void ModuleWithNoServices_DoesNotEmitADependenciesFile() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -211,14 +246,16 @@ namespace TestNamespace; [DependencyModule] public partial class TestModule; - """); + """ + ); result.AssertNoErrors(); Assert.DoesNotContain(result.GeneratedSources.Keys, key => key.Contains("Dependencies")); } [Fact] - public void RecordModule_IsSupported() { + public void RecordModule_IsSupported() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -232,7 +269,8 @@ public class Thing : IThing; [DependencyModule] public partial record TestModule; - """); + """ + ); result.AssertNoErrors(); Assert.Contains("AddSingleton", result.SourceContaining("Dependencies")); @@ -243,14 +281,18 @@ public partial record TestModule; /// itself registered as IDisposable and was unreachable through the interface it existed for. /// [Fact] - public void CapabilityInterface_DoesNotWinOverTheServiceInterface() { - var result = GeneratorTestHarness.Run(Module( - """ - [SingletonService] - public class Thing : System.IDisposable, IThing { - public void Dispose() { } - } - """)); + public void CapabilityInterface_DoesNotWinOverTheServiceInterface() + { + var result = GeneratorTestHarness.Run( + Module( + """ + [SingletonService] + public class Thing : System.IDisposable, IThing { + public void Dispose() { } + } + """ + ) + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -260,14 +302,18 @@ public void Dispose() { } } [Fact] - public void CapabilityInterfaceAlone_RegistersAsSelf() { - var result = GeneratorTestHarness.Run(Module( - """ - [SingletonService] - public class Thing : System.IDisposable { - public void Dispose() { } - } - """)); + public void CapabilityInterfaceAlone_RegistersAsSelf() + { + var result = GeneratorTestHarness.Run( + Module( + """ + [SingletonService] + public class Thing : System.IDisposable { + public void Dispose() { } + } + """ + ) + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -277,16 +323,20 @@ public void Dispose() { } } [Fact] - public void CapabilityInterfaceThroughABaseClass_RegistersAsSelf() { - var result = GeneratorTestHarness.Run(Module( - """ - public abstract class DisposableBase : System.IDisposable { - public void Dispose() { } - } + public void CapabilityInterfaceThroughABaseClass_RegistersAsSelf() + { + var result = GeneratorTestHarness.Run( + Module( + """ + public abstract class DisposableBase : System.IDisposable { + public void Dispose() { } + } - [SingletonService] - public class Thing : DisposableBase; - """)); + [SingletonService] + public class Thing : DisposableBase; + """ + ) + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -301,15 +351,19 @@ public class Thing : DisposableBase; /// IHttpClientFactory are the same shape. /// [Fact] - public void FrameworkRoleInterface_IsStillTheServiceType() { - var result = GeneratorTestHarness.Run(Module( - """ - [SingletonService] - public class Thing : System.Collections.Generic.IEqualityComparer { - public bool Equals(IThing? a, IThing? b) => false; - public int GetHashCode(IThing o) => 0; - } - """)); + public void FrameworkRoleInterface_IsStillTheServiceType() + { + var result = GeneratorTestHarness.Run( + Module( + """ + [SingletonService] + public class Thing : System.Collections.Generic.IEqualityComparer { + public bool Equals(IThing? a, IThing? b) => false; + public int GetHashCode(IThing o) => 0; + } + """ + ) + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -318,14 +372,18 @@ public class Thing : System.Collections.Generic.IEqualityComparer { } [Fact] - public void CapabilityInterface_IsHonouredWhenNamedExplicitly() { - var result = GeneratorTestHarness.Run(Module( - """ - [SingletonService(As = typeof(System.IDisposable))] - public class Thing : System.IDisposable, IThing { - public void Dispose() { } - } - """)); + public void CapabilityInterface_IsHonouredWhenNamedExplicitly() + { + var result = GeneratorTestHarness.Run( + Module( + """ + [SingletonService(As = typeof(System.IDisposable))] + public class Thing : System.IDisposable, IThing { + public void Dispose() { } + } + """ + ) + ); result.AssertNoErrors(); var generated = result.SourceContaining("Dependencies"); @@ -335,15 +393,15 @@ public void Dispose() { } private static string Module(string body) => $$""" - using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - public interface IThing; + public interface IThing; - {{body}} + {{body}} - [DependencyModule] - public partial class TestModule; - """; + [DependencyModule] + public partial class TestModule; + """; } diff --git a/tests/DependencyModules.Tests/GeneratorTests/TypeShapeTests.cs b/tests/DependencyModules.Tests/GeneratorTests/TypeShapeTests.cs index 18c47d4..b8e9d9c 100644 --- a/tests/DependencyModules.Tests/GeneratorTests/TypeShapeTests.cs +++ b/tests/DependencyModules.Tests/GeneratorTests/TypeShapeTests.cs @@ -8,10 +8,11 @@ namespace DependencyModules.Tests.GeneratorTests; /// shapes most likely to be written wrong: nullables, arrays, nested generics, and nested classes. /// Each case asserts the output still compiles, which is what a mis-rendered type breaks. /// -public class TypeShapeTests { - +public class TypeShapeTests +{ [Fact] - public void NullableConstructorParameter_Compiles() { + public void NullableConstructorParameter_Compiles() + { Generate( """ public interface IThing; @@ -20,11 +21,13 @@ public interface IThing; public class Thing : IThing { public Thing(string? optional) { } } - """); + """ + ); } [Fact] - public void NullableValueTypeParameter_Compiles() { + public void NullableValueTypeParameter_Compiles() + { Generate( """ public interface IThing; @@ -33,11 +36,13 @@ public interface IThing; public class Thing : IThing { public Thing(int? count) { } } - """); + """ + ); } [Fact] - public void ArrayParameter_Compiles() { + public void ArrayParameter_Compiles() + { Generate( """ public interface IDependency; @@ -50,11 +55,13 @@ public class Dependency : IDependency; public class Thing : IThing { public Thing(IDependency[] dependencies) { } } - """); + """ + ); } [Fact] - public void NestedGenericParameter_Compiles() { + public void NestedGenericParameter_Compiles() + { Generate( """ using System.Collections.Generic; @@ -69,11 +76,13 @@ public class Dependency : IDependency; public class Thing : IThing { public Thing(IEnumerable dependencies) { } } - """); + """ + ); } [Fact] - public void DeeplyNestedGenericParameter_Compiles() { + public void DeeplyNestedGenericParameter_Compiles() + { Generate( """ using System.Collections.Generic; @@ -84,44 +93,51 @@ public interface IThing; public class Thing : IThing { public Thing(IDictionary> map) { } } - """); + """ + ); } [Fact] - public void GenericServiceWithConstraints_Compiles() { + public void GenericServiceWithConstraints_Compiles() + { var generated = Generate( """ public interface IGeneric where T : class; [SingletonService] public class Generic : IGeneric where T : class; - """); + """ + ); Assert.Contains("IGeneric<>", generated); } [Fact] - public void GenericServiceWithSeveralParameters_Compiles() { + public void GenericServiceWithSeveralParameters_Compiles() + { var generated = Generate( """ public interface IPair; [SingletonService] public class Pair : IPair; - """); + """ + ); Assert.Contains("IPair<,>", generated); } [Fact] - public void ClosedGenericService_RegistersTheClosedType() { + public void ClosedGenericService_RegistersTheClosedType() + { var generated = Generate( """ public interface IGeneric; [SingletonService] public class StringGeneric : IGeneric; - """); + """ + ); Assert.Contains("string", generated); } @@ -131,7 +147,8 @@ public class StringGeneric : IGeneric; /// containing type, so the generated registration failed to compile with CS0234. /// [Fact] - public void NestedClassService_IsQualifiedByItsContainingType() { + public void NestedClassService_IsQualifiedByItsContainingType() + { var generated = Generate( """ public interface IThing; @@ -140,13 +157,15 @@ public static class Outer { [SingletonService] public class Inner : IThing; } - """); + """ + ); Assert.Contains("global::TestNamespace.Outer.Inner", generated); } [Fact] - public void DeeplyNestedClassService_IsQualifiedByEveryContainingType() { + public void DeeplyNestedClassService_IsQualifiedByEveryContainingType() + { var generated = Generate( """ public interface IThing; @@ -157,13 +176,15 @@ public static class Middle { public class Inner : IThing; } } - """); + """ + ); Assert.Contains("global::TestNamespace.Outer.Middle.Inner", generated); } [Fact] - public void NestedGenericService_IsQualifiedByItsContainingType() { + public void NestedGenericService_IsQualifiedByItsContainingType() + { var generated = Generate( """ public interface IGeneric; @@ -172,13 +193,15 @@ public static class Outer { [SingletonService] public class Inner : IGeneric; } - """); + """ + ); Assert.Contains("global::TestNamespace.Outer.Inner<>", generated); } [Fact] - public void ServiceWithAnEnumKey_Compiles() { + public void ServiceWithAnEnumKey_Compiles() + { var generated = Generate( """ public interface IThing; @@ -187,26 +210,30 @@ public enum Flavour { Sweet, Savoury } [SingletonService(Key = Flavour.Sweet)] public class Thing : IThing; - """); + """ + ); Assert.Contains("AddKeyedSingleton", generated); } [Fact] - public void ServiceWithAnIntegerKey_Compiles() { + public void ServiceWithAnIntegerKey_Compiles() + { var generated = Generate( """ public interface IThing; [SingletonService(Key = 42)] public class Thing : IThing; - """); + """ + ); Assert.Contains("42", generated); } [Fact] - public void ServiceKeyedByAConstant_Compiles() { + public void ServiceKeyedByAConstant_Compiles() + { var generated = Generate( """ public interface IThing; @@ -217,26 +244,30 @@ public static class Keys { [SingletonService(Key = Keys.Primary)] public class Thing : IThing; - """); + """ + ); Assert.Contains("AddKeyedSingleton", generated); } [Fact] - public void RecordService_Compiles() { + public void RecordService_Compiles() + { var generated = Generate( """ public interface IThing; [SingletonService] public record ThingRecord : IThing; - """); + """ + ); Assert.Contains("ThingRecord", generated); } [Fact] - public void ServiceWithSeveralConstructors_Compiles() { + public void ServiceWithSeveralConstructors_Compiles() + { Generate( """ public interface IDependency; @@ -250,11 +281,13 @@ public class Thing : IThing { public Thing() { } public Thing(IDependency dependency) { } } - """); + """ + ); } [Fact] - public void AbstractBaseAndConcreteService_Compiles() { + public void AbstractBaseAndConcreteService_Compiles() + { var generated = Generate( """ public interface IThing; @@ -263,13 +296,15 @@ public abstract class ThingBase : IThing; [SingletonService] public class Thing : ThingBase; - """); + """ + ); Assert.Contains("Thing", generated); } [Fact] - public void ModuleWithNullableProperty_Compiles() { + public void ModuleWithNullableProperty_Compiles() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -280,14 +315,16 @@ namespace TestNamespace; public partial class NullablePropertyModule { public string? Optional { get; set; } } - """); + """ + ); result.AssertNoErrors(); Assert.Contains("Optional", result.SourceContaining(".Module.g.cs")); } [Fact] - public void ModuleWithStaticProperty_LeavesItOffTheAttribute() { + public void ModuleWithStaticProperty_LeavesItOffTheAttribute() + { var result = GeneratorTestHarness.Run( """ using DependencyModules.Runtime.Attributes; @@ -299,7 +336,8 @@ public partial class StaticPropertyModule { public static string Shared { get; set; } = ""; public string Instance { get; set; } = ""; } - """); + """ + ); result.AssertNoErrors(); var generated = result.SourceContaining(".Module.g.cs"); @@ -317,7 +355,8 @@ public partial class StaticPropertyModule { /// that constructor's parameters. It now reads the type's own members. /// [Fact] - public void ANestedTypesConstructorIsNotTheOuterTypes() { + public void ANestedTypesConstructorIsNotTheOuterTypes() + { // With factory generation on, the constructor reaches the emitted code as a literal // new Outer(...) call. Without it the container picks the constructor at run time and the // mistake is invisible, which is why this test sets the property. @@ -340,9 +379,11 @@ public class Nested { [DependencyModule] public partial class TestModule; """, - buildProperties: new Dictionary { - ["DependencyModules_GenerateFactories"] = "true" - }); + buildProperties: new Dictionary + { + ["DependencyModules_GenerateFactories"] = "true", + } + ); Assert.IsType(assembly.Type("Outer"), assembly.ResolveRequired("IOuter")); } @@ -350,22 +391,23 @@ public partial class TestModule; /// /// Compiles the supplied declarations alongside a module and returns the registration file. /// - private static string Generate(string body) { + private static string Generate(string body) + { var result = GeneratorTestHarness.Run( $$""" - using DependencyModules.Runtime.Attributes; + using DependencyModules.Runtime.Attributes; - namespace TestNamespace; + namespace TestNamespace; - {{body}} + {{body}} - [DependencyModule] - public partial class TestModule; - """); + [DependencyModule] + public partial class TestModule; + """ + ); result.AssertNoErrors(); return result.SourceContaining("Dependencies"); } - } diff --git a/tests/DependencyModules.Tests/Infrastructure/GeneratedAssembly.cs b/tests/DependencyModules.Tests/Infrastructure/GeneratedAssembly.cs index 17454b0..210b00a 100644 --- a/tests/DependencyModules.Tests/Infrastructure/GeneratedAssembly.cs +++ b/tests/DependencyModules.Tests/Infrastructure/GeneratedAssembly.cs @@ -18,12 +18,14 @@ namespace DependencyModules.Tests.Infrastructure; /// registrations the same way an application would, so a change that still emits plausible-looking /// code but breaks behaviour fails here. /// -public class GeneratedAssembly { +public class GeneratedAssembly +{ private static int _assemblyCounter; private readonly Assembly _assembly; - private GeneratedAssembly(Assembly assembly, IServiceCollection services) { + private GeneratedAssembly(Assembly assembly, IServiceCollection services) + { _assembly = assembly; Services = services; } @@ -49,30 +51,36 @@ public static GeneratedAssembly Create( string source, string moduleName = "TestModule", IReadOnlyDictionary? buildProperties = null, - IModuleEnvironment? environment = null, - IReadOnlyList? additionalReferences = null) { - + IModuleEnvironment? environment = null, + IReadOnlyList? additionalReferences = null + ) + { var assemblyName = "GeneratedAssemblyTest" + Interlocked.Increment(ref _assemblyCounter); var result = GeneratorTestHarness.Run( new Dictionary { ["Test.cs"] = source }, buildProperties, assemblyName: assemblyName, - additionalReferences: additionalReferences); + additionalReferences: additionalReferences + ); result.AssertNoErrors(); var assembly = Emit(result, assemblyName); - var moduleType = assembly.GetType($"TestNamespace.{moduleName}") - ?? throw new InvalidOperationException( - $"The compiled assembly has no type 'TestNamespace.{moduleName}'. " + - $"Types present: {string.Join(", ", assembly.GetTypes().Select(t => t.FullName))}"); + var moduleType = + assembly.GetType($"TestNamespace.{moduleName}") + ?? throw new InvalidOperationException( + $"The compiled assembly has no type 'TestNamespace.{moduleName}'. " + + $"Types present: {string.Join(", ", assembly.GetTypes().Select(t => t.FullName))}" + ); - if (Activator.CreateInstance(moduleType) is not IDependencyModule module) { + if (Activator.CreateInstance(moduleType) is not IDependencyModule module) + { throw new InvalidOperationException( - $"'{moduleType.FullName}' did not implement IDependencyModule. The generator should " + - "have added that to the partial declaration."); + $"'{moduleType.FullName}' did not implement IDependencyModule. The generator should " + + "have added that to the partial declaration." + ); } var services = new ServiceCollection(); @@ -84,17 +92,24 @@ public static GeneratedAssembly Create( return new GeneratedAssembly(assembly, services); } - private static Assembly Emit(GeneratorResult result, string assemblyName) { + private static Assembly Emit(GeneratorResult result, string assemblyName) + { using var stream = new MemoryStream(); EmitResult emitResult = result.Compilation.Emit(stream); - Assert.True(emitResult.Success, - $"The generated code did not emit. Errors:{Environment.NewLine}" + - string.Join(Environment.NewLine, - emitResult.Diagnostics - .Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) - .Select(diagnostic => $" {diagnostic.Id} {diagnostic.GetMessage()}"))); + Assert.True( + emitResult.Success, + $"The generated code did not emit. Errors:{Environment.NewLine}" + + string.Join( + Environment.NewLine, + emitResult + .Diagnostics.Where(diagnostic => + diagnostic.Severity == DiagnosticSeverity.Error + ) + .Select(diagnostic => $" {diagnostic.Id} {diagnostic.GetMessage()}") + ) + ); // Loaded into the default context so it binds against the DependencyModules.Runtime already // loaded by this test assembly. That is what lets tests cast to IDependencyModule and @@ -108,7 +123,8 @@ private static Assembly Emit(GeneratorResult result, string assemblyName) { public Type Type(string name) => _assembly.GetType($"TestNamespace.{name}") ?? throw new InvalidOperationException( - $"No type 'TestNamespace.{name}'. Present: {string.Join(", ", _assembly.GetTypes().Select(t => t.Name))}"); + $"No type 'TestNamespace.{name}'. Present: {string.Join(", ", _assembly.GetTypes().Select(t => t.Name))}" + ); /// /// Builds a provider from the applied registrations. @@ -118,13 +134,16 @@ public Type Type(string name) => /// /// Resolves a service by type name, failing the test if it was never registered. /// - public object ResolveRequired(string typeName) { + public object ResolveRequired(string typeName) + { var provider = BuildProvider(); var service = provider.GetService(Type(typeName)); - Assert.True(service != null, - $"'{typeName}' did not resolve. Registered service types: " + - string.Join(", ", Services.Select(descriptor => descriptor.ServiceType.Name))); + Assert.True( + service != null, + $"'{typeName}' did not resolve. Registered service types: " + + string.Join(", ", Services.Select(descriptor => descriptor.ServiceType.Name)) + ); return service!; } @@ -132,17 +151,21 @@ public object ResolveRequired(string typeName) { /// /// The descriptor registered for a service type, failing the test if there is not exactly one. /// - public ServiceDescriptor Descriptor(string serviceTypeName) { + public ServiceDescriptor Descriptor(string serviceTypeName) + { var serviceType = Type(serviceTypeName); var matches = Services.Where(descriptor => descriptor.ServiceType == serviceType).ToArray(); - Assert.True(matches.Length == 1, - $"Expected exactly one registration for '{serviceTypeName}', found {matches.Length}."); + Assert.True( + matches.Length == 1, + $"Expected exactly one registration for '{serviceTypeName}', found {matches.Length}." + ); return matches[0]; } - public IReadOnlyList Descriptors(string serviceTypeName) { + public IReadOnlyList Descriptors(string serviceTypeName) + { var serviceType = Type(serviceTypeName); return Services.Where(descriptor => descriptor.ServiceType == serviceType).ToArray(); diff --git a/tests/DependencyModules.Tests/Infrastructure/GeneratorTestHarness.cs b/tests/DependencyModules.Tests/Infrastructure/GeneratorTestHarness.cs index d016661..3700cc4 100644 --- a/tests/DependencyModules.Tests/Infrastructure/GeneratorTestHarness.cs +++ b/tests/DependencyModules.Tests/Infrastructure/GeneratorTestHarness.cs @@ -1,8 +1,7 @@ - using System.Collections; using System.Collections.Immutable; -using System.Runtime.CompilerServices; using System.Reflection; +using System.Runtime.CompilerServices; using DependencyModules.Runtime.Attributes; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -16,8 +15,11 @@ namespace DependencyModules.Tests.Infrastructure; /// Runs the DependencyModules source generator over an in-memory compilation so tests can assert /// on the code it produces without going through a real build. /// -public static class GeneratorTestHarness { - private static readonly Lazy> References = new(BuildReferences); +public static class GeneratorTestHarness +{ + private static readonly Lazy> References = new( + BuildReferences + ); /// /// Compiles , runs the generator, and returns everything it emitted. @@ -34,18 +36,22 @@ public static GeneratorResult Run( OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, string assemblyName = "GeneratorTestAssembly", IReadOnlyList? additionalReferences = null, - IReadOnlyList? generators = null) { - + IReadOnlyList? generators = null + ) + { // MSBuild hands the compiler absolute paths, and the generator compares a file's location // against ProjectDir to decide whether it owns the auto-generated ApplicationModule. // Rooting the test sources under ProjectDir keeps that comparison meaningful. var projectDir = ResolveProjectDir(buildProperties); var syntaxTrees = sources - .Select(pair => CSharpSyntaxTree.ParseText( - pair.Value, - new CSharpParseOptions(LanguageVersion.Latest), - path: Path.Combine(projectDir, pair.Key))) + .Select(pair => + CSharpSyntaxTree.ParseText( + pair.Value, + new CSharpParseOptions(LanguageVersion.Latest), + path: Path.Combine(projectDir, pair.Key) + ) + ) .ToArray(); var compilation = CSharpCompilation.Create( @@ -54,15 +60,24 @@ public static GeneratorResult Run( additionalReferences == null ? References.Value : References.Value.Concat(additionalReferences), - new CSharpCompilationOptions(outputKind, nullableContextOptions: NullableContextOptions.Enable)); + new CSharpCompilationOptions( + outputKind, + nullableContextOptions: NullableContextOptions.Enable + ) + ); var driver = CSharpGeneratorDriver.Create( generators == null ? Generators() : generators.ToArray(), optionsProvider: new TestAnalyzerConfigOptionsProvider(buildProperties), - parseOptions: new CSharpParseOptions(LanguageVersion.Latest)); + parseOptions: new CSharpParseOptions(LanguageVersion.Latest) + ); - driver = (CSharpGeneratorDriver)driver.RunGeneratorsAndUpdateCompilation( - compilation, out var outputCompilation, out var generatorDiagnostics); + driver = (CSharpGeneratorDriver) + driver.RunGeneratorsAndUpdateCompilation( + compilation, + out var outputCompilation, + out var generatorDiagnostics + ); var runResult = driver.GetRunResult(); @@ -70,8 +85,8 @@ public static GeneratorResult Run( // generator can produce the same name twice. Keyed rather than grouped it threw, hiding the // duplication behind a dictionary error; two generators emitting one type's partial twice // is a real defect, so it is recorded and asserted on instead. - var emitted = runResult.Results - .SelectMany(result => result.GeneratedSources) + var emitted = runResult + .Results.SelectMany(result => result.GeneratedSources) .Select(generated => (generated.HintName, Source: generated.SourceText.ToString())) .ToArray(); @@ -87,8 +102,8 @@ public static GeneratorResult Run( // The generator catches its own exceptions and, with no log folder configured, discards // them. Surface them here so a crashing generator fails loudly instead of producing nothing. - var exceptions = runResult.Results - .Select(result => result.Exception) + var exceptions = runResult + .Results.Select(result => result.Exception) .Where(exception => exception != null) .ToArray(); @@ -98,7 +113,8 @@ public static GeneratorResult Run( outputCompilation.GetDiagnostics(), outputCompilation, exceptions!, - duplicateHintNames); + duplicateHintNames + ); } /// @@ -106,8 +122,8 @@ public static GeneratorResult Run( /// public static GeneratorResult Run( string source, - IReadOnlyDictionary? buildProperties = null) => - Run(new Dictionary { ["Test.cs"] = source }, buildProperties); + IReadOnlyDictionary? buildProperties = null + ) => Run(new Dictionary { ["Test.cs"] = source }, buildProperties); /// /// One generator. Conventions, services, decorators and interception all come from it. @@ -119,9 +135,7 @@ public static GeneratorResult Run( /// closed over a registration a convention produced. /// private static ISourceGenerator[] Generators() => - new ISourceGenerator[] { - new SourceGenerator.SourceGenerator().AsSourceGenerator(), - }; + new ISourceGenerator[] { new SourceGenerator.SourceGenerator().AsSourceGenerator() }; /// /// Runs the generator over , then re-runs the same driver over @@ -135,25 +149,34 @@ public static IncrementalRunResult RunIncremental( IReadOnlyDictionary first, IReadOnlyDictionary second, IReadOnlyDictionary? buildProperties = null, - bool withConventions = false) { - + bool withConventions = false + ) + { var projectDir = ResolveProjectDir(buildProperties); Compilation Compile(IReadOnlyDictionary sources) => CSharpCompilation.Create( "GeneratorTestAssembly", - sources.Select(pair => CSharpSyntaxTree.ParseText( - pair.Value, - new CSharpParseOptions(LanguageVersion.Latest), - path: Path.Combine(projectDir, pair.Key))), + sources.Select(pair => + CSharpSyntaxTree.ParseText( + pair.Value, + new CSharpParseOptions(LanguageVersion.Latest), + path: Path.Combine(projectDir, pair.Key) + ) + ), References.Value, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: NullableContextOptions.Enable)); + new CSharpCompilationOptions( + OutputKind.DynamicallyLinkedLibrary, + nullableContextOptions: NullableContextOptions.Enable + ) + ); GeneratorDriver driver = CSharpGeneratorDriver.Create( Generators(), optionsProvider: new TestAnalyzerConfigOptionsProvider(buildProperties), parseOptions: new CSharpParseOptions(LanguageVersion.Latest), - driverOptions: new GeneratorDriverOptions(default, trackIncrementalGeneratorSteps: true)); + driverOptions: new GeneratorDriverOptions(default, trackIncrementalGeneratorSteps: true) + ); driver = driver.RunGenerators(Compile(first)); var firstOutputs = Outputs(driver.GetRunResult()); @@ -161,8 +184,8 @@ Compilation Compile(IReadOnlyDictionary sources) => driver = driver.RunGenerators(Compile(second)); var secondRun = driver.GetRunResult(); - var outputs = secondRun.Results - .SelectMany(result => result.TrackedOutputSteps) + var outputs = secondRun + .Results.SelectMany(result => result.TrackedOutputSteps) .SelectMany(step => step.Value) .SelectMany(step => step.Outputs) .Select(output => (output.Reason, EmittedSource: EmittedSourceCount(output.Value))) @@ -186,10 +209,15 @@ Compilation Compile(IReadOnlyDictionary sources) => private static int EmittedSourceCount(object? value) => value is ITuple { Length: 2 } tuple && tuple[0] is ICollection sources ? sources.Count : 0; - private static IReadOnlyDictionary Outputs(GeneratorDriverRunResult runResult) => - runResult.Results - .SelectMany(result => result.GeneratedSources) - .ToDictionary(generated => generated.HintName, generated => generated.SourceText.ToString()); + private static IReadOnlyDictionary Outputs( + GeneratorDriverRunResult runResult + ) => + runResult + .Results.SelectMany(result => result.GeneratedSources) + .ToDictionary( + generated => generated.HintName, + generated => generated.SourceText.ToString() + ); internal static string DefaultProjectDir { get; } = Path.Combine(Path.GetTempPath(), "GeneratorTest") + Path.DirectorySeparatorChar; @@ -214,19 +242,29 @@ private static string ResolveProjectDir(IReadOnlyDictionary? bui /// sees that in metadata. Off by default because most callers only need plain types to scan. /// public static (MetadataReference Reference, System.Reflection.Assembly Assembly) CompileLibrary( - string source, string assemblyName, bool runGenerator = false) { - + string source, + string assemblyName, + bool runGenerator = false + ) + { Compilation compilation = CSharpCompilation.Create( assemblyName, - new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Latest)) }, + new[] + { + CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Latest)), + }, References.Value, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + ); - if (runGenerator) { - CSharpGeneratorDriver.Create( + if (runGenerator) + { + CSharpGeneratorDriver + .Create( Generators(), optionsProvider: new TestAnalyzerConfigOptionsProvider(null), - parseOptions: new CSharpParseOptions(LanguageVersion.Latest)) + parseOptions: new CSharpParseOptions(LanguageVersion.Latest) + ) .RunGeneratorsAndUpdateCompilation(compilation, out compilation, out _); } @@ -234,36 +272,47 @@ public static (MetadataReference Reference, System.Reflection.Assembly Assembly) var result = compilation.Emit(stream); - Xunit.Assert.True(result.Success, - "The test library did not compile: " + string.Join( - Environment.NewLine, - result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error))); + Xunit.Assert.True( + result.Success, + "The test library did not compile: " + + string.Join( + Environment.NewLine, + result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error) + ) + ); var bytes = stream.ToArray(); var assembly = System.Reflection.Assembly.Load(bytes); // Assembly.Load(byte[]) puts it in the default context but does not make it discoverable by // name, so generated code referencing it fails to bind at run time. The resolver closes that. - lock (LoadedLibraries) { + lock (LoadedLibraries) + { // Two test classes compiling different libraries under one name is a trap worth being // loud about. The resolver below is keyed by name, so the second registration decides // what every earlier reference binds to at run time - and the tests that break are // whichever ones happened to run second, which is a failure that appears and disappears // with the filter you ran. Xunit.Assert.True( - !LibrarySources.TryGetValue(assemblyName, out var previousSource) || - previousSource == source, - $"'{assemblyName}' was compiled more than once from different source. The runtime " + - "resolver is keyed by assembly name, so one of them would silently stand in for the " + - "other. Give each test class its own assembly name."); + !LibrarySources.TryGetValue(assemblyName, out var previousSource) + || previousSource == source, + $"'{assemblyName}' was compiled more than once from different source. The runtime " + + "resolver is keyed by assembly name, so one of them would silently stand in for the " + + "other. Give each test class its own assembly name." + ); LibrarySources[assemblyName] = source; LoadedLibraries[assemblyName] = assembly; - if (!_resolverHooked) { - System.Runtime.Loader.AssemblyLoadContext.Default.Resolving += (_, name) => { - lock (LoadedLibraries) { - return name.Name != null && LoadedLibraries.TryGetValue(name.Name, out var found) + if (!_resolverHooked) + { + System.Runtime.Loader.AssemblyLoadContext.Default.Resolving += (_, name) => + { + lock (LoadedLibraries) + { + return + name.Name != null + && LoadedLibraries.TryGetValue(name.Name, out var found) ? found : null; } @@ -283,39 +332,54 @@ public static (MetadataReference Reference, System.Reflection.Assembly Assembly) private static bool _resolverHooked; - private static ImmutableArray BuildReferences() { + private static ImmutableArray BuildReferences() + { var builder = ImmutableArray.CreateBuilder(); // The framework reference set the test host was resolved against. - if (AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") is string trusted) { - foreach (var path in trusted.Split(Path.PathSeparator)) { - if (path.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && File.Exists(path)) { + if (AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") is string trusted) + { + foreach (var path in trusted.Split(Path.PathSeparator)) + { + if (path.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && File.Exists(path)) + { builder.Add(MetadataReference.CreateFromFile(path)); } } } // The assemblies the generated code actually binds against. - foreach (var assembly in new[] { - typeof(DependencyModuleAttribute).Assembly, - typeof(IServiceCollection).Assembly, - typeof(ServiceCollection).Assembly - }) { + foreach ( + var assembly in new[] + { + typeof(DependencyModuleAttribute).Assembly, + typeof(IServiceCollection).Assembly, + typeof(ServiceCollection).Assembly, + } + ) + { AddAssembly(builder, assembly); } return builder.ToImmutable(); } - private static void AddAssembly(ImmutableArray.Builder builder, Assembly assembly) { - if (string.IsNullOrEmpty(assembly.Location)) { + private static void AddAssembly( + ImmutableArray.Builder builder, + Assembly assembly + ) + { + if (string.IsNullOrEmpty(assembly.Location)) + { return; } var alreadyPresent = builder.Any(reference => - string.Equals(reference.Display, assembly.Location, StringComparison.OrdinalIgnoreCase)); + string.Equals(reference.Display, assembly.Location, StringComparison.OrdinalIgnoreCase) + ); - if (!alreadyPresent) { + if (!alreadyPresent) + { builder.Add(MetadataReference.CreateFromFile(assembly.Location)); } } @@ -331,8 +395,9 @@ public class GeneratorResult( ImmutableArray compilationDiagnostics, Compilation compilation, IReadOnlyList generatorExceptions, - IReadOnlyList? duplicateHintNames = null) { - + IReadOnlyList? duplicateHintNames = null +) +{ public IReadOnlyDictionary GeneratedSources { get; } = generatedSources; public ImmutableArray GeneratorDiagnostics { get; } = generatorDiagnostics; @@ -346,24 +411,31 @@ public class GeneratorResult( /// /// Hint names emitted by more than one generator in the same run. /// - public IReadOnlyList DuplicateHintNames { get; } = duplicateHintNames ?? Array.Empty(); + public IReadOnlyList DuplicateHintNames { get; } = + duplicateHintNames ?? Array.Empty(); public IEnumerable Errors => - GeneratorDiagnostics.Concat(CompilationDiagnostics) + GeneratorDiagnostics + .Concat(CompilationDiagnostics) .Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error); /// /// The single generated file whose hint name contains . /// - public string SourceContaining(string fragment) { + public string SourceContaining(string fragment) + { var matches = GeneratedSources .Where(pair => pair.Key.Contains(fragment, StringComparison.OrdinalIgnoreCase)) .ToArray(); - Assert.True(matches.Length > 0, - $"No generated file matched '{fragment}'. Generated: {string.Join(", ", GeneratedSources.Keys)}"); - Assert.True(matches.Length == 1, - $"'{fragment}' matched more than one generated file: {string.Join(", ", matches.Select(m => m.Key))}"); + Assert.True( + matches.Length > 0, + $"No generated file matched '{fragment}'. Generated: {string.Join(", ", GeneratedSources.Keys)}" + ); + Assert.True( + matches.Length == 1, + $"'{fragment}' matched more than one generated file: {string.Join(", ", matches.Select(m => m.Key))}" + ); return matches[0].Value; } @@ -371,16 +443,26 @@ public string SourceContaining(string fragment) { /// /// Asserts the generator produced output that compiles cleanly. /// - public GeneratorResult AssertNoErrors() { - Assert.True(GeneratorExceptions.Count == 0, - "The generator threw:" + Environment.NewLine + - string.Join(Environment.NewLine, GeneratorExceptions.Select(e => $" {e}"))); + public GeneratorResult AssertNoErrors() + { + Assert.True( + GeneratorExceptions.Count == 0, + "The generator threw:" + + Environment.NewLine + + string.Join(Environment.NewLine, GeneratorExceptions.Select(e => $" {e}")) + ); var errors = Errors.ToArray(); - Assert.True(errors.Length == 0, - "Expected no errors, got:" + Environment.NewLine + - string.Join(Environment.NewLine, errors.Select(e => $" {e.Id} {e.GetMessage()} @ {e.Location.GetLineSpan()}"))); + Assert.True( + errors.Length == 0, + "Expected no errors, got:" + + Environment.NewLine + + string.Join( + Environment.NewLine, + errors.Select(e => $" {e.Id} {e.GetMessage()} @ {e.Location.GetLineSpan()}") + ) + ); return this; } @@ -388,10 +470,12 @@ public GeneratorResult AssertNoErrors() { /// /// All generated files concatenated in a stable order, suitable for snapshotting. /// - public string ToSnapshot() { + public string ToSnapshot() + { var builder = new System.Text.StringBuilder(); - foreach (var pair in GeneratedSources.OrderBy(p => p.Key, StringComparer.Ordinal)) { + foreach (var pair in GeneratedSources.OrderBy(p => p.Key, StringComparer.Ordinal)) + { builder.AppendLine($"// ---- {pair.Key} ----"); builder.AppendLine(pair.Value.Replace("\r\n", "\n").TrimEnd()); builder.AppendLine(); @@ -408,8 +492,9 @@ public string ToSnapshot() { public class IncrementalRunResult( IReadOnlyDictionary firstRun, IReadOnlyDictionary secondRun, - IReadOnlyList<(IncrementalStepRunReason Reason, int EmittedSource)> outputs) { - + IReadOnlyList<(IncrementalStepRunReason Reason, int EmittedSource)> outputs +) +{ public IReadOnlyDictionary FirstRun { get; } = firstRun; public IReadOnlyDictionary SecondRun { get; } = secondRun; @@ -431,38 +516,49 @@ public class IncrementalRunResult( /// what this measures. /// public bool AllOutputsCached => - outputs.Any(output => output.EmittedSource > 0) && - outputs + outputs.Any(output => output.EmittedSource > 0) + && outputs .Where(output => output.EmittedSource > 0) - .All(output => output.Reason - is IncrementalStepRunReason.Cached or IncrementalStepRunReason.Unchanged); + .All(output => + output.Reason + is IncrementalStepRunReason.Cached + or IncrementalStepRunReason.Unchanged + ); } -internal class TestAnalyzerConfigOptionsProvider(IReadOnlyDictionary? buildProperties) - : AnalyzerConfigOptionsProvider { - - public override AnalyzerConfigOptions GlobalOptions { get; } = new TestAnalyzerConfigOptions(buildProperties); +internal class TestAnalyzerConfigOptionsProvider( + IReadOnlyDictionary? buildProperties +) : AnalyzerConfigOptionsProvider +{ + public override AnalyzerConfigOptions GlobalOptions { get; } = + new TestAnalyzerConfigOptions(buildProperties); public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => GlobalOptions; public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => GlobalOptions; } -internal class TestAnalyzerConfigOptions : AnalyzerConfigOptions { +internal class TestAnalyzerConfigOptions : AnalyzerConfigOptions +{ private readonly Dictionary _options; - public TestAnalyzerConfigOptions(IReadOnlyDictionary? buildProperties) { - _options = new Dictionary(StringComparer.OrdinalIgnoreCase) { + public TestAnalyzerConfigOptions(IReadOnlyDictionary? buildProperties) + { + _options = new Dictionary(StringComparer.OrdinalIgnoreCase) + { ["build_property.RootNamespace"] = "TestNamespace", - ["build_property.ProjectDir"] = GeneratorTestHarness.DefaultProjectDir + ["build_property.ProjectDir"] = GeneratorTestHarness.DefaultProjectDir, }; - if (buildProperties != null) { - foreach (var pair in buildProperties) { + if (buildProperties != null) + { + foreach (var pair in buildProperties) + { _options["build_property." + pair.Key] = pair.Value; } } } - public override bool TryGetValue(string key, out string value) => _options.TryGetValue(key, out value!); + public override bool TryGetValue(string key, out string value) => + _options.TryGetValue(key, out value!); } diff --git a/tests/DependencyModules.Tests/Infrastructure/ModelFactory.cs b/tests/DependencyModules.Tests/Infrastructure/ModelFactory.cs index 11f3e06..6df7cc5 100644 --- a/tests/DependencyModules.Tests/Infrastructure/ModelFactory.cs +++ b/tests/DependencyModules.Tests/Infrastructure/ModelFactory.cs @@ -7,8 +7,8 @@ namespace DependencyModules.Tests.Infrastructure; /// Builds generator model objects for tests. The model records have wide positional constructors, /// so tests name only the field they care about and take defaults for the rest. /// -public static class ModelFactory { - +public static class ModelFactory +{ public static ModuleEntryPointModel EntryPoint( ModuleEntryPointFeatures features = ModuleEntryPointFeatures.None, string fileLocation = "/project/Module.cs", @@ -22,7 +22,8 @@ public static ModuleEntryPointModel EntryPoint( IReadOnlyList? properties = null, IReadOnlyList? attributes = null, IReadOnlyList? additionalModules = null, - IReadOnlyList? featureTypes = null) => + IReadOnlyList? featureTypes = null + ) => new( features, fileLocation, @@ -37,7 +38,8 @@ public static ModuleEntryPointModel EntryPoint( properties ?? new List(), attributes ?? new List(), additionalModules ?? new List(), - featureTypes ?? new List()); + featureTypes ?? new List() + ); public static DependencyModuleConfigurationModel Configuration( RegistrationType registrationType = RegistrationType.Add, @@ -49,7 +51,8 @@ public static DependencyModuleConfigurationModel Configuration( LogOutputLevel logOutputLevel = LogOutputLevel.Debug, bool generateFactories = false, bool excludeGeneratedCodeFromCoverage = true, - BraceStyle generatedCodeStyle = BraceStyle.Allman) => + BraceStyle generatedCodeStyle = BraceStyle.Allman + ) => new( registrationType, registerSourceGenerator, @@ -60,5 +63,6 @@ public static DependencyModuleConfigurationModel Configuration( logOutputLevel, generateFactories, excludeGeneratedCodeFromCoverage, - generatedCodeStyle); + generatedCodeStyle + ); } diff --git a/tests/DependencyModules.Tests/Infrastructure/Snapshot.cs b/tests/DependencyModules.Tests/Infrastructure/Snapshot.cs index e409436..ee60a21 100644 --- a/tests/DependencyModules.Tests/Infrastructure/Snapshot.cs +++ b/tests/DependencyModules.Tests/Infrastructure/Snapshot.cs @@ -16,7 +16,8 @@ namespace DependencyModules.Tests.Infrastructure; /// [CallerFilePath] is deliberately not used to locate them: deterministic builds /// (ContinuousIntegrationBuild) rewrite source paths to /_/..., which does not exist on disk. /// -public static class Snapshot { +public static class Snapshot +{ private const string UpdateVariable = "UPDATE_SNAPSHOTS"; private const string SnapshotFolderName = "Snapshots"; @@ -31,42 +32,58 @@ public static class Snapshot { /// when updating, and only meaningful when the tests run on the machine that built them. /// private static string? SourceSnapshotDirectory { get; } = - typeof(Snapshot).Assembly - .GetCustomAttributes() + typeof(Snapshot) + .Assembly.GetCustomAttributes() .FirstOrDefault(attribute => attribute.Key == "SnapshotDirectory") ?.Value; public static void Match( string actual, [CallerFilePath] string callerFilePath = "", - [CallerMemberName] string callerMemberName = "") { - + [CallerMemberName] string callerMemberName = "" + ) + { var testClass = Path.GetFileNameWithoutExtension(callerFilePath); var fileName = $"{testClass}.{callerMemberName}.verified.txt"; var normalized = Normalize(actual); - if (ShouldUpdate) { + if (ShouldUpdate) + { Update(fileName, normalized); return; } var snapshotPath = Path.Combine(OutputSnapshotDirectory, fileName); - Assert.True(File.Exists(snapshotPath), - $"Missing snapshot '{fileName}'. Re-run with {UpdateVariable}=1 to create it." + - Environment.NewLine + "Actual output was:" + Environment.NewLine + normalized); + Assert.True( + File.Exists(snapshotPath), + $"Missing snapshot '{fileName}'. Re-run with {UpdateVariable}=1 to create it." + + Environment.NewLine + + "Actual output was:" + + Environment.NewLine + + normalized + ); var expected = Normalize(File.ReadAllText(snapshotPath)); - if (expected != normalized) { - var receivedPath = Path.Combine(OutputSnapshotDirectory, fileName.Replace(".verified.txt", ".received.txt")); + if (expected != normalized) + { + var receivedPath = Path.Combine( + OutputSnapshotDirectory, + fileName.Replace(".verified.txt", ".received.txt") + ); File.WriteAllText(receivedPath, normalized); Assert.Fail( - $"Generated output does not match '{fileName}'." + Environment.NewLine + - $"Wrote actual output to '{receivedPath}'." + Environment.NewLine + - $"If the change is intended, re-run with {UpdateVariable}=1." + Environment.NewLine + - Environment.NewLine + FirstDifference(expected, normalized)); + $"Generated output does not match '{fileName}'." + + Environment.NewLine + + $"Wrote actual output to '{receivedPath}'." + + Environment.NewLine + + $"If the change is intended, re-run with {UpdateVariable}=1." + + Environment.NewLine + + Environment.NewLine + + FirstDifference(expected, normalized) + ); } } @@ -74,14 +91,19 @@ public static void Match( /// Writes to the source tree so the change can be reviewed and committed, and to the build /// output so a subsequent run in the same session reads the updated value. /// - private static void Update(string fileName, string content) { - Assert.True(!string.IsNullOrEmpty(SourceSnapshotDirectory), - $"Cannot update snapshots: the assembly has no SnapshotDirectory metadata. " + - $"Rebuild the test project, then re-run with {UpdateVariable}=1."); - - Assert.True(Directory.Exists(Path.GetDirectoryName(SourceSnapshotDirectory!)), - $"Cannot update snapshots: '{SourceSnapshotDirectory}' is not reachable from this machine. " + - "Snapshots can only be updated from a checkout of the source tree."); + private static void Update(string fileName, string content) + { + Assert.True( + !string.IsNullOrEmpty(SourceSnapshotDirectory), + $"Cannot update snapshots: the assembly has no SnapshotDirectory metadata. " + + $"Rebuild the test project, then re-run with {UpdateVariable}=1." + ); + + Assert.True( + Directory.Exists(Path.GetDirectoryName(SourceSnapshotDirectory!)), + $"Cannot update snapshots: '{SourceSnapshotDirectory}' is not reachable from this machine. " + + "Snapshots can only be updated from a checkout of the source tree." + ); Directory.CreateDirectory(SourceSnapshotDirectory!); File.WriteAllText(Path.Combine(SourceSnapshotDirectory!, fileName), content); @@ -90,29 +112,36 @@ private static void Update(string fileName, string content) { File.WriteAllText(Path.Combine(OutputSnapshotDirectory, fileName), content); } - private static bool ShouldUpdate { - get { + private static bool ShouldUpdate + { + get + { var value = Environment.GetEnvironmentVariable(UpdateVariable); - return !string.IsNullOrEmpty(value) && !value.Equals("0", StringComparison.Ordinal) && - !value.Equals("false", StringComparison.OrdinalIgnoreCase); + return !string.IsNullOrEmpty(value) + && !value.Equals("0", StringComparison.Ordinal) + && !value.Equals("false", StringComparison.OrdinalIgnoreCase); } } - private static string Normalize(string value) => - value.Replace("\r\n", "\n").TrimEnd() + "\n"; + private static string Normalize(string value) => value.Replace("\r\n", "\n").TrimEnd() + "\n"; - private static string FirstDifference(string expected, string actual) { + private static string FirstDifference(string expected, string actual) + { var expectedLines = expected.Split('\n'); var actualLines = actual.Split('\n'); - for (var i = 0; i < Math.Max(expectedLines.Length, actualLines.Length); i++) { + for (var i = 0; i < Math.Max(expectedLines.Length, actualLines.Length); i++) + { var expectedLine = i < expectedLines.Length ? expectedLines[i] : ""; var actualLine = i < actualLines.Length ? actualLines[i] : ""; - if (expectedLine != actualLine) { - return $"First difference at line {i + 1}:" + Environment.NewLine + - $" expected: {expectedLine}" + Environment.NewLine + - $" actual: {actualLine}"; + if (expectedLine != actualLine) + { + return $"First difference at line {i + 1}:" + + Environment.NewLine + + $" expected: {expectedLine}" + + Environment.NewLine + + $" actual: {actualLine}"; } } diff --git a/tests/DependencyModules.Tests/NUnitTests/ModuleTestAttributeTests.cs b/tests/DependencyModules.Tests/NUnitTests/ModuleTestAttributeTests.cs index 1a4bda1..9b420c9 100644 --- a/tests/DependencyModules.Tests/NUnitTests/ModuleTestAttributeTests.cs +++ b/tests/DependencyModules.Tests/NUnitTests/ModuleTestAttributeTests.cs @@ -17,16 +17,16 @@ namespace DependencyModules.Tests.NUnitTests; /// services, and the row kept aside so execution knows which leading arguments are real. Building a /// container here instead would construct every mock in an assembly during discovery. /// -public class ModuleTestAttributeTests { - +public class ModuleTestAttributeTests +{ private interface IService; /// /// The methods under test, carrying real attributes — the same reflection BuildFrom reads /// from at discovery. /// - private class Samples { - + private class Samples + { public void NoParameters() { } public void OneServiceParameter(IService service) { } @@ -56,7 +56,8 @@ public void OneGoodRowAndOneBad(int first, int second) { } } [Fact] - public void BuildsOneCaseWhenThereAreNoRows() { + public void BuildsOneCaseWhenThereAreNoRows() + { var testMethod = Assert.Single(Build(nameof(Samples.OneServiceParameter))); Assert.Equal(nameof(Samples.OneServiceParameter), testMethod.Name); @@ -68,7 +69,8 @@ public void BuildsOneCaseWhenThereAreNoRows() { /// NUnit checks the argument count against the method's parameters when the case is built. /// [Fact] - public void APlaceholderIsSuppliedForEveryParameter() { + public void APlaceholderIsSuppliedForEveryParameter() + { var testMethod = Assert.Single(Build(nameof(Samples.NumberThenService))); Assert.Equal(2, testMethod.Arguments.Length); @@ -76,12 +78,14 @@ public void APlaceholderIsSuppliedForEveryParameter() { } [Fact] - public void AMethodWithNoParametersBuildsWithNoArguments() { + public void AMethodWithNoParametersBuildsWithNoArguments() + { Assert.Empty(Assert.Single(Build(nameof(Samples.NoParameters))).Arguments); } [Fact] - public void BuildsOneCasePerRow() { + public void BuildsOneCasePerRow() + { var built = Build(nameof(Samples.TwoRows)); Assert.Equal(2, built.Length); @@ -93,7 +97,8 @@ public void BuildsOneCasePerRow() { /// A row covers the leading parameters only; the rest stay null until the container fills them. /// [Fact] - public void ARowLeavesTheRemainingParametersToTheContainer() { + public void ARowLeavesTheRemainingParametersToTheContainer() + { var testMethod = Assert.Single(Build(nameof(Samples.OneRowCoveringOneOfTwoParameters))); Assert.Equal(7, testMethod.Arguments[0]); @@ -101,9 +106,12 @@ public void ARowLeavesTheRemainingParametersToTheContainer() { } [Fact] - public void RowsAreNamedAfterTheirOwnArguments() { - Assert.Equal("OneRowCoveringOneOfTwoParameters(7)", - Assert.Single(Build(nameof(Samples.OneRowCoveringOneOfTwoParameters))).Name); + public void RowsAreNamedAfterTheirOwnArguments() + { + Assert.Equal( + "OneRowCoveringOneOfTwoParameters(7)", + Assert.Single(Build(nameof(Samples.OneRowCoveringOneOfTwoParameters))).Name + ); } /// @@ -112,13 +120,17 @@ public void RowsAreNamedAfterTheirOwnArguments() { /// the time the test runs. /// [Fact] - public void ARowsNameOmitsTheParametersTheContainerSupplies() { - Assert.DoesNotContain("null", - Assert.Single(Build(nameof(Samples.OneRowCoveringOneOfTwoParameters))).Name); + public void ARowsNameOmitsTheParametersTheContainerSupplies() + { + Assert.DoesNotContain( + "null", + Assert.Single(Build(nameof(Samples.OneRowCoveringOneOfTwoParameters))).Name + ); } [Fact] - public void StringsAreQuotedAndNullsSpelledOutInARowsName() { + public void StringsAreQuotedAndNullsSpelledOutInARowsName() + { var built = Build(nameof(Samples.RowsNeedingQuoting)); Assert.Equal("RowsNeedingQuoting(1, \"text\")", built[0].Name); @@ -126,7 +138,8 @@ public void StringsAreQuotedAndNullsSpelledOutInARowsName() { } [Fact] - public void ARowCanNameItself() { + public void ARowCanNameItself() + { Assert.Equal("the first one", Assert.Single(Build(nameof(Samples.NamedRow))).Name); } @@ -134,7 +147,8 @@ public void ARowCanNameItself() { /// The case a live fixture cannot cover, because a non-runnable test is a failing one. /// [Fact] - public void ARowWithTooManyArgumentsIsReportedRatherThanThrown() { + public void ARowWithTooManyArgumentsIsReportedRatherThanThrown() + { var testMethod = Assert.Single(Build(nameof(Samples.TooManyArguments))); Assert.Equal(RunState.NotRunnable, testMethod.RunState); @@ -149,7 +163,8 @@ public void ARowWithTooManyArgumentsIsReportedRatherThanThrown() { /// discovery would do. /// [Fact] - public void AGoodRowStillBuildsAlongsideABadOne() { + public void AGoodRowStillBuildsAlongsideABadOne() + { var built = Build(nameof(Samples.OneGoodRowAndOneBad)); Assert.Equal(2, built.Length); @@ -157,7 +172,8 @@ public void AGoodRowStillBuildsAlongsideABadOne() { Assert.Equal(RunState.NotRunnable, built[1].RunState); } - private static TestMethod[] Build(string methodName) { + private static TestMethod[] Build(string methodName) + { var method = typeof(Samples).GetMethod(methodName)!; return new ModuleTestAttribute() diff --git a/tests/DependencyModules.Tests/RuntimeTests/AttributeTests.cs b/tests/DependencyModules.Tests/RuntimeTests/AttributeTests.cs index 896f7ad..6c04419 100644 --- a/tests/DependencyModules.Tests/RuntimeTests/AttributeTests.cs +++ b/tests/DependencyModules.Tests/RuntimeTests/AttributeTests.cs @@ -9,27 +9,31 @@ namespace DependencyModules.Tests.RuntimeTests; /// The service attributes are read by the generator at compile time, but they are also public API: /// their property surface and attribute targets are part of the 1.0 contract. /// -public class AttributeTests { - +public class AttributeTests +{ private interface IThing; [Fact] - public void SingletonService_ReportsSingletonLifetime() { + public void SingletonService_ReportsSingletonLifetime() + { Assert.Equal(ServiceLifetime.Singleton, LifetimeOf(new SingletonServiceAttribute())); } [Fact] - public void ScopedService_ReportsScopedLifetime() { + public void ScopedService_ReportsScopedLifetime() + { Assert.Equal(ServiceLifetime.Scoped, LifetimeOf(new ScopedServiceAttribute())); } [Fact] - public void TransientService_ReportsTransientLifetime() { + public void TransientService_ReportsTransientLifetime() + { Assert.Equal(ServiceLifetime.Transient, LifetimeOf(new TransientServiceAttribute())); } [Fact] - public void SettingLifetimeThroughTheInterface_IsRejected() { + public void SettingLifetimeThroughTheInterface_IsRejected() + { IServiceRegistrationAttribute attribute = new SingletonServiceAttribute(); var exception = Assert.Throws(() => attribute.Lifetime = ServiceLifetime.Scoped); @@ -38,17 +42,20 @@ public void SettingLifetimeThroughTheInterface_IsRejected() { } [Fact] - public void ServiceAttribute_DefaultsToAddRegistration() { + public void ServiceAttribute_DefaultsToAddRegistration() + { Assert.Equal(RegistrationType.Add, new SingletonServiceAttribute().Using); } [Fact] - public void ServiceAttribute_RoundTripsItsProperties() { - var attribute = new SingletonServiceAttribute { + public void ServiceAttribute_RoundTripsItsProperties() + { + var attribute = new SingletonServiceAttribute + { Key = "the-key", As = typeof(IThing), Using = RegistrationType.Try, - Realm = typeof(AttributeTests) + Realm = typeof(AttributeTests), }; Assert.Equal("the-key", attribute.Key); @@ -58,7 +65,8 @@ public void ServiceAttribute_RoundTripsItsProperties() { } [Fact] - public void ServiceAttribute_DefaultsItsOptionalPropertiesToNull() { + public void ServiceAttribute_DefaultsItsOptionalPropertiesToNull() + { var attribute = new TransientServiceAttribute(); Assert.Null(attribute.Key); @@ -67,7 +75,8 @@ public void ServiceAttribute_DefaultsItsOptionalPropertiesToNull() { } [Fact] - public void DependencyModuleAttribute_HasTheDocumentedDefaults() { + public void DependencyModuleAttribute_HasTheDocumentedDefaults() + { var attribute = new DependencyModuleAttribute(); Assert.False(attribute.OnlyRealm); @@ -79,14 +88,16 @@ public void DependencyModuleAttribute_HasTheDocumentedDefaults() { } [Fact] - public void DependencyModuleAttribute_RoundTripsItsProperties() { - var attribute = new DependencyModuleAttribute { + public void DependencyModuleAttribute_RoundTripsItsProperties() + { + var attribute = new DependencyModuleAttribute + { OnlyRealm = true, Using = RegistrationType.Replace, GenerateAttribute = false, RegisterJsonSerializers = true, GenerateFactories = true, - GenerateUseMethod = "UseThing" + GenerateUseMethod = "UseThing", }; Assert.True(attribute.OnlyRealm); @@ -102,8 +113,10 @@ public void DependencyModuleAttribute_RoundTripsItsProperties() { [InlineData(typeof(ScopedServiceAttribute))] [InlineData(typeof(TransientServiceAttribute))] [InlineData(typeof(CrossWireServiceAttribute))] - public void ServiceAttributes_TargetClassesAndMethods(Type attributeType) { - var usage = attributeType.GetCustomAttributes(typeof(AttributeUsageAttribute), false) + public void ServiceAttributes_TargetClassesAndMethods(Type attributeType) + { + var usage = attributeType + .GetCustomAttributes(typeof(AttributeUsageAttribute), false) .Cast() .Single(); @@ -114,7 +127,8 @@ public void ServiceAttributes_TargetClassesAndMethods(Type attributeType) { } [Fact] - public void DependencyModuleAttribute_TargetsClassesAndAssemblies() { + public void DependencyModuleAttribute_TargetsClassesAndAssemblies() + { var usage = typeof(DependencyModuleAttribute) .GetCustomAttributes(typeof(AttributeUsageAttribute), false) .Cast() @@ -125,5 +139,6 @@ public void DependencyModuleAttribute_TargetsClassesAndAssemblies() { Assert.False(usage.Inherited); } - private static ServiceLifetime LifetimeOf(IServiceRegistrationAttribute attribute) => attribute.Lifetime; + private static ServiceLifetime LifetimeOf(IServiceRegistrationAttribute attribute) => + attribute.Lifetime; } diff --git a/tests/DependencyModules.Tests/RuntimeTests/DecoratorHelperTests.cs b/tests/DependencyModules.Tests/RuntimeTests/DecoratorHelperTests.cs index af799eb..713c149 100644 --- a/tests/DependencyModules.Tests/RuntimeTests/DecoratorHelperTests.cs +++ b/tests/DependencyModules.Tests/RuntimeTests/DecoratorHelperTests.cs @@ -8,45 +8,54 @@ namespace DependencyModules.Tests.RuntimeTests; /// The descriptor rewrite is where decoration actually goes wrong, so it is tested directly rather /// than only through generated code. /// -public class DecoratorHelperTests { - - private interface IThing { +public class DecoratorHelperTests +{ + private interface IThing + { string Describe(); } - private class Thing : IThing { + private class Thing : IThing + { public string Describe() => "thing"; } - private class OtherThing : IThing { + private class OtherThing : IThing + { public string Describe() => "other"; } - private class Wrapper(IThing inner) : IThing { + private class Wrapper(IThing inner) : IThing + { public IThing Inner { get; } = inner; public string Describe() => $"wrapped({Inner.Describe()})"; } - private class SecondWrapper(IThing inner) : IThing { + private class SecondWrapper(IThing inner) : IThing + { public string Describe() => $"second({inner.Describe()})"; } - private interface IRepo { + private interface IRepo + { string Describe(); } - private class Repo : IRepo { + private class Repo : IRepo + { public string Describe() => "repo"; } - private class RepoWrapper(IRepo inner) : IRepo { + private class RepoWrapper(IRepo inner) : IRepo + { public string Describe() => $"wrapped({inner.Describe()})"; } private class StringRepo : Repo; - private class DisposableThing : IThing, IDisposable { + private class DisposableThing : IThing, IDisposable + { public int Disposals { get; private set; } public string Describe() => "thing"; @@ -54,7 +63,8 @@ private class DisposableThing : IThing, IDisposable { public void Dispose() => Disposals++; } - private class DisposableWrapper(IThing inner) : IThing, IDisposable { + private class DisposableWrapper(IThing inner) : IThing, IDisposable + { public IThing Inner { get; } = inner; public int Disposals { get; private set; } @@ -68,59 +78,81 @@ private static IThing Resolve(IServiceCollection services) => services.BuildServiceProvider().GetRequiredService(); [Fact] - public void Decorate_WrapsAnImplementationTypeRegistration() { + public void Decorate_WrapsAnImplementationTypeRegistration() + { var services = new ServiceCollection(); services.AddSingleton(); - DecoratorHelper.Decorate(services, typeof(IThing), (_, inner) => new Wrapper((IThing)inner)); + DecoratorHelper.Decorate( + services, + typeof(IThing), + (_, inner) => new Wrapper((IThing)inner) + ); Assert.Equal("wrapped(thing)", Resolve(services).Describe()); } [Fact] - public void Decorate_WrapsAFactoryRegistration() { + public void Decorate_WrapsAFactoryRegistration() + { var services = new ServiceCollection(); services.AddSingleton(_ => new Thing()); - DecoratorHelper.Decorate(services, typeof(IThing), (_, inner) => new Wrapper((IThing)inner)); + DecoratorHelper.Decorate( + services, + typeof(IThing), + (_, inner) => new Wrapper((IThing)inner) + ); Assert.Equal("wrapped(thing)", Resolve(services).Describe()); } [Fact] - public void Decorate_WrapsAnInstanceRegistration() { + public void Decorate_WrapsAnInstanceRegistration() + { var services = new ServiceCollection(); services.AddSingleton(new Thing()); - DecoratorHelper.Decorate(services, typeof(IThing), (_, inner) => new Wrapper((IThing)inner)); + DecoratorHelper.Decorate( + services, + typeof(IThing), + (_, inner) => new Wrapper((IThing)inner) + ); Assert.Equal("wrapped(thing)", Resolve(services).Describe()); } - - - /// /// The generic overload wraps the same shapes the type-driven one does, without a cast at the /// call site. /// [Fact] - public void DecorateOfT_WrapsAnImplementationTypeRegistration() { + public void DecorateOfT_WrapsAnImplementationTypeRegistration() + { var services = new ServiceCollection(); services.AddSingleton(); - DecoratorHelper.Decorate(services, typeof(Wrapper), (_, inner) => new Wrapper(inner)); + DecoratorHelper.Decorate( + services, + typeof(Wrapper), + (_, inner) => new Wrapper(inner) + ); Assert.Equal("wrapped(thing)", Resolve(services).Describe()); } [Fact] - public void DecorateOfT_WrapsEveryRegistrationOfTheService() { + public void DecorateOfT_WrapsEveryRegistrationOfTheService() + { var services = new ServiceCollection(); services.AddSingleton(); services.AddSingleton(); - DecoratorHelper.Decorate(services, typeof(Wrapper), (_, inner) => new Wrapper(inner)); + DecoratorHelper.Decorate( + services, + typeof(Wrapper), + (_, inner) => new Wrapper(inner) + ); var all = services.BuildServiceProvider().GetServices().ToArray(); @@ -129,12 +161,21 @@ public void DecorateOfT_WrapsEveryRegistrationOfTheService() { } [Fact] - public void DecorateOfT_StacksInApplicationOrder() { + public void DecorateOfT_StacksInApplicationOrder() + { var services = new ServiceCollection(); services.AddSingleton(); - DecoratorHelper.Decorate(services, typeof(Wrapper), (_, inner) => new Wrapper(inner)); - DecoratorHelper.Decorate(services, typeof(SecondWrapper), (_, inner) => new SecondWrapper(inner)); + DecoratorHelper.Decorate( + services, + typeof(Wrapper), + (_, inner) => new Wrapper(inner) + ); + DecoratorHelper.Decorate( + services, + typeof(SecondWrapper), + (_, inner) => new SecondWrapper(inner) + ); Assert.Equal("second(wrapped(thing))", Resolve(services).Describe()); } @@ -144,13 +185,22 @@ public void DecorateOfT_StacksInApplicationOrder() { /// generic decorator: one call per closed registration rather than one open-generic call. /// [Fact] - public void DecorateOfT_WrapsEachClosedConstructionIndependently() { + public void DecorateOfT_WrapsEachClosedConstructionIndependently() + { var services = new ServiceCollection(); services.AddSingleton, StringRepo>(); services.AddSingleton(typeof(IRepo), typeof(Repo)); - DecoratorHelper.Decorate>(services, typeof(RepoWrapper), (_, inner) => new RepoWrapper(inner)); - DecoratorHelper.Decorate>(services, typeof(RepoWrapper), (_, inner) => new RepoWrapper(inner)); + DecoratorHelper.Decorate>( + services, + typeof(RepoWrapper), + (_, inner) => new RepoWrapper(inner) + ); + DecoratorHelper.Decorate>( + services, + typeof(RepoWrapper), + (_, inner) => new RepoWrapper(inner) + ); var provider = services.BuildServiceProvider(); @@ -162,17 +212,23 @@ public void DecorateOfT_WrapsEachClosedConstructionIndependently() { /// The inner stays owned by the container here too. /// [Fact] - public void DecorateOfT_LeavesTheInnerImplementationOwnedByTheContainer() { + public void DecorateOfT_LeavesTheInnerImplementationOwnedByTheContainer() + { var services = new ServiceCollection(); services.AddScoped(); - DecoratorHelper.Decorate(services, typeof(DisposableWrapper), (_, inner) => new DisposableWrapper(inner)); + DecoratorHelper.Decorate( + services, + typeof(DisposableWrapper), + (_, inner) => new DisposableWrapper(inner) + ); var provider = services.BuildServiceProvider(); DisposableWrapper wrapper; - using (var scope = provider.CreateScope()) { + using (var scope = provider.CreateScope()) + { wrapper = (DisposableWrapper)scope.ServiceProvider.GetRequiredService(); } diff --git a/tests/DependencyModules.Tests/RuntimeTests/DependencyRegistryTests.cs b/tests/DependencyModules.Tests/RuntimeTests/DependencyRegistryTests.cs index 04350fe..9d752e6 100644 --- a/tests/DependencyModules.Tests/RuntimeTests/DependencyRegistryTests.cs +++ b/tests/DependencyModules.Tests/RuntimeTests/DependencyRegistryTests.cs @@ -10,8 +10,8 @@ namespace DependencyModules.Tests.RuntimeTests; /// DependencyRegistry keeps its state in static fields on a generic type, so every test here uses /// its own marker type to stay isolated from the others. /// -public class DependencyRegistryTests { - +public class DependencyRegistryTests +{ private interface IThing; private class Thing : IThing; @@ -19,9 +19,12 @@ private class Thing : IThing; private class OtherThing : IThing; [Fact] - public void ApplyServices_RunsRegisteredFunctionsInOrder() { + public void ApplyServices_RunsRegisteredFunctionsInOrder() + { DependencyRegistry.Add(services => services.AddSingleton()); - DependencyRegistry.Add(services => services.AddSingleton()); + DependencyRegistry.Add(services => + services.AddSingleton() + ); var collection = new ServiceCollection(); DependencyRegistry.ApplyServices(collection); @@ -34,8 +37,11 @@ public void ApplyServices_RunsRegisteredFunctionsInOrder() { private class OrderMarker; [Fact] - public void Registry_IsIsolatedPerTypeArgument() { - DependencyRegistry.Add(services => services.AddSingleton()); + public void Registry_IsIsolatedPerTypeArgument() + { + DependencyRegistry.Add(services => + services.AddSingleton() + ); var collection = new ServiceCollection(); DependencyRegistry.ApplyServices(collection); @@ -48,7 +54,8 @@ private class IsolationMarkerA; private class IsolationMarkerB; [Fact] - public void Add_WithFactory_RegistersWithRequestedLifetime() { + public void Add_WithFactory_RegistersWithRequestedLifetime() + { DependencyRegistry.Add(_ => new Thing(), ServiceLifetime.Scoped); var collection = new ServiceCollection(); @@ -63,8 +70,13 @@ public void Add_WithFactory_RegistersWithRequestedLifetime() { private class FactoryMarker; [Fact] - public void Add_WithImplementationTypeAndKey_RegistersKeyedService() { - DependencyRegistry.Add(typeof(Thing), ServiceLifetime.Singleton, "the-key"); + public void Add_WithImplementationTypeAndKey_RegistersKeyedService() + { + DependencyRegistry.Add( + typeof(Thing), + ServiceLifetime.Singleton, + "the-key" + ); var collection = new ServiceCollection(); DependencyRegistry.ApplyServices(collection); @@ -84,7 +96,8 @@ private class KeyedMarker; /// ordering has to hold across every decorator in the registry, not just within one group. /// [Fact] - public void ApplyDecorators_AppliesInAscendingOrder() { + public void ApplyDecorators_AppliesInAscendingOrder() + { var applied = new List(); DependencyRegistry.AddDecorator(_ => applied.Add("third"), 30); @@ -99,7 +112,8 @@ public void ApplyDecorators_AppliesInAscendingOrder() { private class OrderedDecoratorMarker; [Fact] - public void ApplyDecorators_WithoutAnOrder_AppliesInRegistrationOrder() { + public void ApplyDecorators_WithoutAnOrder_AppliesInRegistrationOrder() + { var applied = new List(); DependencyRegistry.AddDecorator(_ => applied.Add("first")); @@ -117,7 +131,8 @@ private class UnorderedDecoratorMarker; /// reproducible between runs. /// [Fact] - public void ApplyDecorators_WithEqualOrders_KeepsRegistrationOrder() { + public void ApplyDecorators_WithEqualOrders_KeepsRegistrationOrder() + { var applied = new List(); DependencyRegistry.AddDecorator(_ => applied.Add("first"), 5); @@ -132,12 +147,16 @@ public void ApplyDecorators_WithEqualOrders_KeepsRegistrationOrder() { private class StableDecoratorMarker; [Fact] - public void ApplyDecorators_MixesOrderedAndUnorderedRegistrations() { + public void ApplyDecorators_MixesOrderedAndUnorderedRegistrations() + { var applied = new List(); // An unordered decorator defaults to 0, so a negative order still sits inside it. DependencyRegistry.AddDecorator(_ => applied.Add("default")); - DependencyRegistry.AddDecorator(_ => applied.Add("application"), 1000); + DependencyRegistry.AddDecorator( + _ => applied.Add("application"), + 1000 + ); DependencyRegistry.AddDecorator(_ => applied.Add("innermost"), -10); DependencyRegistry.ApplyDecorators(new ServiceCollection()); @@ -148,9 +167,12 @@ public void ApplyDecorators_MixesOrderedAndUnorderedRegistrations() { private class MixedDecoratorMarker; [Fact] - public void ApplyDecorators_RunsSeparatelyFromServices() { + public void ApplyDecorators_RunsSeparatelyFromServices() + { DependencyRegistry.Add(services => services.AddSingleton()); - DependencyRegistry.AddDecorator(services => services.AddSingleton()); + DependencyRegistry.AddDecorator(services => + services.AddSingleton() + ); var servicesOnly = new ServiceCollection(); DependencyRegistry.ApplyServices(servicesOnly); @@ -165,7 +187,8 @@ public void ApplyDecorators_RunsSeparatelyFromServices() { private class DecoratorMarker; [Fact] - public void GetModules_WithNoRegisteredModules_ReturnsSuppliedModules() { + public void GetModules_WithNoRegisteredModules_ReturnsSuppliedModules() + { var module = new StubModule(); var result = DependencyRegistry.GetModules(module); @@ -174,7 +197,8 @@ public void GetModules_WithNoRegisteredModules_ReturnsSuppliedModules() { } [Fact] - public void GetModules_ConcatenatesRegisteredAndSuppliedModules() { + public void GetModules_ConcatenatesRegisteredAndSuppliedModules() + { var registered = new StubModule(); var supplied = new StubModule(); DependencyRegistry.AddModule(registered); @@ -196,45 +220,69 @@ private class GetModulesConcatMarker; /// thread can be enumerating the same list in ApplyServices. /// [Fact] - public void Add_IsSafeUnderConcurrentWritersAndReaders() { + public void Add_IsSafeUnderConcurrentWritersAndReaders() + { const int writerCount = 8; const int perWriter = 250; var failures = new ConcurrentBag(); using var start = new ManualResetEventSlim(false); - var writers = Enumerable.Range(0, writerCount).Select(_ => new Thread(() => { - try { - start.Wait(TestContext.Current.CancellationToken); - for (var i = 0; i < perWriter; i++) { - DependencyRegistry.Add(services => services.AddSingleton()); + var writers = Enumerable + .Range(0, writerCount) + .Select(_ => new Thread(() => + { + try + { + start.Wait(TestContext.Current.CancellationToken); + for (var i = 0; i < perWriter; i++) + { + DependencyRegistry.Add(services => + services.AddSingleton() + ); + } } - } - catch (Exception e) { - failures.Add(e); - } - })).ToArray(); - - var readers = Enumerable.Range(0, 4).Select(_ => new Thread(() => { - try { - start.Wait(TestContext.Current.CancellationToken); - for (var i = 0; i < perWriter; i++) { - DependencyRegistry.ApplyServices(new ServiceCollection()); + catch (Exception e) + { + failures.Add(e); } - } - catch (Exception e) { - failures.Add(e); - } - })).ToArray(); + })) + .ToArray(); + + var readers = Enumerable + .Range(0, 4) + .Select(_ => new Thread(() => + { + try + { + start.Wait(TestContext.Current.CancellationToken); + for (var i = 0; i < perWriter; i++) + { + DependencyRegistry.ApplyServices( + new ServiceCollection() + ); + } + } + catch (Exception e) + { + failures.Add(e); + } + })) + .ToArray(); - foreach (var thread in writers.Concat(readers)) { + foreach (var thread in writers.Concat(readers)) + { thread.Start(); } start.Set(); - foreach (var thread in writers.Concat(readers)) { - Assert.True(thread.Join(TimeSpan.FromSeconds(30)), "A registry thread did not finish in time."); + foreach (var thread in writers.Concat(readers)) + { + Assert.True( + thread.Join(TimeSpan.FromSeconds(30)), + "A registry thread did not finish in time." + ); } Assert.Empty(failures); @@ -247,42 +295,59 @@ public void Add_IsSafeUnderConcurrentWritersAndReaders() { private class ConcurrencyMarker; [Fact] - public void AddModule_IsSafeUnderConcurrentWriters() { + public void AddModule_IsSafeUnderConcurrentWriters() + { const int writerCount = 8; const int perWriter = 100; var failures = new ConcurrentBag(); using var start = new ManualResetEventSlim(false); - var threads = Enumerable.Range(0, writerCount).Select(_ => new Thread(() => { - try { - start.Wait(TestContext.Current.CancellationToken); - for (var i = 0; i < perWriter; i++) { - DependencyRegistry.AddModule(new StubModule()); + var threads = Enumerable + .Range(0, writerCount) + .Select(_ => new Thread(() => + { + try + { + start.Wait(TestContext.Current.CancellationToken); + for (var i = 0; i < perWriter; i++) + { + DependencyRegistry.AddModule(new StubModule()); + } } - } - catch (Exception e) { - failures.Add(e); - } - })).ToArray(); + catch (Exception e) + { + failures.Add(e); + } + })) + .ToArray(); - foreach (var thread in threads) { + foreach (var thread in threads) + { thread.Start(); } start.Set(); - foreach (var thread in threads) { - Assert.True(thread.Join(TimeSpan.FromSeconds(30)), "A registry thread did not finish in time."); + foreach (var thread in threads) + { + Assert.True( + thread.Join(TimeSpan.FromSeconds(30)), + "A registry thread did not finish in time." + ); } Assert.Empty(failures); - Assert.Equal(writerCount * perWriter, DependencyRegistry.GetModules().Count()); + Assert.Equal( + writerCount * perWriter, + DependencyRegistry.GetModules().Count() + ); } private class ModuleConcurrencyMarker; - private class StubModule : IDependencyModule { + private class StubModule : IDependencyModule + { public void PopulateServiceCollection(IServiceCollection serviceCollection) { } } @@ -293,30 +358,39 @@ public void PopulateServiceCollection(IServiceCollection serviceCollection) { } /// unnoticed. /// [Fact] - public void ApplyServices_UsesAnEnvironmentAlreadyInTheCollection() { + public void ApplyServices_UsesAnEnvironmentAlreadyInTheCollection() + { DependencyRegistry.Add( - (services, environment) => { - if (environment.EnvironmentName == "Development") { + (services, environment) => + { + if (environment.EnvironmentName == "Development") + { services.AddSingleton(); } - }); + } + ); var collection = new ServiceCollection(); collection.AddSingleton(new StubEnvironment("Development")); DependencyRegistry.ApplyServices(collection); - Assert.Contains(collection, descriptor => descriptor.ImplementationType == typeof(OtherThing)); + Assert.Contains( + collection, + descriptor => descriptor.ImplementationType == typeof(OtherThing) + ); } private class SuppliedEnvironmentMarker; [Fact] - public void ApplyDecorators_UsesAnEnvironmentAlreadyInTheCollection() { + public void ApplyDecorators_UsesAnEnvironmentAlreadyInTheCollection() + { var seen = ""; DependencyRegistry.AddDecorator( - (EnvironmentRegistryFunc)((_, environment) => seen = environment.EnvironmentName)); + (EnvironmentRegistryFunc)((_, environment) => seen = environment.EnvironmentName) + ); var collection = new ServiceCollection(); collection.AddSingleton(new StubEnvironment("Staging")); @@ -335,23 +409,28 @@ private class SuppliedDecoratorEnvironmentMarker; [Theory] [InlineData(true)] [InlineData(false)] - public void ApplyServices_RefusesAnEnvironmentItCannotUse(bool registeredByType) { + public void ApplyServices_RefusesAnEnvironmentItCannotUse(bool registeredByType) + { var collection = new ServiceCollection(); - if (registeredByType) { + if (registeredByType) + { collection.AddSingleton(); } - else { + else + { collection.AddSingleton(_ => new StubEnvironment("Development")); } - Assert.Throws( - () => DependencyRegistry.ApplyServices(collection)); + Assert.Throws(() => + DependencyRegistry.ApplyServices(collection) + ); } private class RefusedEnvironmentMarker; - private class StubEnvironment(string name) : IModuleEnvironment { + private class StubEnvironment(string name) : IModuleEnvironment + { public string EnvironmentName => name; public string? Value(string valueName) => null; diff --git a/tests/DependencyModules.Tests/RuntimeTests/FeatureApplicatorTests.cs b/tests/DependencyModules.Tests/RuntimeTests/FeatureApplicatorTests.cs index fa3801f..3bf343d 100644 --- a/tests/DependencyModules.Tests/RuntimeTests/FeatureApplicatorTests.cs +++ b/tests/DependencyModules.Tests/RuntimeTests/FeatureApplicatorTests.cs @@ -10,12 +10,13 @@ namespace DependencyModules.Tests.RuntimeTests; /// modules carrying that feature. It must hand the handler exactly the modules that implement the /// feature type, and nothing else. /// -public class FeatureApplicatorTests { - +public class FeatureApplicatorTests +{ private interface ISomeFeature; [Fact] - public void Apply_PassesOnlyModulesImplementingTheFeature() { + public void Apply_PassesOnlyModulesImplementingTheFeature() + { var handler = new RecordingHandler(); var applicator = new FeatureApplicator(handler); @@ -29,7 +30,8 @@ public void Apply_PassesOnlyModulesImplementingTheFeature() { } [Fact] - public void Apply_WithNoMatchingModules_PassesAnEmptySequence() { + public void Apply_WithNoMatchingModules_PassesAnEmptySequence() + { var handler = new RecordingHandler(); var applicator = new FeatureApplicator(handler); @@ -39,7 +41,8 @@ public void Apply_WithNoMatchingModules_PassesAnEmptySequence() { } [Fact] - public void Apply_PassesTheServiceCollectionThrough() { + public void Apply_PassesTheServiceCollectionThrough() + { var handler = new RecordingHandler(); var applicator = new FeatureApplicator(handler); var collection = new ServiceCollection(); @@ -50,18 +53,23 @@ public void Apply_PassesTheServiceCollectionThrough() { } [Fact] - public void Order_ComesFromTheHandler() { - var applicator = new FeatureApplicator(new RecordingHandler { HandlerOrder = 42 }); + public void Order_ComesFromTheHandler() + { + var applicator = new FeatureApplicator( + new RecordingHandler { HandlerOrder = 42 } + ); Assert.Equal(42, applicator.Order); } [Fact] - public void Order_DefaultsToZero() { + public void Order_DefaultsToZero() + { Assert.Equal(0, new FeatureApplicator(new DefaultOrderHandler()).Order); } - private class RecordingHandler : IDependencyModuleFeature { + private class RecordingHandler : IDependencyModuleFeature + { public int HandlerOrder { get; init; } public int Order => HandlerOrder; @@ -70,21 +78,28 @@ private class RecordingHandler : IDependencyModuleFeature { public IServiceCollection? ReceivedCollection { get; private set; } - public void HandleFeature(IServiceCollection collection, IEnumerable feature) { + public void HandleFeature(IServiceCollection collection, IEnumerable feature) + { ReceivedCollection = collection; Received = feature.ToList(); } } - private class DefaultOrderHandler : IDependencyModuleFeature { - public void HandleFeature(IServiceCollection collection, IEnumerable feature) { } + private class DefaultOrderHandler : IDependencyModuleFeature + { + public void HandleFeature( + IServiceCollection collection, + IEnumerable feature + ) { } } - private class FeatureModule : IDependencyModule, ISomeFeature { + private class FeatureModule : IDependencyModule, ISomeFeature + { public void PopulateServiceCollection(IServiceCollection serviceCollection) { } } - private class PlainModule : IDependencyModule { + private class PlainModule : IDependencyModule + { public void PopulateServiceCollection(IServiceCollection serviceCollection) { } } } diff --git a/tests/DependencyModules.Tests/RuntimeTests/InvocationPipelineTests.cs b/tests/DependencyModules.Tests/RuntimeTests/InvocationPipelineTests.cs index 2eace70..c1d810e 100644 --- a/tests/DependencyModules.Tests/RuntimeTests/InvocationPipelineTests.cs +++ b/tests/DependencyModules.Tests/RuntimeTests/InvocationPipelineTests.cs @@ -17,9 +17,10 @@ namespace DependencyModules.Tests.RuntimeTests; /// named after their member, because overloads would collide; argument fields are _arg0 /// onwards, because a parameter named Self or a keyword would collide too. /// -public class InvocationPipelineTests { - - public interface IWork { +public class InvocationPipelineTests +{ + public interface IWork + { int Double(int value, string label); void Record(string entry); @@ -30,7 +31,8 @@ public interface IWork { } [Fact] - public void SyncMember_ReturnsTheInnerResultThroughBothInterceptors() { + public void SyncMember_ReturnsTheInnerResultThroughBothInterceptors() + { var fixture = new Fixture(); var result = fixture.Service.Double(21, "label"); @@ -43,21 +45,26 @@ public void SyncMember_ReturnsTheInnerResultThroughBothInterceptors() { /// Interceptors nest: the first declared wraps the second, so it enters first and exits last. /// [Fact] - public void SeveralInterceptors_NestInDeclarationOrder() { + public void SeveralInterceptors_NestInDeclarationOrder() + { var fixture = new Fixture(); fixture.Service.Double(1, "label"); - Assert.Equal([ - "first enter IWork.Double", - "second enter IWork.Double", - "second exit IWork.Double", - "first exit IWork.Double" - ], fixture.Log); + Assert.Equal( + [ + "first enter IWork.Double", + "second enter IWork.Double", + "second exit IWork.Double", + "first exit IWork.Double", + ], + fixture.Log + ); } [Fact] - public void VoidMember_RoundTripsThroughNoResult() { + public void VoidMember_RoundTripsThroughNoResult() + { var fixture = new Fixture(); fixture.Service.Record("entry"); @@ -71,7 +78,8 @@ public void VoidMember_RoundTripsThroughNoResult() { /// through the indexer are the fields the last stage passes on. /// [Fact] - public void WritingAnArgument_ReplacesWhatTheImplementationReceives() { + public void WritingAnArgument_ReplacesWhatTheImplementationReceives() + { var fixture = new Fixture(); fixture.First.BeforeProceed = arguments => arguments[0] = 5; @@ -82,12 +90,15 @@ public void WritingAnArgument_ReplacesWhatTheImplementationReceives() { } [Fact] - public void Arguments_ReadByPositionAndName() { + public void Arguments_ReadByPositionAndName() + { var fixture = new Fixture(); var seen = new List(); - fixture.First.BeforeProceed = arguments => { - for (var i = 0; i < arguments.Count; i++) { + fixture.First.BeforeProceed = arguments => + { + for (var i = 0; i < arguments.Count; i++) + { seen.Add($"{arguments.NameAt(i)}={arguments[i]}"); } }; @@ -98,7 +109,8 @@ public void Arguments_ReadByPositionAndName() { } [Fact] - public void Caller_CarriesTheInterfaceAndTheMember() { + public void Caller_CarriesTheInterfaceAndTheMember() + { var fixture = new Fixture(); CallerInfo caller = default; @@ -115,7 +127,8 @@ public void Caller_CarriesTheInterfaceAndTheMember() { /// same next stage. A mutable index would walk past it and call the implementation once. /// [Fact] - public void ProceedingTwice_ReEntersTheSameStage() { + public void ProceedingTwice_ReEntersTheSameStage() + { var fixture = new Fixture(); fixture.First.ProceedCount = 2; @@ -127,7 +140,8 @@ public void ProceedingTwice_ReEntersTheSameStage() { } [Fact] - public void NotProceeding_SkipsTheImplementationAndEverythingBelow() { + public void NotProceeding_SkipsTheImplementationAndEverythingBelow() + { var fixture = new Fixture(); fixture.First.Substitute = 7; @@ -139,14 +153,17 @@ public void NotProceeding_SkipsTheImplementationAndEverythingBelow() { } [Fact] - public void AnException_PropagatesThroughThePipeline() { + public void AnException_PropagatesThroughThePipeline() + { var fixture = new Fixture(); fixture.Implementation.Throw = true; Assert.Throws(() => fixture.Service.Double(1, "label")); - Assert.Equal(["second exit IWork.Double", "first exit IWork.Double"], - fixture.Log.Where(entry => entry.Contains("exit")).ToArray()); + Assert.Equal( + ["second exit IWork.Double", "first exit IWork.Double"], + fixture.Log.Where(entry => entry.Contains("exit")).ToArray() + ); } /// @@ -155,7 +172,8 @@ public void AnException_PropagatesThroughThePipeline() { /// has finished. /// [Fact] - public async Task AsyncMember_ExitsWhenTheWorkFinishesRatherThanWhenTheTaskIsHandedBack() { + public async Task AsyncMember_ExitsWhenTheWorkFinishesRatherThanWhenTheTaskIsHandedBack() + { var fixture = new Fixture(); var gate = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); fixture.Implementation.ComputeGate = gate.Task; @@ -170,12 +188,15 @@ public async Task AsyncMember_ExitsWhenTheWorkFinishesRatherThanWhenTheTaskIsHan var result = await task; Assert.Equal(42, result); - Assert.Equal([ - "first enter IWork.ComputeAsync", - "second enter IWork.ComputeAsync", - "second exit IWork.ComputeAsync", - "first exit IWork.ComputeAsync" - ], fixture.Log); + Assert.Equal( + [ + "first enter IWork.ComputeAsync", + "second enter IWork.ComputeAsync", + "second exit IWork.ComputeAsync", + "first exit IWork.ComputeAsync", + ], + fixture.Log + ); } /// @@ -183,33 +204,39 @@ public async Task AsyncMember_ExitsWhenTheWorkFinishesRatherThanWhenTheTaskIsHan /// ordinary value would observe the construction of the iterator and nothing else. /// [Fact] - public async Task StreamMember_ObservesEachItemAsItIsProduced() { + public async Task StreamMember_ObservesEachItemAsItIsProduced() + { var fixture = new Fixture(); var items = new List(); - await foreach (var item in fixture.Service.Stream(3)) { + await foreach (var item in fixture.Service.Stream(3)) + { items.Add(item); } Assert.Equal([0, 1, 2], items); - Assert.Equal([ - "first enter IWork.Stream", - "second enter IWork.Stream", - "second item 0", - "first item 0", - "second item 1", - "first item 1", - "second item 2", - "first item 2", - "second exit IWork.Stream", - "first exit IWork.Stream" - ], fixture.Log); + Assert.Equal( + [ + "first enter IWork.Stream", + "second enter IWork.Stream", + "second item 0", + "first item 0", + "second item 1", + "first item 1", + "second item 2", + "first item 2", + "second exit IWork.Stream", + "first exit IWork.Stream", + ], + fixture.Log + ); } - private sealed class Fixture { - - public Fixture() { + private sealed class Fixture + { + public Fixture() + { Log = []; Implementation = new WorkImplementation(); First = new TestInterceptor("first", Log); @@ -228,23 +255,26 @@ public Fixture() { public IWork Service { get; } } - private sealed class WorkImplementation : IWork { - + private sealed class WorkImplementation : IWork + { public List Calls { get; } = []; public bool Throw { get; set; } - public int Double(int value, string label) { + public int Double(int value, string label) + { Calls.Add($"Double({value}, {label})"); - if (Throw) { + if (Throw) + { throw new InvalidOperationException("boom"); } return value * 2; } - public void Record(string entry) { + public void Record(string entry) + { Calls.Add($"Record({entry})"); } @@ -255,7 +285,8 @@ public void Record(string entry) { /// public Task ComputeGate { get; set; } = Task.CompletedTask; - public async Task ComputeAsync(int value) { + public async Task ComputeAsync(int value) + { await ComputeGate; Calls.Add($"ComputeAsync({value})"); @@ -263,8 +294,10 @@ public async Task ComputeAsync(int value) { return value * 2; } - public async IAsyncEnumerable Stream(int count) { - for (var i = 0; i < count; i++) { + public async IAsyncEnumerable Stream(int count) + { + for (var i = 0; i < count; i++) + { await Task.Yield(); yield return i; @@ -277,8 +310,10 @@ public async IAsyncEnumerable Stream(int count) { /// rather than expressed as another type, so the wrapper's typed fields stay one type. /// private sealed class TestInterceptor(string name, List log) - : IInterceptor, IAsyncInterceptor, IAsyncEnumerableInterceptor { - + : IInterceptor, + IAsyncInterceptor, + IAsyncEnumerableInterceptor + { public Action? BeforeProceed { get; set; } public Action? BeforeCall { get; set; } @@ -287,56 +322,74 @@ private sealed class TestInterceptor(string name, List log) public object? Substitute { get; set; } - public TResult Intercept(InvocationContext context) { + public TResult Intercept(InvocationContext context) + { log.Add($"{name} enter {context.Caller}"); BeforeCall?.Invoke(context.Caller); BeforeProceed?.Invoke(context.Arguments); - if (Substitute != null) { + if (Substitute != null) + { return (TResult)Substitute; } - try { + try + { var result = default(TResult)!; - for (var i = 0; i < ProceedCount; i++) { + for (var i = 0; i < ProceedCount; i++) + { result = context.Proceed(); } return result; - } finally { + } + finally + { log.Add($"{name} exit {context.Caller}"); } } - public async ValueTask InterceptAsync(AsyncInvocationContext context) { + public async ValueTask InterceptAsync( + AsyncInvocationContext context + ) + { log.Add($"{name} enter {context.Caller}"); BeforeCall?.Invoke(context.Caller); BeforeProceed?.Invoke(context.Arguments); - if (Substitute != null) { + if (Substitute != null) + { return (TResult)Substitute; } - try { + try + { var result = default(TResult)!; - for (var i = 0; i < ProceedCount; i++) { + for (var i = 0; i < ProceedCount; i++) + { result = await context.ProceedAsync(); } return result; - } finally { + } + finally + { log.Add($"{name} exit {context.Caller}"); } } - public async IAsyncEnumerable InterceptStream(StreamInvocationContext context) { + public async IAsyncEnumerable InterceptStream( + StreamInvocationContext context + ) + { log.Add($"{name} enter {context.Caller}"); BeforeCall?.Invoke(context.Caller); BeforeProceed?.Invoke(context.Arguments); - await foreach (var item in context.Proceed()) { + await foreach (var item in context.Proceed()) + { log.Add($"{name} item {item}"); yield return item; @@ -349,7 +402,8 @@ public async IAsyncEnumerable InterceptStream(StreamInvocationCont /// /// Stands in for generated output. Every construct here is one the generator emits. /// - private sealed class Work_Intercepted : IWork { + private sealed class Work_Intercepted : IWork + { private readonly IWork _inner; private readonly TestInterceptor _i0; private readonly TestInterceptor _i1; @@ -359,42 +413,49 @@ private sealed class Work_Intercepted : IWork { private static readonly CallerInfo Caller2 = new(typeof(IWork), "ComputeAsync"); private static readonly CallerInfo Caller3 = new(typeof(IWork), "Stream"); - public Work_Intercepted(IWork inner, TestInterceptor i0, TestInterceptor i1) { + public Work_Intercepted(IWork inner, TestInterceptor i0, TestInterceptor i1) + { _inner = inner; _i0 = i0; _i1 = i1; } - public int Double(int value, string label) { + public int Double(int value, string label) + { var state = new State0(this, value, label); return state.Invoke(0); } - public void Record(string entry) { + public void Record(string entry) + { var state = new State1(this, entry); state.Invoke(0); } - public Task ComputeAsync(int value) { + public Task ComputeAsync(int value) + { var state = new State2(this, value); return state.Invoke(0).AsTask(); } - public IAsyncEnumerable Stream(int count) { + public IAsyncEnumerable Stream(int count) + { var state = new State3(this, count); return state.Invoke(0); } - private sealed class State0 : InvocationState { + private sealed class State0 : InvocationState + { private readonly Work_Intercepted _self; private int _arg0; private string _arg1; - public State0(Work_Intercepted self, int arg0, string arg1) { + public State0(Work_Intercepted self, int arg0, string arg1) + { _self = self; _arg0 = arg0; _arg1 = arg1; @@ -404,15 +465,19 @@ public State0(Work_Intercepted self, int arg0, string arg1) { public override int Count => 2; - public override object? this[int index] { + public override object? this[int index] + { get => - index switch { + index switch + { 0 => _arg0, 1 => _arg1, - _ => throw new ArgumentOutOfRangeException(nameof(index)) + _ => throw new ArgumentOutOfRangeException(nameof(index)), }; - set { - switch (index) { + set + { + switch (index) + { case 0: _arg0 = (int)value!; break; @@ -426,14 +491,17 @@ public override object? this[int index] { } public override string NameAt(int index) => - index switch { + index switch + { 0 => "value", 1 => "label", - _ => throw new ArgumentOutOfRangeException(nameof(index)) + _ => throw new ArgumentOutOfRangeException(nameof(index)), }; - public override int Invoke(int stage) { - switch (stage) { + public override int Invoke(int stage) + { + switch (stage) + { case 0: return _self._i0.Intercept(new InvocationContext(this, 0)); case 1: @@ -444,11 +512,13 @@ public override int Invoke(int stage) { } } - private sealed class State1 : InvocationState { + private sealed class State1 : InvocationState + { private readonly Work_Intercepted _self; private string _arg0; - public State1(Work_Intercepted self, string arg0) { + public State1(Work_Intercepted self, string arg0) + { _self = self; _arg0 = arg0; } @@ -457,14 +527,18 @@ public State1(Work_Intercepted self, string arg0) { public override int Count => 1; - public override object? this[int index] { + public override object? this[int index] + { get => - index switch { + index switch + { 0 => _arg0, - _ => throw new ArgumentOutOfRangeException(nameof(index)) + _ => throw new ArgumentOutOfRangeException(nameof(index)), }; - set { - switch (index) { + set + { + switch (index) + { case 0: _arg0 = (string)value!; break; @@ -475,13 +549,16 @@ public override object? this[int index] { } public override string NameAt(int index) => - index switch { + index switch + { 0 => "entry", - _ => throw new ArgumentOutOfRangeException(nameof(index)) + _ => throw new ArgumentOutOfRangeException(nameof(index)), }; - public override NoResult Invoke(int stage) { - switch (stage) { + public override NoResult Invoke(int stage) + { + switch (stage) + { case 0: return _self._i0.Intercept(new InvocationContext(this, 0)); case 1: @@ -494,11 +571,13 @@ public override NoResult Invoke(int stage) { } } - private sealed class State2 : AsyncInvocationState { + private sealed class State2 : AsyncInvocationState + { private readonly Work_Intercepted _self; private int _arg0; - public State2(Work_Intercepted self, int arg0) { + public State2(Work_Intercepted self, int arg0) + { _self = self; _arg0 = arg0; } @@ -507,14 +586,18 @@ public State2(Work_Intercepted self, int arg0) { public override int Count => 1; - public override object? this[int index] { + public override object? this[int index] + { get => - index switch { + index switch + { 0 => _arg0, - _ => throw new ArgumentOutOfRangeException(nameof(index)) + _ => throw new ArgumentOutOfRangeException(nameof(index)), }; - set { - switch (index) { + set + { + switch (index) + { case 0: _arg0 = (int)value!; break; @@ -525,13 +608,16 @@ public override object? this[int index] { } public override string NameAt(int index) => - index switch { + index switch + { 0 => "value", - _ => throw new ArgumentOutOfRangeException(nameof(index)) + _ => throw new ArgumentOutOfRangeException(nameof(index)), }; - public override ValueTask Invoke(int stage) { - switch (stage) { + public override ValueTask Invoke(int stage) + { + switch (stage) + { case 0: return _self._i0.InterceptAsync(new AsyncInvocationContext(this, 0)); case 1: @@ -542,11 +628,13 @@ public override ValueTask Invoke(int stage) { } } - private sealed class State3 : StreamInvocationState { + private sealed class State3 : StreamInvocationState + { private readonly Work_Intercepted _self; private int _arg0; - public State3(Work_Intercepted self, int arg0) { + public State3(Work_Intercepted self, int arg0) + { _self = self; _arg0 = arg0; } @@ -555,14 +643,18 @@ public State3(Work_Intercepted self, int arg0) { public override int Count => 1; - public override object? this[int index] { + public override object? this[int index] + { get => - index switch { + index switch + { 0 => _arg0, - _ => throw new ArgumentOutOfRangeException(nameof(index)) + _ => throw new ArgumentOutOfRangeException(nameof(index)), }; - set { - switch (index) { + set + { + switch (index) + { case 0: _arg0 = (int)value!; break; @@ -573,13 +665,16 @@ public override object? this[int index] { } public override string NameAt(int index) => - index switch { + index switch + { 0 => "count", - _ => throw new ArgumentOutOfRangeException(nameof(index)) + _ => throw new ArgumentOutOfRangeException(nameof(index)), }; - public override IAsyncEnumerable Invoke(int stage) { - switch (stage) { + public override IAsyncEnumerable Invoke(int stage) + { + switch (stage) + { case 0: return _self._i0.InterceptStream(new StreamInvocationContext(this, 0)); case 1: diff --git a/tests/DependencyModules.Tests/RuntimeTests/ModuleEnvironmentTests.cs b/tests/DependencyModules.Tests/RuntimeTests/ModuleEnvironmentTests.cs index f9ab88e..4dd3dd5 100644 --- a/tests/DependencyModules.Tests/RuntimeTests/ModuleEnvironmentTests.cs +++ b/tests/DependencyModules.Tests/RuntimeTests/ModuleEnvironmentTests.cs @@ -1,18 +1,20 @@ -using Microsoft.Extensions.DependencyInjection; using DependencyModules.Runtime; using DependencyModules.Runtime.Helpers; using DependencyModules.Runtime.Interfaces; +using Microsoft.Extensions.DependencyInjection; using Xunit; namespace DependencyModules.Tests.RuntimeTests; -public class ModuleEnvironmentTests { - +public class ModuleEnvironmentTests +{ [Fact] - public void ValuesComeBackByName() { + public void ValuesComeBackByName() + { var environment = new ModuleEnvironment( "Development", - new Dictionary { ["A"] = "1", ["Empty"] = "" }); + new Dictionary { ["A"] = "1", ["Empty"] = "" } + ); Assert.Equal("Development", environment.EnvironmentName); Assert.Equal("1", environment.Value("A")); @@ -21,18 +23,17 @@ public void ValuesComeBackByName() { } [Fact] - public void ValuesAreOptional() { + public void ValuesAreOptional() + { var environment = new ModuleEnvironment("Production"); Assert.Null(environment.Value("Anything")); } [Fact] - public void ValuesCanBeWrittenInAnInitializer() { - var environment = new ModuleEnvironment("Development") { - { "A", "1" }, - { "Null", null } - }; + public void ValuesCanBeWrittenInAnInitializer() + { + var environment = new ModuleEnvironment("Development") { { "A", "1" }, { "Null", null } }; Assert.Equal("1", environment.Value("A")); Assert.Null(environment.Value("Null")); @@ -43,11 +44,14 @@ public void ValuesCanBeWrittenInAnInitializer() { /// So a fixed set can be seeded and then adjusted, rather than the two forms being exclusive. /// [Fact] - public void AnInitializerOverridesAValueFromTheConstructor() { + public void AnInitializerOverridesAValueFromTheConstructor() + { var environment = new ModuleEnvironment( "Development", - new Dictionary { ["Seed"] = "original", ["Kept"] = "kept" }) { - { "Seed", "replaced" } + new Dictionary { ["Seed"] = "original", ["Kept"] = "kept" } + ) + { + { "Seed", "replaced" }, }; Assert.Equal("replaced", environment.Value("Seed")); @@ -58,7 +62,8 @@ public void AnInitializerOverridesAValueFromTheConstructor() { /// The values are copied, so the dictionary the caller still holds is not written to. /// [Fact] - public void AddDoesNotWriteToTheCallersDictionary() { + public void AddDoesNotWriteToTheCallersDictionary() + { var values = new Dictionary { ["A"] = "1" }; var environment = new ModuleEnvironment("Development", values) { { "B", "2" } }; @@ -71,9 +76,11 @@ public void AddDoesNotWriteToTheCallersDictionary() { /// so copying the values must not quietly reset it to ordinal. /// [Fact] - public void ACallersComparerSurvivesTheCopy() { - var values = new Dictionary(StringComparer.OrdinalIgnoreCase) { - ["Key"] = "value" + public void ACallersComparerSurvivesTheCopy() + { + var values = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["Key"] = "value", }; var environment = new ModuleEnvironment("Development", values); @@ -82,22 +89,22 @@ public void ACallersComparerSurvivesTheCopy() { } [Fact] - public void ValuesEnumerate() { - var environment = new ModuleEnvironment("Development") { - { "A", "1" }, - { "B", "2" } - }; + public void ValuesEnumerate() + { + var environment = new ModuleEnvironment("Development") { { "A", "1" }, { "B", "2" } }; Assert.Equal( new Dictionary { ["A"] = "1", ["B"] = "2" }, - environment.ToDictionary(pair => pair.Key, pair => pair.Value)); + environment.ToDictionary(pair => pair.Key, pair => pair.Value) + ); } /// /// Shared by every application in the process, so it cannot be one of the mutable ones. /// [Fact] - public void NoneCannotBeGivenValues() { + public void NoneCannotBeGivenValues() + { Assert.IsNotType(ModuleEnvironment.None); } @@ -107,10 +114,12 @@ public void NoneCannotBeGivenValues() { private static string UniqueKey() => "DM_TEST_" + Guid.NewGuid().ToString("N"); [Fact] - public void AKeyNotSuppliedFallsBackToAnEnvironmentVariable() { + public void AKeyNotSuppliedFallsBackToAnEnvironmentVariable() + { var key = UniqueKey(); - try { + try + { // Set before the environment is built. An instance caches what it reads, so reading the // key first and setting the variable afterwards would be testing the cache instead of // the fallback — see FallBackToTheProcessIsCachedPerInstance for that. @@ -121,22 +130,28 @@ public void AKeyNotSuppliedFallsBackToAnEnvironmentVariable() { Assert.Equal("from-process", environment.Value(key)); Assert.Equal("value", environment.Value("Supplied")); Assert.Null(environment.Value(UniqueKey())); - } finally { + } + finally + { Environment.SetEnvironmentVariable(key, null); } } [Fact] - public void ASuppliedValueWinsOverAnEnvironmentVariable() { + public void ASuppliedValueWinsOverAnEnvironmentVariable() + { var key = UniqueKey(); - try { + try + { Environment.SetEnvironmentVariable(key, "from-process"); var environment = new ModuleEnvironment("Development") { { key, "supplied" } }; Assert.Equal("supplied", environment.Value(key)); - } finally { + } + finally + { Environment.SetEnvironmentVariable(key, null); } } @@ -145,33 +160,44 @@ public void ASuppliedValueWinsOverAnEnvironmentVariable() { /// Saying a key has no value is how an environment variable of the same name is hidden. /// [Fact] - public void ASuppliedNullHidesAnEnvironmentVariable() { + public void ASuppliedNullHidesAnEnvironmentVariable() + { var key = UniqueKey(); - try { + try + { Environment.SetEnvironmentVariable(key, "from-process"); var environment = new ModuleEnvironment("Development") { { key, null } }; Assert.Null(environment.Value(key)); Assert.False(EnvironmentConditions.HasValue(environment, key)); - } finally { + } + finally + { Environment.SetEnvironmentVariable(key, null); } } [Fact] - public void FallBackCanBeTurnedOff() { + public void FallBackCanBeTurnedOff() + { var key = UniqueKey(); - try { + try + { Environment.SetEnvironmentVariable(key, "from-process"); - var environment = new ModuleEnvironment(false, "Development") { { "Supplied", "value" } }; + var environment = new ModuleEnvironment(false, "Development") + { + { "Supplied", "value" }, + }; Assert.Null(environment.Value(key)); Assert.Equal("value", environment.Value("Supplied")); - } finally { + } + finally + { Environment.SetEnvironmentVariable(key, null); } } @@ -181,11 +207,13 @@ public void FallBackCanBeTurnedOff() { /// constructor that takes a dictionary. /// [Fact] - public void FallBackCanBeTurnedOffWithValuesSuppliedUpFront() { + public void FallBackCanBeTurnedOffWithValuesSuppliedUpFront() + { var environment = new ModuleEnvironment( false, "Development", - new Dictionary { ["A"] = "1" }); + new Dictionary { ["A"] = "1" } + ); Assert.Equal("Development", environment.EnvironmentName); Assert.Equal("1", environment.Value("A")); @@ -195,20 +223,25 @@ public void FallBackCanBeTurnedOffWithValuesSuppliedUpFront() { /// An empty name and no values, whatever the machine running this has set. /// [Fact] - public void NoneDoesNotFallBack() { + public void NoneDoesNotFallBack() + { var key = UniqueKey(); - try { + try + { Environment.SetEnvironmentVariable(key, "from-process"); Assert.Null(ModuleEnvironment.None.Value(key)); - } finally { + } + finally + { Environment.SetEnvironmentVariable(key, null); } } [Fact] - public void NoneHasNoNameAndNoValues() { + public void NoneHasNoNameAndNoValues() + { Assert.Equal("", ModuleEnvironment.None.EnvironmentName); Assert.Null(ModuleEnvironment.None.Value("Anything")); } @@ -217,17 +250,21 @@ public void NoneHasNoNameAndNoValues() { /// A fresh default reads the process as it is now, which is what asking again is for. /// [Fact] - public void DefaultReadsValuesFromTheProcess() { + public void DefaultReadsValuesFromTheProcess() + { // Uniquely named so nothing else in the suite can be looking at it. var key = "DM_TEST_" + Guid.NewGuid().ToString("N"); Assert.Null(ModuleEnvironment.CreateDefault().Value(key)); - try { + try + { Environment.SetEnvironmentVariable(key, "set-after-startup"); Assert.Equal("set-after-startup", ModuleEnvironment.CreateDefault().Value(key)); - } finally { + } + finally + { Environment.SetEnvironmentVariable(key, null); } } @@ -242,14 +279,16 @@ public void DefaultReadsValuesFromTheProcess() { /// default exists for, and re-reading it each call would leave the common path uncached. /// [Fact] - public void AHeldDefaultCachesWhatItRead() { + public void AHeldDefaultCachesWhatItRead() + { var key = "DM_TEST_" + Guid.NewGuid().ToString("N"); var held = ModuleEnvironment.CreateDefault(); Assert.Null(held.Value(key)); - try { + try + { Environment.SetEnvironmentVariable(key, "set-after-the-read"); // The miss was cached, so this instance keeps answering with what it saw. @@ -257,7 +296,9 @@ public void AHeldDefaultCachesWhatItRead() { // Asking for a new one is how a current view is obtained. Assert.Equal("set-after-the-read", ModuleEnvironment.CreateDefault().Value(key)); - } finally { + } + finally + { Environment.SetEnvironmentVariable(key, null); } } @@ -266,10 +307,12 @@ public void AHeldDefaultCachesWhatItRead() { /// The fallback on a named environment caches the same way. /// [Fact] - public void FallBackToTheProcessIsCachedPerInstance() { + public void FallBackToTheProcessIsCachedPerInstance() + { var key = "DM_TEST_" + Guid.NewGuid().ToString("N"); - try { + try + { Environment.SetEnvironmentVariable(key, "first"); var environment = new ModuleEnvironment("Development"); @@ -280,7 +323,9 @@ public void FallBackToTheProcessIsCachedPerInstance() { Assert.Equal("first", environment.Value(key)); Assert.Equal("second", new ModuleEnvironment("Development").Value(key)); - } finally { + } + finally + { Environment.SetEnvironmentVariable(key, null); } } @@ -289,10 +334,12 @@ public void FallBackToTheProcessIsCachedPerInstance() { /// Caching the process must not make the environment report values nobody supplied. /// [Fact] - public void CachedProcessValuesDoNotAppearInEnumeration() { + public void CachedProcessValuesDoNotAppearInEnumeration() + { var key = "DM_TEST_" + Guid.NewGuid().ToString("N"); - try { + try + { Environment.SetEnvironmentVariable(key, "from-process"); var environment = new ModuleEnvironment("Development") { { "Supplied", "yes" } }; @@ -301,8 +348,11 @@ public void CachedProcessValuesDoNotAppearInEnumeration() { Assert.Equal( new Dictionary { ["Supplied"] = "yes" }, - environment.ToDictionary(pair => pair.Key, pair => pair.Value)); - } finally { + environment.ToDictionary(pair => pair.Key, pair => pair.Value) + ); + } + finally + { Environment.SetEnvironmentVariable(key, null); } } @@ -317,8 +367,8 @@ public void CachedProcessValuesDoNotAppearInEnumeration() { /// rather than against a literal name, for this reason. /// [Collection("ProcessEnvironment")] -public class ModuleEnvironmentDefaultNameTests { - +public class ModuleEnvironmentDefaultNameTests +{ private const string AspNetCore = "ASPNETCORE_ENVIRONMENT"; private const string DotNet = "DOTNET_ENVIRONMENT"; @@ -328,16 +378,24 @@ public class ModuleEnvironmentDefaultNameTests { [InlineData(null, "Staging", "Staging")] // ASPNETCORE_ENVIRONMENT wins, matching how a web host resolves it. [InlineData("Development", "Staging", "Development")] - public void DefaultResolvesTheEnvironmentName(string? aspNetCore, string? dotNet, string expected) { + public void DefaultResolvesTheEnvironmentName( + string? aspNetCore, + string? dotNet, + string expected + ) + { var originalAspNetCore = Environment.GetEnvironmentVariable(AspNetCore); var originalDotNet = Environment.GetEnvironmentVariable(DotNet); - try { + try + { Environment.SetEnvironmentVariable(AspNetCore, aspNetCore); Environment.SetEnvironmentVariable(DotNet, dotNet); Assert.Equal(expected, ModuleEnvironment.CreateDefault().EnvironmentName); - } finally { + } + finally + { Environment.SetEnvironmentVariable(AspNetCore, originalAspNetCore); Environment.SetEnvironmentVariable(DotNet, originalDotNet); } @@ -352,24 +410,30 @@ public void DefaultResolvesTheEnvironmentName(string? aspNetCore, string? dotNet /// single answer; several would need a rule for which one the conditions read, and that rule would /// fall out of module ordering rather than out of anything the developer wrote. /// -public class EnvironmentDiscoveryTests { - - private class StubEnvironment(string name) : IModuleEnvironment { +public class EnvironmentDiscoveryTests +{ + private class StubEnvironment(string name) : IModuleEnvironment + { public string EnvironmentName => name; + public string? Value(string valueName) => null; } - private class ProbeModule : IDependencyModule, IEnvironmentServiceCollectionConfiguration { + private class ProbeModule : IDependencyModule, IEnvironmentServiceCollectionConfiguration + { public IModuleEnvironment? Seen { get; private set; } public void PopulateServiceCollection(IServiceCollection serviceCollection) { } - public void ConfigureServices(IServiceCollection services, IModuleEnvironment environment) => - Seen = environment; + public void ConfigureServices( + IServiceCollection services, + IModuleEnvironment environment + ) => Seen = environment; } [Fact] - public void AnInstanceRegisteredBeforeAddModulesIsUsed() { + public void AnInstanceRegisteredBeforeAddModulesIsUsed() + { var environment = new StubEnvironment("Staging"); var module = new ProbeModule(); @@ -382,7 +446,8 @@ public void AnInstanceRegisteredBeforeAddModulesIsUsed() { } [Fact] - public void AnEnvironmentPassedToAddModulesReplacesOneAlreadyRegistered() { + public void AnEnvironmentPassedToAddModulesReplacesOneAlreadyRegistered() + { var registered = new StubEnvironment("Staging"); var passed = new StubEnvironment("Development"); var module = new ProbeModule(); @@ -394,7 +459,10 @@ public void AnEnvironmentPassedToAddModulesReplacesOneAlreadyRegistered() { Assert.Same(passed, module.Seen); // Replaced rather than joined, so what resolves is what decided the registrations. - var descriptor = Assert.Single(collection, d => d.ServiceType == typeof(IModuleEnvironment)); + var descriptor = Assert.Single( + collection, + d => d.ServiceType == typeof(IModuleEnvironment) + ); Assert.Same(passed, descriptor.ImplementationInstance); } @@ -403,38 +471,45 @@ public void AnEnvironmentPassedToAddModulesReplacesOneAlreadyRegistered() { /// rather than ignored in favour of the process default. /// [Fact] - public void AnEnvironmentRegisteredByTypeIsRefused() { + public void AnEnvironmentRegisteredByTypeIsRefused() + { var collection = new ServiceCollection(); collection.AddSingleton(); - var exception = Assert.Throws( - () => collection.AddModules(new ProbeModule())); + var exception = Assert.Throws(() => + collection.AddModules(new ProbeModule()) + ); Assert.Contains("singleton instance", exception.Message); } [Fact] - public void AnEnvironmentRegisteredByFactoryIsRefused() { + public void AnEnvironmentRegisteredByFactoryIsRefused() + { var collection = new ServiceCollection(); collection.AddSingleton(_ => new StubEnvironment("Development")); Assert.Throws(() => collection.AddModules(new ProbeModule())); } - private class StubByType : IModuleEnvironment { + private class StubByType : IModuleEnvironment + { public string EnvironmentName => "Development"; + public string? Value(string valueName) => null; } } -public class EnvironmentConditionsTests { - +public class EnvironmentConditionsTests +{ /// /// Pinned to the values written here. These assert what a condition does with a given set of /// values, so a variable set on the machine running them must not reach a key they never name. /// - private static IModuleEnvironment Env(string name, params (string Key, string? Value)[] values) => - new ModuleEnvironment(false, name, values.ToDictionary(v => v.Key, v => v.Value)); + private static IModuleEnvironment Env( + string name, + params (string Key, string? Value)[] values + ) => new ModuleEnvironment(false, name, values.ToDictionary(v => v.Key, v => v.Value)); [Theory] [InlineData("Development", true)] @@ -445,7 +520,8 @@ public void NameIsIgnoresCase(string environmentName, bool expected) => Assert.Equal(expected, EnvironmentConditions.NameIs(Env(environmentName), "Development")); [Fact] - public void NameIsAcceptsAnyOfSeveral() { + public void NameIsAcceptsAnyOfSeveral() + { Assert.True(EnvironmentConditions.NameIs(Env("Staging"), "Development", "Staging")); Assert.False(EnvironmentConditions.NameIs(Env("Production"), "Development", "Staging")); } @@ -455,14 +531,16 @@ public void NameIsWithNoNamesMatchesNothing() => Assert.False(EnvironmentConditions.NameIs(Env("Development"))); [Fact] - public void HasValueIsPresenceNotTruthiness() { + public void HasValueIsPresenceNotTruthiness() + { Assert.True(EnvironmentConditions.HasValue(Env("Any", ("K", "v")), "K")); Assert.True(EnvironmentConditions.HasValue(Env("Any", ("K", "")), "K")); Assert.False(EnvironmentConditions.HasValue(Env("Any"), "K")); } [Fact] - public void ValueIsComparesOrdinally() { + public void ValueIsComparesOrdinally() + { Assert.True(EnvironmentConditions.ValueIs(Env("Any", ("K", "on")), "K", "on")); Assert.False(EnvironmentConditions.ValueIs(Env("Any", ("K", "On")), "K", "on")); Assert.False(EnvironmentConditions.ValueIs(Env("Any"), "K", "on")); @@ -473,7 +551,8 @@ public void ValueIsComparesOrdinally() { /// module can reach them. Refusing to match beats throwing out of a registration. /// [Fact] - public void ANullEnvironmentMatchesNothing() { + public void ANullEnvironmentMatchesNothing() + { Assert.False(EnvironmentConditions.NameIs(null!, "Development")); Assert.False(EnvironmentConditions.HasValue(null!, "K")); Assert.False(EnvironmentConditions.ValueIs(null!, "K", "v")); diff --git a/tests/DependencyModules.Tests/RuntimeTests/ModuleLoadingTests.cs b/tests/DependencyModules.Tests/RuntimeTests/ModuleLoadingTests.cs index 8c34b4b..34aae0f 100644 --- a/tests/DependencyModules.Tests/RuntimeTests/ModuleLoadingTests.cs +++ b/tests/DependencyModules.Tests/RuntimeTests/ModuleLoadingTests.cs @@ -11,14 +11,15 @@ namespace DependencyModules.Tests.RuntimeTests; /// Covers how DependencyRegistry.LoadModules walks a module graph: de-duplication, ordering, /// feature application, and environment-aware configuration. /// -public class ModuleLoadingTests { - +public class ModuleLoadingTests +{ private interface IThing; private class Thing : IThing; [Fact] - public void LoadModules_AppliesEachModuleOnce() { + public void LoadModules_AppliesEachModuleOnce() + { var module = new CountingModule(); var collection = new ServiceCollection(); @@ -28,7 +29,8 @@ public void LoadModules_AppliesEachModuleOnce() { } [Fact] - public void LoadModules_DeduplicatesEqualModules() { + public void LoadModules_DeduplicatesEqualModules() + { var first = new EquatableModule("same"); var second = new EquatableModule("same"); @@ -39,7 +41,8 @@ public void LoadModules_DeduplicatesEqualModules() { } [Fact] - public void LoadModules_KeepsModulesThatCompareUnequal() { + public void LoadModules_KeepsModulesThatCompareUnequal() + { var first = new EquatableModule("one"); var second = new EquatableModule("two"); @@ -51,7 +54,8 @@ public void LoadModules_KeepsModulesThatCompareUnequal() { } [Fact] - public void LoadModules_SkipsModulesThatOptOut() { + public void LoadModules_SkipsModulesThatOptOut() + { var module = new CountingModule { LoadModule = false }; var collection = new ServiceCollection(); @@ -61,7 +65,8 @@ public void LoadModules_SkipsModulesThatOptOut() { } [Fact] - public void LoadModules_LoadsNestedModulesReturnedByGetModules() { + public void LoadModules_LoadsNestedModulesReturnedByGetModules() + { var child = new CountingModule(); var parent = new CountingModule { Children = [child] }; @@ -73,7 +78,8 @@ public void LoadModules_LoadsNestedModulesReturnedByGetModules() { } [Fact] - public void LoadModules_TerminatesOnCircularModuleReferences() { + public void LoadModules_TerminatesOnCircularModuleReferences() + { var first = new CountingModule(); var second = new CountingModule { Children = [first] }; first.Children = [second]; @@ -86,7 +92,8 @@ public void LoadModules_TerminatesOnCircularModuleReferences() { } [Fact] - public void LoadModules_InvokesServiceCollectionConfiguration() { + public void LoadModules_InvokesServiceCollectionConfiguration() + { var module = new ConfiguringModule(); var collection = new ServiceCollection(); @@ -96,7 +103,8 @@ public void LoadModules_InvokesServiceCollectionConfiguration() { } [Fact] - public void LoadModules_AppliesFeaturesBeforeServices() { + public void LoadModules_AppliesFeaturesBeforeServices() + { var module = new FeatureModule(); var collection = new ServiceCollection(); @@ -111,7 +119,8 @@ public void LoadModules_AppliesFeaturesBeforeServices() { /// never invoked by anything, so a module implementing it silently did nothing. /// [Fact] - public void LoadModules_InvokesConfigureDecorators() { + public void LoadModules_InvokesConfigureDecorators() + { var module = new DecoratingModule(); DependencyRegistry.LoadModules(new ServiceCollection(), module); @@ -124,7 +133,8 @@ public void LoadModules_InvokesConfigureDecorators() { /// registered its services or there would be nothing to decorate. /// [Fact] - public void LoadModules_RunsConfigureDecoratorsAfterAllServices() { + public void LoadModules_RunsConfigureDecoratorsAfterAllServices() + { var decorating = new DecoratingModule(); var registering = new ConfiguringModule(); @@ -136,9 +146,14 @@ public void LoadModules_RunsConfigureDecoratorsAfterAllServices() { } [Fact] - public void LoadModules_CanDecorateARegistrationFromAnotherModule() { + public void LoadModules_CanDecorateARegistrationFromAnotherModule() + { var collection = new ServiceCollection(); - DependencyRegistry.LoadModules(collection, new DecoratingModule(), new ConfiguringModule()); + DependencyRegistry.LoadModules( + collection, + new DecoratingModule(), + new ConfiguringModule() + ); var provider = collection.BuildServiceProvider(); @@ -146,7 +161,8 @@ public void LoadModules_CanDecorateARegistrationFromAnotherModule() { } [Fact] - public void LoadModules_AppliesFeaturesInOrder() { + public void LoadModules_AppliesFeaturesInOrder() + { var module = new OrderedFeatureModule(); var collection = new ServiceCollection(); @@ -156,7 +172,8 @@ public void LoadModules_AppliesFeaturesInOrder() { } [Fact] - public void AddModules_WithEnvironment_PassesEnvironmentToConfiguration() { + public void AddModules_WithEnvironment_PassesEnvironmentToConfiguration() + { var environment = new StubEnvironment("Staging"); var module = new EnvironmentModule(); @@ -175,7 +192,8 @@ public void AddModules_WithEnvironment_PassesEnvironmentToConfiguration() { /// looking at two different answers to the same question. /// [Fact] - public void AddModules_WithoutEnvironment_PassesTheProcessDefaultToConfiguration() { + public void AddModules_WithoutEnvironment_PassesTheProcessDefaultToConfiguration() + { var module = new EnvironmentModule(); var collection = new ServiceCollection(); @@ -185,17 +203,21 @@ public void AddModules_WithoutEnvironment_PassesTheProcessDefaultToConfiguration // The instance registered into the collection, rather than whatever CreateDefault hands out // next — it builds a fresh one per call, so comparing against it would test nothing. var registered = Assert.Single( - collection, descriptor => descriptor.ServiceType == typeof(IModuleEnvironment)); + collection, + descriptor => descriptor.ServiceType == typeof(IModuleEnvironment) + ); Assert.Same(registered.ImplementationInstance, module.ObservedEnvironment); Assert.Equal( ModuleEnvironment.CreateDefault().EnvironmentName, - module.ObservedEnvironment!.EnvironmentName); + module.ObservedEnvironment!.EnvironmentName + ); Assert.True(module.ConfigureCalled); } [Fact] - public void AddModules_WithModuleEnvironmentNone_PassesNoneRatherThanTheDefault() { + public void AddModules_WithModuleEnvironmentNone_PassesNoneRatherThanTheDefault() + { var module = new EnvironmentModule(); var collection = new ServiceCollection(); @@ -205,7 +227,8 @@ public void AddModules_WithModuleEnvironmentNone_PassesNoneRatherThanTheDefault( } [Fact] - public void AddModules_WithEnvironment_RegistersEnvironmentAsSingleton() { + public void AddModules_WithEnvironment_RegistersEnvironmentAsSingleton() + { var environment = new StubEnvironment("Production"); var collection = new ServiceCollection(); @@ -215,7 +238,8 @@ public void AddModules_WithEnvironment_RegistersEnvironmentAsSingleton() { Assert.Same(environment, provider.GetService()); } - private class CountingModule : IDependencyModule { + private class CountingModule : IDependencyModule + { public int ApplyCount { get; private set; } public bool LoadModule { get; init; } = true; @@ -229,7 +253,8 @@ public void PopulateServiceCollection(IServiceCollection serviceCollection) { } public void InternalApplyServices(IServiceCollection serviceCollection) => ApplyCount++; } - private class EquatableModule(string key) : IDependencyModule { + private class EquatableModule(string key) : IDependencyModule + { private string Key { get; } = key; public int ApplyCount { get; private set; } @@ -238,16 +263,19 @@ public void PopulateServiceCollection(IServiceCollection serviceCollection) { } public void InternalApplyServices(IServiceCollection serviceCollection) => ApplyCount++; - public override bool Equals(object? obj) => obj is EquatableModule other && other.Key == Key; + public override bool Equals(object? obj) => + obj is EquatableModule other && other.Key == Key; public override int GetHashCode() => Key.GetHashCode(); } - private class DecoratedThing(IThing inner) : IThing { + private class DecoratedThing(IThing inner) : IThing + { public IThing Inner { get; } = inner; } - private class DecoratingModule : IDependencyModule, IServiceCollectionConfiguration { + private class DecoratingModule : IDependencyModule, IServiceCollectionConfiguration + { public bool ConfigureDecoratorsCalled { get; private set; } public IReadOnlyList? ServicesVisibleWhenDecorating { get; private set; } @@ -256,12 +284,17 @@ public void PopulateServiceCollection(IServiceCollection serviceCollection) { } public void ConfigureServices(IServiceCollection services) { } - public void ConfigureDecorators(IServiceCollection services) { + public void ConfigureDecorators(IServiceCollection services) + { ConfigureDecoratorsCalled = true; - ServicesVisibleWhenDecorating = services.Select(descriptor => descriptor.ServiceType).ToList(); - - for (var i = services.Count - 1; i >= 0; i--) { - if (services[i].ServiceType != typeof(IThing)) { + ServicesVisibleWhenDecorating = services + .Select(descriptor => descriptor.ServiceType) + .ToList(); + + for (var i = services.Count - 1; i >= 0; i--) + { + if (services[i].ServiceType != typeof(IThing)) + { continue; } @@ -270,19 +303,28 @@ public void ConfigureDecorators(IServiceCollection services) { services[i] = new ServiceDescriptor( typeof(IThing), provider => new DecoratedThing( - (IThing)ActivatorUtilities.CreateInstance(provider, inner.ImplementationType!)), - inner.Lifetime); + (IThing) + ActivatorUtilities.CreateInstance(provider, inner.ImplementationType!) + ), + inner.Lifetime + ); } } } - private class ConfiguringModule : IDependencyModule, IServiceCollectionConfiguration { + private class ConfiguringModule : IDependencyModule, IServiceCollectionConfiguration + { public void PopulateServiceCollection(IServiceCollection serviceCollection) { } - public void ConfigureServices(IServiceCollection services) => services.AddSingleton(); + public void ConfigureServices(IServiceCollection services) => + services.AddSingleton(); } - private class FeatureModule : IDependencyModule, IDependencyModuleApplicatorProvider, IServiceCollectionConfiguration { + private class FeatureModule + : IDependencyModule, + IDependencyModuleApplicatorProvider, + IServiceCollectionConfiguration + { public bool FeatureApplied { get; private set; } public bool FeatureAppliedBeforeConfigure { get; private set; } @@ -291,48 +333,60 @@ private class FeatureModule : IDependencyModule, IDependencyModuleApplicatorProv public void PopulateServiceCollection(IServiceCollection serviceCollection) { } - public IEnumerable FeatureApplicators() { - yield return new DelegateApplicator(0, () => { - FeatureApplied = true; - FeatureAppliedBeforeConfigure = !_configured; - }); + public IEnumerable FeatureApplicators() + { + yield return new DelegateApplicator( + 0, + () => + { + FeatureApplied = true; + FeatureAppliedBeforeConfigure = !_configured; + } + ); } public void ConfigureServices(IServiceCollection services) => _configured = true; } - private class OrderedFeatureModule : IDependencyModule, IDependencyModuleApplicatorProvider { + private class OrderedFeatureModule : IDependencyModule, IDependencyModuleApplicatorProvider + { public List AppliedOrders { get; } = []; public void PopulateServiceCollection(IServiceCollection serviceCollection) { } - public IEnumerable FeatureApplicators() { + public IEnumerable FeatureApplicators() + { yield return new DelegateApplicator(10, () => AppliedOrders.Add(10)); yield return new DelegateApplicator(1, () => AppliedOrders.Add(1)); yield return new DelegateApplicator(5, () => AppliedOrders.Add(5)); } } - private class DelegateApplicator(int order, Action onApply) : IFeatureApplicator { + private class DelegateApplicator(int order, Action onApply) : IFeatureApplicator + { public int Order => order; - public void Apply(IServiceCollection services, IReadOnlyList modules) => onApply(); + public void Apply(IServiceCollection services, IReadOnlyList modules) => + onApply(); } - private class EnvironmentModule : IDependencyModule, IEnvironmentServiceCollectionConfiguration { + private class EnvironmentModule : IDependencyModule, IEnvironmentServiceCollectionConfiguration + { public IModuleEnvironment? ObservedEnvironment { get; private set; } public bool ConfigureCalled { get; private set; } public void PopulateServiceCollection(IServiceCollection serviceCollection) { } - public void ConfigureServices(IServiceCollection services, IModuleEnvironment environment) { + public void ConfigureServices(IServiceCollection services, IModuleEnvironment environment) + { ConfigureCalled = true; ObservedEnvironment = environment; } } - private class StubEnvironment(string name) : IModuleEnvironment { + private class StubEnvironment(string name) : IModuleEnvironment + { public string EnvironmentName => name; public string? Value(string valueName) => null; diff --git a/tests/DependencyModules.Tests/RuntimeTests/ServiceCollectionExtensionsTests.cs b/tests/DependencyModules.Tests/RuntimeTests/ServiceCollectionExtensionsTests.cs index 374b0e2..7d28670 100644 --- a/tests/DependencyModules.Tests/RuntimeTests/ServiceCollectionExtensionsTests.cs +++ b/tests/DependencyModules.Tests/RuntimeTests/ServiceCollectionExtensionsTests.cs @@ -8,14 +8,15 @@ namespace DependencyModules.Tests.RuntimeTests; /// /// The AddModule/AddModules overloads are the library's entry point, so each one is covered here. /// -public class ServiceCollectionExtensionsTests { - +public class ServiceCollectionExtensionsTests +{ private interface IThing; private class Thing : IThing; [Fact] - public void AddModule_Generic_PopulatesTheCollection() { + public void AddModule_Generic_PopulatesTheCollection() + { var collection = new ServiceCollection(); collection.AddModule(); @@ -24,7 +25,8 @@ public void AddModule_Generic_PopulatesTheCollection() { } [Fact] - public void AddModule_Generic_ReturnsTheSameCollectionForChaining() { + public void AddModule_Generic_ReturnsTheSameCollectionForChaining() + { var collection = new ServiceCollection(); var returned = collection.AddModule(); @@ -33,7 +35,8 @@ public void AddModule_Generic_ReturnsTheSameCollectionForChaining() { } [Fact] - public void AddModule_Instance_PopulatesTheCollection() { + public void AddModule_Instance_PopulatesTheCollection() + { var collection = new ServiceCollection(); collection.AddModule(new RegisteringModule()); @@ -42,7 +45,8 @@ public void AddModule_Instance_PopulatesTheCollection() { } [Fact] - public void AddModule_Instance_ReturnsTheSameCollectionForChaining() { + public void AddModule_Instance_ReturnsTheSameCollectionForChaining() + { var collection = new ServiceCollection(); var module = new RegisteringModule(); @@ -52,7 +56,8 @@ public void AddModule_Instance_ReturnsTheSameCollectionForChaining() { } [Fact] - public void AddModules_AppliesEveryModule() { + public void AddModules_AppliesEveryModule() + { var first = new RegisteringModule(); var second = new OtherRegisteringModule(); @@ -64,7 +69,8 @@ public void AddModules_AppliesEveryModule() { } [Fact] - public void AddModules_ReturnsTheSameCollectionForChaining() { + public void AddModules_ReturnsTheSameCollectionForChaining() + { var collection = new ServiceCollection(); var returned = collection.AddModules(new RegisteringModule()); @@ -73,7 +79,8 @@ public void AddModules_ReturnsTheSameCollectionForChaining() { } [Fact] - public void AddModules_WithNoModules_LeavesTheCollectionEmpty() { + public void AddModules_WithNoModules_LeavesTheCollectionEmpty() + { var collection = new ServiceCollection(); collection.AddModules(); @@ -82,7 +89,8 @@ public void AddModules_WithNoModules_LeavesTheCollectionEmpty() { } [Fact] - public void AddModules_WithEnvironment_ReturnsTheSameCollectionForChaining() { + public void AddModules_WithEnvironment_ReturnsTheSameCollectionForChaining() + { var collection = new ServiceCollection(); var returned = collection.AddModules(new StubEnvironment(), new RegisteringModule()); @@ -91,7 +99,8 @@ public void AddModules_WithEnvironment_ReturnsTheSameCollectionForChaining() { } [Fact] - public void AddedServices_ResolveFromTheBuiltProvider() { + public void AddedServices_ResolveFromTheBuiltProvider() + { var collection = new ServiceCollection(); collection.AddModule(); @@ -104,21 +113,26 @@ private interface IOther; private class Other : IOther; - private class RegisteringModule : IDependencyModule, IServiceCollectionConfiguration { + private class RegisteringModule : IDependencyModule, IServiceCollectionConfiguration + { public void PopulateServiceCollection(IServiceCollection serviceCollection) => DependencyRegistryTestHelper.LoadSingleModule(serviceCollection, this); - public void ConfigureServices(IServiceCollection services) => services.AddSingleton(); + public void ConfigureServices(IServiceCollection services) => + services.AddSingleton(); } - private class OtherRegisteringModule : IDependencyModule, IServiceCollectionConfiguration { + private class OtherRegisteringModule : IDependencyModule, IServiceCollectionConfiguration + { public void PopulateServiceCollection(IServiceCollection serviceCollection) => DependencyRegistryTestHelper.LoadSingleModule(serviceCollection, this); - public void ConfigureServices(IServiceCollection services) => services.AddSingleton(); + public void ConfigureServices(IServiceCollection services) => + services.AddSingleton(); } - private class StubEnvironment : IModuleEnvironment { + private class StubEnvironment : IModuleEnvironment + { public string EnvironmentName => "Test"; public string? Value(string name) => null; @@ -129,7 +143,8 @@ private class StubEnvironment : IModuleEnvironment { /// Generated modules route PopulateServiceCollection through DependencyRegistry; hand-written test /// modules do the same so they exercise the real code path. /// -internal static class DependencyRegistryTestHelper { +internal static class DependencyRegistryTestHelper +{ public static void LoadSingleModule(IServiceCollection services, IDependencyModule module) => Runtime.Helpers.DependencyRegistry.LoadModules(services, module); } diff --git a/tests/DependencyModules.Tests/TestingTests/SharedRegistrationsTests.cs b/tests/DependencyModules.Tests/TestingTests/SharedRegistrationsTests.cs index 1109c48..318410b 100644 --- a/tests/DependencyModules.Tests/TestingTests/SharedRegistrationsTests.cs +++ b/tests/DependencyModules.Tests/TestingTests/SharedRegistrationsTests.cs @@ -14,8 +14,8 @@ namespace DependencyModules.Tests.TestingTests; /// One test per row of the rule, because the rule has been wrong once already and the way it was /// wrong was a case nobody had written down. /// -public class SharedRegistrationsTests { - +public class SharedRegistrationsTests +{ private interface IThing; private interface IOther; @@ -26,14 +26,17 @@ private class Thing : IThing; private interface IDriver; /// An attribute that declines, which is the only thing worth saying on a parameter. - private class NotSharedAttribute : Attribute, ISharedTestRegistration { + private class NotSharedAttribute : Attribute, ISharedTestRegistration + { public bool Shared => false; } /// A harness naming what it supplies, the way the trigger and web attributes do. - private class DrivenByAttribute : Attribute, ISharedTestRegistration { + private class DrivenByAttribute : Attribute, ISharedTestRegistration + { public IReadOnlyList IsolatedServices(MethodInfo testMethod) => - testMethod.GetParameters() + testMethod + .GetParameters() .Where(parameter => parameter.ParameterType == typeof(IDriver)) .Select(parameter => parameter.ParameterType) .ToArray(); @@ -41,8 +44,12 @@ public IReadOnlyList IsolatedServices(MethodInfo testMethod) => private static IReadOnlyCollection Collect(string method, params Attribute[] known) => SharedRegistrations.Collect( - typeof(SharedRegistrationsTests).GetMethod(method, BindingFlags.NonPublic | BindingFlags.Static)!, - known); + typeof(SharedRegistrationsTests).GetMethod( + method, + BindingFlags.NonPublic | BindingFlags.Static + )!, + known + ); private static void Plain(IThing thing) { } @@ -69,29 +76,34 @@ private static void None() { } /// first: a plain application service the handler writes to and the test reads. /// [Fact] - public void AnUnmarkedParameterIsPinned() { + public void AnUnmarkedParameterIsPinned() + { Assert.Equal([typeof(IThing)], Collect(nameof(Plain))); } [Fact] - public void EveryParameterIsPinned() { + public void EveryParameterIsPinned() + { Assert.Equal([typeof(IThing), typeof(IOther)], Collect(nameof(Bare))); } /// A mock needs no attribute to be pinned, which is what makes the rule general. [Fact] - public void AMockIsPinnedLikeAnythingElse() { + public void AMockIsPinnedLikeAnythingElse() + { Assert.Equal([typeof(IThing)], Collect(nameof(Mocked))); } /// Redundant now, and still allowed: it is how a driving parameter asks for reuse. [Fact] - public void SharedOnAValueParameterChangesNothing() { + public void SharedOnAValueParameterChangesNothing() + { Assert.Equal(Collect(nameof(Plain)), Collect(nameof(Marked))); } [Fact] - public void ATestWithNoParametersPinsNothing() { + public void ATestWithNoParametersPinsNothing() + { Assert.Empty(Collect(nameof(None))); } @@ -101,7 +113,8 @@ public void ATestWithNoParametersPinsNothing() { /// The parameter that drives the application is not pinned, because it builds the containers. /// [Fact] - public void AParameterTheHarnessDrivesWithIsNotPinned() { + public void AParameterTheHarnessDrivesWithIsNotPinned() + { var pinned = Collect(nameof(Driving), new DrivenByAttribute()); Assert.Equal([typeof(IThing)], pinned); @@ -112,18 +125,21 @@ public void AParameterTheHarnessDrivesWithIsNotPinned() { /// either way; it turns the isolation off while the test believes it is on. /// [Fact] - public void IsolatedWinsOverAnExplicitShared() { + public void IsolatedWinsOverAnExplicitShared() + { Assert.Empty(Collect(nameof(DrivingAndMarked), new DrivenByAttribute())); } [Fact] - public void AnAttributeDecliningUnpinsItsParameter() { + public void AnAttributeDecliningUnpinsItsParameter() + { Assert.Empty(Collect(nameof(Declined))); } /// The container itself is the one question pinning cannot answer. [Fact] - public void TheServiceProviderIsNeverPinned() { + public void TheServiceProviderIsNeverPinned() + { Assert.Equal([typeof(IThing)], Collect(nameof(Container))); } @@ -133,9 +149,12 @@ public void TheServiceProviderIsNeverPinned() { /// A harness keeps its own per-test services by naming them, since no parameter holds them. /// [Fact] - public void AnAttributeCanPinWhatNoParameterHolds() { - var shared = new TestExportAttribute(typeof(IOther)) { - Implementation = typeof(Thing), Shared = true + public void AnAttributeCanPinWhatNoParameterHolds() + { + var shared = new TestExportAttribute(typeof(IOther)) + { + Implementation = typeof(Thing), + Shared = true, }; Assert.Equal([typeof(IThing), typeof(IOther)], Collect(nameof(Plain), shared)); @@ -143,7 +162,8 @@ public void AnAttributeCanPinWhatNoParameterHolds() { /// An export nothing holds stays per container until it asks. [Fact] - public void AnExportIsNotPinnedUnlessItAsks() { + public void AnExportIsNotPinnedUnlessItAsks() + { var isolated = new TestExportAttribute(typeof(IOther)) { Implementation = typeof(Thing) }; Assert.Equal([typeof(IThing)], Collect(nameof(Plain), isolated)); @@ -154,7 +174,8 @@ public void AnExportIsNotPinnedUnlessItAsks() { /// harness resolves. Isolating it would recreate the bug the rule exists to fix. /// [Fact] - public void AnExportTheTestHoldsIsPinnedEvenWhenItDeclined() { + public void AnExportTheTestHoldsIsPinnedEvenWhenItDeclined() + { var isolated = new TestExportAttribute(typeof(IThing)) { Implementation = typeof(Thing) }; Assert.Equal([typeof(IThing)], Collect(nameof(Plain), isolated)); diff --git a/tests/DependencyModules.Tests/TestingTests/TestContainerSourceTests.cs b/tests/DependencyModules.Tests/TestingTests/TestContainerSourceTests.cs index 603a051..2c4fa20 100644 --- a/tests/DependencyModules.Tests/TestingTests/TestContainerSourceTests.cs +++ b/tests/DependencyModules.Tests/TestingTests/TestContainerSourceTests.cs @@ -19,26 +19,29 @@ namespace DependencyModules.Tests.TestingTests; [DependencyModule] public partial class ContainerSourceModule { } -public interface ICounter { +public interface ICounter +{ int Value { get; } void Bump(); } [SingletonService] -public class Counter : ICounter { +public class Counter : ICounter +{ public int Value { get; private set; } public void Bump() => Value++; } -public interface IAudit { +public interface IAudit +{ void Record(string what); } [NSubstituteSupport] -public class TestContainerSourceTests { - +public class TestContainerSourceTests +{ /// /// A parameter the test holds crosses every container, with nothing said about it. /// @@ -50,7 +53,10 @@ public class TestContainerSourceTests { /// [ModuleTest(typeof(ContainerSourceModule))] public async Task ABareParameterIsTheSameObjectInEveryContainer( - ITestContainerSource source, ICounter counter) { + ITestContainerSource source, + ICounter counter + ) + { var first = await source.CreateAsync(); var second = await source.CreateAsync(); @@ -71,7 +77,8 @@ public async Task ABareParameterIsTheSameObjectInEveryContainer( /// anything. /// [ModuleTest(typeof(ContainerSourceModule))] - public async Task EachContainerGetsItsOwnApplicationSingleton(ITestContainerSource source) { + public async Task EachContainerGetsItsOwnApplicationSingleton(ITestContainerSource source) + { var first = await source.CreateAsync(); var second = await source.CreateAsync(); @@ -89,7 +96,11 @@ public async Task EachContainerGetsItsOwnApplicationSingleton(ITestContainerSour /// is the one every container was built against, so what a container did is visible here. /// [ModuleTest(typeof(ContainerSourceModule))] - public async Task AMockIsTheSameObjectInEveryContainer(ITestContainerSource source, [Mock] IAudit audit) { + public async Task AMockIsTheSameObjectInEveryContainer( + ITestContainerSource source, + [Mock] IAudit audit + ) + { var first = await source.CreateAsync(); var second = await source.CreateAsync(); @@ -99,7 +110,8 @@ public async Task AMockIsTheSameObjectInEveryContainer(ITestContainerSource sour first.GetRequiredService().Record("one"); second.GetRequiredService().Record("two"); - Received.InOrder(() => { + Received.InOrder(() => + { audit.Record("one"); audit.Record("two"); }); @@ -110,7 +122,11 @@ public async Task AMockIsTheSameObjectInEveryContainer(ITestContainerSource sour /// [ModuleTest(typeof(ContainerSourceModule))] public async Task TheTestsOwnContainerHoldsTheSameMock( - ITestContainerSource source, IServiceProvider own, [Mock] IAudit audit) { + ITestContainerSource source, + IServiceProvider own, + [Mock] IAudit audit + ) + { var built = await source.CreateAsync(); Assert.Same(audit, own.GetRequiredService()); @@ -123,7 +139,8 @@ public async Task TheTestsOwnContainerHoldsTheSameMock( /// that wants a cold one. /// [ModuleTest(typeof(ContainerSourceModule))] - public async Task EveryCallBuildsAContainer(ITestContainerSource source) { + public async Task EveryCallBuildsAContainer(ITestContainerSource source) + { var first = await source.CreateAsync(); var second = await source.CreateAsync(); @@ -140,7 +157,11 @@ public async Task EveryCallBuildsAContainer(ITestContainerSource source) { /// to know which kind of parameter they are looking at before writing it. /// [ModuleTest(typeof(ContainerSourceModule))] - public async Task SharedOnAValueParameterIsRedundant(ITestContainerSource source, [Shared] ICounter counter) { + public async Task SharedOnAValueParameterIsRedundant( + ITestContainerSource source, + [Shared] ICounter counter + ) + { var first = await source.CreateAsync(); var second = await source.CreateAsync(); @@ -162,11 +183,16 @@ public async Task SharedOnAValueParameterIsRedundant(ITestContainerSource source /// clear the bar for sharing without a word at the use site. /// [NSubstituteSupport] -[TestExport(typeof(ICounter), Implementation = typeof(Counter), Lifetime = ServiceLifetime.Singleton)] -public class IsolatedTestExportTests { - +[TestExport( + typeof(ICounter), + Implementation = typeof(Counter), + Lifetime = ServiceLifetime.Singleton +)] +public class IsolatedTestExportTests +{ [ModuleTest] - public async Task AnExportIsRebuiltWithEachContainer(ITestContainerSource source) { + public async Task AnExportIsRebuiltWithEachContainer(ITestContainerSource source) + { var first = await source.CreateAsync(); var second = await source.CreateAsync(); @@ -179,12 +205,17 @@ public async Task AnExportIsRebuiltWithEachContainer(ITestContainerSource source } [NSubstituteSupport] -[TestExport(typeof(ICounter), Implementation = typeof(Counter), Lifetime = ServiceLifetime.Singleton, - Shared = true)] -public class SharedTestExportTests { - +[TestExport( + typeof(ICounter), + Implementation = typeof(Counter), + Lifetime = ServiceLifetime.Singleton, + Shared = true +)] +public class SharedTestExportTests +{ [ModuleTest] - public async Task AnExportAskingToBeSharedCrossesEveryContainer(ITestContainerSource source) { + public async Task AnExportAskingToBeSharedCrossesEveryContainer(ITestContainerSource source) + { var first = await source.CreateAsync(); var second = await source.CreateAsync(); @@ -200,18 +231,23 @@ public async Task AnExportAskingToBeSharedCrossesEveryContainer(ITestContainerSo /// registration said. /// [ModuleTest] - [TestExport(typeof(IAudit), Implementation = typeof(RecordingAudit), - Lifetime = ServiceLifetime.Transient, Shared = true)] - public async Task SharedOverridesATransientLifetime(ITestContainerSource source) { + [TestExport( + typeof(IAudit), + Implementation = typeof(RecordingAudit), + Lifetime = ServiceLifetime.Transient, + Shared = true + )] + public async Task SharedOverridesATransientLifetime(ITestContainerSource source) + { var built = await source.CreateAsync(); Assert.Same(built.GetRequiredService(), built.GetRequiredService()); } } -public class RecordingAudit : IAudit { +public class RecordingAudit : IAudit +{ public List Records { get; } = []; public void Record(string what) => Records.Add(what); } - diff --git a/tests/DependencyModules.Tests/TestingTests/TestContainerSourceUnitTests.cs b/tests/DependencyModules.Tests/TestingTests/TestContainerSourceUnitTests.cs index 316e833..3856308 100644 --- a/tests/DependencyModules.Tests/TestingTests/TestContainerSourceUnitTests.cs +++ b/tests/DependencyModules.Tests/TestingTests/TestContainerSourceUnitTests.cs @@ -8,19 +8,21 @@ namespace DependencyModules.Tests.TestingTests; /// /// The source driven directly, for the parts a test running through a framework cannot observe. /// -public class TestContainerSourceUnitTests { - +public class TestContainerSourceUnitTests +{ private interface IThing; private class Thing : IThing; - private sealed class Tracked : IDisposable { + private sealed class Tracked : IDisposable + { public int Disposals { get; private set; } public void Dispose() => Disposals++; } - private sealed class Harness { + private sealed class Harness + { public List Tracked { get; } = []; public List Started { get; } = []; @@ -29,7 +31,8 @@ private sealed class Harness { public IServiceProvider Pin { get; } - public Harness(Action compose, params Type[] pinned) { + public Harness(Action compose, params Type[] pinned) + { var services = new ServiceCollection(); services.AddSingleton(Source); @@ -43,12 +46,14 @@ public Harness(Action compose, params Type[] pinned) { Pin, pinned, collection => collection.BuildServiceProvider(), - provider => { + provider => + { Started.Add(provider); return ValueTask.CompletedTask; }, - Tracked.Add); + Tracked.Add + ); } } @@ -57,7 +62,8 @@ public Harness(Action compose, params Type[] pinned) { /// rather than the caller's. /// [Fact] - public async Task EveryContainerBuiltIsHandedToTheRunner() { + public async Task EveryContainerBuiltIsHandedToTheRunner() + { var harness = new Harness(services => services.AddSingleton()); var first = await harness.Source.CreateAsync(); @@ -70,7 +76,8 @@ public async Task EveryContainerBuiltIsHandedToTheRunner() { /// Startup runs against each one. A container that skipped it is not the one the test composed. /// [Fact] - public async Task StartupRunsForEveryContainer() { + public async Task StartupRunsForEveryContainer() + { var harness = new Harness(services => services.AddSingleton()); var first = await harness.Source.CreateAsync(); @@ -84,12 +91,11 @@ public async Task StartupRunsForEveryContainer() { /// uses an instance registration rather than a factory returning the same object. /// [Fact] - public async Task APinnedDisposableIsNotDisposedByTheContainersItIsHandedTo() { + public async Task APinnedDisposableIsNotDisposedByTheContainersItIsHandedTo() + { var tracked = new Tracked(); - var harness = new Harness( - services => services.AddSingleton(_ => tracked), - typeof(Tracked)); + var harness = new Harness(services => services.AddSingleton(_ => tracked), typeof(Tracked)); var first = await harness.Source.CreateAsync(); var second = await harness.Source.CreateAsync(); @@ -108,27 +114,29 @@ public async Task APinnedDisposableIsNotDisposedByTheContainersItIsHandedTo() { /// last member would leave anything injecting the sequence one element long. /// [Fact] - public async Task PinningKeepsEveryRegistrationOfAService() { + public async Task PinningKeepsEveryRegistrationOfAService() + { var harness = new Harness( - services => { + services => + { services.AddSingleton(); services.AddSingleton(); }, - typeof(IThing)); + typeof(IThing) + ); var built = await harness.Source.CreateAsync(); Assert.Equal(2, built.GetServices().Count()); - Assert.Equal( - harness.Pin.GetServices(), - built.GetServices()); + Assert.Equal(harness.Pin.GetServices(), built.GetServices()); } /// /// Nothing is built until something asks, so a test that never rebuilds pays for none of this. /// [Fact] - public void NothingIsBuiltUntilAsked() { + public void NothingIsBuiltUntilAsked() + { var harness = new Harness(services => services.AddSingleton()); Assert.Empty(harness.Tracked); @@ -140,11 +148,13 @@ public void NothingIsBuiltUntilAsked() { /// nothing. /// [Fact] - public async Task AnUninitializedSourceRefuses() { + public async Task AnUninitializedSourceRefuses() + { var source = new TestContainerSource(); - var refused = await Assert.ThrowsAsync( - async () => await source.CreateAsync()); + var refused = await Assert.ThrowsAsync(async () => + await source.CreateAsync() + ); Assert.Contains("never initialized", refused.Message); } @@ -160,19 +170,27 @@ public async Task AnUninitializedSourceRefuses() { /// such a parameter and never resolved it. /// [Fact] - public async Task APinnedServiceThatCannotBeBuiltIsLeftAlone() { + public async Task APinnedServiceThatCannotBeBuiltIsLeftAlone() + { var harness = new Harness( - services => { + services => + { services.AddSingleton(); - services.AddSingleton(_ => throw new InvalidOperationException("name the fix")); + services.AddSingleton(_ => + throw new InvalidOperationException("name the fix") + ); }, - typeof(IThing), typeof(string)); + typeof(IThing), + typeof(string) + ); var built = await harness.Source.CreateAsync(); Assert.NotNull(built.GetRequiredService()); - var refused = Assert.Throws(() => built.GetRequiredService()); + var refused = Assert.Throws(() => + built.GetRequiredService() + ); Assert.Equal("name the fix", refused.Message); } @@ -182,10 +200,14 @@ public async Task APinnedServiceThatCannotBeBuiltIsLeftAlone() { /// signature without filtering it first. /// [Fact] - public async Task APinnedTypeNothingRegisteredIsInert() { + public async Task APinnedTypeNothingRegisteredIsInert() + { var harness = new Harness( services => services.AddSingleton(), - typeof(IThing), typeof(int), typeof(Uri)); + typeof(IThing), + typeof(int), + typeof(Uri) + ); var built = await harness.Source.CreateAsync(); @@ -193,4 +215,3 @@ public async Task APinnedTypeNothingRegisteredIsInert() { Assert.Null(built.GetService()); } } - diff --git a/tests/DependencyModules.Tests/TestingTests/TestExportAttributeTests.cs b/tests/DependencyModules.Tests/TestingTests/TestExportAttributeTests.cs index 8d6a9f3..dc76e00 100644 --- a/tests/DependencyModules.Tests/TestingTests/TestExportAttributeTests.cs +++ b/tests/DependencyModules.Tests/TestingTests/TestExportAttributeTests.cs @@ -9,8 +9,8 @@ namespace DependencyModules.Tests.TestingTests; /// [TestExport] lets a test override a registration for the duration of that test, so the lifetime /// and implementation it produces have to match what was asked for. /// -public class TestExportAttributeTests { - +public class TestExportAttributeTests +{ private interface IThing; private class Thing : IThing; @@ -18,17 +18,20 @@ private class Thing : IThing; private class OtherThing : IThing; [Fact] - public void DefaultsToTransient() { + public void DefaultsToTransient() + { Assert.Equal(ServiceLifetime.Transient, new TestExportAttribute(typeof(IThing)).Lifetime); } [Fact] - public void DefaultsToNoSeparateImplementation() { + public void DefaultsToNoSeparateImplementation() + { Assert.Null(new TestExportAttribute(typeof(IThing)).Implementation); } [Fact] - public void ExposesTheServiceItWasGiven() { + public void ExposesTheServiceItWasGiven() + { Assert.Equal(typeof(IThing), new TestExportAttribute(typeof(IThing)).Service); } @@ -36,10 +39,12 @@ public void ExposesTheServiceItWasGiven() { [InlineData(ServiceLifetime.Singleton)] [InlineData(ServiceLifetime.Scoped)] [InlineData(ServiceLifetime.Transient)] - public void RegistersWithTheRequestedLifetime(ServiceLifetime lifetime) { - var attribute = new TestExportAttribute(typeof(IThing)) { + public void RegistersWithTheRequestedLifetime(ServiceLifetime lifetime) + { + var attribute = new TestExportAttribute(typeof(IThing)) + { Implementation = typeof(Thing), - Lifetime = lifetime + Lifetime = lifetime, }; var collection = Setup(attribute); @@ -51,7 +56,8 @@ public void RegistersWithTheRequestedLifetime(ServiceLifetime lifetime) { } [Fact] - public void WithoutAnImplementation_RegistersTheServiceAsItsOwnImplementation() { + public void WithoutAnImplementation_RegistersTheServiceAsItsOwnImplementation() + { var collection = Setup(new TestExportAttribute(typeof(Thing))); var descriptor = Assert.Single(collection); @@ -60,11 +66,15 @@ public void WithoutAnImplementation_RegistersTheServiceAsItsOwnImplementation() } [Fact] - public void RegisteredServiceResolvesFromTheProvider() { - var collection = Setup(new TestExportAttribute(typeof(IThing)) { - Implementation = typeof(OtherThing), - Lifetime = ServiceLifetime.Singleton - }); + public void RegisteredServiceResolvesFromTheProvider() + { + var collection = Setup( + new TestExportAttribute(typeof(IThing)) + { + Implementation = typeof(OtherThing), + Lifetime = ServiceLifetime.Singleton, + } + ); var provider = collection.BuildServiceProvider(); @@ -77,13 +87,17 @@ public void RegisteredServiceResolvesFromTheProvider() { /// the split here means re-adding a lifecycle hook has to be a decision rather than an accident. /// [Fact] - public void RegistersServicesWithoutTakingPartInTestStartup() { - Assert.IsAssignableFrom(new TestExportAttribute(typeof(IThing))); + public void RegistersServicesWithoutTakingPartInTestStartup() + { + Assert.IsAssignableFrom( + new TestExportAttribute(typeof(IThing)) + ); Assert.False(new TestExportAttribute(typeof(IThing)) is ITestStartupAttribute); } [Fact] - public void AppliesToAssembliesClassesAndMethods() { + public void AppliesToAssembliesClassesAndMethods() + { var usage = typeof(TestExportAttribute) .GetCustomAttributes(typeof(AttributeUsageAttribute), false) .Cast() @@ -98,7 +112,8 @@ public void AppliesToAssembliesClassesAndMethods() { /// /// SetupServiceCollection does not read the test method, so tests supply none. /// - private static IServiceCollection Setup(TestExportAttribute attribute) { + private static IServiceCollection Setup(TestExportAttribute attribute) + { var collection = new ServiceCollection(); attribute.SetupServiceCollection(null!, collection); return collection; diff --git a/tests/DependencyModules.Tests/TestingTests/TestParameterResolverTests.cs b/tests/DependencyModules.Tests/TestingTests/TestParameterResolverTests.cs index 0625d32..da59d94 100644 --- a/tests/DependencyModules.Tests/TestingTests/TestParameterResolverTests.cs +++ b/tests/DependencyModules.Tests/TestingTests/TestParameterResolverTests.cs @@ -15,8 +15,8 @@ namespace DependencyModules.Tests.TestingTests; /// unrelated integration test failing, if at all. The resolver is the piece an NUnit integration /// would share, which makes its behaviour a contract rather than an implementation detail. /// -public class TestParameterResolverTests { - +public class TestParameterResolverTests +{ private interface IThing; private class Thing : IThing; @@ -30,14 +30,19 @@ private class Other2 : IThing; /// Takes a dependency the container has and a value it cannot possibly know, which is what /// [InjectValues] is for. /// - private class NeedsAValue(IThing thing, string text) { + private class NeedsAValue(IThing thing, string text) + { public IThing Thing { get; } = thing; public string Text { get; } = text; } [Fact] - public async Task ResolvesAServiceFromTheContainer() { - var arguments = await Resolve(nameof(Samples.OneService), services => services.AddSingleton()); + public async Task ResolvesAServiceFromTheContainer() + { + var arguments = await Resolve( + nameof(Samples.OneService), + services => services.AddSingleton() + ); Assert.IsType(Assert.Single(arguments)); } @@ -46,7 +51,8 @@ public async Task ResolvesAServiceFromTheContainer() { /// A test asking for the container itself cannot have it resolved from the container. /// [Fact] - public async Task ServiceProviderParameterGetsTheContainerItself() { + public async Task ServiceProviderParameterGetsTheContainerItself() + { var (resolver, provider) = Build(nameof(Samples.WantsTheProvider), _ => { }); var arguments = await resolver.ResolveArgumentsAsync(provider, []); @@ -59,9 +65,12 @@ public async Task ServiceProviderParameterGetsTheContainerItself() { /// when the container could have supplied that type. /// [Fact] - public async Task DataTakesTheLeadingParametersAndTheContainerTakesTheRest() { + public async Task DataTakesTheLeadingParametersAndTheContainerTakesTheRest() + { var (resolver, provider) = Build( - nameof(Samples.DataThenService), services => services.AddSingleton()); + nameof(Samples.DataThenService), + services => services.AddSingleton() + ); var arguments = await resolver.ResolveArgumentsAsync(provider, [42]); @@ -76,9 +85,12 @@ public async Task DataTakesTheLeadingParametersAndTheContainerTakesTheRest() { /// only for the parameter holding it. /// [Fact] - public async Task ParameterAttributeRegistrationBeatsTheModuleRegistration() { + public async Task ParameterAttributeRegistrationBeatsTheModuleRegistration() + { var arguments = await Resolve( - nameof(Samples.RegisteringAttribute), services => services.AddSingleton()); + nameof(Samples.RegisteringAttribute), + services => services.AddSingleton() + ); Assert.IsType(Assert.Single(arguments)); } @@ -88,20 +100,27 @@ public async Task ParameterAttributeRegistrationBeatsTheModuleRegistration() { /// attributes can sit on one parameter with the first that answers winning. /// [Fact] - public async Task AProviderReturningNullDefersToTheNextOne() { + public async Task AProviderReturningNullDefersToTheNextOne() + { var arguments = await Resolve( - nameof(Samples.AbstainingThenAnswering), services => services.AddSingleton()); + nameof(Samples.AbstainingThenAnswering), + services => services.AddSingleton() + ); Assert.IsType(Assert.Single(arguments)); } [Fact] - public async Task ResolvesKeyedServices() { + public async Task ResolvesKeyedServices() + { var arguments = await Resolve( - nameof(Samples.Keyed), services => { + nameof(Samples.Keyed), + services => + { services.AddSingleton(); services.AddKeyedSingleton("other"); - }); + } + ); Assert.IsType(Assert.Single(arguments)); } @@ -111,9 +130,12 @@ public async Task ResolvesKeyedServices() { /// under test without registering it. /// [Fact] - public async Task ConstructsAnUnregisteredConcreteType() { + public async Task ConstructsAnUnregisteredConcreteType() + { var arguments = await Resolve( - nameof(Samples.UnregisteredWithInjectedValue), services => services.AddSingleton()); + nameof(Samples.UnregisteredWithInjectedValue), + services => services.AddSingleton() + ); var value = Assert.IsType(Assert.Single(arguments)); @@ -126,12 +148,14 @@ public async Task ConstructsAnUnregisteredConcreteType() { /// parameter would hand back the real service. It fails loudly instead. /// [Fact] - public async Task ResolvingBeforeSetupThrows() { + public async Task ResolvingBeforeSetupThrows() + { var resolver = new TestParameterResolver(ContextFor(nameof(Samples.OneService))); var provider = new ServiceCollection().BuildServiceProvider(); - var exception = await Assert.ThrowsAsync( - () => resolver.ResolveArgumentsAsync(provider, [])); + var exception = await Assert.ThrowsAsync(() => + resolver.ResolveArgumentsAsync(provider, []) + ); Assert.Contains(nameof(TestParameterResolver.SetupServiceCollection), exception.Message); } @@ -143,10 +167,12 @@ public async Task ResolvingBeforeSetupThrows() { /// was wired in. /// [Fact] - public async Task KeyedMockReplacesTheKeyedRegistration() { + public async Task KeyedMockReplacesTheKeyedRegistration() + { var (resolver, provider) = Build( nameof(Samples.KeyedMock), - services => services.AddKeyedSingleton("primary")); + services => services.AddKeyedSingleton("primary") + ); var arguments = await resolver.ResolveArgumentsAsync(provider, []); @@ -158,10 +184,12 @@ public async Task KeyedMockReplacesTheKeyedRegistration() { /// And it does not spill into the unkeyed slot, where nothing asked for it. /// [Fact] - public void KeyedMockRegistersNothingUnkeyed() { + public void KeyedMockRegistersNothingUnkeyed() + { var (_, provider) = Build( nameof(Samples.KeyedMock), - services => services.AddKeyedSingleton("primary")); + services => services.AddKeyedSingleton("primary") + ); Assert.Null(provider.GetService()); } @@ -171,22 +199,28 @@ public void KeyedMockRegistersNothingUnkeyed() { /// siblings real. /// [Fact] - public void KeyedMockLeavesOtherKeysAlone() { + public void KeyedMockLeavesOtherKeysAlone() + { var (_, provider) = Build( nameof(Samples.KeyedMock), - services => { + services => + { services.AddKeyedSingleton("primary"); services.AddKeyedSingleton("secondary"); - }); + } + ); Assert.IsType(provider.GetRequiredKeyedService("secondary")); } /// Control: an unkeyed mock still replaces the unkeyed registration. [Fact] - public async Task UnkeyedMockReplacesTheUnkeyedRegistration() { + public async Task UnkeyedMockReplacesTheUnkeyedRegistration() + { var arguments = await Resolve( - nameof(Samples.UnkeyedMock), services => services.AddSingleton()); + nameof(Samples.UnkeyedMock), + services => services.AddSingleton() + ); Assert.IsType(Assert.Single(arguments)); } @@ -201,7 +235,8 @@ public async Task UnkeyedMockReplacesTheUnkeyedRegistration() { /// is the one the container ends up with. /// [Fact] - public async Task AMockOnAParameter_BeatsARegistrationMadeBeforeIt() { + public async Task AMockOnAParameter_BeatsARegistrationMadeBeforeIt() + { var services = new ServiceCollection(); var resolver = new TestParameterResolver(ContextFor(nameof(Samples.UnkeyedMock))); @@ -222,7 +257,8 @@ public async Task AMockOnAParameter_BeatsARegistrationMadeBeforeIt() { /// than merely supplying an argument. /// [Fact] - public void AMockOnAParameter_IsWhatTheContainerHandsOut() { + public void AMockOnAParameter_IsWhatTheContainerHandsOut() + { var services = new ServiceCollection(); var resolver = new TestParameterResolver(ContextFor(nameof(Samples.UnkeyedMock))); @@ -233,25 +269,34 @@ public void AMockOnAParameter_IsWhatTheContainerHandsOut() { } [Fact] - public void SetupIsOfferedEveryParameter() { + public void SetupIsOfferedEveryParameter() + { var services = new ServiceCollection(); - new TestParameterResolver(ContextFor(nameof(Samples.TwoRegisteringAttributes))) - .SetupServiceCollection(services); + new TestParameterResolver( + ContextFor(nameof(Samples.TwoRegisteringAttributes)) + ).SetupServiceCollection(services); Assert.Equal(2, services.Count); } // ---- harness ------------------------------------------------------------------------------- - private static async Task Resolve(string methodName, Action configure) { + private static async Task Resolve( + string methodName, + Action configure + ) + { var (resolver, provider) = Build(methodName, configure); return await resolver.ResolveArgumentsAsync(provider, []); } private static (TestParameterResolver Resolver, IServiceProvider Provider) Build( - string methodName, Action configure) { + string methodName, + Action configure + ) + { var services = new ServiceCollection(); var resolver = new TestParameterResolver(ContextFor(methodName)); @@ -263,9 +308,12 @@ private static (TestParameterResolver Resolver, IServiceProvider Provider) Build } private static ITestMethodContext ContextFor(string methodName) => - new StubContext(typeof(Samples).GetMethod(methodName, BindingFlags.Public | BindingFlags.Static)!); + new StubContext( + typeof(Samples).GetMethod(methodName, BindingFlags.Public | BindingFlags.Static)! + ); - private class StubContext(MethodInfo method) : ITestMethodContext { + private class StubContext(MethodInfo method) : ITestMethodContext + { public MethodInfo Method { get; } = method; public IReadOnlyList Attributes { get; } = []; } @@ -275,7 +323,8 @@ private class StubContext(MethodInfo method) : ITestMethodContext { /// within this private class so one set of binding flags finds them all. /// [StubMockSupport] - private static class Samples { + private static class Samples + { public static void OneService(IThing thing) { } public static void KeyedMock([Mock] [FromKeyedServices("primary")] IThing thing) { } @@ -292,9 +341,14 @@ public static void AbstainingThenAnswering([Abstains] [RegistersOther] IThing th public static void Keyed([FromKeyedServices("other")] IThing thing) { } - public static void UnregisteredWithInjectedValue([InjectValues("supplied")] NeedsAValue value) { } + public static void UnregisteredWithInjectedValue( + [InjectValues("supplied")] NeedsAValue value + ) { } - public static void TwoRegisteringAttributes([RegistersOther] IThing first, [RegistersOther] IThing second) { } + public static void TwoRegisteringAttributes( + [RegistersOther] IThing first, + [RegistersOther] IThing second + ) { } } /// @@ -302,7 +356,8 @@ public static void TwoRegisteringAttributes([RegistersOther] IThing first, [Regi /// double actually is does not matter here; where it gets registered does. /// [AttributeUsage(AttributeTargets.Class)] - private class StubMockSupportAttribute : Attribute, IMockSupportAttribute { + private class StubMockSupportAttribute : Attribute, IMockSupportAttribute + { public object ProvideMock(Type type) => new Other(); } @@ -311,26 +366,37 @@ private class StubMockSupportAttribute : Attribute, IMockSupportAttribute { /// resolution hand it back. /// [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)] - private class RegistersOtherAttribute : Attribute, ITestParameterValueProvider { + private class RegistersOtherAttribute : Attribute, ITestParameterValueProvider + { public void SetupServiceCollection( - ITestMethodContext testMethod, IServiceCollection serviceCollection, ParameterInfo parameter) => - serviceCollection.AddSingleton(parameter.ParameterType, new Other()); + ITestMethodContext testMethod, + IServiceCollection serviceCollection, + ParameterInfo parameter + ) => serviceCollection.AddSingleton(parameter.ParameterType, new Other()); public Task GetParameterValueAsync( - ITestMethodContext testMethod, IServiceProvider serviceProvider, ParameterInfo parameter) => - Task.FromResult(serviceProvider.GetService(parameter.ParameterType)); + ITestMethodContext testMethod, + IServiceProvider serviceProvider, + ParameterInfo parameter + ) => Task.FromResult(serviceProvider.GetService(parameter.ParameterType)); } /// /// Registers nothing and answers null, so the next provider on the parameter gets its turn. /// [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)] - private class AbstainsAttribute : Attribute, ITestParameterValueProvider { + private class AbstainsAttribute : Attribute, ITestParameterValueProvider + { public void SetupServiceCollection( - ITestMethodContext testMethod, IServiceCollection serviceCollection, ParameterInfo parameter) { } + ITestMethodContext testMethod, + IServiceCollection serviceCollection, + ParameterInfo parameter + ) { } public Task GetParameterValueAsync( - ITestMethodContext testMethod, IServiceProvider serviceProvider, ParameterInfo parameter) => - Task.FromResult(null); + ITestMethodContext testMethod, + IServiceProvider serviceProvider, + ParameterInfo parameter + ) => Task.FromResult(null); } } diff --git a/tests/DependencyModules.Tests/xUnitTests/AttributeUtilityTests.cs b/tests/DependencyModules.Tests/xUnitTests/AttributeUtilityTests.cs index 6b9b160..576d66f 100644 --- a/tests/DependencyModules.Tests/xUnitTests/AttributeUtilityTests.cs +++ b/tests/DependencyModules.Tests/xUnitTests/AttributeUtilityTests.cs @@ -8,10 +8,11 @@ namespace DependencyModules.Tests.xUnitTests; /// AttributeUtility backs the documented "test attributes can be applied at the assembly, class, /// and test method level" behaviour, so the lookup order across those levels is the contract. /// -public class AttributeUtilityTests { - +public class AttributeUtilityTests +{ [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] - private class MarkerAttribute(string source) : Attribute { + private class MarkerAttribute(string source) : Attribute + { public string Source { get; } = source; } @@ -19,7 +20,8 @@ private class MarkerAttribute(string source) : Attribute { private class UnusedAttribute : Attribute; [Marker("class")] - private class WithClassAttribute { + private class WithClassAttribute + { [Marker("method")] public void MethodWithItsOwn(string plain) { } @@ -31,29 +33,38 @@ public void MethodWithParameterAttribute([Marker("parameter")] string annotated) private static MethodInfo Method(string name) => typeof(WithClassAttribute).GetMethod(name, BindingFlags.Public | BindingFlags.Instance)!; - private static ParameterInfo Parameter(string methodName) => Method(methodName).GetParameters()[0]; + private static ParameterInfo Parameter(string methodName) => + Method(methodName).GetParameters()[0]; [Fact] - public void GetTestAttribute_FindsAnAttributeOnTheMethod() { - var attribute = Method(nameof(WithClassAttribute.MethodWithItsOwn)).GetTestAttribute(); + public void GetTestAttribute_FindsAnAttributeOnTheMethod() + { + var attribute = Method(nameof(WithClassAttribute.MethodWithItsOwn)) + .GetTestAttribute(); Assert.Equal("method", attribute?.Source); } [Fact] - public void GetTestAttribute_FallsBackToTheDeclaringType() { - var attribute = Method(nameof(WithClassAttribute.MethodWithout)).GetTestAttribute(); + public void GetTestAttribute_FallsBackToTheDeclaringType() + { + var attribute = Method(nameof(WithClassAttribute.MethodWithout)) + .GetTestAttribute(); Assert.Equal("class", attribute?.Source); } [Fact] - public void GetTestAttribute_ReturnsNullWhenNothingMatches() { - Assert.Null(Method(nameof(WithClassAttribute.MethodWithout)).GetTestAttribute()); + public void GetTestAttribute_ReturnsNullWhenNothingMatches() + { + Assert.Null( + Method(nameof(WithClassAttribute.MethodWithout)).GetTestAttribute() + ); } [Fact] - public void GetTestAttribute_OnAParameter_PrefersTheParameterAttribute() { + public void GetTestAttribute_OnAParameter_PrefersTheParameterAttribute() + { var attribute = Parameter(nameof(WithClassAttribute.MethodWithParameterAttribute)) .GetTestAttribute(); @@ -61,26 +72,34 @@ public void GetTestAttribute_OnAParameter_PrefersTheParameterAttribute() { } [Fact] - public void GetTestAttribute_OnAnUnannotatedParameter_FallsBackToTheMethod() { - var attribute = Parameter(nameof(WithClassAttribute.MethodWithItsOwn)).GetTestAttribute(); + public void GetTestAttribute_OnAnUnannotatedParameter_FallsBackToTheMethod() + { + var attribute = Parameter(nameof(WithClassAttribute.MethodWithItsOwn)) + .GetTestAttribute(); Assert.Equal("method", attribute?.Source); } [Fact] - public void GetTestAttribute_OnAnUnannotatedParameter_FallsBackToTheDeclaringType() { - var attribute = Parameter(nameof(WithClassAttribute.MethodWithout)).GetTestAttribute(); + public void GetTestAttribute_OnAnUnannotatedParameter_FallsBackToTheDeclaringType() + { + var attribute = Parameter(nameof(WithClassAttribute.MethodWithout)) + .GetTestAttribute(); Assert.Equal("class", attribute?.Source); } [Fact] - public void GetTestAttribute_OnAParameter_ReturnsNullWhenNothingMatches() { - Assert.Null(Parameter(nameof(WithClassAttribute.MethodWithout)).GetTestAttribute()); + public void GetTestAttribute_OnAParameter_ReturnsNullWhenNothingMatches() + { + Assert.Null( + Parameter(nameof(WithClassAttribute.MethodWithout)).GetTestAttribute() + ); } [Fact] - public void GetTestAttributes_AccumulatesTypeThenMethod() { + public void GetTestAttributes_AccumulatesTypeThenMethod() + { var sources = Method(nameof(WithClassAttribute.MethodWithItsOwn)) .GetTestAttributes() .Select(attribute => attribute.Source) @@ -90,7 +109,8 @@ public void GetTestAttributes_AccumulatesTypeThenMethod() { } [Fact] - public void GetTestAttributes_ReturnsJustTheTypeAttributeWhenTheMethodHasNone() { + public void GetTestAttributes_ReturnsJustTheTypeAttributeWhenTheMethodHasNone() + { var sources = Method(nameof(WithClassAttribute.MethodWithout)) .GetTestAttributes() .Select(attribute => attribute.Source) @@ -100,12 +120,16 @@ public void GetTestAttributes_ReturnsJustTheTypeAttributeWhenTheMethodHasNone() } [Fact] - public void GetTestAttributes_ReturnsEmptyWhenNothingMatches() { - Assert.Empty(Method(nameof(WithClassAttribute.MethodWithout)).GetTestAttributes()); + public void GetTestAttributes_ReturnsEmptyWhenNothingMatches() + { + Assert.Empty( + Method(nameof(WithClassAttribute.MethodWithout)).GetTestAttributes() + ); } [Fact] - public void GetTestAttributes_OnAParameter_AccumulatesTypeMethodThenParameter() { + public void GetTestAttributes_OnAParameter_AccumulatesTypeMethodThenParameter() + { var sources = Parameter(nameof(WithClassAttribute.MethodWithParameterAttribute)) .GetTestAttributes() .Select(attribute => attribute.Source) @@ -115,7 +139,10 @@ public void GetTestAttributes_OnAParameter_AccumulatesTypeMethodThenParameter() } [Fact] - public void GetTestAttributes_OnAParameter_ReturnsEmptyWhenNothingMatches() { - Assert.Empty(Parameter(nameof(WithClassAttribute.MethodWithout)).GetTestAttributes()); + public void GetTestAttributes_OnAParameter_ReturnsEmptyWhenNothingMatches() + { + Assert.Empty( + Parameter(nameof(WithClassAttribute.MethodWithout)).GetTestAttributes() + ); } } diff --git a/tests/DependencyModules.Tests/xUnitTests/ModuleTestCaseDataTests.cs b/tests/DependencyModules.Tests/xUnitTests/ModuleTestCaseDataTests.cs index db9093b..88b1e3e 100644 --- a/tests/DependencyModules.Tests/xUnitTests/ModuleTestCaseDataTests.cs +++ b/tests/DependencyModules.Tests/xUnitTests/ModuleTestCaseDataTests.cs @@ -16,8 +16,8 @@ namespace DependencyModules.Tests.xUnitTests; /// count of tests produced is the only way that becomes visible, because every integration test /// runs through [ModuleTest] and a case that is never created is a suite that is quietly smaller. /// -public class ModuleTestCaseDataTests { - +public class ModuleTestCaseDataTests +{ /// /// Regression test. [MemberData] resolves its member off ITypeAwareDataAttribute.MemberType, /// which xUnit back-fills in ExtensibilityPointFactory.GetMethodDataAttributes — a path @@ -26,21 +26,24 @@ public class ModuleTestCaseDataTests { /// throwing, so the rows vanished without a diagnostic. /// [Fact] - public async Task MemberData_WithoutExplicitMemberType_ProducesOneTestPerRow() { + public async Task MemberData_WithoutExplicitMemberType_ProducesOneTestPerRow() + { var tests = await CreateTests(nameof(DataSample.FromTheoryData)); Assert.Equal(2, tests.Count); } [Fact] - public async Task MemberData_ReturningObjectArrays_ProducesOneTestPerRow() { + public async Task MemberData_ReturningObjectArrays_ProducesOneTestPerRow() + { var tests = await CreateTests(nameof(DataSample.FromObjectArrays)); Assert.Equal(2, tests.Count); } [Fact] - public async Task MemberData_ReturningTheoryDataRows_ProducesOneTestPerRow() { + public async Task MemberData_ReturningTheoryDataRows_ProducesOneTestPerRow() + { var tests = await CreateTests(nameof(DataSample.FromTheoryDataRows)); Assert.Equal(2, tests.Count); @@ -51,14 +54,16 @@ public async Task MemberData_ReturningTheoryDataRows_ProducesOneTestPerRow() { /// shape worked throughout. /// [Fact] - public async Task MemberData_WithExplicitMemberType_ProducesOneTestPerRow() { + public async Task MemberData_WithExplicitMemberType_ProducesOneTestPerRow() + { var tests = await CreateTests(nameof(DataSample.FromExplicitMemberType)); Assert.Equal(2, tests.Count); } [Fact] - public async Task ClassData_ProducesOneTestPerRow() { + public async Task ClassData_ProducesOneTestPerRow() + { var tests = await CreateTests(nameof(DataSample.FromClassData)); Assert.Equal(2, tests.Count); @@ -68,7 +73,8 @@ public async Task ClassData_ProducesOneTestPerRow() { /// The other control. [InlineData] carries its own literals and never needed the back-fill. /// [Fact] - public async Task InlineData_ProducesOneTestPerRow() { + public async Task InlineData_ProducesOneTestPerRow() + { var tests = await CreateTests(nameof(DataSample.FromInlineData)); Assert.Equal(2, tests.Count); @@ -87,7 +93,10 @@ public async Task InlineData_ProducesOneTestPerRow() { [InlineData(nameof(DataSample.TheoryDataRowsWithContainerParameter))] [InlineData(nameof(DataSample.ClassDataWithContainerParameter))] [InlineData(nameof(DataSample.InlineDataWithContainerParameter))] - public async Task RowSupplyingFewerArgumentsThanTheMethodTakes_ProducesOneTestPerRow(string methodName) { + public async Task RowSupplyingFewerArgumentsThanTheMethodTakes_ProducesOneTestPerRow( + string methodName + ) + { var tests = await CreateTests(methodName); Assert.Equal(2, tests.Count); @@ -99,9 +108,11 @@ public async Task RowSupplyingFewerArgumentsThanTheMethodTakes_ProducesOneTestPe /// above into a green suite instead of a red one. /// [Fact] - public async Task DataAttributeYieldingNoRows_Fails() { - var exception = await Assert.ThrowsAnyAsync( - async () => await CreateTests(nameof(DataSample.FromEmptySource))); + public async Task DataAttributeYieldingNoRows_Fails() + { + var exception = await Assert.ThrowsAnyAsync(async () => + await CreateTests(nameof(DataSample.FromEmptySource)) + ); Assert.Contains(nameof(DataSample.FromEmptySource), exception.Message); } @@ -111,24 +122,30 @@ public async Task DataAttributeYieldingNoRows_Fails() { /// test — the guard above must not catch it. /// [Fact] - public async Task NoDataAttribute_ProducesOneTest() { + public async Task NoDataAttribute_ProducesOneTest() + { var tests = await CreateTests(nameof(DataSample.NoRows)); Assert.Single(tests); } - private static async Task> CreateTests(string methodName) { + private static async Task> CreateTests(string methodName) + { var testMethod = BuildTestMethod(typeof(DataSample), methodName); var testCases = await new ModuleTestDiscoverer().Discover( - new DiscoveryOptions(), testMethod, new ModuleTestAttribute()); + new DiscoveryOptions(), + testMethod, + new ModuleTestAttribute() + ); var testCase = Assert.Single(testCases); return await testCase.CreateTests(); } - private static IXunitTestMethod BuildTestMethod(Type testClass, string methodName) { + private static IXunitTestMethod BuildTestMethod(Type testClass, string methodName) + { var assembly = new XunitTestAssembly(testClass.Assembly); var collection = new XunitTestCollection(assembly, null, false, "Test collection"); var xunitClass = new XunitTestClass(testClass, collection); @@ -137,8 +154,11 @@ private static IXunitTestMethod BuildTestMethod(Type testClass, string methodNam return new XunitTestMethod(xunitClass, method, []); } - private class DiscoveryOptions : ITestFrameworkDiscoveryOptions { - private readonly Dictionary _values = new(StringComparer.OrdinalIgnoreCase); + private class DiscoveryOptions : ITestFrameworkDiscoveryOptions + { + private readonly Dictionary _values = new( + StringComparer.OrdinalIgnoreCase + ); public TValue? GetValue(string name) => _values.TryGetValue(name, out var value) && value is TValue typed ? typed : default; @@ -161,10 +181,15 @@ private class DiscoveryOptions : ITestFrameworkDiscoveryOptions { // documented feature — the container supplies the rest — so the rule cannot hold here. Worth // noting that the guide teaches this shape without mentioning that it trips an analyzer error. #pragma warning disable xUnit1008, xUnit1037 - private class DataSample { + private class DataSample + { public static TheoryData Rows => new("first", "second"); - public static IEnumerable ObjectArrayRows => [["first"], ["second"]]; + public static IEnumerable ObjectArrayRows => + [ + ["first"], + ["second"], + ]; public static IEnumerable> TheoryDataRows => [new TheoryDataRow("first"), new TheoryDataRow("second")]; @@ -204,7 +229,10 @@ public void TheoryDataWithContainerParameter(string value, ContainerSupplied sup public void ObjectArraysWithContainerParameter(string value, ContainerSupplied supplied) { } [MemberData(nameof(TheoryDataRows))] - public void TheoryDataRowsWithContainerParameter(string value, ContainerSupplied supplied) { } + public void TheoryDataRowsWithContainerParameter( + string value, + ContainerSupplied supplied + ) { } [ClassData(typeof(SampleClassData))] public void ClassDataWithContainerParameter(string value, ContainerSupplied supplied) { } @@ -221,8 +249,10 @@ public void InlineDataWithContainerParameter(string value, ContainerSupplied sup private class ContainerSupplied { } #pragma warning restore xUnit1008, xUnit1037 - private class SampleClassData : TheoryData { - public SampleClassData() { + private class SampleClassData : TheoryData + { + public SampleClassData() + { Add("first"); Add("second"); } diff --git a/tests/DependencyModules.Tests/xUnitTests/ModuleTestDiscovererTests.cs b/tests/DependencyModules.Tests/xUnitTests/ModuleTestDiscovererTests.cs index 78d9caf..29a7bba 100644 --- a/tests/DependencyModules.Tests/xUnitTests/ModuleTestDiscovererTests.cs +++ b/tests/DependencyModules.Tests/xUnitTests/ModuleTestDiscovererTests.cs @@ -15,14 +15,15 @@ namespace DependencyModules.Tests.xUnitTests; /// exactly how a unique ID collision shipped undetected. Testing the discoverer from outside the /// framework it provides is the only way those failures become visible. /// -public class ModuleTestDiscovererTests { - +public class ModuleTestDiscovererTests +{ /// /// Regression test. Unique IDs used to be the bare method name, so two test classes each /// declaring a same-named test produced colliding IDs and xUnit silently discarded one. /// [Fact] - public async Task SameMethodNameInDifferentClasses_ProducesDifferentUniqueIDs() { + public async Task SameMethodNameInDifferentClasses_ProducesDifferentUniqueIDs() + { var first = await DiscoverSingle(typeof(FirstSample), nameof(FirstSample.SharedName)); var second = await DiscoverSingle(typeof(SecondSample), nameof(SecondSample.SharedName)); @@ -30,7 +31,8 @@ public async Task SameMethodNameInDifferentClasses_ProducesDifferentUniqueIDs() } [Fact] - public async Task SameMethodNameInDifferentClasses_ProducesDifferentDisplayNames() { + public async Task SameMethodNameInDifferentClasses_ProducesDifferentDisplayNames() + { var first = await DiscoverSingle(typeof(FirstSample), nameof(FirstSample.SharedName)); var second = await DiscoverSingle(typeof(SecondSample), nameof(SecondSample.SharedName)); @@ -38,14 +40,16 @@ public async Task SameMethodNameInDifferentClasses_ProducesDifferentDisplayNames } [Fact] - public async Task UniqueID_IsNotJustTheMethodName() { + public async Task UniqueID_IsNotJustTheMethodName() + { var testCase = await DiscoverSingle(typeof(FirstSample), nameof(FirstSample.SharedName)); Assert.NotEqual(nameof(FirstSample.SharedName), testCase.UniqueID); } [Fact] - public async Task UniqueID_IsStableAcrossRepeatedDiscovery() { + public async Task UniqueID_IsStableAcrossRepeatedDiscovery() + { var first = await DiscoverSingle(typeof(FirstSample), nameof(FirstSample.SharedName)); var second = await DiscoverSingle(typeof(FirstSample), nameof(FirstSample.SharedName)); @@ -53,7 +57,8 @@ public async Task UniqueID_IsStableAcrossRepeatedDiscovery() { } [Fact] - public async Task DifferentMethodsInOneClass_ProduceDifferentUniqueIDs() { + public async Task DifferentMethodsInOneClass_ProduceDifferentUniqueIDs() + { var first = await DiscoverSingle(typeof(FirstSample), nameof(FirstSample.SharedName)); var second = await DiscoverSingle(typeof(FirstSample), nameof(FirstSample.AnotherName)); @@ -61,21 +66,24 @@ public async Task DifferentMethodsInOneClass_ProduceDifferentUniqueIDs() { } [Fact] - public async Task DisplayName_IsQualifiedByItsDeclaringClass() { + public async Task DisplayName_IsQualifiedByItsDeclaringClass() + { var testCase = await DiscoverSingle(typeof(FirstSample), nameof(FirstSample.SharedName)); Assert.Contains(nameof(FirstSample), testCase.TestCaseDisplayName); } [Fact] - public async Task Discovery_ProducesExactlyOneTestCasePerMethod() { + public async Task Discovery_ProducesExactlyOneTestCasePerMethod() + { var cases = await Discover(typeof(FirstSample), nameof(FirstSample.SharedName)); Assert.Single(cases); } [Fact] - public async Task Discovery_ProducesAModuleTestCase() { + public async Task Discovery_ProducesAModuleTestCase() + { var testCase = await DiscoverSingle(typeof(FirstSample), nameof(FirstSample.SharedName)); Assert.IsType(testCase); @@ -86,15 +94,20 @@ public async Task Discovery_ProducesAModuleTestCase() { /// every sample method asserts the set is distinct, which is the property that was violated. /// [Fact] - public async Task EveryDiscoveredTestCase_HasADistinctUniqueID() { + public async Task EveryDiscoveredTestCase_HasADistinctUniqueID() + { var ids = new List(); - foreach (var (type, method) in new[] { - (typeof(FirstSample), nameof(FirstSample.SharedName)), - (typeof(FirstSample), nameof(FirstSample.AnotherName)), - (typeof(SecondSample), nameof(SecondSample.SharedName)), - (typeof(SecondSample), nameof(SecondSample.AnotherName)) - }) { + foreach ( + var (type, method) in new[] + { + (typeof(FirstSample), nameof(FirstSample.SharedName)), + (typeof(FirstSample), nameof(FirstSample.AnotherName)), + (typeof(SecondSample), nameof(SecondSample.SharedName)), + (typeof(SecondSample), nameof(SecondSample.AnotherName)), + } + ) + { ids.Add((await DiscoverSingle(type, method)).UniqueID); } @@ -109,7 +122,8 @@ public async Task EveryDiscoveredTestCase_HasADistinctUniqueID() { /// [ModuleTest]. /// [Fact] - public async Task Traits_OnTheTestMethod_ReachTheTestCase() { + public async Task Traits_OnTheTestMethod_ReachTheTestCase() + { var testCase = await DiscoverSingle(typeof(TraitSample), nameof(TraitSample.Categorised)); Assert.Contains("Fast", testCase.Traits["Category"]); @@ -127,14 +141,16 @@ public async Task Traits_OnTheTestMethod_ReachTheTestCase() { /// Traits_SurviveOntoTheCreatedTests do that. /// [Fact] - public async Task Traits_AreKeyedCaseInsensitively() { + public async Task Traits_AreKeyedCaseInsensitively() + { var testCase = await DiscoverSingle(typeof(TraitSample), nameof(TraitSample.Categorised)); Assert.True(testCase.Traits.ContainsKey("cAtEgOrY")); } [Fact] - public async Task Traits_KeepEveryValueOfARepeatedKey() { + public async Task Traits_KeepEveryValueOfARepeatedKey() + { var testCase = await DiscoverSingle(typeof(TraitSample), nameof(TraitSample.MultiValued)); Assert.Equal(["one", "two"], testCase.Traits["Category"].OrderBy(value => value)); @@ -153,9 +169,10 @@ public async Task Traits_KeepEveryValueOfARepeatedKey() { /// under the Xunit.Internal helpers or their replacement. /// [Fact] - public async Task Traits_SurviveOntoTheCreatedTests() { - var testCase = (ModuleTestCase)await DiscoverSingle( - typeof(TraitSample), nameof(TraitSample.Categorised)); + public async Task Traits_SurviveOntoTheCreatedTests() + { + var testCase = (ModuleTestCase) + await DiscoverSingle(typeof(TraitSample), nameof(TraitSample.Categorised)); var test = Assert.Single(await testCase.CreateTests()); @@ -163,8 +180,12 @@ public async Task Traits_SurviveOntoTheCreatedTests() { } [Fact] - public async Task Traits_AreNotSharedBetweenTestCases() { - var categorised = await DiscoverSingle(typeof(TraitSample), nameof(TraitSample.Categorised)); + public async Task Traits_AreNotSharedBetweenTestCases() + { + var categorised = await DiscoverSingle( + typeof(TraitSample), + nameof(TraitSample.Categorised) + ); var untraited = await DiscoverSingle(typeof(FirstSample), nameof(FirstSample.SharedName)); Assert.False(untraited.Traits.ContainsKey("Category")); @@ -185,7 +206,8 @@ public async Task Traits_AreNotSharedBetweenTestCases() { /// the location captured is this file. That is the point: it is a real usage site. /// [Fact] - public async Task TestCase_CarriesTheSourceLocationOfItsAttribute() { + public async Task TestCase_CarriesTheSourceLocationOfItsAttribute() + { var testCase = await DiscoverSingle(typeof(FirstSample), nameof(FirstSample.SharedName)); Assert.EndsWith("ModuleTestDiscovererTests.cs", testCase.SourceFilePath); @@ -203,7 +225,8 @@ public async Task TestCase_CarriesTheSourceLocationOfItsAttribute() { /// lose its source location. /// [Fact] - public void NamingOneModule_StillCapturesTheSourceLocation() { + public void NamingOneModule_StillCapturesTheSourceLocation() + { var attribute = new ModuleTestAttribute(typeof(FirstSample)); Assert.Single(attribute.ModuleTypes); @@ -220,7 +243,8 @@ public void NamingOneModule_StillCapturesTheSourceLocation() { /// says so. /// [Fact] - public void NamingSeveralModules_FallsBackToTheOverloadWithoutASourceLocation() { + public void NamingSeveralModules_FallsBackToTheOverloadWithoutASourceLocation() + { var attribute = new ModuleTestAttribute(typeof(FirstSample), typeof(SecondSample)); Assert.Equal(2, attribute.ModuleTypes.Length); @@ -230,21 +254,31 @@ public void NamingSeveralModules_FallsBackToTheOverloadWithoutASourceLocation() private static async Task DiscoverSingle(Type testClass, string methodName) => Assert.Single(await Discover(testClass, methodName)); - private static async Task> Discover(Type testClass, string methodName) { + private static async Task> Discover( + Type testClass, + string methodName + ) + { var testMethod = BuildTestMethod(testClass, methodName); // Supplied directly rather than read off the sample methods: annotating private nested // classes with [ModuleTest] makes the xUnit analyzer treat them as test classes. return await new ModuleTestDiscoverer().Discover( - new DiscoveryOptions(), testMethod, new ModuleTestAttribute()); + new DiscoveryOptions(), + testMethod, + new ModuleTestAttribute() + ); } /// /// The discoverer only reads method display settings, and xUnit falls back to its defaults for /// anything unset, so an empty option bag is enough to exercise it. /// - private class DiscoveryOptions : ITestFrameworkDiscoveryOptions { - private readonly Dictionary _values = new(StringComparer.OrdinalIgnoreCase); + private class DiscoveryOptions : ITestFrameworkDiscoveryOptions + { + private readonly Dictionary _values = new( + StringComparer.OrdinalIgnoreCase + ); public TValue? GetValue(string name) => _values.TryGetValue(name, out var value) && value is TValue typed ? typed : default; @@ -254,7 +288,8 @@ private class DiscoveryOptions : ITestFrameworkDiscoveryOptions { public string ToJson() => "{}"; } - private static IXunitTestMethod BuildTestMethod(Type testClass, string methodName) { + private static IXunitTestMethod BuildTestMethod(Type testClass, string methodName) + { var assembly = new XunitTestAssembly(testClass.Assembly); var collection = new XunitTestCollection(assembly, null, false, "Test collection"); var xunitClass = new XunitTestClass(testClass, collection); @@ -263,19 +298,22 @@ private static IXunitTestMethod BuildTestMethod(Type testClass, string methodNam return new XunitTestMethod(xunitClass, method, []); } - private class FirstSample { + private class FirstSample + { public void SharedName() { } public void AnotherName() { } } - private class SecondSample { + private class SecondSample + { public void SharedName() { } public void AnotherName() { } } - private class TraitSample { + private class TraitSample + { [Trait("Category", "Fast")] public void Categorised() { } diff --git a/website/guide/conventions.md b/website/guide/conventions.md index 89f6556..ca0701d 100644 --- a/website/guide/conventions.md +++ b/website/guide/conventions.md @@ -24,8 +24,10 @@ State the rule once, and let the generator find the types that fit **while it bu using DependencyModules.Runtime.Conventions; [DependencyModule] -public partial class DataModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class DataModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll(typeof(IRequestHandler<,>)).AsTransient(); } } @@ -311,12 +313,15 @@ Use `IServiceCollectionConfiguration` for those, alongside your conventions: ```csharp [DependencyModule] -public partial class DataModule : IConventionModule, IServiceCollectionConfiguration { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class DataModule : IConventionModule, IServiceCollectionConfiguration +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().AsScoped(); } - public void ConfigureServices(IServiceCollection services) { + public void ConfigureServices(IServiceCollection services) + { // unrestricted access to IServiceCollection, at run time } } diff --git a/website/guide/decorators.md b/website/guide/decorators.md index f8fb57d..8e41b50 100644 --- a/website/guide/decorators.md +++ b/website/guide/decorators.md @@ -6,7 +6,8 @@ You want to cache the results of a repository: ```csharp [SingletonService] -public class SqlRepository : IRepository { +public class SqlRepository : IRepository +{ public Item Get(int id) => /* a database round trip */; } ``` @@ -25,12 +26,14 @@ Write the wrapper as an ordinary class, mark it `[Decorator]`, and it takes over public interface IRepository { Item Get(int id); } [SingletonService] -public class SqlRepository : IRepository { +public class SqlRepository : IRepository +{ public Item Get(int id) => /* … */; } [Decorator] -public class CachingRepository(IRepository inner, IMemoryCache cache) : IRepository { +public class CachingRepository(IRepository inner, IMemoryCache cache) : IRepository +{ public Item Get(int id) => cache.GetOrCreate(id, _ => inner.Get(id))!; } ``` @@ -80,9 +83,10 @@ validators, written once: [Decorator] public class LoggingHandler( IRequestHandler inner, ILogger log) - : IRequestHandler { - - public TResponse Handle(TRequest request) { + : IRequestHandler +{ + public TResponse Handle(TRequest request) + { log.LogInformation("handling {Request}", typeof(TRequest).Name); return inner.Handle(request); } @@ -106,8 +110,10 @@ circuit breaker only in production: ```csharp [Decorator] [IfEnvironment("Development")] -public class LoggingRepository(IRepository inner, ILogger log) : IRepository { - public Item Get(int id) { +public class LoggingRepository(IRepository inner, ILogger log) : IRepository +{ + public Item Get(int id) + { log.LogInformation("getting {Id}", id); return inner.Get(id); } diff --git a/website/guide/environments.md b/website/guide/environments.md index c41cc48..fe6c5be 100644 --- a/website/guide/environments.md +++ b/website/guide/environments.md @@ -6,9 +6,12 @@ You do not want your development machine sending real email. So the registration ```csharp // Program.cs -if (builder.Environment.IsDevelopment()) { +if (builder.Environment.IsDevelopment()) +{ services.AddSingleton(); -} else { +} +else +{ services.AddSingleton(); } ``` @@ -78,7 +81,8 @@ Values go inline, since a `ModuleEnvironment` is a collection of them: ```csharp services.AddModules( - new ModuleEnvironment("Development") { + new ModuleEnvironment("Development") + { { "FEATURE_PROFILING", "on" }, { "REGION", "eu" } }, @@ -97,7 +101,8 @@ A key you did write wins — including one written as `null`, which is how you h same name: ```csharp -new ModuleEnvironment("Development") { +new ModuleEnvironment("Development") +{ { "REGION", "eu" }, // wins over any REGION variable { "FEATURE_PROFILING", null } // hides a FEATURE_PROFILING variable } @@ -255,9 +260,12 @@ For registration that depends on the environment but is not a simple condition: ```csharp [DependencyModule] -public partial class ApplicationModule : IEnvironmentServiceCollectionConfiguration { - public void ConfigureServices(IServiceCollection services, IModuleEnvironment environment) { - if (environment.Value("REGION") == "eu") { +public partial class ApplicationModule : IEnvironmentServiceCollectionConfiguration +{ + public void ConfigureServices(IServiceCollection services, IModuleEnvironment environment) + { + if (environment.Value("REGION") == "eu") + { services.AddSingleton(); } } diff --git a/website/guide/extending.md b/website/guide/extending.md index 9c96dca..2506956 100644 --- a/website/guide/extending.md +++ b/website/guide/extending.md @@ -45,9 +45,10 @@ that want module models: ```csharp [Generator] -public class MySourceGenerator : BaseSourceGenerator { - - protected override IEnumerable AttributeSourceGenerators() { +public class MySourceGenerator : BaseSourceGenerator +{ + protected override IEnumerable AttributeSourceGenerators() + { yield return new MyGenerator(); } @@ -62,12 +63,13 @@ match: nothing else can write those modules, so the base class writes them for y ```csharp [Generator] -public class MyFrameworkGenerator : BaseSourceGenerator { - +public class MyFrameworkGenerator : BaseSourceGenerator +{ protected override ITypeDefinition[] ModuleAttributeTypes() => [TypeDefinition.Get("My.Framework", "MyModuleAttribute")]; - protected override IEnumerable AttributeSourceGenerators() { + protected override IEnumerable AttributeSourceGenerators() + { yield return new MyGenerator(); } } @@ -88,12 +90,12 @@ things follow from declaring your own attribute: provider of every discovered module paired with the configuration in effect: ```csharp -public class MyGenerator : IDependencyModuleSourceGenerator { - +public class MyGenerator : IDependencyModuleSourceGenerator +{ public void SetupGenerator( IncrementalGeneratorInitializationContext context, - IncrementalValuesProvider<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> modules) { - + IncrementalValuesProvider<(ModuleEntryPointModel Left, DependencyModuleConfigurationModel Right)> modules) + { var candidates = context.SyntaxProvider .CreateSyntaxProvider(IsCandidate, GetModel) .Where(model => !model.IsIgnored) @@ -108,7 +110,8 @@ For an attribute-driven mechanism, `BaseAttributeSourceGenerator` does m supply the attribute types, a transform, a comparer and an ignored sentinel: ```csharp -public class MyGenerator : BaseAttributeSourceGenerator { +public class MyGenerator : BaseAttributeSourceGenerator +{ protected override IEnumerable AttributeTypes() => [MyAttributeType]; protected override MyModel GenerateAttributeModel(GeneratorAttributeSyntaxContext c, CancellationToken t) => …; protected override IEqualityComparer GetComparer() => new MyModelComparer(); diff --git a/website/guide/getting-started.md b/website/guide/getting-started.md index 72428a8..9e5ce82 100644 --- a/website/guide/getting-started.md +++ b/website/guide/getting-started.md @@ -79,7 +79,8 @@ namespace MyApp; public interface IEmailSender { void Send(string to); } [SingletonService] -public class SmtpEmailSender : IEmailSender { +public class SmtpEmailSender : IEmailSender +{ public void Send(string to) { } } ``` @@ -104,7 +105,8 @@ If you want to add a `ConfigureServices` to that generated module, declare the p `[DependencyModule]` and implement `IServiceCollectionConfiguration`: ```csharp -public partial class ApplicationModule : IServiceCollectionConfiguration { +public partial class ApplicationModule : IServiceCollectionConfiguration +{ public void ConfigureServices(IServiceCollection services) => services.AddHttpClient(); } @@ -148,7 +150,8 @@ reads as concrete rather than magic. Turn it on: Build, then open `obj/…/ApplicationModule.Dependencies.g.cs`. Inside it: ```csharp -private static void ModuleDependencies(IServiceCollection services) { +private static void ModuleDependencies(IServiceCollection services) +{ services.AddSingleton(typeof(MyApp.IEmailSender), typeof(MyApp.SmtpEmailSender)); } ``` diff --git a/website/guide/interception.md b/website/guide/interception.md index 28c123e..c94798c 100644 --- a/website/guide/interception.md +++ b/website/guide/interception.md @@ -21,13 +21,18 @@ interface and routes its members through it — every member by default, and [the kinds you name](#covering-some-members-and-not-others) when that is too much: ```csharp -public class TimingInterceptor(ILogger log) : IInterceptor { - public TResult Intercept(InvocationContext context) { +public class TimingInterceptor(ILogger log) : IInterceptor +{ + public TResult Intercept(InvocationContext context) + { var stopwatch = Stopwatch.StartNew(); - try { + try + { return context.Proceed(); - } finally { + } + finally + { log.LogInformation("{Member} took {Elapsed}", context.Caller.MemberName, stopwatch.Elapsed); } } @@ -64,10 +69,12 @@ Implement whichever kinds your services actually have: One type may implement any combination, and **the generator picks per member**: ```csharp -public class TracingInterceptor : IInterceptor, IAsyncInterceptor { +public class TracingInterceptor : IInterceptor, IAsyncInterceptor +{ public TResult Intercept(InvocationContext context) => context.Proceed(); - public async ValueTask InterceptAsync(AsyncInvocationContext context) { + public async ValueTask InterceptAsync(AsyncInvocationContext context) + { using var span = tracer.StartSpan(context.Caller.MemberName); return await context.ProceedAsync(); @@ -86,7 +93,8 @@ which means anything after the await runs once the work has genuinely finished whole call sits in one method body, state that spans it is an ordinary local: ```csharp -public async ValueTask InterceptAsync(AsyncInvocationContext context) { +public async ValueTask InterceptAsync(AsyncInvocationContext context) +{ using var scope = _tracer.StartSpan(context.Caller.MemberName); // spans the whole call return await context.ProceedAsync(); @@ -101,10 +109,12 @@ An `IAsyncEnumerable` member returns its stream immediately, before any item interceptor enumerates it, so it observes each item as it is produced: ```csharp -public async IAsyncEnumerable InterceptStream(StreamInvocationContext context) { +public async IAsyncEnumerable InterceptStream(StreamInvocationContext context) +{ var count = 0; - await foreach (var item in context.Proceed()) { + await foreach (var item in context.Proceed()) + { count++; yield return item; } @@ -271,7 +281,8 @@ public class AuditInterceptor : IInterceptor { … } // sync only [SingletonService] [Intercept(typeof(AuditInterceptor))] -public class Orders : IOrders { +public class Orders : IOrders +{ public int Count(string customer) { … } // audited public Task CountAsync(string customer) { … } // not audited } diff --git a/website/guide/modules.md b/website/guide/modules.md index 58756ca..30897a9 100644 --- a/website/guide/modules.md +++ b/website/guide/modules.md @@ -179,7 +179,8 @@ properties, and the generated attribute mirrors them: ```csharp [DependencyModule] -public partial class ApplicationModule { +public partial class ApplicationModule +{ public string? ConnectionString { get; set; } } ``` @@ -218,8 +219,10 @@ binding, anything from a third-party library with its own extension method. Impl ```csharp [DependencyModule] -public partial class ApplicationModule : IServiceCollectionConfiguration { - public void ConfigureServices(IServiceCollection services) { +public partial class ApplicationModule : IServiceCollectionConfiguration +{ + public void ConfigureServices(IServiceCollection services) + { services.AddHttpClient(); } } diff --git a/website/guide/services.md b/website/guide/services.md index 2bfad8a..c570e92 100644 --- a/website/guide/services.md +++ b/website/guide/services.md @@ -129,8 +129,10 @@ public class BusinessRules : IValidator { } ```csharp [ScopedService] -public class OrderService(IEnumerable validators) { - public void Place(Order order) { +public class OrderService(IEnumerable validators) +{ + public void Place(Order order) + { foreach (var validator in validators) { // RequiredFields, then BusinessRules validator.Validate(order); } @@ -179,7 +181,8 @@ configuration, an object built by a factory somewhere else. Put the attribute on method** instead of on the class: ```csharp -public class SomeClass : ISomeInterface { +public class SomeClass : ISomeInterface +{ public SomeClass(IDep one, IDepTwo two, DateTime timestamp) { } [SingletonService] diff --git a/website/guide/testing-mocking.md b/website/guide/testing-mocking.md index 939cf42..e8f1269 100644 --- a/website/guide/testing-mocking.md +++ b/website/guide/testing-mocking.md @@ -7,7 +7,8 @@ occasionally the problem. One of the services behind `Weather` is non-determinis ```csharp [SingletonService] -public class TemperatureProvider : ITemperatureProvider { +public class TemperatureProvider : ITemperatureProvider +{ public int GetTemperature() => Random.Shared.Next(-20, 55); } ``` @@ -26,8 +27,8 @@ resolved. Everything constructed afterwards gets the substitute: public void GetStaticForecast( Weather weather, [Mock] ITemperatureProvider temperatureProvider, - [Mock] IAiSummaryProvider aiSummaryProvider) { - + [Mock] IAiSummaryProvider aiSummaryProvider) +{ temperatureProvider.GetTemperature().Returns(38); aiSummaryProvider.GetSummary().Returns("Sunny"); @@ -135,7 +136,8 @@ using DependencyModules.NSubstitute; ```csharp [ModuleTest] -public void SendsTheMail(IEmailSender sender, [Mock] IAuditLog log) { +public void SendsTheMail(IEmailSender sender, [Mock] IAuditLog log) +{ sender.Send("someone@example.com"); log.Received().Write(Arg.Any()); @@ -156,7 +158,8 @@ using DependencyModules.FakeItEasy; ```csharp [ModuleTest] -public void SendsTheMail(IEmailSender sender, [Mock] IAuditLog log) { +public void SendsTheMail(IEmailSender sender, [Mock] IAuditLog log) +{ sender.Send("someone@example.com"); A.CallTo(() => log.Write(A._)).MustHaveHappened(); @@ -173,7 +176,8 @@ Moq is the one that needs a paragraph, because it keeps the mock and the object ```csharp [ModuleTest] -public void SendsTheMail(IEmailSender sender, [Mock] IAuditLog log) { +public void SendsTheMail(IEmailSender sender, [Mock] IAuditLog log) +{ Mock.Get(log).Verify(x => x.Write(It.IsAny())); } ``` @@ -188,8 +192,8 @@ says what it is: public void GetStaticForecast( Weather weather, Mock temperatureProvider, - Mock aiSummaryProvider) { - + Mock aiSummaryProvider) +{ temperatureProvider.Setup(x => x.GetTemperature()).Returns(38); aiSummaryProvider.Setup(x => x.GetSummary()).Returns("Sunny"); @@ -227,8 +231,8 @@ default and one test opts out: ```csharp [TestExport(typeof(IClock), Implementation = typeof(SystemClock))] // the fixture default -public class ExpiryTests { - +public class ExpiryTests +{ [ModuleTest] public void UsesTheRealClock(IClock clock) { } // SystemClock @@ -247,7 +251,8 @@ service mocked, so a `[TestExport]` still beats it: ```csharp [ModuleTest] [TestExport(typeof(IClock), Implementation = typeof(SystemClock))] -public void RealClock(IClock clock, Mock mock) { +public void RealClock(IClock clock, Mock mock) +{ Assert.IsType(clock); // the export won } ``` diff --git a/website/guide/testing-nunit.md b/website/guide/testing-nunit.md index ad1f761..d59c594 100644 --- a/website/guide/testing-nunit.md +++ b/website/guide/testing-nunit.md @@ -10,10 +10,12 @@ dotnet add package DependencyModules.NUnit ```csharp using DependencyModules.NUnit.Attributes; -public class WeatherTests { +public class WeatherTests +{ [ModuleTest] [ApplicationModule] - public void GetForecast(Weather weather) { + public void GetForecast(Weather weather) + { var forecast = weather.GetWeatherForecast().ToArray(); Assert.That(forecast, Has.Length.EqualTo(5)); @@ -38,7 +40,8 @@ each `[Repeat]` pass and each `[Retry]` attempt, not just each test case: [ModuleTest] [ApplicationModule] [Repeat(3)] -public void EachPassStartsClean(ICallCounter counter) { +public void EachPassStartsClean(ICallCounter counter) +{ counter.Record(); Assert.That(counter.Count, Is.EqualTo(1)); // never 2, never 3 @@ -66,7 +69,8 @@ after: [ApplicationModule] [ModuleTestCase("one")] [ModuleTestCase("two")] -public void MultipleRows(string value, ITemperatureProvider provider) { +public void MultipleRows(string value, ITemperatureProvider provider) +{ Assert.That(value, Is.Not.Null); // from [ModuleTestCase] Assert.That(provider, Is.Not.Null); // from the container } @@ -90,7 +94,8 @@ To supply rows from somewhere other than an attribute literal, implement `IModul ```csharp [AttributeUsage(AttributeTargets.Method)] -public class CsvRowsAttribute(string path) : Attribute, IModuleTestDataAttribute { +public class CsvRowsAttribute(string path) : Attribute, IModuleTestDataAttribute +{ public IEnumerable GetRows(MethodInfo method) => File.ReadLines(path).Select(line => line.Split(',').Cast().ToArray()); } diff --git a/website/guide/testing-registrations.md b/website/guide/testing-registrations.md index f63421b..ff916e5 100644 --- a/website/guide/testing-registrations.md +++ b/website/guide/testing-registrations.md @@ -75,7 +75,8 @@ Both sides fit in one theory: [Theory] [InlineData("Development", typeof(FakeEmailSender))] [InlineData("Production", typeof(SmtpEmailSender))] -public void SelectsTheSenderByEnvironment(string environment, Type expected) { +public void SelectsTheSenderByEnvironment(string environment, Type expected) +{ var services = new ServiceCollection(); services.AddModules(new ModuleEnvironment(environment), new ApplicationModule()); diff --git a/website/guide/testing-xunit.md b/website/guide/testing-xunit.md index 44a2363..5258524 100644 --- a/website/guide/testing-xunit.md +++ b/website/guide/testing-xunit.md @@ -10,10 +10,12 @@ dotnet add package DependencyModules.xUnit ```csharp using DependencyModules.xUnit.Attributes; -public class WeatherTests { +public class WeatherTests +{ [ModuleTest] [ApplicationModule] - public void GetForecast(Weather weather) { + public void GetForecast(Weather weather) + { var forecast = weather.GetWeatherForecast().ToArray(); Assert.Equal(5, forecast.Length); @@ -89,7 +91,8 @@ implementing `IDataAttribute`. Row arguments come first, injected ones after: [ModuleTest] [InlineData("one")] [InlineData("two")] -public void MultipleRows(string value, ITemperatureProvider provider) { +public void MultipleRows(string value, ITemperatureProvider provider) +{ Assert.NotNull(value); // from [InlineData] Assert.NotNull(provider); // from the container } @@ -119,7 +122,8 @@ green. `TheoryDataRow`'s own metadata is honoured per row, so a single row can skip or carry its own traits: ```csharp -public static TheoryData Cases => new() { +public static TheoryData Cases => new() +{ new TheoryDataRow("ok"), new TheoryDataRow("broken") { Skip = "pending #412" }, }; @@ -136,7 +140,8 @@ test: ```csharp [ModuleTest] -public void KnowsWhatItIs(ITestCaseInfo testCase) { +public void KnowsWhatItIs(ITestCaseInfo testCase) +{ IXunitTestMethod method = testCase.TestMethod; Assert.Equal(nameof(KnowsWhatItIs), method.MethodName); @@ -167,10 +172,12 @@ a different container: ```csharp [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] -public class ValidatingProviderAttribute : Attribute, IServiceProviderBuilderAttribute { +public class ValidatingProviderAttribute : Attribute, IServiceProviderBuilderAttribute +{ public IServiceProvider BuildServiceProvider( ITestMethodContext testMethod, IServiceCollection serviceCollection) => - serviceCollection.BuildServiceProvider(new ServiceProviderOptions { + serviceCollection.BuildServiceProvider(new ServiceProviderOptions + { ValidateScopes = true, ValidateOnBuild = true, }); diff --git a/website/guide/testing.md b/website/guide/testing.md index 8e289a7..b7194a6 100644 --- a/website/guide/testing.md +++ b/website/guide/testing.md @@ -6,7 +6,8 @@ Here is a service with two dependencies, one of which has a dependency of its ow ```csharp [SingletonService] -public class Weather(ISummaryProvider summaryProvider, ITemperatureProvider temperatureProvider) { +public class Weather(ISummaryProvider summaryProvider, ITemperatureProvider temperatureProvider) +{ public IEnumerable GetWeatherForecast() { /* … */ } } ``` @@ -42,10 +43,12 @@ services your test needs arrive as **method parameters**, resolved from a provid real modules: ```csharp -public class WeatherTests { +public class WeatherTests +{ [ModuleTest] [ApplicationModule] - public void GetForecast(Weather weather) { + public void GetForecast(Weather weather) + { var forecast = weather.GetWeatherForecast().ToArray(); // assert on forecast @@ -123,7 +126,8 @@ they belong. Every test in the project now gets `ApplicationModule` without saying so: ```csharp -public class WeatherTests { +public class WeatherTests +{ [ModuleTest] public void UsesTheAssemblyModules(Weather weather) { } @@ -147,7 +151,8 @@ Within a test, ask for `IServiceProvider` and create scopes as usual: ```csharp [ModuleTest] -public void ScopedServicesAreScoped(IServiceProvider provider) { +public void ScopedServicesAreScoped(IServiceProvider provider) +{ using var first = provider.CreateScope(); using var second = provider.CreateScope(); @@ -201,7 +206,8 @@ differently, a mock makes you stub out every member you touch. `[TestExport]` registers a real type into the test's container without touching the module: ```csharp -public class FixedClock : IClock { +public class FixedClock : IClock +{ public DateTime UtcNow => new(2026, 1, 1); } @@ -231,7 +237,8 @@ data rather than a service. `[InjectValues]` supplies the parts the container ca public record InjectModel(IDependencyOne DependencyOne, string StringValue); [ModuleTest] -public void InjectTestValue([InjectValues("Hello World!")] InjectModel model) { +public void InjectTestValue([InjectValues("Hello World!")] InjectModel model) +{ // model.DependencyOne came from the container // model.StringValue came from the attribute } @@ -248,7 +255,8 @@ parameter that should simply **be** a value wants a data row instead. `[InlineDa [ModuleTest] [InlineData("978-0132350884")] [InlineData("978-0201616224")] -public async Task GetBook_FindsEachIsbn(string isbn, IRequestHandler handler) { +public async Task GetBook_FindsEachIsbn(string isbn, IRequestHandler handler) +{ // isbn came from the row, handler from the container } ``` diff --git a/website/index.md b/website/index.md index 47171ec..677ee99 100644 --- a/website/index.md +++ b/website/index.md @@ -109,8 +109,10 @@ next year. ```csharp [DependencyModule] -public partial class HandlerModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class HandlerModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll(typeof(IRequestHandler<,>)).AsScoped(); conventions.RegisterAll().InNamespaceOf().AsScoped(); } diff --git a/website/reference/conventions-api.md b/website/reference/conventions-api.md index d96ea91..4ea449f 100644 --- a/website/reference/conventions-api.md +++ b/website/reference/conventions-api.md @@ -5,8 +5,10 @@ fit together. ```csharp [DependencyModule] -public partial class DataModule : IConventionModule { - void IConventionModule.Conventions(IConventionDefinitions conventions) { +public partial class DataModule : IConventionModule +{ + void IConventionModule.Conventions(IConventionDefinitions conventions) + { conventions.RegisterAll().InNamespaceOf().AsScoped(); } } diff --git a/website/reference/diagnostics.md b/website/reference/diagnostics.md index 851b8e1..fb4e3cd 100644 --- a/website/reference/diagnostics.md +++ b/website/reference/diagnostics.md @@ -235,7 +235,8 @@ public class AuditInterceptor : IInterceptor { … } // sync only [SingletonService] [Intercept(typeof(AuditInterceptor))] -public class Orders : IOrders { +public class Orders : IOrders +{ public int Count(string customer) { … } // audited public Task CountAsync(string customer) { … } // DM0015 — not audited } @@ -298,7 +299,8 @@ while the nested declaration never implemented `IDependencyModule` — a green b nothing. ```csharp -public static class Outer { +public static class Outer +{ [DependencyModule] public partial class NestedModule; // DM0017 } @@ -322,7 +324,8 @@ silently. ```csharp [DependencyModule] -public partial class CacheModule : IServiceCollectionConfiguration { +public partial class CacheModule : IServiceCollectionConfiguration +{ public int SizeLimit { get; set; } // DM0018 public void ConfigureServices(IServiceCollection services) => services.AddSingleton(new CacheSettings(SizeLimit)); @@ -401,7 +404,8 @@ The case that reaches here is a realm-only module registering the class *by conv public class Greeter : IGreeter { … } [DependencyModule(OnlyRealm = true)] -public partial class GreetingModule : IConventionModule { +public partial class GreetingModule : IConventionModule +{ void IConventionModule.Conventions(IConventionDefinitions conventions) => conventions.RegisterAll().AsSingleton(); } @@ -440,8 +444,8 @@ under it, and one test overriding that for one argument is what having both scop ```csharp [TestExport(typeof(IClock), Implementation = typeof(SystemClock))] // the fixture default -public class ExpiryTests { - +public class ExpiryTests +{ [ModuleTest] public void UsesTheRealClock(IClock clock) { } // SystemClock diff --git a/website/reference/interfaces.md b/website/reference/interfaces.md index e30e3e7..4f94fe2 100644 --- a/website/reference/interfaces.md +++ b/website/reference/interfaces.md @@ -42,8 +42,10 @@ calls it alongside the registrations it wrote: ```csharp [DependencyModule] -public partial class ApplicationModule : IServiceCollectionConfiguration { - public void ConfigureServices(IServiceCollection services) { +public partial class ApplicationModule : IServiceCollectionConfiguration +{ + public void ConfigureServices(IServiceCollection services) + { services.AddHttpClient(); services.Configure(options => options.SizeLimit = 1024); } @@ -56,7 +58,8 @@ gets an `ApplicationModule` it did not declare; adding a partial for it **withou with it: ```csharp -public partial class ApplicationModule : IServiceCollectionConfiguration { +public partial class ApplicationModule : IServiceCollectionConfiguration +{ public void ConfigureServices(IServiceCollection services) => services.AddHttpClient(); }