From 515393c75445c65d136927d3ac172041fc946f6f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 21:12:07 -0400 Subject: [PATCH] Fix four silent-failure findings and give diagnostics source locations DM-F01: read a service attribute's lifetime from the attribute type the usage resolved to, not from how it was spelled. AttributeTypeMatcher already resolves the usage through the semantic model to decide the attribute matched at all; the lifetime branch re-derived it from attributeSyntax.Name and so only a spelling literally starting with "Singleton"/"Scoped" produced that lifetime. A qualified name, a global:: prefix and any using alias not named after its target fell through to Transient - registered, wrong lifetime, no diagnostic. Costs nothing extra: the caller now passes on the answer instead of discarding it. DM-F03: apply realm filtering to the interceptor pass, and give [Intercept] a Realm property to match [Decorator]. Every module previously received every interception in the compilation, so an OnlyRealm module emitted applicators for interceptions that named no realm - wrapping unrelated services, or throwing while building the provider when the leaked interceptor needed a dependency the isolated container did not have. DM-F07: DM0005's advice is now chosen by cause rather than being a fixed tail. Naming a class as the service type can never match, so recommending IncludeBaseClasses() sent readers hunting for a typo; a convention that already calls it gets filter advice instead. DM-F14: DM0017 reports a module nested inside another type - documented as always wrong in the README and two guides, and until now reported by none of them. Generation stops rather than emitting a detached same-named type. DM0018 reports a module with settable properties relying on generated equality. Info, not Warning: dedupe-by-type is correct for a module composed once, and this repo's own integration tests carry eleven such modules doing nothing wrong. DM-F13: LocationModel moves from the Conventions subtree into .Impl so service, interceptor, decorator and module models can carry one. DM0002, DM0003, DM0007, DM0008, DM0009, DM0011, DM0012, DM0013, DM0014 and DM0015 now report at the declaration instead of at the project. The two remaining Location.None sites are the DM0001 generator-failure handlers, where there is genuinely no location. Location is deliberately excluded from the incremental cache keys: it shifts when anything above a declaration is edited, and including it regenerated every model on a comment keystroke. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01R9DWh8FWTib6hRYURbypWU --- .../Attributes/InterceptAttribute.cs | 12 ++++ .../AnalyzerReleases.Unshipped.md | 7 +++ .../BaseSourceGenerator.cs | 9 +++ .../DependencyModuleDiagnostics.cs | 58 +++++++++++++++-- .../DependencyModuleWriter.cs | 25 +++++++- .../Models/DecoratorModel.cs | 9 ++- .../Models/InterceptorModel.cs | 13 +++- .../Models/LocationModel.cs | 2 +- .../Models/ModuleEntryPointModel.cs | 17 +++++ .../Models/ServiceModel.cs | 10 ++- .../Utilities/DecoratorModelUtility.cs | 3 +- .../Utilities/InterceptorModelUtility.cs | 24 ++++--- .../Utilities/ServiceModelUtility.cs | 51 +++++++++++---- .../Conventions/ConventionGenerator.cs | 6 +- .../Conventions/Models/ConventionModel.cs | 7 ++- .../Utilities/ConventionCandidateCache.cs | 1 + .../Utilities/ConventionMatcher.cs | 32 +++++++++- .../Utilities/ConventionModelUtility.cs | 3 +- .../InterceptorSourceGenerator.cs | 43 +++++++++++-- .../ServiceSourceGenerator.cs | 8 +-- .../Infrastructure/ModelFactory.cs | 1 + .../PublicApiTests.RuntimeApi.verified.txt | 1 + ...icApiTests.SourceGeneratorApi.verified.txt | 63 +++++++++++-------- 23 files changed, 332 insertions(+), 73 deletions(-) rename src/{DependencyModules.SourceGenerator/Conventions => DependencyModules.SourceGenerator.Impl}/Models/LocationModel.cs (98%) diff --git a/src/DependencyModules.Runtime/Attributes/InterceptAttribute.cs b/src/DependencyModules.Runtime/Attributes/InterceptAttribute.cs index 16cd910..1adba36 100644 --- a/src/DependencyModules.Runtime/Attributes/InterceptAttribute.cs +++ b/src/DependencyModules.Runtime/Attributes/InterceptAttribute.cs @@ -34,4 +34,16 @@ public class InterceptAttribute(params Type[] interceptors) : Attribute { /// . /// public int Order { get; set; } + + /// + /// Restricts the interception to a single module, matching the Realm property on the service + /// registration attributes and on . + /// + /// + /// Without a realm the interception belongs to every module that is not realm-only, which is how + /// services and decorators already behave. An OnlyRealm module previously received every + /// interception in the compilation regardless — including ones naming no realm at all — so a + /// module built to be isolated wrapped services it had never heard of. + /// + public Type? Realm { get; set; } } diff --git a/src/DependencyModules.SourceGenerator.Impl/AnalyzerReleases.Unshipped.md b/src/DependencyModules.SourceGenerator.Impl/AnalyzerReleases.Unshipped.md index f2b7fad..fa9d395 100644 --- a/src/DependencyModules.SourceGenerator.Impl/AnalyzerReleases.Unshipped.md +++ b/src/DependencyModules.SourceGenerator.Impl/AnalyzerReleases.Unshipped.md @@ -1,2 +1,9 @@ ; Unshipped analyzer release ; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +DM0017 | DependencyModules | Error | A dependency module is declared inside another type. +DM0018 | DependencyModules | Info | A module with settable properties relies on generated equality. diff --git a/src/DependencyModules.SourceGenerator.Impl/BaseSourceGenerator.cs b/src/DependencyModules.SourceGenerator.Impl/BaseSourceGenerator.cs index de44627..a1ab779 100644 --- a/src/DependencyModules.SourceGenerator.Impl/BaseSourceGenerator.cs +++ b/src/DependencyModules.SourceGenerator.Impl/BaseSourceGenerator.cs @@ -230,10 +230,18 @@ private ModuleEntryPointModel GetClassEntryPointModel(GeneratorSyntaxContext con 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) { + features |= ModuleEntryPointFeatures.NestedInType; + } return new ModuleEntryPointModel( features, context.Node.SyntaxTree?.FilePath ?? "", + LocationModel.From(context.Node), ((TypeDeclarationSyntax)context.Node).GetTypeDefinition(), dependencyFlags.RegistrationType, dependencyFlags.GenerateAttribute, @@ -276,6 +284,7 @@ private ModuleEntryPointModel GetCompilationUnitSyntaxEntry(GeneratorSyntaxConte return new ModuleEntryPointModel( ModuleEntryPointFeatures.AutoGenerateModule, context.Node.SyntaxTree?.FilePath ?? "", + LocationModel.From(context.Node), TypeDefinition.Get("", "ApplicationModule"), null, true, diff --git a/src/DependencyModules.SourceGenerator.Impl/DependencyModuleDiagnostics.cs b/src/DependencyModules.SourceGenerator.Impl/DependencyModuleDiagnostics.cs index 5505421..cb5a394 100644 --- a/src/DependencyModules.SourceGenerator.Impl/DependencyModuleDiagnostics.cs +++ b/src/DependencyModules.SourceGenerator.Impl/DependencyModuleDiagnostics.cs @@ -56,6 +56,55 @@ public static class DependencyModuleDiagnostics { defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); + /// + /// Raised for a module declared inside another type. + /// + /// + /// The generated half is written at namespace level, so a nested declaration produces a second, + /// detached type of the same name while the nested one never implements + /// IDependencyModule. AddModule<Outer.Nested>() then fails to compile, but + /// [assembly: Nested] binds to the detached type's attribute and builds green, registering + /// nothing. + /// + 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.", + category: Category, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Raised for a module carrying settable properties while relying on the generated + /// Equals/GetHashCode. + /// + /// + /// Modules de-duplicate by type, which is what stops a module reached twice from registering + /// everything twice. A module with parameters is a different case: two instances carrying + /// different values are the same module by that rule, so the first one reached wins and the + /// other is silently discarded. Supplying an Equals that accounts for the properties + /// suppresses the generated one and makes the intent explicit either way. + /// + 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.", + category: Category, + // Info, not Warning. Dedupe-by-type is correct for the common case — a parameterised module + // composed once — and this repo's own integration tests carry eleven such modules that are + // not doing anything wrong. Only a module reached twice with different values is actually + // bitten, and that is not decidable here. Promote it per project with + // dotnet_diagnostic.DM0018.severity = warning if you want it enforced. + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true); + /// /// Raised when two conventions in one module register a type as the same service type. /// @@ -89,10 +138,11 @@ public static class DependencyModuleDiagnostics { public static readonly DiagnosticDescriptor ConventionMatchedNothing = new( id: "DM0005", title: "Convention matched no types", - messageFormat: - "The convention registering '{0}' in '{1}' matched no types. " + - "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.", + // The advice is a parameter rather than a fixed tail, because the causes need different + // fixes. Suggesting IncludeBaseClasses() unconditionally told a reader who had already + // called it — or who had named a class, which can never match whatever they call — to go + // looking for a typo in their own code. + messageFormat: "The convention registering '{0}' in '{1}' matched no types. {2}.", category: Category, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); diff --git a/src/DependencyModules.SourceGenerator.Impl/DependencyModuleWriter.cs b/src/DependencyModules.SourceGenerator.Impl/DependencyModuleWriter.cs index 7e36124..4345f0c 100644 --- a/src/DependencyModules.SourceGenerator.Impl/DependencyModuleWriter.cs +++ b/src/DependencyModules.SourceGenerator.Impl/DependencyModuleWriter.cs @@ -75,12 +75,35 @@ private void ProcessEntryPoint( context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.ModuleMustBePartial, - Location.None, + entryPointModel.Location.ToLocationOrNone(), entryPointModel.EntryPointType.Name)); return; } + // Generating anyway would emit a same-named type at namespace level, which compiles and + // registers nothing — the failure this is here to replace. + if (entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.NestedInType)) { + context.ReportDiagnostic( + Diagnostic.Create( + DependencyModuleDiagnostics.ModuleCannotBeNested, + entryPointModel.Location.ToLocationOrNone(), + entryPointModel.EntryPointType.Name)); + + return; + } + + // Only when the generator is supplying equality. A module that declares its own Equals has + // already answered the question this asks about. + if (entryPointModel.PropertyInfoModels.Count > 0 && + entryPointModel.ModuleFeatures.HasFlag(ModuleEntryPointFeatures.ShouldImplementEquals)) { + context.ReportDiagnostic( + Diagnostic.Create( + DependencyModuleDiagnostics.ModuleWithPropertiesShouldImplementEquals, + entryPointModel.Location.ToLocationOrNone(), + entryPointModel.EntryPointType.Name)); + } + entryPointModel = EntryModelUtil.EnsureNamespace(entryPointModel,configurationModel); var csharpFile = new CSharpFileDefinition(entryPointModel.EntryPointType.Namespace); diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/DecoratorModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/DecoratorModel.cs index 2acc6c6..496e90d 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/DecoratorModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/DecoratorModel.cs @@ -42,7 +42,14 @@ public record DecoratorModel( IReadOnlyList? Conditions = null, ConstructorInfoModel? Constructor = null, int InnerParameterIndex = -1, - bool TypeParametersMatchService = true) { + bool TypeParametersMatchService = true, + + /// + /// 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) { /// /// Whether the decorator can be constructed by generated code. diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/InterceptorModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/InterceptorModel.cs index 08e8669..ee273a2 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/InterceptorModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/InterceptorModel.cs @@ -344,7 +344,14 @@ public record InterceptorModel( IReadOnlyList Declarations, int Order, InterceptionRefusal? Refusal = null, - IReadOnlyList? TypeParameters = 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) { /// /// Whether the intercepted service is an open generic, and so registers as an implementation type @@ -368,8 +375,8 @@ public record InterceptorModel( /// 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) => - Ignore with { Refusal = new InterceptionRefusal(message) }; + public static InterceptorModel Refused(string message, LocationModel? location = null) => + Ignore with { Refusal = new InterceptionRefusal(message), Location = location }; public bool IsIgnored => ReferenceEquals(this, Ignore); } diff --git a/src/DependencyModules.SourceGenerator/Conventions/Models/LocationModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/LocationModel.cs similarity index 98% rename from src/DependencyModules.SourceGenerator/Conventions/Models/LocationModel.cs rename to src/DependencyModules.SourceGenerator.Impl/Models/LocationModel.cs index 93c302f..1c3a68e 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Models/LocationModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/LocationModel.cs @@ -2,7 +2,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; -namespace DependencyModules.Conventions.Models; +namespace DependencyModules.SourceGenerator.Impl.Models; /// /// Where a declaration sits in source, in a form that can live in an incremental model. diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/ModuleEntryPointModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/ModuleEntryPointModel.cs index 7e76bc6..01afeb2 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/ModuleEntryPointModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/ModuleEntryPointModel.cs @@ -14,11 +14,22 @@ public enum ModuleEntryPointFeatures { /// The module was declared without the partial modifier, so the generator cannot complete it. /// NotPartial = 16, + + /// + /// The module was declared inside another type rather than directly in a namespace. + /// + /// + /// Documented as always wrong in the README, the modules guide and the troubleshooting guide, + /// and until now reported by none of them: the generator emitted a second, detached type at + /// namespace level, the nested declaration never became a module, and the build stayed green. + /// + NestedInType = 32, } public record ModuleEntryPointModel( ModuleEntryPointFeatures ModuleFeatures, string FileLocation, + LocationModel Location, ITypeDefinition EntryPointType, RegistrationType? RegistrationType, bool? GenerateAttribute, @@ -39,6 +50,12 @@ 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 + // comment — and this comparer is the incremental cache key. Including it regenerated every + // 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 && diff --git a/src/DependencyModules.SourceGenerator.Impl/Models/ServiceModel.cs b/src/DependencyModules.SourceGenerator.Impl/Models/ServiceModel.cs index 874fe42..aa2f5ed 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Models/ServiceModel.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Models/ServiceModel.cs @@ -79,7 +79,15 @@ public record ServiceModel( /// attributes are declared on the class, so every registration it produces shares them and the /// writer emits one guard around the lot. /// - IReadOnlyList? Conditions = null) { + 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) { public static ServiceModel Ignore = new ServiceModel( TypeDefinition.Get("", "Ignore"), null, diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorModelUtility.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorModelUtility.cs index 405a28b..21a3d14 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorModelUtility.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/DecoratorModelUtility.cs @@ -83,7 +83,8 @@ public static class DecoratorModelUtility { conditions, constructor, IndexOfInnerParameter(constructor, written), - TypeParametersMatchService(typeDeclarationSyntax, written)); + TypeParametersMatchService(typeDeclarationSyntax, written), + LocationModel.From(typeDeclarationSyntax)); } /// diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptorModelUtility.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptorModelUtility.cs index 65b3f23..6f7ddf3 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptorModelUtility.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/InterceptorModelUtility.cs @@ -55,12 +55,14 @@ public static InterceptorModel GetInterceptorModel( var interceptorSymbols = new List(); var order = 0; INamedTypeSymbol? explicitService = null; + INamedTypeSymbol? realm = null; // 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) { - ReadAttribute(attribute, context, cancellationToken, interceptorSymbols, ref order, ref explicitService); + ReadAttribute( + attribute, context, cancellationToken, interceptorSymbols, ref order, ref explicitService, ref realm); } if (interceptorSymbols.Count == 0) { @@ -70,11 +72,11 @@ public static InterceptorModel GetInterceptorModel( var serviceSymbol = ResolveServiceInterface(implementationSymbol, explicitService, out var unsupported); if (serviceSymbol == null) { - return Refuse(unsupported); + return Refuse(unsupported, context); } if (!InterceptedMemberReader.Read(serviceSymbol, out var members, out var declarations, out unsupported)) { - return Refuse(unsupported); + return Refuse(unsupported, context); } if (members.Count == 0) { @@ -101,13 +103,15 @@ public static InterceptorModel GetInterceptorModel( members, declarations, order, - TypeParameters: TypeParameterModels(implementationSymbol)); + TypeParameters: TypeParameterModels(implementationSymbol), + Realm: realm?.GetTypeDefinition(), + Location: LocationModel.From(context.Node)); } - private static InterceptorModel Refuse(string? reason) => + private static InterceptorModel Refuse(string? reason, SyntaxTransformContext context) => reason == null ? InterceptorModel.Ignore - : InterceptorModel.Refused(reason); + : InterceptorModel.Refused(reason, LocationModel.From(context.Node)); /// /// The interfaces an interceptor implements, which decide the members it can be placed around. @@ -167,7 +171,8 @@ private static void ReadAttribute( CancellationToken cancellationToken, List interceptors, ref int order, - ref INamedTypeSymbol? explicitService) { + ref INamedTypeSymbol? explicitService, + ref INamedTypeSymbol? realm) { if (attribute.ArgumentList == null) { return; @@ -199,6 +204,11 @@ private static void ReadAttribute( explicitService = ResolveType(serviceTypeOf, context, cancellationToken); } break; + case "Realm": + if (argument.Expression is TypeOfExpressionSyntax realmTypeOf) { + realm = ResolveType(realmTypeOf, context, cancellationToken); + } + break; } } } diff --git a/src/DependencyModules.SourceGenerator.Impl/Utilities/ServiceModelUtility.cs b/src/DependencyModules.SourceGenerator.Impl/Utilities/ServiceModelUtility.cs index 019b700..60cc613 100644 --- a/src/DependencyModules.SourceGenerator.Impl/Utilities/ServiceModelUtility.cs +++ b/src/DependencyModules.SourceGenerator.Impl/Utilities/ServiceModelUtility.cs @@ -70,6 +70,21 @@ public class ServiceModelUtility { 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) { + return ServiceLifestyle.Singleton; + } + + if (attributeType.Name == KnownTypes.DependencyModules.Attributes.ScopedServiceAttribute.Name) { + return ServiceLifestyle.Scoped; + } + + return ServiceLifestyle.Transient; + } + public static ServiceModel? GetServiceModel( SyntaxTransformContext context, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -111,7 +126,8 @@ public class ServiceModelUtility { null, factoryModel, null, GetRegistrations(context, returnType, models, cancellationToken), - RegistrationFeature.None); + RegistrationFeature.None, + Location: LocationModel.From(context.Node)); } private static ServiceFactoryModel? GetFactoryModel(SyntaxTransformContext context, MethodDeclarationSyntax methodDeclarationSyntax, CancellationToken cancellationToken) { @@ -173,7 +189,8 @@ private static RegistrationFeature GetConstructionFeatures(SyntaxNode node) { ServiceLifestyle.Transient ) }, - RegistrationFeature.AutoRegisterSourceGenerator + RegistrationFeature.AutoRegisterSourceGenerator, + Location: LocationModel.From(context.Node) ); } @@ -190,7 +207,8 @@ private static RegistrationFeature GetConstructionFeatures(SyntaxNode node) { factoryOutput, registrations, GetConstructionFeatures(context.Node), - EnvironmentConditionUtility.GetConditions(context, context.Node, cancellationToken)); + EnvironmentConditionUtility.GetConditions(context, context.Node, cancellationToken), + LocationModel.From(context.Node)); } /// @@ -328,7 +346,7 @@ private static List GetRegistrations(SyntaxTransformCo // skipped — leaving the class unregistered with nothing to say so. if (AttributeTypeMatcher.Matches( context.SemanticModel, attributeSyntax, typeDefinition, cancellationToken)) { - list.Add(GetServiceRegistration(context, attributeSyntax, classDefinition)); + list.Add(GetServiceRegistration(context, attributeSyntax, classDefinition, typeDefinition)); } } @@ -418,15 +436,22 @@ private static ServiceLifestyle GetLifestyle(string toString) { return ServiceLifestyle.Singleton; } - private static ServiceRegistrationModel GetServiceRegistration(SyntaxTransformContext context, AttributeSyntax attributeSyntax, ITypeDefinition classDefinition) { - var lifestyle = ServiceLifestyle.Transient; - - if (attributeSyntax.Name.ToString().StartsWith("Singleton")) { - lifestyle = ServiceLifestyle.Singleton; - } - else if (attributeSyntax.Name.ToString().StartsWith("Scoped")) { - lifestyle = ServiceLifestyle.Scoped; - } + private static ServiceRegistrationModel GetServiceRegistration( + SyntaxTransformContext context, + AttributeSyntax attributeSyntax, + ITypeDefinition classDefinition, + 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:: + // prefix, and any `using` alias not named after its target all fell through to Transient — + // registered, with the wrong lifetime, and nothing said so. + // + // This costs nothing extra. AttributeTypeMatcher has already resolved the usage through the + // semantic model to decide that this attribute matched at all; the caller simply passes on + // which one it was rather than discarding the answer. + var lifestyle = LifestyleOf(attributeType); ITypeDefinition? registration = null; RegistrationType? registrationType = null; diff --git a/src/DependencyModules.SourceGenerator/Conventions/ConventionGenerator.cs b/src/DependencyModules.SourceGenerator/Conventions/ConventionGenerator.cs index 89177dc..845ba5f 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/ConventionGenerator.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/ConventionGenerator.cs @@ -456,7 +456,7 @@ private static void ReportOpenGenericDecoration( context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.OpenGenericCannotBeDecorated, - Location.None, + decorator.Location?.ToLocationOrNone() ?? Location.None, serviceName, decoratorName)); } @@ -483,7 +483,7 @@ private static void ReportAmbiguousOrdering( context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.AmbiguousDecoratorOrder, - Location.None, + decorators[i].Location?.ToLocationOrNone() ?? Location.None, decorators[i].DecoratorType.Name, decorators[j].DecoratorType.Name, decorators[i].ServiceType.Name, @@ -516,7 +516,7 @@ private static void ReportUnclaimedModules( context.ReportDiagnostic(Diagnostic.Create( DependencyModuleDiagnostics.ConventionCannotBeRead, - Location.None, + conventionModule.Location?.ToLocationOrNone() ?? Location.None, "the declaring type is not marked with [DependencyModule], so it registers nothing", name)); } diff --git a/src/DependencyModules.SourceGenerator/Conventions/Models/ConventionModel.cs b/src/DependencyModules.SourceGenerator/Conventions/Models/ConventionModel.cs index 0a0a0ef..df23c2c 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Models/ConventionModel.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Models/ConventionModel.cs @@ -291,7 +291,12 @@ public record UnreadableStatementModel(string Text, string Reason, LocationModel public record ConventionModuleModel( ITypeDefinition ModuleType, IReadOnlyList Conventions, - IReadOnlyList Unreadable) { + IReadOnlyList Unreadable, + + /// + /// Where the declaring type sits, so a diagnostic about the module itself can point at it. + /// + LocationModel? Location = null) { /// /// The sentinel for a declaration this generator does not own, matching how every other model diff --git a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionCandidateCache.cs b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionCandidateCache.cs index 4daf43b..55b243a 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionCandidateCache.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionCandidateCache.cs @@ -2,6 +2,7 @@ using DependencyModules.Conventions.Models; using DependencyModules.SourceGenerator.Impl.Utilities; using Microsoft.CodeAnalysis; +using DependencyModules.SourceGenerator.Impl.Models; namespace DependencyModules.Conventions.Utilities; diff --git a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionMatcher.cs b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionMatcher.cs index 3c3c635..62bbc55 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionMatcher.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionMatcher.cs @@ -190,10 +190,40 @@ or ConventionRegisterAs.AlsoSelf DependencyModuleDiagnostics.ConventionMatchedNothing, convention.Location.ToLocationOrNone(), serviceName, - moduleName)); + moduleName, + AdviceForEmptyMatch(convention, serviceName))); } } + /// + /// What to tell someone whose convention matched nothing, chosen by why it could not match. + /// + /// + /// A class named as the service type is the sharpest case: conventions match a type through the + /// interfaces it declares, so no call and no filter change can ever make one match. That is also + /// 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"; + } + + 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"; + } + /// /// Translates each name glob to an anchored regular expression, once per convention. /// diff --git a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionModelUtility.cs b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionModelUtility.cs index a8f1a79..7a32e00 100644 --- a/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionModelUtility.cs +++ b/src/DependencyModules.SourceGenerator/Conventions/Utilities/ConventionModelUtility.cs @@ -146,7 +146,8 @@ public static ConventionModuleModel GetConventionModuleModel( ReadStatement(context, statement, parameterName!, conventions, unreadable); } - return new ConventionModuleModel(typeDeclaration.GetTypeDefinition(), conventions, unreadable); + return new ConventionModuleModel( + typeDeclaration.GetTypeDefinition(), conventions, unreadable, LocationModel.From(typeDeclaration)); } private static void ReadStatement( diff --git a/src/DependencyModules.SourceGenerator/InterceptorSourceGenerator.cs b/src/DependencyModules.SourceGenerator/InterceptorSourceGenerator.cs index d894a98..a989df6 100644 --- a/src/DependencyModules.SourceGenerator/InterceptorSourceGenerator.cs +++ b/src/DependencyModules.SourceGenerator/InterceptorSourceGenerator.cs @@ -73,7 +73,11 @@ protected override void GenerateSourceOutput( writer.Write(model, wrapperName, model.ImplementationType.Namespace)); } - // One registration file per module, so every wrapper is applied wherever its service is. + // One registration file per module, carrying the interceptions that belong to that module. + // This used to hand every module the whole compilation's worth: an OnlyRealm module emitted + // 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)) { var registrationWriter = new InterceptorRegistrationWriter(); @@ -83,10 +87,41 @@ protected override void GenerateSourceOutput( registrationWriter.Write( EntryModelUtil.EnsureNamespace(entryPointModel, configurationModel), configurationModel, - usable)); + ForModule(usable, entryPointModel))); } } + /// + /// The interceptions one module is responsible for applying. + /// + /// + /// The same rule services and decorators already follow: a realm-scoped interception belongs + /// only to the module it names, and an unscoped one belongs to every module that is not + /// realm-only. + /// + private static IReadOnlyList ForModule( + 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)) { + selected.Add(model); + } + + continue; + } + + if (!onlyRealm) { + selected.Add(model); + } + } + + return selected; + } + /// /// Reports declarations that cannot be intercepted and drops them, so an unsupported shape /// produces an explanation rather than a wrapper that does not compile. @@ -103,7 +138,7 @@ private static IReadOnlyList ReportUnsupported( context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.CannotIntercept, - Location.None, + model.Location?.ToLocationOrNone() ?? Location.None, model.Refusal.Message)); continue; @@ -172,7 +207,7 @@ private static void ReportUnservedMembers( context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.InterceptorCannotServeMembers, - Location.None, + model.Location?.ToLocationOrNone() ?? Location.None, interceptor.Type.Name, InterfaceFor(kind), DescriptionFor(kind), diff --git a/src/DependencyModules.SourceGenerator/ServiceSourceGenerator.cs b/src/DependencyModules.SourceGenerator/ServiceSourceGenerator.cs index b356e56..6acc624 100644 --- a/src/DependencyModules.SourceGenerator/ServiceSourceGenerator.cs +++ b/src/DependencyModules.SourceGenerator/ServiceSourceGenerator.cs @@ -154,7 +154,7 @@ private static ImmutableArray ReportUnconstructableServices( context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.ServiceCannotBeConstructed, - Location.None, + serviceModel.Location?.ToLocationOrNone() ?? Location.None, typeName, reason)); } @@ -200,7 +200,7 @@ private static ImmutableArray ReportCrossWiredGenerics( context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.CrossWireCannotBeGeneric, - Location.None, + serviceModel.Location?.ToLocationOrNone() ?? Location.None, typeName)); } @@ -246,7 +246,7 @@ private static void ReportEnvironmentConditions( context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.EmptyEnvironmentCondition, - Location.None, + serviceModel.Location?.ToLocationOrNone() ?? Location.None, typeName, kind)); } @@ -267,7 +267,7 @@ private static void ReportEnvironmentConditions( context.ReportDiagnostic( Diagnostic.Create( DependencyModuleDiagnostics.RegisteredConditionally, - Location.None, + serviceModel.Location?.ToLocationOrNone() ?? Location.None, summary)); } } diff --git a/tests/DependencyModules.Tests/Infrastructure/ModelFactory.cs b/tests/DependencyModules.Tests/Infrastructure/ModelFactory.cs index 6e0b922..b4c5721 100644 --- a/tests/DependencyModules.Tests/Infrastructure/ModelFactory.cs +++ b/tests/DependencyModules.Tests/Infrastructure/ModelFactory.cs @@ -26,6 +26,7 @@ public static ModuleEntryPointModel EntryPoint( new( features, fileLocation, + LocationModel.None, entryPointType ?? TypeDefinition.Get("TestNamespace", "TestModule"), registrationType, generateAttribute, diff --git a/tests/DependencyModules.Tests/Snapshots/PublicApiTests.RuntimeApi.verified.txt b/tests/DependencyModules.Tests/Snapshots/PublicApiTests.RuntimeApi.verified.txt index 6f3b991..33fc788 100644 --- a/tests/DependencyModules.Tests/Snapshots/PublicApiTests.RuntimeApi.verified.txt +++ b/tests/DependencyModules.Tests/Snapshots/PublicApiTests.RuntimeApi.verified.txt @@ -86,6 +86,7 @@ namespace DependencyModules.Runtime.Attributes public InterceptAttribute(params System.Type[] interceptors) { } public System.Type[] Interceptors { get; } public int Order { get; set; } + public System.Type? Realm { get; set; } public System.Type? Service { get; set; } } public enum RegistrationType diff --git a/tests/DependencyModules.Tests/Snapshots/PublicApiTests.SourceGeneratorApi.verified.txt b/tests/DependencyModules.Tests/Snapshots/PublicApiTests.SourceGeneratorApi.verified.txt index ccfaa07..bdf9ec7 100644 --- a/tests/DependencyModules.Tests/Snapshots/PublicApiTests.SourceGeneratorApi.verified.txt +++ b/tests/DependencyModules.Tests/Snapshots/PublicApiTests.SourceGeneratorApi.verified.txt @@ -838,7 +838,7 @@ namespace DependencyModules.Conventions.Models public class ConventionCandidateModel : System.IEquatable { public static readonly DependencyModules.Conventions.Models.ConventionCandidateModel Ignore; - public ConventionCandidateModel(CSharpAuthor.ITypeDefinition ImplementationType, System.Collections.Generic.IReadOnlyList DeclaredInterfaces, System.Collections.Generic.IReadOnlyList BaseClassInterfaces, DependencyModules.SourceGenerator.Impl.Models.ConstructorInfoModel? Constructor, bool HasAccessibleConstructor, DependencyModules.Conventions.Models.LocationModel Location, System.Collections.Generic.IReadOnlyList? Conditions = null, System.Collections.Generic.IReadOnlyList? AttributeTypeKeys = null, string? AssemblyName = null) { } + public ConventionCandidateModel(CSharpAuthor.ITypeDefinition ImplementationType, System.Collections.Generic.IReadOnlyList DeclaredInterfaces, System.Collections.Generic.IReadOnlyList BaseClassInterfaces, DependencyModules.SourceGenerator.Impl.Models.ConstructorInfoModel? Constructor, bool HasAccessibleConstructor, DependencyModules.SourceGenerator.Impl.Models.LocationModel Location, System.Collections.Generic.IReadOnlyList? Conditions = null, System.Collections.Generic.IReadOnlyList? AttributeTypeKeys = null, string? AssemblyName = null) { } public string? AssemblyName { get; init; } public System.Collections.Generic.IReadOnlyList? AttributeTypeKeys { get; init; } public System.Collections.Generic.IReadOnlyList BaseClassInterfaces { get; init; } @@ -848,7 +848,7 @@ namespace DependencyModules.Conventions.Models public bool HasAccessibleConstructor { get; init; } public CSharpAuthor.ITypeDefinition ImplementationType { get; init; } public bool IsIgnored { get; } - public DependencyModules.Conventions.Models.LocationModel Location { get; init; } + public DependencyModules.SourceGenerator.Impl.Models.LocationModel Location { get; init; } public virtual bool Equals(DependencyModules.Conventions.Models.ConventionCandidateModel? other) { } public override int GetHashCode() { } public System.Collections.Generic.IEnumerable InterfacesInReach(bool includeBaseClasses) { } @@ -861,7 +861,7 @@ namespace DependencyModules.Conventions.Models bool IsOpenGeneric, DependencyModules.SourceGenerator.Impl.Models.ServiceLifestyle? Lifestyle, bool IncludeBaseClasses, - DependencyModules.Conventions.Models.LocationModel Location, + DependencyModules.SourceGenerator.Impl.Models.LocationModel Location, DependencyModules.Conventions.Models.ConventionRegisterAs RegisterAs = 0, System.Collections.Generic.IReadOnlyList? NamespaceFilters = null, DependencyModules.SourceGenerator.Impl.Models.RegistrationType? RegistrationType = default, @@ -883,7 +883,7 @@ namespace DependencyModules.Conventions.Models public object? Key { get; init; } public System.Collections.Generic.IReadOnlyList? KeyNamespaces { get; init; } public DependencyModules.SourceGenerator.Impl.Models.ServiceLifestyle? Lifestyle { get; init; } - public DependencyModules.Conventions.Models.LocationModel Location { get; init; } + public DependencyModules.SourceGenerator.Impl.Models.LocationModel Location { get; init; } public System.Collections.Generic.IReadOnlyList? NameFilters { get; init; } public System.Collections.Generic.IReadOnlyList? NamespaceFilters { get; init; } public DependencyModules.Conventions.Models.ConventionRegisterAs RegisterAs { get; init; } @@ -897,9 +897,10 @@ namespace DependencyModules.Conventions.Models public class ConventionModuleModel : System.IEquatable { public static readonly DependencyModules.Conventions.Models.ConventionModuleModel Ignore; - public ConventionModuleModel(CSharpAuthor.ITypeDefinition ModuleType, System.Collections.Generic.IReadOnlyList Conventions, System.Collections.Generic.IReadOnlyList Unreadable) { } + public ConventionModuleModel(CSharpAuthor.ITypeDefinition ModuleType, System.Collections.Generic.IReadOnlyList Conventions, System.Collections.Generic.IReadOnlyList Unreadable, DependencyModules.SourceGenerator.Impl.Models.LocationModel? Location = null) { } public System.Collections.Generic.IReadOnlyList Conventions { get; init; } public bool IsIgnored { get; } + public DependencyModules.SourceGenerator.Impl.Models.LocationModel? Location { get; init; } public CSharpAuthor.ITypeDefinition ModuleType { get; init; } public System.Collections.Generic.IReadOnlyList Unreadable { get; init; } public virtual bool Equals(DependencyModules.Conventions.Models.ConventionModuleModel? other) { } @@ -934,21 +935,6 @@ namespace DependencyModules.Conventions.Models public CSharpAuthor.ITypeDefinition InterfaceType { get; init; } public string? ViaTypeName { get; init; } } - public class LocationModel : System.IEquatable - { - public static readonly DependencyModules.Conventions.Models.LocationModel None; - public LocationModel(string FilePath, int SpanStart, int SpanLength, int StartLine, int StartCharacter, int EndLine, int EndCharacter) { } - public int EndCharacter { get; init; } - public int EndLine { get; init; } - public string FilePath { get; init; } - public int SpanLength { get; init; } - public int SpanStart { get; init; } - public int StartCharacter { get; init; } - public int StartLine { get; init; } - public Microsoft.CodeAnalysis.Location ToLocation() { } - public Microsoft.CodeAnalysis.Location ToLocationOrNone() { } - public static DependencyModules.Conventions.Models.LocationModel From(Microsoft.CodeAnalysis.SyntaxNode node) { } - } public class NameFilterModel : System.IEquatable { public NameFilterModel(string Pattern, bool Exclude) { } @@ -966,8 +952,8 @@ namespace DependencyModules.Conventions.Models } public class UnreadableStatementModel : System.IEquatable { - public UnreadableStatementModel(string Text, string Reason, DependencyModules.Conventions.Models.LocationModel Location) { } - public DependencyModules.Conventions.Models.LocationModel Location { get; init; } + public UnreadableStatementModel(string Text, string Reason, DependencyModules.SourceGenerator.Impl.Models.LocationModel Location) { } + public DependencyModules.SourceGenerator.Impl.Models.LocationModel Location { get; init; } public string Reason { get; init; } public string Text { get; init; } } @@ -1091,7 +1077,9 @@ namespace DependencyModules.SourceGenerator.Impl public static readonly Microsoft.CodeAnalysis.DiagnosticDescriptor GeneratorFailure; public static readonly Microsoft.CodeAnalysis.DiagnosticDescriptor InterceptorCannotServeMembers; public static readonly Microsoft.CodeAnalysis.DiagnosticDescriptor ModuleAttributeNamespaceNotImported; + public static readonly Microsoft.CodeAnalysis.DiagnosticDescriptor ModuleCannotBeNested; public static readonly Microsoft.CodeAnalysis.DiagnosticDescriptor ModuleMustBePartial; + public static readonly Microsoft.CodeAnalysis.DiagnosticDescriptor ModuleWithPropertiesShouldImplementEquals; public static readonly Microsoft.CodeAnalysis.DiagnosticDescriptor OpenGenericCannotBeDecorated; public static readonly Microsoft.CodeAnalysis.DiagnosticDescriptor RegisteredConditionally; public static readonly Microsoft.CodeAnalysis.DiagnosticDescriptor ServiceCannotBeConstructed; @@ -1262,7 +1250,7 @@ namespace DependencyModules.SourceGenerator.Impl.Models public class DecoratorModel : System.IEquatable { public static readonly DependencyModules.SourceGenerator.Impl.Models.DecoratorModel Ignore; - public DecoratorModel(CSharpAuthor.ITypeDefinition ServiceType, CSharpAuthor.ITypeDefinition DecoratorType, int Order, CSharpAuthor.ITypeDefinition? Realm, System.Collections.Generic.IReadOnlyList? Conditions = null, DependencyModules.SourceGenerator.Impl.Models.ConstructorInfoModel? Constructor = null, int InnerParameterIndex = -1, bool TypeParametersMatchService = true) { } + public DecoratorModel(CSharpAuthor.ITypeDefinition ServiceType, CSharpAuthor.ITypeDefinition DecoratorType, int Order, CSharpAuthor.ITypeDefinition? Realm, System.Collections.Generic.IReadOnlyList? Conditions = null, DependencyModules.SourceGenerator.Impl.Models.ConstructorInfoModel? Constructor = null, int InnerParameterIndex = -1, bool TypeParametersMatchService = true, DependencyModules.SourceGenerator.Impl.Models.LocationModel? Location = null) { } public bool CanMonomorphise { get; } public System.Collections.Generic.IReadOnlyList? Conditions { get; init; } public DependencyModules.SourceGenerator.Impl.Models.ConstructorInfoModel? Constructor { get; init; } @@ -1271,6 +1259,7 @@ namespace DependencyModules.SourceGenerator.Impl.Models public int InnerParameterIndex { get; init; } public bool IsIgnored { get; } public bool IsOpenGeneric { get; } + public DependencyModules.SourceGenerator.Impl.Models.LocationModel? Location { get; init; } public int Order { get; init; } public CSharpAuthor.ITypeDefinition? Realm { get; init; } public CSharpAuthor.ITypeDefinition ServiceType { get; init; } @@ -1374,18 +1363,20 @@ namespace DependencyModules.SourceGenerator.Impl.Models public class InterceptorModel : System.IEquatable { public static readonly DependencyModules.SourceGenerator.Impl.Models.InterceptorModel Ignore; - public InterceptorModel(CSharpAuthor.ITypeDefinition ServiceType, CSharpAuthor.ITypeDefinition ImplementationType, System.Collections.Generic.IReadOnlyList Interceptors, System.Collections.Generic.IReadOnlyList Members, System.Collections.Generic.IReadOnlyList Declarations, int Order, DependencyModules.SourceGenerator.Impl.Models.InterceptionRefusal? Refusal = null, System.Collections.Generic.IReadOnlyList? TypeParameters = null) { } + public InterceptorModel(CSharpAuthor.ITypeDefinition ServiceType, CSharpAuthor.ITypeDefinition ImplementationType, System.Collections.Generic.IReadOnlyList Interceptors, System.Collections.Generic.IReadOnlyList Members, System.Collections.Generic.IReadOnlyList Declarations, int Order, DependencyModules.SourceGenerator.Impl.Models.InterceptionRefusal? Refusal = null, System.Collections.Generic.IReadOnlyList? TypeParameters = null, CSharpAuthor.ITypeDefinition? Realm = null, DependencyModules.SourceGenerator.Impl.Models.LocationModel? Location = null) { } public System.Collections.Generic.IReadOnlyList Declarations { get; init; } public CSharpAuthor.ITypeDefinition ImplementationType { get; init; } public System.Collections.Generic.IReadOnlyList Interceptors { get; init; } public bool IsIgnored { get; } public bool IsOpenGeneric { get; } + public DependencyModules.SourceGenerator.Impl.Models.LocationModel? Location { get; init; } public System.Collections.Generic.IReadOnlyList Members { get; init; } public int Order { get; init; } + public CSharpAuthor.ITypeDefinition? Realm { get; init; } public DependencyModules.SourceGenerator.Impl.Models.InterceptionRefusal? Refusal { get; init; } public CSharpAuthor.ITypeDefinition ServiceType { get; init; } public System.Collections.Generic.IReadOnlyList? TypeParameters { get; init; } - public static DependencyModules.SourceGenerator.Impl.Models.InterceptorModel Refused(string message) { } + public static DependencyModules.SourceGenerator.Impl.Models.InterceptorModel Refused(string message, DependencyModules.SourceGenerator.Impl.Models.LocationModel? location = null) { } } public class InterceptorModelComparer : System.Collections.Generic.IEqualityComparer { @@ -1402,6 +1393,21 @@ namespace DependencyModules.SourceGenerator.Impl.Models public CSharpAuthor.ITypeDefinition Type { get; init; } public bool CanServe(DependencyModules.SourceGenerator.Impl.Models.InterceptorKind kind) { } } + public class LocationModel : System.IEquatable + { + public static readonly DependencyModules.SourceGenerator.Impl.Models.LocationModel None; + public LocationModel(string FilePath, int SpanStart, int SpanLength, int StartLine, int StartCharacter, int EndLine, int EndCharacter) { } + public int EndCharacter { get; init; } + public int EndLine { get; init; } + public string FilePath { get; init; } + public int SpanLength { get; init; } + public int SpanStart { get; init; } + public int StartCharacter { get; init; } + public int StartLine { get; init; } + public Microsoft.CodeAnalysis.Location ToLocation() { } + public Microsoft.CodeAnalysis.Location ToLocationOrNone() { } + public static DependencyModules.SourceGenerator.Impl.Models.LocationModel From(Microsoft.CodeAnalysis.SyntaxNode node) { } + } public enum LogOutputLevel { Debug = 1, @@ -1428,10 +1434,11 @@ namespace DependencyModules.SourceGenerator.Impl.Models ShouldImplementEquals = 4, IsRecord = 8, NotPartial = 16, + NestedInType = 32, } public class ModuleEntryPointModel : DependencyModules.SourceGenerator.Impl.Models.IClassModel, System.IEquatable { - public ModuleEntryPointModel(DependencyModules.SourceGenerator.Impl.Models.ModuleEntryPointFeatures ModuleFeatures, string FileLocation, CSharpAuthor.ITypeDefinition EntryPointType, DependencyModules.SourceGenerator.Impl.Models.RegistrationType? RegistrationType, bool? GenerateAttribute, bool? RegisterJsonSerializers, string? UseMethod, bool? GenerateFactories, System.Collections.Generic.IReadOnlyList Parameters, System.Collections.Generic.IReadOnlyList PropertyInfoModels, System.Collections.Generic.IReadOnlyList AttributeModels, System.Collections.Generic.IReadOnlyList AdditionalModules, System.Collections.Generic.IReadOnlyList Features) { } + public ModuleEntryPointModel(DependencyModules.SourceGenerator.Impl.Models.ModuleEntryPointFeatures ModuleFeatures, string FileLocation, DependencyModules.SourceGenerator.Impl.Models.LocationModel Location, CSharpAuthor.ITypeDefinition EntryPointType, DependencyModules.SourceGenerator.Impl.Models.RegistrationType? RegistrationType, bool? GenerateAttribute, bool? RegisterJsonSerializers, string? UseMethod, bool? GenerateFactories, System.Collections.Generic.IReadOnlyList Parameters, System.Collections.Generic.IReadOnlyList PropertyInfoModels, System.Collections.Generic.IReadOnlyList AttributeModels, System.Collections.Generic.IReadOnlyList AdditionalModules, System.Collections.Generic.IReadOnlyList Features) { } public System.Collections.Generic.IReadOnlyList AdditionalModules { get; init; } public System.Collections.Generic.IReadOnlyList AttributeModels { get; init; } public CSharpAuthor.ITypeDefinition ClassType { get; } @@ -1440,6 +1447,7 @@ namespace DependencyModules.SourceGenerator.Impl.Models public string FileLocation { get; init; } public bool? GenerateAttribute { get; init; } public bool? GenerateFactories { get; init; } + public DependencyModules.SourceGenerator.Impl.Models.LocationModel Location { get; init; } public DependencyModules.SourceGenerator.Impl.Models.ModuleEntryPointFeatures ModuleFeatures { get; init; } public System.Collections.Generic.IReadOnlyList Parameters { get; init; } public System.Collections.Generic.IReadOnlyList PropertyInfoModels { get; init; } @@ -1518,13 +1526,14 @@ namespace DependencyModules.SourceGenerator.Impl.Models public class ServiceModel : System.IEquatable { public static DependencyModules.SourceGenerator.Impl.Models.ServiceModel Ignore; - public ServiceModel(CSharpAuthor.ITypeDefinition ImplementationType, DependencyModules.SourceGenerator.Impl.Models.ConstructorInfoModel? Constructor, DependencyModules.SourceGenerator.Impl.Models.ServiceFactoryModel? Factory, DependencyModules.SourceGenerator.Impl.Models.FactoryOutputDelegate? FactoryOutput, System.Collections.Generic.IReadOnlyList Registrations, DependencyModules.SourceGenerator.Impl.Models.RegistrationFeature Features, System.Collections.Generic.IReadOnlyList? Conditions = null) { } + public ServiceModel(CSharpAuthor.ITypeDefinition ImplementationType, DependencyModules.SourceGenerator.Impl.Models.ConstructorInfoModel? Constructor, DependencyModules.SourceGenerator.Impl.Models.ServiceFactoryModel? Factory, DependencyModules.SourceGenerator.Impl.Models.FactoryOutputDelegate? FactoryOutput, System.Collections.Generic.IReadOnlyList Registrations, DependencyModules.SourceGenerator.Impl.Models.RegistrationFeature Features, System.Collections.Generic.IReadOnlyList? Conditions = null, DependencyModules.SourceGenerator.Impl.Models.LocationModel? Location = null) { } public System.Collections.Generic.IReadOnlyList? Conditions { get; init; } public DependencyModules.SourceGenerator.Impl.Models.ConstructorInfoModel? Constructor { get; init; } public DependencyModules.SourceGenerator.Impl.Models.ServiceFactoryModel? Factory { get; init; } public DependencyModules.SourceGenerator.Impl.Models.FactoryOutputDelegate? FactoryOutput { get; init; } public DependencyModules.SourceGenerator.Impl.Models.RegistrationFeature Features { get; init; } public CSharpAuthor.ITypeDefinition ImplementationType { get; init; } + public DependencyModules.SourceGenerator.Impl.Models.LocationModel? Location { get; init; } public System.Collections.Generic.IReadOnlyList Registrations { get; init; } } public class ServiceModelComparer : System.Collections.Generic.IEqualityComparer