From 3561088907345cf3e7e77f4ab18fa85e23c39ac6 Mon Sep 17 00:00:00 2001 From: Patryk Pochmara Date: Mon, 2 Sep 2024 14:46:33 +0200 Subject: [PATCH 1/9] Change analyzer target to .NETStandard2.0 --- src/Tools/LeanCode.CodeAnalysis/LeanCode.CodeAnalysis.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tools/LeanCode.CodeAnalysis/LeanCode.CodeAnalysis.csproj b/src/Tools/LeanCode.CodeAnalysis/LeanCode.CodeAnalysis.csproj index 24535772a..be783af08 100644 --- a/src/Tools/LeanCode.CodeAnalysis/LeanCode.CodeAnalysis.csproj +++ b/src/Tools/LeanCode.CodeAnalysis/LeanCode.CodeAnalysis.csproj @@ -6,7 +6,8 @@ true - netstandard2.1 + + netstandard2.0 From e6ab0baea0c1107d9305687d00b3ca807f095f53 Mon Sep 17 00:00:00 2001 From: Patryk Pochmara Date: Mon, 2 Sep 2024 14:47:25 +0200 Subject: [PATCH 2/9] Add functionality from .NETStandard2.1 which is missing in 2.0 --- .../MissingFileMethods.cs | 42 ++++ .../MissingStringMethods.cs | 10 + ...nostics.CodeAnalysis.NullableAttributes.cs | 208 ++++++++++++++++++ .../System.Index.cs | 183 +++++++++++++++ .../System.Numerics.Hashing.HashHelpers.cs | 18 ++ .../System.Range.cs | 133 +++++++++++ 6 files changed, 594 insertions(+) create mode 100644 src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs create mode 100644 src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingStringMethods.cs create mode 100644 src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Diagnostics.CodeAnalysis.NullableAttributes.cs create mode 100644 src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Index.cs create mode 100644 src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Numerics.Hashing.HashHelpers.cs create mode 100644 src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Range.cs diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs new file mode 100644 index 000000000..ef1b41fa5 --- /dev/null +++ b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs @@ -0,0 +1,42 @@ +// Based on https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +// as linked by docs on 02.09.2024 + +using System.Text; + +namespace System.IO; + +public static class MissingFileMethods +{ + private static readonly Encoding UTF8NoBOM = new UTF8Encoding( + encoderShouldEmitUTF8Identifier: false, + throwOnInvalidBytes: true + ); + + public static Task WriteAllTextAsync( + string path, + string? contents, + CancellationToken cancellationToken = default + ) => WriteAllTextAsync(path, contents, UTF8NoBOM, cancellationToken); + + public static async Task WriteAllTextAsync( + string path, + string? contents, + Encoding encoding, + CancellationToken cancellationToken = default + ) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentException("Path is invalid"); + } + + using var stream = File.OpenWrite(path); + var preamble = encoding.GetPreamble(); + await stream.WriteAsync(preamble, 0, preamble.Length, cancellationToken); + if (contents is not null) + { + var encoded = Encoding.Default.GetBytes(contents); + await stream.WriteAsync(encoded, preamble.Length, encoded.Length, cancellationToken); + } + } +} diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingStringMethods.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingStringMethods.cs new file mode 100644 index 000000000..dc12dbf22 --- /dev/null +++ b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingStringMethods.cs @@ -0,0 +1,10 @@ +// Based on https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/String.Searching.cs#L26C13-L26C56 +// as linked by docs on 02.09.2024 + +namespace System; + +public static class MissingStringMethods +{ + public static bool Contains(this string s, string value, StringComparison comparisonType) => + s.IndexOf(value, comparisonType) >= 0; +} diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Diagnostics.CodeAnalysis.NullableAttributes.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Diagnostics.CodeAnalysis.NullableAttributes.cs new file mode 100644 index 000000000..3bd2aa417 --- /dev/null +++ b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Diagnostics.CodeAnalysis.NullableAttributes.cs @@ -0,0 +1,208 @@ +// From https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs +// as linked by docs on 02.09.2024 with minimal changes + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis; + +#if !NETSTANDARD2_1 +/// Specifies that null is allowed as an input even if the corresponding type disallows it. +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +sealed class AllowNullAttribute : Attribute { } + +/// Specifies that null is disallowed as an input even if the corresponding type allows it. +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +sealed class DisallowNullAttribute : Attribute { } + +/// Specifies that an output may be null even if the corresponding type disallows it. +[AttributeUsage( + AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, + Inherited = false +)] +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +sealed class MaybeNullAttribute : Attribute { } + +/// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. +[AttributeUsage( + AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, + Inherited = false +)] +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +sealed class NotNullAttribute : Attribute { } + +/// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +sealed class MaybeNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } +} + +/// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +sealed class NotNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } +} + +/// Specifies that the output will be non-null if the named parameter is non-null. +[AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, + AllowMultiple = true, + Inherited = false +)] +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +sealed class NotNullIfNotNullAttribute : Attribute +{ + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + + /// Gets the associated parameter name. + public string ParameterName { get; } +} + +/// Applied to a method that will never return under any circumstance. +[AttributeUsage(AttributeTargets.Method, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +sealed class DoesNotReturnAttribute : Attribute { } + +/// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +sealed class DoesNotReturnIfAttribute : Attribute +{ + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + + /// Gets the condition parameter value. + public bool ParameterValue { get; } +} +#endif + +/// Specifies that the method or property will ensure that the listed field and property members have not-null values. +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +sealed class MemberNotNullAttribute : Attribute +{ + /// Initializes the attribute with a field or property member. + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullAttribute(string member) => Members = new[] { member }; + + /// Initializes the attribute with the list of field and property members. + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullAttribute(params string[] members) => Members = members; + + /// Gets field or property member names. + public string[] Members { get; } +} + +/// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +sealed class MemberNotNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = new[] { member }; + } + + /// Initializes the attribute with the specified return value condition and list of field and property members. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + + /// Gets the return value condition. + public bool ReturnValue { get; } + + /// Gets field or property member names. + public string[] Members { get; } +} diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Index.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Index.cs new file mode 100644 index 000000000..dba8bc17a --- /dev/null +++ b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Index.cs @@ -0,0 +1,183 @@ +// From https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Index.cs +// as linked by docs on 02.09.2024 with minimal changes + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace System; + +/// Represent a type can be used to index a collection either from the start or the end. +/// +/// Index is used by the C# compiler to support the new index syntax +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; +/// int lastElement = someArray[^1]; // lastElement = 5 +/// +/// +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +readonly struct Index : IEquatable +{ + private readonly int value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Index(int value, bool fromEnd = false) + { + if (value < 0) + { + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + if (fromEnd) + { + this.value = ~value; + } + else + { + this.value = value; + } + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + this.value = value; + } + + /// Create an Index pointing at first element. + public static Index Start => new Index(0); + + /// Create an Index pointing at beyond last element. + public static Index End => new Index(~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromStart(int value) + { + if (value < 0) + { + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + return new Index(value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromEnd(int value) + { + if (value < 0) + { + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + return new Index(~value); + } + + /// Returns the index value. + public int Value + { + get + { + if (value < 0) + { + return ~value; + } + else + { + return value; + } + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int length) + { + int offset = value; + if (IsFromEnd) + { + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; + } + return offset; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals([NotNullWhen(true)] object? value) => + value is Index && this.value == ((Index)value).value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(Index other) => value == other.value; + + /// Returns the hash code for this instance. + public override int GetHashCode() => value; + + /// Converts integer number to an Index. + public static implicit operator Index(int value) => FromStart(value); + + /// Converts the value of the current Index object to its equivalent string representation. + [SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "Behaviour of System Corelib")] + public override string ToString() + { + if (IsFromEnd) + { + return ToStringFromEnd(); + } + + return ((uint)Value).ToString(); + } + + private static void ThrowValueArgumentOutOfRange_NeedNonNegNumException() + { +#if SYSTEM_PRIVATE_CORELIB + throw new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_NeedNonNegNum); +#else + throw new ArgumentOutOfRangeException("value", "value must be non-negative"); +#endif + } + + [SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "Behaviour of System Corelib")] + private string ToStringFromEnd() + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + Span span = stackalloc char[11]; // 1 for ^ and 10 for longest possible uint value + bool formatted = ((uint)Value).TryFormat(span.Slice(1), out int charsWritten); + Debug.Assert(formatted); + span[0] = '^'; + return new string(span.Slice(0, charsWritten + 1)); +#else + return '^' + Value.ToString(); +#endif + } +} diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Numerics.Hashing.HashHelpers.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Numerics.Hashing.HashHelpers.cs new file mode 100644 index 000000000..e61e8b6e0 --- /dev/null +++ b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Numerics.Hashing.HashHelpers.cs @@ -0,0 +1,18 @@ +// From https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Numerics/Hashing/HashHelpers.cs +// as linked by docs on 02.09.2024 with minimal changes + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Numerics.Hashing; + +internal static class HashHelpers +{ + public static int Combine(int h1, int h2) + { + // RyuJIT optimizes this to use the ROL instruction + // Related GitHub pull request: https://github.com/dotnet/coreclr/pull/1830 + uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); + return ((int)rol5 + h1) ^ h2; + } +} diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Range.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Range.cs new file mode 100644 index 000000000..7fc234c3c --- /dev/null +++ b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Range.cs @@ -0,0 +1,133 @@ +// From https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Range.cs +// as linked by docs on 02.09.2024 with minimal changes + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +#if NETSTANDARD2_0 || NETFRAMEWORK +using System.Numerics.Hashing; +#endif + +namespace System; + +/// Represent a range has start and end indexes. +/// +/// Range is used by the C# compiler to support the range syntax. +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; +/// int[] subArray1 = someArray[0..2]; // { 1, 2 } +/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } +/// +/// +#if SYSTEM_PRIVATE_CORELIB +public +#else +internal +#endif +readonly struct Range : IEquatable +{ + /// Represent the inclusive start index of the Range. + public Index Start { get; } + + /// Represent the exclusive end index of the Range. + public Index End { get; } + + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range(Index start, Index end) + { + Start = start; + End = end; + } + + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals([NotNullWhen(true)] object? value) => + value is Range r && r.Start.Equals(Start) && r.End.Equals(End); + + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); + + /// Returns the hash code for this instance. + public override int GetHashCode() + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + return HashCode.Combine(Start.GetHashCode(), End.GetHashCode()); +#else + return HashHelpers.Combine(Start.GetHashCode(), End.GetHashCode()); +#endif + } + + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString() + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + Span span = stackalloc char[2 + (2 * 11)]; // 2 for "..", then for each index 1 for '^' and 10 for longest possible uint + int pos = 0; + + if (Start.IsFromEnd) + { + span[0] = '^'; + pos = 1; + } + bool formatted = ((uint)Start.Value).TryFormat(span.Slice(pos), out int charsWritten); + Debug.Assert(formatted); + pos += charsWritten; + + span[pos++] = '.'; + span[pos++] = '.'; + + if (End.IsFromEnd) + { + span[pos++] = '^'; + } + formatted = ((uint)End.Value).TryFormat(span.Slice(pos), out charsWritten); + Debug.Assert(formatted); + pos += charsWritten; + + return new string(span.Slice(0, pos)); +#else + return Start.ToString() + ".." + End.ToString(); +#endif + } + + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt(Index start) => new Range(start, Index.End); + + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt(Index end) => new Range(Index.Start, end); + + /// Create a Range object starting from first element to the end. + public static Range All => new Range(Index.Start, Index.End); + + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public (int Offset, int Length) GetOffsetAndLength(int length) + { + int start = Start.GetOffset(length); + int end = End.GetOffset(length); + + if ((uint)end > (uint)length || (uint)start > (uint)end) + { + ThrowArgumentOutOfRangeException(); + } + + return (start, end - start); + } + + private static void ThrowArgumentOutOfRangeException() + { + throw new ArgumentOutOfRangeException("length"); + } +} From 84892bab7761e0a496f9a4e36b1308ceea9790e5 Mon Sep 17 00:00:00 2001 From: Patryk Pochmara Date: Mon, 2 Sep 2024 14:48:12 +0200 Subject: [PATCH 3/9] Update existing code to use provided substitutes --- .../CodeActions/FixCQRSHandlerNamespaceCodeAction.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/LeanCode.CodeAnalysis/CodeActions/FixCQRSHandlerNamespaceCodeAction.cs b/src/Tools/LeanCode.CodeAnalysis/CodeActions/FixCQRSHandlerNamespaceCodeAction.cs index f2c68c135..5311b12f6 100644 --- a/src/Tools/LeanCode.CodeAnalysis/CodeActions/FixCQRSHandlerNamespaceCodeAction.cs +++ b/src/Tools/LeanCode.CodeAnalysis/CodeActions/FixCQRSHandlerNamespaceCodeAction.cs @@ -115,7 +115,7 @@ CancellationToken cancellationToken } var updatedText = (await document.GetTextAsync(cancellationToken)).ToString(); - await File.WriteAllTextAsync(newPath, updatedText, cancellationToken); + await MissingFileMethods.WriteAllTextAsync(newPath, updatedText, cancellationToken); File.Delete(document.FilePath); } } From 9ca17726b9250acb8f44da251fd6af0adb25bf2d Mon Sep 17 00:00:00 2001 From: Patryk Pochmara Date: Mon, 2 Sep 2024 14:57:01 +0200 Subject: [PATCH 4/9] Fix writing offset --- .../NetStandard21Compatibility/MissingFileMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs index ef1b41fa5..e0438eaad 100644 --- a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs +++ b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs @@ -36,7 +36,7 @@ public static async Task WriteAllTextAsync( if (contents is not null) { var encoded = Encoding.Default.GetBytes(contents); - await stream.WriteAsync(encoded, preamble.Length, encoded.Length, cancellationToken); + await stream.WriteAsync(encoded, 0, encoded.Length, cancellationToken); } } } From b8f73132e4ac9a9b5e50cb531a6f72fcebef6624 Mon Sep 17 00:00:00 2001 From: Patryk Pochmara Date: Tue, 3 Sep 2024 10:06:49 +0200 Subject: [PATCH 5/9] Replace vendored implementations with Microsoft.Bcl.Memory --- Directory.Build.targets | 4 + .../LeanCode.CodeAnalysis.csproj | 1 + .../System.Index.cs | 183 ------------------ .../System.Numerics.Hashing.HashHelpers.cs | 18 -- .../System.Range.cs | 133 ------------- 5 files changed, 5 insertions(+), 334 deletions(-) delete mode 100644 src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Index.cs delete mode 100644 src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Numerics.Hashing.HashHelpers.cs delete mode 100644 src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Range.cs diff --git a/Directory.Build.targets b/Directory.Build.targets index bb4eb0ccf..f56227b38 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -138,6 +138,10 @@ + + + + $(MSBuildThisFileDirectory) $(CodeAnalysisSettingsLocation)LeanCode.CodeAnalysis.ruleset diff --git a/src/Tools/LeanCode.CodeAnalysis/LeanCode.CodeAnalysis.csproj b/src/Tools/LeanCode.CodeAnalysis/LeanCode.CodeAnalysis.csproj index be783af08..96af15322 100644 --- a/src/Tools/LeanCode.CodeAnalysis/LeanCode.CodeAnalysis.csproj +++ b/src/Tools/LeanCode.CodeAnalysis/LeanCode.CodeAnalysis.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Index.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Index.cs deleted file mode 100644 index dba8bc17a..000000000 --- a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Index.cs +++ /dev/null @@ -1,183 +0,0 @@ -// From https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Index.cs -// as linked by docs on 02.09.2024 with minimal changes - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace System; - -/// Represent a type can be used to index a collection either from the start or the end. -/// -/// Index is used by the C# compiler to support the new index syntax -/// -/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; -/// int lastElement = someArray[^1]; // lastElement = 5 -/// -/// -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -readonly struct Index : IEquatable -{ - private readonly int value; - - /// Construct an Index using a value and indicating if the index is from the start or from the end. - /// The index value. it has to be zero or positive number. - /// Indicating if the index is from the start or from the end. - /// - /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Index(int value, bool fromEnd = false) - { - if (value < 0) - { - ThrowValueArgumentOutOfRange_NeedNonNegNumException(); - } - - if (fromEnd) - { - this.value = ~value; - } - else - { - this.value = value; - } - } - - // The following private constructors mainly created for perf reason to avoid the checks - private Index(int value) - { - this.value = value; - } - - /// Create an Index pointing at first element. - public static Index Start => new Index(0); - - /// Create an Index pointing at beyond last element. - public static Index End => new Index(~0); - - /// Create an Index from the start at the position indicated by the value. - /// The index value from the start. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Index FromStart(int value) - { - if (value < 0) - { - ThrowValueArgumentOutOfRange_NeedNonNegNumException(); - } - - return new Index(value); - } - - /// Create an Index from the end at the position indicated by the value. - /// The index value from the end. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Index FromEnd(int value) - { - if (value < 0) - { - ThrowValueArgumentOutOfRange_NeedNonNegNumException(); - } - - return new Index(~value); - } - - /// Returns the index value. - public int Value - { - get - { - if (value < 0) - { - return ~value; - } - else - { - return value; - } - } - } - - /// Indicates whether the index is from the start or the end. - public bool IsFromEnd => value < 0; - - /// Calculate the offset from the start using the giving collection length. - /// The length of the collection that the Index will be used with. length has to be a positive value - /// - /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. - /// we don't validate either the returned offset is greater than the input length. - /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and - /// then used to index a collection will get out of range exception which will be same affect as the validation. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetOffset(int length) - { - int offset = value; - if (IsFromEnd) - { - // offset = length - (~value) - // offset = length + (~(~value) + 1) - // offset = length + value + 1 - - offset += length + 1; - } - return offset; - } - - /// Indicates whether the current Index object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals([NotNullWhen(true)] object? value) => - value is Index && this.value == ((Index)value).value; - - /// Indicates whether the current Index object is equal to another Index object. - /// An object to compare with this object - public bool Equals(Index other) => value == other.value; - - /// Returns the hash code for this instance. - public override int GetHashCode() => value; - - /// Converts integer number to an Index. - public static implicit operator Index(int value) => FromStart(value); - - /// Converts the value of the current Index object to its equivalent string representation. - [SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "Behaviour of System Corelib")] - public override string ToString() - { - if (IsFromEnd) - { - return ToStringFromEnd(); - } - - return ((uint)Value).ToString(); - } - - private static void ThrowValueArgumentOutOfRange_NeedNonNegNumException() - { -#if SYSTEM_PRIVATE_CORELIB - throw new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_NeedNonNegNum); -#else - throw new ArgumentOutOfRangeException("value", "value must be non-negative"); -#endif - } - - [SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "Behaviour of System Corelib")] - private string ToStringFromEnd() - { -#if (!NETSTANDARD2_0 && !NETFRAMEWORK) - Span span = stackalloc char[11]; // 1 for ^ and 10 for longest possible uint value - bool formatted = ((uint)Value).TryFormat(span.Slice(1), out int charsWritten); - Debug.Assert(formatted); - span[0] = '^'; - return new string(span.Slice(0, charsWritten + 1)); -#else - return '^' + Value.ToString(); -#endif - } -} diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Numerics.Hashing.HashHelpers.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Numerics.Hashing.HashHelpers.cs deleted file mode 100644 index e61e8b6e0..000000000 --- a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Numerics.Hashing.HashHelpers.cs +++ /dev/null @@ -1,18 +0,0 @@ -// From https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Numerics/Hashing/HashHelpers.cs -// as linked by docs on 02.09.2024 with minimal changes - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Numerics.Hashing; - -internal static class HashHelpers -{ - public static int Combine(int h1, int h2) - { - // RyuJIT optimizes this to use the ROL instruction - // Related GitHub pull request: https://github.com/dotnet/coreclr/pull/1830 - uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); - return ((int)rol5 + h1) ^ h2; - } -} diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Range.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Range.cs deleted file mode 100644 index 7fc234c3c..000000000 --- a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Range.cs +++ /dev/null @@ -1,133 +0,0 @@ -// From https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Range.cs -// as linked by docs on 02.09.2024 with minimal changes - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -#if NETSTANDARD2_0 || NETFRAMEWORK -using System.Numerics.Hashing; -#endif - -namespace System; - -/// Represent a range has start and end indexes. -/// -/// Range is used by the C# compiler to support the range syntax. -/// -/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; -/// int[] subArray1 = someArray[0..2]; // { 1, 2 } -/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } -/// -/// -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -readonly struct Range : IEquatable -{ - /// Represent the inclusive start index of the Range. - public Index Start { get; } - - /// Represent the exclusive end index of the Range. - public Index End { get; } - - /// Construct a Range object using the start and end indexes. - /// Represent the inclusive start index of the range. - /// Represent the exclusive end index of the range. - public Range(Index start, Index end) - { - Start = start; - End = end; - } - - /// Indicates whether the current Range object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals([NotNullWhen(true)] object? value) => - value is Range r && r.Start.Equals(Start) && r.End.Equals(End); - - /// Indicates whether the current Range object is equal to another Range object. - /// An object to compare with this object - public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); - - /// Returns the hash code for this instance. - public override int GetHashCode() - { -#if (!NETSTANDARD2_0 && !NETFRAMEWORK) - return HashCode.Combine(Start.GetHashCode(), End.GetHashCode()); -#else - return HashHelpers.Combine(Start.GetHashCode(), End.GetHashCode()); -#endif - } - - /// Converts the value of the current Range object to its equivalent string representation. - public override string ToString() - { -#if (!NETSTANDARD2_0 && !NETFRAMEWORK) - Span span = stackalloc char[2 + (2 * 11)]; // 2 for "..", then for each index 1 for '^' and 10 for longest possible uint - int pos = 0; - - if (Start.IsFromEnd) - { - span[0] = '^'; - pos = 1; - } - bool formatted = ((uint)Start.Value).TryFormat(span.Slice(pos), out int charsWritten); - Debug.Assert(formatted); - pos += charsWritten; - - span[pos++] = '.'; - span[pos++] = '.'; - - if (End.IsFromEnd) - { - span[pos++] = '^'; - } - formatted = ((uint)End.Value).TryFormat(span.Slice(pos), out charsWritten); - Debug.Assert(formatted); - pos += charsWritten; - - return new string(span.Slice(0, pos)); -#else - return Start.ToString() + ".." + End.ToString(); -#endif - } - - /// Create a Range object starting from start index to the end of the collection. - public static Range StartAt(Index start) => new Range(start, Index.End); - - /// Create a Range object starting from first element in the collection to the end Index. - public static Range EndAt(Index end) => new Range(Index.Start, end); - - /// Create a Range object starting from first element to the end. - public static Range All => new Range(Index.Start, Index.End); - - /// Calculate the start offset and length of range object using a collection length. - /// The length of the collection that the range will be used with. length has to be a positive value. - /// - /// For performance reason, we don't validate the input length parameter against negative values. - /// It is expected Range will be used with collections which always have non negative length/count. - /// We validate the range is inside the length scope though. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (int Offset, int Length) GetOffsetAndLength(int length) - { - int start = Start.GetOffset(length); - int end = End.GetOffset(length); - - if ((uint)end > (uint)length || (uint)start > (uint)end) - { - ThrowArgumentOutOfRangeException(); - } - - return (start, end - start); - } - - private static void ThrowArgumentOutOfRangeException() - { - throw new ArgumentOutOfRangeException("length"); - } -} From f17893eb8eb0a91d5c2936af2f9a8df70d345fc3 Mon Sep 17 00:00:00 2001 From: Patryk Pochmara Date: Tue, 3 Sep 2024 10:07:50 +0200 Subject: [PATCH 6/9] Fix encoding in WriteAllTextAsync --- .../NetStandard21Compatibility/MissingFileMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs index e0438eaad..100bd08b3 100644 --- a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs +++ b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs @@ -35,7 +35,7 @@ public static async Task WriteAllTextAsync( await stream.WriteAsync(preamble, 0, preamble.Length, cancellationToken); if (contents is not null) { - var encoded = Encoding.Default.GetBytes(contents); + var encoded = encoding.GetBytes(contents); await stream.WriteAsync(encoded, 0, encoded.Length, cancellationToken); } } From 9d24f592f1d6cbcae2b4abbf8d9cf0278c056941 Mon Sep 17 00:00:00 2001 From: Patryk Pochmara Date: Tue, 3 Sep 2024 10:09:04 +0200 Subject: [PATCH 7/9] Remove unnecessary conditional compilation --- ...nostics.CodeAnalysis.NullableAttributes.cs | 79 +++---------------- 1 file changed, 11 insertions(+), 68 deletions(-) diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Diagnostics.CodeAnalysis.NullableAttributes.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Diagnostics.CodeAnalysis.NullableAttributes.cs index 3bd2aa417..78fef22bc 100644 --- a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Diagnostics.CodeAnalysis.NullableAttributes.cs +++ b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Diagnostics.CodeAnalysis.NullableAttributes.cs @@ -6,57 +6,31 @@ namespace System.Diagnostics.CodeAnalysis; -#if !NETSTANDARD2_1 /// Specifies that null is allowed as an input even if the corresponding type disallows it. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -sealed class AllowNullAttribute : Attribute { } +internal sealed class AllowNullAttribute : Attribute { } /// Specifies that null is disallowed as an input even if the corresponding type allows it. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -sealed class DisallowNullAttribute : Attribute { } +internal sealed class DisallowNullAttribute : Attribute { } /// Specifies that an output may be null even if the corresponding type disallows it. [AttributeUsage( AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false )] -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -sealed class MaybeNullAttribute : Attribute { } +internal sealed class MaybeNullAttribute : Attribute { } /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. [AttributeUsage( AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false )] -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -sealed class NotNullAttribute : Attribute { } +internal sealed class NotNullAttribute : Attribute { } /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -sealed class MaybeNullWhenAttribute : Attribute +internal sealed class MaybeNullWhenAttribute : Attribute { /// Initializes the attribute with the specified return value condition. /// @@ -70,12 +44,7 @@ sealed class MaybeNullWhenAttribute : Attribute /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -sealed class NotNullWhenAttribute : Attribute +internal sealed class NotNullWhenAttribute : Attribute { /// Initializes the attribute with the specified return value condition. /// @@ -93,12 +62,7 @@ sealed class NotNullWhenAttribute : Attribute AllowMultiple = true, Inherited = false )] -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -sealed class NotNullIfNotNullAttribute : Attribute +internal sealed class NotNullIfNotNullAttribute : Attribute { /// Initializes the attribute with the associated parameter name. /// @@ -112,21 +76,11 @@ sealed class NotNullIfNotNullAttribute : Attribute /// Applied to a method that will never return under any circumstance. [AttributeUsage(AttributeTargets.Method, Inherited = false)] -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -sealed class DoesNotReturnAttribute : Attribute { } +internal sealed class DoesNotReturnAttribute : Attribute { } /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -sealed class DoesNotReturnIfAttribute : Attribute +internal sealed class DoesNotReturnIfAttribute : Attribute { /// Initializes the attribute with the specified parameter value. /// @@ -138,16 +92,10 @@ sealed class DoesNotReturnIfAttribute : Attribute /// Gets the condition parameter value. public bool ParameterValue { get; } } -#endif /// Specifies that the method or property will ensure that the listed field and property members have not-null values. [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -sealed class MemberNotNullAttribute : Attribute +internal sealed class MemberNotNullAttribute : Attribute { /// Initializes the attribute with a field or property member. /// @@ -167,12 +115,7 @@ sealed class MemberNotNullAttribute : Attribute /// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] -#if SYSTEM_PRIVATE_CORELIB -public -#else -internal -#endif -sealed class MemberNotNullWhenAttribute : Attribute +internal sealed class MemberNotNullWhenAttribute : Attribute { /// Initializes the attribute with the specified return value condition and a field or property member. /// From 9860a90e6ed811cf04ce3761a540d417ad3934fe Mon Sep 17 00:00:00 2001 From: Patryk Pochmara Date: Tue, 3 Sep 2024 10:25:59 +0200 Subject: [PATCH 8/9] Remove nullability attributes --- ...CommandValidatorsFollowNamingConvention.cs | 7 +- .../SuggestCommandsHaveValidators.cs | 6 +- ...nostics.CodeAnalysis.NullableAttributes.cs | 151 ------------------ 3 files changed, 5 insertions(+), 159 deletions(-) delete mode 100644 src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Diagnostics.CodeAnalysis.NullableAttributes.cs diff --git a/src/Tools/LeanCode.CodeAnalysis/Analyzers/EnsureCommandValidatorsFollowNamingConvention.cs b/src/Tools/LeanCode.CodeAnalysis/Analyzers/EnsureCommandValidatorsFollowNamingConvention.cs index a7936bc24..fa6c361fc 100644 --- a/src/Tools/LeanCode.CodeAnalysis/Analyzers/EnsureCommandValidatorsFollowNamingConvention.cs +++ b/src/Tools/LeanCode.CodeAnalysis/Analyzers/EnsureCommandValidatorsFollowNamingConvention.cs @@ -39,7 +39,7 @@ private static void AnalyzeSymbol(SyntaxNodeAnalysisContext context) if (TryGetCommandValidator(type, out var commandValidator)) { - var expectedName = GetCommandValidatorExpectedName(commandValidator); + var expectedName = GetCommandValidatorExpectedName(commandValidator!); if (type.Name != expectedName) { @@ -61,10 +61,7 @@ internal static string GetCommandValidatorExpectedName(INamedTypeSymbol commandV ); } - private static bool TryGetCommandValidator( - INamedTypeSymbol type, - [NotNullWhen(true)] out INamedTypeSymbol? commandValidator - ) + private static bool TryGetCommandValidator(INamedTypeSymbol type, out INamedTypeSymbol? commandValidator) { var validator = GetImplementedValidator(type); diff --git a/src/Tools/LeanCode.CodeAnalysis/Analyzers/SuggestCommandsHaveValidators.cs b/src/Tools/LeanCode.CodeAnalysis/Analyzers/SuggestCommandsHaveValidators.cs index 6c05bc279..b146ce6dc 100644 --- a/src/Tools/LeanCode.CodeAnalysis/Analyzers/SuggestCommandsHaveValidators.cs +++ b/src/Tools/LeanCode.CodeAnalysis/Analyzers/SuggestCommandsHaveValidators.cs @@ -45,14 +45,14 @@ private static void AnalyzeSymbol(SyntaxNodeAnalysisContext context) var tree = type.DeclaringSyntaxReferences.First().SyntaxTree; - if (!CommandIsValidated(commandType, tree, context.SemanticModel)) + if (!CommandIsValidated(commandType!, tree, context.SemanticModel)) { - var diagnostic = Diagnostic.Create(Rule, type.Locations[0], commandType.Name); + var diagnostic = Diagnostic.Create(Rule, type.Locations[0], commandType!.Name); context.ReportDiagnostic(diagnostic); } } - private static bool IsCommandHandler(INamedTypeSymbol type, [NotNullWhen(true)] out INamedTypeSymbol? commandType) + private static bool IsCommandHandler(INamedTypeSymbol type, out INamedTypeSymbol? commandType) { var handler = type.AllInterfaces.FirstOrDefault(i => i.GetFullNamespaceName() == HandlerTypeName); diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Diagnostics.CodeAnalysis.NullableAttributes.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Diagnostics.CodeAnalysis.NullableAttributes.cs deleted file mode 100644 index 78fef22bc..000000000 --- a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/System.Diagnostics.CodeAnalysis.NullableAttributes.cs +++ /dev/null @@ -1,151 +0,0 @@ -// From https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs -// as linked by docs on 02.09.2024 with minimal changes - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Diagnostics.CodeAnalysis; - -/// Specifies that null is allowed as an input even if the corresponding type disallows it. -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] -internal sealed class AllowNullAttribute : Attribute { } - -/// Specifies that null is disallowed as an input even if the corresponding type allows it. -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] -internal sealed class DisallowNullAttribute : Attribute { } - -/// Specifies that an output may be null even if the corresponding type disallows it. -[AttributeUsage( - AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, - Inherited = false -)] -internal sealed class MaybeNullAttribute : Attribute { } - -/// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. -[AttributeUsage( - AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, - Inherited = false -)] -internal sealed class NotNullAttribute : Attribute { } - -/// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. -[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -internal sealed class MaybeNullWhenAttribute : Attribute -{ - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter may be null. - /// - public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - - /// Gets the return value condition. - public bool ReturnValue { get; } -} - -/// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. -[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -internal sealed class NotNullWhenAttribute : Attribute -{ - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - - /// Gets the return value condition. - public bool ReturnValue { get; } -} - -/// Specifies that the output will be non-null if the named parameter is non-null. -[AttributeUsage( - AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, - AllowMultiple = true, - Inherited = false -)] -internal sealed class NotNullIfNotNullAttribute : Attribute -{ - /// Initializes the attribute with the associated parameter name. - /// - /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. - /// - public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; - - /// Gets the associated parameter name. - public string ParameterName { get; } -} - -/// Applied to a method that will never return under any circumstance. -[AttributeUsage(AttributeTargets.Method, Inherited = false)] -internal sealed class DoesNotReturnAttribute : Attribute { } - -/// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. -[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -internal sealed class DoesNotReturnIfAttribute : Attribute -{ - /// Initializes the attribute with the specified parameter value. - /// - /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to - /// the associated parameter matches this value. - /// - public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; - - /// Gets the condition parameter value. - public bool ParameterValue { get; } -} - -/// Specifies that the method or property will ensure that the listed field and property members have not-null values. -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] -internal sealed class MemberNotNullAttribute : Attribute -{ - /// Initializes the attribute with a field or property member. - /// - /// The field or property member that is promised to be not-null. - /// - public MemberNotNullAttribute(string member) => Members = new[] { member }; - - /// Initializes the attribute with the list of field and property members. - /// - /// The list of field and property members that are promised to be not-null. - /// - public MemberNotNullAttribute(params string[] members) => Members = members; - - /// Gets field or property member names. - public string[] Members { get; } -} - -/// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] -internal sealed class MemberNotNullWhenAttribute : Attribute -{ - /// Initializes the attribute with the specified return value condition and a field or property member. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - /// - /// The field or property member that is promised to be not-null. - /// - public MemberNotNullWhenAttribute(bool returnValue, string member) - { - ReturnValue = returnValue; - Members = new[] { member }; - } - - /// Initializes the attribute with the specified return value condition and list of field and property members. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - /// - /// The list of field and property members that are promised to be not-null. - /// - public MemberNotNullWhenAttribute(bool returnValue, params string[] members) - { - ReturnValue = returnValue; - Members = members; - } - - /// Gets the return value condition. - public bool ReturnValue { get; } - - /// Gets field or property member names. - public string[] Members { get; } -} From 58fcd134841de6ccfdca0ef41ca8d5184b07de60 Mon Sep 17 00:00:00 2001 From: Patryk Pochmara Date: Wed, 4 Sep 2024 09:54:24 +0200 Subject: [PATCH 9/9] Add a project-scoped alias of File to MissingFileMethods --- .../CodeActions/FixCQRSHandlerNamespaceCodeAction.cs | 2 +- .../NetStandard21Compatibility/MissingFileMethods.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Tools/LeanCode.CodeAnalysis/CodeActions/FixCQRSHandlerNamespaceCodeAction.cs b/src/Tools/LeanCode.CodeAnalysis/CodeActions/FixCQRSHandlerNamespaceCodeAction.cs index 5311b12f6..f2c68c135 100644 --- a/src/Tools/LeanCode.CodeAnalysis/CodeActions/FixCQRSHandlerNamespaceCodeAction.cs +++ b/src/Tools/LeanCode.CodeAnalysis/CodeActions/FixCQRSHandlerNamespaceCodeAction.cs @@ -115,7 +115,7 @@ CancellationToken cancellationToken } var updatedText = (await document.GetTextAsync(cancellationToken)).ToString(); - await MissingFileMethods.WriteAllTextAsync(newPath, updatedText, cancellationToken); + await File.WriteAllTextAsync(newPath, updatedText, cancellationToken); File.Delete(document.FilePath); } } diff --git a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs index 100bd08b3..d3e62aa30 100644 --- a/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs +++ b/src/Tools/LeanCode.CodeAnalysis/NetStandard21Compatibility/MissingFileMethods.cs @@ -1,9 +1,10 @@ // Based on https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/IO/File.cs // as linked by docs on 02.09.2024 +global using File = LeanCode.CodeAnalysis.NetStandard21Compatibility.MissingFileMethods; using System.Text; -namespace System.IO; +namespace LeanCode.CodeAnalysis.NetStandard21Compatibility; public static class MissingFileMethods { @@ -12,6 +13,8 @@ public static class MissingFileMethods throwOnInvalidBytes: true ); + public static void Delete(string path) => System.IO.File.Delete(path); + public static Task WriteAllTextAsync( string path, string? contents, @@ -30,9 +33,11 @@ public static async Task WriteAllTextAsync( throw new ArgumentException("Path is invalid"); } - using var stream = File.OpenWrite(path); + using var stream = System.IO.File.OpenWrite(path); + var preamble = encoding.GetPreamble(); await stream.WriteAsync(preamble, 0, preamble.Length, cancellationToken); + if (contents is not null) { var encoded = encoding.GetBytes(contents);