diff --git a/Source/CodeAnalysis/Cratis.Architecture.CodeAnalysis.csproj b/Source/CodeAnalysis/Cratis.Architecture.CodeAnalysis.csproj
index c4863ad..e855097 100644
--- a/Source/CodeAnalysis/Cratis.Architecture.CodeAnalysis.csproj
+++ b/Source/CodeAnalysis/Cratis.Architecture.CodeAnalysis.csproj
@@ -4,10 +4,18 @@
Cratis.Architecture.CodeAnalysis
Roslyn analyzers enforcing Cratis architecture principles.
true
- net10.0
-
true
- $(NoWarn);RS1038;RS1041;RCS1093
+ true
+ false
+ false
+ false
+ true
+ true
+
+ netstandard2.0
+
+ latest
+ $(NoWarn);RS1038;RS1041;RCS1093;NU5128;NU1507
false
false
@@ -24,4 +32,8 @@
+
+
+
+
diff --git a/Source/CodeAnalysis/RangeAndIndexPolyfill.cs b/Source/CodeAnalysis/RangeAndIndexPolyfill.cs
new file mode 100644
index 0000000..15a31ff
--- /dev/null
+++ b/Source/CodeAnalysis/RangeAndIndexPolyfill.cs
@@ -0,0 +1,178 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// The analyzer targets netstandard2.0 so it loads in every compiler host. netstandard2.0 does not
+// define System.Index and System.Range, which the C# range and index operators require. These
+// minimal polyfills enable the language feature without changing behavior.
+#pragma warning disable
+
+namespace System
+{
+ /// Represent a type can be used to index a collection either from the start or the end.
+ internal readonly struct Index : IEquatable
+ {
+ readonly int _value;
+
+ /// Initializes a new instance of the struct.
+ /// The index value. It has to be zero or positive number.
+ /// Indicating if the index is from the start or from the end.
+ public Index(int value, bool fromEnd = false)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ _value = fromEnd ? ~value : value;
+ }
+
+ Index(int value) => _value = value;
+
+ /// Gets an pointing at first element.
+ public static Index Start => new(0);
+
+ /// Gets an pointing at beyond last element.
+ public static Index End => new(~0);
+
+ /// Gets the index value.
+ public int Value => _value < 0 ? ~_value : _value;
+
+ /// Gets a value indicating whether the index is from the end.
+ public bool IsFromEnd => _value < 0;
+
+ /// Create an from the start at the position indicated by the value.
+ /// The index value from the start.
+ /// The created .
+ public static Index FromStart(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ return new Index(value);
+ }
+
+ /// Create an from the end at the position indicated by the value.
+ /// The index value from the end.
+ /// The created .
+ public static Index FromEnd(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ return new Index(~value);
+ }
+
+ /// Calculate the offset from the start using the given collection length.
+ /// The length of the collection that the will be used with.
+ /// The offset.
+ public int GetOffset(int length)
+ {
+ var offset = _value;
+ if (IsFromEnd)
+ {
+ offset += length + 1;
+ }
+
+ return offset;
+ }
+
+ /// Converts integer number to an .
+ /// The index value from the start.
+ public static implicit operator Index(int value) => FromStart(value);
+
+ ///
+ public override bool Equals(object? value) => value is Index index && _value == index._value;
+
+ ///
+ public bool Equals(Index other) => _value == other._value;
+
+ ///
+ public override int GetHashCode() => _value;
+ }
+
+ /// Represent a range that has start and end indexes.
+ internal readonly struct Range : IEquatable
+ {
+ /// Initializes a new instance of the struct.
+ /// The index representing the start of the range.
+ /// The index representing the end of the range.
+ public Range(Index start, Index end)
+ {
+ Start = start;
+ End = end;
+ }
+
+ /// Gets the index representing the inclusive start of the range.
+ public Index Start { get; }
+
+ /// Gets the index representing the exclusive end of the range.
+ public Index End { get; }
+
+ /// Gets a that covers the whole collection.
+ public static Range All => new(Index.Start, Index.End);
+
+ /// Create a from the given start index to the end of the collection.
+ /// The index representing the start of the range.
+ /// The created .
+ public static Range StartAt(Index start) => new(start, Index.End);
+
+ /// Create a from the start of the collection to the given end index.
+ /// The index representing the end of the range.
+ /// The created .
+ public static Range EndAt(Index end) => new(Index.Start, end);
+
+ /// Calculate the start offset and length of the range using the given collection length.
+ /// The length of the collection that the range will be used with.
+ /// The offset and length of the range.
+ public (int Offset, int Length) GetOffsetAndLength(int length)
+ {
+ var start = Start.IsFromEnd ? length - Start.Value : Start.Value;
+ var end = End.IsFromEnd ? length - End.Value : End.Value;
+
+ if ((uint)end > (uint)length || (uint)start > (uint)end)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+
+ return (start, end - start);
+ }
+
+ ///
+ public override bool Equals(object? value) => value is Range range && range.Start.Equals(Start) && range.End.Equals(End);
+
+ ///
+ public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End);
+
+ ///
+ public override int GetHashCode() => (Start.GetHashCode() * 31) + End.GetHashCode();
+ }
+}
+
+namespace System.Runtime.CompilerServices
+{
+ /// Provides runtime helpers required by the range operator on netstandard2.0.
+ internal static class RuntimeHelpers
+ {
+ /// Slices the specified array using the specified range.
+ /// The type of the array elements.
+ /// The array to slice.
+ /// The range to slice with.
+ /// The sliced array.
+ public static T[] GetSubArray(T[] array, Range range)
+ {
+ var (offset, length) = range.GetOffsetAndLength(array.Length);
+ if (length == 0)
+ {
+ return [];
+ }
+
+ var dest = new T[length];
+ Array.Copy(array, offset, dest, 0, length);
+ return dest;
+ }
+ }
+}
diff --git a/Source/CodeAnalysis/Rules/NamespaceMustAlignWithFolderPathRule.cs b/Source/CodeAnalysis/Rules/NamespaceMustAlignWithFolderPathRule.cs
index 9ff09b4..e9a93fc 100644
--- a/Source/CodeAnalysis/Rules/NamespaceMustAlignWithFolderPathRule.cs
+++ b/Source/CodeAnalysis/Rules/NamespaceMustAlignWithFolderPathRule.cs
@@ -45,7 +45,7 @@ static string GetLogicalFolderPath(string? filePath)
return string.Empty;
}
- var normalized = filePath.Replace('\\', '/');
+ var normalized = filePath!.Replace('\\', '/');
var sourceIndex = normalized.IndexOf("/Source/", StringComparison.Ordinal);
if (sourceIndex < 0)
{
@@ -66,7 +66,7 @@ static string GetLogicalFolderPath(string? filePath)
return string.Empty;
}
- var folders = withinProject[..fileNameIndex].Split('/', StringSplitOptions.RemoveEmptyEntries);
- return string.Join('.', folders);
+ var folders = withinProject[..fileNameIndex].Split(['/'], StringSplitOptions.RemoveEmptyEntries);
+ return string.Join(".", folders);
}
}
diff --git a/Source/CodeAnalysis/Rules/UnusedInterfacesRule.cs b/Source/CodeAnalysis/Rules/UnusedInterfacesRule.cs
index ea65852..f374de6 100644
--- a/Source/CodeAnalysis/Rules/UnusedInterfacesRule.cs
+++ b/Source/CodeAnalysis/Rules/UnusedInterfacesRule.cs
@@ -59,7 +59,7 @@ public static void Register(CompilationStartAnalysisContext startContext)
{
foreach (var @interface in interfaceSymbols)
{
- if (implementedInterfaces.Contains(@interface) || !@interface.Name.StartsWith('I'))
+ if (implementedInterfaces.Contains(@interface) || !@interface.Name.StartsWith("I", StringComparison.Ordinal))
{
continue;
}
diff --git a/Source/CodeAnalysis/UseStringInterpolationCodeFix.cs b/Source/CodeAnalysis/UseStringInterpolationCodeFix.cs
index 918eedf..e4d8a48 100644
--- a/Source/CodeAnalysis/UseStringInterpolationCodeFix.cs
+++ b/Source/CodeAnalysis/UseStringInterpolationCodeFix.cs
@@ -66,10 +66,10 @@ static async Task ConvertStringFormatToInterpolationAsync(Document doc
var interpolated = format;
for (var i = 0; i < arguments.Length; i++)
{
- interpolated = interpolated.Replace("{" + i + "}", "{" + arguments[i] + "}", StringComparison.Ordinal);
+ interpolated = interpolated.Replace("{" + i + "}", "{" + arguments[i] + "}");
}
- var replacement = SyntaxFactory.ParseExpression("$\"" + interpolated.Replace("\"", "\\\"", StringComparison.Ordinal) + "\"")
+ var replacement = SyntaxFactory.ParseExpression("$\"" + interpolated.Replace("\"", "\\\"") + "\"")
.WithTriviaFrom(invocation);
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);