From 21afb4738f3998173e4ad5472b0a614129c33fa1 Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:34:46 +0100 Subject: [PATCH 01/13] fix: recognise applied AutoInclude so LC045 avoids false positives Follow exact source-visible IEntityTypeConfiguration applications while preserving conservative invalidation for conditional, runtime, helper, disablement, and lookalike paths. --- docs/LC045_MissingInclude.md | 15 +- .../MissingIncludeAppliedConfiguration.cs | 311 + .../MissingIncludeAutoIncludeConfiguration.cs | 78 +- ...MissingIncludeAppliedConfigurationTests.cs | 364 + .../MissingIncludeAutoIncludeTests.cs | 121 + .../MissingIncludeEdgeCasesTests.cs | 18 + .../AnalyzerModularizationTests.cs | 6723 +---------------- 7 files changed, 1298 insertions(+), 6332 deletions(-) create mode 100644 src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs create mode 100644 tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs diff --git a/docs/LC045_MissingInclude.md b/docs/LC045_MissingInclude.md index 1db6117..b9ac483 100644 --- a/docs/LC045_MissingInclude.md +++ b/docs/LC045_MissingInclude.md @@ -49,7 +49,7 @@ The fix only registers when the source expression it would wrap is statically `I 1. **Anchor**: register on entity-producing materializers — `ToList`/`ToArray`/`ToHashSet` (+ supported `Async` forms), `First`/`Single`/`Last` (`OrDefault`, `Async`), and query-root `ElementAt` (`OrDefault`, supported `Async`) — plus synchronous `foreach` directly over a statically `IQueryable` DbSet-rooted source. Inline collection materializers use the same source proof. Aggregates (`Count`, `Any`, …) never materialize entities and are ignored. 2. **Chain proof**: walk the semantic source parameter back to a `DbSet` property/field on a `DbContext`, or to `DbContext.Set()`. Exact `Queryable`, EF, and relational symbols preserve only known query shapes, including `AsQueryable`, `IgnoreAutoIncludes`, and EF Core `FromSql*`; reordered static arguments are resolved by parameter ordinal. Anything else — `Select`, `Join`, `GroupBy`, custom extensions, and lookalikes — bails. 3. **Included paths**: parse every `Include`/`ThenInclude` (lambda, filtered-lambda, and constant-string overloads) into navigation paths and record every prefix (`Include(o => o.A.B)` covers `A` and `A.B`). If any Include cannot be parsed (dynamic string), the whole query is skipped — it could cover anything. -4. **Model-level eager loading**: cache exact top-level `OnModelCreating` overrides per context, matching constructed generic contexts through their original symbols. An exact EF `modelBuilder.Entity().Navigation(e => e.Nav).AutoInclude()` chain counts as loading only that entity/navigation path. Later fluent or standalone disablement removes the proof, an unproven later model-configuration boundary invalidates prior evidence, and a query-level `IgnoreAutoIncludes()` disables all model-level evidence for that query. +4. **Model-level eager loading**: cache exact top-level `OnModelCreating` overrides per context, matching constructed generic contexts through their original symbols. An exact EF `modelBuilder.Entity().Navigation(e => e.Nav).AutoInclude()` chain counts as loading only that entity/navigation path. The same proof follows an unconditional `modelBuilder.ApplyConfiguration(new TConfiguration())` into the source-visible implementation of the exact `IEntityTypeConfiguration.Configure` method, where direct top-level `builder.Navigation(...).AutoInclude()` settings are applied in execution order. Later fluent, standalone, or applied-configuration disablement removes the proof; conditional/runtime settings and unknown builder-consuming helpers invalidate it. A query-level `IgnoreAutoIncludes()` disables all model-level evidence for that query. 5. **Navigation classification**: a property is a navigation when its type (or collection element type) has a `DbSet` on the same context. Owned and unmapped types have no `DbSet` and are never flagged. 6. **Usage scan**: build and cache the Roslyn CFG for the containing method or constructor, then analyse forward from the materializer or direct loop source. Track entity-bearing locals — collection results, `foreach` iteration variables, indexer-initialized locals, exact `System.Linq.Enumerable` element extraction (`First*`, `Single*`, `Last*`, `ElementAt*`) from a materialized collection, aliases, and locals extracted from reference navigations — by origin, binding generation, and navigation prefix. Nested collection iteration carries that prefix (`order.Items` then `item.Product` → `Items.Product`). At joins, keep only identical bindings, retain navigation writes that occurred on every incoming path, and treat an escape or uncertain reassignment on any incoming path as uncertainty for subsequent reads. Exact `List.ForEach` and single-source `Enumerable.Where`/`Select`/`Any`/`All` inline callbacks receive their own nested CFG only while the original materialized collection generation is proven active at the call. `Where` forwards provenance only through an effect-free inline predicate, and scalar `Select` projections do not poison later ordinary reads; entity-returning projections, arbitrary callbacks, and delegate/method-group forms remain boundaries. Direct property-subpattern reads use the same navigation-path and dominance proof. 7. **Emit**: one diagnostic per distinct missing navigation path, at the first access site, carrying the exact query source location and the dotted path for the code fix. Only maximal paths are reported — fixing `Customer.Address` eagerly loads `Customer` too. @@ -60,14 +60,14 @@ The fix only registers when the source expression it would wrap is statically `I - Reassigning a result local or repointing an entity local similarly suppresses only subsequent reads whose origin is no longer proven. If only one control-flow branch escapes or repoints the value, the merged origin is uncertain and stays quiet afterward. - Navigation writes are not reads: `o.Customer = c` (including compound, `??=`, and deconstruction assignments) and `o.Items.Add(x)` are recognized relationship-fix-up patterns and stay quiet. A navigation write satisfies a later read only for the same entity origin and only when every path reaching that read performs the write; a one-branch write or a write to a different extracted entity does not suppress the diagnostic. - Mid-path casts and null-forgiving operators in Include lambdas (`Include(o => o.Customer!.Address)`, `Include(o => ((Derived)o.Nav).Child)`) parse as the full path; an Include shape the parser cannot prove silences the whole query. -- Model-level eager-loading evidence is context- and path-scoped. Conditional, deferred, runtime-valued, or early-exit-guarded `AutoInclude()` calls, shadowed or hidden-slot `OnModelCreating` lookalikes, fluent or standalone `AutoInclude(false)`, later base/helper/indirect configuration boundaries, and configuration belonging to another context or navigation never suppress LC045. +- Model-level eager-loading evidence is context- and path-scoped. Conditional, deferred, runtime-valued, or early-exit-guarded `AutoInclude()` calls, shadowed or hidden-slot `OnModelCreating` lookalikes, fluent, standalone, or applied-configuration `AutoInclude(false)`, later base/helper/indirect configuration boundaries, and configuration belonging to another context, entity, or navigation never suppress LC045. Ordinary exact EF builder calls such as `HasKey` and relational mapping extensions do not invalidate an otherwise direct configuration proof. - `nameof(o.Customer)` evaluates nothing and is never flagged. - Properties whose type has no `DbSet` (owned/unmapped types) are never navigations. - Non-EF sources (`List` LINQ) never match the DbSet root proof. ## Deliberate Decisions & Known Limits - **Null-guarded access still fires.** `if (o.Customer != null)` and `order?.Customer` are flagged on purpose: with proxies the null check itself can trigger the N+1 load, and without another loading mechanism a consistently null navigation makes the guard dead code hiding the bug. Suppress with `#pragma warning disable LC045` if the guard is intentional. This holds for every null-conditional spelling: chained inline access on the materializer (`FirstOrDefault()?.Customer?.Name`, `FirstOrDefault()?.Customer.Address?.City`), parenthesized regrouping (`(order?.Customer)?.Address?.City`, reported as `Customer.Address`, including inline materializer and inherited-navigation forms), conditional element access on the result (`orders?[0].Customer`), and locals initialized from a conditional indexer (`var o = orders?[0];`). Conditional method-call results such as `(order?.Customer.GetDetached())?.Address` are treated as call results, not as a continuation of the queried navigation path. -- Exact top-level `AutoInclude()` chains in the nearest real `OnModelCreating` override are recognised, including constructed generic contexts. Indirect builder locals, `IEntityTypeConfiguration`/`ApplyConfiguration`, inferred base-method calls, deferred calls, later unproven model mutations, and transitive auto-includes on a different entity remain unproven and can still report; keep an explicit `Include`, project, or use a reviewed suppression for those shapes. +- Exact top-level `AutoInclude()` chains in the nearest real `OnModelCreating` override are recognised, including constructed generic contexts. Exact unconditional `ApplyConfiguration(new TConfiguration())` calls are also recognised when the source-visible `IEntityTypeConfiguration.Configure` implementation contains direct builder chains. Configuration instances supplied through locals, fields, factories, assembly scanning, builder aliases, helper-delegated settings, inferred base-method calls, deferred calls, later unproven model mutations, and transitive auto-includes on a different entity remain unproven and can still report; keep an explicit `Include`, project, or use a reviewed suppression for those shapes. - Widened `IEnumerable` aliases are diagnostic-only: the analyzer can still prove the DbSet root, but the fixer will not emit `Include` against a non-queryable source expression. - Current scope is intra-procedural and local-based (methods and constructors). Out of scope (quiet, not flagged): `await foreach`, arbitrary callbacks and delegate/method-group consumers, predicate/default-value element extractor overloads, custom extraction lookalikes, provider-specific temporal APIs, `Find*`, `Entry(...).Reference/Collection(...).Load()` recognition, and `IQueryable` parameters / repository-returned queries as roots. @@ -106,6 +106,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) var autoIncluded = db.Orders.ToList(); foreach (var order in autoIncluded) Console.WriteLine(order.Customer.Name); +// Exact source-visible configuration classes are also recognised. +sealed class OrderConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + => builder.Navigation(o => o.Customer).AutoInclude(); +} +protected override void OnModelCreating(ModelBuilder modelBuilder) + => modelBuilder.ApplyConfiguration(new OrderConfiguration()); + var names = db.Orders.Select(o => o.Customer.Name).ToList(); // projection — out of scope var list = db.Orders.ToList(); diff --git a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs new file mode 100644 index 0000000..c1a7d87 --- /dev/null +++ b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs @@ -0,0 +1,311 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using LinqContraband.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; + +namespace LinqContraband.Analyzers.LC045_MissingInclude; + +public sealed partial class MissingIncludeAnalyzer +{ + private static bool TryApplyConfigurationAutoIncludes( + IInvocationOperation invocation, + IParameterSymbol modelBuilderParameter, + Compilation compilation, + Dictionary> prefixesByEntity, + CancellationToken cancellationToken + ) + { + if ( + invocation.TargetMethod.Name != "ApplyConfiguration" + || invocation.TargetMethod.ContainingType.Name != "ModelBuilder" + || invocation.TargetMethod.ContainingType.ContainingNamespace.ToDisplayString() + != "Microsoft.EntityFrameworkCore" + || invocation.Instance?.UnwrapConversions() + is not IParameterReferenceOperation modelBuilderReference + || !SymbolEqualityComparer.Default.Equals( + modelBuilderReference.Parameter.OriginalDefinition, + modelBuilderParameter.OriginalDefinition + ) + || invocation.TargetMethod.TypeArguments.Length != 1 + || invocation.TargetMethod.TypeArguments[0] is not INamedTypeSymbol entityType + ) + { + return false; + } + + var configurationArgument = invocation.Arguments.FirstOrDefault(argument => + argument.Parameter?.Ordinal == 0 + ); + if ( + configurationArgument?.Value.UnwrapConversions() + is not IObjectCreationOperation configurationCreation + || configurationCreation.Type is not INamedTypeSymbol configurationType + || !TryGetExactConfigureMethod( + configurationType, + entityType, + out var configureMethod + ) + || configureMethod.Parameters.Length != 1 + || configureMethod.Parameters[0].Type is not INamedTypeSymbol builderType + || !IsEfBuilderType(builderType, "EntityTypeBuilder") + ) + { + return false; + } + + var changes = new List(); + foreach (var syntaxReference in configureMethod.DeclaringSyntaxReferences) + { + cancellationToken.ThrowIfCancellationRequested(); + var syntax = syntaxReference.GetSyntax(cancellationToken); + var semanticModel = compilation.GetSemanticModel(syntax.SyntaxTree); + + foreach ( + var invocationSyntax in syntax.DescendantNodes().OfType() + ) + { + cancellationToken.ThrowIfCancellationRequested(); + if ( + semanticModel.GetOperation(invocationSyntax, cancellationToken) + is not IInvocationOperation configurationInvocation + ) + { + return false; + } + + var isTopLevel = TryGetTopLevelConfigurationExecution( + configurationInvocation, + semanticModel, + out var isUnconditional + ); + if ( + TryGetAppliedConfigurationAutoInclude( + configurationInvocation, + configureMethod.Parameters[0], + entityType, + semanticModel, + out var includePath, + out var enabled + ) + ) + { + if (enabled == true) + { + if (isTopLevel && isUnconditional) + changes.Add(new AppliedAutoIncludeChange(includePath, true)); + } + else + { + changes.Add(new AppliedAutoIncludeChange(includePath, false)); + } + + continue; + } + + if ( + IsUnprovenAppliedConfigurationBoundary( + configurationInvocation, + configureMethod.Parameters[0], + cancellationToken + ) + ) + { + return false; + } + } + } + + if (!prefixesByEntity.TryGetValue(entityType, out var prefixes)) + { + prefixes = new HashSet(System.StringComparer.Ordinal); + prefixesByEntity[entityType] = prefixes; + } + + foreach (var change in changes) + { + if (change.Enabled) + { + AddPathPrefixes(change.Path, prefixes); + continue; + } + + var disabledPath = change.Path.Key; + prefixes.RemoveWhere(path => + path == disabledPath + || path.StartsWith(disabledPath + ".", System.StringComparison.Ordinal) + ); + } + + return true; + } + + private static bool TryGetExactConfigureMethod( + INamedTypeSymbol configurationType, + INamedTypeSymbol entityType, + out IMethodSymbol configureMethod + ) + { + configureMethod = null!; + foreach (var interfaceType in configurationType.AllInterfaces) + { + if ( + interfaceType.Name != "IEntityTypeConfiguration" + || interfaceType.ContainingNamespace.ToDisplayString() + != "Microsoft.EntityFrameworkCore" + || interfaceType.TypeArguments.Length != 1 + || !SymbolEqualityComparer.Default.Equals( + interfaceType.TypeArguments[0], + entityType + ) + ) + { + continue; + } + + var interfaceMethod = interfaceType + .GetMembers("Configure") + .OfType() + .SingleOrDefault(method => method.Parameters.Length == 1); + if ( + interfaceMethod == null + || configurationType.FindImplementationForInterfaceMember(interfaceMethod) + is not IMethodSymbol implementation + || implementation.DeclaringSyntaxReferences.Length == 0 + ) + { + return false; + } + + configureMethod = implementation; + return true; + } + + return false; + } + + private static bool TryGetAppliedConfigurationAutoInclude( + IInvocationOperation autoInclude, + IParameterSymbol builderParameter, + INamedTypeSymbol entityType, + SemanticModel semanticModel, + out IncludePath includePath, + out bool? enabled + ) + { + includePath = null!; + enabled = null; + if ( + IsChainedAutoIncludeReceiver(autoInclude) + || autoInclude.TargetMethod.Name != "AutoInclude" + || !IsEfBuilderType(autoInclude.TargetMethod.ContainingType, "NavigationBuilder") + || !TryGetAutoIncludeSetting(autoInclude, out enabled) + || !TryGetNavigationInvocation(autoInclude, out var navigationInvocation) + || navigationInvocation.TargetMethod.Name != "Navigation" + || !IsEfBuilderType( + navigationInvocation.TargetMethod.ContainingType, + "EntityTypeBuilder" + ) + || navigationInvocation.Instance?.UnwrapConversions() + is not IParameterReferenceOperation builderReference + || !SymbolEqualityComparer.Default.Equals( + builderReference.Parameter.OriginalDefinition, + builderParameter.OriginalDefinition + ) + || builderParameter.Type is not INamedTypeSymbol configuredBuilder + || configuredBuilder.TypeArguments.Length != 1 + || !SymbolEqualityComparer.Default.Equals( + configuredBuilder.TypeArguments[0], + entityType + ) + || !IncludePathParser.TryGetIncludePath( + navigationInvocation, + semanticModel, + null, + out includePath + ) + ) + { + return false; + } + + return true; + } + + private static bool IsUnprovenAppliedConfigurationBoundary( + IInvocationOperation invocation, + IParameterSymbol builderParameter, + CancellationToken cancellationToken + ) + { + if (IsChainedAutoIncludeReceiver(invocation)) + return false; + + if ( + !ReferencesParameter(invocation, builderParameter, cancellationToken) + || IsKnownEfBuilderOperation(invocation.TargetMethod, builderParameter) + ) + { + return false; + } + + return true; + } + + private static bool IsKnownEfBuilderOperation( + IMethodSymbol method, + IParameterSymbol builderParameter + ) + { + var containingType = method.ContainingType; + var containingNamespace = containingType.ContainingNamespace.ToDisplayString(); + var builderAssembly = builderParameter.Type.ContainingAssembly; + if ( + containingNamespace == "Microsoft.EntityFrameworkCore.Metadata.Builders" + && SymbolEqualityComparer.Default.Equals( + containingType.ContainingAssembly, + builderAssembly + ) + && containingType.Name + is "EntityTypeBuilder" + or "KeyBuilder" + or "IndexBuilder" + or "PropertyBuilder" + or "PrimitiveCollectionBuilder" + or "ComplexPropertyBuilder" + or "DiscriminatorBuilder" + or "ReferenceNavigationBuilder" + or "ReferenceCollectionBuilder" + or "CollectionNavigationBuilder" + or "OwnedNavigationBuilder" + && method.Name is not ("AutoInclude" or "SetIsEagerLoaded") + ) + { + return true; + } + + return method.Name == "ToTable" + && containingType.Name == "RelationalEntityTypeBuilderExtensions" + && containingNamespace == "Microsoft.EntityFrameworkCore" + && (containingType.ContainingAssembly.Name == "Microsoft.EntityFrameworkCore.Relational" + || SymbolEqualityComparer.Default.Equals( + containingType.ContainingAssembly, + builderAssembly + )); + } + + private readonly struct AppliedAutoIncludeChange + { + public AppliedAutoIncludeChange(IncludePath path, bool enabled) + { + Path = path; + Enabled = enabled; + } + + public IncludePath Path { get; } + + public bool Enabled { get; } + } +} diff --git a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAutoIncludeConfiguration.cs b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAutoIncludeConfiguration.cs index d8aceba..a5b9d40 100644 --- a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAutoIncludeConfiguration.cs +++ b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAutoIncludeConfiguration.cs @@ -85,6 +85,21 @@ is not IInvocationOperation configurationInvocation semanticModel, out var isUnconditional ); + if ( + isTopLevel + && isUnconditional + && TryApplyConfigurationAutoIncludes( + configurationInvocation, + method.Parameters[0], + compilation, + prefixesByEntity, + cancellationToken + ) + ) + { + continue; + } + if ( !TryGetDirectAutoInclude( configurationInvocation, @@ -97,7 +112,10 @@ out var enabled ) { if ( - isTopLevel + (isTopLevel + || CanNestedInvocationChangeAutoIncludes( + configurationInvocation + )) && IsUnprovenModelConfigurationBoundary( configurationInvocation, method.Parameters[0], @@ -187,7 +205,8 @@ out bool? enabled enabled = null; if ( - autoInclude.TargetMethod.Name != "AutoInclude" + IsChainedAutoIncludeReceiver(autoInclude) + || autoInclude.TargetMethod.Name != "AutoInclude" || !IsEfBuilderType(autoInclude.TargetMethod.ContainingType, "NavigationBuilder") || !TryGetAutoIncludeSetting(autoInclude, out enabled) || !TryGetNavigationInvocation(autoInclude, out var navigationInvocation) @@ -226,6 +245,21 @@ out includePath return true; } + private static bool IsChainedAutoIncludeReceiver(IInvocationOperation invocation) + { + IOperation current = invocation; + while (current.Parent is IConversionOperation or IParenthesizedOperation) + current = current.Parent; + + return current.Parent is IInvocationOperation outerInvocation + && outerInvocation.TargetMethod.Name == "AutoInclude" + && IsEfBuilderType( + outerInvocation.TargetMethod.ContainingType, + "NavigationBuilder" + ) + && object.ReferenceEquals(outerInvocation.Instance?.UnwrapConversions(), current); + } + private static bool TryGetNavigationInvocation( IInvocationOperation autoInclude, out IInvocationOperation navigationInvocation @@ -336,6 +370,46 @@ CancellationToken cancellationToken ); } + private static bool CanNestedInvocationChangeAutoIncludes( + IInvocationOperation invocation + ) + { + if ( + invocation.TargetMethod.Name + is "ApplyConfiguration" + or "ApplyConfigurationsFromAssembly" + or "OnModelCreating" + or "SetIsEagerLoaded" + ) + { + return true; + } + + var containingType = invocation.TargetMethod.ContainingType; + var containingNamespace = containingType.ContainingNamespace.ToDisplayString(); + if ( + containingType.Name == "ModelBuilder" + && containingNamespace == "Microsoft.EntityFrameworkCore" + && invocation.TargetMethod.Name == "Entity" + ) + { + return false; + } + + if ( + containingNamespace == "Microsoft.EntityFrameworkCore.Metadata.Builders" + && ((containingType.Name == "EntityTypeBuilder" + && invocation.TargetMethod.Name == "Navigation") + || (containingType.Name == "NavigationBuilder" + && invocation.TargetMethod.Name == "AutoInclude")) + ) + { + return false; + } + + return true; + } + private static bool ReferencesParameter( IOperation? operation, IParameterSymbol parameter, diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs new file mode 100644 index 0000000..2fcea0b --- /dev/null +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs @@ -0,0 +1,364 @@ +using VerifyCS = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; + +namespace LinqContraband.Tests.Analyzers.LC045_MissingInclude; + +public partial class MissingIncludeEdgeCasesTests +{ + [Fact] + public async Task TestInnocent_AppliedConfigurationAutoInclude_NoDiagnostic() + { + var test = CreateAppliedConfigurationTest( + "builder.Navigation(o => o.Customer).AutoInclude();", + "Console.WriteLine(order.Customer.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test); + } + + [Fact] + public async Task TestInnocent_UnrelatedEfBuilderCallsBeforeAutoInclude_NoDiagnostic() + { + var test = CreateAppliedConfigurationTest( + @"builder.HasKey(o => o.Id); + builder.Navigation(o => o.Customer).AutoInclude();", + "Console.WriteLine(order.Customer.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test); + } + + [Fact] + public async Task TestInnocent_RelationalBuilderExtensionBeforeAutoInclude_NoDiagnostic() + { + var test = CreateAppliedConfigurationTest( + @"builder.ToTable(""Orders""); + builder.Navigation(o => o.Customer).AutoInclude();", + "Console.WriteLine(order.Customer.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test); + } + + [Fact] + public async Task TestCrime_AppliedConfigurationDifferentNavigation_DoesNotSuppress() + { + var test = CreateAppliedConfigurationTest( + "builder.Navigation(o => o.BillingCustomer).AutoInclude();", + "Console.WriteLine({|#0:order.Customer|}.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_AppliedConfigurationDisabledAutoInclude_DoesNotSuppress() + { + var test = CreateAppliedConfigurationTest( + "builder.Navigation(o => o.Customer).AutoInclude(false);", + "Console.WriteLine({|#0:order.Customer|}.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_AppliedConfigurationRuntimeAutoInclude_DoesNotSuppress() + { + var test = CreateAppliedConfigurationTest( + "builder.Navigation(o => o.Customer).AutoInclude(DateTime.UtcNow.Ticks > 0);", + "Console.WriteLine({|#0:order.Customer|}.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_AppliedConfigurationConditionalAutoInclude_DoesNotSuppress() + { + var test = CreateAppliedConfigurationTest( + @"if (DateTime.UtcNow.Ticks > 0) + { + builder.Navigation(o => o.Customer).AutoInclude(); + }", + "Console.WriteLine({|#0:order.Customer|}.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_AppliedConfigurationHelperBoundary_DoesNotSuppress() + { + var test = + Usings + + @" +class OrderConfiguration : IEntityTypeConfiguration +{ + private static void ConfigureNavigation(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(false); + } + + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(); + if (DateTime.UtcNow.Ticks > 0) + { + ConfigureNavigation(builder); + } + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new OrderConfiguration()); + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_SourceDefinedEfNamespaceHelper_DoesNotSuppress() + { + var test = + Usings + + @" +namespace Microsoft.EntityFrameworkCore +{ + public static class CustomBuilderExtensions + { + public static Metadata.Builders.EntityTypeBuilder ConfigureAutoInclude( + this Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(false); + return builder; + } + } +} + +class OrderConfiguration : IEntityTypeConfiguration +{ + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(); + builder.ConfigureAutoInclude(); + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new OrderConfiguration()); + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_LaterAppliedConfigurationDisableWins() + { + var test = + Usings + + @" +class EnableOrderConfiguration : IEntityTypeConfiguration +{ + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(); + } +} + +class DisableOrderConfiguration : IEntityTypeConfiguration +{ + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(false); + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new EnableOrderConfiguration()); + modelBuilder.ApplyConfiguration(new DisableOrderConfiguration()); + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_ConditionalAppliedConfigurationDisableInvalidatesEarlierEnable() + { + var test = + Usings + + @" +class DisableOrderConfiguration : IEntityTypeConfiguration +{ + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(false); + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Navigation(o => o.Customer).AutoInclude(); + if (DateTime.UtcNow.Ticks > 0) + { + modelBuilder.ApplyConfiguration(new DisableOrderConfiguration()); + } + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestInnocent_LaterFluentAutoIncludeEnableWins() + { + var test = CreateAppliedConfigurationTest( + "builder.Navigation(o => o.Customer).AutoInclude(false).AutoInclude();", + "Console.WriteLine(order.Customer.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test); + } + + [Fact] + public async Task TestCrime_LaterFluentAutoIncludeDisableWins() + { + var test = CreateAppliedConfigurationTest( + "builder.Navigation(o => o.Customer).AutoInclude().AutoInclude(false);", + "Console.WriteLine({|#0:order.Customer|}.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_IgnoreAutoIncludesOverridesAppliedConfiguration() + { + var test = CreateAppliedConfigurationTest( + "builder.Navigation(o => o.Customer).AutoInclude();", + "Console.WriteLine({|#0:order.Customer|}.Name);", + "db.Orders.IgnoreAutoIncludes().ToList()" + ); + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + private static string CreateAppliedConfigurationTest( + string configurationBody, + string access, + string query = "db.Orders.ToList()" + ) + { + return Usings + + @" +class OrderConfiguration : IEntityTypeConfiguration +{ + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + " + + configurationBody + + @" + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new OrderConfiguration()); + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = " + + query + + @"; + foreach (var order in orders) + { + " + + access + + @" + } + } +} +" + + MockNamespace; + } +} diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAutoIncludeTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAutoIncludeTests.cs index 05a11c1..08a8d50 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAutoIncludeTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAutoIncludeTests.cs @@ -695,4 +695,125 @@ void Main() await VerifyCS.VerifyAnalyzerAsync(test); } + + [Fact] + public async Task TestInnocent_FluentAutoIncludeEnableAfterDisable_NoDiagnostic() + { + var test = + Usings + + @" +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Navigation(o => o.Customer).AutoInclude(false).AutoInclude(); + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine(order.Customer.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test); + } + + [Fact] + public async Task TestCrime_ConditionalHelperDisableInvalidatesEarlierEnable() + { + var test = + Usings + + @" +class AutoIncludeDbContext : MyDbContext +{ + private static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Navigation(o => o.Customer).AutoInclude(false); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Navigation(o => o.Customer).AutoInclude(); + if (DateTime.UtcNow.Ticks > 0) + { + Configure(modelBuilder); + } + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_ConditionalSourceDefinedEfNamespaceHelperInvalidatesEarlierEnable() + { + var test = + Usings + + @" +namespace Microsoft.EntityFrameworkCore +{ + public static class CustomModelBuilderExtensions + { + public static void ConfigureAutoInclude(this ModelBuilder modelBuilder) + { + modelBuilder.Entity().Navigation(o => o.Customer).AutoInclude(false); + } + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Navigation(o => o.Customer).AutoInclude(); + if (DateTime.UtcNow.Ticks > 0) + { + modelBuilder.ConfigureAutoInclude(); + } + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + } diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeEdgeCasesTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeEdgeCasesTests.cs index bc96915..d234d43 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeEdgeCasesTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeEdgeCasesTests.cs @@ -51,12 +51,23 @@ public void Dispose() { } public class ModelBuilder { public Metadata.Builders.EntityTypeBuilder Entity() where TEntity : class => null; + + public void ApplyConfiguration(IEntityTypeConfiguration configuration) + where TEntity : class { } + } + + public interface IEntityTypeConfiguration where TEntity : class + { + void Configure(Metadata.Builders.EntityTypeBuilder builder); } namespace Metadata.Builders { public class EntityTypeBuilder where TEntity : class { + public EntityTypeBuilder HasKey( + System.Linq.Expressions.Expression> keyExpression) => this; + public NavigationBuilder Navigation( System.Linq.Expressions.Expression> navigationExpression) => null; } @@ -72,6 +83,13 @@ public class NavigationBuilder : NavigationBuilder public class EntityEntry where T : class { } + public static class RelationalEntityTypeBuilderExtensions + { + public static Metadata.Builders.EntityTypeBuilder ToTable( + this Metadata.Builders.EntityTypeBuilder builder, + string name) where TEntity : class => builder; + } + public class DbSet : IQueryable where T : class { public Type ElementType => typeof(T); diff --git a/tests/LinqContraband.Tests/Architecture/AnalyzerModularizationTests.cs b/tests/LinqContraband.Tests/Architecture/AnalyzerModularizationTests.cs index 65cb029..7f7e889 100644 --- a/tests/LinqContraband.Tests/Architecture/AnalyzerModularizationTests.cs +++ b/tests/LinqContraband.Tests/Architecture/AnalyzerModularizationTests.cs @@ -1,6327 +1,396 @@ -using System.IO; -using Xunit; - -namespace LinqContraband.Tests.Architecture; - -public sealed class AnalyzerModularizationTests -{ - private readonly string _repoRoot = RepositoryLayout.GetRepositoryRoot(); - - [Fact] - public void LC044_RootScanEntries_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC044_AsNoTrackingThenModify"); - var rootScanPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScan.cs"); - var entriesPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScanEntries.cs"); - - Assert.True(File.Exists(entriesPath), "LC044 root-scan entry DTOs should live in a focused partial file."); - - var rootScanSource = File.ReadAllText(rootScanPath); - Assert.DoesNotContain("internal readonly struct MutationEntry", rootScanSource); - Assert.DoesNotContain("internal readonly struct ReattachEntry", rootScanSource); - Assert.DoesNotContain("internal readonly struct DetachEntry", rootScanSource); - Assert.DoesNotContain("internal readonly struct TrackerClearEntry", rootScanSource); - Assert.DoesNotContain("internal readonly struct SaveChangesEntry", rootScanSource); - - var entriesSource = File.ReadAllText(entriesPath); - Assert.Contains("internal readonly struct MutationEntry", entriesSource); - Assert.Contains("internal readonly struct ReattachEntry", entriesSource); - Assert.Contains("internal readonly struct DetachEntry", entriesSource); - Assert.Contains("internal readonly struct TrackerClearEntry", entriesSource); - Assert.Contains("internal readonly struct SaveChangesEntry", entriesSource); - } - - [Fact] - public void LC044_RootScanOperationRecording_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC044_AsNoTrackingThenModify"); - var rootScanPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScan.cs"); - var operationRecordingPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScanOperationRecording.cs"); - - Assert.True(File.Exists(operationRecordingPath), "LC044 root-scan operation recording should live in a focused partial file."); - - var rootScanSource = File.ReadAllText(rootScanPath); - Assert.DoesNotContain("private static void HandleAssignment", rootScanSource); - Assert.DoesNotContain("private static void HandlePropertyMutation", rootScanSource); - Assert.DoesNotContain("private static void HandleInvocation", rootScanSource); - - var operationRecordingSource = File.ReadAllText(operationRecordingPath); - Assert.Contains("private static void HandleAssignment", operationRecordingSource); - Assert.Contains("private static void HandlePropertyMutation", operationRecordingSource); - Assert.Contains("private static void HandleInvocation", operationRecordingSource); - Assert.Contains("TryParseEntryStateAssignment", operationRecordingSource); - Assert.Contains("TryParseReattachInvocation", operationRecordingSource); - Assert.Contains("TryParseTrackerClear", operationRecordingSource); - } - - [Fact] - public void LC044_OptionalControlFlow_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC044_AsNoTrackingThenModify"); - var reachabilityPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyReachability.cs"); - var optionalFlowPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyOptionalFlow.cs"); - - Assert.True(File.Exists(optionalFlowPath), "LC044 optional branch/loop reachability rules should live in a focused partial file."); - - var reachabilitySource = File.ReadAllText(reachabilityPath); - Assert.DoesNotContain("private static bool IsNestedUnderOptionalControlFlow", reachabilitySource); - Assert.DoesNotContain("private static bool IfStatementMakesBranchMandatory", reachabilitySource); - Assert.DoesNotContain("private static bool StatementSkipsLater", reachabilitySource); - Assert.DoesNotContain("private static bool BranchSkipsLater", reachabilitySource); - - var optionalFlowSource = File.ReadAllText(optionalFlowPath); - Assert.Contains("private static bool IsNestedUnderOptionalControlFlow", optionalFlowSource); - Assert.Contains("private static bool IfStatementMakesBranchMandatory", optionalFlowSource); - Assert.Contains("private static bool StatementSkipsLater", optionalFlowSource); - Assert.Contains("private static bool BranchSkipsLater", optionalFlowSource); - } - - [Fact] - public void LC044_QuerySourceResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC044_AsNoTrackingThenModify"); - var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyAnalyzer.cs"); - var querySourcePath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyQuerySource.cs"); - - Assert.True(File.Exists(querySourcePath), "LC044 AsNoTracking query/materialization source resolution should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AsNoTrackingThenModifyAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool IsAsNoTrackingMaterialization", analyzerSource); - Assert.DoesNotContain("private static bool ChainContainsAsNoTracking", analyzerSource); - Assert.DoesNotContain("private static bool TryGetQueryContextSymbol", analyzerSource); - - var querySource = File.ReadAllText(querySourcePath); - Assert.Contains("private static bool IsAsNoTrackingMaterialization", querySource); - Assert.Contains("private static bool ChainContainsAsNoTracking", querySource); - Assert.Contains("private static bool TryGetQueryContextSymbol", querySource); - } - - [Fact] - public void LC008_AsyncContextResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC008_SyncBlocker"); - var analyzerPath = Path.Combine(analyzerDir, "SyncBlockerAnalyzer.cs"); - var asyncContextPath = Path.Combine(analyzerDir, "SyncBlockerAsyncContext.cs"); - - Assert.True(File.Exists(asyncContextPath), "LC008 async-context boundary checks should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class SyncBlockerAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool IsInsideAsyncMethod", analyzerSource); - - var asyncContextSource = File.ReadAllText(asyncContextPath); - Assert.Contains("private static bool IsInsideAsyncMethod", asyncContextSource); - Assert.Contains("ILocalFunctionOperation", asyncContextSource); - Assert.Contains("IAnonymousFunctionOperation", asyncContextSource); - } - - [Fact] - public void LC004_MethodSummaryConsumption_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC004_IQueryableLeak"); - var summaryPath = Path.Combine(analyzerDir, "IQueryableLeakMethodSummaryAnalysis.cs"); - var consumptionPath = Path.Combine(analyzerDir, "IQueryableLeakMethodSummaryConsumption.cs"); - - Assert.True(File.Exists(consumptionPath), "LC004 method-summary local consumption checks should live in a focused partial file."); - - var summarySource = File.ReadAllText(summaryPath); - Assert.DoesNotContain("private void MarkMaterializingConstructorHazards", summarySource); - Assert.DoesNotContain("private bool IsMaterializingCollectionConstructor", summarySource); - Assert.DoesNotContain("private bool IsDirectConsumption", summarySource); - Assert.DoesNotContain("private bool IsGetEnumeratorInvocation", summarySource); - Assert.DoesNotContain("private void MarkHazardIfParameterSource", summarySource); - - var consumptionSource = File.ReadAllText(consumptionPath); - Assert.Contains("private void MarkMaterializingConstructorHazards", consumptionSource); - Assert.Contains("private bool IsMaterializingCollectionConstructor", consumptionSource); - Assert.Contains("private bool IsDirectConsumption", consumptionSource); - Assert.Contains("private bool IsGetEnumeratorInvocation", consumptionSource); - Assert.Contains("private void MarkHazardIfParameterSource", consumptionSource); - } - - [Fact] - public void LC004_InvocationInputMapping_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC004_IQueryableLeak"); - var sourceResolutionPath = Path.Combine(analyzerDir, "IQueryableLeakSourceResolution.cs"); - var invocationInputsPath = Path.Combine(analyzerDir, "IQueryableLeakInvocationInputs.cs"); - - Assert.True(File.Exists(invocationInputsPath), "LC004 invocation input mapping should live in a focused partial file."); - - var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); - Assert.DoesNotContain("private IEnumerable EnumerateInvocationInputs", sourceResolutionSource); - - var invocationInputsSource = File.ReadAllText(invocationInputsPath); - Assert.Contains("private IEnumerable EnumerateInvocationInputs", invocationInputsSource); - } - - [Fact] - public void LC004_ExecutableRootTraversal_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC004_IQueryableLeak"); - var symbolHelpersPath = Path.Combine(analyzerDir, "IQueryableLeakSymbolHelpers.cs"); - var executableRootsPath = Path.Combine(analyzerDir, "IQueryableLeakExecutableRoots.cs"); - - Assert.True(File.Exists(executableRootsPath), "LC004 executable-root traversal should live in a focused partial file."); - - var symbolHelpersSource = File.ReadAllText(symbolHelpersPath); - Assert.DoesNotContain("private bool TryGetExecutableRoot", symbolHelpersSource); - Assert.DoesNotContain("private IEnumerable EnumerateOperations", symbolHelpersSource); - Assert.DoesNotContain("private static bool IsInsideNestedExecutable", symbolHelpersSource); - - var executableRootsSource = File.ReadAllText(executableRootsPath); - Assert.Contains("private bool TryGetExecutableRoot", executableRootsSource); - Assert.Contains("private IEnumerable EnumerateOperations", executableRootsSource); - Assert.Contains("private static bool IsInsideNestedExecutable", executableRootsSource); - } - - [Fact] - public void LC004_HazardousLookupTables_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC004_IQueryableLeak"); - var compilationStatePath = Path.Combine(analyzerDir, "IQueryableLeakCompilationState.cs"); - var lookupTablesPath = Path.Combine(analyzerDir, "IQueryableLeakHazardousLookupTables.cs"); - - Assert.True(File.Exists(lookupTablesPath), "LC004 hazardous method/type lookup tables should live in a focused partial file."); - - var compilationStateSource = File.ReadAllText(compilationStatePath); - Assert.DoesNotContain("private static readonly ImmutableHashSet HazardousEnumerableMethods", compilationStateSource); - Assert.DoesNotContain("private static readonly ImmutableHashSet MaterializingCollectionTypes", compilationStateSource); - - var lookupTablesSource = File.ReadAllText(lookupTablesPath); - Assert.Contains("private static readonly ImmutableHashSet HazardousEnumerableMethods", lookupTablesSource); - Assert.Contains("private static readonly ImmutableHashSet MaterializingCollectionTypes", lookupTablesSource); - } - - [Fact] - public void LC001_StaticQueryableOrderingRewrite_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC001_LocalMethod"); - var staticRewritePath = Path.Combine(analyzerDir, "LocalMethodFixerStaticQueryableRewrite.cs"); - var orderingRewritePath = Path.Combine(analyzerDir, "LocalMethodFixerStaticQueryableOrdering.cs"); - - Assert.True(File.Exists(orderingRewritePath), "LC001 static Queryable ordering-source rewrite should live in a focused partial file."); - - var staticRewriteSource = File.ReadAllText(staticRewritePath); - Assert.DoesNotContain("private static bool RewriteQueryableExtensionOrderingSourceChain", staticRewriteSource); - Assert.DoesNotContain("private static bool IsRewritableOrderedSource", staticRewriteSource); - Assert.DoesNotContain("private static bool IsQueryableOrderingInvocation", staticRewriteSource); - - var orderingRewriteSource = File.ReadAllText(orderingRewritePath); - Assert.Contains("private static bool RewriteQueryableExtensionOrderingSourceChain", orderingRewriteSource); - Assert.Contains("private static bool IsRewritableOrderedSource", orderingRewriteSource); - Assert.Contains("private static bool IsQueryableOrderingInvocation", orderingRewriteSource); - } - - [Fact] - public void LC044_DiagnosticReporting_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC044_AsNoTrackingThenModify"); - var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyAnalyzer.cs"); - var reportingPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyReporting.cs"); - - Assert.True(File.Exists(reportingPath), "LC044 diagnostic reporting should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static void TryReportForLocal", analyzerSource); - Assert.DoesNotContain("private static void TryReportForForeach", analyzerSource); - Assert.DoesNotContain("private readonly struct MutationHit", analyzerSource); - Assert.DoesNotContain("private static MutationHit? FindFirstPropertyMutation", analyzerSource); - - var reportingSource = File.ReadAllText(reportingPath); - Assert.Contains("private static void TryReportForLocal", reportingSource); - Assert.Contains("private static void TryReportForForeach", reportingSource); - Assert.Contains("private readonly struct MutationHit", reportingSource); - Assert.Contains("private static MutationHit? FindFirstPropertyMutation", reportingSource); - } - - [Fact] - public void LC017_FixerPropertyAccessSafety_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC017_WholeEntityProjection"); - var contextPath = Path.Combine(analyzerDir, "WholeEntityProjectionFixerContextAnalysis.cs"); - var propertyAccessPath = Path.Combine(analyzerDir, "WholeEntityProjectionFixerPropertyAccess.cs"); - var trackedExpressionsPath = Path.Combine(analyzerDir, "WholeEntityProjectionFixerTrackedEntityExpressions.cs"); - - Assert.True(File.Exists(propertyAccessPath), "LC017 fixer property-access safety checks should live in a focused partial file."); - Assert.True(File.Exists(trackedExpressionsPath), "LC017 fixer tracked-entity expression recognition should live in a focused partial file."); - - var contextSource = File.ReadAllText(contextPath); - Assert.DoesNotContain("private static bool HasUnsupportedEntityPropertyAccess", contextSource); - Assert.DoesNotContain("private static bool IsTrackedEntityConversionExpression", contextSource); - Assert.DoesNotContain("private static bool IsTrackedEntityExpression", contextSource); - Assert.DoesNotContain("private static bool HasUnsafeIndexedEntityAccess", contextSource); - Assert.DoesNotContain("private static bool IsPropertyOfType", contextSource); - - var propertyAccessSource = File.ReadAllText(propertyAccessPath); - Assert.Contains("private static bool HasUnsupportedEntityPropertyAccess", propertyAccessSource); - Assert.DoesNotContain("private static bool IsTrackedEntityConversionExpression", propertyAccessSource); - Assert.DoesNotContain("private static bool IsTrackedEntityExpression", propertyAccessSource); - Assert.Contains("private static bool HasUnsafeIndexedEntityAccess", propertyAccessSource); - Assert.Contains("private static bool IsPropertyOfType", propertyAccessSource); - - var trackedExpressionsSource = File.ReadAllText(trackedExpressionsPath); - Assert.Contains("private static bool IsTrackedEntityConversionExpression", trackedExpressionsSource); - Assert.Contains("private static bool IsTrackedEntityExpression", trackedExpressionsSource); - } - - [Fact] - public void LC017_UsageReferenceClassification_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC017_WholeEntityProjection"); - var usageAnalysisPath = Path.Combine(analyzerDir, "WholeEntityProjectionUsageAnalysis.cs"); - var usageReferencesPath = Path.Combine(analyzerDir, "WholeEntityProjectionUsageReferences.cs"); - - Assert.True(File.Exists(usageReferencesPath), "LC017 usage reference classification should live in a focused partial file."); - - var usageAnalysisSource = File.ReadAllText(usageAnalysisPath); - Assert.DoesNotContain("private static bool IsTrackedEntityReference", usageAnalysisSource); - Assert.DoesNotContain("private static bool IsDirectVariableEscape", usageAnalysisSource); - Assert.DoesNotContain("private static bool LambdaDirectlyReferences", usageAnalysisSource); - - var usageReferencesSource = File.ReadAllText(usageReferencesPath); - Assert.Contains("private static bool IsTrackedEntityReference", usageReferencesSource); - Assert.Contains("private static bool IsDirectVariableEscape", usageReferencesSource); - Assert.Contains("private static bool LambdaDirectlyReferences", usageReferencesSource); - } - - [Fact] - public void LC017_SyntaxPropertyCollection_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC017_WholeEntityProjection"); - var syntaxAnalysisPath = Path.Combine(analyzerDir, "WholeEntityProjectionSyntaxAnalysis.cs"); - var propertyCollectionPath = Path.Combine(analyzerDir, "WholeEntityProjectionSyntaxPropertyCollection.cs"); - - Assert.True(File.Exists(propertyCollectionPath), "LC017 syntax-based property collection should live in a focused partial file."); - - var syntaxAnalysisSource = File.ReadAllText(syntaxAnalysisPath); - Assert.DoesNotContain("private static void CollectSyntaxBasedPropertyAccesses", syntaxAnalysisSource); - - var propertyCollectionSource = File.ReadAllText(propertyCollectionPath); - Assert.Contains("private static void CollectSyntaxBasedPropertyAccesses", propertyCollectionSource); - Assert.Contains("ConditionalAccessExpressionSyntax", propertyCollectionSource); - Assert.Contains("MemberAccessExpressionSyntax", propertyCollectionSource); - } - - [Fact] - public void LC025_FixerSourceResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC025_AsNoTrackingWithUpdate"); - var fixerPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateFixer.cs"); - var sourceResolutionPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateFixerSourceResolution.cs"); - var noTrackingSourcePath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateFixerNoTrackingSource.cs"); - - Assert.True(File.Exists(sourceResolutionPath), "LC025 fixer AsNoTracking source resolution should live in a focused partial file."); - Assert.True(File.Exists(noTrackingSourcePath), "LC025 fixer recursive no-tracking source tracing should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class AsNoTrackingWithUpdateFixer", fixerSource); - Assert.DoesNotContain("private static InvocationExpressionSyntax? FindAsNoTrackingOrigin", fixerSource); - Assert.DoesNotContain("private readonly struct AsNoTrackingOrigin", fixerSource); - Assert.DoesNotContain("private static bool IsNoTrackingSource", fixerSource); - Assert.DoesNotContain("private static bool IsLocalFromNoTracking", fixerSource); - Assert.DoesNotContain("private static bool HasAsNoTrackingInChain", fixerSource); - - var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); - Assert.Contains("private static InvocationExpressionSyntax? FindAsNoTrackingOrigin", sourceResolutionSource); - Assert.Contains("private readonly struct AsNoTrackingOrigin", sourceResolutionSource); - Assert.DoesNotContain("private static bool IsNoTrackingSource", sourceResolutionSource); - Assert.DoesNotContain("private static bool IsLocalFromNoTracking", sourceResolutionSource); - - var noTrackingSource = File.ReadAllText(noTrackingSourcePath); - Assert.Contains("private static bool IsNoTrackingSource", noTrackingSource); - Assert.Contains("private static bool IsLocalFromNoTracking", noTrackingSource); - } - - [Fact] - public void LC025_FixerTrackingDirectiveAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC025_AsNoTrackingWithUpdate"); - var sourceResolutionPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateFixerSourceResolution.cs"); - var trackingDirectivePath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateFixerTrackingDirectiveAnalysis.cs"); - - Assert.True(File.Exists(trackingDirectivePath), "LC025 fixer tracking-directive chain analysis should live in a focused partial file."); - - var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); - Assert.DoesNotContain("private static bool HasAsNoTrackingInChain", sourceResolutionSource); - Assert.DoesNotContain("private static bool IsEfCoreNoTrackingDirective", sourceResolutionSource); - Assert.DoesNotContain("private static bool IsEfCoreAsTracking", sourceResolutionSource); - Assert.DoesNotContain("private static InvocationExpressionSyntax? FindAsNoTrackingInvocation", sourceResolutionSource); - - var trackingDirectiveSource = File.ReadAllText(trackingDirectivePath); - Assert.Contains("private static bool HasAsNoTrackingInChain", trackingDirectiveSource); - Assert.Contains("private static bool IsEfCoreNoTrackingDirective", trackingDirectiveSource); - Assert.Contains("private static bool IsEfCoreAsTracking", trackingDirectiveSource); - Assert.Contains("private static InvocationExpressionSyntax? FindAsNoTrackingInvocation", trackingDirectiveSource); - } - - [Fact] - public void IncludePathParser_HelperResponsibilities_LiveInDedicatedPartials() - { - var extensionsDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Extensions"); - var parserPath = Path.Combine(extensionsDir, "IncludePathParser.cs"); - var lambdaPath = Path.Combine(extensionsDir, "IncludePathParserLambdaPaths.cs"); - var typeAnalysisPath = Path.Combine(extensionsDir, "IncludePathParserTypeAnalysis.cs"); - - Assert.True(File.Exists(lambdaPath), "IncludePathParser lambda navigation parsing should live in a focused partial file."); - Assert.True(File.Exists(typeAnalysisPath), "IncludePathParser type/collection analysis should live in a focused partial file."); - - var parserSource = File.ReadAllText(parserPath); - Assert.Contains("internal static partial class IncludePathParser", parserSource); - Assert.DoesNotContain("private static bool TryAddNavigationSegments", parserSource); - Assert.DoesNotContain("public static bool TryGetCollectionElementType", parserSource); - - var lambdaSource = File.ReadAllText(lambdaPath); - Assert.Contains("private static bool TryAddNavigationSegments", lambdaSource); - Assert.Contains("private static CSharpSyntaxNode UnwrapExpression", lambdaSource); - - var typeAnalysisSource = File.ReadAllText(typeAnalysisPath); - Assert.Contains("public static bool TryGetCollectionElementType", typeAnalysisSource); - Assert.Contains("private static bool TryGetNamedGenericElementType", typeAnalysisSource); - } - - [Fact] - public void LC045_UsageScan_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC045_MissingInclude"); - var usageAnalysisPath = Path.Combine(analyzerDir, "MissingIncludeUsageAnalysis.cs"); - var usageScanPath = Path.Combine(analyzerDir, "MissingIncludeUsageScan.cs"); - - Assert.True(File.Exists(usageScanPath), "LC045 executable-root usage scanning should live in a focused partial file."); - - var usageAnalysisSource = File.ReadAllText(usageAnalysisPath); - Assert.DoesNotContain("private static List? CollectNavigationAccessesFromExecutableRoot", usageAnalysisSource); - - var usageScanSource = File.ReadAllText(usageScanPath); - Assert.Contains("private static List? CollectNavigationAccessesFromExecutableRoot", usageScanSource); - Assert.Contains("LambdaReferencesTrackedLocal", usageScanSource); - Assert.Contains("satisfiedPaths", usageScanSource); - } - - [Fact] - public void LC045_OriginAwareFlowResponsibilities_LiveInDedicatedPartials() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC045_MissingInclude"); - var analyzerPath = Path.Combine(analyzerDir, "MissingIncludeAnalyzer.cs"); - var usageScanPath = Path.Combine(analyzerDir, "MissingIncludeUsageScan.cs"); - var flowAnalysisPath = Path.Combine(analyzerDir, "MissingIncludeOriginFlowAnalysis.cs"); - var flowBindingsPath = Path.Combine(analyzerDir, "MissingIncludeOriginFlowBindings.cs"); - var flowContextPath = Path.Combine(analyzerDir, "MissingIncludeOriginFlowContext.cs"); - var flowEventsPath = Path.Combine(analyzerDir, "MissingIncludeOriginFlowEvents.cs"); - var flowStatePath = Path.Combine(analyzerDir, "MissingIncludeOriginFlowState.cs"); - var autoIncludePath = Path.Combine(analyzerDir, "MissingIncludeAutoIncludeConfiguration.cs"); - - Assert.True(File.Exists(flowAnalysisPath), "LC045 origin-aware control-flow analysis should live in a focused partial file."); - Assert.True(File.Exists(flowBindingsPath), "LC045 origin and alias binding discovery should live in a focused partial file."); - Assert.True(File.Exists(flowContextPath), "LC045 origin-flow context construction should live in a focused partial file."); - Assert.True(File.Exists(flowEventsPath), "LC045 origin-flow event collection should live in a focused partial file."); - Assert.True(File.Exists(flowStatePath), "LC045 origin-flow state and event models should live in a focused partial file."); - Assert.True(File.Exists(autoIncludePath), "LC045 model-level AutoInclude proof should live in a focused partial file."); - - var usageScanSource = File.ReadAllText(usageScanPath); - Assert.DoesNotContain("private static bool TryGetFlowGraph", usageScanSource); - Assert.DoesNotContain("private sealed partial class OriginFlowContext", usageScanSource); - Assert.DoesNotContain("private readonly struct FlowProbeState", usageScanSource); - Assert.DoesNotContain("private enum FlowEventKind", usageScanSource); - Assert.DoesNotContain("private void DiscoverStableAliases", usageScanSource); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("RegisterCompilationStartAction", analyzerSource); - Assert.Contains("var flowGraphCache = new System.Runtime.CompilerServices.ConditionalWeakTable<", analyzerSource); - Assert.Contains("var autoIncludeCache = new System.Collections.Concurrent.ConcurrentDictionary<", analyzerSource); - Assert.Contains("AnalyzeInvocation(", analyzerSource); - Assert.Contains("autoIncludeCache,", analyzerSource); - - var autoIncludeSource = File.ReadAllText(autoIncludePath); - Assert.Contains("private static void AddModelAutoIncludePrefixes", autoIncludeSource); - Assert.Contains("private static bool TryGetDirectAutoInclude", autoIncludeSource); - - var flowAnalysisSource = File.ReadAllText(flowAnalysisPath); - Assert.Contains("private static bool TryCollectOriginAwareNavigationAccesses", flowAnalysisSource); - Assert.Contains("private static bool TryGetFlowGraph", flowAnalysisSource); - Assert.Contains("ConditionalWeakTable flowGraphCache", flowAnalysisSource); - Assert.DoesNotContain("static readonly ConditionalWeakTable CreateProperties", analyzerSource); - - var diagnosticsSource = File.ReadAllText(diagnosticsPath); - Assert.Contains("private static readonly LocalizableString Title", diagnosticsSource); - Assert.Contains("public static readonly DiagnosticDescriptor Rule", diagnosticsSource); - Assert.Contains("public static readonly DiagnosticDescriptor RedundantRule", diagnosticsSource); - Assert.Contains("private static ImmutableDictionary CreateProperties", diagnosticsSource); - Assert.DoesNotContain("private static void AnalyzeInvocation", diagnosticsSource); - } - - [Fact] - public void LC005_LocalInitializerFlow_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC005_MultipleOrderBy"); - var analyzerPath = Path.Combine(analyzerDir, "MultipleOrderByAnalyzer.cs"); - var localFlowPath = Path.Combine(analyzerDir, "MultipleOrderByLocalInitializerFlow.cs"); - - Assert.True(File.Exists(localFlowPath), "LC005 local initializer and write tracking should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class MultipleOrderByAnalyzer", analyzerSource); - Assert.DoesNotContain("private bool TryGetSingleAssignmentLocalInitializer", analyzerSource); - Assert.DoesNotContain("private static bool HasWriteBeforeUse", analyzerSource); - Assert.DoesNotContain("private static bool IsWriteToLocal", analyzerSource); - - var localFlowSource = File.ReadAllText(localFlowPath); - Assert.Contains("private bool TryGetSingleAssignmentLocalInitializer", localFlowSource); - Assert.Contains("private static bool HasWriteBeforeUse", localFlowSource); - Assert.Contains("private static bool IsWriteToLocal", localFlowSource); - } - - [Fact] - public void LC006_IncludeChainResultModel_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC006_CartesianExplosion"); - var analyzerPath = Path.Combine(analyzerDir, "CartesianExplosionAnalyzer.cs"); - var chainResultPath = Path.Combine(analyzerDir, "CartesianExplosionIncludeChainResult.cs"); - - Assert.True(File.Exists(chainResultPath), "LC006 include-chain result model should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private enum QuerySplittingMode", analyzerSource); - Assert.DoesNotContain("private sealed class IncludeChainAnalysis", analyzerSource); - - var chainResultSource = File.ReadAllText(chainResultPath); - Assert.Contains("private enum QuerySplittingMode", chainResultSource); - Assert.Contains("private sealed class IncludeChainAnalysis", chainResultSource); - Assert.Contains("public bool TryGetRiskySiblingCollections", chainResultSource); - } - - [Fact] - public void LC024_GroupAccessTranslation_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC024_GroupByNonTranslatable"); - var analyzerPath = Path.Combine(analyzerDir, "GroupByNonTranslatableAnalyzer.cs"); - var groupAccessPath = Path.Combine(analyzerDir, "GroupByNonTranslatableGroupAccess.cs"); - - Assert.True(File.Exists(groupAccessPath), "LC024 group-chain translatability rules should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class GroupByNonTranslatableAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool IsTranslatableGroupAccess", analyzerSource); - Assert.DoesNotContain("private static IInvocationOperation FindOutermostGroupChainInvocation", analyzerSource); - - var groupAccessSource = File.ReadAllText(groupAccessPath); - Assert.Contains("private static bool IsTranslatableGroupAccess", groupAccessSource); - Assert.Contains("private static IInvocationOperation FindOutermostGroupChainInvocation", groupAccessSource); - } - - [Fact] - public void LC024_GroupingTypeAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC024_GroupByNonTranslatable"); - var analyzerPath = Path.Combine(analyzerDir, "GroupByNonTranslatableAnalyzer.cs"); - var groupingTypesPath = Path.Combine(analyzerDir, "GroupByNonTranslatableGroupingTypes.cs"); - - Assert.True(File.Exists(groupingTypesPath), "LC024 grouping IQueryable/IGrouping type checks should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static bool IsGroupingQueryable", analyzerSource); - Assert.DoesNotContain("private static ITypeSymbol? GetQueryableElementType", analyzerSource); - Assert.DoesNotContain("private static bool IsGrouping(ITypeSymbol type)", analyzerSource); - - var groupingTypesSource = File.ReadAllText(groupingTypesPath); - Assert.Contains("private static bool IsGroupingQueryable", groupingTypesSource); - Assert.Contains("private static ITypeSymbol? GetQueryableElementType", groupingTypesSource); - Assert.Contains("private static bool IsGrouping(ITypeSymbol type)", groupingTypesSource); - } - - [Fact] - public void LC037_StringBuilderAliasIdentity_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var detectionPath = Path.Combine(analyzerDir, "RawSqlStringConstructionDetection.cs"); - var aliasPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderAliases.cs"); - var identityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderIdentity.cs"); - - Assert.True(File.Exists(aliasPath), "LC037 StringBuilder alias matching should live in a focused partial file."); - Assert.True(File.Exists(identityPath), "LC037 StringBuilder alias identity resolution should live in a focused partial file."); - - var detectionSource = File.ReadAllText(detectionPath); - Assert.DoesNotContain("private readonly struct LocalIdentity", detectionSource); - - var aliasSource = File.ReadAllText(aliasPath); - Assert.DoesNotContain("private readonly struct LocalIdentity", aliasSource); - Assert.DoesNotContain("private static LocalIdentity ResolveLocalIdentity", aliasSource); - - var identitySource = File.ReadAllText(identityPath); - Assert.Contains("private readonly struct LocalIdentity", identitySource); - Assert.Contains("private static LocalIdentity ResolveLocalIdentity", identitySource); - } - - [Fact] - public void LC037_StringBuilderAliasGuaranteedWrites_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var aliasPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderAliases.cs"); - var guaranteedWritesPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderAliasWrites.cs"); - - Assert.True(File.Exists(guaranteedWritesPath), "LC037 StringBuilder alias guaranteed-write resolution should live in a focused partial file."); - - var aliasSource = File.ReadAllText(aliasPath); - Assert.DoesNotContain("private static bool HasNonGuaranteedWriteAfterLatestGuaranteed", aliasSource); - Assert.DoesNotContain("private static bool TryResolveGuaranteedLocalValue", aliasSource); - - var guaranteedWritesSource = File.ReadAllText(guaranteedWritesPath); - Assert.Contains("private static bool HasNonGuaranteedWriteAfterLatestGuaranteed", guaranteedWritesSource); - Assert.Contains("private static bool TryResolveGuaranteedLocalValue", guaranteedWritesSource); - } - - [Fact] - public void LC037_StringBuilderAppendFlow_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var detectionPath = Path.Combine(analyzerDir, "RawSqlStringConstructionDetection.cs"); - var flowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderFlow.cs"); - - Assert.True(File.Exists(flowPath), "LC037 StringBuilder append/reset flow should live in a focused partial file."); - - var detectionSource = File.ReadAllText(detectionPath); - Assert.DoesNotContain("private static bool IsStringBuilderAppendArgumentNonConstant", detectionSource); - - var flowSource = File.ReadAllText(flowPath); - Assert.Contains("private static bool IsStringBuilderAppendArgumentNonConstant", flowSource); - } - - [Fact] - public void LC037_StringBuilderLocalWriteFlow_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var flowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderFlow.cs"); - var localWritesPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderLocalWrites.cs"); - var latestWritesPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderLatestLocalWrite.cs"); - - Assert.True(File.Exists(localWritesPath), "LC037 StringBuilder local-write flow should live in a focused partial file."); - Assert.True(File.Exists(latestWritesPath), "LC037 StringBuilder latest local-write flow should live in a focused partial file."); - - var flowSource = File.ReadAllText(flowPath); - Assert.DoesNotContain("private static bool HasOnlyConstantLocalWritesBeforeReference", flowSource); - Assert.DoesNotContain("private static bool HasLatestNonConstantLocalWriteBeforeReference", flowSource); - Assert.DoesNotContain("private static IOperation GetCompoundAssignmentRightValue", flowSource); - - var localWritesSource = File.ReadAllText(localWritesPath); - Assert.Contains("private static bool HasOnlyConstantLocalWritesBeforeReference", localWritesSource); - Assert.DoesNotContain("private static bool HasLatestNonConstantLocalWriteBeforeReference", localWritesSource); - - var latestWritesSource = File.ReadAllText(latestWritesPath); - Assert.Contains("private static bool HasLatestNonConstantLocalWriteBeforeReference", latestWritesSource); - Assert.Contains("private static IOperation GetCompoundAssignmentRightValue", latestWritesSource); - } - - [Fact] - public void LC037_StringBuilderLoopFlow_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var flowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderFlow.cs"); - var loopFlowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderLoopFlow.cs"); - - Assert.True(File.Exists(loopFlowPath), "LC037 StringBuilder loop-carried local write flow should live in a focused partial file."); - - var flowSource = File.ReadAllText(flowPath); - Assert.DoesNotContain("private static bool HasNonConstantLoopCarriedLocalWrite", flowSource); - Assert.DoesNotContain("private static bool HasGuaranteedConstantLocalWriteInSameIterationBeforeReference", flowSource); - - var loopFlowSource = File.ReadAllText(loopFlowPath); - Assert.Contains("private static bool HasNonConstantLoopCarriedLocalWrite", loopFlowSource); - Assert.Contains("private static bool HasGuaranteedConstantLocalWriteInSameIterationBeforeReference", loopFlowSource); - } - - [Fact] - public void LC037_LoopCarriedReachability_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var reachabilityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionReachability.cs"); - var loopReachabilityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionLoopReachability.cs"); - - Assert.True(File.Exists(loopReachabilityPath), "LC037 loop-carried write reachability should live in a focused partial file."); - - var reachabilitySource = File.ReadAllText(reachabilityPath); - Assert.DoesNotContain("private static bool IsLoopCarriedWriteForReference", reachabilitySource); - Assert.DoesNotContain("private static bool IsLoopSyntax", reachabilitySource); - Assert.DoesNotContain("private static bool CanWriteReachLaterLoopIteration", reachabilitySource); - - var loopReachabilitySource = File.ReadAllText(loopReachabilityPath); - Assert.Contains("private static bool IsLoopCarriedWriteForReference", loopReachabilitySource); - Assert.Contains("private static bool IsLoopSyntax", loopReachabilitySource); - Assert.Contains("private static bool CanWriteReachLaterLoopIteration", loopReachabilitySource); - } - - [Fact] - public void LC037_StringBuilderAppendScan_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var flowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderFlow.cs"); - var appendScanPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderAppendScan.cs"); - - Assert.True(File.Exists(appendScanPath), "LC037 StringBuilder append scan should live in a focused partial file."); - - var flowSource = File.ReadAllText(flowPath); - Assert.DoesNotContain("private static bool ContainsSuspiciousStringBuilderAppend", flowSource); - Assert.DoesNotContain("private static int GetLatestGuaranteedStringBuilderReset", flowSource); - - var appendScanSource = File.ReadAllText(appendScanPath); - Assert.Contains("private static bool ContainsSuspiciousStringBuilderAppend", appendScanSource); - Assert.DoesNotContain("private static int GetLatestGuaranteedStringBuilderReset", appendScanSource); - } - - [Fact] - public void LC037_StringBuilderResetDetection_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var appendScanPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderAppendScan.cs"); - var resetDetectionPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderResetDetection.cs"); - - Assert.True(File.Exists(resetDetectionPath), "LC037 StringBuilder reset detection should live in a focused partial file."); - - var appendScanSource = File.ReadAllText(appendScanPath); - Assert.DoesNotContain("private static int GetLatestGuaranteedStringBuilderReset", appendScanSource); - Assert.DoesNotContain("void TrackReset", appendScanSource); - - var resetDetectionSource = File.ReadAllText(resetDetectionPath); - Assert.Contains("private static int GetLatestGuaranteedStringBuilderReset", resetDetectionSource); - Assert.Contains("void TrackReset", resetDetectionSource); - } - - [Fact] - public void LC011_AssemblyConfigurationResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var configurationScanPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyConfigurationScan.cs"); - var assemblyResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyResolution.cs"); - var assemblyLocalsPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyLocalResolution.cs"); - var assemblyMembersPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyMemberResolution.cs"); - - Assert.True(File.Exists(assemblyResolutionPath), "LC011 ApplyConfigurationsFromAssembly resolution should live in a focused partial file."); - Assert.True(File.Exists(assemblyLocalsPath), "LC011 ApplyConfigurationsFromAssembly local resolution should live in a focused partial file."); - Assert.True(File.Exists(assemblyMembersPath), "LC011 ApplyConfigurationsFromAssembly member resolution should live in a focused partial file."); - - var configurationScanSource = File.ReadAllText(configurationScanPath); - Assert.DoesNotContain("private static bool IsCurrentAssemblyExpression", configurationScanSource); - Assert.DoesNotContain("private static bool TryResolveLocalCurrentAssembly", configurationScanSource); - - var assemblyResolutionSource = File.ReadAllText(assemblyResolutionPath); - Assert.Contains("private static bool IsCurrentAssemblyExpression", assemblyResolutionSource); - Assert.DoesNotContain("private static bool TryResolveLocalCurrentAssembly", assemblyResolutionSource); - - var assemblyLocalsSource = File.ReadAllText(assemblyLocalsPath); - Assert.Contains("private static bool TryResolveLocalCurrentAssembly", assemblyLocalsSource); - Assert.DoesNotContain("private static bool TryResolveMemberCurrentAssembly", assemblyLocalsSource); - - var assemblyMembersSource = File.ReadAllText(assemblyMembersPath); - Assert.Contains("private static bool TryResolveMemberCurrentAssembly", assemblyMembersSource); - } - - [Fact] - public void LC011_AssemblyLocalAssignmentScan_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var assemblyLocalsPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyLocalResolution.cs"); - var assignmentScanPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyLocalAssignments.cs"); - - Assert.True(File.Exists(assignmentScanPath), "LC011 ApplyConfigurationsFromAssembly local assignment scanning should live in a focused partial file."); - - var assemblyLocalsSource = File.ReadAllText(assemblyLocalsPath); - Assert.DoesNotContain("private static bool TryGetLocalAssignment", assemblyLocalsSource); - Assert.DoesNotContain("private static bool ContainsLocalAssignment", assemblyLocalsSource); - - var assignmentScanSource = File.ReadAllText(assignmentScanPath); - Assert.Contains("private static bool TryGetLocalAssignment", assignmentScanSource); - Assert.Contains("private static bool ContainsLocalAssignment", assignmentScanSource); - } - - [Fact] - public void LC011_AppliedConfigurationResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var configurationScanPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyConfigurationScan.cs"); - var appliedConfigurationPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAppliedConfigurationResolution.cs"); - - Assert.True(File.Exists(appliedConfigurationPath), "LC011 ApplyConfiguration argument/member/local resolution should live in a focused partial file."); - - var configurationScanSource = File.ReadAllText(configurationScanPath); - Assert.DoesNotContain("private static INamedTypeSymbol? ResolveConfigurationType", configurationScanSource); - Assert.DoesNotContain("private static bool TryResolveLocalConfiguration", configurationScanSource); - - var appliedConfigurationSource = File.ReadAllText(appliedConfigurationPath); - Assert.Contains("private static INamedTypeSymbol? ResolveConfigurationType", appliedConfigurationSource); - Assert.Contains("private static bool TryResolveLocalConfiguration", appliedConfigurationSource); - } - - [Fact] - public void LC011_EntityTypeConfigurationScan_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var configurationScanPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyConfigurationScan.cs"); - var entityTypeConfigurationPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyEntityTypeConfigurationScan.cs"); - - Assert.True(File.Exists(entityTypeConfigurationPath), "LC011 IEntityTypeConfiguration scanning should live in a focused partial file."); - - var configurationScanSource = File.ReadAllText(configurationScanPath); - Assert.DoesNotContain("private static EntityTypeConfigurationScan BuildEntityTypeConfigurationScan", configurationScanSource); - Assert.DoesNotContain("private static (bool hasKey, bool hasNoKey) CheckConfigureMethod", configurationScanSource); - Assert.DoesNotContain("private static bool TryGetConfiguredEntityType", configurationScanSource); - - var entityTypeConfigurationSource = File.ReadAllText(entityTypeConfigurationPath); - Assert.Contains("private static EntityTypeConfigurationScan BuildEntityTypeConfigurationScan", entityTypeConfigurationSource); - Assert.Contains("private static (bool hasKey, bool hasNoKey) CheckConfigureMethod", entityTypeConfigurationSource); - Assert.Contains("private static bool TryGetConfiguredEntityType", entityTypeConfigurationSource); - } - - [Fact] - public void LC011_CompilationModel_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var typeLookupPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyTypeLookup.cs"); - var compilationModelPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyCompilationModel.cs"); - - Assert.True(File.Exists(compilationModelPath), "LC011 compilation model and type index should live in a focused partial file."); - - var typeLookupSource = File.ReadAllText(typeLookupPath); - Assert.DoesNotContain("private sealed class CompilationModel", typeLookupSource); - Assert.DoesNotContain("private sealed class TypeIndex", typeLookupSource); - Assert.DoesNotContain("private sealed class EntityTypeConfigurationScan", typeLookupSource); - - var compilationModelSource = File.ReadAllText(compilationModelPath); - Assert.Contains("private sealed class CompilationModel", compilationModelSource); - Assert.Contains("private sealed class TypeIndex", compilationModelSource); - Assert.Contains("private sealed class EntityTypeConfigurationScan", compilationModelSource); - } - - [Fact] - public void LC011_PrimaryKeyAttributeRules_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var keyRulesPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyKeyRules.cs"); - var primaryKeyAttributePath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyPrimaryKeyAttributeRules.cs"); - - Assert.True(File.Exists(primaryKeyAttributePath), "LC011 [PrimaryKey] attribute parsing should live in a focused partial file."); - - var keyRulesSource = File.ReadAllText(keyRulesPath); - Assert.DoesNotContain("private bool HasValidPrimaryKeyAttribute", keyRulesSource); - Assert.DoesNotContain("private static List GetPrimaryKeyPropertyNames", keyRulesSource); - - var primaryKeyAttributeSource = File.ReadAllText(primaryKeyAttributePath); - Assert.Contains("private bool HasValidPrimaryKeyAttribute", primaryKeyAttributeSource); - Assert.Contains("private static List GetPrimaryKeyPropertyNames", primaryKeyAttributeSource); - } - - [Fact] - public void LC011_AssemblyUsingVisibility_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var assemblyResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyResolution.cs"); - var usingVisibilityPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyUsingVisibility.cs"); - - Assert.True(File.Exists(usingVisibilityPath), "LC011 System.Reflection using and Assembly alias visibility should live in a focused partial file."); - - var assemblyResolutionSource = File.ReadAllText(assemblyResolutionPath); - Assert.DoesNotContain("private static bool HasSystemReflectionAssemblyAliasInScope", assemblyResolutionSource); - Assert.DoesNotContain("private static bool HasSystemReflectionUsing", assemblyResolutionSource); - - var usingVisibilitySource = File.ReadAllText(usingVisibilityPath); - Assert.Contains("private static bool HasSystemReflectionAssemblyAliasInScope", usingVisibilitySource); - Assert.Contains("private static bool HasSystemReflectionUsing", usingVisibilitySource); - } - - [Fact] - public void LC017_FixerAccessedProperties_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC017_WholeEntityProjection"); - var contextAnalysisPath = Path.Combine(analyzerDir, "WholeEntityProjectionFixerContextAnalysis.cs"); - var accessedPropertiesPath = Path.Combine(analyzerDir, "WholeEntityProjectionFixerAccessedProperties.cs"); - - Assert.True(File.Exists(accessedPropertiesPath), "LC017 fixer accessed-property scanning should live in a focused partial file."); - - var contextAnalysisSource = File.ReadAllText(contextAnalysisPath); - Assert.Contains("private static bool TryCreateProjectionFixContext", contextAnalysisSource); - Assert.DoesNotContain("private static HashSet FindAccessedProperties", contextAnalysisSource); - - var accessedPropertiesSource = File.ReadAllText(accessedPropertiesPath); - Assert.Contains("private static HashSet FindAccessedProperties", accessedPropertiesSource); - Assert.Contains("ForEachStatementSyntax", accessedPropertiesSource); - } - - [Fact] - public void LC011_AssemblyConfigurationTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC011_EntityMissingPrimaryKey"); - var edgeCasesPath = Path.Combine(testDir, "EntityMissingPrimaryKeyEdgeCasesTests.cs"); - var assemblyTestsPath = Path.Combine(testDir, "EntityMissingPrimaryKeyAssemblyConfigurationTests.cs"); - - Assert.True(File.Exists(assemblyTestsPath), "LC011 ApplyConfigurationsFromAssembly edge cases should live in a focused partial test file."); - - var edgeCasesSource = File.ReadAllText(edgeCasesPath); - Assert.Contains("public partial class EntityMissingPrimaryKeyEdgeCasesTests", edgeCasesSource); - Assert.DoesNotContain("TestCrime_ExternalApplyConfigurationsFromAssembly_ShouldNotApplyLocalConfig", edgeCasesSource); - Assert.DoesNotContain("TestInnocent_MemberExecutingAssemblyApplyConfigurationsFromAssembly_ShouldNotTrigger", edgeCasesSource); - - var assemblyTestsSource = File.ReadAllText(assemblyTestsPath); - Assert.Contains("public partial class EntityMissingPrimaryKeyEdgeCasesTests", assemblyTestsSource); - Assert.Contains("TestCrime_ExternalApplyConfigurationsFromAssembly_ShouldNotApplyLocalConfig", assemblyTestsSource); - Assert.Contains("TestInnocent_MemberExecutingAssemblyApplyConfigurationsFromAssembly_ShouldNotTrigger", assemblyTestsSource); - } - - [Fact] - public void LC011_BuilderResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var configurationScanPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyConfigurationScan.cs"); - var builderResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyBuilderResolution.cs"); - var localBuilderResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyLocalBuilderResolution.cs"); - - Assert.True(File.Exists(builderResolutionPath), "LC011 builder-expression and owned-type resolution should live in a focused partial file."); - Assert.True(File.Exists(localBuilderResolutionPath), "LC011 local builder backtracking should live in a focused partial file."); - - var configurationScanSource = File.ReadAllText(configurationScanPath); - Assert.DoesNotContain("private static bool TryResolveLocalBuilder", configurationScanSource); - - var builderResolutionSource = File.ReadAllText(builderResolutionPath); - Assert.Contains("private static bool TryResolveEntityTypeFromBuilderExpression", builderResolutionSource); - Assert.DoesNotContain("private static bool TryResolveLocalBuilder", builderResolutionSource); - - var localBuilderResolutionSource = File.ReadAllText(localBuilderResolutionPath); - Assert.Contains("private static bool TryResolveLocalBuilder", localBuilderResolutionSource); - Assert.Contains("LocalDeclarationStatementSyntax", localBuilderResolutionSource); - } - - [Fact] - public void LC011_OwnedBuilderNavigationResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var builderResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyBuilderResolution.cs"); - var ownedNavigationPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyOwnedBuilderNavigationResolution.cs"); - - Assert.True(File.Exists(ownedNavigationPath), "LC011 owned-entity builder navigation resolution should live in a focused partial file."); - - var builderResolutionSource = File.ReadAllText(builderResolutionPath); - Assert.DoesNotContain("private static bool TryGetOwnedEntityType", builderResolutionSource); - Assert.DoesNotContain("private static INamedTypeSymbol? TryGetCollectionElementType", builderResolutionSource); - - var ownedNavigationSource = File.ReadAllText(ownedNavigationPath); - Assert.Contains("private static bool TryGetOwnedEntityType", ownedNavigationSource); - Assert.Contains("private static INamedTypeSymbol? TryGetCollectionElementType", ownedNavigationSource); - } - - [Fact] - public void LC007_QueryProvenanceAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC007_NPlusOneLooper"); - var analysisPath = Path.Combine(analyzerDir, "NPlusOneLooperAnalysis.cs"); - var queryProvenancePath = Path.Combine(analyzerDir, "NPlusOneLooperQueryProvenance.cs"); - - Assert.True(File.Exists(queryProvenancePath), "LC007 query provenance analysis should live in a focused partial file."); - - var analysisSource = File.ReadAllText(analysisPath); - Assert.Contains("internal static partial class NPlusOneLooperAnalysis", analysisSource); - Assert.DoesNotContain("private static QueryProvenance AnalyzeQueryProvenance", analysisSource); - Assert.DoesNotContain("private enum QueryProvenanceKind", analysisSource); - - var queryProvenanceSource = File.ReadAllText(queryProvenancePath); - Assert.Contains("private static QueryProvenance AnalyzeQueryProvenance", queryProvenanceSource); - Assert.Contains("private enum QueryProvenanceKind", queryProvenanceSource); - } - - [Fact] - public void LC007_QueryProvenanceClassification_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC007_NPlusOneLooper"); - var queryProvenancePath = Path.Combine(analyzerDir, "NPlusOneLooperQueryProvenance.cs"); - var classificationPath = Path.Combine(analyzerDir, "NPlusOneLooperQueryProvenanceClassification.cs"); - - Assert.True(File.Exists(classificationPath), "LC007 query provenance source classification should live in a focused partial file."); - - var queryProvenanceSource = File.ReadAllText(queryProvenancePath); - Assert.Contains("private static QueryProvenance AnalyzeQueryProvenance", queryProvenanceSource); - Assert.Contains("private enum QueryProvenanceKind", queryProvenanceSource); - Assert.DoesNotContain("private static bool IsDbContextSetInvocation", queryProvenanceSource); - Assert.DoesNotContain("private static bool IsClientBoundaryInvocation", queryProvenanceSource); - Assert.DoesNotContain("private static bool IsNavigationQueryInvocation", queryProvenanceSource); - Assert.DoesNotContain("private static bool IsAsQueryableInvocation", queryProvenanceSource); - Assert.DoesNotContain("private static bool IsChangeTrackingNamespace", queryProvenanceSource); - - var classificationSource = File.ReadAllText(classificationPath); - Assert.Contains("private static bool IsDbContextSetInvocation", classificationSource); - Assert.Contains("private static bool IsClientBoundaryInvocation", classificationSource); - Assert.Contains("private static bool IsNavigationQueryInvocation", classificationSource); - Assert.Contains("private static bool IsAsQueryableInvocation", classificationSource); - Assert.Contains("private static bool IsChangeTrackingNamespace", classificationSource); - Assert.DoesNotContain("private static QueryProvenance AnalyzeQueryProvenance", classificationSource); - Assert.DoesNotContain("private enum QueryProvenanceKind", classificationSource); - } - - [Fact] - public void LC007_ExecutionMethodClassification_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC007_NPlusOneLooper"); - var analysisPath = Path.Combine(analyzerDir, "NPlusOneLooperAnalysis.cs"); - var executionMethodsPath = Path.Combine(analyzerDir, "NPlusOneLooperExecutionMethods.cs"); - - Assert.True(File.Exists(executionMethodsPath), "LC007 immediate execution and set-based method classification should live in a focused partial file."); - - var analysisSource = File.ReadAllText(analysisPath); - Assert.DoesNotContain("private static readonly HashSet ImmediateQueryExecutionMethods", analysisSource); - Assert.DoesNotContain("private static readonly HashSet SetBasedExecutorMethods", analysisSource); - - var executionMethodsSource = File.ReadAllText(executionMethodsPath); - Assert.Contains("private static readonly HashSet ImmediateQueryExecutionMethods", executionMethodsSource); - Assert.Contains("private static readonly HashSet SetBasedExecutorMethods", executionMethodsSource); - } - - [Fact] - public void LC007_DatabaseExecutionMatching_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC007_NPlusOneLooper"); - var analysisPath = Path.Combine(analyzerDir, "NPlusOneLooperAnalysis.cs"); - var databaseExecutionPath = Path.Combine(analyzerDir, "NPlusOneLooperDatabaseExecution.cs"); - - Assert.True(File.Exists(databaseExecutionPath), "LC007 database execution matching should live in a focused partial file."); - - var analysisSource = File.ReadAllText(analysisPath); - Assert.Contains("internal static partial class NPlusOneLooperAnalysis", analysisSource); - Assert.DoesNotContain("private static bool TryMatchDatabaseExecution", analysisSource); - Assert.DoesNotContain("public static bool HasStronglyTypedNavigationAccessor", analysisSource); - - var databaseExecutionSource = File.ReadAllText(databaseExecutionPath); - Assert.Contains("internal static partial class NPlusOneLooperAnalysis", databaseExecutionSource); - Assert.Contains("private static bool TryMatchDatabaseExecution", databaseExecutionSource); - Assert.Contains("public static bool HasStronglyTypedNavigationAccessor", databaseExecutionSource); - } - - [Fact] - public void AnalyzerPerformance_LC023Sources_LiveInDedicatedPartial() - { - var architectureDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Architecture"); - var performancePath = Path.Combine(architectureDir, "AnalyzerPerformanceTests.cs"); - var lc023SourcesPath = Path.Combine(architectureDir, "AnalyzerPerformanceLc023Sources.cs"); - - Assert.True(File.Exists(lc023SourcesPath), "LC023 performance source builders should live in a focused partial file."); - - var performanceSource = File.ReadAllText(performancePath); - Assert.Contains("public partial class AnalyzerPerformanceTests", performanceSource); - Assert.DoesNotContain("private static string GenerateLc023StressSource", performanceSource); - Assert.DoesNotContain("private static string[] GenerateLc023MultiTreeStressSources", performanceSource); - - var lc023Sources = File.ReadAllText(lc023SourcesPath); - Assert.Contains("public partial class AnalyzerPerformanceTests", lc023Sources); - Assert.Contains("private static string GenerateLc023StressSource", lc023Sources); - Assert.Contains("private static string[] GenerateLc023MultiTreeStressSources", lc023Sources); - } - - [Fact] - public void LC002_FixerInlineMaterializationHelpers_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC002_PrematureMaterialization"); - var fixerPath = Path.Combine(analyzerDir, "PrematureMaterializationFixer.cs"); - var inlineMaterializationPath = Path.Combine(analyzerDir, "PrematureMaterializationFixerInlineMaterialization.cs"); - - Assert.True(File.Exists(inlineMaterializationPath), "LC002 fixer inline materializer shape/safety helpers should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class PrematureMaterializationFixer", fixerSource); - Assert.DoesNotContain("private static bool IsInlineMaterializerReceiver", fixerSource); - Assert.DoesNotContain("private static bool TryGetInlineMaterializerParts", fixerSource); - Assert.DoesNotContain("private static bool IsInsideOuterMaterialization", fixerSource); - Assert.DoesNotContain("private static bool IsMaterializingConstructor", fixerSource); - - var inlineMaterializationSource = File.ReadAllText(inlineMaterializationPath); - Assert.Contains("private static bool IsInlineMaterializerReceiver", inlineMaterializationSource); - Assert.Contains("private static bool TryGetInlineMaterializerParts", inlineMaterializationSource); - Assert.Contains("private static bool IsInsideOuterMaterialization", inlineMaterializationSource); - Assert.Contains("private static bool IsMaterializingConstructor", inlineMaterializationSource); - } - - [Fact] - public void LC010_RetryGuardAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC010_SaveChangesInLoop"); - var analyzerPath = Path.Combine(analyzerDir, "SaveChangesInLoopAnalyzer.cs"); - var retryGuardPath = Path.Combine(analyzerDir, "SaveChangesInLoopRetryGuard.cs"); - - Assert.True(File.Exists(retryGuardPath), "LC010 catch-guarded retry suppression should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class SaveChangesInLoopAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool IsSaveInsideCatchGuardedRetryAttempt", analyzerSource); - Assert.DoesNotContain("private static TryStatementSyntax? FindTryStatementBetweenInvocationAndLoop", analyzerSource); - Assert.DoesNotContain("private static bool HasLoopExitAfterSave", analyzerSource); - - var retryGuardSource = File.ReadAllText(retryGuardPath); - Assert.Contains("private static bool IsSaveInsideCatchGuardedRetryAttempt", retryGuardSource); - Assert.Contains("private static TryStatementSyntax? FindTryStatementBetweenInvocationAndLoop", retryGuardSource); - Assert.Contains("private static bool HasLoopExitAfterSave", retryGuardSource); - } - - [Fact] - public void LC010_DelegateExecutionAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC010_SaveChangesInLoop"); - var analyzerPath = Path.Combine(analyzerDir, "SaveChangesInLoopAnalyzer.cs"); - var delegateExecutionPath = Path.Combine(analyzerDir, "SaveChangesInLoopDelegateExecution.cs"); - - Assert.True(File.Exists(delegateExecutionPath), "LC010 delegate-loop execution proof should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class SaveChangesInLoopAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool IsInsideDelegateCalledFromLoop", analyzerSource); - Assert.DoesNotContain("private static bool IsDelegateLocalCalledFromLoop", analyzerSource); - - var delegateExecutionSource = File.ReadAllText(delegateExecutionPath); - Assert.Contains("private static bool IsInsideDelegateCalledFromLoop", delegateExecutionSource); - Assert.Contains("private static bool IsDelegateLocalCalledFromLoop", delegateExecutionSource); - Assert.Contains("private static bool IsLocalAssignedInStraightLinePathBetween", delegateExecutionSource); - } - - [Fact] - public void LC010_FixerMoveSafety_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC010_SaveChangesInLoop"); - var fixerPath = Path.Combine(analyzerDir, "SaveChangesInLoopFixer.cs"); - var moveSafetyPath = Path.Combine(analyzerDir, "SaveChangesInLoopFixerMoveSafety.cs"); - - Assert.True(File.Exists(moveSafetyPath), "LC010 fixer move-safety predicates should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class SaveChangesInLoopFixer", fixerSource); - Assert.DoesNotContain("private static bool TryGetMovableSaveStatement", fixerSource); - Assert.DoesNotContain("private static bool ContainsUnsafeControlFlow", fixerSource); - Assert.DoesNotContain("private static bool IsSaveReceiverDeclaredInsideLoop", fixerSource); - - var moveSafetySource = File.ReadAllText(moveSafetyPath); - Assert.Contains("private static bool TryGetMovableSaveStatement", moveSafetySource); - Assert.Contains("private static bool ContainsUnsafeControlFlow", moveSafetySource); - Assert.Contains("private static bool IsSaveReceiverDeclaredInsideLoop", moveSafetySource); - } - - [Fact] - public void LC032_FixerExtensionNamespaceResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC032_ExecuteUpdateForBulkUpdates"); - var fixerPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesFixer.cs"); - var namespaceResolutionPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesFixerNamespaceResolution.cs"); - - Assert.True(File.Exists(namespaceResolutionPath), "LC032 fixer ExecuteUpdate extension namespace resolution should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.DoesNotContain("private static string? ResolveExecuteUpdateNamespace", fixerSource); - Assert.DoesNotContain("private static bool IsExecuteUpdateLikeMethod", fixerSource); - - var namespaceResolutionSource = File.ReadAllText(namespaceResolutionPath); - Assert.Contains("private static string? ResolveExecuteUpdateNamespace", namespaceResolutionSource); - Assert.Contains("private static bool IsExecuteUpdateLikeMethod", namespaceResolutionSource); - } - - [Fact] - public void LC016_FixerVariableNameGeneration_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC016_AvoidDateTimeNow"); - var fixerPath = Path.Combine(analyzerDir, "AvoidDateTimeNowFixer.cs"); - var variableNamesPath = Path.Combine(analyzerDir, "AvoidDateTimeNowFixerVariableNames.cs"); - - Assert.True(File.Exists(variableNamesPath), "LC016 fixer variable-name collision handling should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.DoesNotContain("private static string GetUniqueVariableName", fixerSource); - Assert.DoesNotContain("private static HashSet CollectExistingNames", fixerSource); - Assert.DoesNotContain("private static void AddEnclosingParameterNames", fixerSource); - Assert.DoesNotContain("private static void AddParameterNames", fixerSource); - - var variableNamesSource = File.ReadAllText(variableNamesPath); - Assert.Contains("private static string GetUniqueVariableName", variableNamesSource); - Assert.Contains("private static HashSet CollectExistingNames", variableNamesSource); - Assert.Contains("private static void AddEnclosingParameterNames", variableNamesSource); - Assert.Contains("private static void AddParameterNames", variableNamesSource); - } - - [Fact] - public void LC023_KeyConfigurationAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC023_FindInsteadOfFirstOrDefault"); - var keyAnalysisPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultKeyAnalysis.cs"); - var keyConfigurationPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultKeyConfiguration.cs"); - - Assert.True(File.Exists(keyConfigurationPath), "LC023 Fluent API key-configuration parsing should live in a focused partial file."); - - var keyAnalysisSource = File.ReadAllText(keyAnalysisPath); - Assert.DoesNotContain("private static bool TryGetEntityTypeBuilderEntity", keyAnalysisSource); - Assert.DoesNotContain("private static ConfiguredPrimaryKey AnalyzeKeyArgument", keyAnalysisSource); - Assert.DoesNotContain("private readonly struct ConfiguredPrimaryKey", keyAnalysisSource); - - var keyConfigurationSource = File.ReadAllText(keyConfigurationPath); - Assert.Contains("private static bool TryGetEntityTypeBuilderEntity", keyConfigurationSource); - Assert.Contains("private static ConfiguredPrimaryKey AnalyzeKeyArgument", keyConfigurationSource); - Assert.Contains("private readonly struct ConfiguredPrimaryKey", keyConfigurationSource); - } - - [Fact] - public void LC023_PredicateAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC023_FindInsteadOfFirstOrDefault"); - var analyzerPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultAnalyzer.cs"); - var predicateAnalysisPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultPredicateAnalysis.cs"); - - Assert.True(File.Exists(predicateAnalysisPath), "LC023 primary-key predicate analysis should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class FindInsteadOfFirstOrDefaultAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool TryGetPrimaryKeyEqualityProperty", analyzerSource); - Assert.DoesNotContain("private static bool TryGetLambdaParameterProperty", analyzerSource); - - var predicateAnalysisSource = File.ReadAllText(predicateAnalysisPath); - Assert.Contains("private static bool TryGetPrimaryKeyEqualityProperty", predicateAnalysisSource); - Assert.Contains("private static bool TryGetLambdaParameterProperty", predicateAnalysisSource); - } - - [Fact] - public void LC023_FixerKeyValueAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC023_FindInsteadOfFirstOrDefault"); - var contextPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultFixerContext.cs"); - var keyValueAnalysisPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultFixerKeyValueAnalysis.cs"); - - Assert.True(File.Exists(keyValueAnalysisPath), "LC023 fixer key-value analysis should live in a focused partial file."); - - var contextSource = File.ReadAllText(contextPath); - Assert.DoesNotContain("private static bool TryGetKeyValueExpression", contextSource); - Assert.DoesNotContain("private static bool ReferencesLambdaParameter", contextSource); - Assert.DoesNotContain("private static bool IsPrimaryKeyAccess", contextSource); - - var keyValueAnalysisSource = File.ReadAllText(keyValueAnalysisPath); - Assert.Contains("private static bool TryGetKeyValueExpression", keyValueAnalysisSource); - Assert.Contains("private static bool ReferencesLambdaParameter", keyValueAnalysisSource); - Assert.Contains("private static bool IsPrimaryKeyAccess", keyValueAnalysisSource); - } - - [Fact] - public void LC045_UsageTargetHelpers_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC045_MissingInclude"); - var usageAnalysisPath = Path.Combine(analyzerDir, "MissingIncludeUsageAnalysis.cs"); - var usageTargetsPath = Path.Combine(analyzerDir, "MissingIncludeUsageTargets.cs"); - var indexedAccessPath = Path.Combine(analyzerDir, "MissingIncludeIndexedAccess.cs"); - - Assert.True(File.Exists(usageTargetsPath), "LC045 usage target and result-local helpers should live in a focused partial file."); - Assert.True(File.Exists(indexedAccessPath), "LC045 indexed collection access classification should live in a focused partial file."); - - var usageAnalysisSource = File.ReadAllText(usageAnalysisPath); - Assert.DoesNotContain("private static bool IsWriteTarget", usageAnalysisSource); - Assert.DoesNotContain("private static bool IsIndexedAccessOf", usageAnalysisSource); - Assert.DoesNotContain("private static ILocalSymbol? FindVariableAssignment", usageAnalysisSource); - - var usageTargetsSource = File.ReadAllText(usageTargetsPath); - Assert.Contains("private static bool IsWriteTarget", usageTargetsSource); - Assert.DoesNotContain("private static bool IsIndexedAccessOf", usageTargetsSource); - Assert.Contains("private static ILocalSymbol? FindVariableAssignment", usageTargetsSource); - - var indexedAccessSource = File.ReadAllText(indexedAccessPath); - Assert.Contains("private static bool IsIndexedAccessOf", indexedAccessSource); - } - - [Fact] - public void LC045_NavigationAccessCollection_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC045_MissingInclude"); - var usageAnalysisPath = Path.Combine(analyzerDir, "MissingIncludeUsageAnalysis.cs"); - var accessCollectionPath = Path.Combine(analyzerDir, "MissingIncludeNavigationAccessCollection.cs"); - - Assert.True(File.Exists(accessCollectionPath), "LC045 navigation access collection and satisfaction helpers should live in a focused partial file."); - - var usageAnalysisSource = File.ReadAllText(usageAnalysisPath); - Assert.DoesNotContain("private readonly struct NavigationAccess", usageAnalysisSource); - Assert.DoesNotContain("private static bool IsSatisfied", usageAnalysisSource); - Assert.DoesNotContain("private static List CollectInlineAccesses", usageAnalysisSource); - - var accessCollectionSource = File.ReadAllText(accessCollectionPath); - Assert.Contains("private readonly struct NavigationAccess", accessCollectionSource); - Assert.Contains("private static bool IsSatisfied", accessCollectionSource); - Assert.Contains("private static List CollectInlineAccesses", accessCollectionSource); - } - - [Fact] - public void LC045_QueryOperatorClassification_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC045_MissingInclude"); - var queryAnalysisPath = Path.Combine(analyzerDir, "MissingIncludeQueryAnalysis.cs"); - var queryOperatorsPath = Path.Combine(analyzerDir, "MissingIncludeQueryOperators.cs"); - var querySemanticsPath = Path.Combine(analyzerDir, "MissingIncludeQuerySemantics.cs"); - - Assert.True(File.Exists(queryOperatorsPath), "LC045 query operator/materializer classification should live in a focused partial file."); - Assert.True(File.Exists(querySemanticsPath), "LC045 exact query symbol/source classification should live in a focused partial file."); - - var queryAnalysisSource = File.ReadAllText(queryAnalysisPath); - Assert.DoesNotContain("private static readonly ImmutableHashSet ShapePreservingQueryableOperators", queryAnalysisSource); - Assert.DoesNotContain("private static bool IsEntityMaterializer", queryAnalysisSource); - - var queryOperatorsSource = File.ReadAllText(queryOperatorsPath); - Assert.Contains("private static readonly ImmutableHashSet ShapePreservingQueryableOperators", queryOperatorsSource); - Assert.Contains("private static readonly ImmutableHashSet ShapePreservingEntityFrameworkOperators", queryOperatorsSource); - Assert.Contains("private static bool IsEntityMaterializer", queryOperatorsSource); - - var querySemanticsSource = File.ReadAllText(querySemanticsPath); - Assert.Contains("private static IOperation? GetQuerySource", querySemanticsSource); - Assert.Contains("private static bool IsExactShapePreservingQueryStep", querySemanticsSource); - } - - [Fact] - public void LC025_NoTrackingSourceResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC025_AsNoTrackingWithUpdate"); - var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateAnalyzer.cs"); - var sourceResolutionPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateSourceResolution.cs"); - - Assert.True(File.Exists(sourceResolutionPath), "LC025 local source resolution should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AsNoTrackingWithUpdateAnalyzer", analyzerSource); - Assert.DoesNotContain("private bool IsFromNoTrackingQuery", analyzerSource); - Assert.DoesNotContain("private readonly struct LocalOrigin", analyzerSource); - - var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); - Assert.Contains("private bool IsFromNoTrackingQuery", sourceResolutionSource); - Assert.DoesNotContain("private readonly struct LocalOrigin", sourceResolutionSource); - } - - [Fact] - public void LC025_EntryStateParsing_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC025_AsNoTrackingWithUpdate"); - var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateAnalyzer.cs"); - var entryStatePath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateEntryStateParsing.cs"); - - Assert.True(File.Exists(entryStatePath), "LC025 Entry(entity).State parsing should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static bool TryParseEntryStateWrite", analyzerSource); - Assert.DoesNotContain("private static bool TryGetEntityStateName", analyzerSource); - - var entryStateSource = File.ReadAllText(entryStatePath); - Assert.Contains("private static bool TryParseEntryStateWrite", entryStateSource); - Assert.Contains("private static bool TryGetEntityStateName", entryStateSource); - } - - [Fact] - public void LC014_QuerySourceResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC014_AvoidStringCaseConversion"); - var analyzerPath = Path.Combine(analyzerDir, "AvoidStringCaseConversionAnalyzer.cs"); - var querySourcePath = Path.Combine(analyzerDir, "AvoidStringCaseConversionQuerySource.cs"); - - Assert.True(File.Exists(querySourcePath), "LC014 EF query-source resolution should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AvoidStringCaseConversionAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool HasEntityFrameworkQuerySource", analyzerSource); - Assert.DoesNotContain("private static bool TryResolveLocalValue", analyzerSource); - - var querySource = File.ReadAllText(querySourcePath); - Assert.Contains("private static bool HasEntityFrameworkQuerySource", querySource); - Assert.Contains("private static bool TryResolveLocalValue", querySource); - } - - [Fact] - public void LC014_ReceiverDependency_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC014_AvoidStringCaseConversion"); - var analyzerPath = Path.Combine(analyzerDir, "AvoidStringCaseConversionAnalyzer.cs"); - var receiverDependencyPath = Path.Combine(analyzerDir, "AvoidStringCaseConversionReceiverDependency.cs"); - - Assert.True(File.Exists(receiverDependencyPath), "LC014 receiver dependency analysis should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AvoidStringCaseConversionAnalyzer", analyzerSource); - Assert.DoesNotContain("private bool ReceiverDependsOnParameter", analyzerSource); - - var receiverDependencySource = File.ReadAllText(receiverDependencyPath); - Assert.Contains("private bool ReceiverDependsOnParameter", receiverDependencySource); - } - - [Fact] - public void LC030_DependencyInjectionRegistration_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC030_DbContextInSingleton"); - var analyzerPath = Path.Combine(analyzerDir, "DbContextInSingletonAnalyzer.cs"); - var registrationPath = Path.Combine(analyzerDir, "DbContextInSingletonRegistrationAnalysis.cs"); - - Assert.True(File.Exists(registrationPath), "LC030 dependency-injection registration analysis should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class DbContextInSingletonAnalyzer", analyzerSource); - Assert.DoesNotContain("private static void AnalyzeAddSingleton", analyzerSource); - - var registrationSource = File.ReadAllText(registrationPath); - Assert.Contains("private static void AnalyzeAddSingleton", registrationSource); - Assert.Contains("private static void AnalyzeAddDbContext", registrationSource); - } - - [Fact] - public void LC030_RegistrationLifetimeArgumentRules_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC030_DbContextInSingleton"); - var registrationPath = Path.Combine(analyzerDir, "DbContextInSingletonRegistrationAnalysis.cs"); - var lifetimePath = Path.Combine(analyzerDir, "DbContextInSingletonRegistrationLifetime.cs"); - - Assert.True(File.Exists(lifetimePath), "LC030 AddDbContext singleton-lifetime argument parsing should live in a focused partial file."); - - var registrationSource = File.ReadAllText(registrationPath); - Assert.Contains("private static void AnalyzeAddDbContext", registrationSource); - Assert.DoesNotContain("private static bool HasSingletonContextLifetimeArgument", registrationSource); - Assert.DoesNotContain("private static bool IsSingletonLifetime", registrationSource); - Assert.DoesNotContain("private static IOperation UnwrapConversion", registrationSource); - - var lifetimeSource = File.ReadAllText(lifetimePath); - Assert.Contains("private static bool HasSingletonContextLifetimeArgument", lifetimeSource); - Assert.Contains("private static bool IsSingletonLifetime", lifetimeSource); - Assert.Contains("private static IOperation UnwrapConversion", lifetimeSource); - Assert.DoesNotContain("private static void AnalyzeAddDbContext", lifetimeSource); - } - - [Fact] - public void LC030_LongLivedEvidence_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC030_DbContextInSingleton"); - var analyzerPath = Path.Combine(analyzerDir, "DbContextInSingletonAnalyzer.cs"); - var evidencePath = Path.Combine(analyzerDir, "DbContextInSingletonLongLivedEvidence.cs"); - - Assert.True(File.Exists(evidencePath), "LC030 long-lived type evidence should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static void AddIntrinsicLongLivedEvidence", analyzerSource); - Assert.DoesNotContain("private static bool HasConventionalMiddlewareSignature", analyzerSource); - - var evidenceSource = File.ReadAllText(evidencePath); - Assert.Contains("private static void AddIntrinsicLongLivedEvidence", evidenceSource); - Assert.DoesNotContain("private static bool HasConventionalMiddlewareSignature", evidenceSource); - } - - [Fact] - public void LC030_LongLivedOptions_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC030_DbContextInSingleton"); - var evidencePath = Path.Combine(analyzerDir, "DbContextInSingletonLongLivedEvidence.cs"); - var optionsPath = Path.Combine(analyzerDir, "DbContextInSingletonLongLivedOptions.cs"); - - Assert.True(File.Exists(optionsPath), "LC030 long-lived options and configured type matching should live in a focused partial file."); - - var evidenceSource = File.ReadAllText(evidencePath); - Assert.DoesNotContain("private static Lc030Options GetOptions", evidenceSource); - Assert.DoesNotContain("private static bool TryGetConfiguredLongLivedReason", evidenceSource); - Assert.DoesNotContain("private sealed class Lc030Options", evidenceSource); - - var optionsSource = File.ReadAllText(optionsPath); - Assert.Contains("private static Lc030Options GetOptions", optionsSource); - Assert.Contains("private static bool TryGetConfiguredLongLivedReason", optionsSource); - Assert.Contains("private sealed class Lc030Options", optionsSource); - } - - [Fact] - public void LC030_FreshComputedPropertyAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC030_DbContextInSingleton"); - var analyzerPath = Path.Combine(analyzerDir, "DbContextInSingletonAnalyzer.cs"); - var freshPropertyPath = Path.Combine(analyzerDir, "DbContextInSingletonFreshComputedProperty.cs"); - - Assert.True(File.Exists(freshPropertyPath), "LC030 fresh computed DbContext property analysis should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static bool IsFreshComputedProperty", analyzerSource); - Assert.DoesNotContain("private static bool IsDbContextFactoryCreate", analyzerSource); - - var freshPropertySource = File.ReadAllText(freshPropertyPath); - Assert.Contains("private static bool IsFreshComputedProperty", freshPropertySource); - Assert.Contains("private static bool IsDbContextFactoryCreate", freshPropertySource); - } - - [Fact] - public void LC030_CandidateCollectionAndReporting_LiveInDedicatedPartials() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC030_DbContextInSingleton"); - var analyzerPath = Path.Combine(analyzerDir, "DbContextInSingletonAnalyzer.cs"); - var candidateCollectionPath = Path.Combine(analyzerDir, "DbContextInSingletonCandidateCollection.cs"); - var candidateReportingPath = Path.Combine(analyzerDir, "DbContextInSingletonCandidateReporting.cs"); - - Assert.True(File.Exists(candidateCollectionPath), "LC030 candidate field/property/constructor collection should live in a focused partial file."); - Assert.True(File.Exists(candidateReportingPath), "LC030 candidate collection and final diagnostic reporting should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static void AnalyzeField", analyzerSource); - Assert.DoesNotContain("private static void ReportCandidateDiagnostics", analyzerSource); - Assert.DoesNotContain("private sealed class DbContextCandidate", analyzerSource); - - var candidateCollectionSource = File.ReadAllText(candidateCollectionPath); - Assert.Contains("private static void AnalyzeField", candidateCollectionSource); - Assert.Contains("private static void AnalyzeProperty", candidateCollectionSource); - Assert.Contains("private static void AnalyzeConstructor", candidateCollectionSource); - Assert.Contains("private static void AddCandidate", candidateCollectionSource); - - var candidateReportingSource = File.ReadAllText(candidateReportingPath); - Assert.DoesNotContain("private static void AnalyzeField", candidateReportingSource); - Assert.DoesNotContain("private static void AnalyzeProperty", candidateReportingSource); - Assert.DoesNotContain("private static void AnalyzeConstructor", candidateReportingSource); - Assert.DoesNotContain("private static void AddCandidate", candidateReportingSource); - Assert.Contains("private static void ReportCandidateDiagnostics", candidateReportingSource); - Assert.Contains("private sealed class DbContextCandidate", candidateReportingSource); - } - - [Fact] - public void LC033_FixerSyntaxHelpers_LiveInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC033_UseFrozenSetForStaticMembershipCaches"); - var rewritePath = Path.Combine(fixerDir, "UseFrozenSetForStaticMembershipCachesFixerRewrite.cs"); - var syntaxPath = Path.Combine(fixerDir, "UseFrozenSetForStaticMembershipCachesFixerSyntax.cs"); - - Assert.True(File.Exists(syntaxPath), "LC033 FrozenSet fixer syntax construction helpers should live in a focused partial file."); - - var rewriteSource = File.ReadAllText(rewritePath); - Assert.DoesNotContain("private static ExpressionSyntax CreateToFrozenSetInvocation", rewriteSource); - Assert.DoesNotContain("private static TypeSyntax CreateTypeSyntax", rewriteSource); - Assert.DoesNotContain("private static ExpressionSyntax ParenthesizeIfNeeded", rewriteSource); - - var syntaxSource = File.ReadAllText(syntaxPath); - Assert.Contains("private static ExpressionSyntax CreateToFrozenSetInvocation", syntaxSource); - Assert.Contains("private static TypeSyntax CreateTypeSyntax", syntaxSource); - Assert.Contains("private static ExpressionSyntax ParenthesizeIfNeeded", syntaxSource); - } - - [Fact] - public void LC035_LocalConditionalFlow_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC035_MissingWhereBeforeExecuteDeleteUpdate"); - var analyzerPath = Path.Combine(analyzerDir, "MissingWhereBeforeExecuteDeleteUpdateAnalyzer.cs"); - var localFlowPath = Path.Combine(analyzerDir, "MissingWhereBeforeExecuteDeleteUpdateLocalFlow.cs"); - - Assert.True(File.Exists(localFlowPath), "LC035 local initializer and conditional assignment flow should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class MissingWhereBeforeExecuteDeleteUpdateAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool HasWhereInLocalInitializer", analyzerSource); - Assert.DoesNotContain("private static bool HasWhereInExhaustiveIfElseAssignments", analyzerSource); - Assert.DoesNotContain("private static bool IsControlFlowConditionalAssignment", analyzerSource); - - var localFlowSource = File.ReadAllText(localFlowPath); - Assert.Contains("private static bool HasWhereInLocalInitializer", localFlowSource); - Assert.Contains("private static bool HasWhereInExhaustiveIfElseAssignments", localFlowSource); - Assert.Contains("private static bool IsControlFlowConditionalAssignment", localFlowSource); - } - - [Fact] - public void LC027_RelationshipBuilderLocalResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC027_MissingExplicitForeignKey"); - var configurationPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyConfigurationAnalysis.cs"); - var relationshipLocalsPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyRelationshipLocals.cs"); - - Assert.True(File.Exists(relationshipLocalsPath), "LC027 relationship-builder local resolution should live in a focused partial file."); - - var configurationSource = File.ReadAllText(configurationPath); - Assert.DoesNotContain("private static List BuildRelationshipBuilderLocalMap", configurationSource); - Assert.DoesNotContain("private static bool IsShadowedByNestedLocal", configurationSource); - - var relationshipLocalsSource = File.ReadAllText(relationshipLocalsPath); - Assert.Contains("private static List BuildRelationshipBuilderLocalMap", relationshipLocalsSource); - Assert.Contains("private static bool TryResolveRelationshipBuilderLocal", relationshipLocalsSource); - } - - [Fact] - public void LC027_RelationshipBuilderLocalScope_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC027_MissingExplicitForeignKey"); - var relationshipLocalsPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyRelationshipLocals.cs"); - var relationshipLocalScopePath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyRelationshipLocalScope.cs"); - var relationshipLocalShadowingPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyRelationshipLocalShadowing.cs"); - - Assert.True(File.Exists(relationshipLocalScopePath), "LC027 relationship-builder local assignment/write-target logic should live in a focused partial file."); - Assert.True(File.Exists(relationshipLocalShadowingPath), "LC027 relationship-builder local shadowing logic should live in a focused partial file."); - - var relationshipLocalsSource = File.ReadAllText(relationshipLocalsPath); - Assert.DoesNotContain("private static bool HasSingleLocalAssignment", relationshipLocalsSource); - Assert.DoesNotContain("private static bool IsShadowedByNestedLocal", relationshipLocalsSource); - Assert.DoesNotContain("private static SyntaxNode? FindDesignationScope", relationshipLocalsSource); - Assert.DoesNotContain("private static bool IsVisibleAt", relationshipLocalsSource); - - var relationshipLocalScopeSource = File.ReadAllText(relationshipLocalScopePath); - Assert.Contains("private static bool HasSingleLocalAssignment", relationshipLocalScopeSource); - Assert.DoesNotContain("private static bool IsShadowedByNestedLocal", relationshipLocalScopeSource); - Assert.DoesNotContain("private static SyntaxNode? FindDesignationScope", relationshipLocalScopeSource); - Assert.Contains("private static bool IsVisibleAt", relationshipLocalScopeSource); - - var relationshipLocalShadowingSource = File.ReadAllText(relationshipLocalShadowingPath); - Assert.Contains("private static bool IsShadowedByNestedLocal", relationshipLocalShadowingSource); - Assert.Contains("private static SyntaxNode? FindDesignationScope", relationshipLocalShadowingSource); - } - - [Fact] - public void LC027_ConfigurationNameExtraction_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC027_MissingExplicitForeignKey"); - var configurationPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyConfigurationAnalysis.cs"); - var nameExtractionPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyConfigurationNameExtraction.cs"); - - Assert.True(File.Exists(nameExtractionPath), "LC027 configuration navigation/entity/owned-type extraction should live in a focused partial file."); - - var configurationSource = File.ReadAllText(configurationPath); - Assert.Contains("public sealed partial class MissingExplicitForeignKeyAnalyzer", configurationSource); - Assert.DoesNotContain("private static string? ExtractNavigationNameFromChain", configurationSource); - Assert.DoesNotContain("private static string? ExtractEntityTypeNameFromChain", configurationSource); - Assert.DoesNotContain("private static INamedTypeSymbol? ResolveOwnedTypeFromConfiguration", configurationSource); - - var nameExtractionSource = File.ReadAllText(nameExtractionPath); - Assert.Contains("private static string? ExtractNavigationNameFromChain", nameExtractionSource); - Assert.Contains("private static string? ExtractEntityTypeNameFromChain", nameExtractionSource); - Assert.Contains("private static INamedTypeSymbol? ResolveOwnedTypeFromConfiguration", nameExtractionSource); - } - - [Fact] - public void LC032_SaveChangesRewriteMode_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC032_ExecuteUpdateForBulkUpdates"); - var fixerPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixer.cs"); - var saveChangesModePath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerSaveChangesMode.cs"); - var asyncSupportPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerAsyncSupport.cs"); - - Assert.True(File.Exists(saveChangesModePath), "LC032 SaveChanges/async rewrite-mode logic should live in a focused partial file."); - Assert.True(File.Exists(asyncSupportPath), "LC032 ExecuteUpdateAsync capability detection should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class ExecuteUpdateForBulkUpdatesFixer", fixerSource); - Assert.DoesNotContain("private static bool TryClassifyTrailingSaveChanges", fixerSource); - Assert.DoesNotContain("private static bool HasExecuteUpdateAsyncTokenOverload", fixerSource); - - var saveChangesModeSource = File.ReadAllText(saveChangesModePath); - Assert.Contains("private static bool TryClassifyTrailingSaveChanges", saveChangesModeSource); - Assert.DoesNotContain("private static bool HasExecuteUpdateAsyncTokenOverload", saveChangesModeSource); - - var asyncSupportSource = File.ReadAllText(asyncSupportPath); - Assert.Contains("private static bool HasExecuteUpdateAsyncTokenOverload", asyncSupportSource); - } - - [Fact] - public void LC032_FixerRewriteApplication_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC032_ExecuteUpdateForBulkUpdates"); - var fixerPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixer.cs"); - var rewriteApplicationPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerRewriteApplication.cs"); - - Assert.True(File.Exists(rewriteApplicationPath), "LC032 ExecuteUpdate statement generation and document rewrite should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class ExecuteUpdateForBulkUpdatesFixer", fixerSource); - Assert.DoesNotContain("private const string WarningComment", fixerSource); - Assert.DoesNotContain("private static async Task ApplyFixAsync", fixerSource); - - var rewriteApplicationSource = File.ReadAllText(rewriteApplicationPath); - Assert.Contains("private const string WarningComment", rewriteApplicationSource); - Assert.Contains("private static async Task ApplyFixAsync", rewriteApplicationSource); - Assert.Contains("DocumentEditor.CreateAsync", rewriteApplicationSource); - Assert.Contains("SyntaxFactory.ParseStatement", rewriteApplicationSource); - } - - [Fact] - public void LC032_SetterExtraction_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC032_ExecuteUpdateForBulkUpdates"); - var fixerPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixer.cs"); - var setterExtractionPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerSetters.cs"); - - Assert.True(File.Exists(setterExtractionPath), "LC032 setter extraction and duplicate-target safety should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class ExecuteUpdateForBulkUpdatesFixer", fixerSource); - Assert.DoesNotContain("private static bool TryGetSetters", fixerSource); - Assert.DoesNotContain("private static bool ReadsAnyProperty", fixerSource); - - var setterExtractionSource = File.ReadAllText(setterExtractionPath); - Assert.Contains("private static bool TryGetSetters", setterExtractionSource); - Assert.Contains("private static bool ReadsAnyProperty", setterExtractionSource); - } - - [Fact] - public void LC032_ReceiverNormalization_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC032_ExecuteUpdateForBulkUpdates"); - var fixerPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixer.cs"); - var receiverPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerReceiver.cs"); - - Assert.True(File.Exists(receiverPath), "LC032 receiver materializer stripping and unsupported-step detection should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class ExecuteUpdateForBulkUpdatesFixer", fixerSource); - Assert.DoesNotContain("UnsupportedExecuteUpdateReceiverSteps", fixerSource); - Assert.DoesNotContain("private static ExpressionSyntax StripCollectionMaterializer", fixerSource); - Assert.DoesNotContain("private static bool HasUnsupportedExecuteUpdateReceiverStep", fixerSource); - Assert.DoesNotContain("private static bool IsStaticLinqTypeExpression", fixerSource); - - var receiverSource = File.ReadAllText(receiverPath); - Assert.Contains("UnsupportedExecuteUpdateReceiverSteps", receiverSource); - Assert.Contains("private static ExpressionSyntax StripCollectionMaterializer", receiverSource); - Assert.Contains("private static bool HasUnsupportedExecuteUpdateReceiverStep", receiverSource); - Assert.Contains("private static bool IsStaticLinqTypeExpression", receiverSource); - } - - [Fact] - public void LC012_RewriteSafetyAnalysis_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC012_OptimizeRemoveRange"); - var fixerPath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixer.cs"); - var rewriteSafetyPath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerRewriteSafety.cs"); - var querySourcePath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerQuerySourceContext.cs"); - - Assert.True(File.Exists(rewriteSafetyPath), "LC012 rewrite-safety analysis should live in a focused partial file."); - Assert.True(File.Exists(querySourcePath), "LC012 query-source context resolution should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class OptimizeRemoveRangeFixer", fixerSource); - Assert.DoesNotContain("private static async Task CanSafelyRewriteAsync", fixerSource); - Assert.DoesNotContain("private static bool TryResolveQuerySourceFreshContextLocal", fixerSource); - - var rewriteSafetySource = File.ReadAllText(rewriteSafetyPath); - Assert.Contains("private static async Task CanSafelyRewriteAsync", rewriteSafetySource); - Assert.DoesNotContain("private static bool TryResolveQuerySourceFreshContextLocal", rewriteSafetySource); - - var querySource = File.ReadAllText(querySourcePath); - Assert.Contains("private static bool TryResolveQuerySourceFreshContextLocal", querySource); - } - - [Fact] - public void LC012_RewriteModeSelection_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC012_OptimizeRemoveRange"); - var rewriteSafetyPath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerRewriteSafety.cs"); - var rewriteModePath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerRewriteMode.cs"); - - Assert.True(File.Exists(rewriteModePath), "LC012 async/sync ExecuteDelete rewrite mode selection should live in a focused partial file."); - - var rewriteSafetySource = File.ReadAllText(rewriteSafetyPath); - Assert.DoesNotContain("private static RewriteMode DetermineRewriteMode", rewriteSafetySource); - Assert.DoesNotContain("private static bool IsAsyncContext", rewriteSafetySource); - Assert.DoesNotContain("private static bool HasExecuteDeleteAsyncSupport", rewriteSafetySource); - - var rewriteModeSource = File.ReadAllText(rewriteModePath); - Assert.Contains("private static RewriteMode DetermineRewriteMode", rewriteModeSource); - Assert.Contains("private static bool IsAsyncContext", rewriteModeSource); - Assert.Contains("private static bool HasExecuteDeleteAsyncSupport", rewriteModeSource); - } - - [Fact] - public void LC012_SaveChangesSafetyAnalysis_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC012_OptimizeRemoveRange"); - var rewriteSafetyPath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerRewriteSafety.cs"); - var saveChangesSafetyPath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerSaveChangesSafety.cs"); - - Assert.True(File.Exists(saveChangesSafetyPath), "LC012 SaveChanges detection safety should live in a focused partial file."); - - var rewriteSafetySource = File.ReadAllText(rewriteSafetyPath); - Assert.DoesNotContain("private static bool HasSubsequentSaveChangesInvocation", rewriteSafetySource); - Assert.DoesNotContain("private static bool AreMutuallyExclusiveBranches", rewriteSafetySource); - - var saveChangesSafetySource = File.ReadAllText(saveChangesSafetyPath); - Assert.Contains("private static bool HasSubsequentSaveChangesInvocation", saveChangesSafetySource); - Assert.DoesNotContain("private static bool AreMutuallyExclusiveBranches", saveChangesSafetySource); - } - - [Fact] - public void LC012_AnalyzerSaveChangesSafety_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC012_OptimizeRemoveRange"); - var analyzerPath = Path.Combine(analyzerDir, "OptimizeRemoveRangeAnalyzer.cs"); - var saveChangesSafetyPath = Path.Combine(analyzerDir, "OptimizeRemoveRangeAnalyzerSaveChangesSafety.cs"); - - Assert.True(File.Exists(saveChangesSafetyPath), "LC012 analyzer SaveChanges detection and context-aliasing safety should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class OptimizeRemoveRangeAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool HasSubsequentSaveChangesInvocation", analyzerSource); - Assert.DoesNotContain("private static bool TryResolveFreshContextLocal", analyzerSource); - - var saveChangesSafetySource = File.ReadAllText(saveChangesSafetyPath); - Assert.Contains("private static bool HasSubsequentSaveChangesInvocation", saveChangesSafetySource); - Assert.Contains("private static bool TryResolveFreshContextLocal", saveChangesSafetySource); - } - - [Fact] - public void LC012_AnalyzerBranchExclusion_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC012_OptimizeRemoveRange"); - var saveChangesSafetyPath = Path.Combine(analyzerDir, "OptimizeRemoveRangeAnalyzerSaveChangesSafety.cs"); - var branchExclusionPath = Path.Combine(analyzerDir, "OptimizeRemoveRangeAnalyzerBranchExclusion.cs"); - - Assert.True(File.Exists(branchExclusionPath), "LC012 analyzer if/switch mutual-exclusion checks should live in a focused partial file."); - - var saveChangesSafetySource = File.ReadAllText(saveChangesSafetyPath); - Assert.Contains("private static bool HasSubsequentSaveChangesInvocation", saveChangesSafetySource); - Assert.DoesNotContain("private static bool AreMutuallyExclusiveBranches", saveChangesSafetySource); - Assert.DoesNotContain("private static SyntaxNode? GetContainingIfBranch", saveChangesSafetySource); - Assert.DoesNotContain("private static SwitchSectionSyntax? GetContainingSwitchSection", saveChangesSafetySource); - - var branchExclusionSource = File.ReadAllText(branchExclusionPath); - Assert.Contains("private static bool AreMutuallyExclusiveBranches", branchExclusionSource); - Assert.Contains("private static SyntaxNode? GetContainingIfBranch", branchExclusionSource); - Assert.Contains("private static SwitchSectionSyntax? GetContainingSwitchSection", branchExclusionSource); - Assert.DoesNotContain("private static bool HasSubsequentSaveChangesInvocation", branchExclusionSource); - } - - [Fact] - public void LC013_AssignedOriginResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC013_DisposedContextQuery"); - var originPath = Path.Combine(analyzerDir, "DisposedContextQueryOriginResolution.cs"); - var assignedOriginPath = Path.Combine(analyzerDir, "DisposedContextQueryAssignedOriginResolution.cs"); - - Assert.True(File.Exists(assignedOriginPath), "LC013 assigned local origin resolution should live in a focused partial file."); - - var originSource = File.ReadAllText(originPath); - Assert.Contains("public sealed partial class DisposedContextQueryAnalyzer", originSource); - Assert.DoesNotContain("private static bool TryResolveAssignedDisposedContextOrigin", originSource); - Assert.DoesNotContain("private static bool TryGetSharedAssignedDisposedContextOrigin", originSource); - Assert.DoesNotContain("private static List GetAssignedValues", originSource); - Assert.DoesNotContain("private static IEnumerable EnumerateOperations", originSource); - - var assignedOriginSource = File.ReadAllText(assignedOriginPath); - Assert.Contains("private static bool TryResolveAssignedDisposedContextOrigin", assignedOriginSource); - Assert.Contains("private static bool TryGetSharedAssignedDisposedContextOrigin", assignedOriginSource); - Assert.DoesNotContain("private static List GetAssignedValues", assignedOriginSource); - Assert.DoesNotContain("private static IEnumerable EnumerateOperations", assignedOriginSource); - } - - [Fact] - public void LC016_ExpressionBodyFixer_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC016_AvoidDateTimeNow"); - var fixerPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixer.cs"); - var expressionBodyPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixerExpressionBody.cs"); - - Assert.True(File.Exists(expressionBodyPath), "LC016 expression-bodied member rewrite logic should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class AvoidDateTimeNowFixer", fixerSource); - Assert.DoesNotContain("private static Document ConvertExpressionBodiedMember", fixerSource); - Assert.DoesNotContain("private static IReadOnlyList BuildExpressionBodyReplacements", fixerSource); - - var expressionBodySource = File.ReadAllText(expressionBodyPath); - Assert.Contains("private static Document ConvertExpressionBodiedMember", expressionBodySource); - Assert.DoesNotContain("private static IReadOnlyList BuildExpressionBodyReplacements", expressionBodySource); - } - - [Fact] - public void LC016_ExpressionBodyStatements_LiveInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC016_AvoidDateTimeNow"); - var expressionBodyPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixerExpressionBody.cs"); - var statementPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixerExpressionBodyStatements.cs"); - - Assert.True(File.Exists(statementPath), "LC016 expression-bodied return/expression-statement selection should live in a focused partial file."); - - var expressionBodySource = File.ReadAllText(expressionBodyPath); - Assert.DoesNotContain("private static StatementSyntax CreateExpressionBodyStatement", expressionBodySource); - Assert.DoesNotContain("private static bool RequiresExpressionStatement", expressionBodySource); - Assert.DoesNotContain("private static bool IsNonGenericTaskLike", expressionBodySource); - - var statementSource = File.ReadAllText(statementPath); - Assert.Contains("private static StatementSyntax CreateExpressionBodyStatement", statementSource); - Assert.Contains("private static bool RequiresExpressionStatement", statementSource); - Assert.Contains("private static bool IsNonGenericTaskLike", statementSource); - } - - [Fact] - public void RuleCatalog_LC016ToLC030Entries_LiveInDedicatedPartial() - { - var catalogDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Catalog"); - var catalogPath = Path.Combine(catalogDir, "RuleCatalog.cs"); - var lc016ToLc030Path = Path.Combine(catalogDir, "RuleCatalog.LC016ToLC030.cs"); - - Assert.True(File.Exists(lc016ToLc030Path), "LC016-LC030 catalog entries should live in a focused partial file."); - - var catalogSource = File.ReadAllText(catalogPath); - Assert.Contains("public static partial class RuleCatalog", catalogSource); - Assert.DoesNotContain("id: \"LC016\"", catalogSource); - Assert.DoesNotContain("id: \"LC030\"", catalogSource); - - var lc016ToLc030Source = File.ReadAllText(lc016ToLc030Path); - Assert.Contains("private static ImmutableArray CreateLC016ToLC030Entries", lc016ToLc030Source); - Assert.Contains("id: \"LC016\"", lc016ToLc030Source); - Assert.Contains("id: \"LC030\"", lc016ToLc030Source); - } - - [Fact] - public void RuleCatalog_LC031ToLC045Entries_LiveInDedicatedPartial() - { - var catalogDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Catalog"); - var catalogPath = Path.Combine(catalogDir, "RuleCatalog.cs"); - var lc031ToLc045Path = Path.Combine(catalogDir, "RuleCatalog.LC031ToLC045.cs"); - - Assert.True(File.Exists(lc031ToLc045Path), "LC031-LC045 catalog entries should live in a focused partial file."); - - var catalogSource = File.ReadAllText(catalogPath); - Assert.Contains("public static partial class RuleCatalog", catalogSource); - Assert.DoesNotContain("id: \"LC031\"", catalogSource); - Assert.DoesNotContain("id: \"LC045\"", catalogSource); - - var lc031ToLc045Source = File.ReadAllText(lc031ToLc045Path); - Assert.Contains("private static ImmutableArray CreateLC031ToLC045Entries", lc031ToLc045Source); - Assert.Contains("id: \"LC031\"", lc031ToLc045Source); - Assert.Contains("id: \"LC045\"", lc031ToLc045Source); - } - - [Fact] - public void LC044_ReachabilityAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC044_AsNoTrackingThenModify"); - var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyAnalyzer.cs"); - var reachabilityPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyReachability.cs"); - var terminatorsPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyReachabilityTerminators.cs"); - - Assert.True(File.Exists(reachabilityPath), "LC044 reachability/control-flow analysis should live in a focused partial file."); - Assert.True(File.Exists(terminatorsPath), "LC044 reachability terminator and branch-blocking rules should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AsNoTrackingThenModifyAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool BlockReaches", analyzerSource); - Assert.DoesNotContain("private static bool HasTerminatorBetween", analyzerSource); - - var reachabilitySource = File.ReadAllText(reachabilityPath); - Assert.Contains("private static bool BlockReaches", reachabilitySource); - Assert.DoesNotContain("private static bool HasTerminatorBetween", reachabilitySource); - - var terminatorsSource = File.ReadAllText(terminatorsPath); - Assert.Contains("private static bool HasTerminatorBetween", terminatorsSource); - Assert.Contains("private static bool IsBreakBlocking", terminatorsSource); - Assert.Contains("private static bool IsContinueBlocking", terminatorsSource); - } - - [Fact] - public void LC044_TrackingStateAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC044_AsNoTrackingThenModify"); - var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyAnalyzer.cs"); - var trackingStatePath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyTrackingState.cs"); - - Assert.True(File.Exists(trackingStatePath), "LC044 reattach/save-state analysis should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AsNoTrackingThenModifyAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool HasDominatingPriorReattach", analyzerSource); - Assert.DoesNotContain("private static bool HasInterveningDetach", analyzerSource); - - var trackingStateSource = File.ReadAllText(trackingStatePath); - Assert.Contains("private static bool HasDominatingPriorReattach", trackingStateSource); - Assert.DoesNotContain("private static bool HasInterveningDetach", trackingStateSource); - } - - [Fact] - public void LC044_RootScanTrackingStateParsing_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC044_AsNoTrackingThenModify"); - var rootScanPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScan.cs"); - var parsingPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScanTrackingState.cs"); - - Assert.True(File.Exists(parsingPath), "LC044 root-scan reattach/detach parsing should live in a focused partial file."); - - var rootScanSource = File.ReadAllText(rootScanPath); - Assert.Contains("internal sealed partial class AsNoTrackingThenModifyRootScan", rootScanSource); - Assert.DoesNotContain("private static bool TryParseReattachInvocation", rootScanSource); - Assert.DoesNotContain("private static bool TryParseEntryStateAssignment", rootScanSource); - - var parsingSource = File.ReadAllText(parsingPath); - Assert.Contains("private static bool TryParseReattachInvocation", parsingSource); - Assert.Contains("private static bool TryParseEntryStateAssignment", parsingSource); - } - - [Fact] - public void LC044_RootScanBuckets_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC044_AsNoTrackingThenModify"); - var rootScanPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScan.cs"); - var bucketsPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScanBuckets.cs"); - - Assert.True(File.Exists(bucketsPath), "LC044 root-scan bucket/index helpers should live in a focused partial file."); - - var rootScanSource = File.ReadAllText(rootScanPath); - Assert.DoesNotContain("internal static bool TryGetSymbol", rootScanSource); - Assert.DoesNotContain("private static void AddMutation", rootScanSource); - Assert.DoesNotContain("private static void AddReattach", rootScanSource); - Assert.DoesNotContain("private static void AddDetach", rootScanSource); - - var bucketsSource = File.ReadAllText(bucketsPath); - Assert.Contains("internal static bool TryGetSymbol", bucketsSource); - Assert.Contains("private static void AddMutation", bucketsSource); - Assert.Contains("private static void AddReattach", bucketsSource); - Assert.Contains("private static void AddDetach", bucketsSource); - Assert.Contains("private static void AddToBucket", bucketsSource); - } - - [Fact] - public void LC045_ConditionalAccessAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC045_MissingInclude"); - var usagePath = Path.Combine(analyzerDir, "MissingIncludeUsageAnalysis.cs"); - var conditionalAccessPath = Path.Combine(analyzerDir, "MissingIncludeConditionalAccessAnalysis.cs"); - - Assert.True(File.Exists(conditionalAccessPath), "LC045 conditional-access path analysis should live in a focused partial file."); - - var usageSource = File.ReadAllText(usagePath); - Assert.DoesNotContain("private static IOperation? FindConditionalAccessEntryProperty", usageSource); - Assert.DoesNotContain("private static IOperation? ResolveConditionalAccessReceiver", usageSource); - - var conditionalAccessSource = File.ReadAllText(conditionalAccessPath); - Assert.Contains("private static IOperation? FindConditionalAccessEntryProperty", conditionalAccessSource); - Assert.Contains("private static IOperation? ResolveConditionalAccessReceiver", conditionalAccessSource); - } - - [Fact] - public void LC045_NavigationPathResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC045_MissingInclude"); - var usagePath = Path.Combine(analyzerDir, "MissingIncludeUsageAnalysis.cs"); - var pathResolutionPath = Path.Combine(analyzerDir, "MissingIncludeNavigationPathAnalysis.cs"); - - Assert.True(File.Exists(pathResolutionPath), "LC045 navigation-path resolution should live in a focused partial file."); - - var usageSource = File.ReadAllText(usagePath); - Assert.DoesNotContain("private static bool TryGetAccessPath", usageSource); - Assert.DoesNotContain("private static bool TryResolveNavigationTargetForPath", usageSource); - - var pathResolutionSource = File.ReadAllText(pathResolutionPath); - Assert.Contains("private static bool TryGetAccessPath", pathResolutionSource); - Assert.Contains("private static bool TryResolveNavigationTargetForPath", pathResolutionSource); - } - - [Fact] - public void LC037_LocalWriteReachability_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var detectionPath = Path.Combine(analyzerDir, "RawSqlStringConstructionDetection.cs"); - var reachabilityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionReachability.cs"); - - Assert.True(File.Exists(reachabilityPath), "LC037 local-write reachability analysis should live in a focused partial file."); - - var detectionSource = File.ReadAllText(detectionPath); - Assert.DoesNotContain("private static bool CanWriteReachLaterLoopIteration", detectionSource); - Assert.DoesNotContain("private static bool CanOperationReachReference", detectionSource); - - var reachabilitySource = File.ReadAllText(reachabilityPath); - Assert.DoesNotContain("private static bool CanWriteReachLaterLoopIteration", reachabilitySource); - Assert.Contains("private static bool CanOperationReachReference", reachabilitySource); - } - - [Fact] - public void LC037_ExceptionFlowReachability_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var reachabilityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionReachability.cs"); - var exceptionFlowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionExceptionFlow.cs"); - - Assert.True(File.Exists(exceptionFlowPath), "LC037 throw/catch reachability analysis should live in a focused partial file."); - - var reachabilitySource = File.ReadAllText(reachabilityPath); - Assert.DoesNotContain("private static bool ThrowCanContinueThroughCatch", reachabilitySource); - Assert.DoesNotContain("private static bool IsKnownExceptionBase", reachabilitySource); - - var exceptionFlowSource = File.ReadAllText(exceptionFlowPath); - Assert.Contains("private static bool ThrowCanContinueThroughCatch", exceptionFlowSource); - } - - [Fact] - public void LC037_ExceptionTypeResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var exceptionFlowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionExceptionFlow.cs"); - var exceptionTypesPath = Path.Combine(analyzerDir, "RawSqlStringConstructionExceptionTypes.cs"); - - Assert.True(File.Exists(exceptionTypesPath), "LC037 exception type-name and hierarchy matching should live in a focused partial file."); - - var exceptionFlowSource = File.ReadAllText(exceptionFlowPath); - Assert.DoesNotContain("private static string? GetSimpleTypeName", exceptionFlowSource); - Assert.DoesNotContain("private static string? GetResolvedSimpleTypeName", exceptionFlowSource); - Assert.DoesNotContain("private static bool HasLocalExceptionBase", exceptionFlowSource); - Assert.DoesNotContain("private static bool IsKnownExceptionBase", exceptionFlowSource); - - var exceptionTypesSource = File.ReadAllText(exceptionTypesPath); - Assert.Contains("private static string? GetSimpleTypeName", exceptionTypesSource); - Assert.Contains("private static string? GetResolvedSimpleTypeName", exceptionTypesSource); - Assert.Contains("private static bool HasLocalExceptionBase", exceptionTypesSource); - Assert.Contains("private static bool IsKnownExceptionBase", exceptionTypesSource); - } - - [Fact] - public void LC037_TerminationFlowReachability_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var reachabilityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionReachability.cs"); - var terminationFlowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionTerminationFlow.cs"); - - Assert.True(File.Exists(terminationFlowPath), "LC037 branch, jump, and terminator reachability should live in a focused partial file."); - - var reachabilitySource = File.ReadAllText(reachabilityPath); - Assert.DoesNotContain("private static bool BlockTerminatesAfterNode", reachabilitySource); - Assert.DoesNotContain("private static bool StatementTerminates", reachabilitySource); - - var terminationFlowSource = File.ReadAllText(terminationFlowPath); - Assert.Contains("private static bool BlockTerminatesAfterNode", terminationFlowSource); - Assert.Contains("private static bool StatementTerminates", terminationFlowSource); - } - - [Fact] - public void LC037_StringBuilderFlowTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC037_RawSqlStringConstruction"); - var generalTestsPath = Path.Combine(testDir, "RawSqlStringConstructionTests.cs"); - var stringBuilderTestsPath = Path.Combine(testDir, "RawSqlStringConstructionStringBuilderTests.cs"); - - Assert.True(File.Exists(stringBuilderTestsPath), "LC037 StringBuilder flow tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class RawSqlStringConstructionTests", generalTestsSource); - Assert.DoesNotContain("ExecuteSqlRaw_WithStringBuilderAliasAndLaterAppend_ShouldTrigger", generalTestsSource); - Assert.DoesNotContain("ExecuteSqlRaw_WithStringBuilderInitializerAppendsClearedBeforeCall_ShouldNotTrigger", generalTestsSource); - - var stringBuilderTestsSource = File.ReadAllText(stringBuilderTestsPath); - Assert.Contains("public partial class RawSqlStringConstructionTests", stringBuilderTestsSource); - Assert.Contains("ExecuteSqlRaw_WithStringBuilderAliasAndLaterAppend_ShouldTrigger", stringBuilderTestsSource); - Assert.Contains("ExecuteSqlRaw_WithStringBuilderInitializerAppendsClearedBeforeCall_ShouldNotTrigger", stringBuilderTestsSource); - } - - [Fact] - public void LC037_StringBuilderReachabilityTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC037_RawSqlStringConstruction"); - var generalTestsPath = Path.Combine(testDir, "RawSqlStringConstructionTests.cs"); - var reachabilityTestsPath = Path.Combine(testDir, "RawSqlStringConstructionStringBuilderReachabilityTests.cs"); - - Assert.True(File.Exists(reachabilityTestsPath), "LC037 StringBuilder reachability tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.DoesNotContain("ExecuteSqlRaw_WithStringBuilderAppendBeforeCaughtThrow_ShouldTrigger", generalTestsSource); - Assert.DoesNotContain("ExecuteSqlRaw_WithStringBuilderConstructorTaintClearedInFluentChain_ShouldNotTrigger", generalTestsSource); - - var reachabilityTestsSource = File.ReadAllText(reachabilityTestsPath); - Assert.Contains("public partial class RawSqlStringConstructionTests", reachabilityTestsSource); - Assert.Contains("ExecuteSqlRaw_WithStringBuilderAppendBeforeCaughtThrow_ShouldTrigger", reachabilityTestsSource); - Assert.Contains("ExecuteSqlRaw_WithStringBuilderConstructorTaintClearedInFluentChain_ShouldNotTrigger", reachabilityTestsSource); - } - - [Fact] - public void LC037_LocalWriteFlowTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC037_RawSqlStringConstruction"); - var generalTestsPath = Path.Combine(testDir, "RawSqlStringConstructionTests.cs"); - var localWriteTestsPath = Path.Combine(testDir, "RawSqlStringConstructionLocalWriteTests.cs"); - - Assert.True(File.Exists(localWriteTestsPath), "LC037 local-write flow tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.DoesNotContain("ExecuteSqlRaw_WithConstructedInitialValueOverwrittenByConstantBeforeCall_ShouldNotTrigger", generalTestsSource); - Assert.DoesNotContain("ExecuteSqlRaw_WithConditionalConstructedSqlOverwrittenByConstantBeforeCall_ShouldNotTrigger", generalTestsSource); - - var localWriteTestsSource = File.ReadAllText(localWriteTestsPath); - Assert.Contains("public partial class RawSqlStringConstructionTests", localWriteTestsSource); - Assert.Contains("ExecuteSqlRaw_WithConstructedInitialValueOverwrittenByConstantBeforeCall_ShouldNotTrigger", localWriteTestsSource); - Assert.Contains("ExecuteSqlRaw_WithConditionalConstructedSqlOverwrittenByConstantBeforeCall_ShouldNotTrigger", localWriteTestsSource); - } - - [Fact] - public void LC018_ProviderVariantTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC018_AvoidFromSqlRawWithInterpolation"); - var generalTestsPath = Path.Combine(testDir, "AvoidFromSqlRawWithInterpolationTests.cs"); - var providerTestsPath = Path.Combine(testDir, "AvoidFromSqlRawWithInterpolationProviderVariantTests.cs"); - - Assert.True(File.Exists(providerTestsPath), "LC018 provider-variant and SqlQueryRaw coverage should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class AvoidFromSqlRawWithInterpolationTests", generalTestsSource); - Assert.DoesNotContain("FromSqlRaw_OnDbSet_WithUnsafeInterpolation_ShouldTrigger", generalTestsSource); - Assert.DoesNotContain("SqlQueryRaw_WithInterpolatedString_ShouldTriggerLC018", generalTestsSource); - - var providerTestsSource = File.ReadAllText(providerTestsPath); - Assert.Contains("public partial class AvoidFromSqlRawWithInterpolationTests", providerTestsSource); - Assert.Contains("FromSqlRaw_OnDbSet_WithUnsafeInterpolation_ShouldTrigger", providerTestsSource); - Assert.Contains("SqlQueryRaw_WithInterpolatedString_ShouldTriggerLC018", providerTestsSource); - } - - [Fact] - public void LC032_FixerSafetyTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC032_ExecuteUpdateForBulkUpdates"); - var fixerTestsPath = Path.Combine(testDir, "ExecuteUpdateForBulkUpdatesFixerTests.cs"); - var safetyTestsPath = Path.Combine(testDir, "ExecuteUpdateForBulkUpdatesFixerSafetyTests.cs"); - - Assert.True(File.Exists(safetyTestsPath), "LC032 fixer safety and decline cases should live in a focused partial test file."); - - var fixerTestsSource = File.ReadAllText(fixerTestsPath); - Assert.Contains("public partial class ExecuteUpdateForBulkUpdatesFixerTests", fixerTestsSource); - Assert.DoesNotContain("Fixer_DuplicateProperty_LaterReadsEarlierWrite_DoesNotRegister", fixerTestsSource); - Assert.DoesNotContain("Fixer_SyncLocalFunctionInsideAsyncMethod_UsesSyncExecuteUpdate", fixerTestsSource); - - var safetyTestsSource = File.ReadAllText(safetyTestsPath); - Assert.Contains("public partial class ExecuteUpdateForBulkUpdatesFixerTests", safetyTestsSource); - Assert.Contains("Fixer_DuplicateProperty_LaterReadsEarlierWrite_DoesNotRegister", safetyTestsSource); - Assert.Contains("Fixer_SyncLocalFunctionInsideAsyncMethod_UsesSyncExecuteUpdate", safetyTestsSource); - } - - [Fact] - public void LC032_FixerQuerySourceTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC032_ExecuteUpdateForBulkUpdates"); - var fixerTestsPath = Path.Combine(testDir, "ExecuteUpdateForBulkUpdatesFixerTests.cs"); - var querySourceTestsPath = Path.Combine(testDir, "ExecuteUpdateForBulkUpdatesFixerQuerySourceTests.cs"); - - Assert.True(File.Exists(querySourceTestsPath), "LC032 query-source and materializer fixer coverage should live in a focused partial test file."); - - var fixerTestsSource = File.ReadAllText(fixerTestsPath); - Assert.Contains("public partial class ExecuteUpdateForBulkUpdatesFixerTests", fixerTestsSource); - Assert.DoesNotContain("Fixer_DbContextSetSource_Rewrites", fixerTestsSource); - Assert.DoesNotContain("Fixer_AwaitedInlineToListAsync_StripsMaterializer", fixerTestsSource); - - var querySourceTestsSource = File.ReadAllText(querySourceTestsPath); - Assert.Contains("public partial class ExecuteUpdateForBulkUpdatesFixerTests", querySourceTestsSource); - Assert.Contains("Fixer_DbContextSetSource_Rewrites", querySourceTestsSource); - Assert.Contains("Fixer_AwaitedInlineToListAsync_StripsMaterializer", querySourceTestsSource); - } - - [Fact] - public void LC035_ConditionalFlowTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC035_MissingWhereBeforeExecuteDeleteUpdate"); - var generalTestsPath = Path.Combine(testDir, "MissingWhereBeforeExecuteDeleteUpdateTests.cs"); - var conditionalFlowTestsPath = Path.Combine(testDir, "MissingWhereBeforeExecuteDeleteUpdateConditionalFlowTests.cs"); - - Assert.True(File.Exists(conditionalFlowTestsPath), "LC035 conditional-flow and reassignment tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class MissingWhereBeforeExecuteDeleteUpdateTests", generalTestsSource); - Assert.DoesNotContain("ExecuteDelete_UnconditionalFilterThenOptionalNarrowing_ShouldNotTrigger", generalTestsSource); - Assert.DoesNotContain("ExecuteUpdate_CatchPathReassignsToUnfilteredQuery_ShouldTrigger", generalTestsSource); - - var conditionalFlowTestsSource = File.ReadAllText(conditionalFlowTestsPath); - Assert.Contains("public partial class MissingWhereBeforeExecuteDeleteUpdateTests", conditionalFlowTestsSource); - Assert.Contains("ExecuteDelete_UnconditionalFilterThenOptionalNarrowing_ShouldNotTrigger", conditionalFlowTestsSource); - Assert.Contains("ExecuteUpdate_CatchPathReassignsToUnfilteredQuery_ShouldTrigger", conditionalFlowTestsSource); - } - - [Fact] - public void LC001_StaticQueryableFixerTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC001_LocalMethod"); - var fixerTestsPath = Path.Combine(testDir, "LocalMethodFixerTests.cs"); - var staticQueryableTestsPath = Path.Combine(testDir, "LocalMethodFixerStaticQueryableTests.cs"); - - Assert.True(File.Exists(staticQueryableTestsPath), "LC001 static Queryable fixer coverage should live in a focused partial test file."); - - var fixerTestsSource = File.ReadAllText(fixerTestsPath); - Assert.Contains("public partial class LocalMethodFixerTests", fixerTestsSource); - Assert.DoesNotContain("FixCrime_StaticQueryableWhere_SwitchesToEnumerable", fixerTestsSource); - Assert.DoesNotContain("FixCrime_StaticQueryableThenBy_RewritesExtensionOrderedSourceChain", fixerTestsSource); - - var staticQueryableTestsSource = File.ReadAllText(staticQueryableTestsPath); - Assert.Contains("public partial class LocalMethodFixerTests", staticQueryableTestsSource); - Assert.Contains("FixCrime_StaticQueryableWhere_SwitchesToEnumerable", staticQueryableTestsSource); - Assert.Contains("FixCrime_StaticQueryableThenBy_RewritesExtensionOrderedSourceChain", staticQueryableTestsSource); - } - - [Fact] - public void LC001_StaticQueryableRewrite_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC001_LocalMethod"); - var fixerPath = Path.Combine(fixerDir, "LocalMethodFixer.cs"); - var staticQueryableRewritePath = Path.Combine(fixerDir, "LocalMethodFixerStaticQueryableRewrite.cs"); - - Assert.True(File.Exists(staticQueryableRewritePath), "LC001 static Queryable rewrite logic should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class LocalMethodFixer", fixerSource); - Assert.DoesNotContain("private static bool RewriteStaticQueryableInvocation", fixerSource); - Assert.DoesNotContain("private static bool IsSystemLinqQueryableType", fixerSource); - - var staticQueryableRewriteSource = File.ReadAllText(staticQueryableRewritePath); - Assert.Contains("private static bool RewriteStaticQueryableInvocation", staticQueryableRewriteSource); - Assert.DoesNotContain("private static bool IsSystemLinqQueryableType", staticQueryableRewriteSource); - } - - [Fact] - public void LC001_StaticQueryableQualifiers_LiveInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC001_LocalMethod"); - var staticQueryableRewritePath = Path.Combine(fixerDir, "LocalMethodFixerStaticQueryableRewrite.cs"); - var staticQueryableQualifiersPath = Path.Combine(fixerDir, "LocalMethodFixerStaticQueryableQualifiers.cs"); - - Assert.True(File.Exists(staticQueryableQualifiersPath), "LC001 static Queryable qualifier and method classification should live in a focused partial file."); - - var staticQueryableRewriteSource = File.ReadAllText(staticQueryableRewritePath); - Assert.DoesNotContain("private static ExpressionSyntax CreateEnumerableQualifier", staticQueryableRewriteSource); - Assert.DoesNotContain("private static bool IsSystemLinqQueryableType", staticQueryableRewriteSource); - Assert.DoesNotContain("private static bool CanSwitchStaticQueryableMethodToEnumerable", staticQueryableRewriteSource); - - var staticQueryableQualifiersSource = File.ReadAllText(staticQueryableQualifiersPath); - Assert.Contains("private static ExpressionSyntax CreateEnumerableQualifier", staticQueryableQualifiersSource); - Assert.Contains("private static bool IsSystemLinqQueryableType", staticQueryableQualifiersSource); - Assert.Contains("private static bool CanSwitchStaticQueryableMethodToEnumerable", staticQueryableQualifiersSource); - } - - [Fact] - public void LC001_QueryDiscovery_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC001_LocalMethod"); - var fixerPath = Path.Combine(fixerDir, "LocalMethodFixer.cs"); - var queryDiscoveryPath = Path.Combine(fixerDir, "LocalMethodFixerQueryDiscovery.cs"); - - Assert.True(File.Exists(queryDiscoveryPath), "LC001 query invocation discovery and rewrite eligibility should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.DoesNotContain("private static InvocationExpressionSyntax? FindQueryInvocation", fixerSource); - Assert.DoesNotContain("private static bool IsNestedQueryInvocation", fixerSource); - Assert.DoesNotContain("private static bool CanRewriteQueryInvocation", fixerSource); - - var queryDiscoverySource = File.ReadAllText(queryDiscoveryPath); - Assert.Contains("private static InvocationExpressionSyntax? FindQueryInvocation", queryDiscoverySource); - Assert.Contains("private static bool IsNestedQueryInvocation", queryDiscoverySource); - Assert.Contains("private static bool CanRewriteQueryInvocation", queryDiscoverySource); - } - - [Fact] - public void LC001_AnalyzerTrustPolicy_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC001_LocalMethod"); - var analyzerPath = Path.Combine(analyzerDir, "LocalMethodAnalyzer.cs"); - var trustPolicyPath = Path.Combine(analyzerDir, "LocalMethodAnalyzerTrustPolicy.cs"); - - Assert.True(File.Exists(trustPolicyPath), "LC001 trusted method and translation-marker policy should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class LocalMethodAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool IsTrustedTranslatableMethod", analyzerSource); - Assert.DoesNotContain("private static bool HasExplicitTranslationMarker", analyzerSource); - Assert.DoesNotContain("private static IEnumerable EnumerateMethodVariants", analyzerSource); - - var trustPolicySource = File.ReadAllText(trustPolicyPath); - Assert.Contains("private static bool IsTrustedTranslatableMethod", trustPolicySource); - Assert.Contains("private static bool HasExplicitTranslationMarker", trustPolicySource); - Assert.Contains("private static IEnumerable EnumerateMethodVariants", trustPolicySource); - } - - [Fact] - public void LC036_CapturedContextAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC036_DbContextCapturedAcrossThreads"); - var analyzerPath = Path.Combine(analyzerDir, "DbContextCapturedAcrossThreadsAnalyzer.cs"); - var captureAnalysisPath = Path.Combine(analyzerDir, "DbContextCapturedAcrossThreadsCaptureAnalysis.cs"); - - Assert.True(File.Exists(captureAnalysisPath), "LC036 captured DbContext syntax/symbol analysis should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class DbContextCapturedAcrossThreadsAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool TryFindCapturedDbContext", analyzerSource); - Assert.DoesNotContain("private static bool TryFindCapturedDbContextInLambda", analyzerSource); - Assert.DoesNotContain("private static bool IsCapturedDbContext", analyzerSource); - - var captureAnalysisSource = File.ReadAllText(captureAnalysisPath); - Assert.Contains("private static bool TryFindCapturedDbContext", captureAnalysisSource); - Assert.Contains("private static bool TryFindCapturedDbContextInLambda", captureAnalysisSource); - Assert.Contains("private static bool IsCapturedDbContext", captureAnalysisSource); - } - - [Fact] - public void LC007_LocalWriteTracking_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC007_NPlusOneLooper"); - var analysisPath = Path.Combine(analyzerDir, "NPlusOneLooperAnalysis.cs"); - var localWritePath = Path.Combine(analyzerDir, "NPlusOneLooperLocalWriteAnalysis.cs"); - - Assert.True(File.Exists(localWritePath), "LC007 local-write tracking should live in a focused partial file."); - - var analysisSource = File.ReadAllText(analysisPath); - Assert.Contains("internal static partial class NPlusOneLooperAnalysis", analysisSource); - Assert.DoesNotContain("private sealed class LocalWriteCache", analysisSource); - Assert.DoesNotContain("private static bool TryGetSingleAssignedLocalValue", analysisSource); - - var localWriteSource = File.ReadAllText(localWritePath); - Assert.Contains("private sealed class LocalWriteCache", localWriteSource); - Assert.Contains("private static bool TryGetSingleAssignedLocalValue", localWriteSource); - } - - [Fact] - public void LC045_ConditionalAccessEdgeCaseTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC045_MissingInclude"); - var edgeCasesPath = Path.Combine(testDir, "MissingIncludeEdgeCasesTests.cs"); - var conditionalAccessTestsPath = Path.Combine(testDir, "MissingIncludeConditionalAccessEdgeCasesTests.cs"); - - Assert.True(File.Exists(conditionalAccessTestsPath), "LC045 conditional-access edge cases should live in a focused partial test file."); - - var edgeCasesSource = File.ReadAllText(edgeCasesPath); - Assert.Contains("public partial class MissingIncludeEdgeCasesTests", edgeCasesSource); - Assert.DoesNotContain("TestInnocent_ConditionalCollectionMutatorCall_NoDiagnostic", edgeCasesSource); - Assert.DoesNotContain("TestCrime_InheritedNavigationParenthesizedConditionalAccessReportsFullNestedPath", edgeCasesSource); - - var conditionalAccessTestsSource = File.ReadAllText(conditionalAccessTestsPath); - Assert.Contains("public partial class MissingIncludeEdgeCasesTests", conditionalAccessTestsSource); - Assert.Contains("TestInnocent_ConditionalCollectionMutatorCall_NoDiagnostic", conditionalAccessTestsSource); - Assert.Contains("TestCrime_InheritedNavigationParenthesizedConditionalAccessReportsFullNestedPath", conditionalAccessTestsSource); - } - - [Fact] - public void LC045_ConditionalAccessCrimeTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC045_MissingInclude"); - var generalTestsPath = Path.Combine(testDir, "MissingIncludeTests.cs"); - var conditionalAccessTestsPath = Path.Combine(testDir, "MissingIncludeConditionalAccessTests.cs"); - - Assert.True(File.Exists(conditionalAccessTestsPath), "LC045 conditional-access crime cases should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class MissingIncludeTests", generalTestsSource); - Assert.DoesNotContain("TestCrime_ConditionalAccessNav_TriggersDiagnostic", generalTestsSource); - Assert.DoesNotContain("TestCrime_LocalFromConditionalIndexer_TriggersDiagnostic", generalTestsSource); - - var conditionalAccessTestsSource = File.ReadAllText(conditionalAccessTestsPath); - Assert.Contains("public partial class MissingIncludeTests", conditionalAccessTestsSource); - Assert.Contains("TestCrime_ConditionalAccessNav_TriggersDiagnostic", conditionalAccessTestsSource); - Assert.Contains("TestCrime_LocalFromConditionalIndexer_TriggersDiagnostic", conditionalAccessTestsSource); - } - - [Fact] - public void LC045_CoveredNavigationTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC045_MissingInclude"); - var edgeCasesPath = Path.Combine(testDir, "MissingIncludeEdgeCasesTests.cs"); - var coveredNavigationTestsPath = Path.Combine(testDir, "MissingIncludeCoveredNavigationTests.cs"); - - Assert.True(File.Exists(coveredNavigationTestsPath), "LC045 include-covered navigation tests should live in a focused partial test file."); - - var edgeCasesSource = File.ReadAllText(edgeCasesPath); - Assert.Contains("public partial class MissingIncludeEdgeCasesTests", edgeCasesSource); - Assert.DoesNotContain("TestInnocent_LambdaIncludeCoversAccess_NoDiagnostic", edgeCasesSource); - Assert.DoesNotContain("TestInnocent_CastMidPathInclude_NoDiagnostic", edgeCasesSource); - - var coveredNavigationTestsSource = File.ReadAllText(coveredNavigationTestsPath); - Assert.Contains("public partial class MissingIncludeEdgeCasesTests", coveredNavigationTestsSource); - Assert.Contains("TestInnocent_LambdaIncludeCoversAccess_NoDiagnostic", coveredNavigationTestsSource); - Assert.Contains("TestInnocent_CastMidPathInclude_NoDiagnostic", coveredNavigationTestsSource); - } - - [Fact] - public void LC045_FixerSyntaxTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC045_MissingInclude"); - var fixerTestsPath = Path.Combine(testDir, "MissingIncludeFixerTests.cs"); - var syntaxTestsPath = Path.Combine(testDir, "MissingIncludeFixerSyntaxTests.cs"); - - Assert.True(File.Exists(syntaxTestsPath), "LC045 fixer syntax and fix-all edge cases should live in a focused partial test file."); - - var fixerTestsSource = File.ReadAllText(fixerTestsPath); - Assert.Contains("public partial class MissingIncludeFixerTests", fixerTestsSource); - Assert.DoesNotContain("FixCrime_StaticCallSyntax_WrapsTheArgumentNotTheTypeName", fixerTestsSource); - Assert.DoesNotContain("FixAll_AddsIncludeToEveryFlaggedQuery", fixerTestsSource); - - var syntaxTestsSource = File.ReadAllText(syntaxTestsPath); - Assert.Contains("public partial class MissingIncludeFixerTests", syntaxTestsSource); - Assert.Contains("FixCrime_StaticCallSyntax_WrapsTheArgumentNotTheTypeName", syntaxTestsSource); - Assert.Contains("FixAll_AddsIncludeToEveryFlaggedQuery", syntaxTestsSource); - } - - [Fact] - public void LC025_ReassignmentTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC025_AsNoTrackingWithUpdate"); - var generalTestsPath = Path.Combine(testDir, "AsNoTrackingWithUpdateTests.cs"); - var reassignmentTestsPath = Path.Combine(testDir, "AsNoTrackingWithUpdateReassignmentTests.cs"); - - Assert.True(File.Exists(reassignmentTestsPath), "LC025 reassignment and ambiguous-origin tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class AsNoTrackingWithUpdateTests", generalTestsSource); - Assert.DoesNotContain("ConditionallyReassignedToNoTracking_AmbiguousOrigin_ShouldNotTrigger", generalTestsSource); - Assert.DoesNotContain("ReassignedInsideSameBranchAsUpdate_ShouldStillTrigger", generalTestsSource); - - var reassignmentTestsSource = File.ReadAllText(reassignmentTestsPath); - Assert.Contains("public partial class AsNoTrackingWithUpdateTests", reassignmentTestsSource); - Assert.Contains("ConditionallyReassignedToNoTracking_AmbiguousOrigin_ShouldNotTrigger", reassignmentTestsSource); - Assert.Contains("ReassignedInsideSameBranchAsUpdate_ShouldStillTrigger", reassignmentTestsSource); - } - - [Fact] - public void LC025_ProjectionTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC025_AsNoTrackingWithUpdate"); - var generalTestsPath = Path.Combine(testDir, "AsNoTrackingWithUpdateTests.cs"); - var projectionTestsPath = Path.Combine(testDir, "AsNoTrackingWithUpdateProjectionTests.cs"); - - Assert.True(File.Exists(projectionTestsPath), "LC025 projection and query-alias tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class AsNoTrackingWithUpdateTests", generalTestsSource); - Assert.DoesNotContain("MaterializedFromNoTrackingQueryAlias_ShouldTriggerLC025", generalTestsSource); - Assert.DoesNotContain("IdentitySelect_ThenUpdate_ShouldStillTrigger", generalTestsSource); - - var projectionTestsSource = File.ReadAllText(projectionTestsPath); - Assert.Contains("public partial class AsNoTrackingWithUpdateTests", projectionTestsSource); - Assert.Contains("MaterializedFromNoTrackingQueryAlias_ShouldTriggerLC025", projectionTestsSource); - Assert.Contains("IdentitySelect_ThenUpdate_ShouldStillTrigger", projectionTestsSource); - } - - [Fact] - public void LC024_QuerySyntaxTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC024_GroupByNonTranslatable"); - var generalTestsPath = Path.Combine(testDir, "GroupByNonTranslatableTests.cs"); - var querySyntaxTestsPath = Path.Combine(testDir, "GroupByNonTranslatableQuerySyntaxTests.cs"); - - Assert.True(File.Exists(querySyntaxTestsPath), "LC024 query-syntax GroupBy coverage should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class GroupByNonTranslatableTests", generalTestsSource); - Assert.DoesNotContain("QuerySyntaxGroupBy_Select_ToList_ShouldTriggerLC024", generalTestsSource); - Assert.DoesNotContain("EnumerableQuerySyntaxGroupBy_Select_ClientProjection_ShouldNotTrigger", generalTestsSource); - - var querySyntaxTestsSource = File.ReadAllText(querySyntaxTestsPath); - Assert.Contains("public partial class GroupByNonTranslatableTests", querySyntaxTestsSource); - Assert.Contains("QuerySyntaxGroupBy_Select_ToList_ShouldTriggerLC024", querySyntaxTestsSource); - Assert.Contains("EnumerableQuerySyntaxGroupBy_Select_ClientProjection_ShouldNotTrigger", querySyntaxTestsSource); - } - - [Fact] - public void LC012_FixerContextSafetyTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC012_OptimizeRemoveRange"); - var fixerTestsPath = Path.Combine(testDir, "OptimizeRemoveRangeFixerTests.cs"); - var contextSafetyTestsPath = Path.Combine(testDir, "OptimizeRemoveRangeFixerContextSafetyTests.cs"); - - Assert.True(File.Exists(contextSafetyTestsPath), "LC012 fixer SaveChanges/context safety tests should live in a focused partial test file."); - - var fixerTestsSource = File.ReadAllText(fixerTestsPath); - Assert.Contains("public partial class OptimizeRemoveRangeFixerTests", fixerTestsSource); - Assert.DoesNotContain("Fixer_ShouldNotRegister_WhenRemoveRangeIsFollowedBySaveChanges", fixerTestsSource); - Assert.DoesNotContain("Fixer_ShouldNotRegister_WhenQueryCombinesLaterSaveContextSource", fixerTestsSource); - - var contextSafetyTestsSource = File.ReadAllText(contextSafetyTestsPath); - Assert.Contains("public partial class OptimizeRemoveRangeFixerTests", contextSafetyTestsSource); - Assert.Contains("Fixer_ShouldNotRegister_WhenRemoveRangeIsFollowedBySaveChanges", contextSafetyTestsSource); - Assert.Contains("Fixer_ShouldNotRegister_WhenQueryCombinesLaterSaveContextSource", contextSafetyTestsSource); - } - - [Fact] - public void LC012_AnalyzerSaveChangesTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC012_OptimizeRemoveRange"); - var analyzerTestsPath = Path.Combine(testDir, "OptimizeRemoveRangeAnalyzerTests.cs"); - var saveChangesTestsPath = Path.Combine(testDir, "OptimizeRemoveRangeAnalyzerSaveChangesTests.cs"); - - Assert.True(File.Exists(saveChangesTestsPath), "LC012 analyzer SaveChanges/control-flow suppression tests should live in a focused partial test file."); - - var analyzerTestsSource = File.ReadAllText(analyzerTestsPath); - Assert.Contains("public partial class OptimizeRemoveRangeAnalyzerTests", analyzerTestsSource); - Assert.DoesNotContain("RemoveRange_FollowedBySaveChanges_ShouldNotTrigger", analyzerTestsSource); - Assert.DoesNotContain("RemoveRange_InTryWithSaveChangesInCatch_ShouldNotTrigger", analyzerTestsSource); - - var saveChangesTestsSource = File.ReadAllText(saveChangesTestsPath); - Assert.Contains("public partial class OptimizeRemoveRangeAnalyzerTests", saveChangesTestsSource); - Assert.Contains("RemoveRange_FollowedBySaveChanges_ShouldNotTrigger", saveChangesTestsSource); - Assert.Contains("RemoveRange_InTryWithSaveChangesInCatch_ShouldNotTrigger", saveChangesTestsSource); - } - - [Fact] - public void LC014_ColumnDerivedArgumentTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC014_AvoidStringCaseConversion"); - var generalTestsPath = Path.Combine(testDir, "AvoidStringCaseConversionTests.cs"); - var argumentTestsPath = Path.Combine(testDir, "AvoidStringCaseConversionColumnDerivedArgumentTests.cs"); - - Assert.True(File.Exists(argumentTestsPath), "LC014 column-derived method argument tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class AvoidStringCaseConversionTests", generalTestsSource); - Assert.DoesNotContain("ToLower_OnMethodResultWithParameterArgument_ShouldTrigger", generalTestsSource); - Assert.DoesNotContain("ConstantReceiver_ColumnAsStringArgument_ShouldTrigger", generalTestsSource); - - var argumentTestsSource = File.ReadAllText(argumentTestsPath); - Assert.Contains("public partial class AvoidStringCaseConversionTests", argumentTestsSource); - Assert.Contains("ToLower_OnMethodResultWithParameterArgument_ShouldTrigger", argumentTestsSource); - Assert.Contains("ConstantReceiver_ColumnAsStringArgument_ShouldTrigger", argumentTestsSource); - } - - [Fact] - public void LC015_PositionOperatorTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC015_MissingOrderBy"); - var generalTestsPath = Path.Combine(testDir, "MissingOrderByTests.cs"); - var positionOperatorTestsPath = Path.Combine(testDir, "MissingOrderByPositionOperatorTests.cs"); - - Assert.True(File.Exists(positionOperatorTestsPath), "LC015 ElementAt/Last operator coverage should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class MissingOrderByTests", generalTestsSource); - Assert.DoesNotContain("ElementAt_WithoutOrderBy_ShouldTrigger", generalTestsSource); - Assert.DoesNotContain("TakeLast_IsNotFlagged_BecauseEfCannotTranslateItAtAll", generalTestsSource); - - var positionOperatorTestsSource = File.ReadAllText(positionOperatorTestsPath); - Assert.Contains("public partial class MissingOrderByTests", positionOperatorTestsSource); - Assert.Contains("ElementAt_WithoutOrderBy_ShouldTrigger", positionOperatorTestsSource); - Assert.Contains("TakeLast_IsNotFlagged_BecauseEfCannotTranslateItAtAll", positionOperatorTestsSource); - } - - [Fact] - public void LC023_QueryFilterTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC023_FindInsteadOfFirstOrDefault"); - var generalTestsPath = Path.Combine(testDir, "FindInsteadOfFirstOrDefaultTests.cs"); - var queryFilterTestsPath = Path.Combine(testDir, "FindInsteadOfFirstOrDefaultQueryFilterTests.cs"); - - Assert.True(File.Exists(queryFilterTestsPath), "LC023 query-filter gate tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class FindInsteadOfFirstOrDefaultTests", generalTestsSource); - Assert.DoesNotContain("FirstOrDefault_WithId_WhenEntityHasQueryFilter_ShouldNotTrigger", generalTestsSource); - Assert.DoesNotContain("FirstOrDefault_WithUnrelatedGenericHasQueryFilter_ShouldStillTrigger", generalTestsSource); - - var queryFilterTestsSource = File.ReadAllText(queryFilterTestsPath); - Assert.Contains("public partial class FindInsteadOfFirstOrDefaultTests", queryFilterTestsSource); - Assert.Contains("FirstOrDefault_WithId_WhenEntityHasQueryFilter_ShouldNotTrigger", queryFilterTestsSource); - Assert.Contains("FirstOrDefault_WithUnrelatedGenericHasQueryFilter_ShouldStillTrigger", queryFilterTestsSource); - } - - [Fact] - public void LC023_KeyConfigurationTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC023_FindInsteadOfFirstOrDefault"); - var generalTestsPath = Path.Combine(testDir, "FindInsteadOfFirstOrDefaultTests.cs"); - var keyConfigurationTestsPath = Path.Combine(testDir, "FindInsteadOfFirstOrDefaultKeyConfigurationTests.cs"); - - Assert.True(File.Exists(keyConfigurationTestsPath), "LC023 key-configuration tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class FindInsteadOfFirstOrDefaultTests", generalTestsSource); - Assert.DoesNotContain("FirstOrDefault_WithConventionId_WhenFluentApiConfiguresDifferentKey_ShouldNotTrigger", generalTestsSource); - Assert.DoesNotContain("FirstOrDefault_WithPartialCompositeFluentApiKey_ShouldNotTrigger", generalTestsSource); - - var keyConfigurationTestsSource = File.ReadAllText(keyConfigurationTestsPath); - Assert.Contains("public partial class FindInsteadOfFirstOrDefaultTests", keyConfigurationTestsSource); - Assert.Contains("FirstOrDefault_WithConventionId_WhenFluentApiConfiguresDifferentKey_ShouldNotTrigger", keyConfigurationTestsSource); - Assert.Contains("FirstOrDefault_WithPartialCompositeFluentApiKey_ShouldNotTrigger", keyConfigurationTestsSource); - } - - [Fact] - public void LC023_QueryFilterCache_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC023_FindInsteadOfFirstOrDefault"); - var keyAnalysisPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultKeyAnalysis.cs"); - var primaryKeyCachePath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultPrimaryKeyCache.cs"); - var queryFilterCachePath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultQueryFilterCache.cs"); - - Assert.True(File.Exists(primaryKeyCachePath), "LC023 primary-key cache state should live in a focused partial file."); - Assert.True(File.Exists(queryFilterCachePath), "LC023 query-filter cache registration and lookup should live in a focused partial file."); - - var keyAnalysisSource = File.ReadAllText(keyAnalysisPath); - Assert.Contains("internal static partial class FindInsteadOfFirstOrDefaultKeyAnalysis", keyAnalysisSource); - Assert.DoesNotContain("internal sealed partial class PrimaryKeyCache", keyAnalysisSource); - Assert.DoesNotContain("public void RegisterQueryFilter", keyAnalysisSource); - Assert.DoesNotContain("public bool HasQueryFilter", keyAnalysisSource); - Assert.DoesNotContain("private bool HasRegisteredQueryFilter", keyAnalysisSource); - - var primaryKeyCacheSource = File.ReadAllText(primaryKeyCachePath); - Assert.Contains("internal static partial class FindInsteadOfFirstOrDefaultKeyAnalysis", primaryKeyCacheSource); - Assert.Contains("internal sealed partial class PrimaryKeyCache", primaryKeyCacheSource); - Assert.DoesNotContain("public void RegisterQueryFilter", primaryKeyCacheSource); - Assert.DoesNotContain("public bool HasQueryFilter", primaryKeyCacheSource); - - var queryFilterCacheSource = File.ReadAllText(queryFilterCachePath); - Assert.Contains("internal static partial class FindInsteadOfFirstOrDefaultKeyAnalysis", queryFilterCacheSource); - Assert.Contains("internal sealed partial class PrimaryKeyCache", queryFilterCacheSource); - Assert.Contains("public void RegisterQueryFilter", queryFilterCacheSource); - Assert.Contains("public bool HasQueryFilter", queryFilterCacheSource); - Assert.Contains("private bool HasRegisteredQueryFilter", queryFilterCacheSource); - } - - [Fact] - public void LC023_FixerContext_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC023_FindInsteadOfFirstOrDefault"); - var fixerPath = Path.Combine(fixerDir, "FindInsteadOfFirstOrDefaultFixer.cs"); - var contextPath = Path.Combine(fixerDir, "FindInsteadOfFirstOrDefaultFixerContext.cs"); - - Assert.True(File.Exists(contextPath), "LC023 fixer context derivation should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class FindInsteadOfFirstOrDefaultFixer", fixerSource); - Assert.DoesNotContain("private static bool TryCreateFixContext", fixerSource); - Assert.DoesNotContain("private static bool TryGetKeyValueExpression", fixerSource); - Assert.DoesNotContain("private sealed class FixContext", fixerSource); - - var contextSource = File.ReadAllText(contextPath); - Assert.Contains("private static bool TryCreateFixContext", contextSource); - Assert.DoesNotContain("private static bool TryGetKeyValueExpression", contextSource); - Assert.Contains("private sealed class FixContext", contextSource); - } - - [Fact] - public void LC027_RelationshipBuilderLocalTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC027_MissingExplicitForeignKey"); - var generalTestsPath = Path.Combine(testDir, "MissingExplicitForeignKeyEdgeCasesTests.cs"); - var relationshipBuilderTestsPath = Path.Combine(testDir, "MissingExplicitForeignKeyRelationshipBuilderLocalTests.cs"); - - Assert.True(File.Exists(relationshipBuilderTestsPath), "LC027 relationship-builder local and shadowing tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class MissingExplicitForeignKeyEdgeCasesTests", generalTestsSource); - Assert.DoesNotContain("Navigation_WithRelationshipBuilderLocalShadowForeignKey_ShouldNotTrigger", generalTestsSource); - Assert.DoesNotContain("Navigation_WithMemberAssignmentSharingRelationshipBuilderLocalName_ShouldNotTrigger", generalTestsSource); - - var relationshipBuilderTestsSource = File.ReadAllText(relationshipBuilderTestsPath); - Assert.Contains("public partial class MissingExplicitForeignKeyEdgeCasesTests", relationshipBuilderTestsSource); - Assert.Contains("Navigation_WithRelationshipBuilderLocalShadowForeignKey_ShouldNotTrigger", relationshipBuilderTestsSource); - Assert.Contains("Navigation_WithMemberAssignmentSharingRelationshipBuilderLocalName_ShouldNotTrigger", relationshipBuilderTestsSource); - } - - [Fact] - public void LC041_NullConditionalTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC041_SingleEntityScalarProjection"); - var generalTestsPath = Path.Combine(testDir, "SingleEntityScalarProjectionTests.cs"); - var nullConditionalTestsPath = Path.Combine(testDir, "SingleEntityScalarProjectionNullConditionalTests.cs"); - - Assert.True(File.Exists(nullConditionalTestsPath), "LC041 null-conditional projection tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class SingleEntityScalarProjectionTests", generalTestsSource); - Assert.DoesNotContain("FirstOrDefault_WithNullConditionalSinglePropertyUsage_Triggers", generalTestsSource); - Assert.DoesNotContain("First_WithNullConditionalSinglePropertyMethodUsage_ShouldNotOfferFix", generalTestsSource); - - var nullConditionalTestsSource = File.ReadAllText(nullConditionalTestsPath); - Assert.Contains("public partial class SingleEntityScalarProjectionTests", nullConditionalTestsSource); - Assert.Contains("FirstOrDefault_WithNullConditionalSinglePropertyUsage_Triggers", nullConditionalTestsSource); - Assert.Contains("First_WithNullConditionalSinglePropertyMethodUsage_ShouldNotOfferFix", nullConditionalTestsSource); - } - - [Fact] - public void LC041_FixerTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC041_SingleEntityScalarProjection"); - var generalTestsPath = Path.Combine(testDir, "SingleEntityScalarProjectionTests.cs"); - var fixerTestsPath = Path.Combine(testDir, "SingleEntityScalarProjectionFixerTests.cs"); - - Assert.True(File.Exists(fixerTestsPath), "LC041 fixer and fix-all coverage should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class SingleEntityScalarProjectionTests", generalTestsSource); - Assert.DoesNotContain("Fixer_ShouldProjectSingleConsumedProperty", generalTestsSource); - Assert.DoesNotContain("FixAll_RewritesMultipleScalarProjections", generalTestsSource); - - var fixerTestsSource = File.ReadAllText(fixerTestsPath); - Assert.Contains("public partial class SingleEntityScalarProjectionTests", fixerTestsSource); - Assert.Contains("Fixer_ShouldProjectSingleConsumedProperty", fixerTestsSource); - Assert.Contains("FixAll_RewritesMultipleScalarProjections", fixerTestsSource); - } - - [Fact] - public void LC041_FixerContext_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC041_SingleEntityScalarProjection"); - var fixerPath = Path.Combine(fixerDir, "SingleEntityScalarProjectionFixer.cs"); - var contextPath = Path.Combine(fixerDir, "SingleEntityScalarProjectionFixerContext.cs"); - - Assert.True(File.Exists(contextPath), "LC041 fixer context derivation and predicate safety should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class SingleEntityScalarProjectionFixer", fixerSource); - Assert.DoesNotContain("private static bool TryGetFixContext", fixerSource); - Assert.DoesNotContain("private static bool HasUnsupportedPredicateArgument", fixerSource); - Assert.DoesNotContain("private sealed class FixContext", fixerSource); - - var contextSource = File.ReadAllText(contextPath); - Assert.Contains("private static bool TryGetFixContext", contextSource); - Assert.Contains("private static bool HasUnsupportedPredicateArgument", contextSource); - Assert.Contains("private sealed class FixContext", contextSource); - } - - [Fact] - public void LC041_PrimaryKeyLookupAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC041_SingleEntityScalarProjection"); - var queryAnalysisPath = Path.Combine(analyzerDir, "SingleEntityScalarProjectionQueryAnalysis.cs"); - var primaryKeyLookupPath = Path.Combine(analyzerDir, "SingleEntityScalarProjectionPrimaryKeyLookup.cs"); - - Assert.True(File.Exists(primaryKeyLookupPath), "LC041 primary-key lookup exemption analysis should live in a focused partial file."); - - var queryAnalysisSource = File.ReadAllText(queryAnalysisPath); - Assert.DoesNotContain("private static bool IsPrimaryKeyLookupInChain", queryAnalysisSource); - Assert.DoesNotContain("private static bool IsPrimaryKeyLookup", queryAnalysisSource); - Assert.DoesNotContain("private static bool IsPrimaryKeyProperty", queryAnalysisSource); - - var primaryKeyLookupSource = File.ReadAllText(primaryKeyLookupPath); - Assert.Contains("private static bool IsPrimaryKeyLookupInChain", primaryKeyLookupSource); - Assert.Contains("private static bool IsPrimaryKeyLookup", primaryKeyLookupSource); - Assert.Contains("private static bool IsPrimaryKeyProperty", primaryKeyLookupSource); - } - - [Fact] - public void LC033_InitializerClassification_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC033_UseFrozenSetForStaticMembershipCaches"); - var analysisPath = Path.Combine(analyzerDir, "UseFrozenSetForStaticMembershipCachesAnalysis.cs"); - var classificationPath = Path.Combine(analyzerDir, "UseFrozenSetForStaticMembershipCachesInitializerClassification.cs"); - var toHashSetPath = Path.Combine(analyzerDir, "UseFrozenSetForStaticMembershipCachesToHashSetClassification.cs"); - - Assert.True(File.Exists(classificationPath), "LC033 initializer classification should live in a focused partial file."); - Assert.True(File.Exists(toHashSetPath), "LC033 ToHashSet classification should live in a focused partial file."); - - var analysisSource = File.ReadAllText(analysisPath); - Assert.Contains("internal static partial class UseFrozenSetForStaticMembershipCachesAnalysis", analysisSource); - Assert.DoesNotContain("public static bool TryClassifyInitializer", analysisSource); - Assert.DoesNotContain("private static bool IsSupportedCollectionInitializer", analysisSource); - Assert.DoesNotContain("private static bool IsSupportedToHashSetInvocation", analysisSource); - - var classificationSource = File.ReadAllText(classificationPath); - Assert.Contains("internal static partial class UseFrozenSetForStaticMembershipCachesAnalysis", classificationSource); - Assert.Contains("public static bool TryClassifyInitializer", classificationSource); - Assert.Contains("private static bool IsSupportedCollectionInitializer", classificationSource); - Assert.DoesNotContain("private static bool IsSupportedToHashSetInvocation", classificationSource); - - var toHashSetSource = File.ReadAllText(toHashSetPath); - Assert.Contains("private static bool IsSupportedToHashSetInvocation", toHashSetSource); - } - - [Fact] - public void LC018_ConstantInterpolationTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC018_AvoidFromSqlRawWithInterpolation"); - var generalTestsPath = Path.Combine(testDir, "AvoidFromSqlRawWithInterpolationTests.cs"); - var constantInterpolationTestsPath = Path.Combine(testDir, "AvoidFromSqlRawWithInterpolationConstantInterpolationTests.cs"); - - Assert.True(File.Exists(constantInterpolationTestsPath), "LC018 constant-interpolation boundary tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class AvoidFromSqlRawWithInterpolationTests", generalTestsSource); - Assert.DoesNotContain("FromSqlRaw_WithConstantOnlyInterpolation_ShouldNotTriggerLC018", generalTestsSource); - Assert.DoesNotContain("FromSqlRaw_WithStaticReadonlyFieldInterpolation_ShouldTriggerLC018", generalTestsSource); - - var constantInterpolationTestsSource = File.ReadAllText(constantInterpolationTestsPath); - Assert.Contains("public partial class AvoidFromSqlRawWithInterpolationTests", constantInterpolationTestsSource); - Assert.Contains("FromSqlRaw_WithConstantOnlyInterpolation_ShouldNotTriggerLC018", constantInterpolationTestsSource); - Assert.Contains("FromSqlRaw_WithStaticReadonlyFieldInterpolation_ShouldTriggerLC018", constantInterpolationTestsSource); - } - - [Fact] - public void LC018_FixerTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC018_AvoidFromSqlRawWithInterpolation"); - var generalTestsPath = Path.Combine(testDir, "AvoidFromSqlRawWithInterpolationTests.cs"); - var fixerTestsPath = Path.Combine(testDir, "AvoidFromSqlRawWithInterpolationFixerTests.cs"); - - Assert.True(File.Exists(fixerTestsPath), "LC018 fixer and fix-all coverage should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class AvoidFromSqlRawWithInterpolationTests", generalTestsSource); - Assert.DoesNotContain("Fixer_ShouldReplaceFromSqlRawWithFromSqlInterpolated", generalTestsSource); - Assert.DoesNotContain("FixAll_RewritesAllFromSqlRawWithInterpolatedStringInstances", generalTestsSource); - - var fixerTestsSource = File.ReadAllText(fixerTestsPath); - Assert.Contains("public partial class AvoidFromSqlRawWithInterpolationTests", fixerTestsSource); - Assert.Contains("Fixer_ShouldReplaceFromSqlRawWithFromSqlInterpolated", fixerTestsSource); - Assert.Contains("FixAll_RewritesAllFromSqlRawWithInterpolatedStringInstances", fixerTestsSource); - } - - [Fact] - public void LC018_FixerSqlSafety_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC018_AvoidFromSqlRawWithInterpolation"); - var fixerPath = Path.Combine(fixerDir, "AvoidFromSqlRawWithInterpolationFixer.cs"); - var sqlSafetyPath = Path.Combine(fixerDir, "AvoidFromSqlRawWithInterpolationFixerSqlSafety.cs"); - - Assert.True(File.Exists(sqlSafetyPath), "LC018 fixer SQL interpolation safety checks should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class AvoidFromSqlRawWithInterpolationFixer", fixerSource); - Assert.DoesNotContain("private static bool HasInterpolationInsideSqlStringLiteral", fixerSource); - Assert.DoesNotContain("private static bool HasInterpolationOutsideLikelySqlValuePosition", fixerSource); - Assert.DoesNotContain("private static bool IsLikelySqlValuePosition", fixerSource); - Assert.DoesNotContain("private static bool ToggleSqlStringLiteralState", fixerSource); - - var sqlSafetySource = File.ReadAllText(sqlSafetyPath); - Assert.Contains("private static bool HasInterpolationInsideSqlStringLiteral", sqlSafetySource); - Assert.Contains("private static bool HasInterpolationOutsideLikelySqlValuePosition", sqlSafetySource); - Assert.Contains("private static bool IsLikelySqlValuePosition", sqlSafetySource); - Assert.Contains("private static bool ToggleSqlStringLiteralState", sqlSafetySource); - } - - [Fact] - public void LC034_FixerTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC034_AvoidExecuteSqlRawWithInterpolation"); - var generalTestsPath = Path.Combine(testDir, "AvoidExecuteSqlRawWithInterpolationTests.cs"); - var fixerTestsPath = Path.Combine(testDir, "AvoidExecuteSqlRawWithInterpolationFixerTests.cs"); - - Assert.True(File.Exists(fixerTestsPath), "LC034 fixer coverage should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class AvoidExecuteSqlRawWithInterpolationTests", generalTestsSource); - Assert.DoesNotContain("Fixer_ShouldReplaceExecuteSqlRawWithExecuteSql", generalTestsSource); - Assert.DoesNotContain("FixAll_RewritesAllExecuteSqlRawCalls", generalTestsSource); - - var fixerTestsSource = File.ReadAllText(fixerTestsPath); - Assert.Contains("public partial class AvoidExecuteSqlRawWithInterpolationTests", fixerTestsSource); - Assert.Contains("Fixer_ShouldReplaceExecuteSqlRawWithExecuteSql", fixerTestsSource); - Assert.Contains("FixAll_RewritesAllExecuteSqlRawCalls", fixerTestsSource); - } - - [Fact] - public void LC011_FluentConfigurationTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC011_EntityMissingPrimaryKey"); - var edgeCasesPath = Path.Combine(testDir, "EntityMissingPrimaryKeyEdgeCasesTests.cs"); - var fluentConfigurationTestsPath = Path.Combine(testDir, "EntityMissingPrimaryKeyFluentConfigurationTests.cs"); - - Assert.True(File.Exists(fluentConfigurationTestsPath), "LC011 fluent configuration edge cases should live in a focused partial test file."); - - var edgeCasesSource = File.ReadAllText(edgeCasesPath); - Assert.Contains("public partial class EntityMissingPrimaryKeyEdgeCasesTests", edgeCasesSource); - Assert.DoesNotContain("TestCrime_UnappliedEntityTypeConfiguration_ShouldTrigger", edgeCasesSource); - Assert.DoesNotContain("TestInnocent_ScopedBuilderVariableReuse_ShouldNotTrigger", edgeCasesSource); - - var fluentConfigurationTestsSource = File.ReadAllText(fluentConfigurationTestsPath); - Assert.Contains("public partial class EntityMissingPrimaryKeyEdgeCasesTests", fluentConfigurationTestsSource); - Assert.Contains("TestCrime_UnappliedEntityTypeConfiguration_ShouldTrigger", fluentConfigurationTestsSource); - Assert.Contains("TestInnocent_ScopedBuilderVariableReuse_ShouldNotTrigger", fluentConfigurationTestsSource); - } - - [Fact] - public void LC011_KeyConventionTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC011_EntityMissingPrimaryKey"); - var generalTestsPath = Path.Combine(testDir, "EntityMissingPrimaryKeyTests.cs"); - var conventionTestsPath = Path.Combine(testDir, "EntityMissingPrimaryKeyKeyConventionTests.cs"); - - Assert.True(File.Exists(conventionTestsPath), "LC011 key convention and key-shape tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class EntityMissingPrimaryKeyTests", generalTestsSource); - Assert.DoesNotContain("TestInnocent_EntityWithId_ShouldNotTrigger", generalTestsSource); - Assert.DoesNotContain("TestInnocent_StringIdProperty_ShouldNotTrigger", generalTestsSource); - - var conventionTestsSource = File.ReadAllText(conventionTestsPath); - Assert.Contains("public partial class EntityMissingPrimaryKeyTests", conventionTestsSource); - Assert.Contains("TestInnocent_EntityWithId_ShouldNotTrigger", conventionTestsSource); - Assert.Contains("TestInnocent_StringIdProperty_ShouldNotTrigger", conventionTestsSource); - } - - [Fact] - public void LC030_RegistrationTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC030_DbContextInSingleton"); - var generalTestsPath = Path.Combine(testDir, "DbContextInSingletonTests.cs"); - var registrationTestsPath = Path.Combine(testDir, "DbContextInSingletonRegistrationTests.cs"); - - Assert.True(File.Exists(registrationTestsPath), "LC030 dependency-injection registration tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class DbContextInSingletonTests", generalTestsSource); - Assert.DoesNotContain("AddSingletonRegisteredService_WithStoredDbContext_ShouldTriggerLC030", generalTestsSource); - Assert.DoesNotContain("AmbiguousServiceRegistration_ShouldNotTrigger", generalTestsSource); - - var registrationTestsSource = File.ReadAllText(registrationTestsPath); - Assert.Contains("public partial class DbContextInSingletonTests", registrationTestsSource); - Assert.Contains("AddSingletonRegisteredService_WithStoredDbContext_ShouldTriggerLC030", registrationTestsSource); - Assert.Contains("AmbiguousServiceRegistration_ShouldNotTrigger", registrationTestsSource); - } - - [Fact] - public void LC030_RegistrationTypeResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC030_DbContextInSingleton"); - var registrationPath = Path.Combine(analyzerDir, "DbContextInSingletonRegistrationAnalysis.cs"); - var typeResolutionPath = Path.Combine(analyzerDir, "DbContextInSingletonRegistrationTypeResolution.cs"); - - Assert.True(File.Exists(typeResolutionPath), "LC030 registration type resolution should live in a focused partial file."); - - var registrationSource = File.ReadAllText(registrationPath); - Assert.DoesNotContain("private static IEnumerable GetRegisteredTypes", registrationSource); - Assert.DoesNotContain("private static ITypeSymbol? GetTypeOfOperand", registrationSource); - - var typeResolutionSource = File.ReadAllText(typeResolutionPath); - Assert.Contains("private static IEnumerable GetRegisteredTypes", typeResolutionSource); - Assert.Contains("private static ITypeSymbol? GetTypeOfOperand", typeResolutionSource); - } - - [Fact] - public void LC017_PropertyAccessEdgeCaseTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC017_WholeEntityProjection"); - var edgeCasesPath = Path.Combine(testDir, "WholeEntityProjectionEdgeCasesTests.cs"); - var propertyAccessTestsPath = Path.Combine(testDir, "WholeEntityProjectionPropertyAccessEdgeCasesTests.cs"); - - Assert.True(File.Exists(propertyAccessTestsPath), "LC017 property-access edge cases should live in a focused partial test file."); - - var edgeCasesSource = File.ReadAllText(edgeCasesPath); - Assert.Contains("public partial class WholeEntityProjectionEdgeCasesTests", edgeCasesSource); - Assert.DoesNotContain("TestCrime_NullConditionalPropertyAccess_TriggersDiagnostic", edgeCasesSource); - Assert.DoesNotContain("TestCrime_BinaryExpression_TriggersDiagnostic", edgeCasesSource); - - var propertyAccessTestsSource = File.ReadAllText(propertyAccessTestsPath); - Assert.Contains("public partial class WholeEntityProjectionEdgeCasesTests", propertyAccessTestsSource); - Assert.Contains("TestCrime_NullConditionalPropertyAccess_TriggersDiagnostic", propertyAccessTestsSource); - Assert.Contains("TestCrime_BinaryExpression_TriggersDiagnostic", propertyAccessTestsSource); - } - - [Fact] - public void LC017_FixerSafetyTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC017_WholeEntityProjection"); - var fixerTestsPath = Path.Combine(testDir, "WholeEntityProjectionFixerTests.cs"); - var safetyTestsPath = Path.Combine(testDir, "WholeEntityProjectionFixerSafetyTests.cs"); - - Assert.True(File.Exists(safetyTestsPath), "LC017 fixer safety/no-fix cases should live in a focused partial test file."); - - var fixerTestsSource = File.ReadAllText(fixerTestsPath); - Assert.Contains("public partial class WholeEntityProjectionFixerTests", fixerTestsSource); - Assert.DoesNotContain("IndexedEntityEscape_HasNoFix", fixerTestsSource); - Assert.DoesNotContain("ConditionalInterfaceCastAccessedProperty_HasNoFix", fixerTestsSource); - - var safetyTestsSource = File.ReadAllText(safetyTestsPath); - Assert.Contains("public partial class WholeEntityProjectionFixerTests", safetyTestsSource); - Assert.Contains("IndexedEntityEscape_HasNoFix", safetyTestsSource); - Assert.Contains("ConditionalInterfaceCastAccessedProperty_HasNoFix", safetyTestsSource); - } - - [Fact] - public void RuleCatalog_LC001ToLC015Entries_LiveInDedicatedPartial() - { - var catalogDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Catalog"); - var catalogPath = Path.Combine(catalogDir, "RuleCatalog.cs"); - var firstRangePath = Path.Combine(catalogDir, "RuleCatalog.LC001ToLC015.cs"); - - Assert.True(File.Exists(firstRangePath), "RuleCatalog LC001-LC015 entries should live in a focused partial file."); - - var catalogSource = File.ReadAllText(catalogPath); - Assert.Contains("public static partial class RuleCatalog", catalogSource); - Assert.DoesNotContain("id: \"LC001\"", catalogSource); - Assert.DoesNotContain("id: \"LC015\"", catalogSource); - - var firstRangeSource = File.ReadAllText(firstRangePath); - Assert.Contains("public static partial class RuleCatalog", firstRangeSource); - Assert.Contains("id: \"LC001\"", firstRangeSource); - Assert.Contains("id: \"LC015\"", firstRangeSource); - } - - [Fact] - public void LC039_TransactionTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC039_NestedSaveChanges"); - var generalTestsPath = Path.Combine(testDir, "NestedSaveChangesTests.cs"); - var transactionTestsPath = Path.Combine(testDir, "NestedSaveChangesTransactionTests.cs"); - - Assert.True(File.Exists(transactionTestsPath), "LC039 transaction-boundary tests should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class NestedSaveChangesTests", generalTestsSource); - Assert.DoesNotContain("TransactionBoundaryBetweenSaves_DoesNotTrigger", generalTestsSource); - Assert.DoesNotContain("RepeatedSavesInsideAwaitUsingDeclarationTransaction_DoesNotTrigger", generalTestsSource); - - var transactionTestsSource = File.ReadAllText(transactionTestsPath); - Assert.Contains("public partial class NestedSaveChangesTests", transactionTestsSource); - Assert.Contains("TransactionBoundaryBetweenSaves_DoesNotTrigger", transactionTestsSource); - Assert.Contains("RepeatedSavesInsideAwaitUsingDeclarationTransaction_DoesNotTrigger", transactionTestsSource); - } - - [Fact] - public void LC039_TransactionBoundaryClassification_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC039_NestedSaveChanges"); - var analyzerPath = Path.Combine(analyzerDir, "NestedSaveChangesAnalyzer.cs"); - var transactionBoundaryPath = Path.Combine(analyzerDir, "NestedSaveChangesTransactionBoundaryClassification.cs"); - - Assert.True(File.Exists(transactionBoundaryPath), "LC039 EF transaction-boundary classification should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static bool IsTransactionBoundaryInvocation", analyzerSource); - Assert.DoesNotContain("private static bool IsEfCoreTransactionBoundaryType", analyzerSource); - Assert.DoesNotContain("private static bool IsEfCoreTransactionBoundaryNamedType", analyzerSource); - - var transactionBoundarySource = File.ReadAllText(transactionBoundaryPath); - Assert.Contains("private static bool IsTransactionBoundaryInvocation", transactionBoundarySource); - Assert.Contains("private static bool IsEfCoreTransactionBoundaryType", transactionBoundarySource); - Assert.Contains("private static bool IsEfCoreTransactionBoundaryNamedType", transactionBoundarySource); - } - - [Fact] - public void LC039_BranchExclusivity_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC039_NestedSaveChanges"); - var boundaryPath = Path.Combine(analyzerDir, "NestedSaveChangesBoundaryAnalysis.cs"); - var branchExclusivityPath = Path.Combine(analyzerDir, "NestedSaveChangesBranchExclusivity.cs"); - - Assert.True(File.Exists(branchExclusivityPath), "LC039 mutually-exclusive branch detection should live in a focused partial file."); - - var boundarySource = File.ReadAllText(boundaryPath); - Assert.Contains("private static bool HasTransactionBoundaryBetween", boundarySource); - Assert.DoesNotContain("private static bool AreMutuallyExclusiveBranches", boundarySource); - Assert.DoesNotContain("private static SyntaxNode? GetContainingTryBranch", boundarySource); - Assert.DoesNotContain("private static StatementSyntax? GetContainingBranch", boundarySource); - Assert.DoesNotContain("private static SwitchSectionSyntax? GetContainingSwitchSection", boundarySource); - Assert.DoesNotContain("private static SwitchExpressionArmSyntax? GetContainingSwitchExpressionArm", boundarySource); - - var branchExclusivitySource = File.ReadAllText(branchExclusivityPath); - Assert.Contains("private static bool AreMutuallyExclusiveBranches", branchExclusivitySource); - Assert.Contains("private static SyntaxNode? GetContainingTryBranch", branchExclusivitySource); - Assert.Contains("private static StatementSyntax? GetContainingBranch", branchExclusivitySource); - Assert.Contains("private static SwitchSectionSyntax? GetContainingSwitchSection", branchExclusivitySource); - Assert.Contains("private static SwitchExpressionArmSyntax? GetContainingSwitchExpressionArm", branchExclusivitySource); - } - - [Fact] - public void LC039_DiagnosticReporting_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC039_NestedSaveChanges"); - var analyzerPath = Path.Combine(analyzerDir, "NestedSaveChangesAnalyzer.cs"); - var reportingPath = Path.Combine(analyzerDir, "NestedSaveChangesReporting.cs"); - - Assert.True(File.Exists(reportingPath), "LC039 compilation-end diagnostic grouping/reporting should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class NestedSaveChangesAnalyzer", analyzerSource); - Assert.Contains("public void AnalyzeInvocation", analyzerSource); - Assert.DoesNotContain("public void ReportDiagnostics", analyzerSource); - - var reportingSource = File.ReadAllText(reportingPath); - Assert.Contains("public void ReportDiagnostics", reportingSource); - Assert.Contains("GroupBy(record => record.Root", reportingSource); - Assert.Contains("HasTransactionBoundaryBetween", reportingSource); - Assert.Contains("AreMutuallyExclusiveBranches", reportingSource); - Assert.Contains("AreInsideSameTransactionUsing", reportingSource); - Assert.Contains("Diagnostic.Create(Rule", reportingSource); - } - - [Fact] - public void LC020_QueryableParameterTracing_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC020_StringContainsWithComparison"); - var analyzerPath = Path.Combine(analyzerDir, "StringContainsWithComparisonAnalyzer.cs"); - var queryableParametersPath = Path.Combine(analyzerDir, "StringContainsWithComparisonQueryableParameters.cs"); - - Assert.True(File.Exists(queryableParametersPath), "LC020 queryable lambda parameter tracing should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class StringContainsWithComparisonAnalyzer", analyzerSource); - Assert.DoesNotContain("private static ImmutableArray GetQueryableExpressionLambdaParameters", analyzerSource); - Assert.DoesNotContain("private static ImmutableArray GetQueryDependentLambdaParameters", analyzerSource); - Assert.DoesNotContain("private static bool LambdaSourceDependsOnParameters", analyzerSource); - Assert.DoesNotContain("private static bool IsQueryableInvocation", analyzerSource); - - var queryableParametersSource = File.ReadAllText(queryableParametersPath); - Assert.Contains("private static ImmutableArray GetQueryableExpressionLambdaParameters", queryableParametersSource); - Assert.Contains("private static ImmutableArray GetQueryDependentLambdaParameters", queryableParametersSource); - Assert.Contains("private static bool LambdaSourceDependsOnParameters", queryableParametersSource); - Assert.Contains("private static bool IsQueryableInvocation", queryableParametersSource); - } - - [Fact] - public void LC024_ProjectionAccessScanning_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC024_GroupByNonTranslatable"); - var analyzerPath = Path.Combine(analyzerDir, "GroupByNonTranslatableAnalyzer.cs"); - var projectionAccessPath = Path.Combine(analyzerDir, "GroupByNonTranslatableProjectionAccess.cs"); - - Assert.True(File.Exists(projectionAccessPath), "LC024 projection access scanning/reporting should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class GroupByNonTranslatableAnalyzer", analyzerSource); - Assert.Contains("private void AnalyzeSelectProjection", analyzerSource); - Assert.Contains("private void AnalyzeGroupByResultSelector", analyzerSource); - Assert.DoesNotContain("private void CheckOperationForNonTranslatableAccess", analyzerSource); - Assert.DoesNotContain("private static System.Collections.Generic.IEnumerable GetAllOperations", analyzerSource); - - var projectionAccessSource = File.ReadAllText(projectionAccessPath); - Assert.Contains("private static void CheckOperationForNonTranslatableAccess", projectionAccessSource); - Assert.Contains("private static System.Collections.Generic.IEnumerable GetAllOperations", projectionAccessSource); - Assert.Contains("private static void ReportNonTranslatableAccess", projectionAccessSource); - Assert.Contains("Diagnostic.Create(Rule", projectionAccessSource); - } - - [Fact] - public void LC014_QueryableLambdaScope_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC014_AvoidStringCaseConversion"); - var analyzerPath = Path.Combine(analyzerDir, "AvoidStringCaseConversionAnalyzer.cs"); - var queryableLambdaScopePath = Path.Combine(analyzerDir, "AvoidStringCaseConversionQueryableLambdaScope.cs"); - - Assert.True(File.Exists(queryableLambdaScopePath), "LC014 queryable lambda scope and target method classification should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AvoidStringCaseConversionAnalyzer", analyzerSource); - Assert.Contains("private void AnalyzeInvocation", analyzerSource); - Assert.DoesNotContain("private ImmutableArray GetEnclosingQueryableLambdaParameters", analyzerSource); - Assert.DoesNotContain("private static bool IsTargetQueryableMethod", analyzerSource); - Assert.DoesNotContain("private static bool IsLambdaScopedToEntityFrameworkSource", analyzerSource); - Assert.DoesNotContain("TargetLinqMethods", analyzerSource); - Assert.DoesNotContain("TargetEfAsyncPredicateMethods", analyzerSource); - - var queryableLambdaScopeSource = File.ReadAllText(queryableLambdaScopePath); - Assert.Contains("private static ImmutableArray GetEnclosingQueryableLambdaParameters", queryableLambdaScopeSource); - Assert.Contains("private static bool IsTargetQueryableMethod", queryableLambdaScopeSource); - Assert.Contains("private static bool IsLambdaScopedToEntityFrameworkSource", queryableLambdaScopeSource); - Assert.Contains("TargetLinqMethods", queryableLambdaScopeSource); - Assert.Contains("TargetEfAsyncPredicateMethods", queryableLambdaScopeSource); - Assert.Contains("HasEntityFrameworkQuerySource", queryableLambdaScopeSource); - } - - [Fact] - public void LC045_EntityLocalCollection_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC045_MissingInclude"); - var usageAnalysisPath = Path.Combine(analyzerDir, "MissingIncludeUsageAnalysis.cs"); - var entityLocalCollectionPath = Path.Combine(analyzerDir, "MissingIncludeEntityLocalCollection.cs"); - - Assert.True(File.Exists(entityLocalCollectionPath), "LC045 materialized-result entity local collection should live in a focused partial file."); - - var usageAnalysisSource = File.ReadAllText(usageAnalysisPath); - Assert.Contains("private static List? CollectNavigationAccesses", usageAnalysisSource); - Assert.DoesNotContain("private static HashSet CollectEntityLocals", usageAnalysisSource); - Assert.Contains("var entityLocals = CollectEntityLocals", usageAnalysisSource); - - var entityLocalCollectionSource = File.ReadAllText(entityLocalCollectionPath); - Assert.Contains("private static HashSet CollectEntityLocals", entityLocalCollectionSource); - Assert.Contains("IForEachLoopOperation", entityLocalCollectionSource); - Assert.Contains("IsIndexedAccessOf", entityLocalCollectionSource); - Assert.Contains("LocalAssignmentCache.GetAssignments", entityLocalCollectionSource); - } - - [Fact] - public void LC025_FixerNoTrackingSourceTracing_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC025_AsNoTrackingWithUpdate"); - var sourceResolutionPath = Path.Combine(fixerDir, "AsNoTrackingWithUpdateFixerSourceResolution.cs"); - var noTrackingSourcePath = Path.Combine(fixerDir, "AsNoTrackingWithUpdateFixerNoTrackingSource.cs"); - - Assert.True(File.Exists(noTrackingSourcePath), "LC025 fixer recursive no-tracking source tracing should live in a focused partial file."); - - var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); - Assert.Contains("private static InvocationExpressionSyntax? FindAsNoTrackingOrigin", sourceResolutionSource); - Assert.DoesNotContain("private static bool IsNoTrackingSource", sourceResolutionSource); - Assert.DoesNotContain("private static bool IsLocalFromNoTracking", sourceResolutionSource); - Assert.Contains("IsConditionalRelativeTo", sourceResolutionSource); - - var noTrackingSource = File.ReadAllText(noTrackingSourcePath); - Assert.Contains("private static bool IsNoTrackingSource", noTrackingSource); - Assert.Contains("private static bool IsLocalFromNoTracking", noTrackingSource); - Assert.Contains("HasAsNoTrackingInChain", noTrackingSource); - Assert.Contains("IsMaterializerMethod", noTrackingSource); - Assert.Contains("new AsNoTrackingOrigin", noTrackingSource); - } - - [Fact] - public void LC025_AnalyzerNoTrackingSourceTracing_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC025_AsNoTrackingWithUpdate"); - var sourceResolutionPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateSourceResolution.cs"); - var noTrackingSourcePath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateNoTrackingSource.cs"); - - Assert.True(File.Exists(noTrackingSourcePath), "LC025 analyzer no-tracking source evaluation should live in a focused partial file."); - - var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); - Assert.Contains("private bool IsFromNoTrackingQuery", sourceResolutionSource); - Assert.DoesNotContain("private bool IsNoTrackingSource", sourceResolutionSource); - Assert.DoesNotContain("private static IEnumerable EnumerateOperations", sourceResolutionSource); - - var noTrackingSource = File.ReadAllText(noTrackingSourcePath); - Assert.Contains("private bool IsNoTrackingSource", noTrackingSource); - Assert.Contains("private static IEnumerable EnumerateOperations", noTrackingSource); - Assert.Contains("IsAsNoTrackingQuery", noTrackingSource); - Assert.Contains("IsMaterializerMethod", noTrackingSource); - } - - [Fact] - public void LC040_DiagnosticReporting_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC040_MixedTrackingAndNoTracking"); - var analyzerPath = Path.Combine(analyzerDir, "MixedTrackingAndNoTrackingAnalyzer.cs"); - var reportingPath = Path.Combine(analyzerDir, "MixedTrackingAndNoTrackingReporting.cs"); - - Assert.True(File.Exists(reportingPath), "LC040 compilation-end diagnostic grouping/reporting should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class MixedTrackingAndNoTrackingAnalyzer", analyzerSource); - Assert.DoesNotContain("public void ReportDiagnostics", analyzerSource); - Assert.Contains("public void AnalyzeInvocation", analyzerSource); - - var reportingSource = File.ReadAllText(reportingPath); - Assert.Contains("public void ReportDiagnostics", reportingSource); - Assert.Contains("GroupBy(record => record.Root", reportingSource); - Assert.Contains("AreMutuallyExclusiveBranches", reportingSource); - Assert.Contains("Diagnostic.Create(Rule", reportingSource); - } - - [Fact] - public void LC043_BufferedLocalPattern_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC043_AsyncEnumerableBuffering"); - var analyzerPath = Path.Combine(analyzerDir, "AsyncEnumerableBufferingAnalyzer.cs"); - var patternPath = Path.Combine(analyzerDir, "AsyncEnumerableBufferingPattern.cs"); - - Assert.True(File.Exists(patternPath), "LC043 buffered-local syntax matching should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AsyncEnumerableBufferingAnalyzer", analyzerSource); - Assert.DoesNotContain("internal static bool TryGetImmediateBufferedLocal", analyzerSource); - Assert.DoesNotContain("internal sealed class BufferInfo", analyzerSource); - - var patternSource = File.ReadAllText(patternPath); - Assert.Contains("internal static bool TryGetImmediateBufferedLocal", patternSource); - Assert.Contains("internal sealed class BufferInfo", patternSource); - } - - [Fact] - public void LC043_FixerLoopDiscovery_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC043_AsyncEnumerableBuffering"); - var fixerPath = Path.Combine(analyzerDir, "AsyncEnumerableBufferingFixer.cs"); - var loopDiscoveryPath = Path.Combine(analyzerDir, "AsyncEnumerableBufferingFixerLoopDiscovery.cs"); - - Assert.True(File.Exists(loopDiscoveryPath), "LC043 fixer loop/declaration discovery should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class AsyncEnumerableBufferingFixer", fixerSource); - Assert.DoesNotContain("private static bool TryGetContainingLoopAndDeclaration", fixerSource); - - var loopDiscoverySource = File.ReadAllText(loopDiscoveryPath); - Assert.Contains("private static bool TryGetContainingLoopAndDeclaration", loopDiscoverySource); - } - - [Fact] - public void LC044_TrackedStateEdgeCases_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC044_AsNoTrackingThenModify"); - var edgeCasesPath = Path.Combine(testDir, "AsNoTrackingThenModifyEdgeCasesTests.cs"); - var trackedStateTestsPath = Path.Combine(testDir, "AsNoTrackingThenModifyTrackedStateTests.cs"); - - Assert.True(File.Exists(trackedStateTestsPath), "LC044 attach/update/state-reset edge cases should live in a focused partial test file."); - - var edgeCasesSource = File.ReadAllText(edgeCasesPath); - Assert.Contains("public partial class AsNoTrackingThenModifyEdgeCasesTests", edgeCasesSource); - Assert.DoesNotContain("AsNoTracking_Mutate_ContextUpdate_ThenSave_DoesNotTrigger", edgeCasesSource); - Assert.DoesNotContain("AsNoTracking_ContextAttach_ThenChangeTrackerClear_ThenMutate_ThenSave_StillTriggers", edgeCasesSource); - - var trackedStateTestsSource = File.ReadAllText(trackedStateTestsPath); - Assert.Contains("public partial class AsNoTrackingThenModifyEdgeCasesTests", trackedStateTestsSource); - Assert.Contains("AsNoTracking_Mutate_ContextUpdate_ThenSave_DoesNotTrigger", trackedStateTestsSource); - Assert.Contains("AsNoTracking_ContextAttach_ThenChangeTrackerClear_ThenMutate_ThenSave_StillTriggers", trackedStateTestsSource); - } - - [Fact] - public void LC009_SetGenericSourceTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC009_MissingAsNoTracking"); - var generalTestsPath = Path.Combine(testDir, "MissingAsNoTrackingTests.cs"); - var setSourceTestsPath = Path.Combine(testDir, "MissingAsNoTrackingSetSourceTests.cs"); - - Assert.True(File.Exists(setSourceTestsPath), "LC009 DbContext.Set query-source coverage should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class MissingAsNoTrackingTests", generalTestsSource); - Assert.DoesNotContain("TestCrime_SetGenericSource_ReadOnly_TriggersDiagnostic", generalTestsSource); - Assert.DoesNotContain("TestInnocent_SetGenericSource_WithSelect_NoDiagnostic", generalTestsSource); - - var setSourceTestsSource = File.ReadAllText(setSourceTestsPath); - Assert.Contains("public partial class MissingAsNoTrackingTests", setSourceTestsSource); - Assert.Contains("TestCrime_SetGenericSource_ReadOnly_TriggersDiagnostic", setSourceTestsSource); - Assert.Contains("TestInnocent_SetGenericSource_WithSelect_NoDiagnostic", setSourceTestsSource); - } - - [Fact] - public void LC009_ResultLocalResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC009_MissingAsNoTracking"); - var mutationDetectionPath = Path.Combine(analyzerDir, "MissingAsNoTrackingMutationDetection.cs"); - var resultLocalPath = Path.Combine(analyzerDir, "MissingAsNoTrackingResultLocal.cs"); - - Assert.True(File.Exists(resultLocalPath), "LC009 materialized-result local and wrapper resolution should live in a focused partial file."); - - var mutationDetectionSource = File.ReadAllText(mutationDetectionPath); - Assert.Contains("private static bool MaterializedEntityIsMutated", mutationDetectionSource); - Assert.DoesNotContain("private static ILocalSymbol? FindResultLocal", mutationDetectionSource); - Assert.DoesNotContain("private static IOperation? WalkUpThroughWrappers", mutationDetectionSource); - - var resultLocalSource = File.ReadAllText(resultLocalPath); - Assert.Contains("private static ILocalSymbol? FindResultLocal", resultLocalSource); - Assert.Contains("private static IOperation? WalkUpThroughWrappers", resultLocalSource); - Assert.DoesNotContain("private static bool MaterializedEntityIsMutated", resultLocalSource); - } - - [Fact] - public void LC007_IgnoredSourceTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC007_NPlusOneLooper"); - var generalTestsPath = Path.Combine(testDir, "NPlusOneLooperTests.cs"); - var ignoredSourceTestsPath = Path.Combine(testDir, "NPlusOneLooperIgnoredSourceTests.cs"); - - Assert.True(File.Exists(ignoredSourceTestsPath), "LC007 ignored and ambiguous query-source coverage should live in a focused partial test file."); - - var generalTestsSource = File.ReadAllText(generalTestsPath); - Assert.Contains("public partial class NPlusOneLooperTests", generalTestsSource); - Assert.DoesNotContain("InMemoryAsQueryable_IsIgnored", generalTestsSource); - Assert.DoesNotContain("InvocationInsideLambdaDeclaredInLoop_IsIgnored", generalTestsSource); - - var ignoredSourceTestsSource = File.ReadAllText(ignoredSourceTestsPath); - Assert.Contains("public partial class NPlusOneLooperTests", ignoredSourceTestsSource); - Assert.Contains("InMemoryAsQueryable_IsIgnored", ignoredSourceTestsSource); - Assert.Contains("InvocationInsideLambdaDeclaredInLoop_IsIgnored", ignoredSourceTestsSource); - } - - [Fact] - public void LC045_EscapedResultEdgeCaseTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC045_MissingInclude"); - var edgeCasesPath = Path.Combine(testDir, "MissingIncludeEdgeCasesTests.cs"); - var escapedResultTestsPath = Path.Combine(testDir, "MissingIncludeEscapedResultTests.cs"); - - Assert.True(File.Exists(escapedResultTestsPath), "LC045 escaped or ambiguous result edge cases should live in a focused partial test file."); - - var edgeCasesSource = File.ReadAllText(edgeCasesPath); - Assert.Contains("public partial class MissingIncludeEdgeCasesTests", edgeCasesSource); - Assert.DoesNotContain("TestInnocent_ResultPassedAsArgument_NoDiagnostic", edgeCasesSource); - Assert.DoesNotContain("TestInnocent_IndexedEntityPassedAsArgument_NoDiagnostic", edgeCasesSource); - - var escapedResultTestsSource = File.ReadAllText(escapedResultTestsPath); - Assert.Contains("public partial class MissingIncludeEdgeCasesTests", escapedResultTestsSource); - Assert.Contains("TestInnocent_ResultPassedAsArgument_NoDiagnostic", escapedResultTestsSource); - Assert.Contains("TestInnocent_IndexedEntityPassedAsArgument_NoDiagnostic", escapedResultTestsSource); - } - - [Fact] - public void LC045_OriginAwareFlowTests_LiveInDedicatedPartial() - { - var testDir = Path.Combine( - _repoRoot, - "tests", - "LinqContraband.Tests", - "Analyzers", - "LC045_MissingInclude"); - var escapedResultTestsPath = Path.Combine(testDir, "MissingIncludeEscapedResultTests.cs"); - var originFlowTestsPath = Path.Combine(testDir, "MissingIncludeOriginFlowTests.cs"); - var uncertainReadTestsPath = Path.Combine(testDir, "MissingIncludeOriginFlowUncertainReadTests.cs"); - - Assert.True(File.Exists(originFlowTestsPath), "LC045 origin-aware control-flow coverage should live in a focused partial test file."); - Assert.True(File.Exists(uncertainReadTestsPath), "LC045 uncertain-read control-flow guardrails should live in a focused partial test file."); - - var escapedResultTestsSource = File.ReadAllText(escapedResultTestsPath); - Assert.DoesNotContain("TestCrime_OriginFlow_", escapedResultTestsSource); - Assert.DoesNotContain("TestInnocent_OriginFlow_", escapedResultTestsSource); - - var originFlowTestsSource = File.ReadAllText(originFlowTestsPath); - Assert.Contains("public partial class MissingIncludeEdgeCasesTests", originFlowTestsSource); - Assert.Contains("TestCrime_OriginFlow_ReadBeforeHelperEscape_StillReports", originFlowTestsSource); - Assert.Contains("TestCrime_OriginFlow_OneBranchNavigationAssignment_StillReports", originFlowTestsSource); - Assert.Contains("TestCrime_OriginFlow_WriteOnDifferentEntity_DoesNotSatisfyRead", originFlowTestsSource); - Assert.Contains("TestCrime_OriginFlow_LoopReadBeforeWrite_StillReports", originFlowTestsSource); - Assert.DoesNotContain("TestInnocent_OriginFlow_HelperEscapeBeforeRead_NoDiagnostic", originFlowTestsSource); - - var uncertainReadTestsSource = File.ReadAllText(uncertainReadTestsPath); - Assert.Contains("public partial class MissingIncludeEdgeCasesTests", uncertainReadTestsSource); - Assert.Contains("TestInnocent_OriginFlow_HelperEscapeBeforeRead_NoDiagnostic", uncertainReadTestsSource); - Assert.Contains("TestInnocent_OriginFlow_BothBranchesAssignNavigationBeforeRead_NoDiagnostic", uncertainReadTestsSource); - } - - [Fact] - public void LC031_QuerySourceResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC031_UnboundedQueryMaterialization"); - var analyzerPath = Path.Combine(analyzerDir, "UnboundedQueryMaterializationAnalyzer.cs"); - var querySourcePath = Path.Combine(analyzerDir, "UnboundedQueryMaterializationQuerySource.cs"); - - Assert.True(File.Exists(querySourcePath), "LC031 DbSet/query-source chain resolution should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class UnboundedQueryMaterializationAnalyzer", analyzerSource); - Assert.DoesNotContain("private static QuerySourceResolution ResolveQuerySource", analyzerSource); - Assert.DoesNotContain("private readonly struct QuerySourceResolution", analyzerSource); - Assert.DoesNotContain("private static bool IsDbContextSetInvocation", analyzerSource); - Assert.DoesNotContain("private static bool TryResolveSingleAssignedValue", analyzerSource); - - var querySource = File.ReadAllText(querySourcePath); - Assert.Contains("private static QuerySourceResolution ResolveQuerySource", querySource); - Assert.Contains("private readonly struct QuerySourceResolution", querySource); - Assert.Contains("private static bool IsDbContextSetInvocation", querySource); - Assert.Contains("private static bool TryResolveSingleAssignedValue", querySource); - } - - [Fact] - public void LC003_ExistenceComparisonRecognition_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC003_AnyOverCount"); - var analyzerPath = Path.Combine(analyzerDir, "AnyOverCountAnalyzer.cs"); - var comparisonPath = Path.Combine(analyzerDir, "AnyOverCountExistenceComparison.cs"); - - Assert.True(File.Exists(comparisonPath), "LC003 Count comparison recognition should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AnyOverCountAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool TryGetCountExistenceCheck", analyzerSource); - Assert.DoesNotContain("private static bool IsInvocation", analyzerSource); - Assert.DoesNotContain("private static bool IsConstant", analyzerSource); - Assert.DoesNotContain("private static bool IsZero", analyzerSource); - Assert.DoesNotContain("private static bool IsOne", analyzerSource); - - var comparisonSource = File.ReadAllText(comparisonPath); - Assert.Contains("private static bool TryGetCountExistenceCheck", comparisonSource); - Assert.Contains("private static bool IsInvocation", comparisonSource); - Assert.Contains("private static bool IsConstant", comparisonSource); - Assert.Contains("private static bool IsZero", comparisonSource); - Assert.Contains("private static bool IsOne", comparisonSource); - } - - [Fact] - public void LC025_AnalyzerTrackingDirectiveAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC025_AsNoTrackingWithUpdate"); - var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateAnalyzer.cs"); - var trackingDirectivePath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateTrackingDirectiveAnalysis.cs"); - - Assert.True(File.Exists(trackingDirectivePath), "LC025 analyzer tracking-directive chain analysis should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AsNoTrackingWithUpdateAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool IsAsNoTrackingQuery", analyzerSource); - Assert.DoesNotContain("private static bool HasAsNoTrackingInChain", analyzerSource); - Assert.DoesNotContain("private static bool IsProjectionToConstructedObject", analyzerSource); - Assert.DoesNotContain("private static bool IsEfCoreNoTrackingDirective", analyzerSource); - Assert.DoesNotContain("private static bool IsEfCoreAsTracking", analyzerSource); - - var trackingDirectiveSource = File.ReadAllText(trackingDirectivePath); - Assert.Contains("private static bool IsAsNoTrackingQuery", trackingDirectiveSource); - Assert.Contains("private static bool HasAsNoTrackingInChain", trackingDirectiveSource); - Assert.Contains("private static bool IsProjectionToConstructedObject", trackingDirectiveSource); - Assert.Contains("private static bool IsEfCoreNoTrackingDirective", trackingDirectiveSource); - Assert.Contains("private static bool IsEfCoreAsTracking", trackingDirectiveSource); - } - - [Fact] - public void LC032_FixerAsyncSupportDetection_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC032_ExecuteUpdateForBulkUpdates"); - var saveChangesModePath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerSaveChangesMode.cs"); - var asyncSupportPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerAsyncSupport.cs"); - - Assert.True(File.Exists(asyncSupportPath), "LC032 fixer ExecuteUpdateAsync capability detection should live in a focused partial file."); - - var saveChangesModeSource = File.ReadAllText(saveChangesModePath); - Assert.DoesNotContain("private static bool HasExecuteUpdateAsyncTokenOverload", saveChangesModeSource); - Assert.DoesNotContain("private static bool HasExecuteUpdateAsyncSupport", saveChangesModeSource); - Assert.DoesNotContain("private static bool HasExecuteUpdateAsyncMethod", saveChangesModeSource); - Assert.DoesNotContain("private static bool IsExecuteUpdateAsyncLikeMethod", saveChangesModeSource); - Assert.DoesNotContain("private static bool IsEntityFrameworkCoreNamespace", saveChangesModeSource); - - var asyncSupportSource = File.ReadAllText(asyncSupportPath); - Assert.Contains("private static bool HasExecuteUpdateAsyncTokenOverload", asyncSupportSource); - Assert.Contains("private static bool HasExecuteUpdateAsyncSupport", asyncSupportSource); - Assert.Contains("private static bool HasExecuteUpdateAsyncMethod", asyncSupportSource); - Assert.Contains("private static bool IsExecuteUpdateAsyncLikeMethod", asyncSupportSource); - Assert.Contains("private static bool IsEntityFrameworkCoreNamespace", asyncSupportSource); - } - - [Fact] - public void LC032_LocalWriteDetection_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC032_ExecuteUpdateForBulkUpdates"); - var statementAnalysisPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesStatementAnalysis.cs"); - var localWriteDetectionPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesLocalWriteDetection.cs"); - - Assert.True(File.Exists(localWriteDetectionPath), "LC032 local write detection should live in a focused analyzer partial file."); - - var statementAnalysisSource = File.ReadAllText(statementAnalysisPath); - Assert.DoesNotContain("private static bool HasLocalWrites", statementAnalysisSource); - - var localWriteDetectionSource = File.ReadAllText(localWriteDetectionPath); - Assert.Contains("private static bool HasLocalWrites", localWriteDetectionSource); - } - - [Fact] - public void LC011_AssemblyUsingScopeTraversal_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var visibilityPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyUsingVisibility.cs"); - var usingScopePath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyUsingScope.cs"); - - Assert.True(File.Exists(usingScopePath), "LC011 assembly using-scope traversal should live in a focused partial file."); - - var visibilitySource = File.ReadAllText(visibilityPath); - Assert.DoesNotContain("private static bool AnyUsingDirectiveInScope", visibilitySource); - Assert.DoesNotContain("foreach (var namespaceDeclaration in node.Ancestors().OfType())", visibilitySource); - Assert.DoesNotContain("foreach (var fileScopedNamespace in node.Ancestors().OfType())", visibilitySource); - - var usingScopeSource = File.ReadAllText(usingScopePath); - Assert.Contains("private static bool AnyUsingDirectiveInScope", usingScopeSource); - Assert.Contains("foreach (var namespaceDeclaration in node.Ancestors().OfType())", usingScopeSource); - Assert.Contains("foreach (var fileScopedNamespace in node.Ancestors().OfType())", usingScopeSource); - } - - [Fact] - public void LC011_AssemblyMemberResolution_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var localResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyLocalResolution.cs"); - var memberResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyMemberResolution.cs"); - - Assert.True(File.Exists(memberResolutionPath), "LC011 assembly member initializer resolution should live in a focused partial file."); - - var localResolutionSource = File.ReadAllText(localResolutionPath); - Assert.DoesNotContain("private static bool TryResolveMemberCurrentAssembly", localResolutionSource); - - var memberResolutionSource = File.ReadAllText(memberResolutionPath); - Assert.Contains("private static bool TryResolveMemberCurrentAssembly", memberResolutionSource); - Assert.Contains("foreach (var member in members)", memberResolutionSource); - Assert.Contains("DeclaringSyntaxReferences", memberResolutionSource); - } - - [Fact] - public void LC016_FixerExpressionBodyClockAccessDiscovery_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC016_AvoidDateTimeNow"); - var expressionBodyPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixerExpressionBody.cs"); - var clockAccessPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixerExpressionBodyClockAccesses.cs"); - - Assert.True(File.Exists(clockAccessPath), "LC016 expression-bodied clock access discovery should live in a focused partial file."); - - var expressionBodySource = File.ReadAllText(expressionBodyPath); - Assert.DoesNotContain("private static IReadOnlyList BuildExpressionBodyReplacements", expressionBodySource); - Assert.DoesNotContain("private static ClockReplacement? FindReplacementFor", expressionBodySource); - Assert.DoesNotContain("private static IEnumerable FindExpressionBodyClockAccesses", expressionBodySource); - Assert.DoesNotContain("private static bool IsClockPropertyAccess", expressionBodySource); - Assert.DoesNotContain("private sealed class ClockReplacement", expressionBodySource); - - var clockAccessSource = File.ReadAllText(clockAccessPath); - Assert.Contains("private static IReadOnlyList BuildExpressionBodyReplacements", clockAccessSource); - Assert.Contains("private static ClockReplacement? FindReplacementFor", clockAccessSource); - Assert.Contains("private static IEnumerable FindExpressionBodyClockAccesses", clockAccessSource); - Assert.Contains("private static bool IsClockPropertyAccess", clockAccessSource); - Assert.Contains("private sealed class ClockReplacement", clockAccessSource); - } - - [Fact] - public void LC007_FixerIncludeRewrite_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC007_NPlusOneLooper"); - var syntaxAnalysisPath = Path.Combine(fixerDir, "NPlusOneLooperFixerSyntaxAnalysis.cs"); - var includeRewritePath = Path.Combine(fixerDir, "NPlusOneLooperFixerIncludeRewrite.cs"); - - Assert.True(File.Exists(includeRewritePath), "LC007 fixer Include rewrite construction should live in a focused partial file."); - - var syntaxAnalysisSource = File.ReadAllText(syntaxAnalysisPath); - Assert.DoesNotContain("private static bool TryAddInclude", syntaxAnalysisSource); - - var includeRewriteSource = File.ReadAllText(includeRewritePath); - Assert.Contains("private static bool TryAddInclude", includeRewriteSource); - Assert.Contains("SyntaxFactory.InvocationExpression", includeRewriteSource); - Assert.Contains("SyntaxFactory.IdentifierName(\"Include\")", includeRewriteSource); - } - - [Fact] - public void LC010_FreshContextExemption_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC010_SaveChangesInLoop"); - var analyzerPath = Path.Combine(analyzerDir, "SaveChangesInLoopAnalyzer.cs"); - var freshContextPath = Path.Combine(analyzerDir, "SaveChangesInLoopFreshContext.cs"); - - Assert.True(File.Exists(freshContextPath), "LC010 fresh DbContext-in-loop exemption should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static bool IsSaveReceiverFreshContextDeclaredInsideLoopBody", analyzerSource); - Assert.DoesNotContain("private static bool IsLocalWrittenBeforeOperation", analyzerSource); - Assert.DoesNotContain("private static bool IsLocalReference", analyzerSource); - - var freshContextSource = File.ReadAllText(freshContextPath); - Assert.Contains("private static bool IsSaveReceiverFreshContextDeclaredInsideLoopBody", freshContextSource); - Assert.Contains("private static bool IsLocalWrittenBeforeOperation", freshContextSource); - Assert.Contains("private static bool IsLocalReference", freshContextSource); - } - - [Fact] - public void LC033_ToHashSetInvocationClassification_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC033_UseFrozenSetForStaticMembershipCaches"); - var initializerPath = Path.Combine(analyzerDir, "UseFrozenSetForStaticMembershipCachesInitializerClassification.cs"); - var toHashSetPath = Path.Combine(analyzerDir, "UseFrozenSetForStaticMembershipCachesToHashSetClassification.cs"); - - Assert.True(File.Exists(toHashSetPath), "LC033 ToHashSet invocation classification should live in a focused partial file."); - - var initializerSource = File.ReadAllText(initializerPath); - Assert.DoesNotContain("private static bool IsSupportedToHashSetInvocation", initializerSource); - Assert.DoesNotContain("private static bool IsStaticTypeOrNamespaceAccess", initializerSource); - Assert.DoesNotContain("private static bool IsTypeOrNamespaceSymbol", initializerSource); - - var toHashSetSource = File.ReadAllText(toHashSetPath); - Assert.Contains("private static bool IsSupportedToHashSetInvocation", toHashSetSource); - Assert.Contains("private static bool IsStaticTypeOrNamespaceAccess", toHashSetSource); - Assert.Contains("private static bool IsTypeOrNamespaceSymbol", toHashSetSource); - } - - [Fact] - public void LC022_GroupingQueryableExemption_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC022_ToListInSelectProjection"); - var analyzerPath = Path.Combine(analyzerDir, "ToListInSelectProjectionAnalyzer.cs"); - var groupingPath = Path.Combine(analyzerDir, "ToListInSelectProjectionGrouping.cs"); - - Assert.True(File.Exists(groupingPath), "LC022 grouping-queryable exemption should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class ToListInSelectProjectionAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool IsGroupingQueryable", analyzerSource); - Assert.DoesNotContain("private static ITypeSymbol? GetQueryableElementType", analyzerSource); - Assert.DoesNotContain("private static bool IsGroupingType", analyzerSource); - - var groupingSource = File.ReadAllText(groupingPath); - Assert.Contains("private static bool IsGroupingQueryable", groupingSource); - Assert.Contains("private static ITypeSymbol? GetQueryableElementType", groupingSource); - Assert.Contains("private static bool IsGroupingType", groupingSource); - } - - [Fact] - public void LC003_FixerZeroConstantDetection_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC003_AnyOverCount"); - var fixerPath = Path.Combine(fixerDir, "AnyOverCountFixer.cs"); - var zeroConstantPath = Path.Combine(fixerDir, "AnyOverCountFixerZeroConstant.cs"); - - Assert.True(File.Exists(zeroConstantPath), "LC003 fixer zero-constant detection should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class AnyOverCountFixer", fixerSource); - Assert.DoesNotContain("private static bool HasZeroConstant", fixerSource); - Assert.DoesNotContain("private static bool IsZeroConstant", fixerSource); - Assert.DoesNotContain("private static bool IsZeroValue", fixerSource); - - var zeroConstantSource = File.ReadAllText(zeroConstantPath); - Assert.Contains("private static bool HasZeroConstant", zeroConstantSource); - Assert.Contains("private static bool IsZeroConstant", zeroConstantSource); - Assert.Contains("private static bool IsZeroValue", zeroConstantSource); - } - - [Fact] - public void LC027_FixerPrimaryKeyDiscovery_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC027_MissingExplicitForeignKey"); - var fixerPath = Path.Combine(fixerDir, "MissingExplicitForeignKeyFixer.cs"); - var primaryKeyPath = Path.Combine(fixerDir, "MissingExplicitForeignKeyFixerPrimaryKeyDiscovery.cs"); - - Assert.True(File.Exists(primaryKeyPath), "LC027 fixer primary-key discovery should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class MissingExplicitForeignKeyFixer", fixerSource); - Assert.DoesNotContain("private static IPropertySymbol? TryFindConventionPrimaryKey", fixerSource); - Assert.DoesNotContain("private static IPropertySymbol? TryFindConfiguredPrimaryKey", fixerSource); - Assert.DoesNotContain("private static bool TryGetEntityTypeBuilderEntity", fixerSource); - - var primaryKeySource = File.ReadAllText(primaryKeyPath); - Assert.Contains("private static IPropertySymbol? TryFindConventionPrimaryKey", primaryKeySource); - Assert.Contains("private static IPropertySymbol? TryFindConfiguredPrimaryKey", primaryKeySource); - Assert.Contains("private static bool TryGetEntityTypeBuilderEntity", primaryKeySource); - } - - [Fact] - public void LC016_QueryableScopeDetection_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC016_AvoidDateTimeNow"); - var analyzerPath = Path.Combine(analyzerDir, "AvoidDateTimeNowAnalyzer.cs"); - var queryableScopePath = Path.Combine(analyzerDir, "AvoidDateTimeNowQueryableScope.cs"); - - Assert.True(File.Exists(queryableScopePath), "LC016 queryable-lambda scope detection should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AvoidDateTimeNowAnalyzer", analyzerSource); - Assert.DoesNotContain("private static IAnonymousFunctionOperation? FindQueryableLambda", analyzerSource); - Assert.DoesNotContain("private static bool IsTargetQueryableInvocation", analyzerSource); - Assert.DoesNotContain("private static IAnonymousFunctionOperation? FindEnclosingLambda", analyzerSource); - Assert.DoesNotContain("private static IEnumerable EnumerateOperations", analyzerSource); - - var queryableScopeSource = File.ReadAllText(queryableScopePath); - Assert.Contains("private static IAnonymousFunctionOperation? FindQueryableLambda", queryableScopeSource); - Assert.Contains("private static bool IsTargetQueryableInvocation", queryableScopeSource); - Assert.Contains("private static IAnonymousFunctionOperation? FindEnclosingLambda", queryableScopeSource); - Assert.Contains("private static IEnumerable EnumerateOperations", queryableScopeSource); - } - - [Fact] - public void LC027_TypeIndexConstruction_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC027_MissingExplicitForeignKey"); - var typeLookupPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyTypeLookup.cs"); - var typeIndexPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyTypeIndex.cs"); - - Assert.True(File.Exists(typeIndexPath), "LC027 type-index construction should live in a focused partial file."); - - var typeLookupSource = File.ReadAllText(typeLookupPath); - Assert.Contains("private sealed partial class CompilationModel", typeLookupSource); - Assert.DoesNotContain("private TypeIndex BuildTypeIndex", typeLookupSource); - Assert.DoesNotContain("private sealed class TypeIndex", typeLookupSource); - Assert.DoesNotContain("private static void AddNamespaceTypes", typeLookupSource); - Assert.DoesNotContain("private static void AddTypeAndNestedTypes", typeLookupSource); - Assert.DoesNotContain("private static void AddLookupName", typeLookupSource); - - var typeIndexSource = File.ReadAllText(typeIndexPath); - Assert.Contains("private TypeIndex BuildTypeIndex", typeIndexSource); - Assert.Contains("private sealed class TypeIndex", typeIndexSource); - Assert.Contains("private static void AddNamespaceTypes", typeIndexSource); - Assert.Contains("private static void AddTypeAndNestedTypes", typeIndexSource); - Assert.Contains("private static void AddLookupName", typeIndexSource); - } - - [Fact] - public void LC001_QueryableScopeDetection_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC001_LocalMethod"); - var analyzerPath = Path.Combine(analyzerDir, "LocalMethodAnalyzer.cs"); - var queryableScopePath = Path.Combine(analyzerDir, "LocalMethodAnalyzerQueryableScope.cs"); - - Assert.True(File.Exists(queryableScopePath), "LC001 queryable-scope detection should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static bool IsTranslationCriticalQueryableInvocation", analyzerSource); - Assert.DoesNotContain("private static IArgumentOperation? GetInputSequenceArgument", analyzerSource); - Assert.DoesNotContain("private static bool InvocationDependsOnLambdaParameter", analyzerSource); - - var queryableScopeSource = File.ReadAllText(queryableScopePath); - Assert.Contains("private static bool IsTranslationCriticalQueryableInvocation", queryableScopeSource); - Assert.Contains("private static IArgumentOperation? GetInputSequenceArgument", queryableScopeSource); - Assert.Contains("private static bool InvocationDependsOnLambdaParameter", queryableScopeSource); - } - - [Fact] - public void LC029_IdentitySelectorClassification_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC029_RedundantIdentitySelect"); - var analyzerPath = Path.Combine(analyzerDir, "RedundantIdentitySelectAnalyzer.cs"); - var identitySelectorPath = Path.Combine(analyzerDir, "RedundantIdentitySelectIdentitySelector.cs"); - - Assert.True(File.Exists(identitySelectorPath), "LC029 identity-selector classification should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class RedundantIdentitySelectAnalyzer", analyzerSource); - Assert.DoesNotContain("private static IAnonymousFunctionOperation? TryGetLambda", analyzerSource); - Assert.DoesNotContain("private static bool IsExactEnumerableInterface", analyzerSource); - Assert.DoesNotContain("private static bool IsTypePreservingSelector", analyzerSource); - Assert.DoesNotContain("private static bool IsIdentityLambda", analyzerSource); - Assert.DoesNotContain("private static IOperation UnwrapIdentityValue", analyzerSource); - - var identitySelectorSource = File.ReadAllText(identitySelectorPath); - Assert.Contains("private static IAnonymousFunctionOperation? TryGetLambda", identitySelectorSource); - Assert.Contains("private static bool IsExactEnumerableInterface", identitySelectorSource); - Assert.Contains("private static bool IsTypePreservingSelector", identitySelectorSource); - Assert.Contains("private static bool IsIdentityLambda", identitySelectorSource); - Assert.Contains("private static IOperation UnwrapIdentityValue", identitySelectorSource); - } - - [Fact] - public void LC012_FixerQuerySourceContextResolution_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC012_OptimizeRemoveRange"); - var rewriteSafetyPath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerRewriteSafety.cs"); - var querySourcePath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerQuerySourceContext.cs"); - - Assert.True(File.Exists(querySourcePath), "LC012 fixer query-source context resolution should live in a focused partial file."); - - var rewriteSafetySource = File.ReadAllText(rewriteSafetyPath); - Assert.DoesNotContain("private static bool TryResolveQuerySourceFreshContextLocal", rewriteSafetySource); - Assert.DoesNotContain("private static bool TryGetTransparentQueryInvocationSource", rewriteSafetySource); - Assert.DoesNotContain("private static bool IsSingleSourceTransparentQueryMethod", rewriteSafetySource); - - var querySource = File.ReadAllText(querySourcePath); - Assert.Contains("private static bool TryResolveQuerySourceFreshContextLocal", querySource); - Assert.Contains("private static bool TryGetTransparentQueryInvocationSource", querySource); - Assert.Contains("private static bool IsSingleSourceTransparentQueryMethod", querySource); - } - - [Fact] - public void LC036_LocalFunctionCallbackCapture_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC036_DbContextCapturedAcrossThreads"); - var captureAnalysisPath = Path.Combine(analyzerDir, "DbContextCapturedAcrossThreadsCaptureAnalysis.cs"); - var localFunctionPath = Path.Combine(analyzerDir, "DbContextCapturedAcrossThreadsLocalFunctionCapture.cs"); - - Assert.True(File.Exists(localFunctionPath), "LC036 local-function callback capture should live in a focused partial file."); - - var captureAnalysisSource = File.ReadAllText(captureAnalysisPath); - Assert.DoesNotContain("private static bool TryFindCapturedDbContextInLocalFunctionCallback", captureAnalysisSource); - - var localFunctionSource = File.ReadAllText(localFunctionPath); - Assert.Contains("private static bool TryFindCapturedDbContextInLocalFunctionCallback", localFunctionSource); - Assert.Contains("LocalFunctionStatementSyntax", localFunctionSource); - Assert.Contains("MethodKind.LocalFunction", localFunctionSource); - } - - [Fact] - public void LC035_WhereChainTraversal_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC035_MissingWhereBeforeExecuteDeleteUpdate"); - var analyzerPath = Path.Combine(analyzerDir, "MissingWhereBeforeExecuteDeleteUpdateAnalyzer.cs"); - var whereChainPath = Path.Combine(analyzerDir, "MissingWhereBeforeExecuteDeleteUpdateWhereChain.cs"); - - Assert.True(File.Exists(whereChainPath), "LC035 Where-chain traversal should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static bool HasWhereInChain", analyzerSource); - Assert.DoesNotContain("private static bool IsKnownLinqWhere", analyzerSource); - Assert.DoesNotContain("private static bool HasQuerySyntaxWhere", analyzerSource); - - var whereChainSource = File.ReadAllText(whereChainPath); - Assert.Contains("private static bool HasWhereInChain", whereChainSource); - Assert.Contains("private static bool IsKnownLinqWhere", whereChainSource); - Assert.Contains("private static bool HasQuerySyntaxWhere", whereChainSource); - } - - [Fact] - public void LC002_FixerRedundantMaterializationRewrite_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC002_PrematureMaterialization"); - var fixerPath = Path.Combine(fixerDir, "PrematureMaterializationFixer.cs"); - var redundantRewritePath = Path.Combine(fixerDir, "PrematureMaterializationFixerRedundantMaterialization.cs"); - - Assert.True(File.Exists(redundantRewritePath), "LC002 redundant-materialization fixer rewrite should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.DoesNotContain("private static async Task RemoveRedundantMaterializationAsync", fixerSource); - - var redundantRewriteSource = File.ReadAllText(redundantRewritePath); - Assert.Contains("private static async Task RemoveRedundantMaterializationAsync", redundantRewriteSource); - Assert.Contains("TryGetInlineMaterializerParts", redundantRewriteSource); - Assert.Contains("Formatter.Annotation", redundantRewriteSource); - } - - [Fact] - public void LC037_JumpSkippingFlow_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var terminationFlowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionTerminationFlow.cs"); - var jumpFlowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionJumpFlow.cs"); - - Assert.True(File.Exists(jumpFlowPath), "LC037 jump/reference skipping flow should live in a focused partial file."); - - var terminationFlowSource = File.ReadAllText(terminationFlowPath); - Assert.DoesNotContain("private static bool HasPotentiallySkippingAncestor", terminationFlowSource); - Assert.DoesNotContain("private static bool ContainsPosition", terminationFlowSource); - Assert.DoesNotContain("private static bool JumpSkipsReference", terminationFlowSource); - - var jumpFlowSource = File.ReadAllText(jumpFlowPath); - Assert.Contains("private static bool HasPotentiallySkippingAncestor", jumpFlowSource); - Assert.Contains("private static bool ContainsPosition", jumpFlowSource); - Assert.Contains("private static bool JumpSkipsReference", jumpFlowSource); - } - - [Fact] - public void LocalAssignmentCache_RootScan_LivesInDedicatedPartial() - { - var extensionsDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Extensions"); - var cachePath = Path.Combine(extensionsDir, "LocalAssignmentCache.cs"); - var rootScanPath = Path.Combine(extensionsDir, "LocalAssignmentCacheRootScan.cs"); - - Assert.True(File.Exists(rootScanPath), "LocalAssignmentCache root scanning should live in a focused partial file."); - - var cacheSource = File.ReadAllText(cachePath); - Assert.Contains("internal static partial class LocalAssignmentCache", cacheSource); - Assert.DoesNotContain("private sealed class RootScan", cacheSource); - Assert.DoesNotContain("public static RootScan Build", cacheSource); - - var rootScanSource = File.ReadAllText(rootScanPath); - Assert.Contains("private sealed class RootScan", rootScanSource); - Assert.Contains("public static RootScan Build", rootScanSource); - Assert.Contains("private static void Add", rootScanSource); - } - - [Fact] - public void LC002_ProviderSafeStringMethods_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC002_PrematureMaterialization"); - var providerSafePath = Path.Combine(analyzerDir, "PrematureMaterializationProviderSafeExpressions.cs"); - var stringMethodsPath = Path.Combine(analyzerDir, "PrematureMaterializationProviderSafeStringMethods.cs"); - - Assert.True(File.Exists(stringMethodsPath), "LC002 provider-safe string method rules should live in a focused partial file."); - - var providerSafeSource = File.ReadAllText(providerSafePath); - Assert.DoesNotContain("private static bool IsAllowedProviderSafeStringMethod", providerSafeSource); - Assert.DoesNotContain("private static bool HasStringComparisonParameter", providerSafeSource); - - var stringMethodsSource = File.ReadAllText(stringMethodsPath); - Assert.Contains("private static bool IsAllowedProviderSafeStringMethod", stringMethodsSource); - Assert.Contains("private static bool HasStringComparisonParameter", stringMethodsSource); - Assert.Contains("StringComparison", stringMethodsSource); - } - - [Fact] - public void LC016_FixerStaticLambdaSafety_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC016_AvoidDateTimeNow"); - var fixerPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixer.cs"); - var staticLambdaPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixerStaticLambda.cs"); - - Assert.True(File.Exists(staticLambdaPath), "LC016 static-lambda fixer safety should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.DoesNotContain("private static bool IsInsideStaticLambda", fixerSource); - Assert.DoesNotContain("private static bool HasStaticModifier", fixerSource); - - var staticLambdaSource = File.ReadAllText(staticLambdaPath); - Assert.Contains("private static bool IsInsideStaticLambda", staticLambdaSource); - Assert.Contains("private static bool HasStaticModifier", staticLambdaSource); - Assert.Contains("SyntaxKind.StaticKeyword", staticLambdaSource); - } - - [Fact] - public void LC044_ReachabilityBlockNavigation_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC044_AsNoTrackingThenModify"); - var reachabilityPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyReachability.cs"); - var blockNavigationPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyReachabilityBlocks.cs"); - - Assert.True(File.Exists(blockNavigationPath), "LC044 reachability block navigation should live in a focused partial file."); - - var reachabilitySource = File.ReadAllText(reachabilityPath); - Assert.DoesNotContain("private static bool IsBlockAncestor", reachabilitySource); - Assert.DoesNotContain("private static IOperation? FindDirectChildOperationContainingSpan", reachabilitySource); - Assert.DoesNotContain("private static IBlockOperation? FindEnclosingBlock", reachabilitySource); - - var blockNavigationSource = File.ReadAllText(blockNavigationPath); - Assert.Contains("private static bool IsBlockAncestor", blockNavigationSource); - Assert.Contains("private static IOperation? FindDirectChildOperationContainingSpan", blockNavigationSource); - Assert.Contains("private static IBlockOperation? FindEnclosingBlock", blockNavigationSource); - } - - [Fact] - public void LC030_LongLivedTypeRecognition_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC030_DbContextInSingleton"); - var evidencePath = Path.Combine(analyzerDir, "DbContextInSingletonLongLivedEvidence.cs"); - var typeRecognitionPath = Path.Combine(analyzerDir, "DbContextInSingletonLongLivedTypeRecognition.cs"); - - Assert.True(File.Exists(typeRecognitionPath), "LC030 long-lived/scoped type recognition helpers should live in a focused partial file."); - - var evidenceSource = File.ReadAllText(evidencePath); - Assert.DoesNotContain("private static bool IsKnownScopedType", evidenceSource); - Assert.DoesNotContain("private static bool HasConventionalMiddlewareSignature", evidenceSource); - Assert.DoesNotContain("private static bool ImplementsInterface", evidenceSource); - Assert.DoesNotContain("private static bool InheritsFrom", evidenceSource); - - var typeRecognitionSource = File.ReadAllText(typeRecognitionPath); - Assert.Contains("private static bool IsKnownScopedType", typeRecognitionSource); - Assert.Contains("private static bool HasConventionalMiddlewareSignature", typeRecognitionSource); - Assert.Contains("private static bool ImplementsInterface", typeRecognitionSource); - Assert.Contains("private static bool InheritsFrom", typeRecognitionSource); - } - - [Fact] - public void LC012_FixerSaveChangesBranchExclusion_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC012_OptimizeRemoveRange"); - var saveChangesSafetyPath = Path.Combine(analyzerDir, "OptimizeRemoveRangeFixerSaveChangesSafety.cs"); - var branchExclusionPath = Path.Combine(analyzerDir, "OptimizeRemoveRangeFixerSaveChangesBranchExclusion.cs"); - - Assert.True(File.Exists(branchExclusionPath), "LC012 fixer save-changes branch exclusion should live in a focused partial file."); - - var saveChangesSafetySource = File.ReadAllText(saveChangesSafetyPath); - Assert.DoesNotContain("private static bool AreMutuallyExclusiveBranches", saveChangesSafetySource); - Assert.DoesNotContain("private static SyntaxNode? GetContainingIfBranch", saveChangesSafetySource); - Assert.DoesNotContain("private static SwitchSectionSyntax? GetContainingSwitchSection", saveChangesSafetySource); - - var branchExclusionSource = File.ReadAllText(branchExclusionPath); - Assert.Contains("private static bool AreMutuallyExclusiveBranches", branchExclusionSource); - Assert.Contains("private static SyntaxNode? GetContainingIfBranch", branchExclusionSource); - Assert.Contains("private static SwitchSectionSyntax? GetContainingSwitchSection", branchExclusionSource); - } - - [Fact] - public void LC037_StringBuilderIdentityReferenceTraversal_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC037_RawSqlStringConstruction"); - var identityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderIdentity.cs"); - var referenceTraversalPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderIdentityReferences.cs"); - - Assert.True(File.Exists(referenceTraversalPath), "LC037 StringBuilder identity reference traversal should live in a focused partial file."); - - var identitySource = File.ReadAllText(identityPath); - Assert.DoesNotContain("private static bool MayReferenceIdentity", identitySource); - - var referenceTraversalSource = File.ReadAllText(referenceTraversalPath); - Assert.Contains("private static bool MayReferenceIdentity", referenceTraversalSource); - Assert.Contains("IInterpolatedStringOperation", referenceTraversalSource); - Assert.Contains("IObjectCreationOperation", referenceTraversalSource); - Assert.Contains("IInvocationOperation", referenceTraversalSource); - } - - [Fact] - public void LC013_AssignedValueCollection_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC013_DisposedContextQuery"); - var originResolutionPath = Path.Combine(analyzerDir, "DisposedContextQueryAssignedOriginResolution.cs"); - var assignedValuesPath = Path.Combine(analyzerDir, "DisposedContextQueryAssignedValues.cs"); - - Assert.True(File.Exists(assignedValuesPath), "LC013 assigned-value collection should live in a focused partial file."); - - var originResolutionSource = File.ReadAllText(originResolutionPath); - Assert.DoesNotContain("private static bool TryGetSingleAssignedValue", originResolutionSource); - Assert.DoesNotContain("private static List GetAssignedValues", originResolutionSource); - Assert.DoesNotContain("private static bool IsLocalTarget", originResolutionSource); - Assert.DoesNotContain("private static IEnumerable EnumerateOperations", originResolutionSource); - - var assignedValuesSource = File.ReadAllText(assignedValuesPath); - Assert.Contains("private static bool TryGetSingleAssignedValue", assignedValuesSource); - Assert.Contains("private static List GetAssignedValues", assignedValuesSource); - Assert.Contains("private static bool IsLocalTarget", assignedValuesSource); - Assert.Contains("private static IEnumerable EnumerateOperations", assignedValuesSource); - } - - [Fact] - public void LC041_FixerInvocationRewrite_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC041_SingleEntityScalarProjection"); - var fixerPath = Path.Combine(fixerDir, "SingleEntityScalarProjectionFixer.cs"); - var rewritePath = Path.Combine(fixerDir, "SingleEntityScalarProjectionFixerRewrite.cs"); - - Assert.True(File.Exists(rewritePath), "LC041 fixer invocation rewrite should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.DoesNotContain("private static ExpressionSyntax RewriteInvocation", fixerSource); - Assert.DoesNotContain("private static int FindPredicateIndex", fixerSource); - - var rewriteSource = File.ReadAllText(rewritePath); - Assert.Contains("private static ExpressionSyntax RewriteInvocation", rewriteSource); - Assert.Contains("private static int FindPredicateIndex", rewriteSource); - Assert.Contains(".Select(x => x.", rewriteSource); - } - - [Fact] - public void LC002_MaterializerClassification_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "MaterializationAndProjection", - "LC002_PrematureMaterialization"); - var methodRulesPath = Path.Combine(analyzerDir, "PrematureMaterializationMethodRules.cs"); - var materializersPath = Path.Combine(analyzerDir, "PrematureMaterializationMaterializerRules.cs"); - - Assert.True(File.Exists(materializersPath), "LC002 materializer classification rules should live in a focused partial file."); - - var methodRulesSource = File.ReadAllText(methodRulesPath); - Assert.DoesNotContain("private static bool IsMaterializingMethod", methodRulesSource); - Assert.DoesNotContain("private static bool IsDirectCollectionMaterializer", methodRulesSource); - Assert.DoesNotContain("private static bool IsMaterializingConstructor", methodRulesSource); - - var materializersSource = File.ReadAllText(materializersPath); - Assert.Contains("private static bool IsMaterializingMethod", materializersSource); - Assert.Contains("private static bool IsDirectCollectionMaterializer", materializersSource); - Assert.Contains("private static bool IsDeduplicatingSetMaterializer", materializersSource); - Assert.Contains("private static bool IsKeyedOrGroupedMaterializer", materializersSource); - Assert.Contains("private static bool IsMaterializingConstructor", materializersSource); - } - - [Fact] - public void LC044_TrackingStateDetachInvalidation_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC044_AsNoTrackingThenModify"); - var trackingStatePath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyTrackingState.cs"); - var detachInvalidationPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyTrackingDetachInvalidation.cs"); - - Assert.True(File.Exists(detachInvalidationPath), "LC044 detach/tracker-clear invalidation should live in a focused partial file."); - - var trackingStateSource = File.ReadAllText(trackingStatePath); - Assert.DoesNotContain("private static bool HasInterveningDetach", trackingStateSource); - - var detachInvalidationSource = File.ReadAllText(detachInvalidationPath); - Assert.Contains("private static bool HasInterveningDetach", detachInvalidationSource); - Assert.Contains("scan.DetachesByLocal", detachInvalidationSource); - Assert.Contains("scan.TrackerClears", detachInvalidationSource); - } - - [Fact] - public void LC025_SourceResolutionConditionalOrigins_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC025_AsNoTrackingWithUpdate"); - var sourceResolutionPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateSourceResolution.cs"); - var conditionalOriginsPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateSourceConditionalOrigins.cs"); - - Assert.True(File.Exists(conditionalOriginsPath), "LC025 conditional origin state should live in a focused partial file."); - - var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); - Assert.DoesNotContain("private readonly struct LocalOrigin", sourceResolutionSource); - Assert.DoesNotContain("private static bool IsConditionalRelativeTo", sourceResolutionSource); - - var conditionalOriginsSource = File.ReadAllText(conditionalOriginsPath); - Assert.Contains("private readonly struct LocalOrigin", conditionalOriginsSource); - Assert.Contains("private static bool IsConditionalRelativeTo", conditionalOriginsSource); - Assert.Contains("SwitchExpressionSyntax", conditionalOriginsSource); - Assert.Contains("CommonForEachStatementSyntax", conditionalOriginsSource); - } - - [Fact] - public void LC034_SqlSafetyDetection_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC034_AvoidExecuteSqlRawWithInterpolation"); - var analyzerPath = Path.Combine(analyzerDir, "AvoidExecuteSqlRawWithInterpolationAnalyzer.cs"); - var sqlSafetyPath = Path.Combine(analyzerDir, "AvoidExecuteSqlRawWithInterpolationSqlSafety.cs"); - - Assert.True(File.Exists(sqlSafetyPath), "LC034 SQL safety classification should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AvoidExecuteSqlRawWithInterpolationAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool IsPotentiallyUnsafeSql", analyzerSource); - Assert.DoesNotContain("private static bool IsUnsafeConcatenation", analyzerSource); - Assert.DoesNotContain("private static bool HasNonConstantInterpolation", analyzerSource); - - var sqlSafetySource = File.ReadAllText(sqlSafetyPath); - Assert.Contains("private static bool IsPotentiallyUnsafeSql", sqlSafetySource); - Assert.Contains("private static bool IsUnsafeConcatenation", sqlSafetySource); - Assert.Contains("private static bool HasNonConstantInterpolation", sqlSafetySource); - Assert.Contains("IInterpolatedStringOperation", sqlSafetySource); - } - - [Fact] - public void LC005_FixerRewriteValidation_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "QueryShapeAndTranslation", - "LC005_MultipleOrderBy"); - var fixerPath = Path.Combine(fixerDir, "MultipleOrderByFixer.cs"); - var validationPath = Path.Combine(fixerDir, "MultipleOrderByFixerRewriteValidation.cs"); - - Assert.True(File.Exists(validationPath), "LC005 fixer rewrite validation should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class MultipleOrderByFixer", fixerSource); - Assert.DoesNotContain("private static bool CanRewriteToThenBy", fixerSource); - Assert.DoesNotContain("private static ExpressionSyntax? GetLogicalReceiverExpression", fixerSource); - Assert.DoesNotContain("private static bool IsOrderedSequence", fixerSource); - - var validationSource = File.ReadAllText(validationPath); - Assert.Contains("private static bool CanRewriteToThenBy", validationSource); - Assert.Contains("private static ExpressionSyntax? GetLogicalReceiverExpression", validationSource); - Assert.Contains("private static bool IsOrderedSequence", validationSource); - Assert.Contains("IOrderedQueryable", validationSource); - } - - [Fact] - public void LC008_FixerAwaitContextValidation_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC008_SyncBlocker"); - var fixerPath = Path.Combine(fixerDir, "SyncBlockerFixer.cs"); - var awaitContextPath = Path.Combine(fixerDir, "SyncBlockerFixerAwaitContext.cs"); - - Assert.True(File.Exists(awaitContextPath), "LC008 fixer await-context validation should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class SyncBlockerFixer", fixerSource); - Assert.DoesNotContain("private static bool IsInvalidAwaitContext", fixerSource); - Assert.DoesNotContain("FromClauseSyntax", fixerSource); - Assert.DoesNotContain("AnonymousFunctionExpressionSyntax", fixerSource); - - var awaitContextSource = File.ReadAllText(awaitContextPath); - Assert.Contains("private static bool IsInvalidAwaitContext", awaitContextSource); - Assert.Contains("FromClauseSyntax", awaitContextSource); - Assert.Contains("JoinClauseSyntax", awaitContextSource); - Assert.Contains("AnonymousFunctionExpressionSyntax", awaitContextSource); - Assert.Contains("LocalFunctionStatementSyntax", awaitContextSource); - } - - [Fact] - public void LC018_AnalyzerSqlSafetyDetection_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC018_AvoidFromSqlRawWithInterpolation"); - var analyzerPath = Path.Combine(analyzerDir, "AvoidFromSqlRawWithInterpolationAnalyzer.cs"); - var sqlSafetyPath = Path.Combine(analyzerDir, "AvoidFromSqlRawWithInterpolationAnalyzerSqlSafety.cs"); - - Assert.True(File.Exists(sqlSafetyPath), "LC018 analyzer SQL safety classification should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class AvoidFromSqlRawWithInterpolationAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool IsPotentiallyUnsafe", analyzerSource); - Assert.DoesNotContain("private static bool IsUnsafeConcatenation", analyzerSource); - Assert.DoesNotContain("private static bool HasNonConstantInterpolation", analyzerSource); - - var sqlSafetySource = File.ReadAllText(sqlSafetyPath); - Assert.Contains("private static bool IsPotentiallyUnsafe", sqlSafetySource); - Assert.Contains("private static bool IsUnsafeConcatenation", sqlSafetySource); - Assert.Contains("private static bool HasNonConstantInterpolation", sqlSafetySource); - Assert.Contains("IInterpolatedStringOperation", sqlSafetySource); - } - - [Fact] - public void LC042_QueryMethodLookupTables_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC042_MissingQueryTags"); - var analyzerPath = Path.Combine(analyzerDir, "MissingQueryTagsAnalyzer.cs"); - var methodSetsPath = Path.Combine(analyzerDir, "MissingQueryTagsMethodSets.cs"); - - Assert.True(File.Exists(methodSetsPath), "LC042 query method lookup tables should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static readonly ImmutableHashSet TargetMethods", analyzerSource); - Assert.DoesNotContain("private static readonly ImmutableHashSet QuerySteps", analyzerSource); - - var methodSetsSource = File.ReadAllText(methodSetsPath); - Assert.Contains("private static readonly ImmutableHashSet TargetMethods", methodSetsSource); - Assert.Contains("private static readonly ImmutableHashSet QuerySteps", methodSetsSource); - Assert.Contains("\"TagWithCallSite\"", methodSetsSource); - Assert.Contains("\"ToHashSetAsync\"", methodSetsSource); - } - - [Fact] - public void LC028_ThenIncludeChainAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC028_DeepThenInclude"); - var analyzerPath = Path.Combine(analyzerDir, "DeepThenIncludeAnalyzer.cs"); - var chainAnalysisPath = Path.Combine(analyzerDir, "DeepThenIncludeChainAnalysis.cs"); - - Assert.True(File.Exists(chainAnalysisPath), "LC028 ThenInclude chain analysis should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class DeepThenIncludeAnalyzer", analyzerSource); - Assert.DoesNotContain("private static int CountThenIncludeDepth", analyzerSource); - Assert.DoesNotContain("private static Location GetDiagnosticLocation", analyzerSource); - Assert.DoesNotContain("TextSpan.FromBounds", analyzerSource); - - var chainAnalysisSource = File.ReadAllText(chainAnalysisPath); - Assert.Contains("private static int CountThenIncludeDepth", chainAnalysisSource); - Assert.Contains("private static Location GetDiagnosticLocation", chainAnalysisSource); - Assert.Contains("TextSpan.FromBounds", chainAnalysisSource); - Assert.Contains("MemberAccessExpressionSyntax", chainAnalysisSource); - } - - [Fact] - public void SymbolAnalysis_PrimaryKeyDiscovery_LivesInDedicatedPartial() - { - var extensionsDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Extensions"); - var symbolExtensionsPath = Path.Combine(extensionsDir, "SymbolAnalysisExtensions.cs"); - var primaryKeyPath = Path.Combine(extensionsDir, "SymbolAnalysisPrimaryKeyExtensions.cs"); - - Assert.True(File.Exists(primaryKeyPath), "Primary-key symbol discovery should live in a focused extension partial file."); - - var symbolExtensionsSource = File.ReadAllText(symbolExtensionsPath); - Assert.DoesNotContain("public static string? TryFindPrimaryKey", symbolExtensionsSource); - Assert.DoesNotContain("private static bool IsDataAnnotationsKeyAttribute", symbolExtensionsSource); - - var primaryKeySource = File.ReadAllText(primaryKeyPath); - Assert.Contains("public static string? TryFindPrimaryKey", primaryKeySource); - Assert.Contains("private static bool IsDataAnnotationsKeyAttribute", primaryKeySource); - Assert.Contains("System.ComponentModel.DataAnnotations", primaryKeySource); - } - - [Fact] - public void LC019_ConditionalIncludePathAnalysis_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC019_ConditionalInclude"); - var analyzerPath = Path.Combine(analyzerDir, "ConditionalIncludeAnalyzer.cs"); - var pathAnalysisPath = Path.Combine(analyzerDir, "ConditionalIncludePathAnalysis.cs"); - - Assert.True(File.Exists(pathAnalysisPath), "LC019 conditional include path traversal should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.Contains("public sealed partial class ConditionalIncludeAnalyzer", analyzerSource); - Assert.DoesNotContain("private static bool HasConditionalIncludePath", analyzerSource); - Assert.DoesNotContain("private static bool HasConditionalIncludeInvocationSource", analyzerSource); - - var pathAnalysisSource = File.ReadAllText(pathAnalysisPath); - Assert.Contains("private static bool HasConditionalIncludePath", pathAnalysisSource); - Assert.Contains("private static bool HasConditionalIncludeInvocationSource", pathAnalysisSource); - Assert.Contains("IConditionalOperation or ICoalesceOperation", pathAnalysisSource); - } - - [Fact] - public void LC026_FixerExplicitTokenArgument_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ExecutionAndAsync", - "LC026_MissingCancellationToken"); - var fixerPath = Path.Combine(fixerDir, "MissingCancellationTokenFixer.cs"); - var explicitArgumentPath = Path.Combine(fixerDir, "MissingCancellationTokenFixerExplicitArgument.cs"); - - Assert.True(File.Exists(explicitArgumentPath), "LC026 fixer explicit-token argument lookup should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class MissingCancellationTokenFixer", fixerSource); - Assert.DoesNotContain("private static ArgumentSyntax? FindExplicitCancellationTokenArgument", fixerSource); - Assert.DoesNotContain("private static bool IsCancellationTokenParameter", fixerSource); - - var explicitArgumentSource = File.ReadAllText(explicitArgumentPath); - Assert.Contains("private static ArgumentSyntax? FindExplicitCancellationTokenArgument", explicitArgumentSource); - Assert.Contains("private static bool IsCancellationTokenParameter", explicitArgumentSource); - Assert.Contains("IInvocationOperation", explicitArgumentSource); - } - - [Fact] - public void LC032_ScalarTypeClassification_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC032_ExecuteUpdateForBulkUpdates"); - var assignmentAnalysisPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesAssignmentAnalysis.cs"); - var scalarTypesPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesScalarTypes.cs"); - - Assert.True(File.Exists(scalarTypesPath), "LC032 scalar type classification should live in a focused partial file."); - - var assignmentAnalysisSource = File.ReadAllText(assignmentAnalysisPath); - Assert.DoesNotContain("private static bool IsScalarLikeType", assignmentAnalysisSource); - Assert.DoesNotContain("SymbolDisplayFormat.FullyQualifiedFormat", assignmentAnalysisSource); - - var scalarTypesSource = File.ReadAllText(scalarTypesPath); - Assert.Contains("private static bool IsScalarLikeType", scalarTypesSource); - Assert.Contains("SymbolDisplayFormat.FullyQualifiedFormat", scalarTypesSource); - Assert.Contains("global::System.DateTimeOffset", scalarTypesSource); - } - - [Fact] - public void LC032_QueryStepLookupTables_LiveInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "BulkOperationsAndSetBasedWrites", - "LC032_ExecuteUpdateForBulkUpdates"); - var queryAnalysisPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesQueryAnalysis.cs"); - var queryStepsPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesQuerySteps.cs"); - - Assert.True(File.Exists(queryStepsPath), "LC032 allowed query/materializer step lookup tables should live in a focused partial file."); - - var queryAnalysisSource = File.ReadAllText(queryAnalysisPath); - Assert.DoesNotContain("private static readonly ImmutableHashSet AllowedQuerySteps", queryAnalysisSource); - Assert.DoesNotContain("private static readonly ImmutableHashSet MaterializerSteps", queryAnalysisSource); - - var queryStepsSource = File.ReadAllText(queryStepsPath); - Assert.Contains("private static readonly ImmutableHashSet AllowedQuerySteps", queryStepsSource); - Assert.Contains("private static readonly ImmutableHashSet MaterializerSteps", queryStepsSource); - Assert.Contains("\"TagWithCallSite\"", queryStepsSource); - Assert.Contains("\"ToArrayAsync\"", queryStepsSource); - } - - [Fact] - public void LC045_FixerQuerySourceResolution_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC045_MissingInclude"); - var fixerPath = Path.Combine(fixerDir, "MissingIncludeFixer.cs"); - var querySourcePath = Path.Combine(fixerDir, "MissingIncludeFixerQuerySource.cs"); - - Assert.True(File.Exists(querySourcePath), "LC045 fixer query-source resolution should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class MissingIncludeFixer", fixerSource); - Assert.DoesNotContain("private static async Task GetQueryableSourceAsync", fixerSource); - Assert.DoesNotContain("semanticModel?.GetTypeInfo(querySource, cancellationToken)", fixerSource); - - var querySource = File.ReadAllText(querySourcePath); - Assert.Contains("private static async Task GetQueryableSourceAsync", querySource); - Assert.Contains("semanticModel?.GetTypeInfo(querySource, cancellationToken)", querySource); - Assert.Contains("query source", querySource); - } - - [Fact] - public void LC006_ReceiverChainTraversal_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC006_CartesianExplosion"); - var chainAnalysisPath = Path.Combine(analyzerDir, "CartesianExplosionChainAnalysis.cs"); - var receiverChainPath = Path.Combine(analyzerDir, "CartesianExplosionReceiverChain.cs"); - - Assert.True(File.Exists(receiverChainPath), "LC006 receiver-chain traversal should live in a focused partial file."); - - var chainAnalysisSource = File.ReadAllText(chainAnalysisPath); - Assert.DoesNotContain("private static ImmutableArray CollectReceiverChainInvocations", chainAnalysisSource); - Assert.DoesNotContain("private static bool HasRelevantQueryOperatorAncestor", chainAnalysisSource); - Assert.DoesNotContain("private static bool InvocationUsesReceiverChain", chainAnalysisSource); - - var receiverChainSource = File.ReadAllText(receiverChainPath); - Assert.Contains("private static ImmutableArray CollectReceiverChainInvocations", receiverChainSource); - Assert.Contains("private static bool HasRelevantQueryOperatorAncestor", receiverChainSource); - Assert.Contains("private static bool InvocationUsesReceiverChain", receiverChainSource); - Assert.Contains("LocalAssignmentCache.TryGetSingleAssignedValueBefore", receiverChainSource); - } - - [Fact] - public void LC011_DbSetMemberExtraction_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var analyzerPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAnalyzer.cs"); - var dbSetMembersPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyDbSetMembers.cs"); - - Assert.True(File.Exists(dbSetMembersPath), "LC011 DbSet member extraction should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static bool TryGetDbSetMember", analyzerSource); - Assert.DoesNotContain("field.Name.StartsWith(\"<\", StringComparison.Ordinal)", analyzerSource); - - var dbSetMembersSource = File.ReadAllText(dbSetMembersPath); - Assert.Contains("private static bool TryGetDbSetMember", dbSetMembersSource); - Assert.Contains("IPropertySymbol property", dbSetMembersSource); - Assert.Contains("IFieldSymbol field", dbSetMembersSource); - Assert.Contains("field.Name.StartsWith(\"<\", StringComparison.Ordinal)", dbSetMembersSource); - } - - [Fact] - public void LC011_FixerEntityTypeResolution_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC011_EntityMissingPrimaryKey"); - var fixerPath = Path.Combine(fixerDir, "EntityMissingPrimaryKeyFixer.cs"); - var entityTypePath = Path.Combine(fixerDir, "EntityMissingPrimaryKeyFixerEntityType.cs"); - - Assert.True(File.Exists(entityTypePath), "LC011 fixer entity-type and Id-member checks should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class EntityMissingPrimaryKeyFixer", fixerSource); - Assert.DoesNotContain("private static bool TryGetEntityType", fixerSource); - Assert.DoesNotContain("private static bool HasIdMember", fixerSource); - - var entityTypeSource = File.ReadAllText(entityTypePath); - Assert.Contains("private static bool TryGetEntityType", entityTypeSource); - Assert.Contains("private static bool HasIdMember", entityTypeSource); - Assert.Contains("StringComparison.OrdinalIgnoreCase", entityTypeSource); - } - - [Fact] - public void LC034_FixerSqlArgumentSafety_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "RawSqlAndSecurity", - "LC034_AvoidExecuteSqlRawWithInterpolation"); - var fixerPath = Path.Combine(fixerDir, "AvoidExecuteSqlRawWithInterpolationFixer.cs"); - var sqlArgumentPath = Path.Combine(fixerDir, "AvoidExecuteSqlRawWithInterpolationFixerSqlArgument.cs"); - - Assert.True(File.Exists(sqlArgumentPath), "LC034 fixer SQL argument and literal-safety helpers should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class AvoidExecuteSqlRawWithInterpolationFixer", fixerSource); - Assert.DoesNotContain("private static ArgumentSyntax? GetSqlArgument", fixerSource); - Assert.DoesNotContain("private static bool HasInterpolationInsideSqlStringLiteral", fixerSource); - Assert.DoesNotContain("private static void UpdateSqlDelimiterState", fixerSource); - - var sqlArgumentSource = File.ReadAllText(sqlArgumentPath); - Assert.Contains("private static ArgumentSyntax? GetSqlArgument", sqlArgumentSource); - Assert.Contains("private static bool HasInterpolationInsideSqlStringLiteral", sqlArgumentSource); - Assert.Contains("private static void UpdateSqlDelimiterState", sqlArgumentSource); - Assert.Contains("InterpolationSyntax when insideSqlStringLiteral", sqlArgumentSource); - } - - [Fact] - public void LC006_FixerSyntaxChainSearch_LivesInDedicatedPartial() - { - var fixerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "LoadingAndIncludes", - "LC006_CartesianExplosion"); - var fixerPath = Path.Combine(fixerDir, "CartesianExplosionFixer.cs"); - var syntaxChainPath = Path.Combine(fixerDir, "CartesianExplosionFixerSyntaxChain.cs"); - - Assert.True(File.Exists(syntaxChainPath), "LC006 fixer syntax-chain search helpers should live in a focused partial file."); - - var fixerSource = File.ReadAllText(fixerPath); - Assert.Contains("public sealed partial class CartesianExplosionFixer", fixerSource); - Assert.DoesNotContain("private static InvocationExpressionSyntax? FindEffectiveAsSingleQueryInvocation", fixerSource); - Assert.DoesNotContain("private static InvocationExpressionSyntax? FindFirstIncludeInvocation", fixerSource); - Assert.DoesNotContain("private static bool IsInvocationOf", fixerSource); - - var syntaxChainSource = File.ReadAllText(syntaxChainPath); - Assert.Contains("private static InvocationExpressionSyntax? FindEffectiveAsSingleQueryInvocation", syntaxChainSource); - Assert.Contains("private static InvocationExpressionSyntax? FindFirstIncludeInvocation", syntaxChainSource); - Assert.Contains("private static bool IsInvocationOf", syntaxChainSource); - Assert.Contains("\"AsSingleQuery\"", syntaxChainSource); - } - - [Fact] - public void LC027_CollectionTypeClassification_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "SchemaAndModeling", - "LC027_MissingExplicitForeignKey"); - var entityAnalysisPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyEntityAnalysis.cs"); - var collectionTypesPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyCollectionTypes.cs"); - - Assert.True(File.Exists(collectionTypesPath), "LC027 collection-type classification should live in a focused partial file."); - - var entityAnalysisSource = File.ReadAllText(entityAnalysisPath); - Assert.DoesNotContain("private static bool IsCollectionType", entityAnalysisSource); - Assert.DoesNotContain("IReadOnlyCollection", entityAnalysisSource); - Assert.DoesNotContain("iface.Name == \"IEnumerable\"", entityAnalysisSource); - - var collectionTypesSource = File.ReadAllText(collectionTypesPath); - Assert.Contains("private static bool IsCollectionType", collectionTypesSource); - Assert.Contains("System.Collections.Generic", collectionTypesSource); - Assert.Contains("IReadOnlyCollection", collectionTypesSource); - } - - [Fact] - public void LC009_MaterializerMethodClassification_LivesInDedicatedPartial() - { - var analyzerDir = Path.Combine( - _repoRoot, - "src", - "LinqContraband", - "Analyzers", - "ChangeTrackingAndContextLifetime", - "LC009_MissingAsNoTracking"); - var analyzerPath = Path.Combine(analyzerDir, "MissingAsNoTrackingAnalyzer.cs"); - var materializersPath = Path.Combine(analyzerDir, "MissingAsNoTrackingMaterializers.cs"); - - Assert.True(File.Exists(materializersPath), "LC009 materializer method classification should live in a focused partial file."); - - var analyzerSource = File.ReadAllText(analyzerPath); - Assert.DoesNotContain("private static bool IsEntityMaterializer", analyzerSource); - Assert.DoesNotContain("\"ToDictionaryAsync\"", analyzerSource); - - var materializersSource = File.ReadAllText(materializersPath); - Assert.Contains("private static bool IsEntityMaterializer", materializersSource); - Assert.Contains("\"ToDictionaryAsync\"", materializersSource); - Assert.Contains("\"SingleOrDefaultAsync\"", materializersSource); - } - - [Fact] - public void SampleDiagnosticsVerifier_SarifParsing_LivesInDedicatedPartial() - { - var verifierDir = Path.Combine( - _repoRoot, - "tools", - "SampleDiagnosticsVerifier"); - var programPath = Path.Combine(verifierDir, "Program.cs"); - var sarifPath = Path.Combine(verifierDir, "Program.SarifDiagnostics.cs"); - - Assert.True(File.Exists(sarifPath), "Sample diagnostics SARIF parsing should live in a focused Program partial file."); - - var programSource = File.ReadAllText(programPath); - Assert.DoesNotContain("static IReadOnlyList ParseDiagnostics", programSource); - Assert.DoesNotContain("static bool TryGetDiagnosticPath", programSource); - Assert.DoesNotContain("static string NormalizeRelativePath", programSource); - - var sarifSource = File.ReadAllText(sarifPath); - Assert.Contains("private static IReadOnlyList ParseDiagnostics", sarifSource); - Assert.Contains("private static bool TryGetDiagnosticPath", sarifSource); - Assert.Contains("private static string NormalizeRelativePath", sarifSource); - Assert.Contains("internal sealed record SampleDiagnostic", sarifSource); - } - - [Fact] - public void SampleDiagnosticsVerifier_ManifestParsing_LivesInDedicatedPartial() - { - var verifierDir = Path.Combine( - _repoRoot, - "tools", - "SampleDiagnosticsVerifier"); - var programPath = Path.Combine(verifierDir, "Program.cs"); - var manifestPath = Path.Combine(verifierDir, "Program.Manifest.cs"); - - Assert.True(File.Exists(manifestPath), "Sample diagnostics manifest parsing should live in a focused Program partial file."); - - var programSource = File.ReadAllText(programPath); - Assert.DoesNotContain("static SampleExpectationGroup[] LoadExpectationGroups", programSource); - Assert.DoesNotContain("static string[] LoadSafeSamplePaths", programSource); - Assert.DoesNotContain("internal sealed record SampleExpectation", programSource); - - var manifestSource = File.ReadAllText(manifestPath); - Assert.Contains("private static SampleExpectationGroup[] LoadExpectationGroups", manifestSource); - Assert.Contains("private static string[] LoadSafeSamplePaths", manifestSource); - Assert.Contains("internal sealed record SampleExpectation", manifestSource); - Assert.Contains("internal sealed record SampleExpectationGroup", manifestSource); - } - - [Fact] - public void SampleDiagnosticsVerifier_DotnetProcessExecution_LivesInDedicatedPartial() - { - var verifierDir = Path.Combine( - _repoRoot, - "tools", - "SampleDiagnosticsVerifier"); - var programPath = Path.Combine(verifierDir, "Program.cs"); - var dotnetPath = Path.Combine(verifierDir, "Program.Dotnet.cs"); - - Assert.True(File.Exists(dotnetPath), "Sample diagnostics dotnet process execution should live in a focused Program partial file."); - - var programSource = File.ReadAllText(programPath); - Assert.DoesNotContain("static string RunDotnetBuild", programSource); - Assert.DoesNotContain("static void RunDotnetRestore", programSource); - Assert.DoesNotContain("static string Quote", programSource); - - var dotnetSource = File.ReadAllText(dotnetPath); - Assert.Contains("private static string RunDotnetBuild", dotnetSource); - Assert.Contains("private static void RunDotnetRestore", dotnetSource); - Assert.Contains("private static string Quote", dotnetSource); - Assert.Contains("ProcessStartInfo", dotnetSource); - } -} +Yx-jםi+j[hܢ^vT赩hnXz]\[\[KS\[[]‚[Y\XH[P۝X[ \ː\]X\N‚XXX[Y\[[^\[[\^][ە\žˆ]]HXYۛH[ܙ\ԛH\]ܞS^[] ]\]ܞT + +N‚јXBXXY ԛ[[Y\]R[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH \ӛX[[[YHNˆ\[]H] X[J[[^\\\ӛX[[[YT[ȊNˆ\[Y\]H] X[J[[^\\\ӛX[[[YT[[Y\˘ȊN‚\\ YJ[K^\[Y\] +K  \[[H[]H[H\Y\X[[KN‚\[\HH[KXY[^ +[] +Nˆ\\ \ӛ۝Z[[\[XYۛHX]]][ۑ[H[\JNˆ\\ \ӛ۝Z[[\[XYۛHXX]X[H[\JNˆ\\ \ӛ۝Z[[\[XYۛHX]X[H[\JNˆ\\ \ӛ۝Z[[\[XYۛHXX\X\[H[\JNˆ\\ \ӛ۝Z[[\[XYۛHX]P[\[H[\JN‚\[Y\\HH[KXY[^ +[Y\] +Nˆ\\ ۝Z[[\[XYۛHX]]][ۑ[H[Y\\JNˆ\\ ۝Z[[\[XYۛHXX]X[H[Y\\JNˆ\\ ۝Z[[\[XYۛHX]X[H[Y\\JNˆ\\ ۝Z[[\[XYۛHXX\X\[H[Y\\JNˆ\\ ۝Z[[\[XYۛHX]P[\[H[Y\\JNˆBјXBXXY ԛ[\][۔Xܙ[]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH \ӛX[[[YHNˆ\[]H] X[J[[^\\\ӛX[[[YT[ȊNˆ\\][۔Xܙ[]H] X[J[[^\\\ӛX[[[YT[\][۔Xܙ[˘ȊN‚\\ YJ[K^\\][۔Xܙ[] +K  \[\][ۈXܙ[[]H[H\Y\X[[KN‚\[\HH[KXY[^ +[] +Nˆ\\ \ӛ۝Z[]]H]XY[P\YۛY[[\JNˆ\\ \ӛ۝Z[]]H]XY[T\S]]][ۈ[\JNˆ\\ \ӛ۝Z[]]H]XY[R[][ۈ[\JN‚\\][۔Xܙ[\HH[KXY[^ +\][۔Xܙ[] +Nˆ\\ ۝Z[]]H]XY[P\YۛY[\][۔Xܙ[\JNˆ\\ ۝Z[]]H]XY[T\S]]][ۈ\][۔Xܙ[\JNˆ\\ ۝Z[]]H]XY[R[][ۈ\][۔Xܙ[\JNˆ\\ ۝Z[T\Q[T]P\YۛY[\][۔Xܙ[\JNˆ\\ ۝Z[T\TX]X[][ۈ\][۔Xܙ[\JNˆ\\ ۝Z[T\UX\X\\][۔Xܙ[\JNˆBјXBXXY [ۘ[۝]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH \ӛX[[[YHNˆ\XXX[]T]H] X[J[[^\\\ӛX[[[YTXXX[]KȊNˆ\[ۘ[]H] X[J[[^\\\ӛX[[[YS[ۘ[˘ȊN‚\\ YJ[K^\[ۘ[] +K [ۘ[[ XXX[]H[\[]H[H\Y\X[[KN‚\XXX[]T\HH[KXY[^ +XXX[]T] +Nˆ\\ \ӛ۝Z[]]H]X\ә\Y[\[ۘ[۝ȋXXX[]T\JNˆ\\ \ӛ۝Z[]]H]XY][Y[XZ\М[X[]ܞHXXX[]T\JNˆ\\ \ӛ۝Z[]]H]X][Y[\]\XXX[]T\JNˆ\\ \ӛ۝Z[]]H]X[\]\XXX[]T\JN‚\[ۘ[\HH[KXY[^ +[ۘ[] +Nˆ\\ ۝Z[]]H]X\ә\Y[\[ۘ[۝ȋ[ۘ[\JNˆ\\ ۝Z[]]H]XY][Y[XZ\М[X[]ܞH[ۘ[\JNˆ\\ ۝Z[]]H]X][Y[\]\[ۘ[\JNˆ\\ ۝Z[]]H]X[\]\[ۘ[\JNˆBјXBXXY ]Y\T\T\][ۗ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH \ӛX[[[YHNˆ\[[^\]H] X[J[[^\\\ӛX[[[YP[[^\ȊNˆ\]Y\T\T]H] X[J[[^\\\ӛX[[[YT]Y\T\KȊN‚\\ YJ[K^\]Y\T\T] +K \ӛX[]Y\KX]\X[^][ۈ\H\][ۈ[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ +[[^\] +Nˆ\\ ۝Z[XXX[Y\X[\\ӛX[[[YP[[^\[[^\\JNˆ\\ \ӛ۝Z[]]H]X\\ӛX[X]\X[^][ۈ[[^\\JNˆ\\ \ӛ۝Z[]]H]XZ[۝Z[\ӛX[ȋ[[^\\JNˆ\\ \ӛ۝Z[]]H]XQ]]Y\P۝^[X[[^\\JN‚\]Y\T\HH[KXY[^ +]Y\T\T] +Nˆ\\ ۝Z[]]H]X\\ӛX[X]\X[^][ۈ]Y\T\JNˆ\\ ۝Z[]]H]XZ[۝Z[\ӛX[ȋ]Y\T\JNˆ\\ ۝Z[]]H]XQ]]Y\P۝^[X]Y\T\JNˆBјXBXXY \[۝^\][ۗ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ^X][ې[\[ȋ [Л\Nˆ\[[^\]H] X[J[[^\\[Л\[[^\ȊNˆ\\[۝^]H] X[J[[^\\[Л\\[۝^ ȊN‚\\ YJ[K^\\[۝^] +K \[X۝^[\HX[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ +[[^\] +Nˆ\\ ۝Z[XXX[Y\X[\[Л\[[^\[[^\\JNˆ\\ \ӛ۝Z[]]H]X\[YP\[Y][[^\\JN‚\\[۝^\HH[KXY[^ +\[۝^] +Nˆ\\ ۝Z[]]H]X\[YP\[Y]\[۝^\JNˆ\\ ۝Z[S[[[ۓ\][ۈ\[۝^\JNˆ\\ ۝Z[P[۞[[\ѝ[[ۓ\][ۈ\[۝^\JNˆBјXBXXY Y][[X\Pۜ[\[ۗ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ T]Y\XXSXZȊNˆ\[[X\T]H] X[J[[^\\T]Y\XXSXZY][[X\P[[\\˘ȊNˆ\ۜ[\[۔]H] X[J[[^\\T]Y\XXSXZY][[X\Pۜ[\[ۋȊN‚\\ YJ[K^\ۜ[\[۔] +K Y] \[[X\H[ۜ[\[ۈX[]H[H\Y\X[[KN‚\[[X\T\HH[KXY[^ +[[X\T] +Nˆ\\ \ӛ۝Z[]]HYX\X]\X[^[ۜXܒ^\ȋ[[X\T\JNˆ\\ \ӛ۝Z[]]H\X]\X[^[X[ېۜX܈[[X\T\JNˆ\\ \ӛ۝Z[]]H\\Xۜ[\[ۈ[[X\T\JNˆ\\ \ӛ۝Z[]]H\][[Y\]ܒ[][ۈ[[X\T\JNˆ\\ \ӛ۝Z[]]HYX\^\Y\[Y]\\H[[X\T\JN‚\ۜ[\[۔\HH[KXY[^ +ۜ[\[۔] +Nˆ\\ ۝Z[]]HYX\X]\X[^[ۜXܒ^\ȋۜ[\[۔\JNˆ\\ ۝Z[]]H\X]\X[^[X[ېۜX܈ۜ[\[۔\JNˆ\\ ۝Z[]]H\\Xۜ[\[ۈۜ[\[۔\JNˆ\\ ۝Z[]]H\][[Y\]ܒ[][ۈۜ[\[۔\JNˆ\\ ۝Z[]]HYX\^\Y\[Y]\\Hۜ[\[۔\JNˆBјXBXXY [][ے[]X\[]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ T]Y\XXSXZȊNˆ\\T\][۔]H] X[J[[^\\T]Y\XXSXZ\T\][ۋȊNˆ\[][ے[]]H] X[J[[^\\T]Y\XXSXZ[][ے[]˘ȊN‚\\ YJ[K^\[][ے[]] +K [][ۈ[]X\[[]H[H\Y\X[[KN‚\\T\][۔\HH[KXY[^ +\T\][۔] +Nˆ\\ \ӛ۝Z[]]HQ[[Y\XO[][ے[][[Y\]R[][ے[]ȋ\T\][۔\JN‚\[][ے[]\HH[KXY[^ +[][ے[]] +Nˆ\\ ۝Z[]]HQ[[Y\XO[][ے[][[Y\]R[][ے[]ȋ[][ے[]\JNˆBјXBXXY ^X]XT]\[]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ T]Y\XXSXZȊNˆ\[X[\]H] X[J[[^\\T]Y\XXSXZ[X[\˘ȊNˆ\^X]XT]H] X[J[[^\\T]Y\XXSXZ^X]XT˘ȊN‚\\ YJ[K^\^X]XT] +K ^X]XK\]\[[]H[H\Y\X[[KN‚\[X[\\HH[KXY[^ +[X[\] +Nˆ\\ \ӛ۝Z[]]HQ]^X]XT[X[\\JNˆ\\ \ӛ۝Z[]]HQ[[Y\XOS\][ۏ[[Y\]S\][ۜȋ[X[\\JNˆ\\ \ӛ۝Z[]]H]X\[YS\Y^X]XH[X[\\JN‚\^X]XT\HH[KXY[^ +^X]XT] +Nˆ\\ ۝Z[]]HQ]^X]XT^X]XT\JNˆ\\ ۝Z[]]HQ[[Y\XOS\][ۏ[[Y\]S\][ۜȋ^X]XT\JNˆ\\ ۝Z[]]H]X\[YS\Y^X]XH^X]XT\JNˆBјXBXXY ^\\\X\]R[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ T]Y\XXSXZȊNˆ\\[][۔]T]H] X[J[[^\\T]Y\XXSXZ\[][۔]KȊNˆ\\X\]H] X[J[[^\\T]Y\XXSXZ^\\\X\˘ȊN‚\\ YJ[K^\\X\] +K ^\\Y] \H\X\[]H[H\Y\X[[KN‚\\[][۔]T\HH[KXY[^ +\[][۔]T] +Nˆ\\ \ӛ۝Z[]]H]XXYۛH[[]]XR\][ψ^\\[[Y\XSY]ȋ\[][۔]T\JNˆ\\ \ӛ۝Z[]]H]XXYۛH[[]]XR\][ψX]\X[^[X[ە\\ȋ\[][۔]T\JN‚\\X\\HH[KXY[^ +\X\] +Nˆ\\ ۝Z[]]H]XXYۛH[[]]XR\][ψ^\\[[Y\XSY]ȋ\X\\JNˆ\\ ۝Z[]]H]XXYۛH[[]]XR\][ψX]\X[^[X[ە\\ȋ\X\\JNˆBјXBXXY W]X]Y\XXSܙ\[ԙ]ܚ]W]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ W[Y]Nˆ\]Xԙ]ܚ]T]H] X[J[[^\\[Y]^\]X]Y\XXT]ܚ]KȊNˆ\ܙ\[ԙ]ܚ]T]H] X[J[[^\\[Y]^\]X]Y\XXSܙ\[˘ȊN‚\\ YJ[K^\ܙ\[ԙ]ܚ]T] +K H]X]Y\XXHܙ\[\\H]ܚ]H[]H[H\Y\X[[KN‚\]Xԙ]ܚ]T\HH[KXY[^ +]Xԙ]ܚ]T] +Nˆ\\ \ӛ۝Z[]]H]X]ܚ]T]Y\XXQ^[[ۓܙ\[\PZ[]Xԙ]ܚ]T\JNˆ\\ \ӛ۝Z[]]H]X\ԙ]ܚ]XSܙ\Y\H]Xԙ]ܚ]T\JNˆ\\ \ӛ۝Z[]]H]X\]Y\XXSܙ\[[][ۈ]Xԙ]ܚ]T\JN‚\ܙ\[ԙ]ܚ]T\HH[KXY[^ +ܙ\[ԙ]ܚ]T] +Nˆ\\ ۝Z[]]H]X]ܚ]T]Y\XXQ^[[ۓܙ\[\PZ[ܙ\[ԙ]ܚ]T\JNˆ\\ ۝Z[]]H]X\ԙ]ܚ]XSܙ\Y\Hܙ\[ԙ]ܚ]T\JNˆ\\ ۝Z[]]H]X\]Y\XXSܙ\[[][ۈܙ\[ԙ]ܚ]T\JNˆBјXBXXY XYۛXԙ\ܝ[]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH \ӛX[[[YHNˆ\[[^\]H] X[J[[^\\\ӛX[[[YP[[^\ȊNˆ\\ܝ[]H] X[J[[^\\\ӛX[[[YT\ܝ[˘ȊN‚\\ YJ[K^\\ܝ[] +K XYۛX\ܝ[[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ +[[^\] +Nˆ\\ \ӛ۝Z[]]H]XYT\ܝܓ[[[^\\JNˆ\\ \ӛ۝Z[]]H]XYT\ܝܑܙXX[[^\\JNˆ\\ \ӛ۝Z[]]HXYۛHX]]][ے][[^\\JNˆ\\ \ӛ۝Z[]]H]X]]][ے][\\S]]][ۈ[[^\\JN‚\\ܝ[\HH[KXY[^ +\ܝ[] +Nˆ\\ ۝Z[]]H]XYT\ܝܓ[\ܝ[\JNˆ\\ ۝Z[]]H]XYT\ܝܑܙXX\ܝ[\JNˆ\\ ۝Z[]]HXYۛHX]]][ے]\ܝ[\JNˆ\\ ۝Z[]]H]X]]][ے][\\S]]][ۈ\ܝ[\JNˆBјXBXXY Mњ^\\PX\Y]W]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ MQ[]TڙX[ۈNˆ\۝^]H] X[J[[^\\Q[]TڙX[ۑ^\۝^[[\\˘ȊNˆ\\PX\]H] X[J[[^\\Q[]TڙX[ۑ^\\PX\˘ȊNˆ\XY^\[ۜ]H] X[J[[^\\Q[]TڙX[ۑ^\XY[]Q^\[ۜ˘ȊN‚\\ YJ[K^\\PX\] +K M^\\KXX\Y]HX[]H[H\Y\X[[KNˆ\\ YJ[K^\XY^\[ۜ] +K M^\XY Y[]H^\[ۈXۚ][ۈ[]H[H\Y\X[[KN‚\۝^\HH[KXY[^ +۝^] +Nˆ\\ \ӛ۝Z[]]H]X\[\ܝY[]T\PX\ȋ۝^\JNˆ\\ \ӛ۝Z[]]H]X\XY[]P۝\[ۑ^\[ۈ۝^\JNˆ\\ \ӛ۝Z[]]H]X\XY[]Q^\[ۈ۝^\JNˆ\\ \ӛ۝Z[]]H]X\[YR[^Y[]PX\ȋ۝^\JNˆ\\ \ӛ۝Z[]]H]X\\Sٕ\H۝^\JN‚\\PX\\HH[KXY[^ +\PX\] +Nˆ\\ ۝Z[]]H]X\[\ܝY[]T\PX\ȋ\PX\\JNˆ\\ \ӛ۝Z[]]H]X\XY[]P۝\[ۑ^\[ۈ\PX\\JNˆ\\ \ӛ۝Z[]]H]X\XY[]Q^\[ۈ\PX\\JNˆ\\ ۝Z[]]H]X\[YR[^Y[]PX\ȋ\PX\\JNˆ\\ ۝Z[]]H]X\\Sٕ\H\PX\\JN‚\XY^\[ۜ\HH[KXY[^ +XY^\[ۜ] +Nˆ\\ ۝Z[]]H]X\XY[]P۝\[ۑ^\[ۈXY^\[ۜ\JNˆ\\ ۝Z[]]H]X\XY[]Q^\[ۈXY^\[ۜ\JNˆBјXBXXY M\YTY\[P\YX][ۗ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ MQ[]TڙX[ۈNˆ\\YP[[\\]H] X[J[[^\\Q[]TڙX[ە\YP[[\\˘ȊNˆ\\YTY\[\]H] X[J[[^\\Q[]TڙX[ە\YTY\[\˘ȊN‚\\ YJ[K^\\YTY\[\] +K M\YHY\[H\YX][ۈ[]H[H\Y\X[[KN‚\\YP[[\\\HH[KXY[^ +\YP[[\\] +Nˆ\\ \ӛ۝Z[]]H]X\XY[]TY\[H\YP[[\\\JNˆ\\ \ӛ۝Z[]]H]X\\X\XXQ\\H\YP[[\\\JNˆ\\ \ӛ۝Z[]]H]X[XQ\XTY\[\ȋ\YP[[\\\JN‚\\YTY\[\\HH[KXY[^ +\YTY\[\] +Nˆ\\ ۝Z[]]H]X\XY[]TY\[H\YTY\[\\JNˆ\\ ۝Z[]]H]X\\X\XXQ\\H\YTY\[\\JNˆ\\ ۝Z[]]H]X[XQ\XTY\[\ȋ\YTY\[\\JNˆBјXBXXY M[^\PX[ۗ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ MQ[]TڙX[ۈNˆ\[^[[\\]H] X[J[[^\\Q[]TڙX[۔[^[[\\˘ȊNˆ\\PX[۔]H] X[J[[^\\Q[]TڙX[۔[^\PX[ۋȊN‚\\ YJ[K^\\PX[۔] +K M[^ X\Y\HX[ۈ[]H[H\Y\X[[KN‚\[^[[\\\HH[KXY[^ +[^[[\\] +Nˆ\\ \ӛ۝Z[]]H]XYX[^\Y\PX\\ȋ[^[[\\\JN‚\\PX[۔\HH[KXY[^ +\PX[۔] +Nˆ\\ ۝Z[]]H]XYX[^\Y\PX\\ȋ\PX[۔\JNˆ\\ ۝Z[ۙ][ۘ[X\^\[۔[^\PX[۔\JNˆ\\ ۝Z[Y[X\X\^\[۔[^\PX[۔\JNˆBјXBXXY Wњ^\\T\][ۗ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH W\ӛX[]\]HNˆ\^\]H] X[J[[^\\\ӛX[]\]Q^\ȊNˆ\\T\][۔]H] X[J[[^\\\ӛX[]\]Q^\\T\][ۋȊNˆ\X[\T]H] X[J[[^\\\ӛX[]\]Q^\X[\KȊN‚\\ YJ[K^\\T\][۔] +K H^\\ӛX[\H\][ۈ[]H[H\Y\X[[KNˆ\\ YJ[K^\X[\T] +K H^\X\]H]X[\HX[[]H[H\Y\X[[KN‚\^\\HH[KXY[^ +^\] +Nˆ\\ ۝Z[XXX[Y\X[\\ӛX[]\]Q^\^\\JNˆ\\ \ӛ۝Z[]]H]X[][ۑ^\[۔[^[\ӛX[ܚY[^\\JNˆ\\ \ӛ۝Z[]]HXYۛHX\ӛX[ܚY[^\\JNˆ\\ \ӛ۝Z[]]H]X\ӛX[\H^\\JNˆ\\ \ӛ۝Z[]]H]X\[SX[ȋ^\\JNˆ\\ \ӛ۝Z[]]H]X\\ӛX[[Z[^\\JN‚\\T\][۔\HH[KXY[^ +\T\][۔] +Nˆ\\ ۝Z[]]H]X[][ۑ^\[۔[^[\ӛX[ܚY[\T\][۔\JNˆ\\ ۝Z[]]HXYۛHX\ӛX[ܚY[\T\][۔\JNˆ\\ \ӛ۝Z[]]H]X\ӛX[\H\T\][۔\JNˆ\\ \ӛ۝Z[]]H]X\[SX[ȋ\T\][۔\JN‚\X[\HH[KXY[^ +X[\T] +Nˆ\\ ۝Z[]]H]X\ӛX[\HX[\JNˆ\\ ۝Z[]]H]X\[SX[ȋX[\JNˆBјXBXXY Wњ^\X[\X]P[[\\]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH W\ӛX[]\]HNˆ\\T\][۔]H] X[J[[^\\\ӛX[]\]Q^\\T\][ۋȊNˆ\X[\X]T]H] X[J[[^\\\ӛX[]\]Q^\X[\X]P[[\\˘ȊN‚\\ YJ[K^\X[\X]T] +K H^\X[Y\X]HZ[[[\\[]H[H\Y\X[[KN‚\\T\][۔\HH[KXY[^ +\T\][۔] +Nˆ\\ \ӛ۝Z[]]H]X\\ӛX[[Z[\T\][۔\JNˆ\\ \ӛ۝Z[]]H]X\YܙSX[\X]H\T\][۔\JNˆ\\ \ӛ۝Z[]]H]X\YܙP\X[ȋ\T\][۔\JNˆ\\ \ӛ۝Z[]]H]X[][ۑ^\[۔[^[\ӛX[[][ۈ\T\][۔\JN‚\X[\X]T\HH[KXY[^ +X[\X]T] +Nˆ\\ ۝Z[]]H]X\\ӛX[[Z[X[\X]T\JNˆ\\ ۝Z[]]H]X\YܙSX[\X]HX[\X]T\JNˆ\\ ۝Z[]]H]X\YܙP\X[ȋX[\X]T\JNˆ\\ ۝Z[]]H]X[][ۑ^\[۔[^[\ӛX[[][ۈX[\X]T\JNˆBјXBXXY[YT]\\[\\ۜX[]Y\]R[YX]Y\X[ +Bˆ\^[[ۜ\H] X[Jܙ\ԛ ܘȋ[P۝X[^[[ۜȊNˆ\\\]H] X[J^[[ۜ\[YT]\\ȊNˆ\[XT]H] X[J^[[ۜ\[YT]\\[XT]˘ȊNˆ\\P[[\\]H] X[J^[[ۜ\[YT]\\\P[[\\˘ȊN‚\\ YJ[K^\[XT] +K[YT]\\[XH]Y][ۈ\[[]H[H\Y\X[[KNˆ\\ YJ[K^\\P[[\\] +K[YT]\\\KX[ۈ[[\\[]H[H\Y\X[[KN‚\\\\HH[KXY[^ +\\] +Nˆ\\ ۝Z[[\[]X\X[\[YT]\\\\\JNˆ\\ \ӛ۝Z[]]H]XPY]Y][۔YY[ȋ\\\JNˆ\\ \ӛ۝Z[XX]XQ]X[ۑ[[Y[\H\\\JN‚\[XT\HH[KXY[^ +[XT] +Nˆ\\ ۝Z[]]H]XPY]Y][۔YY[ȋ[XT\JNˆ\\ ۝Z[]]H]X\[^H[ܘ\^\[ۈ[XT\JN‚\\P[[\\\HH[KXY[^ +\P[[\\] +Nˆ\\ ۝Z[XX]XQ]X[ۑ[[Y[\H\P[[\\\JNˆ\\ ۝Z[]]H]XQ][YY[\X[[Y[\H\P[[\\\JNˆBјXBXXY W\YT[]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋY[[[Y\ȋ WZ\[[YHNˆ\\YP[[\\]H] X[J[[^\\Z\[[YU\YP[[\\˘ȊNˆ\\YT[]H] X[J[[^\\Z\[[YU\YT[ȊN‚\\ YJ[K^\\YT[] +K H^X]XK\\YH[[[]H[H\Y\X[[KN‚\\YP[[\\\HH[KXY[^ +\YP[[\\] +Nˆ\\ \ӛ۝Z[]]H]X\]Y][ېX\ϏX]Y][ېX\\ќQ^X]XT\YP[[\\\JN‚\\YT[\HH[KXY[^ +\YT[] +Nˆ\\ ۝Z[]]H]X\]Y][ېX\ϏX]Y][ېX\\ќQ^X]XT\YT[\JNˆ\\ ۝Z[[XTY\[\XY[\YT[\JNˆ\\ ۝Z[]\ٚYY]ȋ\YT[\JNˆBјXBXXY WܚY[]\Qԙ\ۜX[]Y\]R[YX]Y\X[ +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋY[[[Y\ȋ WZ\[[YHNˆ\[[^\]H] X[J[[^\\Z\[[YP[[^\ȊNˆ\\YT[]H] X[J[[^\\Z\[[YU\YT[ȊNˆ\[[\\]H] X[J[[^\\Z\[[YSܚY[[[\\˘ȊNˆ\К[[]H] X[J[[^\\Z\[[YSܚY[К[[˘ȊNˆ\۝^]H] X[J[[^\\Z\[[YSܚY[۝^ ȊNˆ\][]H] X[J[[^\\Z\[[YSܚY[][˘ȊNˆ\]T]H] X[J[[^\\Z\[[YSܚY[]KȊNˆ\]][YT]H] X[J[[^\\Z\[[YP]][YPۙY\][ۋȊNˆ\\YYۙY\][۔]H] X[J[[^\\Z\[[YP\YYۙY\][ۋȊN‚\\ YJ[K^\[[\\] +K HܚY[X]\H۝ Y[[\\[]H[H\Y\X[[KNˆ\\ YJ[K^\К[[] +K HܚY[[[X\[[\ݙ\H[]H[H\Y\X[[KNˆ\\ YJ[K^\۝^] +K HܚY[Y۝^ۜX[ۈ[]H[H\Y\X[[KNˆ\\ YJ[K^\][] +K HܚY[Y][X[ۈ[]H[H\Y\X[[KNˆ\\ YJ[K^\]T] +K HܚY[Y]H[][[[[]H[H\Y\X[[KNˆ\\ YJ[K^\]][YT] +K H[[ [][]][YHو[]H[H\Y\X[[KNˆ\\ YJ[K^\\YYۙY\][۔] +K H\YY]][YHۙY\][ۈو[]H[H\Y\X[[KN‚\\YT[\HH[KXY[^ +\YT[] +Nˆ\\ \ӛ۝Z[]]H]XQ]ܘ\\YT[\JNˆ\\ \ӛ۝Z[]]HX[Y\X[\ܚY[۝^\YT[\JNˆ\\ \ӛ۝Z[]]HXYۛHXؙT]H\YT[\JNˆ\\ \ӛ۝Z[]]H[[H][[\YT[\JNˆ\\ \ӛ۝Z[]]HY\ݙ\XP[X\\ȋ\YT[\JN‚\[[^\\HH[KXY[^ +[[^\] +Nˆ\\ ۝Z[Y\\\[][۔\X[ۈ[[^\\JNˆ\\ ۝Z[\ܘ\XHH]\[K[[YK\[\\X\ːۙ][ۘ[XZXO[[^\\JNˆ\\ ۝Z[\]][YPXHH]\[KX[ۜːۘ\[ ۘ\[X[ۘ\O[[^\\JNˆ\\ ۝Z[[[^R[][ۊ[[^\\JNˆ\\ ۝Z[]][YPXK[[^\\JN‚\]][YT\HH[KXY[^ +]][YT] +Nˆ\\ ۝Z[]]H]XYY[[]][YTY^\ȋ]][YT\JNˆ\\ ۝Z[]]H]XQ]\X]][YH]][YT\JN‚\\YYۙY\][۔\HH[KXY[^ +\YYۙY\][۔] +Nˆ\\ ۝Z[]]H]XP\PۙY\][ې]][Y\ȋ\YYۙY\][۔\JNˆ\\ ۝Z[]]H]XQ]\YYۙY\][ې]][YH\YYۙY\][۔\JN‚\\YYۙY\][ە\]H] X[Jܙ\ԛ \ȋ[P۝X[ \ȋ[[^\ȋ WZ\[[YHZ\[[YP\YYۙY\][ە\˘ȊNˆ\\ YJ[K^\\YYۙY\][ە\] +K H\YY]][YHۙY\][ۈ\[]H[H\Y\X[[KNˆ\\YYۙY\][ە\\HH[KXY[^ +\YYۙY\][ە\] +Nˆ\\ ۝Z[\[[\YYۙY\][ې]][YWӛXYۛXȋ\YYۙY\][ە\\JNˆ\\ ۝Z[\ܚ[YW\YYۙY\][ے[\[\W\ӛ\\ȋ\YYۙY\][ە\\JN‚\[[\\\HH[KXY[^ +[[\\] +Nˆ\\ ۝Z[]]H]XPXܚY[]\S]Y][ېX\\ȋ[[\\\JNˆ\\ ۝Z[]]H]XQ]ܘ\[[\\\JNˆ\\ ۝Z[ۙ][ۘ[XZXOS\][ۋܘ\\ܘ\XH[[\\\JNˆ\\ \ӛ۝Z[]XXYۛHۙ][ۘ[XZXOS\][ۈ[[\\\JN‚\К[[\HH[KXY[^ +К[[] +Nˆ\\ ۝Z[]]HY\ݙ\XP[X\\ȋК[[\JNˆ\\ ۝Z[]]HX[Y\]\][ې[[ȋК[[\JN‚\۝^\HH[KXY[^ +۝^] +Nˆ\\ ۝Z[]]HX[Y\X[\ܚY[۝^۝^\JNˆ\\ ۝Z[XXSX\][Лȋ۝^\JNˆ\\ ۝Z[]]HT\Q[]SܚY[۝^\JN‚\][\HH[KXY[^ +][] +Nˆ\\ ۝Z[]]HYX[[[\\Q][ȋ][\JNˆ\\ ۝Z[]]HYX]Y][ۑ][][\JN‚\]T\HH[KXY[^ +]T] +Nˆ\\ ۝Z[]]HX[Y\[]SܚY[]T\JNˆ\\ ۝Z[]]H[[H][[]T\JNˆ\\ ۝Z[]]HXYۛHXؙT]H]T\JNˆBјXBXXY WXYۛXԙ\ܝ[]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋY[[[Y\ȋ WZ\[[YHNˆ\[[^\]H] X[J[[^\\Z\[[YP[[^\ȊNˆ\\ܝ[]H] X[J[[^\\Z\[[YT\ܝ[˘ȊN‚\\ YJ[K^\\ܝ[] +K HZ\[\]XYۛX\ܝ[[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ +[[^\] +Nˆ\\ \ӛ۝Z[]]H]XY\ܝZ\[[YQXYۛXȋ[[^\\JNˆ\\ \ӛ۝Z[]]H]X\ۙ\Z\[][[^\\JN‚\\ܝ[\HH[KXY[^ +\ܝ[] +Nˆ\\ ۝Z[]]H]XY\ܝZ\[[YQXYۛXȋ\ܝ[\JNˆ\\ ۝Z[]]H]X\ۙ\Z\[]\ܝ[\JNˆ\\ ۝Z[\ܝXYۛXȋ\ܝ[\JNˆBјXBXXY LW\[XS[W]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[XP[[[[ȋ LW[]SZ\[[X\R^HNˆ\[\][۔]H] X[J[[^\\[]SZ\[[X\R^P\[XS[\][ۋȊNˆ\[T]H] X[J[[^\\[]SZ\[[X\R^P\[XS[KȊN‚\\ YJ[K^\[T] +K LH[\[XHKY[X[]H[H\Y\X[[KN‚\[\][۔\HH[KXY[^ +[\][۔] +Nˆ\\ \ӛ۝Z[]]H]X\[\[XU[YR[H[\][۔\JNˆ\\ \ӛ۝Z[]]H]XX\\\[XU[YH[\][۔\JN‚\[T\HH[KXY[^ +[T] +Nˆ\\ ۝Z[]]H]X\[\[XU[YR[H[T\JNˆ\\ ۝Z[]]H]XX\\\[XU[YH[T\JNˆ\\ ۝Z[\[Y]\\[^[T\JNˆBјXBXXY LW^T\T[\]R[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[XP[[[[ȋ LW[]SZ\[[X\R^HNˆ\^T[\]H] X[J[[^\\[]SZ\[[X\R^R^T[\˘ȊNˆ\^T\T[\]H] X[J[[^\\[]SZ\[[X\R^R^T\T[\˘ȊN‚\\ YJ[K^\^T\T[\] +K LH۝[[ۋ^K\\H[Y][ۈ[]H[H\Y\X[[KN‚\^T[\\HH[KXY[^ +^T[\] +Nˆ\\ \ӛ۝Z[]]H\՘[Y^T\H^T[\\JNˆ\\ \ӛ۝Z[]]H]XQ[\H^T[\\JNˆ\\ \ӛ۝Z[]]H]X\\XR^T\H^T[\\JNˆ\\ \ӛ۝Z[]]H]X\՘[Y^U\H^T[\\JN‚\^T\T[\\HH[KXY[^ +^T\T[\] +Nˆ\\ ۝Z[]]H\՘[Y^T\H^T\T[\\JNˆ\\ ۝Z[]]H]XQ[\H^T\T[\\JNˆ\\ ۝Z[]]H]X\\XR^T\H^T\T[\\JNˆ\\ ۝Z[]]H]X\՘[Y^U\H^T\T[\\JNˆBјXBXXY MW[[YPXW]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ MWZ\[ܙ\HNˆ\[[^\]H] X[J[[^\\Z\[ܙ\P[[^\ȊNˆ\[[YPXT]H] X[J[[^\\Z\[ܙ\S[[YPXKȊNˆ\[Y\[R^T]H] X[J[[^\\Z\[ܙ\S[Y\[R^KȊN‚\\ YJ[K^\[[YPXT] +K MH[\YۛY[\][ۈXH[]H[H\Y\X[[KNˆ\\ YJ[K^\[Y\[R^T] +K MH[ \Y\[HXH^\[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ +[[^\] +Nˆ\\ \ӛ۝Z[]]HX[Y\[[YPXH[[^\\JNˆ\\ \ӛ۝Z[]]HXYۛHX[ܚ]H[[^\\JNˆ\\ \ӛ۝Z[]]HXYۛHX[Y\[R^H[[^\\JN‚\[[YPXT\HH[KXY[^ +[[YPXT] +Nˆ\\ ۝Z[]]HX[Y\[[YPXH[[YPXT\JNˆ\\ ۝Z[]]HXYۛHX[ܚ]H[[YPXT\JNˆ\\ \ӛ۝Z[]]HXYۛHX[Y\[R^H[[YPXT\JN‚\[Y\[R^T\HH[KXY[^ +[Y\[R^T] +Nˆ\\ ۝Z[]]HXYۛHX[Y\[R^H[Y\[R^T\JNˆ\\ ۝Z[]]HX[Y\[Y\[R^P\\\[Y\[R^T\JNˆBјXBXXY MW]Y\T\T\][ۗ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ MWZ\[ܙ\HNˆ\[[^\]H] X[J[[^\\Z\[ܙ\P[[^\ȊNˆ\]Y\T\T]H] X[J[[^\\Z\[ܙ\T]Y\T\KȊN‚\\ YJ[K^\]Y\T\T] +K MHQ]Y\H\H\][ۈ[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ +[[^\] +Nˆ\\ \ӛ۝Z[]]H]X\[]Q[Y]ܚ]Y\T\H[[^\\JNˆ\\ \ӛ۝Z[]]H]XT\S[[YH[[^\\JN‚\]Y\T\HH[KXY[^ +]Y\T\T] +Nˆ\\ ۝Z[]]H]X\[]Q[Y]ܚ]Y\T\H]Y\T\JNˆ\\ ۝Z[]]H]XT\S[[YH]Y\T\JNˆBјXBXXY MWۜX[S[[[\\]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ MWZ\[ܙ\HNˆ\ۜX[T]H] X[J[[^\\Z\[ܙ\QۜX[P[[\\˘ȊNˆ\[[[\\]H] X[J[[^\\Z\[ܙ\QۜX[S[[[\\˘ȊN‚\\ YJ[K^\[[[\\] +K MHۜX[Hܝ[Y\YۙY[[]H[H\Y\X[[KN‚\ۜX[T\HH[KXY[^ +ۜX[T] +Nˆ\\ \ӛ۝Z[]]H\ܝ[ۜX[UY[ۜX[T\JNˆ\\ \ӛ۝Z[]]H]XQ]\YۙY[ۜX[T\JN‚\[[[\\\HH[KXY[^ +[[[\\] +Nˆ\\ ۝Z[]]H\ܝ[ۜX[UY[[[[\\\JNˆ\\ ۝Z[]]H]XQ]\YۙY[[[[\\\JNˆBјXBXXY MWۜX[U[YQ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ MWZ\[ܙ\HNˆ\ۜX[T]H] X[J[[^\\Z\[ܙ\QۜX[P[[\\˘ȊNˆ\[YQ]H] X[J[[^\\Z\[ܙ\QۜX[U[YQ˘ȊN‚\\ YJ[K^\[YQ] +K MHۜX[H[ ݘ[YKYX[[]H[H\Y\X[[KN‚\ۜX[T\HH[KXY[^ +ۜX[T] +Nˆ\\ ۝Z[]]H\Y[][ېY\ۜX[TܝۜX[T\JNˆ\\ \ӛ۝Z[]]HXZ]\ќS\][ۈۜX[T\JN‚\[YQ\HH[KXY[^ +[YQ] +Nˆ\\ ۝Z[]]HXZ]\ќS\][ۈ[YQ\JNˆ\\ ۝Z[T\S[[YH[YQ\JNˆBјXBXXY MWњ^\^T\PX]R[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ MWZ\[ܙ\HNˆ\^\]H] X[J[[^\\Z\[ܙ\Q^\ȊNˆ\^T\T]H] X[J[[^\\Z\[ܙ\Q^\^T\KȊN‚\\ YJ[K^\^T\T] +K MH^\\]K^[\^K\\HX[]H[H\Y\X[[KN‚\^\\HH[KXY[^ +^\] +Nˆ\\ ۝Z[XXX[Y\X[\Z\[ܙ\Q^\^\\JNˆ\\ \ӛ۝Z[]]H]X\\]R^P]X]H^\\JNˆ\\ \ӛ۝Z[]]H]X\^[\]X]H^\\JNˆ\\ \ӛ۝Z[]]H]X[[[X\R^T\ȋ^\\JN‚\^T\T\HH[KXY[^ +^T\T] +Nˆ\\ ۝Z[]]H]X\\]R^P]X]H^T\T\JNˆ\\ ۝Z[]]H]X\^[\]X]H^T\T\JNˆ\\ ۝Z[]]H]X[[[X\R^T\ȋ^T\T\JNˆBјXBXXY [X\R^PXW]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ њ[[XYّ\ܑY][Nˆ\^P[[\\]H] X[J[[^\\[[XYّ\ܑY][^P[[\\˘ȊNˆ\[X\R^PXT]H] X[J[[^\\[[XYّ\ܑY][[X\R^PXKȊN‚\\ YJ[K^\[X\R^PXT] +K [X\KZ^HXH]H[[[[]H[H\Y\X[[KN‚\^P[[\\\HH[KXY[^ +^P[[\\] +Nˆ\\ ۝Z[XX]X[X\R^PXHܙX]PXH^P[[\\\JNˆ\\ \ӛ۝Z[[\[X[Y\X[\[X\R^PXH^P[[\\\JN‚\[X\R^PXT\HH[KXY[^ +[X\R^PXT] +Nˆ\\ ۝Z[[\[X[Y\X[\[X\R^PXH[X\R^PXT\JNˆ\\ ۝Z[]]HY[\Q[T[Y[X\R^PXT\JNˆBјXBXXY ۙY\][۔[^\[]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[XP[[[[ȋ Z\[^X]ܙZYے^HNˆ\ۙY\][ې[[\\]H] X[J[[^\\Z\[^X]ܙZYے^PۙY\][ې[[\\˘ȊNˆ\[^\[]H] X[J[[^\\Z\[^X]ܙZYے^PۙY\][۔[^\[˘ȊN‚\\ YJ[K^\[^\[] +K \\[^[][ۜ\ۙY\][ۈ\[[]H[H\Y\X[[KN‚\ۙY\][ې[[\\\HH[KXY[^ +ۙY\][ې[[\\] +Nˆ\\ ۝Z[]]H]XۙY\][۔[Z[ۙY\][۔[ۙY\][ې[[\\\JNˆ\\ \ӛ۝Z[]]H]XY\ۙY\][۔[^ۙY\][ې[[\\\JN‚\[^\[\HH[KXY[^ +[^\[] +Nˆ\\ ۝Z[]]H]XY\ۙY\][۔[^[^\[\JNˆ\\ ۝Z[]]H]X[]]Y][ېۙY\][ے^H[^\[\JNˆBјXBXXY W]Y\T\][ۗ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋY[[[Y\ȋ WZ\[[YHNˆ\]Y\P[[\\]H] X[J[[^\\Z\[[YT]Y\P[[\\˘ȊNˆ\]Y\T]H] X[J[[^\\Z\[[YT]Y\T ȊN‚\\ YJ[K^\]Y\T] +K H]]Y\K\و[]H[H\Y\X[[KN‚\]Y\P[[\\\HH[KXY[^ +]Y\P[[\\] +Nˆ\\ ۝Z[]]H]XP[[^T]Y\PZ[]Y\P[[\\\JNˆ\\ \ӛ۝Z[]]H]XQ]]]Y\P[[\\\JN‚\]Y\T\HH[KXY[^ +]Y\T] +Nˆ\\ ۝Z[]]H]XQ]]]Y\T\JNˆ\\ ۝Z[\۝^]Y\T\JNˆBјXBXXY њ^\X[ے[]X[^\]ܚ]W]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ \Qޙ[]ܔ]XY[X\\X\ȊNˆ\]ܚ]T]H] X[J[[^\\\Qޙ[]ܔ]XY[X\\X\њ^\]ܚ]KȊNˆ\X[۔]ܚ]T]H] X[J[[^\\\Qޙ[]ܔ]XY[X\\X\њ^\X[۔]ܚ]KȊN‚\\ YJ[K^\X[۔]ܚ]T] +K X[ۋZ[]X[^\]ܚ]H[\[]H[H\Y\X[[KN‚\]ܚ]T\HH[KXY[^ +]ܚ]T] +Nˆ\\ \ӛ۝Z[]]H]XT]ܚ]PX[ے[]X[^\]ܚ]T\JN‚\X[۔]ܚ]T\HH[KXY[^ +X[۔]ܚ]T] +Nˆ\\ ۝Z[]]H]XT]ܚ]PX[ے[]X[^\X[۔]ܚ]T\JNˆ\\ ۝Z[[^[ \^R[]X[^\^\[ۈX[۔]ܚ]T\JNˆBјXBXXY W]X]Y\XXP\[Y[[X[ۗ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ W[Y]Nˆ\]ܚ]T]H] X[J[[^\\[Y]^\]X]Y\XXT]ܚ]KȊNˆ\\[Y[]H] X[J[[^\\[Y]^\]X]Y\XXP\[Y[˘ȊN‚\\ YJ[K^\\[Y[] +K H]X]Y\XXH\KX\[Y[[X[ۈ[]H[H\Y\X[[KN‚\]ܚ]T\HH[KXY[^ +]ܚ]T] +Nˆ\\ \ӛ۝Z[]]H]XQ][]\]Y[P\[Y[]ܚ]T\JNˆ\\ \ӛ۝Z[]]H]XQ][YY\]Y[P\[Y[]ܚ]T\JN‚\\[Y[\HH[KXY[^ +\[Y[] +Nˆ\\ ۝Z[]]H]XQ][]\]Y[P\[Y[\[Y[\JNˆ\\ ۝Z[]]H]XQ][YY\]Y[P\[Y[\[Y[\JNˆ\\ ۝Z[P\[Y[\][ۈ\[Y[\JNˆBјXBXXY W\Pۜ[\[ې[[\\]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ W[Q[]T[\ڙX[ۈNˆ\\YT]H] X[J[[^\\[Q[]T[\ڙX[ە\YP[[\\˘ȊNˆ\\U\YT]H] X[J[[^\\[Q[]T[\ڙX[۔\U\YKȊN‚\\ YJ[K^\\U\YT] +K H[\KXۜ[\[ۈ[\[]H[H\Y\X[[KN‚\\YT\HH[KXY[^ +\YT] +Nˆ\\ \ӛ۝Z[]]H]XQ]ۜ[YY\H\YT\JNˆ\\ \ӛ۝Z[]]H]XQ]ۙ][ۘ[X\\H\YT\JNˆ\\ \ӛ۝Z[]]H]X\ԙXYۛT\TY\[H\YT\JN‚\\U\YT\HH[KXY[^ +\U\YT] +Nˆ\\ ۝Z[]]H]XQ]ۜ[YY\H\U\YT\JNˆ\\ ۝Z[]]H]XQ]ۙ][ۘ[X\\H\U\YT\JNˆ\\ ۝Z[]]H]X\ԙXYۛT\TY\[H\U\YT\JNˆBјXBXXY ԙ[][ۜ\[Y[]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[XP[[[[ȋ Z\[^X]ܙZYے^HNˆ\[T]H] X[J[[^\\Z\[^X]ܙZYے^T[][ۜ\[KȊNˆ\Y[]H] X[J[[^\\Z\[^X]ܙZYے^T[][ۜ\[Y[˘ȊN‚\\ YJ[K^\Y[] +K \Y[Y[X[]H[H\Y\X[[KN‚\[T\HH[KXY[^ +[T] +Nˆ\\ \ӛ۝Z[]]H]X\YYS\Y[[T\JNˆ\\ \ӛ۝Z[]]H]X[^O[\Yۘ][۔H[T\JNˆ\\ \ӛ۝Z[]]H]X[^O[\[Y]\H[T\JN‚\Y[\HH[KXY[^ +Y[] +Nˆ\\ ۝Z[]]H]X\YYS\Y[Y[\JNˆ\\ ۝Z[]]H]X[^O[\Yۘ][۔HY[\JNˆ\\ ۝Z[]]H]X[^O[\[Y]\HY[\JNˆBјXBXXY ݚY\YQ^\[۔[\]R[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ [X]\SX]\X[^][ۈNˆ\۝[X][۔Y]T]H] X[J[[^\\[X]\SX]\X[^][ې۝[X][۔Y]KȊNˆ\ݚY\YQ^\[ۜ]H] X[J[[^\\[X]\SX]\X[^][۔ݚY\YQ^\[ۜ˘ȊN‚\\ YJ[K^\ݚY\YQ^\[ۜ] +K ݚY\\YH^\[ۈX\[ۈ[]H[H\Y\X[[KN‚\۝[X][۔Y]T\HH[KXY[^ +۝[X][۔Y]T] +Nˆ\\ \ӛ۝Z[]]H]X\ݚY\YQ^\[ۈ۝[X][۔Y]T\JNˆ\\ \ӛ۝Z[]]H]X\ݚY\YSؚXܙX][ۈ۝[X][۔Y]T\JNˆ\\ \ӛ۝Z[]]H]X\ݚY\YR[][ۈ۝[X][۔Y]T\JN‚\ݚY\YQ^\[ۜ\HH[KXY[^ +ݚY\YQ^\[ۜ] +Nˆ\\ ۝Z[]]H]X\ݚY\YQ^\[ۈݚY\YQ^\[ۜ\JNˆ\\ ۝Z[]]H]X\ݚY\YSؚXܙX][ۈݚY\YQ^\[ۜ\JNˆ\\ ۝Z[]]H]X\ݚY\YR[][ۈݚY\YQ^\[ۜ\JNˆBјXBXXY ԙY[[XYۛXXW]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ [X]\SX]\X[^][ۈNˆ\ܚY[[[\\]H] X[J[[^\\[X]\SX]\X[^][ۓܚY[[[\\˘ȊNˆ\Y[[XYۛX]H] X[J[[^\\[X]\SX]\X[^][۔Y[[XYۛX˘ȊN‚\\ YJ[K^\Y[[XYۛX] +K Y[[ [X]\X[^][ۈXYۛXXH[]H[H\Y\X[[KN‚\ܚY[[[\\\HH[KXY[^ +ܚY[[[\\] +Nˆ\\ \ӛ۝Z[]]H]XPܙX]TY[[XYۛXȋܚY[[[\\\JN‚\Y[[XYۛX\HH[KXY[^ +Y[[XYۛX] +Nˆ\\ ۝Z[]]H]XPܙX]TY[[XYۛXȋY[[XYۛX\JNˆ\\ ۝Z[[[ݙTY[[X]\X[^][ۑ^[Y[[XYۛX\JNˆBјXBXXY XYۛX\ܚ\ܜ]R[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ [X]\SX]\X[^][ۈNˆ\[[^\]H] X[J[[^\\[X]\SX]\X[^][ې[[^\ȊNˆ\XYۛX]H] X[J[[^\\[X]\SX]\X[^][ۑXYۛX˘ȊN‚\\ YJ[K^\XYۛX] +K XYۛX\ܚ\ܜ[XYۛX\\HY]Y]H[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ +[[^\] +Nˆ\\ ۝Z[]]H]XY[[^R[][ۈ[[^\\JNˆ\\ \ӛ۝Z[]]H]XXYۛH[^XT[]H[[^\\JNˆ\\ \ӛ۝Z[XX]XXYۛHXYۛX\ܚ\܈[H[[^\\JNˆ\\ \ӛ۝Z[XX]XXYۛHXYۛX\ܚ\܈Y[[[H[[^\\JNˆ\\ \ӛ۝Z[]]H]X[[]]XQX[ۘ\O[[ψܙX]T\Y\ȋ[[^\\JN‚\XYۛX\HH[KXY[^ +XYۛX] +Nˆ\\ ۝Z[]]H]XXYۛH[^XT[]HXYۛX\JNˆ\\ ۝Z[XX]XXYۛHXYۛX\ܚ\܈[HXYۛX\JNˆ\\ ۝Z[XX]XXYۛHXYۛX\ܚ\܈Y[[[HXYۛX\JNˆ\\ ۝Z[]]H]X[[]]XQX[ۘ\O[[ψܙX]T\Y\ȋXYۛX\JNˆ\\ \ӛ۝Z[]]H]XY[[^R[][ۈXYۛX\JNˆBјXBXXY W[[]X[^\]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ W][\Sܙ\HNˆ\[[^\]H] X[J[[^\\][\Sܙ\P[[^\ȊNˆ\[]H] X[J[[^\\][\Sܙ\S[[]X[^\˘ȊN‚\\ YJ[K^\[] +K H[[]X[^\[ܚ]HX[[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ +[[^\] +Nˆ\\ ۝Z[XXX[Y\X[\][\Sܙ\P[[^\[[^\\JNˆ\\ \ӛ۝Z[]]HQ][P\YۛY[[[]X[^\[[^\\JNˆ\\ \ӛ۝Z[]]H]X\ܚ]PYܙU\H[[^\\JNˆ\\ \ӛ۝Z[]]H]X\ܚ]U[[[^\\JN‚\[\HH[KXY[^ +[] +Nˆ\\ ۝Z[]]HQ][P\YۛY[[[]X[^\[\JNˆ\\ ۝Z[]]H]X\ܚ]PYܙU\H[\JNˆ\\ ۝Z[]]H]X\ܚ]U[[\JNˆBјXBXXY [YPZ[\[[[]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋY[[[Y\ȋ \\X[^[ۈNˆ\[[^\]H] X[J[[^\\\\X[^[ې[[^\ȊNˆ\Z[\[]H] X[J[[^\\\\X[^[ے[YPZ[\[ ȊN‚\\ YJ[K^\Z[\[] +K [YKXZ[\[[[[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ +[[^\] +Nˆ\\ \ӛ۝Z[]]H[[H]Y\T][[H[[^\\JNˆ\\ \ӛ۝Z[]]HX[Y\[YPZ[[[\\ȋ[[^\\JN‚\Z[\[\HH[KXY[^ +Z[\[] +Nˆ\\ ۝Z[]]H[[H]Y\T][[HZ[\[\JNˆ\\ ۝Z[]]HX[Y\[YPZ[[[\\ȋZ[\[\JNˆ\\ ۝Z[XXQ]\TX[X[ۜȋZ[\[\JNˆBјXBXXY ܛ\X\[][ۗ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ ܛ\Sە[]XHNˆ\[[^\]H] X[J[[^\\ܛ\Sە[]XP[[^\ȊNˆ\ܛ\X\]H] X[J[[^\\ܛ\Sە[]XQܛ\X\˘ȊN‚\\ YJ[K^\ܛ\X\] +K ܛ\ XZ[[]X[]H[\[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ +[[^\] +Nˆ\\ ۝Z[XXX[Y\X[\ܛ\Sە[]XP[[^\[[^\\JNˆ\\ \ӛ۝Z[]]H]X\[]XQܛ\X\ȋ[[^\\JNˆ\\ \ӛ۝Z[]]H]XR[][ۓ\][ۈ[]\[ܛ\Z[[][ۈ[[^\\JN‚\ܛ\X\\HH[KXY[^ +ܛ\X\] +Nˆ\\ ۝Z[]]H]X\[]XQܛ\X\ȋܛ\X\\JNˆ\\ ۝Z[]]H]XR[][ۓ\][ۈ[]\[ܛ\Z[[][ۈܛ\X\\JNˆBјXBXXY ܛ\[\P[[\\]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ ܛ\Sە[]XHNˆ\[[^\]H] X[J[[^\\ܛ\Sە[]XP[[^\ȊNˆ\ܛ\[\\]H] X[J[[^\\ܛ\Sە[]XQܛ\[\\˘ȊN‚\\ YJ[K^\ܛ\[\\] +K ܛ\[T]Y\XXKQܛ\[\HX[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ +[[^\] +Nˆ\\ \ӛ۝Z[]]H]X\ܛ\[]Y\XXH[[^\\JNˆ\\ \ӛ۝Z[]]H]XU\T[X]]Y\XXQ[[Y[\H[[^\\JNˆ\\ \ӛ۝Z[]]H]X\ܛ\[U\T[X\JH[[^\\JN‚\ܛ\[\\\HH[KXY[^ +ܛ\[\\] +Nˆ\\ ۝Z[]]H]X\ܛ\[]Y\XXHܛ\[\\\JNˆ\\ ۝Z[]]H]XU\T[X]]Y\XXQ[[Y[\Hܛ\[\\\JNˆ\\ ۝Z[]]H]X\ܛ\[U\T[X\JHܛ\[\\\JNˆBјXBXXY [НZ[\[X\Y[]W]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\]X[۔]H] X[J[[^\\][[ۜX[ۑ]X[ۋȊNˆ\[X\]H] X[J[[^\\][[ۜX[۔[НZ[\[X\\˘ȊNˆ\Y[]T]H] X[J[[^\\][[ۜX[۔[НZ[\Y[]KȊN‚\\ YJ[K^\[X\] +K [НZ[\[X\X][[]H[H\Y\X[[KNˆ\\ YJ[K^\Y[]T] +K [НZ[\[X\Y[]H\][ۈ[]H[H\Y\X[[KN‚\]X[۔\HH[KXY[^ +]X[۔] +Nˆ\\ \ӛ۝Z[]]HXYۛHX[Y[]H]X[۔\JN‚\[X\\HH[KXY[^ +[X\] +Nˆ\\ \ӛ۝Z[]]HXYۛHX[Y[]H[X\\JNˆ\\ \ӛ۝Z[]]H]X[Y[]H\S[Y[]H[X\\JN‚\Y[]T\HH[KXY[^ +Y[]T] +Nˆ\\ ۝Z[]]HXYۛHX[Y[]HY[]T\JNˆ\\ ۝Z[]]H]X[Y[]H\S[Y[]HY[]T\JNˆBјXBXXY [НZ[\[X\X\[YYܚ]\]R[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\[X\]H] X[J[[^\\][[ۜX[۔[НZ[\[X\\˘ȊNˆ\X\[YYܚ]\]H] X[J[[^\\][[ۜX[۔[НZ[\[X\ܚ]\˘ȊN‚\\ YJ[K^\X\[YYܚ]\] +K [НZ[\[X\X\[YY ]ܚ]H\][ۈ[]H[H\Y\X[[KN‚\[X\\HH[KXY[^ +[X\] +Nˆ\\ \ӛ۝Z[]]H]X\ӛۑX\[YYܚ]PY\]\X\[YY[X\\JNˆ\\ \ӛ۝Z[]]H]XT\QX\[YY[[YH[X\\JN‚\X\[YYܚ]\\HH[KXY[^ +X\[YYܚ]\] +Nˆ\\ ۝Z[]]H]X\ӛۑX\[YYܚ]PY\]\X\[YYX\[YYܚ]\\JNˆ\\ ۝Z[]]H]XT\QX\[YY[[YHX\[YYܚ]\\JNˆBјXBXXY [НZ[\\[]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\]X[۔]H] X[J[[^\\][[ۜX[ۑ]X[ۋȊNˆ\]H] X[J[[^\\][[ۜX[۔[НZ[\˘ȊN‚\\ YJ[K^\] +K [НZ[\\[ ܙ\][]H[H\Y\X[[KN‚\]X[۔\HH[KXY[^ +]X[۔] +Nˆ\\ \ӛ۝Z[]]H]X\[НZ[\\[\[Y[ېۜ[]X[۔\JN‚\\HH[KXY[^ +] +Nˆ\\ ۝Z[]]H]X\[НZ[\\[\[Y[ېۜ[\JNˆBјXBXXY [НZ[\[ܚ]Q]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\]H] X[J[[^\\][[ۜX[۔[НZ[\˘ȊNˆ\[ܚ]\]H] X[J[[^\\][[ۜX[۔[НZ[\[ܚ]\˘ȊNˆ\]\ܚ]\]H] X[J[[^\\][[ۜX[۔[НZ[\]\[ܚ]KȊN‚\\ YJ[K^\[ܚ]\] +K [НZ[\[ ]ܚ]H[]H[H\Y\X[[KNˆ\\ YJ[K^\]\ܚ]\] +K [НZ[\]\[ ]ܚ]H[]H[H\Y\X[[KN‚\\HH[KXY[^ +] +Nˆ\\ \ӛ۝Z[]]H]X\ۛPۜ[[ܚ]\ЙYܙTY\[H\JNˆ\\ \ӛ۝Z[]]H]X\]\ېۜ[[ܚ]PYܙTY\[H\JNˆ\\ \ӛ۝Z[]]H]XS\][ۈ]\[\YۛY[Y[YH\JN‚\[ܚ]\\HH[KXY[^ +[ܚ]\] +Nˆ\\ ۝Z[]]H]X\ۛPۜ[[ܚ]\ЙYܙTY\[H[ܚ]\\JNˆ\\ \ӛ۝Z[]]H]X\]\ېۜ[[ܚ]PYܙTY\[H[ܚ]\\JN‚\]\ܚ]\\HH[KXY[^ +]\ܚ]\] +Nˆ\\ ۝Z[]]H]X\]\ېۜ[[ܚ]PYܙTY\[H]\ܚ]\\JNˆ\\ ۝Z[]]H]XS\][ۈ]\[\YۛY[Y[YH]\ܚ]\\JNˆBјXBXXY [НZ[\]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\]H] X[J[[^\\][[ۜX[۔[НZ[\˘ȊNˆ\]H] X[J[[^\\][[ۜX[۔[НZ[\˘ȊN‚\\ YJ[K^\] +K [НZ[\ X\YY[ܚ]H[]H[H\Y\X[[KN‚\\HH[KXY[^ +] +Nˆ\\ \ӛ۝Z[]]H]X\ӛېۜ[\YY[ܚ]H\JNˆ\\ \ӛ۝Z[]]H]X\X\[YYۜ[[ܚ]R[[YR]\][ېYܙTY\[H\JN‚\\HH[KXY[^ +] +Nˆ\\ ۝Z[]]H]X\ӛېۜ[\YY[ܚ]H\JNˆ\\ ۝Z[]]H]X\X\[YYۜ[[ܚ]R[[YR]\][ېYܙTY\[H\JNˆBјXBXXY \YYXXX[]W]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\XXX[]T]H] X[J[[^\\][[ۜX[۔XXX[]KȊNˆ\XXX[]T]H] X[J[[^\\][[ۜX[ۓXXX[]KȊN‚\\ YJ[K^\XXX[]T] +K  X\YYܚ]HXXX[]H[]H[H\Y\X[[KN‚\XXX[]T\HH[KXY[^ +XXX[]T] +Nˆ\\ \ӛ۝Z[]]H]X\\YYܚ]QܔY\[HXXX[]T\JNˆ\\ \ӛ۝Z[]]H]X\[^XXX[]T\JNˆ\\ \ӛ۝Z[]]H]X[ܚ]TXX]\]\][ۈXXX[]T\JN‚\XXX[]T\HH[KXY[^ +XXX[]T] +Nˆ\\ ۝Z[]]H]X\\YYܚ]QܔY\[HXXX[]T\JNˆ\\ ۝Z[]]H]X\[^XXX[]T\JNˆ\\ ۝Z[]]H]X[ܚ]TXX]\]\][ۈXXX[]T\JNˆBјXBXXY [НZ[\\[[]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\]H] X[J[[^\\][[ۜX[۔[НZ[\˘ȊNˆ\\[[]H] X[J[[^\\][[ۜX[۔[НZ[\\[[ȊN‚\\ YJ[K^\\[[] +K [НZ[\\[[[]H[H\Y\X[[KN‚\\HH[KXY[^ +] +Nˆ\\ \ӛ۝Z[]]H]X۝Z[\X[\[НZ[\\[\JNˆ\\ \ӛ۝Z[]]H]X[]]\X\[YY[НZ[\\]\JN‚\\[[\HH[KXY[^ +\[[] +Nˆ\\ ۝Z[]]H]X۝Z[\X[\[НZ[\\[\[[\JNˆ\\ \ӛ۝Z[]]H]X[]]\X\[YY[НZ[\\]\[[\JNˆBјXBXXY [НZ[\\]]X[ۗ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\\[[]H] X[J[[^\\][[ۜX[۔[НZ[\\[[ȊNˆ\\]]X[۔]H] X[J[[^\\][[ۜX[۔[НZ[\\]]X[ۋȊN‚\\ YJ[K^\\]]X[۔] +K [НZ[\\]]X[ۈ[]H[H\Y\X[[KN‚\\[[\HH[KXY[^ +\[[] +Nˆ\\ \ӛ۝Z[]]H]X[]]\X\[YY[НZ[\\]\[[\JNˆ\\ \ӛ۝Z[YXԙ\]\[[\JN‚\\]]X[۔\HH[KXY[^ +\]]X[۔] +Nˆ\\ ۝Z[]]H]X[]]\X\[YY[НZ[\\]\]]X[۔\JNˆ\\ ۝Z[YXԙ\]\]]X[۔\JNˆBјXBXXY LW\[XPۙY\][۔\][ۗ]\[YX]Y\X[ + +Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[XP[[[[ȋ LW[]SZ\[[X\R^HNˆ\ۙY\][۔[]H] X[J[[^\\[]SZ\[[X\R^PۙY\][۔[ȊNˆ\\[XT\][۔]H] X[J[[^\\[]SZ\[[X\R^P\[XT\][ۋȊNˆ\\[XS[]H] X[J[[^\\[]SZ\[[X\R^P\[XS[\][ۋȊNˆ\\[XSY[X\]H] X[J[[^\\[]SZ\[[X\R^P\[XSY[X\\][ۋȊN‚\\ YJ[K^\\[XT\][۔] +K LH\PۙY\][ۜќP\[XH\][ۈ[]H[H\Y\X[[KNˆ\\ YJ[K^\\[XS[] +K LH\PۙY\][ۜќP\[XH[\][ۈ[]H[H\Y\X[[KNˆ\\ YJ[K^\\[XSY[X\] +K LH\PۙY\][ۜќP\[XHY[X\\][ۈ[]H[H\Y\X[[KN‚\ۙY\][۔[\HH[KXY[^ +ۙY\][۔[] +Nˆ\\ \ӛ۝Z[]]H]X\\[\[XQ^\[ۈۙY\][۔[\JNˆ\\ \ӛ۝Z[]]H]XT\S[\[\[XHۙY\][۔[\JN‚\\[XT\][۔\HH[KXY[^ +\[XT\][۔] +Nˆ\\ ۝Z[]]H]X\\[\[XQ^\[ۈ\[XT\][۔\JNˆ\\ \ӛ۝Z[]]H]XT\S[\[\[XH\[XT\][۔\JN‚\\[XS[\HH[KXY[^ +\[XS[] +Nˆ\\ ۝Z[]]H]XT\S[\[\[XH\[XS[\JNˆ\\ \ӛ۝Z[]]HzmkwB$33%WV7WFUWFFTf$'VƵWFFW2"f"6fT6vW4FUFF6&RfW$F"$WV7WFUWFFTf$'VƵWFFW4fW%6fT6vW4FR72"f"757W'EFF6&RfW$F"$WV7WFUWFFTf$'VƵWFFW4fW$757W'B72"76W'BG'VRfRW7G2757W'EF$33"fW"WV7WFUWFFT726&ƗGFWFV7F6VBƗfRf7W6VB'FfR"f"6fT6vW4FU6W&6RfR&VDFWB6fT6vW4FUF76W'BFW4D6F'&fFR7FF2&4WV7WFUWFFT75FVfW&B"6fT6vW4FU6W&6R76W'BFW4D6F'&fFR7FF2&4WV7WFUWFFT757W'B"6fT6vW4FU6W&6R76W'BFW4D6F'&fFR7FF2&4WV7WFUWFFT74WFB"6fT6vW4FU6W&6R76W'BFW4D6F'&fFR7FF2&4WV7WFUWFFT74ƖTWFB"6fT6vW4FU6W&6R76W'BFW4D6F'&fFR7FF2&4VFGg&Wv&6&TW76R"6fT6vW4FU6W&6Rf"757W'E6W&6RfR&VDFWB757W'EF76W'B6F2'&fFR7FF2&4WV7WFUWFFT75FVfW&B"757W'E6W&6R76W'B6F2'&fFR7FF2&4WV7WFUWFFT757W'B"757W'E6W&6R76W'B6F2'&fFR7FF2&4WV7WFUWFFT74WFB"757W'E6W&6R76W'B6F2'&fFR7FF2&4WV7WFUWFFT74ƖTWFB"757W'E6W&6R76W'B6F2'&fFR7FF2&4VFGg&Wv&6&TW76R"757W'E6W&6RРf7EТV&Ɩ2fB33%6w&FTFWFV7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$'VƴW&F4E6WD&6VEw&FW2"$33%WV7WFUWFFTf$'VƵWFFW2"f"7FFVVDǗ65FF6&RǗW$F"$WV7WFUWFFTf$'VƵWFFW57FFVVDǗ6272"f"6w&FTFWFV7FFF6&RǗW$F"$WV7WFUWFFTf$'VƵWFFW46w&FTFWFV7F72"76W'BG'VRfRW7G26w&FTFWFV7FF$33"6w&FRFWFV7F6VBƗfRf7W6VBǗW"'FfR"f"7FFVVDǗ656W&6RfR&VDFWB7FFVVDǗ65F76W'BFW4D6F'&fFR7FF2&46w&FW2"7FFVVDǗ656W&6Rf"6w&FTFWFV7F6W&6RfR&VDFWB6w&FTFWFV7FF76W'B6F2'&fFR7FF2&46w&FW2"6w&FTFWFV7F6W&6RРf7EТV&Ɩ2fB376V&ǕW6u66UG&fW'6ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3VFG֗76u&'W"f"f6&ƗGFF6&RǗW$F"$VFG֗76u&'W76V&ǕW6uf6&ƗG72"f"W6u66UFF6&RǗW$F"$VFG֗76u&'W76V&ǕW6u66R72"76W'BG'VRfRW7G2W6u66UF$376V&ǒW6r66RG&fW'66VBƗfRf7W6VB'FfR"f"f6&ƗG6W&6RfR&VDFWBf6&ƗGF76W'BFW4D6F'&fFR7FF2&W6tF&V7FfT66R"f6&ƗG6W&6R76W'BFW4D6F&f&V6f"W76TFV6&FFR6W7F'2eGSW76TFV6&F7Fₒ"f6&ƗG6W&6R76W'BFW4D6F&f&V6f"fU66VDW76RFR6W7F'2eGSfU66VDW76TFV6&F7Fₒ"f6&ƗG6W&6Rf"W6u66U6W&6RfR&VDFWBW6u66UF76W'B6F2'&fFR7FF2&W6tF&V7FfT66R"W6u66U6W&6R76W'B6F2&f&V6f"W76TFV6&FFR6W7F'2eGSW76TFV6&F7Fₒ"W6u66U6W&6R76W'B6F2&f&V6f"fU66VDW76RFR6W7F'2eGSfU66VDW76TFV6&F7Fₒ"W6u66U6W&6RРf7EТV&Ɩ2fB376V&ǔV&W%&W6WFƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3VFG֗76u&'W"f"6&W6WFFF6&RǗW$F"$VFG֗76u&'W76V&ǔ6&W6WF72"f"V&W%&W6WFFF6&RǗW$F"$VFG֗76u&'W76V&ǔV&W%&W6WF72"76W'BG'VRfRW7G2V&W%&W6WFF$376V&ǒV&W"FƗW"&W6WF6VBƗfRf7W6VB'FfR"f"6&W6WF6W&6RfR&VDFWB6&W6WFF76W'BFW4D6F'&fFR7FF2&G'&W6fTV&W$7W'&VD76V&ǒ"6&W6WF6W&6Rf"V&W%&W6WF6W&6RfR&VDFWBV&W%&W6WFF76W'B6F2'&fFR7FF2&G'&W6fTV&W$7W'&VD76V&ǒ"V&W%&W6WF6W&6R76W'B6F2&f&V6f"V&W"V&W'2"V&W%&W6WF6W&6R76W'B6F2$FV6&u7F&VfW&V6W2"V&W%&W6WF6W&6RРf7EТV&Ɩ2fB3efW$W&W76&G6666W74F66fW'ƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%VW'6TEG&6F"$3efDFFUFTr"f"W&W76&GFF6&RfW$F"$fDFFUFTtfW$W&W76&G72"f"6666W75FF6&RfW$F"$fDFFUFTtfW$W&W76&G6666W76W272"76W'BG'VRfRW7G26666W75F$3bW&W76&FVB6666W72F66fW'6VBƗfRf7W6VB'FfR"f"W&W76&G6W&6RfR&VDFWBW&W76&GF76W'BFW4D6F'&fFR7FF2&VDǔƗ7C66&W6VVC'VDW&W76&G&W6VVG2"W&W76&G6W&6R76W'BFW4D6F'&fFR7FF266&W6VVCfE&W6VVDf""W&W76&G6W&6R76W'BFW4D6F'&fFR7FF2VVW&&SV&W$66W74W&W767FfDW&W76&G6666W76W2"W&W76&G6W&6R76W'BFW4D6F'&fFR7FF2&466&W'G66W72"W&W76&G6W&6R76W'BFW4D6F'&fFR6VVB67266&W6VVB"W&W76&G6W&6Rf"6666W756W&6RfR&VDFWB6666W75F76W'B6F2'&fFR7FF2&VDǔƗ7C66&W6VVC'VDW&W76&G&W6VVG2"6666W756W&6R76W'B6F2'&fFR7FF266&W6VVCfE&W6VVDf""6666W756W&6R76W'B6F2'&fFR7FF2VVW&&SV&W$66W74W&W767FfDW&W76&G6666W76W2"6666W756W&6R76W'B6F2'&fFR7FF2&466&W'G66W72"6666W756W&6R76W'B6F2'&fFR6VVB67266&W6VVB"6666W756W&6RРf7EТV&Ɩ2fB3ufW$6VFU&Ww&FUƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$WV7WFD72"$3uW4TW""f"7FǗ65FF6&RfW$F"$W4TW$fW%7FǗ6272"f"6VFU&Ww&FUFF6&RfW$F"$W4TW$fW$6VFU&Ww&FR72"76W'BG'VRfRW7G26VFU&Ww&FUF$3rfW"6VFR&Ww&FR67G'V7F6VBƗfRf7W6VB'FfR"f"7FǗ656W&6RfR&VDFWB7FǗ65F76W'BFW4D6F'&fFR7FF2&G'FD6VFR"7FǗ656W&6Rf"6VFU&Ww&FU6W&6RfR&VDFWB6VFU&Ww&FUF76W'B6F2'&fFR7FF2&G'FD6VFR"6VFU&Ww&FU6W&6R76W'B6F2%7Ff7F'f6FW&W76"6VFU&Ww&FU6W&6R76W'B6F2%7Ff7F'FVFfW$R$6VFU""6VFU&Ww&FU6W&6RРf7EТV&Ɩ2fB3g&W66FWDWVFƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$36fT6vW4"f"ǗW%FF6&RǗW$F"%6fT6vW4ǗW"72"f"g&W66FWEFF6&RǗW$F"%6fT6vW4g&W66FWB72"76W'BG'VRfRW7G2g&W66FWEF$3g&W6F$6FWB֖WVF6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'BFW4D6F'&fFR7FF2&56fU&V6VfW$g&W66FWDFV6&VD6FT&G"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&46w&GFV&Vf&TW&F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&46&VfW&V6R"ǗW%6W&6Rf"g&W66FWE6W&6RfR&VDFWBg&W66FWEF76W'B6F2'&fFR7FF2&56fU&V6VfW$g&W66FWDFV6&VD6FT&G"g&W66FWE6W&6R76W'B6F2'&fFR7FF2&46w&GFV&Vf&TW&F"g&W66FWE6W&6R76W'B6F2'&fFR7FF2&46&VfW&V6R"g&W66FWE6W&6RРf7EТV&Ɩ2fB335F66WDf6F676f6FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$335W6Tg&V6WDf%7FF4V&W'666W2"f"FƗW%FF6&RǗW$F"%W6Tg&V6WDf%7FF4V&W'666W4FƗW$676f6F72"f"F66WEFF6&RǗW$F"%W6Tg&V6WDf%7FF4V&W'666W5F66WD676f6F72"76W'BG'VRfRW7G2F66WEF$332F66WBf6F676f6F6VBƗfRf7W6VB'FfR"f"FƗW%6W&6RfR&VDFWBFƗW%F76W'BFW4D6F'&fFR7FF2&57W'FVEF66WDf6F"FƗW%6W&6R76W'BFW4D6F'&fFR7FF2&57FF5GT$W76T66W72"FƗW%6W&6R76W'BFW4D6F'&fFR7FF2&5GT$W76U7&"FƗW%6W&6Rf"F66WE6W&6RfR&VDFWBF66WEF76W'B6F2'&fFR7FF2&57W'FVEF66WDf6F"F66WE6W&6R76W'B6F2'&fFR7FF2&57FF5GT$W76T66W72"F66WE6W&6R76W'B6F2'&fFR7FF2&5GT$W76U7&"F66WE6W&6RРf7EТV&Ɩ2fB3#%w&WuVW'&TWVFƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$3#%FƗ7D6VV7E&V7F"f"ǗW%FF6&RǗW$F"%FƗ7D6VV7E&V7FǗW"72"f"w&WuFF6&RǗW$F"%FƗ7D6VV7E&V7Fw&Wr72"76W'BG'VRfRW7G2w&WuF$3#"w&WrVW'&RWVF6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F672FƗ7D6VV7E&V7FǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&4w&WuVW'&R"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2GU7&vWEVW'&TVVVEGR"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&4w&WuGR"ǗW%6W&6Rf"w&Wu6W&6RfR&VDFWBw&WuF76W'B6F2'&fFR7FF2&4w&WuVW'&R"w&Wu6W&6R76W'B6F2'&fFR7FF2GU7&vWEVW'&TVVVEGR"w&Wu6W&6R76W'B6F2'&fFR7FF2&4w&WuGR"w&Wu6W&6RРf7EТV&Ɩ2fB35fW%W&67FDFWFV7FƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$35fW$6VB"f"fW%FF6&RfW$F"$fW$6VDfW"72"f"W&67FEFF6&RfW$F"$fW$6VDfW%W&67FB72"76W'BG'VRfRW7G2W&67FEF$32fW"W&67FBFWFV7F6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672fW$6VDfW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&5W&67FB"fW%6W&6R76W'BFW4D6F'&fFR7FF2&5W&67FB"fW%6W&6R76W'BFW4D6F'&fFR7FF2&5W&fVR"fW%6W&6Rf"W&67FE6W&6RfR&VDFWBW&67FEF76W'B6F2'&fFR7FF2&5W&67FB"W&67FE6W&6R76W'B6F2'&fFR7FF2&5W&67FB"W&67FE6W&6R76W'B6F2'&fFR7FF2&5W&fVR"W&67FE6W&6RРf7EТV&Ɩ2fB3#ufW%&'WF66fW'ƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3#u֗76tWƖ6Df&VvW"f"fW%FF6&RfW$F"$֗76tWƖ6Df&VvWfW"72"f"&'WFF6&RfW$F"$֗76tWƖ6Df&VvWfW%&'WF66fW'72"76W'BG'VRfRW7G2&'WF$3#rfW"&'ֶWF66fW'6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672֗76tWƖ6Df&VvWfW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&W'G7&G'fD6fVF&'W"fW%6W&6R76W'BFW4D6F'&fFR7FF2&W'G7&G'fD6fwW&VE&'W"fW%6W&6R76W'BFW4D6F'&fFR7FF2&G'vWDVFGGT'VFW$VFG"fW%6W&6Rf"&'W6W&6RfR&VDFWB&'WF76W'B6F2'&fFR7FF2&W'G7&G'fD6fVF&'W"&'W6W&6R76W'B6F2'&fFR7FF2&W'G7&G'fD6fwW&VE&'W"&'W6W&6R76W'B6F2'&fFR7FF2&G'vWDVFGGT'VFW$VFG"&'W6W&6RРf7EТV&Ɩ2fB3eVW'&U66TFWFV7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%VW'6TEG&6F"$3efDFFUFTr"f"ǗW%FF6&RǗW$F"$fDFFUFTtǗW"72"f"VW'&U66UFF6&RǗW$F"$fDFFUFTuVW'&U66R72"76W'BG'VRfRW7G2VW'&U66UF$3bVW'&R&F66RFWFV7F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F672fDFFUFTtǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2W4gV7FW&FfEVW'&T&F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5F&vWEVW'&Tf6F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2W4gV7FW&FfDV66t&F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2VVW&&SĔW&FVVW&FTW&F2"ǗW%6W&6Rf"VW'&U66U6W&6RfR&VDFWBVW'&U66UF76W'B6F2'&fFR7FF2W4gV7FW&FfEVW'&T&F"VW'&U66U6W&6R76W'B6F2'&fFR7FF2&5F&vWEVW'&Tf6F"VW'&U66U6W&6R76W'B6F2'&fFR7FF2W4gV7FW&FfDV66t&F"VW'&U66U6W&6R76W'B6F2'&fFR7FF2VVW&&SĔW&FVVW&FTW&F2"VW'&U66U6W&6RРf7EТV&Ɩ2fB3#uGTFW67G'V7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3#u֗76tWƖ6Df&VvW"f"GTWFF6&RǗW$F"$֗76tWƖ6Df&VvWGTW72"f"GTFWFF6&RǗW$F"$֗76tWƖ6Df&VvWGTFW72"76W'BG'VRfRW7G2GTFWF$3#rGR֖FW67G'V7F6VBƗfRf7W6VB'FfR"f"GTW6W&6RfR&VDFWBGTWF76W'B6F2'&fFR6VVB'F6726FFV"GTW6W&6R76W'BFW4D6F'&fFRGTFW'VEGTFW"GTW6W&6R76W'BFW4D6F'&fFR6VVB672GTFW"GTW6W&6R76W'BFW4D6F'&fFR7FF2fBFDW76UGW2"GTW6W&6R76W'BFW4D6F'&fFR7FF2fBFEGTDW7FVEGW2"GTW6W&6R76W'BFW4D6F'&fFR7FF2fBFDWR"GTW6W&6Rf"GTFW6W&6RfR&VDFWBGTFWF76W'B6F2'&fFRGTFW'VEGTFW"GTFW6W&6R76W'B6F2'&fFR6VVB672GTFW"GTFW6W&6R76W'B6F2'&fFR7FF2fBFDW76UGW2"GTFW6W&6R76W'B6F2'&fFR7FF2fBFEGTDW7FVEGW2"GTFW6W&6R76W'B6F2'&fFR7FF2fBFDWR"GTFW6W&6RРf7EТV&Ɩ2fB3VW'&U66TFWFV7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%VW'6TEG&6F"$36WFB"f"ǗW%FF6&RǗW$F"$6WFDǗW"72"f"VW'&U66UFF6&RǗW$F"$6WFDǗW%VW'&U66R72"76W'BG'VRfRW7G2VW'&U66UF$3VW'&R66RFWFV7F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'BFW4D6F'&fFR7FF2&5G&6F7&F6VW'&Tf6F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&wVVDW&FvWDWE6WVV6T&wVVB"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&f6FFWVG4&F&WFW""ǗW%6W&6Rf"VW'&U66U6W&6RfR&VDFWBVW'&U66UF76W'B6F2'&fFR7FF2&5G&6F7&F6VW'&Tf6F"VW'&U66U6W&6R76W'B6F2'&fFR7FF2&wVVDW&FvWDWE6WVV6T&wVVB"VW'&U66U6W&6R76W'B6F2'&fFR7FF2&f6FFWVG4&F&WFW""VW'&U66U6W&6RРf7EТV&Ɩ2fB3#FVFG6VV7F$676f6FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$3#&VGVFDFVFG6VV7B"f"ǗW%FF6&RǗW$F"%&VGVFDFVFG6VV7DǗW"72"f"FVFG6VV7F%FF6&RǗW$F"%&VGVFDFVFG6VV7DFVFG6VV7F"72"76W'BG'VRfRW7G2FVFG6VV7F%F$3#FVFG6VV7F"676f6F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F672&VGVFDFVFG6VV7DǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2W4gV7FW&FG'vWD&F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&4W7DVVW&&TFW&f6R"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5GU&W6W'fu6VV7F""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&4FVFG&F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2W&FVw&FVFGfVR"ǗW%6W&6Rf"FVFG6VV7F%6W&6RfR&VDFWBFVFG6VV7F%F76W'B6F2'&fFR7FF2W4gV7FW&FG'vWD&F"FVFG6VV7F%6W&6R76W'B6F2'&fFR7FF2&4W7DVVW&&TFW&f6R"FVFG6VV7F%6W&6R76W'B6F2'&fFR7FF2&5GU&W6W'fu6VV7F""FVFG6VV7F%6W&6R76W'B6F2'&fFR7FF2&4FVFG&F"FVFG6VV7F%6W&6R76W'B6F2'&fFR7FF2W&FVw&FVFGfVR"FVFG6VV7F%6W&6RРf7EТV&Ɩ2fB3%fW%VW'6W&6T6FWE&W6WFƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$'VƴW&F4E6WD&6VEw&FW2"$3%F֗U&VfU&vR"f"&Ww&FU6fWGFF6&RfW$F"$F֗U&VfU&vTfW%&Ww&FU6fWG72"f"VW'6W&6UFF6&RfW$F"$F֗U&VfU&vTfW%VW'6W&6T6FWB72"76W'BG'VRfRW7G2VW'6W&6UF$3"fW"VW'6W&6R6FWB&W6WF6VBƗfRf7W6VB'FfR"f"&Ww&FU6fWG6W&6RfR&VDFWB&Ww&FU6fWGF76W'BFW4D6F'&fFR7FF2&G'&W6fUVW'6W&6Tg&W66FWD6"&Ww&FU6fWG6W&6R76W'BFW4D6F'&fFR7FF2&G'vWEG&7&VEVW'f6F6W&6R"&Ww&FU6fWG6W&6R76W'BFW4D6F'&fFR7FF2&56vU6W&6UG&7&VEVW'WFB"&Ww&FU6fWG6W&6Rf"VW'6W&6RfR&VDFWBVW'6W&6UF76W'B6F2'&fFR7FF2&G'&W6fUVW'6W&6Tg&W66FWD6"VW'6W&6R76W'B6F2'&fFR7FF2&G'vWEG&7&VEVW'f6F6W&6R"VW'6W&6R76W'B6F2'&fFR7FF2&56vU6W&6UG&7&VEVW'WFB"VW'6W&6RРf7EТV&Ɩ2fB33e6gV7F6&66GW&UƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$WV7WFD72"$33eF$6FWD6GW&VD7&75F&VG2"f"6GW&TǗ65FF6&RǗW$F"$F$6FWD6GW&VD7&75F&VG46GW&TǗ6272"f"6gV7FFF6&RǗW$F"$F$6FWD6GW&VD7&75F&VG46gV7F6GW&R72"76W'BG'VRfRW7G26gV7FF$33b6gV7F6&66GW&R6VBƗfRf7W6VB'FfR"f"6GW&TǗ656W&6RfR&VDFWB6GW&TǗ65F76W'BFW4D6F'&fFR7FF2&G'fD6GW&VDF$6FWD6gV7F6&6"6GW&TǗ656W&6Rf"6gV7F6W&6RfR&VDFWB6gV7FF76W'B6F2'&fFR7FF2&G'fD6GW&VDF$6FWD6gV7F6&6"6gV7F6W&6R76W'B6F2$6gV7F7FFVVE7F"6gV7F6W&6R76W'B6F2$WFDB6gV7F"6gV7F6W&6RРf7EТV&Ɩ2fB33UvW&T6G&fW'6ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$'VƴW&F4E6WD&6VEw&FW2"$33U֗76uvW&T&Vf&TWV7WFTFVWFUWFFR"f"ǗW%FF6&RǗW$F"$֗76uvW&T&Vf&TWV7WFTFVWFUWFFTǗW"72"f"vW&T6FF6&RǗW$F"$֗76uvW&T&Vf&TWV7WFTFVWFUWFFUvW&T672"76W'BG'VRfRW7G2vW&T6F$33RvW&R6G&fW'66VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'BFW4D6F'&fFR7FF2&5vW&T6"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&4vƖvW&R"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5VW'7FvW&R"ǗW%6W&6Rf"vW&T66W&6RfR&VDFWBvW&T6F76W'B6F2'&fFR7FF2&5vW&T6"vW&T66W&6R76W'B6F2'&fFR7FF2&4vƖvW&R"vW&T66W&6R76W'B6F2'&fFR7FF2&5VW'7FvW&R"vW&T66W&6RРf7EТV&Ɩ2fB3%fW%&VGVFDFW&ƗF&Ww&FUƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$3%&VGW&TFW&ƗF"f"fW%FF6&RfW$F"%&VGW&TFW&ƗFfW"72"f"&VGVFE&Ww&FUFF6&RfW$F"%&VGW&TFW&ƗFfW%&VGVFDFW&ƗF72"76W'BG'VRfRW7G2&VGVFE&Ww&FUF$3"&VGVFBFW&ƗFfW"&Ww&FR6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'BFW4D6F'&fFR7FF272F6F7VVC&VfU&VGVFDFW&ƗF72"fW%6W&6Rf"&VGVFE&Ww&FU6W&6RfR&VDFWB&VGVFE&Ww&FUF76W'B6F2'&fFR7FF272F6F7VVC&VfU&VGVFDFW&ƗF72"&VGVFE&Ww&FU6W&6R76W'B6F2%G'vWDƖTFW&ƗW%'G2"&VGVFE&Ww&FU6W&6R76W'B6F2$f&GFW"FF"&VGVFE&Ww&FU6W&6RРf7EТV&Ɩ2fB33uV6tfuƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%&u7E6V7W&G"$33u&u77G&t67G'V7F"f"FW&֖FfuFF6&RǗW$F"%&u77G&t67G'V7FFW&֖Ffr72"f"VfuFF6&RǗW$F"%&u77G&t67G'V7FVfr72"76W'BG'VRfRW7G2VfuF$33rV&VfW&V6R6rfr6VBƗfRf7W6VB'FfR"f"FW&֖Ffu6W&6RfR&VDFWBFW&֖FfuF76W'BFW4D6F'&fFR7FF2&5FVFǕ6t6W7F""FW&֖Ffu6W&6R76W'BFW4D6F'&fFR7FF2&6F56F"FW&֖Ffu6W&6R76W'BFW4D6F'&fFR7FF2&V65&VfW&V6R"FW&֖Ffu6W&6Rf"Vfu6W&6RfR&VDFWBVfuF76W'B6F2'&fFR7FF2&5FVFǕ6t6W7F""Vfu6W&6R76W'B6F2'&fFR7FF2&6F56F"Vfu6W&6R76W'B6F2'&fFR7FF2&V65&VfW&V6R"Vfu6W&6RРf7EТV&Ɩ2fB676vVD66U&E66ƗfW4FVF6FVE'F‚f"WFV64F"F6&R&W&B'7&2"$Ɩ6G&&B"$WFV62"f"66UFF6&RWFV64F"$676vVD66R72"f"&E66FF6&RWFV64F"$676vVD66U&E6672"76W'BG'VRfRW7G2&E66F$676vVD66R&B66r6VBƗfRf7W6VB'FfR"f"66U6W&6RfR&VDFWB66UF76W'B6F2&FW&7FF2'F672676vVD66R"66U6W&6R76W'BFW4D6F'&fFR6VVB672&E66"66U6W&6R76W'BFW4D6F'V&Ɩ27FF2&E66'VB"66U6W&6Rf"&E666W&6RfR&VDFWB&E66F76W'B6F2'&fFR6VVB672&E66"&E666W&6R76W'B6F2'V&Ɩ27FF2&E66'VB"&E666W&6R76W'B6F2'&fFR7FF2fBFB"&E666W&6RРf7EТV&Ɩ2fB3%&fFW%6fU7G&tWFG5ƗfTFVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$3%&VGW&TFW&ƗF"f"&fFW%6fUFF6&RǗW$F"%&VGW&TFW&ƗF&fFW%6fTW&W76272"f"7G&tWFG5FF6&RǗW$F"%&VGW&TFW&ƗF&fFW%6fU7G&tWFG272"76W'BG'VRfRW7G27G&tWFG5F$3"&fFW"6fR7G&rWFB'VW26VBƗfRf7W6VB'FfR"f"&fFW%6fU6W&6RfR&VDFWB&fFW%6fUF76W'BFW4D6F'&fFR7FF2&4vVE&fFW%6fU7G&tWFB"&fFW%6fU6W&6R76W'BFW4D6F'&fFR7FF2&57G&t6&6&WFW""&fFW%6fU6W&6Rf"7G&tWFG56W&6RfR&VDFWB7G&tWFG5F76W'B6F2'&fFR7FF2&4vVE&fFW%6fU7G&tWFB"7G&tWFG56W&6R76W'B6F2'&fFR7FF2&57G&t6&6&WFW""7G&tWFG56W&6R76W'B6F2%7G&t6&6"7G&tWFG56W&6RРf7EТV&Ɩ2fB3efW%7FF4&F6fWGƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%VW'6TEG&6F"$3efDFFUFTr"f"fW%FF6&RfW$F"$fDFFUFTtfW"72"f"7FF4&FFF6&RfW$F"$fDFFUFTtfW%7FF4&F72"76W'BG'VRfRW7G27FF4&FF$3b7FF2&FfW"6fWG6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'BFW4D6F'&fFR7FF2&46FU7FF4&F"fW%6W&6R76W'BFW4D6F'&fFR7FF2&57FF4FfW""fW%6W&6Rf"7FF4&F6W&6RfR&VDFWB7FF4&FF76W'B6F2'&fFR7FF2&46FU7FF4&F"7FF4&F6W&6R76W'B6F2'&fFR7FF2&57FF4FfW""7FF4&F6W&6R76W'B6F2%7FB7FF4Wv&B"7FF4&F6W&6RРf7EТV&Ɩ2fB3CE&V6&ƗG&6fvFƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$3CE4G&6uFVFg"f"&V6&ƗGFF6&RǗW$F"$4G&6uFVFg&V6&ƗG72"f"&6fvFFF6&RǗW$F"$4G&6uFVFg&V6&ƗG&6272"76W'BG'VRfRW7G2&6fvFF$3CB&V6&ƗG&6fvF6VBƗfRf7W6VB'FfR"f"&V6&ƗG6W&6RfR&VDFWB&V6&ƗGF76W'BFW4D6F'&fFR7FF2&4&66W7F""&V6&ƗG6W&6R76W'BFW4D6F'&fFR7FF2W&FfDF&V7D6DW&F6Fu7"&V6&ƗG6W&6R76W'BFW4D6F'&fFR7FF2&6W&FfDV66t&6"&V6&ƗG6W&6Rf"&6fvF6W&6RfR&VDFWB&6fvFF76W'B6F2'&fFR7FF2&4&66W7F""&6fvF6W&6R76W'B6F2'&fFR7FF2W&FfDF&V7D6DW&F6Fu7"&6fvF6W&6R76W'B6F2'&fFR7FF2&6W&FfDV66t&6"&6fvF6W&6RРf7EТV&Ɩ2fB33tƗfVEGU&V6vFƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$33F$6FWD6vWF"f"WfFV6UFF6&RǗW$F"$F$6FWD6vWFtƗfVDWfFV6R72"f"GU&V6vFFF6&RǗW$F"$F$6FWD6vWFtƗfVEGU&V6vF72"76W'BG'VRfRW7G2GU&V6vFF$33rƗfVB66VBGR&V6vFVW'26VBƗfRf7W6VB'FfR"f"WfFV6U6W&6RfR&VDFWBWfFV6UF76W'BFW4D6F'&fFR7FF2&4v66VEGR"WfFV6U6W&6R76W'BFW4D6F'&fFR7FF2&46fVF֖FFWv&U6vGW&R"WfFV6U6W&6R76W'BFW4D6F'&fFR7FF2&VVG4FW&f6R"WfFV6U6W&6R76W'BFW4D6F'&fFR7FF2&W&G4g&"WfFV6U6W&6Rf"GU&V6vF6W&6RfR&VDFWBGU&V6vFF76W'B6F2'&fFR7FF2&4v66VEGR"GU&V6vF6W&6R76W'B6F2'&fFR7FF2&46fVF֖FFWv&U6vGW&R"GU&V6vF6W&6R76W'B6F2'&fFR7FF2&VVG4FW&f6R"GU&V6vF6W&6R76W'B6F2'&fFR7FF2&W&G4g&"GU&V6vF6W&6RРf7EТV&Ɩ2fB3%fW%6fT6vW4'&6W6W6ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$'VƴW&F4E6WD&6VEw&FW2"$3%F֗U&VfU&vR"f"6fT6vW56fWGFF6&RǗW$F"$F֗U&VfU&vTfW%6fT6vW56fWG72"f"'&6W6W6FF6&RǗW$F"$F֗U&VfU&vTfW%6fT6vW4'&6W6W672"76W'BG'VRfRW7G2'&6W6W6F$3"fW"6fR6vW2'&6W6W66VBƗfRf7W6VB'FfR"f"6fT6vW56fWG6W&6RfR&VDFWB6fT6vW56fWGF76W'BFW4D6F'&fFR7FF2&&TWGVǔW6W6fT'&6W2"6fT6vW56fWG6W&6R76W'BFW4D6F'&fFR7FF27FFSvWD6Ftd'&6"6fT6vW56fWG6W&6R76W'BFW4D6F'&fFR7FF27vF66V7F7FvWD6Fu7vF66V7F"6fT6vW56fWG6W&6Rf"'&6W6W66W&6RfR&VDFWB'&6W6W6F76W'B6F2'&fFR7FF2&&TWGVǔW6W6fT'&6W2"'&6W6W66W&6R76W'B6F2'&fFR7FF27FFSvWD6Ftd'&6"'&6W6W66W&6R76W'B6F2'&fFR7FF27vF66V7F7FvWD6Fu7vF66V7F"'&6W6W66W&6RРf7EТV&Ɩ2fB33u7G&t'VFW$FVFG&VfW&V6UG&fW'6ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%&u7E6V7W&G"$33u&u77G&t67G'V7F"f"FVFGFF6&RǗW$F"%&u77G&t67G'V7F7G&t'VFW$FVFG72"f"&VfW&V6UG&fW'6FF6&RǗW$F"%&u77G&t67G'V7F7G&t'VFW$FVFG&VfW&V6W272"76W'BG'VRfRW7G2&VfW&V6UG&fW'6F$33r7G&t'VFW"FVFG&VfW&V6RG&fW'66VBƗfRf7W6VB'FfR"f"FVFG6W&6RfR&VDFWBFVFGF76W'BFW4D6F'&fFR7FF2&&VfW&V6TFVFG"FVFG6W&6Rf"&VfW&V6UG&fW'66W&6RfR&VDFWB&VfW&V6UG&fW'6F76W'B6F2'&fFR7FF2&&VfW&V6TFVFG"&VfW&V6UG&fW'66W&6R76W'B6F2$FW'FVE7G&tW&F"&VfW&V6UG&fW'66W&6R76W'B6F2$&V7D7&VFW&F"&VfW&V6UG&fW'66W&6R76W'B6F2$f6FW&F"&VfW&V6UG&fW'66W&6RРf7EТV&Ɩ2fB3576vVEfVT6V7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$35F76VD6FWEVW'"f"&v&W6WFFF6&RǗW$F"$F76VD6FWEVW'76vVD&v&W6WF72"f"76vVEfVW5FF6&RǗW$F"$F76VD6FWEVW'76vVEfVW272"76W'BG'VRfRW7G276vVEfVW5F$3276vVBfVR6V7F6VBƗfRf7W6VB'FfR"f"&v&W6WF6W&6RfR&VDFWB&v&W6WFF76W'BFW4D6F'&fFR7FF2&G'vWE6vT76vVEfVR"&v&W6WF6W&6R76W'BFW4D6F'&fFR7FF2Ɨ7CĔW&FvWD76vVEfVW2"&v&W6WF6W&6R76W'BFW4D6F'&fFR7FF2&46F&vWB"&v&W6WF6W&6R76W'BFW4D6F'&fFR7FF2VVW&&SĔW&FVVW&FTW&F2"&v&W6WF6W&6Rf"76vVEfVW56W&6RfR&VDFWB76vVEfVW5F76W'B6F2'&fFR7FF2&G'vWE6vT76vVEfVR"76vVEfVW56W&6R76W'B6F2'&fFR7FF2Ɨ7CĔW&FvWD76vVEfVW2"76vVEfVW56W&6R76W'B6F2'&fFR7FF2&46F&vWB"76vVEfVW56W&6R76W'B6F2'&fFR7FF2VVW&&SĔW&FVVW&FTW&F2"76vVEfVW56W&6RРf7EТV&Ɩ2fB3CfW$f6F&Ww&FUƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$3C6vTVFG66%&V7F"f"fW%FF6&RfW$F"%6vTVFG66%&V7FfW"72"f"&Ww&FUFF6&RfW$F"%6vTVFG66%&V7FfW%&Ww&FR72"76W'BG'VRfRW7G2&Ww&FUF$3CfW"f6F&Ww&FR6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'BFW4D6F'&fFR7FF2W&W767F&Ww&FTf6F"fW%6W&6R76W'BFW4D6F'&fFR7FF2BfE&VF6FTFW"fW%6W&6Rf"&Ww&FU6W&6RfR&VDFWB&Ww&FUF76W'B6F2'&fFR7FF2W&W767F&Ww&FTf6F"&Ww&FU6W&6R76W'B6F2'&fFR7FF2BfE&VF6FTFW"&Ww&FU6W&6R76W'B6F2"6VV7B"&Ww&FU6W&6RРf7EТV&Ɩ2fB3%FW&ƗW$676f6FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$3%&VGW&TFW&ƗF"f"WFE'VW5FF6&RǗW$F"%&VGW&TFW&ƗFWFE'VW272"f"FW&ƗW'5FF6&RǗW$F"%&VGW&TFW&ƗFFW&ƗW%'VW272"76W'BG'VRfRW7G2FW&ƗW'5F$3"FW&ƗW"676f6F'VW26VBƗfRf7W6VB'FfR"f"WFE'VW56W&6RfR&VDFWBWFE'VW5F76W'BFW4D6F'&fFR7FF2&4FW&ƗtWFB"WFE'VW56W&6R76W'BFW4D6F'&fFR7FF2&4F&V7D6V7FFW&ƗW""WFE'VW56W&6R76W'BFW4D6F'&fFR7FF2&4FW&Ɨt67G'V7F""WFE'VW56W&6Rf"FW&ƗW'56W&6RfR&VDFWBFW&ƗW'5F76W'B6F2'&fFR7FF2&4FW&ƗtWFB"FW&ƗW'56W&6R76W'B6F2'&fFR7FF2&4F&V7D6V7FFW&ƗW""FW&ƗW'56W&6R76W'B6F2'&fFR7FF2&4FVGWƖ6Fu6WDFW&ƗW""FW&ƗW'56W&6R76W'B6F2'&fFR7FF2&4WVD$w&WVDFW&ƗW""FW&ƗW'56W&6R76W'B6F2'&fFR7FF2&4FW&Ɨt67G'V7F""FW&ƗW'56W&6RРf7EТV&Ɩ2fB3CEG&6u7FFTFWF6fƖFFƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$3CE4G&6uFVFg"f"G&6u7FFUFF6&RǗW$F"$4G&6uFVFgG&6u7FFR72"f"FWF6fƖFFFF6&RǗW$F"$4G&6uFVFgG&6tFWF6fƖFF72"76W'BG'VRfRW7G2FWF6fƖFFF$3CBFWF6G&6W"6V"fƖFF6VBƗfRf7W6VB'FfR"f"G&6u7FFU6W&6RfR&VDFWBG&6u7FFUF76W'BFW4D6F'&fFR7FF2&4FW'fVtFWF6"G&6u7FFU6W&6Rf"FWF6fƖFF6W&6RfR&VDFWBFWF6fƖFFF76W'B6F2'&fFR7FF2&4FW'fVtFWF6"FWF6fƖFF6W&6R76W'B6F2'66FWF6W4'6"FWF6fƖFF6W&6R76W'B6F2'66G&6W$6V'2"FWF6fƖFF6W&6RРf7EТV&Ɩ2fB3#U6W&6U&W6WF6FF&v5ƗfTFVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$3#U4G&6uvFWFFR"f"6W&6U&W6WFFF6&RǗW$F"$4G&6uvFWFFU6W&6U&W6WF72"f"6FF&v5FF6&RǗW$F"$4G&6uvFWFFU6W&6T6FF&v272"76W'BG'VRfRW7G26FF&v5F$3#R6FF&v7FFR6VBƗfRf7W6VB'FfR"f"6W&6U&W6WF6W&6RfR&VDFWB6W&6U&W6WFF76W'BFW4D6F'&fFR&VFǒ7G'V7B6&v"6W&6U&W6WF6W&6R76W'BFW4D6F'&fFR7FF2&46FF&VFfUF"6W&6U&W6WF6W&6Rf"6FF&v56W&6RfR&VDFWB6FF&v5F76W'B6F2'&fFR&VFǒ7G'V7B6&v"6FF&v56W&6R76W'B6F2'&fFR7FF2&46FF&VFfUF"6FF&v56W&6R76W'B6F2%7vF6W&W767F"6FF&v56W&6R76W'B6F2$6f$V67FFVVE7F"6FF&v56W&6RРf7EТV&Ɩ2fB33E76fWGFWFV7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%&u7E6V7W&G"$33EfDWV7WFU7&uvFFW'F"f"ǗW%FF6&RǗW$F"$fDWV7WFU7&uvFFW'FǗW"72"f"76fWGFF6&RǗW$F"$fDWV7WFU7&uvFFW'F76fWG72"76W'BG'VRfRW7G276fWGF$33B56fWG676f6F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F672fDWV7WFU7&uvFFW'FǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5FVFǕV6fU7"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5V6fT66FVF"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&467FDFW'F"ǗW%6W&6Rf"76fWG6W&6RfR&VDFWB76fWGF76W'B6F2'&fFR7FF2&5FVFǕV6fU7"76fWG6W&6R76W'B6F2'&fFR7FF2&5V6fT66FVF"76fWG6W&6R76W'B6F2'&fFR7FF2&467FDFW'F"76fWG6W&6R76W'B6F2$FW'FVE7G&tW&F"76fWG6W&6RРf7EТV&Ɩ2fB3UfW%&Ww&FUfƖFFƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%VW'6TEG&6F"$3UVFT&FW$'"f"fW%FF6&RfW$F"$VFT&FW$'fW"72"f"fƖFFFF6&RfW$F"$VFT&FW$'fW%&Ww&FUfƖFF72"76W'BG'VRfRW7G2fƖFFF$3RfW"&Ww&FRfƖFF6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672VFT&FW$'fW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&6&Ww&FUFFV'"fW%6W&6R76W'BFW4D6F'&fFR7FF2W&W767FvWDv6&V6VfW$W&W76"fW%6W&6R76W'BFW4D6F'&fFR7FF2&4&FW&VE6WVV6R"fW%6W&6Rf"fƖFF6W&6RfR&VDFWBfƖFFF76W'B6F2'&fFR7FF2&6&Ww&FUFFV'"fƖFF6W&6R76W'B6F2'&fFR7FF2W&W767FvWDv6&V6VfW$W&W76"fƖFF6W&6R76W'B6F2'&fFR7FF2&4&FW&VE6WVV6R"fƖFF6W&6R76W'B6F2$&FW&VEVW'&R"fƖFF6W&6RРf7EТV&Ɩ2fB3fW$vD6FWEfƖFFƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$WV7WFD72"$374&6W""f"fW%FF6&RfW$F"%74&6W$fW"72"f"vD6FWEFF6&RfW$F"%74&6W$fW$vD6FWB72"76W'BG'VRfRW7G2vD6FWEF$3fW"vB6FWBfƖFF6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F67274&6W$fW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&4fƖDvD6FWB"fW%6W&6R76W'BFW4D6F$g&6W6U7F"fW%6W&6R76W'BFW4D6F$W4gV7FW&W767F"fW%6W&6Rf"vD6FWE6W&6RfR&VDFWBvD6FWEF76W'B6F2'&fFR7FF2&4fƖDvD6FWB"vD6FWE6W&6R76W'B6F2$g&6W6U7F"vD6FWE6W&6R76W'B6F2$6W6U7F"vD6FWE6W&6R76W'B6F2$W4gV7FW&W767F"vD6FWE6W&6R76W'B6F2$6gV7F7FFVVE7F"vD6FWE6W&6RРf7EТV&Ɩ2fB3ǗW%76fWGFWFV7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%&u7E6V7W&G"$3fDg&7&uvFFW'F"f"ǗW%FF6&RǗW$F"$fDg&7&uvFFW'FǗW"72"f"76fWGFF6&RǗW$F"$fDg&7&uvFFW'FǗW%76fWG72"76W'BG'VRfRW7G276fWGF$3ǗW"56fWG676f6F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F672fDg&7&uvFFW'FǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5FVFǕV6fR"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5V6fT66FVF"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&467FDFW'F"ǗW%6W&6Rf"76fWG6W&6RfR&VDFWB76fWGF76W'B6F2'&fFR7FF2&5FVFǕV6fR"76fWG6W&6R76W'B6F2'&fFR7FF2&5V6fT66FVF"76fWG6W&6R76W'B6F2'&fFR7FF2&467FDFW'F"76fWG6W&6R76W'B6F2$FW'FVE7G&tW&F"76fWG6W&6RРf7EТV&Ɩ2fB3C%VW'WFDWF&W5ƗfTFVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FtD6VFW2"$3C%֗76uVW'Fw2"f"ǗW%FF6&RǗW$F"$֗76uVW'Fw4ǗW"72"f"WFE6WG5FF6&RǗW$F"$֗76uVW'Fw4WFE6WG272"76W'BG'VRfRW7G2WFE6WG5F$3C"VW'WFBWF&W26VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'BFW4D6F'&fFR7FF2&VFǒWF&T66WC7G&sF&vWDWFG2"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&VFǒWF&T66WC7G&sVW'7FW2"ǗW%6W&6Rf"WFE6WG56W&6RfR&VDFWBWFE6WG5F76W'B6F2'&fFR7FF2&VFǒWF&T66WC7G&sF&vWDWFG2"WFE6WG56W&6R76W'B6F2'&fFR7FF2&VFǒWF&T66WC7G&sVW'7FW2"WFE6WG56W&6R76W'B6F2%%FuvF66FU""WFE6WG56W&6R76W'B6F2%%F66WD75""WFE6WG56W&6RРf7EТV&Ɩ2fB3#FV6VFT6Ǘ65ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FtD6VFW2"$3#FVWFV6VFR"f"ǗW%FF6&RǗW$F"$FVWFV6VFTǗW"72"f"6Ǘ65FF6&RǗW$F"$FVWFV6VFT6Ǘ6272"76W'BG'VRfRW7G26Ǘ65F$3#FV6VFR6Ǘ626VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F672FVWFV6VFTǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2B6VEFV6VFTFWF"ǗW%6W&6R76W'BFW4D6F'&fFR7FF26FvWDFv7F46F"ǗW%6W&6R76W'BFW4D6F%FWE7g&&VG2"ǗW%6W&6Rf"6Ǘ656W&6RfR&VDFWB6Ǘ65F76W'B6F2'&fFR7FF2B6VEFV6VFTFWF"6Ǘ656W&6R76W'B6F2'&fFR7FF26FvWDFv7F46F"6Ǘ656W&6R76W'B6F2%FWE7g&&VG2"6Ǘ656W&6R76W'B6F2$V&W$66W74W&W767F"6Ǘ656W&6RРf7EТV&Ɩ2fB7&Ǘ65&'WF66fW'ƗfW4FVF6FVE'F‚f"WFV64F"F6&R&W&B'7&2"$Ɩ6G&&B"$WFV62"f"7&WFV65FF6&RWFV64F"%7&Ǘ64WFV6272"f"&'WFF6&RWFV64F"%7&Ǘ65&'WWFV6272"76W'BG'VRfRW7G2&'WF%&'ֶW7&F66fW'6VBƗfRf7W6VBWFV6'FfR"f"7&WFV656W&6RfR&VDFWB7&WFV65F76W'BFW4D6F'V&Ɩ27FF27G&sG'fE&'W"7&WFV656W&6R76W'BFW4D6F'&fFR7FF2&4FFFF4WGG&'WFR"7&WFV656W&6Rf"&'W6W&6RfR&VDFWB&'WF76W'B6F2'V&Ɩ27FF27G&sG'fE&'W"&'W6W&6R76W'B6F2'&fFR7FF2&4FFFF4WGG&'WFR"&'W6W&6R76W'B6F2%77FV6VDFVFFFF2"&'W6W&6RРf7EТV&Ɩ2fB36FFĖ6VFUFǗ65ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FtD6VFW2"$36FFĖ6VFR"f"ǗW%FF6&RǗW$F"$6FFĖ6VFTǗW"72"f"FǗ65FF6&RǗW$F"$6FFĖ6VFUFǗ6272"76W'BG'VRfRW7G2FǗ65F$36FF6VFRFG&fW'66VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F6726FFĖ6VFTǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&46FFĖ6VFUF"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&46FFĖ6VFTf6F6W&6R"ǗW%6W&6Rf"FǗ656W&6RfR&VDFWBFǗ65F76W'B6F2'&fFR7FF2&46FFĖ6VFUF"FǗ656W&6R76W'B6F2'&fFR7FF2&46FFĖ6VFTf6F6W&6R"FǗ656W&6R76W'B6F2$6FFW&F"6W66TW&F"FǗ656W&6RРf7EТV&Ɩ2fB3#efW$WƖ6EFV&wVVEƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$WV7WFD72"$3#e֗76t66VFFV"f"fW%FF6&RfW$F"$֗76t66VFFVfW"72"f"WƖ6D&wVVEFF6&RfW$F"$֗76t66VFFVfW$WƖ6D&wVVB72"76W'BG'VRfRW7G2WƖ6D&wVVEF$3#bfW"WƖ6BFV&wVVBW6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672֗76t66VFFVfW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&wVVE7FfDWƖ6D66VFFV&wVVB"fW%6W&6R76W'BFW4D6F'&fFR7FF2&466VFFV&WFW""fW%6W&6Rf"WƖ6D&wVVE6W&6RfR&VDFWBWƖ6D&wVVEF76W'B6F2'&fFR7FF2&wVVE7FfDWƖ6D66VFFV&wVVB"WƖ6D&wVVE6W&6R76W'B6F2'&fFR7FF2&466VFFV&WFW""WƖ6D&wVVE6W&6R76W'B6F2$f6FW&F"WƖ6D&wVVE6W&6RРf7EТV&Ɩ2fB33%66%GT676f6FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$'VƴW&F4E6WD&6VEw&FW2"$33%WV7WFUWFFTf$'VƵWFFW2"f"76vVDǗ65FF6&RǗW$F"$WV7WFUWFFTf$'VƵWFFW476vVDǗ6272"f"66%GW5FF6&RǗW$F"$WV7WFUWFFTf$'VƵWFFW566%GW272"76W'BG'VRfRW7G266%GW5F$33"66"GR676f6F6VBƗfRf7W6VB'FfR"f"76vVDǗ656W&6RfR&VDFWB76vVDǗ65F76W'BFW4D6F'&fFR7FF2&566$ƖUGR"76vVDǗ656W&6R76W'BFW4D6F%7&F7f&BgVǕVƖfVDf&B"76vVDǗ656W&6Rf"66%GW56W&6RfR&VDFWB66%GW5F76W'B6F2'&fFR7FF2&566$ƖUGR"66%GW56W&6R76W'B6F2%7&F7f&BgVǕVƖfVDf&B"66%GW56W&6R76W'B6F2&v&ã77FVFFUFTfg6WB"66%GW56W&6RРf7EТV&Ɩ2fB33%VW'7FWWF&W5ƗfTFVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$'VƴW&F4E6WD&6VEw&FW2"$33%WV7WFUWFFTf$'VƵWFFW2"f"VW'Ǘ65FF6&RǗW$F"$WV7WFUWFFTf$'VƵWFFW5VW'Ǘ6272"f"VW'7FW5FF6&RǗW$F"$WV7WFUWFFTf$'VƵWFFW5VW'7FW272"76W'BG'VRfRW7G2VW'7FW5F$33"vVBVW'FW&ƗW"7FWWF&W26VBƗfRf7W6VB'FfR"f"VW'Ǘ656W&6RfR&VDFWBVW'Ǘ65F76W'BFW4D6F'&fFR7FF2&VFǒWF&T66WC7G&svVEVW'7FW2"VW'Ǘ656W&6R76W'BFW4D6F'&fFR7FF2&VFǒWF&T66WC7G&sFW&ƗW%7FW2"VW'Ǘ656W&6Rf"VW'7FW56W&6RfR&VDFWBVW'7FW5F76W'B6F2'&fFR7FF2&VFǒWF&T66WC7G&svVEVW'7FW2"VW'7FW56W&6R76W'B6F2'&fFR7FF2&VFǒWF&T66WC7G&sFW&ƗW%7FW2"VW'7FW56W&6R76W'B6F2%%FuvF66FU""VW'7FW56W&6R76W'B6F2%%F'&75""VW'7FW56W&6RРf7EТV&Ɩ2fB3CUfW%VW'6W&6U&W6WFƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FtD6VFW2"$3CU֗76t6VFR"f"fW%FF6&RfW$F"$֗76t6VFTfW"72"f"VW'6W&6UFF6&RfW$F"$֗76t6VFTfW%VW'6W&6R72"76W'BG'VRfRW7G2VW'6W&6UF$3CRfW"VW'6W&6R&W6WF6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672֗76t6VFTfW""fW%6W&6R76W'BFW4D6F'&fFR7FF272F6W&W767FvWEVW'&U6W&6T72"fW%6W&6R76W'BFW4D6F'6VF4FVvWEGTfVW'6W&6R66VFFV"fW%6W&6Rf"VW'6W&6RfR&VDFWBVW'6W&6UF76W'B6F2'&fFR7FF272F6W&W767FvWEVW'&U6W&6T72"VW'6W&6R76W'B6F2'6VF4FVvWEGTfVW'6W&6R66VFFV"VW'6W&6R76W'B6F2'VW'6W&6R"VW'6W&6RРf7EТV&Ɩ2fB3e&V6VfW$6G&fW'6ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FtD6VFW2"$3e6'FW6W6"f"6Ǘ65FF6&RǗW$F"$6'FW6W66Ǘ6272"f"&V6VfW$6FF6&RǗW$F"$6'FW6W6&V6VfW$672"76W'BG'VRfRW7G2&V6VfW$6F$3b&V6VfW"6G&fW'66VBƗfRf7W6VB'FfR"f"6Ǘ656W&6RfR&VDFWB6Ǘ65F76W'BFW4D6F'&fFR7FF2WF&T'&Ĕf6FW&F6V7E&V6VfW$6f6F2"6Ǘ656W&6R76W'BFW4D6F'&fFR7FF2&5&VWfEVW'W&F$6W7F""6Ǘ656W&6R76W'BFW4D6F'&fFR7FF2&f6FW6W5&V6VfW$6"6Ǘ656W&6Rf"&V6VfW$66W&6RfR&VDFWB&V6VfW$6F76W'B6F2'&fFR7FF2WF&T'&Ĕf6FW&F6V7E&V6VfW$6f6F2"&V6VfW$66W&6R76W'B6F2'&fFR7FF2&5&VWfEVW'W&F$6W7F""&V6VfW$66W&6R76W'B6F2'&fFR7FF2&f6FW6W5&V6VfW$6"&V6VfW$66W&6R76W'B6F2$676vVD66RG'vWE6vT76vVEfVT&Vf&R"&V6VfW$66W&6RРf7EТV&Ɩ2fB3F%6WDV&W$WG&7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3VFG֗76u&'W"f"ǗW%FF6&RǗW$F"$VFG֗76u&'WǗW"72"f"F%6WDV&W'5FF6&RǗW$F"$VFG֗76u&'WF%6WDV&W'272"76W'BG'VRfRW7G2F%6WDV&W'5F$3F%6WBV&W"WG&7F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'BFW4D6F'&fFR7FF2&G'vWDF%6WDV&W""ǗW%6W&6R76W'BFW4D6F&fVBR7F'G5vF#"7G&t6&6&F’"ǗW%6W&6Rf"F%6WDV&W'56W&6RfR&VDFWBF%6WDV&W'5F76W'B6F2'&fFR7FF2&G'vWDF%6WDV&W""F%6WDV&W'56W&6R76W'B6F2$&W'G7&&W'G"F%6WDV&W'56W&6R76W'B6F2$fVE7&fVB"F%6WDV&W'56W&6R76W'B6F2&fVBR7F'G5vF#"7G&t6&6&F’"F%6WDV&W'56W&6RРf7EТV&Ɩ2fB3fW$VFGGU&W6WFƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3VFG֗76u&'W"f"fW%FF6&RfW$F"$VFG֗76u&'WfW"72"f"VFGGUFF6&RfW$F"$VFG֗76u&'WfW$VFGGR72"76W'BG'VRfRW7G2VFGGUF$3fW"VFGGRBBV&W"6V626VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672VFG֗76u&'WfW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&G'vWDVFGGR"fW%6W&6R76W'BFW4D6F'&fFR7FF2&4DV&W""fW%6W&6Rf"VFGGU6W&6RfR&VDFWBVFGGUF76W'B6F2'&fFR7FF2&G'vWDVFGGR"VFGGU6W&6R76W'B6F2'&fFR7FF2&4DV&W""VFGGU6W&6R76W'B6F2%7G&t6&6&FĖv&T66R"VFGGU6W&6RРf7EТV&Ɩ2fB33EfW%7&wVVE6fWGƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%&u7E6V7W&G"$33EfDWV7WFU7&uvFFW'F"f"fW%FF6&RfW$F"$fDWV7WFU7&uvFFW'FfW"72"f"7&wVVEFF6&RfW$F"$fDWV7WFU7&uvFFW'FfW%7&wVVB72"76W'BG'VRfRW7G27&wVVEF$33BfW"5&wVVBBƗFW&6fWGVW'26VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672fDWV7WFU7&uvFFW'FfW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&wVVE7FvWE7&wVVB"fW%6W&6R76W'BFW4D6F'&fFR7FF2&4FW'F6FU77G&tƗFW&"fW%6W&6R76W'BFW4D6F'&fFR7FF2fBWFFU7FVƖ֗FW%7FFR"fW%6W&6Rf"7&wVVE6W&6RfR&VDFWB7&wVVEF76W'B6F2'&fFR7FF2&wVVE7FvWE7&wVVB"7&wVVE6W&6R76W'B6F2'&fFR7FF2&4FW'F6FU77G&tƗFW&"7&wVVE6W&6R76W'B6F2'&fFR7FF2fBWFFU7FVƖ֗FW%7FFR"7&wVVE6W&6R76W'B6F2$FW'F7FvV6FU77G&tƗFW&"7&wVVE6W&6RРf7EТV&Ɩ2fB3efW%7F66V&6ƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FtD6VFW2"$3e6'FW6W6"f"fW%FF6&RfW$F"$6'FW6W6fW"72"f"7F6FF6&RfW$F"$6'FW6W6fW%7F672"76W'BG'VRfRW7G27F6F$3bfW"7F66V&6VW'26VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F6726'FW6W6fW""fW%6W&6R76W'BFW4D6F'&fFR7FF2f6FW&W767FfDVffV7FfT56vUVW'f6F"fW%6W&6R76W'BFW4D6F'&fFR7FF2f6FW&W767FfDf'7D6VFTf6F"fW%6W&6R76W'BFW4D6F'&fFR7FF2&4f6Fb"fW%6W&6Rf"7F66W&6RfR&VDFWB7F6F76W'B6F2'&fFR7FF2f6FW&W767FfDVffV7FfT56vUVW'f6F"7F66W&6R76W'B6F2'&fFR7FF2f6FW&W767FfDf'7D6VFTf6F"7F66W&6R76W'B6F2'&fFR7FF2&4f6Fb"7F66W&6R76W'B6F2%$56vUVW'""7F66W&6RРf7EТV&Ɩ2fB3#u6V7FGT676f6FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3#u֗76tWƖ6Df&VvW"f"VFGǗ65FF6&RǗW$F"$֗76tWƖ6Df&VvWVFGǗ6272"f"6V7FGW5FF6&RǗW$F"$֗76tWƖ6Df&VvW6V7FGW272"76W'BG'VRfRW7G26V7FGW5F$3#r6V7FGR676f6F6VBƗfRf7W6VB'FfR"f"VFGǗ656W&6RfR&VDFWBVFGǗ65F76W'BFW4D6F'&fFR7FF2&46V7FGR"VFGǗ656W&6R76W'BFW4D6F$&VDǔ6V7F"VFGǗ656W&6R76W'BFW4D6F&f6RR$VVW&&U""VFGǗ656W&6Rf"6V7FGW56W&6RfR&VDFWB6V7FGW5F76W'B6F2'&fFR7FF2&46V7FGR"6V7FGW56W&6R76W'B6F2%77FV6V7F2vVW&2"6V7FGW56W&6R76W'B6F2$&VDǔ6V7F"6V7FGW56W&6RРf7EТV&Ɩ2fB3FW&ƗW$WFD676f6FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$3֗76t4G&6r"f"ǗW%FF6&RǗW$F"$֗76t4G&6tǗW"72"f"FW&ƗW'5FF6&RǗW$F"$֗76t4G&6tFW&ƗW'272"76W'BG'VRfRW7G2FW&ƗW'5F$3FW&ƗW"WFB676f6F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'BFW4D6F'&fFR7FF2&4VFGFW&ƗW""ǗW%6W&6R76W'BFW4D6F%%FF7F'75""ǗW%6W&6Rf"FW&ƗW'56W&6RfR&VDFWBFW&ƗW'5F76W'B6F2'&fFR7FF2&4VFGFW&ƗW""FW&ƗW'56W&6R76W'B6F2%%FF7F'75""FW&ƗW'56W&6R76W'B6F2%%6vT$FVfVD75""FW&ƗW'56W&6RРf7EТV&Ɩ2fB6TFv7F75fW&fW%6&e'6uƗfW4FVF6FVE'F‚f"fW&fW$F"F6&R&W&B'F2"%6TFv7F75fW&fW""f"&w&FF6&RfW&fW$F"%&w&72"f"6&eFF6&RfW&fW$F"%&w&6&dFv7F7272"76W'BG'VRfRW7G26&eF%6RFv7F724$b'6r6VBƗfRf7W6VB&w&'FfR"f"&w&6W&6RfR&VDFWB&w&F76W'BFW4D6F'7FF2&VDǔƗ7C6TFv7F3'6TFv7F72"&w&6W&6R76W'BFW4D6F'7FF2&G'vWDFv7F5F"&w&6W&6R76W'BFW4D6F'7FF27G&r&ƗU&VFfUF"&w&6W&6Rf"6&e6W&6RfR&VDFWB6&eF76W'B6F2'&fFR7FF2&VDǔƗ7C6TFv7F3'6TFv7F72"6&e6W&6R76W'B6F2'&fFR7FF2&G'vWDFv7F5F"6&e6W&6R76W'B6F2'&fFR7FF27G&r&ƗU&VFfUF"6&e6W&6R76W'B6F2&FW&6VVB&V6&B6TFv7F2"6&e6W&6RРf7EТV&Ɩ2fB6TFv7F75fW&fW%fW7E'6uƗfW4FVF6FVE'F‚f"fW&fW$F"F6&R&W&B'F2"%6TFv7F75fW&fW""f"&w&FF6&RfW&fW$F"%&w&72"f"fW7EFF6&RfW&fW$F"%&w&fW7B72"76W'BG'VRfRW7G2fW7EF%6RFv7F72fW7B'6r6VBƗfRf7W6VB&w&'FfR"f"&w&6W&6RfR&VDFWB&w&F76W'BFW4D6F'7FF26TWV7FFw&WDWV7FFw&W2"&w&6W&6R76W'BFW4D6F'7FF27G&uE6fU6UF2"&w&6W&6R76W'BFW4D6F&FW&6VVB&V6&B6TWV7FF"&w&6W&6Rf"fW7E6W&6RfR&VDFWBfW7EF76W'B6F2'&fFR7FF26TWV7FFw&WDWV7FFw&W2"fW7E6W&6R76W'B6F2'&fFR7FF27G&uE6fU6UF2"fW7E6W&6R76W'B6F2&FW&6VVB&V6&B6TWV7FF"fW7E6W&6R76W'B6F2&FW&6VVB&V6&B6TWV7FFw&W"fW7E6W&6RРf7EТV&Ɩ2fB6TFv7F75fW&fW%FFWE&6W74WV7WFƗfW4FVF6FVE'F‚f"fW&fW$F"F6&R&W&B'F2"%6TFv7F75fW&fW""f"&w&FF6&RfW&fW$F"%&w&72"f"FFWEFF6&RfW&fW$F"%&w&FFWB72"76W'BG'VRfRW7G2FFWEF%6RFv7F72FFWB&6W72WV7WF6VBƗfRf7W6VB&w&'FfR"f"&w&6W&6RfR&VDFWB&w&F76W'BFW4D6F'7FF27G&r'VFFWD'VB"&w&6W&6R76W'BFW4D6F'7FF2fB'VFFWE&W7F&R"&w&6W&6R76W'BFW4D6F'7FF27G&rVFR"&w&6W&6Rf"FFWE6W&6RfR&VDFWBFFWEF76W'B6F2'&fFR7FF27G&r'VFFWD'VB"FFWE6W&6R76W'B6F2'&fFR7FF2fB'VFFWE&W7F&R"FFWE6W&6R76W'B6F2'&fFR7FF27G&rVFR"FFWE6W&6R76W'B6F2%&6W757F'Df"FFWE6W&6RЧР \ No newline at end of file From e6528d047735b1e7d124881320fad0ce0776ad50 Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:38:10 +0100 Subject: [PATCH 02/13] fix: restore complete architecture test after transport truncation --- .../AnalyzerModularizationTests.cs | 6741 ++++++++++++++++- 1 file changed, 6345 insertions(+), 396 deletions(-) diff --git a/tests/LinqContraband.Tests/Architecture/AnalyzerModularizationTests.cs b/tests/LinqContraband.Tests/Architecture/AnalyzerModularizationTests.cs index 7f7e889..37b22d5 100644 --- a/tests/LinqContraband.Tests/Architecture/AnalyzerModularizationTests.cs +++ b/tests/LinqContraband.Tests/Architecture/AnalyzerModularizationTests.cs @@ -1,396 +1,6345 @@ -Yx-jםi+j[hܢ^vT赩hnXz]\[\[KS\[[]‚[Y\XH[P۝X[ \ː\]X\N‚XXX[Y\[[^\[[\^][ە\žˆ]]HXYۛH[ܙ\ԛH\]ܞS^[] ]\]ܞT - -N‚јXBXXY ԛ[[Y\]R[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH \ӛX[[[YHNˆ\[]H] X[J[[^\\\ӛX[[[YT[ȊNˆ\[Y\]H] X[J[[^\\\ӛX[[[YT[[Y\˘ȊN‚\\ YJ[K^\[Y\] -K  \[[H[]H[H\Y\X[[KN‚\[\HH[KXY[^ -[] -Nˆ\\ \ӛ۝Z[[\[XYۛHX]]][ۑ[H[\JNˆ\\ \ӛ۝Z[[\[XYۛHXX]X[H[\JNˆ\\ \ӛ۝Z[[\[XYۛHX]X[H[\JNˆ\\ \ӛ۝Z[[\[XYۛHXX\X\[H[\JNˆ\\ \ӛ۝Z[[\[XYۛHX]P[\[H[\JN‚\[Y\\HH[KXY[^ -[Y\] -Nˆ\\ ۝Z[[\[XYۛHX]]][ۑ[H[Y\\JNˆ\\ ۝Z[[\[XYۛHXX]X[H[Y\\JNˆ\\ ۝Z[[\[XYۛHX]X[H[Y\\JNˆ\\ ۝Z[[\[XYۛHXX\X\[H[Y\\JNˆ\\ ۝Z[[\[XYۛHX]P[\[H[Y\\JNˆBјXBXXY ԛ[\][۔Xܙ[]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH \ӛX[[[YHNˆ\[]H] X[J[[^\\\ӛX[[[YT[ȊNˆ\\][۔Xܙ[]H] X[J[[^\\\ӛX[[[YT[\][۔Xܙ[˘ȊN‚\\ YJ[K^\\][۔Xܙ[] -K  \[\][ۈXܙ[[]H[H\Y\X[[KN‚\[\HH[KXY[^ -[] -Nˆ\\ \ӛ۝Z[]]H]XY[P\YۛY[[\JNˆ\\ \ӛ۝Z[]]H]XY[T\S]]][ۈ[\JNˆ\\ \ӛ۝Z[]]H]XY[R[][ۈ[\JN‚\\][۔Xܙ[\HH[KXY[^ -\][۔Xܙ[] -Nˆ\\ ۝Z[]]H]XY[P\YۛY[\][۔Xܙ[\JNˆ\\ ۝Z[]]H]XY[T\S]]][ۈ\][۔Xܙ[\JNˆ\\ ۝Z[]]H]XY[R[][ۈ\][۔Xܙ[\JNˆ\\ ۝Z[T\Q[T]P\YۛY[\][۔Xܙ[\JNˆ\\ ۝Z[T\TX]X[][ۈ\][۔Xܙ[\JNˆ\\ ۝Z[T\UX\X\\][۔Xܙ[\JNˆBјXBXXY [ۘ[۝]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH \ӛX[[[YHNˆ\XXX[]T]H] X[J[[^\\\ӛX[[[YTXXX[]KȊNˆ\[ۘ[]H] X[J[[^\\\ӛX[[[YS[ۘ[˘ȊN‚\\ YJ[K^\[ۘ[] -K [ۘ[[ XXX[]H[\[]H[H\Y\X[[KN‚\XXX[]T\HH[KXY[^ -XXX[]T] -Nˆ\\ \ӛ۝Z[]]H]X\ә\Y[\[ۘ[۝ȋXXX[]T\JNˆ\\ \ӛ۝Z[]]H]XY][Y[XZ\М[X[]ܞHXXX[]T\JNˆ\\ \ӛ۝Z[]]H]X][Y[\]\XXX[]T\JNˆ\\ \ӛ۝Z[]]H]X[\]\XXX[]T\JN‚\[ۘ[\HH[KXY[^ -[ۘ[] -Nˆ\\ ۝Z[]]H]X\ә\Y[\[ۘ[۝ȋ[ۘ[\JNˆ\\ ۝Z[]]H]XY][Y[XZ\М[X[]ܞH[ۘ[\JNˆ\\ ۝Z[]]H]X][Y[\]\[ۘ[\JNˆ\\ ۝Z[]]H]X[\]\[ۘ[\JNˆBјXBXXY ]Y\T\T\][ۗ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH \ӛX[[[YHNˆ\[[^\]H] X[J[[^\\\ӛX[[[YP[[^\ȊNˆ\]Y\T\T]H] X[J[[^\\\ӛX[[[YT]Y\T\KȊN‚\\ YJ[K^\]Y\T\T] -K \ӛX[]Y\KX]\X[^][ۈ\H\][ۈ[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ -[[^\] -Nˆ\\ ۝Z[XXX[Y\X[\\ӛX[[[YP[[^\[[^\\JNˆ\\ \ӛ۝Z[]]H]X\\ӛX[X]\X[^][ۈ[[^\\JNˆ\\ \ӛ۝Z[]]H]XZ[۝Z[\ӛX[ȋ[[^\\JNˆ\\ \ӛ۝Z[]]H]XQ]]Y\P۝^[X[[^\\JN‚\]Y\T\HH[KXY[^ -]Y\T\T] -Nˆ\\ ۝Z[]]H]X\\ӛX[X]\X[^][ۈ]Y\T\JNˆ\\ ۝Z[]]H]XZ[۝Z[\ӛX[ȋ]Y\T\JNˆ\\ ۝Z[]]H]XQ]]Y\P۝^[X]Y\T\JNˆBјXBXXY \[۝^\][ۗ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ^X][ې[\[ȋ [Л\Nˆ\[[^\]H] X[J[[^\\[Л\[[^\ȊNˆ\\[۝^]H] X[J[[^\\[Л\\[۝^ ȊN‚\\ YJ[K^\\[۝^] -K \[X۝^[\HX[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ -[[^\] -Nˆ\\ ۝Z[XXX[Y\X[\[Л\[[^\[[^\\JNˆ\\ \ӛ۝Z[]]H]X\[YP\[Y][[^\\JN‚\\[۝^\HH[KXY[^ -\[۝^] -Nˆ\\ ۝Z[]]H]X\[YP\[Y]\[۝^\JNˆ\\ ۝Z[S[[[ۓ\][ۈ\[۝^\JNˆ\\ ۝Z[P[۞[[\ѝ[[ۓ\][ۈ\[۝^\JNˆBјXBXXY Y][[X\Pۜ[\[ۗ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ T]Y\XXSXZȊNˆ\[[X\T]H] X[J[[^\\T]Y\XXSXZY][[X\P[[\\˘ȊNˆ\ۜ[\[۔]H] X[J[[^\\T]Y\XXSXZY][[X\Pۜ[\[ۋȊN‚\\ YJ[K^\ۜ[\[۔] -K Y] \[[X\H[ۜ[\[ۈX[]H[H\Y\X[[KN‚\[[X\T\HH[KXY[^ -[[X\T] -Nˆ\\ \ӛ۝Z[]]HYX\X]\X[^[ۜXܒ^\ȋ[[X\T\JNˆ\\ \ӛ۝Z[]]H\X]\X[^[X[ېۜX܈[[X\T\JNˆ\\ \ӛ۝Z[]]H\\Xۜ[\[ۈ[[X\T\JNˆ\\ \ӛ۝Z[]]H\][[Y\]ܒ[][ۈ[[X\T\JNˆ\\ \ӛ۝Z[]]HYX\^\Y\[Y]\\H[[X\T\JN‚\ۜ[\[۔\HH[KXY[^ -ۜ[\[۔] -Nˆ\\ ۝Z[]]HYX\X]\X[^[ۜXܒ^\ȋۜ[\[۔\JNˆ\\ ۝Z[]]H\X]\X[^[X[ېۜX܈ۜ[\[۔\JNˆ\\ ۝Z[]]H\\Xۜ[\[ۈۜ[\[۔\JNˆ\\ ۝Z[]]H\][[Y\]ܒ[][ۈۜ[\[۔\JNˆ\\ ۝Z[]]HYX\^\Y\[Y]\\Hۜ[\[۔\JNˆBјXBXXY [][ے[]X\[]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ T]Y\XXSXZȊNˆ\\T\][۔]H] X[J[[^\\T]Y\XXSXZ\T\][ۋȊNˆ\[][ے[]]H] X[J[[^\\T]Y\XXSXZ[][ے[]˘ȊN‚\\ YJ[K^\[][ے[]] -K [][ۈ[]X\[[]H[H\Y\X[[KN‚\\T\][۔\HH[KXY[^ -\T\][۔] -Nˆ\\ \ӛ۝Z[]]HQ[[Y\XO[][ے[][[Y\]R[][ے[]ȋ\T\][۔\JN‚\[][ے[]\HH[KXY[^ -[][ے[]] -Nˆ\\ ۝Z[]]HQ[[Y\XO[][ے[][[Y\]R[][ے[]ȋ[][ے[]\JNˆBјXBXXY ^X]XT]\[]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ T]Y\XXSXZȊNˆ\[X[\]H] X[J[[^\\T]Y\XXSXZ[X[\˘ȊNˆ\^X]XT]H] X[J[[^\\T]Y\XXSXZ^X]XT˘ȊN‚\\ YJ[K^\^X]XT] -K ^X]XK\]\[[]H[H\Y\X[[KN‚\[X[\\HH[KXY[^ -[X[\] -Nˆ\\ \ӛ۝Z[]]HQ]^X]XT[X[\\JNˆ\\ \ӛ۝Z[]]HQ[[Y\XOS\][ۏ[[Y\]S\][ۜȋ[X[\\JNˆ\\ \ӛ۝Z[]]H]X\[YS\Y^X]XH[X[\\JN‚\^X]XT\HH[KXY[^ -^X]XT] -Nˆ\\ ۝Z[]]HQ]^X]XT^X]XT\JNˆ\\ ۝Z[]]HQ[[Y\XOS\][ۏ[[Y\]S\][ۜȋ^X]XT\JNˆ\\ ۝Z[]]H]X\[YS\Y^X]XH^X]XT\JNˆBјXBXXY ^\\\X\]R[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ T]Y\XXSXZȊNˆ\\[][۔]T]H] X[J[[^\\T]Y\XXSXZ\[][۔]KȊNˆ\\X\]H] X[J[[^\\T]Y\XXSXZ^\\\X\˘ȊN‚\\ YJ[K^\\X\] -K ^\\Y] \H\X\[]H[H\Y\X[[KN‚\\[][۔]T\HH[KXY[^ -\[][۔]T] -Nˆ\\ \ӛ۝Z[]]H]XXYۛH[[]]XR\][ψ^\\[[Y\XSY]ȋ\[][۔]T\JNˆ\\ \ӛ۝Z[]]H]XXYۛH[[]]XR\][ψX]\X[^[X[ە\\ȋ\[][۔]T\JN‚\\X\\HH[KXY[^ -\X\] -Nˆ\\ ۝Z[]]H]XXYۛH[[]]XR\][ψ^\\[[Y\XSY]ȋ\X\\JNˆ\\ ۝Z[]]H]XXYۛH[[]]XR\][ψX]\X[^[X[ە\\ȋ\X\\JNˆBјXBXXY W]X]Y\XXSܙ\[ԙ]ܚ]W]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ W[Y]Nˆ\]Xԙ]ܚ]T]H] X[J[[^\\[Y]^\]X]Y\XXT]ܚ]KȊNˆ\ܙ\[ԙ]ܚ]T]H] X[J[[^\\[Y]^\]X]Y\XXSܙ\[˘ȊN‚\\ YJ[K^\ܙ\[ԙ]ܚ]T] -K H]X]Y\XXHܙ\[\\H]ܚ]H[]H[H\Y\X[[KN‚\]Xԙ]ܚ]T\HH[KXY[^ -]Xԙ]ܚ]T] -Nˆ\\ \ӛ۝Z[]]H]X]ܚ]T]Y\XXQ^[[ۓܙ\[\PZ[]Xԙ]ܚ]T\JNˆ\\ \ӛ۝Z[]]H]X\ԙ]ܚ]XSܙ\Y\H]Xԙ]ܚ]T\JNˆ\\ \ӛ۝Z[]]H]X\]Y\XXSܙ\[[][ۈ]Xԙ]ܚ]T\JN‚\ܙ\[ԙ]ܚ]T\HH[KXY[^ -ܙ\[ԙ]ܚ]T] -Nˆ\\ ۝Z[]]H]X]ܚ]T]Y\XXQ^[[ۓܙ\[\PZ[ܙ\[ԙ]ܚ]T\JNˆ\\ ۝Z[]]H]X\ԙ]ܚ]XSܙ\Y\Hܙ\[ԙ]ܚ]T\JNˆ\\ ۝Z[]]H]X\]Y\XXSܙ\[[][ۈܙ\[ԙ]ܚ]T\JNˆBјXBXXY XYۛXԙ\ܝ[]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH \ӛX[[[YHNˆ\[[^\]H] X[J[[^\\\ӛX[[[YP[[^\ȊNˆ\\ܝ[]H] X[J[[^\\\ӛX[[[YT\ܝ[˘ȊN‚\\ YJ[K^\\ܝ[] -K XYۛX\ܝ[[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ -[[^\] -Nˆ\\ \ӛ۝Z[]]H]XYT\ܝܓ[[[^\\JNˆ\\ \ӛ۝Z[]]H]XYT\ܝܑܙXX[[^\\JNˆ\\ \ӛ۝Z[]]HXYۛHX]]][ے][[^\\JNˆ\\ \ӛ۝Z[]]H]X]]][ے][\\S]]][ۈ[[^\\JN‚\\ܝ[\HH[KXY[^ -\ܝ[] -Nˆ\\ ۝Z[]]H]XYT\ܝܓ[\ܝ[\JNˆ\\ ۝Z[]]H]XYT\ܝܑܙXX\ܝ[\JNˆ\\ ۝Z[]]HXYۛHX]]][ے]\ܝ[\JNˆ\\ ۝Z[]]H]X]]][ے][\\S]]][ۈ\ܝ[\JNˆBјXBXXY Mњ^\\PX\Y]W]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ MQ[]TڙX[ۈNˆ\۝^]H] X[J[[^\\Q[]TڙX[ۑ^\۝^[[\\˘ȊNˆ\\PX\]H] X[J[[^\\Q[]TڙX[ۑ^\\PX\˘ȊNˆ\XY^\[ۜ]H] X[J[[^\\Q[]TڙX[ۑ^\XY[]Q^\[ۜ˘ȊN‚\\ YJ[K^\\PX\] -K M^\\KXX\Y]HX[]H[H\Y\X[[KNˆ\\ YJ[K^\XY^\[ۜ] -K M^\XY Y[]H^\[ۈXۚ][ۈ[]H[H\Y\X[[KN‚\۝^\HH[KXY[^ -۝^] -Nˆ\\ \ӛ۝Z[]]H]X\[\ܝY[]T\PX\ȋ۝^\JNˆ\\ \ӛ۝Z[]]H]X\XY[]P۝\[ۑ^\[ۈ۝^\JNˆ\\ \ӛ۝Z[]]H]X\XY[]Q^\[ۈ۝^\JNˆ\\ \ӛ۝Z[]]H]X\[YR[^Y[]PX\ȋ۝^\JNˆ\\ \ӛ۝Z[]]H]X\\Sٕ\H۝^\JN‚\\PX\\HH[KXY[^ -\PX\] -Nˆ\\ ۝Z[]]H]X\[\ܝY[]T\PX\ȋ\PX\\JNˆ\\ \ӛ۝Z[]]H]X\XY[]P۝\[ۑ^\[ۈ\PX\\JNˆ\\ \ӛ۝Z[]]H]X\XY[]Q^\[ۈ\PX\\JNˆ\\ ۝Z[]]H]X\[YR[^Y[]PX\ȋ\PX\\JNˆ\\ ۝Z[]]H]X\\Sٕ\H\PX\\JN‚\XY^\[ۜ\HH[KXY[^ -XY^\[ۜ] -Nˆ\\ ۝Z[]]H]X\XY[]P۝\[ۑ^\[ۈXY^\[ۜ\JNˆ\\ ۝Z[]]H]X\XY[]Q^\[ۈXY^\[ۜ\JNˆBјXBXXY M\YTY\[P\YX][ۗ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ MQ[]TڙX[ۈNˆ\\YP[[\\]H] X[J[[^\\Q[]TڙX[ە\YP[[\\˘ȊNˆ\\YTY\[\]H] X[J[[^\\Q[]TڙX[ە\YTY\[\˘ȊN‚\\ YJ[K^\\YTY\[\] -K M\YHY\[H\YX][ۈ[]H[H\Y\X[[KN‚\\YP[[\\\HH[KXY[^ -\YP[[\\] -Nˆ\\ \ӛ۝Z[]]H]X\XY[]TY\[H\YP[[\\\JNˆ\\ \ӛ۝Z[]]H]X\\X\XXQ\\H\YP[[\\\JNˆ\\ \ӛ۝Z[]]H]X[XQ\XTY\[\ȋ\YP[[\\\JN‚\\YTY\[\\HH[KXY[^ -\YTY\[\] -Nˆ\\ ۝Z[]]H]X\XY[]TY\[H\YTY\[\\JNˆ\\ ۝Z[]]H]X\\X\XXQ\\H\YTY\[\\JNˆ\\ ۝Z[]]H]X[XQ\XTY\[\ȋ\YTY\[\\JNˆBјXBXXY M[^\PX[ۗ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ MQ[]TڙX[ۈNˆ\[^[[\\]H] X[J[[^\\Q[]TڙX[۔[^[[\\˘ȊNˆ\\PX[۔]H] X[J[[^\\Q[]TڙX[۔[^\PX[ۋȊN‚\\ YJ[K^\\PX[۔] -K M[^ X\Y\HX[ۈ[]H[H\Y\X[[KN‚\[^[[\\\HH[KXY[^ -[^[[\\] -Nˆ\\ \ӛ۝Z[]]H]XYX[^\Y\PX\\ȋ[^[[\\\JN‚\\PX[۔\HH[KXY[^ -\PX[۔] -Nˆ\\ ۝Z[]]H]XYX[^\Y\PX\\ȋ\PX[۔\JNˆ\\ ۝Z[ۙ][ۘ[X\^\[۔[^\PX[۔\JNˆ\\ ۝Z[Y[X\X\^\[۔[^\PX[۔\JNˆBјXBXXY Wњ^\\T\][ۗ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH W\ӛX[]\]HNˆ\^\]H] X[J[[^\\\ӛX[]\]Q^\ȊNˆ\\T\][۔]H] X[J[[^\\\ӛX[]\]Q^\\T\][ۋȊNˆ\X[\T]H] X[J[[^\\\ӛX[]\]Q^\X[\KȊN‚\\ YJ[K^\\T\][۔] -K H^\\ӛX[\H\][ۈ[]H[H\Y\X[[KNˆ\\ YJ[K^\X[\T] -K H^\X\]H]X[\HX[[]H[H\Y\X[[KN‚\^\\HH[KXY[^ -^\] -Nˆ\\ ۝Z[XXX[Y\X[\\ӛX[]\]Q^\^\\JNˆ\\ \ӛ۝Z[]]H]X[][ۑ^\[۔[^[\ӛX[ܚY[^\\JNˆ\\ \ӛ۝Z[]]HXYۛHX\ӛX[ܚY[^\\JNˆ\\ \ӛ۝Z[]]H]X\ӛX[\H^\\JNˆ\\ \ӛ۝Z[]]H]X\[SX[ȋ^\\JNˆ\\ \ӛ۝Z[]]H]X\\ӛX[[Z[^\\JN‚\\T\][۔\HH[KXY[^ -\T\][۔] -Nˆ\\ ۝Z[]]H]X[][ۑ^\[۔[^[\ӛX[ܚY[\T\][۔\JNˆ\\ ۝Z[]]HXYۛHX\ӛX[ܚY[\T\][۔\JNˆ\\ \ӛ۝Z[]]H]X\ӛX[\H\T\][۔\JNˆ\\ \ӛ۝Z[]]H]X\[SX[ȋ\T\][۔\JN‚\X[\HH[KXY[^ -X[\T] -Nˆ\\ ۝Z[]]H]X\ӛX[\HX[\JNˆ\\ ۝Z[]]H]X\[SX[ȋX[\JNˆBјXBXXY Wњ^\X[\X]P[[\\]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[UX[[۝^Y][YH W\ӛX[]\]HNˆ\\T\][۔]H] X[J[[^\\\ӛX[]\]Q^\\T\][ۋȊNˆ\X[\X]T]H] X[J[[^\\\ӛX[]\]Q^\X[\X]P[[\\˘ȊN‚\\ YJ[K^\X[\X]T] -K H^\X[Y\X]HZ[[[\\[]H[H\Y\X[[KN‚\\T\][۔\HH[KXY[^ -\T\][۔] -Nˆ\\ \ӛ۝Z[]]H]X\\ӛX[[Z[\T\][۔\JNˆ\\ \ӛ۝Z[]]H]X\YܙSX[\X]H\T\][۔\JNˆ\\ \ӛ۝Z[]]H]X\YܙP\X[ȋ\T\][۔\JNˆ\\ \ӛ۝Z[]]H]X[][ۑ^\[۔[^[\ӛX[[][ۈ\T\][۔\JN‚\X[\X]T\HH[KXY[^ -X[\X]T] -Nˆ\\ ۝Z[]]H]X\\ӛX[[Z[X[\X]T\JNˆ\\ ۝Z[]]H]X\YܙSX[\X]HX[\X]T\JNˆ\\ ۝Z[]]H]X\YܙP\X[ȋX[\X]T\JNˆ\\ ۝Z[]]H]X[][ۑ^\[۔[^[\ӛX[[][ۈX[\X]T\JNˆBјXBXXY[YT]\\[\\ۜX[]Y\]R[YX]Y\X[ -Bˆ\^[[ۜ\H] X[Jܙ\ԛ ܘȋ[P۝X[^[[ۜȊNˆ\\\]H] X[J^[[ۜ\[YT]\\ȊNˆ\[XT]H] X[J^[[ۜ\[YT]\\[XT]˘ȊNˆ\\P[[\\]H] X[J^[[ۜ\[YT]\\\P[[\\˘ȊN‚\\ YJ[K^\[XT] -K[YT]\\[XH]Y][ۈ\[[]H[H\Y\X[[KNˆ\\ YJ[K^\\P[[\\] -K[YT]\\\KX[ۈ[[\\[]H[H\Y\X[[KN‚\\\\HH[KXY[^ -\\] -Nˆ\\ ۝Z[[\[]X\X[\[YT]\\\\\JNˆ\\ \ӛ۝Z[]]H]XPY]Y][۔YY[ȋ\\\JNˆ\\ \ӛ۝Z[XX]XQ]X[ۑ[[Y[\H\\\JN‚\[XT\HH[KXY[^ -[XT] -Nˆ\\ ۝Z[]]H]XPY]Y][۔YY[ȋ[XT\JNˆ\\ ۝Z[]]H]X\[^H[ܘ\^\[ۈ[XT\JN‚\\P[[\\\HH[KXY[^ -\P[[\\] -Nˆ\\ ۝Z[XX]XQ]X[ۑ[[Y[\H\P[[\\\JNˆ\\ ۝Z[]]H]XQ][YY[\X[[Y[\H\P[[\\\JNˆBјXBXXY W\YT[]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋY[[[Y\ȋ WZ\[[YHNˆ\\YP[[\\]H] X[J[[^\\Z\[[YU\YP[[\\˘ȊNˆ\\YT[]H] X[J[[^\\Z\[[YU\YT[ȊN‚\\ YJ[K^\\YT[] -K H^X]XK\\YH[[[]H[H\Y\X[[KN‚\\YP[[\\\HH[KXY[^ -\YP[[\\] -Nˆ\\ \ӛ۝Z[]]H]X\]Y][ېX\ϏX]Y][ېX\\ќQ^X]XT\YP[[\\\JN‚\\YT[\HH[KXY[^ -\YT[] -Nˆ\\ ۝Z[]]H]X\]Y][ېX\ϏX]Y][ېX\\ќQ^X]XT\YT[\JNˆ\\ ۝Z[[XTY\[\XY[\YT[\JNˆ\\ ۝Z[]\ٚYY]ȋ\YT[\JNˆBјXBXXY WܚY[]\Qԙ\ۜX[]Y\]R[YX]Y\X[ -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋY[[[Y\ȋ WZ\[[YHNˆ\[[^\]H] X[J[[^\\Z\[[YP[[^\ȊNˆ\\YT[]H] X[J[[^\\Z\[[YU\YT[ȊNˆ\[[\\]H] X[J[[^\\Z\[[YSܚY[[[\\˘ȊNˆ\К[[]H] X[J[[^\\Z\[[YSܚY[К[[˘ȊNˆ\۝^]H] X[J[[^\\Z\[[YSܚY[۝^ ȊNˆ\][]H] X[J[[^\\Z\[[YSܚY[][˘ȊNˆ\]T]H] X[J[[^\\Z\[[YSܚY[]KȊNˆ\]][YT]H] X[J[[^\\Z\[[YP]][YPۙY\][ۋȊNˆ\\YYۙY\][۔]H] X[J[[^\\Z\[[YP\YYۙY\][ۋȊN‚\\ YJ[K^\[[\\] -K HܚY[X]\H۝ Y[[\\[]H[H\Y\X[[KNˆ\\ YJ[K^\К[[] -K HܚY[[[X\[[\ݙ\H[]H[H\Y\X[[KNˆ\\ YJ[K^\۝^] -K HܚY[Y۝^ۜX[ۈ[]H[H\Y\X[[KNˆ\\ YJ[K^\][] -K HܚY[Y][X[ۈ[]H[H\Y\X[[KNˆ\\ YJ[K^\]T] -K HܚY[Y]H[][[[[]H[H\Y\X[[KNˆ\\ YJ[K^\]][YT] -K H[[ [][]][YHو[]H[H\Y\X[[KNˆ\\ YJ[K^\\YYۙY\][۔] -K H\YY]][YHۙY\][ۈو[]H[H\Y\X[[KN‚\\YT[\HH[KXY[^ -\YT[] -Nˆ\\ \ӛ۝Z[]]H]XQ]ܘ\\YT[\JNˆ\\ \ӛ۝Z[]]HX[Y\X[\ܚY[۝^\YT[\JNˆ\\ \ӛ۝Z[]]HXYۛHXؙT]H\YT[\JNˆ\\ \ӛ۝Z[]]H[[H][[\YT[\JNˆ\\ \ӛ۝Z[]]HY\ݙ\XP[X\\ȋ\YT[\JN‚\[[^\\HH[KXY[^ -[[^\] -Nˆ\\ ۝Z[Y\\\[][۔\X[ۈ[[^\\JNˆ\\ ۝Z[\ܘ\XHH]\[K[[YK\[\\X\ːۙ][ۘ[XZXO[[^\\JNˆ\\ ۝Z[\]][YPXHH]\[KX[ۜːۘ\[ ۘ\[X[ۘ\O[[^\\JNˆ\\ ۝Z[[[^R[][ۊ[[^\\JNˆ\\ ۝Z[]][YPXK[[^\\JN‚\]][YT\HH[KXY[^ -]][YT] -Nˆ\\ ۝Z[]]H]XYY[[]][YTY^\ȋ]][YT\JNˆ\\ ۝Z[]]H]XQ]\X]][YH]][YT\JN‚\\YYۙY\][۔\HH[KXY[^ -\YYۙY\][۔] -Nˆ\\ ۝Z[]]H]XP\PۙY\][ې]][Y\ȋ\YYۙY\][۔\JNˆ\\ ۝Z[]]H]XQ]\YYۙY\][ې]][YH\YYۙY\][۔\JN‚\\YYۙY\][ە\]H] X[Jܙ\ԛ \ȋ[P۝X[ \ȋ[[^\ȋ WZ\[[YHZ\[[YP\YYۙY\][ە\˘ȊNˆ\\ YJ[K^\\YYۙY\][ە\] -K H\YY]][YHۙY\][ۈ\[]H[H\Y\X[[KNˆ\\YYۙY\][ە\\HH[KXY[^ -\YYۙY\][ە\] -Nˆ\\ ۝Z[\[[\YYۙY\][ې]][YWӛXYۛXȋ\YYۙY\][ە\\JNˆ\\ ۝Z[\ܚ[YW\YYۙY\][ے[\[\W\ӛ\\ȋ\YYۙY\][ە\\JN‚\[[\\\HH[KXY[^ -[[\\] -Nˆ\\ ۝Z[]]H]XPXܚY[]\S]Y][ېX\\ȋ[[\\\JNˆ\\ ۝Z[]]H]XQ]ܘ\[[\\\JNˆ\\ ۝Z[ۙ][ۘ[XZXOS\][ۋܘ\\ܘ\XH[[\\\JNˆ\\ \ӛ۝Z[]XXYۛHۙ][ۘ[XZXOS\][ۈ[[\\\JN‚\К[[\HH[KXY[^ -К[[] -Nˆ\\ ۝Z[]]HY\ݙ\XP[X\\ȋК[[\JNˆ\\ ۝Z[]]HX[Y\]\][ې[[ȋК[[\JN‚\۝^\HH[KXY[^ -۝^] -Nˆ\\ ۝Z[]]HX[Y\X[\ܚY[۝^۝^\JNˆ\\ ۝Z[XXSX\][Лȋ۝^\JNˆ\\ ۝Z[]]HT\Q[]SܚY[۝^\JN‚\][\HH[KXY[^ -][] -Nˆ\\ ۝Z[]]HYX[[[\\Q][ȋ][\JNˆ\\ ۝Z[]]HYX]Y][ۑ][][\JN‚\]T\HH[KXY[^ -]T] -Nˆ\\ ۝Z[]]HX[Y\[]SܚY[]T\JNˆ\\ ۝Z[]]H[[H][[]T\JNˆ\\ ۝Z[]]HXYۛHXؙT]H]T\JNˆBјXBXXY WXYۛXԙ\ܝ[]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋY[[[Y\ȋ WZ\[[YHNˆ\[[^\]H] X[J[[^\\Z\[[YP[[^\ȊNˆ\\ܝ[]H] X[J[[^\\Z\[[YT\ܝ[˘ȊN‚\\ YJ[K^\\ܝ[] -K HZ\[\]XYۛX\ܝ[[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ -[[^\] -Nˆ\\ \ӛ۝Z[]]H]XY\ܝZ\[[YQXYۛXȋ[[^\\JNˆ\\ \ӛ۝Z[]]H]X\ۙ\Z\[][[^\\JN‚\\ܝ[\HH[KXY[^ -\ܝ[] -Nˆ\\ ۝Z[]]H]XY\ܝZ\[[YQXYۛXȋ\ܝ[\JNˆ\\ ۝Z[]]H]X\ۙ\Z\[]\ܝ[\JNˆ\\ ۝Z[\ܝXYۛXȋ\ܝ[\JNˆBјXBXXY LW\[XS[W]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[XP[[[[ȋ LW[]SZ\[[X\R^HNˆ\[\][۔]H] X[J[[^\\[]SZ\[[X\R^P\[XS[\][ۋȊNˆ\[T]H] X[J[[^\\[]SZ\[[X\R^P\[XS[KȊN‚\\ YJ[K^\[T] -K LH[\[XHKY[X[]H[H\Y\X[[KN‚\[\][۔\HH[KXY[^ -[\][۔] -Nˆ\\ \ӛ۝Z[]]H]X\[\[XU[YR[H[\][۔\JNˆ\\ \ӛ۝Z[]]H]XX\\\[XU[YH[\][۔\JN‚\[T\HH[KXY[^ -[T] -Nˆ\\ ۝Z[]]H]X\[\[XU[YR[H[T\JNˆ\\ ۝Z[]]H]XX\\\[XU[YH[T\JNˆ\\ ۝Z[\[Y]\\[^[T\JNˆBјXBXXY LW^T\T[\]R[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[XP[[[[ȋ LW[]SZ\[[X\R^HNˆ\^T[\]H] X[J[[^\\[]SZ\[[X\R^R^T[\˘ȊNˆ\^T\T[\]H] X[J[[^\\[]SZ\[[X\R^R^T\T[\˘ȊN‚\\ YJ[K^\^T\T[\] -K LH۝[[ۋ^K\\H[Y][ۈ[]H[H\Y\X[[KN‚\^T[\\HH[KXY[^ -^T[\] -Nˆ\\ \ӛ۝Z[]]H\՘[Y^T\H^T[\\JNˆ\\ \ӛ۝Z[]]H]XQ[\H^T[\\JNˆ\\ \ӛ۝Z[]]H]X\\XR^T\H^T[\\JNˆ\\ \ӛ۝Z[]]H]X\՘[Y^U\H^T[\\JN‚\^T\T[\\HH[KXY[^ -^T\T[\] -Nˆ\\ ۝Z[]]H\՘[Y^T\H^T\T[\\JNˆ\\ ۝Z[]]H]XQ[\H^T\T[\\JNˆ\\ ۝Z[]]H]X\\XR^T\H^T\T[\\JNˆ\\ ۝Z[]]H]X\՘[Y^U\H^T\T[\\JNˆBјXBXXY MW[[YPXW]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ MWZ\[ܙ\HNˆ\[[^\]H] X[J[[^\\Z\[ܙ\P[[^\ȊNˆ\[[YPXT]H] X[J[[^\\Z\[ܙ\S[[YPXKȊNˆ\[Y\[R^T]H] X[J[[^\\Z\[ܙ\S[Y\[R^KȊN‚\\ YJ[K^\[[YPXT] -K MH[\YۛY[\][ۈXH[]H[H\Y\X[[KNˆ\\ YJ[K^\[Y\[R^T] -K MH[ \Y\[HXH^\[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ -[[^\] -Nˆ\\ \ӛ۝Z[]]HX[Y\[[YPXH[[^\\JNˆ\\ \ӛ۝Z[]]HXYۛHX[ܚ]H[[^\\JNˆ\\ \ӛ۝Z[]]HXYۛHX[Y\[R^H[[^\\JN‚\[[YPXT\HH[KXY[^ -[[YPXT] -Nˆ\\ ۝Z[]]HX[Y\[[YPXH[[YPXT\JNˆ\\ ۝Z[]]HXYۛHX[ܚ]H[[YPXT\JNˆ\\ \ӛ۝Z[]]HXYۛHX[Y\[R^H[[YPXT\JN‚\[Y\[R^T\HH[KXY[^ -[Y\[R^T] -Nˆ\\ ۝Z[]]HXYۛHX[Y\[R^H[Y\[R^T\JNˆ\\ ۝Z[]]HX[Y\[Y\[R^P\\\[Y\[R^T\JNˆBјXBXXY MW]Y\T\T\][ۗ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ MWZ\[ܙ\HNˆ\[[^\]H] X[J[[^\\Z\[ܙ\P[[^\ȊNˆ\]Y\T\T]H] X[J[[^\\Z\[ܙ\T]Y\T\KȊN‚\\ YJ[K^\]Y\T\T] -K MHQ]Y\H\H\][ۈ[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ -[[^\] -Nˆ\\ \ӛ۝Z[]]H]X\[]Q[Y]ܚ]Y\T\H[[^\\JNˆ\\ \ӛ۝Z[]]H]XT\S[[YH[[^\\JN‚\]Y\T\HH[KXY[^ -]Y\T\T] -Nˆ\\ ۝Z[]]H]X\[]Q[Y]ܚ]Y\T\H]Y\T\JNˆ\\ ۝Z[]]H]XT\S[[YH]Y\T\JNˆBјXBXXY MWۜX[S[[[\\]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ MWZ\[ܙ\HNˆ\ۜX[T]H] X[J[[^\\Z\[ܙ\QۜX[P[[\\˘ȊNˆ\[[[\\]H] X[J[[^\\Z\[ܙ\QۜX[S[[[\\˘ȊN‚\\ YJ[K^\[[[\\] -K MHۜX[Hܝ[Y\YۙY[[]H[H\Y\X[[KN‚\ۜX[T\HH[KXY[^ -ۜX[T] -Nˆ\\ \ӛ۝Z[]]H\ܝ[ۜX[UY[ۜX[T\JNˆ\\ \ӛ۝Z[]]H]XQ]\YۙY[ۜX[T\JN‚\[[[\\\HH[KXY[^ -[[[\\] -Nˆ\\ ۝Z[]]H\ܝ[ۜX[UY[[[[\\\JNˆ\\ ۝Z[]]H]XQ]\YۙY[[[[\\\JNˆBјXBXXY MWۜX[U[YQ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ MWZ\[ܙ\HNˆ\ۜX[T]H] X[J[[^\\Z\[ܙ\QۜX[P[[\\˘ȊNˆ\[YQ]H] X[J[[^\\Z\[ܙ\QۜX[U[YQ˘ȊN‚\\ YJ[K^\[YQ] -K MHۜX[H[ ݘ[YKYX[[]H[H\Y\X[[KN‚\ۜX[T\HH[KXY[^ -ۜX[T] -Nˆ\\ ۝Z[]]H\Y[][ېY\ۜX[TܝۜX[T\JNˆ\\ \ӛ۝Z[]]HXZ]\ќS\][ۈۜX[T\JN‚\[YQ\HH[KXY[^ -[YQ] -Nˆ\\ ۝Z[]]HXZ]\ќS\][ۈ[YQ\JNˆ\\ ۝Z[T\S[[YH[YQ\JNˆBјXBXXY MWњ^\^T\PX]R[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ MWZ\[ܙ\HNˆ\^\]H] X[J[[^\\Z\[ܙ\Q^\ȊNˆ\^T\T]H] X[J[[^\\Z\[ܙ\Q^\^T\KȊN‚\\ YJ[K^\^T\T] -K MH^\\]K^[\^K\\HX[]H[H\Y\X[[KN‚\^\\HH[KXY[^ -^\] -Nˆ\\ ۝Z[XXX[Y\X[\Z\[ܙ\Q^\^\\JNˆ\\ \ӛ۝Z[]]H]X\\]R^P]X]H^\\JNˆ\\ \ӛ۝Z[]]H]X\^[\]X]H^\\JNˆ\\ \ӛ۝Z[]]H]X[[[X\R^T\ȋ^\\JN‚\^T\T\HH[KXY[^ -^T\T] -Nˆ\\ ۝Z[]]H]X\\]R^P]X]H^T\T\JNˆ\\ ۝Z[]]H]X\^[\]X]H^T\T\JNˆ\\ ۝Z[]]H]X[[[X\R^T\ȋ^T\T\JNˆBјXBXXY [X\R^PXW]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ њ[[XYّ\ܑY][Nˆ\^P[[\\]H] X[J[[^\\[[XYّ\ܑY][^P[[\\˘ȊNˆ\[X\R^PXT]H] X[J[[^\\[[XYّ\ܑY][[X\R^PXKȊN‚\\ YJ[K^\[X\R^PXT] -K [X\KZ^HXH]H[[[[]H[H\Y\X[[KN‚\^P[[\\\HH[KXY[^ -^P[[\\] -Nˆ\\ ۝Z[XX]X[X\R^PXHܙX]PXH^P[[\\\JNˆ\\ \ӛ۝Z[[\[X[Y\X[\[X\R^PXH^P[[\\\JN‚\[X\R^PXT\HH[KXY[^ -[X\R^PXT] -Nˆ\\ ۝Z[[\[X[Y\X[\[X\R^PXH[X\R^PXT\JNˆ\\ ۝Z[]]HY[\Q[T[Y[X\R^PXT\JNˆBјXBXXY ۙY\][۔[^\[]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[XP[[[[ȋ Z\[^X]ܙZYے^HNˆ\ۙY\][ې[[\\]H] X[J[[^\\Z\[^X]ܙZYے^PۙY\][ې[[\\˘ȊNˆ\[^\[]H] X[J[[^\\Z\[^X]ܙZYے^PۙY\][۔[^\[˘ȊN‚\\ YJ[K^\[^\[] -K \\[^[][ۜ\ۙY\][ۈ\[[]H[H\Y\X[[KN‚\ۙY\][ې[[\\\HH[KXY[^ -ۙY\][ې[[\\] -Nˆ\\ ۝Z[]]H]XۙY\][۔[Z[ۙY\][۔[ۙY\][ې[[\\\JNˆ\\ \ӛ۝Z[]]H]XY\ۙY\][۔[^ۙY\][ې[[\\\JN‚\[^\[\HH[KXY[^ -[^\[] -Nˆ\\ ۝Z[]]H]XY\ۙY\][۔[^[^\[\JNˆ\\ ۝Z[]]H]X[]]Y][ېۙY\][ے^H[^\[\JNˆBјXBXXY W]Y\T\][ۗ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋY[[[Y\ȋ WZ\[[YHNˆ\]Y\P[[\\]H] X[J[[^\\Z\[[YT]Y\P[[\\˘ȊNˆ\]Y\T]H] X[J[[^\\Z\[[YT]Y\T ȊN‚\\ YJ[K^\]Y\T] -K H]]Y\K\و[]H[H\Y\X[[KN‚\]Y\P[[\\\HH[KXY[^ -]Y\P[[\\] -Nˆ\\ ۝Z[]]H]XP[[^T]Y\PZ[]Y\P[[\\\JNˆ\\ \ӛ۝Z[]]H]XQ]]]Y\P[[\\\JN‚\]Y\T\HH[KXY[^ -]Y\T] -Nˆ\\ ۝Z[]]H]XQ]]]Y\T\JNˆ\\ ۝Z[\۝^]Y\T\JNˆBјXBXXY њ^\X[ے[]X[^\]ܚ]W]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ \Qޙ[]ܔ]XY[X\\X\ȊNˆ\]ܚ]T]H] X[J[[^\\\Qޙ[]ܔ]XY[X\\X\њ^\]ܚ]KȊNˆ\X[۔]ܚ]T]H] X[J[[^\\\Qޙ[]ܔ]XY[X\\X\њ^\X[۔]ܚ]KȊN‚\\ YJ[K^\X[۔]ܚ]T] -K X[ۋZ[]X[^\]ܚ]H[\[]H[H\Y\X[[KN‚\]ܚ]T\HH[KXY[^ -]ܚ]T] -Nˆ\\ \ӛ۝Z[]]H]XT]ܚ]PX[ے[]X[^\]ܚ]T\JN‚\X[۔]ܚ]T\HH[KXY[^ -X[۔]ܚ]T] -Nˆ\\ ۝Z[]]H]XT]ܚ]PX[ے[]X[^\X[۔]ܚ]T\JNˆ\\ ۝Z[[^[ \^R[]X[^\^\[ۈX[۔]ܚ]T\JNˆBјXBXXY W]X]Y\XXP\[Y[[X[ۗ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ W[Y]Nˆ\]ܚ]T]H] X[J[[^\\[Y]^\]X]Y\XXT]ܚ]KȊNˆ\\[Y[]H] X[J[[^\\[Y]^\]X]Y\XXP\[Y[˘ȊN‚\\ YJ[K^\\[Y[] -K H]X]Y\XXH\KX\[Y[[X[ۈ[]H[H\Y\X[[KN‚\]ܚ]T\HH[KXY[^ -]ܚ]T] -Nˆ\\ \ӛ۝Z[]]H]XQ][]\]Y[P\[Y[]ܚ]T\JNˆ\\ \ӛ۝Z[]]H]XQ][YY\]Y[P\[Y[]ܚ]T\JN‚\\[Y[\HH[KXY[^ -\[Y[] -Nˆ\\ ۝Z[]]H]XQ][]\]Y[P\[Y[\[Y[\JNˆ\\ ۝Z[]]H]XQ][YY\]Y[P\[Y[\[Y[\JNˆ\\ ۝Z[P\[Y[\][ۈ\[Y[\JNˆBјXBXXY W\Pۜ[\[ې[[\\]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ W[Q[]T[\ڙX[ۈNˆ\\YT]H] X[J[[^\\[Q[]T[\ڙX[ە\YP[[\\˘ȊNˆ\\U\YT]H] X[J[[^\\[Q[]T[\ڙX[۔\U\YKȊN‚\\ YJ[K^\\U\YT] -K H[\KXۜ[\[ۈ[\[]H[H\Y\X[[KN‚\\YT\HH[KXY[^ -\YT] -Nˆ\\ \ӛ۝Z[]]H]XQ]ۜ[YY\H\YT\JNˆ\\ \ӛ۝Z[]]H]XQ]ۙ][ۘ[X\\H\YT\JNˆ\\ \ӛ۝Z[]]H]X\ԙXYۛT\TY\[H\YT\JN‚\\U\YT\HH[KXY[^ -\U\YT] -Nˆ\\ ۝Z[]]H]XQ]ۜ[YY\H\U\YT\JNˆ\\ ۝Z[]]H]XQ]ۙ][ۘ[X\\H\U\YT\JNˆ\\ ۝Z[]]H]X\ԙXYۛT\TY\[H\U\YT\JNˆBјXBXXY ԙ[][ۜ\[Y[]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[XP[[[[ȋ Z\[^X]ܙZYے^HNˆ\[T]H] X[J[[^\\Z\[^X]ܙZYے^T[][ۜ\[KȊNˆ\Y[]H] X[J[[^\\Z\[^X]ܙZYے^T[][ۜ\[Y[˘ȊN‚\\ YJ[K^\Y[] -K \Y[Y[X[]H[H\Y\X[[KN‚\[T\HH[KXY[^ -[T] -Nˆ\\ \ӛ۝Z[]]H]X\YYS\Y[[T\JNˆ\\ \ӛ۝Z[]]H]X[^O[\Yۘ][۔H[T\JNˆ\\ \ӛ۝Z[]]H]X[^O[\[Y]\H[T\JN‚\Y[\HH[KXY[^ -Y[] -Nˆ\\ ۝Z[]]H]X\YYS\Y[Y[\JNˆ\\ ۝Z[]]H]X[^O[\Yۘ][۔HY[\JNˆ\\ ۝Z[]]H]X[^O[\[Y]\HY[\JNˆBјXBXXY ݚY\YQ^\[۔[\]R[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ [X]\SX]\X[^][ۈNˆ\۝[X][۔Y]T]H] X[J[[^\\[X]\SX]\X[^][ې۝[X][۔Y]KȊNˆ\ݚY\YQ^\[ۜ]H] X[J[[^\\[X]\SX]\X[^][۔ݚY\YQ^\[ۜ˘ȊN‚\\ YJ[K^\ݚY\YQ^\[ۜ] -K ݚY\\YH^\[ۈX\[ۈ[]H[H\Y\X[[KN‚\۝[X][۔Y]T\HH[KXY[^ -۝[X][۔Y]T] -Nˆ\\ \ӛ۝Z[]]H]X\ݚY\YQ^\[ۈ۝[X][۔Y]T\JNˆ\\ \ӛ۝Z[]]H]X\ݚY\YSؚXܙX][ۈ۝[X][۔Y]T\JNˆ\\ \ӛ۝Z[]]H]X\ݚY\YR[][ۈ۝[X][۔Y]T\JN‚\ݚY\YQ^\[ۜ\HH[KXY[^ -ݚY\YQ^\[ۜ] -Nˆ\\ ۝Z[]]H]X\ݚY\YQ^\[ۈݚY\YQ^\[ۜ\JNˆ\\ ۝Z[]]H]X\ݚY\YSؚXܙX][ۈݚY\YQ^\[ۜ\JNˆ\\ ۝Z[]]H]X\ݚY\YR[][ۈݚY\YQ^\[ۜ\JNˆBјXBXXY ԙY[[XYۛXXW]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ [X]\SX]\X[^][ۈNˆ\ܚY[[[\\]H] X[J[[^\\[X]\SX]\X[^][ۓܚY[[[\\˘ȊNˆ\Y[[XYۛX]H] X[J[[^\\[X]\SX]\X[^][۔Y[[XYۛX˘ȊN‚\\ YJ[K^\Y[[XYۛX] -K Y[[ [X]\X[^][ۈXYۛXXH[]H[H\Y\X[[KN‚\ܚY[[[\\\HH[KXY[^ -ܚY[[[\\] -Nˆ\\ \ӛ۝Z[]]H]XPܙX]TY[[XYۛXȋܚY[[[\\\JN‚\Y[[XYۛX\HH[KXY[^ -Y[[XYۛX] -Nˆ\\ ۝Z[]]H]XPܙX]TY[[XYۛXȋY[[XYۛX\JNˆ\\ ۝Z[[[ݙTY[[X]\X[^][ۑ^[Y[[XYۛX\JNˆBјXBXXY XYۛX\ܚ\ܜ]R[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋX]\X[^][ې[ڙX[ۈ [X]\SX]\X[^][ۈNˆ\[[^\]H] X[J[[^\\[X]\SX]\X[^][ې[[^\ȊNˆ\XYۛX]H] X[J[[^\\[X]\SX]\X[^][ۑXYۛX˘ȊN‚\\ YJ[K^\XYۛX] -K XYۛX\ܚ\ܜ[XYۛX\\HY]Y]H[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ -[[^\] -Nˆ\\ ۝Z[]]H]XY[[^R[][ۈ[[^\\JNˆ\\ \ӛ۝Z[]]H]XXYۛH[^XT[]H[[^\\JNˆ\\ \ӛ۝Z[XX]XXYۛHXYۛX\ܚ\܈[H[[^\\JNˆ\\ \ӛ۝Z[XX]XXYۛHXYۛX\ܚ\܈Y[[[H[[^\\JNˆ\\ \ӛ۝Z[]]H]X[[]]XQX[ۘ\O[[ψܙX]T\Y\ȋ[[^\\JN‚\XYۛX\HH[KXY[^ -XYۛX] -Nˆ\\ ۝Z[]]H]XXYۛH[^XT[]HXYۛX\JNˆ\\ ۝Z[XX]XXYۛHXYۛX\ܚ\܈[HXYۛX\JNˆ\\ ۝Z[XX]XXYۛHXYۛX\ܚ\܈Y[[[HXYۛX\JNˆ\\ ۝Z[]]H]X[[]]XQX[ۘ\O[[ψܙX]T\Y\ȋXYۛX\JNˆ\\ \ӛ۝Z[]]H]XY[[^R[][ۈXYۛX\JNˆBјXBXXY W[[]X[^\]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ W][\Sܙ\HNˆ\[[^\]H] X[J[[^\\][\Sܙ\P[[^\ȊNˆ\[]H] X[J[[^\\][\Sܙ\S[[]X[^\˘ȊN‚\\ YJ[K^\[] -K H[[]X[^\[ܚ]HX[[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ -[[^\] -Nˆ\\ ۝Z[XXX[Y\X[\][\Sܙ\P[[^\[[^\\JNˆ\\ \ӛ۝Z[]]HQ][P\YۛY[[[]X[^\[[^\\JNˆ\\ \ӛ۝Z[]]H]X\ܚ]PYܙU\H[[^\\JNˆ\\ \ӛ۝Z[]]H]X\ܚ]U[[[^\\JN‚\[\HH[KXY[^ -[] -Nˆ\\ ۝Z[]]HQ][P\YۛY[[[]X[^\[\JNˆ\\ ۝Z[]]H]X\ܚ]PYܙU\H[\JNˆ\\ ۝Z[]]H]X\ܚ]U[[\JNˆBјXBXXY [YPZ[\[[[]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋY[[[Y\ȋ \\X[^[ۈNˆ\[[^\]H] X[J[[^\\\\X[^[ې[[^\ȊNˆ\Z[\[]H] X[J[[^\\\\X[^[ے[YPZ[\[ ȊN‚\\ YJ[K^\Z[\[] -K [YKXZ[\[[[[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ -[[^\] -Nˆ\\ \ӛ۝Z[]]H[[H]Y\T][[H[[^\\JNˆ\\ \ӛ۝Z[]]HX[Y\[YPZ[[[\\ȋ[[^\\JN‚\Z[\[\HH[KXY[^ -Z[\[] -Nˆ\\ ۝Z[]]H[[H]Y\T][[HZ[\[\JNˆ\\ ۝Z[]]HX[Y\[YPZ[[[\\ȋZ[\[\JNˆ\\ ۝Z[XXQ]\TX[X[ۜȋZ[\[\JNˆBјXBXXY ܛ\X\[][ۗ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ ܛ\Sە[]XHNˆ\[[^\]H] X[J[[^\\ܛ\Sە[]XP[[^\ȊNˆ\ܛ\X\]H] X[J[[^\\ܛ\Sە[]XQܛ\X\˘ȊN‚\\ YJ[K^\ܛ\X\] -K ܛ\ XZ[[]X[]H[\[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ -[[^\] -Nˆ\\ ۝Z[XXX[Y\X[\ܛ\Sە[]XP[[^\[[^\\JNˆ\\ \ӛ۝Z[]]H]X\[]XQܛ\X\ȋ[[^\\JNˆ\\ \ӛ۝Z[]]H]XR[][ۓ\][ۈ[]\[ܛ\Z[[][ۈ[[^\\JN‚\ܛ\X\\HH[KXY[^ -ܛ\X\] -Nˆ\\ ۝Z[]]H]X\[]XQܛ\X\ȋܛ\X\\JNˆ\\ ۝Z[]]H]XR[][ۓ\][ۈ[]\[ܛ\Z[[][ۈܛ\X\\JNˆBјXBXXY ܛ\[\P[[\\]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ]Y\T\P[[][ۈ ܛ\Sە[]XHNˆ\[[^\]H] X[J[[^\\ܛ\Sە[]XP[[^\ȊNˆ\ܛ\[\\]H] X[J[[^\\ܛ\Sە[]XQܛ\[\\˘ȊN‚\\ YJ[K^\ܛ\[\\] -K ܛ\[T]Y\XXKQܛ\[\HX[]H[H\Y\X[[KN‚\[[^\\HH[KXY[^ -[[^\] -Nˆ\\ \ӛ۝Z[]]H]X\ܛ\[]Y\XXH[[^\\JNˆ\\ \ӛ۝Z[]]H]XU\T[X]]Y\XXQ[[Y[\H[[^\\JNˆ\\ \ӛ۝Z[]]H]X\ܛ\[U\T[X\JH[[^\\JN‚\ܛ\[\\\HH[KXY[^ -ܛ\[\\] -Nˆ\\ ۝Z[]]H]X\ܛ\[]Y\XXHܛ\[\\\JNˆ\\ ۝Z[]]H]XU\T[X]]Y\XXQ[[Y[\Hܛ\[\\\JNˆ\\ ۝Z[]]H]X\ܛ\[U\T[X\JHܛ\[\\\JNˆBјXBXXY [НZ[\[X\Y[]W]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\]X[۔]H] X[J[[^\\][[ۜX[ۑ]X[ۋȊNˆ\[X\]H] X[J[[^\\][[ۜX[۔[НZ[\[X\\˘ȊNˆ\Y[]T]H] X[J[[^\\][[ۜX[۔[НZ[\Y[]KȊN‚\\ YJ[K^\[X\] -K [НZ[\[X\X][[]H[H\Y\X[[KNˆ\\ YJ[K^\Y[]T] -K [НZ[\[X\Y[]H\][ۈ[]H[H\Y\X[[KN‚\]X[۔\HH[KXY[^ -]X[۔] -Nˆ\\ \ӛ۝Z[]]HXYۛHX[Y[]H]X[۔\JN‚\[X\\HH[KXY[^ -[X\] -Nˆ\\ \ӛ۝Z[]]HXYۛHX[Y[]H[X\\JNˆ\\ \ӛ۝Z[]]H]X[Y[]H\S[Y[]H[X\\JN‚\Y[]T\HH[KXY[^ -Y[]T] -Nˆ\\ ۝Z[]]HXYۛHX[Y[]HY[]T\JNˆ\\ ۝Z[]]H]X[Y[]H\S[Y[]HY[]T\JNˆBјXBXXY [НZ[\[X\X\[YYܚ]\]R[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\[X\]H] X[J[[^\\][[ۜX[۔[НZ[\[X\\˘ȊNˆ\X\[YYܚ]\]H] X[J[[^\\][[ۜX[۔[НZ[\[X\ܚ]\˘ȊN‚\\ YJ[K^\X\[YYܚ]\] -K [НZ[\[X\X\[YY ]ܚ]H\][ۈ[]H[H\Y\X[[KN‚\[X\\HH[KXY[^ -[X\] -Nˆ\\ \ӛ۝Z[]]H]X\ӛۑX\[YYܚ]PY\]\X\[YY[X\\JNˆ\\ \ӛ۝Z[]]H]XT\QX\[YY[[YH[X\\JN‚\X\[YYܚ]\\HH[KXY[^ -X\[YYܚ]\] -Nˆ\\ ۝Z[]]H]X\ӛۑX\[YYܚ]PY\]\X\[YYX\[YYܚ]\\JNˆ\\ ۝Z[]]H]XT\QX\[YY[[YHX\[YYܚ]\\JNˆBјXBXXY [НZ[\\[]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\]X[۔]H] X[J[[^\\][[ۜX[ۑ]X[ۋȊNˆ\]H] X[J[[^\\][[ۜX[۔[НZ[\˘ȊN‚\\ YJ[K^\] -K [НZ[\\[ ܙ\][]H[H\Y\X[[KN‚\]X[۔\HH[KXY[^ -]X[۔] -Nˆ\\ \ӛ۝Z[]]H]X\[НZ[\\[\[Y[ېۜ[]X[۔\JN‚\\HH[KXY[^ -] -Nˆ\\ ۝Z[]]H]X\[НZ[\\[\[Y[ېۜ[\JNˆBјXBXXY [НZ[\[ܚ]Q]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\]H] X[J[[^\\][[ۜX[۔[НZ[\˘ȊNˆ\[ܚ]\]H] X[J[[^\\][[ۜX[۔[НZ[\[ܚ]\˘ȊNˆ\]\ܚ]\]H] X[J[[^\\][[ۜX[۔[НZ[\]\[ܚ]KȊN‚\\ YJ[K^\[ܚ]\] -K [НZ[\[ ]ܚ]H[]H[H\Y\X[[KNˆ\\ YJ[K^\]\ܚ]\] -K [НZ[\]\[ ]ܚ]H[]H[H\Y\X[[KN‚\\HH[KXY[^ -] -Nˆ\\ \ӛ۝Z[]]H]X\ۛPۜ[[ܚ]\ЙYܙTY\[H\JNˆ\\ \ӛ۝Z[]]H]X\]\ېۜ[[ܚ]PYܙTY\[H\JNˆ\\ \ӛ۝Z[]]H]XS\][ۈ]\[\YۛY[Y[YH\JN‚\[ܚ]\\HH[KXY[^ -[ܚ]\] -Nˆ\\ ۝Z[]]H]X\ۛPۜ[[ܚ]\ЙYܙTY\[H[ܚ]\\JNˆ\\ \ӛ۝Z[]]H]X\]\ېۜ[[ܚ]PYܙTY\[H[ܚ]\\JN‚\]\ܚ]\\HH[KXY[^ -]\ܚ]\] -Nˆ\\ ۝Z[]]H]X\]\ېۜ[[ܚ]PYܙTY\[H]\ܚ]\\JNˆ\\ ۝Z[]]H]XS\][ۈ]\[\YۛY[Y[YH]\ܚ]\\JNˆBјXBXXY [НZ[\]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\]H] X[J[[^\\][[ۜX[۔[НZ[\˘ȊNˆ\]H] X[J[[^\\][[ۜX[۔[НZ[\˘ȊN‚\\ YJ[K^\] -K [НZ[\ X\YY[ܚ]H[]H[H\Y\X[[KN‚\\HH[KXY[^ -] -Nˆ\\ \ӛ۝Z[]]H]X\ӛېۜ[\YY[ܚ]H\JNˆ\\ \ӛ۝Z[]]H]X\X\[YYۜ[[ܚ]R[[YR]\][ېYܙTY\[H\JN‚\\HH[KXY[^ -] -Nˆ\\ ۝Z[]]H]X\ӛېۜ[\YY[ܚ]H\JNˆ\\ ۝Z[]]H]X\X\[YYۜ[[ܚ]R[[YR]\][ېYܙTY\[H\JNˆBјXBXXY \YYXXX[]W]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\XXX[]T]H] X[J[[^\\][[ۜX[۔XXX[]KȊNˆ\XXX[]T]H] X[J[[^\\][[ۜX[ۓXXX[]KȊN‚\\ YJ[K^\XXX[]T] -K  X\YYܚ]HXXX[]H[]H[H\Y\X[[KN‚\XXX[]T\HH[KXY[^ -XXX[]T] -Nˆ\\ \ӛ۝Z[]]H]X\\YYܚ]QܔY\[HXXX[]T\JNˆ\\ \ӛ۝Z[]]H]X\[^XXX[]T\JNˆ\\ \ӛ۝Z[]]H]X[ܚ]TXX]\]\][ۈXXX[]T\JN‚\XXX[]T\HH[KXY[^ -XXX[]T] -Nˆ\\ ۝Z[]]H]X\\YYܚ]QܔY\[HXXX[]T\JNˆ\\ ۝Z[]]H]X\[^XXX[]T\JNˆ\\ ۝Z[]]H]X[ܚ]TXX]\]\][ۈXXX[]T\JNˆBјXBXXY [НZ[\\[[]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\]H] X[J[[^\\][[ۜX[۔[НZ[\˘ȊNˆ\\[[]H] X[J[[^\\][[ۜX[۔[НZ[\\[[ȊN‚\\ YJ[K^\\[[] -K [НZ[\\[[[]H[H\Y\X[[KN‚\\HH[KXY[^ -] -Nˆ\\ \ӛ۝Z[]]H]X۝Z[\X[\[НZ[\\[\JNˆ\\ \ӛ۝Z[]]H]X[]]\X\[YY[НZ[\\]\JN‚\\[[\HH[KXY[^ -\[[] -Nˆ\\ ۝Z[]]H]X۝Z[\X[\[НZ[\\[\[[\JNˆ\\ \ӛ۝Z[]]H]X[]]\X\[YY[НZ[\\]\[[\JNˆBјXBXXY [НZ[\\]]X[ۗ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ][[X\]H Ԙ][[ۜX[ۈNˆ\\[[]H] X[J[[^\\][[ۜX[۔[НZ[\\[[ȊNˆ\\]]X[۔]H] X[J[[^\\][[ۜX[۔[НZ[\\]]X[ۋȊN‚\\ YJ[K^\\]]X[۔] -K [НZ[\\]]X[ۈ[]H[H\Y\X[[KN‚\\[[\HH[KXY[^ -\[[] -Nˆ\\ \ӛ۝Z[]]H]X[]]\X\[YY[НZ[\\]\[[\JNˆ\\ \ӛ۝Z[YXԙ\]\[[\JN‚\\]]X[۔\HH[KXY[^ -\]]X[۔] -Nˆ\\ ۝Z[]]H]X[]]\X\[YY[НZ[\\]\]]X[۔\JNˆ\\ ۝Z[YXԙ\]\]]X[۔\JNˆBјXBXXY LW\[XPۙY\][۔\][ۗ]\[YX]Y\X[ - -Bˆ\[[^\\H] X[Jܙ\ԛ ܘȋ[P۝X[[[^\ȋ[XP[[[[ȋ LW[]SZ\[[X\R^HNˆ\ۙY\][۔[]H] X[J[[^\\[]SZ\[[X\R^PۙY\][۔[ȊNˆ\\[XT\][۔]H] X[J[[^\\[]SZ\[[X\R^P\[XT\][ۋȊNˆ\\[XS[]H] X[J[[^\\[]SZ\[[X\R^P\[XS[\][ۋȊNˆ\\[XSY[X\]H] X[J[[^\\[]SZ\[[X\R^P\[XSY[X\\][ۋȊN‚\\ YJ[K^\\[XT\][۔] -K LH\PۙY\][ۜќP\[XH\][ۈ[]H[H\Y\X[[KNˆ\\ YJ[K^\\[XS[] -K LH\PۙY\][ۜќP\[XH[\][ۈ[]H[H\Y\X[[KNˆ\\ YJ[K^\\[XSY[X\] -K LH\PۙY\][ۜќP\[XHY[X\\][ۈ[]H[H\Y\X[[KN‚\ۙY\][۔[\HH[KXY[^ -ۙY\][۔[] -Nˆ\\ \ӛ۝Z[]]H]X\\[\[XQ^\[ۈۙY\][۔[\JNˆ\\ \ӛ۝Z[]]H]XT\S[\[\[XHۙY\][۔[\JN‚\\[XT\][۔\HH[KXY[^ -\[XT\][۔] -Nˆ\\ ۝Z[]]H]X\\[\[XQ^\[ۈ\[XT\][۔\JNˆ\\ \ӛ۝Z[]]H]XT\S[\[\[XH\[XT\][۔\JN‚\\[XS[\HH[KXY[^ -\[XS[] -Nˆ\\ ۝Z[]]H]XT\S[\[\[XH\[XS[\JNˆ\\ \ӛ۝Z[]]HzmkwB$33%WV7WFUWFFTf$'VƵWFFW2"f"6fT6vW4FUFF6&RfW$F"$WV7WFUWFFTf$'VƵWFFW4fW%6fT6vW4FR72"f"757W'EFF6&RfW$F"$WV7WFUWFFTf$'VƵWFFW4fW$757W'B72"76W'BG'VRfRW7G2757W'EF$33"fW"WV7WFUWFFT726&ƗGFWFV7F6VBƗfRf7W6VB'FfR"f"6fT6vW4FU6W&6RfR&VDFWB6fT6vW4FUF76W'BFW4D6F'&fFR7FF2&4WV7WFUWFFT75FVfW&B"6fT6vW4FU6W&6R76W'BFW4D6F'&fFR7FF2&4WV7WFUWFFT757W'B"6fT6vW4FU6W&6R76W'BFW4D6F'&fFR7FF2&4WV7WFUWFFT74WFB"6fT6vW4FU6W&6R76W'BFW4D6F'&fFR7FF2&4WV7WFUWFFT74ƖTWFB"6fT6vW4FU6W&6R76W'BFW4D6F'&fFR7FF2&4VFGg&Wv&6&TW76R"6fT6vW4FU6W&6Rf"757W'E6W&6RfR&VDFWB757W'EF76W'B6F2'&fFR7FF2&4WV7WFUWFFT75FVfW&B"757W'E6W&6R76W'B6F2'&fFR7FF2&4WV7WFUWFFT757W'B"757W'E6W&6R76W'B6F2'&fFR7FF2&4WV7WFUWFFT74WFB"757W'E6W&6R76W'B6F2'&fFR7FF2&4WV7WFUWFFT74ƖTWFB"757W'E6W&6R76W'B6F2'&fFR7FF2&4VFGg&Wv&6&TW76R"757W'E6W&6RРf7EТV&Ɩ2fB33%6w&FTFWFV7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$'VƴW&F4E6WD&6VEw&FW2"$33%WV7WFUWFFTf$'VƵWFFW2"f"7FFVVDǗ65FF6&RǗW$F"$WV7WFUWFFTf$'VƵWFFW57FFVVDǗ6272"f"6w&FTFWFV7FFF6&RǗW$F"$WV7WFUWFFTf$'VƵWFFW46w&FTFWFV7F72"76W'BG'VRfRW7G26w&FTFWFV7FF$33"6w&FRFWFV7F6VBƗfRf7W6VBǗW"'FfR"f"7FFVVDǗ656W&6RfR&VDFWB7FFVVDǗ65F76W'BFW4D6F'&fFR7FF2&46w&FW2"7FFVVDǗ656W&6Rf"6w&FTFWFV7F6W&6RfR&VDFWB6w&FTFWFV7FF76W'B6F2'&fFR7FF2&46w&FW2"6w&FTFWFV7F6W&6RРf7EТV&Ɩ2fB376V&ǕW6u66UG&fW'6ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3VFG֗76u&'W"f"f6&ƗGFF6&RǗW$F"$VFG֗76u&'W76V&ǕW6uf6&ƗG72"f"W6u66UFF6&RǗW$F"$VFG֗76u&'W76V&ǕW6u66R72"76W'BG'VRfRW7G2W6u66UF$376V&ǒW6r66RG&fW'66VBƗfRf7W6VB'FfR"f"f6&ƗG6W&6RfR&VDFWBf6&ƗGF76W'BFW4D6F'&fFR7FF2&W6tF&V7FfT66R"f6&ƗG6W&6R76W'BFW4D6F&f&V6f"W76TFV6&FFR6W7F'2eGSW76TFV6&F7Fₒ"f6&ƗG6W&6R76W'BFW4D6F&f&V6f"fU66VDW76RFR6W7F'2eGSfU66VDW76TFV6&F7Fₒ"f6&ƗG6W&6Rf"W6u66U6W&6RfR&VDFWBW6u66UF76W'B6F2'&fFR7FF2&W6tF&V7FfT66R"W6u66U6W&6R76W'B6F2&f&V6f"W76TFV6&FFR6W7F'2eGSW76TFV6&F7Fₒ"W6u66U6W&6R76W'B6F2&f&V6f"fU66VDW76RFR6W7F'2eGSfU66VDW76TFV6&F7Fₒ"W6u66U6W&6RРf7EТV&Ɩ2fB376V&ǔV&W%&W6WFƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3VFG֗76u&'W"f"6&W6WFFF6&RǗW$F"$VFG֗76u&'W76V&ǔ6&W6WF72"f"V&W%&W6WFFF6&RǗW$F"$VFG֗76u&'W76V&ǔV&W%&W6WF72"76W'BG'VRfRW7G2V&W%&W6WFF$376V&ǒV&W"FƗW"&W6WF6VBƗfRf7W6VB'FfR"f"6&W6WF6W&6RfR&VDFWB6&W6WFF76W'BFW4D6F'&fFR7FF2&G'&W6fTV&W$7W'&VD76V&ǒ"6&W6WF6W&6Rf"V&W%&W6WF6W&6RfR&VDFWBV&W%&W6WFF76W'B6F2'&fFR7FF2&G'&W6fTV&W$7W'&VD76V&ǒ"V&W%&W6WF6W&6R76W'B6F2&f&V6f"V&W"V&W'2"V&W%&W6WF6W&6R76W'B6F2$FV6&u7F&VfW&V6W2"V&W%&W6WF6W&6RРf7EТV&Ɩ2fB3efW$W&W76&G6666W74F66fW'ƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%VW'6TEG&6F"$3efDFFUFTr"f"W&W76&GFF6&RfW$F"$fDFFUFTtfW$W&W76&G72"f"6666W75FF6&RfW$F"$fDFFUFTtfW$W&W76&G6666W76W272"76W'BG'VRfRW7G26666W75F$3bW&W76&FVB6666W72F66fW'6VBƗfRf7W6VB'FfR"f"W&W76&G6W&6RfR&VDFWBW&W76&GF76W'BFW4D6F'&fFR7FF2&VDǔƗ7C66&W6VVC'VDW&W76&G&W6VVG2"W&W76&G6W&6R76W'BFW4D6F'&fFR7FF266&W6VVCfE&W6VVDf""W&W76&G6W&6R76W'BFW4D6F'&fFR7FF2VVW&&SV&W$66W74W&W767FfDW&W76&G6666W76W2"W&W76&G6W&6R76W'BFW4D6F'&fFR7FF2&466&W'G66W72"W&W76&G6W&6R76W'BFW4D6F'&fFR6VVB67266&W6VVB"W&W76&G6W&6Rf"6666W756W&6RfR&VDFWB6666W75F76W'B6F2'&fFR7FF2&VDǔƗ7C66&W6VVC'VDW&W76&G&W6VVG2"6666W756W&6R76W'B6F2'&fFR7FF266&W6VVCfE&W6VVDf""6666W756W&6R76W'B6F2'&fFR7FF2VVW&&SV&W$66W74W&W767FfDW&W76&G6666W76W2"6666W756W&6R76W'B6F2'&fFR7FF2&466&W'G66W72"6666W756W&6R76W'B6F2'&fFR6VVB67266&W6VVB"6666W756W&6RРf7EТV&Ɩ2fB3ufW$6VFU&Ww&FUƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$WV7WFD72"$3uW4TW""f"7FǗ65FF6&RfW$F"$W4TW$fW%7FǗ6272"f"6VFU&Ww&FUFF6&RfW$F"$W4TW$fW$6VFU&Ww&FR72"76W'BG'VRfRW7G26VFU&Ww&FUF$3rfW"6VFR&Ww&FR67G'V7F6VBƗfRf7W6VB'FfR"f"7FǗ656W&6RfR&VDFWB7FǗ65F76W'BFW4D6F'&fFR7FF2&G'FD6VFR"7FǗ656W&6Rf"6VFU&Ww&FU6W&6RfR&VDFWB6VFU&Ww&FUF76W'B6F2'&fFR7FF2&G'FD6VFR"6VFU&Ww&FU6W&6R76W'B6F2%7Ff7F'f6FW&W76"6VFU&Ww&FU6W&6R76W'B6F2%7Ff7F'FVFfW$R$6VFU""6VFU&Ww&FU6W&6RРf7EТV&Ɩ2fB3g&W66FWDWVFƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$36fT6vW4"f"ǗW%FF6&RǗW$F"%6fT6vW4ǗW"72"f"g&W66FWEFF6&RǗW$F"%6fT6vW4g&W66FWB72"76W'BG'VRfRW7G2g&W66FWEF$3g&W6F$6FWB֖WVF6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'BFW4D6F'&fFR7FF2&56fU&V6VfW$g&W66FWDFV6&VD6FT&G"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&46w&GFV&Vf&TW&F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&46&VfW&V6R"ǗW%6W&6Rf"g&W66FWE6W&6RfR&VDFWBg&W66FWEF76W'B6F2'&fFR7FF2&56fU&V6VfW$g&W66FWDFV6&VD6FT&G"g&W66FWE6W&6R76W'B6F2'&fFR7FF2&46w&GFV&Vf&TW&F"g&W66FWE6W&6R76W'B6F2'&fFR7FF2&46&VfW&V6R"g&W66FWE6W&6RРf7EТV&Ɩ2fB335F66WDf6F676f6FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$335W6Tg&V6WDf%7FF4V&W'666W2"f"FƗW%FF6&RǗW$F"%W6Tg&V6WDf%7FF4V&W'666W4FƗW$676f6F72"f"F66WEFF6&RǗW$F"%W6Tg&V6WDf%7FF4V&W'666W5F66WD676f6F72"76W'BG'VRfRW7G2F66WEF$332F66WBf6F676f6F6VBƗfRf7W6VB'FfR"f"FƗW%6W&6RfR&VDFWBFƗW%F76W'BFW4D6F'&fFR7FF2&57W'FVEF66WDf6F"FƗW%6W&6R76W'BFW4D6F'&fFR7FF2&57FF5GT$W76T66W72"FƗW%6W&6R76W'BFW4D6F'&fFR7FF2&5GT$W76U7&"FƗW%6W&6Rf"F66WE6W&6RfR&VDFWBF66WEF76W'B6F2'&fFR7FF2&57W'FVEF66WDf6F"F66WE6W&6R76W'B6F2'&fFR7FF2&57FF5GT$W76T66W72"F66WE6W&6R76W'B6F2'&fFR7FF2&5GT$W76U7&"F66WE6W&6RРf7EТV&Ɩ2fB3#%w&WuVW'&TWVFƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$3#%FƗ7D6VV7E&V7F"f"ǗW%FF6&RǗW$F"%FƗ7D6VV7E&V7FǗW"72"f"w&WuFF6&RǗW$F"%FƗ7D6VV7E&V7Fw&Wr72"76W'BG'VRfRW7G2w&WuF$3#"w&WrVW'&RWVF6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F672FƗ7D6VV7E&V7FǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&4w&WuVW'&R"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2GU7&vWEVW'&TVVVEGR"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&4w&WuGR"ǗW%6W&6Rf"w&Wu6W&6RfR&VDFWBw&WuF76W'B6F2'&fFR7FF2&4w&WuVW'&R"w&Wu6W&6R76W'B6F2'&fFR7FF2GU7&vWEVW'&TVVVEGR"w&Wu6W&6R76W'B6F2'&fFR7FF2&4w&WuGR"w&Wu6W&6RРf7EТV&Ɩ2fB35fW%W&67FDFWFV7FƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$35fW$6VB"f"fW%FF6&RfW$F"$fW$6VDfW"72"f"W&67FEFF6&RfW$F"$fW$6VDfW%W&67FB72"76W'BG'VRfRW7G2W&67FEF$32fW"W&67FBFWFV7F6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672fW$6VDfW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&5W&67FB"fW%6W&6R76W'BFW4D6F'&fFR7FF2&5W&67FB"fW%6W&6R76W'BFW4D6F'&fFR7FF2&5W&fVR"fW%6W&6Rf"W&67FE6W&6RfR&VDFWBW&67FEF76W'B6F2'&fFR7FF2&5W&67FB"W&67FE6W&6R76W'B6F2'&fFR7FF2&5W&67FB"W&67FE6W&6R76W'B6F2'&fFR7FF2&5W&fVR"W&67FE6W&6RРf7EТV&Ɩ2fB3#ufW%&'WF66fW'ƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3#u֗76tWƖ6Df&VvW"f"fW%FF6&RfW$F"$֗76tWƖ6Df&VvWfW"72"f"&'WFF6&RfW$F"$֗76tWƖ6Df&VvWfW%&'WF66fW'72"76W'BG'VRfRW7G2&'WF$3#rfW"&'ֶWF66fW'6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672֗76tWƖ6Df&VvWfW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&W'G7&G'fD6fVF&'W"fW%6W&6R76W'BFW4D6F'&fFR7FF2&W'G7&G'fD6fwW&VE&'W"fW%6W&6R76W'BFW4D6F'&fFR7FF2&G'vWDVFGGT'VFW$VFG"fW%6W&6Rf"&'W6W&6RfR&VDFWB&'WF76W'B6F2'&fFR7FF2&W'G7&G'fD6fVF&'W"&'W6W&6R76W'B6F2'&fFR7FF2&W'G7&G'fD6fwW&VE&'W"&'W6W&6R76W'B6F2'&fFR7FF2&G'vWDVFGGT'VFW$VFG"&'W6W&6RРf7EТV&Ɩ2fB3eVW'&U66TFWFV7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%VW'6TEG&6F"$3efDFFUFTr"f"ǗW%FF6&RǗW$F"$fDFFUFTtǗW"72"f"VW'&U66UFF6&RǗW$F"$fDFFUFTuVW'&U66R72"76W'BG'VRfRW7G2VW'&U66UF$3bVW'&R&F66RFWFV7F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F672fDFFUFTtǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2W4gV7FW&FfEVW'&T&F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5F&vWEVW'&Tf6F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2W4gV7FW&FfDV66t&F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2VVW&&SĔW&FVVW&FTW&F2"ǗW%6W&6Rf"VW'&U66U6W&6RfR&VDFWBVW'&U66UF76W'B6F2'&fFR7FF2W4gV7FW&FfEVW'&T&F"VW'&U66U6W&6R76W'B6F2'&fFR7FF2&5F&vWEVW'&Tf6F"VW'&U66U6W&6R76W'B6F2'&fFR7FF2W4gV7FW&FfDV66t&F"VW'&U66U6W&6R76W'B6F2'&fFR7FF2VVW&&SĔW&FVVW&FTW&F2"VW'&U66U6W&6RРf7EТV&Ɩ2fB3#uGTFW67G'V7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3#u֗76tWƖ6Df&VvW"f"GTWFF6&RǗW$F"$֗76tWƖ6Df&VvWGTW72"f"GTFWFF6&RǗW$F"$֗76tWƖ6Df&VvWGTFW72"76W'BG'VRfRW7G2GTFWF$3#rGR֖FW67G'V7F6VBƗfRf7W6VB'FfR"f"GTW6W&6RfR&VDFWBGTWF76W'B6F2'&fFR6VVB'F6726FFV"GTW6W&6R76W'BFW4D6F'&fFRGTFW'VEGTFW"GTW6W&6R76W'BFW4D6F'&fFR6VVB672GTFW"GTW6W&6R76W'BFW4D6F'&fFR7FF2fBFDW76UGW2"GTW6W&6R76W'BFW4D6F'&fFR7FF2fBFEGTDW7FVEGW2"GTW6W&6R76W'BFW4D6F'&fFR7FF2fBFDWR"GTW6W&6Rf"GTFW6W&6RfR&VDFWBGTFWF76W'B6F2'&fFRGTFW'VEGTFW"GTFW6W&6R76W'B6F2'&fFR6VVB672GTFW"GTFW6W&6R76W'B6F2'&fFR7FF2fBFDW76UGW2"GTFW6W&6R76W'B6F2'&fFR7FF2fBFEGTDW7FVEGW2"GTFW6W&6R76W'B6F2'&fFR7FF2fBFDWR"GTFW6W&6RРf7EТV&Ɩ2fB3VW'&U66TFWFV7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%VW'6TEG&6F"$36WFB"f"ǗW%FF6&RǗW$F"$6WFDǗW"72"f"VW'&U66UFF6&RǗW$F"$6WFDǗW%VW'&U66R72"76W'BG'VRfRW7G2VW'&U66UF$3VW'&R66RFWFV7F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'BFW4D6F'&fFR7FF2&5G&6F7&F6VW'&Tf6F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&wVVDW&FvWDWE6WVV6T&wVVB"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&f6FFWVG4&F&WFW""ǗW%6W&6Rf"VW'&U66U6W&6RfR&VDFWBVW'&U66UF76W'B6F2'&fFR7FF2&5G&6F7&F6VW'&Tf6F"VW'&U66U6W&6R76W'B6F2'&fFR7FF2&wVVDW&FvWDWE6WVV6T&wVVB"VW'&U66U6W&6R76W'B6F2'&fFR7FF2&f6FFWVG4&F&WFW""VW'&U66U6W&6RРf7EТV&Ɩ2fB3#FVFG6VV7F$676f6FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$3#&VGVFDFVFG6VV7B"f"ǗW%FF6&RǗW$F"%&VGVFDFVFG6VV7DǗW"72"f"FVFG6VV7F%FF6&RǗW$F"%&VGVFDFVFG6VV7DFVFG6VV7F"72"76W'BG'VRfRW7G2FVFG6VV7F%F$3#FVFG6VV7F"676f6F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F672&VGVFDFVFG6VV7DǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2W4gV7FW&FG'vWD&F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&4W7DVVW&&TFW&f6R"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5GU&W6W'fu6VV7F""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&4FVFG&F"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2W&FVw&FVFGfVR"ǗW%6W&6Rf"FVFG6VV7F%6W&6RfR&VDFWBFVFG6VV7F%F76W'B6F2'&fFR7FF2W4gV7FW&FG'vWD&F"FVFG6VV7F%6W&6R76W'B6F2'&fFR7FF2&4W7DVVW&&TFW&f6R"FVFG6VV7F%6W&6R76W'B6F2'&fFR7FF2&5GU&W6W'fu6VV7F""FVFG6VV7F%6W&6R76W'B6F2'&fFR7FF2&4FVFG&F"FVFG6VV7F%6W&6R76W'B6F2'&fFR7FF2W&FVw&FVFGfVR"FVFG6VV7F%6W&6RРf7EТV&Ɩ2fB3%fW%VW'6W&6T6FWE&W6WFƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$'VƴW&F4E6WD&6VEw&FW2"$3%F֗U&VfU&vR"f"&Ww&FU6fWGFF6&RfW$F"$F֗U&VfU&vTfW%&Ww&FU6fWG72"f"VW'6W&6UFF6&RfW$F"$F֗U&VfU&vTfW%VW'6W&6T6FWB72"76W'BG'VRfRW7G2VW'6W&6UF$3"fW"VW'6W&6R6FWB&W6WF6VBƗfRf7W6VB'FfR"f"&Ww&FU6fWG6W&6RfR&VDFWB&Ww&FU6fWGF76W'BFW4D6F'&fFR7FF2&G'&W6fUVW'6W&6Tg&W66FWD6"&Ww&FU6fWG6W&6R76W'BFW4D6F'&fFR7FF2&G'vWEG&7&VEVW'f6F6W&6R"&Ww&FU6fWG6W&6R76W'BFW4D6F'&fFR7FF2&56vU6W&6UG&7&VEVW'WFB"&Ww&FU6fWG6W&6Rf"VW'6W&6RfR&VDFWBVW'6W&6UF76W'B6F2'&fFR7FF2&G'&W6fUVW'6W&6Tg&W66FWD6"VW'6W&6R76W'B6F2'&fFR7FF2&G'vWEG&7&VEVW'f6F6W&6R"VW'6W&6R76W'B6F2'&fFR7FF2&56vU6W&6UG&7&VEVW'WFB"VW'6W&6RРf7EТV&Ɩ2fB33e6gV7F6&66GW&UƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$WV7WFD72"$33eF$6FWD6GW&VD7&75F&VG2"f"6GW&TǗ65FF6&RǗW$F"$F$6FWD6GW&VD7&75F&VG46GW&TǗ6272"f"6gV7FFF6&RǗW$F"$F$6FWD6GW&VD7&75F&VG46gV7F6GW&R72"76W'BG'VRfRW7G26gV7FF$33b6gV7F6&66GW&R6VBƗfRf7W6VB'FfR"f"6GW&TǗ656W&6RfR&VDFWB6GW&TǗ65F76W'BFW4D6F'&fFR7FF2&G'fD6GW&VDF$6FWD6gV7F6&6"6GW&TǗ656W&6Rf"6gV7F6W&6RfR&VDFWB6gV7FF76W'B6F2'&fFR7FF2&G'fD6GW&VDF$6FWD6gV7F6&6"6gV7F6W&6R76W'B6F2$6gV7F7FFVVE7F"6gV7F6W&6R76W'B6F2$WFDB6gV7F"6gV7F6W&6RРf7EТV&Ɩ2fB33UvW&T6G&fW'6ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$'VƴW&F4E6WD&6VEw&FW2"$33U֗76uvW&T&Vf&TWV7WFTFVWFUWFFR"f"ǗW%FF6&RǗW$F"$֗76uvW&T&Vf&TWV7WFTFVWFUWFFTǗW"72"f"vW&T6FF6&RǗW$F"$֗76uvW&T&Vf&TWV7WFTFVWFUWFFUvW&T672"76W'BG'VRfRW7G2vW&T6F$33RvW&R6G&fW'66VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'BFW4D6F'&fFR7FF2&5vW&T6"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&4vƖvW&R"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5VW'7FvW&R"ǗW%6W&6Rf"vW&T66W&6RfR&VDFWBvW&T6F76W'B6F2'&fFR7FF2&5vW&T6"vW&T66W&6R76W'B6F2'&fFR7FF2&4vƖvW&R"vW&T66W&6R76W'B6F2'&fFR7FF2&5VW'7FvW&R"vW&T66W&6RРf7EТV&Ɩ2fB3%fW%&VGVFDFW&ƗF&Ww&FUƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$3%&VGW&TFW&ƗF"f"fW%FF6&RfW$F"%&VGW&TFW&ƗFfW"72"f"&VGVFE&Ww&FUFF6&RfW$F"%&VGW&TFW&ƗFfW%&VGVFDFW&ƗF72"76W'BG'VRfRW7G2&VGVFE&Ww&FUF$3"&VGVFBFW&ƗFfW"&Ww&FR6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'BFW4D6F'&fFR7FF272F6F7VVC&VfU&VGVFDFW&ƗF72"fW%6W&6Rf"&VGVFE&Ww&FU6W&6RfR&VDFWB&VGVFE&Ww&FUF76W'B6F2'&fFR7FF272F6F7VVC&VfU&VGVFDFW&ƗF72"&VGVFE&Ww&FU6W&6R76W'B6F2%G'vWDƖTFW&ƗW%'G2"&VGVFE&Ww&FU6W&6R76W'B6F2$f&GFW"FF"&VGVFE&Ww&FU6W&6RРf7EТV&Ɩ2fB33uV6tfuƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%&u7E6V7W&G"$33u&u77G&t67G'V7F"f"FW&֖FfuFF6&RǗW$F"%&u77G&t67G'V7FFW&֖Ffr72"f"VfuFF6&RǗW$F"%&u77G&t67G'V7FVfr72"76W'BG'VRfRW7G2VfuF$33rV&VfW&V6R6rfr6VBƗfRf7W6VB'FfR"f"FW&֖Ffu6W&6RfR&VDFWBFW&֖FfuF76W'BFW4D6F'&fFR7FF2&5FVFǕ6t6W7F""FW&֖Ffu6W&6R76W'BFW4D6F'&fFR7FF2&6F56F"FW&֖Ffu6W&6R76W'BFW4D6F'&fFR7FF2&V65&VfW&V6R"FW&֖Ffu6W&6Rf"Vfu6W&6RfR&VDFWBVfuF76W'B6F2'&fFR7FF2&5FVFǕ6t6W7F""Vfu6W&6R76W'B6F2'&fFR7FF2&6F56F"Vfu6W&6R76W'B6F2'&fFR7FF2&V65&VfW&V6R"Vfu6W&6RРf7EТV&Ɩ2fB676vVD66U&E66ƗfW4FVF6FVE'F‚f"WFV64F"F6&R&W&B'7&2"$Ɩ6G&&B"$WFV62"f"66UFF6&RWFV64F"$676vVD66R72"f"&E66FF6&RWFV64F"$676vVD66U&E6672"76W'BG'VRfRW7G2&E66F$676vVD66R&B66r6VBƗfRf7W6VB'FfR"f"66U6W&6RfR&VDFWB66UF76W'B6F2&FW&7FF2'F672676vVD66R"66U6W&6R76W'BFW4D6F'&fFR6VVB672&E66"66U6W&6R76W'BFW4D6F'V&Ɩ27FF2&E66'VB"66U6W&6Rf"&E666W&6RfR&VDFWB&E66F76W'B6F2'&fFR6VVB672&E66"&E666W&6R76W'B6F2'V&Ɩ27FF2&E66'VB"&E666W&6R76W'B6F2'&fFR7FF2fBFB"&E666W&6RРf7EТV&Ɩ2fB3%&fFW%6fU7G&tWFG5ƗfTFVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$3%&VGW&TFW&ƗF"f"&fFW%6fUFF6&RǗW$F"%&VGW&TFW&ƗF&fFW%6fTW&W76272"f"7G&tWFG5FF6&RǗW$F"%&VGW&TFW&ƗF&fFW%6fU7G&tWFG272"76W'BG'VRfRW7G27G&tWFG5F$3"&fFW"6fR7G&rWFB'VW26VBƗfRf7W6VB'FfR"f"&fFW%6fU6W&6RfR&VDFWB&fFW%6fUF76W'BFW4D6F'&fFR7FF2&4vVE&fFW%6fU7G&tWFB"&fFW%6fU6W&6R76W'BFW4D6F'&fFR7FF2&57G&t6&6&WFW""&fFW%6fU6W&6Rf"7G&tWFG56W&6RfR&VDFWB7G&tWFG5F76W'B6F2'&fFR7FF2&4vVE&fFW%6fU7G&tWFB"7G&tWFG56W&6R76W'B6F2'&fFR7FF2&57G&t6&6&WFW""7G&tWFG56W&6R76W'B6F2%7G&t6&6"7G&tWFG56W&6RРf7EТV&Ɩ2fB3efW%7FF4&F6fWGƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%VW'6TEG&6F"$3efDFFUFTr"f"fW%FF6&RfW$F"$fDFFUFTtfW"72"f"7FF4&FFF6&RfW$F"$fDFFUFTtfW%7FF4&F72"76W'BG'VRfRW7G27FF4&FF$3b7FF2&FfW"6fWG6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'BFW4D6F'&fFR7FF2&46FU7FF4&F"fW%6W&6R76W'BFW4D6F'&fFR7FF2&57FF4FfW""fW%6W&6Rf"7FF4&F6W&6RfR&VDFWB7FF4&FF76W'B6F2'&fFR7FF2&46FU7FF4&F"7FF4&F6W&6R76W'B6F2'&fFR7FF2&57FF4FfW""7FF4&F6W&6R76W'B6F2%7FB7FF4Wv&B"7FF4&F6W&6RРf7EТV&Ɩ2fB3CE&V6&ƗG&6fvFƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$3CE4G&6uFVFg"f"&V6&ƗGFF6&RǗW$F"$4G&6uFVFg&V6&ƗG72"f"&6fvFFF6&RǗW$F"$4G&6uFVFg&V6&ƗG&6272"76W'BG'VRfRW7G2&6fvFF$3CB&V6&ƗG&6fvF6VBƗfRf7W6VB'FfR"f"&V6&ƗG6W&6RfR&VDFWB&V6&ƗGF76W'BFW4D6F'&fFR7FF2&4&66W7F""&V6&ƗG6W&6R76W'BFW4D6F'&fFR7FF2W&FfDF&V7D6DW&F6Fu7"&V6&ƗG6W&6R76W'BFW4D6F'&fFR7FF2&6W&FfDV66t&6"&V6&ƗG6W&6Rf"&6fvF6W&6RfR&VDFWB&6fvFF76W'B6F2'&fFR7FF2&4&66W7F""&6fvF6W&6R76W'B6F2'&fFR7FF2W&FfDF&V7D6DW&F6Fu7"&6fvF6W&6R76W'B6F2'&fFR7FF2&6W&FfDV66t&6"&6fvF6W&6RРf7EТV&Ɩ2fB33tƗfVEGU&V6vFƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$33F$6FWD6vWF"f"WfFV6UFF6&RǗW$F"$F$6FWD6vWFtƗfVDWfFV6R72"f"GU&V6vFFF6&RǗW$F"$F$6FWD6vWFtƗfVEGU&V6vF72"76W'BG'VRfRW7G2GU&V6vFF$33rƗfVB66VBGR&V6vFVW'26VBƗfRf7W6VB'FfR"f"WfFV6U6W&6RfR&VDFWBWfFV6UF76W'BFW4D6F'&fFR7FF2&4v66VEGR"WfFV6U6W&6R76W'BFW4D6F'&fFR7FF2&46fVF֖FFWv&U6vGW&R"WfFV6U6W&6R76W'BFW4D6F'&fFR7FF2&VVG4FW&f6R"WfFV6U6W&6R76W'BFW4D6F'&fFR7FF2&W&G4g&"WfFV6U6W&6Rf"GU&V6vF6W&6RfR&VDFWBGU&V6vFF76W'B6F2'&fFR7FF2&4v66VEGR"GU&V6vF6W&6R76W'B6F2'&fFR7FF2&46fVF֖FFWv&U6vGW&R"GU&V6vF6W&6R76W'B6F2'&fFR7FF2&VVG4FW&f6R"GU&V6vF6W&6R76W'B6F2'&fFR7FF2&W&G4g&"GU&V6vF6W&6RРf7EТV&Ɩ2fB3%fW%6fT6vW4'&6W6W6ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$'VƴW&F4E6WD&6VEw&FW2"$3%F֗U&VfU&vR"f"6fT6vW56fWGFF6&RǗW$F"$F֗U&VfU&vTfW%6fT6vW56fWG72"f"'&6W6W6FF6&RǗW$F"$F֗U&VfU&vTfW%6fT6vW4'&6W6W672"76W'BG'VRfRW7G2'&6W6W6F$3"fW"6fR6vW2'&6W6W66VBƗfRf7W6VB'FfR"f"6fT6vW56fWG6W&6RfR&VDFWB6fT6vW56fWGF76W'BFW4D6F'&fFR7FF2&&TWGVǔW6W6fT'&6W2"6fT6vW56fWG6W&6R76W'BFW4D6F'&fFR7FF27FFSvWD6Ftd'&6"6fT6vW56fWG6W&6R76W'BFW4D6F'&fFR7FF27vF66V7F7FvWD6Fu7vF66V7F"6fT6vW56fWG6W&6Rf"'&6W6W66W&6RfR&VDFWB'&6W6W6F76W'B6F2'&fFR7FF2&&TWGVǔW6W6fT'&6W2"'&6W6W66W&6R76W'B6F2'&fFR7FF27FFSvWD6Ftd'&6"'&6W6W66W&6R76W'B6F2'&fFR7FF27vF66V7F7FvWD6Fu7vF66V7F"'&6W6W66W&6RРf7EТV&Ɩ2fB33u7G&t'VFW$FVFG&VfW&V6UG&fW'6ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%&u7E6V7W&G"$33u&u77G&t67G'V7F"f"FVFGFF6&RǗW$F"%&u77G&t67G'V7F7G&t'VFW$FVFG72"f"&VfW&V6UG&fW'6FF6&RǗW$F"%&u77G&t67G'V7F7G&t'VFW$FVFG&VfW&V6W272"76W'BG'VRfRW7G2&VfW&V6UG&fW'6F$33r7G&t'VFW"FVFG&VfW&V6RG&fW'66VBƗfRf7W6VB'FfR"f"FVFG6W&6RfR&VDFWBFVFGF76W'BFW4D6F'&fFR7FF2&&VfW&V6TFVFG"FVFG6W&6Rf"&VfW&V6UG&fW'66W&6RfR&VDFWB&VfW&V6UG&fW'6F76W'B6F2'&fFR7FF2&&VfW&V6TFVFG"&VfW&V6UG&fW'66W&6R76W'B6F2$FW'FVE7G&tW&F"&VfW&V6UG&fW'66W&6R76W'B6F2$&V7D7&VFW&F"&VfW&V6UG&fW'66W&6R76W'B6F2$f6FW&F"&VfW&V6UG&fW'66W&6RРf7EТV&Ɩ2fB3576vVEfVT6V7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$35F76VD6FWEVW'"f"&v&W6WFFF6&RǗW$F"$F76VD6FWEVW'76vVD&v&W6WF72"f"76vVEfVW5FF6&RǗW$F"$F76VD6FWEVW'76vVEfVW272"76W'BG'VRfRW7G276vVEfVW5F$3276vVBfVR6V7F6VBƗfRf7W6VB'FfR"f"&v&W6WF6W&6RfR&VDFWB&v&W6WFF76W'BFW4D6F'&fFR7FF2&G'vWE6vT76vVEfVR"&v&W6WF6W&6R76W'BFW4D6F'&fFR7FF2Ɨ7CĔW&FvWD76vVEfVW2"&v&W6WF6W&6R76W'BFW4D6F'&fFR7FF2&46F&vWB"&v&W6WF6W&6R76W'BFW4D6F'&fFR7FF2VVW&&SĔW&FVVW&FTW&F2"&v&W6WF6W&6Rf"76vVEfVW56W&6RfR&VDFWB76vVEfVW5F76W'B6F2'&fFR7FF2&G'vWE6vT76vVEfVR"76vVEfVW56W&6R76W'B6F2'&fFR7FF2Ɨ7CĔW&FvWD76vVEfVW2"76vVEfVW56W&6R76W'B6F2'&fFR7FF2&46F&vWB"76vVEfVW56W&6R76W'B6F2'&fFR7FF2VVW&&SĔW&FVVW&FTW&F2"76vVEfVW56W&6RРf7EТV&Ɩ2fB3CfW$f6F&Ww&FUƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$3C6vTVFG66%&V7F"f"fW%FF6&RfW$F"%6vTVFG66%&V7FfW"72"f"&Ww&FUFF6&RfW$F"%6vTVFG66%&V7FfW%&Ww&FR72"76W'BG'VRfRW7G2&Ww&FUF$3CfW"f6F&Ww&FR6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'BFW4D6F'&fFR7FF2W&W767F&Ww&FTf6F"fW%6W&6R76W'BFW4D6F'&fFR7FF2BfE&VF6FTFW"fW%6W&6Rf"&Ww&FU6W&6RfR&VDFWB&Ww&FUF76W'B6F2'&fFR7FF2W&W767F&Ww&FTf6F"&Ww&FU6W&6R76W'B6F2'&fFR7FF2BfE&VF6FTFW"&Ww&FU6W&6R76W'B6F2"6VV7B"&Ww&FU6W&6RРf7EТV&Ɩ2fB3%FW&ƗW$676f6FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FW&ƗFE&V7F"$3%&VGW&TFW&ƗF"f"WFE'VW5FF6&RǗW$F"%&VGW&TFW&ƗFWFE'VW272"f"FW&ƗW'5FF6&RǗW$F"%&VGW&TFW&ƗFFW&ƗW%'VW272"76W'BG'VRfRW7G2FW&ƗW'5F$3"FW&ƗW"676f6F'VW26VBƗfRf7W6VB'FfR"f"WFE'VW56W&6RfR&VDFWBWFE'VW5F76W'BFW4D6F'&fFR7FF2&4FW&ƗtWFB"WFE'VW56W&6R76W'BFW4D6F'&fFR7FF2&4F&V7D6V7FFW&ƗW""WFE'VW56W&6R76W'BFW4D6F'&fFR7FF2&4FW&Ɨt67G'V7F""WFE'VW56W&6Rf"FW&ƗW'56W&6RfR&VDFWBFW&ƗW'5F76W'B6F2'&fFR7FF2&4FW&ƗtWFB"FW&ƗW'56W&6R76W'B6F2'&fFR7FF2&4F&V7D6V7FFW&ƗW""FW&ƗW'56W&6R76W'B6F2'&fFR7FF2&4FVGWƖ6Fu6WDFW&ƗW""FW&ƗW'56W&6R76W'B6F2'&fFR7FF2&4WVD$w&WVDFW&ƗW""FW&ƗW'56W&6R76W'B6F2'&fFR7FF2&4FW&Ɨt67G'V7F""FW&ƗW'56W&6RРf7EТV&Ɩ2fB3CEG&6u7FFTFWF6fƖFFƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$3CE4G&6uFVFg"f"G&6u7FFUFF6&RǗW$F"$4G&6uFVFgG&6u7FFR72"f"FWF6fƖFFFF6&RǗW$F"$4G&6uFVFgG&6tFWF6fƖFF72"76W'BG'VRfRW7G2FWF6fƖFFF$3CBFWF6G&6W"6V"fƖFF6VBƗfRf7W6VB'FfR"f"G&6u7FFU6W&6RfR&VDFWBG&6u7FFUF76W'BFW4D6F'&fFR7FF2&4FW'fVtFWF6"G&6u7FFU6W&6Rf"FWF6fƖFF6W&6RfR&VDFWBFWF6fƖFFF76W'B6F2'&fFR7FF2&4FW'fVtFWF6"FWF6fƖFF6W&6R76W'B6F2'66FWF6W4'6"FWF6fƖFF6W&6R76W'B6F2'66G&6W$6V'2"FWF6fƖFF6W&6RРf7EТV&Ɩ2fB3#U6W&6U&W6WF6FF&v5ƗfTFVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$3#U4G&6uvFWFFR"f"6W&6U&W6WFFF6&RǗW$F"$4G&6uvFWFFU6W&6U&W6WF72"f"6FF&v5FF6&RǗW$F"$4G&6uvFWFFU6W&6T6FF&v272"76W'BG'VRfRW7G26FF&v5F$3#R6FF&v7FFR6VBƗfRf7W6VB'FfR"f"6W&6U&W6WF6W&6RfR&VDFWB6W&6U&W6WFF76W'BFW4D6F'&fFR&VFǒ7G'V7B6&v"6W&6U&W6WF6W&6R76W'BFW4D6F'&fFR7FF2&46FF&VFfUF"6W&6U&W6WF6W&6Rf"6FF&v56W&6RfR&VDFWB6FF&v5F76W'B6F2'&fFR&VFǒ7G'V7B6&v"6FF&v56W&6R76W'B6F2'&fFR7FF2&46FF&VFfUF"6FF&v56W&6R76W'B6F2%7vF6W&W767F"6FF&v56W&6R76W'B6F2$6f$V67FFVVE7F"6FF&v56W&6RРf7EТV&Ɩ2fB33E76fWGFWFV7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%&u7E6V7W&G"$33EfDWV7WFU7&uvFFW'F"f"ǗW%FF6&RǗW$F"$fDWV7WFU7&uvFFW'FǗW"72"f"76fWGFF6&RǗW$F"$fDWV7WFU7&uvFFW'F76fWG72"76W'BG'VRfRW7G276fWGF$33B56fWG676f6F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F672fDWV7WFU7&uvFFW'FǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5FVFǕV6fU7"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5V6fT66FVF"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&467FDFW'F"ǗW%6W&6Rf"76fWG6W&6RfR&VDFWB76fWGF76W'B6F2'&fFR7FF2&5FVFǕV6fU7"76fWG6W&6R76W'B6F2'&fFR7FF2&5V6fT66FVF"76fWG6W&6R76W'B6F2'&fFR7FF2&467FDFW'F"76fWG6W&6R76W'B6F2$FW'FVE7G&tW&F"76fWG6W&6RРf7EТV&Ɩ2fB3UfW%&Ww&FUfƖFFƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%VW'6TEG&6F"$3UVFT&FW$'"f"fW%FF6&RfW$F"$VFT&FW$'fW"72"f"fƖFFFF6&RfW$F"$VFT&FW$'fW%&Ww&FUfƖFF72"76W'BG'VRfRW7G2fƖFFF$3RfW"&Ww&FRfƖFF6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672VFT&FW$'fW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&6&Ww&FUFFV'"fW%6W&6R76W'BFW4D6F'&fFR7FF2W&W767FvWDv6&V6VfW$W&W76"fW%6W&6R76W'BFW4D6F'&fFR7FF2&4&FW&VE6WVV6R"fW%6W&6Rf"fƖFF6W&6RfR&VDFWBfƖFFF76W'B6F2'&fFR7FF2&6&Ww&FUFFV'"fƖFF6W&6R76W'B6F2'&fFR7FF2W&W767FvWDv6&V6VfW$W&W76"fƖFF6W&6R76W'B6F2'&fFR7FF2&4&FW&VE6WVV6R"fƖFF6W&6R76W'B6F2$&FW&VEVW'&R"fƖFF6W&6RРf7EТV&Ɩ2fB3fW$vD6FWEfƖFFƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$WV7WFD72"$374&6W""f"fW%FF6&RfW$F"%74&6W$fW"72"f"vD6FWEFF6&RfW$F"%74&6W$fW$vD6FWB72"76W'BG'VRfRW7G2vD6FWEF$3fW"vB6FWBfƖFF6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F67274&6W$fW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&4fƖDvD6FWB"fW%6W&6R76W'BFW4D6F$g&6W6U7F"fW%6W&6R76W'BFW4D6F$W4gV7FW&W767F"fW%6W&6Rf"vD6FWE6W&6RfR&VDFWBvD6FWEF76W'B6F2'&fFR7FF2&4fƖDvD6FWB"vD6FWE6W&6R76W'B6F2$g&6W6U7F"vD6FWE6W&6R76W'B6F2$6W6U7F"vD6FWE6W&6R76W'B6F2$W4gV7FW&W767F"vD6FWE6W&6R76W'B6F2$6gV7F7FFVVE7F"vD6FWE6W&6RРf7EТV&Ɩ2fB3ǗW%76fWGFWFV7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%&u7E6V7W&G"$3fDg&7&uvFFW'F"f"ǗW%FF6&RǗW$F"$fDg&7&uvFFW'FǗW"72"f"76fWGFF6&RǗW$F"$fDg&7&uvFFW'FǗW%76fWG72"76W'BG'VRfRW7G276fWGF$3ǗW"56fWG676f6F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F672fDg&7&uvFFW'FǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5FVFǕV6fR"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&5V6fT66FVF"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&467FDFW'F"ǗW%6W&6Rf"76fWG6W&6RfR&VDFWB76fWGF76W'B6F2'&fFR7FF2&5FVFǕV6fR"76fWG6W&6R76W'B6F2'&fFR7FF2&5V6fT66FVF"76fWG6W&6R76W'B6F2'&fFR7FF2&467FDFW'F"76fWG6W&6R76W'B6F2$FW'FVE7G&tW&F"76fWG6W&6RРf7EТV&Ɩ2fB3C%VW'WFDWF&W5ƗfTFVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FtD6VFW2"$3C%֗76uVW'Fw2"f"ǗW%FF6&RǗW$F"$֗76uVW'Fw4ǗW"72"f"WFE6WG5FF6&RǗW$F"$֗76uVW'Fw4WFE6WG272"76W'BG'VRfRW7G2WFE6WG5F$3C"VW'WFBWF&W26VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'BFW4D6F'&fFR7FF2&VFǒWF&T66WC7G&sF&vWDWFG2"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&VFǒWF&T66WC7G&sVW'7FW2"ǗW%6W&6Rf"WFE6WG56W&6RfR&VDFWBWFE6WG5F76W'B6F2'&fFR7FF2&VFǒWF&T66WC7G&sF&vWDWFG2"WFE6WG56W&6R76W'B6F2'&fFR7FF2&VFǒWF&T66WC7G&sVW'7FW2"WFE6WG56W&6R76W'B6F2%%FuvF66FU""WFE6WG56W&6R76W'B6F2%%F66WD75""WFE6WG56W&6RРf7EТV&Ɩ2fB3#FV6VFT6Ǘ65ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FtD6VFW2"$3#FVWFV6VFR"f"ǗW%FF6&RǗW$F"$FVWFV6VFTǗW"72"f"6Ǘ65FF6&RǗW$F"$FVWFV6VFT6Ǘ6272"76W'BG'VRfRW7G26Ǘ65F$3#FV6VFR6Ǘ626VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F672FVWFV6VFTǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2B6VEFV6VFTFWF"ǗW%6W&6R76W'BFW4D6F'&fFR7FF26FvWDFv7F46F"ǗW%6W&6R76W'BFW4D6F%FWE7g&&VG2"ǗW%6W&6Rf"6Ǘ656W&6RfR&VDFWB6Ǘ65F76W'B6F2'&fFR7FF2B6VEFV6VFTFWF"6Ǘ656W&6R76W'B6F2'&fFR7FF26FvWDFv7F46F"6Ǘ656W&6R76W'B6F2%FWE7g&&VG2"6Ǘ656W&6R76W'B6F2$V&W$66W74W&W767F"6Ǘ656W&6RРf7EТV&Ɩ2fB7&Ǘ65&'WF66fW'ƗfW4FVF6FVE'F‚f"WFV64F"F6&R&W&B'7&2"$Ɩ6G&&B"$WFV62"f"7&WFV65FF6&RWFV64F"%7&Ǘ64WFV6272"f"&'WFF6&RWFV64F"%7&Ǘ65&'WWFV6272"76W'BG'VRfRW7G2&'WF%&'ֶW7&F66fW'6VBƗfRf7W6VBWFV6'FfR"f"7&WFV656W&6RfR&VDFWB7&WFV65F76W'BFW4D6F'V&Ɩ27FF27G&sG'fE&'W"7&WFV656W&6R76W'BFW4D6F'&fFR7FF2&4FFFF4WGG&'WFR"7&WFV656W&6Rf"&'W6W&6RfR&VDFWB&'WF76W'B6F2'V&Ɩ27FF27G&sG'fE&'W"&'W6W&6R76W'B6F2'&fFR7FF2&4FFFF4WGG&'WFR"&'W6W&6R76W'B6F2%77FV6VDFVFFFF2"&'W6W&6RРf7EТV&Ɩ2fB36FFĖ6VFUFǗ65ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FtD6VFW2"$36FFĖ6VFR"f"ǗW%FF6&RǗW$F"$6FFĖ6VFTǗW"72"f"FǗ65FF6&RǗW$F"$6FFĖ6VFUFǗ6272"76W'BG'VRfRW7G2FǗ65F$36FF6VFRFG&fW'66VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'B6F2'V&Ɩ26VVB'F6726FFĖ6VFTǗW""ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&46FFĖ6VFUF"ǗW%6W&6R76W'BFW4D6F'&fFR7FF2&46FFĖ6VFTf6F6W&6R"ǗW%6W&6Rf"FǗ656W&6RfR&VDFWBFǗ65F76W'B6F2'&fFR7FF2&46FFĖ6VFUF"FǗ656W&6R76W'B6F2'&fFR7FF2&46FFĖ6VFTf6F6W&6R"FǗ656W&6R76W'B6F2$6FFW&F"6W66TW&F"FǗ656W&6RРf7EТV&Ɩ2fB3#efW$WƖ6EFV&wVVEƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$WV7WFD72"$3#e֗76t66VFFV"f"fW%FF6&RfW$F"$֗76t66VFFVfW"72"f"WƖ6D&wVVEFF6&RfW$F"$֗76t66VFFVfW$WƖ6D&wVVB72"76W'BG'VRfRW7G2WƖ6D&wVVEF$3#bfW"WƖ6BFV&wVVBW6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672֗76t66VFFVfW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&wVVE7FfDWƖ6D66VFFV&wVVB"fW%6W&6R76W'BFW4D6F'&fFR7FF2&466VFFV&WFW""fW%6W&6Rf"WƖ6D&wVVE6W&6RfR&VDFWBWƖ6D&wVVEF76W'B6F2'&fFR7FF2&wVVE7FfDWƖ6D66VFFV&wVVB"WƖ6D&wVVE6W&6R76W'B6F2'&fFR7FF2&466VFFV&WFW""WƖ6D&wVVE6W&6R76W'B6F2$f6FW&F"WƖ6D&wVVE6W&6RРf7EТV&Ɩ2fB33%66%GT676f6FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$'VƴW&F4E6WD&6VEw&FW2"$33%WV7WFUWFFTf$'VƵWFFW2"f"76vVDǗ65FF6&RǗW$F"$WV7WFUWFFTf$'VƵWFFW476vVDǗ6272"f"66%GW5FF6&RǗW$F"$WV7WFUWFFTf$'VƵWFFW566%GW272"76W'BG'VRfRW7G266%GW5F$33"66"GR676f6F6VBƗfRf7W6VB'FfR"f"76vVDǗ656W&6RfR&VDFWB76vVDǗ65F76W'BFW4D6F'&fFR7FF2&566$ƖUGR"76vVDǗ656W&6R76W'BFW4D6F%7&F7f&BgVǕVƖfVDf&B"76vVDǗ656W&6Rf"66%GW56W&6RfR&VDFWB66%GW5F76W'B6F2'&fFR7FF2&566$ƖUGR"66%GW56W&6R76W'B6F2%7&F7f&BgVǕVƖfVDf&B"66%GW56W&6R76W'B6F2&v&ã77FVFFUFTfg6WB"66%GW56W&6RРf7EТV&Ɩ2fB33%VW'7FWWF&W5ƗfTFVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$'VƴW&F4E6WD&6VEw&FW2"$33%WV7WFUWFFTf$'VƵWFFW2"f"VW'Ǘ65FF6&RǗW$F"$WV7WFUWFFTf$'VƵWFFW5VW'Ǘ6272"f"VW'7FW5FF6&RǗW$F"$WV7WFUWFFTf$'VƵWFFW5VW'7FW272"76W'BG'VRfRW7G2VW'7FW5F$33"vVBVW'FW&ƗW"7FWWF&W26VBƗfRf7W6VB'FfR"f"VW'Ǘ656W&6RfR&VDFWBVW'Ǘ65F76W'BFW4D6F'&fFR7FF2&VFǒWF&T66WC7G&svVEVW'7FW2"VW'Ǘ656W&6R76W'BFW4D6F'&fFR7FF2&VFǒWF&T66WC7G&sFW&ƗW%7FW2"VW'Ǘ656W&6Rf"VW'7FW56W&6RfR&VDFWBVW'7FW5F76W'B6F2'&fFR7FF2&VFǒWF&T66WC7G&svVEVW'7FW2"VW'7FW56W&6R76W'B6F2'&fFR7FF2&VFǒWF&T66WC7G&sFW&ƗW%7FW2"VW'7FW56W&6R76W'B6F2%%FuvF66FU""VW'7FW56W&6R76W'B6F2%%F'&75""VW'7FW56W&6RРf7EТV&Ɩ2fB3CUfW%VW'6W&6U&W6WFƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FtD6VFW2"$3CU֗76t6VFR"f"fW%FF6&RfW$F"$֗76t6VFTfW"72"f"VW'6W&6UFF6&RfW$F"$֗76t6VFTfW%VW'6W&6R72"76W'BG'VRfRW7G2VW'6W&6UF$3CRfW"VW'6W&6R&W6WF6VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672֗76t6VFTfW""fW%6W&6R76W'BFW4D6F'&fFR7FF272F6W&W767FvWEVW'&U6W&6T72"fW%6W&6R76W'BFW4D6F'6VF4FVvWEGTfVW'6W&6R66VFFV"fW%6W&6Rf"VW'6W&6RfR&VDFWBVW'6W&6UF76W'B6F2'&fFR7FF272F6W&W767FvWEVW'&U6W&6T72"VW'6W&6R76W'B6F2'6VF4FVvWEGTfVW'6W&6R66VFFV"VW'6W&6R76W'B6F2'VW'6W&6R"VW'6W&6RРf7EТV&Ɩ2fB3e&V6VfW$6G&fW'6ƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FtD6VFW2"$3e6'FW6W6"f"6Ǘ65FF6&RǗW$F"$6'FW6W66Ǘ6272"f"&V6VfW$6FF6&RǗW$F"$6'FW6W6&V6VfW$672"76W'BG'VRfRW7G2&V6VfW$6F$3b&V6VfW"6G&fW'66VBƗfRf7W6VB'FfR"f"6Ǘ656W&6RfR&VDFWB6Ǘ65F76W'BFW4D6F'&fFR7FF2WF&T'&Ĕf6FW&F6V7E&V6VfW$6f6F2"6Ǘ656W&6R76W'BFW4D6F'&fFR7FF2&5&VWfEVW'W&F$6W7F""6Ǘ656W&6R76W'BFW4D6F'&fFR7FF2&f6FW6W5&V6VfW$6"6Ǘ656W&6Rf"&V6VfW$66W&6RfR&VDFWB&V6VfW$6F76W'B6F2'&fFR7FF2WF&T'&Ĕf6FW&F6V7E&V6VfW$6f6F2"&V6VfW$66W&6R76W'B6F2'&fFR7FF2&5&VWfEVW'W&F$6W7F""&V6VfW$66W&6R76W'B6F2'&fFR7FF2&f6FW6W5&V6VfW$6"&V6VfW$66W&6R76W'B6F2$676vVD66RG'vWE6vT76vVEfVT&Vf&R"&V6VfW$66W&6RРf7EТV&Ɩ2fB3F%6WDV&W$WG&7FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3VFG֗76u&'W"f"ǗW%FF6&RǗW$F"$VFG֗76u&'WǗW"72"f"F%6WDV&W'5FF6&RǗW$F"$VFG֗76u&'WF%6WDV&W'272"76W'BG'VRfRW7G2F%6WDV&W'5F$3F%6WBV&W"WG&7F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'BFW4D6F'&fFR7FF2&G'vWDF%6WDV&W""ǗW%6W&6R76W'BFW4D6F&fVBR7F'G5vF#"7G&t6&6&F’"ǗW%6W&6Rf"F%6WDV&W'56W&6RfR&VDFWBF%6WDV&W'5F76W'B6F2'&fFR7FF2&G'vWDF%6WDV&W""F%6WDV&W'56W&6R76W'B6F2$&W'G7&&W'G"F%6WDV&W'56W&6R76W'B6F2$fVE7&fVB"F%6WDV&W'56W&6R76W'B6F2&fVBR7F'G5vF#"7G&t6&6&F’"F%6WDV&W'56W&6RРf7EТV&Ɩ2fB3fW$VFGGU&W6WFƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3VFG֗76u&'W"f"fW%FF6&RfW$F"$VFG֗76u&'WfW"72"f"VFGGUFF6&RfW$F"$VFG֗76u&'WfW$VFGGR72"76W'BG'VRfRW7G2VFGGUF$3fW"VFGGRBBV&W"6V626VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672VFG֗76u&'WfW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&G'vWDVFGGR"fW%6W&6R76W'BFW4D6F'&fFR7FF2&4DV&W""fW%6W&6Rf"VFGGU6W&6RfR&VDFWBVFGGUF76W'B6F2'&fFR7FF2&G'vWDVFGGR"VFGGU6W&6R76W'B6F2'&fFR7FF2&4DV&W""VFGGU6W&6R76W'B6F2%7G&t6&6&FĖv&T66R"VFGGU6W&6RРf7EТV&Ɩ2fB33EfW%7&wVVE6fWGƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%&u7E6V7W&G"$33EfDWV7WFU7&uvFFW'F"f"fW%FF6&RfW$F"$fDWV7WFU7&uvFFW'FfW"72"f"7&wVVEFF6&RfW$F"$fDWV7WFU7&uvFFW'FfW%7&wVVB72"76W'BG'VRfRW7G27&wVVEF$33BfW"5&wVVBBƗFW&6fWGVW'26VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F672fDWV7WFU7&uvFFW'FfW""fW%6W&6R76W'BFW4D6F'&fFR7FF2&wVVE7FvWE7&wVVB"fW%6W&6R76W'BFW4D6F'&fFR7FF2&4FW'F6FU77G&tƗFW&"fW%6W&6R76W'BFW4D6F'&fFR7FF2fBWFFU7FVƖ֗FW%7FFR"fW%6W&6Rf"7&wVVE6W&6RfR&VDFWB7&wVVEF76W'B6F2'&fFR7FF2&wVVE7FvWE7&wVVB"7&wVVE6W&6R76W'B6F2'&fFR7FF2&4FW'F6FU77G&tƗFW&"7&wVVE6W&6R76W'B6F2'&fFR7FF2fBWFFU7FVƖ֗FW%7FFR"7&wVVE6W&6R76W'B6F2$FW'F7FvV6FU77G&tƗFW&"7&wVVE6W&6RРf7EТV&Ɩ2fB3efW%7F66V&6ƗfW4FVF6FVE'F‚f"fW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$FtD6VFW2"$3e6'FW6W6"f"fW%FF6&RfW$F"$6'FW6W6fW"72"f"7F6FF6&RfW$F"$6'FW6W6fW%7F672"76W'BG'VRfRW7G27F6F$3bfW"7F66V&6VW'26VBƗfRf7W6VB'FfR"f"fW%6W&6RfR&VDFWBfW%F76W'B6F2'V&Ɩ26VVB'F6726'FW6W6fW""fW%6W&6R76W'BFW4D6F'&fFR7FF2f6FW&W767FfDVffV7FfT56vUVW'f6F"fW%6W&6R76W'BFW4D6F'&fFR7FF2f6FW&W767FfDf'7D6VFTf6F"fW%6W&6R76W'BFW4D6F'&fFR7FF2&4f6Fb"fW%6W&6Rf"7F66W&6RfR&VDFWB7F6F76W'B6F2'&fFR7FF2f6FW&W767FfDVffV7FfT56vUVW'f6F"7F66W&6R76W'B6F2'&fFR7FF2f6FW&W767FfDf'7D6VFTf6F"7F66W&6R76W'B6F2'&fFR7FF2&4f6Fb"7F66W&6R76W'B6F2%$56vUVW'""7F66W&6RРf7EТV&Ɩ2fB3#u6V7FGT676f6FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"%66VDFVƖr"$3#u֗76tWƖ6Df&VvW"f"VFGǗ65FF6&RǗW$F"$֗76tWƖ6Df&VvWVFGǗ6272"f"6V7FGW5FF6&RǗW$F"$֗76tWƖ6Df&VvW6V7FGW272"76W'BG'VRfRW7G26V7FGW5F$3#r6V7FGR676f6F6VBƗfRf7W6VB'FfR"f"VFGǗ656W&6RfR&VDFWBVFGǗ65F76W'BFW4D6F'&fFR7FF2&46V7FGR"VFGǗ656W&6R76W'BFW4D6F$&VDǔ6V7F"VFGǗ656W&6R76W'BFW4D6F&f6RR$VVW&&U""VFGǗ656W&6Rf"6V7FGW56W&6RfR&VDFWB6V7FGW5F76W'B6F2'&fFR7FF2&46V7FGR"6V7FGW56W&6R76W'B6F2%77FV6V7F2vVW&2"6V7FGW56W&6R76W'B6F2$&VDǔ6V7F"6V7FGW56W&6RРf7EТV&Ɩ2fB3FW&ƗW$WFD676f6FƗfW4FVF6FVE'F‚f"ǗW$F"F6&R&W&B'7&2"$Ɩ6G&&B"$ǗW'2"$6vUG&6tD6FWDƖfWFR"$3֗76t4G&6r"f"ǗW%FF6&RǗW$F"$֗76t4G&6tǗW"72"f"FW&ƗW'5FF6&RǗW$F"$֗76t4G&6tFW&ƗW'272"76W'BG'VRfRW7G2FW&ƗW'5F$3FW&ƗW"WFB676f6F6VBƗfRf7W6VB'FfR"f"ǗW%6W&6RfR&VDFWBǗW%F76W'BFW4D6F'&fFR7FF2&4VFGFW&ƗW""ǗW%6W&6R76W'BFW4D6F%%FF7F'75""ǗW%6W&6Rf"FW&ƗW'56W&6RfR&VDFWBFW&ƗW'5F76W'B6F2'&fFR7FF2&4VFGFW&ƗW""FW&ƗW'56W&6R76W'B6F2%%FF7F'75""FW&ƗW'56W&6R76W'B6F2%%6vT$FVfVD75""FW&ƗW'56W&6RРf7EТV&Ɩ2fB6TFv7F75fW&fW%6&e'6uƗfW4FVF6FVE'F‚f"fW&fW$F"F6&R&W&B'F2"%6TFv7F75fW&fW""f"&w&FF6&RfW&fW$F"%&w&72"f"6&eFF6&RfW&fW$F"%&w&6&dFv7F7272"76W'BG'VRfRW7G26&eF%6RFv7F724$b'6r6VBƗfRf7W6VB&w&'FfR"f"&w&6W&6RfR&VDFWB&w&F76W'BFW4D6F'7FF2&VDǔƗ7C6TFv7F3'6TFv7F72"&w&6W&6R76W'BFW4D6F'7FF2&G'vWDFv7F5F"&w&6W&6R76W'BFW4D6F'7FF27G&r&ƗU&VFfUF"&w&6W&6Rf"6&e6W&6RfR&VDFWB6&eF76W'B6F2'&fFR7FF2&VDǔƗ7C6TFv7F3'6TFv7F72"6&e6W&6R76W'B6F2'&fFR7FF2&G'vWDFv7F5F"6&e6W&6R76W'B6F2'&fFR7FF27G&r&ƗU&VFfUF"6&e6W&6R76W'B6F2&FW&6VVB&V6&B6TFv7F2"6&e6W&6RРf7EТV&Ɩ2fB6TFv7F75fW&fW%fW7E'6uƗfW4FVF6FVE'F‚f"fW&fW$F"F6&R&W&B'F2"%6TFv7F75fW&fW""f"&w&FF6&RfW&fW$F"%&w&72"f"fW7EFF6&RfW&fW$F"%&w&fW7B72"76W'BG'VRfRW7G2fW7EF%6RFv7F72fW7B'6r6VBƗfRf7W6VB&w&'FfR"f"&w&6W&6RfR&VDFWB&w&F76W'BFW4D6F'7FF26TWV7FFw&WDWV7FFw&W2"&w&6W&6R76W'BFW4D6F'7FF27G&uE6fU6UF2"&w&6W&6R76W'BFW4D6F&FW&6VVB&V6&B6TWV7FF"&w&6W&6Rf"fW7E6W&6RfR&VDFWBfW7EF76W'B6F2'&fFR7FF26TWV7FFw&WDWV7FFw&W2"fW7E6W&6R76W'B6F2'&fFR7FF27G&uE6fU6UF2"fW7E6W&6R76W'B6F2&FW&6VVB&V6&B6TWV7FF"fW7E6W&6R76W'B6F2&FW&6VVB&V6&B6TWV7FFw&W"fW7E6W&6RРf7EТV&Ɩ2fB6TFv7F75fW&fW%FFWE&6W74WV7WFƗfW4FVF6FVE'F‚f"fW&fW$F"F6&R&W&B'F2"%6TFv7F75fW&fW""f"&w&FF6&RfW&fW$F"%&w&72"f"FFWEFF6&RfW&fW$F"%&w&FFWB72"76W'BG'VRfRW7G2FFWEF%6RFv7F72FFWB&6W72WV7WF6VBƗfRf7W6VB&w&'FfR"f"&w&6W&6RfR&VDFWB&w&F76W'BFW4D6F'7FF27G&r'VFFWD'VB"&w&6W&6R76W'BFW4D6F'7FF2fB'VFFWE&W7F&R"&w&6W&6R76W'BFW4D6F'7FF27G&rVFR"&w&6W&6Rf"FFWE6W&6RfR&VDFWBFFWEF76W'B6F2'&fFR7FF27G&r'VFFWD'VB"FFWE6W&6R76W'B6F2'&fFR7FF2fB'VFFWE&W7F&R"FFWE6W&6R76W'B6F2'&fFR7FF27G&rVFR"FFWE6W&6R76W'B6F2%&6W757F'Df"FFWE6W&6RЧР \ No newline at end of file +using System.IO; +using Xunit; + +namespace LinqContraband.Tests.Architecture; + +public sealed class AnalyzerModularizationTests +{ + private readonly string _repoRoot = RepositoryLayout.GetRepositoryRoot(); + + [Fact] + public void LC044_RootScanEntries_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC044_AsNoTrackingThenModify"); + var rootScanPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScan.cs"); + var entriesPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScanEntries.cs"); + + Assert.True(File.Exists(entriesPath), "LC044 root-scan entry DTOs should live in a focused partial file."); + + var rootScanSource = File.ReadAllText(rootScanPath); + Assert.DoesNotContain("internal readonly struct MutationEntry", rootScanSource); + Assert.DoesNotContain("internal readonly struct ReattachEntry", rootScanSource); + Assert.DoesNotContain("internal readonly struct DetachEntry", rootScanSource); + Assert.DoesNotContain("internal readonly struct TrackerClearEntry", rootScanSource); + Assert.DoesNotContain("internal readonly struct SaveChangesEntry", rootScanSource); + + var entriesSource = File.ReadAllText(entriesPath); + Assert.Contains("internal readonly struct MutationEntry", entriesSource); + Assert.Contains("internal readonly struct ReattachEntry", entriesSource); + Assert.Contains("internal readonly struct DetachEntry", entriesSource); + Assert.Contains("internal readonly struct TrackerClearEntry", entriesSource); + Assert.Contains("internal readonly struct SaveChangesEntry", entriesSource); + } + + [Fact] + public void LC044_RootScanOperationRecording_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC044_AsNoTrackingThenModify"); + var rootScanPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScan.cs"); + var operationRecordingPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScanOperationRecording.cs"); + + Assert.True(File.Exists(operationRecordingPath), "LC044 root-scan operation recording should live in a focused partial file."); + + var rootScanSource = File.ReadAllText(rootScanPath); + Assert.DoesNotContain("private static void HandleAssignment", rootScanSource); + Assert.DoesNotContain("private static void HandlePropertyMutation", rootScanSource); + Assert.DoesNotContain("private static void HandleInvocation", rootScanSource); + + var operationRecordingSource = File.ReadAllText(operationRecordingPath); + Assert.Contains("private static void HandleAssignment", operationRecordingSource); + Assert.Contains("private static void HandlePropertyMutation", operationRecordingSource); + Assert.Contains("private static void HandleInvocation", operationRecordingSource); + Assert.Contains("TryParseEntryStateAssignment", operationRecordingSource); + Assert.Contains("TryParseReattachInvocation", operationRecordingSource); + Assert.Contains("TryParseTrackerClear", operationRecordingSource); + } + + [Fact] + public void LC044_OptionalControlFlow_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC044_AsNoTrackingThenModify"); + var reachabilityPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyReachability.cs"); + var optionalFlowPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyOptionalFlow.cs"); + + Assert.True(File.Exists(optionalFlowPath), "LC044 optional branch/loop reachability rules should live in a focused partial file."); + + var reachabilitySource = File.ReadAllText(reachabilityPath); + Assert.DoesNotContain("private static bool IsNestedUnderOptionalControlFlow", reachabilitySource); + Assert.DoesNotContain("private static bool IfStatementMakesBranchMandatory", reachabilitySource); + Assert.DoesNotContain("private static bool StatementSkipsLater", reachabilitySource); + Assert.DoesNotContain("private static bool BranchSkipsLater", reachabilitySource); + + var optionalFlowSource = File.ReadAllText(optionalFlowPath); + Assert.Contains("private static bool IsNestedUnderOptionalControlFlow", optionalFlowSource); + Assert.Contains("private static bool IfStatementMakesBranchMandatory", optionalFlowSource); + Assert.Contains("private static bool StatementSkipsLater", optionalFlowSource); + Assert.Contains("private static bool BranchSkipsLater", optionalFlowSource); + } + + [Fact] + public void LC044_QuerySourceResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC044_AsNoTrackingThenModify"); + var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyAnalyzer.cs"); + var querySourcePath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyQuerySource.cs"); + + Assert.True(File.Exists(querySourcePath), "LC044 AsNoTracking query/materialization source resolution should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AsNoTrackingThenModifyAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool IsAsNoTrackingMaterialization", analyzerSource); + Assert.DoesNotContain("private static bool ChainContainsAsNoTracking", analyzerSource); + Assert.DoesNotContain("private static bool TryGetQueryContextSymbol", analyzerSource); + + var querySource = File.ReadAllText(querySourcePath); + Assert.Contains("private static bool IsAsNoTrackingMaterialization", querySource); + Assert.Contains("private static bool ChainContainsAsNoTracking", querySource); + Assert.Contains("private static bool TryGetQueryContextSymbol", querySource); + } + + [Fact] + public void LC008_AsyncContextResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC008_SyncBlocker"); + var analyzerPath = Path.Combine(analyzerDir, "SyncBlockerAnalyzer.cs"); + var asyncContextPath = Path.Combine(analyzerDir, "SyncBlockerAsyncContext.cs"); + + Assert.True(File.Exists(asyncContextPath), "LC008 async-context boundary checks should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class SyncBlockerAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool IsInsideAsyncMethod", analyzerSource); + + var asyncContextSource = File.ReadAllText(asyncContextPath); + Assert.Contains("private static bool IsInsideAsyncMethod", asyncContextSource); + Assert.Contains("ILocalFunctionOperation", asyncContextSource); + Assert.Contains("IAnonymousFunctionOperation", asyncContextSource); + } + + [Fact] + public void LC004_MethodSummaryConsumption_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC004_IQueryableLeak"); + var summaryPath = Path.Combine(analyzerDir, "IQueryableLeakMethodSummaryAnalysis.cs"); + var consumptionPath = Path.Combine(analyzerDir, "IQueryableLeakMethodSummaryConsumption.cs"); + + Assert.True(File.Exists(consumptionPath), "LC004 method-summary local consumption checks should live in a focused partial file."); + + var summarySource = File.ReadAllText(summaryPath); + Assert.DoesNotContain("private void MarkMaterializingConstructorHazards", summarySource); + Assert.DoesNotContain("private bool IsMaterializingCollectionConstructor", summarySource); + Assert.DoesNotContain("private bool IsDirectConsumption", summarySource); + Assert.DoesNotContain("private bool IsGetEnumeratorInvocation", summarySource); + Assert.DoesNotContain("private void MarkHazardIfParameterSource", summarySource); + + var consumptionSource = File.ReadAllText(consumptionPath); + Assert.Contains("private void MarkMaterializingConstructorHazards", consumptionSource); + Assert.Contains("private bool IsMaterializingCollectionConstructor", consumptionSource); + Assert.Contains("private bool IsDirectConsumption", consumptionSource); + Assert.Contains("private bool IsGetEnumeratorInvocation", consumptionSource); + Assert.Contains("private void MarkHazardIfParameterSource", consumptionSource); + } + + [Fact] + public void LC004_InvocationInputMapping_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC004_IQueryableLeak"); + var sourceResolutionPath = Path.Combine(analyzerDir, "IQueryableLeakSourceResolution.cs"); + var invocationInputsPath = Path.Combine(analyzerDir, "IQueryableLeakInvocationInputs.cs"); + + Assert.True(File.Exists(invocationInputsPath), "LC004 invocation input mapping should live in a focused partial file."); + + var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); + Assert.DoesNotContain("private IEnumerable EnumerateInvocationInputs", sourceResolutionSource); + + var invocationInputsSource = File.ReadAllText(invocationInputsPath); + Assert.Contains("private IEnumerable EnumerateInvocationInputs", invocationInputsSource); + } + + [Fact] + public void LC004_ExecutableRootTraversal_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC004_IQueryableLeak"); + var symbolHelpersPath = Path.Combine(analyzerDir, "IQueryableLeakSymbolHelpers.cs"); + var executableRootsPath = Path.Combine(analyzerDir, "IQueryableLeakExecutableRoots.cs"); + + Assert.True(File.Exists(executableRootsPath), "LC004 executable-root traversal should live in a focused partial file."); + + var symbolHelpersSource = File.ReadAllText(symbolHelpersPath); + Assert.DoesNotContain("private bool TryGetExecutableRoot", symbolHelpersSource); + Assert.DoesNotContain("private IEnumerable EnumerateOperations", symbolHelpersSource); + Assert.DoesNotContain("private static bool IsInsideNestedExecutable", symbolHelpersSource); + + var executableRootsSource = File.ReadAllText(executableRootsPath); + Assert.Contains("private bool TryGetExecutableRoot", executableRootsSource); + Assert.Contains("private IEnumerable EnumerateOperations", executableRootsSource); + Assert.Contains("private static bool IsInsideNestedExecutable", executableRootsSource); + } + + [Fact] + public void LC004_HazardousLookupTables_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC004_IQueryableLeak"); + var compilationStatePath = Path.Combine(analyzerDir, "IQueryableLeakCompilationState.cs"); + var lookupTablesPath = Path.Combine(analyzerDir, "IQueryableLeakHazardousLookupTables.cs"); + + Assert.True(File.Exists(lookupTablesPath), "LC004 hazardous method/type lookup tables should live in a focused partial file."); + + var compilationStateSource = File.ReadAllText(compilationStatePath); + Assert.DoesNotContain("private static readonly ImmutableHashSet HazardousEnumerableMethods", compilationStateSource); + Assert.DoesNotContain("private static readonly ImmutableHashSet MaterializingCollectionTypes", compilationStateSource); + + var lookupTablesSource = File.ReadAllText(lookupTablesPath); + Assert.Contains("private static readonly ImmutableHashSet HazardousEnumerableMethods", lookupTablesSource); + Assert.Contains("private static readonly ImmutableHashSet MaterializingCollectionTypes", lookupTablesSource); + } + + [Fact] + public void LC001_StaticQueryableOrderingRewrite_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC001_LocalMethod"); + var staticRewritePath = Path.Combine(analyzerDir, "LocalMethodFixerStaticQueryableRewrite.cs"); + var orderingRewritePath = Path.Combine(analyzerDir, "LocalMethodFixerStaticQueryableOrdering.cs"); + + Assert.True(File.Exists(orderingRewritePath), "LC001 static Queryable ordering-source rewrite should live in a focused partial file."); + + var staticRewriteSource = File.ReadAllText(staticRewritePath); + Assert.DoesNotContain("private static bool RewriteQueryableExtensionOrderingSourceChain", staticRewriteSource); + Assert.DoesNotContain("private static bool IsRewritableOrderedSource", staticRewriteSource); + Assert.DoesNotContain("private static bool IsQueryableOrderingInvocation", staticRewriteSource); + + var orderingRewriteSource = File.ReadAllText(orderingRewritePath); + Assert.Contains("private static bool RewriteQueryableExtensionOrderingSourceChain", orderingRewriteSource); + Assert.Contains("private static bool IsRewritableOrderedSource", orderingRewriteSource); + Assert.Contains("private static bool IsQueryableOrderingInvocation", orderingRewriteSource); + } + + [Fact] + public void LC044_DiagnosticReporting_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC044_AsNoTrackingThenModify"); + var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyAnalyzer.cs"); + var reportingPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyReporting.cs"); + + Assert.True(File.Exists(reportingPath), "LC044 diagnostic reporting should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static void TryReportForLocal", analyzerSource); + Assert.DoesNotContain("private static void TryReportForForeach", analyzerSource); + Assert.DoesNotContain("private readonly struct MutationHit", analyzerSource); + Assert.DoesNotContain("private static MutationHit? FindFirstPropertyMutation", analyzerSource); + + var reportingSource = File.ReadAllText(reportingPath); + Assert.Contains("private static void TryReportForLocal", reportingSource); + Assert.Contains("private static void TryReportForForeach", reportingSource); + Assert.Contains("private readonly struct MutationHit", reportingSource); + Assert.Contains("private static MutationHit? FindFirstPropertyMutation", reportingSource); + } + + [Fact] + public void LC017_FixerPropertyAccessSafety_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC017_WholeEntityProjection"); + var contextPath = Path.Combine(analyzerDir, "WholeEntityProjectionFixerContextAnalysis.cs"); + var propertyAccessPath = Path.Combine(analyzerDir, "WholeEntityProjectionFixerPropertyAccess.cs"); + var trackedExpressionsPath = Path.Combine(analyzerDir, "WholeEntityProjectionFixerTrackedEntityExpressions.cs"); + + Assert.True(File.Exists(propertyAccessPath), "LC017 fixer property-access safety checks should live in a focused partial file."); + Assert.True(File.Exists(trackedExpressionsPath), "LC017 fixer tracked-entity expression recognition should live in a focused partial file."); + + var contextSource = File.ReadAllText(contextPath); + Assert.DoesNotContain("private static bool HasUnsupportedEntityPropertyAccess", contextSource); + Assert.DoesNotContain("private static bool IsTrackedEntityConversionExpression", contextSource); + Assert.DoesNotContain("private static bool IsTrackedEntityExpression", contextSource); + Assert.DoesNotContain("private static bool HasUnsafeIndexedEntityAccess", contextSource); + Assert.DoesNotContain("private static bool IsPropertyOfType", contextSource); + + var propertyAccessSource = File.ReadAllText(propertyAccessPath); + Assert.Contains("private static bool HasUnsupportedEntityPropertyAccess", propertyAccessSource); + Assert.DoesNotContain("private static bool IsTrackedEntityConversionExpression", propertyAccessSource); + Assert.DoesNotContain("private static bool IsTrackedEntityExpression", propertyAccessSource); + Assert.Contains("private static bool HasUnsafeIndexedEntityAccess", propertyAccessSource); + Assert.Contains("private static bool IsPropertyOfType", propertyAccessSource); + + var trackedExpressionsSource = File.ReadAllText(trackedExpressionsPath); + Assert.Contains("private static bool IsTrackedEntityConversionExpression", trackedExpressionsSource); + Assert.Contains("private static bool IsTrackedEntityExpression", trackedExpressionsSource); + } + + [Fact] + public void LC017_UsageReferenceClassification_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC017_WholeEntityProjection"); + var usageAnalysisPath = Path.Combine(analyzerDir, "WholeEntityProjectionUsageAnalysis.cs"); + var usageReferencesPath = Path.Combine(analyzerDir, "WholeEntityProjectionUsageReferences.cs"); + + Assert.True(File.Exists(usageReferencesPath), "LC017 usage reference classification should live in a focused partial file."); + + var usageAnalysisSource = File.ReadAllText(usageAnalysisPath); + Assert.DoesNotContain("private static bool IsTrackedEntityReference", usageAnalysisSource); + Assert.DoesNotContain("private static bool IsDirectVariableEscape", usageAnalysisSource); + Assert.DoesNotContain("private static bool LambdaDirectlyReferences", usageAnalysisSource); + + var usageReferencesSource = File.ReadAllText(usageReferencesPath); + Assert.Contains("private static bool IsTrackedEntityReference", usageReferencesSource); + Assert.Contains("private static bool IsDirectVariableEscape", usageReferencesSource); + Assert.Contains("private static bool LambdaDirectlyReferences", usageReferencesSource); + } + + [Fact] + public void LC017_SyntaxPropertyCollection_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC017_WholeEntityProjection"); + var syntaxAnalysisPath = Path.Combine(analyzerDir, "WholeEntityProjectionSyntaxAnalysis.cs"); + var propertyCollectionPath = Path.Combine(analyzerDir, "WholeEntityProjectionSyntaxPropertyCollection.cs"); + + Assert.True(File.Exists(propertyCollectionPath), "LC017 syntax-based property collection should live in a focused partial file."); + + var syntaxAnalysisSource = File.ReadAllText(syntaxAnalysisPath); + Assert.DoesNotContain("private static void CollectSyntaxBasedPropertyAccesses", syntaxAnalysisSource); + + var propertyCollectionSource = File.ReadAllText(propertyCollectionPath); + Assert.Contains("private static void CollectSyntaxBasedPropertyAccesses", propertyCollectionSource); + Assert.Contains("ConditionalAccessExpressionSyntax", propertyCollectionSource); + Assert.Contains("MemberAccessExpressionSyntax", propertyCollectionSource); + } + + [Fact] + public void LC025_FixerSourceResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC025_AsNoTrackingWithUpdate"); + var fixerPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateFixer.cs"); + var sourceResolutionPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateFixerSourceResolution.cs"); + var noTrackingSourcePath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateFixerNoTrackingSource.cs"); + + Assert.True(File.Exists(sourceResolutionPath), "LC025 fixer AsNoTracking source resolution should live in a focused partial file."); + Assert.True(File.Exists(noTrackingSourcePath), "LC025 fixer recursive no-tracking source tracing should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class AsNoTrackingWithUpdateFixer", fixerSource); + Assert.DoesNotContain("private static InvocationExpressionSyntax? FindAsNoTrackingOrigin", fixerSource); + Assert.DoesNotContain("private readonly struct AsNoTrackingOrigin", fixerSource); + Assert.DoesNotContain("private static bool IsNoTrackingSource", fixerSource); + Assert.DoesNotContain("private static bool IsLocalFromNoTracking", fixerSource); + Assert.DoesNotContain("private static bool HasAsNoTrackingInChain", fixerSource); + + var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); + Assert.Contains("private static InvocationExpressionSyntax? FindAsNoTrackingOrigin", sourceResolutionSource); + Assert.Contains("private readonly struct AsNoTrackingOrigin", sourceResolutionSource); + Assert.DoesNotContain("private static bool IsNoTrackingSource", sourceResolutionSource); + Assert.DoesNotContain("private static bool IsLocalFromNoTracking", sourceResolutionSource); + + var noTrackingSource = File.ReadAllText(noTrackingSourcePath); + Assert.Contains("private static bool IsNoTrackingSource", noTrackingSource); + Assert.Contains("private static bool IsLocalFromNoTracking", noTrackingSource); + } + + [Fact] + public void LC025_FixerTrackingDirectiveAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC025_AsNoTrackingWithUpdate"); + var sourceResolutionPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateFixerSourceResolution.cs"); + var trackingDirectivePath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateFixerTrackingDirectiveAnalysis.cs"); + + Assert.True(File.Exists(trackingDirectivePath), "LC025 fixer tracking-directive chain analysis should live in a focused partial file."); + + var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); + Assert.DoesNotContain("private static bool HasAsNoTrackingInChain", sourceResolutionSource); + Assert.DoesNotContain("private static bool IsEfCoreNoTrackingDirective", sourceResolutionSource); + Assert.DoesNotContain("private static bool IsEfCoreAsTracking", sourceResolutionSource); + Assert.DoesNotContain("private static InvocationExpressionSyntax? FindAsNoTrackingInvocation", sourceResolutionSource); + + var trackingDirectiveSource = File.ReadAllText(trackingDirectivePath); + Assert.Contains("private static bool HasAsNoTrackingInChain", trackingDirectiveSource); + Assert.Contains("private static bool IsEfCoreNoTrackingDirective", trackingDirectiveSource); + Assert.Contains("private static bool IsEfCoreAsTracking", trackingDirectiveSource); + Assert.Contains("private static InvocationExpressionSyntax? FindAsNoTrackingInvocation", trackingDirectiveSource); + } + + [Fact] + public void IncludePathParser_HelperResponsibilities_LiveInDedicatedPartials() + { + var extensionsDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Extensions"); + var parserPath = Path.Combine(extensionsDir, "IncludePathParser.cs"); + var lambdaPath = Path.Combine(extensionsDir, "IncludePathParserLambdaPaths.cs"); + var typeAnalysisPath = Path.Combine(extensionsDir, "IncludePathParserTypeAnalysis.cs"); + + Assert.True(File.Exists(lambdaPath), "IncludePathParser lambda navigation parsing should live in a focused partial file."); + Assert.True(File.Exists(typeAnalysisPath), "IncludePathParser type/collection analysis should live in a focused partial file."); + + var parserSource = File.ReadAllText(parserPath); + Assert.Contains("internal static partial class IncludePathParser", parserSource); + Assert.DoesNotContain("private static bool TryAddNavigationSegments", parserSource); + Assert.DoesNotContain("public static bool TryGetCollectionElementType", parserSource); + + var lambdaSource = File.ReadAllText(lambdaPath); + Assert.Contains("private static bool TryAddNavigationSegments", lambdaSource); + Assert.Contains("private static CSharpSyntaxNode UnwrapExpression", lambdaSource); + + var typeAnalysisSource = File.ReadAllText(typeAnalysisPath); + Assert.Contains("public static bool TryGetCollectionElementType", typeAnalysisSource); + Assert.Contains("private static bool TryGetNamedGenericElementType", typeAnalysisSource); + } + + [Fact] + public void LC045_UsageScan_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC045_MissingInclude"); + var usageAnalysisPath = Path.Combine(analyzerDir, "MissingIncludeUsageAnalysis.cs"); + var usageScanPath = Path.Combine(analyzerDir, "MissingIncludeUsageScan.cs"); + + Assert.True(File.Exists(usageScanPath), "LC045 executable-root usage scanning should live in a focused partial file."); + + var usageAnalysisSource = File.ReadAllText(usageAnalysisPath); + Assert.DoesNotContain("private static List? CollectNavigationAccessesFromExecutableRoot", usageAnalysisSource); + + var usageScanSource = File.ReadAllText(usageScanPath); + Assert.Contains("private static List? CollectNavigationAccessesFromExecutableRoot", usageScanSource); + Assert.Contains("LambdaReferencesTrackedLocal", usageScanSource); + Assert.Contains("satisfiedPaths", usageScanSource); + } + + [Fact] + public void LC045_OriginAwareFlowResponsibilities_LiveInDedicatedPartials() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC045_MissingInclude"); + var analyzerPath = Path.Combine(analyzerDir, "MissingIncludeAnalyzer.cs"); + var usageScanPath = Path.Combine(analyzerDir, "MissingIncludeUsageScan.cs"); + var flowAnalysisPath = Path.Combine(analyzerDir, "MissingIncludeOriginFlowAnalysis.cs"); + var flowBindingsPath = Path.Combine(analyzerDir, "MissingIncludeOriginFlowBindings.cs"); + var flowContextPath = Path.Combine(analyzerDir, "MissingIncludeOriginFlowContext.cs"); + var flowEventsPath = Path.Combine(analyzerDir, "MissingIncludeOriginFlowEvents.cs"); + var flowStatePath = Path.Combine(analyzerDir, "MissingIncludeOriginFlowState.cs"); + var autoIncludePath = Path.Combine(analyzerDir, "MissingIncludeAutoIncludeConfiguration.cs"); + var appliedConfigurationPath = Path.Combine(analyzerDir, "MissingIncludeAppliedConfiguration.cs"); + + Assert.True(File.Exists(flowAnalysisPath), "LC045 origin-aware control-flow analysis should live in a focused partial file."); + Assert.True(File.Exists(flowBindingsPath), "LC045 origin and alias binding discovery should live in a focused partial file."); + Assert.True(File.Exists(flowContextPath), "LC045 origin-flow context construction should live in a focused partial file."); + Assert.True(File.Exists(flowEventsPath), "LC045 origin-flow event collection should live in a focused partial file."); + Assert.True(File.Exists(flowStatePath), "LC045 origin-flow state and event models should live in a focused partial file."); + Assert.True(File.Exists(autoIncludePath), "LC045 model-level AutoInclude proof should live in a focused partial file."); + Assert.True(File.Exists(appliedConfigurationPath), "LC045 applied AutoInclude configuration proof should live in a focused partial file."); + + var usageScanSource = File.ReadAllText(usageScanPath); + Assert.DoesNotContain("private static bool TryGetFlowGraph", usageScanSource); + Assert.DoesNotContain("private sealed partial class OriginFlowContext", usageScanSource); + Assert.DoesNotContain("private readonly struct FlowProbeState", usageScanSource); + Assert.DoesNotContain("private enum FlowEventKind", usageScanSource); + Assert.DoesNotContain("private void DiscoverStableAliases", usageScanSource); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("RegisterCompilationStartAction", analyzerSource); + Assert.Contains("var flowGraphCache = new System.Runtime.CompilerServices.ConditionalWeakTable<", analyzerSource); + Assert.Contains("var autoIncludeCache = new System.Collections.Concurrent.ConcurrentDictionary<", analyzerSource); + Assert.Contains("AnalyzeInvocation(", analyzerSource); + Assert.Contains("autoIncludeCache,", analyzerSource); + + var autoIncludeSource = File.ReadAllText(autoIncludePath); + Assert.Contains("private static void AddModelAutoIncludePrefixes", autoIncludeSource); + Assert.Contains("private static bool TryGetDirectAutoInclude", autoIncludeSource); + + var appliedConfigurationSource = File.ReadAllText(appliedConfigurationPath); + Assert.Contains("private static bool TryApplyConfigurationAutoIncludes", appliedConfigurationSource); + Assert.Contains("private static bool TryGetAppliedConfigurationAutoInclude", appliedConfigurationSource); + + var appliedConfigurationTestsPath = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC045_MissingInclude", + "MissingIncludeAppliedConfigurationTests.cs"); + Assert.True(File.Exists(appliedConfigurationTestsPath), "LC045 applied AutoInclude configuration tests should live in a focused partial file."); + var appliedConfigurationTestsSource = File.ReadAllText(appliedConfigurationTestsPath); + Assert.Contains("TestInnocent_AppliedConfigurationAutoInclude_NoDiagnostic", appliedConfigurationTestsSource); + Assert.Contains("TestCrime_AppliedConfigurationHelperBoundary_DoesNotSuppress", appliedConfigurationTestsSource); + + var flowAnalysisSource = File.ReadAllText(flowAnalysisPath); + Assert.Contains("private static bool TryCollectOriginAwareNavigationAccesses", flowAnalysisSource); + Assert.Contains("private static bool TryGetFlowGraph", flowAnalysisSource); + Assert.Contains("ConditionalWeakTable flowGraphCache", flowAnalysisSource); + Assert.DoesNotContain("static readonly ConditionalWeakTable CreateProperties", analyzerSource); + + var diagnosticsSource = File.ReadAllText(diagnosticsPath); + Assert.Contains("private static readonly LocalizableString Title", diagnosticsSource); + Assert.Contains("public static readonly DiagnosticDescriptor Rule", diagnosticsSource); + Assert.Contains("public static readonly DiagnosticDescriptor RedundantRule", diagnosticsSource); + Assert.Contains("private static ImmutableDictionary CreateProperties", diagnosticsSource); + Assert.DoesNotContain("private static void AnalyzeInvocation", diagnosticsSource); + } + + [Fact] + public void LC005_LocalInitializerFlow_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC005_MultipleOrderBy"); + var analyzerPath = Path.Combine(analyzerDir, "MultipleOrderByAnalyzer.cs"); + var localFlowPath = Path.Combine(analyzerDir, "MultipleOrderByLocalInitializerFlow.cs"); + + Assert.True(File.Exists(localFlowPath), "LC005 local initializer and write tracking should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class MultipleOrderByAnalyzer", analyzerSource); + Assert.DoesNotContain("private bool TryGetSingleAssignmentLocalInitializer", analyzerSource); + Assert.DoesNotContain("private static bool HasWriteBeforeUse", analyzerSource); + Assert.DoesNotContain("private static bool IsWriteToLocal", analyzerSource); + + var localFlowSource = File.ReadAllText(localFlowPath); + Assert.Contains("private bool TryGetSingleAssignmentLocalInitializer", localFlowSource); + Assert.Contains("private static bool HasWriteBeforeUse", localFlowSource); + Assert.Contains("private static bool IsWriteToLocal", localFlowSource); + } + + [Fact] + public void LC006_IncludeChainResultModel_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC006_CartesianExplosion"); + var analyzerPath = Path.Combine(analyzerDir, "CartesianExplosionAnalyzer.cs"); + var chainResultPath = Path.Combine(analyzerDir, "CartesianExplosionIncludeChainResult.cs"); + + Assert.True(File.Exists(chainResultPath), "LC006 include-chain result model should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private enum QuerySplittingMode", analyzerSource); + Assert.DoesNotContain("private sealed class IncludeChainAnalysis", analyzerSource); + + var chainResultSource = File.ReadAllText(chainResultPath); + Assert.Contains("private enum QuerySplittingMode", chainResultSource); + Assert.Contains("private sealed class IncludeChainAnalysis", chainResultSource); + Assert.Contains("public bool TryGetRiskySiblingCollections", chainResultSource); + } + + [Fact] + public void LC024_GroupAccessTranslation_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC024_GroupByNonTranslatable"); + var analyzerPath = Path.Combine(analyzerDir, "GroupByNonTranslatableAnalyzer.cs"); + var groupAccessPath = Path.Combine(analyzerDir, "GroupByNonTranslatableGroupAccess.cs"); + + Assert.True(File.Exists(groupAccessPath), "LC024 group-chain translatability rules should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class GroupByNonTranslatableAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool IsTranslatableGroupAccess", analyzerSource); + Assert.DoesNotContain("private static IInvocationOperation FindOutermostGroupChainInvocation", analyzerSource); + + var groupAccessSource = File.ReadAllText(groupAccessPath); + Assert.Contains("private static bool IsTranslatableGroupAccess", groupAccessSource); + Assert.Contains("private static IInvocationOperation FindOutermostGroupChainInvocation", groupAccessSource); + } + + [Fact] + public void LC024_GroupingTypeAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC024_GroupByNonTranslatable"); + var analyzerPath = Path.Combine(analyzerDir, "GroupByNonTranslatableAnalyzer.cs"); + var groupingTypesPath = Path.Combine(analyzerDir, "GroupByNonTranslatableGroupingTypes.cs"); + + Assert.True(File.Exists(groupingTypesPath), "LC024 grouping IQueryable/IGrouping type checks should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static bool IsGroupingQueryable", analyzerSource); + Assert.DoesNotContain("private static ITypeSymbol? GetQueryableElementType", analyzerSource); + Assert.DoesNotContain("private static bool IsGrouping(ITypeSymbol type)", analyzerSource); + + var groupingTypesSource = File.ReadAllText(groupingTypesPath); + Assert.Contains("private static bool IsGroupingQueryable", groupingTypesSource); + Assert.Contains("private static ITypeSymbol? GetQueryableElementType", groupingTypesSource); + Assert.Contains("private static bool IsGrouping(ITypeSymbol type)", groupingTypesSource); + } + + [Fact] + public void LC037_StringBuilderAliasIdentity_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var detectionPath = Path.Combine(analyzerDir, "RawSqlStringConstructionDetection.cs"); + var aliasPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderAliases.cs"); + var identityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderIdentity.cs"); + + Assert.True(File.Exists(aliasPath), "LC037 StringBuilder alias matching should live in a focused partial file."); + Assert.True(File.Exists(identityPath), "LC037 StringBuilder alias identity resolution should live in a focused partial file."); + + var detectionSource = File.ReadAllText(detectionPath); + Assert.DoesNotContain("private readonly struct LocalIdentity", detectionSource); + + var aliasSource = File.ReadAllText(aliasPath); + Assert.DoesNotContain("private readonly struct LocalIdentity", aliasSource); + Assert.DoesNotContain("private static LocalIdentity ResolveLocalIdentity", aliasSource); + + var identitySource = File.ReadAllText(identityPath); + Assert.Contains("private readonly struct LocalIdentity", identitySource); + Assert.Contains("private static LocalIdentity ResolveLocalIdentity", identitySource); + } + + [Fact] + public void LC037_StringBuilderAliasGuaranteedWrites_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var aliasPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderAliases.cs"); + var guaranteedWritesPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderAliasWrites.cs"); + + Assert.True(File.Exists(guaranteedWritesPath), "LC037 StringBuilder alias guaranteed-write resolution should live in a focused partial file."); + + var aliasSource = File.ReadAllText(aliasPath); + Assert.DoesNotContain("private static bool HasNonGuaranteedWriteAfterLatestGuaranteed", aliasSource); + Assert.DoesNotContain("private static bool TryResolveGuaranteedLocalValue", aliasSource); + + var guaranteedWritesSource = File.ReadAllText(guaranteedWritesPath); + Assert.Contains("private static bool HasNonGuaranteedWriteAfterLatestGuaranteed", guaranteedWritesSource); + Assert.Contains("private static bool TryResolveGuaranteedLocalValue", guaranteedWritesSource); + } + + [Fact] + public void LC037_StringBuilderAppendFlow_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var detectionPath = Path.Combine(analyzerDir, "RawSqlStringConstructionDetection.cs"); + var flowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderFlow.cs"); + + Assert.True(File.Exists(flowPath), "LC037 StringBuilder append/reset flow should live in a focused partial file."); + + var detectionSource = File.ReadAllText(detectionPath); + Assert.DoesNotContain("private static bool IsStringBuilderAppendArgumentNonConstant", detectionSource); + + var flowSource = File.ReadAllText(flowPath); + Assert.Contains("private static bool IsStringBuilderAppendArgumentNonConstant", flowSource); + } + + [Fact] + public void LC037_StringBuilderLocalWriteFlow_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var flowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderFlow.cs"); + var localWritesPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderLocalWrites.cs"); + var latestWritesPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderLatestLocalWrite.cs"); + + Assert.True(File.Exists(localWritesPath), "LC037 StringBuilder local-write flow should live in a focused partial file."); + Assert.True(File.Exists(latestWritesPath), "LC037 StringBuilder latest local-write flow should live in a focused partial file."); + + var flowSource = File.ReadAllText(flowPath); + Assert.DoesNotContain("private static bool HasOnlyConstantLocalWritesBeforeReference", flowSource); + Assert.DoesNotContain("private static bool HasLatestNonConstantLocalWriteBeforeReference", flowSource); + Assert.DoesNotContain("private static IOperation GetCompoundAssignmentRightValue", flowSource); + + var localWritesSource = File.ReadAllText(localWritesPath); + Assert.Contains("private static bool HasOnlyConstantLocalWritesBeforeReference", localWritesSource); + Assert.DoesNotContain("private static bool HasLatestNonConstantLocalWriteBeforeReference", localWritesSource); + + var latestWritesSource = File.ReadAllText(latestWritesPath); + Assert.Contains("private static bool HasLatestNonConstantLocalWriteBeforeReference", latestWritesSource); + Assert.Contains("private static IOperation GetCompoundAssignmentRightValue", latestWritesSource); + } + + [Fact] + public void LC037_StringBuilderLoopFlow_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var flowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderFlow.cs"); + var loopFlowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderLoopFlow.cs"); + + Assert.True(File.Exists(loopFlowPath), "LC037 StringBuilder loop-carried local write flow should live in a focused partial file."); + + var flowSource = File.ReadAllText(flowPath); + Assert.DoesNotContain("private static bool HasNonConstantLoopCarriedLocalWrite", flowSource); + Assert.DoesNotContain("private static bool HasGuaranteedConstantLocalWriteInSameIterationBeforeReference", flowSource); + + var loopFlowSource = File.ReadAllText(loopFlowPath); + Assert.Contains("private static bool HasNonConstantLoopCarriedLocalWrite", loopFlowSource); + Assert.Contains("private static bool HasGuaranteedConstantLocalWriteInSameIterationBeforeReference", loopFlowSource); + } + + [Fact] + public void LC037_LoopCarriedReachability_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var reachabilityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionReachability.cs"); + var loopReachabilityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionLoopReachability.cs"); + + Assert.True(File.Exists(loopReachabilityPath), "LC037 loop-carried write reachability should live in a focused partial file."); + + var reachabilitySource = File.ReadAllText(reachabilityPath); + Assert.DoesNotContain("private static bool IsLoopCarriedWriteForReference", reachabilitySource); + Assert.DoesNotContain("private static bool IsLoopSyntax", reachabilitySource); + Assert.DoesNotContain("private static bool CanWriteReachLaterLoopIteration", reachabilitySource); + + var loopReachabilitySource = File.ReadAllText(loopReachabilityPath); + Assert.Contains("private static bool IsLoopCarriedWriteForReference", loopReachabilitySource); + Assert.Contains("private static bool IsLoopSyntax", loopReachabilitySource); + Assert.Contains("private static bool CanWriteReachLaterLoopIteration", loopReachabilitySource); + } + + [Fact] + public void LC037_StringBuilderAppendScan_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var flowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderFlow.cs"); + var appendScanPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderAppendScan.cs"); + + Assert.True(File.Exists(appendScanPath), "LC037 StringBuilder append scan should live in a focused partial file."); + + var flowSource = File.ReadAllText(flowPath); + Assert.DoesNotContain("private static bool ContainsSuspiciousStringBuilderAppend", flowSource); + Assert.DoesNotContain("private static int GetLatestGuaranteedStringBuilderReset", flowSource); + + var appendScanSource = File.ReadAllText(appendScanPath); + Assert.Contains("private static bool ContainsSuspiciousStringBuilderAppend", appendScanSource); + Assert.DoesNotContain("private static int GetLatestGuaranteedStringBuilderReset", appendScanSource); + } + + [Fact] + public void LC037_StringBuilderResetDetection_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var appendScanPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderAppendScan.cs"); + var resetDetectionPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderResetDetection.cs"); + + Assert.True(File.Exists(resetDetectionPath), "LC037 StringBuilder reset detection should live in a focused partial file."); + + var appendScanSource = File.ReadAllText(appendScanPath); + Assert.DoesNotContain("private static int GetLatestGuaranteedStringBuilderReset", appendScanSource); + Assert.DoesNotContain("void TrackReset", appendScanSource); + + var resetDetectionSource = File.ReadAllText(resetDetectionPath); + Assert.Contains("private static int GetLatestGuaranteedStringBuilderReset", resetDetectionSource); + Assert.Contains("void TrackReset", resetDetectionSource); + } + + [Fact] + public void LC011_AssemblyConfigurationResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var configurationScanPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyConfigurationScan.cs"); + var assemblyResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyResolution.cs"); + var assemblyLocalsPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyLocalResolution.cs"); + var assemblyMembersPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyMemberResolution.cs"); + + Assert.True(File.Exists(assemblyResolutionPath), "LC011 ApplyConfigurationsFromAssembly resolution should live in a focused partial file."); + Assert.True(File.Exists(assemblyLocalsPath), "LC011 ApplyConfigurationsFromAssembly local resolution should live in a focused partial file."); + Assert.True(File.Exists(assemblyMembersPath), "LC011 ApplyConfigurationsFromAssembly member resolution should live in a focused partial file."); + + var configurationScanSource = File.ReadAllText(configurationScanPath); + Assert.DoesNotContain("private static bool IsCurrentAssemblyExpression", configurationScanSource); + Assert.DoesNotContain("private static bool TryResolveLocalCurrentAssembly", configurationScanSource); + + var assemblyResolutionSource = File.ReadAllText(assemblyResolutionPath); + Assert.Contains("private static bool IsCurrentAssemblyExpression", assemblyResolutionSource); + Assert.DoesNotContain("private static bool TryResolveLocalCurrentAssembly", assemblyResolutionSource); + + var assemblyLocalsSource = File.ReadAllText(assemblyLocalsPath); + Assert.Contains("private static bool TryResolveLocalCurrentAssembly", assemblyLocalsSource); + Assert.DoesNotContain("private static bool TryResolveMemberCurrentAssembly", assemblyLocalsSource); + + var assemblyMembersSource = File.ReadAllText(assemblyMembersPath); + Assert.Contains("private static bool TryResolveMemberCurrentAssembly", assemblyMembersSource); + } + + [Fact] + public void LC011_AssemblyLocalAssignmentScan_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var assemblyLocalsPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyLocalResolution.cs"); + var assignmentScanPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyLocalAssignments.cs"); + + Assert.True(File.Exists(assignmentScanPath), "LC011 ApplyConfigurationsFromAssembly local assignment scanning should live in a focused partial file."); + + var assemblyLocalsSource = File.ReadAllText(assemblyLocalsPath); + Assert.DoesNotContain("private static bool TryGetLocalAssignment", assemblyLocalsSource); + Assert.DoesNotContain("private static bool ContainsLocalAssignment", assemblyLocalsSource); + + var assignmentScanSource = File.ReadAllText(assignmentScanPath); + Assert.Contains("private static bool TryGetLocalAssignment", assignmentScanSource); + Assert.Contains("private static bool ContainsLocalAssignment", assignmentScanSource); + } + + [Fact] + public void LC011_AppliedConfigurationResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var configurationScanPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyConfigurationScan.cs"); + var appliedConfigurationPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAppliedConfigurationResolution.cs"); + + Assert.True(File.Exists(appliedConfigurationPath), "LC011 ApplyConfiguration argument/member/local resolution should live in a focused partial file."); + + var configurationScanSource = File.ReadAllText(configurationScanPath); + Assert.DoesNotContain("private static INamedTypeSymbol? ResolveConfigurationType", configurationScanSource); + Assert.DoesNotContain("private static bool TryResolveLocalConfiguration", configurationScanSource); + + var appliedConfigurationSource = File.ReadAllText(appliedConfigurationPath); + Assert.Contains("private static INamedTypeSymbol? ResolveConfigurationType", appliedConfigurationSource); + Assert.Contains("private static bool TryResolveLocalConfiguration", appliedConfigurationSource); + } + + [Fact] + public void LC011_EntityTypeConfigurationScan_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var configurationScanPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyConfigurationScan.cs"); + var entityTypeConfigurationPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyEntityTypeConfigurationScan.cs"); + + Assert.True(File.Exists(entityTypeConfigurationPath), "LC011 IEntityTypeConfiguration scanning should live in a focused partial file."); + + var configurationScanSource = File.ReadAllText(configurationScanPath); + Assert.DoesNotContain("private static EntityTypeConfigurationScan BuildEntityTypeConfigurationScan", configurationScanSource); + Assert.DoesNotContain("private static (bool hasKey, bool hasNoKey) CheckConfigureMethod", configurationScanSource); + Assert.DoesNotContain("private static bool TryGetConfiguredEntityType", configurationScanSource); + + var entityTypeConfigurationSource = File.ReadAllText(entityTypeConfigurationPath); + Assert.Contains("private static EntityTypeConfigurationScan BuildEntityTypeConfigurationScan", entityTypeConfigurationSource); + Assert.Contains("private static (bool hasKey, bool hasNoKey) CheckConfigureMethod", entityTypeConfigurationSource); + Assert.Contains("private static bool TryGetConfiguredEntityType", entityTypeConfigurationSource); + } + + [Fact] + public void LC011_CompilationModel_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var typeLookupPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyTypeLookup.cs"); + var compilationModelPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyCompilationModel.cs"); + + Assert.True(File.Exists(compilationModelPath), "LC011 compilation model and type index should live in a focused partial file."); + + var typeLookupSource = File.ReadAllText(typeLookupPath); + Assert.DoesNotContain("private sealed class CompilationModel", typeLookupSource); + Assert.DoesNotContain("private sealed class TypeIndex", typeLookupSource); + Assert.DoesNotContain("private sealed class EntityTypeConfigurationScan", typeLookupSource); + + var compilationModelSource = File.ReadAllText(compilationModelPath); + Assert.Contains("private sealed class CompilationModel", compilationModelSource); + Assert.Contains("private sealed class TypeIndex", compilationModelSource); + Assert.Contains("private sealed class EntityTypeConfigurationScan", compilationModelSource); + } + + [Fact] + public void LC011_PrimaryKeyAttributeRules_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var keyRulesPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyKeyRules.cs"); + var primaryKeyAttributePath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyPrimaryKeyAttributeRules.cs"); + + Assert.True(File.Exists(primaryKeyAttributePath), "LC011 [PrimaryKey] attribute parsing should live in a focused partial file."); + + var keyRulesSource = File.ReadAllText(keyRulesPath); + Assert.DoesNotContain("private bool HasValidPrimaryKeyAttribute", keyRulesSource); + Assert.DoesNotContain("private static List GetPrimaryKeyPropertyNames", keyRulesSource); + + var primaryKeyAttributeSource = File.ReadAllText(primaryKeyAttributePath); + Assert.Contains("private bool HasValidPrimaryKeyAttribute", primaryKeyAttributeSource); + Assert.Contains("private static List GetPrimaryKeyPropertyNames", primaryKeyAttributeSource); + } + + [Fact] + public void LC011_AssemblyUsingVisibility_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var assemblyResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyResolution.cs"); + var usingVisibilityPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyUsingVisibility.cs"); + + Assert.True(File.Exists(usingVisibilityPath), "LC011 System.Reflection using and Assembly alias visibility should live in a focused partial file."); + + var assemblyResolutionSource = File.ReadAllText(assemblyResolutionPath); + Assert.DoesNotContain("private static bool HasSystemReflectionAssemblyAliasInScope", assemblyResolutionSource); + Assert.DoesNotContain("private static bool HasSystemReflectionUsing", assemblyResolutionSource); + + var usingVisibilitySource = File.ReadAllText(usingVisibilityPath); + Assert.Contains("private static bool HasSystemReflectionAssemblyAliasInScope", usingVisibilitySource); + Assert.Contains("private static bool HasSystemReflectionUsing", usingVisibilitySource); + } + + [Fact] + public void LC017_FixerAccessedProperties_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC017_WholeEntityProjection"); + var contextAnalysisPath = Path.Combine(analyzerDir, "WholeEntityProjectionFixerContextAnalysis.cs"); + var accessedPropertiesPath = Path.Combine(analyzerDir, "WholeEntityProjectionFixerAccessedProperties.cs"); + + Assert.True(File.Exists(accessedPropertiesPath), "LC017 fixer accessed-property scanning should live in a focused partial file."); + + var contextAnalysisSource = File.ReadAllText(contextAnalysisPath); + Assert.Contains("private static bool TryCreateProjectionFixContext", contextAnalysisSource); + Assert.DoesNotContain("private static HashSet FindAccessedProperties", contextAnalysisSource); + + var accessedPropertiesSource = File.ReadAllText(accessedPropertiesPath); + Assert.Contains("private static HashSet FindAccessedProperties", accessedPropertiesSource); + Assert.Contains("ForEachStatementSyntax", accessedPropertiesSource); + } + + [Fact] + public void LC011_AssemblyConfigurationTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC011_EntityMissingPrimaryKey"); + var edgeCasesPath = Path.Combine(testDir, "EntityMissingPrimaryKeyEdgeCasesTests.cs"); + var assemblyTestsPath = Path.Combine(testDir, "EntityMissingPrimaryKeyAssemblyConfigurationTests.cs"); + + Assert.True(File.Exists(assemblyTestsPath), "LC011 ApplyConfigurationsFromAssembly edge cases should live in a focused partial test file."); + + var edgeCasesSource = File.ReadAllText(edgeCasesPath); + Assert.Contains("public partial class EntityMissingPrimaryKeyEdgeCasesTests", edgeCasesSource); + Assert.DoesNotContain("TestCrime_ExternalApplyConfigurationsFromAssembly_ShouldNotApplyLocalConfig", edgeCasesSource); + Assert.DoesNotContain("TestInnocent_MemberExecutingAssemblyApplyConfigurationsFromAssembly_ShouldNotTrigger", edgeCasesSource); + + var assemblyTestsSource = File.ReadAllText(assemblyTestsPath); + Assert.Contains("public partial class EntityMissingPrimaryKeyEdgeCasesTests", assemblyTestsSource); + Assert.Contains("TestCrime_ExternalApplyConfigurationsFromAssembly_ShouldNotApplyLocalConfig", assemblyTestsSource); + Assert.Contains("TestInnocent_MemberExecutingAssemblyApplyConfigurationsFromAssembly_ShouldNotTrigger", assemblyTestsSource); + } + + [Fact] + public void LC011_BuilderResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var configurationScanPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyConfigurationScan.cs"); + var builderResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyBuilderResolution.cs"); + var localBuilderResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyLocalBuilderResolution.cs"); + + Assert.True(File.Exists(builderResolutionPath), "LC011 builder-expression and owned-type resolution should live in a focused partial file."); + Assert.True(File.Exists(localBuilderResolutionPath), "LC011 local builder backtracking should live in a focused partial file."); + + var configurationScanSource = File.ReadAllText(configurationScanPath); + Assert.DoesNotContain("private static bool TryResolveLocalBuilder", configurationScanSource); + + var builderResolutionSource = File.ReadAllText(builderResolutionPath); + Assert.Contains("private static bool TryResolveEntityTypeFromBuilderExpression", builderResolutionSource); + Assert.DoesNotContain("private static bool TryResolveLocalBuilder", builderResolutionSource); + + var localBuilderResolutionSource = File.ReadAllText(localBuilderResolutionPath); + Assert.Contains("private static bool TryResolveLocalBuilder", localBuilderResolutionSource); + Assert.Contains("LocalDeclarationStatementSyntax", localBuilderResolutionSource); + } + + [Fact] + public void LC011_OwnedBuilderNavigationResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var builderResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyBuilderResolution.cs"); + var ownedNavigationPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyOwnedBuilderNavigationResolution.cs"); + + Assert.True(File.Exists(ownedNavigationPath), "LC011 owned-entity builder navigation resolution should live in a focused partial file."); + + var builderResolutionSource = File.ReadAllText(builderResolutionPath); + Assert.DoesNotContain("private static bool TryGetOwnedEntityType", builderResolutionSource); + Assert.DoesNotContain("private static INamedTypeSymbol? TryGetCollectionElementType", builderResolutionSource); + + var ownedNavigationSource = File.ReadAllText(ownedNavigationPath); + Assert.Contains("private static bool TryGetOwnedEntityType", ownedNavigationSource); + Assert.Contains("private static INamedTypeSymbol? TryGetCollectionElementType", ownedNavigationSource); + } + + [Fact] + public void LC007_QueryProvenanceAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC007_NPlusOneLooper"); + var analysisPath = Path.Combine(analyzerDir, "NPlusOneLooperAnalysis.cs"); + var queryProvenancePath = Path.Combine(analyzerDir, "NPlusOneLooperQueryProvenance.cs"); + + Assert.True(File.Exists(queryProvenancePath), "LC007 query provenance analysis should live in a focused partial file."); + + var analysisSource = File.ReadAllText(analysisPath); + Assert.Contains("internal static partial class NPlusOneLooperAnalysis", analysisSource); + Assert.DoesNotContain("private static QueryProvenance AnalyzeQueryProvenance", analysisSource); + Assert.DoesNotContain("private enum QueryProvenanceKind", analysisSource); + + var queryProvenanceSource = File.ReadAllText(queryProvenancePath); + Assert.Contains("private static QueryProvenance AnalyzeQueryProvenance", queryProvenanceSource); + Assert.Contains("private enum QueryProvenanceKind", queryProvenanceSource); + } + + [Fact] + public void LC007_QueryProvenanceClassification_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC007_NPlusOneLooper"); + var queryProvenancePath = Path.Combine(analyzerDir, "NPlusOneLooperQueryProvenance.cs"); + var classificationPath = Path.Combine(analyzerDir, "NPlusOneLooperQueryProvenanceClassification.cs"); + + Assert.True(File.Exists(classificationPath), "LC007 query provenance source classification should live in a focused partial file."); + + var queryProvenanceSource = File.ReadAllText(queryProvenancePath); + Assert.Contains("private static QueryProvenance AnalyzeQueryProvenance", queryProvenanceSource); + Assert.Contains("private enum QueryProvenanceKind", queryProvenanceSource); + Assert.DoesNotContain("private static bool IsDbContextSetInvocation", queryProvenanceSource); + Assert.DoesNotContain("private static bool IsClientBoundaryInvocation", queryProvenanceSource); + Assert.DoesNotContain("private static bool IsNavigationQueryInvocation", queryProvenanceSource); + Assert.DoesNotContain("private static bool IsAsQueryableInvocation", queryProvenanceSource); + Assert.DoesNotContain("private static bool IsChangeTrackingNamespace", queryProvenanceSource); + + var classificationSource = File.ReadAllText(classificationPath); + Assert.Contains("private static bool IsDbContextSetInvocation", classificationSource); + Assert.Contains("private static bool IsClientBoundaryInvocation", classificationSource); + Assert.Contains("private static bool IsNavigationQueryInvocation", classificationSource); + Assert.Contains("private static bool IsAsQueryableInvocation", classificationSource); + Assert.Contains("private static bool IsChangeTrackingNamespace", classificationSource); + Assert.DoesNotContain("private static QueryProvenance AnalyzeQueryProvenance", classificationSource); + Assert.DoesNotContain("private enum QueryProvenanceKind", classificationSource); + } + + [Fact] + public void LC007_ExecutionMethodClassification_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC007_NPlusOneLooper"); + var analysisPath = Path.Combine(analyzerDir, "NPlusOneLooperAnalysis.cs"); + var executionMethodsPath = Path.Combine(analyzerDir, "NPlusOneLooperExecutionMethods.cs"); + + Assert.True(File.Exists(executionMethodsPath), "LC007 immediate execution and set-based method classification should live in a focused partial file."); + + var analysisSource = File.ReadAllText(analysisPath); + Assert.DoesNotContain("private static readonly HashSet ImmediateQueryExecutionMethods", analysisSource); + Assert.DoesNotContain("private static readonly HashSet SetBasedExecutorMethods", analysisSource); + + var executionMethodsSource = File.ReadAllText(executionMethodsPath); + Assert.Contains("private static readonly HashSet ImmediateQueryExecutionMethods", executionMethodsSource); + Assert.Contains("private static readonly HashSet SetBasedExecutorMethods", executionMethodsSource); + } + + [Fact] + public void LC007_DatabaseExecutionMatching_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC007_NPlusOneLooper"); + var analysisPath = Path.Combine(analyzerDir, "NPlusOneLooperAnalysis.cs"); + var databaseExecutionPath = Path.Combine(analyzerDir, "NPlusOneLooperDatabaseExecution.cs"); + + Assert.True(File.Exists(databaseExecutionPath), "LC007 database execution matching should live in a focused partial file."); + + var analysisSource = File.ReadAllText(analysisPath); + Assert.Contains("internal static partial class NPlusOneLooperAnalysis", analysisSource); + Assert.DoesNotContain("private static bool TryMatchDatabaseExecution", analysisSource); + Assert.DoesNotContain("public static bool HasStronglyTypedNavigationAccessor", analysisSource); + + var databaseExecutionSource = File.ReadAllText(databaseExecutionPath); + Assert.Contains("internal static partial class NPlusOneLooperAnalysis", databaseExecutionSource); + Assert.Contains("private static bool TryMatchDatabaseExecution", databaseExecutionSource); + Assert.Contains("public static bool HasStronglyTypedNavigationAccessor", databaseExecutionSource); + } + + [Fact] + public void AnalyzerPerformance_LC023Sources_LiveInDedicatedPartial() + { + var architectureDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Architecture"); + var performancePath = Path.Combine(architectureDir, "AnalyzerPerformanceTests.cs"); + var lc023SourcesPath = Path.Combine(architectureDir, "AnalyzerPerformanceLc023Sources.cs"); + + Assert.True(File.Exists(lc023SourcesPath), "LC023 performance source builders should live in a focused partial file."); + + var performanceSource = File.ReadAllText(performancePath); + Assert.Contains("public partial class AnalyzerPerformanceTests", performanceSource); + Assert.DoesNotContain("private static string GenerateLc023StressSource", performanceSource); + Assert.DoesNotContain("private static string[] GenerateLc023MultiTreeStressSources", performanceSource); + + var lc023Sources = File.ReadAllText(lc023SourcesPath); + Assert.Contains("public partial class AnalyzerPerformanceTests", lc023Sources); + Assert.Contains("private static string GenerateLc023StressSource", lc023Sources); + Assert.Contains("private static string[] GenerateLc023MultiTreeStressSources", lc023Sources); + } + + [Fact] + public void LC002_FixerInlineMaterializationHelpers_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC002_PrematureMaterialization"); + var fixerPath = Path.Combine(analyzerDir, "PrematureMaterializationFixer.cs"); + var inlineMaterializationPath = Path.Combine(analyzerDir, "PrematureMaterializationFixerInlineMaterialization.cs"); + + Assert.True(File.Exists(inlineMaterializationPath), "LC002 fixer inline materializer shape/safety helpers should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class PrematureMaterializationFixer", fixerSource); + Assert.DoesNotContain("private static bool IsInlineMaterializerReceiver", fixerSource); + Assert.DoesNotContain("private static bool TryGetInlineMaterializerParts", fixerSource); + Assert.DoesNotContain("private static bool IsInsideOuterMaterialization", fixerSource); + Assert.DoesNotContain("private static bool IsMaterializingConstructor", fixerSource); + + var inlineMaterializationSource = File.ReadAllText(inlineMaterializationPath); + Assert.Contains("private static bool IsInlineMaterializerReceiver", inlineMaterializationSource); + Assert.Contains("private static bool TryGetInlineMaterializerParts", inlineMaterializationSource); + Assert.Contains("private static bool IsInsideOuterMaterialization", inlineMaterializationSource); + Assert.Contains("private static bool IsMaterializingConstructor", inlineMaterializationSource); + } + + [Fact] + public void LC010_RetryGuardAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC010_SaveChangesInLoop"); + var analyzerPath = Path.Combine(analyzerDir, "SaveChangesInLoopAnalyzer.cs"); + var retryGuardPath = Path.Combine(analyzerDir, "SaveChangesInLoopRetryGuard.cs"); + + Assert.True(File.Exists(retryGuardPath), "LC010 catch-guarded retry suppression should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class SaveChangesInLoopAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool IsSaveInsideCatchGuardedRetryAttempt", analyzerSource); + Assert.DoesNotContain("private static TryStatementSyntax? FindTryStatementBetweenInvocationAndLoop", analyzerSource); + Assert.DoesNotContain("private static bool HasLoopExitAfterSave", analyzerSource); + + var retryGuardSource = File.ReadAllText(retryGuardPath); + Assert.Contains("private static bool IsSaveInsideCatchGuardedRetryAttempt", retryGuardSource); + Assert.Contains("private static TryStatementSyntax? FindTryStatementBetweenInvocationAndLoop", retryGuardSource); + Assert.Contains("private static bool HasLoopExitAfterSave", retryGuardSource); + } + + [Fact] + public void LC010_DelegateExecutionAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC010_SaveChangesInLoop"); + var analyzerPath = Path.Combine(analyzerDir, "SaveChangesInLoopAnalyzer.cs"); + var delegateExecutionPath = Path.Combine(analyzerDir, "SaveChangesInLoopDelegateExecution.cs"); + + Assert.True(File.Exists(delegateExecutionPath), "LC010 delegate-loop execution proof should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class SaveChangesInLoopAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool IsInsideDelegateCalledFromLoop", analyzerSource); + Assert.DoesNotContain("private static bool IsDelegateLocalCalledFromLoop", analyzerSource); + + var delegateExecutionSource = File.ReadAllText(delegateExecutionPath); + Assert.Contains("private static bool IsInsideDelegateCalledFromLoop", delegateExecutionSource); + Assert.Contains("private static bool IsDelegateLocalCalledFromLoop", delegateExecutionSource); + Assert.Contains("private static bool IsLocalAssignedInStraightLinePathBetween", delegateExecutionSource); + } + + [Fact] + public void LC010_FixerMoveSafety_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC010_SaveChangesInLoop"); + var fixerPath = Path.Combine(analyzerDir, "SaveChangesInLoopFixer.cs"); + var moveSafetyPath = Path.Combine(analyzerDir, "SaveChangesInLoopFixerMoveSafety.cs"); + + Assert.True(File.Exists(moveSafetyPath), "LC010 fixer move-safety predicates should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class SaveChangesInLoopFixer", fixerSource); + Assert.DoesNotContain("private static bool TryGetMovableSaveStatement", fixerSource); + Assert.DoesNotContain("private static bool ContainsUnsafeControlFlow", fixerSource); + Assert.DoesNotContain("private static bool IsSaveReceiverDeclaredInsideLoop", fixerSource); + + var moveSafetySource = File.ReadAllText(moveSafetyPath); + Assert.Contains("private static bool TryGetMovableSaveStatement", moveSafetySource); + Assert.Contains("private static bool ContainsUnsafeControlFlow", moveSafetySource); + Assert.Contains("private static bool IsSaveReceiverDeclaredInsideLoop", moveSafetySource); + } + + [Fact] + public void LC032_FixerExtensionNamespaceResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC032_ExecuteUpdateForBulkUpdates"); + var fixerPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesFixer.cs"); + var namespaceResolutionPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesFixerNamespaceResolution.cs"); + + Assert.True(File.Exists(namespaceResolutionPath), "LC032 fixer ExecuteUpdate extension namespace resolution should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.DoesNotContain("private static string? ResolveExecuteUpdateNamespace", fixerSource); + Assert.DoesNotContain("private static bool IsExecuteUpdateLikeMethod", fixerSource); + + var namespaceResolutionSource = File.ReadAllText(namespaceResolutionPath); + Assert.Contains("private static string? ResolveExecuteUpdateNamespace", namespaceResolutionSource); + Assert.Contains("private static bool IsExecuteUpdateLikeMethod", namespaceResolutionSource); + } + + [Fact] + public void LC016_FixerVariableNameGeneration_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC016_AvoidDateTimeNow"); + var fixerPath = Path.Combine(analyzerDir, "AvoidDateTimeNowFixer.cs"); + var variableNamesPath = Path.Combine(analyzerDir, "AvoidDateTimeNowFixerVariableNames.cs"); + + Assert.True(File.Exists(variableNamesPath), "LC016 fixer variable-name collision handling should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.DoesNotContain("private static string GetUniqueVariableName", fixerSource); + Assert.DoesNotContain("private static HashSet CollectExistingNames", fixerSource); + Assert.DoesNotContain("private static void AddEnclosingParameterNames", fixerSource); + Assert.DoesNotContain("private static void AddParameterNames", fixerSource); + + var variableNamesSource = File.ReadAllText(variableNamesPath); + Assert.Contains("private static string GetUniqueVariableName", variableNamesSource); + Assert.Contains("private static HashSet CollectExistingNames", variableNamesSource); + Assert.Contains("private static void AddEnclosingParameterNames", variableNamesSource); + Assert.Contains("private static void AddParameterNames", variableNamesSource); + } + + [Fact] + public void LC023_KeyConfigurationAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC023_FindInsteadOfFirstOrDefault"); + var keyAnalysisPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultKeyAnalysis.cs"); + var keyConfigurationPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultKeyConfiguration.cs"); + + Assert.True(File.Exists(keyConfigurationPath), "LC023 Fluent API key-configuration parsing should live in a focused partial file."); + + var keyAnalysisSource = File.ReadAllText(keyAnalysisPath); + Assert.DoesNotContain("private static bool TryGetEntityTypeBuilderEntity", keyAnalysisSource); + Assert.DoesNotContain("private static ConfiguredPrimaryKey AnalyzeKeyArgument", keyAnalysisSource); + Assert.DoesNotContain("private readonly struct ConfiguredPrimaryKey", keyAnalysisSource); + + var keyConfigurationSource = File.ReadAllText(keyConfigurationPath); + Assert.Contains("private static bool TryGetEntityTypeBuilderEntity", keyConfigurationSource); + Assert.Contains("private static ConfiguredPrimaryKey AnalyzeKeyArgument", keyConfigurationSource); + Assert.Contains("private readonly struct ConfiguredPrimaryKey", keyConfigurationSource); + } + + [Fact] + public void LC023_PredicateAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC023_FindInsteadOfFirstOrDefault"); + var analyzerPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultAnalyzer.cs"); + var predicateAnalysisPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultPredicateAnalysis.cs"); + + Assert.True(File.Exists(predicateAnalysisPath), "LC023 primary-key predicate analysis should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class FindInsteadOfFirstOrDefaultAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool TryGetPrimaryKeyEqualityProperty", analyzerSource); + Assert.DoesNotContain("private static bool TryGetLambdaParameterProperty", analyzerSource); + + var predicateAnalysisSource = File.ReadAllText(predicateAnalysisPath); + Assert.Contains("private static bool TryGetPrimaryKeyEqualityProperty", predicateAnalysisSource); + Assert.Contains("private static bool TryGetLambdaParameterProperty", predicateAnalysisSource); + } + + [Fact] + public void LC023_FixerKeyValueAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC023_FindInsteadOfFirstOrDefault"); + var contextPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultFixerContext.cs"); + var keyValueAnalysisPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultFixerKeyValueAnalysis.cs"); + + Assert.True(File.Exists(keyValueAnalysisPath), "LC023 fixer key-value analysis should live in a focused partial file."); + + var contextSource = File.ReadAllText(contextPath); + Assert.DoesNotContain("private static bool TryGetKeyValueExpression", contextSource); + Assert.DoesNotContain("private static bool ReferencesLambdaParameter", contextSource); + Assert.DoesNotContain("private static bool IsPrimaryKeyAccess", contextSource); + + var keyValueAnalysisSource = File.ReadAllText(keyValueAnalysisPath); + Assert.Contains("private static bool TryGetKeyValueExpression", keyValueAnalysisSource); + Assert.Contains("private static bool ReferencesLambdaParameter", keyValueAnalysisSource); + Assert.Contains("private static bool IsPrimaryKeyAccess", keyValueAnalysisSource); + } + + [Fact] + public void LC045_UsageTargetHelpers_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC045_MissingInclude"); + var usageAnalysisPath = Path.Combine(analyzerDir, "MissingIncludeUsageAnalysis.cs"); + var usageTargetsPath = Path.Combine(analyzerDir, "MissingIncludeUsageTargets.cs"); + var indexedAccessPath = Path.Combine(analyzerDir, "MissingIncludeIndexedAccess.cs"); + + Assert.True(File.Exists(usageTargetsPath), "LC045 usage target and result-local helpers should live in a focused partial file."); + Assert.True(File.Exists(indexedAccessPath), "LC045 indexed collection access classification should live in a focused partial file."); + + var usageAnalysisSource = File.ReadAllText(usageAnalysisPath); + Assert.DoesNotContain("private static bool IsWriteTarget", usageAnalysisSource); + Assert.DoesNotContain("private static bool IsIndexedAccessOf", usageAnalysisSource); + Assert.DoesNotContain("private static ILocalSymbol? FindVariableAssignment", usageAnalysisSource); + + var usageTargetsSource = File.ReadAllText(usageTargetsPath); + Assert.Contains("private static bool IsWriteTarget", usageTargetsSource); + Assert.DoesNotContain("private static bool IsIndexedAccessOf", usageTargetsSource); + Assert.Contains("private static ILocalSymbol? FindVariableAssignment", usageTargetsSource); + + var indexedAccessSource = File.ReadAllText(indexedAccessPath); + Assert.Contains("private static bool IsIndexedAccessOf", indexedAccessSource); + } + + [Fact] + public void LC045_NavigationAccessCollection_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC045_MissingInclude"); + var usageAnalysisPath = Path.Combine(analyzerDir, "MissingIncludeUsageAnalysis.cs"); + var accessCollectionPath = Path.Combine(analyzerDir, "MissingIncludeNavigationAccessCollection.cs"); + + Assert.True(File.Exists(accessCollectionPath), "LC045 navigation access collection and satisfaction helpers should live in a focused partial file."); + + var usageAnalysisSource = File.ReadAllText(usageAnalysisPath); + Assert.DoesNotContain("private readonly struct NavigationAccess", usageAnalysisSource); + Assert.DoesNotContain("private static bool IsSatisfied", usageAnalysisSource); + Assert.DoesNotContain("private static List CollectInlineAccesses", usageAnalysisSource); + + var accessCollectionSource = File.ReadAllText(accessCollectionPath); + Assert.Contains("private readonly struct NavigationAccess", accessCollectionSource); + Assert.Contains("private static bool IsSatisfied", accessCollectionSource); + Assert.Contains("private static List CollectInlineAccesses", accessCollectionSource); + } + + [Fact] + public void LC045_QueryOperatorClassification_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC045_MissingInclude"); + var queryAnalysisPath = Path.Combine(analyzerDir, "MissingIncludeQueryAnalysis.cs"); + var queryOperatorsPath = Path.Combine(analyzerDir, "MissingIncludeQueryOperators.cs"); + var querySemanticsPath = Path.Combine(analyzerDir, "MissingIncludeQuerySemantics.cs"); + + Assert.True(File.Exists(queryOperatorsPath), "LC045 query operator/materializer classification should live in a focused partial file."); + Assert.True(File.Exists(querySemanticsPath), "LC045 exact query symbol/source classification should live in a focused partial file."); + + var queryAnalysisSource = File.ReadAllText(queryAnalysisPath); + Assert.DoesNotContain("private static readonly ImmutableHashSet ShapePreservingQueryableOperators", queryAnalysisSource); + Assert.DoesNotContain("private static bool IsEntityMaterializer", queryAnalysisSource); + + var queryOperatorsSource = File.ReadAllText(queryOperatorsPath); + Assert.Contains("private static readonly ImmutableHashSet ShapePreservingQueryableOperators", queryOperatorsSource); + Assert.Contains("private static readonly ImmutableHashSet ShapePreservingEntityFrameworkOperators", queryOperatorsSource); + Assert.Contains("private static bool IsEntityMaterializer", queryOperatorsSource); + + var querySemanticsSource = File.ReadAllText(querySemanticsPath); + Assert.Contains("private static IOperation? GetQuerySource", querySemanticsSource); + Assert.Contains("private static bool IsExactShapePreservingQueryStep", querySemanticsSource); + } + + [Fact] + public void LC025_NoTrackingSourceResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC025_AsNoTrackingWithUpdate"); + var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateAnalyzer.cs"); + var sourceResolutionPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateSourceResolution.cs"); + + Assert.True(File.Exists(sourceResolutionPath), "LC025 local source resolution should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AsNoTrackingWithUpdateAnalyzer", analyzerSource); + Assert.DoesNotContain("private bool IsFromNoTrackingQuery", analyzerSource); + Assert.DoesNotContain("private readonly struct LocalOrigin", analyzerSource); + + var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); + Assert.Contains("private bool IsFromNoTrackingQuery", sourceResolutionSource); + Assert.DoesNotContain("private readonly struct LocalOrigin", sourceResolutionSource); + } + + [Fact] + public void LC025_EntryStateParsing_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC025_AsNoTrackingWithUpdate"); + var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateAnalyzer.cs"); + var entryStatePath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateEntryStateParsing.cs"); + + Assert.True(File.Exists(entryStatePath), "LC025 Entry(entity).State parsing should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static bool TryParseEntryStateWrite", analyzerSource); + Assert.DoesNotContain("private static bool TryGetEntityStateName", analyzerSource); + + var entryStateSource = File.ReadAllText(entryStatePath); + Assert.Contains("private static bool TryParseEntryStateWrite", entryStateSource); + Assert.Contains("private static bool TryGetEntityStateName", entryStateSource); + } + + [Fact] + public void LC014_QuerySourceResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC014_AvoidStringCaseConversion"); + var analyzerPath = Path.Combine(analyzerDir, "AvoidStringCaseConversionAnalyzer.cs"); + var querySourcePath = Path.Combine(analyzerDir, "AvoidStringCaseConversionQuerySource.cs"); + + Assert.True(File.Exists(querySourcePath), "LC014 EF query-source resolution should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AvoidStringCaseConversionAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool HasEntityFrameworkQuerySource", analyzerSource); + Assert.DoesNotContain("private static bool TryResolveLocalValue", analyzerSource); + + var querySource = File.ReadAllText(querySourcePath); + Assert.Contains("private static bool HasEntityFrameworkQuerySource", querySource); + Assert.Contains("private static bool TryResolveLocalValue", querySource); + } + + [Fact] + public void LC014_ReceiverDependency_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC014_AvoidStringCaseConversion"); + var analyzerPath = Path.Combine(analyzerDir, "AvoidStringCaseConversionAnalyzer.cs"); + var receiverDependencyPath = Path.Combine(analyzerDir, "AvoidStringCaseConversionReceiverDependency.cs"); + + Assert.True(File.Exists(receiverDependencyPath), "LC014 receiver dependency analysis should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AvoidStringCaseConversionAnalyzer", analyzerSource); + Assert.DoesNotContain("private bool ReceiverDependsOnParameter", analyzerSource); + + var receiverDependencySource = File.ReadAllText(receiverDependencyPath); + Assert.Contains("private bool ReceiverDependsOnParameter", receiverDependencySource); + } + + [Fact] + public void LC030_DependencyInjectionRegistration_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC030_DbContextInSingleton"); + var analyzerPath = Path.Combine(analyzerDir, "DbContextInSingletonAnalyzer.cs"); + var registrationPath = Path.Combine(analyzerDir, "DbContextInSingletonRegistrationAnalysis.cs"); + + Assert.True(File.Exists(registrationPath), "LC030 dependency-injection registration analysis should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class DbContextInSingletonAnalyzer", analyzerSource); + Assert.DoesNotContain("private static void AnalyzeAddSingleton", analyzerSource); + + var registrationSource = File.ReadAllText(registrationPath); + Assert.Contains("private static void AnalyzeAddSingleton", registrationSource); + Assert.Contains("private static void AnalyzeAddDbContext", registrationSource); + } + + [Fact] + public void LC030_RegistrationLifetimeArgumentRules_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC030_DbContextInSingleton"); + var registrationPath = Path.Combine(analyzerDir, "DbContextInSingletonRegistrationAnalysis.cs"); + var lifetimePath = Path.Combine(analyzerDir, "DbContextInSingletonRegistrationLifetime.cs"); + + Assert.True(File.Exists(lifetimePath), "LC030 AddDbContext singleton-lifetime argument parsing should live in a focused partial file."); + + var registrationSource = File.ReadAllText(registrationPath); + Assert.Contains("private static void AnalyzeAddDbContext", registrationSource); + Assert.DoesNotContain("private static bool HasSingletonContextLifetimeArgument", registrationSource); + Assert.DoesNotContain("private static bool IsSingletonLifetime", registrationSource); + Assert.DoesNotContain("private static IOperation UnwrapConversion", registrationSource); + + var lifetimeSource = File.ReadAllText(lifetimePath); + Assert.Contains("private static bool HasSingletonContextLifetimeArgument", lifetimeSource); + Assert.Contains("private static bool IsSingletonLifetime", lifetimeSource); + Assert.Contains("private static IOperation UnwrapConversion", lifetimeSource); + Assert.DoesNotContain("private static void AnalyzeAddDbContext", lifetimeSource); + } + + [Fact] + public void LC030_LongLivedEvidence_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC030_DbContextInSingleton"); + var analyzerPath = Path.Combine(analyzerDir, "DbContextInSingletonAnalyzer.cs"); + var evidencePath = Path.Combine(analyzerDir, "DbContextInSingletonLongLivedEvidence.cs"); + + Assert.True(File.Exists(evidencePath), "LC030 long-lived type evidence should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static void AddIntrinsicLongLivedEvidence", analyzerSource); + Assert.DoesNotContain("private static bool HasConventionalMiddlewareSignature", analyzerSource); + + var evidenceSource = File.ReadAllText(evidencePath); + Assert.Contains("private static void AddIntrinsicLongLivedEvidence", evidenceSource); + Assert.DoesNotContain("private static bool HasConventionalMiddlewareSignature", evidenceSource); + } + + [Fact] + public void LC030_LongLivedOptions_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC030_DbContextInSingleton"); + var evidencePath = Path.Combine(analyzerDir, "DbContextInSingletonLongLivedEvidence.cs"); + var optionsPath = Path.Combine(analyzerDir, "DbContextInSingletonLongLivedOptions.cs"); + + Assert.True(File.Exists(optionsPath), "LC030 long-lived options and configured type matching should live in a focused partial file."); + + var evidenceSource = File.ReadAllText(evidencePath); + Assert.DoesNotContain("private static Lc030Options GetOptions", evidenceSource); + Assert.DoesNotContain("private static bool TryGetConfiguredLongLivedReason", evidenceSource); + Assert.DoesNotContain("private sealed class Lc030Options", evidenceSource); + + var optionsSource = File.ReadAllText(optionsPath); + Assert.Contains("private static Lc030Options GetOptions", optionsSource); + Assert.Contains("private static bool TryGetConfiguredLongLivedReason", optionsSource); + Assert.Contains("private sealed class Lc030Options", optionsSource); + } + + [Fact] + public void LC030_FreshComputedPropertyAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC030_DbContextInSingleton"); + var analyzerPath = Path.Combine(analyzerDir, "DbContextInSingletonAnalyzer.cs"); + var freshPropertyPath = Path.Combine(analyzerDir, "DbContextInSingletonFreshComputedProperty.cs"); + + Assert.True(File.Exists(freshPropertyPath), "LC030 fresh computed DbContext property analysis should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static bool IsFreshComputedProperty", analyzerSource); + Assert.DoesNotContain("private static bool IsDbContextFactoryCreate", analyzerSource); + + var freshPropertySource = File.ReadAllText(freshPropertyPath); + Assert.Contains("private static bool IsFreshComputedProperty", freshPropertySource); + Assert.Contains("private static bool IsDbContextFactoryCreate", freshPropertySource); + } + + [Fact] + public void LC030_CandidateCollectionAndReporting_LiveInDedicatedPartials() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC030_DbContextInSingleton"); + var analyzerPath = Path.Combine(analyzerDir, "DbContextInSingletonAnalyzer.cs"); + var candidateCollectionPath = Path.Combine(analyzerDir, "DbContextInSingletonCandidateCollection.cs"); + var candidateReportingPath = Path.Combine(analyzerDir, "DbContextInSingletonCandidateReporting.cs"); + + Assert.True(File.Exists(candidateCollectionPath), "LC030 candidate field/property/constructor collection should live in a focused partial file."); + Assert.True(File.Exists(candidateReportingPath), "LC030 candidate collection and final diagnostic reporting should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static void AnalyzeField", analyzerSource); + Assert.DoesNotContain("private static void ReportCandidateDiagnostics", analyzerSource); + Assert.DoesNotContain("private sealed class DbContextCandidate", analyzerSource); + + var candidateCollectionSource = File.ReadAllText(candidateCollectionPath); + Assert.Contains("private static void AnalyzeField", candidateCollectionSource); + Assert.Contains("private static void AnalyzeProperty", candidateCollectionSource); + Assert.Contains("private static void AnalyzeConstructor", candidateCollectionSource); + Assert.Contains("private static void AddCandidate", candidateCollectionSource); + + var candidateReportingSource = File.ReadAllText(candidateReportingPath); + Assert.DoesNotContain("private static void AnalyzeField", candidateReportingSource); + Assert.DoesNotContain("private static void AnalyzeProperty", candidateReportingSource); + Assert.DoesNotContain("private static void AnalyzeConstructor", candidateReportingSource); + Assert.DoesNotContain("private static void AddCandidate", candidateReportingSource); + Assert.Contains("private static void ReportCandidateDiagnostics", candidateReportingSource); + Assert.Contains("private sealed class DbContextCandidate", candidateReportingSource); + } + + [Fact] + public void LC033_FixerSyntaxHelpers_LiveInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC033_UseFrozenSetForStaticMembershipCaches"); + var rewritePath = Path.Combine(fixerDir, "UseFrozenSetForStaticMembershipCachesFixerRewrite.cs"); + var syntaxPath = Path.Combine(fixerDir, "UseFrozenSetForStaticMembershipCachesFixerSyntax.cs"); + + Assert.True(File.Exists(syntaxPath), "LC033 FrozenSet fixer syntax construction helpers should live in a focused partial file."); + + var rewriteSource = File.ReadAllText(rewritePath); + Assert.DoesNotContain("private static ExpressionSyntax CreateToFrozenSetInvocation", rewriteSource); + Assert.DoesNotContain("private static TypeSyntax CreateTypeSyntax", rewriteSource); + Assert.DoesNotContain("private static ExpressionSyntax ParenthesizeIfNeeded", rewriteSource); + + var syntaxSource = File.ReadAllText(syntaxPath); + Assert.Contains("private static ExpressionSyntax CreateToFrozenSetInvocation", syntaxSource); + Assert.Contains("private static TypeSyntax CreateTypeSyntax", syntaxSource); + Assert.Contains("private static ExpressionSyntax ParenthesizeIfNeeded", syntaxSource); + } + + [Fact] + public void LC035_LocalConditionalFlow_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC035_MissingWhereBeforeExecuteDeleteUpdate"); + var analyzerPath = Path.Combine(analyzerDir, "MissingWhereBeforeExecuteDeleteUpdateAnalyzer.cs"); + var localFlowPath = Path.Combine(analyzerDir, "MissingWhereBeforeExecuteDeleteUpdateLocalFlow.cs"); + + Assert.True(File.Exists(localFlowPath), "LC035 local initializer and conditional assignment flow should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class MissingWhereBeforeExecuteDeleteUpdateAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool HasWhereInLocalInitializer", analyzerSource); + Assert.DoesNotContain("private static bool HasWhereInExhaustiveIfElseAssignments", analyzerSource); + Assert.DoesNotContain("private static bool IsControlFlowConditionalAssignment", analyzerSource); + + var localFlowSource = File.ReadAllText(localFlowPath); + Assert.Contains("private static bool HasWhereInLocalInitializer", localFlowSource); + Assert.Contains("private static bool HasWhereInExhaustiveIfElseAssignments", localFlowSource); + Assert.Contains("private static bool IsControlFlowConditionalAssignment", localFlowSource); + } + + [Fact] + public void LC027_RelationshipBuilderLocalResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC027_MissingExplicitForeignKey"); + var configurationPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyConfigurationAnalysis.cs"); + var relationshipLocalsPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyRelationshipLocals.cs"); + + Assert.True(File.Exists(relationshipLocalsPath), "LC027 relationship-builder local resolution should live in a focused partial file."); + + var configurationSource = File.ReadAllText(configurationPath); + Assert.DoesNotContain("private static List BuildRelationshipBuilderLocalMap", configurationSource); + Assert.DoesNotContain("private static bool IsShadowedByNestedLocal", configurationSource); + + var relationshipLocalsSource = File.ReadAllText(relationshipLocalsPath); + Assert.Contains("private static List BuildRelationshipBuilderLocalMap", relationshipLocalsSource); + Assert.Contains("private static bool TryResolveRelationshipBuilderLocal", relationshipLocalsSource); + } + + [Fact] + public void LC027_RelationshipBuilderLocalScope_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC027_MissingExplicitForeignKey"); + var relationshipLocalsPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyRelationshipLocals.cs"); + var relationshipLocalScopePath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyRelationshipLocalScope.cs"); + var relationshipLocalShadowingPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyRelationshipLocalShadowing.cs"); + + Assert.True(File.Exists(relationshipLocalScopePath), "LC027 relationship-builder local assignment/write-target logic should live in a focused partial file."); + Assert.True(File.Exists(relationshipLocalShadowingPath), "LC027 relationship-builder local shadowing logic should live in a focused partial file."); + + var relationshipLocalsSource = File.ReadAllText(relationshipLocalsPath); + Assert.DoesNotContain("private static bool HasSingleLocalAssignment", relationshipLocalsSource); + Assert.DoesNotContain("private static bool IsShadowedByNestedLocal", relationshipLocalsSource); + Assert.DoesNotContain("private static SyntaxNode? FindDesignationScope", relationshipLocalsSource); + Assert.DoesNotContain("private static bool IsVisibleAt", relationshipLocalsSource); + + var relationshipLocalScopeSource = File.ReadAllText(relationshipLocalScopePath); + Assert.Contains("private static bool HasSingleLocalAssignment", relationshipLocalScopeSource); + Assert.DoesNotContain("private static bool IsShadowedByNestedLocal", relationshipLocalScopeSource); + Assert.DoesNotContain("private static SyntaxNode? FindDesignationScope", relationshipLocalScopeSource); + Assert.Contains("private static bool IsVisibleAt", relationshipLocalScopeSource); + + var relationshipLocalShadowingSource = File.ReadAllText(relationshipLocalShadowingPath); + Assert.Contains("private static bool IsShadowedByNestedLocal", relationshipLocalShadowingSource); + Assert.Contains("private static SyntaxNode? FindDesignationScope", relationshipLocalShadowingSource); + } + + [Fact] + public void LC027_ConfigurationNameExtraction_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC027_MissingExplicitForeignKey"); + var configurationPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyConfigurationAnalysis.cs"); + var nameExtractionPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyConfigurationNameExtraction.cs"); + + Assert.True(File.Exists(nameExtractionPath), "LC027 configuration navigation/entity/owned-type extraction should live in a focused partial file."); + + var configurationSource = File.ReadAllText(configurationPath); + Assert.Contains("public sealed partial class MissingExplicitForeignKeyAnalyzer", configurationSource); + Assert.DoesNotContain("private static string? ExtractNavigationNameFromChain", configurationSource); + Assert.DoesNotContain("private static string? ExtractEntityTypeNameFromChain", configurationSource); + Assert.DoesNotContain("private static INamedTypeSymbol? ResolveOwnedTypeFromConfiguration", configurationSource); + + var nameExtractionSource = File.ReadAllText(nameExtractionPath); + Assert.Contains("private static string? ExtractNavigationNameFromChain", nameExtractionSource); + Assert.Contains("private static string? ExtractEntityTypeNameFromChain", nameExtractionSource); + Assert.Contains("private static INamedTypeSymbol? ResolveOwnedTypeFromConfiguration", nameExtractionSource); + } + + [Fact] + public void LC032_SaveChangesRewriteMode_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC032_ExecuteUpdateForBulkUpdates"); + var fixerPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixer.cs"); + var saveChangesModePath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerSaveChangesMode.cs"); + var asyncSupportPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerAsyncSupport.cs"); + + Assert.True(File.Exists(saveChangesModePath), "LC032 SaveChanges/async rewrite-mode logic should live in a focused partial file."); + Assert.True(File.Exists(asyncSupportPath), "LC032 ExecuteUpdateAsync capability detection should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class ExecuteUpdateForBulkUpdatesFixer", fixerSource); + Assert.DoesNotContain("private static bool TryClassifyTrailingSaveChanges", fixerSource); + Assert.DoesNotContain("private static bool HasExecuteUpdateAsyncTokenOverload", fixerSource); + + var saveChangesModeSource = File.ReadAllText(saveChangesModePath); + Assert.Contains("private static bool TryClassifyTrailingSaveChanges", saveChangesModeSource); + Assert.DoesNotContain("private static bool HasExecuteUpdateAsyncTokenOverload", saveChangesModeSource); + + var asyncSupportSource = File.ReadAllText(asyncSupportPath); + Assert.Contains("private static bool HasExecuteUpdateAsyncTokenOverload", asyncSupportSource); + } + + [Fact] + public void LC032_FixerRewriteApplication_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC032_ExecuteUpdateForBulkUpdates"); + var fixerPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixer.cs"); + var rewriteApplicationPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerRewriteApplication.cs"); + + Assert.True(File.Exists(rewriteApplicationPath), "LC032 ExecuteUpdate statement generation and document rewrite should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class ExecuteUpdateForBulkUpdatesFixer", fixerSource); + Assert.DoesNotContain("private const string WarningComment", fixerSource); + Assert.DoesNotContain("private static async Task ApplyFixAsync", fixerSource); + + var rewriteApplicationSource = File.ReadAllText(rewriteApplicationPath); + Assert.Contains("private const string WarningComment", rewriteApplicationSource); + Assert.Contains("private static async Task ApplyFixAsync", rewriteApplicationSource); + Assert.Contains("DocumentEditor.CreateAsync", rewriteApplicationSource); + Assert.Contains("SyntaxFactory.ParseStatement", rewriteApplicationSource); + } + + [Fact] + public void LC032_SetterExtraction_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC032_ExecuteUpdateForBulkUpdates"); + var fixerPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixer.cs"); + var setterExtractionPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerSetters.cs"); + + Assert.True(File.Exists(setterExtractionPath), "LC032 setter extraction and duplicate-target safety should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class ExecuteUpdateForBulkUpdatesFixer", fixerSource); + Assert.DoesNotContain("private static bool TryGetSetters", fixerSource); + Assert.DoesNotContain("private static bool ReadsAnyProperty", fixerSource); + + var setterExtractionSource = File.ReadAllText(setterExtractionPath); + Assert.Contains("private static bool TryGetSetters", setterExtractionSource); + Assert.Contains("private static bool ReadsAnyProperty", setterExtractionSource); + } + + [Fact] + public void LC032_ReceiverNormalization_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC032_ExecuteUpdateForBulkUpdates"); + var fixerPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixer.cs"); + var receiverPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerReceiver.cs"); + + Assert.True(File.Exists(receiverPath), "LC032 receiver materializer stripping and unsupported-step detection should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class ExecuteUpdateForBulkUpdatesFixer", fixerSource); + Assert.DoesNotContain("UnsupportedExecuteUpdateReceiverSteps", fixerSource); + Assert.DoesNotContain("private static ExpressionSyntax StripCollectionMaterializer", fixerSource); + Assert.DoesNotContain("private static bool HasUnsupportedExecuteUpdateReceiverStep", fixerSource); + Assert.DoesNotContain("private static bool IsStaticLinqTypeExpression", fixerSource); + + var receiverSource = File.ReadAllText(receiverPath); + Assert.Contains("UnsupportedExecuteUpdateReceiverSteps", receiverSource); + Assert.Contains("private static ExpressionSyntax StripCollectionMaterializer", receiverSource); + Assert.Contains("private static bool HasUnsupportedExecuteUpdateReceiverStep", receiverSource); + Assert.Contains("private static bool IsStaticLinqTypeExpression", receiverSource); + } + + [Fact] + public void LC012_RewriteSafetyAnalysis_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC012_OptimizeRemoveRange"); + var fixerPath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixer.cs"); + var rewriteSafetyPath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerRewriteSafety.cs"); + var querySourcePath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerQuerySourceContext.cs"); + + Assert.True(File.Exists(rewriteSafetyPath), "LC012 rewrite-safety analysis should live in a focused partial file."); + Assert.True(File.Exists(querySourcePath), "LC012 query-source context resolution should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class OptimizeRemoveRangeFixer", fixerSource); + Assert.DoesNotContain("private static async Task CanSafelyRewriteAsync", fixerSource); + Assert.DoesNotContain("private static bool TryResolveQuerySourceFreshContextLocal", fixerSource); + + var rewriteSafetySource = File.ReadAllText(rewriteSafetyPath); + Assert.Contains("private static async Task CanSafelyRewriteAsync", rewriteSafetySource); + Assert.DoesNotContain("private static bool TryResolveQuerySourceFreshContextLocal", rewriteSafetySource); + + var querySource = File.ReadAllText(querySourcePath); + Assert.Contains("private static bool TryResolveQuerySourceFreshContextLocal", querySource); + } + + [Fact] + public void LC012_RewriteModeSelection_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC012_OptimizeRemoveRange"); + var rewriteSafetyPath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerRewriteSafety.cs"); + var rewriteModePath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerRewriteMode.cs"); + + Assert.True(File.Exists(rewriteModePath), "LC012 async/sync ExecuteDelete rewrite mode selection should live in a focused partial file."); + + var rewriteSafetySource = File.ReadAllText(rewriteSafetyPath); + Assert.DoesNotContain("private static RewriteMode DetermineRewriteMode", rewriteSafetySource); + Assert.DoesNotContain("private static bool IsAsyncContext", rewriteSafetySource); + Assert.DoesNotContain("private static bool HasExecuteDeleteAsyncSupport", rewriteSafetySource); + + var rewriteModeSource = File.ReadAllText(rewriteModePath); + Assert.Contains("private static RewriteMode DetermineRewriteMode", rewriteModeSource); + Assert.Contains("private static bool IsAsyncContext", rewriteModeSource); + Assert.Contains("private static bool HasExecuteDeleteAsyncSupport", rewriteModeSource); + } + + [Fact] + public void LC012_SaveChangesSafetyAnalysis_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC012_OptimizeRemoveRange"); + var rewriteSafetyPath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerRewriteSafety.cs"); + var saveChangesSafetyPath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerSaveChangesSafety.cs"); + + Assert.True(File.Exists(saveChangesSafetyPath), "LC012 SaveChanges detection safety should live in a focused partial file."); + + var rewriteSafetySource = File.ReadAllText(rewriteSafetyPath); + Assert.DoesNotContain("private static bool HasSubsequentSaveChangesInvocation", rewriteSafetySource); + Assert.DoesNotContain("private static bool AreMutuallyExclusiveBranches", rewriteSafetySource); + + var saveChangesSafetySource = File.ReadAllText(saveChangesSafetyPath); + Assert.Contains("private static bool HasSubsequentSaveChangesInvocation", saveChangesSafetySource); + Assert.DoesNotContain("private static bool AreMutuallyExclusiveBranches", saveChangesSafetySource); + } + + [Fact] + public void LC012_AnalyzerSaveChangesSafety_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC012_OptimizeRemoveRange"); + var analyzerPath = Path.Combine(analyzerDir, "OptimizeRemoveRangeAnalyzer.cs"); + var saveChangesSafetyPath = Path.Combine(analyzerDir, "OptimizeRemoveRangeAnalyzerSaveChangesSafety.cs"); + + Assert.True(File.Exists(saveChangesSafetyPath), "LC012 analyzer SaveChanges detection and context-aliasing safety should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class OptimizeRemoveRangeAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool HasSubsequentSaveChangesInvocation", analyzerSource); + Assert.DoesNotContain("private static bool TryResolveFreshContextLocal", analyzerSource); + + var saveChangesSafetySource = File.ReadAllText(saveChangesSafetyPath); + Assert.Contains("private static bool HasSubsequentSaveChangesInvocation", saveChangesSafetySource); + Assert.Contains("private static bool TryResolveFreshContextLocal", saveChangesSafetySource); + } + + [Fact] + public void LC012_AnalyzerBranchExclusion_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC012_OptimizeRemoveRange"); + var saveChangesSafetyPath = Path.Combine(analyzerDir, "OptimizeRemoveRangeAnalyzerSaveChangesSafety.cs"); + var branchExclusionPath = Path.Combine(analyzerDir, "OptimizeRemoveRangeAnalyzerBranchExclusion.cs"); + + Assert.True(File.Exists(branchExclusionPath), "LC012 analyzer if/switch mutual-exclusion checks should live in a focused partial file."); + + var saveChangesSafetySource = File.ReadAllText(saveChangesSafetyPath); + Assert.Contains("private static bool HasSubsequentSaveChangesInvocation", saveChangesSafetySource); + Assert.DoesNotContain("private static bool AreMutuallyExclusiveBranches", saveChangesSafetySource); + Assert.DoesNotContain("private static SyntaxNode? GetContainingIfBranch", saveChangesSafetySource); + Assert.DoesNotContain("private static SwitchSectionSyntax? GetContainingSwitchSection", saveChangesSafetySource); + + var branchExclusionSource = File.ReadAllText(branchExclusionPath); + Assert.Contains("private static bool AreMutuallyExclusiveBranches", branchExclusionSource); + Assert.Contains("private static SyntaxNode? GetContainingIfBranch", branchExclusionSource); + Assert.Contains("private static SwitchSectionSyntax? GetContainingSwitchSection", branchExclusionSource); + Assert.DoesNotContain("private static bool HasSubsequentSaveChangesInvocation", branchExclusionSource); + } + + [Fact] + public void LC013_AssignedOriginResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC013_DisposedContextQuery"); + var originPath = Path.Combine(analyzerDir, "DisposedContextQueryOriginResolution.cs"); + var assignedOriginPath = Path.Combine(analyzerDir, "DisposedContextQueryAssignedOriginResolution.cs"); + + Assert.True(File.Exists(assignedOriginPath), "LC013 assigned local origin resolution should live in a focused partial file."); + + var originSource = File.ReadAllText(originPath); + Assert.Contains("public sealed partial class DisposedContextQueryAnalyzer", originSource); + Assert.DoesNotContain("private static bool TryResolveAssignedDisposedContextOrigin", originSource); + Assert.DoesNotContain("private static bool TryGetSharedAssignedDisposedContextOrigin", originSource); + Assert.DoesNotContain("private static List GetAssignedValues", originSource); + Assert.DoesNotContain("private static IEnumerable EnumerateOperations", originSource); + + var assignedOriginSource = File.ReadAllText(assignedOriginPath); + Assert.Contains("private static bool TryResolveAssignedDisposedContextOrigin", assignedOriginSource); + Assert.Contains("private static bool TryGetSharedAssignedDisposedContextOrigin", assignedOriginSource); + Assert.DoesNotContain("private static List GetAssignedValues", assignedOriginSource); + Assert.DoesNotContain("private static IEnumerable EnumerateOperations", assignedOriginSource); + } + + [Fact] + public void LC016_ExpressionBodyFixer_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC016_AvoidDateTimeNow"); + var fixerPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixer.cs"); + var expressionBodyPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixerExpressionBody.cs"); + + Assert.True(File.Exists(expressionBodyPath), "LC016 expression-bodied member rewrite logic should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class AvoidDateTimeNowFixer", fixerSource); + Assert.DoesNotContain("private static Document ConvertExpressionBodiedMember", fixerSource); + Assert.DoesNotContain("private static IReadOnlyList BuildExpressionBodyReplacements", fixerSource); + + var expressionBodySource = File.ReadAllText(expressionBodyPath); + Assert.Contains("private static Document ConvertExpressionBodiedMember", expressionBodySource); + Assert.DoesNotContain("private static IReadOnlyList BuildExpressionBodyReplacements", expressionBodySource); + } + + [Fact] + public void LC016_ExpressionBodyStatements_LiveInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC016_AvoidDateTimeNow"); + var expressionBodyPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixerExpressionBody.cs"); + var statementPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixerExpressionBodyStatements.cs"); + + Assert.True(File.Exists(statementPath), "LC016 expression-bodied return/expression-statement selection should live in a focused partial file."); + + var expressionBodySource = File.ReadAllText(expressionBodyPath); + Assert.DoesNotContain("private static StatementSyntax CreateExpressionBodyStatement", expressionBodySource); + Assert.DoesNotContain("private static bool RequiresExpressionStatement", expressionBodySource); + Assert.DoesNotContain("private static bool IsNonGenericTaskLike", expressionBodySource); + + var statementSource = File.ReadAllText(statementPath); + Assert.Contains("private static StatementSyntax CreateExpressionBodyStatement", statementSource); + Assert.Contains("private static bool RequiresExpressionStatement", statementSource); + Assert.Contains("private static bool IsNonGenericTaskLike", statementSource); + } + + [Fact] + public void RuleCatalog_LC016ToLC030Entries_LiveInDedicatedPartial() + { + var catalogDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Catalog"); + var catalogPath = Path.Combine(catalogDir, "RuleCatalog.cs"); + var lc016ToLc030Path = Path.Combine(catalogDir, "RuleCatalog.LC016ToLC030.cs"); + + Assert.True(File.Exists(lc016ToLc030Path), "LC016-LC030 catalog entries should live in a focused partial file."); + + var catalogSource = File.ReadAllText(catalogPath); + Assert.Contains("public static partial class RuleCatalog", catalogSource); + Assert.DoesNotContain("id: \"LC016\"", catalogSource); + Assert.DoesNotContain("id: \"LC030\"", catalogSource); + + var lc016ToLc030Source = File.ReadAllText(lc016ToLc030Path); + Assert.Contains("private static ImmutableArray CreateLC016ToLC030Entries", lc016ToLc030Source); + Assert.Contains("id: \"LC016\"", lc016ToLc030Source); + Assert.Contains("id: \"LC030\"", lc016ToLc030Source); + } + + [Fact] + public void RuleCatalog_LC031ToLC045Entries_LiveInDedicatedPartial() + { + var catalogDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Catalog"); + var catalogPath = Path.Combine(catalogDir, "RuleCatalog.cs"); + var lc031ToLc045Path = Path.Combine(catalogDir, "RuleCatalog.LC031ToLC045.cs"); + + Assert.True(File.Exists(lc031ToLc045Path), "LC031-LC045 catalog entries should live in a focused partial file."); + + var catalogSource = File.ReadAllText(catalogPath); + Assert.Contains("public static partial class RuleCatalog", catalogSource); + Assert.DoesNotContain("id: \"LC031\"", catalogSource); + Assert.DoesNotContain("id: \"LC045\"", catalogSource); + + var lc031ToLc045Source = File.ReadAllText(lc031ToLc045Path); + Assert.Contains("private static ImmutableArray CreateLC031ToLC045Entries", lc031ToLc045Source); + Assert.Contains("id: \"LC031\"", lc031ToLc045Source); + Assert.Contains("id: \"LC045\"", lc031ToLc045Source); + } + + [Fact] + public void LC044_ReachabilityAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC044_AsNoTrackingThenModify"); + var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyAnalyzer.cs"); + var reachabilityPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyReachability.cs"); + var terminatorsPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyReachabilityTerminators.cs"); + + Assert.True(File.Exists(reachabilityPath), "LC044 reachability/control-flow analysis should live in a focused partial file."); + Assert.True(File.Exists(terminatorsPath), "LC044 reachability terminator and branch-blocking rules should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AsNoTrackingThenModifyAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool BlockReaches", analyzerSource); + Assert.DoesNotContain("private static bool HasTerminatorBetween", analyzerSource); + + var reachabilitySource = File.ReadAllText(reachabilityPath); + Assert.Contains("private static bool BlockReaches", reachabilitySource); + Assert.DoesNotContain("private static bool HasTerminatorBetween", reachabilitySource); + + var terminatorsSource = File.ReadAllText(terminatorsPath); + Assert.Contains("private static bool HasTerminatorBetween", terminatorsSource); + Assert.Contains("private static bool IsBreakBlocking", terminatorsSource); + Assert.Contains("private static bool IsContinueBlocking", terminatorsSource); + } + + [Fact] + public void LC044_TrackingStateAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC044_AsNoTrackingThenModify"); + var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyAnalyzer.cs"); + var trackingStatePath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyTrackingState.cs"); + + Assert.True(File.Exists(trackingStatePath), "LC044 reattach/save-state analysis should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AsNoTrackingThenModifyAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool HasDominatingPriorReattach", analyzerSource); + Assert.DoesNotContain("private static bool HasInterveningDetach", analyzerSource); + + var trackingStateSource = File.ReadAllText(trackingStatePath); + Assert.Contains("private static bool HasDominatingPriorReattach", trackingStateSource); + Assert.DoesNotContain("private static bool HasInterveningDetach", trackingStateSource); + } + + [Fact] + public void LC044_RootScanTrackingStateParsing_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC044_AsNoTrackingThenModify"); + var rootScanPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScan.cs"); + var parsingPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScanTrackingState.cs"); + + Assert.True(File.Exists(parsingPath), "LC044 root-scan reattach/detach parsing should live in a focused partial file."); + + var rootScanSource = File.ReadAllText(rootScanPath); + Assert.Contains("internal sealed partial class AsNoTrackingThenModifyRootScan", rootScanSource); + Assert.DoesNotContain("private static bool TryParseReattachInvocation", rootScanSource); + Assert.DoesNotContain("private static bool TryParseEntryStateAssignment", rootScanSource); + + var parsingSource = File.ReadAllText(parsingPath); + Assert.Contains("private static bool TryParseReattachInvocation", parsingSource); + Assert.Contains("private static bool TryParseEntryStateAssignment", parsingSource); + } + + [Fact] + public void LC044_RootScanBuckets_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC044_AsNoTrackingThenModify"); + var rootScanPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScan.cs"); + var bucketsPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyRootScanBuckets.cs"); + + Assert.True(File.Exists(bucketsPath), "LC044 root-scan bucket/index helpers should live in a focused partial file."); + + var rootScanSource = File.ReadAllText(rootScanPath); + Assert.DoesNotContain("internal static bool TryGetSymbol", rootScanSource); + Assert.DoesNotContain("private static void AddMutation", rootScanSource); + Assert.DoesNotContain("private static void AddReattach", rootScanSource); + Assert.DoesNotContain("private static void AddDetach", rootScanSource); + + var bucketsSource = File.ReadAllText(bucketsPath); + Assert.Contains("internal static bool TryGetSymbol", bucketsSource); + Assert.Contains("private static void AddMutation", bucketsSource); + Assert.Contains("private static void AddReattach", bucketsSource); + Assert.Contains("private static void AddDetach", bucketsSource); + Assert.Contains("private static void AddToBucket", bucketsSource); + } + + [Fact] + public void LC045_ConditionalAccessAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC045_MissingInclude"); + var usagePath = Path.Combine(analyzerDir, "MissingIncludeUsageAnalysis.cs"); + var conditionalAccessPath = Path.Combine(analyzerDir, "MissingIncludeConditionalAccessAnalysis.cs"); + + Assert.True(File.Exists(conditionalAccessPath), "LC045 conditional-access path analysis should live in a focused partial file."); + + var usageSource = File.ReadAllText(usagePath); + Assert.DoesNotContain("private static IOperation? FindConditionalAccessEntryProperty", usageSource); + Assert.DoesNotContain("private static IOperation? ResolveConditionalAccessReceiver", usageSource); + + var conditionalAccessSource = File.ReadAllText(conditionalAccessPath); + Assert.Contains("private static IOperation? FindConditionalAccessEntryProperty", conditionalAccessSource); + Assert.Contains("private static IOperation? ResolveConditionalAccessReceiver", conditionalAccessSource); + } + + [Fact] + public void LC045_NavigationPathResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC045_MissingInclude"); + var usagePath = Path.Combine(analyzerDir, "MissingIncludeUsageAnalysis.cs"); + var pathResolutionPath = Path.Combine(analyzerDir, "MissingIncludeNavigationPathAnalysis.cs"); + + Assert.True(File.Exists(pathResolutionPath), "LC045 navigation-path resolution should live in a focused partial file."); + + var usageSource = File.ReadAllText(usagePath); + Assert.DoesNotContain("private static bool TryGetAccessPath", usageSource); + Assert.DoesNotContain("private static bool TryResolveNavigationTargetForPath", usageSource); + + var pathResolutionSource = File.ReadAllText(pathResolutionPath); + Assert.Contains("private static bool TryGetAccessPath", pathResolutionSource); + Assert.Contains("private static bool TryResolveNavigationTargetForPath", pathResolutionSource); + } + + [Fact] + public void LC037_LocalWriteReachability_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var detectionPath = Path.Combine(analyzerDir, "RawSqlStringConstructionDetection.cs"); + var reachabilityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionReachability.cs"); + + Assert.True(File.Exists(reachabilityPath), "LC037 local-write reachability analysis should live in a focused partial file."); + + var detectionSource = File.ReadAllText(detectionPath); + Assert.DoesNotContain("private static bool CanWriteReachLaterLoopIteration", detectionSource); + Assert.DoesNotContain("private static bool CanOperationReachReference", detectionSource); + + var reachabilitySource = File.ReadAllText(reachabilityPath); + Assert.DoesNotContain("private static bool CanWriteReachLaterLoopIteration", reachabilitySource); + Assert.Contains("private static bool CanOperationReachReference", reachabilitySource); + } + + [Fact] + public void LC037_ExceptionFlowReachability_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var reachabilityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionReachability.cs"); + var exceptionFlowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionExceptionFlow.cs"); + + Assert.True(File.Exists(exceptionFlowPath), "LC037 throw/catch reachability analysis should live in a focused partial file."); + + var reachabilitySource = File.ReadAllText(reachabilityPath); + Assert.DoesNotContain("private static bool ThrowCanContinueThroughCatch", reachabilitySource); + Assert.DoesNotContain("private static bool IsKnownExceptionBase", reachabilitySource); + + var exceptionFlowSource = File.ReadAllText(exceptionFlowPath); + Assert.Contains("private static bool ThrowCanContinueThroughCatch", exceptionFlowSource); + } + + [Fact] + public void LC037_ExceptionTypeResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var exceptionFlowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionExceptionFlow.cs"); + var exceptionTypesPath = Path.Combine(analyzerDir, "RawSqlStringConstructionExceptionTypes.cs"); + + Assert.True(File.Exists(exceptionTypesPath), "LC037 exception type-name and hierarchy matching should live in a focused partial file."); + + var exceptionFlowSource = File.ReadAllText(exceptionFlowPath); + Assert.DoesNotContain("private static string? GetSimpleTypeName", exceptionFlowSource); + Assert.DoesNotContain("private static string? GetResolvedSimpleTypeName", exceptionFlowSource); + Assert.DoesNotContain("private static bool HasLocalExceptionBase", exceptionFlowSource); + Assert.DoesNotContain("private static bool IsKnownExceptionBase", exceptionFlowSource); + + var exceptionTypesSource = File.ReadAllText(exceptionTypesPath); + Assert.Contains("private static string? GetSimpleTypeName", exceptionTypesSource); + Assert.Contains("private static string? GetResolvedSimpleTypeName", exceptionTypesSource); + Assert.Contains("private static bool HasLocalExceptionBase", exceptionTypesSource); + Assert.Contains("private static bool IsKnownExceptionBase", exceptionTypesSource); + } + + [Fact] + public void LC037_TerminationFlowReachability_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var reachabilityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionReachability.cs"); + var terminationFlowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionTerminationFlow.cs"); + + Assert.True(File.Exists(terminationFlowPath), "LC037 branch, jump, and terminator reachability should live in a focused partial file."); + + var reachabilitySource = File.ReadAllText(reachabilityPath); + Assert.DoesNotContain("private static bool BlockTerminatesAfterNode", reachabilitySource); + Assert.DoesNotContain("private static bool StatementTerminates", reachabilitySource); + + var terminationFlowSource = File.ReadAllText(terminationFlowPath); + Assert.Contains("private static bool BlockTerminatesAfterNode", terminationFlowSource); + Assert.Contains("private static bool StatementTerminates", terminationFlowSource); + } + + [Fact] + public void LC037_StringBuilderFlowTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC037_RawSqlStringConstruction"); + var generalTestsPath = Path.Combine(testDir, "RawSqlStringConstructionTests.cs"); + var stringBuilderTestsPath = Path.Combine(testDir, "RawSqlStringConstructionStringBuilderTests.cs"); + + Assert.True(File.Exists(stringBuilderTestsPath), "LC037 StringBuilder flow tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class RawSqlStringConstructionTests", generalTestsSource); + Assert.DoesNotContain("ExecuteSqlRaw_WithStringBuilderAliasAndLaterAppend_ShouldTrigger", generalTestsSource); + Assert.DoesNotContain("ExecuteSqlRaw_WithStringBuilderInitializerAppendsClearedBeforeCall_ShouldNotTrigger", generalTestsSource); + + var stringBuilderTestsSource = File.ReadAllText(stringBuilderTestsPath); + Assert.Contains("public partial class RawSqlStringConstructionTests", stringBuilderTestsSource); + Assert.Contains("ExecuteSqlRaw_WithStringBuilderAliasAndLaterAppend_ShouldTrigger", stringBuilderTestsSource); + Assert.Contains("ExecuteSqlRaw_WithStringBuilderInitializerAppendsClearedBeforeCall_ShouldNotTrigger", stringBuilderTestsSource); + } + + [Fact] + public void LC037_StringBuilderReachabilityTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC037_RawSqlStringConstruction"); + var generalTestsPath = Path.Combine(testDir, "RawSqlStringConstructionTests.cs"); + var reachabilityTestsPath = Path.Combine(testDir, "RawSqlStringConstructionStringBuilderReachabilityTests.cs"); + + Assert.True(File.Exists(reachabilityTestsPath), "LC037 StringBuilder reachability tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.DoesNotContain("ExecuteSqlRaw_WithStringBuilderAppendBeforeCaughtThrow_ShouldTrigger", generalTestsSource); + Assert.DoesNotContain("ExecuteSqlRaw_WithStringBuilderConstructorTaintClearedInFluentChain_ShouldNotTrigger", generalTestsSource); + + var reachabilityTestsSource = File.ReadAllText(reachabilityTestsPath); + Assert.Contains("public partial class RawSqlStringConstructionTests", reachabilityTestsSource); + Assert.Contains("ExecuteSqlRaw_WithStringBuilderAppendBeforeCaughtThrow_ShouldTrigger", reachabilityTestsSource); + Assert.Contains("ExecuteSqlRaw_WithStringBuilderConstructorTaintClearedInFluentChain_ShouldNotTrigger", reachabilityTestsSource); + } + + [Fact] + public void LC037_LocalWriteFlowTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC037_RawSqlStringConstruction"); + var generalTestsPath = Path.Combine(testDir, "RawSqlStringConstructionTests.cs"); + var localWriteTestsPath = Path.Combine(testDir, "RawSqlStringConstructionLocalWriteTests.cs"); + + Assert.True(File.Exists(localWriteTestsPath), "LC037 local-write flow tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.DoesNotContain("ExecuteSqlRaw_WithConstructedInitialValueOverwrittenByConstantBeforeCall_ShouldNotTrigger", generalTestsSource); + Assert.DoesNotContain("ExecuteSqlRaw_WithConditionalConstructedSqlOverwrittenByConstantBeforeCall_ShouldNotTrigger", generalTestsSource); + + var localWriteTestsSource = File.ReadAllText(localWriteTestsPath); + Assert.Contains("public partial class RawSqlStringConstructionTests", localWriteTestsSource); + Assert.Contains("ExecuteSqlRaw_WithConstructedInitialValueOverwrittenByConstantBeforeCall_ShouldNotTrigger", localWriteTestsSource); + Assert.Contains("ExecuteSqlRaw_WithConditionalConstructedSqlOverwrittenByConstantBeforeCall_ShouldNotTrigger", localWriteTestsSource); + } + + [Fact] + public void LC018_ProviderVariantTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC018_AvoidFromSqlRawWithInterpolation"); + var generalTestsPath = Path.Combine(testDir, "AvoidFromSqlRawWithInterpolationTests.cs"); + var providerTestsPath = Path.Combine(testDir, "AvoidFromSqlRawWithInterpolationProviderVariantTests.cs"); + + Assert.True(File.Exists(providerTestsPath), "LC018 provider-variant and SqlQueryRaw coverage should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class AvoidFromSqlRawWithInterpolationTests", generalTestsSource); + Assert.DoesNotContain("FromSqlRaw_OnDbSet_WithUnsafeInterpolation_ShouldTrigger", generalTestsSource); + Assert.DoesNotContain("SqlQueryRaw_WithInterpolatedString_ShouldTriggerLC018", generalTestsSource); + + var providerTestsSource = File.ReadAllText(providerTestsPath); + Assert.Contains("public partial class AvoidFromSqlRawWithInterpolationTests", providerTestsSource); + Assert.Contains("FromSqlRaw_OnDbSet_WithUnsafeInterpolation_ShouldTrigger", providerTestsSource); + Assert.Contains("SqlQueryRaw_WithInterpolatedString_ShouldTriggerLC018", providerTestsSource); + } + + [Fact] + public void LC032_FixerSafetyTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC032_ExecuteUpdateForBulkUpdates"); + var fixerTestsPath = Path.Combine(testDir, "ExecuteUpdateForBulkUpdatesFixerTests.cs"); + var safetyTestsPath = Path.Combine(testDir, "ExecuteUpdateForBulkUpdatesFixerSafetyTests.cs"); + + Assert.True(File.Exists(safetyTestsPath), "LC032 fixer safety and decline cases should live in a focused partial test file."); + + var fixerTestsSource = File.ReadAllText(fixerTestsPath); + Assert.Contains("public partial class ExecuteUpdateForBulkUpdatesFixerTests", fixerTestsSource); + Assert.DoesNotContain("Fixer_DuplicateProperty_LaterReadsEarlierWrite_DoesNotRegister", fixerTestsSource); + Assert.DoesNotContain("Fixer_SyncLocalFunctionInsideAsyncMethod_UsesSyncExecuteUpdate", fixerTestsSource); + + var safetyTestsSource = File.ReadAllText(safetyTestsPath); + Assert.Contains("public partial class ExecuteUpdateForBulkUpdatesFixerTests", safetyTestsSource); + Assert.Contains("Fixer_DuplicateProperty_LaterReadsEarlierWrite_DoesNotRegister", safetyTestsSource); + Assert.Contains("Fixer_SyncLocalFunctionInsideAsyncMethod_UsesSyncExecuteUpdate", safetyTestsSource); + } + + [Fact] + public void LC032_FixerQuerySourceTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC032_ExecuteUpdateForBulkUpdates"); + var fixerTestsPath = Path.Combine(testDir, "ExecuteUpdateForBulkUpdatesFixerTests.cs"); + var querySourceTestsPath = Path.Combine(testDir, "ExecuteUpdateForBulkUpdatesFixerQuerySourceTests.cs"); + + Assert.True(File.Exists(querySourceTestsPath), "LC032 query-source and materializer fixer coverage should live in a focused partial test file."); + + var fixerTestsSource = File.ReadAllText(fixerTestsPath); + Assert.Contains("public partial class ExecuteUpdateForBulkUpdatesFixerTests", fixerTestsSource); + Assert.DoesNotContain("Fixer_DbContextSetSource_Rewrites", fixerTestsSource); + Assert.DoesNotContain("Fixer_AwaitedInlineToListAsync_StripsMaterializer", fixerTestsSource); + + var querySourceTestsSource = File.ReadAllText(querySourceTestsPath); + Assert.Contains("public partial class ExecuteUpdateForBulkUpdatesFixerTests", querySourceTestsSource); + Assert.Contains("Fixer_DbContextSetSource_Rewrites", querySourceTestsSource); + Assert.Contains("Fixer_AwaitedInlineToListAsync_StripsMaterializer", querySourceTestsSource); + } + + [Fact] + public void LC035_ConditionalFlowTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC035_MissingWhereBeforeExecuteDeleteUpdate"); + var generalTestsPath = Path.Combine(testDir, "MissingWhereBeforeExecuteDeleteUpdateTests.cs"); + var conditionalFlowTestsPath = Path.Combine(testDir, "MissingWhereBeforeExecuteDeleteUpdateConditionalFlowTests.cs"); + + Assert.True(File.Exists(conditionalFlowTestsPath), "LC035 conditional-flow and reassignment tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class MissingWhereBeforeExecuteDeleteUpdateTests", generalTestsSource); + Assert.DoesNotContain("ExecuteDelete_UnconditionalFilterThenOptionalNarrowing_ShouldNotTrigger", generalTestsSource); + Assert.DoesNotContain("ExecuteUpdate_CatchPathReassignsToUnfilteredQuery_ShouldTrigger", generalTestsSource); + + var conditionalFlowTestsSource = File.ReadAllText(conditionalFlowTestsPath); + Assert.Contains("public partial class MissingWhereBeforeExecuteDeleteUpdateTests", conditionalFlowTestsSource); + Assert.Contains("ExecuteDelete_UnconditionalFilterThenOptionalNarrowing_ShouldNotTrigger", conditionalFlowTestsSource); + Assert.Contains("ExecuteUpdate_CatchPathReassignsToUnfilteredQuery_ShouldTrigger", conditionalFlowTestsSource); + } + + [Fact] + public void LC001_StaticQueryableFixerTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC001_LocalMethod"); + var fixerTestsPath = Path.Combine(testDir, "LocalMethodFixerTests.cs"); + var staticQueryableTestsPath = Path.Combine(testDir, "LocalMethodFixerStaticQueryableTests.cs"); + + Assert.True(File.Exists(staticQueryableTestsPath), "LC001 static Queryable fixer coverage should live in a focused partial test file."); + + var fixerTestsSource = File.ReadAllText(fixerTestsPath); + Assert.Contains("public partial class LocalMethodFixerTests", fixerTestsSource); + Assert.DoesNotContain("FixCrime_StaticQueryableWhere_SwitchesToEnumerable", fixerTestsSource); + Assert.DoesNotContain("FixCrime_StaticQueryableThenBy_RewritesExtensionOrderedSourceChain", fixerTestsSource); + + var staticQueryableTestsSource = File.ReadAllText(staticQueryableTestsPath); + Assert.Contains("public partial class LocalMethodFixerTests", staticQueryableTestsSource); + Assert.Contains("FixCrime_StaticQueryableWhere_SwitchesToEnumerable", staticQueryableTestsSource); + Assert.Contains("FixCrime_StaticQueryableThenBy_RewritesExtensionOrderedSourceChain", staticQueryableTestsSource); + } + + [Fact] + public void LC001_StaticQueryableRewrite_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC001_LocalMethod"); + var fixerPath = Path.Combine(fixerDir, "LocalMethodFixer.cs"); + var staticQueryableRewritePath = Path.Combine(fixerDir, "LocalMethodFixerStaticQueryableRewrite.cs"); + + Assert.True(File.Exists(staticQueryableRewritePath), "LC001 static Queryable rewrite logic should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class LocalMethodFixer", fixerSource); + Assert.DoesNotContain("private static bool RewriteStaticQueryableInvocation", fixerSource); + Assert.DoesNotContain("private static bool IsSystemLinqQueryableType", fixerSource); + + var staticQueryableRewriteSource = File.ReadAllText(staticQueryableRewritePath); + Assert.Contains("private static bool RewriteStaticQueryableInvocation", staticQueryableRewriteSource); + Assert.DoesNotContain("private static bool IsSystemLinqQueryableType", staticQueryableRewriteSource); + } + + [Fact] + public void LC001_StaticQueryableQualifiers_LiveInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC001_LocalMethod"); + var staticQueryableRewritePath = Path.Combine(fixerDir, "LocalMethodFixerStaticQueryableRewrite.cs"); + var staticQueryableQualifiersPath = Path.Combine(fixerDir, "LocalMethodFixerStaticQueryableQualifiers.cs"); + + Assert.True(File.Exists(staticQueryableQualifiersPath), "LC001 static Queryable qualifier and method classification should live in a focused partial file."); + + var staticQueryableRewriteSource = File.ReadAllText(staticQueryableRewritePath); + Assert.DoesNotContain("private static ExpressionSyntax CreateEnumerableQualifier", staticQueryableRewriteSource); + Assert.DoesNotContain("private static bool IsSystemLinqQueryableType", staticQueryableRewriteSource); + Assert.DoesNotContain("private static bool CanSwitchStaticQueryableMethodToEnumerable", staticQueryableRewriteSource); + + var staticQueryableQualifiersSource = File.ReadAllText(staticQueryableQualifiersPath); + Assert.Contains("private static ExpressionSyntax CreateEnumerableQualifier", staticQueryableQualifiersSource); + Assert.Contains("private static bool IsSystemLinqQueryableType", staticQueryableQualifiersSource); + Assert.Contains("private static bool CanSwitchStaticQueryableMethodToEnumerable", staticQueryableQualifiersSource); + } + + [Fact] + public void LC001_QueryDiscovery_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC001_LocalMethod"); + var fixerPath = Path.Combine(fixerDir, "LocalMethodFixer.cs"); + var queryDiscoveryPath = Path.Combine(fixerDir, "LocalMethodFixerQueryDiscovery.cs"); + + Assert.True(File.Exists(queryDiscoveryPath), "LC001 query invocation discovery and rewrite eligibility should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.DoesNotContain("private static InvocationExpressionSyntax? FindQueryInvocation", fixerSource); + Assert.DoesNotContain("private static bool IsNestedQueryInvocation", fixerSource); + Assert.DoesNotContain("private static bool CanRewriteQueryInvocation", fixerSource); + + var queryDiscoverySource = File.ReadAllText(queryDiscoveryPath); + Assert.Contains("private static InvocationExpressionSyntax? FindQueryInvocation", queryDiscoverySource); + Assert.Contains("private static bool IsNestedQueryInvocation", queryDiscoverySource); + Assert.Contains("private static bool CanRewriteQueryInvocation", queryDiscoverySource); + } + + [Fact] + public void LC001_AnalyzerTrustPolicy_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC001_LocalMethod"); + var analyzerPath = Path.Combine(analyzerDir, "LocalMethodAnalyzer.cs"); + var trustPolicyPath = Path.Combine(analyzerDir, "LocalMethodAnalyzerTrustPolicy.cs"); + + Assert.True(File.Exists(trustPolicyPath), "LC001 trusted method and translation-marker policy should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class LocalMethodAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool IsTrustedTranslatableMethod", analyzerSource); + Assert.DoesNotContain("private static bool HasExplicitTranslationMarker", analyzerSource); + Assert.DoesNotContain("private static IEnumerable EnumerateMethodVariants", analyzerSource); + + var trustPolicySource = File.ReadAllText(trustPolicyPath); + Assert.Contains("private static bool IsTrustedTranslatableMethod", trustPolicySource); + Assert.Contains("private static bool HasExplicitTranslationMarker", trustPolicySource); + Assert.Contains("private static IEnumerable EnumerateMethodVariants", trustPolicySource); + } + + [Fact] + public void LC036_CapturedContextAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC036_DbContextCapturedAcrossThreads"); + var analyzerPath = Path.Combine(analyzerDir, "DbContextCapturedAcrossThreadsAnalyzer.cs"); + var captureAnalysisPath = Path.Combine(analyzerDir, "DbContextCapturedAcrossThreadsCaptureAnalysis.cs"); + + Assert.True(File.Exists(captureAnalysisPath), "LC036 captured DbContext syntax/symbol analysis should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class DbContextCapturedAcrossThreadsAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool TryFindCapturedDbContext", analyzerSource); + Assert.DoesNotContain("private static bool TryFindCapturedDbContextInLambda", analyzerSource); + Assert.DoesNotContain("private static bool IsCapturedDbContext", analyzerSource); + + var captureAnalysisSource = File.ReadAllText(captureAnalysisPath); + Assert.Contains("private static bool TryFindCapturedDbContext", captureAnalysisSource); + Assert.Contains("private static bool TryFindCapturedDbContextInLambda", captureAnalysisSource); + Assert.Contains("private static bool IsCapturedDbContext", captureAnalysisSource); + } + + [Fact] + public void LC007_LocalWriteTracking_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC007_NPlusOneLooper"); + var analysisPath = Path.Combine(analyzerDir, "NPlusOneLooperAnalysis.cs"); + var localWritePath = Path.Combine(analyzerDir, "NPlusOneLooperLocalWriteAnalysis.cs"); + + Assert.True(File.Exists(localWritePath), "LC007 local-write tracking should live in a focused partial file."); + + var analysisSource = File.ReadAllText(analysisPath); + Assert.Contains("internal static partial class NPlusOneLooperAnalysis", analysisSource); + Assert.DoesNotContain("private sealed class LocalWriteCache", analysisSource); + Assert.DoesNotContain("private static bool TryGetSingleAssignedLocalValue", analysisSource); + + var localWriteSource = File.ReadAllText(localWritePath); + Assert.Contains("private sealed class LocalWriteCache", localWriteSource); + Assert.Contains("private static bool TryGetSingleAssignedLocalValue", localWriteSource); + } + + [Fact] + public void LC045_ConditionalAccessEdgeCaseTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC045_MissingInclude"); + var edgeCasesPath = Path.Combine(testDir, "MissingIncludeEdgeCasesTests.cs"); + var conditionalAccessTestsPath = Path.Combine(testDir, "MissingIncludeConditionalAccessEdgeCasesTests.cs"); + + Assert.True(File.Exists(conditionalAccessTestsPath), "LC045 conditional-access edge cases should live in a focused partial test file."); + + var edgeCasesSource = File.ReadAllText(edgeCasesPath); + Assert.Contains("public partial class MissingIncludeEdgeCasesTests", edgeCasesSource); + Assert.DoesNotContain("TestInnocent_ConditionalCollectionMutatorCall_NoDiagnostic", edgeCasesSource); + Assert.DoesNotContain("TestCrime_InheritedNavigationParenthesizedConditionalAccessReportsFullNestedPath", edgeCasesSource); + + var conditionalAccessTestsSource = File.ReadAllText(conditionalAccessTestsPath); + Assert.Contains("public partial class MissingIncludeEdgeCasesTests", conditionalAccessTestsSource); + Assert.Contains("TestInnocent_ConditionalCollectionMutatorCall_NoDiagnostic", conditionalAccessTestsSource); + Assert.Contains("TestCrime_InheritedNavigationParenthesizedConditionalAccessReportsFullNestedPath", conditionalAccessTestsSource); + } + + [Fact] + public void LC045_ConditionalAccessCrimeTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC045_MissingInclude"); + var generalTestsPath = Path.Combine(testDir, "MissingIncludeTests.cs"); + var conditionalAccessTestsPath = Path.Combine(testDir, "MissingIncludeConditionalAccessTests.cs"); + + Assert.True(File.Exists(conditionalAccessTestsPath), "LC045 conditional-access crime cases should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class MissingIncludeTests", generalTestsSource); + Assert.DoesNotContain("TestCrime_ConditionalAccessNav_TriggersDiagnostic", generalTestsSource); + Assert.DoesNotContain("TestCrime_LocalFromConditionalIndexer_TriggersDiagnostic", generalTestsSource); + + var conditionalAccessTestsSource = File.ReadAllText(conditionalAccessTestsPath); + Assert.Contains("public partial class MissingIncludeTests", conditionalAccessTestsSource); + Assert.Contains("TestCrime_ConditionalAccessNav_TriggersDiagnostic", conditionalAccessTestsSource); + Assert.Contains("TestCrime_LocalFromConditionalIndexer_TriggersDiagnostic", conditionalAccessTestsSource); + } + + [Fact] + public void LC045_CoveredNavigationTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC045_MissingInclude"); + var edgeCasesPath = Path.Combine(testDir, "MissingIncludeEdgeCasesTests.cs"); + var coveredNavigationTestsPath = Path.Combine(testDir, "MissingIncludeCoveredNavigationTests.cs"); + + Assert.True(File.Exists(coveredNavigationTestsPath), "LC045 include-covered navigation tests should live in a focused partial test file."); + + var edgeCasesSource = File.ReadAllText(edgeCasesPath); + Assert.Contains("public partial class MissingIncludeEdgeCasesTests", edgeCasesSource); + Assert.DoesNotContain("TestInnocent_LambdaIncludeCoversAccess_NoDiagnostic", edgeCasesSource); + Assert.DoesNotContain("TestInnocent_CastMidPathInclude_NoDiagnostic", edgeCasesSource); + + var coveredNavigationTestsSource = File.ReadAllText(coveredNavigationTestsPath); + Assert.Contains("public partial class MissingIncludeEdgeCasesTests", coveredNavigationTestsSource); + Assert.Contains("TestInnocent_LambdaIncludeCoversAccess_NoDiagnostic", coveredNavigationTestsSource); + Assert.Contains("TestInnocent_CastMidPathInclude_NoDiagnostic", coveredNavigationTestsSource); + } + + [Fact] + public void LC045_FixerSyntaxTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC045_MissingInclude"); + var fixerTestsPath = Path.Combine(testDir, "MissingIncludeFixerTests.cs"); + var syntaxTestsPath = Path.Combine(testDir, "MissingIncludeFixerSyntaxTests.cs"); + + Assert.True(File.Exists(syntaxTestsPath), "LC045 fixer syntax and fix-all edge cases should live in a focused partial test file."); + + var fixerTestsSource = File.ReadAllText(fixerTestsPath); + Assert.Contains("public partial class MissingIncludeFixerTests", fixerTestsSource); + Assert.DoesNotContain("FixCrime_StaticCallSyntax_WrapsTheArgumentNotTheTypeName", fixerTestsSource); + Assert.DoesNotContain("FixAll_AddsIncludeToEveryFlaggedQuery", fixerTestsSource); + + var syntaxTestsSource = File.ReadAllText(syntaxTestsPath); + Assert.Contains("public partial class MissingIncludeFixerTests", syntaxTestsSource); + Assert.Contains("FixCrime_StaticCallSyntax_WrapsTheArgumentNotTheTypeName", syntaxTestsSource); + Assert.Contains("FixAll_AddsIncludeToEveryFlaggedQuery", syntaxTestsSource); + } + + [Fact] + public void LC025_ReassignmentTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC025_AsNoTrackingWithUpdate"); + var generalTestsPath = Path.Combine(testDir, "AsNoTrackingWithUpdateTests.cs"); + var reassignmentTestsPath = Path.Combine(testDir, "AsNoTrackingWithUpdateReassignmentTests.cs"); + + Assert.True(File.Exists(reassignmentTestsPath), "LC025 reassignment and ambiguous-origin tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class AsNoTrackingWithUpdateTests", generalTestsSource); + Assert.DoesNotContain("ConditionallyReassignedToNoTracking_AmbiguousOrigin_ShouldNotTrigger", generalTestsSource); + Assert.DoesNotContain("ReassignedInsideSameBranchAsUpdate_ShouldStillTrigger", generalTestsSource); + + var reassignmentTestsSource = File.ReadAllText(reassignmentTestsPath); + Assert.Contains("public partial class AsNoTrackingWithUpdateTests", reassignmentTestsSource); + Assert.Contains("ConditionallyReassignedToNoTracking_AmbiguousOrigin_ShouldNotTrigger", reassignmentTestsSource); + Assert.Contains("ReassignedInsideSameBranchAsUpdate_ShouldStillTrigger", reassignmentTestsSource); + } + + [Fact] + public void LC025_ProjectionTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC025_AsNoTrackingWithUpdate"); + var generalTestsPath = Path.Combine(testDir, "AsNoTrackingWithUpdateTests.cs"); + var projectionTestsPath = Path.Combine(testDir, "AsNoTrackingWithUpdateProjectionTests.cs"); + + Assert.True(File.Exists(projectionTestsPath), "LC025 projection and query-alias tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class AsNoTrackingWithUpdateTests", generalTestsSource); + Assert.DoesNotContain("MaterializedFromNoTrackingQueryAlias_ShouldTriggerLC025", generalTestsSource); + Assert.DoesNotContain("IdentitySelect_ThenUpdate_ShouldStillTrigger", generalTestsSource); + + var projectionTestsSource = File.ReadAllText(projectionTestsPath); + Assert.Contains("public partial class AsNoTrackingWithUpdateTests", projectionTestsSource); + Assert.Contains("MaterializedFromNoTrackingQueryAlias_ShouldTriggerLC025", projectionTestsSource); + Assert.Contains("IdentitySelect_ThenUpdate_ShouldStillTrigger", projectionTestsSource); + } + + [Fact] + public void LC024_QuerySyntaxTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC024_GroupByNonTranslatable"); + var generalTestsPath = Path.Combine(testDir, "GroupByNonTranslatableTests.cs"); + var querySyntaxTestsPath = Path.Combine(testDir, "GroupByNonTranslatableQuerySyntaxTests.cs"); + + Assert.True(File.Exists(querySyntaxTestsPath), "LC024 query-syntax GroupBy coverage should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class GroupByNonTranslatableTests", generalTestsSource); + Assert.DoesNotContain("QuerySyntaxGroupBy_Select_ToList_ShouldTriggerLC024", generalTestsSource); + Assert.DoesNotContain("EnumerableQuerySyntaxGroupBy_Select_ClientProjection_ShouldNotTrigger", generalTestsSource); + + var querySyntaxTestsSource = File.ReadAllText(querySyntaxTestsPath); + Assert.Contains("public partial class GroupByNonTranslatableTests", querySyntaxTestsSource); + Assert.Contains("QuerySyntaxGroupBy_Select_ToList_ShouldTriggerLC024", querySyntaxTestsSource); + Assert.Contains("EnumerableQuerySyntaxGroupBy_Select_ClientProjection_ShouldNotTrigger", querySyntaxTestsSource); + } + + [Fact] + public void LC012_FixerContextSafetyTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC012_OptimizeRemoveRange"); + var fixerTestsPath = Path.Combine(testDir, "OptimizeRemoveRangeFixerTests.cs"); + var contextSafetyTestsPath = Path.Combine(testDir, "OptimizeRemoveRangeFixerContextSafetyTests.cs"); + + Assert.True(File.Exists(contextSafetyTestsPath), "LC012 fixer SaveChanges/context safety tests should live in a focused partial test file."); + + var fixerTestsSource = File.ReadAllText(fixerTestsPath); + Assert.Contains("public partial class OptimizeRemoveRangeFixerTests", fixerTestsSource); + Assert.DoesNotContain("Fixer_ShouldNotRegister_WhenRemoveRangeIsFollowedBySaveChanges", fixerTestsSource); + Assert.DoesNotContain("Fixer_ShouldNotRegister_WhenQueryCombinesLaterSaveContextSource", fixerTestsSource); + + var contextSafetyTestsSource = File.ReadAllText(contextSafetyTestsPath); + Assert.Contains("public partial class OptimizeRemoveRangeFixerTests", contextSafetyTestsSource); + Assert.Contains("Fixer_ShouldNotRegister_WhenRemoveRangeIsFollowedBySaveChanges", contextSafetyTestsSource); + Assert.Contains("Fixer_ShouldNotRegister_WhenQueryCombinesLaterSaveContextSource", contextSafetyTestsSource); + } + + [Fact] + public void LC012_AnalyzerSaveChangesTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC012_OptimizeRemoveRange"); + var analyzerTestsPath = Path.Combine(testDir, "OptimizeRemoveRangeAnalyzerTests.cs"); + var saveChangesTestsPath = Path.Combine(testDir, "OptimizeRemoveRangeAnalyzerSaveChangesTests.cs"); + + Assert.True(File.Exists(saveChangesTestsPath), "LC012 analyzer SaveChanges/control-flow suppression tests should live in a focused partial test file."); + + var analyzerTestsSource = File.ReadAllText(analyzerTestsPath); + Assert.Contains("public partial class OptimizeRemoveRangeAnalyzerTests", analyzerTestsSource); + Assert.DoesNotContain("RemoveRange_FollowedBySaveChanges_ShouldNotTrigger", analyzerTestsSource); + Assert.DoesNotContain("RemoveRange_InTryWithSaveChangesInCatch_ShouldNotTrigger", analyzerTestsSource); + + var saveChangesTestsSource = File.ReadAllText(saveChangesTestsPath); + Assert.Contains("public partial class OptimizeRemoveRangeAnalyzerTests", saveChangesTestsSource); + Assert.Contains("RemoveRange_FollowedBySaveChanges_ShouldNotTrigger", saveChangesTestsSource); + Assert.Contains("RemoveRange_InTryWithSaveChangesInCatch_ShouldNotTrigger", saveChangesTestsSource); + } + + [Fact] + public void LC014_ColumnDerivedArgumentTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC014_AvoidStringCaseConversion"); + var generalTestsPath = Path.Combine(testDir, "AvoidStringCaseConversionTests.cs"); + var argumentTestsPath = Path.Combine(testDir, "AvoidStringCaseConversionColumnDerivedArgumentTests.cs"); + + Assert.True(File.Exists(argumentTestsPath), "LC014 column-derived method argument tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class AvoidStringCaseConversionTests", generalTestsSource); + Assert.DoesNotContain("ToLower_OnMethodResultWithParameterArgument_ShouldTrigger", generalTestsSource); + Assert.DoesNotContain("ConstantReceiver_ColumnAsStringArgument_ShouldTrigger", generalTestsSource); + + var argumentTestsSource = File.ReadAllText(argumentTestsPath); + Assert.Contains("public partial class AvoidStringCaseConversionTests", argumentTestsSource); + Assert.Contains("ToLower_OnMethodResultWithParameterArgument_ShouldTrigger", argumentTestsSource); + Assert.Contains("ConstantReceiver_ColumnAsStringArgument_ShouldTrigger", argumentTestsSource); + } + + [Fact] + public void LC015_PositionOperatorTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC015_MissingOrderBy"); + var generalTestsPath = Path.Combine(testDir, "MissingOrderByTests.cs"); + var positionOperatorTestsPath = Path.Combine(testDir, "MissingOrderByPositionOperatorTests.cs"); + + Assert.True(File.Exists(positionOperatorTestsPath), "LC015 ElementAt/Last operator coverage should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class MissingOrderByTests", generalTestsSource); + Assert.DoesNotContain("ElementAt_WithoutOrderBy_ShouldTrigger", generalTestsSource); + Assert.DoesNotContain("TakeLast_IsNotFlagged_BecauseEfCannotTranslateItAtAll", generalTestsSource); + + var positionOperatorTestsSource = File.ReadAllText(positionOperatorTestsPath); + Assert.Contains("public partial class MissingOrderByTests", positionOperatorTestsSource); + Assert.Contains("ElementAt_WithoutOrderBy_ShouldTrigger", positionOperatorTestsSource); + Assert.Contains("TakeLast_IsNotFlagged_BecauseEfCannotTranslateItAtAll", positionOperatorTestsSource); + } + + [Fact] + public void LC023_QueryFilterTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC023_FindInsteadOfFirstOrDefault"); + var generalTestsPath = Path.Combine(testDir, "FindInsteadOfFirstOrDefaultTests.cs"); + var queryFilterTestsPath = Path.Combine(testDir, "FindInsteadOfFirstOrDefaultQueryFilterTests.cs"); + + Assert.True(File.Exists(queryFilterTestsPath), "LC023 query-filter gate tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class FindInsteadOfFirstOrDefaultTests", generalTestsSource); + Assert.DoesNotContain("FirstOrDefault_WithId_WhenEntityHasQueryFilter_ShouldNotTrigger", generalTestsSource); + Assert.DoesNotContain("FirstOrDefault_WithUnrelatedGenericHasQueryFilter_ShouldStillTrigger", generalTestsSource); + + var queryFilterTestsSource = File.ReadAllText(queryFilterTestsPath); + Assert.Contains("public partial class FindInsteadOfFirstOrDefaultTests", queryFilterTestsSource); + Assert.Contains("FirstOrDefault_WithId_WhenEntityHasQueryFilter_ShouldNotTrigger", queryFilterTestsSource); + Assert.Contains("FirstOrDefault_WithUnrelatedGenericHasQueryFilter_ShouldStillTrigger", queryFilterTestsSource); + } + + [Fact] + public void LC023_KeyConfigurationTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC023_FindInsteadOfFirstOrDefault"); + var generalTestsPath = Path.Combine(testDir, "FindInsteadOfFirstOrDefaultTests.cs"); + var keyConfigurationTestsPath = Path.Combine(testDir, "FindInsteadOfFirstOrDefaultKeyConfigurationTests.cs"); + + Assert.True(File.Exists(keyConfigurationTestsPath), "LC023 key-configuration tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class FindInsteadOfFirstOrDefaultTests", generalTestsSource); + Assert.DoesNotContain("FirstOrDefault_WithConventionId_WhenFluentApiConfiguresDifferentKey_ShouldNotTrigger", generalTestsSource); + Assert.DoesNotContain("FirstOrDefault_WithPartialCompositeFluentApiKey_ShouldNotTrigger", generalTestsSource); + + var keyConfigurationTestsSource = File.ReadAllText(keyConfigurationTestsPath); + Assert.Contains("public partial class FindInsteadOfFirstOrDefaultTests", keyConfigurationTestsSource); + Assert.Contains("FirstOrDefault_WithConventionId_WhenFluentApiConfiguresDifferentKey_ShouldNotTrigger", keyConfigurationTestsSource); + Assert.Contains("FirstOrDefault_WithPartialCompositeFluentApiKey_ShouldNotTrigger", keyConfigurationTestsSource); + } + + [Fact] + public void LC023_QueryFilterCache_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC023_FindInsteadOfFirstOrDefault"); + var keyAnalysisPath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultKeyAnalysis.cs"); + var primaryKeyCachePath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultPrimaryKeyCache.cs"); + var queryFilterCachePath = Path.Combine(analyzerDir, "FindInsteadOfFirstOrDefaultQueryFilterCache.cs"); + + Assert.True(File.Exists(primaryKeyCachePath), "LC023 primary-key cache state should live in a focused partial file."); + Assert.True(File.Exists(queryFilterCachePath), "LC023 query-filter cache registration and lookup should live in a focused partial file."); + + var keyAnalysisSource = File.ReadAllText(keyAnalysisPath); + Assert.Contains("internal static partial class FindInsteadOfFirstOrDefaultKeyAnalysis", keyAnalysisSource); + Assert.DoesNotContain("internal sealed partial class PrimaryKeyCache", keyAnalysisSource); + Assert.DoesNotContain("public void RegisterQueryFilter", keyAnalysisSource); + Assert.DoesNotContain("public bool HasQueryFilter", keyAnalysisSource); + Assert.DoesNotContain("private bool HasRegisteredQueryFilter", keyAnalysisSource); + + var primaryKeyCacheSource = File.ReadAllText(primaryKeyCachePath); + Assert.Contains("internal static partial class FindInsteadOfFirstOrDefaultKeyAnalysis", primaryKeyCacheSource); + Assert.Contains("internal sealed partial class PrimaryKeyCache", primaryKeyCacheSource); + Assert.DoesNotContain("public void RegisterQueryFilter", primaryKeyCacheSource); + Assert.DoesNotContain("public bool HasQueryFilter", primaryKeyCacheSource); + + var queryFilterCacheSource = File.ReadAllText(queryFilterCachePath); + Assert.Contains("internal static partial class FindInsteadOfFirstOrDefaultKeyAnalysis", queryFilterCacheSource); + Assert.Contains("internal sealed partial class PrimaryKeyCache", queryFilterCacheSource); + Assert.Contains("public void RegisterQueryFilter", queryFilterCacheSource); + Assert.Contains("public bool HasQueryFilter", queryFilterCacheSource); + Assert.Contains("private bool HasRegisteredQueryFilter", queryFilterCacheSource); + } + + [Fact] + public void LC023_FixerContext_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC023_FindInsteadOfFirstOrDefault"); + var fixerPath = Path.Combine(fixerDir, "FindInsteadOfFirstOrDefaultFixer.cs"); + var contextPath = Path.Combine(fixerDir, "FindInsteadOfFirstOrDefaultFixerContext.cs"); + + Assert.True(File.Exists(contextPath), "LC023 fixer context derivation should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class FindInsteadOfFirstOrDefaultFixer", fixerSource); + Assert.DoesNotContain("private static bool TryCreateFixContext", fixerSource); + Assert.DoesNotContain("private static bool TryGetKeyValueExpression", fixerSource); + Assert.DoesNotContain("private sealed class FixContext", fixerSource); + + var contextSource = File.ReadAllText(contextPath); + Assert.Contains("private static bool TryCreateFixContext", contextSource); + Assert.DoesNotContain("private static bool TryGetKeyValueExpression", contextSource); + Assert.Contains("private sealed class FixContext", contextSource); + } + + [Fact] + public void LC027_RelationshipBuilderLocalTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC027_MissingExplicitForeignKey"); + var generalTestsPath = Path.Combine(testDir, "MissingExplicitForeignKeyEdgeCasesTests.cs"); + var relationshipBuilderTestsPath = Path.Combine(testDir, "MissingExplicitForeignKeyRelationshipBuilderLocalTests.cs"); + + Assert.True(File.Exists(relationshipBuilderTestsPath), "LC027 relationship-builder local and shadowing tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class MissingExplicitForeignKeyEdgeCasesTests", generalTestsSource); + Assert.DoesNotContain("Navigation_WithRelationshipBuilderLocalShadowForeignKey_ShouldNotTrigger", generalTestsSource); + Assert.DoesNotContain("Navigation_WithMemberAssignmentSharingRelationshipBuilderLocalName_ShouldNotTrigger", generalTestsSource); + + var relationshipBuilderTestsSource = File.ReadAllText(relationshipBuilderTestsPath); + Assert.Contains("public partial class MissingExplicitForeignKeyEdgeCasesTests", relationshipBuilderTestsSource); + Assert.Contains("Navigation_WithRelationshipBuilderLocalShadowForeignKey_ShouldNotTrigger", relationshipBuilderTestsSource); + Assert.Contains("Navigation_WithMemberAssignmentSharingRelationshipBuilderLocalName_ShouldNotTrigger", relationshipBuilderTestsSource); + } + + [Fact] + public void LC041_NullConditionalTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC041_SingleEntityScalarProjection"); + var generalTestsPath = Path.Combine(testDir, "SingleEntityScalarProjectionTests.cs"); + var nullConditionalTestsPath = Path.Combine(testDir, "SingleEntityScalarProjectionNullConditionalTests.cs"); + + Assert.True(File.Exists(nullConditionalTestsPath), "LC041 null-conditional projection tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class SingleEntityScalarProjectionTests", generalTestsSource); + Assert.DoesNotContain("FirstOrDefault_WithNullConditionalSinglePropertyUsage_Triggers", generalTestsSource); + Assert.DoesNotContain("First_WithNullConditionalSinglePropertyMethodUsage_ShouldNotOfferFix", generalTestsSource); + + var nullConditionalTestsSource = File.ReadAllText(nullConditionalTestsPath); + Assert.Contains("public partial class SingleEntityScalarProjectionTests", nullConditionalTestsSource); + Assert.Contains("FirstOrDefault_WithNullConditionalSinglePropertyUsage_Triggers", nullConditionalTestsSource); + Assert.Contains("First_WithNullConditionalSinglePropertyMethodUsage_ShouldNotOfferFix", nullConditionalTestsSource); + } + + [Fact] + public void LC041_FixerTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC041_SingleEntityScalarProjection"); + var generalTestsPath = Path.Combine(testDir, "SingleEntityScalarProjectionTests.cs"); + var fixerTestsPath = Path.Combine(testDir, "SingleEntityScalarProjectionFixerTests.cs"); + + Assert.True(File.Exists(fixerTestsPath), "LC041 fixer and fix-all coverage should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class SingleEntityScalarProjectionTests", generalTestsSource); + Assert.DoesNotContain("Fixer_ShouldProjectSingleConsumedProperty", generalTestsSource); + Assert.DoesNotContain("FixAll_RewritesMultipleScalarProjections", generalTestsSource); + + var fixerTestsSource = File.ReadAllText(fixerTestsPath); + Assert.Contains("public partial class SingleEntityScalarProjectionTests", fixerTestsSource); + Assert.Contains("Fixer_ShouldProjectSingleConsumedProperty", fixerTestsSource); + Assert.Contains("FixAll_RewritesMultipleScalarProjections", fixerTestsSource); + } + + [Fact] + public void LC041_FixerContext_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC041_SingleEntityScalarProjection"); + var fixerPath = Path.Combine(fixerDir, "SingleEntityScalarProjectionFixer.cs"); + var contextPath = Path.Combine(fixerDir, "SingleEntityScalarProjectionFixerContext.cs"); + + Assert.True(File.Exists(contextPath), "LC041 fixer context derivation and predicate safety should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class SingleEntityScalarProjectionFixer", fixerSource); + Assert.DoesNotContain("private static bool TryGetFixContext", fixerSource); + Assert.DoesNotContain("private static bool HasUnsupportedPredicateArgument", fixerSource); + Assert.DoesNotContain("private sealed class FixContext", fixerSource); + + var contextSource = File.ReadAllText(contextPath); + Assert.Contains("private static bool TryGetFixContext", contextSource); + Assert.Contains("private static bool HasUnsupportedPredicateArgument", contextSource); + Assert.Contains("private sealed class FixContext", contextSource); + } + + [Fact] + public void LC041_PrimaryKeyLookupAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC041_SingleEntityScalarProjection"); + var queryAnalysisPath = Path.Combine(analyzerDir, "SingleEntityScalarProjectionQueryAnalysis.cs"); + var primaryKeyLookupPath = Path.Combine(analyzerDir, "SingleEntityScalarProjectionPrimaryKeyLookup.cs"); + + Assert.True(File.Exists(primaryKeyLookupPath), "LC041 primary-key lookup exemption analysis should live in a focused partial file."); + + var queryAnalysisSource = File.ReadAllText(queryAnalysisPath); + Assert.DoesNotContain("private static bool IsPrimaryKeyLookupInChain", queryAnalysisSource); + Assert.DoesNotContain("private static bool IsPrimaryKeyLookup", queryAnalysisSource); + Assert.DoesNotContain("private static bool IsPrimaryKeyProperty", queryAnalysisSource); + + var primaryKeyLookupSource = File.ReadAllText(primaryKeyLookupPath); + Assert.Contains("private static bool IsPrimaryKeyLookupInChain", primaryKeyLookupSource); + Assert.Contains("private static bool IsPrimaryKeyLookup", primaryKeyLookupSource); + Assert.Contains("private static bool IsPrimaryKeyProperty", primaryKeyLookupSource); + } + + [Fact] + public void LC033_InitializerClassification_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC033_UseFrozenSetForStaticMembershipCaches"); + var analysisPath = Path.Combine(analyzerDir, "UseFrozenSetForStaticMembershipCachesAnalysis.cs"); + var classificationPath = Path.Combine(analyzerDir, "UseFrozenSetForStaticMembershipCachesInitializerClassification.cs"); + var toHashSetPath = Path.Combine(analyzerDir, "UseFrozenSetForStaticMembershipCachesToHashSetClassification.cs"); + + Assert.True(File.Exists(classificationPath), "LC033 initializer classification should live in a focused partial file."); + Assert.True(File.Exists(toHashSetPath), "LC033 ToHashSet classification should live in a focused partial file."); + + var analysisSource = File.ReadAllText(analysisPath); + Assert.Contains("internal static partial class UseFrozenSetForStaticMembershipCachesAnalysis", analysisSource); + Assert.DoesNotContain("public static bool TryClassifyInitializer", analysisSource); + Assert.DoesNotContain("private static bool IsSupportedCollectionInitializer", analysisSource); + Assert.DoesNotContain("private static bool IsSupportedToHashSetInvocation", analysisSource); + + var classificationSource = File.ReadAllText(classificationPath); + Assert.Contains("internal static partial class UseFrozenSetForStaticMembershipCachesAnalysis", classificationSource); + Assert.Contains("public static bool TryClassifyInitializer", classificationSource); + Assert.Contains("private static bool IsSupportedCollectionInitializer", classificationSource); + Assert.DoesNotContain("private static bool IsSupportedToHashSetInvocation", classificationSource); + + var toHashSetSource = File.ReadAllText(toHashSetPath); + Assert.Contains("private static bool IsSupportedToHashSetInvocation", toHashSetSource); + } + + [Fact] + public void LC018_ConstantInterpolationTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC018_AvoidFromSqlRawWithInterpolation"); + var generalTestsPath = Path.Combine(testDir, "AvoidFromSqlRawWithInterpolationTests.cs"); + var constantInterpolationTestsPath = Path.Combine(testDir, "AvoidFromSqlRawWithInterpolationConstantInterpolationTests.cs"); + + Assert.True(File.Exists(constantInterpolationTestsPath), "LC018 constant-interpolation boundary tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class AvoidFromSqlRawWithInterpolationTests", generalTestsSource); + Assert.DoesNotContain("FromSqlRaw_WithConstantOnlyInterpolation_ShouldNotTriggerLC018", generalTestsSource); + Assert.DoesNotContain("FromSqlRaw_WithStaticReadonlyFieldInterpolation_ShouldTriggerLC018", generalTestsSource); + + var constantInterpolationTestsSource = File.ReadAllText(constantInterpolationTestsPath); + Assert.Contains("public partial class AvoidFromSqlRawWithInterpolationTests", constantInterpolationTestsSource); + Assert.Contains("FromSqlRaw_WithConstantOnlyInterpolation_ShouldNotTriggerLC018", constantInterpolationTestsSource); + Assert.Contains("FromSqlRaw_WithStaticReadonlyFieldInterpolation_ShouldTriggerLC018", constantInterpolationTestsSource); + } + + [Fact] + public void LC018_FixerTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC018_AvoidFromSqlRawWithInterpolation"); + var generalTestsPath = Path.Combine(testDir, "AvoidFromSqlRawWithInterpolationTests.cs"); + var fixerTestsPath = Path.Combine(testDir, "AvoidFromSqlRawWithInterpolationFixerTests.cs"); + + Assert.True(File.Exists(fixerTestsPath), "LC018 fixer and fix-all coverage should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class AvoidFromSqlRawWithInterpolationTests", generalTestsSource); + Assert.DoesNotContain("Fixer_ShouldReplaceFromSqlRawWithFromSqlInterpolated", generalTestsSource); + Assert.DoesNotContain("FixAll_RewritesAllFromSqlRawWithInterpolatedStringInstances", generalTestsSource); + + var fixerTestsSource = File.ReadAllText(fixerTestsPath); + Assert.Contains("public partial class AvoidFromSqlRawWithInterpolationTests", fixerTestsSource); + Assert.Contains("Fixer_ShouldReplaceFromSqlRawWithFromSqlInterpolated", fixerTestsSource); + Assert.Contains("FixAll_RewritesAllFromSqlRawWithInterpolatedStringInstances", fixerTestsSource); + } + + [Fact] + public void LC018_FixerSqlSafety_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC018_AvoidFromSqlRawWithInterpolation"); + var fixerPath = Path.Combine(fixerDir, "AvoidFromSqlRawWithInterpolationFixer.cs"); + var sqlSafetyPath = Path.Combine(fixerDir, "AvoidFromSqlRawWithInterpolationFixerSqlSafety.cs"); + + Assert.True(File.Exists(sqlSafetyPath), "LC018 fixer SQL interpolation safety checks should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class AvoidFromSqlRawWithInterpolationFixer", fixerSource); + Assert.DoesNotContain("private static bool HasInterpolationInsideSqlStringLiteral", fixerSource); + Assert.DoesNotContain("private static bool HasInterpolationOutsideLikelySqlValuePosition", fixerSource); + Assert.DoesNotContain("private static bool IsLikelySqlValuePosition", fixerSource); + Assert.DoesNotContain("private static bool ToggleSqlStringLiteralState", fixerSource); + + var sqlSafetySource = File.ReadAllText(sqlSafetyPath); + Assert.Contains("private static bool HasInterpolationInsideSqlStringLiteral", sqlSafetySource); + Assert.Contains("private static bool HasInterpolationOutsideLikelySqlValuePosition", sqlSafetySource); + Assert.Contains("private static bool IsLikelySqlValuePosition", sqlSafetySource); + Assert.Contains("private static bool ToggleSqlStringLiteralState", sqlSafetySource); + } + + [Fact] + public void LC034_FixerTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC034_AvoidExecuteSqlRawWithInterpolation"); + var generalTestsPath = Path.Combine(testDir, "AvoidExecuteSqlRawWithInterpolationTests.cs"); + var fixerTestsPath = Path.Combine(testDir, "AvoidExecuteSqlRawWithInterpolationFixerTests.cs"); + + Assert.True(File.Exists(fixerTestsPath), "LC034 fixer coverage should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class AvoidExecuteSqlRawWithInterpolationTests", generalTestsSource); + Assert.DoesNotContain("Fixer_ShouldReplaceExecuteSqlRawWithExecuteSql", generalTestsSource); + Assert.DoesNotContain("FixAll_RewritesAllExecuteSqlRawCalls", generalTestsSource); + + var fixerTestsSource = File.ReadAllText(fixerTestsPath); + Assert.Contains("public partial class AvoidExecuteSqlRawWithInterpolationTests", fixerTestsSource); + Assert.Contains("Fixer_ShouldReplaceExecuteSqlRawWithExecuteSql", fixerTestsSource); + Assert.Contains("FixAll_RewritesAllExecuteSqlRawCalls", fixerTestsSource); + } + + [Fact] + public void LC011_FluentConfigurationTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC011_EntityMissingPrimaryKey"); + var edgeCasesPath = Path.Combine(testDir, "EntityMissingPrimaryKeyEdgeCasesTests.cs"); + var fluentConfigurationTestsPath = Path.Combine(testDir, "EntityMissingPrimaryKeyFluentConfigurationTests.cs"); + + Assert.True(File.Exists(fluentConfigurationTestsPath), "LC011 fluent configuration edge cases should live in a focused partial test file."); + + var edgeCasesSource = File.ReadAllText(edgeCasesPath); + Assert.Contains("public partial class EntityMissingPrimaryKeyEdgeCasesTests", edgeCasesSource); + Assert.DoesNotContain("TestCrime_UnappliedEntityTypeConfiguration_ShouldTrigger", edgeCasesSource); + Assert.DoesNotContain("TestInnocent_ScopedBuilderVariableReuse_ShouldNotTrigger", edgeCasesSource); + + var fluentConfigurationTestsSource = File.ReadAllText(fluentConfigurationTestsPath); + Assert.Contains("public partial class EntityMissingPrimaryKeyEdgeCasesTests", fluentConfigurationTestsSource); + Assert.Contains("TestCrime_UnappliedEntityTypeConfiguration_ShouldTrigger", fluentConfigurationTestsSource); + Assert.Contains("TestInnocent_ScopedBuilderVariableReuse_ShouldNotTrigger", fluentConfigurationTestsSource); + } + + [Fact] + public void LC011_KeyConventionTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC011_EntityMissingPrimaryKey"); + var generalTestsPath = Path.Combine(testDir, "EntityMissingPrimaryKeyTests.cs"); + var conventionTestsPath = Path.Combine(testDir, "EntityMissingPrimaryKeyKeyConventionTests.cs"); + + Assert.True(File.Exists(conventionTestsPath), "LC011 key convention and key-shape tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class EntityMissingPrimaryKeyTests", generalTestsSource); + Assert.DoesNotContain("TestInnocent_EntityWithId_ShouldNotTrigger", generalTestsSource); + Assert.DoesNotContain("TestInnocent_StringIdProperty_ShouldNotTrigger", generalTestsSource); + + var conventionTestsSource = File.ReadAllText(conventionTestsPath); + Assert.Contains("public partial class EntityMissingPrimaryKeyTests", conventionTestsSource); + Assert.Contains("TestInnocent_EntityWithId_ShouldNotTrigger", conventionTestsSource); + Assert.Contains("TestInnocent_StringIdProperty_ShouldNotTrigger", conventionTestsSource); + } + + [Fact] + public void LC030_RegistrationTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC030_DbContextInSingleton"); + var generalTestsPath = Path.Combine(testDir, "DbContextInSingletonTests.cs"); + var registrationTestsPath = Path.Combine(testDir, "DbContextInSingletonRegistrationTests.cs"); + + Assert.True(File.Exists(registrationTestsPath), "LC030 dependency-injection registration tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class DbContextInSingletonTests", generalTestsSource); + Assert.DoesNotContain("AddSingletonRegisteredService_WithStoredDbContext_ShouldTriggerLC030", generalTestsSource); + Assert.DoesNotContain("AmbiguousServiceRegistration_ShouldNotTrigger", generalTestsSource); + + var registrationTestsSource = File.ReadAllText(registrationTestsPath); + Assert.Contains("public partial class DbContextInSingletonTests", registrationTestsSource); + Assert.Contains("AddSingletonRegisteredService_WithStoredDbContext_ShouldTriggerLC030", registrationTestsSource); + Assert.Contains("AmbiguousServiceRegistration_ShouldNotTrigger", registrationTestsSource); + } + + [Fact] + public void LC030_RegistrationTypeResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC030_DbContextInSingleton"); + var registrationPath = Path.Combine(analyzerDir, "DbContextInSingletonRegistrationAnalysis.cs"); + var typeResolutionPath = Path.Combine(analyzerDir, "DbContextInSingletonRegistrationTypeResolution.cs"); + + Assert.True(File.Exists(typeResolutionPath), "LC030 registration type resolution should live in a focused partial file."); + + var registrationSource = File.ReadAllText(registrationPath); + Assert.DoesNotContain("private static IEnumerable GetRegisteredTypes", registrationSource); + Assert.DoesNotContain("private static ITypeSymbol? GetTypeOfOperand", registrationSource); + + var typeResolutionSource = File.ReadAllText(typeResolutionPath); + Assert.Contains("private static IEnumerable GetRegisteredTypes", typeResolutionSource); + Assert.Contains("private static ITypeSymbol? GetTypeOfOperand", typeResolutionSource); + } + + [Fact] + public void LC017_PropertyAccessEdgeCaseTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC017_WholeEntityProjection"); + var edgeCasesPath = Path.Combine(testDir, "WholeEntityProjectionEdgeCasesTests.cs"); + var propertyAccessTestsPath = Path.Combine(testDir, "WholeEntityProjectionPropertyAccessEdgeCasesTests.cs"); + + Assert.True(File.Exists(propertyAccessTestsPath), "LC017 property-access edge cases should live in a focused partial test file."); + + var edgeCasesSource = File.ReadAllText(edgeCasesPath); + Assert.Contains("public partial class WholeEntityProjectionEdgeCasesTests", edgeCasesSource); + Assert.DoesNotContain("TestCrime_NullConditionalPropertyAccess_TriggersDiagnostic", edgeCasesSource); + Assert.DoesNotContain("TestCrime_BinaryExpression_TriggersDiagnostic", edgeCasesSource); + + var propertyAccessTestsSource = File.ReadAllText(propertyAccessTestsPath); + Assert.Contains("public partial class WholeEntityProjectionEdgeCasesTests", propertyAccessTestsSource); + Assert.Contains("TestCrime_NullConditionalPropertyAccess_TriggersDiagnostic", propertyAccessTestsSource); + Assert.Contains("TestCrime_BinaryExpression_TriggersDiagnostic", propertyAccessTestsSource); + } + + [Fact] + public void LC017_FixerSafetyTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC017_WholeEntityProjection"); + var fixerTestsPath = Path.Combine(testDir, "WholeEntityProjectionFixerTests.cs"); + var safetyTestsPath = Path.Combine(testDir, "WholeEntityProjectionFixerSafetyTests.cs"); + + Assert.True(File.Exists(safetyTestsPath), "LC017 fixer safety/no-fix cases should live in a focused partial test file."); + + var fixerTestsSource = File.ReadAllText(fixerTestsPath); + Assert.Contains("public partial class WholeEntityProjectionFixerTests", fixerTestsSource); + Assert.DoesNotContain("IndexedEntityEscape_HasNoFix", fixerTestsSource); + Assert.DoesNotContain("ConditionalInterfaceCastAccessedProperty_HasNoFix", fixerTestsSource); + + var safetyTestsSource = File.ReadAllText(safetyTestsPath); + Assert.Contains("public partial class WholeEntityProjectionFixerTests", safetyTestsSource); + Assert.Contains("IndexedEntityEscape_HasNoFix", safetyTestsSource); + Assert.Contains("ConditionalInterfaceCastAccessedProperty_HasNoFix", safetyTestsSource); + } + + [Fact] + public void RuleCatalog_LC001ToLC015Entries_LiveInDedicatedPartial() + { + var catalogDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Catalog"); + var catalogPath = Path.Combine(catalogDir, "RuleCatalog.cs"); + var firstRangePath = Path.Combine(catalogDir, "RuleCatalog.LC001ToLC015.cs"); + + Assert.True(File.Exists(firstRangePath), "RuleCatalog LC001-LC015 entries should live in a focused partial file."); + + var catalogSource = File.ReadAllText(catalogPath); + Assert.Contains("public static partial class RuleCatalog", catalogSource); + Assert.DoesNotContain("id: \"LC001\"", catalogSource); + Assert.DoesNotContain("id: \"LC015\"", catalogSource); + + var firstRangeSource = File.ReadAllText(firstRangePath); + Assert.Contains("public static partial class RuleCatalog", firstRangeSource); + Assert.Contains("id: \"LC001\"", firstRangeSource); + Assert.Contains("id: \"LC015\"", firstRangeSource); + } + + [Fact] + public void LC039_TransactionTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC039_NestedSaveChanges"); + var generalTestsPath = Path.Combine(testDir, "NestedSaveChangesTests.cs"); + var transactionTestsPath = Path.Combine(testDir, "NestedSaveChangesTransactionTests.cs"); + + Assert.True(File.Exists(transactionTestsPath), "LC039 transaction-boundary tests should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class NestedSaveChangesTests", generalTestsSource); + Assert.DoesNotContain("TransactionBoundaryBetweenSaves_DoesNotTrigger", generalTestsSource); + Assert.DoesNotContain("RepeatedSavesInsideAwaitUsingDeclarationTransaction_DoesNotTrigger", generalTestsSource); + + var transactionTestsSource = File.ReadAllText(transactionTestsPath); + Assert.Contains("public partial class NestedSaveChangesTests", transactionTestsSource); + Assert.Contains("TransactionBoundaryBetweenSaves_DoesNotTrigger", transactionTestsSource); + Assert.Contains("RepeatedSavesInsideAwaitUsingDeclarationTransaction_DoesNotTrigger", transactionTestsSource); + } + + [Fact] + public void LC039_TransactionBoundaryClassification_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC039_NestedSaveChanges"); + var analyzerPath = Path.Combine(analyzerDir, "NestedSaveChangesAnalyzer.cs"); + var transactionBoundaryPath = Path.Combine(analyzerDir, "NestedSaveChangesTransactionBoundaryClassification.cs"); + + Assert.True(File.Exists(transactionBoundaryPath), "LC039 EF transaction-boundary classification should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static bool IsTransactionBoundaryInvocation", analyzerSource); + Assert.DoesNotContain("private static bool IsEfCoreTransactionBoundaryType", analyzerSource); + Assert.DoesNotContain("private static bool IsEfCoreTransactionBoundaryNamedType", analyzerSource); + + var transactionBoundarySource = File.ReadAllText(transactionBoundaryPath); + Assert.Contains("private static bool IsTransactionBoundaryInvocation", transactionBoundarySource); + Assert.Contains("private static bool IsEfCoreTransactionBoundaryType", transactionBoundarySource); + Assert.Contains("private static bool IsEfCoreTransactionBoundaryNamedType", transactionBoundarySource); + } + + [Fact] + public void LC039_BranchExclusivity_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC039_NestedSaveChanges"); + var boundaryPath = Path.Combine(analyzerDir, "NestedSaveChangesBoundaryAnalysis.cs"); + var branchExclusivityPath = Path.Combine(analyzerDir, "NestedSaveChangesBranchExclusivity.cs"); + + Assert.True(File.Exists(branchExclusivityPath), "LC039 mutually-exclusive branch detection should live in a focused partial file."); + + var boundarySource = File.ReadAllText(boundaryPath); + Assert.Contains("private static bool HasTransactionBoundaryBetween", boundarySource); + Assert.DoesNotContain("private static bool AreMutuallyExclusiveBranches", boundarySource); + Assert.DoesNotContain("private static SyntaxNode? GetContainingTryBranch", boundarySource); + Assert.DoesNotContain("private static StatementSyntax? GetContainingBranch", boundarySource); + Assert.DoesNotContain("private static SwitchSectionSyntax? GetContainingSwitchSection", boundarySource); + Assert.DoesNotContain("private static SwitchExpressionArmSyntax? GetContainingSwitchExpressionArm", boundarySource); + + var branchExclusivitySource = File.ReadAllText(branchExclusivityPath); + Assert.Contains("private static bool AreMutuallyExclusiveBranches", branchExclusivitySource); + Assert.Contains("private static SyntaxNode? GetContainingTryBranch", branchExclusivitySource); + Assert.Contains("private static StatementSyntax? GetContainingBranch", branchExclusivitySource); + Assert.Contains("private static SwitchSectionSyntax? GetContainingSwitchSection", branchExclusivitySource); + Assert.Contains("private static SwitchExpressionArmSyntax? GetContainingSwitchExpressionArm", branchExclusivitySource); + } + + [Fact] + public void LC039_DiagnosticReporting_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC039_NestedSaveChanges"); + var analyzerPath = Path.Combine(analyzerDir, "NestedSaveChangesAnalyzer.cs"); + var reportingPath = Path.Combine(analyzerDir, "NestedSaveChangesReporting.cs"); + + Assert.True(File.Exists(reportingPath), "LC039 compilation-end diagnostic grouping/reporting should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class NestedSaveChangesAnalyzer", analyzerSource); + Assert.Contains("public void AnalyzeInvocation", analyzerSource); + Assert.DoesNotContain("public void ReportDiagnostics", analyzerSource); + + var reportingSource = File.ReadAllText(reportingPath); + Assert.Contains("public void ReportDiagnostics", reportingSource); + Assert.Contains("GroupBy(record => record.Root", reportingSource); + Assert.Contains("HasTransactionBoundaryBetween", reportingSource); + Assert.Contains("AreMutuallyExclusiveBranches", reportingSource); + Assert.Contains("AreInsideSameTransactionUsing", reportingSource); + Assert.Contains("Diagnostic.Create(Rule", reportingSource); + } + + [Fact] + public void LC020_QueryableParameterTracing_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC020_StringContainsWithComparison"); + var analyzerPath = Path.Combine(analyzerDir, "StringContainsWithComparisonAnalyzer.cs"); + var queryableParametersPath = Path.Combine(analyzerDir, "StringContainsWithComparisonQueryableParameters.cs"); + + Assert.True(File.Exists(queryableParametersPath), "LC020 queryable lambda parameter tracing should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class StringContainsWithComparisonAnalyzer", analyzerSource); + Assert.DoesNotContain("private static ImmutableArray GetQueryableExpressionLambdaParameters", analyzerSource); + Assert.DoesNotContain("private static ImmutableArray GetQueryDependentLambdaParameters", analyzerSource); + Assert.DoesNotContain("private static bool LambdaSourceDependsOnParameters", analyzerSource); + Assert.DoesNotContain("private static bool IsQueryableInvocation", analyzerSource); + + var queryableParametersSource = File.ReadAllText(queryableParametersPath); + Assert.Contains("private static ImmutableArray GetQueryableExpressionLambdaParameters", queryableParametersSource); + Assert.Contains("private static ImmutableArray GetQueryDependentLambdaParameters", queryableParametersSource); + Assert.Contains("private static bool LambdaSourceDependsOnParameters", queryableParametersSource); + Assert.Contains("private static bool IsQueryableInvocation", queryableParametersSource); + } + + [Fact] + public void LC024_ProjectionAccessScanning_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC024_GroupByNonTranslatable"); + var analyzerPath = Path.Combine(analyzerDir, "GroupByNonTranslatableAnalyzer.cs"); + var projectionAccessPath = Path.Combine(analyzerDir, "GroupByNonTranslatableProjectionAccess.cs"); + + Assert.True(File.Exists(projectionAccessPath), "LC024 projection access scanning/reporting should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class GroupByNonTranslatableAnalyzer", analyzerSource); + Assert.Contains("private void AnalyzeSelectProjection", analyzerSource); + Assert.Contains("private void AnalyzeGroupByResultSelector", analyzerSource); + Assert.DoesNotContain("private void CheckOperationForNonTranslatableAccess", analyzerSource); + Assert.DoesNotContain("private static System.Collections.Generic.IEnumerable GetAllOperations", analyzerSource); + + var projectionAccessSource = File.ReadAllText(projectionAccessPath); + Assert.Contains("private static void CheckOperationForNonTranslatableAccess", projectionAccessSource); + Assert.Contains("private static System.Collections.Generic.IEnumerable GetAllOperations", projectionAccessSource); + Assert.Contains("private static void ReportNonTranslatableAccess", projectionAccessSource); + Assert.Contains("Diagnostic.Create(Rule", projectionAccessSource); + } + + [Fact] + public void LC014_QueryableLambdaScope_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC014_AvoidStringCaseConversion"); + var analyzerPath = Path.Combine(analyzerDir, "AvoidStringCaseConversionAnalyzer.cs"); + var queryableLambdaScopePath = Path.Combine(analyzerDir, "AvoidStringCaseConversionQueryableLambdaScope.cs"); + + Assert.True(File.Exists(queryableLambdaScopePath), "LC014 queryable lambda scope and target method classification should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AvoidStringCaseConversionAnalyzer", analyzerSource); + Assert.Contains("private void AnalyzeInvocation", analyzerSource); + Assert.DoesNotContain("private ImmutableArray GetEnclosingQueryableLambdaParameters", analyzerSource); + Assert.DoesNotContain("private static bool IsTargetQueryableMethod", analyzerSource); + Assert.DoesNotContain("private static bool IsLambdaScopedToEntityFrameworkSource", analyzerSource); + Assert.DoesNotContain("TargetLinqMethods", analyzerSource); + Assert.DoesNotContain("TargetEfAsyncPredicateMethods", analyzerSource); + + var queryableLambdaScopeSource = File.ReadAllText(queryableLambdaScopePath); + Assert.Contains("private static ImmutableArray GetEnclosingQueryableLambdaParameters", queryableLambdaScopeSource); + Assert.Contains("private static bool IsTargetQueryableMethod", queryableLambdaScopeSource); + Assert.Contains("private static bool IsLambdaScopedToEntityFrameworkSource", queryableLambdaScopeSource); + Assert.Contains("TargetLinqMethods", queryableLambdaScopeSource); + Assert.Contains("TargetEfAsyncPredicateMethods", queryableLambdaScopeSource); + Assert.Contains("HasEntityFrameworkQuerySource", queryableLambdaScopeSource); + } + + [Fact] + public void LC045_EntityLocalCollection_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC045_MissingInclude"); + var usageAnalysisPath = Path.Combine(analyzerDir, "MissingIncludeUsageAnalysis.cs"); + var entityLocalCollectionPath = Path.Combine(analyzerDir, "MissingIncludeEntityLocalCollection.cs"); + + Assert.True(File.Exists(entityLocalCollectionPath), "LC045 materialized-result entity local collection should live in a focused partial file."); + + var usageAnalysisSource = File.ReadAllText(usageAnalysisPath); + Assert.Contains("private static List? CollectNavigationAccesses", usageAnalysisSource); + Assert.DoesNotContain("private static HashSet CollectEntityLocals", usageAnalysisSource); + Assert.Contains("var entityLocals = CollectEntityLocals", usageAnalysisSource); + + var entityLocalCollectionSource = File.ReadAllText(entityLocalCollectionPath); + Assert.Contains("private static HashSet CollectEntityLocals", entityLocalCollectionSource); + Assert.Contains("IForEachLoopOperation", entityLocalCollectionSource); + Assert.Contains("IsIndexedAccessOf", entityLocalCollectionSource); + Assert.Contains("LocalAssignmentCache.GetAssignments", entityLocalCollectionSource); + } + + [Fact] + public void LC025_FixerNoTrackingSourceTracing_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC025_AsNoTrackingWithUpdate"); + var sourceResolutionPath = Path.Combine(fixerDir, "AsNoTrackingWithUpdateFixerSourceResolution.cs"); + var noTrackingSourcePath = Path.Combine(fixerDir, "AsNoTrackingWithUpdateFixerNoTrackingSource.cs"); + + Assert.True(File.Exists(noTrackingSourcePath), "LC025 fixer recursive no-tracking source tracing should live in a focused partial file."); + + var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); + Assert.Contains("private static InvocationExpressionSyntax? FindAsNoTrackingOrigin", sourceResolutionSource); + Assert.DoesNotContain("private static bool IsNoTrackingSource", sourceResolutionSource); + Assert.DoesNotContain("private static bool IsLocalFromNoTracking", sourceResolutionSource); + Assert.Contains("IsConditionalRelativeTo", sourceResolutionSource); + + var noTrackingSource = File.ReadAllText(noTrackingSourcePath); + Assert.Contains("private static bool IsNoTrackingSource", noTrackingSource); + Assert.Contains("private static bool IsLocalFromNoTracking", noTrackingSource); + Assert.Contains("HasAsNoTrackingInChain", noTrackingSource); + Assert.Contains("IsMaterializerMethod", noTrackingSource); + Assert.Contains("new AsNoTrackingOrigin", noTrackingSource); + } + + [Fact] + public void LC025_AnalyzerNoTrackingSourceTracing_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC025_AsNoTrackingWithUpdate"); + var sourceResolutionPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateSourceResolution.cs"); + var noTrackingSourcePath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateNoTrackingSource.cs"); + + Assert.True(File.Exists(noTrackingSourcePath), "LC025 analyzer no-tracking source evaluation should live in a focused partial file."); + + var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); + Assert.Contains("private bool IsFromNoTrackingQuery", sourceResolutionSource); + Assert.DoesNotContain("private bool IsNoTrackingSource", sourceResolutionSource); + Assert.DoesNotContain("private static IEnumerable EnumerateOperations", sourceResolutionSource); + + var noTrackingSource = File.ReadAllText(noTrackingSourcePath); + Assert.Contains("private bool IsNoTrackingSource", noTrackingSource); + Assert.Contains("private static IEnumerable EnumerateOperations", noTrackingSource); + Assert.Contains("IsAsNoTrackingQuery", noTrackingSource); + Assert.Contains("IsMaterializerMethod", noTrackingSource); + } + + [Fact] + public void LC040_DiagnosticReporting_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC040_MixedTrackingAndNoTracking"); + var analyzerPath = Path.Combine(analyzerDir, "MixedTrackingAndNoTrackingAnalyzer.cs"); + var reportingPath = Path.Combine(analyzerDir, "MixedTrackingAndNoTrackingReporting.cs"); + + Assert.True(File.Exists(reportingPath), "LC040 compilation-end diagnostic grouping/reporting should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class MixedTrackingAndNoTrackingAnalyzer", analyzerSource); + Assert.DoesNotContain("public void ReportDiagnostics", analyzerSource); + Assert.Contains("public void AnalyzeInvocation", analyzerSource); + + var reportingSource = File.ReadAllText(reportingPath); + Assert.Contains("public void ReportDiagnostics", reportingSource); + Assert.Contains("GroupBy(record => record.Root", reportingSource); + Assert.Contains("AreMutuallyExclusiveBranches", reportingSource); + Assert.Contains("Diagnostic.Create(Rule", reportingSource); + } + + [Fact] + public void LC043_BufferedLocalPattern_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC043_AsyncEnumerableBuffering"); + var analyzerPath = Path.Combine(analyzerDir, "AsyncEnumerableBufferingAnalyzer.cs"); + var patternPath = Path.Combine(analyzerDir, "AsyncEnumerableBufferingPattern.cs"); + + Assert.True(File.Exists(patternPath), "LC043 buffered-local syntax matching should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AsyncEnumerableBufferingAnalyzer", analyzerSource); + Assert.DoesNotContain("internal static bool TryGetImmediateBufferedLocal", analyzerSource); + Assert.DoesNotContain("internal sealed class BufferInfo", analyzerSource); + + var patternSource = File.ReadAllText(patternPath); + Assert.Contains("internal static bool TryGetImmediateBufferedLocal", patternSource); + Assert.Contains("internal sealed class BufferInfo", patternSource); + } + + [Fact] + public void LC043_FixerLoopDiscovery_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC043_AsyncEnumerableBuffering"); + var fixerPath = Path.Combine(analyzerDir, "AsyncEnumerableBufferingFixer.cs"); + var loopDiscoveryPath = Path.Combine(analyzerDir, "AsyncEnumerableBufferingFixerLoopDiscovery.cs"); + + Assert.True(File.Exists(loopDiscoveryPath), "LC043 fixer loop/declaration discovery should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class AsyncEnumerableBufferingFixer", fixerSource); + Assert.DoesNotContain("private static bool TryGetContainingLoopAndDeclaration", fixerSource); + + var loopDiscoverySource = File.ReadAllText(loopDiscoveryPath); + Assert.Contains("private static bool TryGetContainingLoopAndDeclaration", loopDiscoverySource); + } + + [Fact] + public void LC044_TrackedStateEdgeCases_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC044_AsNoTrackingThenModify"); + var edgeCasesPath = Path.Combine(testDir, "AsNoTrackingThenModifyEdgeCasesTests.cs"); + var trackedStateTestsPath = Path.Combine(testDir, "AsNoTrackingThenModifyTrackedStateTests.cs"); + + Assert.True(File.Exists(trackedStateTestsPath), "LC044 attach/update/state-reset edge cases should live in a focused partial test file."); + + var edgeCasesSource = File.ReadAllText(edgeCasesPath); + Assert.Contains("public partial class AsNoTrackingThenModifyEdgeCasesTests", edgeCasesSource); + Assert.DoesNotContain("AsNoTracking_Mutate_ContextUpdate_ThenSave_DoesNotTrigger", edgeCasesSource); + Assert.DoesNotContain("AsNoTracking_ContextAttach_ThenChangeTrackerClear_ThenMutate_ThenSave_StillTriggers", edgeCasesSource); + + var trackedStateTestsSource = File.ReadAllText(trackedStateTestsPath); + Assert.Contains("public partial class AsNoTrackingThenModifyEdgeCasesTests", trackedStateTestsSource); + Assert.Contains("AsNoTracking_Mutate_ContextUpdate_ThenSave_DoesNotTrigger", trackedStateTestsSource); + Assert.Contains("AsNoTracking_ContextAttach_ThenChangeTrackerClear_ThenMutate_ThenSave_StillTriggers", trackedStateTestsSource); + } + + [Fact] + public void LC009_SetGenericSourceTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC009_MissingAsNoTracking"); + var generalTestsPath = Path.Combine(testDir, "MissingAsNoTrackingTests.cs"); + var setSourceTestsPath = Path.Combine(testDir, "MissingAsNoTrackingSetSourceTests.cs"); + + Assert.True(File.Exists(setSourceTestsPath), "LC009 DbContext.Set query-source coverage should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class MissingAsNoTrackingTests", generalTestsSource); + Assert.DoesNotContain("TestCrime_SetGenericSource_ReadOnly_TriggersDiagnostic", generalTestsSource); + Assert.DoesNotContain("TestInnocent_SetGenericSource_WithSelect_NoDiagnostic", generalTestsSource); + + var setSourceTestsSource = File.ReadAllText(setSourceTestsPath); + Assert.Contains("public partial class MissingAsNoTrackingTests", setSourceTestsSource); + Assert.Contains("TestCrime_SetGenericSource_ReadOnly_TriggersDiagnostic", setSourceTestsSource); + Assert.Contains("TestInnocent_SetGenericSource_WithSelect_NoDiagnostic", setSourceTestsSource); + } + + [Fact] + public void LC009_ResultLocalResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC009_MissingAsNoTracking"); + var mutationDetectionPath = Path.Combine(analyzerDir, "MissingAsNoTrackingMutationDetection.cs"); + var resultLocalPath = Path.Combine(analyzerDir, "MissingAsNoTrackingResultLocal.cs"); + + Assert.True(File.Exists(resultLocalPath), "LC009 materialized-result local and wrapper resolution should live in a focused partial file."); + + var mutationDetectionSource = File.ReadAllText(mutationDetectionPath); + Assert.Contains("private static bool MaterializedEntityIsMutated", mutationDetectionSource); + Assert.DoesNotContain("private static ILocalSymbol? FindResultLocal", mutationDetectionSource); + Assert.DoesNotContain("private static IOperation? WalkUpThroughWrappers", mutationDetectionSource); + + var resultLocalSource = File.ReadAllText(resultLocalPath); + Assert.Contains("private static ILocalSymbol? FindResultLocal", resultLocalSource); + Assert.Contains("private static IOperation? WalkUpThroughWrappers", resultLocalSource); + Assert.DoesNotContain("private static bool MaterializedEntityIsMutated", resultLocalSource); + } + + [Fact] + public void LC007_IgnoredSourceTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC007_NPlusOneLooper"); + var generalTestsPath = Path.Combine(testDir, "NPlusOneLooperTests.cs"); + var ignoredSourceTestsPath = Path.Combine(testDir, "NPlusOneLooperIgnoredSourceTests.cs"); + + Assert.True(File.Exists(ignoredSourceTestsPath), "LC007 ignored and ambiguous query-source coverage should live in a focused partial test file."); + + var generalTestsSource = File.ReadAllText(generalTestsPath); + Assert.Contains("public partial class NPlusOneLooperTests", generalTestsSource); + Assert.DoesNotContain("InMemoryAsQueryable_IsIgnored", generalTestsSource); + Assert.DoesNotContain("InvocationInsideLambdaDeclaredInLoop_IsIgnored", generalTestsSource); + + var ignoredSourceTestsSource = File.ReadAllText(ignoredSourceTestsPath); + Assert.Contains("public partial class NPlusOneLooperTests", ignoredSourceTestsSource); + Assert.Contains("InMemoryAsQueryable_IsIgnored", ignoredSourceTestsSource); + Assert.Contains("InvocationInsideLambdaDeclaredInLoop_IsIgnored", ignoredSourceTestsSource); + } + + [Fact] + public void LC045_EscapedResultEdgeCaseTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC045_MissingInclude"); + var edgeCasesPath = Path.Combine(testDir, "MissingIncludeEdgeCasesTests.cs"); + var escapedResultTestsPath = Path.Combine(testDir, "MissingIncludeEscapedResultTests.cs"); + + Assert.True(File.Exists(escapedResultTestsPath), "LC045 escaped or ambiguous result edge cases should live in a focused partial test file."); + + var edgeCasesSource = File.ReadAllText(edgeCasesPath); + Assert.Contains("public partial class MissingIncludeEdgeCasesTests", edgeCasesSource); + Assert.DoesNotContain("TestInnocent_ResultPassedAsArgument_NoDiagnostic", edgeCasesSource); + Assert.DoesNotContain("TestInnocent_IndexedEntityPassedAsArgument_NoDiagnostic", edgeCasesSource); + + var escapedResultTestsSource = File.ReadAllText(escapedResultTestsPath); + Assert.Contains("public partial class MissingIncludeEdgeCasesTests", escapedResultTestsSource); + Assert.Contains("TestInnocent_ResultPassedAsArgument_NoDiagnostic", escapedResultTestsSource); + Assert.Contains("TestInnocent_IndexedEntityPassedAsArgument_NoDiagnostic", escapedResultTestsSource); + } + + [Fact] + public void LC045_OriginAwareFlowTests_LiveInDedicatedPartial() + { + var testDir = Path.Combine( + _repoRoot, + "tests", + "LinqContraband.Tests", + "Analyzers", + "LC045_MissingInclude"); + var escapedResultTestsPath = Path.Combine(testDir, "MissingIncludeEscapedResultTests.cs"); + var originFlowTestsPath = Path.Combine(testDir, "MissingIncludeOriginFlowTests.cs"); + var uncertainReadTestsPath = Path.Combine(testDir, "MissingIncludeOriginFlowUncertainReadTests.cs"); + + Assert.True(File.Exists(originFlowTestsPath), "LC045 origin-aware control-flow coverage should live in a focused partial test file."); + Assert.True(File.Exists(uncertainReadTestsPath), "LC045 uncertain-read control-flow guardrails should live in a focused partial test file."); + + var escapedResultTestsSource = File.ReadAllText(escapedResultTestsPath); + Assert.DoesNotContain("TestCrime_OriginFlow_", escapedResultTestsSource); + Assert.DoesNotContain("TestInnocent_OriginFlow_", escapedResultTestsSource); + + var originFlowTestsSource = File.ReadAllText(originFlowTestsPath); + Assert.Contains("public partial class MissingIncludeEdgeCasesTests", originFlowTestsSource); + Assert.Contains("TestCrime_OriginFlow_ReadBeforeHelperEscape_StillReports", originFlowTestsSource); + Assert.Contains("TestCrime_OriginFlow_OneBranchNavigationAssignment_StillReports", originFlowTestsSource); + Assert.Contains("TestCrime_OriginFlow_WriteOnDifferentEntity_DoesNotSatisfyRead", originFlowTestsSource); + Assert.Contains("TestCrime_OriginFlow_LoopReadBeforeWrite_StillReports", originFlowTestsSource); + Assert.DoesNotContain("TestInnocent_OriginFlow_HelperEscapeBeforeRead_NoDiagnostic", originFlowTestsSource); + + var uncertainReadTestsSource = File.ReadAllText(uncertainReadTestsPath); + Assert.Contains("public partial class MissingIncludeEdgeCasesTests", uncertainReadTestsSource); + Assert.Contains("TestInnocent_OriginFlow_HelperEscapeBeforeRead_NoDiagnostic", uncertainReadTestsSource); + Assert.Contains("TestInnocent_OriginFlow_BothBranchesAssignNavigationBeforeRead_NoDiagnostic", uncertainReadTestsSource); + } + + [Fact] + public void LC031_QuerySourceResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC031_UnboundedQueryMaterialization"); + var analyzerPath = Path.Combine(analyzerDir, "UnboundedQueryMaterializationAnalyzer.cs"); + var querySourcePath = Path.Combine(analyzerDir, "UnboundedQueryMaterializationQuerySource.cs"); + + Assert.True(File.Exists(querySourcePath), "LC031 DbSet/query-source chain resolution should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class UnboundedQueryMaterializationAnalyzer", analyzerSource); + Assert.DoesNotContain("private static QuerySourceResolution ResolveQuerySource", analyzerSource); + Assert.DoesNotContain("private readonly struct QuerySourceResolution", analyzerSource); + Assert.DoesNotContain("private static bool IsDbContextSetInvocation", analyzerSource); + Assert.DoesNotContain("private static bool TryResolveSingleAssignedValue", analyzerSource); + + var querySource = File.ReadAllText(querySourcePath); + Assert.Contains("private static QuerySourceResolution ResolveQuerySource", querySource); + Assert.Contains("private readonly struct QuerySourceResolution", querySource); + Assert.Contains("private static bool IsDbContextSetInvocation", querySource); + Assert.Contains("private static bool TryResolveSingleAssignedValue", querySource); + } + + [Fact] + public void LC003_ExistenceComparisonRecognition_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC003_AnyOverCount"); + var analyzerPath = Path.Combine(analyzerDir, "AnyOverCountAnalyzer.cs"); + var comparisonPath = Path.Combine(analyzerDir, "AnyOverCountExistenceComparison.cs"); + + Assert.True(File.Exists(comparisonPath), "LC003 Count comparison recognition should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AnyOverCountAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool TryGetCountExistenceCheck", analyzerSource); + Assert.DoesNotContain("private static bool IsInvocation", analyzerSource); + Assert.DoesNotContain("private static bool IsConstant", analyzerSource); + Assert.DoesNotContain("private static bool IsZero", analyzerSource); + Assert.DoesNotContain("private static bool IsOne", analyzerSource); + + var comparisonSource = File.ReadAllText(comparisonPath); + Assert.Contains("private static bool TryGetCountExistenceCheck", comparisonSource); + Assert.Contains("private static bool IsInvocation", comparisonSource); + Assert.Contains("private static bool IsConstant", comparisonSource); + Assert.Contains("private static bool IsZero", comparisonSource); + Assert.Contains("private static bool IsOne", comparisonSource); + } + + [Fact] + public void LC025_AnalyzerTrackingDirectiveAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC025_AsNoTrackingWithUpdate"); + var analyzerPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateAnalyzer.cs"); + var trackingDirectivePath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateTrackingDirectiveAnalysis.cs"); + + Assert.True(File.Exists(trackingDirectivePath), "LC025 analyzer tracking-directive chain analysis should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AsNoTrackingWithUpdateAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool IsAsNoTrackingQuery", analyzerSource); + Assert.DoesNotContain("private static bool HasAsNoTrackingInChain", analyzerSource); + Assert.DoesNotContain("private static bool IsProjectionToConstructedObject", analyzerSource); + Assert.DoesNotContain("private static bool IsEfCoreNoTrackingDirective", analyzerSource); + Assert.DoesNotContain("private static bool IsEfCoreAsTracking", analyzerSource); + + var trackingDirectiveSource = File.ReadAllText(trackingDirectivePath); + Assert.Contains("private static bool IsAsNoTrackingQuery", trackingDirectiveSource); + Assert.Contains("private static bool HasAsNoTrackingInChain", trackingDirectiveSource); + Assert.Contains("private static bool IsProjectionToConstructedObject", trackingDirectiveSource); + Assert.Contains("private static bool IsEfCoreNoTrackingDirective", trackingDirectiveSource); + Assert.Contains("private static bool IsEfCoreAsTracking", trackingDirectiveSource); + } + + [Fact] + public void LC032_FixerAsyncSupportDetection_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC032_ExecuteUpdateForBulkUpdates"); + var saveChangesModePath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerSaveChangesMode.cs"); + var asyncSupportPath = Path.Combine(fixerDir, "ExecuteUpdateForBulkUpdatesFixerAsyncSupport.cs"); + + Assert.True(File.Exists(asyncSupportPath), "LC032 fixer ExecuteUpdateAsync capability detection should live in a focused partial file."); + + var saveChangesModeSource = File.ReadAllText(saveChangesModePath); + Assert.DoesNotContain("private static bool HasExecuteUpdateAsyncTokenOverload", saveChangesModeSource); + Assert.DoesNotContain("private static bool HasExecuteUpdateAsyncSupport", saveChangesModeSource); + Assert.DoesNotContain("private static bool HasExecuteUpdateAsyncMethod", saveChangesModeSource); + Assert.DoesNotContain("private static bool IsExecuteUpdateAsyncLikeMethod", saveChangesModeSource); + Assert.DoesNotContain("private static bool IsEntityFrameworkCoreNamespace", saveChangesModeSource); + + var asyncSupportSource = File.ReadAllText(asyncSupportPath); + Assert.Contains("private static bool HasExecuteUpdateAsyncTokenOverload", asyncSupportSource); + Assert.Contains("private static bool HasExecuteUpdateAsyncSupport", asyncSupportSource); + Assert.Contains("private static bool HasExecuteUpdateAsyncMethod", asyncSupportSource); + Assert.Contains("private static bool IsExecuteUpdateAsyncLikeMethod", asyncSupportSource); + Assert.Contains("private static bool IsEntityFrameworkCoreNamespace", asyncSupportSource); + } + + [Fact] + public void LC032_LocalWriteDetection_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC032_ExecuteUpdateForBulkUpdates"); + var statementAnalysisPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesStatementAnalysis.cs"); + var localWriteDetectionPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesLocalWriteDetection.cs"); + + Assert.True(File.Exists(localWriteDetectionPath), "LC032 local write detection should live in a focused analyzer partial file."); + + var statementAnalysisSource = File.ReadAllText(statementAnalysisPath); + Assert.DoesNotContain("private static bool HasLocalWrites", statementAnalysisSource); + + var localWriteDetectionSource = File.ReadAllText(localWriteDetectionPath); + Assert.Contains("private static bool HasLocalWrites", localWriteDetectionSource); + } + + [Fact] + public void LC011_AssemblyUsingScopeTraversal_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var visibilityPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyUsingVisibility.cs"); + var usingScopePath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyUsingScope.cs"); + + Assert.True(File.Exists(usingScopePath), "LC011 assembly using-scope traversal should live in a focused partial file."); + + var visibilitySource = File.ReadAllText(visibilityPath); + Assert.DoesNotContain("private static bool AnyUsingDirectiveInScope", visibilitySource); + Assert.DoesNotContain("foreach (var namespaceDeclaration in node.Ancestors().OfType())", visibilitySource); + Assert.DoesNotContain("foreach (var fileScopedNamespace in node.Ancestors().OfType())", visibilitySource); + + var usingScopeSource = File.ReadAllText(usingScopePath); + Assert.Contains("private static bool AnyUsingDirectiveInScope", usingScopeSource); + Assert.Contains("foreach (var namespaceDeclaration in node.Ancestors().OfType())", usingScopeSource); + Assert.Contains("foreach (var fileScopedNamespace in node.Ancestors().OfType())", usingScopeSource); + } + + [Fact] + public void LC011_AssemblyMemberResolution_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var localResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyLocalResolution.cs"); + var memberResolutionPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAssemblyMemberResolution.cs"); + + Assert.True(File.Exists(memberResolutionPath), "LC011 assembly member initializer resolution should live in a focused partial file."); + + var localResolutionSource = File.ReadAllText(localResolutionPath); + Assert.DoesNotContain("private static bool TryResolveMemberCurrentAssembly", localResolutionSource); + + var memberResolutionSource = File.ReadAllText(memberResolutionPath); + Assert.Contains("private static bool TryResolveMemberCurrentAssembly", memberResolutionSource); + Assert.Contains("foreach (var member in members)", memberResolutionSource); + Assert.Contains("DeclaringSyntaxReferences", memberResolutionSource); + } + + [Fact] + public void LC016_FixerExpressionBodyClockAccessDiscovery_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC016_AvoidDateTimeNow"); + var expressionBodyPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixerExpressionBody.cs"); + var clockAccessPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixerExpressionBodyClockAccesses.cs"); + + Assert.True(File.Exists(clockAccessPath), "LC016 expression-bodied clock access discovery should live in a focused partial file."); + + var expressionBodySource = File.ReadAllText(expressionBodyPath); + Assert.DoesNotContain("private static IReadOnlyList BuildExpressionBodyReplacements", expressionBodySource); + Assert.DoesNotContain("private static ClockReplacement? FindReplacementFor", expressionBodySource); + Assert.DoesNotContain("private static IEnumerable FindExpressionBodyClockAccesses", expressionBodySource); + Assert.DoesNotContain("private static bool IsClockPropertyAccess", expressionBodySource); + Assert.DoesNotContain("private sealed class ClockReplacement", expressionBodySource); + + var clockAccessSource = File.ReadAllText(clockAccessPath); + Assert.Contains("private static IReadOnlyList BuildExpressionBodyReplacements", clockAccessSource); + Assert.Contains("private static ClockReplacement? FindReplacementFor", clockAccessSource); + Assert.Contains("private static IEnumerable FindExpressionBodyClockAccesses", clockAccessSource); + Assert.Contains("private static bool IsClockPropertyAccess", clockAccessSource); + Assert.Contains("private sealed class ClockReplacement", clockAccessSource); + } + + [Fact] + public void LC007_FixerIncludeRewrite_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC007_NPlusOneLooper"); + var syntaxAnalysisPath = Path.Combine(fixerDir, "NPlusOneLooperFixerSyntaxAnalysis.cs"); + var includeRewritePath = Path.Combine(fixerDir, "NPlusOneLooperFixerIncludeRewrite.cs"); + + Assert.True(File.Exists(includeRewritePath), "LC007 fixer Include rewrite construction should live in a focused partial file."); + + var syntaxAnalysisSource = File.ReadAllText(syntaxAnalysisPath); + Assert.DoesNotContain("private static bool TryAddInclude", syntaxAnalysisSource); + + var includeRewriteSource = File.ReadAllText(includeRewritePath); + Assert.Contains("private static bool TryAddInclude", includeRewriteSource); + Assert.Contains("SyntaxFactory.InvocationExpression", includeRewriteSource); + Assert.Contains("SyntaxFactory.IdentifierName(\"Include\")", includeRewriteSource); + } + + [Fact] + public void LC010_FreshContextExemption_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC010_SaveChangesInLoop"); + var analyzerPath = Path.Combine(analyzerDir, "SaveChangesInLoopAnalyzer.cs"); + var freshContextPath = Path.Combine(analyzerDir, "SaveChangesInLoopFreshContext.cs"); + + Assert.True(File.Exists(freshContextPath), "LC010 fresh DbContext-in-loop exemption should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static bool IsSaveReceiverFreshContextDeclaredInsideLoopBody", analyzerSource); + Assert.DoesNotContain("private static bool IsLocalWrittenBeforeOperation", analyzerSource); + Assert.DoesNotContain("private static bool IsLocalReference", analyzerSource); + + var freshContextSource = File.ReadAllText(freshContextPath); + Assert.Contains("private static bool IsSaveReceiverFreshContextDeclaredInsideLoopBody", freshContextSource); + Assert.Contains("private static bool IsLocalWrittenBeforeOperation", freshContextSource); + Assert.Contains("private static bool IsLocalReference", freshContextSource); + } + + [Fact] + public void LC033_ToHashSetInvocationClassification_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC033_UseFrozenSetForStaticMembershipCaches"); + var initializerPath = Path.Combine(analyzerDir, "UseFrozenSetForStaticMembershipCachesInitializerClassification.cs"); + var toHashSetPath = Path.Combine(analyzerDir, "UseFrozenSetForStaticMembershipCachesToHashSetClassification.cs"); + + Assert.True(File.Exists(toHashSetPath), "LC033 ToHashSet invocation classification should live in a focused partial file."); + + var initializerSource = File.ReadAllText(initializerPath); + Assert.DoesNotContain("private static bool IsSupportedToHashSetInvocation", initializerSource); + Assert.DoesNotContain("private static bool IsStaticTypeOrNamespaceAccess", initializerSource); + Assert.DoesNotContain("private static bool IsTypeOrNamespaceSymbol", initializerSource); + + var toHashSetSource = File.ReadAllText(toHashSetPath); + Assert.Contains("private static bool IsSupportedToHashSetInvocation", toHashSetSource); + Assert.Contains("private static bool IsStaticTypeOrNamespaceAccess", toHashSetSource); + Assert.Contains("private static bool IsTypeOrNamespaceSymbol", toHashSetSource); + } + + [Fact] + public void LC022_GroupingQueryableExemption_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC022_ToListInSelectProjection"); + var analyzerPath = Path.Combine(analyzerDir, "ToListInSelectProjectionAnalyzer.cs"); + var groupingPath = Path.Combine(analyzerDir, "ToListInSelectProjectionGrouping.cs"); + + Assert.True(File.Exists(groupingPath), "LC022 grouping-queryable exemption should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class ToListInSelectProjectionAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool IsGroupingQueryable", analyzerSource); + Assert.DoesNotContain("private static ITypeSymbol? GetQueryableElementType", analyzerSource); + Assert.DoesNotContain("private static bool IsGroupingType", analyzerSource); + + var groupingSource = File.ReadAllText(groupingPath); + Assert.Contains("private static bool IsGroupingQueryable", groupingSource); + Assert.Contains("private static ITypeSymbol? GetQueryableElementType", groupingSource); + Assert.Contains("private static bool IsGroupingType", groupingSource); + } + + [Fact] + public void LC003_FixerZeroConstantDetection_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC003_AnyOverCount"); + var fixerPath = Path.Combine(fixerDir, "AnyOverCountFixer.cs"); + var zeroConstantPath = Path.Combine(fixerDir, "AnyOverCountFixerZeroConstant.cs"); + + Assert.True(File.Exists(zeroConstantPath), "LC003 fixer zero-constant detection should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class AnyOverCountFixer", fixerSource); + Assert.DoesNotContain("private static bool HasZeroConstant", fixerSource); + Assert.DoesNotContain("private static bool IsZeroConstant", fixerSource); + Assert.DoesNotContain("private static bool IsZeroValue", fixerSource); + + var zeroConstantSource = File.ReadAllText(zeroConstantPath); + Assert.Contains("private static bool HasZeroConstant", zeroConstantSource); + Assert.Contains("private static bool IsZeroConstant", zeroConstantSource); + Assert.Contains("private static bool IsZeroValue", zeroConstantSource); + } + + [Fact] + public void LC027_FixerPrimaryKeyDiscovery_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC027_MissingExplicitForeignKey"); + var fixerPath = Path.Combine(fixerDir, "MissingExplicitForeignKeyFixer.cs"); + var primaryKeyPath = Path.Combine(fixerDir, "MissingExplicitForeignKeyFixerPrimaryKeyDiscovery.cs"); + + Assert.True(File.Exists(primaryKeyPath), "LC027 fixer primary-key discovery should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class MissingExplicitForeignKeyFixer", fixerSource); + Assert.DoesNotContain("private static IPropertySymbol? TryFindConventionPrimaryKey", fixerSource); + Assert.DoesNotContain("private static IPropertySymbol? TryFindConfiguredPrimaryKey", fixerSource); + Assert.DoesNotContain("private static bool TryGetEntityTypeBuilderEntity", fixerSource); + + var primaryKeySource = File.ReadAllText(primaryKeyPath); + Assert.Contains("private static IPropertySymbol? TryFindConventionPrimaryKey", primaryKeySource); + Assert.Contains("private static IPropertySymbol? TryFindConfiguredPrimaryKey", primaryKeySource); + Assert.Contains("private static bool TryGetEntityTypeBuilderEntity", primaryKeySource); + } + + [Fact] + public void LC016_QueryableScopeDetection_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC016_AvoidDateTimeNow"); + var analyzerPath = Path.Combine(analyzerDir, "AvoidDateTimeNowAnalyzer.cs"); + var queryableScopePath = Path.Combine(analyzerDir, "AvoidDateTimeNowQueryableScope.cs"); + + Assert.True(File.Exists(queryableScopePath), "LC016 queryable-lambda scope detection should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AvoidDateTimeNowAnalyzer", analyzerSource); + Assert.DoesNotContain("private static IAnonymousFunctionOperation? FindQueryableLambda", analyzerSource); + Assert.DoesNotContain("private static bool IsTargetQueryableInvocation", analyzerSource); + Assert.DoesNotContain("private static IAnonymousFunctionOperation? FindEnclosingLambda", analyzerSource); + Assert.DoesNotContain("private static IEnumerable EnumerateOperations", analyzerSource); + + var queryableScopeSource = File.ReadAllText(queryableScopePath); + Assert.Contains("private static IAnonymousFunctionOperation? FindQueryableLambda", queryableScopeSource); + Assert.Contains("private static bool IsTargetQueryableInvocation", queryableScopeSource); + Assert.Contains("private static IAnonymousFunctionOperation? FindEnclosingLambda", queryableScopeSource); + Assert.Contains("private static IEnumerable EnumerateOperations", queryableScopeSource); + } + + [Fact] + public void LC027_TypeIndexConstruction_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC027_MissingExplicitForeignKey"); + var typeLookupPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyTypeLookup.cs"); + var typeIndexPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyTypeIndex.cs"); + + Assert.True(File.Exists(typeIndexPath), "LC027 type-index construction should live in a focused partial file."); + + var typeLookupSource = File.ReadAllText(typeLookupPath); + Assert.Contains("private sealed partial class CompilationModel", typeLookupSource); + Assert.DoesNotContain("private TypeIndex BuildTypeIndex", typeLookupSource); + Assert.DoesNotContain("private sealed class TypeIndex", typeLookupSource); + Assert.DoesNotContain("private static void AddNamespaceTypes", typeLookupSource); + Assert.DoesNotContain("private static void AddTypeAndNestedTypes", typeLookupSource); + Assert.DoesNotContain("private static void AddLookupName", typeLookupSource); + + var typeIndexSource = File.ReadAllText(typeIndexPath); + Assert.Contains("private TypeIndex BuildTypeIndex", typeIndexSource); + Assert.Contains("private sealed class TypeIndex", typeIndexSource); + Assert.Contains("private static void AddNamespaceTypes", typeIndexSource); + Assert.Contains("private static void AddTypeAndNestedTypes", typeIndexSource); + Assert.Contains("private static void AddLookupName", typeIndexSource); + } + + [Fact] + public void LC001_QueryableScopeDetection_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC001_LocalMethod"); + var analyzerPath = Path.Combine(analyzerDir, "LocalMethodAnalyzer.cs"); + var queryableScopePath = Path.Combine(analyzerDir, "LocalMethodAnalyzerQueryableScope.cs"); + + Assert.True(File.Exists(queryableScopePath), "LC001 queryable-scope detection should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static bool IsTranslationCriticalQueryableInvocation", analyzerSource); + Assert.DoesNotContain("private static IArgumentOperation? GetInputSequenceArgument", analyzerSource); + Assert.DoesNotContain("private static bool InvocationDependsOnLambdaParameter", analyzerSource); + + var queryableScopeSource = File.ReadAllText(queryableScopePath); + Assert.Contains("private static bool IsTranslationCriticalQueryableInvocation", queryableScopeSource); + Assert.Contains("private static IArgumentOperation? GetInputSequenceArgument", queryableScopeSource); + Assert.Contains("private static bool InvocationDependsOnLambdaParameter", queryableScopeSource); + } + + [Fact] + public void LC029_IdentitySelectorClassification_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC029_RedundantIdentitySelect"); + var analyzerPath = Path.Combine(analyzerDir, "RedundantIdentitySelectAnalyzer.cs"); + var identitySelectorPath = Path.Combine(analyzerDir, "RedundantIdentitySelectIdentitySelector.cs"); + + Assert.True(File.Exists(identitySelectorPath), "LC029 identity-selector classification should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class RedundantIdentitySelectAnalyzer", analyzerSource); + Assert.DoesNotContain("private static IAnonymousFunctionOperation? TryGetLambda", analyzerSource); + Assert.DoesNotContain("private static bool IsExactEnumerableInterface", analyzerSource); + Assert.DoesNotContain("private static bool IsTypePreservingSelector", analyzerSource); + Assert.DoesNotContain("private static bool IsIdentityLambda", analyzerSource); + Assert.DoesNotContain("private static IOperation UnwrapIdentityValue", analyzerSource); + + var identitySelectorSource = File.ReadAllText(identitySelectorPath); + Assert.Contains("private static IAnonymousFunctionOperation? TryGetLambda", identitySelectorSource); + Assert.Contains("private static bool IsExactEnumerableInterface", identitySelectorSource); + Assert.Contains("private static bool IsTypePreservingSelector", identitySelectorSource); + Assert.Contains("private static bool IsIdentityLambda", identitySelectorSource); + Assert.Contains("private static IOperation UnwrapIdentityValue", identitySelectorSource); + } + + [Fact] + public void LC012_FixerQuerySourceContextResolution_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC012_OptimizeRemoveRange"); + var rewriteSafetyPath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerRewriteSafety.cs"); + var querySourcePath = Path.Combine(fixerDir, "OptimizeRemoveRangeFixerQuerySourceContext.cs"); + + Assert.True(File.Exists(querySourcePath), "LC012 fixer query-source context resolution should live in a focused partial file."); + + var rewriteSafetySource = File.ReadAllText(rewriteSafetyPath); + Assert.DoesNotContain("private static bool TryResolveQuerySourceFreshContextLocal", rewriteSafetySource); + Assert.DoesNotContain("private static bool TryGetTransparentQueryInvocationSource", rewriteSafetySource); + Assert.DoesNotContain("private static bool IsSingleSourceTransparentQueryMethod", rewriteSafetySource); + + var querySource = File.ReadAllText(querySourcePath); + Assert.Contains("private static bool TryResolveQuerySourceFreshContextLocal", querySource); + Assert.Contains("private static bool TryGetTransparentQueryInvocationSource", querySource); + Assert.Contains("private static bool IsSingleSourceTransparentQueryMethod", querySource); + } + + [Fact] + public void LC036_LocalFunctionCallbackCapture_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC036_DbContextCapturedAcrossThreads"); + var captureAnalysisPath = Path.Combine(analyzerDir, "DbContextCapturedAcrossThreadsCaptureAnalysis.cs"); + var localFunctionPath = Path.Combine(analyzerDir, "DbContextCapturedAcrossThreadsLocalFunctionCapture.cs"); + + Assert.True(File.Exists(localFunctionPath), "LC036 local-function callback capture should live in a focused partial file."); + + var captureAnalysisSource = File.ReadAllText(captureAnalysisPath); + Assert.DoesNotContain("private static bool TryFindCapturedDbContextInLocalFunctionCallback", captureAnalysisSource); + + var localFunctionSource = File.ReadAllText(localFunctionPath); + Assert.Contains("private static bool TryFindCapturedDbContextInLocalFunctionCallback", localFunctionSource); + Assert.Contains("LocalFunctionStatementSyntax", localFunctionSource); + Assert.Contains("MethodKind.LocalFunction", localFunctionSource); + } + + [Fact] + public void LC035_WhereChainTraversal_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC035_MissingWhereBeforeExecuteDeleteUpdate"); + var analyzerPath = Path.Combine(analyzerDir, "MissingWhereBeforeExecuteDeleteUpdateAnalyzer.cs"); + var whereChainPath = Path.Combine(analyzerDir, "MissingWhereBeforeExecuteDeleteUpdateWhereChain.cs"); + + Assert.True(File.Exists(whereChainPath), "LC035 Where-chain traversal should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static bool HasWhereInChain", analyzerSource); + Assert.DoesNotContain("private static bool IsKnownLinqWhere", analyzerSource); + Assert.DoesNotContain("private static bool HasQuerySyntaxWhere", analyzerSource); + + var whereChainSource = File.ReadAllText(whereChainPath); + Assert.Contains("private static bool HasWhereInChain", whereChainSource); + Assert.Contains("private static bool IsKnownLinqWhere", whereChainSource); + Assert.Contains("private static bool HasQuerySyntaxWhere", whereChainSource); + } + + [Fact] + public void LC002_FixerRedundantMaterializationRewrite_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC002_PrematureMaterialization"); + var fixerPath = Path.Combine(fixerDir, "PrematureMaterializationFixer.cs"); + var redundantRewritePath = Path.Combine(fixerDir, "PrematureMaterializationFixerRedundantMaterialization.cs"); + + Assert.True(File.Exists(redundantRewritePath), "LC002 redundant-materialization fixer rewrite should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.DoesNotContain("private static async Task RemoveRedundantMaterializationAsync", fixerSource); + + var redundantRewriteSource = File.ReadAllText(redundantRewritePath); + Assert.Contains("private static async Task RemoveRedundantMaterializationAsync", redundantRewriteSource); + Assert.Contains("TryGetInlineMaterializerParts", redundantRewriteSource); + Assert.Contains("Formatter.Annotation", redundantRewriteSource); + } + + [Fact] + public void LC037_JumpSkippingFlow_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var terminationFlowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionTerminationFlow.cs"); + var jumpFlowPath = Path.Combine(analyzerDir, "RawSqlStringConstructionJumpFlow.cs"); + + Assert.True(File.Exists(jumpFlowPath), "LC037 jump/reference skipping flow should live in a focused partial file."); + + var terminationFlowSource = File.ReadAllText(terminationFlowPath); + Assert.DoesNotContain("private static bool HasPotentiallySkippingAncestor", terminationFlowSource); + Assert.DoesNotContain("private static bool ContainsPosition", terminationFlowSource); + Assert.DoesNotContain("private static bool JumpSkipsReference", terminationFlowSource); + + var jumpFlowSource = File.ReadAllText(jumpFlowPath); + Assert.Contains("private static bool HasPotentiallySkippingAncestor", jumpFlowSource); + Assert.Contains("private static bool ContainsPosition", jumpFlowSource); + Assert.Contains("private static bool JumpSkipsReference", jumpFlowSource); + } + + [Fact] + public void LocalAssignmentCache_RootScan_LivesInDedicatedPartial() + { + var extensionsDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Extensions"); + var cachePath = Path.Combine(extensionsDir, "LocalAssignmentCache.cs"); + var rootScanPath = Path.Combine(extensionsDir, "LocalAssignmentCacheRootScan.cs"); + + Assert.True(File.Exists(rootScanPath), "LocalAssignmentCache root scanning should live in a focused partial file."); + + var cacheSource = File.ReadAllText(cachePath); + Assert.Contains("internal static partial class LocalAssignmentCache", cacheSource); + Assert.DoesNotContain("private sealed class RootScan", cacheSource); + Assert.DoesNotContain("public static RootScan Build", cacheSource); + + var rootScanSource = File.ReadAllText(rootScanPath); + Assert.Contains("private sealed class RootScan", rootScanSource); + Assert.Contains("public static RootScan Build", rootScanSource); + Assert.Contains("private static void Add", rootScanSource); + } + + [Fact] + public void LC002_ProviderSafeStringMethods_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC002_PrematureMaterialization"); + var providerSafePath = Path.Combine(analyzerDir, "PrematureMaterializationProviderSafeExpressions.cs"); + var stringMethodsPath = Path.Combine(analyzerDir, "PrematureMaterializationProviderSafeStringMethods.cs"); + + Assert.True(File.Exists(stringMethodsPath), "LC002 provider-safe string method rules should live in a focused partial file."); + + var providerSafeSource = File.ReadAllText(providerSafePath); + Assert.DoesNotContain("private static bool IsAllowedProviderSafeStringMethod", providerSafeSource); + Assert.DoesNotContain("private static bool HasStringComparisonParameter", providerSafeSource); + + var stringMethodsSource = File.ReadAllText(stringMethodsPath); + Assert.Contains("private static bool IsAllowedProviderSafeStringMethod", stringMethodsSource); + Assert.Contains("private static bool HasStringComparisonParameter", stringMethodsSource); + Assert.Contains("StringComparison", stringMethodsSource); + } + + [Fact] + public void LC016_FixerStaticLambdaSafety_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC016_AvoidDateTimeNow"); + var fixerPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixer.cs"); + var staticLambdaPath = Path.Combine(fixerDir, "AvoidDateTimeNowFixerStaticLambda.cs"); + + Assert.True(File.Exists(staticLambdaPath), "LC016 static-lambda fixer safety should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.DoesNotContain("private static bool IsInsideStaticLambda", fixerSource); + Assert.DoesNotContain("private static bool HasStaticModifier", fixerSource); + + var staticLambdaSource = File.ReadAllText(staticLambdaPath); + Assert.Contains("private static bool IsInsideStaticLambda", staticLambdaSource); + Assert.Contains("private static bool HasStaticModifier", staticLambdaSource); + Assert.Contains("SyntaxKind.StaticKeyword", staticLambdaSource); + } + + [Fact] + public void LC044_ReachabilityBlockNavigation_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC044_AsNoTrackingThenModify"); + var reachabilityPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyReachability.cs"); + var blockNavigationPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyReachabilityBlocks.cs"); + + Assert.True(File.Exists(blockNavigationPath), "LC044 reachability block navigation should live in a focused partial file."); + + var reachabilitySource = File.ReadAllText(reachabilityPath); + Assert.DoesNotContain("private static bool IsBlockAncestor", reachabilitySource); + Assert.DoesNotContain("private static IOperation? FindDirectChildOperationContainingSpan", reachabilitySource); + Assert.DoesNotContain("private static IBlockOperation? FindEnclosingBlock", reachabilitySource); + + var blockNavigationSource = File.ReadAllText(blockNavigationPath); + Assert.Contains("private static bool IsBlockAncestor", blockNavigationSource); + Assert.Contains("private static IOperation? FindDirectChildOperationContainingSpan", blockNavigationSource); + Assert.Contains("private static IBlockOperation? FindEnclosingBlock", blockNavigationSource); + } + + [Fact] + public void LC030_LongLivedTypeRecognition_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC030_DbContextInSingleton"); + var evidencePath = Path.Combine(analyzerDir, "DbContextInSingletonLongLivedEvidence.cs"); + var typeRecognitionPath = Path.Combine(analyzerDir, "DbContextInSingletonLongLivedTypeRecognition.cs"); + + Assert.True(File.Exists(typeRecognitionPath), "LC030 long-lived/scoped type recognition helpers should live in a focused partial file."); + + var evidenceSource = File.ReadAllText(evidencePath); + Assert.DoesNotContain("private static bool IsKnownScopedType", evidenceSource); + Assert.DoesNotContain("private static bool HasConventionalMiddlewareSignature", evidenceSource); + Assert.DoesNotContain("private static bool ImplementsInterface", evidenceSource); + Assert.DoesNotContain("private static bool InheritsFrom", evidenceSource); + + var typeRecognitionSource = File.ReadAllText(typeRecognitionPath); + Assert.Contains("private static bool IsKnownScopedType", typeRecognitionSource); + Assert.Contains("private static bool HasConventionalMiddlewareSignature", typeRecognitionSource); + Assert.Contains("private static bool ImplementsInterface", typeRecognitionSource); + Assert.Contains("private static bool InheritsFrom", typeRecognitionSource); + } + + [Fact] + public void LC012_FixerSaveChangesBranchExclusion_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC012_OptimizeRemoveRange"); + var saveChangesSafetyPath = Path.Combine(analyzerDir, "OptimizeRemoveRangeFixerSaveChangesSafety.cs"); + var branchExclusionPath = Path.Combine(analyzerDir, "OptimizeRemoveRangeFixerSaveChangesBranchExclusion.cs"); + + Assert.True(File.Exists(branchExclusionPath), "LC012 fixer save-changes branch exclusion should live in a focused partial file."); + + var saveChangesSafetySource = File.ReadAllText(saveChangesSafetyPath); + Assert.DoesNotContain("private static bool AreMutuallyExclusiveBranches", saveChangesSafetySource); + Assert.DoesNotContain("private static SyntaxNode? GetContainingIfBranch", saveChangesSafetySource); + Assert.DoesNotContain("private static SwitchSectionSyntax? GetContainingSwitchSection", saveChangesSafetySource); + + var branchExclusionSource = File.ReadAllText(branchExclusionPath); + Assert.Contains("private static bool AreMutuallyExclusiveBranches", branchExclusionSource); + Assert.Contains("private static SyntaxNode? GetContainingIfBranch", branchExclusionSource); + Assert.Contains("private static SwitchSectionSyntax? GetContainingSwitchSection", branchExclusionSource); + } + + [Fact] + public void LC037_StringBuilderIdentityReferenceTraversal_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC037_RawSqlStringConstruction"); + var identityPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderIdentity.cs"); + var referenceTraversalPath = Path.Combine(analyzerDir, "RawSqlStringConstructionStringBuilderIdentityReferences.cs"); + + Assert.True(File.Exists(referenceTraversalPath), "LC037 StringBuilder identity reference traversal should live in a focused partial file."); + + var identitySource = File.ReadAllText(identityPath); + Assert.DoesNotContain("private static bool MayReferenceIdentity", identitySource); + + var referenceTraversalSource = File.ReadAllText(referenceTraversalPath); + Assert.Contains("private static bool MayReferenceIdentity", referenceTraversalSource); + Assert.Contains("IInterpolatedStringOperation", referenceTraversalSource); + Assert.Contains("IObjectCreationOperation", referenceTraversalSource); + Assert.Contains("IInvocationOperation", referenceTraversalSource); + } + + [Fact] + public void LC013_AssignedValueCollection_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC013_DisposedContextQuery"); + var originResolutionPath = Path.Combine(analyzerDir, "DisposedContextQueryAssignedOriginResolution.cs"); + var assignedValuesPath = Path.Combine(analyzerDir, "DisposedContextQueryAssignedValues.cs"); + + Assert.True(File.Exists(assignedValuesPath), "LC013 assigned-value collection should live in a focused partial file."); + + var originResolutionSource = File.ReadAllText(originResolutionPath); + Assert.DoesNotContain("private static bool TryGetSingleAssignedValue", originResolutionSource); + Assert.DoesNotContain("private static List GetAssignedValues", originResolutionSource); + Assert.DoesNotContain("private static bool IsLocalTarget", originResolutionSource); + Assert.DoesNotContain("private static IEnumerable EnumerateOperations", originResolutionSource); + + var assignedValuesSource = File.ReadAllText(assignedValuesPath); + Assert.Contains("private static bool TryGetSingleAssignedValue", assignedValuesSource); + Assert.Contains("private static List GetAssignedValues", assignedValuesSource); + Assert.Contains("private static bool IsLocalTarget", assignedValuesSource); + Assert.Contains("private static IEnumerable EnumerateOperations", assignedValuesSource); + } + + [Fact] + public void LC041_FixerInvocationRewrite_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC041_SingleEntityScalarProjection"); + var fixerPath = Path.Combine(fixerDir, "SingleEntityScalarProjectionFixer.cs"); + var rewritePath = Path.Combine(fixerDir, "SingleEntityScalarProjectionFixerRewrite.cs"); + + Assert.True(File.Exists(rewritePath), "LC041 fixer invocation rewrite should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.DoesNotContain("private static ExpressionSyntax RewriteInvocation", fixerSource); + Assert.DoesNotContain("private static int FindPredicateIndex", fixerSource); + + var rewriteSource = File.ReadAllText(rewritePath); + Assert.Contains("private static ExpressionSyntax RewriteInvocation", rewriteSource); + Assert.Contains("private static int FindPredicateIndex", rewriteSource); + Assert.Contains(".Select(x => x.", rewriteSource); + } + + [Fact] + public void LC002_MaterializerClassification_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "MaterializationAndProjection", + "LC002_PrematureMaterialization"); + var methodRulesPath = Path.Combine(analyzerDir, "PrematureMaterializationMethodRules.cs"); + var materializersPath = Path.Combine(analyzerDir, "PrematureMaterializationMaterializerRules.cs"); + + Assert.True(File.Exists(materializersPath), "LC002 materializer classification rules should live in a focused partial file."); + + var methodRulesSource = File.ReadAllText(methodRulesPath); + Assert.DoesNotContain("private static bool IsMaterializingMethod", methodRulesSource); + Assert.DoesNotContain("private static bool IsDirectCollectionMaterializer", methodRulesSource); + Assert.DoesNotContain("private static bool IsMaterializingConstructor", methodRulesSource); + + var materializersSource = File.ReadAllText(materializersPath); + Assert.Contains("private static bool IsMaterializingMethod", materializersSource); + Assert.Contains("private static bool IsDirectCollectionMaterializer", materializersSource); + Assert.Contains("private static bool IsDeduplicatingSetMaterializer", materializersSource); + Assert.Contains("private static bool IsKeyedOrGroupedMaterializer", materializersSource); + Assert.Contains("private static bool IsMaterializingConstructor", materializersSource); + } + + [Fact] + public void LC044_TrackingStateDetachInvalidation_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC044_AsNoTrackingThenModify"); + var trackingStatePath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyTrackingState.cs"); + var detachInvalidationPath = Path.Combine(analyzerDir, "AsNoTrackingThenModifyTrackingDetachInvalidation.cs"); + + Assert.True(File.Exists(detachInvalidationPath), "LC044 detach/tracker-clear invalidation should live in a focused partial file."); + + var trackingStateSource = File.ReadAllText(trackingStatePath); + Assert.DoesNotContain("private static bool HasInterveningDetach", trackingStateSource); + + var detachInvalidationSource = File.ReadAllText(detachInvalidationPath); + Assert.Contains("private static bool HasInterveningDetach", detachInvalidationSource); + Assert.Contains("scan.DetachesByLocal", detachInvalidationSource); + Assert.Contains("scan.TrackerClears", detachInvalidationSource); + } + + [Fact] + public void LC025_SourceResolutionConditionalOrigins_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC025_AsNoTrackingWithUpdate"); + var sourceResolutionPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateSourceResolution.cs"); + var conditionalOriginsPath = Path.Combine(analyzerDir, "AsNoTrackingWithUpdateSourceConditionalOrigins.cs"); + + Assert.True(File.Exists(conditionalOriginsPath), "LC025 conditional origin state should live in a focused partial file."); + + var sourceResolutionSource = File.ReadAllText(sourceResolutionPath); + Assert.DoesNotContain("private readonly struct LocalOrigin", sourceResolutionSource); + Assert.DoesNotContain("private static bool IsConditionalRelativeTo", sourceResolutionSource); + + var conditionalOriginsSource = File.ReadAllText(conditionalOriginsPath); + Assert.Contains("private readonly struct LocalOrigin", conditionalOriginsSource); + Assert.Contains("private static bool IsConditionalRelativeTo", conditionalOriginsSource); + Assert.Contains("SwitchExpressionSyntax", conditionalOriginsSource); + Assert.Contains("CommonForEachStatementSyntax", conditionalOriginsSource); + } + + [Fact] + public void LC034_SqlSafetyDetection_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC034_AvoidExecuteSqlRawWithInterpolation"); + var analyzerPath = Path.Combine(analyzerDir, "AvoidExecuteSqlRawWithInterpolationAnalyzer.cs"); + var sqlSafetyPath = Path.Combine(analyzerDir, "AvoidExecuteSqlRawWithInterpolationSqlSafety.cs"); + + Assert.True(File.Exists(sqlSafetyPath), "LC034 SQL safety classification should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AvoidExecuteSqlRawWithInterpolationAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool IsPotentiallyUnsafeSql", analyzerSource); + Assert.DoesNotContain("private static bool IsUnsafeConcatenation", analyzerSource); + Assert.DoesNotContain("private static bool HasNonConstantInterpolation", analyzerSource); + + var sqlSafetySource = File.ReadAllText(sqlSafetyPath); + Assert.Contains("private static bool IsPotentiallyUnsafeSql", sqlSafetySource); + Assert.Contains("private static bool IsUnsafeConcatenation", sqlSafetySource); + Assert.Contains("private static bool HasNonConstantInterpolation", sqlSafetySource); + Assert.Contains("IInterpolatedStringOperation", sqlSafetySource); + } + + [Fact] + public void LC005_FixerRewriteValidation_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "QueryShapeAndTranslation", + "LC005_MultipleOrderBy"); + var fixerPath = Path.Combine(fixerDir, "MultipleOrderByFixer.cs"); + var validationPath = Path.Combine(fixerDir, "MultipleOrderByFixerRewriteValidation.cs"); + + Assert.True(File.Exists(validationPath), "LC005 fixer rewrite validation should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class MultipleOrderByFixer", fixerSource); + Assert.DoesNotContain("private static bool CanRewriteToThenBy", fixerSource); + Assert.DoesNotContain("private static ExpressionSyntax? GetLogicalReceiverExpression", fixerSource); + Assert.DoesNotContain("private static bool IsOrderedSequence", fixerSource); + + var validationSource = File.ReadAllText(validationPath); + Assert.Contains("private static bool CanRewriteToThenBy", validationSource); + Assert.Contains("private static ExpressionSyntax? GetLogicalReceiverExpression", validationSource); + Assert.Contains("private static bool IsOrderedSequence", validationSource); + Assert.Contains("IOrderedQueryable", validationSource); + } + + [Fact] + public void LC008_FixerAwaitContextValidation_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC008_SyncBlocker"); + var fixerPath = Path.Combine(fixerDir, "SyncBlockerFixer.cs"); + var awaitContextPath = Path.Combine(fixerDir, "SyncBlockerFixerAwaitContext.cs"); + + Assert.True(File.Exists(awaitContextPath), "LC008 fixer await-context validation should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class SyncBlockerFixer", fixerSource); + Assert.DoesNotContain("private static bool IsInvalidAwaitContext", fixerSource); + Assert.DoesNotContain("FromClauseSyntax", fixerSource); + Assert.DoesNotContain("AnonymousFunctionExpressionSyntax", fixerSource); + + var awaitContextSource = File.ReadAllText(awaitContextPath); + Assert.Contains("private static bool IsInvalidAwaitContext", awaitContextSource); + Assert.Contains("FromClauseSyntax", awaitContextSource); + Assert.Contains("JoinClauseSyntax", awaitContextSource); + Assert.Contains("AnonymousFunctionExpressionSyntax", awaitContextSource); + Assert.Contains("LocalFunctionStatementSyntax", awaitContextSource); + } + + [Fact] + public void LC018_AnalyzerSqlSafetyDetection_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC018_AvoidFromSqlRawWithInterpolation"); + var analyzerPath = Path.Combine(analyzerDir, "AvoidFromSqlRawWithInterpolationAnalyzer.cs"); + var sqlSafetyPath = Path.Combine(analyzerDir, "AvoidFromSqlRawWithInterpolationAnalyzerSqlSafety.cs"); + + Assert.True(File.Exists(sqlSafetyPath), "LC018 analyzer SQL safety classification should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class AvoidFromSqlRawWithInterpolationAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool IsPotentiallyUnsafe", analyzerSource); + Assert.DoesNotContain("private static bool IsUnsafeConcatenation", analyzerSource); + Assert.DoesNotContain("private static bool HasNonConstantInterpolation", analyzerSource); + + var sqlSafetySource = File.ReadAllText(sqlSafetyPath); + Assert.Contains("private static bool IsPotentiallyUnsafe", sqlSafetySource); + Assert.Contains("private static bool IsUnsafeConcatenation", sqlSafetySource); + Assert.Contains("private static bool HasNonConstantInterpolation", sqlSafetySource); + Assert.Contains("IInterpolatedStringOperation", sqlSafetySource); + } + + [Fact] + public void LC042_QueryMethodLookupTables_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC042_MissingQueryTags"); + var analyzerPath = Path.Combine(analyzerDir, "MissingQueryTagsAnalyzer.cs"); + var methodSetsPath = Path.Combine(analyzerDir, "MissingQueryTagsMethodSets.cs"); + + Assert.True(File.Exists(methodSetsPath), "LC042 query method lookup tables should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static readonly ImmutableHashSet TargetMethods", analyzerSource); + Assert.DoesNotContain("private static readonly ImmutableHashSet QuerySteps", analyzerSource); + + var methodSetsSource = File.ReadAllText(methodSetsPath); + Assert.Contains("private static readonly ImmutableHashSet TargetMethods", methodSetsSource); + Assert.Contains("private static readonly ImmutableHashSet QuerySteps", methodSetsSource); + Assert.Contains("\"TagWithCallSite\"", methodSetsSource); + Assert.Contains("\"ToHashSetAsync\"", methodSetsSource); + } + + [Fact] + public void LC028_ThenIncludeChainAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC028_DeepThenInclude"); + var analyzerPath = Path.Combine(analyzerDir, "DeepThenIncludeAnalyzer.cs"); + var chainAnalysisPath = Path.Combine(analyzerDir, "DeepThenIncludeChainAnalysis.cs"); + + Assert.True(File.Exists(chainAnalysisPath), "LC028 ThenInclude chain analysis should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class DeepThenIncludeAnalyzer", analyzerSource); + Assert.DoesNotContain("private static int CountThenIncludeDepth", analyzerSource); + Assert.DoesNotContain("private static Location GetDiagnosticLocation", analyzerSource); + Assert.DoesNotContain("TextSpan.FromBounds", analyzerSource); + + var chainAnalysisSource = File.ReadAllText(chainAnalysisPath); + Assert.Contains("private static int CountThenIncludeDepth", chainAnalysisSource); + Assert.Contains("private static Location GetDiagnosticLocation", chainAnalysisSource); + Assert.Contains("TextSpan.FromBounds", chainAnalysisSource); + Assert.Contains("MemberAccessExpressionSyntax", chainAnalysisSource); + } + + [Fact] + public void SymbolAnalysis_PrimaryKeyDiscovery_LivesInDedicatedPartial() + { + var extensionsDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Extensions"); + var symbolExtensionsPath = Path.Combine(extensionsDir, "SymbolAnalysisExtensions.cs"); + var primaryKeyPath = Path.Combine(extensionsDir, "SymbolAnalysisPrimaryKeyExtensions.cs"); + + Assert.True(File.Exists(primaryKeyPath), "Primary-key symbol discovery should live in a focused extension partial file."); + + var symbolExtensionsSource = File.ReadAllText(symbolExtensionsPath); + Assert.DoesNotContain("public static string? TryFindPrimaryKey", symbolExtensionsSource); + Assert.DoesNotContain("private static bool IsDataAnnotationsKeyAttribute", symbolExtensionsSource); + + var primaryKeySource = File.ReadAllText(primaryKeyPath); + Assert.Contains("public static string? TryFindPrimaryKey", primaryKeySource); + Assert.Contains("private static bool IsDataAnnotationsKeyAttribute", primaryKeySource); + Assert.Contains("System.ComponentModel.DataAnnotations", primaryKeySource); + } + + [Fact] + public void LC019_ConditionalIncludePathAnalysis_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC019_ConditionalInclude"); + var analyzerPath = Path.Combine(analyzerDir, "ConditionalIncludeAnalyzer.cs"); + var pathAnalysisPath = Path.Combine(analyzerDir, "ConditionalIncludePathAnalysis.cs"); + + Assert.True(File.Exists(pathAnalysisPath), "LC019 conditional include path traversal should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.Contains("public sealed partial class ConditionalIncludeAnalyzer", analyzerSource); + Assert.DoesNotContain("private static bool HasConditionalIncludePath", analyzerSource); + Assert.DoesNotContain("private static bool HasConditionalIncludeInvocationSource", analyzerSource); + + var pathAnalysisSource = File.ReadAllText(pathAnalysisPath); + Assert.Contains("private static bool HasConditionalIncludePath", pathAnalysisSource); + Assert.Contains("private static bool HasConditionalIncludeInvocationSource", pathAnalysisSource); + Assert.Contains("IConditionalOperation or ICoalesceOperation", pathAnalysisSource); + } + + [Fact] + public void LC026_FixerExplicitTokenArgument_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ExecutionAndAsync", + "LC026_MissingCancellationToken"); + var fixerPath = Path.Combine(fixerDir, "MissingCancellationTokenFixer.cs"); + var explicitArgumentPath = Path.Combine(fixerDir, "MissingCancellationTokenFixerExplicitArgument.cs"); + + Assert.True(File.Exists(explicitArgumentPath), "LC026 fixer explicit-token argument lookup should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class MissingCancellationTokenFixer", fixerSource); + Assert.DoesNotContain("private static ArgumentSyntax? FindExplicitCancellationTokenArgument", fixerSource); + Assert.DoesNotContain("private static bool IsCancellationTokenParameter", fixerSource); + + var explicitArgumentSource = File.ReadAllText(explicitArgumentPath); + Assert.Contains("private static ArgumentSyntax? FindExplicitCancellationTokenArgument", explicitArgumentSource); + Assert.Contains("private static bool IsCancellationTokenParameter", explicitArgumentSource); + Assert.Contains("IInvocationOperation", explicitArgumentSource); + } + + [Fact] + public void LC032_ScalarTypeClassification_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC032_ExecuteUpdateForBulkUpdates"); + var assignmentAnalysisPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesAssignmentAnalysis.cs"); + var scalarTypesPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesScalarTypes.cs"); + + Assert.True(File.Exists(scalarTypesPath), "LC032 scalar type classification should live in a focused partial file."); + + var assignmentAnalysisSource = File.ReadAllText(assignmentAnalysisPath); + Assert.DoesNotContain("private static bool IsScalarLikeType", assignmentAnalysisSource); + Assert.DoesNotContain("SymbolDisplayFormat.FullyQualifiedFormat", assignmentAnalysisSource); + + var scalarTypesSource = File.ReadAllText(scalarTypesPath); + Assert.Contains("private static bool IsScalarLikeType", scalarTypesSource); + Assert.Contains("SymbolDisplayFormat.FullyQualifiedFormat", scalarTypesSource); + Assert.Contains("global::System.DateTimeOffset", scalarTypesSource); + } + + [Fact] + public void LC032_QueryStepLookupTables_LiveInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "BulkOperationsAndSetBasedWrites", + "LC032_ExecuteUpdateForBulkUpdates"); + var queryAnalysisPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesQueryAnalysis.cs"); + var queryStepsPath = Path.Combine(analyzerDir, "ExecuteUpdateForBulkUpdatesQuerySteps.cs"); + + Assert.True(File.Exists(queryStepsPath), "LC032 allowed query/materializer step lookup tables should live in a focused partial file."); + + var queryAnalysisSource = File.ReadAllText(queryAnalysisPath); + Assert.DoesNotContain("private static readonly ImmutableHashSet AllowedQuerySteps", queryAnalysisSource); + Assert.DoesNotContain("private static readonly ImmutableHashSet MaterializerSteps", queryAnalysisSource); + + var queryStepsSource = File.ReadAllText(queryStepsPath); + Assert.Contains("private static readonly ImmutableHashSet AllowedQuerySteps", queryStepsSource); + Assert.Contains("private static readonly ImmutableHashSet MaterializerSteps", queryStepsSource); + Assert.Contains("\"TagWithCallSite\"", queryStepsSource); + Assert.Contains("\"ToArrayAsync\"", queryStepsSource); + } + + [Fact] + public void LC045_FixerQuerySourceResolution_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC045_MissingInclude"); + var fixerPath = Path.Combine(fixerDir, "MissingIncludeFixer.cs"); + var querySourcePath = Path.Combine(fixerDir, "MissingIncludeFixerQuerySource.cs"); + + Assert.True(File.Exists(querySourcePath), "LC045 fixer query-source resolution should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class MissingIncludeFixer", fixerSource); + Assert.DoesNotContain("private static async Task GetQueryableSourceAsync", fixerSource); + Assert.DoesNotContain("semanticModel?.GetTypeInfo(querySource, cancellationToken)", fixerSource); + + var querySource = File.ReadAllText(querySourcePath); + Assert.Contains("private static async Task GetQueryableSourceAsync", querySource); + Assert.Contains("semanticModel?.GetTypeInfo(querySource, cancellationToken)", querySource); + Assert.Contains("query source", querySource); + } + + [Fact] + public void LC006_ReceiverChainTraversal_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC006_CartesianExplosion"); + var chainAnalysisPath = Path.Combine(analyzerDir, "CartesianExplosionChainAnalysis.cs"); + var receiverChainPath = Path.Combine(analyzerDir, "CartesianExplosionReceiverChain.cs"); + + Assert.True(File.Exists(receiverChainPath), "LC006 receiver-chain traversal should live in a focused partial file."); + + var chainAnalysisSource = File.ReadAllText(chainAnalysisPath); + Assert.DoesNotContain("private static ImmutableArray CollectReceiverChainInvocations", chainAnalysisSource); + Assert.DoesNotContain("private static bool HasRelevantQueryOperatorAncestor", chainAnalysisSource); + Assert.DoesNotContain("private static bool InvocationUsesReceiverChain", chainAnalysisSource); + + var receiverChainSource = File.ReadAllText(receiverChainPath); + Assert.Contains("private static ImmutableArray CollectReceiverChainInvocations", receiverChainSource); + Assert.Contains("private static bool HasRelevantQueryOperatorAncestor", receiverChainSource); + Assert.Contains("private static bool InvocationUsesReceiverChain", receiverChainSource); + Assert.Contains("LocalAssignmentCache.TryGetSingleAssignedValueBefore", receiverChainSource); + } + + [Fact] + public void LC011_DbSetMemberExtraction_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var analyzerPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyAnalyzer.cs"); + var dbSetMembersPath = Path.Combine(analyzerDir, "EntityMissingPrimaryKeyDbSetMembers.cs"); + + Assert.True(File.Exists(dbSetMembersPath), "LC011 DbSet member extraction should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static bool TryGetDbSetMember", analyzerSource); + Assert.DoesNotContain("field.Name.StartsWith(\"<\", StringComparison.Ordinal)", analyzerSource); + + var dbSetMembersSource = File.ReadAllText(dbSetMembersPath); + Assert.Contains("private static bool TryGetDbSetMember", dbSetMembersSource); + Assert.Contains("IPropertySymbol property", dbSetMembersSource); + Assert.Contains("IFieldSymbol field", dbSetMembersSource); + Assert.Contains("field.Name.StartsWith(\"<\", StringComparison.Ordinal)", dbSetMembersSource); + } + + [Fact] + public void LC011_FixerEntityTypeResolution_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC011_EntityMissingPrimaryKey"); + var fixerPath = Path.Combine(fixerDir, "EntityMissingPrimaryKeyFixer.cs"); + var entityTypePath = Path.Combine(fixerDir, "EntityMissingPrimaryKeyFixerEntityType.cs"); + + Assert.True(File.Exists(entityTypePath), "LC011 fixer entity-type and Id-member checks should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class EntityMissingPrimaryKeyFixer", fixerSource); + Assert.DoesNotContain("private static bool TryGetEntityType", fixerSource); + Assert.DoesNotContain("private static bool HasIdMember", fixerSource); + + var entityTypeSource = File.ReadAllText(entityTypePath); + Assert.Contains("private static bool TryGetEntityType", entityTypeSource); + Assert.Contains("private static bool HasIdMember", entityTypeSource); + Assert.Contains("StringComparison.OrdinalIgnoreCase", entityTypeSource); + } + + [Fact] + public void LC034_FixerSqlArgumentSafety_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "RawSqlAndSecurity", + "LC034_AvoidExecuteSqlRawWithInterpolation"); + var fixerPath = Path.Combine(fixerDir, "AvoidExecuteSqlRawWithInterpolationFixer.cs"); + var sqlArgumentPath = Path.Combine(fixerDir, "AvoidExecuteSqlRawWithInterpolationFixerSqlArgument.cs"); + + Assert.True(File.Exists(sqlArgumentPath), "LC034 fixer SQL argument and literal-safety helpers should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class AvoidExecuteSqlRawWithInterpolationFixer", fixerSource); + Assert.DoesNotContain("private static ArgumentSyntax? GetSqlArgument", fixerSource); + Assert.DoesNotContain("private static bool HasInterpolationInsideSqlStringLiteral", fixerSource); + Assert.DoesNotContain("private static void UpdateSqlDelimiterState", fixerSource); + + var sqlArgumentSource = File.ReadAllText(sqlArgumentPath); + Assert.Contains("private static ArgumentSyntax? GetSqlArgument", sqlArgumentSource); + Assert.Contains("private static bool HasInterpolationInsideSqlStringLiteral", sqlArgumentSource); + Assert.Contains("private static void UpdateSqlDelimiterState", sqlArgumentSource); + Assert.Contains("InterpolationSyntax when insideSqlStringLiteral", sqlArgumentSource); + } + + [Fact] + public void LC006_FixerSyntaxChainSearch_LivesInDedicatedPartial() + { + var fixerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "LoadingAndIncludes", + "LC006_CartesianExplosion"); + var fixerPath = Path.Combine(fixerDir, "CartesianExplosionFixer.cs"); + var syntaxChainPath = Path.Combine(fixerDir, "CartesianExplosionFixerSyntaxChain.cs"); + + Assert.True(File.Exists(syntaxChainPath), "LC006 fixer syntax-chain search helpers should live in a focused partial file."); + + var fixerSource = File.ReadAllText(fixerPath); + Assert.Contains("public sealed partial class CartesianExplosionFixer", fixerSource); + Assert.DoesNotContain("private static InvocationExpressionSyntax? FindEffectiveAsSingleQueryInvocation", fixerSource); + Assert.DoesNotContain("private static InvocationExpressionSyntax? FindFirstIncludeInvocation", fixerSource); + Assert.DoesNotContain("private static bool IsInvocationOf", fixerSource); + + var syntaxChainSource = File.ReadAllText(syntaxChainPath); + Assert.Contains("private static InvocationExpressionSyntax? FindEffectiveAsSingleQueryInvocation", syntaxChainSource); + Assert.Contains("private static InvocationExpressionSyntax? FindFirstIncludeInvocation", syntaxChainSource); + Assert.Contains("private static bool IsInvocationOf", syntaxChainSource); + Assert.Contains("\"AsSingleQuery\"", syntaxChainSource); + } + + [Fact] + public void LC027_CollectionTypeClassification_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "SchemaAndModeling", + "LC027_MissingExplicitForeignKey"); + var entityAnalysisPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyEntityAnalysis.cs"); + var collectionTypesPath = Path.Combine(analyzerDir, "MissingExplicitForeignKeyCollectionTypes.cs"); + + Assert.True(File.Exists(collectionTypesPath), "LC027 collection-type classification should live in a focused partial file."); + + var entityAnalysisSource = File.ReadAllText(entityAnalysisPath); + Assert.DoesNotContain("private static bool IsCollectionType", entityAnalysisSource); + Assert.DoesNotContain("IReadOnlyCollection", entityAnalysisSource); + Assert.DoesNotContain("iface.Name == \"IEnumerable\"", entityAnalysisSource); + + var collectionTypesSource = File.ReadAllText(collectionTypesPath); + Assert.Contains("private static bool IsCollectionType", collectionTypesSource); + Assert.Contains("System.Collections.Generic", collectionTypesSource); + Assert.Contains("IReadOnlyCollection", collectionTypesSource); + } + + [Fact] + public void LC009_MaterializerMethodClassification_LivesInDedicatedPartial() + { + var analyzerDir = Path.Combine( + _repoRoot, + "src", + "LinqContraband", + "Analyzers", + "ChangeTrackingAndContextLifetime", + "LC009_MissingAsNoTracking"); + var analyzerPath = Path.Combine(analyzerDir, "MissingAsNoTrackingAnalyzer.cs"); + var materializersPath = Path.Combine(analyzerDir, "MissingAsNoTrackingMaterializers.cs"); + + Assert.True(File.Exists(materializersPath), "LC009 materializer method classification should live in a focused partial file."); + + var analyzerSource = File.ReadAllText(analyzerPath); + Assert.DoesNotContain("private static bool IsEntityMaterializer", analyzerSource); + Assert.DoesNotContain("\"ToDictionaryAsync\"", analyzerSource); + + var materializersSource = File.ReadAllText(materializersPath); + Assert.Contains("private static bool IsEntityMaterializer", materializersSource); + Assert.Contains("\"ToDictionaryAsync\"", materializersSource); + Assert.Contains("\"SingleOrDefaultAsync\"", materializersSource); + } + + [Fact] + public void SampleDiagnosticsVerifier_SarifParsing_LivesInDedicatedPartial() + { + var verifierDir = Path.Combine( + _repoRoot, + "tools", + "SampleDiagnosticsVerifier"); + var programPath = Path.Combine(verifierDir, "Program.cs"); + var sarifPath = Path.Combine(verifierDir, "Program.SarifDiagnostics.cs"); + + Assert.True(File.Exists(sarifPath), "Sample diagnostics SARIF parsing should live in a focused Program partial file."); + + var programSource = File.ReadAllText(programPath); + Assert.DoesNotContain("static IReadOnlyList ParseDiagnostics", programSource); + Assert.DoesNotContain("static bool TryGetDiagnosticPath", programSource); + Assert.DoesNotContain("static string NormalizeRelativePath", programSource); + + var sarifSource = File.ReadAllText(sarifPath); + Assert.Contains("private static IReadOnlyList ParseDiagnostics", sarifSource); + Assert.Contains("private static bool TryGetDiagnosticPath", sarifSource); + Assert.Contains("private static string NormalizeRelativePath", sarifSource); + Assert.Contains("internal sealed record SampleDiagnostic", sarifSource); + } + + [Fact] + public void SampleDiagnosticsVerifier_ManifestParsing_LivesInDedicatedPartial() + { + var verifierDir = Path.Combine( + _repoRoot, + "tools", + "SampleDiagnosticsVerifier"); + var programPath = Path.Combine(verifierDir, "Program.cs"); + var manifestPath = Path.Combine(verifierDir, "Program.Manifest.cs"); + + Assert.True(File.Exists(manifestPath), "Sample diagnostics manifest parsing should live in a focused Program partial file."); + + var programSource = File.ReadAllText(programPath); + Assert.DoesNotContain("static SampleExpectationGroup[] LoadExpectationGroups", programSource); + Assert.DoesNotContain("static string[] LoadSafeSamplePaths", programSource); + Assert.DoesNotContain("internal sealed record SampleExpectation", programSource); + + var manifestSource = File.ReadAllText(manifestPath); + Assert.Contains("private static SampleExpectationGroup[] LoadExpectationGroups", manifestSource); + Assert.Contains("private static string[] LoadSafeSamplePaths", manifestSource); + Assert.Contains("internal sealed record SampleExpectation", manifestSource); + Assert.Contains("internal sealed record SampleExpectationGroup", manifestSource); + } + + [Fact] + public void SampleDiagnosticsVerifier_DotnetProcessExecution_LivesInDedicatedPartial() + { + var verifierDir = Path.Combine( + _repoRoot, + "tools", + "SampleDiagnosticsVerifier"); + var programPath = Path.Combine(verifierDir, "Program.cs"); + var dotnetPath = Path.Combine(verifierDir, "Program.Dotnet.cs"); + + Assert.True(File.Exists(dotnetPath), "Sample diagnostics dotnet process execution should live in a focused Program partial file."); + + var programSource = File.ReadAllText(programPath); + Assert.DoesNotContain("static string RunDotnetBuild", programSource); + Assert.DoesNotContain("static void RunDotnetRestore", programSource); + Assert.DoesNotContain("static string Quote", programSource); + + var dotnetSource = File.ReadAllText(dotnetPath); + Assert.Contains("private static string RunDotnetBuild", dotnetSource); + Assert.Contains("private static void RunDotnetRestore", dotnetSource); + Assert.Contains("private static string Quote", dotnetSource); + Assert.Contains("ProcessStartInfo", dotnetSource); + } +} From f6f0fa84bcd1a82ddf79eafe040549e9ef5b3de3 Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:48:08 +0100 Subject: [PATCH 03/13] fix: invalidate LC045 proof at model-reshaping calls --- .../MissingIncludeAppliedConfiguration.cs | 15 ++------------- .../MissingIncludeAppliedConfigurationTests.cs | 12 ++++++++++++ .../MissingIncludeEdgeCasesTests.cs | 3 +++ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs index c1a7d87..faa3970 100644 --- a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs +++ b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs @@ -268,19 +268,8 @@ IParameterSymbol builderParameter containingType.ContainingAssembly, builderAssembly ) - && containingType.Name - is "EntityTypeBuilder" - or "KeyBuilder" - or "IndexBuilder" - or "PropertyBuilder" - or "PrimitiveCollectionBuilder" - or "ComplexPropertyBuilder" - or "DiscriminatorBuilder" - or "ReferenceNavigationBuilder" - or "ReferenceCollectionBuilder" - or "CollectionNavigationBuilder" - or "OwnedNavigationBuilder" - && method.Name is not ("AutoInclude" or "SetIsEagerLoaded") + && containingType.Name == "EntityTypeBuilder" + && method.Name == "HasKey" ) { return true; diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs index 2fcea0b..424bf59 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs @@ -27,6 +27,18 @@ public async Task TestInnocent_UnrelatedEfBuilderCallsBeforeAutoInclude_NoDiagno await VerifyCS.VerifyAnalyzerAsync(test); } + [Fact] + public async Task TestCrime_IgnoreAfterAutoInclude_DoesNotSuppress() + { + var test = CreateAppliedConfigurationTest( + @"builder.Navigation(o => o.Customer).AutoInclude(); + builder.Ignore(o => o.Customer);", + "Console.WriteLine({|#0:order.Customer|}.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + [Fact] public async Task TestInnocent_RelationalBuilderExtensionBeforeAutoInclude_NoDiagnostic() { diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeEdgeCasesTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeEdgeCasesTests.cs index d234d43..6016f6d 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeEdgeCasesTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeEdgeCasesTests.cs @@ -68,6 +68,9 @@ public class EntityTypeBuilder where TEntity : class public EntityTypeBuilder HasKey( System.Linq.Expressions.Expression> keyExpression) => this; + public EntityTypeBuilder Ignore( + System.Linq.Expressions.Expression> propertyExpression) => this; + public NavigationBuilder Navigation( System.Linq.Expressions.Expression> navigationExpression) => null; } From 226e23933ed054de9868939d83e7eb3757d391bc Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:58:31 +0100 Subject: [PATCH 04/13] fix: recognise string Navigation AutoInclude paths --- .../Extensions/IncludePathParser.cs | 44 ++++++++++++++++++- ...MissingIncludeAppliedConfigurationTests.cs | 34 ++++++++++++++ .../MissingIncludeEdgeCasesTests.cs | 7 ++- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/LinqContraband/Extensions/IncludePathParser.cs b/src/LinqContraband/Extensions/IncludePathParser.cs index 7a47a1c..b93a239 100644 --- a/src/LinqContraband/Extensions/IncludePathParser.cs +++ b/src/LinqContraband/Extensions/IncludePathParser.cs @@ -45,8 +45,8 @@ public IncludePath Append(IncludePath childPath) /// /// Parses EF Core Include/ThenInclude invocations (lambda, filtered-lambda, and constant-string -/// overloads) into values. Shared by LC006 (cartesian explosion) and -/// LC045 (missing include). +/// overloads) plus EntityTypeBuilder.Navigation paths into values. +/// Shared by LC006 (cartesian explosion) and LC045 (missing include). /// internal static partial class IncludePathParser { @@ -67,6 +67,14 @@ out IncludePath includePath return true; } + if ( + invocation.TargetMethod.Name == "Navigation" + && TryGetStringNavigationPath(invocation, out includePath) + ) + { + return true; + } + if (!TryGetLambdaExpression(invocation, out var lambdaExpression)) return false; @@ -139,6 +147,38 @@ out IncludePath includePath return true; } + private static bool TryGetStringNavigationPath( + IInvocationOperation invocation, + out IncludePath includePath + ) + { + includePath = new IncludePath(ImmutableArray.Empty); + var navigationArgument = invocation.Arguments.FirstOrDefault(argument => + argument.Parameter?.Ordinal == 0 + ); + if ( + navigationArgument?.Value.ConstantValue is not { HasValue: true, Value: string pathText } + || string.IsNullOrWhiteSpace(pathText) + || pathText.IndexOf('.') >= 0 + || invocation.Instance?.Type is not INamedTypeSymbol builderType + || builderType.TypeArguments.Length != 1 + ) + { + return false; + } + + var property = TryFindProperty(builderType.TypeArguments[0], pathText.Trim()); + if (property == null) + return false; + + includePath = new IncludePath( + ImmutableArray.Create( + new NavigationSegment(property.Name, IsCollection(property.Type)) + ) + ); + return true; + } + private static ITypeSymbol? GetSourceType(IInvocationOperation invocation) { if (invocation.Instance != null) diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs index 424bf59..4d6d7f8 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs @@ -15,6 +15,17 @@ public async Task TestInnocent_AppliedConfigurationAutoInclude_NoDiagnostic() await VerifyCS.VerifyAnalyzerAsync(test); } + [Fact] + public async Task TestInnocent_AppliedConfigurationStringAutoInclude_NoDiagnostic() + { + var test = CreateAppliedConfigurationTest( + "builder.Navigation(\"Customer\").AutoInclude();", + "Console.WriteLine(order.Customer.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test); + } + [Fact] public async Task TestInnocent_UnrelatedEfBuilderCallsBeforeAutoInclude_NoDiagnostic() { @@ -62,6 +73,29 @@ public async Task TestCrime_AppliedConfigurationDifferentNavigation_DoesNotSuppr await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); } + [Fact] + public async Task TestCrime_AppliedConfigurationDifferentStringNavigation_DoesNotSuppress() + { + var test = CreateAppliedConfigurationTest( + "builder.Navigation(\"BillingCustomer\").AutoInclude();", + "Console.WriteLine({|#0:order.Customer|}.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_AppliedConfigurationRuntimeStringNavigation_DoesNotSuppress() + { + var test = CreateAppliedConfigurationTest( + @"var navigationName = DateTime.UtcNow.Ticks > 0 ? ""Customer"" : ""BillingCustomer""; + builder.Navigation(navigationName).AutoInclude();", + "Console.WriteLine({|#0:order.Customer|}.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + [Fact] public async Task TestCrime_AppliedConfigurationDisabledAutoInclude_DoesNotSuppress() { diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeEdgeCasesTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeEdgeCasesTests.cs index 6016f6d..5b6dbb2 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeEdgeCasesTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeEdgeCasesTests.cs @@ -73,9 +73,14 @@ public EntityTypeBuilder Ignore( public NavigationBuilder Navigation( System.Linq.Expressions.Expression> navigationExpression) => null; + + public NavigationBuilder Navigation(string navigationName) => null; } - public class NavigationBuilder { } + public class NavigationBuilder + { + public virtual NavigationBuilder AutoInclude(bool autoInclude = true) => this; + } public class NavigationBuilder : NavigationBuilder where TSource : class From 1323049523be6dbc32b80aae57a05c006a827d5f Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:07:48 +0100 Subject: [PATCH 05/13] fix: invalidate nested LC045 configuration execution --- .../MissingIncludeAppliedConfiguration.cs | 25 ++++++++++++++++++ ...MissingIncludeAppliedConfigurationTests.cs | 26 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs index faa3970..deaf06f 100644 --- a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs +++ b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs @@ -76,6 +76,18 @@ is not IInvocationOperation configurationInvocation return false; } + if ( + IsNestedConfigurationExecutable(invocationSyntax, syntax) + && ReferencesParameter( + configurationInvocation, + configureMethod.Parameters[0], + cancellationToken + ) + ) + { + return false; + } + var isTopLevel = TryGetTopLevelConfigurationExecution( configurationInvocation, semanticModel, @@ -142,6 +154,19 @@ out var enabled return true; } + private static bool IsNestedConfigurationExecutable( + SyntaxNode invocationSyntax, + SyntaxNode configureSyntax + ) + { + return invocationSyntax + .Ancestors() + .TakeWhile(ancestor => ancestor != configureSyntax) + .Any(ancestor => + ancestor is LocalFunctionStatementSyntax or AnonymousFunctionExpressionSyntax + ); + } + private static bool TryGetExactConfigureMethod( INamedTypeSymbol configurationType, INamedTypeSymbol entityType, diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs index 4d6d7f8..c9ed2e8 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs @@ -181,6 +181,32 @@ void Main() await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); } + [Fact] + public async Task TestCrime_AppliedConfigurationLocalHelperExecutionOrder_DoesNotSuppress() + { + var test = CreateAppliedConfigurationTest( + @"void Disable() => builder.Navigation(o => o.Customer).AutoInclude(false); + builder.Navigation(o => o.Customer).AutoInclude(); + Disable();", + "Console.WriteLine({|#0:order.Customer|}.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestInnocent_AppliedConfigurationUnrelatedLocalHelper_NoDiagnostic() + { + var test = CreateAppliedConfigurationTest( + @"void Log() => Console.WriteLine(""Configuring Order""); + Log(); + builder.Navigation(o => o.Customer).AutoInclude();", + "Console.WriteLine(order.Customer.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test); + } + [Fact] public async Task TestCrime_SourceDefinedEfNamespaceHelper_DoesNotSuppress() { From d4f471f2ee798c7291cf99c47c42092766d9fe09 Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:16:14 +0100 Subject: [PATCH 06/13] fix: invalidate captured EF builder aliases --- .../MissingIncludeAppliedConfiguration.cs | 26 +++++++++++++++---- ...MissingIncludeAppliedConfigurationTests.cs | 14 ++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs index deaf06f..59fc7db 100644 --- a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs +++ b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs @@ -78,11 +78,12 @@ is not IInvocationOperation configurationInvocation if ( IsNestedConfigurationExecutable(invocationSyntax, syntax) - && ReferencesParameter( - configurationInvocation, - configureMethod.Parameters[0], - cancellationToken - ) + && (ReferencesParameter( + configurationInvocation, + configureMethod.Parameters[0], + cancellationToken + ) + || UsesEfConfigurationBuilder(configurationInvocation)) ) { return false; @@ -167,6 +168,21 @@ ancestor is LocalFunctionStatementSyntax or AnonymousFunctionExpressionSyntax ); } + private static bool UsesEfConfigurationBuilder(IInvocationOperation invocation) + { + return IsEfConfigurationBuilderType(invocation.TargetMethod.ContainingType) + || IsEfConfigurationBuilderType(invocation.Instance?.Type) + || invocation.Arguments.Any(argument => + IsEfConfigurationBuilderType(argument.Value.Type) + ); + } + + private static bool IsEfConfigurationBuilderType(ITypeSymbol? type) + { + return type?.ContainingNamespace.ToDisplayString() + == "Microsoft.EntityFrameworkCore.Metadata.Builders"; + } + private static bool TryGetExactConfigureMethod( INamedTypeSymbol configurationType, INamedTypeSymbol entityType, diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs index c9ed2e8..bb7aea3 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs @@ -194,6 +194,20 @@ public async Task TestCrime_AppliedConfigurationLocalHelperExecutionOrder_DoesNo await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); } + [Fact] + public async Task TestCrime_AppliedConfigurationCapturedBuilderAlias_DoesNotSuppress() + { + var test = CreateAppliedConfigurationTest( + @"var alias = builder; + void Disable() => alias.Navigation(o => o.Customer).AutoInclude(false); + builder.Navigation(o => o.Customer).AutoInclude(); + Disable();", + "Console.WriteLine({|#0:order.Customer|}.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + [Fact] public async Task TestInnocent_AppliedConfigurationUnrelatedLocalHelper_NoDiagnostic() { From 7f48532c16ca195a6c8ff2007ef36dcccddb2ef3 Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:25:17 +0100 Subject: [PATCH 07/13] fix: invalidate escaped LC045 builders --- .../MissingIncludeAppliedConfiguration.cs | 44 +++++++++++++++++ ...MissingIncludeAppliedConfigurationTests.cs | 49 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs index 59fc7db..e406f10 100644 --- a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs +++ b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs @@ -62,6 +62,17 @@ out var configureMethod cancellationToken.ThrowIfCancellationRequested(); var syntax = syntaxReference.GetSyntax(cancellationToken); var semanticModel = compilation.GetSemanticModel(syntax.SyntaxTree); + if ( + HasEscapedConfigurationBuilder( + syntax, + semanticModel, + configureMethod.Parameters[0], + cancellationToken + ) + ) + { + return false; + } foreach ( var invocationSyntax in syntax.DescendantNodes().OfType() @@ -155,6 +166,39 @@ out var enabled return true; } + private static bool HasEscapedConfigurationBuilder( + SyntaxNode configureSyntax, + SemanticModel semanticModel, + IParameterSymbol builderParameter, + CancellationToken cancellationToken + ) + { + foreach ( + var assignmentSyntax in configureSyntax + .DescendantNodes() + .OfType() + ) + { + cancellationToken.ThrowIfCancellationRequested(); + if ( + semanticModel.GetOperation(assignmentSyntax, cancellationToken) + is IAssignmentOperation assignment + && assignment.Target.UnwrapConversions() + is not (ILocalReferenceOperation or IDiscardOperation) + && ReferencesParameter( + assignment.Value, + builderParameter, + cancellationToken + ) + ) + { + return true; + } + } + + return false; + } + private static bool IsNestedConfigurationExecutable( SyntaxNode invocationSyntax, SyntaxNode configureSyntax diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs index bb7aea3..ca24ea9 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs @@ -208,6 +208,55 @@ public async Task TestCrime_AppliedConfigurationCapturedBuilderAlias_DoesNotSupp await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); } + [Fact] + public async Task TestCrime_AppliedConfigurationBuilderFieldEscape_DoesNotSuppress() + { + var test = + Usings + + @" +class OrderConfiguration : IEntityTypeConfiguration +{ + private Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder _saved; + + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + _saved = builder; + builder.Navigation(o => o.Customer).AutoInclude(); + Disable(); + } + + private void Disable() + { + _saved.Navigation(o => o.Customer).AutoInclude(false); + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new OrderConfiguration()); + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + [Fact] public async Task TestInnocent_AppliedConfigurationUnrelatedLocalHelper_NoDiagnostic() { From 5a4116574a8112e56d4d63927ac74d11f9a794d6 Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:34:36 +0100 Subject: [PATCH 08/13] fix: invalidate LC045 proof when constructors consume the builder --- .../MissingIncludeAppliedConfiguration.cs | 24 ++++ ...MissingIncludeAppliedConfigurationTests.cs | 111 ++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs index e406f10..ee866df 100644 --- a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs +++ b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs @@ -196,6 +196,30 @@ is not (ILocalReferenceOperation or IDiscardOperation) } } + foreach ( + var objectCreationSyntax in configureSyntax + .DescendantNodes() + .OfType() + ) + { + cancellationToken.ThrowIfCancellationRequested(); + if ( + semanticModel.GetOperation(objectCreationSyntax, cancellationToken) + is IObjectCreationOperation objectCreation + && objectCreation.Arguments.Any(argument => + ReferencesParameter( + argument.Value, + builderParameter, + cancellationToken + ) + || IsEfConfigurationBuilderType(argument.Value.Type) + ) + ) + { + return true; + } + } + return false; } diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs index ca24ea9..6b08502 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs @@ -257,6 +257,117 @@ void Main() await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); } + [Fact] + public async Task TestCrime_AppliedConfigurationBuilderConstructorBoundary_DoesNotSuppress() + { + var test = + Usings + + @" +class DisableCustomer +{ + public DisableCustomer(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(false); + } +} + +class OrderConfiguration : IEntityTypeConfiguration +{ + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(); + _ = new DisableCustomer(builder); + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new OrderConfiguration()); + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_AppliedConfigurationBuilderAliasConstructorBoundary_DoesNotSuppress() + { + var test = + Usings + + @" +class DisableCustomer +{ + public DisableCustomer(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(false); + } +} + +class OrderConfiguration : IEntityTypeConfiguration +{ + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + var alias = builder; + builder.Navigation(o => o.Customer).AutoInclude(); + _ = new DisableCustomer(alias); + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new OrderConfiguration()); + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestInnocent_AppliedConfigurationUnrelatedConstructor_NoDiagnostic() + { + var test = CreateAppliedConfigurationTest( + @"_ = new object(); + builder.Navigation(o => o.Customer).AutoInclude();", + "Console.WriteLine(order.Customer.Name);" + ); + + await VerifyCS.VerifyAnalyzerAsync(test); + } + [Fact] public async Task TestInnocent_AppliedConfigurationUnrelatedLocalHelper_NoDiagnostic() { From 5976c030bfa28f6a71170d63e57df1038c8f4a47 Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:42:41 +0100 Subject: [PATCH 09/13] fix: invalidate LC045 proof when setters consume the builder --- .../MissingIncludeAppliedConfiguration.cs | 15 ++++-- ...MissingIncludeAppliedConfigurationTests.cs | 51 +++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs index ee866df..2e6ac1c 100644 --- a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs +++ b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs @@ -185,11 +185,16 @@ var assignmentSyntax in configureSyntax is IAssignmentOperation assignment && assignment.Target.UnwrapConversions() is not (ILocalReferenceOperation or IDiscardOperation) - && ReferencesParameter( - assignment.Value, - builderParameter, - cancellationToken - ) + && (ReferencesParameter( + assignment.Value, + builderParameter, + cancellationToken + ) + || ReferencesParameter( + assignment.Target, + builderParameter, + cancellationToken + )) ) { return true; diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs index 6b08502..b3c54db 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs @@ -208,6 +208,57 @@ public async Task TestCrime_AppliedConfigurationCapturedBuilderAlias_DoesNotSupp await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); } + [Fact] + public async Task TestCrime_AppliedConfigurationBuilderIndexerSetter_DoesNotSuppress() + { + var test = + Usings + + @" +class DisableCustomerMutator +{ + public bool this[Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder] + { + set => builder.Navigation(o => o.Customer).AutoInclude(false); + } +} + +class OrderConfiguration : IEntityTypeConfiguration +{ + private readonly DisableCustomerMutator _mutator = new(); + + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(); + _mutator[builder] = true; + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new OrderConfiguration()); + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + [Fact] public async Task TestCrime_AppliedConfigurationBuilderFieldEscape_DoesNotSuppress() { From d02cbec06dfe39ec3669cd54270a6ff79df624e8 Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:53:08 +0100 Subject: [PATCH 10/13] fix: reject suspended and indexer-mutated LC045 configurations --- .../MissingIncludeAppliedConfiguration.cs | 23 +++++ ...MissingIncludeAppliedConfigurationTests.cs | 96 +++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs index 2e6ac1c..6a35e87 100644 --- a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs +++ b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs @@ -48,6 +48,7 @@ is not IObjectCreationOperation configurationCreation entityType, out var configureMethod ) + || configureMethod.IsAsync || configureMethod.Parameters.Length != 1 || configureMethod.Parameters[0].Type is not INamedTypeSymbol builderType || !IsEfBuilderType(builderType, "EntityTypeBuilder") @@ -225,6 +226,28 @@ is IObjectCreationOperation objectCreation } } + foreach ( + var elementAccessSyntax in configureSyntax + .DescendantNodes() + .OfType() + ) + { + cancellationToken.ThrowIfCancellationRequested(); + if ( + semanticModel.GetOperation(elementAccessSyntax, cancellationToken) + is IPropertyReferenceOperation propertyReference + && propertyReference.Property.IsIndexer + && ReferencesParameter( + propertyReference, + builderParameter, + cancellationToken + ) + ) + { + return true; + } + } + return false; } diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs index b3c54db..1b26442 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs @@ -241,6 +241,102 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_AppliedConfigurationBuilderIndexerGetter_DoesNotSuppress() + { + var test = + Usings + + @" +class DisableCustomerMutator +{ + public bool this[Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder] + { + get + { + builder.Navigation(o => o.Customer).AutoInclude(false); + return true; + } + } +} + +class OrderConfiguration : IEntityTypeConfiguration +{ + private readonly DisableCustomerMutator _mutator = new(); + + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(); + var ignored = _mutator[builder]; + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new OrderConfiguration()); + } +} + +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_AppliedConfigurationAsyncConfigure_DoesNotSuppress() + { + var test = + Usings + + @" +class OrderConfiguration : IEntityTypeConfiguration +{ + public async void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + await System.Threading.Tasks.Task.Yield(); + builder.Navigation(o => o.Customer).AutoInclude(); + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new OrderConfiguration()); + } +} + class Program { void Main() From f5dcbe6f762379aa4946365db3cba140f2fec7a8 Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:03:53 +0100 Subject: [PATCH 11/13] fix: reject builder-consuming user conversions in LC045 --- .../MissingIncludeAppliedConfiguration.cs | 64 ++++++++++++++++--- ...MissingIncludeAppliedConfigurationTests.cs | 57 +++++++++++++++++ 2 files changed, 112 insertions(+), 9 deletions(-) diff --git a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs index 6a35e87..eab3e00 100644 --- a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs +++ b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs @@ -184,18 +184,64 @@ var assignmentSyntax in configureSyntax if ( semanticModel.GetOperation(assignmentSyntax, cancellationToken) is IAssignmentOperation assignment - && assignment.Target.UnwrapConversions() - is not (ILocalReferenceOperation or IDiscardOperation) - && (ReferencesParameter( - assignment.Value, + ) + { + if ( + assignment.Value is IConversionOperation + { + OperatorMethod: not null, + } conversion + && ReferencesParameter( + conversion.Operand, builderParameter, cancellationToken ) - || ReferencesParameter( - assignment.Target, - builderParameter, - cancellationToken - )) + ) + { + return true; + } + + if ( + assignment.Target.UnwrapConversions() + is not (ILocalReferenceOperation or IDiscardOperation) + && (ReferencesParameter( + assignment.Value, + builderParameter, + cancellationToken + ) + || ReferencesParameter( + assignment.Target, + builderParameter, + cancellationToken + )) + ) + { + return true; + } + } + } + + foreach ( + var variableSyntax in configureSyntax + .DescendantNodes() + .OfType() + ) + { + cancellationToken.ThrowIfCancellationRequested(); + if ( + semanticModel.GetOperation(variableSyntax, cancellationToken) + is IVariableDeclaratorOperation + { + Initializer.Value: IConversionOperation + { + OperatorMethod: not null, + } conversion, + } + && ReferencesParameter( + conversion.Operand, + builderParameter, + cancellationToken + ) ) { return true; diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs index 1b26442..5722192 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs @@ -337,6 +337,63 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Theory] + [InlineData("DisableCustomerMutator ignored; ignored = builder;")] + [InlineData("DisableCustomerMutator ignored = builder;")] + public async Task TestCrime_AppliedConfigurationBuilderUserDefinedConversion_DoesNotSuppress( + string conversion + ) + { + var test = + Usings + + @" +class DisableCustomerMutator +{ + public static implicit operator DisableCustomerMutator( + Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(false); + return new DisableCustomerMutator(); + } +} + +class OrderConfiguration : IEntityTypeConfiguration +{ + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(); + " + + conversion + + @" + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new OrderConfiguration()); + } +} + class Program { void Main() From 61921ab22524e02ff5fffa640fe43a79730bd4f8 Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:12:05 +0100 Subject: [PATCH 12/13] fix: inspect nested builder conversions in LC045 --- .../MissingIncludeAppliedConfiguration.cs | 48 +++++++++++++------ ...MissingIncludeAppliedConfigurationTests.cs | 6 +++ 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs index eab3e00..1a38f76 100644 --- a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs +++ b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs @@ -187,12 +187,8 @@ is IAssignmentOperation assignment ) { if ( - assignment.Value is IConversionOperation - { - OperatorMethod: not null, - } conversion - && ReferencesParameter( - conversion.Operand, + ContainsBuilderConsumingUserDefinedConversion( + assignment.Value, builderParameter, cancellationToken ) @@ -230,15 +226,9 @@ var variableSyntax in configureSyntax cancellationToken.ThrowIfCancellationRequested(); if ( semanticModel.GetOperation(variableSyntax, cancellationToken) - is IVariableDeclaratorOperation - { - Initializer.Value: IConversionOperation - { - OperatorMethod: not null, - } conversion, - } - && ReferencesParameter( - conversion.Operand, + is IVariableDeclaratorOperation { Initializer.Value: var initializer } + && ContainsBuilderConsumingUserDefinedConversion( + initializer, builderParameter, cancellationToken ) @@ -297,6 +287,34 @@ is IPropertyReferenceOperation propertyReference return false; } + private static bool ContainsBuilderConsumingUserDefinedConversion( + IOperation operation, + IParameterSymbol builderParameter, + CancellationToken cancellationToken + ) + { + cancellationToken.ThrowIfCancellationRequested(); + if ( + operation is IConversionOperation { OperatorMethod: not null } conversion + && ReferencesParameter( + conversion.Operand, + builderParameter, + cancellationToken + ) + ) + { + return true; + } + + return operation.ChildOperations.Any(child => + ContainsBuilderConsumingUserDefinedConversion( + child, + builderParameter, + cancellationToken + ) + ); + } + private static bool IsNestedConfigurationExecutable( SyntaxNode invocationSyntax, SyntaxNode configureSyntax diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs index 5722192..e5cfae3 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs @@ -358,6 +358,12 @@ void Main() [Theory] [InlineData("DisableCustomerMutator ignored; ignored = builder;")] [InlineData("DisableCustomerMutator ignored = builder;")] + [InlineData( + "DisableCustomerMutator ignored; ignored = true ? (DisableCustomerMutator)builder : new DisableCustomerMutator();" + )] + [InlineData( + "DisableCustomerMutator ignored = true ? (DisableCustomerMutator)builder : new DisableCustomerMutator();" + )] public async Task TestCrime_AppliedConfigurationBuilderUserDefinedConversion_DoesNotSuppress( string conversion ) From d2edc262a9c180f8487ebc489dbd2ed905b1821e Mon Sep 17 00:00:00 2001 From: George Wall <84231788+georgepwall1991@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:20:53 +0100 Subject: [PATCH 13/13] fix: scan complete LC045 configuration operation proof --- .../MissingIncludeAppliedConfiguration.cs | 48 ++++++----------- ...MissingIncludeAppliedConfigurationTests.cs | 51 +++++++++++++++++++ 2 files changed, 67 insertions(+), 32 deletions(-) diff --git a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs index 1a38f76..236e278 100644 --- a/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs +++ b/src/LinqContraband/Analyzers/LoadingAndIncludes/LC045_MissingInclude/MissingIncludeAppliedConfiguration.cs @@ -42,6 +42,7 @@ is not IParameterReferenceOperation modelBuilderReference if ( configurationArgument?.Value.UnwrapConversions() is not IObjectCreationOperation configurationCreation + || configurationCreation.Arguments.Length != 0 || configurationCreation.Type is not INamedTypeSymbol configurationType || !TryGetExactConfigureMethod( configurationType, @@ -63,7 +64,22 @@ out var configureMethod cancellationToken.ThrowIfCancellationRequested(); var syntax = syntaxReference.GetSyntax(cancellationToken); var semanticModel = compilation.GetSemanticModel(syntax.SyntaxTree); + var configureOperation = syntax switch + { + MethodDeclarationSyntax { Body: not null } method => + semanticModel.GetOperation(method.Body, cancellationToken), + MethodDeclarationSyntax { ExpressionBody.Expression: var expression } => + semanticModel.GetOperation(expression, cancellationToken), + _ => null, + }; if ( + configureOperation == null + || ContainsBuilderConsumingUserDefinedConversion( + configureOperation, + configureMethod.Parameters[0], + cancellationToken + ) + || HasEscapedConfigurationBuilder( syntax, semanticModel, @@ -186,17 +202,6 @@ var assignmentSyntax in configureSyntax is IAssignmentOperation assignment ) { - if ( - ContainsBuilderConsumingUserDefinedConversion( - assignment.Value, - builderParameter, - cancellationToken - ) - ) - { - return true; - } - if ( assignment.Target.UnwrapConversions() is not (ILocalReferenceOperation or IDiscardOperation) @@ -217,27 +222,6 @@ is not (ILocalReferenceOperation or IDiscardOperation) } } - foreach ( - var variableSyntax in configureSyntax - .DescendantNodes() - .OfType() - ) - { - cancellationToken.ThrowIfCancellationRequested(); - if ( - semanticModel.GetOperation(variableSyntax, cancellationToken) - is IVariableDeclaratorOperation { Initializer.Value: var initializer } - && ContainsBuilderConsumingUserDefinedConversion( - initializer, - builderParameter, - cancellationToken - ) - ) - { - return true; - } - } - foreach ( var objectCreationSyntax in configureSyntax .DescendantNodes() diff --git a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs index e5cfae3..3fc6d0e 100644 --- a/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs +++ b/tests/LinqContraband.Tests/Analyzers/LC045_MissingInclude/MissingIncludeAppliedConfigurationTests.cs @@ -364,6 +364,7 @@ void Main() [InlineData( "DisableCustomerMutator ignored = true ? (DisableCustomerMutator)builder : new DisableCustomerMutator();" )] + [InlineData("if ((DisableCustomerMutator)builder != null) { }")] public async Task TestCrime_AppliedConfigurationBuilderUserDefinedConversion_DoesNotSuppress( string conversion ) @@ -400,6 +401,56 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } +class Program +{ + void Main() + { + var db = new AutoIncludeDbContext(); + var orders = db.Orders.ToList(); + foreach (var order in orders) + { + Console.WriteLine({|#0:order.Customer|}.Name); + } + } +} +" + + MockNamespace; + + await VerifyCS.VerifyAnalyzerAsync(test, Diagnostic(0, "Customer", "Order")); + } + + [Fact] + public async Task TestCrime_AppliedConfigurationConstructorConsumesModelBuilder_DoesNotSuppress() + { + var test = + Usings + + @" +class OrderConfiguration : IEntityTypeConfiguration +{ + private readonly Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder _saved; + + public OrderConfiguration( + Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder saved) + { + _saved = saved; + } + + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Navigation(o => o.Customer).AutoInclude(); + _saved.Navigation(o => o.Customer).AutoInclude(false); + } +} + +class AutoIncludeDbContext : MyDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration( + new OrderConfiguration(modelBuilder.Entity())); + } +} + class Program { void Main()