From 9d6467889cd807dfc9479c404f4b27b3d51aa3a7 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:21:57 +1000 Subject: [PATCH 01/18] feat(analyzers): adopt SecuritySharp.Analyzers and update the analyzer set to 3.29.1 - Add SecuritySharp.Analyzers, sharing RoslynCommonAnalyzersVersion with StyleSharp and PerformanceSharp; bump all three to 3.29.1 - Enable all 47 SecuritySharp rules at error - Enable 43 newly available StyleSharp and PerformanceSharp rules at error - Turn off 119 rules in other analyzer groups that duplicate or conflict with the platform rules, each annotated with the rule that covers it - Re-enable SST1121 and SST1200, which were disabled in favour of third-party equivalents - Narrow dead public modifiers on members of internal types, and move usings outside the namespace in PartitionObservable --- .editorconfig | 361 ++++++++++++------ src/Directory.Build.props | 1 + src/Directory.Packages.props | 6 +- .../InvalidOperationExceptionHelper.cs | 6 +- .../Disposables/DisposableSlotHelper.cs | 8 +- .../ConcurrencyRaceHelpers.cs | 4 +- .../ConnectableSignalAsyncHelper.cs | 6 +- .../ConnectableSignalAsyncState{T}.cs | 34 +- .../Observables/Empty.cs | 2 +- .../Observables/Never.cs | 2 +- .../Operators/Blend.cs | 68 ++-- .../Operators/ChainEnumerableSignal{T}.cs | 32 +- .../Operators/ChainSignalSourcesSignal{T}.cs | 38 +- .../Operators/GroupBy.cs | 2 +- .../Operators/GroupedAsyncSignalHelper.cs | 2 +- .../GroupedAsyncSignalState{TKey,TValue}.cs | 8 +- .../Operators/Pair.cs | 10 +- .../Operators/ParityHelpers.Partition.cs | 4 +- .../Operators/SwitchToSignal.cs | 18 +- .../Operators/TakeUntil.PredicateSignals.cs | 4 +- .../Operators/TakeUntil.cs | 82 ++-- .../ReplayLatestSignalAsyncStateHelper.cs | 10 +- .../Base/ReplayLatestSignalAsyncState{T}.cs | 22 +- .../Signals/Base/SerialBroadcastHelpers.cs | 8 +- .../Signals/Base/SignalAsyncStateHelper.cs | 18 +- .../Signals/Base/SignalAsyncState{T}.cs | 8 +- ...elessReplayLatestSignalAsyncStateHelper.cs | 10 +- ...tatelessReplayLatestSignalAsyncState{T}.cs | 20 +- .../Base/StatelessSignalAsyncStateHelper.cs | 10 +- .../Base/StatelessSignalAsyncState{T}.cs | 4 +- .../Core/CoreCompositeFormats.cs | 4 +- .../Core/PriorityQueue.cs | 4 +- .../Signals/BehaviorSignalState{T}.cs | 20 +- .../ConcurrencyLimiter.cs | 6 +- .../Internal/ConcurrencyRaceHelpers.cs | 4 +- .../Internal/IndexedSubscribeHelper.cs | 2 +- .../Internal/ObservableSubscribeExtensions.cs | 2 +- .../Internal/ReduceSinkState.cs | 20 +- .../Operators/PartitionObservable.cs | 4 +- 39 files changed, 497 insertions(+), 377 deletions(-) diff --git a/.editorconfig b/.editorconfig index 3843643d..b3ecd391 100644 --- a/.editorconfig +++ b/.editorconfig @@ -188,11 +188,11 @@ dotnet_diagnostic.AvoidAsyncVoid.severity = suggestion dotnet_diagnostic.CA1000.severity = none # Do not declare static members on generic types — common factory pattern dotnet_diagnostic.CA1001.severity = error # Types that own disposable fields should be disposable dotnet_diagnostic.CA1002.severity = none # Do not expose generic lists — we deliberately expose List; interface-based collections are an older convention we don't follow -dotnet_diagnostic.CA1003.severity = error # Use generic event handler instances +dotnet_diagnostic.CA1003.severity = none # Use generic event handler instances — covered by SST2304 dotnet_diagnostic.CA1005.severity = none # Avoid excessive parameters on generic types — we deliberately expose 3+ type-parameter types (tuple-style handles, raw engine signals); the ergonomic guidance conflicts with that design dotnet_diagnostic.CA1008.severity = error # Enums should have zero value dotnet_diagnostic.CA1010.severity = none # Collections should implement generic interface — we deliberately expose concrete collection types; interface-based collections are an older convention we don't follow -dotnet_diagnostic.CA1012.severity = error # Abstract types should not have public constructors +dotnet_diagnostic.CA1012.severity = none # Abstract types should not have public constructors — covered by SST1428 dotnet_diagnostic.CA1014.severity = none # Mark assemblies with CLSCompliantAttribute — we don't ship CLS-compliant assemblies dotnet_diagnostic.CA1016.severity = error # Mark assemblies with AssemblyVersionAttribute dotnet_diagnostic.CA1017.severity = none # Mark assemblies with ComVisibleAttribute — we don't ship COM-visible assemblies @@ -201,7 +201,7 @@ dotnet_diagnostic.CA1019.severity = error # Define accessors for attribute argum dotnet_diagnostic.CA1021.severity = none # Avoid out parameters - disabled - needed for the zero-allocation idiom in Try/Find APIs and other performance-critical paths dotnet_diagnostic.CA1024.severity = error # Use properties where appropriate dotnet_diagnostic.CA1027.severity = error # Mark enums with FlagsAttribute -dotnet_diagnostic.CA1028.severity = error # Enum storage should be Int32 +dotnet_diagnostic.CA1028.severity = none # Enum storage should be Int32 — covered by SST2313 dotnet_diagnostic.CA1030.severity = none # Use events where appropriate — we use Rx observables instead of CLR events dotnet_diagnostic.CA1031.severity = none # Do not catch general exception types — required at logging/dispose/IO boundaries dotnet_diagnostic.CA1032.severity = none # Implement standard exception constructors — covered by SST1488 @@ -216,17 +216,17 @@ dotnet_diagnostic.CA1045.severity = none # Do not pass types by reference — we dotnet_diagnostic.CA1046.severity = error # Do not overload operator equals on reference types dotnet_diagnostic.CA1047.severity = none # Do not declare protected member in sealed type — covered by SST1427 dotnet_diagnostic.CA1048.severity = none # Do not declare virtual members in sealed types — covered by SST1491 -dotnet_diagnostic.CA1050.severity = error # Declare types in namespaces +dotnet_diagnostic.CA1050.severity = none # Declare types in namespaces — covered by SST2312 dotnet_diagnostic.CA1051.severity = none # Duplicate of SST1401 (canonical) — do not declare visible instance fields -dotnet_diagnostic.CA1052.severity = error # Static holder types should be sealed -dotnet_diagnostic.CA1053.severity = error # Static holder types should not have constructors +dotnet_diagnostic.CA1052.severity = none # Static holder types should be sealed — covered by SST1432 +dotnet_diagnostic.CA1053.severity = none # Static holder types should not have constructors — covered by SST1432 dotnet_diagnostic.CA1054.severity = suggestion # URI parameters should not be strings dotnet_diagnostic.CA1055.severity = suggestion # URI return values should not be strings dotnet_diagnostic.CA1056.severity = suggestion # URI properties should not be strings dotnet_diagnostic.CA1058.severity = error # Types should not extend certain base types dotnet_diagnostic.CA1059.severity = error # Members should not expose certain concrete types dotnet_diagnostic.CA1060.severity = error # Move P/Invokes to NativeMethods class -dotnet_diagnostic.CA1061.severity = error # Do not hide base class methods +dotnet_diagnostic.CA1061.severity = none # Do not hide base class methods — covered by SST2427 dotnet_diagnostic.CA1062.severity = none # Validate arguments of public methods - Nullable=enable + we own every consumer, so the compiler already guarantees non-null params dotnet_diagnostic.CA1063.severity = none # Implement IDisposable correctly — covered by SST2300 dotnet_diagnostic.CA1064.severity = error # Exceptions should be public @@ -265,7 +265,7 @@ dotnet_diagnostic.CA1510.severity = none # Covered by PSH1409 (canonical) dotnet_diagnostic.CA1511.severity = none # Covered by PSH1409 (canonical) dotnet_diagnostic.CA1512.severity = none # Covered by PSH1409 (canonical) dotnet_diagnostic.CA1513.severity = none # Covered by PSH1409 (canonical) -dotnet_diagnostic.CA1514.severity = error # Avoid redundant length argument +dotnet_diagnostic.CA1514.severity = none # Avoid redundant length argument — covered by PSH1220 dotnet_diagnostic.CA1515.severity = none # Consider making public types internal — interferes with tests and reflection-discovered types (BenchmarkDotNet, TUnit, etc.) dotnet_diagnostic.CA1516.severity = error # Use cross-platform intrinsics @@ -289,18 +289,18 @@ dotnet_diagnostic.CA1815.severity = none # Override equals and operator equals o dotnet_diagnostic.CA1819.severity = none # Properties should not return arrays — incompatible with the RxUI/sqlite-net mapping style we use throughout the codebase dotnet_diagnostic.CA1820.severity = none # Test for empty strings using string length — covered by PSH1204 dotnet_diagnostic.CA1821.severity = none # Remove empty finalizers — covered by PSH1002 -dotnet_diagnostic.CA1822.severity = error # Mark members as static +dotnet_diagnostic.CA1822.severity = none # Mark members as static — covered by PSH1414 dotnet_diagnostic.CA1823.severity = none # Avoid unused private fields — covered by SST1441 dotnet_diagnostic.CA1824.severity = error # Mark assemblies with NeutralResourcesLanguageAttribute dotnet_diagnostic.CA1825.severity = none # Avoid zero-length array allocations — covered by PSH1001 dotnet_diagnostic.CA1826.severity = none # Use property instead of Linq Enumerable method — covered by PSH1103 dotnet_diagnostic.CA1827.severity = none # Do not use Count/LongCount when Any can be used — covered by PSH1119 -dotnet_diagnostic.CA1828.severity = error # Do not use CountAsync/LongCountAsync when AnyAsync can be used +dotnet_diagnostic.CA1828.severity = none # Do not use CountAsync/LongCountAsync when AnyAsync can be used — covered by PSH1126 dotnet_diagnostic.CA1829.severity = none # Use Length/Count property instead of Enumerable.Count — covered by PSH1103 dotnet_diagnostic.CA1830.severity = none # Prefer strongly-typed Append/Insert overloads on StringBuilder — covered by PSH1202 dotnet_diagnostic.CA1831.severity = none # Use AsSpan instead of Range-based indexers for string — covered by PSH1212 dotnet_diagnostic.CA1832.severity = none # Use AsSpan or AsMemory instead of Range-based indexers for getting ReadOnlySpan or ReadOnlyMemory portion of an array — covered by PSH1019 -dotnet_diagnostic.CA1833.severity = error # Use AsSpan or AsMemory instead of Range-based indexers for getting Span or Memory portion of an array +dotnet_diagnostic.CA1833.severity = none # Use AsSpan or AsMemory instead of Range-based indexers for getting Span or Memory portion of an array — conflicts with PSH1019, which owns the array range-indexer rewrite and refuses it for mutable Span/Memory targets dotnet_diagnostic.CA1834.severity = none # Use StringBuilder.Append(char) for single character strings — covered by PSH1202 dotnet_diagnostic.CA1835.severity = none # Prefer the memory-based overloads of ReadAsync/WriteAsync methods in stream-based classes — covered by PSH1314 dotnet_diagnostic.CA1836.severity = none # Prefer IsEmpty over Count when available — covered by PSH1117 @@ -312,7 +312,7 @@ dotnet_diagnostic.CA1841.severity = none # Prefer Dictionary.Contains methods dotnet_diagnostic.CA1842.severity = none # Do not use 'WhenAll' with a single task — covered by PSH1301 dotnet_diagnostic.CA1843.severity = none # Do not use 'WaitAll' with a single task — covered by PSH1301 dotnet_diagnostic.CA1844.severity = error # Provide memory-based overrides of async methods when subclassing 'Stream' -dotnet_diagnostic.CA1845.severity = error # Use span-based 'string.Concat' +dotnet_diagnostic.CA1845.severity = none # Use span-based 'string.Concat' — covered by PSH1222 dotnet_diagnostic.CA1846.severity = none # Prefer AsSpan over Substring — covered by PSH1212 dotnet_diagnostic.CA1847.severity = none # Covered by PSH1201 (canonical) dotnet_diagnostic.CA1848.severity = error # Use the LoggerMessage delegates @@ -326,12 +326,12 @@ dotnet_diagnostic.CA1854.severity = none # Prefer the IDictionary.TryGetValue me dotnet_diagnostic.CA1855.severity = error # Prefer 'Clear' over 'Fill' dotnet_diagnostic.CA1856.severity = error # Incorrect usage of ConstantExpected attribute dotnet_diagnostic.CA1857.severity = error # A constant is expected for the parameter -dotnet_diagnostic.CA1858.severity = error # Use 'StartsWith' instead of 'IndexOf' +dotnet_diagnostic.CA1858.severity = none # Use 'StartsWith' instead of 'IndexOf' — covered by PSH1221 dotnet_diagnostic.CA1859.severity = error # Use concrete types when possible for improved performance dotnet_diagnostic.CA1860.severity = none # Avoid using 'Enumerable.Any()' extension method — covered by PSH1103 dotnet_diagnostic.CA1861.severity = none # Avoid constant arrays as arguments — covered by PSH1004 dotnet_diagnostic.CA1862.severity = none # Use the 'StringComparison' overloads for case-insensitive comparisons — covered by PSH1200 -dotnet_diagnostic.CA1863.severity = error # Use 'CompositeFormat' +dotnet_diagnostic.CA1863.severity = none # Use 'CompositeFormat' — covered by PSH1223 dotnet_diagnostic.CA1864.severity = none # Prefer the 'IDictionary.TryAdd' method — covered by PSH1115 dotnet_diagnostic.CA1865.severity = none # Covered by PSH1201 (canonical) dotnet_diagnostic.CA1866.severity = none # Covered by PSH1201 (canonical) @@ -341,10 +341,10 @@ dotnet_diagnostic.CA1869.severity = none # Cache and reuse 'JsonSerializerOption dotnet_diagnostic.CA1870.severity = none # Use a cached 'SearchValues' instance — covered by PSH1213 dotnet_diagnostic.CA1871.severity = error # Do not pass a nullable struct to 'ArgumentNullException.ThrowIfNull' dotnet_diagnostic.CA1872.severity = none # Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' — covered by PSH1224 -dotnet_diagnostic.CA1873.severity = error # Avoid potentially expensive evaluation of arguments to 'Debug.Assert' +dotnet_diagnostic.CA1873.severity = none # Avoid potentially expensive evaluation of arguments to 'Debug.Assert' — covered by PSH1417 dotnet_diagnostic.CA1874.severity = none # Use 'Regex.IsMatch' — covered by PSH1406 dotnet_diagnostic.CA1875.severity = none # Use 'Regex.Count' — covered by PSH1406 -dotnet_diagnostic.CA1877.severity = error # Use 'Encoding.GetString' instead of 'Encoding.GetChars' +dotnet_diagnostic.CA1877.severity = none # Use 'Encoding.GetString' instead of 'Encoding.GetChars' — covered by PSH1225 ################### # Microsoft .NET Analyzers (CA) - Reliability Rules @@ -377,29 +377,29 @@ dotnet_diagnostic.CA2026.severity = error # Do not use methods or types annotate dotnet_diagnostic.CA1801.severity = error # Review unused parameters dotnet_code_quality.CA1801.api_surface = private, internal # only flag non-public APIs so we don't break public signatures dotnet_diagnostic.CA1816.severity = error # Call GC.SuppressFinalize correctly -dotnet_diagnostic.CA2200.severity = error # Rethrow to preserve stack details +dotnet_diagnostic.CA2200.severity = none # Rethrow to preserve stack details — covered by SST1430 dotnet_diagnostic.CA2201.severity = error # Do not raise reserved exception types dotnet_diagnostic.CA2207.severity = error # Initialize value type static fields inline dotnet_diagnostic.CA2208.severity = none # Instantiate argument exceptions correctly — too sensitive: flags valid context-forwarding nameof(arg.Property) patterns dotnet_diagnostic.CA2211.severity = none # Non-constant fields should not be visible — covered by SST1499 dotnet_diagnostic.CA2213.severity = error # Disposable fields should be disposed -dotnet_diagnostic.CA2214.severity = error # Do not call overridable methods in constructors +dotnet_diagnostic.CA2214.severity = none # Do not call overridable methods in constructors — covered by SST1483 dotnet_diagnostic.CA2215.severity = error # Dispose methods should call base class dispose -dotnet_diagnostic.CA2216.severity = error # Disposable types should declare finalizer +dotnet_diagnostic.CA2216.severity = none # Disposable types should declare finalizer — conflicts with SST2317, which owns the owned-native-handle shape and prescribes a SafeHandle instead of a finalizer dotnet_diagnostic.CA2217.severity = none # Do not mark enums with FlagsAttribute — covered by SST2303 dotnet_diagnostic.CA2218.severity = error # Override GetHashCode on overriding Equals dotnet_diagnostic.CA2219.severity = error # Do not raise exceptions in finally clauses -dotnet_diagnostic.CA2224.severity = error # Override Equals on overloading operator equals +dotnet_diagnostic.CA2224.severity = none # Override Equals on overloading operator equals — covered by SST2302 dotnet_diagnostic.CA2225.severity = error # Operator overloads have named alternates dotnet_diagnostic.CA2226.severity = error # Operators should have symmetrical overloads dotnet_diagnostic.CA2227.severity = none # Collection properties should be read only — settable collection properties are common in our DTOs and config types dotnet_diagnostic.CA2231.severity = error # Overload operator equals on overriding ValueType.Equals dotnet_diagnostic.CA2234.severity = error # Pass System.Uri objects instead of strings dotnet_diagnostic.CA2241.severity = error # Provide correct arguments to formatting methods -dotnet_diagnostic.CA2242.severity = error # Test for NaN correctly +dotnet_diagnostic.CA2242.severity = none # Test for NaN correctly — covered by SST1473 dotnet_diagnostic.CA2243.severity = error # Attribute string literals should parse correctly dotnet_diagnostic.CA2244.severity = error # Do not duplicate indexed element initializations -dotnet_diagnostic.CA2245.severity = error # Do not assign a property to itself +dotnet_diagnostic.CA2245.severity = none # Do not assign a property to itself — covered by SST1189 dotnet_diagnostic.CA2246.severity = error # Do not assign a symbol and its member in the same statement dotnet_diagnostic.CA2247.severity = error # Argument passed to TaskCompletionSource constructor should be TaskCreationOptions enum dotnet_diagnostic.CA2248.severity = error # Provide correct enum argument to Enum.HasFlag @@ -854,18 +854,18 @@ dotnet_diagnostic.IDE3000.severity = none # Disabled per project convention — ################### # Roslynator Analyzers (RCS1xxx) - Code Simplification ################### -dotnet_diagnostic.RCS1001.severity = error # Add braces (when expression spans over multiple lines) -dotnet_diagnostic.RCS1003.severity = error # Add braces to if-else (when expression spans over multiple lines) +dotnet_diagnostic.RCS1001.severity = none # Add braces (when expression spans over multiple lines) — covered by SST1519 +dotnet_diagnostic.RCS1003.severity = none # Add braces to if-else (when expression spans over multiple lines) — covered by SST1519 dotnet_diagnostic.RCS1005.severity = error # Simplify nested using statement dotnet_diagnostic.RCS1006.severity = none # Merge 'else' with nested 'if' — covered by SST1465 dotnet_diagnostic.RCS1007.severity = error # Add braces dotnet_diagnostic.RCS1031.severity = none # Remove unnecessary braces in switch section -- we don't mind braces in switch statements dotnet_diagnostic.RCS1032.severity = error # Remove redundant parentheses -dotnet_diagnostic.RCS1033.severity = error # Remove redundant boolean literal -dotnet_diagnostic.RCS1039.severity = error # Remove argument list from attribute +dotnet_diagnostic.RCS1033.severity = none # Remove redundant boolean literal — covered by SST1143 +dotnet_diagnostic.RCS1039.severity = none # Remove argument list from attribute — covered by SST1411 dotnet_diagnostic.RCS1042.severity = none # Remove enum default underlying type — covered by SST1177 dotnet_diagnostic.RCS1043.severity = error # Remove 'partial' modifier from type with a single part -dotnet_diagnostic.RCS1049.severity = error # Simplify boolean comparison +dotnet_diagnostic.RCS1049.severity = none # Simplify boolean comparison — covered by SST1143 dotnet_diagnostic.RCS1058.severity = none # Use compound assignment — covered by SST1185 dotnet_diagnostic.RCS1061.severity = none # Merge 'if' with nested 'if' — covered by SST2013 dotnet_diagnostic.RCS1068.severity = none # Simplify logical negation — covered by SST1172/SST2006 @@ -873,10 +873,10 @@ dotnet_diagnostic.RCS1069.severity = none # Remove unnecessary case label — co dotnet_diagnostic.RCS1070.severity = none # Remove redundant default switch section — covered by SST1179 dotnet_diagnostic.RCS1071.severity = none # Remove redundant base constructor call — covered by SST1178 dotnet_diagnostic.RCS1072.severity = none # Remove empty namespace declaration — covered by SST1435 -dotnet_diagnostic.RCS1073.severity = error # Convert 'if' to 'return' statement +dotnet_diagnostic.RCS1073.severity = none # Convert 'if' to 'return' statement — covered by SST1197 dotnet_diagnostic.RCS1074.severity = none # Remove redundant constructor — covered by SST1433 -dotnet_diagnostic.RCS1078.severity = error # Use "" or 'string.Empty' -dotnet_diagnostic.RCS1084.severity = error # Use coalesce expression instead of conditional expression +dotnet_diagnostic.RCS1078.severity = none # Use "" or 'string.Empty' — conflicts with SST1122, which owns the string.Empty direction +dotnet_diagnostic.RCS1084.severity = none # Use coalesce expression instead of conditional expression — covered by SST1195 dotnet_diagnostic.RCS1085.severity = none # Use auto-implemented property — covered by SST1420 dotnet_diagnostic.RCS1089.severity = error # Use --/++ operator instead of assignment dotnet_diagnostic.RCS1097.severity = error # Remove redundant 'ToString' call @@ -884,7 +884,7 @@ dotnet_diagnostic.RCS1103.severity = error # Convert 'if' to assignment dotnet_diagnostic.RCS1104.severity = none # Simplify conditional expression — covered by SST1182 dotnet_diagnostic.RCS1105.severity = error # Unnecessary interpolation dotnet_diagnostic.RCS1106.severity = none # Remove empty destructor — covered by PSH1002 -dotnet_diagnostic.RCS1107.severity = error # Remove redundant 'ToCharArray' call +dotnet_diagnostic.RCS1107.severity = none # Remove redundant 'ToCharArray' call — covered by PSH1217 dotnet_diagnostic.RCS1114.severity = error # Remove redundant delegate creation dotnet_diagnostic.RCS1124.severity = error # Inline local variable dotnet_diagnostic.RCS1126.severity = error # Add braces to if-else @@ -892,7 +892,7 @@ dotnet_diagnostic.RCS1128.severity = error # Use coalesce expression dotnet_diagnostic.RCS1129.severity = none # Remove redundant field initialization — covered by SST1176 dotnet_diagnostic.RCS1132.severity = none # Remove redundant overriding member — covered by SST1181 dotnet_diagnostic.RCS1133.severity = error # Remove redundant Dispose/Close call -dotnet_diagnostic.RCS1134.severity = error # Remove redundant statement +dotnet_diagnostic.RCS1134.severity = none # Remove redundant statement — covered by SST1174 dotnet_diagnostic.RCS1143.severity = error # Simplify coalesce expression dotnet_diagnostic.RCS1145.severity = error # Remove redundant 'as' operator dotnet_diagnostic.RCS1146.severity = error # Use conditional access @@ -906,17 +906,17 @@ dotnet_diagnostic.RCS1188.severity = none # Remove redundant auto-property initi dotnet_diagnostic.RCS1192.severity = none # Unnecessary usage of verbatim string literal — covered by SST1184 dotnet_diagnostic.RCS1199.severity = error # Unnecessary null check dotnet_diagnostic.RCS1206.severity = error # Use conditional access instead of conditional expression -dotnet_diagnostic.RCS1207.severity = error # Use anonymous function or method group +dotnet_diagnostic.RCS1207.severity = none # Use anonymous function or method group — conflicts with SST2239, which owns the method-group direction dotnet_diagnostic.RCS1211.severity = none # Remove unnecessary 'else' — covered by SST1464 dotnet_diagnostic.RCS1212.severity = error # Remove redundant assignment dotnet_diagnostic.RCS1214.severity = none # Unnecessary interpolated string — covered by SST1183 dotnet_diagnostic.RCS1216.severity = error # Unnecessary unsafe context -dotnet_diagnostic.RCS1217.severity = error # Convert interpolated string to concatenation +dotnet_diagnostic.RCS1217.severity = none # Convert interpolated string to concatenation — reverses SST2249, which owns the concatenation-to-interpolation direction dotnet_diagnostic.RCS1218.severity = error # Simplify code branching -dotnet_diagnostic.RCS1220.severity = error # Use pattern matching instead of combination of 'is' and cast +dotnet_diagnostic.RCS1220.severity = none # Use pattern matching instead of combination of 'is' and cast — covered by SST2007 dotnet_diagnostic.RCS1221.severity = error # Use pattern matching instead of combination of 'as' and null check -dotnet_diagnostic.RCS1238.severity = error # Avoid nested ?: operators -dotnet_diagnostic.RCS1244.severity = error # Simplify 'default' expression +dotnet_diagnostic.RCS1238.severity = none # Avoid nested ?: operators — covered by SST1147 +dotnet_diagnostic.RCS1244.severity = none # Simplify 'default' expression — covered by SST1188 dotnet_diagnostic.RCS1249.severity = none # Unnecessary null-forgiving operator — disabled because multi-TFM nullability annotations can differ per platform, leading to false positives dotnet_diagnostic.RCS1251.severity = error # Remove unnecessary braces from record declaration dotnet_diagnostic.RCS1259.severity = error # Remove empty syntax (replaces RCS1066) @@ -927,40 +927,40 @@ dotnet_diagnostic.RCS1268.severity = error # Simplify numeric comparison ################### # Roslynator Analyzers (RCS1xxx) - Code Quality ################### -dotnet_diagnostic.RCS1013.severity = error # Use predefined type +dotnet_diagnostic.RCS1013.severity = none # Use predefined type — covered by SST1121 dotnet_diagnostic.RCS1014.severity = error # Use explicitly/implicitly typed array dotnet_diagnostic.RCS1015.severity = error # Use nameof operator -dotnet_diagnostic.RCS1016.severity = error # Use block body or expression body +dotnet_diagnostic.RCS1016.severity = none # Use block body or expression body — conflicts with SST2219, which owns the expression-bodied accessor direction dotnet_diagnostic.RCS1020.severity = none # Covered by SST2234 (canonical) dotnet_diagnostic.RCS1021.severity = error # Convert lambda expression body to expression body dotnet_diagnostic.RCS1044.severity = none # Remove original exception from throw statement — covered by SST1430 dotnet_diagnostic.RCS1046.severity = none # Asynchronous method name should end with 'Async' — TUnit test method naming convention doesn't follow the Async suffix — covered by SST1317 dotnet_diagnostic.RCS1047.severity = error # Non-asynchronous method name should not end with 'Async' -dotnet_diagnostic.RCS1048.severity = error # Use lambda expression instead of anonymous method +dotnet_diagnostic.RCS1048.severity = none # Use lambda expression instead of anonymous method — covered by SST1130 dotnet_diagnostic.RCS1050.severity = error # Include/omit parentheses when creating new object -dotnet_diagnostic.RCS1051.severity = error # Add/remove parentheses from condition in conditional operator +dotnet_diagnostic.RCS1051.severity = none # Add/remove parentheses from condition in conditional operator — conflicts with SST1459, which owns the non-grouping-parenthesis removal direction dotnet_diagnostic.RCS1056.severity = none # Avoid usage of using alias directive - used to avoid conflicts -dotnet_diagnostic.RCS1059.severity = error # Avoid locking on publicly accessible instance +dotnet_diagnostic.RCS1059.severity = none # Avoid locking on publicly accessible instance — covered by SST1901 dotnet_diagnostic.RCS1075.severity = none # Avoid empty catch clause that catches System.Exception — covered by SST1429 dotnet_diagnostic.RCS1079.severity = error # Throwing of new NotImplementedException dotnet_diagnostic.RCS1081.severity = error # Split variable declaration dotnet_diagnostic.RCS1093.severity = error # File contains no code -dotnet_diagnostic.RCS1094.severity = error # Declare using directive on top level +dotnet_diagnostic.RCS1094.severity = none # Declare using directive on top level — covered by SST1200 dotnet_diagnostic.RCS1096.severity = none # Use 'HasFlag' method or bitwise operator — covered by PSH1016 dotnet_diagnostic.RCS1098.severity = none # Constant values should be placed on right side of comparisons — covered by SST1186 -dotnet_diagnostic.RCS1099.severity = error # Default label should be the last label in a switch section +dotnet_diagnostic.RCS1099.severity = none # Default label should be the last label in a switch section — covered by SST1466 dotnet_diagnostic.RCS1102.severity = none # Make class static — covered by SST1432 dotnet_diagnostic.RCS1108.severity = error # Add 'static' modifier to all partial class declarations dotnet_diagnostic.RCS1111.severity = error # Add braces to switch section with multiple statements dotnet_diagnostic.RCS1113.severity = error # Use 'string.IsNullOrEmpty' method -dotnet_diagnostic.RCS1118.severity = error # Mark local variable as const -dotnet_diagnostic.RCS1123.severity = error # Add parentheses when necessary -dotnet_diagnostic.RCS1130.severity = error # Bitwise operation on enum without Flags attribute +dotnet_diagnostic.RCS1118.severity = none # Mark local variable as const — covered by PSH1402 +dotnet_diagnostic.RCS1123.severity = none # Add parentheses when necessary — covered by SST1407 +dotnet_diagnostic.RCS1130.severity = none # Bitwise operation on enum without Flags attribute — covered by SST2458 dotnet_diagnostic.RCS1135.severity = error # Declare enum member with zero value (when enum has FlagsAttribute) -dotnet_diagnostic.RCS1136.severity = error # Merge switch sections with equivalent content +dotnet_diagnostic.RCS1136.severity = none # Merge switch sections with equivalent content — covered by SST2414 dotnet_diagnostic.RCS1154.severity = error # Sort enum members -dotnet_diagnostic.RCS1155.severity = error # Use StringComparison when comparing strings -dotnet_diagnostic.RCS1156.severity = error # Use string.Length instead of comparison with empty string +dotnet_diagnostic.RCS1155.severity = none # Use StringComparison when comparing strings — covered by PSH1207 +dotnet_diagnostic.RCS1156.severity = none # Use string.Length instead of comparison with empty string — covered by PSH1204 dotnet_diagnostic.RCS1157.severity = none # Composite enum value contains undefined flag — covered by SST2303 dotnet_diagnostic.RCS1159.severity = error # Use EventHandler dotnet_diagnostic.RCS1160.severity = none # Abstract type should not have public constructors — covered by SST1428 @@ -971,11 +971,11 @@ dotnet_diagnostic.RCS1168.severity = none # Parameter name differs from base nam dotnet_diagnostic.RCS1169.severity = error # Make field read-only dotnet_diagnostic.RCS1170.severity = error # Use read-only auto-implemented property dotnet_diagnostic.RCS1172.severity = none # Use 'is' operator instead of 'as' operator — covered by SST2005 -dotnet_diagnostic.RCS1187.severity = error # Use constant instead of field +dotnet_diagnostic.RCS1187.severity = none # Use constant instead of field — covered by PSH1402 dotnet_diagnostic.RCS1191.severity = error # Declare enum value as combination of names -dotnet_diagnostic.RCS1193.severity = error # Overriding member should not change 'params' modifier +dotnet_diagnostic.RCS1193.severity = none # Overriding member should not change 'params' modifier — covered by SST2426 dotnet_diagnostic.RCS1196.severity = error # Call extension method as instance method -dotnet_diagnostic.RCS1200.severity = error # Call 'Enumerable.ThenBy' instead of 'Enumerable.OrderBy' +dotnet_diagnostic.RCS1200.severity = none # Call 'Enumerable.ThenBy' instead of 'Enumerable.OrderBy' — covered by PSH1108 dotnet_diagnostic.RCS1201.severity = error # Use method chaining dotnet_diagnostic.RCS1202.severity = error # Avoid NullReferenceException dotnet_diagnostic.RCS1204.severity = error # Use EventArgs.Empty @@ -990,22 +990,22 @@ dotnet_diagnostic.RCS1224.severity = error # Make method an extension method dotnet_diagnostic.RCS1225.severity = error # Make class sealed dotnet_diagnostic.RCS1227.severity = none # Validate arguments correctly — covered by SST2404 dotnet_diagnostic.RCS1229.severity = error # Use async/await when necessary -dotnet_diagnostic.RCS1231.severity = suggestion # Make parameter ref read-only +dotnet_diagnostic.RCS1231.severity = none # Make parameter ref read-only — covered by PSH1007 dotnet_diagnostic.RCS1233.severity = none # Use short-circuiting operator — covered by SST1468 dotnet_diagnostic.RCS1234.severity = error # Duplicate enum value dotnet_diagnostic.RCS1239.severity = error # Use 'for' statement instead of 'while' statement dotnet_diagnostic.RCS1240.severity = error # Operator is unnecessary -dotnet_diagnostic.RCS1242.severity = error # Do not pass non-read-only struct by read-only reference +dotnet_diagnostic.RCS1242.severity = none # Do not pass non-read-only struct by read-only reference — covered by PSH1003 dotnet_diagnostic.RCS1243.severity = none # Duplicate word in a comment — covered by SST1658 (documentation comments) dotnet_diagnostic.RCS1247.severity = error # Fix documentation comment tag -dotnet_diagnostic.RCS1248.severity = error # Normalize null check -dotnet_diagnostic.RCS1250.severity = error # Use implicit/explicit object creation +dotnet_diagnostic.RCS1248.severity = none # Normalize null check — conflicts with SST1149, which owns the 'is null' pattern direction +dotnet_diagnostic.RCS1250.severity = none # Use implicit/explicit object creation — conflicts with SST2202, which owns the implicit-target-type direction dotnet_diagnostic.RCS1252.severity = error # Normalize usage of infinite loop dotnet_diagnostic.RCS1254.severity = error # Normalize format of enum flag value dotnet_diagnostic.RCS1255.severity = none # Simplify argument null check — conflicts with our ArgumentExceptionHelper helper pattern dotnet_diagnostic.RCS1257.severity = error # Use enum field explicitly dotnet_diagnostic.RCS1258.severity = error # Unnecessary enum flag -dotnet_diagnostic.RCS1260.severity = error # Add/remove trailing comma +dotnet_diagnostic.RCS1260.severity = none # Add/remove trailing comma — conflicts with SST1413, which owns the trailing-comma direction dotnet_diagnostic.RCS1261.severity = none # Resource can be disposed asynchronously — covered by PSH1310 dotnet_diagnostic.RCS1264.severity = error # Use 'var' or explicit type (replaces RCS1010, RCS1176, RCS1177) dotnet_diagnostic.RCS1266.severity = none # Use raw string literal — covered by SST2243 @@ -1016,16 +1016,16 @@ dotnet_diagnostic.RCS1267.severity = error # Use string interpolation instead of ################### dotnet_diagnostic.RCS1077.severity = none # Covered by the PSH1101-PSH1111 family (canonical) dotnet_diagnostic.RCS1080.severity = none # Covered by PSH1106 (canonical) -dotnet_diagnostic.RCS1112.severity = error # Combine 'Enumerable.Where' method chain +dotnet_diagnostic.RCS1112.severity = none # Combine 'Enumerable.Where' method chain — covered by PSH1109 dotnet_diagnostic.RCS1186.severity = error # Use Regex instance instead of static method -dotnet_diagnostic.RCS1190.severity = error # Join string expressions +dotnet_diagnostic.RCS1190.severity = none # Join string expressions — conflicts with SST2470, which reports the fused-literal seam this would create dotnet_diagnostic.RCS1195.severity = error # Use ^ operator dotnet_diagnostic.RCS1197.severity = none # Optimize StringBuilder.Append/AppendLine call — covered by PSH1203/PSH1214 dotnet_diagnostic.RCS1198.severity = none # Avoid unnecessary boxing of value type — boxing is unavoidable bridging Rx and IEnumerable dotnet_diagnostic.RCS1230.severity = none # Unnecessary explicit use of enumerator — covered by SST1467 dotnet_diagnostic.RCS1235.severity = error # Optimize method call dotnet_diagnostic.RCS1236.severity = none # Use exception filter — covered by SST2009 -dotnet_diagnostic.RCS1246.severity = error # Use element access +dotnet_diagnostic.RCS1246.severity = none # Use element access — covered by PSH1106 ################### # Roslynator Analyzers (RCS1xxx) - Maintainability @@ -1210,7 +1210,7 @@ dotnet_diagnostic.SST1117.severity = error # Instance member access follows the dotnet_diagnostic.SST1118.severity = none # A parameter should not span multiple lines dotnet_diagnostic.SST1119.severity = error # A numeric literal groups its digit separators irregularly dotnet_diagnostic.SST1120.severity = error # A comment contains no text -dotnet_diagnostic.SST1121.severity = none # A framework type name is used instead of its built-in alias — duplicate of RCS1013 +dotnet_diagnostic.SST1121.severity = error # A framework type name is used instead of its built-in alias (opt-in rule, enabled here) dotnet_diagnostic.SST1122.severity = error # An empty string literal is used instead of string.Empty dotnet_diagnostic.SST1123.severity = error # A #region is placed inside a code element body dotnet_diagnostic.SST1124.severity = error # A #region directive is used @@ -1290,7 +1290,7 @@ dotnet_diagnostic.SST1198.severity = error # Collapse assignment-only branches i dotnet_diagnostic.SST1199.severity = error # Prefer compile-time type names # Ordering -dotnet_diagnostic.SST1200.severity = none # Using directives should be placed outside the namespace — usings live outside file-scoped namespaces +dotnet_diagnostic.SST1200.severity = error # Using directives should be placed outside the namespace dotnet_diagnostic.SST1201.severity = error # Members should be ordered by kind dotnet_diagnostic.SST1202.severity = error # Members should be ordered by accessibility dotnet_diagnostic.SST1203.severity = error # Constants should appear before fields @@ -1596,6 +1596,12 @@ dotnet_diagnostic.SST2242.severity = error # Enum switch statement mappings shou dotnet_diagnostic.SST2243.severity = error # A verbatim string with escapes or line breaks can use a raw string literal dotnet_diagnostic.SST2244.severity = error # A numeric literal's suffix is lower case dotnet_diagnostic.SST2245.severity = error # A for loop with only a condition should be a while loop +dotnet_diagnostic.SST2246.severity = error # A chain of conditional expressions testing one value against constants can be a switch expression +dotnet_diagnostic.SST2247.severity = error # Consecutive locals copying one value's members in order can be a deconstruction +dotnet_diagnostic.SST2248.severity = error # Two constant comparisons of the same value can fold into one is-pattern +dotnet_diagnostic.SST2249.severity = error # A literal-format string.Format or literal concatenation can be an interpolated string +dotnet_diagnostic.SST2250.severity = error # A bare local assigned once by the next statement can be an initialized declaration +dotnet_diagnostic.SST2251.severity = error # A method call names type arguments that inference would supply dotnet_diagnostic.RCS1040.severity = none # covered by SST1180 # Design @@ -1621,6 +1627,10 @@ dotnet_diagnostic.SST2318.severity = error # Two members have token-identical bo dotnet_diagnostic.SST2319.severity = error # An overload's optional default can never be used dotnet_diagnostic.SST2320.severity = error # An interface inherits two interfaces that declare the same member dotnet_diagnostic.SST2321.severity = error # Environment.Exit or Environment.FailFast is called from library code +dotnet_diagnostic.SST2322.severity = error # A non-private readonly field holds a mutable collection callers can still change +dotnet_diagnostic.SST2323.severity = error # A stateless abstract class declaring only public abstract members should be an interface +dotnet_diagnostic.SST2324.severity = error # A member is declared more accessible than its containing type +dotnet_diagnostic.SST2325.severity = error # An async method checks an argument after its first await # Correctness dotnet_diagnostic.SST2400.severity = error # Two arguments name each other's parameters and have been transposed @@ -1670,6 +1680,42 @@ dotnet_diagnostic.SST2443.severity = error # An ILogger is injected or created w dotnet_diagnostic.SST2444.severity = error # A regular expression pattern is invalid dotnet_diagnostic.SST2445.severity = error # A culture-sensitive custom date or time format is used without an invariant culture dotnet_diagnostic.SST2446.severity = error # A Stream.ReadAsync result is discarded through ConfigureAwait or a local +dotnet_diagnostic.SST2448.severity = error # A combined or opaque delegate is removed with - or -=, which strips only a contiguous run +dotnet_diagnostic.SST2449.severity = error # A lambda or anonymous-method handler is removed with -=, which never matches it +dotnet_diagnostic.SST2450.severity = error # A Debug.Assert condition performs a side effect that a release build compiles out +dotnet_diagnostic.SST2451.severity = error # Every constructor is private and no member ever creates an instance +dotnet_diagnostic.SST2452.severity = error # A [Pure] method returns void, Task, or ValueTask, so it has no observable result +dotnet_diagnostic.SST2456.severity = error # An override or new field-like event gets its own backing delegate field +dotnet_diagnostic.SST2457.severity = error # An integer Sum wrapped in unchecked still throws on overflow +dotnet_diagnostic.SST2458.severity = error # A bitwise operator is applied to an enum not declared [Flags] +dotnet_diagnostic.SST2459.severity = error # [Optional] on a ref or out parameter advertises an optionality no caller can use +dotnet_diagnostic.SST2460.severity = error # [DefaultValue] on a method or record parameter is inert +dotnet_diagnostic.SST2462.severity = error # A new member is less accessible than the inherited member it hides +dotnet_diagnostic.SST2463.severity = error # A field differs from an inherited accessible field only by case +dotnet_diagnostic.SST2464.severity = error # A mutable class declares a value-equality operator ==, so it is lost as a hash key +dotnet_diagnostic.SST2465.severity = error # A for loop body reassigns the counter or the local its condition tests +dotnet_diagnostic.SST2467.severity = error # A params overload is shadowed by a same-arity overload with a more specific last parameter +dotnet_diagnostic.SST2468.severity = error # A classic partial method is declared but never implemented, so its calls are removed +dotnet_diagnostic.SST2470.severity = error # Two string literals concatenate with no space, fusing a SQL keyword into the next token +dotnet_diagnostic.SST2472.severity = error # A type is exported for a contract it neither implements nor inherits +dotnet_diagnostic.SST2473.severity = error # A shared export part is constructed with new, bypassing the container +dotnet_diagnostic.SST2474.severity = error # A part-creation-policy attribute is applied to a type with no [Export] +dotnet_diagnostic.SST2475.severity = error # An entity's primary key is typed DateTime or DateTimeOffset +dotnet_diagnostic.SST2479.severity = error # A loop variable captured by a callback stored beyond the iteration reads its final value +dotnet_diagnostic.SST2481.severity = error # A GetHashCode override folds the base identity hash into a value hash + +# Testing +dotnet_diagnostic.SST2500.severity = error # A test method contains no assertion and no expected-exception check +dotnet_diagnostic.SST2501.severity = error # An equality or identity assertion compares an expression with itself +dotnet_diagnostic.SST2502.severity = error # An equality assertion passes the constant as actual and the computed value as expected +dotnet_diagnostic.SST2503.severity = error # An equality assertion compares a value against a boolean literal +dotnet_diagnostic.SST2504.severity = error # A test fixture declares and inherits no test method +dotnet_diagnostic.SST2505.severity = error # A test method declares parameters but no data source +dotnet_diagnostic.SST2506.severity = error # A test method calls Thread.Sleep +dotnet_diagnostic.SST2507.severity = error # A test method declares its expected failure with an expected-exception attribute + +# Logging +dotnet_diagnostic.SST2600.severity = error # Application output is written through legacy Trace instead of a structured logger ################### # PerformanceSharp Analyzers (PSH) - severities staged ahead of package adoption; @@ -1697,6 +1743,7 @@ dotnet_diagnostic.PSH1017.severity = error # A property allocates a copy of a co dotnet_diagnostic.PSH1018.severity = error # A hand-written array is passed to a params parameter dotnet_diagnostic.PSH1019.severity = error # The range indexer on an array allocates a copy; slice with AsSpan/AsMemory dotnet_diagnostic.PSH1020.severity = error # Prefer a jagged array over a multidimensional one +dotnet_diagnostic.PSH1021.severity = error # An explicit GC.Collect or GC.WaitForPendingFinalizers forces collection the runtime tunes itself dotnet_diagnostic.PSH1100.severity = error # Hot-path code should avoid System.Linq.Enumerable calls dotnet_diagnostic.PSH1101.severity = none # LINQ terminal predicate simplification is reserved for test code; production code should avoid LINQ on hot paths dotnet_diagnostic.PSH1102.severity = none # LINQ type-filter simplification is reserved for test code; production code should avoid LINQ on hot paths @@ -1786,6 +1833,76 @@ dotnet_diagnostic.PSH1416.severity = error # Cache the serializer options instea dotnet_diagnostic.PSH1417.severity = error # Do not compute an expensive argument for an assertion dotnet_diagnostic.PSH1418.severity = error # An HttpClient is constructed on every call +################### +# SecuritySharp Analyzers (SES) +################### +# Every rule is raised to error, including the three that ship at suggestion (SES1403, SES1506, +# SES1605). Security rules only ever suggest an API they can resolve in the compilation and report +# local shapes only - there is no interprocedural taint tracking, so the taint-flow CA rules stay on. +# securitysharp.SES1003.iterations = 100000 # minimum accepted PBKDF2 iteration count +# securitysharp.SES1403.maxdepth = 64 # highest accepted System.Text.Json MaxDepth + +# Cryptography +dotnet_diagnostic.SES1001.severity = error # AEAD encryption must not use a constant or reused nonce +dotnet_diagnostic.SES1002.severity = error # Password-based key derivation must not use a constant or predictable salt +dotnet_diagnostic.SES1003.severity = error # Password-based key derivation must use a sufficient iteration count +dotnet_diagnostic.SES1004.severity = error # A secret must not be produced from Guid.NewGuid() +dotnet_diagnostic.SES1005.severity = error # Compare secret values in constant time +dotnet_diagnostic.SES1006.severity = error # A Data Protection key ring is persisted without a ProtectKeysWith call, so keys sit unencrypted at rest + +# Transport +dotnet_diagnostic.SES1102.severity = error # Do not accept any server certificate +dotnet_diagnostic.SES1104.severity = error # Certificate-chain validation must not be deliberately weakened +dotnet_diagnostic.SES1105.severity = error # Bearer and OpenID Connect metadata must not be retrieved over plain HTTP outside development +dotnet_diagnostic.SES1106.severity = error # Do not send HttpClient requests to a cleartext http URL +dotnet_diagnostic.SES1107.severity = error # A SQL connection string weakens transport security via TrustServerCertificate or a disabled Encrypt + +# Secrets +dotnet_diagnostic.SES1201.severity = error # Do not hard-code secrets in source +dotnet_diagnostic.SES1202.severity = error # Do not hard-code a credential value + +# Injection +dotnet_diagnostic.SES1301.severity = error # Do not build a process command line from non-constant string parts +dotnet_diagnostic.SES1302.severity = error # A shell-executed process must not use a non-constant FileName +dotnet_diagnostic.SES1303.severity = error # Regular-expression pattern must not be built from non-constant data +dotnet_diagnostic.SES1304.severity = error # An archive entry name must not build a write path without a containment check +dotnet_diagnostic.SES1305.severity = error # Do not build a storage path from an uploaded file name +dotnet_diagnostic.SES1306.severity = error # Do not compile or execute non-constant C# via the scripting API +dotnet_diagnostic.SES1307.severity = error # Path.GetTempFileName creates a predictable, world-readable temporary file +dotnet_diagnostic.SES1308.severity = error # A file or directory is created group- or world-writable +dotnet_diagnostic.SES1309.severity = error # An XSLT stylesheet is loaded with embedded script enabled + +# Serialization +dotnet_diagnostic.SES1401.severity = error # A type resolved from non-constant data must not be instantiated or deserialized +dotnet_diagnostic.SES1402.severity = error # Do not load an assembly from raw bytes or a non-constant location +dotnet_diagnostic.SES1403.severity = error # JSON deserialization depth limit must stay within a safe ceiling +dotnet_diagnostic.SES1404.severity = error # A type is instantiated by name from a non-constant Activator typeName +dotnet_diagnostic.SES1405.severity = error # MessagePack typeless deserialization reconstructs whatever type the payload names + +# Web hardening +dotnet_diagnostic.SES1501.severity = error # A CORS policy must not allow credentials together with any origin +dotnet_diagnostic.SES1502.severity = error # A CORS origin predicate must not unconditionally allow every origin +dotnet_diagnostic.SES1503.severity = error # JWT signature verification must not be disabled on TokenValidationParameters +dotnet_diagnostic.SES1504.severity = error # A cookie with SameSite=None must be marked Secure +dotnet_diagnostic.SES1505.severity = error # The request body size limit must not be removed +dotnet_diagnostic.SES1506.severity = error # The developer exception page must be guarded by a development-environment check +dotnet_diagnostic.SES1507.severity = error # AllowAnonymous and Authorize on the same declaration conflict +dotnet_diagnostic.SES1508.severity = error # A validation method must not fail open by returning success from a catch +dotnet_diagnostic.SES1509.severity = error # A backtracking-prone constant regex runs without a match timeout or NonBacktracking +dotnet_diagnostic.SES1510.severity = error # A controller redirects to a non-constant URL, allowing an open redirect +dotnet_diagnostic.SES1511.severity = error # The forwarded-headers trust boundary is cleared, letting proxies spoof the client IP +dotnet_diagnostic.SES1512.severity = error # Sensitive framework diagnostics are enabled without a development-environment guard +dotnet_diagnostic.SES1513.severity = error # An AuthorizeAsync result is discarded, so the guarded operation runs regardless +dotnet_diagnostic.SES1514.severity = error # OpenID Connect protections (PKCE, state, nonce) are disabled + +# AI trust boundaries +dotnet_diagnostic.SES1601.severity = error # An LLM system prompt must be a constant, trusted template +dotnet_diagnostic.SES1602.severity = error # Do not route AI model output into a process, file, or raw SQL sink +dotnet_diagnostic.SES1603.severity = error # An AI tool declared read-only or non-destructive must not call a state-changing API +dotnet_diagnostic.SES1604.severity = error # Prompt-template input encoding must not be disabled +dotnet_diagnostic.SES1605.severity = error # AI instrumentation must not enable sensitive-data capture +dotnet_diagnostic.SES1606.severity = error # Do not fetch model weights over cleartext HTTP + ################### # PublicApiAnalyzers (RSxxxx) - public API surface tracking ################### @@ -1927,27 +2044,27 @@ dotnet_diagnostic.IL3057.severity = error # Reflection access to generic paramet ################### # SonarAnalyzer (Sxxxx) - Blocker Bug ################### -dotnet_diagnostic.S1048.severity = error # Finalizers should not throw exceptions +dotnet_diagnostic.S1048.severity = none # Finalizers should not throw exceptions — covered by SST1485 dotnet_diagnostic.S2190.severity = none # Loops and recursions should not be infinite dotnet_diagnostic.S2275.severity = none # Composite format strings should not lead to unexpected behavior at runtime - DUPLICATE CA2241 -dotnet_diagnostic.S2857.severity = error # SQL keywords should be delimited by whitespace +dotnet_diagnostic.S2857.severity = none # SQL keywords should be delimited by whitespace — covered by SST2470 dotnet_diagnostic.S2930.severity = none # "IDisposables" should be disposed — covered by SST2410 dotnet_diagnostic.S2931.severity = none # Classes with "IDisposable" members should implement "IDisposable" — covered by SST2315 dotnet_diagnostic.S3464.severity = none # Type inheritance should not be recursive — covered by SST2437 dotnet_diagnostic.S3869.severity = error # "SafeHandle.DangerousGetHandle" should not be called dotnet_diagnostic.S3889.severity = error # "Thread.Resume" and "Thread.Suspend" should not be used -dotnet_diagnostic.S4159.severity = error # Classes should implement their "ExportAttribute" interfaces +dotnet_diagnostic.S4159.severity = none # Classes should implement their "ExportAttribute" interfaces — covered by SST2472 ################### # SonarAnalyzer (Sxxxx) - Critical Bug ################### -dotnet_diagnostic.S2551.severity = error # Shared resources should not be used for locking +dotnet_diagnostic.S2551.severity = none # Shared resources should not be used for locking — covered by SST1902 dotnet_diagnostic.S2952.severity = error # Classes should "Dispose" of members from the classes' own "Dispose" methods dotnet_diagnostic.S3449.severity = error # Right operands of shift operators should be integers dotnet_diagnostic.S4275.severity = none # Getters and setters should access the expected fields — covered by SST2422 -dotnet_diagnostic.S4277.severity = error # "Shared" parts should not be created with "new" +dotnet_diagnostic.S4277.severity = none # "Shared" parts should not be created with "new" — covered by SST2473 dotnet_diagnostic.S4583.severity = error # Calls to delegate's method "BeginInvoke" should be paired with calls to "EndInvoke" -dotnet_diagnostic.S4586.severity = error # Non-async "Task/Task" methods should not return null +dotnet_diagnostic.S4586.severity = none # Non-async "Task/Task" methods should not return null — covered by PSH1312 dotnet_diagnostic.S5856.severity = none # Regular expressions should be syntactically valid — covered by SST2444 dotnet_diagnostic.S6674.severity = none # Log message template should be syntactically correct — covered by SST2441 @@ -1975,16 +2092,16 @@ dotnet_diagnostic.S2996.severity = error # "ThreadStatic" fields should not be i dotnet_diagnostic.S2997.severity = none # "IDisposables" created in a "using" statement should not be returned — covered by SST2423 dotnet_diagnostic.S3005.severity = error # "ThreadStatic" should not be used on non-static fields dotnet_diagnostic.S3168.severity = none # "async" methods should not return "void" — covered by SST1905 -dotnet_diagnostic.S3172.severity = error # Delegates should not be subtracted -dotnet_diagnostic.S3244.severity = error # Anonymous delegates should not be used to unsubscribe from Events +dotnet_diagnostic.S3172.severity = none # Delegates should not be subtracted — covered by SST2448 +dotnet_diagnostic.S3244.severity = none # Anonymous delegates should not be used to unsubscribe from Events — covered by SST2449 dotnet_diagnostic.S3249.severity = none # Covered by SST1447 (canonical) dotnet_diagnostic.S3263.severity = none # Static fields should appear in the order they must be initialized — covered by SST2428 dotnet_diagnostic.S3343.severity = none # Caller information parameters should come at the end of the parameter list — covered by SST2433 -dotnet_diagnostic.S3346.severity = error # Expressions used in "Debug.Assert" should not produce side effects -dotnet_diagnostic.S3453.severity = error # Classes should not have only "private" constructors +dotnet_diagnostic.S3346.severity = none # Expressions used in "Debug.Assert" should not produce side effects — covered by SST2450 +dotnet_diagnostic.S3453.severity = none # Classes should not have only "private" constructors — covered by SST2451 dotnet_diagnostic.S3466.severity = none # Optional parameters should be passed to "base" calls — covered by SST2425 dotnet_diagnostic.S3598.severity = error # One-way "OperationContract" methods should have "void" return type -dotnet_diagnostic.S3603.severity = error # Methods with "Pure" attribute should return a value +dotnet_diagnostic.S3603.severity = none # Methods with "Pure" attribute should return a value — covered by SST2452 dotnet_diagnostic.S3610.severity = error # Nullable type comparison should not be redundant dotnet_diagnostic.S3903.severity = none # Types should be defined in named namespaces — covered by SST2312 dotnet_diagnostic.S3923.severity = none # All branches in a conditional structure should not have exactly the same implementation — covered by SST1476 @@ -1995,7 +2112,7 @@ dotnet_diagnostic.S3984.severity = none # Exceptions should not be created witho dotnet_diagnostic.S4143.severity = none # Collection elements should not be replaced unconditionally — covered by SST1487 dotnet_diagnostic.S4210.severity = error # Windows Forms entry points should be marked with STAThread dotnet_diagnostic.S4260.severity = error # "ConstructorArgument" parameters should exist in constructors -dotnet_diagnostic.S4428.severity = error # "PartCreationPolicyAttribute" should be used with "ExportAttribute" +dotnet_diagnostic.S4428.severity = none # "PartCreationPolicyAttribute" should be used with "ExportAttribute" — covered by SST2474 dotnet_diagnostic.S6507.severity = none # Blocks should not be synchronized on local variables — covered by SST1903 dotnet_diagnostic.S6677.severity = none # Message template placeholders should be unique — covered by SST2442 dotnet_diagnostic.S6797.severity = error # Blazor query parameter type should be supported @@ -2015,10 +2132,10 @@ dotnet_diagnostic.S2345.severity = none # Flags enumerations should explicitly i dotnet_diagnostic.S2674.severity = none # The length returned from a stream read should be checked — covered by SST2446 dotnet_diagnostic.S2934.severity = none # Property assignments should not be made for "readonly" fields not constrained to reference types — covered by SST2421 dotnet_diagnostic.S2955.severity = error # Generic parameters not constrained to reference types should not be compared to "null" -dotnet_diagnostic.S3363.severity = error # Date and time should not be used as a type for primary keys +dotnet_diagnostic.S3363.severity = none # Date and time should not be used as a type for primary keys — covered by SST2475 dotnet_diagnostic.S3397.severity = none # "base.Equals" should not be used to check for reference equality in "Equals" if "base" is not "object" — covered by SST2435 dotnet_diagnostic.S3456.severity = none # "string.ToCharArray()" and "ReadOnlySpan.ToArray()" should not be called redundantly — covered by PSH1217 -dotnet_diagnostic.S3887.severity = error # Mutable, non-private fields should not be "readonly" +dotnet_diagnostic.S3887.severity = none # Mutable, non-private fields should not be "readonly" — covered by SST2322 ################### # SonarAnalyzer (Sxxxx) - Blocker Vulnerability @@ -2026,7 +2143,7 @@ dotnet_diagnostic.S3887.severity = error # Mutable, non-private fields should no dotnet_diagnostic.S2115.severity = error # A secure password should be used when connecting to a database dotnet_diagnostic.S2755.severity = error # XML parsers should not be vulnerable to XXE attacks dotnet_diagnostic.S3884.severity = error # "CoSetProxyBlanket" and "CoInitializeSecurity" should not be used -dotnet_diagnostic.S6418.severity = error # Secrets should not be hard-coded +dotnet_diagnostic.S6418.severity = none # Secrets should not be hard-coded — covered by SES1201 ################### # SonarAnalyzer (Sxxxx) - Critical Vulnerability @@ -2057,7 +2174,7 @@ dotnet_diagnostic.S7039.severity = error # Content Security Policies should be r dotnet_diagnostic.S1147.severity = none # Exit methods should not be called — covered by SST2321 dotnet_diagnostic.S1451.severity = none # Track lack of copyright and license headers dotnet_diagnostic.S2178.severity = none # Short-circuit logic should be used in boolean contexts — covered by SST2415 -dotnet_diagnostic.S2187.severity = error # Test classes should contain at least one test case +dotnet_diagnostic.S2187.severity = none # Test classes should contain at least one test case — covered by SST2504 dotnet_diagnostic.S2306.severity = error # "async" and "await" should not be used as identifiers dotnet_diagnostic.S2368.severity = none # Public methods should not have multidimensional array parameters — jagged arrays are the chosen layout for hot-path lookup tables dotnet_diagnostic.S2387.severity = none # Child class fields should not shadow parent class fields — covered by SST1484 @@ -2073,7 +2190,7 @@ dotnet_diagnostic.S3443.severity = none # Type should not be examined on "System dotnet_diagnostic.S3875.severity = error # "operator==" should not be overloaded on reference types dotnet_diagnostic.S3877.severity = none # Exceptions should not be thrown from unexpected methods — covered by SST1485 dotnet_diagnostic.S4462.severity = none # Calls to "async" methods should not be blocking - covered by PSH1315 -dotnet_diagnostic.S6422.severity = error # Calls to "async" methods should not be blocking in Azure Functions +dotnet_diagnostic.S6422.severity = none # Calls to "async" methods should not be blocking in Azure Functions — covered by PSH1315 dotnet_diagnostic.S6424.severity = error # Interfaces for durable entities should satisfy the restrictions ################### @@ -2084,7 +2201,7 @@ dotnet_diagnostic.S1067.severity = none # Expressions should not be too complex dotnet_diagnostic.S1163.severity = error # Exceptions should not be thrown in finally blocks dotnet_diagnostic.S1186.severity = none # Methods should not be empty — covered by SST1438 dotnet_diagnostic.S121.severity = error # Control structures should use curly braces (kept over SA1503 — Sonar 20ms vs SA1503 50ms) -dotnet_diagnostic.S1215.severity = none # "GC.Collect" should not be called +dotnet_diagnostic.S1215.severity = none # "GC.Collect" should not be called — covered by PSH1021 dotnet_diagnostic.S126.severity = none # "if ... else if" constructs should end with "else" clauses dotnet_diagnostic.S131.severity = none # "switch/Select" statements should contain a "default/Case Else" clauses dotnet_diagnostic.S134.severity = none # Control flow statements "if", "switch", "for", "foreach", "while", "do" and "try" should not be nested too deeply @@ -2097,38 +2214,38 @@ dotnet_diagnostic.S2197.severity = none # Modulus results should not be checked dotnet_diagnostic.S2198.severity = error # Unnecessary mathematical comparisons should not be made dotnet_diagnostic.S2223.severity = none # Non-constant static fields should not be visible - DUPLICATE CA2211 dotnet_diagnostic.S2290.severity = error # Field-like events should not be virtual -dotnet_diagnostic.S2291.severity = error # Overflow checking should not be disabled for "Enumerable.Sum" -dotnet_diagnostic.S2302.severity = error # "nameof" should be used +dotnet_diagnostic.S2291.severity = none # Overflow checking should not be disabled for "Enumerable.Sum" — covered by SST2457 +dotnet_diagnostic.S2302.severity = none # "nameof" should be used — covered by SST1415 dotnet_diagnostic.S2330.severity = none # Array covariance should not be used — covered by SST2434 dotnet_diagnostic.S2339.severity = none # Public constant members should not be used — covered by SST2311 dotnet_diagnostic.S2346.severity = none # Flags enumerations zero-value members should be named "None" - DUPLICATE CA1008 -dotnet_diagnostic.S2360.severity = error # Optional parameters should not be used +dotnet_diagnostic.S2360.severity = none # Optional parameters should not be used — conflicts with SST2433, which owns caller-info parameters requiring a default dotnet_diagnostic.S2365.severity = none # Properties should not make collection or array copies — covered by PSH1017 dotnet_diagnostic.S2479.severity = none # Whitespace and control characters in string literals should be explicit — covered by SST1192 dotnet_diagnostic.S2692.severity = none # "IndexOf" checks should not be for positive numbers — covered by SST2420 dotnet_diagnostic.S2696.severity = error # Instance members should not write to "static" fields -dotnet_diagnostic.S2701.severity = error # Literal boolean values should not be used in assertions +dotnet_diagnostic.S2701.severity = none # Literal boolean values should not be used in assertions — covered by SST2503 dotnet_diagnostic.S3215.severity = error # "interface" instances should not be cast to concrete types dotnet_diagnostic.S3216.severity = error # "ConfigureAwait(false)" should be used -dotnet_diagnostic.S3217.severity = error # "Explicit" conversions of "foreach" loops should not be used -dotnet_diagnostic.S3218.severity = error # Inner class members should not shadow outer class "static" or type members -dotnet_diagnostic.S3265.severity = error # Non-flags enums should not be used in bitwise operations -dotnet_diagnostic.S3353.severity = error # Unchanged variables should be marked as "const" -dotnet_diagnostic.S3447.severity = error # "[Optional]" should not be used on "ref" or "out" parameters -dotnet_diagnostic.S3451.severity = error # "[DefaultValue]" should not be used when "[DefaultParameterValue]" is meant +dotnet_diagnostic.S3217.severity = none # "Explicit" conversions of "foreach" loops should not be used — covered by SST2225 +dotnet_diagnostic.S3218.severity = none # Inner class members should not shadow outer class "static" or type members — covered by SST1484 +dotnet_diagnostic.S3265.severity = none # Non-flags enums should not be used in bitwise operations — covered by SST2458 +dotnet_diagnostic.S3353.severity = none # Unchanged variables should be marked as "const" — covered by PSH1402 +dotnet_diagnostic.S3447.severity = none # "[Optional]" should not be used on "ref" or "out" parameters — covered by SST2459 +dotnet_diagnostic.S3451.severity = none # "[DefaultValue]" should not be used when "[DefaultParameterValue]" is meant — covered by SST2460 dotnet_diagnostic.S3600.severity = none # "params" should not be introduced on overrides — covered by SST2426 dotnet_diagnostic.S3776.severity = none # Cognitive Complexity of methods should not be too high - covered by SST1443 dotnet_diagnostic.S3871.severity = error # Exception types should be "public" dotnet_diagnostic.S3874.severity = none # "out" and "ref" parameters — repo idiom is TryX(..., out T value) dotnet_diagnostic.S3904.severity = error # Assemblies should have version information dotnet_diagnostic.S3937.severity = none # Number patterns should be regular — covered by SST1119 -dotnet_diagnostic.S3972.severity = error # Conditionals should start on new lines +dotnet_diagnostic.S3972.severity = none # Conditionals should start on new lines — covered by SST1146 dotnet_diagnostic.S3973.severity = error # A conditionally executed single line should be denoted by indentation dotnet_diagnostic.S3998.severity = error # Threads should not lock on objects with weak identity dotnet_diagnostic.S4000.severity = error # Pointers to unmanaged memory should not be visible -dotnet_diagnostic.S4015.severity = error # Inherited member visibility should not be decreased +dotnet_diagnostic.S4015.severity = none # Inherited member visibility should not be decreased — covered by SST2462 dotnet_diagnostic.S4019.severity = none # Base class methods should not be hidden — covered by SST2427 -dotnet_diagnostic.S4025.severity = error # Child class fields should not differ from parent class fields only by capitalization +dotnet_diagnostic.S4025.severity = none # Child class fields should not differ from parent class fields only by capitalization — covered by SST2463 dotnet_diagnostic.S4039.severity = none # Interface methods should be callable by derived types - DUPLICATE CA1033 dotnet_diagnostic.S4487.severity = none # Unread "private" fields should be removed dotnet_diagnostic.S4524.severity = none # "default" clauses should be first or last — covered by SST1219 @@ -2161,12 +2278,12 @@ dotnet_diagnostic.S1123.severity = none # "Obsolete" attributes should include e dotnet_diagnostic.S1134.severity = error # Track uses of "FIXME" tags dotnet_diagnostic.S1144.severity = none # Covered by SST1440 (canonical) dotnet_diagnostic.S1151.severity = none # "switch case" clauses should not have too many lines of code — covered by SST1524 -dotnet_diagnostic.S1168.severity = error # Empty arrays and collections should be returned instead of null +dotnet_diagnostic.S1168.severity = none # Empty arrays and collections should be returned instead of null — covered by SST2306 dotnet_diagnostic.S1172.severity = none # Unused method parameters should be removed — covered by SST1461 dotnet_diagnostic.S1200.severity = none # Classes should not be coupled to too many other classes dotnet_diagnostic.S122.severity = none # Statements should be on separate lines — covered by SST1107 dotnet_diagnostic.S125.severity = none # Sections of code should not be commented out — covered by SST1148 -dotnet_diagnostic.S127.severity = error # "for" loop stop conditions should be invariant +dotnet_diagnostic.S127.severity = none # "for" loop stop conditions should be invariant — covered by SST2465 dotnet_diagnostic.S138.severity = none # Functions should not have too many lines of code — covered by SST1523 dotnet_diagnostic.S1479.severity = none # "switch" statements with many "case" clauses — covered by SST1423 dotnet_diagnostic.S1607.severity = error # Tests should not be ignored @@ -2180,11 +2297,11 @@ dotnet_diagnostic.S2326.severity = none # Covered by SST1452 (canonical) dotnet_diagnostic.S2327.severity = error # "try" statements with identical "catch" and/or "finally" blocks should be merged dotnet_diagnostic.S2357.severity = none # Fields should be private — duplicate of SST1401 (canonical); fields intentionally exposed (e.g. public test fields for reflection) already carry per-site SST1401 suppressions dotnet_diagnostic.S2372.severity = none # Exceptions should not be thrown from property getters — covered by SST1485 -dotnet_diagnostic.S2376.severity = error # Write-only properties should not be used +dotnet_diagnostic.S2376.severity = none # Write-only properties should not be used — covered by SST1421 dotnet_diagnostic.S2629.severity = error # Logging templates should be constant dotnet_diagnostic.S2681.severity = none # Multiline blocks should be enclosed in curly braces — covered by SST1503 dotnet_diagnostic.S2743.severity = none # Static fields should not be used in generic types - DUPLICATE CA1000 — covered by SST1431 -dotnet_diagnostic.S2925.severity = error # "Thread.Sleep" should not be used in tests +dotnet_diagnostic.S2925.severity = none # "Thread.Sleep" should not be used in tests — covered by SST2506 dotnet_diagnostic.S2933.severity = none # Fields that are only assigned in the constructor should be "readonly" - DUPLICATE IDE0044 dotnet_diagnostic.S2971.severity = none # LINQ expressions should be simplified — covered by PSH1101/PSH1102 dotnet_diagnostic.S3010.severity = none # Static fields should not be updated in constructors — covered by SST2402 @@ -2197,24 +2314,24 @@ dotnet_diagnostic.S3262.severity = none # "params" should be used on overrides dotnet_diagnostic.S3264.severity = none # Events should be invoked — covered by SST2407 dotnet_diagnostic.S3358.severity = none # Ternary operators should not be nested — covered by SST1147 dotnet_diagnostic.S3366.severity = none # "this" should not be exposed from constructors — covered by SST2403 -dotnet_diagnostic.S3415.severity = error # Assertion arguments should be passed in the correct order -dotnet_diagnostic.S3431.severity = error # "[ExpectedException]" should not be used +dotnet_diagnostic.S3415.severity = none # Assertion arguments should be passed in the correct order — covered by SST2502 +dotnet_diagnostic.S3431.severity = none # "[ExpectedException]" should not be used — covered by SST2507 dotnet_diagnostic.S3442.severity = none # "abstract" classes should not have "public" constructors — covered by SST1428 dotnet_diagnostic.S3445.severity = none # Exceptions should not be explicitly rethrown — covered by SST1430 dotnet_diagnostic.S3457.severity = none # Composite format strings should be used correctly — covered by SST1454 dotnet_diagnostic.S3597.severity = error # "ServiceContract" and "OperationContract" attributes should be used together dotnet_diagnostic.S3880.severity = none # Finalizers should not be empty — covered by PSH1002 -dotnet_diagnostic.S3881.severity = error # "IDisposable" should be implemented correctly +dotnet_diagnostic.S3881.severity = none # "IDisposable" should be implemented correctly — covered by SST2300 dotnet_diagnostic.S3885.severity = error # "Assembly.Load" should be used dotnet_diagnostic.S3898.severity = none # Covered by PSH1005 (canonical) dotnet_diagnostic.S3902.severity = none # Covered by PSH1404 (canonical) -dotnet_diagnostic.S3906.severity = error # Event Handlers should have the correct signature +dotnet_diagnostic.S3906.severity = none # Event Handlers should have the correct signature — covered by SST2304 dotnet_diagnostic.S3908.severity = error # Generic event handlers should be used dotnet_diagnostic.S3909.severity = error # Collections should implement the generic interface dotnet_diagnostic.S3925.severity = none # "ISerializable" should be implemented correctly - BinaryFormatter / ISerializable serialization is obsoleted (SYSLIB0050/0051) in modern .NET; we do not opt into legacy serialization for any exception type dotnet_diagnostic.S3928.severity = none # Parameter names used into ArgumentException constructors should match an existing one - DUPLICATE CA2208 dotnet_diagnostic.S3956.severity = none # "Generic.List" instances should not be part of public APIs -dotnet_diagnostic.S3971.severity = error # "GC.SuppressFinalize" should not be called +dotnet_diagnostic.S3971.severity = none # "GC.SuppressFinalize" should not be called — conflicts with PSH1008, which owns the pointless-SuppressFinalize direction and exempts unsealed types dotnet_diagnostic.S3990.severity = error # Assemblies should be marked as CLS compliant dotnet_diagnostic.S3992.severity = error # Assemblies should explicitly specify COM visibility dotnet_diagnostic.S3993.severity = error # Custom attributes should be marked with "System.AttributeUsageAttribute" @@ -2223,12 +2340,12 @@ dotnet_diagnostic.S3995.severity = none # URI return values should not be string dotnet_diagnostic.S3996.severity = none # URI properties should not be strings dotnet_diagnostic.S3997.severity = error # String URI overloads should call "System.Uri" overloads dotnet_diagnostic.S4002.severity = none # Disposable types should declare finalizers — covered by SST2317 -dotnet_diagnostic.S4004.severity = error # Collection properties should be readonly +dotnet_diagnostic.S4004.severity = none # Collection properties should be readonly — covered by SST2305 dotnet_diagnostic.S4005.severity = none # "System.Uri" arguments should be used instead of strings dotnet_diagnostic.S4016.severity = error # Enumeration members should not be named "Reserved" dotnet_diagnostic.S4017.severity = none # Method signatures should not contain nested generic types dotnet_diagnostic.S4035.severity = none # Classes implementing "IEquatable" should be sealed — covered by SST2301 -dotnet_diagnostic.S4050.severity = error # Operators should be overloaded consistently +dotnet_diagnostic.S4050.severity = none # Operators should be overloaded consistently — covered by SST2302 dotnet_diagnostic.S4055.severity = none # Literals should not be passed as localized parameters dotnet_diagnostic.S4057.severity = error # Locales should be set for data types dotnet_diagnostic.S4059.severity = none # Property names should not match get methods - DUPLICATE CA1721 @@ -2237,18 +2354,18 @@ dotnet_diagnostic.S4144.severity = none # Methods should not have identical impl dotnet_diagnostic.S4200.severity = error # Native methods should be wrapped dotnet_diagnostic.S4214.severity = none # "P/Invoke" methods should not be visible - DUPLICATE CA1401 dotnet_diagnostic.S4220.severity = none # Events should have proper arguments — covered by SST2436 -dotnet_diagnostic.S4456.severity = error # Parameter validation in yielding methods should be wrapped -dotnet_diagnostic.S4457.severity = none # Parameter validation in "async"/"await" methods should be wrapped -dotnet_diagnostic.S4545.severity = error # "DebuggerDisplayAttribute" strings should reference existing members +dotnet_diagnostic.S4456.severity = none # Parameter validation in yielding methods should be wrapped — covered by SST2404 +dotnet_diagnostic.S4457.severity = none # Parameter validation in "async"/"await" methods should be wrapped — covered by SST2325 +dotnet_diagnostic.S4545.severity = none # "DebuggerDisplayAttribute" strings should reference existing members — covered by SST2405 dotnet_diagnostic.S4581.severity = none # "new Guid()" should not be used — covered by SST2012 dotnet_diagnostic.S6354.severity = none # Use a testable date/time provider — covered by SST2010 dotnet_diagnostic.S6419.severity = error # Azure Functions should be stateless -dotnet_diagnostic.S6420.severity = error # Client instances should not be recreated on each Azure Function invocation +dotnet_diagnostic.S6420.severity = none # Client instances should not be recreated on each Azure Function invocation — covered by PSH1418 dotnet_diagnostic.S6421.severity = error # Azure Functions should use Structured Error Handling dotnet_diagnostic.S6423.severity = error # Azure Functions should log all failures dotnet_diagnostic.S6561.severity = none # Avoid using "DateTime.Now" for benchmarking or timing operations — covered by PSH1408 dotnet_diagnostic.S6562.severity = none # Covered by SST1451 (canonical) -dotnet_diagnostic.S6563.severity = error # Use UTC when recording DateTime instants +dotnet_diagnostic.S6563.severity = none # Use UTC when recording DateTime instants — covered by SST2011 dotnet_diagnostic.S6566.severity = none # Use "DateTimeOffset" instead of "DateTime" — covered by SST2016 dotnet_diagnostic.S6575.severity = error # Use "TimeZoneInfo.FindSystemTimeZoneById" without converting the timezones with "TimezoneConverter" dotnet_diagnostic.S6580.severity = none # Use a format provider when parsing date and time - DUPLICATE CA1305 @@ -2266,7 +2383,7 @@ dotnet_diagnostic.S6965.severity = error # REST API actions should be annotated dotnet_diagnostic.S6966.severity = none # Awaitable method should be used — covered by PSH1313 dotnet_diagnostic.S6968.severity = error # Actions that return a value should be annotated with ProducesResponseTypeAttribute containing the return type dotnet_diagnostic.S881.severity = none # Increment (++) and decrement (--) operators should not be used in a method call or mixed with other operators in an expression — covered by SST2015 -dotnet_diagnostic.S907.severity = error # "goto" statement should not be used +dotnet_diagnostic.S907.severity = none # "goto" statement should not be used — covered by SST2014 ################### # SonarAnalyzer (Sxxxx) - Minor Code Smell @@ -2286,19 +2403,19 @@ dotnet_diagnostic.S1192.severity = none # String literals should not be duplicat dotnet_diagnostic.S1199.severity = none # Nested code blocks should not be used — covered by SST1138 dotnet_diagnostic.S1210.severity = none # "Equals" and the comparison operators should be overridden when implementing "IComparable" - DUPLICATE CA1036 dotnet_diagnostic.S1227.severity = none # break statements should not be used except for switch cases -dotnet_diagnostic.S1264.severity = error # A "while" loop should be used instead of a "for" loop +dotnet_diagnostic.S1264.severity = none # A "while" loop should be used instead of a "for" loop — covered by SST2245 dotnet_diagnostic.S1301.severity = none # "switch" statements should have at least 3 "case" clauses dotnet_diagnostic.S1312.severity = none # Logger fields should be "private static readonly" dotnet_diagnostic.S1449.severity = none # Culture should be specified for "string" operations — covered by PSH1207 dotnet_diagnostic.S1450.severity = none # Private fields only used as local variables in methods should become local variables — covered by SST1422 -dotnet_diagnostic.S1481.severity = error # Unused local variables should be removed +dotnet_diagnostic.S1481.severity = none # Unused local variables should be removed — covered by SST1497 dotnet_diagnostic.S1643.severity = none # Covered by PSH1206 (canonical) dotnet_diagnostic.S1659.severity = none # Multiple variables should not be declared on the same line — covered by SST1132 -dotnet_diagnostic.S1694.severity = error # An abstract class should have both abstract and concrete methods -dotnet_diagnostic.S1698.severity = error # "==" should not be used when "Equals" is overridden +dotnet_diagnostic.S1694.severity = none # An abstract class should have both abstract and concrete methods — covered by SST2323 +dotnet_diagnostic.S1698.severity = none # "==" should not be used when "Equals" is overridden — covered by SST1495 dotnet_diagnostic.S1858.severity = none # "ToString()" calls should not be redundant — covered by PSH1211 dotnet_diagnostic.S1905.severity = none # Redundant casts should not be used - DUPLICATE IDE0004 — covered by SST1175 -dotnet_diagnostic.S1939.severity = error # Inheritance list should not be redundant +dotnet_diagnostic.S1939.severity = none # Inheritance list should not be redundant — covered by SST1490 and SST1177 dotnet_diagnostic.S1940.severity = none # Boolean checks should not be inverted — covered by SST1172 dotnet_diagnostic.S2094.severity = none # Classes should not be empty — covered by SST1436 dotnet_diagnostic.S2148.severity = none # Underscores should be used to make large numbers readable — covered by SST1191 @@ -2310,12 +2427,12 @@ dotnet_diagnostic.S2325.severity = none # Methods and properties that don't acce dotnet_diagnostic.S2333.severity = none # Redundant modifiers should not be used — covered by SST1419/SST1491 dotnet_diagnostic.S2342.severity = none # Enumeration types should comply with a naming convention — covered by SST1319 dotnet_diagnostic.S2344.severity = none # Enumeration type names should not have "Flags" or "Enum" suffixes - DUPLICATE CA1711 -dotnet_diagnostic.S2386.severity = error # Mutable fields should not be "public static" +dotnet_diagnostic.S2386.severity = none # Mutable fields should not be "public static" — covered by SST1499 dotnet_diagnostic.S2486.severity = none # Generic exceptions should not be ignored — covered by SST1429 dotnet_diagnostic.S2737.severity = none # "catch" clauses should do more than rethrow — covered by SST1470 -dotnet_diagnostic.S2760.severity = error # Sequential tests should not check the same condition +dotnet_diagnostic.S2760.severity = none # Sequential tests should not check the same condition — covered by SST1475 dotnet_diagnostic.S3052.severity = none # Covered by PSH1403 (canonical) -dotnet_diagnostic.S3220.severity = error # Method calls should not resolve ambiguously to overloads with "params" +dotnet_diagnostic.S3220.severity = none # Method calls should not resolve ambiguously to overloads with "params" — covered by SST2467 dotnet_diagnostic.S3234.severity = none # Covered by PSH1008 (canonical) dotnet_diagnostic.S3235.severity = none # Redundant parentheses should not be used — covered by SST1459 dotnet_diagnostic.S3236.severity = none # Covered by SST1448 (canonical) @@ -2323,7 +2440,7 @@ dotnet_diagnostic.S3240.severity = none # The simplest possible condition syntax dotnet_diagnostic.S3241.severity = error # Methods should not return values that are never used dotnet_diagnostic.S3242.severity = none # Method parameters should be declared with base types dotnet_diagnostic.S3247.severity = none # Duplicate casts should not be made — covered by SST1175 -dotnet_diagnostic.S3251.severity = error # Implementations should be provided for "partial" methods +dotnet_diagnostic.S3251.severity = none # Implementations should be provided for "partial" methods — covered by SST2468 dotnet_diagnostic.S3253.severity = none # Constructor and destructor declarations should not be redundant — covered by SST1433 dotnet_diagnostic.S3254.severity = none # Default parameter values should not be passed as arguments — covered by SST1494 dotnet_diagnostic.S3256.severity = none # "string.IsNullOrEmpty" should be used — covered by PSH1204 (style configurable) @@ -2347,7 +2464,7 @@ dotnet_diagnostic.S3626.severity = none # Jump statements should not be redundan dotnet_diagnostic.S3717.severity = error # Track use of "NotImplementedException" dotnet_diagnostic.S3872.severity = error # Parameter names should not duplicate the names of their methods dotnet_diagnostic.S3876.severity = error # Strings or integral types should be used for indexers -dotnet_diagnostic.S3878.severity = error # Arrays should not be created for params parameters +dotnet_diagnostic.S3878.severity = none # Arrays should not be created for params parameters — covered by PSH1018 dotnet_diagnostic.S3897.severity = error # Classes that provide "Equals()" should implement "IEquatable" dotnet_diagnostic.S3962.severity = none # Covered by PSH1402 (canonical) dotnet_diagnostic.S3963.severity = none # "static" fields should be initialized inline - DUPLICATE CA1810 @@ -2392,7 +2509,7 @@ dotnet_diagnostic.S6664.severity = error # The code block contains too many logg dotnet_diagnostic.S6667.severity = none # Logging in a catch clause should pass the caught exception as a parameter. — covered by SST2438 dotnet_diagnostic.S6668.severity = none # Logging arguments should be passed to the correct parameter — covered by SST2439 dotnet_diagnostic.S6669.severity = error # Logger field or property name should comply with a naming convention -dotnet_diagnostic.S6670.severity = error # "Trace.Write" and "Trace.WriteLine" should not be used +dotnet_diagnostic.S6670.severity = none # "Trace.Write" and "Trace.WriteLine" should not be used — covered by SST2600 dotnet_diagnostic.S6672.severity = none # Generic logger injection should match enclosing type — covered by SST2443 dotnet_diagnostic.S6675.severity = error # "Trace.WriteLineIf" should not be used with "TraceSwitch" levels dotnet_diagnostic.S6678.severity = error # Use PascalCase for named placeholders diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 7ea67957..c9e2cb04 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -142,6 +142,7 @@ + diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index aa537adb..cc5909a7 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -3,8 +3,9 @@ true true - - 3.28.1 + + 3.29.1 @@ -13,6 +14,7 @@ + diff --git a/src/Primitives.Extensions.Shared/Internal/InvalidOperationExceptionHelper.cs b/src/Primitives.Extensions.Shared/Internal/InvalidOperationExceptionHelper.cs index a9040f8a..358e4751 100644 --- a/src/Primitives.Extensions.Shared/Internal/InvalidOperationExceptionHelper.cs +++ b/src/Primitives.Extensions.Shared/Internal/InvalidOperationExceptionHelper.cs @@ -25,7 +25,7 @@ internal static class InvalidOperationExceptionHelper /// The reference type field to validate as non-null. /// The validated member's name, captured from the expression via . /// The void-throwing caller's name, captured via . - public static void ThrowIfNull( + internal static void ThrowIfNull( [NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? memberName = null, @@ -49,7 +49,7 @@ public static void ThrowIfNull( /// The validated reference-type argument's name, captured from the expression via . /// The reference-type-checking caller's name, captured via . /// The non-null argument. - public static T Check( + internal static T Check( [NotNull] T? argument, [CallerArgumentExpression(nameof(argument))] string? memberName = null, @@ -73,7 +73,7 @@ public static T Check( /// The validated string argument's name, captured from the expression via . /// The string-checking caller's name, captured via . /// The non-null, non-empty argument. - public static string Check( + internal static string Check( [NotNull] string? argument, [CallerArgumentExpression(nameof(argument))] string? memberName = null, diff --git a/src/ReactiveUI.Disposables/Disposables/DisposableSlotHelper.cs b/src/ReactiveUI.Disposables/Disposables/DisposableSlotHelper.cs index 194b9125..7c176fe4 100644 --- a/src/ReactiveUI.Disposables/Disposables/DisposableSlotHelper.cs +++ b/src/ReactiveUI.Disposables/Disposables/DisposableSlotHelper.cs @@ -20,7 +20,7 @@ namespace ReactiveUI.Primitives.Disposables; internal static class DisposableSlotHelper { /// Sentinel value indicating the holder has been disposed. - public const int DisposedSentinel = 1; + internal const int DisposedSentinel = 1; /// /// Reassigns an inner disposable slot WITHOUT disposing the previous value (mutable-assign @@ -32,7 +32,7 @@ internal static class DisposableSlotHelper /// The reference to the current-inner field. /// The reference to the disposed-flag field. /// The incoming value (or ). - public static void AssignWithoutDisposingPrevious( + internal static void AssignWithoutDisposingPrevious( ref IDisposable? slot, ref int disposed, IDisposable? value) @@ -56,7 +56,7 @@ public static void AssignWithoutDisposingPrevious( /// The reference to the current-inner field. /// The reference to the disposed-flag field. /// The incoming value (or ). - public static void SwapAndDisposePrevious( + internal static void SwapAndDisposePrevious( ref IDisposable? slot, ref int disposed, IDisposable? value) @@ -84,7 +84,7 @@ public static void SwapAndDisposePrevious( /// if the current invocation latched the flag; otherwise /// . /// - public static bool TryDispose(ref IDisposable? slot, ref int disposed) + internal static bool TryDispose(ref IDisposable? slot, ref int disposed) { if (Interlocked.Exchange(ref disposed, DisposedSentinel) == DisposedSentinel) { diff --git a/src/ReactiveUI.Primitives.Async.Core/ConcurrencyRaceHelpers.cs b/src/ReactiveUI.Primitives.Async.Core/ConcurrencyRaceHelpers.cs index eab3b23c..cdb2c66f 100644 --- a/src/ReactiveUI.Primitives.Async.Core/ConcurrencyRaceHelpers.cs +++ b/src/ReactiveUI.Primitives.Async.Core/ConcurrencyRaceHelpers.cs @@ -26,7 +26,7 @@ internal static class ConcurrencyRaceHelpers /// if the claim succeeded; if another caller /// already claimed the state. /// - public static bool TryClaim(ref int state, int openSentinel, int claimedSentinel) => + internal static bool TryClaim(ref int state, int openSentinel, int claimedSentinel) => Interlocked.CompareExchange(ref state, claimedSentinel, openSentinel) == openSentinel; /// @@ -41,7 +41,7 @@ public static bool TryClaim(ref int state, int openSentinel, int claimedSentinel /// if the cancellation completed; if the /// source was already disposed. /// - public static async ValueTask TryCancelAsync(CancellationTokenSource cts) + internal static async ValueTask TryCancelAsync(CancellationTokenSource cts) { try { diff --git a/src/ReactiveUI.Primitives.Async.Core/ConnectableSignalAsyncHelper.cs b/src/ReactiveUI.Primitives.Async.Core/ConnectableSignalAsyncHelper.cs index 21cbf075..0ec0f8b0 100644 --- a/src/ReactiveUI.Primitives.Async.Core/ConnectableSignalAsyncHelper.cs +++ b/src/ReactiveUI.Primitives.Async.Core/ConnectableSignalAsyncHelper.cs @@ -18,7 +18,7 @@ internal static class ConnectableSignalAsyncHelper /// The active connection handle. /// The connectable signal has been disposed, or /// was cancelled. - public static async ValueTask ConnectAsync( + internal static async ValueTask ConnectAsync( ConnectableSignalAsyncState state, CancellationToken cancellationToken) { @@ -87,7 +87,7 @@ public static async ValueTask ConnectAsync( "PSH1315:A blocking wait on an awaitable that may not be done", Justification = "IDisposable.Dispose is intrinsically synchronous; this method must tear down async connection state on the sync dispose path.")] - public static void Dispose(ConnectableSignalAsyncState state) + internal static void Dispose(ConnectableSignalAsyncState state) { if (!state.TryMarkDisposed()) { @@ -105,7 +105,7 @@ public static void Dispose(ConnectableSignalAsyncState state) /// The observer that receives multicasted values. /// A token that can cancel subscription establishment. /// The subscription to the signal values. - public static ValueTask SubscribeAsync( + internal static ValueTask SubscribeAsync( ConnectableSignalAsyncState state, IObserverAsync observer, CancellationToken cancellationToken) diff --git a/src/ReactiveUI.Primitives.Async.Core/ConnectableSignalAsyncState{T}.cs b/src/ReactiveUI.Primitives.Async.Core/ConnectableSignalAsyncState{T}.cs index 7a76e38d..28509bf9 100644 --- a/src/ReactiveUI.Primitives.Async.Core/ConnectableSignalAsyncState{T}.cs +++ b/src/ReactiveUI.Primitives.Async.Core/ConnectableSignalAsyncState{T}.cs @@ -18,7 +18,7 @@ internal sealed class ConnectableSignalAsyncState : IDisposable "Style", "SST1401:Field should be private", Justification = "Gate fields are intentionally direct readonly state for helper access.")] - public readonly AsyncSerialGate Gate = new(); + internal readonly AsyncSerialGate Gate = new(); /// The monitor used to make synchronous disposal idempotent. private readonly Lock _disposalGate = new(); @@ -34,30 +34,37 @@ public ConnectableSignalAsyncState(IObservableAsync source, ISignalAsync s } /// Gets the cold source sequence that is subscribed when the signal connects. - public IObservableAsync Source { get; } + internal IObservableAsync Source { get; } /// Gets the signal that multicasts source notifications to subscribed observers. - public ISignalAsync Signal { get; } + internal ISignalAsync Signal { get; } /// Gets the cancellation source that is canceled when the connectable signal is disposed. - public CancellationTokenSource DisposedCts { get; } = new(); + internal CancellationTokenSource DisposedCts { get; } = new(); - /// Gets the active source subscription, if connected. - public SingleAssignmentDisposableAsync? Connection { get; internal set; } + /// Gets or sets the active source subscription, if connected. + internal SingleAssignmentDisposableAsync? Connection { get; set; } - /// Gets a value indicating whether synchronous disposal has run. - public bool IsDisposed { get; internal set; } + /// Gets or sets a value indicating whether synchronous disposal has run. + internal bool IsDisposed { get; set; } /// Gets the token canceled when the connectable signal is disposed. Captured while the source is /// still alive because disposes that source, and reading /// from a disposed source throws /// . Disposal always cancels before it disposes, so this token is /// already cancelled by the time anyone can observe it post-disposal. - public CancellationToken DisposedCancellationToken { get; } + internal CancellationToken DisposedCancellationToken { get; } + + /// + public void Dispose() + { + Gate.Dispose(); + DisposedCts.Dispose(); + } /// Marks the state as disposed if disposal has not already started. /// when this call owns disposal; otherwise, . - public bool TryMarkDisposed() + internal bool TryMarkDisposed() { lock (_disposalGate) { @@ -70,11 +77,4 @@ public bool TryMarkDisposed() return true; } } - - /// - public void Dispose() - { - Gate.Dispose(); - DisposedCts.Dispose(); - } } diff --git a/src/ReactiveUI.Primitives.Async.Core/Observables/Empty.cs b/src/ReactiveUI.Primitives.Async.Core/Observables/Empty.cs index ccc657f7..44d4fa6d 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Observables/Empty.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Observables/Empty.cs @@ -44,7 +44,7 @@ public static partial class SignalAsync internal sealed class EmptySignalAsync : IObservableAsync { /// The shared singleton instance for . - public static readonly EmptySignalAsync Instance = new(); + internal static readonly EmptySignalAsync Instance = new(); /// Initializes a new instance of the class. private EmptySignalAsync() diff --git a/src/ReactiveUI.Primitives.Async.Core/Observables/Never.cs b/src/ReactiveUI.Primitives.Async.Core/Observables/Never.cs index fa81c1a8..0b7cbc8e 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Observables/Never.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Observables/Never.cs @@ -30,7 +30,7 @@ public static partial class SignalAsync internal sealed class NeverSignalAsync : IObservableAsync { /// Gets the singleton instance of . - public static NeverSignalAsync Instance { get; } = new(); + internal static NeverSignalAsync Instance { get; } = new(); /// ValueTask IObservableAsync.SubscribeAsync( diff --git a/src/ReactiveUI.Primitives.Async.Core/Operators/Blend.cs b/src/ReactiveUI.Primitives.Async.Core/Operators/Blend.cs index 0d8f6c9e..6a7c12df 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Operators/Blend.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Operators/Blend.cs @@ -175,11 +175,15 @@ internal class BlendCoordinator : IAsyncDisposable /// Gets a cancellation token that is canceled when this subscription is disposed. protected CancellationToken DisposedCancellationToken => _disposeCts.Token; + /// Asynchronously releases resources used by this subscription. + /// A task representing the asynchronous dispose operation. + public ValueTask DisposeAsync() => FinishAsync(null); + /// Subscribes to the outer observable and begins merging inner observable sequences. /// The outer observable whose inner sequences will be merged. /// A token to cancel the subscription. /// A task representing the asynchronous subscribe operation. - public async ValueTask SubscribeSourcesAsync( + internal async ValueTask SubscribeSourcesAsync( IObservableAsync> @this, CancellationToken cancellationToken) { @@ -203,10 +207,6 @@ public async ValueTask SubscribeSourcesAsync( await _outerDisposable.SetDisposableAsync(outerSubscription).ConfigureAwait(false); } - /// Asynchronously releases resources used by this subscription. - /// A task representing the asynchronous dispose operation. - public ValueTask DisposeAsync() => FinishAsync(null); - /// /// Links the original subscribe-time cancellation token into this subscription's dispose chain so /// later per-emission methods can rely on instead of @@ -257,7 +257,7 @@ internal ValueTask RelayErrorIfActiveAsync(Exception exception) => DisposalHelpe /// The value to forward. /// A token to cancel the operation. /// A task representing the asynchronous forward operation. - protected internal async ValueTask RelayNextAsync(T value, CancellationToken cancellationToken) + internal async ValueTask RelayNextAsync(T value, CancellationToken cancellationToken) { _ = cancellationToken; if (DisposalHelper.HasDisposed(_disposed)) @@ -275,7 +275,7 @@ protected internal async ValueTask RelayNextAsync(T value, CancellationToken can /// The error to forward. /// A token to cancel the operation. /// A task representing the asynchronous forward operation. - protected internal async ValueTask RelayErrorAsync( + internal async ValueTask RelayErrorAsync( Exception exception, CancellationToken cancellationToken) { @@ -294,7 +294,7 @@ protected internal async ValueTask RelayErrorAsync( /// Subscribes to an inner observable sequence and begins forwarding its items. /// The inner observable to subscribe to. /// A task representing the asynchronous subscribe operation. - protected internal virtual async ValueTask SubscribeBranchAsync(IObservableAsync inner) + internal virtual async ValueTask SubscribeBranchAsync(IObservableAsync inner) { try { @@ -309,12 +309,12 @@ protected internal virtual async ValueTask SubscribeBranchAsync(IObservableAsync /// Creates a new inner observer for subscribing to an inner observable sequence. /// A new inner async observer instance. - protected internal virtual BlendBranchWitness CreateBranchObserver() => new(this); + internal virtual BlendBranchWitness CreateBranchObserver() => new(this); /// Completes the merged sequence, disposes all subscriptions, and optionally signals the downstream observer. /// The completion result to forward, or null if disposing without signaling completion. /// A task representing the asynchronous completion operation. - protected internal async ValueTask FinishAsync(Result? result) + internal async ValueTask FinishAsync(Result? result) { if (DisposalHelper.TrySetDisposed(ref _disposed)) { @@ -346,7 +346,7 @@ internal class BlendBranchWitness(BlendCoordinator parent) : WitnessAsync /// The inner observable to subscribe to. /// A token to cancel the subscription. /// A task representing the asynchronous subscribe operation. - public async ValueTask SubscribeSourcesAsync(IObservableAsync inner, CancellationToken cancellationToken) + internal async ValueTask SubscribeSourcesAsync(IObservableAsync inner, CancellationToken cancellationToken) { lock (parent._disposeCts) { @@ -403,7 +403,7 @@ internal sealed class BoundedBlendCoordinator(IObserverAsync observer, int private readonly SemaphoreSlim _semaphore = new(maxConcurrent, maxConcurrent); /// - protected internal override async ValueTask SubscribeBranchAsync(IObservableAsync inner) + internal override async ValueTask SubscribeBranchAsync(IObservableAsync inner) { await _semaphore.WaitAsync(DisposedCancellationToken).ConfigureAwait(false); var innerObserver = (BlendBranchWitnessWithPermit)CreateBranchObserver(); @@ -431,7 +431,7 @@ protected internal override async ValueTask SubscribeBranchAsync(IObservableAsyn } /// - protected internal override BlendBranchWitness CreateBranchObserver() => + internal override BlendBranchWitness CreateBranchObserver() => new BlendBranchWitnessWithPermit(this); /// Inner witness that releases a semaphore slot on disposal. @@ -527,12 +527,32 @@ public BlendSequenceCoordinator(IObserverAsync observer, IEnumerableAsynchronously releases resources used by this subscription. + /// A task representing the asynchronous dispose operation. + public ValueTask DisposeAsync() => FinishAsync(null); + + /// + /// Routes an exception from a post-disposal completion result to the unhandled exception handler. + /// Called when returns true (already disposed) + /// and the completion result carries an exception. + /// + /// The completion result, or null if disposing without signaling. + internal static void RoutePostDisposalException(Result? result) + { + if (result?.Exception is not { } ex) + { + return; + } + + UnhandledExceptionHandler.ReportUnhandledException(ex); + } + /// Begins subscribing to all source observables asynchronously. [SuppressMessage( "Roslynator", "RCS1047:Non-asynchronous method name should not end with \'Async\'", Justification = "Method already named with Async")] - public void BeginSubscribing() => FireAndForgetHelper.Run(async () => + internal void BeginSubscribing() => FireAndForgetHelper.Run(async () => { _reentrant.Value = true; try @@ -579,26 +599,6 @@ public void BeginSubscribing() => FireAndForgetHelper.Run(async () => } }); - /// Asynchronously releases resources used by this subscription. - /// A task representing the asynchronous dispose operation. - public ValueTask DisposeAsync() => FinishAsync(null); - - /// - /// Routes an exception from a post-disposal completion result to the unhandled exception handler. - /// Called when returns true (already disposed) - /// and the completion result carries an exception. - /// - /// The completion result, or null if disposing without signaling. - internal static void RoutePostDisposalException(Result? result) - { - if (result?.Exception is not { } ex) - { - return; - } - - UnhandledExceptionHandler.ReportUnhandledException(ex); - } - /// /// Links the original subscribe-time cancellation token into this subscription's dispose chain so /// later per-emission methods can rely on instead of diff --git a/src/ReactiveUI.Primitives.Async.Core/Operators/ChainEnumerableSignal{T}.cs b/src/ReactiveUI.Primitives.Async.Core/Operators/ChainEnumerableSignal{T}.cs index 4b8d3f8d..54a04c7f 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Operators/ChainEnumerableSignal{T}.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Operators/ChainEnumerableSignal{T}.cs @@ -70,9 +70,24 @@ public ChainSequenceCoordinator(ChainEnumerableSignal parent, IObserverAsync< _disposedCancellationToken = _cts.Token; } + /// + public ValueTask DisposeAsync() => FinishAsync(null); + + /// Handles a second call to when already disposed, routing any failure exception to the unhandled exception handler. + /// The completion result from the second call. + internal static void HandleAlreadyDisposed(Result? result) + { + if (result?.Exception is not { } exception) + { + return; + } + + UnhandledExceptionHandler.ReportUnhandledException(exception); + } + /// Advances to and subscribes to the next observable in the enumerable, or completes if no more observables are available. /// A task representing the asynchronous operation. - public async ValueTask SubscribeNextSignalAsync() + internal async ValueTask SubscribeNextSignalAsync() { try { @@ -98,21 +113,6 @@ public async ValueTask SubscribeNextSignalAsync() } } - /// - public ValueTask DisposeAsync() => FinishAsync(null); - - /// Handles a second call to when already disposed, routing any failure exception to the unhandled exception handler. - /// The completion result from the second call. - internal static void HandleAlreadyDisposed(Result? result) - { - if (result?.Exception is not { } exception) - { - return; - } - - UnhandledExceptionHandler.ReportUnhandledException(exception); - } - /// Forwards a non-fatal error from the current inner sequence to the downstream observer. /// The error to forward. /// A token to cancel the operation. diff --git a/src/ReactiveUI.Primitives.Async.Core/Operators/ChainSignalSourcesSignal{T}.cs b/src/ReactiveUI.Primitives.Async.Core/Operators/ChainSignalSourcesSignal{T}.cs index 7b979524..0fe6f189 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Operators/ChainSignalSourcesSignal{T}.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Operators/ChainSignalSourcesSignal{T}.cs @@ -70,11 +70,26 @@ public ChainCoordinator(IObserverAsync observer) _disposedCancellationToken = _disposeCts.Token; } + /// + public ValueTask DisposeAsync() => FinishAsync(null); + + /// Handles a second call to when already disposed, routing any failure exception to the unhandled exception handler. + /// The completion result from the second call. + internal static void HandleAlreadyDisposed(Result? result) + { + if (result?.Exception is not { } exception) + { + return; + } + + UnhandledExceptionHandler.ReportUnhandledException(exception); + } + /// Subscribes to the outer observable sequence. /// The outer observable that emits inner observable sequences. /// A token to cancel the subscription. /// A task representing the asynchronous subscribe operation. - public async ValueTask SubscribeAsync( + internal async ValueTask SubscribeAsync( IObservableAsync> source, CancellationToken subscriptionToken) { @@ -89,7 +104,7 @@ public async ValueTask SubscribeAsync( /// /// The inner observable to enqueue. /// A task representing the asynchronous operation. - public ValueTask AcceptOuterValueAsync(IObservableAsync inner) + internal ValueTask AcceptOuterValueAsync(IObservableAsync inner) { var shouldSubscribe = false; lock (_buffer) @@ -110,7 +125,7 @@ public ValueTask AcceptOuterValueAsync(IObservableAsync inner) /// /// The completion result from the outer sequence. /// A task representing the asynchronous completion operation. - public ValueTask AcceptOuterCompletionAsync(Result result) + internal ValueTask AcceptOuterCompletionAsync(Result result) { var shouldComplete = false; Result? completeResult = null; @@ -133,7 +148,7 @@ public ValueTask AcceptOuterCompletionAsync(Result result) /// /// The completion result from the inner sequence. /// A task representing the asynchronous completion operation. - public ValueTask AcceptInnerCompletionAsync(Result result) + internal ValueTask AcceptInnerCompletionAsync(Result result) { if (result.IsFailure) { @@ -157,21 +172,6 @@ public ValueTask AcceptInnerCompletionAsync(Result result) return SubscribeCurrentInnerAsync(nextInner); } - /// - public ValueTask DisposeAsync() => FinishAsync(null); - - /// Handles a second call to when already disposed, routing any failure exception to the unhandled exception handler. - /// The completion result from the second call. - internal static void HandleAlreadyDisposed(Result? result) - { - if (result?.Exception is not { } exception) - { - return; - } - - UnhandledExceptionHandler.ReportUnhandledException(exception); - } - /// Subscribes to the specified inner observable, setting it as the current active inner subscription. /// The inner observable to subscribe to. /// A task representing the asynchronous operation. diff --git a/src/ReactiveUI.Primitives.Async.Core/Operators/GroupBy.cs b/src/ReactiveUI.Primitives.Async.Core/Operators/GroupBy.cs index 1949c59d..b8b5c913 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Operators/GroupBy.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Operators/GroupBy.cs @@ -120,7 +120,7 @@ internal sealed class GroupingCoordinator( /// Subscribes this observer to the parent's source sequence. /// A token to cancel the subscription. /// An async disposable representing the source subscription. - public ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) => + internal ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) => parent._source.SubscribeAsync(this, cancellationToken); /// Routes the element to the appropriate group signal, creating a new group if the key is new. diff --git a/src/ReactiveUI.Primitives.Async.Core/Operators/GroupedAsyncSignalHelper.cs b/src/ReactiveUI.Primitives.Async.Core/Operators/GroupedAsyncSignalHelper.cs index 8d45f0e8..844230d0 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Operators/GroupedAsyncSignalHelper.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Operators/GroupedAsyncSignalHelper.cs @@ -17,7 +17,7 @@ internal static class GroupedAsyncSignalHelper /// The observer that receives group values. /// A token that can cancel subscription establishment. /// The subscription to the grouped value stream. - public static async ValueTask SubscribeAsync( + internal static async ValueTask SubscribeAsync( GroupedAsyncSignalState state, IObserverAsync observer, CancellationToken cancellationToken) diff --git a/src/ReactiveUI.Primitives.Async.Core/Operators/GroupedAsyncSignalState{TKey,TValue}.cs b/src/ReactiveUI.Primitives.Async.Core/Operators/GroupedAsyncSignalState{TKey,TValue}.cs index a5f86b86..41abf224 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Operators/GroupedAsyncSignalState{TKey,TValue}.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Operators/GroupedAsyncSignalState{TKey,TValue}.cs @@ -20,14 +20,14 @@ internal sealed class GroupedAsyncSignalState( CancellationToken parentDisposedToken) { /// Gets the key associated with the grouped observable. - public TKey Key { get; } = key; + internal TKey Key { get; } = key; /// Gets the value stream backing the grouped observable. - public IObservableAsync SignalValues { get; } = signalValues; + internal IObservableAsync SignalValues { get; } = signalValues; /// Gets the parent-owned collection that tracks group subscriptions. - public MultipleDisposableAsync Disposables { get; } = disposables; + internal MultipleDisposableAsync Disposables { get; } = disposables; /// Gets the token canceled when the parent grouping coordinator is disposed. - public CancellationToken ParentDisposedToken { get; } = parentDisposedToken; + internal CancellationToken ParentDisposedToken { get; } = parentDisposedToken; } diff --git a/src/ReactiveUI.Primitives.Async.Core/Operators/Pair.cs b/src/ReactiveUI.Primitives.Async.Core/Operators/Pair.cs index d87da083..3a951bbb 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Operators/Pair.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Operators/Pair.cs @@ -132,7 +132,7 @@ public async ValueTask DisposeAsync() /// The element from the first source. /// A token to cancel the operation. /// A task representing the asynchronous operation. - public async ValueTask OnNext1Async(T1 value, CancellationToken token) + internal async ValueTask OnNext1Async(T1 value, CancellationToken token) { _ = token; T2 second; @@ -162,7 +162,7 @@ public async ValueTask OnNext1Async(T1 value, CancellationToken token) /// The element from the second source. /// A token to cancel the operation. /// A task representing the asynchronous operation. - public async ValueTask OnNext2Async(T2 value, CancellationToken token) + internal async ValueTask OnNext2Async(T2 value, CancellationToken token) { _ = token; T1 firstVal; @@ -191,7 +191,7 @@ public async ValueTask OnNext2Async(T2 value, CancellationToken token) /// Handles the first source completing, propagating completion downstream when appropriate. /// The completion result from the first source. /// A task representing the asynchronous operation. - public async ValueTask OnCompleted1Async(Result result) + internal async ValueTask OnCompleted1Async(Result result) { bool shouldComplete; lock (_gate) @@ -218,7 +218,7 @@ public async ValueTask OnCompleted1Async(Result result) /// Handles the second source completing, propagating completion downstream when appropriate. /// The completion result from the second source. /// A task representing the asynchronous operation. - public async ValueTask OnCompleted2Async(Result result) + internal async ValueTask OnCompleted2Async(Result result) { bool shouldComplete; lock (_gate) @@ -246,7 +246,7 @@ public async ValueTask OnCompleted2Async(Result result) /// The error to forward. /// A token to cancel the operation. /// A task representing the asynchronous operation. - public ValueTask OnErrorResumeAsync(Exception error, CancellationToken cancellationToken) => + internal ValueTask OnErrorResumeAsync(Exception error, CancellationToken cancellationToken) => observer.OnErrorResumeAsync(error, cancellationToken); /// diff --git a/src/ReactiveUI.Primitives.Async.Core/Operators/ParityHelpers.Partition.cs b/src/ReactiveUI.Primitives.Async.Core/Operators/ParityHelpers.Partition.cs index 046e5568..9e908a86 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Operators/ParityHelpers.Partition.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Operators/ParityHelpers.Partition.cs @@ -58,10 +58,10 @@ public PartitionCoordinator(IObservableAsync source, Func predicate) } /// Gets the truthy-side observable; values for which the predicate returns . - public IObservableAsync TrueBranch { get; } + internal IObservableAsync TrueBranch { get; } /// Gets the falsy-side observable; values for which the predicate returns . - public IObservableAsync FalseBranch { get; } + internal IObservableAsync FalseBranch { get; } /// Subscribes a branch observer, lazily starting the shared upstream subscription on the first branch. /// for the truthy branch; for the falsy branch. diff --git a/src/ReactiveUI.Primitives.Async.Core/Operators/SwitchToSignal.cs b/src/ReactiveUI.Primitives.Async.Core/Operators/SwitchToSignal.cs index 48207f3f..1f656825 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Operators/SwitchToSignal.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Operators/SwitchToSignal.cs @@ -73,11 +73,14 @@ public SwitchToCoordinator(IObserverAsync observer) _disposeCancellationToken = _disposeCts.Token; } + /// + public ValueTask DisposeAsync() => FinishAsync(null); + /// Subscribes to the outer observable sequence. /// The outer observable that emits inner observable sequences. /// A token to cancel the subscription. /// A task representing the asynchronous subscribe operation. - public async ValueTask SubscribeAsync( + internal async ValueTask SubscribeAsync( IObservableAsync> source, CancellationToken subscriptionToken) { @@ -92,7 +95,7 @@ public async ValueTask SubscribeAsync( /// /// The new inner observable to switch to. /// A task representing the asynchronous switch operation. - public ValueTask AcceptOuterValueAsync(IObservableAsync inner) + internal ValueTask AcceptOuterValueAsync(IObservableAsync inner) { IAsyncDisposable? previousSubscription; lock (_gate) @@ -110,7 +113,7 @@ public ValueTask AcceptOuterValueAsync(IObservableAsync inner) /// /// The completion result from the outer sequence. /// A task representing the asynchronous completion operation. - public ValueTask AcceptOuterCompletionAsync(Result result) + internal ValueTask AcceptOuterCompletionAsync(Result result) { if (result.IsFailure) { @@ -133,7 +136,7 @@ public ValueTask AcceptOuterCompletionAsync(Result result) /// /// The completion result from the inner sequence. /// A task representing the asynchronous completion operation. - public ValueTask AcceptInnerCompletionAsync(Result result) + internal ValueTask AcceptInnerCompletionAsync(Result result) { Result? actualResult = null; lock (_gate) @@ -156,7 +159,7 @@ public ValueTask AcceptInnerCompletionAsync(Result result) /// The element to forward. /// A token to cancel the operation. /// A task representing the asynchronous forward operation. - public async ValueTask AcceptInnerValueAsync(T value, CancellationToken cancellationToken) + internal async ValueTask AcceptInnerValueAsync(T value, CancellationToken cancellationToken) { _ = cancellationToken; using (await _observerOnSomethingGate.EnterAsync(_disposeCancellationToken).ConfigureAwait(false)) @@ -169,7 +172,7 @@ public async ValueTask AcceptInnerValueAsync(T value, CancellationToken cancella /// The error to forward. /// A token to cancel the operation. /// A task representing the asynchronous error forwarding operation. - public async ValueTask AcceptInnerErrorAsync(Exception error, CancellationToken cancellationToken) + internal async ValueTask AcceptInnerErrorAsync(Exception error, CancellationToken cancellationToken) { _ = cancellationToken; using (await _observerOnSomethingGate.EnterAsync(_disposeCancellationToken).ConfigureAwait(false)) @@ -178,9 +181,6 @@ public async ValueTask AcceptInnerErrorAsync(Exception error, CancellationToken } } - /// - public ValueTask DisposeAsync() => FinishAsync(null); - /// /// Links the original subscribe-time cancellation token into this subscription's dispose chain so /// later per-emission methods can rely on instead of diff --git a/src/ReactiveUI.Primitives.Async.Core/Operators/TakeUntil.PredicateSignals.cs b/src/ReactiveUI.Primitives.Async.Core/Operators/TakeUntil.PredicateSignals.cs index 6c70741e..242fc134 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Operators/TakeUntil.PredicateSignals.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Operators/TakeUntil.PredicateSignals.cs @@ -47,7 +47,7 @@ internal sealed class PredicateStopCoordinator(PredicateStopSignal parent, IO /// Subscribes to the source observable. /// A token to cancel the subscription. /// A task representing the asynchronous subscribe operation. - public async ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) => + internal async ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) => _subscription = await parent._source.SubscribeAsync(this, cancellationToken).ConfigureAwait(false); /// @@ -114,7 +114,7 @@ internal sealed class AsyncPredicateStopCoordinator( /// Subscribes to the source observable. /// A token to cancel the subscription. /// A task representing the asynchronous subscribe operation. - public async ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) => + internal async ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) => _subscription = await parent._source.SubscribeAsync(this, cancellationToken).ConfigureAwait(false); /// diff --git a/src/ReactiveUI.Primitives.Async.Core/Operators/TakeUntil.cs b/src/ReactiveUI.Primitives.Async.Core/Operators/TakeUntil.cs index 8b522014..69d03464 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Operators/TakeUntil.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Operators/TakeUntil.cs @@ -364,16 +364,6 @@ public CancellationStopCoordinator(CancellationStopSignal parent, IObserverAs _lifecycle = new(observer); } - /// Subscribes to the source observable and registers the cancellation token callback. - /// A token to cancel the subscription. - /// A task representing the asynchronous subscribe operation. - public async ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) - { - _tokenRegistration = _parent._cancellationToken.Register(CompleteFromCancellation); - _subscription = await _parent._source - .SubscribeAsync(new TakeUntilSourceWitness(_lifecycle), cancellationToken).ConfigureAwait(false); - } - /// Asynchronously releases resources used by this subscription. /// A task representing the asynchronous dispose operation. public async ValueTask DisposeAsync() @@ -395,6 +385,16 @@ public async ValueTask DisposeAsync() await _lifecycle.DisposeAsync().ConfigureAwait(false); } + /// Subscribes to the source observable and registers the cancellation token callback. + /// A token to cancel the subscription. + /// A task representing the asynchronous subscribe operation. + internal async ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) + { + _tokenRegistration = _parent._cancellationToken.Register(CompleteFromCancellation); + _subscription = await _parent._source + .SubscribeAsync(new TakeUntilSourceWitness(_lifecycle), cancellationToken).ConfigureAwait(false); + } + /// Forwards the original subscribe-time token into the shared lifecycle's dispose chain. /// The subscribe-time token. internal void LinkExternalCancellation(CancellationToken external) => @@ -470,16 +470,6 @@ public DelegateStopCoordinator(DelegateStopSignal parent, IObserverAsync o _lifecycle = new(observer); } - /// Subscribes to the source observable and begins waiting for the stop signal. - /// A token to cancel the subscription. - /// A task representing the asynchronous subscribe operation. - public async ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) - { - AwaitStopThenComplete(); - _subscription = await _parent._source - .SubscribeAsync(new TakeUntilSourceWitness(_lifecycle), cancellationToken).ConfigureAwait(false); - } - /// Asynchronously releases resources used by this subscription. /// A task representing the asynchronous dispose operation. public async ValueTask DisposeAsync() @@ -493,6 +483,16 @@ public async ValueTask DisposeAsync() await _lifecycle.DisposeAsync().ConfigureAwait(false); } + /// Subscribes to the source observable and begins waiting for the stop signal. + /// A token to cancel the subscription. + /// A task representing the asynchronous subscribe operation. + internal async ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) + { + AwaitStopThenComplete(); + _subscription = await _parent._source + .SubscribeAsync(new TakeUntilSourceWitness(_lifecycle), cancellationToken).ConfigureAwait(false); + } + /// Forwards the original subscribe-time token into the shared lifecycle's dispose chain. /// The subscribe-time token. internal void LinkExternalCancellation(CancellationToken external) => @@ -629,17 +629,6 @@ public TaskStopCoordinator(TaskStopSignal parent, IObserverAsync observer) _lifecycle = new(observer); } - /// Subscribes to the source observable and begins waiting for the task to complete. - /// A token to cancel the subscription. - /// A task representing the asynchronous subscribe operation. - public async ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) - { - var task = _parent._task; - AwaitStopThenComplete(task); - _subscription = await _parent._source - .SubscribeAsync(new TakeUntilSourceWitness(_lifecycle), cancellationToken).ConfigureAwait(false); - } - /// Asynchronously releases resources used by this subscription. /// A task representing the asynchronous dispose operation. public async ValueTask DisposeAsync() @@ -652,6 +641,17 @@ public async ValueTask DisposeAsync() await _lifecycle.DisposeAsync().ConfigureAwait(false); } + /// Subscribes to the source observable and begins waiting for the task to complete. + /// A token to cancel the subscription. + /// A task representing the asynchronous subscribe operation. + internal async ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) + { + var task = _parent._task; + AwaitStopThenComplete(task); + _subscription = await _parent._source + .SubscribeAsync(new TakeUntilSourceWitness(_lifecycle), cancellationToken).ConfigureAwait(false); + } + /// Forwards the original subscribe-time token into the shared lifecycle's dispose chain. /// The subscribe-time token. internal void LinkExternalCancellation(CancellationToken external) => @@ -741,10 +741,19 @@ public AsyncStopCoordinator(TakeUntilAsyncSignal parent, IObserverAsy _lifecycle = new(observer); } + /// Asynchronously releases resources used by this subscription. + /// A task representing the asynchronous dispose operation. + public async ValueTask DisposeAsync() + { + await _otherDisposable.DisposeAsync().ConfigureAwait(false); + await _disposable.DisposeAsync().ConfigureAwait(false); + await _lifecycle.DisposeAsync().ConfigureAwait(false); + } + /// Subscribes to both the source and signal observables. /// A token to cancel the subscription. /// This subscription as an async disposable. - public async ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) + internal async ValueTask SubscribeSourcesAsync(CancellationToken cancellationToken) { var otherSubscription = await _parent._other .SubscribeAsync(new StopSignalWitness(this), cancellationToken).ConfigureAwait(false); @@ -758,15 +767,6 @@ await _parent._source.SubscribeAsync(new TakeUntilSourceWitness(_lifecycle), return this; } - /// Asynchronously releases resources used by this subscription. - /// A task representing the asynchronous dispose operation. - public async ValueTask DisposeAsync() - { - await _otherDisposable.DisposeAsync().ConfigureAwait(false); - await _disposable.DisposeAsync().ConfigureAwait(false); - await _lifecycle.DisposeAsync().ConfigureAwait(false); - } - /// Forwards the original subscribe-time token into the shared lifecycle's dispose chain. /// The subscribe-time token. internal void LinkExternalCancellation(CancellationToken external) => diff --git a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/ReplayLatestSignalAsyncStateHelper.cs b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/ReplayLatestSignalAsyncStateHelper.cs index 6a5564da..5ec98124 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/ReplayLatestSignalAsyncStateHelper.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/ReplayLatestSignalAsyncStateHelper.cs @@ -18,7 +18,7 @@ internal static class ReplayLatestSignalAsyncStateHelper /// A token that can cancel the operation. /// A task that represents the asynchronous notification. /// The signal has been disposed. - public static async ValueTask OnNextAsync( + internal static async ValueTask OnNextAsync( ReplayLatestSignalAsyncState state, SignalBroadcastKind kind, T value, @@ -61,7 +61,7 @@ await SignalAsyncStateHelper.BroadcastOnNextAsync( /// A token that can cancel the operation. /// A task that represents the asynchronous notification. /// The signal has been disposed. - public static async ValueTask OnErrorResumeAsync( + internal static async ValueTask OnErrorResumeAsync( ReplayLatestSignalAsyncState state, SignalBroadcastKind kind, Exception error, @@ -99,7 +99,7 @@ await SignalAsyncStateHelper.BroadcastOnErrorResumeAsync(kind, observers, error, /// The completion result to publish. /// A task that represents the asynchronous notification. /// The signal has been disposed. - public static async ValueTask OnCompletedAsync( + internal static async ValueTask OnCompletedAsync( ReplayLatestSignalAsyncState state, SignalBroadcastKind kind, Result result) @@ -126,7 +126,7 @@ public static async ValueTask OnCompletedAsync( /// The observed value type. /// The mutable signal state. /// A task that represents the asynchronous disposal operation. - public static async ValueTask DisposeAsync(ReplayLatestSignalAsyncState state) + internal static async ValueTask DisposeAsync(ReplayLatestSignalAsyncState state) { if (state.IsDisposed) { @@ -146,7 +146,7 @@ public static async ValueTask DisposeAsync(ReplayLatestSignalAsyncState st /// The subscription handle for the observer. /// The signal has been disposed, or /// was cancelled. - public static async ValueTask SubscribeAsync( + internal static async ValueTask SubscribeAsync( ReplayLatestSignalAsyncState state, IObserverAsync observer, CancellationToken cancellationToken) diff --git a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/ReplayLatestSignalAsyncState{T}.cs b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/ReplayLatestSignalAsyncState{T}.cs index c3188715..f1e4f54c 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/ReplayLatestSignalAsyncState{T}.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/ReplayLatestSignalAsyncState{T}.cs @@ -16,7 +16,7 @@ internal sealed class ReplayLatestSignalAsyncState : IDisposable "Style", "SST1401:Field should be private", Justification = "Gate fields are intentionally direct readonly state for helper access.")] - public readonly AsyncSerialGate Gate = new(); + internal readonly AsyncSerialGate Gate = new(); /// Initializes a new instance of the class. /// The initial value to replay, if any. @@ -27,26 +27,26 @@ public ReplayLatestSignalAsyncState(Optional initialValue) } /// Gets the cancellation token source that is cancelled when this instance is disposed. - public CancellationTokenSource DisposedCts { get; } = new(); + internal CancellationTokenSource DisposedCts { get; } = new(); /// Gets the token cancelled when this instance is disposed. Captured while the source is still /// alive because disposes that source, and reading /// from a disposed source throws /// . Disposal always cancels before it disposes, so this token is /// already cancelled by the time anyone can observe it post-disposal. - public CancellationToken DisposedCancellationToken { get; } + internal CancellationToken DisposedCancellationToken { get; } - /// Gets the most recently published value, replayed to new subscribers upon subscription. - public Optional LastValue { get; internal set; } + /// Gets or sets the most recently published value, replayed to new subscribers upon subscription. + internal Optional LastValue { get; set; } - /// Gets the currently subscribed observers. - public ImmutableArray> Observers { get; internal set; } = []; + /// Gets or sets the currently subscribed observers. + internal ImmutableArray> Observers { get; set; } = []; - /// Gets the completion result, or null if the signal has not completed. - public Result? Result { get; internal set; } + /// Gets or sets the completion result, or null if the signal has not completed. + internal Result? Result { get; set; } - /// Gets a value indicating whether this instance has been disposed. - public bool IsDisposed { get; internal set; } + /// Gets or sets a value indicating whether this instance has been disposed. + internal bool IsDisposed { get; set; } /// public void Dispose() diff --git a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/SerialBroadcastHelpers.cs b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/SerialBroadcastHelpers.cs index 8a0ceaa4..9e894068 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/SerialBroadcastHelpers.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/SerialBroadcastHelpers.cs @@ -28,7 +28,7 @@ internal static class SerialBroadcastHelpers /// The value being broadcast. /// A cancellation token that can be used to cancel the notification operation. /// A task that represents the asynchronous notification operation. - public static ValueTask BroadcastOnNextAsync( + internal static ValueTask BroadcastOnNextAsync( ImmutableArray> observers, T value, CancellationToken cancellationToken) => @@ -42,7 +42,7 @@ public static ValueTask BroadcastOnNextAsync( /// The value being broadcast. /// A cancellation token that can be used to cancel the notification operation. /// A task that represents the asynchronous notification operation. - public static ValueTask BroadcastOnNextAsyncMulti( + internal static ValueTask BroadcastOnNextAsyncMulti( ImmutableArray> observers, T value, CancellationToken cancellationToken) @@ -67,7 +67,7 @@ public static ValueTask BroadcastOnNextAsyncMulti( /// The error being broadcast. /// A cancellation token that can be used to cancel the notification operation. /// A task that represents the asynchronous notification operation. - public static ValueTask BroadcastOnErrorResumeAsync( + internal static ValueTask BroadcastOnErrorResumeAsync( ImmutableArray> observers, Exception error, CancellationToken cancellationToken) @@ -91,7 +91,7 @@ public static ValueTask BroadcastOnErrorResumeAsync( /// The current observer snapshot. /// The terminal result being broadcast. /// A task that represents the asynchronous notification operation. - public static ValueTask BroadcastOnCompletedAsync( + internal static ValueTask BroadcastOnCompletedAsync( ImmutableArray> observers, Result result) { diff --git a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/SignalAsyncStateHelper.cs b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/SignalAsyncStateHelper.cs index b9b145a7..483e351c 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/SignalAsyncStateHelper.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/SignalAsyncStateHelper.cs @@ -14,7 +14,7 @@ internal static class SignalAsyncStateHelper /// The observed value type. /// The signal instance. /// The signal as an observable sequence. - public static IObservableAsync Values(ISignalAsync signal) => signal; + internal static IObservableAsync Values(ISignalAsync signal) => signal; /// Asynchronously notifies subscribed observers of a new value. /// The observed value type. @@ -23,7 +23,7 @@ internal static class SignalAsyncStateHelper /// The value to publish. /// A token that can cancel the operation. /// A task that represents the asynchronous notification. - public static ValueTask OnNextAsync( + internal static ValueTask OnNextAsync( SignalAsyncState state, SignalBroadcastKind kind, T value, @@ -39,7 +39,7 @@ public static ValueTask OnNextAsync( /// The recoverable error to publish. /// A token that can cancel the operation. /// A task that represents the asynchronous notification. - public static ValueTask OnErrorResumeAsync( + internal static ValueTask OnErrorResumeAsync( SignalAsyncState state, SignalBroadcastKind kind, Exception error, @@ -54,7 +54,7 @@ public static ValueTask OnErrorResumeAsync( /// The broadcast mode for observer notifications. /// The completion result to publish. /// A task that represents the asynchronous notification. - public static ValueTask OnCompletedAsync( + internal static ValueTask OnCompletedAsync( SignalAsyncState state, SignalBroadcastKind kind, Result result) => @@ -62,7 +62,7 @@ public static ValueTask OnCompletedAsync( /// Releases resources used by the signal. /// A completed task. - public static ValueTask DisposeAsync() => default; + internal static ValueTask DisposeAsync() => default; /// Subscribes an observer to a signal. /// The observed value type. @@ -70,7 +70,7 @@ public static ValueTask OnCompletedAsync( /// The observer to subscribe. /// A token that can cancel the operation. /// The subscription handle for the observer. - public static async ValueTask SubscribeAsync( + internal static async ValueTask SubscribeAsync( SignalAsyncState state, IObserverAsync observer, CancellationToken cancellationToken) @@ -96,7 +96,7 @@ public static async ValueTask SubscribeAsync( /// The value to publish. /// A token that can cancel the operation. /// A task that represents the asynchronous notification. - public static ValueTask BroadcastOnNextAsync( + internal static ValueTask BroadcastOnNextAsync( SignalBroadcastKind kind, ImmutableArray> observers, T value, @@ -121,7 +121,7 @@ public static ValueTask BroadcastOnNextAsync( /// The recoverable error to publish. /// A token that can cancel the operation. /// A task that represents the asynchronous notification. - public static ValueTask BroadcastOnErrorResumeAsync( + internal static ValueTask BroadcastOnErrorResumeAsync( SignalBroadcastKind kind, ImmutableArray> observers, Exception error, @@ -136,7 +136,7 @@ public static ValueTask BroadcastOnErrorResumeAsync( /// The observers to notify. /// The completion result to publish. /// A task that represents the asynchronous notification. - public static ValueTask BroadcastOnCompletedAsync( + internal static ValueTask BroadcastOnCompletedAsync( SignalBroadcastKind kind, ImmutableArray> observers, Result result) => diff --git a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/SignalAsyncState{T}.cs b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/SignalAsyncState{T}.cs index 45498fdb..eccfef2b 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/SignalAsyncState{T}.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/SignalAsyncState{T}.cs @@ -13,11 +13,11 @@ internal sealed class SignalAsyncState /// The lock used to synchronize mutable state. private readonly Lock _gate = new(); - /// Gets the currently subscribed observers. - public ImmutableArray> Observers { get; internal set; } = []; + /// Gets or sets the currently subscribed observers. + internal ImmutableArray> Observers { get; set; } = []; - /// Gets the completion result, or null if the signal has not completed. - public Result? Result { get; internal set; } + /// Gets or sets the completion result, or null if the signal has not completed. + internal Result? Result { get; set; } /// Gets a stable observer snapshot when the signal is still active. /// Receives the observers to notify when the signal is active. diff --git a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessReplayLatestSignalAsyncStateHelper.cs b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessReplayLatestSignalAsyncStateHelper.cs index 0aa6cf6e..b0f149d0 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessReplayLatestSignalAsyncStateHelper.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessReplayLatestSignalAsyncStateHelper.cs @@ -17,7 +17,7 @@ internal static class StatelessReplayLatestSignalAsyncStateHelper /// A token that can cancel the operation. /// A task that represents the asynchronous notification. /// The signal has been disposed. - public static async ValueTask OnNextAsync( + internal static async ValueTask OnNextAsync( StatelessReplayLatestSignalAsyncState state, SignalBroadcastKind kind, T value, @@ -52,7 +52,7 @@ await SignalAsyncStateHelper.BroadcastOnNextAsync(kind, observers, value, cancel /// A token that can cancel the operation. /// A task that represents the asynchronous notification. /// The signal has been disposed. - public static async ValueTask OnErrorResumeAsync( + internal static async ValueTask OnErrorResumeAsync( StatelessReplayLatestSignalAsyncState state, SignalBroadcastKind kind, Exception error, @@ -85,7 +85,7 @@ await SignalAsyncStateHelper.BroadcastOnErrorResumeAsync(kind, observers, error, /// The completion result to publish. /// A task that represents the asynchronous notification. /// The signal has been disposed. - public static async ValueTask OnCompletedAsync( + internal static async ValueTask OnCompletedAsync( StatelessReplayLatestSignalAsyncState state, SignalBroadcastKind kind, Result result) @@ -107,7 +107,7 @@ public static async ValueTask OnCompletedAsync( /// The observed value type. /// The mutable signal state. /// A task that represents the asynchronous disposal operation. - public static async ValueTask DisposeAsync(StatelessReplayLatestSignalAsyncState state) + internal static async ValueTask DisposeAsync(StatelessReplayLatestSignalAsyncState state) { if (state.IsDisposed) { @@ -127,7 +127,7 @@ public static async ValueTask DisposeAsync(StatelessReplayLatestSignalAsyncSt /// The subscription handle for the observer. /// The signal has been disposed, or /// was cancelled. - public static async ValueTask SubscribeAsync( + internal static async ValueTask SubscribeAsync( StatelessReplayLatestSignalAsyncState state, IObserverAsync observer, CancellationToken cancellationToken) diff --git a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessReplayLatestSignalAsyncState{T}.cs b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessReplayLatestSignalAsyncState{T}.cs index 67ee0e9d..9a0c3c1c 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessReplayLatestSignalAsyncState{T}.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessReplayLatestSignalAsyncState{T}.cs @@ -16,7 +16,7 @@ internal sealed class StatelessReplayLatestSignalAsyncState : IDisposable "Style", "SST1401:Field should be private", Justification = "Gate fields are intentionally direct readonly state for helper access.")] - public readonly AsyncSerialGate Gate = new(); + internal readonly AsyncSerialGate Gate = new(); /// Initializes a new instance of the class. /// The initial value to replay, if any. @@ -28,26 +28,26 @@ public StatelessReplayLatestSignalAsyncState(Optional initialValue) } /// Gets the cancellation token source that is cancelled when this instance is disposed. - public CancellationTokenSource DisposedCts { get; } = new(); + internal CancellationTokenSource DisposedCts { get; } = new(); /// Gets the token cancelled when this instance is disposed. Captured while the source is still /// alive because disposes that source, and reading /// from a disposed source throws /// . Disposal always cancels before it disposes, so this token is /// already cancelled by the time anyone can observe it post-disposal. - public CancellationToken DisposedCancellationToken { get; } + internal CancellationToken DisposedCancellationToken { get; } /// Gets the initial value to replay, if any. - public Optional InitialValue { get; } + internal Optional InitialValue { get; } - /// Gets the most recently published value, or the initial value after reset. - public Optional Value { get; internal set; } + /// Gets or sets the most recently published value, or the initial value after reset. + internal Optional Value { get; set; } - /// Gets the currently subscribed observers. - public ImmutableArray> Observers { get; internal set; } = []; + /// Gets or sets the currently subscribed observers. + internal ImmutableArray> Observers { get; set; } = []; - /// Gets a value indicating whether this instance has been disposed. - public bool IsDisposed { get; internal set; } + /// Gets or sets a value indicating whether this instance has been disposed. + internal bool IsDisposed { get; set; } /// public void Dispose() diff --git a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessSignalAsyncStateHelper.cs b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessSignalAsyncStateHelper.cs index 186bf73a..ecfd34aa 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessSignalAsyncStateHelper.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessSignalAsyncStateHelper.cs @@ -16,7 +16,7 @@ internal static class StatelessSignalAsyncStateHelper /// The value to publish. /// A token that can cancel the operation. /// A task that represents the asynchronous notification. - public static ValueTask OnNextAsync( + internal static ValueTask OnNextAsync( StatelessSignalAsyncState state, SignalBroadcastKind kind, T value, @@ -30,7 +30,7 @@ public static ValueTask OnNextAsync( /// The recoverable error to publish. /// A token that can cancel the operation. /// A task that represents the asynchronous notification. - public static ValueTask OnErrorResumeAsync( + internal static ValueTask OnErrorResumeAsync( StatelessSignalAsyncState state, SignalBroadcastKind kind, Exception error, @@ -43,7 +43,7 @@ public static ValueTask OnErrorResumeAsync( /// The broadcast mode for observer notifications. /// The completion result to publish. /// A task that represents the asynchronous notification. - public static ValueTask OnCompletedAsync( + internal static ValueTask OnCompletedAsync( StatelessSignalAsyncState state, SignalBroadcastKind kind, Result result) => @@ -53,7 +53,7 @@ public static ValueTask OnCompletedAsync( /// The observed value type. /// The mutable signal state. /// A task that represents the asynchronous disposal operation. - public static ValueTask DisposeAsync(StatelessSignalAsyncState state) + internal static ValueTask DisposeAsync(StatelessSignalAsyncState state) { state.Clear(); return default; @@ -65,7 +65,7 @@ public static ValueTask DisposeAsync(StatelessSignalAsyncState state) /// The observer to subscribe. /// A token that can cancel the operation. /// The subscription handle for the observer. - public static ValueTask SubscribeAsync( + internal static ValueTask SubscribeAsync( StatelessSignalAsyncState state, IObserverAsync observer, CancellationToken cancellationToken) diff --git a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessSignalAsyncState{T}.cs b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessSignalAsyncState{T}.cs index 98b67da2..5514d3c5 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessSignalAsyncState{T}.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Signals/Base/StatelessSignalAsyncState{T}.cs @@ -13,8 +13,8 @@ internal sealed class StatelessSignalAsyncState /// The lock used to synchronize observer list updates. private readonly Lock _gate = new(); - /// Gets the currently subscribed observers. - public ImmutableArray> Observers { get; internal set; } = []; + /// Gets or sets the currently subscribed observers. + internal ImmutableArray> Observers { get; set; } = []; /// Gets a stable snapshot of the current observers. /// The observers subscribed when the snapshot is taken. diff --git a/src/ReactiveUI.Primitives.Core/Core/CoreCompositeFormats.cs b/src/ReactiveUI.Primitives.Core/Core/CoreCompositeFormats.cs index 2fa52f72..c72f515d 100644 --- a/src/ReactiveUI.Primitives.Core/Core/CoreCompositeFormats.cs +++ b/src/ReactiveUI.Primitives.Core/Core/CoreCompositeFormats.cs @@ -15,9 +15,9 @@ namespace ReactiveUI.Primitives.Core; internal static class CoreCompositeFormats { /// The format renders with. - public static readonly CompositeFormat Moment = CompositeFormat.Parse("{0}@{1:o}"); + internal static readonly CompositeFormat Moment = CompositeFormat.Parse("{0}@{1:o}"); /// The format renders with. - public static readonly CompositeFormat TimeInterval = CompositeFormat.Parse("{0}@{1}"); + internal static readonly CompositeFormat TimeInterval = CompositeFormat.Parse("{0}@{1}"); } #endif diff --git a/src/ReactiveUI.Primitives.Core/Core/PriorityQueue.cs b/src/ReactiveUI.Primitives.Core/Core/PriorityQueue.cs index 146bcc5e..321846a8 100644 --- a/src/ReactiveUI.Primitives.Core/Core/PriorityQueue.cs +++ b/src/ReactiveUI.Primitives.Core/Core/PriorityQueue.cs @@ -304,10 +304,10 @@ private void RemoveAt(int index) internal readonly record struct IndexedItem : IComparable { /// Gets or sets the insertion order id. - public long Id { get; init; } + internal long Id { get; init; } /// Gets or sets the queued value. - public T Value { get; init; } + internal T Value { get; init; } /// Compares two indexed items. /// The left item. diff --git a/src/ReactiveUI.Primitives.Core/Signals/BehaviorSignalState{T}.cs b/src/ReactiveUI.Primitives.Core/Signals/BehaviorSignalState{T}.cs index 2d3d1b96..d7618286 100644 --- a/src/ReactiveUI.Primitives.Core/Signals/BehaviorSignalState{T}.cs +++ b/src/ReactiveUI.Primitives.Core/Signals/BehaviorSignalState{T}.cs @@ -51,14 +51,14 @@ public BehaviorSignalState(T defaultValue) } /// Gets a value indicating whether the signal has been disposed. - public readonly bool IsDisposed => _isDisposed; + internal readonly bool IsDisposed => _isDisposed; /// Gets a value indicating whether the signal currently has observers. - public bool HasObservers => _broadcaster.HasObservers && !_isStopped && !_isDisposed; + internal bool HasObservers => _broadcaster.HasObservers && !_isStopped && !_isDisposed; /// Gets the current value, throwing if disposed or faulted. /// The current value. - public readonly T GetValue() + internal readonly T GetValue() { ThrowIfDisposed(); _lastError.Rethrow(); @@ -69,7 +69,7 @@ public readonly T GetValue() /// Tries to read the current value without throwing when disposed. /// The current value, or when disposed. /// when a value is available. - public readonly bool TryGetValue(out T? value) + internal readonly bool TryGetValue(out T? value) { lock (_gate) { @@ -92,7 +92,7 @@ public readonly bool TryGetValue(out T? value) /// subscriber is either added before this completes (and is broadcast to here) or after (and replays the /// terminal state itself), never seeing an out-of-order or duplicated notification. /// - public void OnCompleted() + internal void OnCompleted() { lock (_gate) { @@ -110,7 +110,7 @@ public void OnCompleted() /// Notifies all observers about the exception. /// The exception to send to all observers. - public void OnError(Exception error) + internal void OnError(Exception error) { ArgumentExceptionHelper.ThrowIfNull(error); @@ -136,7 +136,7 @@ public void OnError(Exception error) /// with respect to ; a new subscriber never observes a live value before the initial /// value it was promised, and never observes the same value twice. /// - public void OnNext(T value) + internal void OnNext(T value) { lock (_gate) { @@ -154,7 +154,7 @@ public void OnNext(T value) /// The owning signal used to remove the observer on disposal. /// The observer to subscribe. /// A handle that unsubscribes the observer when disposed. - public IDisposable Subscribe(IWitnessRemovable owner, IObserver observer) + internal IDisposable Subscribe(IWitnessRemovable owner, IObserver observer) { ArgumentExceptionHelper.ThrowIfNull(observer); @@ -192,7 +192,7 @@ public IDisposable Subscribe(IWitnessRemovable owner, IObserver observer) /// Removes a previously subscribed observer. /// The observer to remove. - public void RemoveObserver(IObserver observer) + internal void RemoveObserver(IObserver observer) { lock (_gate) { @@ -201,7 +201,7 @@ public void RemoveObserver(IObserver observer) } /// Releases the signal's observers and cached state. - public void Release() + internal void Release() { if (_isDisposed) { diff --git a/src/ReactiveUI.Primitives.Extensions.Core/ConcurrencyLimiter.cs b/src/ReactiveUI.Primitives.Extensions.Core/ConcurrencyLimiter.cs index e1af847e..dd120569 100644 --- a/src/ReactiveUI.Primitives.Extensions.Core/ConcurrencyLimiter.cs +++ b/src/ReactiveUI.Primitives.Extensions.Core/ConcurrencyLimiter.cs @@ -173,13 +173,13 @@ private void PullNextTask(Subscription subscription) internal sealed class Subscription(ConcurrencyLimiter limiter, IObserver observer) : IDisposable { /// Gets the owning limiter. - public ConcurrencyLimiter Limiter { get; } = limiter; + internal ConcurrencyLimiter Limiter { get; } = limiter; /// Gets the downstream observer. - public IObserver Observer { get; } = observer; + internal IObserver Observer { get; } = observer; /// Gets a value indicating whether the subscription has been disposed. - public bool Disposed => Limiter.Disposed; + internal bool Disposed => Limiter.Disposed; /// public void Dispose() => Limiter.Disposed = true; diff --git a/src/ReactiveUI.Primitives.Extensions.Core/Internal/ConcurrencyRaceHelpers.cs b/src/ReactiveUI.Primitives.Extensions.Core/Internal/ConcurrencyRaceHelpers.cs index abe1f852..161c4021 100644 --- a/src/ReactiveUI.Primitives.Extensions.Core/Internal/ConcurrencyRaceHelpers.cs +++ b/src/ReactiveUI.Primitives.Extensions.Core/Internal/ConcurrencyRaceHelpers.cs @@ -26,7 +26,7 @@ internal static class ConcurrencyRaceHelpers /// if the claim succeeded; if another caller /// already claimed the state. /// - public static bool TryClaim(ref int state, int openSentinel, int claimedSentinel) => + internal static bool TryClaim(ref int state, int openSentinel, int claimedSentinel) => Interlocked.CompareExchange(ref state, claimedSentinel, openSentinel) == openSentinel; /// @@ -41,7 +41,7 @@ public static bool TryClaim(ref int state, int openSentinel, int claimedSentinel /// if the cancellation completed; if the /// source was already disposed. /// - public static async ValueTask TryCancelAsync(CancellationTokenSource cts) + internal static async ValueTask TryCancelAsync(CancellationTokenSource cts) { try { diff --git a/src/ReactiveUI.Primitives.Extensions.Core/Internal/IndexedSubscribeHelper.cs b/src/ReactiveUI.Primitives.Extensions.Core/Internal/IndexedSubscribeHelper.cs index 50e5821f..5559cacf 100644 --- a/src/ReactiveUI.Primitives.Extensions.Core/Internal/IndexedSubscribeHelper.cs +++ b/src/ReactiveUI.Primitives.Extensions.Core/Internal/IndexedSubscribeHelper.cs @@ -27,7 +27,7 @@ internal static class IndexedSubscribeHelper /// Shared OnError hook (errors from any source forward unchanged). /// Per-source OnCompleted hook: (index). /// A composite disposable that releases every per-source subscription on dispose. - public static IDisposable SubscribeIndexed( + internal static IDisposable SubscribeIndexed( IReadOnlyList> sources, Action onNext, Action onError, diff --git a/src/ReactiveUI.Primitives.Extensions.Core/Internal/ObservableSubscribeExtensions.cs b/src/ReactiveUI.Primitives.Extensions.Core/Internal/ObservableSubscribeExtensions.cs index d6d7d841..e338dce9 100644 --- a/src/ReactiveUI.Primitives.Extensions.Core/Internal/ObservableSubscribeExtensions.cs +++ b/src/ReactiveUI.Primitives.Extensions.Core/Internal/ObservableSubscribeExtensions.cs @@ -22,7 +22,7 @@ internal static class ObservableSubscribeExtensions /// Error callback. /// Completion callback. /// The subscription disposable. - public IDisposable SubscribeCallbacks( + internal IDisposable SubscribeCallbacks( Action onNext, Action onError, Action onCompleted) => diff --git a/src/ReactiveUI.Primitives.Extensions.Core/Internal/ReduceSinkState.cs b/src/ReactiveUI.Primitives.Extensions.Core/Internal/ReduceSinkState.cs index 580250a6..3b84df37 100644 --- a/src/ReactiveUI.Primitives.Extensions.Core/Internal/ReduceSinkState.cs +++ b/src/ReactiveUI.Primitives.Extensions.Core/Internal/ReduceSinkState.cs @@ -30,31 +30,31 @@ public ReduceSinkState(IObserver downstream, int count) } /// Gets the downstream observer that receives reduced values, error, and completion. - public IObserver Downstream { get; } + internal IObserver Downstream { get; } /// Gets the per-source latest values; index N is set on first OnNext from source N. - public TIn?[] Values { get; } + internal TIn?[] Values { get; } /// Gets the per-source completion bookkeeping. - public bool[] Completed { get; } + internal bool[] Completed { get; } /// Gets or sets the number of sources that have produced at least one value. - public int HasValueCount { get; set; } + internal int HasValueCount { get; set; } /// Gets or sets the number of sources that have completed. - public int CompletedCount { get; set; } + internal int CompletedCount { get; set; } /// Gets or sets a value indicating whether the sink has reached its terminal state. - public bool IsDone { get; set; } + internal bool IsDone { get; set; } /// Gets a value indicating whether every source has produced at least one value. - public bool AllValuesPresent => HasValueCount >= Values.Length; + internal bool AllValuesPresent => HasValueCount >= Values.Length; /// Records source 's latest value and emits the reduced result once every source has one. Runs under the gate. /// The 0-based source index that emitted. /// The latest value from that source. /// Projects the per-source latest values into the downstream result. - public void HandleNext(int index, TIn value, Func reduce) + internal void HandleNext(int index, TIn value, Func reduce) { lock (_gate) { @@ -81,7 +81,7 @@ public void HandleNext(int index, TIn value, Func reduce) /// Forwards a terminal error to the downstream observer and marks the sink terminal. Idempotent. /// The error to forward. - public void HandleError(Exception error) + internal void HandleError(Exception error) { lock (_gate) { @@ -100,7 +100,7 @@ public void HandleError(Exception error) /// once every source has completed OR a source completes without ever having emitted a value. /// /// The 0-based source index that just completed. - public void HandleCompleted(int index) + internal void HandleCompleted(int index) { lock (_gate) { diff --git a/src/ReactiveUI.Primitives.Extensions.Core/Operators/PartitionObservable.cs b/src/ReactiveUI.Primitives.Extensions.Core/Operators/PartitionObservable.cs index c2db3be7..2da98b89 100644 --- a/src/ReactiveUI.Primitives.Extensions.Core/Operators/PartitionObservable.cs +++ b/src/ReactiveUI.Primitives.Extensions.Core/Operators/PartitionObservable.cs @@ -2,11 +2,11 @@ // ReactiveUI Association Incorporated licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. -namespace ReactiveUI.Primitives.Extensions.Operators; - using System; using System.Threading; +namespace ReactiveUI.Primitives.Extensions.Operators; + /// Partitions a sequence into two observables based on a predicate. /// The type of elements in the source sequence. public sealed class PartitionObservable From 3708bee52bcef8ea7200df485bc18e4035ea6857 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:16:31 +1000 Subject: [PATCH 02/18] fix(analyzers): update to 3.30.0 and clear the CI analyzer failures - Bump RoslynCommonAnalyzersVersion to 3.30.0 - Remove CA1822 suppressions left dangling by retiring the rule, and make NSRunloopSequencer.ScheduleDelayed static now PSH1414 owns the shape - Narrow dead public modifiers on members of internal types across the polyfills, platform sequencers and test helpers - Rewrite literal concatenations as interpolated strings and drop inferable explicit type arguments in the test projects - Assert explicitly in two tests that previously verified nothing, and replace a Thread.Sleep race widener with Thread.SpinWait - Turn off CA1019, which conflicts with SST2324 on internal attributes --- .editorconfig | 2 +- src/Directory.Packages.props | 2 +- src/Polyfills/ArgumentExceptionHelper.cs | 2 +- .../ArgumentOutOfRangeExceptionHelper.cs | 10 ++--- .../CallerArgumentExpressionAttribute.cs | 2 +- .../CancellationTokenPolyfillExtensions.cs | 6 +-- ...ncellationTokenSourcePolyfillExtensions.cs | 2 +- .../CompilerFeatureRequiredAttribute.cs | 6 +-- src/Polyfills/MemberNotNullWhenAttribute.cs | 4 +- src/Polyfills/NotNullWhenAttribute.cs | 2 +- .../ObjectDisposedExceptionHelper.cs | 2 +- .../OverloadResolutionPriorityAttribute.cs | 2 +- src/Polyfills/TaskCompletionSource.cs | 14 +++--- src/Polyfills/TaskPolyfillExtensions.cs | 4 +- .../Internal/ScheduledDrainState.cs | 20 ++++----- .../Internal/SequencerPeriodicExtensions.cs | 28 ++++++------ .../ObservableSubscriptionExtensions.cs | 9 +--- .../Operators/ConflateObservable.cs | 8 ++-- .../Operators/ScheduleConfig.cs | 10 ++--- .../Operators/SyncTimerObservable.cs | 2 +- .../DispatchWorkItemBase.cs | 4 +- .../Advanced/CurrentThreadRequirement.cs | 2 +- .../FromAsyncExternalCancellation{T}.cs | 8 ++-- .../Advanced/FromAsyncTaskObservation{T}.cs | 2 +- .../Advanced/LoopSignal{T}.cs | 2 +- .../Advanced/RangeLatestSignalHelper.cs | 2 +- .../Advanced/SubscriptionScheduling.cs | 4 +- .../Advanced/TaskChainCoordinatorState.cs | 2 +- .../Advanced/TaskTerminalWitnessHelper.cs | 10 ++--- .../Advanced/WitnessLifetime.cs | 20 ++++----- .../Concurrency/SequencerWorkItem.cs | 2 +- .../Core/SparkCompositeFormats.cs | 4 +- src/Primitives.Shared/Handle.cs | 6 +-- .../Signals/TaskSignal{T}.cs | 44 +++++++++---------- .../Signals/TaskTerminalCompletion{T}.cs | 10 ++--- .../Concurrency/CurrentThreadSequencer.cs | 4 +- .../Concurrency/Sequencer.cs | 8 ++-- .../SequencerSchedulingExtensions.cs | 14 +++--- .../Concurrency/ThreadPoolSequencer.cs | 2 +- .../Concurrency/WasmScheduler.cs | 4 +- .../Concurrency/ImmediateSequencer.cs | 5 --- .../Concurrency/Sequencer.Simple.cs | 2 +- .../Concurrency/SynchronizedSequencerQueue.cs | 6 +-- .../Concurrency/ThreadPoolSequencer.cs | 4 +- .../Concurrency/VirtualTimeMessages.cs | 2 +- .../VirtualTimeState{TAbsolute,TRelative}.cs | 34 +++++++------- .../Platforms/apple/NSRunloopSequencer.cs | 2 +- .../AotSafeAssertionExtensions.cs | 2 +- .../AsyncSerialGateTests.cs | 1 - .../ConcurrentSignalBaseTests.cs | 1 - .../DisposableAsyncSlotTests.cs | 2 +- .../DisposableTests.cs | 3 +- .../IgnoredResult.cs | 2 +- .../CombineLatestIndexedObserverTests.cs | 6 +-- .../SyncLatestCoordinatorBaseTests.cs | 8 ++-- .../NewThreadTaskScheduler.cs | 2 +- .../ParityHelpersFilterFusionsTests.cs | 1 - .../ParityHelpersOperatorFusionsTests.cs | 1 - .../ParityOperatorTests.cs | 1 - .../AotSafeAssertionExtensions.cs | 2 +- .../Operators/ScanWithInitialTests.cs | 5 ++- .../Operators/SimpleSyncOperatorTests.cs | 2 +- .../Operators/SyncDirectSource.cs | 2 +- .../ReactiveExtensionsTests.FusedSelect.cs | 6 +-- .../VirtualClockTestExtensions.cs | 4 +- .../Common/ManualSequencer.cs | 6 +-- .../Common/MinimalVirtualClock.cs | 4 +- .../Common/OrderingWitness.cs | 2 +- .../Common/RecordingDisposable.cs | 2 +- .../Common/RecordingWitness.cs | 6 +-- .../Common/ScheduledProbe.cs | 2 +- .../Common/TestPolling.cs | 2 +- .../Common/ThrowingWitness.cs | 2 +- .../ConnectableSignalTests.cs | 2 +- .../DummyDisposable.cs | 2 +- .../FromEnumerableSignalTests.cs | 2 +- .../RxNamesTests.DynamicDataCompatibility.cs | 4 +- .../RxNamesTests.SubscribeSafe.cs | 10 ++--- .../SafeWitnessTests.cs | 8 ++-- .../SignalAliasCoverageTests.cs | 4 +- .../SignalCreateTests.cs | 14 +++--- .../SignalFactoriesTests.Behavior.cs | 2 +- .../SignalFactoriesTests.cs | 30 ++++++------- .../SignalOperatorMixinsTests.Behavior.cs | 2 +- ...SignalOperatorMixinsTests.Deterministic.cs | 4 +- .../SignalOperatorMixinsTests.cs | 8 ++-- ...torParityMixinsTests.PostTerminalGuards.cs | 2 +- .../SignalOperatorParityMixinsTests.cs | 26 +++++------ .../ReactiveUI.Primitives.Tests/SparkTests.cs | 8 ++-- .../TestClasses/EmptySequencer.cs | 6 +-- .../WitnessTests.cs | 16 +++---- 91 files changed, 280 insertions(+), 293 deletions(-) diff --git a/.editorconfig b/.editorconfig index b3ecd391..cf1749e8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -197,7 +197,7 @@ dotnet_diagnostic.CA1014.severity = none # Mark assemblies with CLSCompliantAttr dotnet_diagnostic.CA1016.severity = error # Mark assemblies with AssemblyVersionAttribute dotnet_diagnostic.CA1017.severity = none # Mark assemblies with ComVisibleAttribute — we don't ship COM-visible assemblies dotnet_diagnostic.CA1018.severity = error # Mark attributes with AttributeUsageAttribute -dotnet_diagnostic.CA1019.severity = error # Define accessors for attribute arguments +dotnet_diagnostic.CA1019.severity = none # Define accessors for attribute arguments — conflicts with SST2324, which caps an internal attribute's accessor at internal dotnet_diagnostic.CA1021.severity = none # Avoid out parameters - disabled - needed for the zero-allocation idiom in Try/Find APIs and other performance-critical paths dotnet_diagnostic.CA1024.severity = error # Use properties where appropriate dotnet_diagnostic.CA1027.severity = error # Mark enums with FlagsAttribute diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index cc5909a7..67a86c17 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -5,7 +5,7 @@ true - 3.29.1 + 3.30.0 diff --git a/src/Polyfills/ArgumentExceptionHelper.cs b/src/Polyfills/ArgumentExceptionHelper.cs index 64cf7abb..04da4653 100644 --- a/src/Polyfills/ArgumentExceptionHelper.cs +++ b/src/Polyfills/ArgumentExceptionHelper.cs @@ -18,7 +18,7 @@ internal static class ArgumentExceptionHelper /// Throws an if is . /// The reference type argument to validate as non-null. /// The name of the parameter with which corresponds. - public static void ThrowIfNull( + internal static void ThrowIfNull( [NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) diff --git a/src/Polyfills/ArgumentOutOfRangeExceptionHelper.cs b/src/Polyfills/ArgumentOutOfRangeExceptionHelper.cs index 8c242515..1f094b4e 100644 --- a/src/Polyfills/ArgumentOutOfRangeExceptionHelper.cs +++ b/src/Polyfills/ArgumentOutOfRangeExceptionHelper.cs @@ -14,7 +14,7 @@ internal static class ArgumentOutOfRangeExceptionHelper /// Throws when is negative. /// The value to validate. /// The parameter name. - public static void ThrowIfNegative(int value, [CallerArgumentExpression(nameof(value))] string? paramName = null) + internal static void ThrowIfNegative(int value, [CallerArgumentExpression(nameof(value))] string? paramName = null) { if (value >= 0) { @@ -27,7 +27,7 @@ public static void ThrowIfNegative(int value, [CallerArgumentExpression(nameof(v /// Throws when is negative or zero. /// The value to validate. /// The parameter name. - public static void ThrowIfNegativeOrZero( + internal static void ThrowIfNegativeOrZero( int value, [CallerArgumentExpression(nameof(value))] string? paramName = null) @@ -44,7 +44,7 @@ public static void ThrowIfNegativeOrZero( /// The value to validate. /// The lower bound. /// The parameter name. - public static void ThrowIfLessThan( + internal static void ThrowIfLessThan( int value, int other, [CallerArgumentExpression(nameof(value))] @@ -69,7 +69,7 @@ public static void ThrowIfLessThan( "A type-specialized polyfill overload. The bounds-check body is identical to the int overload only " + "because the guard shape is the same; the two operate on different value types (int vs TimeSpan) and " + "cannot forward to one another. This mirrors the BCL's per-type ThrowIfLessThan overloads.")] - public static void ThrowIfLessThan( + internal static void ThrowIfLessThan( TimeSpan value, TimeSpan other, [CallerArgumentExpression(nameof(value))] @@ -87,7 +87,7 @@ public static void ThrowIfLessThan( /// The value to validate. /// The lower bound. /// The parameter name. - public static void ThrowIfLessThanOrEqual( + internal static void ThrowIfLessThanOrEqual( TimeSpan value, TimeSpan other, [CallerArgumentExpression(nameof(value))] diff --git a/src/Polyfills/CallerArgumentExpressionAttribute.cs b/src/Polyfills/CallerArgumentExpressionAttribute.cs index 7aec9d08..e84ffbcb 100644 --- a/src/Polyfills/CallerArgumentExpressionAttribute.cs +++ b/src/Polyfills/CallerArgumentExpressionAttribute.cs @@ -22,6 +22,6 @@ public CallerArgumentExpressionAttribute(string parameterName) => ParameterName = parameterName; /// Gets the name of the parameter whose expression should be captured. - public string ParameterName { get; } + internal string ParameterName { get; } } #endif diff --git a/src/Polyfills/CancellationTokenPolyfillExtensions.cs b/src/Polyfills/CancellationTokenPolyfillExtensions.cs index 859a855d..a198a31a 100644 --- a/src/Polyfills/CancellationTokenPolyfillExtensions.cs +++ b/src/Polyfills/CancellationTokenPolyfillExtensions.cs @@ -18,14 +18,14 @@ internal static class CancellationTokenPolyfillExtensions /// The delegate to invoke on cancellation. /// The state passed to . /// A registration that can be disposed to remove the callback. - public CancellationTokenRegistration UnsafeRegister(Action callback, object? state) => + internal CancellationTokenRegistration UnsafeRegister(Action callback, object? state) => token.Register(callback, state, false); /// Registers a delegate that is invoked with the triggering token when the token is cancelled, without capturing the execution context. /// The delegate to invoke on cancellation, receiving the state and the triggering token. /// The state passed to . /// A registration that can be disposed to remove the callback. - public CancellationTokenRegistration + internal CancellationTokenRegistration UnsafeRegister(Action callback, object? state) => token.Register( static boxed => @@ -44,7 +44,7 @@ public CancellationTokenRegistration { /// Removes the registered callback; on this framework the removal may wait for an in-flight callback. /// when the registration was released. - public bool Unregister() + internal bool Unregister() { registration.Dispose(); return true; diff --git a/src/Polyfills/CancellationTokenSourcePolyfillExtensions.cs b/src/Polyfills/CancellationTokenSourcePolyfillExtensions.cs index 3af3487b..8583b15b 100644 --- a/src/Polyfills/CancellationTokenSourcePolyfillExtensions.cs +++ b/src/Polyfills/CancellationTokenSourcePolyfillExtensions.cs @@ -16,7 +16,7 @@ internal static class CancellationTokenSourcePolyfillExtensions { /// Communicates a request for cancellation, completing synchronously (no asynchronous callback draining on this framework). /// A completed task representing the cancellation request. - public Task CancelAsync() + internal Task CancelAsync() { source.Cancel(); return Task.CompletedTask; diff --git a/src/Polyfills/CompilerFeatureRequiredAttribute.cs b/src/Polyfills/CompilerFeatureRequiredAttribute.cs index 5a953d96..6cccd32f 100644 --- a/src/Polyfills/CompilerFeatureRequiredAttribute.cs +++ b/src/Polyfills/CompilerFeatureRequiredAttribute.cs @@ -17,10 +17,10 @@ namespace System.Runtime.CompilerServices; internal sealed class CompilerFeatureRequiredAttribute : Attribute { /// The used for the ref structs C# feature. - public const string RefStructs = nameof(RefStructs); + internal const string RefStructs = nameof(RefStructs); /// The used for the required members C# feature. - public const string RequiredMembers = nameof(RequiredMembers); + internal const string RequiredMembers = nameof(RequiredMembers); /// Initializes a new instance of the class. /// The name of the required compiler feature. @@ -28,6 +28,6 @@ public CompilerFeatureRequiredAttribute(string featureName) => FeatureName = featureName; /// Gets the name of the required compiler feature. - public string FeatureName { get; } + internal string FeatureName { get; } } #endif diff --git a/src/Polyfills/MemberNotNullWhenAttribute.cs b/src/Polyfills/MemberNotNullWhenAttribute.cs index 3fa787f7..15c6c39a 100644 --- a/src/Polyfills/MemberNotNullWhenAttribute.cs +++ b/src/Polyfills/MemberNotNullWhenAttribute.cs @@ -26,9 +26,9 @@ public MemberNotNullWhenAttribute(bool returnValue, params string[] members) } /// Gets the return value condition for which the members are not . - public bool ReturnValue { get; } + internal bool ReturnValue { get; } /// Gets the field and property member names that are promised to be not-. - public string[] Members { get; } + internal string[] Members { get; } } #endif diff --git a/src/Polyfills/NotNullWhenAttribute.cs b/src/Polyfills/NotNullWhenAttribute.cs index b87f9c97..dfed6e13 100644 --- a/src/Polyfills/NotNullWhenAttribute.cs +++ b/src/Polyfills/NotNullWhenAttribute.cs @@ -19,6 +19,6 @@ public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; /// Gets the return value condition for which the parameter is not . - public bool ReturnValue { get; } + internal bool ReturnValue { get; } } #endif diff --git a/src/Polyfills/ObjectDisposedExceptionHelper.cs b/src/Polyfills/ObjectDisposedExceptionHelper.cs index 03f1e307..60a4dbd8 100644 --- a/src/Polyfills/ObjectDisposedExceptionHelper.cs +++ b/src/Polyfills/ObjectDisposedExceptionHelper.cs @@ -17,7 +17,7 @@ internal static class ObjectDisposedExceptionHelper /// Throws an if is . /// The condition to evaluate for a disposed instance. /// The object whose type name is used to build the exception message. - public static void ThrowIf(bool condition, object instance) + internal static void ThrowIf(bool condition, object instance) { if (!condition) { diff --git a/src/Polyfills/OverloadResolutionPriorityAttribute.cs b/src/Polyfills/OverloadResolutionPriorityAttribute.cs index 988ea152..8dc6ee00 100644 --- a/src/Polyfills/OverloadResolutionPriorityAttribute.cs +++ b/src/Polyfills/OverloadResolutionPriorityAttribute.cs @@ -18,6 +18,6 @@ namespace System.Runtime.CompilerServices; internal sealed class OverloadResolutionPriorityAttribute(int priority) : Attribute { /// Gets the overload resolution priority. - public int Priority { get; } = priority; + internal int Priority { get; } = priority; } #endif diff --git a/src/Polyfills/TaskCompletionSource.cs b/src/Polyfills/TaskCompletionSource.cs index 1c6f755e..b5b10cc2 100644 --- a/src/Polyfills/TaskCompletionSource.cs +++ b/src/Polyfills/TaskCompletionSource.cs @@ -23,23 +23,23 @@ internal sealed class TaskCompletionSource public TaskCompletionSource() => _inner = new(); /// Transitions the underlying task to the state. - public void SetResult() => _inner.SetResult(true); + internal void SetResult() => _inner.SetResult(true); /// Attempts to transition the underlying task to the state. /// if the operation was successful; otherwise . - public bool TrySetResult() => _inner.TrySetResult(true); + internal bool TrySetResult() => _inner.TrySetResult(true); /// Transitions the underlying task to the state with the specified exception. /// The exception to bind to the task. - public void SetException(Exception exception) => _inner.SetException(exception); + internal void SetException(Exception exception) => _inner.SetException(exception); /// Attempts to transition the underlying task to the state with the specified exception. /// The exception to bind to the task. /// if the operation was successful; otherwise . - public bool TrySetException(Exception exception) => _inner.TrySetException(exception); + internal bool TrySetException(Exception exception) => _inner.TrySetException(exception); /// Transitions the underlying task to the state. - public void SetCanceled() => _inner.TrySetCanceled(); + internal void SetCanceled() => _inner.TrySetCanceled(); /// Attempts to transition the underlying task to the state. /// if the operation was successful; otherwise . @@ -50,11 +50,11 @@ internal sealed class TaskCompletionSource "Distinct BCL-parity surface members with different contracts and return types: SetCanceled is void and " + "TrySetCanceled returns bool. They share the same backing call here but must track the framework surface " + "independently, so they are deliberately kept as separate members rather than one forwarding to the other.")] - public bool TrySetCanceled() => _inner.TrySetCanceled(); + internal bool TrySetCanceled() => _inner.TrySetCanceled(); /// Attempts to transition the underlying task to the state for the specified token. /// The token associated with the cancellation. /// if the operation was successful; otherwise . - public bool TrySetCanceled(CancellationToken cancellationToken) => _inner.TrySetCanceled(cancellationToken); + internal bool TrySetCanceled(CancellationToken cancellationToken) => _inner.TrySetCanceled(cancellationToken); } #endif diff --git a/src/Polyfills/TaskPolyfillExtensions.cs b/src/Polyfills/TaskPolyfillExtensions.cs index 0eac770b..83ba3fa9 100644 --- a/src/Polyfills/TaskPolyfillExtensions.cs +++ b/src/Polyfills/TaskPolyfillExtensions.cs @@ -18,7 +18,7 @@ internal static class TaskPolyfillExtensions /// The timeout after which the returned task faults, or for no timeout. /// A token that cancels the wait. /// A task that mirrors subject to the timeout and cancellation. - public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) + internal async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) { await WaitForCompletionAsync(task, timeout, cancellationToken).ConfigureAwait(false); await task.ConfigureAwait(false); @@ -34,7 +34,7 @@ public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToke /// The timeout after which the returned task faults, or for no timeout. /// A token that cancels the wait. /// A task that mirrors subject to the timeout and cancellation. - public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) + internal async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) { await WaitForCompletionAsync(task, timeout, cancellationToken).ConfigureAwait(false); return await task.ConfigureAwait(false); diff --git a/src/Primitives.Extensions.Shared/Internal/ScheduledDrainState.cs b/src/Primitives.Extensions.Shared/Internal/ScheduledDrainState.cs index 9d737168..82c7df32 100644 --- a/src/Primitives.Extensions.Shared/Internal/ScheduledDrainState.cs +++ b/src/Primitives.Extensions.Shared/Internal/ScheduledDrainState.cs @@ -43,22 +43,22 @@ internal sealed class ScheduledDrainState(ISequencer scheduler, IDrainTarget /// Gets a value indicating whether the sink has reached a terminal state. Read inside /// the sink's gate by callers that need to short-circuit once terminated. - public bool Done => _done; + internal bool Done => _done; /// Enqueues an OnNext notification and schedules a drain pass if one isn't already running. /// The value to forward downstream. - public void EnqueueNext(T value) => Enqueue(new(DrainNotificationKind.Next, value, null)); + internal void EnqueueNext(T value) => Enqueue(new(DrainNotificationKind.Next, value, null)); /// Enqueues an OnError notification and schedules a drain pass if one isn't already running. /// The error to forward downstream. - public void EnqueueError(Exception error) => Enqueue(new(DrainNotificationKind.Error, default!, error)); + internal void EnqueueError(Exception error) => Enqueue(new(DrainNotificationKind.Error, default!, error)); /// Enqueues an OnCompleted notification and schedules a drain pass if one isn't already running. - public void EnqueueCompleted() => Enqueue(new(DrainNotificationKind.Completed, default!, null)); + internal void EnqueueCompleted() => Enqueue(new(DrainNotificationKind.Completed, default!, null)); /// Records the upstream subscription, or disposes it immediately if the sink is already done. /// The upstream subscription handle. - public void Attach(IDisposable subscription) + internal void Attach(IDisposable subscription) { lock (_gate) { @@ -75,7 +75,7 @@ public void Attach(IDisposable subscription) /// Dequeues the next pending notification, clearing the drain flag when the queue empties or the sink has terminated. /// The dequeued notification when this returns . /// if a notification was dequeued; otherwise . - public bool TryDequeue(out Notification notification) + internal bool TryDequeue(out Notification notification) { lock (_gate) { @@ -92,7 +92,7 @@ public bool TryDequeue(out Notification notification) } /// Marks the sink terminated and drops any still-queued notifications. Locks the gate. - public void Terminate() + internal void Terminate() { lock (_gate) { @@ -103,13 +103,13 @@ public void Terminate() /// Marks the sink terminated without clearing the queue. Caller must hold the gate; /// the still-queued notifications are abandoned because checks the done flag first. - public void MarkDoneLocked() => _done = true; + internal void MarkDoneLocked() => _done = true; /// Begins disposal under the gate: marks the sink done, clears the queue, and detaches /// the upstream subscription — returned to the caller so it is disposed outside the gate. Returns /// when already disposed. /// The upstream subscription to dispose outside the gate, or . - public IDisposable? BeginDispose() + internal IDisposable? BeginDispose() { lock (_gate) { @@ -122,7 +122,7 @@ public void Terminate() /// . Lets a composing sink dispose its own scheduled-work slot atomically with the /// done transition under the same lock. /// The upstream subscription to dispose outside the gate, or . - public IDisposable? BeginDisposeLocked() + internal IDisposable? BeginDisposeLocked() { _done = true; _queue.Clear(); diff --git a/src/Primitives.Extensions.Shared/Internal/SequencerPeriodicExtensions.cs b/src/Primitives.Extensions.Shared/Internal/SequencerPeriodicExtensions.cs index 8d5afadc..be74d6c7 100644 --- a/src/Primitives.Extensions.Shared/Internal/SequencerPeriodicExtensions.cs +++ b/src/Primitives.Extensions.Shared/Internal/SequencerPeriodicExtensions.cs @@ -23,7 +23,7 @@ internal static class SequencerPeriodicExtensions /// The period between ticks. /// The tick action. /// A disposable that cancels future ticks. - public IDisposable SchedulePeriodic( + internal IDisposable SchedulePeriodic( TState state, TimeSpan period, Action action) => @@ -34,7 +34,7 @@ public IDisposable SchedulePeriodic( /// The period between ticks. /// The tick action. /// A disposable that cancels future ticks. - public IDisposable SchedulePeriodic( + internal IDisposable SchedulePeriodic( TimeSpan dueTime, TimeSpan period, Action action) => @@ -97,9 +97,20 @@ internal sealed class PeriodicSubscription( /// 0 = active, 1 = disposed. private int _disposed; + /// + public void Dispose() + { + if (Interlocked.Exchange(ref _disposed, 1) != 0) + { + return; + } + + _scheduled.Dispose(); + } + /// Schedules the next tick. /// The delay before the tick. - public void ScheduleNext(TimeSpan dueTime) + internal void ScheduleNext(TimeSpan dueTime) { if (Volatile.Read(ref _disposed) != 0) { @@ -113,17 +124,6 @@ public void ScheduleNext(TimeSpan dueTime) }); } - /// - public void Dispose() - { - if (Interlocked.Exchange(ref _disposed, 1) != 0) - { - return; - } - - _scheduled.Dispose(); - } - /// Runs a tick and schedules the next one when still active. internal void Tick() { diff --git a/src/Primitives.Extensions.Shared/ObservableSubscriptionExtensions.cs b/src/Primitives.Extensions.Shared/ObservableSubscriptionExtensions.cs index a8d11368..7f3fa521 100644 --- a/src/Primitives.Extensions.Shared/ObservableSubscriptionExtensions.cs +++ b/src/Primitives.Extensions.Shared/ObservableSubscriptionExtensions.cs @@ -285,15 +285,8 @@ private sealed class ValueCaptureWitness : IObserver /// Gets the captured value, or if none. public T? Value { get; private set; } - /// Gets a value indicating whether at least one value was observed. - public bool HasValue { get; private set; } - /// - public void OnNext(T value) - { - Value = value; - HasValue = true; - } + public void OnNext(T value) => Value = value; /// public void OnError(Exception error) diff --git a/src/Primitives.Extensions.Shared/Operators/ConflateObservable.cs b/src/Primitives.Extensions.Shared/Operators/ConflateObservable.cs index 46721e1c..e09be721 100644 --- a/src/Primitives.Extensions.Shared/Operators/ConflateObservable.cs +++ b/src/Primitives.Extensions.Shared/Operators/ConflateObservable.cs @@ -83,10 +83,6 @@ public ConflateSink(IObserver downstream, TimeSpan minimumUpdatePeriod, ISequ _state = new(scheduler, this, _gate); } - /// Records the upstream subscription so can tear it down. - /// The upstream subscription handle. - public void AttachSourceSubscription(IDisposable subscription) => _state.Attach(subscription); - /// public void OnNext(T value) => _state.EnqueueNext(value); @@ -144,6 +140,10 @@ void IDrainTarget.Drain() } } + /// Records the upstream subscription so can tear it down. + /// The upstream subscription handle. + internal void AttachSourceSubscription(IDisposable subscription) => _state.Attach(subscription); + /// Applies the throttle-window decision to a dequeued value and either emits inline or /// schedules a deferred emission. The emission bodies live in covered helpers; only this /// race-guarded shell (whose already-done early-out is reachable only when a concurrent dispose diff --git a/src/Primitives.Extensions.Shared/Operators/ScheduleConfig.cs b/src/Primitives.Extensions.Shared/Operators/ScheduleConfig.cs index 20e30125..28b7181a 100644 --- a/src/Primitives.Extensions.Shared/Operators/ScheduleConfig.cs +++ b/src/Primitives.Extensions.Shared/Operators/ScheduleConfig.cs @@ -34,30 +34,30 @@ internal readonly record struct ScheduleConfig( /// Creates a config with no delay, no transform, no action. /// The scheduler used to dispatch emissions. /// A new configuration. - public static ScheduleConfig Immediate(ISequencer scheduler) => + internal static ScheduleConfig Immediate(ISequencer scheduler) => new(scheduler, false, false, TimeSpan.Zero, default, null, null); /// Creates a config with a relative delay. /// The scheduler used to dispatch emissions. /// The relative delay before each emission. /// A new configuration. - public static ScheduleConfig Delayed(ISequencer scheduler, TimeSpan dueTime) => + internal static ScheduleConfig Delayed(ISequencer scheduler, TimeSpan dueTime) => new(scheduler, true, false, dueTime, default, null, null); /// Creates a config with an absolute due time. /// The scheduler used to dispatch emissions. /// The absolute time at which each emission fires. /// A new configuration. - public static ScheduleConfig Absolute(ISequencer scheduler, DateTimeOffset absoluteDueTime) => + internal static ScheduleConfig Absolute(ISequencer scheduler, DateTimeOffset absoluteDueTime) => new(scheduler, true, true, TimeSpan.Zero, absoluteDueTime, null, null); /// Returns a new config with the supplied transform applied to each value before emission. /// The transform. /// A new configuration. - public ScheduleConfig WithTransform(Func transform) => this with { Transform = transform }; + internal ScheduleConfig WithTransform(Func transform) => this with { Transform = transform }; /// Returns a new config with the supplied side-effect invoked with each value before emission. /// The action. /// A new configuration. - public ScheduleConfig WithAction(Action action) => this with { Action = action }; + internal ScheduleConfig WithAction(Action action) => this with { Action = action }; } diff --git a/src/Primitives.Extensions.Shared/Operators/SyncTimerObservable.cs b/src/Primitives.Extensions.Shared/Operators/SyncTimerObservable.cs index fed897f6..e6c99001 100644 --- a/src/Primitives.Extensions.Shared/Operators/SyncTimerObservable.cs +++ b/src/Primitives.Extensions.Shared/Operators/SyncTimerObservable.cs @@ -31,7 +31,7 @@ internal static class SyncTimerObservable /// The period. /// The scheduler. /// A shared observable sequence of timer ticks. - public static IObservable Get(TimeSpan timeSpan, ISequencer scheduler) + internal static IObservable Get(TimeSpan timeSpan, ISequencer scheduler) { ArgumentExceptionHelper.ThrowIfNull(scheduler); diff --git a/src/Primitives.Platform.Reactive.Shared/DispatchWorkItemBase.cs b/src/Primitives.Platform.Reactive.Shared/DispatchWorkItemBase.cs index c3630041..326c5188 100644 --- a/src/Primitives.Platform.Reactive.Shared/DispatchWorkItemBase.cs +++ b/src/Primitives.Platform.Reactive.Shared/DispatchWorkItemBase.cs @@ -51,10 +51,10 @@ protected DispatchWorkItemBase( } /// Gets a value indicating whether the work item has been cancelled. - public bool IsDisposed => Volatile.Read(ref _isDisposed) != 0; + internal bool IsDisposed => Volatile.Read(ref _isDisposed) != 0; /// Runs the scheduled action unless it has already been cancelled, disposing its result if a cancel races the start. - public void Run() + internal void Run() { if (IsDisposed) { diff --git a/src/Primitives.Shared/Advanced/CurrentThreadRequirement.cs b/src/Primitives.Shared/Advanced/CurrentThreadRequirement.cs index adba03a8..30fbb5b9 100644 --- a/src/Primitives.Shared/Advanced/CurrentThreadRequirement.cs +++ b/src/Primitives.Shared/Advanced/CurrentThreadRequirement.cs @@ -15,7 +15,7 @@ internal static class CurrentThreadRequirement /// The source value type. /// The source observable. /// when the source requires current-thread subscription. - public static bool IsRequired(IObservable source) => source is not IRequireCurrentThread currentThread + internal static bool IsRequired(IObservable source) => source is not IRequireCurrentThread currentThread ? false : currentThread.IsRequiredSubscribeOnCurrentThread(); } diff --git a/src/Primitives.Shared/Advanced/FromAsyncExternalCancellation{T}.cs b/src/Primitives.Shared/Advanced/FromAsyncExternalCancellation{T}.cs index a167b4e2..1c9c8f67 100644 --- a/src/Primitives.Shared/Advanced/FromAsyncExternalCancellation{T}.cs +++ b/src/Primitives.Shared/Advanced/FromAsyncExternalCancellation{T}.cs @@ -31,7 +31,7 @@ public FromAsyncExternalCancellation( } /// Gets a value indicating whether the external token can cancel. - public bool CanBeCanceled => CancellationToken.CanBeCanceled; + internal bool CanBeCanceled => CancellationToken.CanBeCanceled; /// Gets the downstream observer. private IObserver Observer { get; } @@ -51,12 +51,12 @@ public FromAsyncExternalCancellation( /// Creates a linked source for subscription disposal and external cancellation. /// The subscription-owned cancellation token. /// A linked cancellation token source. - public CancellationTokenSource CreateLinkedSource(CancellationToken subscriptionToken) => + internal CancellationTokenSource CreateLinkedSource(CancellationToken subscriptionToken) => CancellationTokenSource.CreateLinkedTokenSource(CancellationToken, subscriptionToken); /// Registers external cancellation. /// when the subscription should continue starting. - public bool Start() + internal bool Start() { if (!CancellationToken.CanBeCanceled) { @@ -70,7 +70,7 @@ public bool Start() /// Attempts to forward external cancellation as a terminal error. /// when external cancellation was forwarded. - public bool TryForwardCancellation() + internal bool TryForwardCancellation() { if (!CancellationToken.IsCancellationRequested || Lifetime.IsCancellationRequested) { diff --git a/src/Primitives.Shared/Advanced/FromAsyncTaskObservation{T}.cs b/src/Primitives.Shared/Advanced/FromAsyncTaskObservation{T}.cs index 6ae8b778..5f1313f0 100644 --- a/src/Primitives.Shared/Advanced/FromAsyncTaskObservation{T}.cs +++ b/src/Primitives.Shared/Advanced/FromAsyncTaskObservation{T}.cs @@ -49,7 +49,7 @@ public FromAsyncTaskObservation( /// Observes the completed task and forwards its terminal result unless the subscription was disposed. /// The task to observe. - public void Observe(Task task) + internal void Observe(Task task) { try { diff --git a/src/Primitives.Shared/Advanced/LoopSignal{T}.cs b/src/Primitives.Shared/Advanced/LoopSignal{T}.cs index 392599d3..8eb96025 100644 --- a/src/Primitives.Shared/Advanced/LoopSignal{T}.cs +++ b/src/Primitives.Shared/Advanced/LoopSignal{T}.cs @@ -24,7 +24,7 @@ public LoopSignal(T value) } /// Gets the repeated value. - public T Value { get; } + internal T Value { get; } /// Executes the IsRequiredSubscribeOnCurrentThread operation. /// The result. diff --git a/src/Primitives.Shared/Advanced/RangeLatestSignalHelper.cs b/src/Primitives.Shared/Advanced/RangeLatestSignalHelper.cs index 8b507bc5..cff9a53c 100644 --- a/src/Primitives.Shared/Advanced/RangeLatestSignalHelper.cs +++ b/src/Primitives.Shared/Advanced/RangeLatestSignalHelper.cs @@ -18,7 +18,7 @@ internal static class RangeLatestSignalHelper /// The right range. /// The result projection. /// The completed subscription disposable. - public static IDisposable Subscribe( + internal static IDisposable Subscribe( IObserver observer, RangeSignal left, RangeSignal right, diff --git a/src/Primitives.Shared/Advanced/SubscriptionScheduling.cs b/src/Primitives.Shared/Advanced/SubscriptionScheduling.cs index 21dbd8bf..19f4d726 100644 --- a/src/Primitives.Shared/Advanced/SubscriptionScheduling.cs +++ b/src/Primitives.Shared/Advanced/SubscriptionScheduling.cs @@ -24,7 +24,7 @@ internal static class SubscriptionScheduling /// The state carried to . /// Performs the subscription and returns it. /// The subscription, or a placeholder that receives it once the queued work runs. - public static IDisposable OnCurrentThread(TState state, Func subscribe) + internal static IDisposable OnCurrentThread(TState state, Func subscribe) { if (!CurrentThreadSequencer.IsScheduleRequired) { @@ -51,7 +51,7 @@ public static IDisposable OnCurrentThread(TState state, FuncThe state carried to . /// The work to run, which emits to the downstream observer. /// The disposable used to cancel the scheduled work (best effort). - public static IDisposable RunOn(ISequencer sequencer, TState state, Action run) + internal static IDisposable RunOn(ISequencer sequencer, TState state, Action run) { if (sequencer == Sequencer.Immediate) { diff --git a/src/Primitives.Shared/Advanced/TaskChainCoordinatorState.cs b/src/Primitives.Shared/Advanced/TaskChainCoordinatorState.cs index e7a0a376..51d7fe5f 100644 --- a/src/Primitives.Shared/Advanced/TaskChainCoordinatorState.cs +++ b/src/Primitives.Shared/Advanced/TaskChainCoordinatorState.cs @@ -17,7 +17,7 @@ internal static class TaskChainCoordinatorState /// A value indicating whether a terminal notification has already been emitted. /// A value indicating whether an inner source is active. /// The coordinator to drain. - public static void OnInnerCompleted( + internal static void OnInnerCompleted( Lock gate, ref bool done, ref bool active, diff --git a/src/Primitives.Shared/Advanced/TaskTerminalWitnessHelper.cs b/src/Primitives.Shared/Advanced/TaskTerminalWitnessHelper.cs index ffff0ddc..996c6acb 100644 --- a/src/Primitives.Shared/Advanced/TaskTerminalWitnessHelper.cs +++ b/src/Primitives.Shared/Advanced/TaskTerminalWitnessHelper.cs @@ -13,13 +13,13 @@ internal static class TaskTerminalWitnessHelper { /// Disposes the source subscription. /// The subscription slot. - public static void DisposeSubscription(ref IDisposable? subscription) => + internal static void DisposeSubscription(ref IDisposable? subscription) => Interlocked.Exchange(ref subscription, null)?.Dispose(); /// Gets a value indicating whether a witness has stopped. /// The stopped flag. /// when the sink has stopped. - public static bool IsStopped(ref int stopped) => Volatile.Read(ref stopped) != 0; + internal static bool IsStopped(ref int stopped) => Volatile.Read(ref stopped) != 0; /// Registers cancellation for a witness after construction. /// The state type. @@ -27,7 +27,7 @@ public static void DisposeSubscription(ref IDisposable? subscription) => /// The cancellation callback. /// The registration slot. /// The cancellation token. - public static void RegisterCancellation( + internal static void RegisterCancellation( TState state, Action callback, ref CancellationTokenRegistration registration, @@ -46,7 +46,7 @@ public static void RegisterCancellation( /// The subscription slot. /// The stopped flag. /// The subscription to assign. - public static void SetSubscription(ref IDisposable? subscription, ref int stopped, IDisposable value) + internal static void SetSubscription(ref IDisposable? subscription, ref int stopped, IDisposable value) { ArgumentExceptionHelper.ThrowIfNull(value); @@ -67,5 +67,5 @@ public static void SetSubscription(ref IDisposable? subscription, ref int stoppe /// Attempts to mark a witness stopped. /// The stopped flag. /// when this caller won the stop race. - public static bool TryStop(ref int stopped) => Interlocked.Exchange(ref stopped, 1) == 0; + internal static bool TryStop(ref int stopped) => Interlocked.Exchange(ref stopped, 1) == 0; } diff --git a/src/Primitives.Shared/Advanced/WitnessLifetime.cs b/src/Primitives.Shared/Advanced/WitnessLifetime.cs index f3f37afe..128c2836 100644 --- a/src/Primitives.Shared/Advanced/WitnessLifetime.cs +++ b/src/Primitives.Shared/Advanced/WitnessLifetime.cs @@ -14,7 +14,7 @@ internal static class WitnessLifetime /// Disposes a sink and its upstream subscription. /// The stopped flag. /// The upstream subscription slot. - public static void Dispose(ref int stopped, SingleReplaceableDisposable subscription) + internal static void Dispose(ref int stopped, SingleReplaceableDisposable subscription) { _ = Interlocked.Exchange(ref stopped, 1); subscription.Dispose(); @@ -23,7 +23,7 @@ public static void Dispose(ref int stopped, SingleReplaceableDisposable subscrip /// Releases the cancellation resource and marks the witness stopped. /// The slot that owns the cancellation resource. /// The stopped flag. - public static void Dispose(ref IDisposable? cancelSlot, ref int stopped) + internal static void Dispose(ref IDisposable? cancelSlot, ref int stopped) { Interlocked.Exchange(ref cancelSlot, null)?.Dispose(); Volatile.Write(ref stopped, 1); @@ -32,13 +32,13 @@ public static void Dispose(ref IDisposable? cancelSlot, ref int stopped) /// Gets a value indicating whether the sink has stopped. /// The stopped flag. /// when the sink has stopped. - public static bool IsStopped(ref int stopped) => Volatile.Read(ref stopped) != 0; + internal static bool IsStopped(ref int stopped) => Volatile.Read(ref stopped) != 0; /// Assigns the upstream subscription and disposes it when the sink has already stopped. /// The stopped flag. /// The upstream subscription slot. /// The upstream subscription. - public static void SetSubscription(ref int stopped, SingleReplaceableDisposable slot, IDisposable subscription) + internal static void SetSubscription(ref int stopped, SingleReplaceableDisposable slot, IDisposable subscription) { slot.Create(subscription); if (Volatile.Read(ref stopped) == 0) @@ -54,7 +54,7 @@ public static void SetSubscription(ref int stopped, SingleReplaceableDisposable /// The stopped flag. /// The upstream subscription slot. /// The downstream observer. - public static void Complete( + internal static void Complete( ref int stopped, SingleReplaceableDisposable subscription, IObserver observer) @@ -74,7 +74,7 @@ public static void Complete( /// The upstream subscription slot. /// The downstream observer. /// The terminal error. - public static void Error( + internal static void Error( ref int stopped, SingleReplaceableDisposable subscription, IObserver observer, @@ -93,7 +93,7 @@ public static void Error( /// The slot that owns the cancellation resource. /// The stopped flag. /// The cancellation resource to assign. - public static void SetCancel(ref IDisposable? cancelSlot, ref int stopped, IDisposable cancel) + internal static void SetCancel(ref IDisposable? cancelSlot, ref int stopped, IDisposable cancel) { ArgumentExceptionHelper.ThrowIfNull(cancel); @@ -118,7 +118,7 @@ public static void SetCancel(ref IDisposable? cancelSlot, ref int stopped, IDisp /// The witness owner. /// The value to forward. /// The forwarding action. - public static void OnNext(ref int stopped, TOwner owner, T value, Action forward) + internal static void OnNext(ref int stopped, TOwner owner, T value, Action forward) where TOwner : class { if (Volatile.Read(ref stopped) != 0) @@ -136,7 +136,7 @@ public static void OnNext(ref int stopped, TOwner owner, T value, Act /// The error to forward. /// The error forwarding action. /// The owner disposal action. - public static void OnError( + internal static void OnError( ref int stopped, TOwner owner, Exception error, @@ -165,7 +165,7 @@ public static void OnError( /// The witness owner. /// The completion forwarding action. /// The owner disposal action. - public static void OnCompleted( + internal static void OnCompleted( ref int stopped, TOwner owner, Action forward, diff --git a/src/Primitives.Shared/Concurrency/SequencerWorkItem.cs b/src/Primitives.Shared/Concurrency/SequencerWorkItem.cs index 6498dad7..0ca3d771 100644 --- a/src/Primitives.Shared/Concurrency/SequencerWorkItem.cs +++ b/src/Primitives.Shared/Concurrency/SequencerWorkItem.cs @@ -44,7 +44,7 @@ public void Dispose() } /// Invokes the scheduled action if it has not been cancelled. - public void Invoke() + internal void Invoke() { if (Volatile.Read(ref _isDisposed) != 0) { diff --git a/src/Primitives.Shared/Core/SparkCompositeFormats.cs b/src/Primitives.Shared/Core/SparkCompositeFormats.cs index 5a3220e3..744088cd 100644 --- a/src/Primitives.Shared/Core/SparkCompositeFormats.cs +++ b/src/Primitives.Shared/Core/SparkCompositeFormats.cs @@ -19,9 +19,9 @@ namespace ReactiveUI.Primitives.Core; internal static class SparkCompositeFormats { /// The format an spark renders with. - public static readonly CompositeFormat OnNext = CompositeFormat.Parse("OnNext({0})"); + internal static readonly CompositeFormat OnNext = CompositeFormat.Parse("OnNext({0})"); /// The format an spark renders with. - public static readonly CompositeFormat OnError = CompositeFormat.Parse("OnError({0})"); + internal static readonly CompositeFormat OnError = CompositeFormat.Parse("OnError({0})"); } #endif diff --git a/src/Primitives.Shared/Handle.cs b/src/Primitives.Shared/Handle.cs index f7edd0a2..45a1610c 100644 --- a/src/Primitives.Shared/Handle.cs +++ b/src/Primitives.Shared/Handle.cs @@ -12,10 +12,10 @@ namespace ReactiveUI.Primitives; internal static class Handle { /// Action that does nothing. - public static readonly Action Nop = static () => { }; + internal static readonly Action Nop = static () => { }; /// Error handler that throws the supplied exception. - public static readonly Action Throw = static ex => ex.Throw(); + internal static readonly Action Throw = static ex => ex.Throw(); /// Converts an error into an empty observable sequence. /// The source value type. @@ -26,6 +26,6 @@ internal static class Handle "SST2307:Generic method type parameters should be inferable from the parameters", Justification = "The type parameter defines the element type for this Rx-style factory and cannot be inferred from the arguments.")] - public static IObservable CatchIgnore(Exception ex) => + internal static IObservable CatchIgnore(Exception ex) => Signal.None(); } diff --git a/src/Primitives.Shared/Signals/TaskSignal{T}.cs b/src/Primitives.Shared/Signals/TaskSignal{T}.cs index 7f825fbb..88070d46 100644 --- a/src/Primitives.Shared/Signals/TaskSignal{T}.cs +++ b/src/Primitives.Shared/Signals/TaskSignal{T}.cs @@ -52,28 +52,6 @@ private TaskSignal(ISequencer? sequencer, CancellationTokenSource? cancellationT /// Gets a value indicating whether gets a value that indicates whether the object is disposed. public bool IsDisposed => _cleanUp.IsDisposed; - /// Creates a task-backed signal whose source the supplied factory builds. - /// The observable factory. - /// The sequencer. - /// The cancellation token source. - /// The created signal. - /// - /// The factory is handed the signal, so the signal has to be whole before it runs. A factory is - /// caller-supplied code that may subscribe to, dispose, or stash the signal the moment it receives - /// it; from a constructor it would be doing that to an object the runtime had not finished building. - /// - public static TaskSignal Create( - Func, IObservable> observableFactory, - ISequencer? sequencer = null, - CancellationTokenSource? cancellationTokenSource = null) - { - ArgumentExceptionHelper.ThrowIfNull(observableFactory); - - TaskSignal signal = new(sequencer, cancellationTokenSource); - signal.Source = observableFactory(signal); - return signal; - } - /// Gets the operation canceled. /// The observer. public void GetOperationCanceled(IObserver observer) => @@ -96,6 +74,28 @@ public IDisposable Subscribe(IObserver observer) /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public void Dispose() => Dispose(true); + /// Creates a task-backed signal whose source the supplied factory builds. + /// The observable factory. + /// The sequencer. + /// The cancellation token source. + /// The created signal. + /// + /// The factory is handed the signal, so the signal has to be whole before it runs. A factory is + /// caller-supplied code that may subscribe to, dispose, or stash the signal the moment it receives + /// it; from a constructor it would be doing that to an object the runtime had not finished building. + /// + internal static TaskSignal Create( + Func, IObservable> observableFactory, + ISequencer? sequencer = null, + CancellationTokenSource? cancellationTokenSource = null) + { + ArgumentExceptionHelper.ThrowIfNull(observableFactory); + + TaskSignal signal = new(sequencer, cancellationTokenSource); + signal.Source = observableFactory(signal); + return signal; + } + /// Releases unmanaged and - optionally - managed resources. /// true to release both managed and unmanaged resources; false to release only unmanaged resources. private void Dispose(bool disposing) diff --git a/src/Primitives.Shared/Signals/TaskTerminalCompletion{T}.cs b/src/Primitives.Shared/Signals/TaskTerminalCompletion{T}.cs index caecf89a..dfdc3577 100644 --- a/src/Primitives.Shared/Signals/TaskTerminalCompletion{T}.cs +++ b/src/Primitives.Shared/Signals/TaskTerminalCompletion{T}.cs @@ -31,11 +31,11 @@ internal sealed class TaskTerminalCompletion private CancellationToken _cancellationToken; /// Gets the task completed by the terminal. - public Task Task => _completion.Task; + internal Task Task => _completion.Task; /// Completes the task with a value and releases the subscription and registration. /// The result value. - public void Resolve(T value) + internal void Resolve(T value) { Release(); _ = _completion.TrySetResult(value); @@ -43,14 +43,14 @@ public void Resolve(T value) /// Faults the task and releases the subscription and registration. /// The error. - public void Fail(Exception error) + internal void Fail(Exception error) { Release(); _ = _completion.TrySetException(error); } /// Faults the task with the shared empty-source error and releases the subscription and registration. - public void FailEmpty() => + internal void FailEmpty() => Fail(new InvalidOperationException("The source completed without producing a value.")); /// @@ -60,7 +60,7 @@ public void FailEmpty() => /// The source subscription. /// The token that cancels the task and disposes the subscription. /// The terminal task. - public Task Attach(IDisposable subscription, CancellationToken cancellationToken) + internal Task Attach(IDisposable subscription, CancellationToken cancellationToken) { _subscription = subscription; if (_completion.Task.IsCompleted) diff --git a/src/ReactiveUI.Primitives.Reactive/Concurrency/CurrentThreadSequencer.cs b/src/ReactiveUI.Primitives.Reactive/Concurrency/CurrentThreadSequencer.cs index 0f7d7559..5bc4e9b1 100644 --- a/src/ReactiveUI.Primitives.Reactive/Concurrency/CurrentThreadSequencer.cs +++ b/src/ReactiveUI.Primitives.Reactive/Concurrency/CurrentThreadSequencer.cs @@ -10,8 +10,8 @@ namespace ReactiveUI.Primitives.Reactive.Concurrency; internal static class CurrentThreadSequencer { /// Gets the singleton current-thread scheduler. - public static IScheduler Instance => CurrentThreadScheduler.Instance; + internal static IScheduler Instance => CurrentThreadScheduler.Instance; /// Gets a value indicating whether the caller must call a Schedule method. - public static bool IsScheduleRequired => CurrentThreadScheduler.IsScheduleRequired; + internal static bool IsScheduleRequired => CurrentThreadScheduler.IsScheduleRequired; } diff --git a/src/ReactiveUI.Primitives.Reactive/Concurrency/Sequencer.cs b/src/ReactiveUI.Primitives.Reactive/Concurrency/Sequencer.cs index 93a3b3ac..2f48d8d4 100644 --- a/src/ReactiveUI.Primitives.Reactive/Concurrency/Sequencer.cs +++ b/src/ReactiveUI.Primitives.Reactive/Concurrency/Sequencer.cs @@ -11,16 +11,16 @@ namespace ReactiveUI.Primitives.Reactive.Concurrency; internal static class Sequencer { /// Gets a scheduler that schedules work as soon as possible on the current thread. - public static IScheduler CurrentThread => CurrentThreadScheduler.Instance; + internal static IScheduler CurrentThread => CurrentThreadScheduler.Instance; /// Gets a scheduler that schedules work immediately on the current thread. - public static IScheduler Immediate => ImmediateScheduler.Instance; + internal static IScheduler Immediate => ImmediateScheduler.Instance; /// Gets the default queueing scheduler for background work. - public static IScheduler Default => TaskPoolScheduler.Default; + internal static IScheduler Default => TaskPoolScheduler.Default; /// Normalizes the specified value to a positive value. /// The value to normalize. /// when zero or positive; otherwise . - public static TimeSpan Normalize(TimeSpan timeSpan) => RxScheduler.Normalize(timeSpan); + internal static TimeSpan Normalize(TimeSpan timeSpan) => RxScheduler.Normalize(timeSpan); } diff --git a/src/ReactiveUI.Primitives.Reactive/Concurrency/SequencerSchedulingExtensions.cs b/src/ReactiveUI.Primitives.Reactive/Concurrency/SequencerSchedulingExtensions.cs index 2ba92918..17d678dd 100644 --- a/src/ReactiveUI.Primitives.Reactive/Concurrency/SequencerSchedulingExtensions.cs +++ b/src/ReactiveUI.Primitives.Reactive/Concurrency/SequencerSchedulingExtensions.cs @@ -22,7 +22,7 @@ internal static class SequencerSchedulingExtensions { /// Schedules a work item for execution as soon as possible. /// Work item to execute. - public void Schedule(IWorkItem item) + internal void Schedule(IWorkItem item) { ArgumentExceptionHelper.ThrowIfNull(item); @@ -36,33 +36,33 @@ public void Schedule(IWorkItem item) /// Schedules an action to be executed. /// Action to execute. /// The disposable used to cancel the scheduled action. - public IDisposable Schedule(Action action) => RxScheduler.Schedule(scheduler, action); + internal IDisposable Schedule(Action action) => RxScheduler.Schedule(scheduler, action); /// Schedules an action to be executed after a relative due time. /// Relative time after which to execute the action. /// Action to execute. /// The disposable used to cancel the scheduled action. - public IDisposable Schedule(TimeSpan dueTime, Action action) => + internal IDisposable Schedule(TimeSpan dueTime, Action action) => RxScheduler.Schedule(scheduler, dueTime, action); /// Schedules an action to be executed at an absolute due time. /// Absolute time at which to execute the action. /// Action to execute. /// The disposable used to cancel the scheduled action. - public IDisposable Schedule(DateTimeOffset dueTime, Action action) => + internal IDisposable Schedule(DateTimeOffset dueTime, Action action) => RxScheduler.Schedule(scheduler, dueTime, action); /// Schedules a recursive action to be executed. /// Recursive action; invoke its argument to schedule the next iteration. /// The disposable used to cancel recursive work. - public IDisposable Schedule(Action action) => RxScheduler.Schedule(scheduler, action); + internal IDisposable Schedule(Action action) => RxScheduler.Schedule(scheduler, action); /// Schedules a stateful action without capturing state in a closure. /// The type of the state passed to the action. /// State passed to the action. /// Action to execute. /// The disposable used to cancel the scheduled action. - public IDisposable Schedule(TState state, Action action) => + internal IDisposable Schedule(TState state, Action action) => scheduler.Schedule((state, action), static (_, tuple) => { tuple.action(tuple.state); @@ -75,7 +75,7 @@ public IDisposable Schedule(TState state, Action action) => /// Relative time after which to execute the action. /// Action to execute. /// The disposable used to cancel the scheduled action. - public IDisposable Schedule(TState state, TimeSpan dueTime, Action action) => + internal IDisposable Schedule(TState state, TimeSpan dueTime, Action action) => scheduler.Schedule((state, action), dueTime, static (_, tuple) => { tuple.action(tuple.state); diff --git a/src/ReactiveUI.Primitives.Reactive/Concurrency/ThreadPoolSequencer.cs b/src/ReactiveUI.Primitives.Reactive/Concurrency/ThreadPoolSequencer.cs index 678bf955..1e3505b4 100644 --- a/src/ReactiveUI.Primitives.Reactive/Concurrency/ThreadPoolSequencer.cs +++ b/src/ReactiveUI.Primitives.Reactive/Concurrency/ThreadPoolSequencer.cs @@ -10,5 +10,5 @@ namespace ReactiveUI.Primitives.Reactive.Concurrency; internal static class ThreadPoolSequencer { /// Gets the shared thread-pool scheduler instance. - public static IScheduler Instance => ThreadPoolScheduler.Instance; + internal static IScheduler Instance => ThreadPoolScheduler.Instance; } diff --git a/src/ReactiveUI.Primitives.Reactive/Concurrency/WasmScheduler.cs b/src/ReactiveUI.Primitives.Reactive/Concurrency/WasmScheduler.cs index b0e3a225..ae09b76b 100644 --- a/src/ReactiveUI.Primitives.Reactive/Concurrency/WasmScheduler.cs +++ b/src/ReactiveUI.Primitives.Reactive/Concurrency/WasmScheduler.cs @@ -313,7 +313,7 @@ public StatefulWorkItem(WasmScheduler scheduler, TState state, FuncStores the one-shot timer so the caller's disposable cancels and releases it. /// The armed timer. - public void AttachTimer(Timer timer) + internal void AttachTimer(Timer timer) { Volatile.Write(ref _timer, timer); if (!IsDisposed) @@ -379,7 +379,7 @@ private PeriodicWorkItem(TState state, Func action) /// Tick period. /// Scheduled action. /// The armed periodic work item, which cancels the ticks when disposed. - public static PeriodicWorkItem Start(TState state, TimeSpan period, Func action) + internal static PeriodicWorkItem Start(TState state, TimeSpan period, Func action) { PeriodicWorkItem item = new(state, action); Timer timer = new( diff --git a/src/ReactiveUI.Primitives/Concurrency/ImmediateSequencer.cs b/src/ReactiveUI.Primitives/Concurrency/ImmediateSequencer.cs index 9c994f08..87df50ba 100644 --- a/src/ReactiveUI.Primitives/Concurrency/ImmediateSequencer.cs +++ b/src/ReactiveUI.Primitives/Concurrency/ImmediateSequencer.cs @@ -37,11 +37,6 @@ private ImmediateSequencer() /// Action to execute. /// An empty disposable because the action has already run. /// is . - [System.Diagnostics.CodeAnalysis.SuppressMessage( - "Performance", - "CA1822:Mark members as static", - Justification = - "Must remain an instance method so overload resolution selects it over the allocating ISequencer.Schedule(Action) extension, giving an allocation-free immediate path.")] public IDisposable Schedule(Action action) { ArgumentExceptionHelper.ThrowIfNull(action); diff --git a/src/ReactiveUI.Primitives/Concurrency/Sequencer.Simple.cs b/src/ReactiveUI.Primitives/Concurrency/Sequencer.Simple.cs index 35652271..b2e2f1ae 100644 --- a/src/ReactiveUI.Primitives/Concurrency/Sequencer.Simple.cs +++ b/src/ReactiveUI.Primitives/Concurrency/Sequencer.Simple.cs @@ -174,7 +174,7 @@ public RecursiveScheduleState(ISequencer scheduler, Action action) /// Starts recursive scheduling. /// The disposable object used to cancel recursive work. - public RecursiveScheduleState Start() + internal RecursiveScheduleState Start() { Add(_scheduler.Schedule(_recursiveAction)); return this; diff --git a/src/ReactiveUI.Primitives/Concurrency/SynchronizedSequencerQueue.cs b/src/ReactiveUI.Primitives/Concurrency/SynchronizedSequencerQueue.cs index 84793757..2bb0438f 100644 --- a/src/ReactiveUI.Primitives/Concurrency/SynchronizedSequencerQueue.cs +++ b/src/ReactiveUI.Primitives/Concurrency/SynchronizedSequencerQueue.cs @@ -21,7 +21,7 @@ internal sealed class SynchronizedSequencerQueue /// Enqueues a scheduled item. /// The item to enqueue. - public void Enqueue(ScheduledItem item) + internal void Enqueue(ScheduledItem item) { lock (_gate) { @@ -31,7 +31,7 @@ public void Enqueue(ScheduledItem item) /// Removes a scheduled item. /// The item to remove. - public void Remove(ScheduledItem item) + internal void Remove(ScheduledItem item) { lock (_gate) { @@ -41,7 +41,7 @@ public void Remove(ScheduledItem item) /// Gets the next non-cancelled scheduled item, leaving it on the queue and discarding cancelled items it passes. /// The next live scheduled item, or when none remain. - public IScheduledItem? GetNextLive() + internal IScheduledItem? GetNextLive() { lock (_gate) { diff --git a/src/ReactiveUI.Primitives/Concurrency/ThreadPoolSequencer.cs b/src/ReactiveUI.Primitives/Concurrency/ThreadPoolSequencer.cs index b1c14e45..af37f14d 100644 --- a/src/ReactiveUI.Primitives/Concurrency/ThreadPoolSequencer.cs +++ b/src/ReactiveUI.Primitives/Concurrency/ThreadPoolSequencer.cs @@ -238,10 +238,10 @@ public TimedWorkItem(IWorkItem item, long dueTimestamp) } /// Gets the scheduled item. - public IWorkItem Item { get; } + internal IWorkItem Item { get; } /// Gets the monotonic due timestamp. - public long DueTimestamp { get; } + internal long DueTimestamp { get; } /// public int CompareTo(TimedWorkItem other) => DueTimestamp.CompareTo(other.DueTimestamp); diff --git a/src/ReactiveUI.Primitives/Concurrency/VirtualTimeMessages.cs b/src/ReactiveUI.Primitives/Concurrency/VirtualTimeMessages.cs index fcc048e9..9ed28edf 100644 --- a/src/ReactiveUI.Primitives/Concurrency/VirtualTimeMessages.cs +++ b/src/ReactiveUI.Primitives/Concurrency/VirtualTimeMessages.cs @@ -31,7 +31,7 @@ internal static class VirtualTimeMessages /// Builds the message thrown when a clock-advancing method runs against an already-running scheduler. /// The name of the clock-advancing method that was called. /// The formatted message. - public static string SchedulerAlreadyRunning(string methodName) => + internal static string SchedulerAlreadyRunning(string methodName) => #if NET8_0_OR_GREATER string.Format(CultureInfo.CurrentCulture, SchedulerAlreadyRunningComposite, methodName); #else diff --git a/src/ReactiveUI.Primitives/Concurrency/VirtualTimeState{TAbsolute,TRelative}.cs b/src/ReactiveUI.Primitives/Concurrency/VirtualTimeState{TAbsolute,TRelative}.cs index 5391d666..20742fa5 100644 --- a/src/ReactiveUI.Primitives/Concurrency/VirtualTimeState{TAbsolute,TRelative}.cs +++ b/src/ReactiveUI.Primitives/Concurrency/VirtualTimeState{TAbsolute,TRelative}.cs @@ -65,22 +65,22 @@ public VirtualTimeState( } /// Gets the scheduler's absolute time clock value. - public readonly TAbsolute Clock => _clock; + internal readonly TAbsolute Clock => _clock; /// Gets a value indicating whether the scheduler is enabled to run work. - public readonly bool IsEnabled => _isEnabled; + internal readonly bool IsEnabled => _isEnabled; /// Gets the scheduler's notion of current time. - public readonly DateTimeOffset Now => _toDateTimeOffset(_clock); + internal readonly DateTimeOffset Now => _toDateTimeOffset(_clock); /// Gets the virtual clock as a monotonic timestamp. - public readonly long Timestamp => Now.UtcTicks; + internal readonly long Timestamp => Now.UtcTicks; /// Advances the scheduler's clock by the specified relative time, running all work scheduled for that timespan. /// Relative time to advance the scheduler's clock by. /// is negative. /// The scheduler is already running. - public void AdvanceBy(TRelative time) + internal void AdvanceBy(TRelative time) { var dt = _add(_clock, time); @@ -107,7 +107,7 @@ public void AdvanceBy(TRelative time) /// Absolute time to advance the scheduler's clock to. /// is in the past. /// The scheduler is already running. - public void AdvanceTo(TAbsolute time) + internal void AdvanceTo(TAbsolute time) { var dueToClock = _comparer.Compare(time, _clock); if (dueToClock < 0) @@ -150,7 +150,7 @@ public void AdvanceTo(TAbsolute time) /// Advances the scheduler's clock by the specified relative time without running work. /// Relative time to advance the scheduler's clock by. /// is negative. - public void Sleep(TRelative time) + internal void Sleep(TRelative time) { var dt = _add(_clock, time); @@ -164,7 +164,7 @@ public void Sleep(TRelative time) } /// Starts the virtual time scheduler, running all scheduled work. - public void Start() + internal void Start() { if (_isEnabled) { @@ -192,7 +192,7 @@ public void Start() } /// Stops the virtual time scheduler. - public void Stop() => _isEnabled = false; + internal void Stop() => _isEnabled = false; /// Schedules an action to be executed at the current clock. /// The type of the state passed to the scheduled action. @@ -201,7 +201,7 @@ public void Start() /// Action to be executed. /// The disposable object used to cancel the scheduled action (best effort). /// is null. - public readonly IDisposable Schedule( + internal readonly IDisposable Schedule( ISequencer owner, TState state, Func action) @@ -219,7 +219,7 @@ public readonly IDisposable Schedule( /// Action to be executed. /// The disposable object used to cancel the scheduled action (best effort). /// is null. - public readonly IDisposable Schedule( + internal readonly IDisposable Schedule( ISequencer owner, TState state, TimeSpan dueTime, @@ -238,7 +238,7 @@ public readonly IDisposable Schedule( /// Action to be executed. /// The disposable object used to cancel the scheduled action (best effort). /// is null. - public readonly IDisposable Schedule( + internal readonly IDisposable Schedule( ISequencer owner, TState state, DateTimeOffset dueTime, @@ -253,7 +253,7 @@ public readonly IDisposable Schedule( /// The sequencer passed to the scheduled action. /// Work item to execute. /// is . - public readonly void Schedule(ISequencer owner, IWorkItem item) + internal readonly void Schedule(ISequencer owner, IWorkItem item) { ArgumentExceptionHelper.ThrowIfNull(item); @@ -273,7 +273,7 @@ public readonly void Schedule(ISequencer owner, IWorkItem item) /// Work item to execute. /// Absolute sequencer timestamp. /// is . - public readonly void Schedule(ISequencer owner, IWorkItem item, long dueTimestamp) + internal readonly void Schedule(ISequencer owner, IWorkItem item, long dueTimestamp) { ArgumentExceptionHelper.ThrowIfNull(item); @@ -297,7 +297,7 @@ public readonly void Schedule(ISequencer owner, IWorkItem item, long dueTimestam /// Action to be executed. /// The disposable object used to cancel the scheduled action (best effort). /// is null. - public readonly IDisposable ScheduleAbsolute( + internal readonly IDisposable ScheduleAbsolute( ISequencer owner, TState state, TAbsolute dueTime, @@ -328,7 +328,7 @@ public readonly IDisposable ScheduleAbsolute( /// Action to be executed. /// The disposable object used to cancel the scheduled action (best effort). /// is null. - public readonly IDisposable ScheduleRelative( + internal readonly IDisposable ScheduleRelative( ISequencer owner, TState state, TRelative dueTime, @@ -343,5 +343,5 @@ public readonly IDisposable ScheduleRelative( /// Gets the next non-cancelled scheduled item to be executed, leaving it on the queue. /// The next scheduled item, or when none remain. - public readonly IScheduledItem? GetNext() => _queue.GetNextLive(); + internal readonly IScheduledItem? GetNext() => _queue.GetNextLive(); } diff --git a/src/ReactiveUI.Primitives/Platforms/apple/NSRunloopSequencer.cs b/src/ReactiveUI.Primitives/Platforms/apple/NSRunloopSequencer.cs index 635ae54b..f0a70caf 100644 --- a/src/ReactiveUI.Primitives/Platforms/apple/NSRunloopSequencer.cs +++ b/src/ReactiveUI.Primitives/Platforms/apple/NSRunloopSequencer.cs @@ -66,7 +66,7 @@ private bool Post(Action drain) /// Runs delayed work through the main queue's native delayed dispatch. /// Work item to execute at the due time. /// Absolute monotonic timestamp at which to execute the item. - private void ScheduleDelayed(IWorkItem item, long dueTimestamp) + private static void ScheduleDelayed(IWorkItem item, long dueTimestamp) { var nanoseconds = (long)DispatchSequencerState.DelayUntil(dueTimestamp).TotalMilliseconds * NanosecondsPerMillisecond; DispatchQueue.MainQueue.DispatchAfter(new DispatchTime(DispatchTime.Now, nanoseconds), () => DispatchSequencerState.RunIfActive(item)); diff --git a/src/tests/ReactiveUI.Primitives.Async.Tests/AotSafeAssertionExtensions.cs b/src/tests/ReactiveUI.Primitives.Async.Tests/AotSafeAssertionExtensions.cs index b364e799..6ca12381 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Tests/AotSafeAssertionExtensions.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Tests/AotSafeAssertionExtensions.cs @@ -25,7 +25,7 @@ internal static class AotSafeAssertionExtensions /// /// The expected element sequence. /// The chained collection-equivalency assertion. - public IsEquivalentToAssertion IsCollectionEqualTo(IEnumerable expected) => + internal IsEquivalentToAssertion IsCollectionEqualTo(IEnumerable expected) => source.IsEquivalentTo(expected, EqualityComparer.Default); } } diff --git a/src/tests/ReactiveUI.Primitives.Async.Tests/AsyncSerialGateTests.cs b/src/tests/ReactiveUI.Primitives.Async.Tests/AsyncSerialGateTests.cs index 1cfe4705..52bcf6e9 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Tests/AsyncSerialGateTests.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Tests/AsyncSerialGateTests.cs @@ -5,7 +5,6 @@ namespace ReactiveUI.Primitives.Async.Tests; /// Coverage for — uncontended fast path, same-thread reentry, contended slow path, double-dispose idempotency. -[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "TUnit requires instance methods")] public class AsyncSerialGateTests { /// Verifies that the uncontended fast path acquires the gate via pure CAS. diff --git a/src/tests/ReactiveUI.Primitives.Async.Tests/ConcurrentSignalBaseTests.cs b/src/tests/ReactiveUI.Primitives.Async.Tests/ConcurrentSignalBaseTests.cs index c3d3d99e..54cd928c 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Tests/ConcurrentSignalBaseTests.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Tests/ConcurrentSignalBaseTests.cs @@ -12,7 +12,6 @@ namespace ReactiveUI.Primitives.Async.Tests; /// — exercises empty / single / multi-observer paths and the /// slow-path that uses when at /// least one observer's hasn't completed synchronously. -[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "TUnit requires instance methods")] public class ConcurrentSignalBaseTests { /// Value forwarded by the OnNext fan-out tests. diff --git a/src/tests/ReactiveUI.Primitives.Async.Tests/DisposableAsyncSlotTests.cs b/src/tests/ReactiveUI.Primitives.Async.Tests/DisposableAsyncSlotTests.cs index 444ced78..c25aee67 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Tests/DisposableAsyncSlotTests.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Tests/DisposableAsyncSlotTests.cs @@ -110,7 +110,7 @@ public async Task WhenAssignedTwice_ThenThrowsInvalidOperation() /// A representing the asynchronous test operation. [Test] public async Task WhenDisposedSlotMarkerDisposed_ThenCompletesSilently() => - await DisposableAsyncSlot.DisposedSentinel.DisposeAsync(); + await Assert.That(static async () => await DisposableAsyncSlot.DisposedSentinel.DisposeAsync()).ThrowsNothing(); /// Recording async disposable that counts disposals. private sealed class RecordingAsyncDisposable : IAsyncDisposable diff --git a/src/tests/ReactiveUI.Primitives.Async.Tests/DisposableTests.cs b/src/tests/ReactiveUI.Primitives.Async.Tests/DisposableTests.cs index d639ece2..a3e8d435 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Tests/DisposableTests.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Tests/DisposableTests.cs @@ -16,7 +16,8 @@ public class DisposableTests public async Task WhenDisposableAsyncEmpty_ThenDisposeDoesNothing() { var empty = DisposableAsync.Empty; - await empty.DisposeAsync(); + + await Assert.That(async () => await empty.DisposeAsync()).ThrowsNothing(); } /// Tests DisposableAsync.Create callback invoked on dispose. diff --git a/src/tests/ReactiveUI.Primitives.Async.Tests/IgnoredResult.cs b/src/tests/ReactiveUI.Primitives.Async.Tests/IgnoredResult.cs index 2fd00963..6f116567 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Tests/IgnoredResult.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Tests/IgnoredResult.cs @@ -10,5 +10,5 @@ internal static class IgnoredResult /// Consumes an ignored return value. /// The ignored value type. /// The ignored value. - public static void Of(T value) => _ = value; + internal static void Of(T value) => _ = value; } diff --git a/src/tests/ReactiveUI.Primitives.Async.Tests/Internals/CombineLatestIndexedObserverTests.cs b/src/tests/ReactiveUI.Primitives.Async.Tests/Internals/CombineLatestIndexedObserverTests.cs index a6fc8a00..df5666ff 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Tests/Internals/CombineLatestIndexedObserverTests.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Tests/Internals/CombineLatestIndexedObserverTests.cs @@ -98,15 +98,15 @@ protected override ValueTask SubscribeAtAsync(int index, Cance /// Element type captured. private sealed class CaptureObserverAsync : IObserverAsync { - /// Gets the captured OnNext values in order. - public List Values { get; } = []; - /// Gets the captured OnErrorResume exceptions in order. public List Errors { get; } = []; /// Gets the captured OnCompleted results in order. public List Completions { get; } = []; + /// Gets the captured OnNext values in order. + private List Values { get; } = []; + /// public ValueTask OnNextAsync(T value, CancellationToken cancellationToken) { diff --git a/src/tests/ReactiveUI.Primitives.Async.Tests/Internals/SyncLatestCoordinatorBaseTests.cs b/src/tests/ReactiveUI.Primitives.Async.Tests/Internals/SyncLatestCoordinatorBaseTests.cs index 3f05dca7..53149b7b 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Tests/Internals/SyncLatestCoordinatorBaseTests.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Tests/Internals/SyncLatestCoordinatorBaseTests.cs @@ -187,14 +187,14 @@ private sealed class NoopDisposable : IAsyncDisposable /// Element type captured. private sealed class CaptureObserverAsync : IObserverAsync { - /// Gets the captured OnNext values in order. - public List Values { get; } = []; - /// Gets the captured error-resume exceptions in order. public List Errors { get; } = []; + /// Gets the captured OnNext values in order. + private List Values { get; } = []; + /// Gets the captured OnCompleted results in order. - public List Completions { get; } = []; + private List Completions { get; } = []; /// public ValueTask OnNextAsync(T value, CancellationToken cancellationToken) diff --git a/src/tests/ReactiveUI.Primitives.Async.Tests/NewThreadTaskScheduler.cs b/src/tests/ReactiveUI.Primitives.Async.Tests/NewThreadTaskScheduler.cs index a7b342f0..2e25ab02 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Tests/NewThreadTaskScheduler.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Tests/NewThreadTaskScheduler.cs @@ -13,7 +13,7 @@ namespace ReactiveUI.Primitives.Async.Tests; internal sealed class NewThreadTaskScheduler : TaskScheduler { /// Singleton instance. - public static readonly NewThreadTaskScheduler Instance = new(); + internal static readonly NewThreadTaskScheduler Instance = new(); /// Initializes a new instance of the class. private NewThreadTaskScheduler() diff --git a/src/tests/ReactiveUI.Primitives.Async.Tests/ParityHelpersFilterFusionsTests.cs b/src/tests/ReactiveUI.Primitives.Async.Tests/ParityHelpersFilterFusionsTests.cs index 52df97ab..17611cd3 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Tests/ParityHelpersFilterFusionsTests.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Tests/ParityHelpersFilterFusionsTests.cs @@ -10,7 +10,6 @@ namespace ReactiveUI.Primitives.Async.Tests; /// async filter operators in ParityHelpers.FilterFusions — /// SkipWhileNull, WhereIsNotNull, LatestOrDefault, /// WaitUntil, AsSignal, Not, WhereTrue, WhereFalse. -[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "TUnit requires instance methods")] public class ParityHelpersFilterFusionsTests { /// Seconds a test waits for a notification before giving up. diff --git a/src/tests/ReactiveUI.Primitives.Async.Tests/ParityHelpersOperatorFusionsTests.cs b/src/tests/ReactiveUI.Primitives.Async.Tests/ParityHelpersOperatorFusionsTests.cs index 4ee6dfd0..a9465c82 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Tests/ParityHelpersOperatorFusionsTests.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Tests/ParityHelpersOperatorFusionsTests.cs @@ -12,7 +12,6 @@ namespace ReactiveUI.Primitives.Async.Tests; /// ThrottleDistinct upstream/downstream filtering, DebounceUntil /// immediate-bypass branch, and the typed fast paths in ForEach /// (array / IReadOnlyList / general IEnumerable). -[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "TUnit requires instance methods")] public class ParityHelpersOperatorFusionsTests { /// Seconds a test waits for a notification before giving up. diff --git a/src/tests/ReactiveUI.Primitives.Async.Tests/ParityOperatorTests.cs b/src/tests/ReactiveUI.Primitives.Async.Tests/ParityOperatorTests.cs index 715bda02..54e4ff6f 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Tests/ParityOperatorTests.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Tests/ParityOperatorTests.cs @@ -8,7 +8,6 @@ namespace ReactiveUI.Primitives.Async.Tests; /// Tests for async parity helpers that mirror the synchronous helper surface in the repository. -[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "TUnit requires instance methods")] public class ParityOperatorTests { /// Seconds a test waits for a notification before giving up. diff --git a/src/tests/ReactiveUI.Primitives.Extensions.Tests/AotSafeAssertionExtensions.cs b/src/tests/ReactiveUI.Primitives.Extensions.Tests/AotSafeAssertionExtensions.cs index 8116776d..39ae4673 100644 --- a/src/tests/ReactiveUI.Primitives.Extensions.Tests/AotSafeAssertionExtensions.cs +++ b/src/tests/ReactiveUI.Primitives.Extensions.Tests/AotSafeAssertionExtensions.cs @@ -26,7 +26,7 @@ internal static class AotSafeAssertionExtensions /// /// The expected element sequence. /// The chained collection-equivalency assertion. - public IsEquivalentToAssertion IsCollectionEqualTo( + internal IsEquivalentToAssertion IsCollectionEqualTo( IEnumerable expected) => source.IsEquivalentTo(expected, EqualityComparer.Default); } diff --git a/src/tests/ReactiveUI.Primitives.Extensions.Tests/Operators/ScanWithInitialTests.cs b/src/tests/ReactiveUI.Primitives.Extensions.Tests/Operators/ScanWithInitialTests.cs index 59584d7a..83f9ada0 100644 --- a/src/tests/ReactiveUI.Primitives.Extensions.Tests/Operators/ScanWithInitialTests.cs +++ b/src/tests/ReactiveUI.Primitives.Extensions.Tests/Operators/ScanWithInitialTests.cs @@ -9,6 +9,9 @@ namespace ReactiveUI.Primitives.Extensions.Tests.Operators; /// Tests for the class. public partial class ScanWithInitialTests { + /// Spin iterations used to widen the interleaving window in contention tests. + private const int InterleavingSpinIterations = 100; + #if NET9_0_OR_GREATER /// Synchronization gate used by tests. @@ -106,7 +109,7 @@ public async Task Observable_IsThreadSafe() const int Initial = 0; Func accumulator = static (acc, x) => { - Thread.Sleep(1); // Force potential race condition + Thread.SpinWait(InterleavingSpinIterations); return acc + x; }; ScanWithInitialObservable observable = new(source, Initial, accumulator); diff --git a/src/tests/ReactiveUI.Primitives.Extensions.Tests/Operators/SimpleSyncOperatorTests.cs b/src/tests/ReactiveUI.Primitives.Extensions.Tests/Operators/SimpleSyncOperatorTests.cs index c22ffedc..1f67e9a6 100644 --- a/src/tests/ReactiveUI.Primitives.Extensions.Tests/Operators/SimpleSyncOperatorTests.cs +++ b/src/tests/ReactiveUI.Primitives.Extensions.Tests/Operators/SimpleSyncOperatorTests.cs @@ -153,7 +153,7 @@ public async Task WhenFilterRegexThrows_ThenForwardsError() using var sub = subject.Filter(regex).Subscribe( static _ => { }, ex => caught = ex); - subject.OnNext(new string('a', PathologicalInputLength) + "!"); + subject.OnNext($"{new string('a', PathologicalInputLength)}!"); await Assert.That(caught).IsNotNull(); } diff --git a/src/tests/ReactiveUI.Primitives.Extensions.Tests/Operators/SyncDirectSource.cs b/src/tests/ReactiveUI.Primitives.Extensions.Tests/Operators/SyncDirectSource.cs index 8ead97b9..c413d994 100644 --- a/src/tests/ReactiveUI.Primitives.Extensions.Tests/Operators/SyncDirectSource.cs +++ b/src/tests/ReactiveUI.Primitives.Extensions.Tests/Operators/SyncDirectSource.cs @@ -21,7 +21,7 @@ internal sealed class SyncDirectSource : IObservable private IObserver? _observer; /// Gets the captured observer; throws if no one has subscribed yet. - public IObserver Observer => _observer + internal IObserver Observer => _observer ?? throw new InvalidOperationException("No observer is currently subscribed."); /// diff --git a/src/tests/ReactiveUI.Primitives.Extensions.Tests/ReactiveExtensionsTests.FusedSelect.cs b/src/tests/ReactiveUI.Primitives.Extensions.Tests/ReactiveExtensionsTests.FusedSelect.cs index f892e360..6cc6e73b 100644 --- a/src/tests/ReactiveUI.Primitives.Extensions.Tests/ReactiveExtensionsTests.FusedSelect.cs +++ b/src/tests/ReactiveUI.Primitives.Extensions.Tests/ReactiveExtensionsTests.FusedSelect.cs @@ -149,7 +149,7 @@ public async Task WhenFirstMatchFromCandidates_ThenEmitsFirstSatisfyingValue() List results = []; using var subscription = keys.FirstMatchFromCandidates( - static key => Observable.Return(key + "-raw"), + static key => Observable.Return($"{key}-raw"), static raw => raw.ToUpperInvariant(), static transformed => transformed.StartsWith('B'), CandidateFallback) @@ -167,7 +167,7 @@ public async Task WhenFirstMatchFromCandidatesNoMatch_ThenEmitsFallback() List results = []; using var subscription = keys.FirstMatchFromCandidates( - static key => Observable.Return(key + "-raw"), + static key => Observable.Return($"{key}-raw"), static raw => raw.ToUpperInvariant(), static _ => false, CandidateFallback) @@ -187,7 +187,7 @@ public async Task WhenFirstMatchFromCandidatesProjectionErrors_ThenSkipsAndConti using var subscription = keys.FirstMatchFromCandidates( static key => key == "fail" ? Observable.Throw(new InvalidOperationException("ignored")) - : Observable.Return(key + "-raw"), + : Observable.Return($"{key}-raw"), static raw => raw.ToUpperInvariant(), static transformed => transformed.StartsWith('M'), CandidateFallback) diff --git a/src/tests/ReactiveUI.Primitives.Extensions.Tests/VirtualClockTestExtensions.cs b/src/tests/ReactiveUI.Primitives.Extensions.Tests/VirtualClockTestExtensions.cs index 273efa4a..32938c52 100644 --- a/src/tests/ReactiveUI.Primitives.Extensions.Tests/VirtualClockTestExtensions.cs +++ b/src/tests/ReactiveUI.Primitives.Extensions.Tests/VirtualClockTestExtensions.cs @@ -15,12 +15,12 @@ internal static class VirtualClockTestExtensions { /// Advances a virtual clock by ticks. /// The number of ticks to advance. - public void AdvanceBy(long ticks) => + internal void AdvanceBy(long ticks) => clock.AdvanceBy(TimeSpan.FromTicks(ticks)); /// Advances a virtual clock to an absolute tick value. /// The absolute tick value. - public void AdvanceTo(long ticks) => + internal void AdvanceTo(long ticks) => clock.AdvanceTo(new(ticks, TimeSpan.Zero)); } } diff --git a/src/tests/ReactiveUI.Primitives.Tests/Common/ManualSequencer.cs b/src/tests/ReactiveUI.Primitives.Tests/Common/ManualSequencer.cs index ff24b93f..c4a7a354 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/Common/ManualSequencer.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/Common/ManualSequencer.cs @@ -43,10 +43,10 @@ internal sealed class ManualSequencer : ISequencer /// Moves the sequencer's clock forward without running any work. /// The amount of time to move forward by. - public void Advance(TimeSpan time) => Now += time; + internal void Advance(TimeSpan time) => Now += time; /// Runs every work item queued so far. - public void RunPending() + internal void RunPending() { var items = _pending.ToArray(); _pending.Clear(); @@ -58,5 +58,5 @@ public void RunPending() } /// Runs the most recently run work item again, modelling a stale timer tick. - public void RunStaleTick() => _lastRun?.Execute(); + internal void RunStaleTick() => _lastRun?.Execute(); } diff --git a/src/tests/ReactiveUI.Primitives.Tests/Common/MinimalVirtualClock.cs b/src/tests/ReactiveUI.Primitives.Tests/Common/MinimalVirtualClock.cs index ebf8c35f..4558d12b 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/Common/MinimalVirtualClock.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/Common/MinimalVirtualClock.cs @@ -20,11 +20,11 @@ internal static class MinimalVirtualClock /// Creates a tick-based virtual sequencer ordered by the default comparer. /// The virtual sequencer. - public static VirtualTimeSequencer Create() => Create(Comparer.Default); + internal static VirtualTimeSequencer Create() => Create(Comparer.Default); /// Creates a tick-based virtual sequencer ordered by the supplied comparer. /// The comparer used to order scheduled times. /// The virtual sequencer. - public static VirtualTimeSequencer Create(IComparer comparer) => + internal static VirtualTimeSequencer Create(IComparer comparer) => new(0L, comparer, Adder, ToDateTimeOffset, ToRelative); } diff --git a/src/tests/ReactiveUI.Primitives.Tests/Common/OrderingWitness.cs b/src/tests/ReactiveUI.Primitives.Tests/Common/OrderingWitness.cs index 13729b29..a3ffb4cc 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/Common/OrderingWitness.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/Common/OrderingWitness.cs @@ -20,7 +20,7 @@ internal sealed class OrderingWitness : IObserver private bool _hasPrevious; /// Gets the first delivery that broke ordering, or when none did. - public OutOfOrderDelivery? OutOfOrder { get; private set; } + internal OutOfOrderDelivery? OutOfOrder { get; private set; } /// Records a completion callback. public void OnCompleted() diff --git a/src/tests/ReactiveUI.Primitives.Tests/Common/RecordingDisposable.cs b/src/tests/ReactiveUI.Primitives.Tests/Common/RecordingDisposable.cs index b367027e..9c9fb249 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/Common/RecordingDisposable.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/Common/RecordingDisposable.cs @@ -8,7 +8,7 @@ namespace ReactiveUI.Primitives.Tests; internal sealed class RecordingDisposable : IDisposable { /// Gets the number of disposal calls. - public int DisposeCount { get; private set; } + internal int DisposeCount { get; private set; } /// public void Dispose() => DisposeCount++; diff --git a/src/tests/ReactiveUI.Primitives.Tests/Common/RecordingWitness.cs b/src/tests/ReactiveUI.Primitives.Tests/Common/RecordingWitness.cs index 9d4bcb94..2102d37e 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/Common/RecordingWitness.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/Common/RecordingWitness.cs @@ -9,13 +9,13 @@ namespace ReactiveUI.Primitives.Tests; internal sealed class RecordingWitness : IObserver { /// Gets the recorded values. - public List Values { get; } = []; + internal List Values { get; } = []; /// Gets the recorded errors. - public List Errors { get; } = []; + internal List Errors { get; } = []; /// Gets the number of completion callbacks observed. - public int Completed { get; private set; } + internal int Completed { get; private set; } /// Records a completion callback. public void OnCompleted() => Completed++; diff --git a/src/tests/ReactiveUI.Primitives.Tests/Common/ScheduledProbe.cs b/src/tests/ReactiveUI.Primitives.Tests/Common/ScheduledProbe.cs index 5b9fc875..adfe33f9 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/Common/ScheduledProbe.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/Common/ScheduledProbe.cs @@ -13,6 +13,6 @@ internal static class ScheduledProbe /// The due time for the scheduled item. /// The factory invoked when the item runs. /// The scheduled item. - public static ScheduledItem Create(int dueTime, Func invoke) => + internal static ScheduledItem Create(int dueTime, Func invoke) => new(dueTime, Comparer.Default, _ => invoke()); } diff --git a/src/tests/ReactiveUI.Primitives.Tests/Common/TestPolling.cs b/src/tests/ReactiveUI.Primitives.Tests/Common/TestPolling.cs index 2cb23b98..1f962755 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/Common/TestPolling.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/Common/TestPolling.cs @@ -14,7 +14,7 @@ internal static class TestPolling /// The condition to evaluate on each poll. /// The maximum time to wait for the condition. /// A task that completes when the condition is satisfied. - public static async Task SpinUntil(Func condition, TimeSpan timeout) + internal static async Task SpinUntil(Func condition, TimeSpan timeout) { var attempts = (int)(timeout.TotalMilliseconds / PollDelayMilliseconds); for (var attempt = 0; attempt < attempts; attempt++) diff --git a/src/tests/ReactiveUI.Primitives.Tests/Common/ThrowingWitness.cs b/src/tests/ReactiveUI.Primitives.Tests/Common/ThrowingWitness.cs index 98bb760d..5a919f9e 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/Common/ThrowingWitness.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/Common/ThrowingWitness.cs @@ -24,7 +24,7 @@ internal sealed class ThrowingWitness( private readonly bool _throwOnCompleted = throwOnCompleted; /// Gets a value indicating whether an error callback has been observed. - public bool SeenError { get; private set; } + internal bool SeenError { get; private set; } /// Handles completion, throwing when configured to do so. public void OnCompleted() diff --git a/src/tests/ReactiveUI.Primitives.Tests/ConnectableSignalTests.cs b/src/tests/ReactiveUI.Primitives.Tests/ConnectableSignalTests.cs index 472b95cf..745bb73d 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/ConnectableSignalTests.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/ConnectableSignalTests.cs @@ -509,7 +509,7 @@ public async Task ConnectableRxAliasesShareReplayAndRouteSelectorFailures() List selectedValues = []; using var selectedSubscription = ConnectableSignalRxNameExtensions - .Publish(cold, static shared => shared.Map(static value => value + SecondSharedValue)) + .Publish(cold, static shared => shared.Map(static value => value + SecondSharedValue)) .Subscribe(selectedValues.Add); source.OnNext(FirstSharedValue); diff --git a/src/tests/ReactiveUI.Primitives.Tests/DummyDisposable.cs b/src/tests/ReactiveUI.Primitives.Tests/DummyDisposable.cs index 15f5805b..853fc124 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/DummyDisposable.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/DummyDisposable.cs @@ -8,7 +8,7 @@ namespace ReactiveUI.Primitives.Tests; internal sealed class DummyDisposable : IDisposable { /// Gets the shared disposable instance. - public static DummyDisposable Instance { get; } = new(); + internal static DummyDisposable Instance { get; } = new(); /// public void Dispose() diff --git a/src/tests/ReactiveUI.Primitives.Tests/FromEnumerableSignalTests.cs b/src/tests/ReactiveUI.Primitives.Tests/FromEnumerableSignalTests.cs index 17f18c0e..c79e79a9 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/FromEnumerableSignalTests.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/FromEnumerableSignalTests.cs @@ -87,7 +87,7 @@ private sealed class RecordingWitness : IObserver public int Completed { get; private set; } /// Gets the observed errors. - public List Errors { get; } = []; + private List Errors { get; } = []; /// public void OnCompleted() => Completed++; diff --git a/src/tests/ReactiveUI.Primitives.Tests/RxNamesTests.DynamicDataCompatibility.cs b/src/tests/ReactiveUI.Primitives.Tests/RxNamesTests.DynamicDataCompatibility.cs index 7bf9cecd..c5788873 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/RxNamesTests.DynamicDataCompatibility.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/RxNamesTests.DynamicDataCompatibility.cs @@ -273,8 +273,8 @@ public async Task DynamicDataRxConvenienceAliasesCoverPublicEntryPoints() var completed = 0; _ = Signal.FromEnumerable([One, Two]) .Do( - value => sideEffects.Add("next:" + value), - error => sideEffects.Add("error:" + error.Message), + value => sideEffects.Add($"next:{value}"), + error => sideEffects.Add($"error:{error.Message}"), () => completed++) .Subscribe(values.Add); diff --git a/src/tests/ReactiveUI.Primitives.Tests/RxNamesTests.SubscribeSafe.cs b/src/tests/ReactiveUI.Primitives.Tests/RxNamesTests.SubscribeSafe.cs index fe6988b8..879846e6 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/RxNamesTests.SubscribeSafe.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/RxNamesTests.SubscribeSafe.cs @@ -148,28 +148,28 @@ public async Task SubscribeSafeStaticAliasAcceptsNonNullableValueTypeOverloads() void OnError(Exception error) => observed = error; - using var callbackSubscription = PrimitivesLinqExtensions.SubscribeSafe( + using var callbackSubscription = PrimitivesLinqExtensions.SubscribeSafe( source, values.Add, OnError, (byte)0); - using var completionSubscription = PrimitivesLinqExtensions.SubscribeSafe( + using var completionSubscription = PrimitivesLinqExtensions.SubscribeSafe( source, values.Add, OnError, () => completed++, (byte)0); - using var observerSubscription = PrimitivesLinqExtensions.SubscribeSafe( + using var observerSubscription = PrimitivesLinqExtensions.SubscribeSafe( source, Observer.Create(values.Add, OnError), (byte)0); InvalidOperationException expected = new("expected"); - using var errorSubscription = PrimitivesLinqExtensions.SubscribeSafe( + using var errorSubscription = PrimitivesLinqExtensions.SubscribeSafe( Observable.Throw(expected), OnError, (byte)0); - using var terminalSubscription = PrimitivesLinqExtensions.SubscribeSafe( + using var terminalSubscription = PrimitivesLinqExtensions.SubscribeSafe( source, OnError, () => completed++, diff --git a/src/tests/ReactiveUI.Primitives.Tests/SafeWitnessTests.cs b/src/tests/ReactiveUI.Primitives.Tests/SafeWitnessTests.cs index 9d2d2338..61e5557e 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/SafeWitnessTests.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/SafeWitnessTests.cs @@ -32,7 +32,7 @@ public async Task CancelOwningSafeWitnessForwardsTheFirstFaultThenGoesQuiet() { RecordingWitness observer = new(); var cancelled = 0; - var safe = Witness.Safe(observer, new ActionDisposable(() => cancelled++)); + var safe = Witness.Safe(observer, new ActionDisposable(() => cancelled++)); InvalidOperationException error = new("safe-fault"); safe.OnError(error); @@ -105,7 +105,7 @@ public async Task DelegateWitnessMadeSafeStopsAfterItsValueCallbackThrows() public async Task CancelFreeSafeWitnessForwardsValuesThenCompletesExactlyOnce() { RecordingWitness observer = new(); - var safe = Witness.Safe(observer); + var safe = Witness.Safe(observer); safe.OnNext(FirstValue); safe.OnCompleted(); @@ -124,7 +124,7 @@ public async Task CancelFreeSafeWitnessForwardsValuesThenCompletesExactlyOnce() public async Task CancelFreeSafeWitnessForwardsTheFirstFaultThenGoesQuiet() { RecordingWitness observer = new(); - var safe = Witness.Safe(observer); + var safe = Witness.Safe(observer); InvalidOperationException error = new("no-cancel-fault"); safe.OnError(error); @@ -146,7 +146,7 @@ public async Task CancelFreeSafeWitnessForwardsTheFirstFaultThenGoesQuiet() [Test] public async Task CancelFreeSafeWitnessStopsAfterTheObserverThrows() { - var safe = Witness.Safe(new ThrowingWitness(throwOnNext: true)); + var safe = Witness.Safe(new ThrowingWitness(throwOnNext: true)); var thrown = Assert.Throws(() => safe.OnNext(FirstValue)); diff --git a/src/tests/ReactiveUI.Primitives.Tests/SignalAliasCoverageTests.cs b/src/tests/ReactiveUI.Primitives.Tests/SignalAliasCoverageTests.cs index c51b5114..02df22a5 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/SignalAliasCoverageTests.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/SignalAliasCoverageTests.cs @@ -42,7 +42,7 @@ public async Task RxFactoryAliasesCoverScheduledAndSwitchRangeBranches() await Assert.That(Signal.Timeout(Signal.Emit(One), TimeSpan.FromTicks(One), null)).IsNotNull(); List switchedRanges = []; - _ = Signal.Switch(Signal.FromEnumerable>([Signal.Range(One, Two), Signal.Range(Three, Two)])) + _ = Signal.Switch(Signal.FromEnumerable([Signal.Range(One, Two), Signal.Range(Three, Two)])) .Subscribe(switchedRanges.Add); await Assert.That(switchedRanges.SequenceEqual([One, Two, Three, Four])).IsTrue(); @@ -54,7 +54,7 @@ public async Task RxFactoryAliasesCoverScheduledAndSwitchRangeBranches() await Assert.That(emptySwitchCompleted).IsEqualTo(One); List nonRangeSwitch = []; - _ = Signal.Switch(Signal.FromEnumerable>([Signal.Emit(Five)])).Subscribe(nonRangeSwitch.Add); + _ = Signal.Switch(Signal.FromEnumerable([Signal.Emit(Five)])).Subscribe(nonRangeSwitch.Add); await Assert.That(nonRangeSwitch.SequenceEqual([Five])).IsTrue(); } diff --git a/src/tests/ReactiveUI.Primitives.Tests/SignalCreateTests.cs b/src/tests/ReactiveUI.Primitives.Tests/SignalCreateTests.cs index 288447f4..57286ec9 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/SignalCreateTests.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/SignalCreateTests.cs @@ -288,9 +288,9 @@ public async Task AsyncCreateFactoriesAssignDisposablesAndForwardFailures() nullSubscription.Dispose(); _ = Assert.Throws(static () => - Signal.Create((Func, Task>)null!)); + Signal.Create((Func, Task>)null!)); _ = Assert.Throws(static () => - Signal.Create((Func, CancellationToken, Task>)null!)); + Signal.Create((Func, CancellationToken, Task>)null!)); } /// Verifies asynchronous defer overloads emit, fail, and skip subscription after cancellation. @@ -299,15 +299,15 @@ public async Task AsyncCreateFactoriesAssignDisposablesAndForwardFailures() public async Task AsyncDeferFactoriesEmitFailAndHonorCancellation() { List values = []; - _ = Signal.Defer(static () => Task.FromResult>(Signal.Emit(CreatedValue))).Subscribe(values.Add); - _ = Signal.Defer(static _ => Task.FromResult>(Signal.Emit(First))).Subscribe(values.Add); + _ = Signal.Defer(static () => Task.FromResult(Signal.Emit(CreatedValue))).Subscribe(values.Add); + _ = Signal.Defer(static _ => Task.FromResult(Signal.Emit(First))).Subscribe(values.Add); await Task.Yield(); await Assert.That(values.SequenceEqual([CreatedValue, First])).IsTrue(); Exception? observed = null; InvalidOperationException expected = new("defer"); - _ = Signal.Defer(() => Task.FromException>(expected)) + _ = Signal.Defer(() => Task.FromException>(expected)) .Subscribe(static _ => { }, error => observed = error); await Task.Yield(); @@ -323,9 +323,9 @@ public async Task AsyncDeferFactoriesEmitFailAndHonorCancellation() await Assert.That(canceledValues.Count).IsEqualTo(0); - _ = Assert.Throws(static () => Signal.Defer((Func>>)null!)); + _ = Assert.Throws(static () => Signal.Defer((Func>>)null!)); _ = Assert.Throws(static () => - Signal.Defer((Func>>)null!)); + Signal.Defer((Func>>)null!)); } /// Verifies Rx-named signal factories route to the corresponding Primitives factories. diff --git a/src/tests/ReactiveUI.Primitives.Tests/SignalFactoriesTests.Behavior.cs b/src/tests/ReactiveUI.Primitives.Tests/SignalFactoriesTests.Behavior.cs index 2b9958a9..7f06038c 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/SignalFactoriesTests.Behavior.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/SignalFactoriesTests.Behavior.cs @@ -92,6 +92,6 @@ private static async Task AssertTaskFault(Func taskFactory, Typ return exception; } - throw new InvalidOperationException("Expected task fault " + expectedExceptionType.Name + "."); + throw new InvalidOperationException($"Expected task fault {expectedExceptionType.Name}."); } } diff --git a/src/tests/ReactiveUI.Primitives.Tests/SignalFactoriesTests.cs b/src/tests/ReactiveUI.Primitives.Tests/SignalFactoriesTests.cs index b996a336..5ab0f2b4 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/SignalFactoriesTests.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/SignalFactoriesTests.cs @@ -245,7 +245,7 @@ public async Task FromAsyncCancellableFactoryDisposalCancelsSubscriptionToken() TaskCompletionSource observedToken = new(TaskCreationOptions.RunContinuationsAsynchronously); TaskCompletionSource canceled = new(TaskCreationOptions.RunContinuationsAsynchronously); RecordingWitness observer = new(); - var subscription = Signal.FromAsync(async token => + var subscription = Signal.FromAsync(async token => { observedToken.SetResult(token); try @@ -277,7 +277,7 @@ public async Task FromAsyncCancellableFactoryDisposalSuppressesIgnoredCancellati TaskCompletionSource subscribed = new(TaskCreationOptions.RunContinuationsAsynchronously); TaskCompletionSource complete = new(TaskCreationOptions.RunContinuationsAsynchronously); RecordingWitness observer = new(); - var subscription = Signal.FromAsync(token => + var subscription = Signal.FromAsync(token => { subscribed.SetResult(); return complete.Task; @@ -299,7 +299,7 @@ public async Task FromAsyncCancellableFactoryExternalCancellationForwardsObserve using CancellationTokenSource external = new(); TaskCompletionSource observedToken = new(TaskCreationOptions.RunContinuationsAsynchronously); RecordingWitness observer = new(); - using var subscription = Signal.FromAsync( + using var subscription = Signal.FromAsync( async token => { observedToken.SetResult(token); @@ -324,7 +324,7 @@ public async Task FromAsyncCancellableFactoryExternalCancellationForwardsObserve TaskCompletionSource subscribed = new(TaskCreationOptions.RunContinuationsAsynchronously); TaskCompletionSource complete = new(TaskCreationOptions.RunContinuationsAsynchronously); RecordingWitness observer = new(); - using var subscription = Signal.FromAsync( + using var subscription = Signal.FromAsync( token => { subscribed.SetResult(); @@ -349,7 +349,7 @@ public async Task FromAsyncCancellableFactorySuccessEmitsValueAndCompletes() { var calls = 0; RecordingWitness observer = new(); - using var subscription = Signal.FromAsync(token => + using var subscription = Signal.FromAsync(token => { calls++; return Task.FromResult(token.IsCancellationRequested ? NinetyNine : Seven); @@ -367,7 +367,7 @@ public async Task FromAsyncCancellableFactoryFaultForwardsOriginalException() { InvalidOperationException expected = new("from-async-fault"); RecordingWitness observer = new(); - Signal.FromAsync(_ => Task.FromException(expected)).Subscribe(observer).Dispose(); + Signal.FromAsync(_ => Task.FromException(expected)).Subscribe(observer).Dispose(); await Assert.That(observer.Errors[0]).IsSameReferenceAs(expected); await Assert.That(observer.Values.Count).IsEqualTo(0); await Assert.That(observer.Completed).IsEqualTo(0); @@ -400,7 +400,7 @@ public async Task FromAsyncCancellableFactoryCanceledTasksForwardObserverErrors( using CancellationTokenSource taskCancellation = new(); await taskCancellation.CancelAsync(); RecordingWitness canceledTask = new(); - Signal.FromAsync(_ => Task.FromCanceled(taskCancellation.Token)) + Signal.FromAsync(_ => Task.FromCanceled(taskCancellation.Token)) .Subscribe(canceledTask).Dispose(); await Assert.That(canceledTask.Errors[0]).IsTypeOf(); await Assert.That(canceledTask.Values.Count).IsEqualTo(0); @@ -410,7 +410,7 @@ public async Task FromAsyncCancellableFactoryCanceledTasksForwardObserverErrors( await external.CancelAsync(); var invoked = 0; RecordingWitness externallyCanceled = new(); - Signal.FromAsync( + Signal.FromAsync( _ => { invoked++; @@ -430,7 +430,7 @@ public async Task FromAsyncCancellableFactoryPendingTasksForwardTerminalContinua { TaskCompletionSource successfulTask = new(TaskCreationOptions.RunContinuationsAsynchronously); RecordingWitness successful = new(); - using (Signal.FromAsync(_ => successfulTask.Task).Subscribe(successful)) + using (Signal.FromAsync(_ => successfulTask.Task).Subscribe(successful)) { successfulTask.SetResult(Seven); await TestPolling.SpinUntil( @@ -444,7 +444,7 @@ await TestPolling.SpinUntil( InvalidOperationException expected = new("from-async-pending-fault"); TaskCompletionSource faultedTask = new(TaskCreationOptions.RunContinuationsAsynchronously); RecordingWitness faulted = new(); - using (Signal.FromAsync(_ => faultedTask.Task).Subscribe(faulted)) + using (Signal.FromAsync(_ => faultedTask.Task).Subscribe(faulted)) { faultedTask.SetException(expected); await TestPolling.SpinUntil( @@ -460,7 +460,7 @@ await TestPolling.SpinUntil( await cancellation.CancelAsync(); TaskCompletionSource canceledTask = new(TaskCreationOptions.RunContinuationsAsynchronously); RecordingWitness canceled = new(); - using (Signal.FromAsync(_ => canceledTask.Task).Subscribe(canceled)) + using (Signal.FromAsync(_ => canceledTask.Task).Subscribe(canceled)) { canceledTask.SetCanceled(cancellation.Token); await TestPolling.SpinUntil( @@ -576,15 +576,15 @@ private static void AssertRxFactoryAliasesRejectInvalidArguments(Dictionary(static () => Signal.Repeat(One, -1)); _ = Assert.Throws(static () => - Signal.Generate(One, null!, static value => value, static value => value)); + Signal.Generate(One, null!, static value => value, static value => value)); _ = Assert.Throws(static () => - Signal.Generate(One, static _ => true, null!, static value => value)); + Signal.Generate(One, static _ => true, null!, static value => value)); _ = Assert.Throws(static () => Signal.Generate(One, static _ => true, static value => value, null!)); - _ = Assert.Throws(static () => Signal.If(null!, Signal.Emit(One))); + _ = Assert.Throws(static () => Signal.If(null!, Signal.Emit(One))); _ = Assert.Throws(static () => Signal.If(static () => true, null!, Signal.Emit(One))); _ = Assert.Throws(static () => Signal.If(static () => true, Signal.Emit(One), null!)); - _ = Assert.Throws(() => Signal.Case(null!, cases)); + _ = Assert.Throws(() => Signal.Case(null!, cases)); _ = Assert.Throws(static () => Signal.Case(static () => "one", null!)); _ = Assert.Throws(() => Signal.Case(static () => "one", cases, null!)); _ = Assert.Throws(static () => Signal.Using(null!, static _ => Signal.Emit(One))); diff --git a/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorMixinsTests.Behavior.cs b/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorMixinsTests.Behavior.cs index e1be0e84..d52ee57b 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorMixinsTests.Behavior.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorMixinsTests.Behavior.cs @@ -165,7 +165,7 @@ public async Task OperatorSurfaceCoversSuccessErrorAndEarlyTerminationBranches() await Assert.That(longCount.SequenceEqual(ExpectedLongCount)).IsTrue(); List selectMany = []; _ = Signal.FromEnumerable([1, Two]) - .FlatMap(static value => Signal.FromEnumerable([value, value + Ten]), static (outer, inner) => outer + ":" + inner) + .FlatMap(static value => Signal.FromEnumerable([value, value + Ten]), static (outer, inner) => $"{outer}:{inner}") .Subscribe(selectMany.Add); await Assert.That(selectMany.SequenceEqual(ExpectedSelectMany)).IsTrue(); List flatMapValues = []; diff --git a/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorMixinsTests.Deterministic.cs b/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorMixinsTests.Deterministic.cs index 334de531..4621f164 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorMixinsTests.Deterministic.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorMixinsTests.Deterministic.cs @@ -237,9 +237,9 @@ private static void AssertOperatorGuardsRejectNullSourcesAndCallbacks() _ = Assert.Throws(static () => LinqExtensions.Count(null!, static value => value > 0)); _ = Assert.Throws(static () => LinqExtensions.LongCount(null!, static value => value > 0)); _ = Assert.Throws(static () => - LinqExtensions.Blend((IObservable>)null!)); + LinqExtensions.Blend((IObservable>)null!)); _ = Assert.Throws(static () => - LinqExtensions.Blend((IEnumerable>)null!)); + LinqExtensions.Blend((IEnumerable>)null!)); _ = Assert.Throws(static () => LinqExtensions.Race(null!)); _ = Assert.Throws(static () => LinqExtensions.CollectArray(null!)); _ = Assert.Throws(static () => SubscribeExtensions.Subscribe(null!, static _ => { })); diff --git a/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorMixinsTests.cs b/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorMixinsTests.cs index 72c7d1b8..08ad7d98 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorMixinsTests.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorMixinsTests.cs @@ -126,14 +126,14 @@ public async Task ParityOperatorAliasesAndGuardsCoverRemainingBranches() List distinctKeys = []; List isEmptyValues = []; _ = source.Tap( - value => sideEffects.Add("next:" + value), - error => sideEffects.Add("error:" + error.Message), + value => sideEffects.Add($"next:{value}"), + error => sideEffects.Add($"error:{error.Message}"), () => sideEffects.Add("completed")) .Subscribe(values.Add, static ex => throw ex, () => completed++); _ = Signal.Fail(new InvalidOperationException("do-error")) .Tap( value => sideEffects.Add(value.ToString()), - error => sideEffects.Add("error:" + error.Message), + error => sideEffects.Add($"error:{error.Message}"), () => sideEffects.Add("unused")) .Subscribe(static _ => { }, static _ => { }, static () => { }); _ = Signal.None().DefaultIfEmpty().Subscribe(defaultValue.Add); @@ -240,7 +240,7 @@ public async Task PrimitiveShortcutBranchesCoverCancelableFallbackAndPendingTask await Assert.That(iteratorSwitch.SequenceEqual(ExpectedOneTwo)).IsTrue(); List stringSwitch = []; - _ = Signal.FromEnumerable>([Signal.Emit("value")]).SwitchTo().Subscribe(stringSwitch.Add); + _ = Signal.FromEnumerable([Signal.Emit("value")]).SwitchTo().Subscribe(stringSwitch.Add); await Assert.That(stringSwitch.SequenceEqual(ExpectedSingleValue)).IsTrue(); } diff --git a/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorParityMixinsTests.PostTerminalGuards.cs b/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorParityMixinsTests.PostTerminalGuards.cs index 672912cd..8fa6b724 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorParityMixinsTests.PostTerminalGuards.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorParityMixinsTests.PostTerminalGuards.cs @@ -66,7 +66,7 @@ public async Task FlatMapDropsOuterAndInnerNotificationsAfterTheSubscriptionIsDi IObserver? inner = null; RecordingWitness witness = new(); var subscription = new ScriptedObservable(observer => outer = observer) - .FlatMap(_ => new ScriptedObservable(observer => inner = observer)) + .FlatMap(_ => new ScriptedObservable(observer => inner = observer)) .Subscribe(witness); outer!.OnNext(First); inner!.OnNext(Second); diff --git a/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorParityMixinsTests.cs b/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorParityMixinsTests.cs index 205c5300..5da2213b 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorParityMixinsTests.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/SignalOperatorParityMixinsTests.cs @@ -159,28 +159,28 @@ public async Task TerminalObserversIgnoreLateSignalsAndForwardPredicateFailures( List values = []; var badIntegers = CreateSourceThatKeepsSignallingAfterItsError(); var lateCompletionIntegers = CreateSourceThatKeepsSignallingAfterItsCompletion(); - _ = badIntegers.Count().Subscribe(value => values.Add(value), ex => errors.Add("count:" + ex.Message)); - _ = badIntegers.LongCount().Subscribe(value => values.Add(value), ex => errors.Add("long-count:" + ex.Message)); + _ = badIntegers.Count().Subscribe(value => values.Add(value), ex => errors.Add($"count:{ex.Message}")); + _ = badIntegers.LongCount().Subscribe(value => values.Add(value), ex => errors.Add($"long-count:{ex.Message}")); _ = badIntegers.Count(static value => value > 0) - .Subscribe(value => values.Add(value), ex => errors.Add("count-predicate:" + ex.Message)); + .Subscribe(value => values.Add(value), ex => errors.Add($"count-predicate:{ex.Message}")); _ = badIntegers.LongCount(static value => value > 0) - .Subscribe(value => values.Add(value), ex => errors.Add("long-count-predicate:" + ex.Message)); - _ = badIntegers.Any().Subscribe(value => values.Add(value), ex => errors.Add("any:" + ex.Message)); + .Subscribe(value => values.Add(value), ex => errors.Add($"long-count-predicate:{ex.Message}")); + _ = badIntegers.Any().Subscribe(value => values.Add(value), ex => errors.Add($"any:{ex.Message}")); _ = badIntegers.Any(static value => value > 0) - .Subscribe(value => values.Add(value), ex => errors.Add("any-predicate:" + ex.Message)); + .Subscribe(value => values.Add(value), ex => errors.Add($"any-predicate:{ex.Message}")); _ = badIntegers.All(static value => value > 0) - .Subscribe(value => values.Add(value), ex => errors.Add("all:" + ex.Message)); - _ = badIntegers.Contains(Two).Subscribe(value => values.Add(value), ex => errors.Add("contains:" + ex.Message)); + .Subscribe(value => values.Add(value), ex => errors.Add($"all:{ex.Message}")); + _ = badIntegers.Contains(Two).Subscribe(value => values.Add(value), ex => errors.Add($"contains:{ex.Message}")); _ = lateCompletionIntegers.Count() - .Subscribe(value => values.Add(value), ex => errors.Add("late-count:" + ex.Message)); + .Subscribe(value => values.Add(value), ex => errors.Add($"late-count:{ex.Message}")); _ = lateCompletionIntegers.LongCount() - .Subscribe(value => values.Add(value), ex => errors.Add("late-long-count:" + ex.Message)); + .Subscribe(value => values.Add(value), ex => errors.Add($"late-long-count:{ex.Message}")); _ = lateCompletionIntegers.Any(static value => value > 0) - .Subscribe(value => values.Add(value), ex => errors.Add("late-any:" + ex.Message)); + .Subscribe(value => values.Add(value), ex => errors.Add($"late-any:{ex.Message}")); _ = lateCompletionIntegers.All(static value => value > 0) - .Subscribe(value => values.Add(value), ex => errors.Add("late-all:" + ex.Message)); + .Subscribe(value => values.Add(value), ex => errors.Add($"late-all:{ex.Message}")); _ = lateCompletionIntegers.Contains(One) - .Subscribe(value => values.Add(value), ex => errors.Add("late-contains:" + ex.Message)); + .Subscribe(value => values.Add(value), ex => errors.Add($"late-contains:{ex.Message}")); DriveTerminalOperatorsWhosePredicateOrComparerThrows(errors); await Assert.That(errors).Contains("count:first-terminal"); await Assert.That(errors).Contains("long-count:first-terminal"); diff --git a/src/tests/ReactiveUI.Primitives.Tests/SparkTests.cs b/src/tests/ReactiveUI.Primitives.Tests/SparkTests.cs index 626529ad..98e811e8 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/SparkTests.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/SparkTests.cs @@ -61,10 +61,10 @@ public async Task SparkOnNextCoversValueEqualityAndAcceptOverloads() next.Accept((IObserver)observer); await Assert.That(next.Accept(observer)).IsEqualTo("next:42"); next.Accept( - value => observer.Events.Add("delegate-next:" + value), + value => observer.Events.Add($"delegate-next:{value}"), ex => observer.Events.Add(ex.Message), () => observer.Events.Add(DelegateCompletedText)); - await Assert.That(next.Accept(static value => "fn-next:" + value, static ex => ex.Message, static () => FunctionCompletedText)) + await Assert.That(next.Accept(static value => $"fn-next:{value}", static ex => ex.Message, static () => FunctionCompletedText)) .IsEqualTo("fn-next:42"); _ = Assert.Throws(() => next.Accept((IObserver)null!)); _ = Assert.Throws(() => next.Accept(null!)); @@ -104,11 +104,11 @@ await Assert.That( await Assert.That(errorSpark.Accept(observer)).IsEqualTo("error:spark-error"); errorSpark.Accept( value => observer.Events.Add(value.ToString()), - ex => observer.Events.Add("delegate-error:" + ex.Message), + ex => observer.Events.Add($"delegate-error:{ex.Message}"), () => observer.Events.Add(DelegateCompletedText)); var errorResult = errorSpark.Accept( static value => value.ToString(), - static ex => "fn-error:" + ex.Message, + static ex => $"fn-error:{ex.Message}", static () => FunctionCompletedText); await Assert.That(errorResult).IsEqualTo("fn-error:spark-error"); _ = Assert.Throws(static () => Spark.CreateOnError(null!)); diff --git a/src/tests/ReactiveUI.Primitives.Tests/TestClasses/EmptySequencer.cs b/src/tests/ReactiveUI.Primitives.Tests/TestClasses/EmptySequencer.cs index b7f49288..04461dcb 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/TestClasses/EmptySequencer.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/TestClasses/EmptySequencer.cs @@ -7,15 +7,15 @@ namespace ReactiveUI.Primitives.Concurrency; /// Provides a sequencer test double that rejects scheduled work. internal sealed class EmptySequencer : ISequencer { - /// Gets the shared empty sequencer instance. - public static EmptySequencer Instance { get; } = new(); - /// public DateTimeOffset Now => DateTimeOffset.UnixEpoch; /// public long Timestamp => 0; + /// Gets the shared empty sequencer instance. + internal static EmptySequencer Instance { get; } = new(); + /// public void Schedule(IWorkItem item) => throw new NotSupportedException(); diff --git a/src/tests/ReactiveUI.Primitives.Tests/WitnessTests.cs b/src/tests/ReactiveUI.Primitives.Tests/WitnessTests.cs index 6f8de8d3..4cdaba56 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/WitnessTests.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/WitnessTests.cs @@ -63,13 +63,13 @@ public async Task WitnessCreateRoutesCallbacks() List calls = []; InvalidOperationException error = new("boom"); var witness = Witness.Create( - value => calls.Add("N" + value), - ex => calls.Add("E" + ex.Message), + value => calls.Add($"N{value}"), + ex => calls.Add($"E{ex.Message}"), () => calls.Add("C")); witness.OnNext(ObservedValue); witness.OnError(error); witness.OnCompleted(); - string[] expected = ["N" + ObservedValue, "Eboom", "C"]; + string[] expected = [$"N{ObservedValue}", "Eboom", "C"]; await Assert.That(calls.SequenceEqual(expected)).IsTrue(); } @@ -84,8 +84,8 @@ public async Task SafeWitnessIgnoresSignalsAfterTerminalAndDisposesOnce() var disposed = 0; var witness = Witness.Safe( Witness.Create( - value => calls.Add("N" + value), - ex => calls.Add("E" + ex.Message), + value => calls.Add($"N{value}"), + ex => calls.Add($"E{ex.Message}"), () => calls.Add("C")), new ActionDisposable(() => disposed++)); witness.OnNext(FirstValue); @@ -93,7 +93,7 @@ public async Task SafeWitnessIgnoresSignalsAfterTerminalAndDisposesOnce() witness.OnNext(LateValue); witness.OnError(new InvalidOperationException("late")); witness.OnCompleted(); - string[] expected = ["N" + FirstValue, "C"]; + string[] expected = [$"N{FirstValue}", "C"]; await Assert.That(calls.SequenceEqual(expected)).IsTrue(); await Assert.That(disposed).IsEqualTo(1); } @@ -135,8 +135,8 @@ public async Task WitnessesCoverDisposedThrowEmptyAndSafeBranches() var cancelDisposed = 0; var safe = Witness.Safe( Witness.Create( - value => events.Add("next:" + value), - ex => events.Add("error:" + ex.Message), + value => events.Add($"next:{value}"), + ex => events.Add($"error:{ex.Message}"), () => events.Add("completed")), new ActionDisposable(() => cancelDisposed++)); safe.OnNext(Three); From 7cfe5dfedc6d6fbe8326b9106124c48a700f6a7d Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:11:03 +1000 Subject: [PATCH 03/18] build(deps): update RoslynCommonAnalyzers to 3.30.1 Clears the two analyzer false positives that blocked the test projects: SST2505 on TUnit's injected CancellationToken, and SST2324 on a TUnit lifecycle hook that the framework requires to be public. --- src/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 67a86c17..95dc8da2 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -5,7 +5,7 @@ true - 3.30.0 + 3.30.1 From 69857c172f3510f44238d1474905ef3e19b2ce43 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:33:03 +1000 Subject: [PATCH 04/18] fix(concurrency): restore public Run and reorder WasmScheduler disposal members - DispatchWorkItemBase.Run must stay public: derived types use it to implicitly implement IDispatchWorkItem/IReadyWorkItem, which the compiler rejects on a non-public member (CS0737) - Move the public Dispose above the internal members in the WasmScheduler work items so accessibility ordering holds --- .../DispatchWorkItemBase.cs | 2 +- .../Concurrency/WasmScheduler.cs | 60 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Primitives.Platform.Reactive.Shared/DispatchWorkItemBase.cs b/src/Primitives.Platform.Reactive.Shared/DispatchWorkItemBase.cs index 326c5188..ce2ff3aa 100644 --- a/src/Primitives.Platform.Reactive.Shared/DispatchWorkItemBase.cs +++ b/src/Primitives.Platform.Reactive.Shared/DispatchWorkItemBase.cs @@ -54,7 +54,7 @@ protected DispatchWorkItemBase( internal bool IsDisposed => Volatile.Read(ref _isDisposed) != 0; /// Runs the scheduled action unless it has already been cancelled, disposing its result if a cancel races the start. - internal void Run() + public void Run() { if (IsDisposed) { diff --git a/src/ReactiveUI.Primitives.Reactive/Concurrency/WasmScheduler.cs b/src/ReactiveUI.Primitives.Reactive/Concurrency/WasmScheduler.cs index ae09b76b..840940e7 100644 --- a/src/ReactiveUI.Primitives.Reactive/Concurrency/WasmScheduler.cs +++ b/src/ReactiveUI.Primitives.Reactive/Concurrency/WasmScheduler.cs @@ -311,19 +311,6 @@ public StatefulWorkItem(WasmScheduler scheduler, TState state, FuncStores the one-shot timer so the caller's disposable cancels and releases it. - /// The armed timer. - internal void AttachTimer(Timer timer) - { - Volatile.Write(ref _timer, timer); - if (!IsDisposed) - { - return; - } - - timer.Dispose(); - } - /// public void Dispose() { @@ -337,6 +324,19 @@ public void Dispose() Interlocked.Exchange(ref _timer, null)?.Dispose(); ReleaseStartedWork(); } + + /// Stores the one-shot timer so the caller's disposable cancels and releases it. + /// The armed timer. + internal void AttachTimer(Timer timer) + { + Volatile.Write(ref _timer, timer); + if (!IsDisposed) + { + return; + } + + timer.Dispose(); + } } /// Periodic work driven by a timer; ticks are serialized under a gate. @@ -370,6 +370,23 @@ private PeriodicWorkItem(TState state, Func action) _action = action; } + /// + public void Dispose() + { + lock (_gate) + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + _timer?.Dispose(); + _timer = null; + _state = default!; + } + } + /// /// Creates a periodic item and arms its timer. Arming it here rather than in the constructor is what keeps /// the tick callback from ever seeing a half-built item: the timer is created disarmed, attached, and only @@ -393,23 +410,6 @@ internal static PeriodicWorkItem Start(TState state, TimeSpan period, Fu return item; } - /// - public void Dispose() - { - lock (_gate) - { - if (_isDisposed) - { - return; - } - - _isDisposed = true; - _timer?.Dispose(); - _timer = null; - _state = default!; - } - } - /// /// Runs one periodic tick. Internal rather than private so a test can drive the tick a timer callback already /// in flight would deliver after won the race, and prove the action does not run. From b22d42343f17e8c24d234865b1a2b23a07037082 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:24:57 +1000 Subject: [PATCH 05/18] build(deps): update RoslynCommonAnalyzers to 3.30.2 - Narrow dead public modifiers in the benchmarks project, the Async.Reactive sequencer seam, and the remaining test helpers - Move the now-static ScheduleDelayed above the instance members - Rewrite literal concatenations and drop inferable type arguments in the benchmarks and Reactive test projects --- src/Directory.Packages.props | 2 +- .../Concurrency/Sequencer.cs | 8 ++++---- .../SequencerSchedulingExtensions.cs | 14 +++++++------- .../Platforms/apple/NSRunloopSequencer.cs | 18 +++++++++--------- .../AllocationProbe.cs | 2 +- .../BoolSignalWitness.cs | 10 +++++----- .../BooleanSignalWitness.cs | 6 +++--- .../ConnectableMulticastBenchmarks.cs | 2 +- .../CountingR3Witness.cs | 6 +++--- .../CountingSignalWitness.cs | 6 +++--- .../IntR3Witness.cs | 10 +++++----- .../IntSignalWitness.cs | 10 +++++----- .../LongSignalWitness.cs | 10 +++++----- .../Program.cs | 2 +- ...veExtensionsComparisonBenchmarks.Helpers.cs | 4 ++-- .../SmokeBenchmarkRunner.cs | 2 +- .../SmokeParityValidator.cs | 5 ++--- .../LinqExtensionsTests.cs | 10 +++++----- 18 files changed, 63 insertions(+), 64 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 95dc8da2..77db6605 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -5,7 +5,7 @@ true - 3.30.1 + 3.30.2 diff --git a/src/ReactiveUI.Primitives.Async.Reactive/Concurrency/Sequencer.cs b/src/ReactiveUI.Primitives.Async.Reactive/Concurrency/Sequencer.cs index ff8eaa6b..f5372055 100644 --- a/src/ReactiveUI.Primitives.Async.Reactive/Concurrency/Sequencer.cs +++ b/src/ReactiveUI.Primitives.Async.Reactive/Concurrency/Sequencer.cs @@ -11,16 +11,16 @@ namespace ReactiveUI.Primitives.Reactive.Concurrency; internal static class Sequencer { /// Gets a scheduler that schedules work as soon as possible on the current thread. - public static IScheduler CurrentThread => CurrentThreadScheduler.Instance; + internal static IScheduler CurrentThread => CurrentThreadScheduler.Instance; /// Gets a scheduler that schedules work immediately on the current thread. - public static IScheduler Immediate => ImmediateScheduler.Instance; + internal static IScheduler Immediate => ImmediateScheduler.Instance; /// Gets the default queueing scheduler for background work. - public static IScheduler Default => TaskPoolScheduler.Default; + internal static IScheduler Default => TaskPoolScheduler.Default; /// Normalizes the specified value to a positive value. /// The value to normalize. /// when zero or positive; otherwise . - public static TimeSpan Normalize(TimeSpan timeSpan) => RxScheduler.Normalize(timeSpan); + internal static TimeSpan Normalize(TimeSpan timeSpan) => RxScheduler.Normalize(timeSpan); } diff --git a/src/ReactiveUI.Primitives.Async.Reactive/Concurrency/SequencerSchedulingExtensions.cs b/src/ReactiveUI.Primitives.Async.Reactive/Concurrency/SequencerSchedulingExtensions.cs index 6021356e..9406c414 100644 --- a/src/ReactiveUI.Primitives.Async.Reactive/Concurrency/SequencerSchedulingExtensions.cs +++ b/src/ReactiveUI.Primitives.Async.Reactive/Concurrency/SequencerSchedulingExtensions.cs @@ -22,7 +22,7 @@ internal static class SequencerSchedulingExtensions { /// Schedules a work item for execution as soon as possible. /// Work item to execute. - public void Schedule(IWorkItem item) + internal void Schedule(IWorkItem item) { ArgumentExceptionHelper.ThrowIfNull(item); @@ -36,33 +36,33 @@ public void Schedule(IWorkItem item) /// Schedules an action to be executed. /// Action to execute. /// The disposable used to cancel the scheduled action. - public IDisposable Schedule(Action action) => RxScheduler.Schedule(scheduler, action); + internal IDisposable Schedule(Action action) => RxScheduler.Schedule(scheduler, action); /// Schedules an action to be executed after a relative due time. /// Relative time after which to execute the action. /// Action to execute. /// The disposable used to cancel the scheduled action. - public IDisposable Schedule(TimeSpan dueTime, Action action) => + internal IDisposable Schedule(TimeSpan dueTime, Action action) => RxScheduler.Schedule(scheduler, dueTime, action); /// Schedules an action to be executed at an absolute due time. /// Absolute time at which to execute the action. /// Action to execute. /// The disposable used to cancel the scheduled action. - public IDisposable Schedule(DateTimeOffset dueTime, Action action) => + internal IDisposable Schedule(DateTimeOffset dueTime, Action action) => RxScheduler.Schedule(scheduler, dueTime, action); /// Schedules a recursive action to be executed. /// Recursive action; invoke its argument to schedule the next iteration. /// The disposable used to cancel recursive work. - public IDisposable Schedule(Action action) => RxScheduler.Schedule(scheduler, action); + internal IDisposable Schedule(Action action) => RxScheduler.Schedule(scheduler, action); /// Schedules a stateful action without capturing state in a closure. /// The type of the state passed to the action. /// State passed to the action. /// Action to execute. /// The disposable used to cancel the scheduled action. - public IDisposable Schedule(TState state, Action action) => + internal IDisposable Schedule(TState state, Action action) => scheduler.Schedule((state, action), static (_, tuple) => { tuple.action(tuple.state); @@ -75,7 +75,7 @@ public IDisposable Schedule(TState state, Action action) => /// Relative time after which to execute the action. /// Action to execute. /// The disposable used to cancel the scheduled action. - public IDisposable Schedule(TState state, TimeSpan dueTime, Action action) => + internal IDisposable Schedule(TState state, TimeSpan dueTime, Action action) => scheduler.Schedule((state, action), dueTime, static (_, tuple) => { tuple.action(tuple.state); diff --git a/src/ReactiveUI.Primitives/Platforms/apple/NSRunloopSequencer.cs b/src/ReactiveUI.Primitives/Platforms/apple/NSRunloopSequencer.cs index f0a70caf..be0285e6 100644 --- a/src/ReactiveUI.Primitives/Platforms/apple/NSRunloopSequencer.cs +++ b/src/ReactiveUI.Primitives/Platforms/apple/NSRunloopSequencer.cs @@ -53,6 +53,15 @@ public sealed class NSRunloopSequencer : ISequencer /// public void Schedule(IWorkItem item, long dueTimestamp) => _state.Schedule(item, dueTimestamp); + /// Runs delayed work through the main queue's native delayed dispatch. + /// Work item to execute at the due time. + /// Absolute monotonic timestamp at which to execute the item. + private static void ScheduleDelayed(IWorkItem item, long dueTimestamp) + { + var nanoseconds = (long)DispatchSequencerState.DelayUntil(dueTimestamp).TotalMilliseconds * NanosecondsPerMillisecond; + DispatchQueue.MainQueue.DispatchAfter(new DispatchTime(DispatchTime.Now, nanoseconds), () => DispatchSequencerState.RunIfActive(item)); + } + /// Marshals the cached drain callback onto the main dispatch queue. /// The drain callback. /// , since the main queue always accepts the work. @@ -63,15 +72,6 @@ private bool Post(Action drain) return true; } - /// Runs delayed work through the main queue's native delayed dispatch. - /// Work item to execute at the due time. - /// Absolute monotonic timestamp at which to execute the item. - private static void ScheduleDelayed(IWorkItem item, long dueTimestamp) - { - var nanoseconds = (long)DispatchSequencerState.DelayUntil(dueTimestamp).TotalMilliseconds * NanosecondsPerMillisecond; - DispatchQueue.MainQueue.DispatchAfter(new DispatchTime(DispatchTime.Now, nanoseconds), () => DispatchSequencerState.RunIfActive(item)); - } - /// Forwards the cached drain callback to the engine. private void RunDrain() => _state.RunDrain(); } diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/AllocationProbe.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/AllocationProbe.cs index ce896782..e5dca13b 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/AllocationProbe.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/AllocationProbe.cs @@ -76,7 +76,7 @@ internal static class AllocationProbe private const int ValueColumnWidth = 6; /// Runs the probe and prints a per-operator allocation table. - public static void Run() + internal static void Run() { // Shared, reused observer: allocated once here, never inside a measured op, so its bytes // are excluded from every measurement. diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/BoolSignalWitness.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/BoolSignalWitness.cs index 530ee253..85576767 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/BoolSignalWitness.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/BoolSignalWitness.cs @@ -8,19 +8,19 @@ namespace ReactiveUI.Primitives.Benchmarks; internal sealed class BoolSignalWitness : IObserver { /// Gets the total of true values observed. - public int Total { get; private set; } + internal int Total { get; private set; } /// Gets the number of onNext calls. - public int NextCount { get; private set; } + internal int NextCount { get; private set; } /// Gets a value indicating whether the last observed value was true. - public bool LastValue { get; private set; } + internal bool LastValue { get; private set; } /// Gets the number of terminal completions observed. - public int CompletionCount { get; private set; } + internal int CompletionCount { get; private set; } /// Gets the number of errors observed. - public int ErrorCount { get; private set; } + internal int ErrorCount { get; private set; } /// public void OnNext(bool value) diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/BooleanSignalWitness.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/BooleanSignalWitness.cs index 4057df23..7d905bd2 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/BooleanSignalWitness.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/BooleanSignalWitness.cs @@ -8,13 +8,13 @@ namespace ReactiveUI.Primitives.Benchmarks; internal sealed class BooleanSignalWitness : IObserver { /// Gets a value indicating whether the latest sequence value was . - public bool Value { get; private set; } + internal bool Value { get; private set; } /// Gets the number of terminal completions observed. - public int CompletionCount { get; private set; } + internal int CompletionCount { get; private set; } /// Gets the number of errors observed. - public int ErrorCount { get; private set; } + internal int ErrorCount { get; private set; } /// Called when a value is received. /// The value. diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ConnectableMulticastBenchmarks.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ConnectableMulticastBenchmarks.cs index 0fff8d58..c1759fb3 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ConnectableMulticastBenchmarks.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ConnectableMulticastBenchmarks.cs @@ -43,7 +43,7 @@ public int SystemReactiveMulticastConnect() { IntSignalWitness observer = new(); var connectable = RxObservable.Range(Start, Count) - .Multicast(new System.Reactive.Subjects.Subject()); + .Multicast(new System.Reactive.Subjects.Subject()); using var subscription = connectable.Subscribe(observer); using var connection = connectable.Connect(); return observer.Total; diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/CountingR3Witness.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/CountingR3Witness.cs index 72665bae..d4c47070 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/CountingR3Witness.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/CountingR3Witness.cs @@ -11,13 +11,13 @@ namespace ReactiveUI.Primitives.Benchmarks; internal sealed class CountingR3Witness : Observer { /// Gets the number of onNext calls. - public int Count { get; private set; } + internal int Count { get; private set; } /// Gets the number of terminal completions observed. - public int CompletionCount { get; private set; } + internal int CompletionCount { get; private set; } /// Gets the number of errors observed. - public int ErrorCount { get; private set; } + internal int ErrorCount { get; private set; } /// Called for each emitted value. /// The emitted value. diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/CountingSignalWitness.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/CountingSignalWitness.cs index 487d5055..cad50c0b 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/CountingSignalWitness.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/CountingSignalWitness.cs @@ -9,13 +9,13 @@ namespace ReactiveUI.Primitives.Benchmarks; internal sealed class CountingSignalWitness : IObserver { /// Gets the number of onNext calls. - public int Count { get; private set; } + internal int Count { get; private set; } /// Gets the number of terminal completions observed. - public int CompletionCount { get; private set; } + internal int CompletionCount { get; private set; } /// Gets the number of errors observed. - public int ErrorCount { get; private set; } + internal int ErrorCount { get; private set; } /// public void OnNext(T value) => Count++; diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/IntR3Witness.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/IntR3Witness.cs index 808aa5cd..76be3456 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/IntR3Witness.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/IntR3Witness.cs @@ -10,19 +10,19 @@ namespace ReactiveUI.Primitives.Benchmarks; internal sealed class IntR3Witness : Observer { /// Gets the total of received values. - public int Total { get; private set; } + internal int Total { get; private set; } /// Gets the number of onNext calls. - public int NextCount { get; private set; } + internal int NextCount { get; private set; } /// Gets the last value observed. - public int LastValue { get; private set; } + internal int LastValue { get; private set; } /// Gets the number of terminal completions observed. - public int CompletionCount { get; private set; } + internal int CompletionCount { get; private set; } /// Gets the number of errors observed. - public int ErrorCount { get; private set; } + internal int ErrorCount { get; private set; } /// Called for each emitted value. /// The emitted value. diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/IntSignalWitness.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/IntSignalWitness.cs index 8ede16f3..6652f19b 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/IntSignalWitness.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/IntSignalWitness.cs @@ -8,19 +8,19 @@ namespace ReactiveUI.Primitives.Benchmarks; internal sealed class IntSignalWitness : IObserver { /// Gets the total of received values. - public int Total { get; private set; } + internal int Total { get; private set; } /// Gets the number of onNext calls. - public int NextCount { get; private set; } + internal int NextCount { get; private set; } /// Gets the last value observed. - public int LastValue { get; private set; } + internal int LastValue { get; private set; } /// Gets the number of terminal completions observed. - public int CompletionCount { get; private set; } + internal int CompletionCount { get; private set; } /// Gets the number of errors observed. - public int ErrorCount { get; private set; } + internal int ErrorCount { get; private set; } /// public void OnNext(int value) diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/LongSignalWitness.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/LongSignalWitness.cs index 6f98ec15..a2ed404f 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/LongSignalWitness.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/LongSignalWitness.cs @@ -8,19 +8,19 @@ namespace ReactiveUI.Primitives.Benchmarks; internal sealed class LongSignalWitness : IObserver { /// Gets the total of received values. - public long Total { get; private set; } + internal long Total { get; private set; } /// Gets the number of onNext calls. - public int NextCount { get; private set; } + internal int NextCount { get; private set; } /// Gets the last value observed. - public long LastValue { get; private set; } + internal long LastValue { get; private set; } /// Gets the number of terminal completions observed. - public int CompletionCount { get; private set; } + internal int CompletionCount { get; private set; } /// Gets the number of errors observed. - public int ErrorCount { get; private set; } + internal int ErrorCount { get; private set; } /// public void OnNext(long value) diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/Program.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/Program.cs index 93534659..99774a46 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/Program.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/Program.cs @@ -14,7 +14,7 @@ internal static class Program /// Executes benchmarks, or runs a deterministic smoke check with --smoke. /// BenchmarkDotNet command-line arguments. /// A task that completes when execution is finished. - public static async Task Main(string[] args) + internal static async Task Main(string[] args) { if (args.Contains("--alloc", StringComparer.OrdinalIgnoreCase)) { diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReactiveExtensionsComparisonBenchmarks.Helpers.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReactiveExtensionsComparisonBenchmarks.Helpers.cs index eb10a6e7..a762ae57 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReactiveExtensionsComparisonBenchmarks.Helpers.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReactiveExtensionsComparisonBenchmarks.Helpers.cs @@ -279,8 +279,8 @@ public void OnCompleted() /// Provides the DummyResource benchmark helper type. private sealed class DummyResource : IDisposable { - /// Gets the TouchCount benchmark helper value. - public int TouchCount { get; private set; } + /// Gets or sets the TouchCount benchmark helper value. + private int TouchCount { get; set; } /// public void Dispose() diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/SmokeBenchmarkRunner.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/SmokeBenchmarkRunner.cs index 0b0f1d6f..d3284d5b 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/SmokeBenchmarkRunner.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/SmokeBenchmarkRunner.cs @@ -13,7 +13,7 @@ internal static class SmokeBenchmarkRunner { /// Runs the deterministic smoke benchmark scenarios and writes their results to the console. /// A task that completes when all smoke benchmarks have run. - public static async Task RunAsync() + internal static async Task RunAsync() { RunSignalFactorySmoke(); await RunOperatorSmokeAsync(); diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/SmokeParityValidator.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/SmokeParityValidator.cs index 1cb6e90f..0399c192 100644 --- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/SmokeParityValidator.cs +++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/SmokeParityValidator.cs @@ -57,7 +57,7 @@ internal static class SmokeParityValidator /// Validates the captured smoke output for parity across the participating libraries. /// The captured smoke benchmark console output. - public static void Validate(string output) + internal static void Validate(string output) { var results = output.Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries); List failures = []; @@ -94,8 +94,7 @@ public static void Validate(string output) if (failures.Count > 0) { throw new InvalidOperationException( - "Benchmark smoke parity validation failed:" + Environment.NewLine + - string.Join(Environment.NewLine, failures)); + $"Benchmark smoke parity validation failed:{Environment.NewLine}{string.Join(Environment.NewLine, failures)}"); } Console.WriteLine($"Smoke parity validation passed for {groupCount} benchmark groups."); diff --git a/src/tests/ReactiveUI.Primitives.Reactive.Tests/LinqExtensionsTests.cs b/src/tests/ReactiveUI.Primitives.Reactive.Tests/LinqExtensionsTests.cs index da27073d..21349d75 100644 --- a/src/tests/ReactiveUI.Primitives.Reactive.Tests/LinqExtensionsTests.cs +++ b/src/tests/ReactiveUI.Primitives.Reactive.Tests/LinqExtensionsTests.cs @@ -87,28 +87,28 @@ public async Task SubscribeSafeStaticAliasAcceptsNonNullableValueTypeOverloads() void OnError(Exception error) => observed = error; - using var callbackSubscription = ReactiveLinqExtensions.SubscribeSafe( + using var callbackSubscription = ReactiveLinqExtensions.SubscribeSafe( source, values.Add, OnError, (byte)0); - using var completionSubscription = ReactiveLinqExtensions.SubscribeSafe( + using var completionSubscription = ReactiveLinqExtensions.SubscribeSafe( source, values.Add, OnError, () => completed++, (byte)0); - using var observerSubscription = ReactiveLinqExtensions.SubscribeSafe( + using var observerSubscription = ReactiveLinqExtensions.SubscribeSafe( source, Observer.Create(values.Add, OnError), (byte)0); InvalidOperationException expected = new("expected"); - using var errorSubscription = ReactiveLinqExtensions.SubscribeSafe( + using var errorSubscription = ReactiveLinqExtensions.SubscribeSafe( Observable.Throw(expected), OnError, (byte)0); - using var terminalSubscription = ReactiveLinqExtensions.SubscribeSafe( + using var terminalSubscription = ReactiveLinqExtensions.SubscribeSafe( source, OnError, () => completed++, From 063e3649b952b40935d247a086513774c8ab85e5 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:05:30 +1000 Subject: [PATCH 06/18] build(deps): update RoslynCommonAnalyzers to 3.30.3 Clears the PSH1021 false positive on the allocation-measurement harness, which needs GC.Collect to settle the heap before sampling allocations. --- src/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 77db6605..5b4a7ba1 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -5,7 +5,7 @@ true - 3.30.2 + 3.30.3 From 8cc5625b863294939ba849846b584083abec21b6 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:31:15 +1000 Subject: [PATCH 07/18] chore(editorconfig): disable Sonar rules covered in-box or obsolete Turn off 42 SonarAnalyzer rules whose behaviour is already provided by the in-box .NET (CA)/compiler analyzers, or that target obsolete tech (WCF, CAS, COM, APM, legacy serialization). WinForms STAThread (S4210) stays enabled. --- .editorconfig | 84 +++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/.editorconfig b/.editorconfig index cf1749e8..1a82638b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2063,7 +2063,7 @@ dotnet_diagnostic.S2952.severity = error # Classes should "Dispose" of members f dotnet_diagnostic.S3449.severity = error # Right operands of shift operators should be integers dotnet_diagnostic.S4275.severity = none # Getters and setters should access the expected fields — covered by SST2422 dotnet_diagnostic.S4277.severity = none # "Shared" parts should not be created with "new" — covered by SST2473 -dotnet_diagnostic.S4583.severity = error # Calls to delegate's method "BeginInvoke" should be paired with calls to "EndInvoke" +dotnet_diagnostic.S4583.severity = none # Calls to delegate's method "BeginInvoke" should be paired with calls to "EndInvoke" dotnet_diagnostic.S4586.severity = none # Non-async "Task/Task" methods should not return null — covered by PSH1312 dotnet_diagnostic.S5856.severity = none # Regular expressions should be syntactically valid — covered by SST2444 dotnet_diagnostic.S6674.severity = none # Log message template should be syntactically correct — covered by SST2441 @@ -2087,10 +2087,10 @@ dotnet_diagnostic.S2445.severity = none # Blocks should be synchronized on read- dotnet_diagnostic.S2688.severity = none # "NaN" should not be used in comparisons — covered by SST1473 dotnet_diagnostic.S2757.severity = none # Non-existent operators like "=+" should not be used — covered by SST2417 dotnet_diagnostic.S2761.severity = none # Doubled prefix operators "!!" and "~~" should not be used — covered by SST1190 -dotnet_diagnostic.S2995.severity = error # "Object.ReferenceEquals" should not be used for value types -dotnet_diagnostic.S2996.severity = error # "ThreadStatic" fields should not be initialized +dotnet_diagnostic.S2995.severity = none # "Object.ReferenceEquals" should not be used for value types +dotnet_diagnostic.S2996.severity = none # "ThreadStatic" fields should not be initialized dotnet_diagnostic.S2997.severity = none # "IDisposables" created in a "using" statement should not be returned — covered by SST2423 -dotnet_diagnostic.S3005.severity = error # "ThreadStatic" should not be used on non-static fields +dotnet_diagnostic.S3005.severity = none # "ThreadStatic" should not be used on non-static fields dotnet_diagnostic.S3168.severity = none # "async" methods should not return "void" — covered by SST1905 dotnet_diagnostic.S3172.severity = none # Delegates should not be subtracted — covered by SST2448 dotnet_diagnostic.S3244.severity = none # Anonymous delegates should not be used to unsubscribe from Events — covered by SST2449 @@ -2100,12 +2100,12 @@ dotnet_diagnostic.S3343.severity = none # Caller information parameters should c dotnet_diagnostic.S3346.severity = none # Expressions used in "Debug.Assert" should not produce side effects — covered by SST2450 dotnet_diagnostic.S3453.severity = none # Classes should not have only "private" constructors — covered by SST2451 dotnet_diagnostic.S3466.severity = none # Optional parameters should be passed to "base" calls — covered by SST2425 -dotnet_diagnostic.S3598.severity = error # One-way "OperationContract" methods should have "void" return type +dotnet_diagnostic.S3598.severity = none # One-way "OperationContract" methods should have "void" return type dotnet_diagnostic.S3603.severity = none # Methods with "Pure" attribute should return a value — covered by SST2452 -dotnet_diagnostic.S3610.severity = error # Nullable type comparison should not be redundant +dotnet_diagnostic.S3610.severity = none # Nullable type comparison should not be redundant dotnet_diagnostic.S3903.severity = none # Types should be defined in named namespaces — covered by SST2312 dotnet_diagnostic.S3923.severity = none # All branches in a conditional structure should not have exactly the same implementation — covered by SST1476 -dotnet_diagnostic.S3926.severity = error # Deserialization methods should be provided for "OptionalField" members +dotnet_diagnostic.S3926.severity = none # Deserialization methods should be provided for "OptionalField" members dotnet_diagnostic.S3927.severity = none # Serialization event handlers should be implemented correctly — covered by SST2430 dotnet_diagnostic.S3981.severity = none # Collection sizes and array length comparisons should make sense — covered by SST1479 dotnet_diagnostic.S3984.severity = none # Exceptions should not be created without being thrown — covered by SST1480 @@ -2131,7 +2131,7 @@ dotnet_diagnostic.S2328.severity = none # "GetHashCode" should not reference mut dotnet_diagnostic.S2345.severity = none # Flags enumerations should explicitly initialize all their members — covered by SST2303 dotnet_diagnostic.S2674.severity = none # The length returned from a stream read should be checked — covered by SST2446 dotnet_diagnostic.S2934.severity = none # Property assignments should not be made for "readonly" fields not constrained to reference types — covered by SST2421 -dotnet_diagnostic.S2955.severity = error # Generic parameters not constrained to reference types should not be compared to "null" +dotnet_diagnostic.S2955.severity = none # Generic parameters not constrained to reference types should not be compared to "null" dotnet_diagnostic.S3363.severity = none # Date and time should not be used as a type for primary keys — covered by SST2475 dotnet_diagnostic.S3397.severity = none # "base.Equals" should not be used to check for reference equality in "Equals" if "base" is not "object" — covered by SST2435 dotnet_diagnostic.S3456.severity = none # "string.ToCharArray()" and "ReadOnlySpan.ToArray()" should not be called redundantly — covered by PSH1217 @@ -2141,21 +2141,21 @@ dotnet_diagnostic.S3887.severity = none # Mutable, non-private fields should not # SonarAnalyzer (Sxxxx) - Blocker Vulnerability ################### dotnet_diagnostic.S2115.severity = error # A secure password should be used when connecting to a database -dotnet_diagnostic.S2755.severity = error # XML parsers should not be vulnerable to XXE attacks -dotnet_diagnostic.S3884.severity = error # "CoSetProxyBlanket" and "CoInitializeSecurity" should not be used +dotnet_diagnostic.S2755.severity = none # XML parsers should not be vulnerable to XXE attacks +dotnet_diagnostic.S3884.severity = none # "CoSetProxyBlanket" and "CoInitializeSecurity" should not be used dotnet_diagnostic.S6418.severity = none # Secrets should not be hard-coded — covered by SES1201 ################### # SonarAnalyzer (Sxxxx) - Critical Vulnerability ################### -dotnet_diagnostic.S4423.severity = error # Weak SSL/TLS protocols should not be used -dotnet_diagnostic.S4426.severity = error # Cryptographic keys should be robust +dotnet_diagnostic.S4423.severity = none # Weak SSL/TLS protocols should not be used +dotnet_diagnostic.S4426.severity = none # Cryptographic keys should be robust dotnet_diagnostic.S4433.severity = error # LDAP connections should be authenticated dotnet_diagnostic.S4830.severity = error # Server certificates should be verified during SSL/TLS connections dotnet_diagnostic.S5344.severity = error # Passwords should not be stored in plaintext or with a fast hashing algorithm dotnet_diagnostic.S5445.severity = error # Insecure temporary file creation methods should not be used -dotnet_diagnostic.S5542.severity = error # Encryption algorithms should be used with secure mode and padding scheme -dotnet_diagnostic.S5547.severity = error # Cipher algorithms should be robust +dotnet_diagnostic.S5542.severity = none # Encryption algorithms should be used with secure mode and padding scheme +dotnet_diagnostic.S5547.severity = none # Cipher algorithms should be robust dotnet_diagnostic.S5659.severity = error # JWT should be signed and verified with strong cipher algorithms ################### @@ -2163,8 +2163,8 @@ dotnet_diagnostic.S5659.severity = error # JWT should be signed and verified wit ################### dotnet_diagnostic.S2068.severity = error # Credentials should not be hard-coded dotnet_diagnostic.S2612.severity = error # File permissions should not be set to world-accessible values -dotnet_diagnostic.S4211.severity = error # Members should not have conflicting transparency annotations -dotnet_diagnostic.S4212.severity = error # Serialization constructors should be secured +dotnet_diagnostic.S4211.severity = none # Members should not have conflicting transparency annotations +dotnet_diagnostic.S4212.severity = none # Serialization constructors should be secured dotnet_diagnostic.S6377.severity = error # XML signatures should be validated securely dotnet_diagnostic.S7039.severity = error # Content Security Policies should be restrictive @@ -2175,7 +2175,7 @@ dotnet_diagnostic.S1147.severity = none # Exit methods should not be called — dotnet_diagnostic.S1451.severity = none # Track lack of copyright and license headers dotnet_diagnostic.S2178.severity = none # Short-circuit logic should be used in boolean contexts — covered by SST2415 dotnet_diagnostic.S2187.severity = none # Test classes should contain at least one test case — covered by SST2504 -dotnet_diagnostic.S2306.severity = error # "async" and "await" should not be used as identifiers +dotnet_diagnostic.S2306.severity = none # "async" and "await" should not be used as identifiers dotnet_diagnostic.S2368.severity = none # Public methods should not have multidimensional array parameters — jagged arrays are the chosen layout for hot-path lookup tables dotnet_diagnostic.S2387.severity = none # Child class fields should not shadow parent class fields — covered by SST1484 dotnet_diagnostic.S2437.severity = none # Unnecessary bit operations should not be performed — covered by SST1481 @@ -2198,7 +2198,7 @@ dotnet_diagnostic.S6424.severity = error # Interfaces for durable entities shoul ################### dotnet_diagnostic.S1006.severity = none # Method overrides should not change parameter defaults — covered by SST2424 dotnet_diagnostic.S1067.severity = none # Expressions should not be too complex -dotnet_diagnostic.S1163.severity = error # Exceptions should not be thrown in finally blocks +dotnet_diagnostic.S1163.severity = none # Exceptions should not be thrown in finally blocks dotnet_diagnostic.S1186.severity = none # Methods should not be empty — covered by SST1438 dotnet_diagnostic.S121.severity = error # Control structures should use curly braces (kept over SA1503 — Sonar 20ms vs SA1503 50ms) dotnet_diagnostic.S1215.severity = none # "GC.Collect" should not be called — covered by PSH1021 @@ -2208,7 +2208,7 @@ dotnet_diagnostic.S134.severity = none # Control flow statements "if", "switch", dotnet_diagnostic.S1541.severity = none # Methods and properties should not be too complex - covered by SST1442 dotnet_diagnostic.S1699.severity = none # Constructors should only call non-overridable methods — covered by SST1483 dotnet_diagnostic.S1821.severity = error # "switch" statements should not be nested -dotnet_diagnostic.S1944.severity = error # Invalid casts should be avoided +dotnet_diagnostic.S1944.severity = none # Invalid casts should be avoided dotnet_diagnostic.S1994.severity = none # "for" loop increment clauses should modify the loops' counters — covered by SST2411 dotnet_diagnostic.S2197.severity = none # Modulus results should not be checked for direct equality — covered by SST2416 dotnet_diagnostic.S2198.severity = error # Unnecessary mathematical comparisons should not be made @@ -2226,7 +2226,7 @@ dotnet_diagnostic.S2692.severity = none # "IndexOf" checks should not be for pos dotnet_diagnostic.S2696.severity = error # Instance members should not write to "static" fields dotnet_diagnostic.S2701.severity = none # Literal boolean values should not be used in assertions — covered by SST2503 dotnet_diagnostic.S3215.severity = error # "interface" instances should not be cast to concrete types -dotnet_diagnostic.S3216.severity = error # "ConfigureAwait(false)" should be used +dotnet_diagnostic.S3216.severity = none # "ConfigureAwait(false)" should be used dotnet_diagnostic.S3217.severity = none # "Explicit" conversions of "foreach" loops should not be used — covered by SST2225 dotnet_diagnostic.S3218.severity = none # Inner class members should not shadow outer class "static" or type members — covered by SST1484 dotnet_diagnostic.S3265.severity = none # Non-flags enums should not be used in bitwise operations — covered by SST2458 @@ -2235,9 +2235,9 @@ dotnet_diagnostic.S3447.severity = none # "[Optional]" should not be used on "re dotnet_diagnostic.S3451.severity = none # "[DefaultValue]" should not be used when "[DefaultParameterValue]" is meant — covered by SST2460 dotnet_diagnostic.S3600.severity = none # "params" should not be introduced on overrides — covered by SST2426 dotnet_diagnostic.S3776.severity = none # Cognitive Complexity of methods should not be too high - covered by SST1443 -dotnet_diagnostic.S3871.severity = error # Exception types should be "public" +dotnet_diagnostic.S3871.severity = none # Exception types should be "public" dotnet_diagnostic.S3874.severity = none # "out" and "ref" parameters — repo idiom is TryX(..., out T value) -dotnet_diagnostic.S3904.severity = error # Assemblies should have version information +dotnet_diagnostic.S3904.severity = none # Assemblies should have version information dotnet_diagnostic.S3937.severity = none # Number patterns should be regular — covered by SST1119 dotnet_diagnostic.S3972.severity = none # Conditionals should start on new lines — covered by SST1146 dotnet_diagnostic.S3973.severity = error # A conditionally executed single line should be denoted by indentation @@ -2255,7 +2255,7 @@ dotnet_diagnostic.S6967.severity = error # ModelState.IsValid should be called i dotnet_diagnostic.S8367.severity = none # Identifiers should not conflict with the C# 14 "field" contextual keyword - the C# 14 compiler reports this: CS9273 (error) for a local named field in an accessor, CS9258 (warning) for a rebinding read dotnet_diagnostic.S8368.severity = none # Identifiers should not conflict with the C# 14 "extension" contextual keyword - the C# 14 compiler reports this as CS9306, and SST1300 already flags the lowercase type name dotnet_diagnostic.S8380.severity = none # Return types named "partial" should be escaped with "@" - the compiler reports this as CS8981, and SST1300 already flags the lowercase type name -dotnet_diagnostic.S8381.severity = error # "scoped" should be escaped when used as an identifier or type name in parenthesized lambda parameter lists +dotnet_diagnostic.S8381.severity = none # "scoped" should be escaped when used as an identifier or type name in parenthesized lambda parameter lists dotnet_diagnostic.S927.severity = none # Parameter names should match base declaration and other partial definitions - DUPLICATE CA1725 ################### @@ -2298,7 +2298,7 @@ dotnet_diagnostic.S2327.severity = error # "try" statements with identical "catc dotnet_diagnostic.S2357.severity = none # Fields should be private — duplicate of SST1401 (canonical); fields intentionally exposed (e.g. public test fields for reflection) already carry per-site SST1401 suppressions dotnet_diagnostic.S2372.severity = none # Exceptions should not be thrown from property getters — covered by SST1485 dotnet_diagnostic.S2376.severity = none # Write-only properties should not be used — covered by SST1421 -dotnet_diagnostic.S2629.severity = error # Logging templates should be constant +dotnet_diagnostic.S2629.severity = none # Logging templates should be constant dotnet_diagnostic.S2681.severity = none # Multiline blocks should be enclosed in curly braces — covered by SST1503 dotnet_diagnostic.S2743.severity = none # Static fields should not be used in generic types - DUPLICATE CA1000 — covered by SST1431 dotnet_diagnostic.S2925.severity = none # "Thread.Sleep" should not be used in tests — covered by SST2506 @@ -2319,7 +2319,7 @@ dotnet_diagnostic.S3431.severity = none # "[ExpectedException]" should not be us dotnet_diagnostic.S3442.severity = none # "abstract" classes should not have "public" constructors — covered by SST1428 dotnet_diagnostic.S3445.severity = none # Exceptions should not be explicitly rethrown — covered by SST1430 dotnet_diagnostic.S3457.severity = none # Composite format strings should be used correctly — covered by SST1454 -dotnet_diagnostic.S3597.severity = error # "ServiceContract" and "OperationContract" attributes should be used together +dotnet_diagnostic.S3597.severity = none # "ServiceContract" and "OperationContract" attributes should be used together dotnet_diagnostic.S3880.severity = none # Finalizers should not be empty — covered by PSH1002 dotnet_diagnostic.S3881.severity = none # "IDisposable" should be implemented correctly — covered by SST2300 dotnet_diagnostic.S3885.severity = error # "Assembly.Load" should be used @@ -2327,18 +2327,18 @@ dotnet_diagnostic.S3898.severity = none # Covered by PSH1005 (canonical) dotnet_diagnostic.S3902.severity = none # Covered by PSH1404 (canonical) dotnet_diagnostic.S3906.severity = none # Event Handlers should have the correct signature — covered by SST2304 dotnet_diagnostic.S3908.severity = error # Generic event handlers should be used -dotnet_diagnostic.S3909.severity = error # Collections should implement the generic interface +dotnet_diagnostic.S3909.severity = none # Collections should implement the generic interface dotnet_diagnostic.S3925.severity = none # "ISerializable" should be implemented correctly - BinaryFormatter / ISerializable serialization is obsoleted (SYSLIB0050/0051) in modern .NET; we do not opt into legacy serialization for any exception type dotnet_diagnostic.S3928.severity = none # Parameter names used into ArgumentException constructors should match an existing one - DUPLICATE CA2208 dotnet_diagnostic.S3956.severity = none # "Generic.List" instances should not be part of public APIs dotnet_diagnostic.S3971.severity = none # "GC.SuppressFinalize" should not be called — conflicts with PSH1008, which owns the pointless-SuppressFinalize direction and exempts unsealed types -dotnet_diagnostic.S3990.severity = error # Assemblies should be marked as CLS compliant -dotnet_diagnostic.S3992.severity = error # Assemblies should explicitly specify COM visibility -dotnet_diagnostic.S3993.severity = error # Custom attributes should be marked with "System.AttributeUsageAttribute" +dotnet_diagnostic.S3990.severity = none # Assemblies should be marked as CLS compliant +dotnet_diagnostic.S3992.severity = none # Assemblies should explicitly specify COM visibility +dotnet_diagnostic.S3993.severity = none # Custom attributes should be marked with "System.AttributeUsageAttribute" dotnet_diagnostic.S3994.severity = none # URI Parameters should not be strings dotnet_diagnostic.S3995.severity = none # URI return values should not be strings dotnet_diagnostic.S3996.severity = none # URI properties should not be strings -dotnet_diagnostic.S3997.severity = error # String URI overloads should call "System.Uri" overloads +dotnet_diagnostic.S3997.severity = none # String URI overloads should call "System.Uri" overloads dotnet_diagnostic.S4002.severity = none # Disposable types should declare finalizers — covered by SST2317 dotnet_diagnostic.S4004.severity = none # Collection properties should be readonly — covered by SST2305 dotnet_diagnostic.S4005.severity = none # "System.Uri" arguments should be used instead of strings @@ -2455,7 +2455,7 @@ dotnet_diagnostic.S3416.severity = none # Loggers should be named for their encl dotnet_diagnostic.S3440.severity = none # Variables should not be checked against the values they're about to be assigned — covered by SST1492 dotnet_diagnostic.S3441.severity = none # Redundant property names should be omitted in anonymous classes — covered by SST1173 dotnet_diagnostic.S3444.severity = none # Interfaces should not simply inherit from base interfaces with colliding members — covered by SST2320 -dotnet_diagnostic.S3450.severity = error # Parameters with "[DefaultParameterValue]" attributes should also be marked "[Optional]" +dotnet_diagnostic.S3450.severity = none # Parameters with "[DefaultParameterValue]" attributes should also be marked "[Optional]" dotnet_diagnostic.S3458.severity = none # Empty "case" clauses that fall through to the "default" should be omitted — covered by SST1466 dotnet_diagnostic.S3459.severity = none # Unassigned members should be removed - the compiler reports this as CS0649; the rule only ever fires on private never-assigned fields dotnet_diagnostic.S3532.severity = none # Empty "default" clauses should be removed — covered by SST1179 @@ -2463,16 +2463,16 @@ dotnet_diagnostic.S3604.severity = none # Member initializer values should not b dotnet_diagnostic.S3626.severity = none # Jump statements should not be redundant — covered by SST1174 dotnet_diagnostic.S3717.severity = error # Track use of "NotImplementedException" dotnet_diagnostic.S3872.severity = error # Parameter names should not duplicate the names of their methods -dotnet_diagnostic.S3876.severity = error # Strings or integral types should be used for indexers +dotnet_diagnostic.S3876.severity = none # Strings or integral types should be used for indexers dotnet_diagnostic.S3878.severity = none # Arrays should not be created for params parameters — covered by PSH1018 -dotnet_diagnostic.S3897.severity = error # Classes that provide "Equals()" should implement "IEquatable" +dotnet_diagnostic.S3897.severity = none # Classes that provide "Equals()" should implement "IEquatable" dotnet_diagnostic.S3962.severity = none # Covered by PSH1402 (canonical) dotnet_diagnostic.S3963.severity = none # "static" fields should be initialized inline - DUPLICATE CA1810 dotnet_diagnostic.S3967.severity = none # Multidimensional arrays should not be used - DUPLICATE CA1814 dotnet_diagnostic.S4018.severity = none # All type parameters should be used in the parameter list to enable type inference — covered by SST2307 dotnet_diagnostic.S4022.severity = none # Enumerations should have "Int32" storage — covered by SST2313 dotnet_diagnostic.S4023.severity = none # Interfaces should not be empty — covered by SST1437 -dotnet_diagnostic.S4026.severity = error # Assemblies should be marked with "NeutralResourcesLanguageAttribute" +dotnet_diagnostic.S4026.severity = none # Assemblies should be marked with "NeutralResourcesLanguageAttribute" dotnet_diagnostic.S4027.severity = none # Exceptions should provide standard constructors — covered by SST1488 dotnet_diagnostic.S4040.severity = none # Strings should be normalized to uppercase - DUPLICATE CA1308 dotnet_diagnostic.S4041.severity = none # Type names should not match namespaces - DUPLICATE CA1724 @@ -2482,7 +2482,7 @@ dotnet_diagnostic.S4052.severity = error # Types should not extend outdated base dotnet_diagnostic.S4056.severity = none # Overloads with a "CultureInfo" or an "IFormatProvider" parameter should be used - DUPLICATE CA1305 dotnet_diagnostic.S4058.severity = none # Covered by PSH1207 (canonical) dotnet_diagnostic.S4060.severity = none # Non-abstract attributes should be sealed - DUPLICATE CA1813 -dotnet_diagnostic.S4061.severity = error # "params" should be used instead of "varargs" +dotnet_diagnostic.S4061.severity = none # "params" should be used instead of "varargs" dotnet_diagnostic.S4069.severity = none # Operator overloads should have named alternatives - DUPLICATE CA2225 dotnet_diagnostic.S4136.severity = none # Method overloads should be grouped together — covered by SST1218 dotnet_diagnostic.S4201.severity = none # Null checks should not be combined with "is" operator checks — covered by SST2018 @@ -2512,7 +2512,7 @@ dotnet_diagnostic.S6669.severity = error # Logger field or property name should dotnet_diagnostic.S6670.severity = none # "Trace.Write" and "Trace.WriteLine" should not be used — covered by SST2600 dotnet_diagnostic.S6672.severity = none # Generic logger injection should match enclosing type — covered by SST2443 dotnet_diagnostic.S6675.severity = error # "Trace.WriteLineIf" should not be used with "TraceSwitch" levels -dotnet_diagnostic.S6678.severity = error # Use PascalCase for named placeholders +dotnet_diagnostic.S6678.severity = none # Use PascalCase for named placeholders dotnet_diagnostic.S818.severity = none # Literal suffixes should be upper case — covered by SST2244 ################### @@ -2540,8 +2540,8 @@ dotnet_diagnostic.S9999-warning.severity = error # Analysis Warning generator ################### dotnet_diagnostic.S2245.severity = none # Using pseudorandom number generators (PRNGs) is security-sensitive - DUPLICATE CA5394 dotnet_diagnostic.S2257.severity = error # Using non-standard cryptographic algorithms is security-sensitive -dotnet_diagnostic.S4502.severity = error # Disabling CSRF protections is security-sensitive -dotnet_diagnostic.S4790.severity = error # Using weak hashing algorithms is security-sensitive +dotnet_diagnostic.S4502.severity = none # Disabling CSRF protections is security-sensitive +dotnet_diagnostic.S4790.severity = none # Using weak hashing algorithms is security-sensitive dotnet_diagnostic.S4792.severity = error # Configuring loggers is security-sensitive dotnet_diagnostic.S5042.severity = error # Expanding archive files without controlling resource consumption is security-sensitive dotnet_diagnostic.S5332.severity = error # Using clear-text protocols is security-sensitive @@ -2551,9 +2551,9 @@ dotnet_diagnostic.S5443.severity = error # Using publicly writable directories i # SonarAnalyzer (Sxxxx) - Major Security Hotspot ################### dotnet_diagnostic.S1313.severity = error # Using hardcoded IP addresses is security-sensitive -dotnet_diagnostic.S2077.severity = error # Formatting SQL queries is security-sensitive +dotnet_diagnostic.S2077.severity = none # Formatting SQL queries is security-sensitive dotnet_diagnostic.S5693.severity = error # Allowing requests with excessive content length is security-sensitive -dotnet_diagnostic.S5753.severity = error # Disabling ASP.NET "Request Validation" feature is security-sensitive +dotnet_diagnostic.S5753.severity = none # Disabling ASP.NET "Request Validation" feature is security-sensitive dotnet_diagnostic.S5766.severity = error # Creating Serializable objects without data validation checks is security-sensitive dotnet_diagnostic.S6444.severity = error # Not specifying a timeout for regular expressions is security-sensitive dotnet_diagnostic.S6640.severity = error # Using unsafe code blocks is security-sensitive @@ -2561,8 +2561,8 @@ dotnet_diagnostic.S6640.severity = error # Using unsafe code blocks is security- ################### # SonarAnalyzer (Sxxxx) - Minor Security Hotspot ################### -dotnet_diagnostic.S2092.severity = error # Creating cookies without the "secure" flag is security-sensitive -dotnet_diagnostic.S3330.severity = error # Creating cookies without the "HttpOnly" flag is security-sensitive +dotnet_diagnostic.S2092.severity = none # Creating cookies without the "secure" flag is security-sensitive +dotnet_diagnostic.S3330.severity = none # Creating cookies without the "HttpOnly" flag is security-sensitive dotnet_diagnostic.S4507.severity = error # Delivering code in production with debug features activated is security-sensitive dotnet_diagnostic.S5122.severity = error # Having a permissive Cross-Origin Resource Sharing policy is security-sensitive From 7f20375a29ed38798ba51e5d2787ba607fbe2b5b Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:42:58 +1000 Subject: [PATCH 08/18] docs(editorconfig): note which rule supersedes each disabled Sonar rule Append the in-box CA/compiler rule (or "obsolete") that replaces each of the 42 Sonar rules disabled in the previous commit, for tracking. --- .editorconfig | 84 +++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/.editorconfig b/.editorconfig index 1a82638b..b15fa28e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2063,7 +2063,7 @@ dotnet_diagnostic.S2952.severity = error # Classes should "Dispose" of members f dotnet_diagnostic.S3449.severity = error # Right operands of shift operators should be integers dotnet_diagnostic.S4275.severity = none # Getters and setters should access the expected fields — covered by SST2422 dotnet_diagnostic.S4277.severity = none # "Shared" parts should not be created with "new" — covered by SST2473 -dotnet_diagnostic.S4583.severity = none # Calls to delegate's method "BeginInvoke" should be paired with calls to "EndInvoke" +dotnet_diagnostic.S4583.severity = none # Calls to delegate's method "BeginInvoke" should be paired with calls to "EndInvoke" -> replaced by obsolete (APM BeginInvoke/EndInvoke) dotnet_diagnostic.S4586.severity = none # Non-async "Task/Task" methods should not return null — covered by PSH1312 dotnet_diagnostic.S5856.severity = none # Regular expressions should be syntactically valid — covered by SST2444 dotnet_diagnostic.S6674.severity = none # Log message template should be syntactically correct — covered by SST2441 @@ -2087,10 +2087,10 @@ dotnet_diagnostic.S2445.severity = none # Blocks should be synchronized on read- dotnet_diagnostic.S2688.severity = none # "NaN" should not be used in comparisons — covered by SST1473 dotnet_diagnostic.S2757.severity = none # Non-existent operators like "=+" should not be used — covered by SST2417 dotnet_diagnostic.S2761.severity = none # Doubled prefix operators "!!" and "~~" should not be used — covered by SST1190 -dotnet_diagnostic.S2995.severity = none # "Object.ReferenceEquals" should not be used for value types -dotnet_diagnostic.S2996.severity = none # "ThreadStatic" fields should not be initialized +dotnet_diagnostic.S2995.severity = none # "Object.ReferenceEquals" should not be used for value types -> replaced by CA2013 +dotnet_diagnostic.S2996.severity = none # "ThreadStatic" fields should not be initialized -> replaced by CA2019 dotnet_diagnostic.S2997.severity = none # "IDisposables" created in a "using" statement should not be returned — covered by SST2423 -dotnet_diagnostic.S3005.severity = none # "ThreadStatic" should not be used on non-static fields +dotnet_diagnostic.S3005.severity = none # "ThreadStatic" should not be used on non-static fields -> replaced by CA2259 dotnet_diagnostic.S3168.severity = none # "async" methods should not return "void" — covered by SST1905 dotnet_diagnostic.S3172.severity = none # Delegates should not be subtracted — covered by SST2448 dotnet_diagnostic.S3244.severity = none # Anonymous delegates should not be used to unsubscribe from Events — covered by SST2449 @@ -2100,12 +2100,12 @@ dotnet_diagnostic.S3343.severity = none # Caller information parameters should c dotnet_diagnostic.S3346.severity = none # Expressions used in "Debug.Assert" should not produce side effects — covered by SST2450 dotnet_diagnostic.S3453.severity = none # Classes should not have only "private" constructors — covered by SST2451 dotnet_diagnostic.S3466.severity = none # Optional parameters should be passed to "base" calls — covered by SST2425 -dotnet_diagnostic.S3598.severity = none # One-way "OperationContract" methods should have "void" return type +dotnet_diagnostic.S3598.severity = none # One-way "OperationContract" methods should have "void" return type -> replaced by obsolete (WCF) dotnet_diagnostic.S3603.severity = none # Methods with "Pure" attribute should return a value — covered by SST2452 -dotnet_diagnostic.S3610.severity = none # Nullable type comparison should not be redundant +dotnet_diagnostic.S3610.severity = none # Nullable type comparison should not be redundant -> replaced by compiler CS0472 dotnet_diagnostic.S3903.severity = none # Types should be defined in named namespaces — covered by SST2312 dotnet_diagnostic.S3923.severity = none # All branches in a conditional structure should not have exactly the same implementation — covered by SST1476 -dotnet_diagnostic.S3926.severity = none # Deserialization methods should be provided for "OptionalField" members +dotnet_diagnostic.S3926.severity = none # Deserialization methods should be provided for "OptionalField" members -> replaced by obsolete (legacy binary serialization) dotnet_diagnostic.S3927.severity = none # Serialization event handlers should be implemented correctly — covered by SST2430 dotnet_diagnostic.S3981.severity = none # Collection sizes and array length comparisons should make sense — covered by SST1479 dotnet_diagnostic.S3984.severity = none # Exceptions should not be created without being thrown — covered by SST1480 @@ -2131,7 +2131,7 @@ dotnet_diagnostic.S2328.severity = none # "GetHashCode" should not reference mut dotnet_diagnostic.S2345.severity = none # Flags enumerations should explicitly initialize all their members — covered by SST2303 dotnet_diagnostic.S2674.severity = none # The length returned from a stream read should be checked — covered by SST2446 dotnet_diagnostic.S2934.severity = none # Property assignments should not be made for "readonly" fields not constrained to reference types — covered by SST2421 -dotnet_diagnostic.S2955.severity = none # Generic parameters not constrained to reference types should not be compared to "null" +dotnet_diagnostic.S2955.severity = none # Generic parameters not constrained to reference types should not be compared to "null" -> replaced by compiler CS0019/CS0037 dotnet_diagnostic.S3363.severity = none # Date and time should not be used as a type for primary keys — covered by SST2475 dotnet_diagnostic.S3397.severity = none # "base.Equals" should not be used to check for reference equality in "Equals" if "base" is not "object" — covered by SST2435 dotnet_diagnostic.S3456.severity = none # "string.ToCharArray()" and "ReadOnlySpan.ToArray()" should not be called redundantly — covered by PSH1217 @@ -2141,21 +2141,21 @@ dotnet_diagnostic.S3887.severity = none # Mutable, non-private fields should not # SonarAnalyzer (Sxxxx) - Blocker Vulnerability ################### dotnet_diagnostic.S2115.severity = error # A secure password should be used when connecting to a database -dotnet_diagnostic.S2755.severity = none # XML parsers should not be vulnerable to XXE attacks -dotnet_diagnostic.S3884.severity = none # "CoSetProxyBlanket" and "CoInitializeSecurity" should not be used +dotnet_diagnostic.S2755.severity = none # XML parsers should not be vulnerable to XXE attacks -> replaced by CA3075 +dotnet_diagnostic.S3884.severity = none # "CoSetProxyBlanket" and "CoInitializeSecurity" should not be used -> replaced by obsolete (COM interop security) dotnet_diagnostic.S6418.severity = none # Secrets should not be hard-coded — covered by SES1201 ################### # SonarAnalyzer (Sxxxx) - Critical Vulnerability ################### -dotnet_diagnostic.S4423.severity = none # Weak SSL/TLS protocols should not be used -dotnet_diagnostic.S4426.severity = none # Cryptographic keys should be robust +dotnet_diagnostic.S4423.severity = none # Weak SSL/TLS protocols should not be used -> replaced by CA5397/CA5398 +dotnet_diagnostic.S4426.severity = none # Cryptographic keys should be robust -> replaced by CA5385 (RSA) / CA5384 (DSA) dotnet_diagnostic.S4433.severity = error # LDAP connections should be authenticated dotnet_diagnostic.S4830.severity = error # Server certificates should be verified during SSL/TLS connections dotnet_diagnostic.S5344.severity = error # Passwords should not be stored in plaintext or with a fast hashing algorithm dotnet_diagnostic.S5445.severity = error # Insecure temporary file creation methods should not be used -dotnet_diagnostic.S5542.severity = none # Encryption algorithms should be used with secure mode and padding scheme -dotnet_diagnostic.S5547.severity = none # Cipher algorithms should be robust +dotnet_diagnostic.S5542.severity = none # Encryption algorithms should be used with secure mode and padding scheme -> replaced by CA5358 +dotnet_diagnostic.S5547.severity = none # Cipher algorithms should be robust -> replaced by CA5351 dotnet_diagnostic.S5659.severity = error # JWT should be signed and verified with strong cipher algorithms ################### @@ -2163,8 +2163,8 @@ dotnet_diagnostic.S5659.severity = error # JWT should be signed and verified wit ################### dotnet_diagnostic.S2068.severity = error # Credentials should not be hard-coded dotnet_diagnostic.S2612.severity = error # File permissions should not be set to world-accessible values -dotnet_diagnostic.S4211.severity = none # Members should not have conflicting transparency annotations -dotnet_diagnostic.S4212.severity = none # Serialization constructors should be secured +dotnet_diagnostic.S4211.severity = none # Members should not have conflicting transparency annotations -> replaced by obsolete (Code Access Security) +dotnet_diagnostic.S4212.severity = none # Serialization constructors should be secured -> replaced by obsolete (Code Access Security) dotnet_diagnostic.S6377.severity = error # XML signatures should be validated securely dotnet_diagnostic.S7039.severity = error # Content Security Policies should be restrictive @@ -2175,7 +2175,7 @@ dotnet_diagnostic.S1147.severity = none # Exit methods should not be called — dotnet_diagnostic.S1451.severity = none # Track lack of copyright and license headers dotnet_diagnostic.S2178.severity = none # Short-circuit logic should be used in boolean contexts — covered by SST2415 dotnet_diagnostic.S2187.severity = none # Test classes should contain at least one test case — covered by SST2504 -dotnet_diagnostic.S2306.severity = none # "async" and "await" should not be used as identifiers +dotnet_diagnostic.S2306.severity = none # "async" and "await" should not be used as identifiers -> replaced by compiler (contextual keyword) dotnet_diagnostic.S2368.severity = none # Public methods should not have multidimensional array parameters — jagged arrays are the chosen layout for hot-path lookup tables dotnet_diagnostic.S2387.severity = none # Child class fields should not shadow parent class fields — covered by SST1484 dotnet_diagnostic.S2437.severity = none # Unnecessary bit operations should not be performed — covered by SST1481 @@ -2198,7 +2198,7 @@ dotnet_diagnostic.S6424.severity = error # Interfaces for durable entities shoul ################### dotnet_diagnostic.S1006.severity = none # Method overrides should not change parameter defaults — covered by SST2424 dotnet_diagnostic.S1067.severity = none # Expressions should not be too complex -dotnet_diagnostic.S1163.severity = none # Exceptions should not be thrown in finally blocks +dotnet_diagnostic.S1163.severity = none # Exceptions should not be thrown in finally blocks -> replaced by CA2219 dotnet_diagnostic.S1186.severity = none # Methods should not be empty — covered by SST1438 dotnet_diagnostic.S121.severity = error # Control structures should use curly braces (kept over SA1503 — Sonar 20ms vs SA1503 50ms) dotnet_diagnostic.S1215.severity = none # "GC.Collect" should not be called — covered by PSH1021 @@ -2208,7 +2208,7 @@ dotnet_diagnostic.S134.severity = none # Control flow statements "if", "switch", dotnet_diagnostic.S1541.severity = none # Methods and properties should not be too complex - covered by SST1442 dotnet_diagnostic.S1699.severity = none # Constructors should only call non-overridable methods — covered by SST1483 dotnet_diagnostic.S1821.severity = error # "switch" statements should not be nested -dotnet_diagnostic.S1944.severity = none # Invalid casts should be avoided +dotnet_diagnostic.S1944.severity = none # Invalid casts should be avoided -> replaced by compiler CS0030 dotnet_diagnostic.S1994.severity = none # "for" loop increment clauses should modify the loops' counters — covered by SST2411 dotnet_diagnostic.S2197.severity = none # Modulus results should not be checked for direct equality — covered by SST2416 dotnet_diagnostic.S2198.severity = error # Unnecessary mathematical comparisons should not be made @@ -2226,7 +2226,7 @@ dotnet_diagnostic.S2692.severity = none # "IndexOf" checks should not be for pos dotnet_diagnostic.S2696.severity = error # Instance members should not write to "static" fields dotnet_diagnostic.S2701.severity = none # Literal boolean values should not be used in assertions — covered by SST2503 dotnet_diagnostic.S3215.severity = error # "interface" instances should not be cast to concrete types -dotnet_diagnostic.S3216.severity = none # "ConfigureAwait(false)" should be used +dotnet_diagnostic.S3216.severity = none # "ConfigureAwait(false)" should be used -> replaced by CA2007 dotnet_diagnostic.S3217.severity = none # "Explicit" conversions of "foreach" loops should not be used — covered by SST2225 dotnet_diagnostic.S3218.severity = none # Inner class members should not shadow outer class "static" or type members — covered by SST1484 dotnet_diagnostic.S3265.severity = none # Non-flags enums should not be used in bitwise operations — covered by SST2458 @@ -2235,9 +2235,9 @@ dotnet_diagnostic.S3447.severity = none # "[Optional]" should not be used on "re dotnet_diagnostic.S3451.severity = none # "[DefaultValue]" should not be used when "[DefaultParameterValue]" is meant — covered by SST2460 dotnet_diagnostic.S3600.severity = none # "params" should not be introduced on overrides — covered by SST2426 dotnet_diagnostic.S3776.severity = none # Cognitive Complexity of methods should not be too high - covered by SST1443 -dotnet_diagnostic.S3871.severity = none # Exception types should be "public" +dotnet_diagnostic.S3871.severity = none # Exception types should be "public" -> replaced by CA1064 dotnet_diagnostic.S3874.severity = none # "out" and "ref" parameters — repo idiom is TryX(..., out T value) -dotnet_diagnostic.S3904.severity = none # Assemblies should have version information +dotnet_diagnostic.S3904.severity = none # Assemblies should have version information -> replaced by obsolete (SDK supplies assembly version) dotnet_diagnostic.S3937.severity = none # Number patterns should be regular — covered by SST1119 dotnet_diagnostic.S3972.severity = none # Conditionals should start on new lines — covered by SST1146 dotnet_diagnostic.S3973.severity = error # A conditionally executed single line should be denoted by indentation @@ -2255,7 +2255,7 @@ dotnet_diagnostic.S6967.severity = error # ModelState.IsValid should be called i dotnet_diagnostic.S8367.severity = none # Identifiers should not conflict with the C# 14 "field" contextual keyword - the C# 14 compiler reports this: CS9273 (error) for a local named field in an accessor, CS9258 (warning) for a rebinding read dotnet_diagnostic.S8368.severity = none # Identifiers should not conflict with the C# 14 "extension" contextual keyword - the C# 14 compiler reports this as CS9306, and SST1300 already flags the lowercase type name dotnet_diagnostic.S8380.severity = none # Return types named "partial" should be escaped with "@" - the compiler reports this as CS8981, and SST1300 already flags the lowercase type name -dotnet_diagnostic.S8381.severity = none # "scoped" should be escaped when used as an identifier or type name in parenthesized lambda parameter lists +dotnet_diagnostic.S8381.severity = none # "scoped" should be escaped when used as an identifier or type name in parenthesized lambda parameter lists -> replaced by compiler dotnet_diagnostic.S927.severity = none # Parameter names should match base declaration and other partial definitions - DUPLICATE CA1725 ################### @@ -2298,7 +2298,7 @@ dotnet_diagnostic.S2327.severity = error # "try" statements with identical "catc dotnet_diagnostic.S2357.severity = none # Fields should be private — duplicate of SST1401 (canonical); fields intentionally exposed (e.g. public test fields for reflection) already carry per-site SST1401 suppressions dotnet_diagnostic.S2372.severity = none # Exceptions should not be thrown from property getters — covered by SST1485 dotnet_diagnostic.S2376.severity = none # Write-only properties should not be used — covered by SST1421 -dotnet_diagnostic.S2629.severity = none # Logging templates should be constant +dotnet_diagnostic.S2629.severity = none # Logging templates should be constant -> replaced by CA2254 dotnet_diagnostic.S2681.severity = none # Multiline blocks should be enclosed in curly braces — covered by SST1503 dotnet_diagnostic.S2743.severity = none # Static fields should not be used in generic types - DUPLICATE CA1000 — covered by SST1431 dotnet_diagnostic.S2925.severity = none # "Thread.Sleep" should not be used in tests — covered by SST2506 @@ -2319,7 +2319,7 @@ dotnet_diagnostic.S3431.severity = none # "[ExpectedException]" should not be us dotnet_diagnostic.S3442.severity = none # "abstract" classes should not have "public" constructors — covered by SST1428 dotnet_diagnostic.S3445.severity = none # Exceptions should not be explicitly rethrown — covered by SST1430 dotnet_diagnostic.S3457.severity = none # Composite format strings should be used correctly — covered by SST1454 -dotnet_diagnostic.S3597.severity = none # "ServiceContract" and "OperationContract" attributes should be used together +dotnet_diagnostic.S3597.severity = none # "ServiceContract" and "OperationContract" attributes should be used together -> replaced by obsolete (WCF) dotnet_diagnostic.S3880.severity = none # Finalizers should not be empty — covered by PSH1002 dotnet_diagnostic.S3881.severity = none # "IDisposable" should be implemented correctly — covered by SST2300 dotnet_diagnostic.S3885.severity = error # "Assembly.Load" should be used @@ -2327,18 +2327,18 @@ dotnet_diagnostic.S3898.severity = none # Covered by PSH1005 (canonical) dotnet_diagnostic.S3902.severity = none # Covered by PSH1404 (canonical) dotnet_diagnostic.S3906.severity = none # Event Handlers should have the correct signature — covered by SST2304 dotnet_diagnostic.S3908.severity = error # Generic event handlers should be used -dotnet_diagnostic.S3909.severity = none # Collections should implement the generic interface +dotnet_diagnostic.S3909.severity = none # Collections should implement the generic interface -> replaced by CA1010 dotnet_diagnostic.S3925.severity = none # "ISerializable" should be implemented correctly - BinaryFormatter / ISerializable serialization is obsoleted (SYSLIB0050/0051) in modern .NET; we do not opt into legacy serialization for any exception type dotnet_diagnostic.S3928.severity = none # Parameter names used into ArgumentException constructors should match an existing one - DUPLICATE CA2208 dotnet_diagnostic.S3956.severity = none # "Generic.List" instances should not be part of public APIs dotnet_diagnostic.S3971.severity = none # "GC.SuppressFinalize" should not be called — conflicts with PSH1008, which owns the pointless-SuppressFinalize direction and exempts unsealed types -dotnet_diagnostic.S3990.severity = none # Assemblies should be marked as CLS compliant -dotnet_diagnostic.S3992.severity = none # Assemblies should explicitly specify COM visibility -dotnet_diagnostic.S3993.severity = none # Custom attributes should be marked with "System.AttributeUsageAttribute" +dotnet_diagnostic.S3990.severity = none # Assemblies should be marked as CLS compliant -> replaced by CA1014 +dotnet_diagnostic.S3992.severity = none # Assemblies should explicitly specify COM visibility -> replaced by CA1017 +dotnet_diagnostic.S3993.severity = none # Custom attributes should be marked with "System.AttributeUsageAttribute" -> replaced by CA1018 dotnet_diagnostic.S3994.severity = none # URI Parameters should not be strings dotnet_diagnostic.S3995.severity = none # URI return values should not be strings dotnet_diagnostic.S3996.severity = none # URI properties should not be strings -dotnet_diagnostic.S3997.severity = none # String URI overloads should call "System.Uri" overloads +dotnet_diagnostic.S3997.severity = none # String URI overloads should call "System.Uri" overloads -> replaced by CA1054/CA1056/CA1057 dotnet_diagnostic.S4002.severity = none # Disposable types should declare finalizers — covered by SST2317 dotnet_diagnostic.S4004.severity = none # Collection properties should be readonly — covered by SST2305 dotnet_diagnostic.S4005.severity = none # "System.Uri" arguments should be used instead of strings @@ -2455,7 +2455,7 @@ dotnet_diagnostic.S3416.severity = none # Loggers should be named for their encl dotnet_diagnostic.S3440.severity = none # Variables should not be checked against the values they're about to be assigned — covered by SST1492 dotnet_diagnostic.S3441.severity = none # Redundant property names should be omitted in anonymous classes — covered by SST1173 dotnet_diagnostic.S3444.severity = none # Interfaces should not simply inherit from base interfaces with colliding members — covered by SST2320 -dotnet_diagnostic.S3450.severity = none # Parameters with "[DefaultParameterValue]" attributes should also be marked "[Optional]" +dotnet_diagnostic.S3450.severity = none # Parameters with "[DefaultParameterValue]" attributes should also be marked "[Optional]" -> replaced by obsolete (legacy DefaultParameterValue) dotnet_diagnostic.S3458.severity = none # Empty "case" clauses that fall through to the "default" should be omitted — covered by SST1466 dotnet_diagnostic.S3459.severity = none # Unassigned members should be removed - the compiler reports this as CS0649; the rule only ever fires on private never-assigned fields dotnet_diagnostic.S3532.severity = none # Empty "default" clauses should be removed — covered by SST1179 @@ -2463,16 +2463,16 @@ dotnet_diagnostic.S3604.severity = none # Member initializer values should not b dotnet_diagnostic.S3626.severity = none # Jump statements should not be redundant — covered by SST1174 dotnet_diagnostic.S3717.severity = error # Track use of "NotImplementedException" dotnet_diagnostic.S3872.severity = error # Parameter names should not duplicate the names of their methods -dotnet_diagnostic.S3876.severity = none # Strings or integral types should be used for indexers +dotnet_diagnostic.S3876.severity = none # Strings or integral types should be used for indexers -> replaced by CA1043 dotnet_diagnostic.S3878.severity = none # Arrays should not be created for params parameters — covered by PSH1018 -dotnet_diagnostic.S3897.severity = none # Classes that provide "Equals()" should implement "IEquatable" +dotnet_diagnostic.S3897.severity = none # Classes that provide "Equals()" should implement "IEquatable" -> replaced by CA1067 dotnet_diagnostic.S3962.severity = none # Covered by PSH1402 (canonical) dotnet_diagnostic.S3963.severity = none # "static" fields should be initialized inline - DUPLICATE CA1810 dotnet_diagnostic.S3967.severity = none # Multidimensional arrays should not be used - DUPLICATE CA1814 dotnet_diagnostic.S4018.severity = none # All type parameters should be used in the parameter list to enable type inference — covered by SST2307 dotnet_diagnostic.S4022.severity = none # Enumerations should have "Int32" storage — covered by SST2313 dotnet_diagnostic.S4023.severity = none # Interfaces should not be empty — covered by SST1437 -dotnet_diagnostic.S4026.severity = none # Assemblies should be marked with "NeutralResourcesLanguageAttribute" +dotnet_diagnostic.S4026.severity = none # Assemblies should be marked with "NeutralResourcesLanguageAttribute" -> replaced by CA1824 dotnet_diagnostic.S4027.severity = none # Exceptions should provide standard constructors — covered by SST1488 dotnet_diagnostic.S4040.severity = none # Strings should be normalized to uppercase - DUPLICATE CA1308 dotnet_diagnostic.S4041.severity = none # Type names should not match namespaces - DUPLICATE CA1724 @@ -2482,7 +2482,7 @@ dotnet_diagnostic.S4052.severity = error # Types should not extend outdated base dotnet_diagnostic.S4056.severity = none # Overloads with a "CultureInfo" or an "IFormatProvider" parameter should be used - DUPLICATE CA1305 dotnet_diagnostic.S4058.severity = none # Covered by PSH1207 (canonical) dotnet_diagnostic.S4060.severity = none # Non-abstract attributes should be sealed - DUPLICATE CA1813 -dotnet_diagnostic.S4061.severity = none # "params" should be used instead of "varargs" +dotnet_diagnostic.S4061.severity = none # "params" should be used instead of "varargs" -> replaced by obsolete (__arglist varargs) dotnet_diagnostic.S4069.severity = none # Operator overloads should have named alternatives - DUPLICATE CA2225 dotnet_diagnostic.S4136.severity = none # Method overloads should be grouped together — covered by SST1218 dotnet_diagnostic.S4201.severity = none # Null checks should not be combined with "is" operator checks — covered by SST2018 @@ -2512,7 +2512,7 @@ dotnet_diagnostic.S6669.severity = error # Logger field or property name should dotnet_diagnostic.S6670.severity = none # "Trace.Write" and "Trace.WriteLine" should not be used — covered by SST2600 dotnet_diagnostic.S6672.severity = none # Generic logger injection should match enclosing type — covered by SST2443 dotnet_diagnostic.S6675.severity = error # "Trace.WriteLineIf" should not be used with "TraceSwitch" levels -dotnet_diagnostic.S6678.severity = none # Use PascalCase for named placeholders +dotnet_diagnostic.S6678.severity = none # Use PascalCase for named placeholders -> replaced by CA1727 dotnet_diagnostic.S818.severity = none # Literal suffixes should be upper case — covered by SST2244 ################### @@ -2540,8 +2540,8 @@ dotnet_diagnostic.S9999-warning.severity = error # Analysis Warning generator ################### dotnet_diagnostic.S2245.severity = none # Using pseudorandom number generators (PRNGs) is security-sensitive - DUPLICATE CA5394 dotnet_diagnostic.S2257.severity = error # Using non-standard cryptographic algorithms is security-sensitive -dotnet_diagnostic.S4502.severity = none # Disabling CSRF protections is security-sensitive -dotnet_diagnostic.S4790.severity = none # Using weak hashing algorithms is security-sensitive +dotnet_diagnostic.S4502.severity = none # Disabling CSRF protections is security-sensitive -> replaced by in-box ASP.NET antiforgery analyzer +dotnet_diagnostic.S4790.severity = none # Using weak hashing algorithms is security-sensitive -> replaced by CA5350/CA5351 dotnet_diagnostic.S4792.severity = error # Configuring loggers is security-sensitive dotnet_diagnostic.S5042.severity = error # Expanding archive files without controlling resource consumption is security-sensitive dotnet_diagnostic.S5332.severity = error # Using clear-text protocols is security-sensitive @@ -2551,9 +2551,9 @@ dotnet_diagnostic.S5443.severity = error # Using publicly writable directories i # SonarAnalyzer (Sxxxx) - Major Security Hotspot ################### dotnet_diagnostic.S1313.severity = error # Using hardcoded IP addresses is security-sensitive -dotnet_diagnostic.S2077.severity = none # Formatting SQL queries is security-sensitive +dotnet_diagnostic.S2077.severity = none # Formatting SQL queries is security-sensitive -> replaced by CA2100 dotnet_diagnostic.S5693.severity = error # Allowing requests with excessive content length is security-sensitive -dotnet_diagnostic.S5753.severity = none # Disabling ASP.NET "Request Validation" feature is security-sensitive +dotnet_diagnostic.S5753.severity = none # Disabling ASP.NET "Request Validation" feature is security-sensitive -> replaced by obsolete (legacy ASP.NET request validation) dotnet_diagnostic.S5766.severity = error # Creating Serializable objects without data validation checks is security-sensitive dotnet_diagnostic.S6444.severity = error # Not specifying a timeout for regular expressions is security-sensitive dotnet_diagnostic.S6640.severity = error # Using unsafe code blocks is security-sensitive @@ -2561,8 +2561,8 @@ dotnet_diagnostic.S6640.severity = error # Using unsafe code blocks is security- ################### # SonarAnalyzer (Sxxxx) - Minor Security Hotspot ################### -dotnet_diagnostic.S2092.severity = none # Creating cookies without the "secure" flag is security-sensitive -dotnet_diagnostic.S3330.severity = none # Creating cookies without the "HttpOnly" flag is security-sensitive +dotnet_diagnostic.S2092.severity = none # Creating cookies without the "secure" flag is security-sensitive -> replaced by CA5382 +dotnet_diagnostic.S3330.severity = none # Creating cookies without the "HttpOnly" flag is security-sensitive -> replaced by CA5383 dotnet_diagnostic.S4507.severity = error # Delivering code in production with debug features activated is security-sensitive dotnet_diagnostic.S5122.severity = error # Having a permissive Cross-Origin Resource Sharing policy is security-sensitive From ba9b4574f9fb34d6df0d75c772c792395cc0ba15 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:06:10 +1000 Subject: [PATCH 09/18] build(deps): update RoslynCommonAnalyzers to 3.31.0 Enable SES1108 at error: a custom server-certificate validation callback that unconditionally returns true disables TLS server authentication. --- .editorconfig | 1 + src/Directory.Packages.props | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index b15fa28e..7ec1a1b4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1856,6 +1856,7 @@ dotnet_diagnostic.SES1104.severity = error # Certificate-chain validation must n dotnet_diagnostic.SES1105.severity = error # Bearer and OpenID Connect metadata must not be retrieved over plain HTTP outside development dotnet_diagnostic.SES1106.severity = error # Do not send HttpClient requests to a cleartext http URL dotnet_diagnostic.SES1107.severity = error # A SQL connection string weakens transport security via TrustServerCertificate or a disabled Encrypt +dotnet_diagnostic.SES1108.severity = error # A custom server-certificate validation callback unconditionally returns true, so any certificate is trusted # Secrets dotnet_diagnostic.SES1201.severity = error # Do not hard-code secrets in source diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 5b4a7ba1..0ddaa13a 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -5,7 +5,7 @@ true - 3.30.3 + 3.31.0 From 702ff1852ad4e7c720a349ed1bf1268dce0c1ef9 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:10:26 +1000 Subject: [PATCH 10/18] chore(editorconfig): enable the remaining 3.31.0 platform rules - PSH1500-PSH1503, PSH1505: ASP.NET Core rules, inert here but keep the set complete - SES1007, SES1008: hand-rolled crypto primitives and no-key XML signature checks - SES1203: a connection string naming a user with an empty password - SST2326, SST2327: narrowing an interface to its implementation, and a type testing its own runtime type instead of dispatching virtually --- .editorconfig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.editorconfig b/.editorconfig index 7ec1a1b4..845508f3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1631,6 +1631,8 @@ dotnet_diagnostic.SST2322.severity = error # A non-private readonly field holds dotnet_diagnostic.SST2323.severity = error # A stateless abstract class declaring only public abstract members should be an interface dotnet_diagnostic.SST2324.severity = error # A member is declared more accessible than its containing type dotnet_diagnostic.SST2325.severity = error # An async method checks an argument after its first await +dotnet_diagnostic.SST2326.severity = error # An interface-typed value is narrowed to a concrete implementation, coupling the code to it +dotnet_diagnostic.SST2327.severity = error # A type tests its own runtime type against a class instead of dispatching through a virtual member # Correctness dotnet_diagnostic.SST2400.severity = error # Two arguments name each other's parameters and have been transposed @@ -1833,6 +1835,13 @@ dotnet_diagnostic.PSH1416.severity = error # Cache the serializer options instea dotnet_diagnostic.PSH1417.severity = error # Do not compute an expensive argument for an assertion dotnet_diagnostic.PSH1418.severity = error # An HttpClient is constructed on every call +# ASP.NET Core - inert in this repo (no route handlers or middleware), enabled so the set stays complete +dotnet_diagnostic.PSH1500.severity = error # A minimal API handler returns Results instead of TypedResults, boxing the result +dotnet_diagnostic.PSH1501.severity = error # Middleware uses the legacy nested-delegate Use overload, allocating a per-request closure +dotnet_diagnostic.PSH1502.severity = error # A route handler returns a deferred sequence the serializer enumerates on the request thread +dotnet_diagnostic.PSH1503.severity = error # Response caching is used where server-side output caching applies +dotnet_diagnostic.PSH1505.severity = error # Exceptions are handled in an MVC exception filter instead of an IExceptionHandler + ################### # SecuritySharp Analyzers (SES) ################### @@ -1849,6 +1858,8 @@ dotnet_diagnostic.SES1003.severity = error # Password-based key derivation must dotnet_diagnostic.SES1004.severity = error # A secret must not be produced from Guid.NewGuid() dotnet_diagnostic.SES1005.severity = error # Compare secret values in constant time dotnet_diagnostic.SES1006.severity = error # A Data Protection key ring is persisted without a ProtectKeysWith call, so keys sit unencrypted at rest +dotnet_diagnostic.SES1007.severity = error # A cryptographic primitive is implemented by hand instead of using a vetted platform algorithm +dotnet_diagnostic.SES1008.severity = error # An XML signature is verified with the no-key CheckSignature overload, trusting the document's own KeyInfo # Transport dotnet_diagnostic.SES1102.severity = error # Do not accept any server certificate @@ -1861,6 +1872,7 @@ dotnet_diagnostic.SES1108.severity = error # A custom server-certificate validat # Secrets dotnet_diagnostic.SES1201.severity = error # Do not hard-code secrets in source dotnet_diagnostic.SES1202.severity = error # Do not hard-code a credential value +dotnet_diagnostic.SES1203.severity = error # A connection string names a user but supplies an empty or missing password # Injection dotnet_diagnostic.SES1301.severity = error # Do not build a process command line from non-constant string parts From 501fddbca89f0e8573acd5cebae4fe7f8f2540f6 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:04:52 +1000 Subject: [PATCH 11/18] build(deps): update RoslynCommonAnalyzers to 3.32.0 --- src/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 0ddaa13a..8f436976 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -5,7 +5,7 @@ true - 3.31.0 + 3.32.0 From e0c355e168cd4cde709683ff3922af530f84758e Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:11:51 +1000 Subject: [PATCH 12/18] build: replace SonarAnalyzer rules with RoslynCommonAnalyzers 3.33.0 Disable every dotnet_diagnostic.S#### rule. Each disabled line is annotated with the rule that now covers it (an SST/PSH/SES rule, an in-box CA rule, or the compiler/an obsoletion), or marked "no equivalent" for the genuine gaps we do not yet replace (mostly framework-only checks that do not apply to this library, plus WinForms STAThread). Bump RoslynCommonAnalyzersVersion to 3.33.0 ahead of publish. --- .editorconfig | 172 +++++++++++++++++------------------ src/Directory.Packages.props | 2 +- 2 files changed, 87 insertions(+), 87 deletions(-) diff --git a/.editorconfig b/.editorconfig index 845508f3..26c74e11 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2064,16 +2064,16 @@ dotnet_diagnostic.S2857.severity = none # SQL keywords should be delimited by wh dotnet_diagnostic.S2930.severity = none # "IDisposables" should be disposed — covered by SST2410 dotnet_diagnostic.S2931.severity = none # Classes with "IDisposable" members should implement "IDisposable" — covered by SST2315 dotnet_diagnostic.S3464.severity = none # Type inheritance should not be recursive — covered by SST2437 -dotnet_diagnostic.S3869.severity = error # "SafeHandle.DangerousGetHandle" should not be called -dotnet_diagnostic.S3889.severity = error # "Thread.Resume" and "Thread.Suspend" should not be used +dotnet_diagnostic.S3869.severity = none # "SafeHandle.DangerousGetHandle" should not be called -> replaced by SST2484 +dotnet_diagnostic.S3889.severity = none # "Thread.Resume" and "Thread.Suspend" should not be used -> replaced by obsolete or compiler dotnet_diagnostic.S4159.severity = none # Classes should implement their "ExportAttribute" interfaces — covered by SST2472 ################### # SonarAnalyzer (Sxxxx) - Critical Bug ################### dotnet_diagnostic.S2551.severity = none # Shared resources should not be used for locking — covered by SST1902 -dotnet_diagnostic.S2952.severity = error # Classes should "Dispose" of members from the classes' own "Dispose" methods -dotnet_diagnostic.S3449.severity = error # Right operands of shift operators should be integers +dotnet_diagnostic.S2952.severity = none # Classes should "Dispose" of members from the classes' own "Dispose" methods -> replaced by SST2315 +dotnet_diagnostic.S3449.severity = none # Right operands of shift operators should be integers -> replaced by SST1478 dotnet_diagnostic.S4275.severity = none # Getters and setters should access the expected fields — covered by SST2422 dotnet_diagnostic.S4277.severity = none # "Shared" parts should not be created with "new" — covered by SST2473 dotnet_diagnostic.S4583.severity = none # Calls to delegate's method "BeginInvoke" should be paired with calls to "EndInvoke" -> replaced by obsolete (APM BeginInvoke/EndInvoke) @@ -2088,10 +2088,10 @@ dotnet_diagnostic.S1244.severity = none # Floating point numbers should not be t dotnet_diagnostic.S1656.severity = none # Variables should not be self-assigned — covered by SST1189 dotnet_diagnostic.S1751.severity = none # Loops with at most one iteration should be refactored - covered by SST1444 dotnet_diagnostic.S1764.severity = none # Identical expressions should not be used on both sides of operators — covered by SST1474 -dotnet_diagnostic.S1848.severity = error # Objects should not be created to be dropped immediately without being used +dotnet_diagnostic.S1848.severity = none # Objects should not be created to be dropped immediately without being used -> replaced by SST1480 dotnet_diagnostic.S1862.severity = none # Related "if/else if" statements should not have the same condition — covered by SST1475 dotnet_diagnostic.S2114.severity = none # Collections should not be passed as arguments to their own methods — covered by SST2419 -dotnet_diagnostic.S2123.severity = error # Values should not be uselessly incremented +dotnet_diagnostic.S2123.severity = none # Values should not be uselessly incremented -> replaced by SST2222 dotnet_diagnostic.S2201.severity = none # Methods without side effects should not have their return values ignored — covered by SST2418 dotnet_diagnostic.S2225.severity = none # "ToString()" method should not return null — covered by SST2431 dotnet_diagnostic.S2251.severity = none # A "for" loop update clause should move the counter in the right direction — covered by SST2412 @@ -2123,15 +2123,15 @@ dotnet_diagnostic.S3927.severity = none # Serialization event handlers should be dotnet_diagnostic.S3981.severity = none # Collection sizes and array length comparisons should make sense — covered by SST1479 dotnet_diagnostic.S3984.severity = none # Exceptions should not be created without being thrown — covered by SST1480 dotnet_diagnostic.S4143.severity = none # Collection elements should not be replaced unconditionally — covered by SST1487 -dotnet_diagnostic.S4210.severity = error # Windows Forms entry points should be marked with STAThread -dotnet_diagnostic.S4260.severity = error # "ConstructorArgument" parameters should exist in constructors +dotnet_diagnostic.S4210.severity = none # Windows Forms entry points should be marked with STAThread -> no equivalent - WinForms STAThread on Main (candidate to build) +dotnet_diagnostic.S4260.severity = none # "ConstructorArgument" parameters should exist in constructors -> replaced by SST2487 dotnet_diagnostic.S4428.severity = none # "PartCreationPolicyAttribute" should be used with "ExportAttribute" — covered by SST2474 dotnet_diagnostic.S6507.severity = none # Blocks should not be synchronized on local variables — covered by SST1903 dotnet_diagnostic.S6677.severity = none # Message template placeholders should be unique — covered by SST2442 -dotnet_diagnostic.S6797.severity = error # Blazor query parameter type should be supported -dotnet_diagnostic.S6798.severity = error # [JSInvokable] attribute should only be used on public methods -dotnet_diagnostic.S6800.severity = error # Component parameter type should match the route parameter type constraint -dotnet_diagnostic.S6930.severity = error # Backslash should be avoided in route templates +dotnet_diagnostic.S6797.severity = none # Blazor query parameter type should be supported -> no equivalent - Blazor (framework; N.A.) +dotnet_diagnostic.S6798.severity = none # [JSInvokable] attribute should only be used on public methods -> no equivalent - Blazor (framework; N.A.) +dotnet_diagnostic.S6800.severity = none # Component parameter type should match the route parameter type constraint -> no equivalent - Blazor (framework; N.A.) +dotnet_diagnostic.S6930.severity = none # Backslash should be avoided in route templates -> no equivalent - ASP.NET MVC (framework; N.A.) ################### # SonarAnalyzer (Sxxxx) - Minor Bug @@ -2153,7 +2153,7 @@ dotnet_diagnostic.S3887.severity = none # Mutable, non-private fields should not ################### # SonarAnalyzer (Sxxxx) - Blocker Vulnerability ################### -dotnet_diagnostic.S2115.severity = error # A secure password should be used when connecting to a database +dotnet_diagnostic.S2115.severity = none # A secure password should be used when connecting to a database -> replaced by SES1203 dotnet_diagnostic.S2755.severity = none # XML parsers should not be vulnerable to XXE attacks -> replaced by CA3075 dotnet_diagnostic.S3884.severity = none # "CoSetProxyBlanket" and "CoInitializeSecurity" should not be used -> replaced by obsolete (COM interop security) dotnet_diagnostic.S6418.severity = none # Secrets should not be hard-coded — covered by SES1201 @@ -2163,23 +2163,23 @@ dotnet_diagnostic.S6418.severity = none # Secrets should not be hard-coded — c ################### dotnet_diagnostic.S4423.severity = none # Weak SSL/TLS protocols should not be used -> replaced by CA5397/CA5398 dotnet_diagnostic.S4426.severity = none # Cryptographic keys should be robust -> replaced by CA5385 (RSA) / CA5384 (DSA) -dotnet_diagnostic.S4433.severity = error # LDAP connections should be authenticated -dotnet_diagnostic.S4830.severity = error # Server certificates should be verified during SSL/TLS connections -dotnet_diagnostic.S5344.severity = error # Passwords should not be stored in plaintext or with a fast hashing algorithm -dotnet_diagnostic.S5445.severity = error # Insecure temporary file creation methods should not be used +dotnet_diagnostic.S4433.severity = none # LDAP connections should be authenticated -> replaced by SES1310 +dotnet_diagnostic.S4830.severity = none # Server certificates should be verified during SSL/TLS connections -> replaced by SES1102 or SES1108 +dotnet_diagnostic.S5344.severity = none # Passwords should not be stored in plaintext or with a fast hashing algorithm -> replaced by SES1009 +dotnet_diagnostic.S5445.severity = none # Insecure temporary file creation methods should not be used -> replaced by SES1307 dotnet_diagnostic.S5542.severity = none # Encryption algorithms should be used with secure mode and padding scheme -> replaced by CA5358 dotnet_diagnostic.S5547.severity = none # Cipher algorithms should be robust -> replaced by CA5351 -dotnet_diagnostic.S5659.severity = error # JWT should be signed and verified with strong cipher algorithms +dotnet_diagnostic.S5659.severity = none # JWT should be signed and verified with strong cipher algorithms -> replaced by SES1503 ################### # SonarAnalyzer (Sxxxx) - Major Vulnerability ################### -dotnet_diagnostic.S2068.severity = error # Credentials should not be hard-coded -dotnet_diagnostic.S2612.severity = error # File permissions should not be set to world-accessible values +dotnet_diagnostic.S2068.severity = none # Credentials should not be hard-coded -> replaced by SES1201 +dotnet_diagnostic.S2612.severity = none # File permissions should not be set to world-accessible values -> replaced by SES1308 dotnet_diagnostic.S4211.severity = none # Members should not have conflicting transparency annotations -> replaced by obsolete (Code Access Security) dotnet_diagnostic.S4212.severity = none # Serialization constructors should be secured -> replaced by obsolete (Code Access Security) -dotnet_diagnostic.S6377.severity = error # XML signatures should be validated securely -dotnet_diagnostic.S7039.severity = error # Content Security Policies should be restrictive +dotnet_diagnostic.S6377.severity = none # XML signatures should be validated securely -> replaced by SES1008 +dotnet_diagnostic.S7039.severity = none # Content Security Policies should be restrictive -> replaced by SES1515 ################### # SonarAnalyzer (Sxxxx) - Blocker Code Smell @@ -2192,19 +2192,19 @@ dotnet_diagnostic.S2306.severity = none # "async" and "await" should not be used dotnet_diagnostic.S2368.severity = none # Public methods should not have multidimensional array parameters — jagged arrays are the chosen layout for hot-path lookup tables dotnet_diagnostic.S2387.severity = none # Child class fields should not shadow parent class fields — covered by SST1484 dotnet_diagnostic.S2437.severity = none # Unnecessary bit operations should not be performed — covered by SST1481 -dotnet_diagnostic.S2699.severity = error # Tests should include assertions +dotnet_diagnostic.S2699.severity = none # Tests should include assertions -> replaced by SST2500 dotnet_diagnostic.S2953.severity = none # Methods named "Dispose" should implement "IDisposable.Dispose" — covered by SST2316 -dotnet_diagnostic.S2970.severity = error # Assertions should be complete -dotnet_diagnostic.S3060.severity = error # "is" should not be used with "this" +dotnet_diagnostic.S2970.severity = none # Assertions should be complete -> replaced by SST2508 +dotnet_diagnostic.S3060.severity = none # "is" should not be used with "this" -> replaced by SST2327 dotnet_diagnostic.S3237.severity = none # "value" contextual keyword should be used — covered by SST2429 dotnet_diagnostic.S3427.severity = none # Method overloads with default parameter values should not overlap — covered by SST2319 -dotnet_diagnostic.S3433.severity = error # Test method signatures should be correct +dotnet_diagnostic.S3433.severity = none # Test method signatures should be correct -> replaced by SST2509 dotnet_diagnostic.S3443.severity = none # Type should not be examined on "System.Type" instances — covered by SST2432 -dotnet_diagnostic.S3875.severity = error # "operator==" should not be overloaded on reference types +dotnet_diagnostic.S3875.severity = none # "operator==" should not be overloaded on reference types -> replaced by SST2464 dotnet_diagnostic.S3877.severity = none # Exceptions should not be thrown from unexpected methods — covered by SST1485 dotnet_diagnostic.S4462.severity = none # Calls to "async" methods should not be blocking - covered by PSH1315 dotnet_diagnostic.S6422.severity = none # Calls to "async" methods should not be blocking in Azure Functions — covered by PSH1315 -dotnet_diagnostic.S6424.severity = error # Interfaces for durable entities should satisfy the restrictions +dotnet_diagnostic.S6424.severity = none # Interfaces for durable entities should satisfy the restrictions -> no equivalent - Durable Entity (framework; N.A.) ################### # SonarAnalyzer (Sxxxx) - Critical Code Smell @@ -2213,20 +2213,20 @@ dotnet_diagnostic.S1006.severity = none # Method overrides should not change par dotnet_diagnostic.S1067.severity = none # Expressions should not be too complex dotnet_diagnostic.S1163.severity = none # Exceptions should not be thrown in finally blocks -> replaced by CA2219 dotnet_diagnostic.S1186.severity = none # Methods should not be empty — covered by SST1438 -dotnet_diagnostic.S121.severity = error # Control structures should use curly braces (kept over SA1503 — Sonar 20ms vs SA1503 50ms) +dotnet_diagnostic.S121.severity = none # Control structures should use curly braces (kept over SA1503 — Sonar 20ms vs SA1503 50ms) -> replaced by SST1503 dotnet_diagnostic.S1215.severity = none # "GC.Collect" should not be called — covered by PSH1021 dotnet_diagnostic.S126.severity = none # "if ... else if" constructs should end with "else" clauses dotnet_diagnostic.S131.severity = none # "switch/Select" statements should contain a "default/Case Else" clauses dotnet_diagnostic.S134.severity = none # Control flow statements "if", "switch", "for", "foreach", "while", "do" and "try" should not be nested too deeply dotnet_diagnostic.S1541.severity = none # Methods and properties should not be too complex - covered by SST1442 dotnet_diagnostic.S1699.severity = none # Constructors should only call non-overridable methods — covered by SST1483 -dotnet_diagnostic.S1821.severity = error # "switch" statements should not be nested +dotnet_diagnostic.S1821.severity = none # "switch" statements should not be nested -> replaced by SST2252 dotnet_diagnostic.S1944.severity = none # Invalid casts should be avoided -> replaced by compiler CS0030 dotnet_diagnostic.S1994.severity = none # "for" loop increment clauses should modify the loops' counters — covered by SST2411 dotnet_diagnostic.S2197.severity = none # Modulus results should not be checked for direct equality — covered by SST2416 -dotnet_diagnostic.S2198.severity = error # Unnecessary mathematical comparisons should not be made +dotnet_diagnostic.S2198.severity = none # Unnecessary mathematical comparisons should not be made -> replaced by SST2489 dotnet_diagnostic.S2223.severity = none # Non-constant static fields should not be visible - DUPLICATE CA2211 -dotnet_diagnostic.S2290.severity = error # Field-like events should not be virtual +dotnet_diagnostic.S2290.severity = none # Field-like events should not be virtual -> replaced by SST2456 dotnet_diagnostic.S2291.severity = none # Overflow checking should not be disabled for "Enumerable.Sum" — covered by SST2457 dotnet_diagnostic.S2302.severity = none # "nameof" should be used — covered by SST1415 dotnet_diagnostic.S2330.severity = none # Array covariance should not be used — covered by SST2434 @@ -2236,9 +2236,9 @@ dotnet_diagnostic.S2360.severity = none # Optional parameters should not be used dotnet_diagnostic.S2365.severity = none # Properties should not make collection or array copies — covered by PSH1017 dotnet_diagnostic.S2479.severity = none # Whitespace and control characters in string literals should be explicit — covered by SST1192 dotnet_diagnostic.S2692.severity = none # "IndexOf" checks should not be for positive numbers — covered by SST2420 -dotnet_diagnostic.S2696.severity = error # Instance members should not write to "static" fields +dotnet_diagnostic.S2696.severity = none # Instance members should not write to "static" fields -> replaced by SST2402 dotnet_diagnostic.S2701.severity = none # Literal boolean values should not be used in assertions — covered by SST2503 -dotnet_diagnostic.S3215.severity = error # "interface" instances should not be cast to concrete types +dotnet_diagnostic.S3215.severity = none # "interface" instances should not be cast to concrete types -> replaced by SST2326 dotnet_diagnostic.S3216.severity = none # "ConfigureAwait(false)" should be used -> replaced by CA2007 dotnet_diagnostic.S3217.severity = none # "Explicit" conversions of "foreach" loops should not be used — covered by SST2225 dotnet_diagnostic.S3218.severity = none # Inner class members should not shadow outer class "static" or type members — covered by SST1484 @@ -2253,9 +2253,9 @@ dotnet_diagnostic.S3874.severity = none # "out" and "ref" parameters — repo id dotnet_diagnostic.S3904.severity = none # Assemblies should have version information -> replaced by obsolete (SDK supplies assembly version) dotnet_diagnostic.S3937.severity = none # Number patterns should be regular — covered by SST1119 dotnet_diagnostic.S3972.severity = none # Conditionals should start on new lines — covered by SST1146 -dotnet_diagnostic.S3973.severity = error # A conditionally executed single line should be denoted by indentation -dotnet_diagnostic.S3998.severity = error # Threads should not lock on objects with weak identity -dotnet_diagnostic.S4000.severity = error # Pointers to unmanaged memory should not be visible +dotnet_diagnostic.S3973.severity = none # A conditionally executed single line should be denoted by indentation -> replaced by SST1503 +dotnet_diagnostic.S3998.severity = none # Threads should not lock on objects with weak identity -> replaced by SST1902 +dotnet_diagnostic.S4000.severity = none # Pointers to unmanaged memory should not be visible -> replaced by SST2328 dotnet_diagnostic.S4015.severity = none # Inherited member visibility should not be decreased — covered by SST2462 dotnet_diagnostic.S4019.severity = none # Base class methods should not be hidden — covered by SST2427 dotnet_diagnostic.S4025.severity = none # Child class fields should not differ from parent class fields only by capitalization — covered by SST2463 @@ -2264,7 +2264,7 @@ dotnet_diagnostic.S4487.severity = none # Unread "private" fields should be remo dotnet_diagnostic.S4524.severity = none # "default" clauses should be first or last — covered by SST1219 dotnet_diagnostic.S4635.severity = none # Start index should be used instead of calling Substring — covered by PSH1218 dotnet_diagnostic.S5034.severity = none # "ValueTask" should be consumed correctly — covered by PSH1316 -dotnet_diagnostic.S6967.severity = error # ModelState.IsValid should be called in controller actions +dotnet_diagnostic.S6967.severity = none # ModelState.IsValid should be called in controller actions -> no equivalent - ASP.NET MVC (framework; N.A.) dotnet_diagnostic.S8367.severity = none # Identifiers should not conflict with the C# 14 "field" contextual keyword - the C# 14 compiler reports this: CS9273 (error) for a local named field in an accessor, CS9258 (warning) for a rebinding read dotnet_diagnostic.S8368.severity = none # Identifiers should not conflict with the C# 14 "extension" contextual keyword - the C# 14 compiler reports this as CS9306, and SST1300 already flags the lowercase type name dotnet_diagnostic.S8380.severity = none # Return types named "partial" should be escaped with "@" - the compiler reports this as CS8981, and SST1300 already flags the lowercase type name @@ -2288,7 +2288,7 @@ dotnet_diagnostic.S1118.severity = none # Utility classes should not have public dotnet_diagnostic.S112.severity = none # General or reserved exceptions should never be thrown — covered by SST2409 dotnet_diagnostic.S1121.severity = none # Assignments should not be made from within sub-expressions — covered by SST1187 dotnet_diagnostic.S1123.severity = none # "Obsolete" attributes should include explanations — covered by SST2308 -dotnet_diagnostic.S1134.severity = error # Track uses of "FIXME" tags +dotnet_diagnostic.S1134.severity = none # Track uses of "FIXME" tags -> no equivalent - TODO/FIXME tracker (low value) dotnet_diagnostic.S1144.severity = none # Covered by SST1440 (canonical) dotnet_diagnostic.S1151.severity = none # "switch case" clauses should not have too many lines of code — covered by SST1524 dotnet_diagnostic.S1168.severity = none # Empty arrays and collections should be returned instead of null — covered by SST2306 @@ -2299,15 +2299,15 @@ dotnet_diagnostic.S125.severity = none # Sections of code should not be commente dotnet_diagnostic.S127.severity = none # "for" loop stop conditions should be invariant — covered by SST2465 dotnet_diagnostic.S138.severity = none # Functions should not have too many lines of code — covered by SST1523 dotnet_diagnostic.S1479.severity = none # "switch" statements with many "case" clauses — covered by SST1423 -dotnet_diagnostic.S1607.severity = error # Tests should not be ignored +dotnet_diagnostic.S1607.severity = none # Tests should not be ignored -> no equivalent - ignored-test tracker (low value) dotnet_diagnostic.S1696.severity = none # NullReferenceException should not be caught — covered by SST2401 dotnet_diagnostic.S1854.severity = none # Unused assignments should be removed - DUPLICATE IDE0059 dotnet_diagnostic.S1871.severity = none # Two branches in a conditional structure should not have exactly the same implementation — covered by SST2414 -dotnet_diagnostic.S2139.severity = error # Exceptions should be either logged or rethrown but not both +dotnet_diagnostic.S2139.severity = none # Exceptions should be either logged or rethrown but not both -> replaced by SST2488 dotnet_diagnostic.S2166.severity = none # Classes named like "Exception" should extend "Exception" or a subclass - DUPLICATE CA1710 -dotnet_diagnostic.S2234.severity = error # Arguments should be passed in the same order as the method parameters +dotnet_diagnostic.S2234.severity = none # Arguments should be passed in the same order as the method parameters -> replaced by SST2400 dotnet_diagnostic.S2326.severity = none # Covered by SST1452 (canonical) -dotnet_diagnostic.S2327.severity = error # "try" statements with identical "catch" and/or "finally" blocks should be merged +dotnet_diagnostic.S2327.severity = none # "try" statements with identical "catch" and/or "finally" blocks should be merged -> replaced by SST2490 dotnet_diagnostic.S2357.severity = none # Fields should be private — duplicate of SST1401 (canonical); fields intentionally exposed (e.g. public test fields for reflection) already carry per-site SST1401 suppressions dotnet_diagnostic.S2372.severity = none # Exceptions should not be thrown from property getters — covered by SST1485 dotnet_diagnostic.S2376.severity = none # Write-only properties should not be used — covered by SST1421 @@ -2318,11 +2318,11 @@ dotnet_diagnostic.S2925.severity = none # "Thread.Sleep" should not be used in t dotnet_diagnostic.S2933.severity = none # Fields that are only assigned in the constructor should be "readonly" - DUPLICATE IDE0044 dotnet_diagnostic.S2971.severity = none # LINQ expressions should be simplified — covered by PSH1101/PSH1102 dotnet_diagnostic.S3010.severity = none # Static fields should not be updated in constructors — covered by SST2402 -dotnet_diagnostic.S3011.severity = error # Reflection should not be used to increase accessibility of classes, methods, or fields +dotnet_diagnostic.S3011.severity = none # Reflection should not be used to increase accessibility of classes, methods, or fields -> no equivalent - NonPublic reflection (audit hotspot) dotnet_diagnostic.S3059.severity = none # Types should not have members with visibility set higher than the type's visibility dotnet_diagnostic.S3063.severity = none # "StringBuilder" data should be used — covered by SST2408 dotnet_diagnostic.S3169.severity = none # Multiple "OrderBy" calls should not be used — covered by PSH1108 -dotnet_diagnostic.S3246.severity = error # Generic type parameters should be co/contravariant when possible +dotnet_diagnostic.S3246.severity = none # Generic type parameters should be co/contravariant when possible -> no equivalent - in/out variance suggestion (low precision) dotnet_diagnostic.S3262.severity = none # "params" should be used on overrides — covered by SST2426 dotnet_diagnostic.S3264.severity = none # Events should be invoked — covered by SST2407 dotnet_diagnostic.S3358.severity = none # Ternary operators should not be nested — covered by SST1147 @@ -2335,11 +2335,11 @@ dotnet_diagnostic.S3457.severity = none # Composite format strings should be use dotnet_diagnostic.S3597.severity = none # "ServiceContract" and "OperationContract" attributes should be used together -> replaced by obsolete (WCF) dotnet_diagnostic.S3880.severity = none # Finalizers should not be empty — covered by PSH1002 dotnet_diagnostic.S3881.severity = none # "IDisposable" should be implemented correctly — covered by SST2300 -dotnet_diagnostic.S3885.severity = error # "Assembly.Load" should be used +dotnet_diagnostic.S3885.severity = none # "Assembly.Load" should be used -> replaced by SST2486 dotnet_diagnostic.S3898.severity = none # Covered by PSH1005 (canonical) dotnet_diagnostic.S3902.severity = none # Covered by PSH1404 (canonical) dotnet_diagnostic.S3906.severity = none # Event Handlers should have the correct signature — covered by SST2304 -dotnet_diagnostic.S3908.severity = error # Generic event handlers should be used +dotnet_diagnostic.S3908.severity = none # Generic event handlers should be used -> replaced by SST2304 dotnet_diagnostic.S3909.severity = none # Collections should implement the generic interface -> replaced by CA1010 dotnet_diagnostic.S3925.severity = none # "ISerializable" should be implemented correctly - BinaryFormatter / ISerializable serialization is obsoleted (SYSLIB0050/0051) in modern .NET; we do not opt into legacy serialization for any exception type dotnet_diagnostic.S3928.severity = none # Parameter names used into ArgumentException constructors should match an existing one - DUPLICATE CA2208 @@ -2355,16 +2355,16 @@ dotnet_diagnostic.S3997.severity = none # String URI overloads should call "Syst dotnet_diagnostic.S4002.severity = none # Disposable types should declare finalizers — covered by SST2317 dotnet_diagnostic.S4004.severity = none # Collection properties should be readonly — covered by SST2305 dotnet_diagnostic.S4005.severity = none # "System.Uri" arguments should be used instead of strings -dotnet_diagnostic.S4016.severity = error # Enumeration members should not be named "Reserved" +dotnet_diagnostic.S4016.severity = none # Enumeration members should not be named "Reserved" -> replaced by CA1700 dotnet_diagnostic.S4017.severity = none # Method signatures should not contain nested generic types dotnet_diagnostic.S4035.severity = none # Classes implementing "IEquatable" should be sealed — covered by SST2301 dotnet_diagnostic.S4050.severity = none # Operators should be overloaded consistently — covered by SST2302 dotnet_diagnostic.S4055.severity = none # Literals should not be passed as localized parameters -dotnet_diagnostic.S4057.severity = error # Locales should be set for data types +dotnet_diagnostic.S4057.severity = none # Locales should be set for data types -> replaced by obsolete dotnet_diagnostic.S4059.severity = none # Property names should not match get methods - DUPLICATE CA1721 dotnet_diagnostic.S4070.severity = none # Non-flags enums should not be marked with "FlagsAttribute" — covered by SST2303 dotnet_diagnostic.S4144.severity = none # Methods should not have identical implementations — covered by SST2318 -dotnet_diagnostic.S4200.severity = error # Native methods should be wrapped +dotnet_diagnostic.S4200.severity = none # Native methods should be wrapped -> replaced by CA1401 dotnet_diagnostic.S4214.severity = none # "P/Invoke" methods should not be visible - DUPLICATE CA1401 dotnet_diagnostic.S4220.severity = none # Events should have proper arguments — covered by SST2436 dotnet_diagnostic.S4456.severity = none # Parameter validation in yielding methods should be wrapped — covered by SST2404 @@ -2372,29 +2372,29 @@ dotnet_diagnostic.S4457.severity = none # Parameter validation in "async"/"await dotnet_diagnostic.S4545.severity = none # "DebuggerDisplayAttribute" strings should reference existing members — covered by SST2405 dotnet_diagnostic.S4581.severity = none # "new Guid()" should not be used — covered by SST2012 dotnet_diagnostic.S6354.severity = none # Use a testable date/time provider — covered by SST2010 -dotnet_diagnostic.S6419.severity = error # Azure Functions should be stateless +dotnet_diagnostic.S6419.severity = none # Azure Functions should be stateless -> no equivalent - Azure Functions (framework; N.A. to this library) dotnet_diagnostic.S6420.severity = none # Client instances should not be recreated on each Azure Function invocation — covered by PSH1418 -dotnet_diagnostic.S6421.severity = error # Azure Functions should use Structured Error Handling -dotnet_diagnostic.S6423.severity = error # Azure Functions should log all failures +dotnet_diagnostic.S6421.severity = none # Azure Functions should use Structured Error Handling -> no equivalent - Azure Functions (framework; N.A.) +dotnet_diagnostic.S6423.severity = none # Azure Functions should log all failures -> no equivalent - Azure Functions (framework; N.A.) dotnet_diagnostic.S6561.severity = none # Avoid using "DateTime.Now" for benchmarking or timing operations — covered by PSH1408 dotnet_diagnostic.S6562.severity = none # Covered by SST1451 (canonical) dotnet_diagnostic.S6563.severity = none # Use UTC when recording DateTime instants — covered by SST2011 dotnet_diagnostic.S6566.severity = none # Use "DateTimeOffset" instead of "DateTime" — covered by SST2016 -dotnet_diagnostic.S6575.severity = error # Use "TimeZoneInfo.FindSystemTimeZoneById" without converting the timezones with "TimezoneConverter" +dotnet_diagnostic.S6575.severity = none # Use "TimeZoneInfo.FindSystemTimeZoneById" without converting the timezones with "TimezoneConverter" -> replaced by PSH1419 dotnet_diagnostic.S6580.severity = none # Use a format provider when parsing date and time - DUPLICATE CA1305 dotnet_diagnostic.S6673.severity = none # Log message template placeholders should be in the right order — covered by SST2440 -dotnet_diagnostic.S6802.severity = error # Using lambda expressions in loops should be avoided in Blazor markup section -dotnet_diagnostic.S6803.severity = error # Parameters with SupplyParameterFromQuery attribute should be used only in routable components -dotnet_diagnostic.S6931.severity = error # ASP.NET controller actions should not have a route template starting with "/" -dotnet_diagnostic.S6932.severity = error # Use model binding instead of reading raw request data -dotnet_diagnostic.S6934.severity = error # A Route attribute should be added to the controller when a route template is specified at the action level -dotnet_diagnostic.S6960.severity = error # Controllers should not have mixed responsibilities -dotnet_diagnostic.S6961.severity = error # API Controllers should derive from ControllerBase instead of Controller +dotnet_diagnostic.S6802.severity = none # Using lambda expressions in loops should be avoided in Blazor markup section -> no equivalent - Blazor render loop (framework; N.A.) +dotnet_diagnostic.S6803.severity = none # Parameters with SupplyParameterFromQuery attribute should be used only in routable components -> no equivalent - Blazor (framework; N.A.) +dotnet_diagnostic.S6931.severity = none # ASP.NET controller actions should not have a route template starting with "/" -> no equivalent - ASP.NET MVC (framework; N.A.) +dotnet_diagnostic.S6932.severity = none # Use model binding instead of reading raw request data -> no equivalent - ASP.NET MVC (framework; N.A.) +dotnet_diagnostic.S6934.severity = none # A Route attribute should be added to the controller when a route template is specified at the action level -> no equivalent - ASP.NET MVC (framework; N.A.) +dotnet_diagnostic.S6960.severity = none # Controllers should not have mixed responsibilities -> no equivalent - ASP.NET MVC (framework; N.A.) +dotnet_diagnostic.S6961.severity = none # API Controllers should derive from ControllerBase instead of Controller -> no equivalent - ASP.NET MVC (framework; N.A.) dotnet_diagnostic.S6962.severity = none # You should pool HTTP connections with HttpClientFactory — covered by PSH1418 -dotnet_diagnostic.S6964.severity = error # Value type property used as input in a controller action should be nullable, required or annotated with the JsonRequiredAttribute to avoid under-posting. -dotnet_diagnostic.S6965.severity = error # REST API actions should be annotated with an HTTP verb attribute +dotnet_diagnostic.S6964.severity = none # Value type property used as input in a controller action should be nullable, required or annotated with the JsonRequiredAttribute to avoid under-posting. -> no equivalent - ASP.NET MVC (framework; N.A.) +dotnet_diagnostic.S6965.severity = none # REST API actions should be annotated with an HTTP verb attribute -> no equivalent - ASP.NET MVC (framework; N.A.) dotnet_diagnostic.S6966.severity = none # Awaitable method should be used — covered by PSH1313 -dotnet_diagnostic.S6968.severity = error # Actions that return a value should be annotated with ProducesResponseTypeAttribute containing the return type +dotnet_diagnostic.S6968.severity = none # Actions that return a value should be annotated with ProducesResponseTypeAttribute containing the return type -> no equivalent - ASP.NET MVC (framework; N.A.) dotnet_diagnostic.S881.severity = none # Increment (++) and decrement (--) operators should not be used in a method call or mixed with other operators in an expression — covered by SST2015 dotnet_diagnostic.S907.severity = none # "goto" statement should not be used — covered by SST2014 @@ -2450,14 +2450,14 @@ dotnet_diagnostic.S3234.severity = none # Covered by PSH1008 (canonical) dotnet_diagnostic.S3235.severity = none # Redundant parentheses should not be used — covered by SST1459 dotnet_diagnostic.S3236.severity = none # Covered by SST1448 (canonical) dotnet_diagnostic.S3240.severity = none # The simplest possible condition syntax should be used - duplicate of SST1198 -dotnet_diagnostic.S3241.severity = error # Methods should not return values that are never used +dotnet_diagnostic.S3241.severity = none # Methods should not return values that are never used -> no equivalent - unused return value (needs whole-program) dotnet_diagnostic.S3242.severity = none # Method parameters should be declared with base types dotnet_diagnostic.S3247.severity = none # Duplicate casts should not be made — covered by SST1175 dotnet_diagnostic.S3251.severity = none # Implementations should be provided for "partial" methods — covered by SST2468 dotnet_diagnostic.S3253.severity = none # Constructor and destructor declarations should not be redundant — covered by SST1433 dotnet_diagnostic.S3254.severity = none # Default parameter values should not be passed as arguments — covered by SST1494 dotnet_diagnostic.S3256.severity = none # "string.IsNullOrEmpty" should be used — covered by PSH1204 (style configurable) -dotnet_diagnostic.S3257.severity = error # Declarations and initializations should be as concise as possible +dotnet_diagnostic.S3257.severity = none # Declarations and initializations should be as concise as possible -> replaced by SST2202 dotnet_diagnostic.S3260.severity = none # Non-derived "private" classes and records should be "sealed" — covered by PSH1411 dotnet_diagnostic.S3261.severity = none # Namespaces should not be empty — covered by SST1435 dotnet_diagnostic.S3267.severity = none # Loops should be simplified with "LINQ" expressions @@ -2474,8 +2474,8 @@ dotnet_diagnostic.S3459.severity = none # Unassigned members should be removed - dotnet_diagnostic.S3532.severity = none # Empty "default" clauses should be removed — covered by SST1179 dotnet_diagnostic.S3604.severity = none # Member initializer values should not be redundant — covered by PSH1403 dotnet_diagnostic.S3626.severity = none # Jump statements should not be redundant — covered by SST1174 -dotnet_diagnostic.S3717.severity = error # Track use of "NotImplementedException" -dotnet_diagnostic.S3872.severity = error # Parameter names should not duplicate the names of their methods +dotnet_diagnostic.S3717.severity = none # Track use of "NotImplementedException" -> replaced by SST2485 +dotnet_diagnostic.S3872.severity = none # Parameter names should not duplicate the names of their methods -> replaced by SST1320 dotnet_diagnostic.S3876.severity = none # Strings or integral types should be used for indexers -> replaced by CA1043 dotnet_diagnostic.S3878.severity = none # Arrays should not be created for params parameters — covered by PSH1018 dotnet_diagnostic.S3897.severity = none # Classes that provide "Equals()" should implement "IEquatable" -> replaced by CA1067 @@ -2489,9 +2489,9 @@ dotnet_diagnostic.S4026.severity = none # Assemblies should be marked with "Neut dotnet_diagnostic.S4027.severity = none # Exceptions should provide standard constructors — covered by SST1488 dotnet_diagnostic.S4040.severity = none # Strings should be normalized to uppercase - DUPLICATE CA1308 dotnet_diagnostic.S4041.severity = none # Type names should not match namespaces - DUPLICATE CA1724 -dotnet_diagnostic.S4047.severity = error # Generics should be used when appropriate +dotnet_diagnostic.S4047.severity = none # Generics should be used when appropriate -> no equivalent - prefer generics (fuzzy) dotnet_diagnostic.S4049.severity = none # Properties should be preferred - DUPLICATE CA1024 -dotnet_diagnostic.S4052.severity = error # Types should not extend outdated base types +dotnet_diagnostic.S4052.severity = none # Types should not extend outdated base types -> replaced by obsolete dotnet_diagnostic.S4056.severity = none # Overloads with a "CultureInfo" or an "IFormatProvider" parameter should be used - DUPLICATE CA1305 dotnet_diagnostic.S4058.severity = none # Covered by PSH1207 (canonical) dotnet_diagnostic.S4060.severity = none # Non-abstract attributes should be sealed - DUPLICATE CA1813 @@ -2518,13 +2518,13 @@ dotnet_diagnostic.S6612.severity = none # The lambda parameter should be used in dotnet_diagnostic.S6613.severity = none # "First" and "Last" properties of "LinkedList" should be used instead of the "First()" and "Last()" extension methods — covered by PSH1124 dotnet_diagnostic.S6617.severity = none # Covered by PSH1111 (canonical) dotnet_diagnostic.S6618.severity = none # "string.Create" should be used instead of "FormattableString" — covered by PSH1209 -dotnet_diagnostic.S6664.severity = error # The code block contains too many logging calls +dotnet_diagnostic.S6664.severity = none # The code block contains too many logging calls -> no equivalent - too many logging calls (fuzzy) dotnet_diagnostic.S6667.severity = none # Logging in a catch clause should pass the caught exception as a parameter. — covered by SST2438 dotnet_diagnostic.S6668.severity = none # Logging arguments should be passed to the correct parameter — covered by SST2439 -dotnet_diagnostic.S6669.severity = error # Logger field or property name should comply with a naming convention +dotnet_diagnostic.S6669.severity = none # Logger field or property name should comply with a naming convention -> replaced by SST2601 dotnet_diagnostic.S6670.severity = none # "Trace.Write" and "Trace.WriteLine" should not be used — covered by SST2600 dotnet_diagnostic.S6672.severity = none # Generic logger injection should match enclosing type — covered by SST2443 -dotnet_diagnostic.S6675.severity = error # "Trace.WriteLineIf" should not be used with "TraceSwitch" levels +dotnet_diagnostic.S6675.severity = none # "Trace.WriteLineIf" should not be used with "TraceSwitch" levels -> no equivalent - TraceSwitch misuse (niche) dotnet_diagnostic.S6678.severity = none # Use PascalCase for named placeholders -> replaced by CA1727 dotnet_diagnostic.S818.severity = none # Literal suffixes should be upper case — covered by SST2244 @@ -2532,7 +2532,7 @@ dotnet_diagnostic.S818.severity = none # Literal suffixes should be upper case # SonarAnalyzer (Sxxxx) - Info Code Smell ################### dotnet_diagnostic.S1133.severity = none # Deprecated code should be removed — covered by SST2310 -dotnet_diagnostic.S1135.severity = error # Track uses of "TODO" tags +dotnet_diagnostic.S1135.severity = none # Track uses of "TODO" tags -> no equivalent - TODO/FIXME tracker (low value) dotnet_diagnostic.S1309.severity = none # Track uses of in-source issue suppressions ################### @@ -2552,32 +2552,32 @@ dotnet_diagnostic.S9999-warning.severity = error # Analysis Warning generator # SonarAnalyzer (Sxxxx) - Critical Security Hotspot ################### dotnet_diagnostic.S2245.severity = none # Using pseudorandom number generators (PRNGs) is security-sensitive - DUPLICATE CA5394 -dotnet_diagnostic.S2257.severity = error # Using non-standard cryptographic algorithms is security-sensitive +dotnet_diagnostic.S2257.severity = none # Using non-standard cryptographic algorithms is security-sensitive -> replaced by SES1007 dotnet_diagnostic.S4502.severity = none # Disabling CSRF protections is security-sensitive -> replaced by in-box ASP.NET antiforgery analyzer dotnet_diagnostic.S4790.severity = none # Using weak hashing algorithms is security-sensitive -> replaced by CA5350/CA5351 -dotnet_diagnostic.S4792.severity = error # Configuring loggers is security-sensitive -dotnet_diagnostic.S5042.severity = error # Expanding archive files without controlling resource consumption is security-sensitive -dotnet_diagnostic.S5332.severity = error # Using clear-text protocols is security-sensitive -dotnet_diagnostic.S5443.severity = error # Using publicly writable directories is security-sensitive +dotnet_diagnostic.S4792.severity = none # Configuring loggers is security-sensitive -> no equivalent - logger configuration hotspot (audit) +dotnet_diagnostic.S5042.severity = none # Expanding archive files without controlling resource consumption is security-sensitive -> no equivalent - unbounded decompression (needs taint) +dotnet_diagnostic.S5332.severity = none # Using clear-text protocols is security-sensitive -> replaced by SES1106 +dotnet_diagnostic.S5443.severity = none # Using publicly writable directories is security-sensitive -> replaced by SES1308 ################### # SonarAnalyzer (Sxxxx) - Major Security Hotspot ################### -dotnet_diagnostic.S1313.severity = error # Using hardcoded IP addresses is security-sensitive +dotnet_diagnostic.S1313.severity = none # Using hardcoded IP addresses is security-sensitive -> no equivalent - hardcoded IP literal (noisy) dotnet_diagnostic.S2077.severity = none # Formatting SQL queries is security-sensitive -> replaced by CA2100 -dotnet_diagnostic.S5693.severity = error # Allowing requests with excessive content length is security-sensitive +dotnet_diagnostic.S5693.severity = none # Allowing requests with excessive content length is security-sensitive -> replaced by SES1505 dotnet_diagnostic.S5753.severity = none # Disabling ASP.NET "Request Validation" feature is security-sensitive -> replaced by obsolete (legacy ASP.NET request validation) -dotnet_diagnostic.S5766.severity = error # Creating Serializable objects without data validation checks is security-sensitive -dotnet_diagnostic.S6444.severity = error # Not specifying a timeout for regular expressions is security-sensitive -dotnet_diagnostic.S6640.severity = error # Using unsafe code blocks is security-sensitive +dotnet_diagnostic.S5766.severity = none # Creating Serializable objects without data validation checks is security-sensitive -> no equivalent - Serializable deserialization (obsolete BinaryFormatter) +dotnet_diagnostic.S6444.severity = none # Not specifying a timeout for regular expressions is security-sensitive -> replaced by SES1509 +dotnet_diagnostic.S6640.severity = none # Using unsafe code blocks is security-sensitive -> no equivalent - unsafe code blocks (audit hotspot) ################### # SonarAnalyzer (Sxxxx) - Minor Security Hotspot ################### dotnet_diagnostic.S2092.severity = none # Creating cookies without the "secure" flag is security-sensitive -> replaced by CA5382 dotnet_diagnostic.S3330.severity = none # Creating cookies without the "HttpOnly" flag is security-sensitive -> replaced by CA5383 -dotnet_diagnostic.S4507.severity = error # Delivering code in production with debug features activated is security-sensitive -dotnet_diagnostic.S5122.severity = error # Having a permissive Cross-Origin Resource Sharing policy is security-sensitive +dotnet_diagnostic.S4507.severity = none # Delivering code in production with debug features activated is security-sensitive -> no equivalent - debug features in production (partial SES1506) +dotnet_diagnostic.S5122.severity = none # Having a permissive Cross-Origin Resource Sharing policy is security-sensitive -> replaced by SES1501 ############################################# # JetBrains ReSharper / Rider Inspections diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 8f436976..320ac819 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -5,7 +5,7 @@ true - 3.32.0 + 3.33.0 From 8f9208d985a88d43ab18b3fcced958568464ffef Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:23:16 +1000 Subject: [PATCH 13/18] test: add unit tests for Sequencer and ThreadPoolSequencer functionality --- .editorconfig | 87 +++++++++++-------- .../SequencerTests.cs | 53 +++++++++++ .../CurrentThreadSequencerTests.cs | 44 ++++++++++ .../SequencerTests.cs | 53 +++++++++++ .../ThreadPoolSequencerTests.cs | 18 ++++ 5 files changed, 220 insertions(+), 35 deletions(-) create mode 100644 src/tests/ReactiveUI.Primitives.Async.Reactive.Tests/SequencerTests.cs create mode 100644 src/tests/ReactiveUI.Primitives.Reactive.Tests/CurrentThreadSequencerTests.cs create mode 100644 src/tests/ReactiveUI.Primitives.Reactive.Tests/SequencerTests.cs create mode 100644 src/tests/ReactiveUI.Primitives.Reactive.Tests/ThreadPoolSequencerTests.cs diff --git a/.editorconfig b/.editorconfig index 26c74e11..d054d1f4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1331,6 +1331,7 @@ dotnet_diagnostic.SST1316.severity = none # Tuple element names should use the c dotnet_diagnostic.SST1317.severity = none # Asynchronous method names should end with 'Async' — conflicts with this project's Rx-compatibility and naming mechanism dotnet_diagnostic.SST1318.severity = error # Overriding parameter names should match the base declaration dotnet_diagnostic.SST1319.severity = error # An enumeration's type name is not PascalCase +dotnet_diagnostic.SST1320.severity = error # A parameter name matches its method's name # Maintainability dotnet_diagnostic.SST1400.severity = error # An element does not declare an access modifier @@ -1602,6 +1603,7 @@ dotnet_diagnostic.SST2248.severity = error # Two constant comparisons of the sam dotnet_diagnostic.SST2249.severity = error # A literal-format string.Format or literal concatenation can be an interpolated string dotnet_diagnostic.SST2250.severity = error # A bare local assigned once by the next statement can be an initialized declaration dotnet_diagnostic.SST2251.severity = error # A method call names type arguments that inference would supply +dotnet_diagnostic.SST2252.severity = error # A switch statement is nested inside another switch statement dotnet_diagnostic.RCS1040.severity = none # covered by SST1180 # Design @@ -1633,6 +1635,7 @@ dotnet_diagnostic.SST2324.severity = error # A member is declared more accessibl dotnet_diagnostic.SST2325.severity = error # An async method checks an argument after its first await dotnet_diagnostic.SST2326.severity = error # An interface-typed value is narrowed to a concrete implementation, coupling the code to it dotnet_diagnostic.SST2327.severity = error # A type tests its own runtime type against a class instead of dispatching through a virtual member +dotnet_diagnostic.SST2328.severity = error # A raw native pointer handle is exposed instead of a SafeHandle # Correctness dotnet_diagnostic.SST2400.severity = error # Two arguments name each other's parameters and have been transposed @@ -1705,6 +1708,13 @@ dotnet_diagnostic.SST2474.severity = error # A part-creation-policy attribute is dotnet_diagnostic.SST2475.severity = error # An entity's primary key is typed DateTime or DateTimeOffset dotnet_diagnostic.SST2479.severity = error # A loop variable captured by a callback stored beyond the iteration reads its final value dotnet_diagnostic.SST2481.severity = error # A GetHashCode override folds the base identity hash into a value hash +dotnet_diagnostic.SST2484.severity = error # A handle read through DangerousGetHandle is not reference-counted +dotnet_diagnostic.SST2485.severity = error # A NotImplementedException is left in shipped code +dotnet_diagnostic.SST2486.severity = error # An assembly is loaded by path or partial name instead of Assembly.Load +dotnet_diagnostic.SST2487.severity = error # A [ConstructorArgument] does not name a constructor parameter +dotnet_diagnostic.SST2488.severity = error # An exception is logged and rethrown, duplicating the record +dotnet_diagnostic.SST2489.severity = error # A relational comparison is decided by the operand's type rather than its value +dotnet_diagnostic.SST2490.severity = error # Adjacent try statements with identical handling should be merged # Testing dotnet_diagnostic.SST2500.severity = error # A test method contains no assertion and no expected-exception check @@ -1715,9 +1725,12 @@ dotnet_diagnostic.SST2504.severity = error # A test fixture declares and inherit dotnet_diagnostic.SST2505.severity = error # A test method declares parameters but no data source dotnet_diagnostic.SST2506.severity = error # A test method calls Thread.Sleep dotnet_diagnostic.SST2507.severity = error # A test method declares its expected failure with an expected-exception attribute +dotnet_diagnostic.SST2508.severity = error # A fluent assertion is started but never completed +dotnet_diagnostic.SST2509.severity = error # A test method has a shape the runner cannot execute # Logging dotnet_diagnostic.SST2600.severity = error # Application output is written through legacy Trace instead of a structured logger +dotnet_diagnostic.SST2601.severity = error # A logger field or property does not follow the logger naming convention ################### # PerformanceSharp Analyzers (PSH) - severities staged ahead of package adoption; @@ -1834,6 +1847,7 @@ dotnet_diagnostic.PSH1415.severity = error # Hold the concrete type when the con dotnet_diagnostic.PSH1416.severity = error # Cache the serializer options instead of building them per call dotnet_diagnostic.PSH1417.severity = error # Do not compute an expensive argument for an assertion dotnet_diagnostic.PSH1418.severity = error # An HttpClient is constructed on every call +dotnet_diagnostic.PSH1419.severity = error # A time-zone is resolved with a platform-specific id instead of the cross-platform API # ASP.NET Core - inert in this repo (no route handlers or middleware), enabled so the set stays complete dotnet_diagnostic.PSH1500.severity = error # A minimal API handler returns Results instead of TypedResults, boxing the result @@ -1860,6 +1874,7 @@ dotnet_diagnostic.SES1005.severity = error # Compare secret values in constant t dotnet_diagnostic.SES1006.severity = error # A Data Protection key ring is persisted without a ProtectKeysWith call, so keys sit unencrypted at rest dotnet_diagnostic.SES1007.severity = error # A cryptographic primitive is implemented by hand instead of using a vetted platform algorithm dotnet_diagnostic.SES1008.severity = error # An XML signature is verified with the no-key CheckSignature overload, trusting the document's own KeyInfo +dotnet_diagnostic.SES1009.severity = error # A password is stored without a slow, salted key-derivation function # Transport dotnet_diagnostic.SES1102.severity = error # Do not accept any server certificate @@ -1881,6 +1896,7 @@ dotnet_diagnostic.SES1303.severity = error # Regular-expression pattern must not dotnet_diagnostic.SES1304.severity = error # An archive entry name must not build a write path without a containment check dotnet_diagnostic.SES1305.severity = error # Do not build a storage path from an uploaded file name dotnet_diagnostic.SES1306.severity = error # Do not compile or execute non-constant C# via the scripting API +dotnet_diagnostic.SES1310.severity = error # A directory bind is performed without authenticating dotnet_diagnostic.SES1307.severity = error # Path.GetTempFileName creates a predictable, world-readable temporary file dotnet_diagnostic.SES1308.severity = error # A file or directory is created group- or world-writable dotnet_diagnostic.SES1309.severity = error # An XSLT stylesheet is loaded with embedded script enabled @@ -1907,6 +1923,7 @@ dotnet_diagnostic.SES1511.severity = error # The forwarded-headers trust boundar dotnet_diagnostic.SES1512.severity = error # Sensitive framework diagnostics are enabled without a development-environment guard dotnet_diagnostic.SES1513.severity = error # An AuthorizeAsync result is discarded, so the guarded operation runs regardless dotnet_diagnostic.SES1514.severity = error # OpenID Connect protections (PKCE, state, nonce) are disabled +dotnet_diagnostic.SES1515.severity = error # A Content-Security-Policy value disables its own protection # AI trust boundaries dotnet_diagnostic.SES1601.severity = error # An LLM system prompt must be a constant, trusted template @@ -2123,15 +2140,15 @@ dotnet_diagnostic.S3927.severity = none # Serialization event handlers should be dotnet_diagnostic.S3981.severity = none # Collection sizes and array length comparisons should make sense — covered by SST1479 dotnet_diagnostic.S3984.severity = none # Exceptions should not be created without being thrown — covered by SST1480 dotnet_diagnostic.S4143.severity = none # Collection elements should not be replaced unconditionally — covered by SST1487 -dotnet_diagnostic.S4210.severity = none # Windows Forms entry points should be marked with STAThread -> no equivalent - WinForms STAThread on Main (candidate to build) +dotnet_diagnostic.S4210.severity = none # Windows Forms entry points should be marked with STAThread -> off: WinForms entry point; not applicable to this cross-platform library dotnet_diagnostic.S4260.severity = none # "ConstructorArgument" parameters should exist in constructors -> replaced by SST2487 dotnet_diagnostic.S4428.severity = none # "PartCreationPolicyAttribute" should be used with "ExportAttribute" — covered by SST2474 dotnet_diagnostic.S6507.severity = none # Blocks should not be synchronized on local variables — covered by SST1903 dotnet_diagnostic.S6677.severity = none # Message template placeholders should be unique — covered by SST2442 -dotnet_diagnostic.S6797.severity = none # Blazor query parameter type should be supported -> no equivalent - Blazor (framework; N.A.) -dotnet_diagnostic.S6798.severity = none # [JSInvokable] attribute should only be used on public methods -> no equivalent - Blazor (framework; N.A.) -dotnet_diagnostic.S6800.severity = none # Component parameter type should match the route parameter type constraint -> no equivalent - Blazor (framework; N.A.) -dotnet_diagnostic.S6930.severity = none # Backslash should be avoided in route templates -> no equivalent - ASP.NET MVC (framework; N.A.) +dotnet_diagnostic.S6797.severity = none # Blazor query parameter type should be supported -> off: Blazor-specific; no Blazor surface in this library +dotnet_diagnostic.S6798.severity = none # [JSInvokable] attribute should only be used on public methods -> off: Blazor-specific; no Blazor surface in this library +dotnet_diagnostic.S6800.severity = none # Component parameter type should match the route parameter type constraint -> off: Blazor-specific; no Blazor surface in this library +dotnet_diagnostic.S6930.severity = none # Backslash should be avoided in route templates -> off: ASP.NET MVC-specific; no controllers in this library ################### # SonarAnalyzer (Sxxxx) - Minor Bug @@ -2204,7 +2221,7 @@ dotnet_diagnostic.S3875.severity = none # "operator==" should not be overloaded dotnet_diagnostic.S3877.severity = none # Exceptions should not be thrown from unexpected methods — covered by SST1485 dotnet_diagnostic.S4462.severity = none # Calls to "async" methods should not be blocking - covered by PSH1315 dotnet_diagnostic.S6422.severity = none # Calls to "async" methods should not be blocking in Azure Functions — covered by PSH1315 -dotnet_diagnostic.S6424.severity = none # Interfaces for durable entities should satisfy the restrictions -> no equivalent - Durable Entity (framework; N.A.) +dotnet_diagnostic.S6424.severity = none # Interfaces for durable entities should satisfy the restrictions -> off: Durable Entity-specific; not used in this library ################### # SonarAnalyzer (Sxxxx) - Critical Code Smell @@ -2264,7 +2281,7 @@ dotnet_diagnostic.S4487.severity = none # Unread "private" fields should be remo dotnet_diagnostic.S4524.severity = none # "default" clauses should be first or last — covered by SST1219 dotnet_diagnostic.S4635.severity = none # Start index should be used instead of calling Substring — covered by PSH1218 dotnet_diagnostic.S5034.severity = none # "ValueTask" should be consumed correctly — covered by PSH1316 -dotnet_diagnostic.S6967.severity = none # ModelState.IsValid should be called in controller actions -> no equivalent - ASP.NET MVC (framework; N.A.) +dotnet_diagnostic.S6967.severity = none # ModelState.IsValid should be called in controller actions -> off: ASP.NET MVC-specific; no controllers in this library dotnet_diagnostic.S8367.severity = none # Identifiers should not conflict with the C# 14 "field" contextual keyword - the C# 14 compiler reports this: CS9273 (error) for a local named field in an accessor, CS9258 (warning) for a rebinding read dotnet_diagnostic.S8368.severity = none # Identifiers should not conflict with the C# 14 "extension" contextual keyword - the C# 14 compiler reports this as CS9306, and SST1300 already flags the lowercase type name dotnet_diagnostic.S8380.severity = none # Return types named "partial" should be escaped with "@" - the compiler reports this as CS8981, and SST1300 already flags the lowercase type name @@ -2288,7 +2305,7 @@ dotnet_diagnostic.S1118.severity = none # Utility classes should not have public dotnet_diagnostic.S112.severity = none # General or reserved exceptions should never be thrown — covered by SST2409 dotnet_diagnostic.S1121.severity = none # Assignments should not be made from within sub-expressions — covered by SST1187 dotnet_diagnostic.S1123.severity = none # "Obsolete" attributes should include explanations — covered by SST2308 -dotnet_diagnostic.S1134.severity = none # Track uses of "FIXME" tags -> no equivalent - TODO/FIXME tracker (low value) +dotnet_diagnostic.S1134.severity = none # Track uses of "FIXME" tags -> off: TODO comment tracker; not enforced here dotnet_diagnostic.S1144.severity = none # Covered by SST1440 (canonical) dotnet_diagnostic.S1151.severity = none # "switch case" clauses should not have too many lines of code — covered by SST1524 dotnet_diagnostic.S1168.severity = none # Empty arrays and collections should be returned instead of null — covered by SST2306 @@ -2299,7 +2316,7 @@ dotnet_diagnostic.S125.severity = none # Sections of code should not be commente dotnet_diagnostic.S127.severity = none # "for" loop stop conditions should be invariant — covered by SST2465 dotnet_diagnostic.S138.severity = none # Functions should not have too many lines of code — covered by SST1523 dotnet_diagnostic.S1479.severity = none # "switch" statements with many "case" clauses — covered by SST1423 -dotnet_diagnostic.S1607.severity = none # Tests should not be ignored -> no equivalent - ignored-test tracker (low value) +dotnet_diagnostic.S1607.severity = none # Tests should not be ignored -> off: ignored-test tracker; not enforced here dotnet_diagnostic.S1696.severity = none # NullReferenceException should not be caught — covered by SST2401 dotnet_diagnostic.S1854.severity = none # Unused assignments should be removed - DUPLICATE IDE0059 dotnet_diagnostic.S1871.severity = none # Two branches in a conditional structure should not have exactly the same implementation — covered by SST2414 @@ -2318,11 +2335,11 @@ dotnet_diagnostic.S2925.severity = none # "Thread.Sleep" should not be used in t dotnet_diagnostic.S2933.severity = none # Fields that are only assigned in the constructor should be "readonly" - DUPLICATE IDE0044 dotnet_diagnostic.S2971.severity = none # LINQ expressions should be simplified — covered by PSH1101/PSH1102 dotnet_diagnostic.S3010.severity = none # Static fields should not be updated in constructors — covered by SST2402 -dotnet_diagnostic.S3011.severity = none # Reflection should not be used to increase accessibility of classes, methods, or fields -> no equivalent - NonPublic reflection (audit hotspot) +dotnet_diagnostic.S3011.severity = none # Reflection should not be used to increase accessibility of classes, methods, or fields -> off: reflection-audit hotspot; not enforced here dotnet_diagnostic.S3059.severity = none # Types should not have members with visibility set higher than the type's visibility dotnet_diagnostic.S3063.severity = none # "StringBuilder" data should be used — covered by SST2408 dotnet_diagnostic.S3169.severity = none # Multiple "OrderBy" calls should not be used — covered by PSH1108 -dotnet_diagnostic.S3246.severity = none # Generic type parameters should be co/contravariant when possible -> no equivalent - in/out variance suggestion (low precision) +dotnet_diagnostic.S3246.severity = none # Generic type parameters should be co/contravariant when possible -> off: low-precision variance suggestion; not enforced dotnet_diagnostic.S3262.severity = none # "params" should be used on overrides — covered by SST2426 dotnet_diagnostic.S3264.severity = none # Events should be invoked — covered by SST2407 dotnet_diagnostic.S3358.severity = none # Ternary operators should not be nested — covered by SST1147 @@ -2372,10 +2389,10 @@ dotnet_diagnostic.S4457.severity = none # Parameter validation in "async"/"await dotnet_diagnostic.S4545.severity = none # "DebuggerDisplayAttribute" strings should reference existing members — covered by SST2405 dotnet_diagnostic.S4581.severity = none # "new Guid()" should not be used — covered by SST2012 dotnet_diagnostic.S6354.severity = none # Use a testable date/time provider — covered by SST2010 -dotnet_diagnostic.S6419.severity = none # Azure Functions should be stateless -> no equivalent - Azure Functions (framework; N.A. to this library) +dotnet_diagnostic.S6419.severity = none # Azure Functions should be stateless -> off: Azure Functions-specific; not used in this library dotnet_diagnostic.S6420.severity = none # Client instances should not be recreated on each Azure Function invocation — covered by PSH1418 -dotnet_diagnostic.S6421.severity = none # Azure Functions should use Structured Error Handling -> no equivalent - Azure Functions (framework; N.A.) -dotnet_diagnostic.S6423.severity = none # Azure Functions should log all failures -> no equivalent - Azure Functions (framework; N.A.) +dotnet_diagnostic.S6421.severity = none # Azure Functions should use Structured Error Handling -> off: Azure Functions-specific; not used in this library +dotnet_diagnostic.S6423.severity = none # Azure Functions should log all failures -> off: Azure Functions-specific; not used in this library dotnet_diagnostic.S6561.severity = none # Avoid using "DateTime.Now" for benchmarking or timing operations — covered by PSH1408 dotnet_diagnostic.S6562.severity = none # Covered by SST1451 (canonical) dotnet_diagnostic.S6563.severity = none # Use UTC when recording DateTime instants — covered by SST2011 @@ -2383,18 +2400,18 @@ dotnet_diagnostic.S6566.severity = none # Use "DateTimeOffset" instead of "DateT dotnet_diagnostic.S6575.severity = none # Use "TimeZoneInfo.FindSystemTimeZoneById" without converting the timezones with "TimezoneConverter" -> replaced by PSH1419 dotnet_diagnostic.S6580.severity = none # Use a format provider when parsing date and time - DUPLICATE CA1305 dotnet_diagnostic.S6673.severity = none # Log message template placeholders should be in the right order — covered by SST2440 -dotnet_diagnostic.S6802.severity = none # Using lambda expressions in loops should be avoided in Blazor markup section -> no equivalent - Blazor render loop (framework; N.A.) -dotnet_diagnostic.S6803.severity = none # Parameters with SupplyParameterFromQuery attribute should be used only in routable components -> no equivalent - Blazor (framework; N.A.) -dotnet_diagnostic.S6931.severity = none # ASP.NET controller actions should not have a route template starting with "/" -> no equivalent - ASP.NET MVC (framework; N.A.) -dotnet_diagnostic.S6932.severity = none # Use model binding instead of reading raw request data -> no equivalent - ASP.NET MVC (framework; N.A.) -dotnet_diagnostic.S6934.severity = none # A Route attribute should be added to the controller when a route template is specified at the action level -> no equivalent - ASP.NET MVC (framework; N.A.) -dotnet_diagnostic.S6960.severity = none # Controllers should not have mixed responsibilities -> no equivalent - ASP.NET MVC (framework; N.A.) -dotnet_diagnostic.S6961.severity = none # API Controllers should derive from ControllerBase instead of Controller -> no equivalent - ASP.NET MVC (framework; N.A.) +dotnet_diagnostic.S6802.severity = none # Using lambda expressions in loops should be avoided in Blazor markup section -> off: Blazor-specific; no Blazor surface in this library +dotnet_diagnostic.S6803.severity = none # Parameters with SupplyParameterFromQuery attribute should be used only in routable components -> off: Blazor-specific; no Blazor surface in this library +dotnet_diagnostic.S6931.severity = none # ASP.NET controller actions should not have a route template starting with "/" -> off: ASP.NET MVC-specific; no controllers in this library +dotnet_diagnostic.S6932.severity = none # Use model binding instead of reading raw request data -> off: ASP.NET MVC-specific; no controllers in this library +dotnet_diagnostic.S6934.severity = none # A Route attribute should be added to the controller when a route template is specified at the action level -> off: ASP.NET MVC-specific; no controllers in this library +dotnet_diagnostic.S6960.severity = none # Controllers should not have mixed responsibilities -> off: ASP.NET MVC-specific; no controllers in this library +dotnet_diagnostic.S6961.severity = none # API Controllers should derive from ControllerBase instead of Controller -> off: ASP.NET MVC-specific; no controllers in this library dotnet_diagnostic.S6962.severity = none # You should pool HTTP connections with HttpClientFactory — covered by PSH1418 -dotnet_diagnostic.S6964.severity = none # Value type property used as input in a controller action should be nullable, required or annotated with the JsonRequiredAttribute to avoid under-posting. -> no equivalent - ASP.NET MVC (framework; N.A.) -dotnet_diagnostic.S6965.severity = none # REST API actions should be annotated with an HTTP verb attribute -> no equivalent - ASP.NET MVC (framework; N.A.) +dotnet_diagnostic.S6964.severity = none # Value type property used as input in a controller action should be nullable, required or annotated with the JsonRequiredAttribute to avoid under-posting. -> off: ASP.NET MVC-specific; no controllers in this library +dotnet_diagnostic.S6965.severity = none # REST API actions should be annotated with an HTTP verb attribute -> off: ASP.NET MVC-specific; no controllers in this library dotnet_diagnostic.S6966.severity = none # Awaitable method should be used — covered by PSH1313 -dotnet_diagnostic.S6968.severity = none # Actions that return a value should be annotated with ProducesResponseTypeAttribute containing the return type -> no equivalent - ASP.NET MVC (framework; N.A.) +dotnet_diagnostic.S6968.severity = none # Actions that return a value should be annotated with ProducesResponseTypeAttribute containing the return type -> off: ASP.NET MVC-specific; no controllers in this library dotnet_diagnostic.S881.severity = none # Increment (++) and decrement (--) operators should not be used in a method call or mixed with other operators in an expression — covered by SST2015 dotnet_diagnostic.S907.severity = none # "goto" statement should not be used — covered by SST2014 @@ -2450,7 +2467,7 @@ dotnet_diagnostic.S3234.severity = none # Covered by PSH1008 (canonical) dotnet_diagnostic.S3235.severity = none # Redundant parentheses should not be used — covered by SST1459 dotnet_diagnostic.S3236.severity = none # Covered by SST1448 (canonical) dotnet_diagnostic.S3240.severity = none # The simplest possible condition syntax should be used - duplicate of SST1198 -dotnet_diagnostic.S3241.severity = none # Methods should not return values that are never used -> no equivalent - unused return value (needs whole-program) +dotnet_diagnostic.S3241.severity = none # Methods should not return values that are never used -> off: needs whole-program analysis; not enforced here dotnet_diagnostic.S3242.severity = none # Method parameters should be declared with base types dotnet_diagnostic.S3247.severity = none # Duplicate casts should not be made — covered by SST1175 dotnet_diagnostic.S3251.severity = none # Implementations should be provided for "partial" methods — covered by SST2468 @@ -2489,7 +2506,7 @@ dotnet_diagnostic.S4026.severity = none # Assemblies should be marked with "Neut dotnet_diagnostic.S4027.severity = none # Exceptions should provide standard constructors — covered by SST1488 dotnet_diagnostic.S4040.severity = none # Strings should be normalized to uppercase - DUPLICATE CA1308 dotnet_diagnostic.S4041.severity = none # Type names should not match namespaces - DUPLICATE CA1724 -dotnet_diagnostic.S4047.severity = none # Generics should be used when appropriate -> no equivalent - prefer generics (fuzzy) +dotnet_diagnostic.S4047.severity = none # Generics should be used when appropriate -> off: fuzzy prefer-generics suggestion; not enforced dotnet_diagnostic.S4049.severity = none # Properties should be preferred - DUPLICATE CA1024 dotnet_diagnostic.S4052.severity = none # Types should not extend outdated base types -> replaced by obsolete dotnet_diagnostic.S4056.severity = none # Overloads with a "CultureInfo" or an "IFormatProvider" parameter should be used - DUPLICATE CA1305 @@ -2518,13 +2535,13 @@ dotnet_diagnostic.S6612.severity = none # The lambda parameter should be used in dotnet_diagnostic.S6613.severity = none # "First" and "Last" properties of "LinkedList" should be used instead of the "First()" and "Last()" extension methods — covered by PSH1124 dotnet_diagnostic.S6617.severity = none # Covered by PSH1111 (canonical) dotnet_diagnostic.S6618.severity = none # "string.Create" should be used instead of "FormattableString" — covered by PSH1209 -dotnet_diagnostic.S6664.severity = none # The code block contains too many logging calls -> no equivalent - too many logging calls (fuzzy) +dotnet_diagnostic.S6664.severity = none # The code block contains too many logging calls -> off: fuzzy too-many-logging-calls metric; not enforced dotnet_diagnostic.S6667.severity = none # Logging in a catch clause should pass the caught exception as a parameter. — covered by SST2438 dotnet_diagnostic.S6668.severity = none # Logging arguments should be passed to the correct parameter — covered by SST2439 dotnet_diagnostic.S6669.severity = none # Logger field or property name should comply with a naming convention -> replaced by SST2601 dotnet_diagnostic.S6670.severity = none # "Trace.Write" and "Trace.WriteLine" should not be used — covered by SST2600 dotnet_diagnostic.S6672.severity = none # Generic logger injection should match enclosing type — covered by SST2443 -dotnet_diagnostic.S6675.severity = none # "Trace.WriteLineIf" should not be used with "TraceSwitch" levels -> no equivalent - TraceSwitch misuse (niche) +dotnet_diagnostic.S6675.severity = none # "Trace.WriteLineIf" should not be used with "TraceSwitch" levels -> off: niche TraceSwitch misuse; not enforced here dotnet_diagnostic.S6678.severity = none # Use PascalCase for named placeholders -> replaced by CA1727 dotnet_diagnostic.S818.severity = none # Literal suffixes should be upper case — covered by SST2244 @@ -2532,7 +2549,7 @@ dotnet_diagnostic.S818.severity = none # Literal suffixes should be upper case # SonarAnalyzer (Sxxxx) - Info Code Smell ################### dotnet_diagnostic.S1133.severity = none # Deprecated code should be removed — covered by SST2310 -dotnet_diagnostic.S1135.severity = none # Track uses of "TODO" tags -> no equivalent - TODO/FIXME tracker (low value) +dotnet_diagnostic.S1135.severity = none # Track uses of "TODO" tags -> off: FIXME comment tracker; not enforced here dotnet_diagnostic.S1309.severity = none # Track uses of in-source issue suppressions ################### @@ -2555,28 +2572,28 @@ dotnet_diagnostic.S2245.severity = none # Using pseudorandom number generators ( dotnet_diagnostic.S2257.severity = none # Using non-standard cryptographic algorithms is security-sensitive -> replaced by SES1007 dotnet_diagnostic.S4502.severity = none # Disabling CSRF protections is security-sensitive -> replaced by in-box ASP.NET antiforgery analyzer dotnet_diagnostic.S4790.severity = none # Using weak hashing algorithms is security-sensitive -> replaced by CA5350/CA5351 -dotnet_diagnostic.S4792.severity = none # Configuring loggers is security-sensitive -> no equivalent - logger configuration hotspot (audit) -dotnet_diagnostic.S5042.severity = none # Expanding archive files without controlling resource consumption is security-sensitive -> no equivalent - unbounded decompression (needs taint) +dotnet_diagnostic.S4792.severity = none # Configuring loggers is security-sensitive -> off: logging-configuration audit hotspot; not enforced here +dotnet_diagnostic.S5042.severity = none # Expanding archive files without controlling resource consumption is security-sensitive -> off: unbounded decompression; needs taint analysis, out of scope dotnet_diagnostic.S5332.severity = none # Using clear-text protocols is security-sensitive -> replaced by SES1106 dotnet_diagnostic.S5443.severity = none # Using publicly writable directories is security-sensitive -> replaced by SES1308 ################### # SonarAnalyzer (Sxxxx) - Major Security Hotspot ################### -dotnet_diagnostic.S1313.severity = none # Using hardcoded IP addresses is security-sensitive -> no equivalent - hardcoded IP literal (noisy) +dotnet_diagnostic.S1313.severity = none # Using hardcoded IP addresses is security-sensitive -> off: hardcoded-IP heuristic; too noisy to enforce dotnet_diagnostic.S2077.severity = none # Formatting SQL queries is security-sensitive -> replaced by CA2100 dotnet_diagnostic.S5693.severity = none # Allowing requests with excessive content length is security-sensitive -> replaced by SES1505 dotnet_diagnostic.S5753.severity = none # Disabling ASP.NET "Request Validation" feature is security-sensitive -> replaced by obsolete (legacy ASP.NET request validation) -dotnet_diagnostic.S5766.severity = none # Creating Serializable objects without data validation checks is security-sensitive -> no equivalent - Serializable deserialization (obsolete BinaryFormatter) +dotnet_diagnostic.S5766.severity = none # Creating Serializable objects without data validation checks is security-sensitive -> off: legacy BinaryFormatter deserialization; obsolete path, not used here dotnet_diagnostic.S6444.severity = none # Not specifying a timeout for regular expressions is security-sensitive -> replaced by SES1509 -dotnet_diagnostic.S6640.severity = none # Using unsafe code blocks is security-sensitive -> no equivalent - unsafe code blocks (audit hotspot) +dotnet_diagnostic.S6640.severity = none # Using unsafe code blocks is security-sensitive -> off: unsafe-code audit; not enforced here ################### # SonarAnalyzer (Sxxxx) - Minor Security Hotspot ################### dotnet_diagnostic.S2092.severity = none # Creating cookies without the "secure" flag is security-sensitive -> replaced by CA5382 dotnet_diagnostic.S3330.severity = none # Creating cookies without the "HttpOnly" flag is security-sensitive -> replaced by CA5383 -dotnet_diagnostic.S4507.severity = none # Delivering code in production with debug features activated is security-sensitive -> no equivalent - debug features in production (partial SES1506) +dotnet_diagnostic.S4507.severity = none # Delivering code in production with debug features activated is security-sensitive -> off: debug features in production; partly covered by SES1506, rest not enforced dotnet_diagnostic.S5122.severity = none # Having a permissive Cross-Origin Resource Sharing policy is security-sensitive -> replaced by SES1501 ############################################# diff --git a/src/tests/ReactiveUI.Primitives.Async.Reactive.Tests/SequencerTests.cs b/src/tests/ReactiveUI.Primitives.Async.Reactive.Tests/SequencerTests.cs new file mode 100644 index 00000000..df707d37 --- /dev/null +++ b/src/tests/ReactiveUI.Primitives.Async.Reactive.Tests/SequencerTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2019-2026 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI.Primitives.Reactive.Concurrency; + +namespace ReactiveUI.Primitives.Async.Reactive.Tests; + +/// +/// Verifies the async leaf's maps each built-in sequencer onto the matching +/// System.Reactive scheduler singleton, and that time normalization forwards to System.Reactive. +/// +public class SequencerTests +{ + /// A negative interval, which normalization clamps to zero. + private static readonly TimeSpan NegativeInterval = TimeSpan.FromSeconds(-5); + + /// A positive interval, which normalization leaves untouched. + private static readonly TimeSpan PositiveInterval = TimeSpan.FromSeconds(5); + + /// Verifies the current-thread sequencer is System.Reactive's current-thread scheduler. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenCurrentThreadRead_ThenItIsTheCurrentThreadScheduler() => + await Assert.That(Sequencer.CurrentThread) + .IsSameReferenceAs(System.Reactive.Concurrency.CurrentThreadScheduler.Instance); + + /// Verifies the immediate sequencer is System.Reactive's immediate scheduler. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenImmediateRead_ThenItIsTheImmediateScheduler() => + await Assert.That(Sequencer.Immediate) + .IsSameReferenceAs(System.Reactive.Concurrency.ImmediateScheduler.Instance); + + /// Verifies the default sequencer is System.Reactive's task-pool scheduler. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenDefaultRead_ThenItIsTheTaskPoolScheduler() => + await Assert.That(Sequencer.Default) + .IsSameReferenceAs(System.Reactive.Concurrency.TaskPoolScheduler.Default); + + /// Verifies a negative interval normalizes to zero. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenNegativeIntervalNormalized_ThenItBecomesZero() => + await Assert.That(Sequencer.Normalize(NegativeInterval)).IsEqualTo(TimeSpan.Zero); + + /// Verifies a positive interval survives normalization unchanged. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenPositiveIntervalNormalized_ThenItIsUnchanged() => + await Assert.That(Sequencer.Normalize(PositiveInterval)).IsEqualTo(PositiveInterval); +} diff --git a/src/tests/ReactiveUI.Primitives.Reactive.Tests/CurrentThreadSequencerTests.cs b/src/tests/ReactiveUI.Primitives.Reactive.Tests/CurrentThreadSequencerTests.cs new file mode 100644 index 00000000..45e6af31 --- /dev/null +++ b/src/tests/ReactiveUI.Primitives.Reactive.Tests/CurrentThreadSequencerTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2019-2026 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI.Primitives.Reactive.Concurrency; + +namespace ReactiveUI.Primitives.Reactive.Tests; + +/// +/// Verifies forwards to System.Reactive's current-thread scheduler, +/// including the trampoline flag callers use to decide whether they must schedule. +/// +public class CurrentThreadSequencerTests +{ + /// Verifies the exposed instance is System.Reactive's current-thread scheduler singleton. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenInstanceRead_ThenItIsTheCurrentThreadScheduler() => + await Assert.That(CurrentThreadSequencer.Instance) + .IsSameReferenceAs(System.Reactive.Concurrency.CurrentThreadScheduler.Instance); + + /// + /// Verifies the schedule-required flag mirrors System.Reactive. Outside a trampoline both are + /// ; the assertion compares the two rather than a literal so the test states the + /// forwarding contract rather than a snapshot of the runtime's state. + /// + /// A representing the asynchronous test operation. + [Test] + public async Task WhenScheduleRequiredRead_ThenItMirrorsTheCurrentThreadScheduler() => + await Assert.That(CurrentThreadSequencer.IsScheduleRequired) + .IsEqualTo(System.Reactive.Concurrency.CurrentThreadScheduler.IsScheduleRequired); + + /// Verifies the flag reports false while running inside the trampoline. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenInsideTrampoline_ThenScheduleIsNotRequired() + { + var observedInsideTrampoline = true; + + CurrentThreadSequencer.Instance.Schedule(() => observedInsideTrampoline = CurrentThreadSequencer.IsScheduleRequired); + + await Assert.That(observedInsideTrampoline).IsFalse(); + } +} diff --git a/src/tests/ReactiveUI.Primitives.Reactive.Tests/SequencerTests.cs b/src/tests/ReactiveUI.Primitives.Reactive.Tests/SequencerTests.cs new file mode 100644 index 00000000..afaacbfd --- /dev/null +++ b/src/tests/ReactiveUI.Primitives.Reactive.Tests/SequencerTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2019-2026 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI.Primitives.Reactive.Concurrency; + +namespace ReactiveUI.Primitives.Reactive.Tests; + +/// +/// Verifies maps each built-in sequencer onto the matching System.Reactive +/// scheduler singleton, and that time normalization forwards to System.Reactive. +/// +public class SequencerTests +{ + /// A negative interval, which normalization clamps to zero. + private static readonly TimeSpan NegativeInterval = TimeSpan.FromSeconds(-5); + + /// A positive interval, which normalization leaves untouched. + private static readonly TimeSpan PositiveInterval = TimeSpan.FromSeconds(5); + + /// Verifies the current-thread sequencer is System.Reactive's current-thread scheduler. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenCurrentThreadRead_ThenItIsTheCurrentThreadScheduler() => + await Assert.That(Sequencer.CurrentThread) + .IsSameReferenceAs(System.Reactive.Concurrency.CurrentThreadScheduler.Instance); + + /// Verifies the immediate sequencer is System.Reactive's immediate scheduler. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenImmediateRead_ThenItIsTheImmediateScheduler() => + await Assert.That(Sequencer.Immediate) + .IsSameReferenceAs(System.Reactive.Concurrency.ImmediateScheduler.Instance); + + /// Verifies the default sequencer is System.Reactive's task-pool scheduler. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenDefaultRead_ThenItIsTheTaskPoolScheduler() => + await Assert.That(Sequencer.Default) + .IsSameReferenceAs(System.Reactive.Concurrency.TaskPoolScheduler.Default); + + /// Verifies a negative interval normalizes to zero. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenNegativeIntervalNormalized_ThenItBecomesZero() => + await Assert.That(Sequencer.Normalize(NegativeInterval)).IsEqualTo(TimeSpan.Zero); + + /// Verifies a positive interval survives normalization unchanged. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenPositiveIntervalNormalized_ThenItIsUnchanged() => + await Assert.That(Sequencer.Normalize(PositiveInterval)).IsEqualTo(PositiveInterval); +} diff --git a/src/tests/ReactiveUI.Primitives.Reactive.Tests/ThreadPoolSequencerTests.cs b/src/tests/ReactiveUI.Primitives.Reactive.Tests/ThreadPoolSequencerTests.cs new file mode 100644 index 00000000..1c39a7a6 --- /dev/null +++ b/src/tests/ReactiveUI.Primitives.Reactive.Tests/ThreadPoolSequencerTests.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI.Primitives.Reactive.Concurrency; + +namespace ReactiveUI.Primitives.Reactive.Tests; + +/// Verifies forwards to System.Reactive's thread-pool scheduler. +public class ThreadPoolSequencerTests +{ + /// Verifies the exposed instance is System.Reactive's thread-pool scheduler singleton. + /// A representing the asynchronous test operation. + [Test] + public async Task WhenInstanceRead_ThenItIsTheThreadPoolScheduler() => + await Assert.That(ThreadPoolSequencer.Instance) + .IsSameReferenceAs(System.Reactive.Concurrency.ThreadPoolScheduler.Instance); +} From 84d95927277efa3e900da6860e430e70def45052 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:30:12 +1000 Subject: [PATCH 14/18] build: adjust severity levels for specific analyzers in .editorconfig --- .editorconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index d054d1f4..6428c8c9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1633,7 +1633,7 @@ dotnet_diagnostic.SST2322.severity = error # A non-private readonly field holds dotnet_diagnostic.SST2323.severity = error # A stateless abstract class declaring only public abstract members should be an interface dotnet_diagnostic.SST2324.severity = error # A member is declared more accessible than its containing type dotnet_diagnostic.SST2325.severity = error # An async method checks an argument after its first await -dotnet_diagnostic.SST2326.severity = error # An interface-typed value is narrowed to a concrete implementation, coupling the code to it +dotnet_diagnostic.SST2326.severity = none # An interface-typed value is narrowed to a concrete implementation — this is a high-performance core library, not strictly SOLID code, and narrowing to a known runtime type is a normal fast-path technique here: foreach over IEnumerable boxes List's struct enumerator (40 bytes per call, measured) where the indexed loop behind the type test allocates nothing dotnet_diagnostic.SST2327.severity = error # A type tests its own runtime type against a class instead of dispatching through a virtual member dotnet_diagnostic.SST2328.severity = error # A raw native pointer handle is exposed instead of a SafeHandle @@ -2255,7 +2255,7 @@ dotnet_diagnostic.S2479.severity = none # Whitespace and control characters in s dotnet_diagnostic.S2692.severity = none # "IndexOf" checks should not be for positive numbers — covered by SST2420 dotnet_diagnostic.S2696.severity = none # Instance members should not write to "static" fields -> replaced by SST2402 dotnet_diagnostic.S2701.severity = none # Literal boolean values should not be used in assertions — covered by SST2503 -dotnet_diagnostic.S3215.severity = none # "interface" instances should not be cast to concrete types -> replaced by SST2326 +dotnet_diagnostic.S3215.severity = none # "interface" instances should not be cast to concrete types -> deliberately unenforced, same reason as SST2326 dotnet_diagnostic.S3216.severity = none # "ConfigureAwait(false)" should be used -> replaced by CA2007 dotnet_diagnostic.S3217.severity = none # "Explicit" conversions of "foreach" loops should not be used — covered by SST2225 dotnet_diagnostic.S3218.severity = none # Inner class members should not shadow outer class "static" or type members — covered by SST1484 From 42e62db4e0c437a6ee856d33e9f7916424cb0b13 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:46:01 +1000 Subject: [PATCH 15/18] refactor: remove unnecessary SuppressMessage attributes and improve code clarity in PooledDelaySource and WasmScheduler test: update CurrentThreadSequencerTests to use StrongBox for state management --- .../Advanced/PooledDelaySource.cs | 8 -- .../Concurrency/WasmScheduler.cs | 91 +++++++++++-------- .../CurrentThreadSequencerTests.cs | 14 ++- 3 files changed, 65 insertions(+), 48 deletions(-) diff --git a/src/ReactiveUI.Primitives.Async.Core/Advanced/PooledDelaySource.cs b/src/ReactiveUI.Primitives.Async.Core/Advanced/PooledDelaySource.cs index a2784516..9c2f2f31 100644 --- a/src/ReactiveUI.Primitives.Async.Core/Advanced/PooledDelaySource.cs +++ b/src/ReactiveUI.Primitives.Async.Core/Advanced/PooledDelaySource.cs @@ -40,10 +40,6 @@ public sealed class PooledDelaySource : IValueTaskSource /// Concurrent delays from different threads each get their own cached slot. /// [ThreadStatic] - [SuppressMessage( - "Critical Code Smell", - "S2696:Instance members should not write to \"static\" fields", - Justification = "Thread-static slot is intentional — each thread caches its own instance.")] private static PooledDelaySource? _threadCached; /// Backing source. Continuations run asynchronously so awaiters never re-enter the timer / cancel path. @@ -175,10 +171,6 @@ private void OnCancelled(CancellationToken cancellationToken) } /// Resets per-call state and caches the instance in the per-thread slot. - [SuppressMessage( - "Critical Code Smell", - "S2696:Instance members should not write to \"static\" fields", - Justification = "Thread-static slot is intentional — each thread caches its own instance.")] private void ReturnToPool() { _cancellationRegistration.Dispose(); diff --git a/src/ReactiveUI.Primitives.Reactive/Concurrency/WasmScheduler.cs b/src/ReactiveUI.Primitives.Reactive/Concurrency/WasmScheduler.cs index 840940e7..6fa9d2dc 100644 --- a/src/ReactiveUI.Primitives.Reactive/Concurrency/WasmScheduler.cs +++ b/src/ReactiveUI.Primitives.Reactive/Concurrency/WasmScheduler.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for full license information. using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Reactive.Concurrency; using Timer = System.Threading.Timer; @@ -211,32 +212,36 @@ private void ReleaseReady() } } - /// Arms a single drain if none is in flight, otherwise flags the running drain to loop again. + /// + /// Arms a single drain if none is in flight, otherwise flags the running drain to loop again. + /// + /// The whole body is the claim protocol: it spins only while a compare-exchange + /// loses to a concurrent claim, and exits early only when a concurrent drain empties the queue between the + /// caller's enqueue and this read. Neither path is reachable without a second thread interleaving, so the + /// shell carries the coverage exclusion; the work it schedules lives in ArmDrain, which is covered. + /// + /// + [ExcludeFromCodeCoverage] private void PostDrain() { - while (true) + while (Volatile.Read(ref _readyCount) != 0) { - if (Volatile.Read(ref _readyCount) == 0) - { - return; - } - var state = Volatile.Read(ref _drainState); - if (state == DrainIdle) + if (state != DrainIdle) { - // Become the sole drainer, then yield a batch to the event loop. - if (Interlocked.CompareExchange(ref _drainState, DrainRunning, DrainIdle) != DrainIdle) + // A drain is already running; flag that more work arrived so it drains again. + if (Interlocked.CompareExchange(ref _drainState, DrainRunningPending, state) == state) { - continue; + return; } - ArmDrain(); - return; + continue; } - // A drain is already running; flag that more work arrived so it drains again. - if (Interlocked.CompareExchange(ref _drainState, DrainRunningPending, state) == state) + // Become the sole drainer, then yield a batch to the event loop. + if (Interlocked.CompareExchange(ref _drainState, DrainRunning, DrainIdle) == DrainIdle) { + ArmDrain(); return; } } @@ -257,36 +262,45 @@ private void ArmDrain() _ = _drainTimer.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan); } - /// Runs event-loop batches for the single in-flight drain until no more work is queued. + /// + /// Runs event-loop batches for the single in-flight drain until no more work is queued. + /// + /// This is a thin batching shell around . It repeats a pass only when a concurrent + /// flagged more work mid-pass, which needs a second thread to interleave, so the shell + /// carries the coverage exclusion and the per-item work lives in the method it calls. + /// + /// + [ExcludeFromCodeCoverage] private void RunDrain() { - while (true) + do { // Claim this pass; a concurrent PostDrain that observes DrainRunning will bump it to DrainRunningPending. Volatile.Write(ref _drainState, DrainRunning); + RunReadyBatch(); - for (var remaining = Volatile.Read(ref _readyCount); remaining > 0; remaining--) - { - if (!_ready.TryDequeue(out var item)) - { - break; - } + // Finish only when no work was flagged during this pass. + } + while (Interlocked.CompareExchange(ref _drainState, DrainIdle, DrainRunning) != DrainRunning); - _ = Interlocked.Decrement(ref _readyCount); - item.Run(); - } + if (Volatile.Read(ref _readyCount) == 0) + { + return; + } - // Finish only when no work was flagged during this pass. - if (Interlocked.CompareExchange(ref _drainState, DrainIdle, DrainRunning) == DrainRunning) - { - // Cover the narrow window where an item was enqueued but its PostDrain has not run yet. - if (Volatile.Read(ref _readyCount) != 0) - { - PostDrain(); - } + // Cover the narrow window where an item was enqueued but its PostDrain has not run yet. + PostDrain(); + } - return; - } + /// Runs one batch: every item the ready count promised, stopping early if a concurrent drain took one first. + private void RunReadyBatch() + { + for (var remaining = Volatile.Read(ref _readyCount); + remaining > 0 && _ready.TryDequeue(out var item); + remaining--) + { + _ = Interlocked.Decrement(ref _readyCount); + item.Run(); } } @@ -381,7 +395,10 @@ public void Dispose() } _isDisposed = true; - _timer?.Dispose(); + + // Start is the only construction path and always assigns the timer before returning, and a second + // Dispose exits at the flag above, so the timer is always present on the one pass that reaches here. + _timer!.Dispose(); _timer = null; _state = default!; } diff --git a/src/tests/ReactiveUI.Primitives.Reactive.Tests/CurrentThreadSequencerTests.cs b/src/tests/ReactiveUI.Primitives.Reactive.Tests/CurrentThreadSequencerTests.cs index 45e6af31..c6dd36ac 100644 --- a/src/tests/ReactiveUI.Primitives.Reactive.Tests/CurrentThreadSequencerTests.cs +++ b/src/tests/ReactiveUI.Primitives.Reactive.Tests/CurrentThreadSequencerTests.cs @@ -2,6 +2,8 @@ // ReactiveUI Association Incorporated licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using System.Reactive.Disposables; +using System.Runtime.CompilerServices; using ReactiveUI.Primitives.Reactive.Concurrency; namespace ReactiveUI.Primitives.Reactive.Tests; @@ -35,10 +37,16 @@ await Assert.That(CurrentThreadSequencer.IsScheduleRequired) [Test] public async Task WhenInsideTrampoline_ThenScheduleIsNotRequired() { - var observedInsideTrampoline = true; + StrongBox observedInsideTrampoline = new(true); - CurrentThreadSequencer.Instance.Schedule(() => observedInsideTrampoline = CurrentThreadSequencer.IsScheduleRequired); + _ = CurrentThreadSequencer.Instance.Schedule( + observedInsideTrampoline, + static (_, state) => + { + state.Value = CurrentThreadSequencer.IsScheduleRequired; + return Disposable.Empty; + }); - await Assert.That(observedInsideTrampoline).IsFalse(); + await Assert.That(observedInsideTrampoline.Value).IsFalse(); } } From c6e4f483d4095f1801d824f093100596dfceba91 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:47:27 +1000 Subject: [PATCH 16/18] refactor: simplify summary documentation for SequencerTests class --- .../SequencerTests.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/tests/ReactiveUI.Primitives.Async.Reactive.Tests/SequencerTests.cs b/src/tests/ReactiveUI.Primitives.Async.Reactive.Tests/SequencerTests.cs index df707d37..9debc751 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Reactive.Tests/SequencerTests.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Reactive.Tests/SequencerTests.cs @@ -6,10 +6,7 @@ namespace ReactiveUI.Primitives.Async.Reactive.Tests; -/// -/// Verifies the async leaf's maps each built-in sequencer onto the matching -/// System.Reactive scheduler singleton, and that time normalization forwards to System.Reactive. -/// +/// Verifies the async leaf's sequencers map onto the matching System.Reactive scheduler singletons. public class SequencerTests { /// A negative interval, which normalization clamps to zero. From aa479bd3d67073c77cb2440621a610b49ab36c0f Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:48:14 +1000 Subject: [PATCH 17/18] refactor: simplify summary documentation for SequencerTests class --- .../SequencerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/ReactiveUI.Primitives.Async.Reactive.Tests/SequencerTests.cs b/src/tests/ReactiveUI.Primitives.Async.Reactive.Tests/SequencerTests.cs index 9debc751..a5ebfc8b 100644 --- a/src/tests/ReactiveUI.Primitives.Async.Reactive.Tests/SequencerTests.cs +++ b/src/tests/ReactiveUI.Primitives.Async.Reactive.Tests/SequencerTests.cs @@ -6,7 +6,7 @@ namespace ReactiveUI.Primitives.Async.Reactive.Tests; -/// Verifies the async leaf's sequencers map onto the matching System.Reactive scheduler singletons. +/// Verifies sequencers. public class SequencerTests { /// A negative interval, which normalization clamps to zero. From 3637e2ab6b2da838e7a3175cd90d1a00db7f65f4 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Tue, 21 Jul 2026 01:11:41 +1000 Subject: [PATCH 18/18] test: stop DelayUntil racing the wall clock DelayUntilClampsElapsedDueTimestampsToZero asserted a still-pending delay against a 100ms due time captured before the preceding assertion ran. A loaded runner can spend longer than that inside the first assertion, so the deadline had already passed and DelayUntil clamped to zero. Assert against a due time no scheduling pause can elapse instead. --- .../DispatchSequencerBaseTests.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/tests/ReactiveUI.Primitives.Tests/DispatchSequencerBaseTests.cs b/src/tests/ReactiveUI.Primitives.Tests/DispatchSequencerBaseTests.cs index 9a0c5a98..46d1207c 100644 --- a/src/tests/ReactiveUI.Primitives.Tests/DispatchSequencerBaseTests.cs +++ b/src/tests/ReactiveUI.Primitives.Tests/DispatchSequencerBaseTests.cs @@ -53,6 +53,14 @@ public sealed class DispatchSequencerBaseTests /// How far ahead delayed work is scheduled. private static readonly TimeSpan DelayedDueTime = TimeSpan.FromMilliseconds(100); + /// + /// A due time far enough out that no scheduling pause between capturing the timestamp and reading the + /// remaining delay can elapse it. Asserting a still-pending delay against a short due time races the wall + /// clock: a loaded runner can spend longer than the due time inside the preceding assertion, leaving nothing + /// to wait for and clamping the result to zero. + /// + private static readonly TimeSpan UnreachableDueTime = TimeSpan.FromHours(1); + /// How long a test watches for delayed work that must never reach the dispatcher. private static readonly TimeSpan CancelObservationWindow = TimeSpan.FromMilliseconds(400); @@ -137,7 +145,7 @@ public async Task DelayUntilClampsElapsedDueTimestampsToZero() var now = DispatchSequencerState.Timestamp; await Assert.That(DispatchSequencerState.DelayUntil(now - ElapsedTimestampOffset)).IsEqualTo(TimeSpan.Zero); - await Assert.That(DispatchSequencerState.DelayUntil(Sequencer.AddTimestamp(now, DelayedDueTime)) > TimeSpan.Zero) + await Assert.That(DispatchSequencerState.DelayUntil(Sequencer.AddTimestamp(now, UnreachableDueTime)) > TimeSpan.Zero) .IsTrue(); }