diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ArrayBuilder.Enumerator.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ArrayBuilder.Enumerator.cs index e6cf4dacee..2a2fd9a7e4 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ArrayBuilder.Enumerator.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ArrayBuilder.Enumerator.cs @@ -2,39 +2,38 @@ #pragma warning disable CA1710 // Rename Microsoft.CodeAnalysis.ArrayBuilder to end in 'Collection'. -namespace Analyzer.Utilities.PooledObjects +namespace Analyzer.Utilities.PooledObjects; + +internal partial class ArrayBuilder { - internal partial class ArrayBuilder + /// + /// struct enumerator used in foreach. + /// + internal struct Enumerator : IEnumerator { - /// - /// struct enumerator used in foreach. - /// - internal struct Enumerator : IEnumerator - { - private readonly ArrayBuilder _builder; - private int _index; + private readonly ArrayBuilder _builder; + private int _index; - public Enumerator(ArrayBuilder builder) - { - _builder = builder; - _index = -1; - } + public Enumerator(ArrayBuilder builder) + { + _builder = builder; + _index = -1; + } - public readonly T Current => _builder[_index]; + public readonly T Current => _builder[_index]; - public bool MoveNext() - { - _index++; - return _index < _builder.Count; - } + public bool MoveNext() + { + _index++; + return _index < _builder.Count; + } - public readonly void Dispose() - { - } + public readonly void Dispose() + { + } - readonly object? System.Collections.IEnumerator.Current => Current; + readonly object? System.Collections.IEnumerator.Current => Current; - public void Reset() => _index = -1; - } + public void Reset() => _index = -1; } } diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ArrayBuilder.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ArrayBuilder.cs index d90193edf0..b9e8180a29 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ArrayBuilder.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ArrayBuilder.cs @@ -6,432 +6,431 @@ #pragma warning disable CA1000 // Do not declare static members on generic types -namespace Analyzer.Utilities.PooledObjects +namespace Analyzer.Utilities.PooledObjects; + +[DebuggerDisplay("Count = {Count,nq}")] +[DebuggerTypeProxy(typeof(ArrayBuilder<>.DebuggerProxy))] +internal sealed partial class ArrayBuilder : IReadOnlyList, IDisposable { - [DebuggerDisplay("Count = {Count,nq}")] - [DebuggerTypeProxy(typeof(ArrayBuilder<>.DebuggerProxy))] - internal sealed partial class ArrayBuilder : IReadOnlyList, IDisposable - { - #region DebuggerProxy + #region DebuggerProxy #pragma warning disable CA1812 // ArrayBuilder.DebuggerProxy is an internal class that is apparently never instantiated - used in DebuggerTypeProxy attribute above. - private sealed class DebuggerProxy - { - private readonly ArrayBuilder _builder; + private sealed class DebuggerProxy + { + private readonly ArrayBuilder _builder; - public DebuggerProxy(ArrayBuilder builder) - { - _builder = builder; - } + public DebuggerProxy(ArrayBuilder builder) + { + _builder = builder; + } #pragma warning disable CA1819 // Properties should not return arrays - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public T[] A + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] A + { + get { - get + var result = new T[_builder.Count]; + for (int i = 0; i < result.Length; i++) { - var result = new T[_builder.Count]; - for (int i = 0; i < result.Length; i++) - { - result[i] = _builder[i]; - } - - return result; + result[i] = _builder[i]; } + + return result; } } + } #pragma warning restore CA1819 #pragma warning restore CA1812 - #endregion + #endregion - private readonly ImmutableArray.Builder _builder; + private readonly ImmutableArray.Builder _builder; - private readonly ObjectPool>? _pool; + private readonly ObjectPool>? _pool; - public ArrayBuilder(int size) - { - _builder = ImmutableArray.CreateBuilder(size); - } + public ArrayBuilder(int size) + { + _builder = ImmutableArray.CreateBuilder(size); + } - public ArrayBuilder() - : this(8) - { } + public ArrayBuilder() + : this(8) + { } - private ArrayBuilder(ObjectPool>? pool) - : this() - { - _pool = pool; - } + private ArrayBuilder(ObjectPool>? pool) + : this() + { + _pool = pool; + } + + /// + /// Realizes the array. + /// + public ImmutableArray ToImmutable() => _builder.ToImmutable(); + + public int Count + { + get => _builder.Count; + set => _builder.Count = value; + } - /// - /// Realizes the array. - /// - public ImmutableArray ToImmutable() => _builder.ToImmutable(); + public T this[int index] + { + get => _builder[index]; + set => _builder[index] = value; + } - public int Count + /// + /// Write to slot . + /// Fills in unallocated slots preceding the , if any. + /// + public void SetItem(int index, T value) + { + while (index > _builder.Count) { - get => _builder.Count; - set => _builder.Count = value; + _builder.Add(default!); } - public T this[int index] + if (index == _builder.Count) { - get => _builder[index]; - set => _builder[index] = value; + _builder.Add(value); } - - /// - /// Write to slot . - /// Fills in unallocated slots preceding the , if any. - /// - public void SetItem(int index, T value) + else { - while (index > _builder.Count) - { - _builder.Add(default!); - } - - if (index == _builder.Count) - { - _builder.Add(value); - } - else - { - _builder[index] = value; - } + _builder[index] = value; } + } - public void Add(T item) => _builder.Add(item); + public void Add(T item) => _builder.Add(item); - public void Insert(int index, T item) => _builder.Insert(index, item); + public void Insert(int index, T item) => _builder.Insert(index, item); - public void EnsureCapacity(int capacity) + public void EnsureCapacity(int capacity) + { + if (_builder.Capacity < capacity) { - if (_builder.Capacity < capacity) - { - _builder.Capacity = capacity; - } + _builder.Capacity = capacity; } + } - public void Clear() => _builder.Clear(); + public void Clear() => _builder.Clear(); - public bool Contains(T item) => _builder.Contains(item); + public bool Contains(T item) => _builder.Contains(item); - public int IndexOf(T item) => _builder.IndexOf(item); + public int IndexOf(T item) => _builder.IndexOf(item); - public int IndexOf(T item, IEqualityComparer equalityComparer) => _builder.IndexOf(item, 0, _builder.Count, equalityComparer); + public int IndexOf(T item, IEqualityComparer equalityComparer) => _builder.IndexOf(item, 0, _builder.Count, equalityComparer); - public int IndexOf(T item, int startIndex, int count) => _builder.IndexOf(item, startIndex, count); + public int IndexOf(T item, int startIndex, int count) => _builder.IndexOf(item, startIndex, count); - public int FindIndex(Predicate match) - => FindIndex(0, Count, match); + public int FindIndex(Predicate match) + => FindIndex(0, Count, match); - public int FindIndex(int startIndex, Predicate match) - => FindIndex(startIndex, Count - startIndex, match); + public int FindIndex(int startIndex, Predicate match) + => FindIndex(startIndex, Count - startIndex, match); - public int FindIndex(int startIndex, int count, Predicate match) + public int FindIndex(int startIndex, int count, Predicate match) + { + int endIndex = startIndex + count; + for (int i = startIndex; i < endIndex; i++) { - int endIndex = startIndex + count; - for (int i = startIndex; i < endIndex; i++) + if (match(_builder[i])) { - if (match(_builder[i])) - { - return i; - } + return i; } - - return -1; } - public void RemoveAt(int index) => _builder.RemoveAt(index); + return -1; + } - public void RemoveLast() => _builder.RemoveAt(_builder.Count - 1); + public void RemoveAt(int index) => _builder.RemoveAt(index); - public void ReverseContents() => _builder.Reverse(); + public void RemoveLast() => _builder.RemoveAt(_builder.Count - 1); - public void Sort() => _builder.Sort(); + public void ReverseContents() => _builder.Reverse(); - public void Sort(IComparer comparer) => _builder.Sort(comparer); + public void Sort() => _builder.Sort(); - public void Sort(Comparison compare) - => Sort(Comparer.Create(compare)); + public void Sort(IComparer comparer) => _builder.Sort(comparer); - public void Sort(int startIndex, IComparer comparer) => _builder.Sort(startIndex, _builder.Count - startIndex, comparer); + public void Sort(Comparison compare) + => Sort(Comparer.Create(compare)); - public T[] ToArray() => _builder.ToArray(); + public void Sort(int startIndex, IComparer comparer) => _builder.Sort(startIndex, _builder.Count - startIndex, comparer); - public void CopyTo(T[] array, int start) => _builder.CopyTo(array, start); + public T[] ToArray() => _builder.ToArray(); - public T Last() => + public void CopyTo(T[] array, int start) => _builder.CopyTo(array, start); + + public T Last() => #pragma warning disable IDE0056 - _builder[_builder.Count - 1]; + _builder[_builder.Count - 1]; #pragma warning restore IDE0056 - public T First() => _builder[0]; + public T First() => _builder[0]; - public bool Any() => _builder.Count > 0; + public bool Any() => _builder.Count > 0; - /// - /// Realizes the array. - /// - public ImmutableArray ToImmutableOrNull() + /// + /// Realizes the array. + /// + public ImmutableArray ToImmutableOrNull() + { + if (Count == 0) { - if (Count == 0) - { - return default; - } - - return ToImmutable(); + return default; } - /// - /// Realizes the array, downcasting each element to a derived type. - /// - public ImmutableArray ToDowncastedImmutable() - where U : T - { - if (Count == 0) - { - return ImmutableArray.Empty; - } - - var tmp = ArrayBuilder.GetInstance(Count); - foreach (T i in _builder) - { - tmp.Add((U)i!); - } + return ToImmutable(); + } - return tmp.ToImmutableAndFree(); + /// + /// Realizes the array, downcasting each element to a derived type. + /// + public ImmutableArray ToDowncastedImmutable() + where U : T + { + if (Count == 0) + { + return ImmutableArray.Empty; } - /// - /// Realizes the array and disposes the builder in one operation. - /// - public ImmutableArray ToImmutableAndFree() + var tmp = ArrayBuilder.GetInstance(Count); + foreach (T i in _builder) { - ImmutableArray result; - if (_builder.Capacity == Count) - { - result = _builder.MoveToImmutable(); - } - else - { - result = ToImmutable(); - } - - Free(); - return result; + tmp.Add((U)i!); } - public T[] ToArrayAndFree() + return tmp.ToImmutableAndFree(); + } + + /// + /// Realizes the array and disposes the builder in one operation. + /// + public ImmutableArray ToImmutableAndFree() + { + ImmutableArray result; + if (_builder.Capacity == Count) { - T[] result = ToArray(); - Free(); - return result; + result = _builder.MoveToImmutable(); } + else + { + result = ToImmutable(); + } + + Free(); + return result; + } + + public T[] ToArrayAndFree() + { + T[] result = ToArray(); + Free(); + return result; + } - public void Dispose() => Free(); + public void Dispose() => Free(); - #region Poolable + #region Poolable - // To implement Poolable, you need two things: - // 1) Expose Freeing primitive. - private void Free() + // To implement Poolable, you need two things: + // 1) Expose Freeing primitive. + private void Free() + { + ObjectPool>? pool = _pool; + if (pool != null) { - ObjectPool>? pool = _pool; - if (pool != null) + // According to the statistics of a C# compiler self-build, the most commonly used builder size is 0. (808003 uses). + // The distant second is the Count == 1 (455619), then 2 (106362) ... + // After about 50 (just 67) we have a long tail of infrequently used builder sizes. + // However we have builders with size up to 50K (just one such thing) + // + // We do not want to retain (potentially indefinitely) very large builders + // while the chance that we will need their size is diminishingly small. + // It makes sense to constrain the size to some "not too small" number. + // Overall perf does not seem to be very sensitive to this number, so I picked 128 as a limit. + if (_builder.Capacity < 128) { - // According to the statistics of a C# compiler self-build, the most commonly used builder size is 0. (808003 uses). - // The distant second is the Count == 1 (455619), then 2 (106362) ... - // After about 50 (just 67) we have a long tail of infrequently used builder sizes. - // However we have builders with size up to 50K (just one such thing) - // - // We do not want to retain (potentially indefinitely) very large builders - // while the chance that we will need their size is diminishingly small. - // It makes sense to constrain the size to some "not too small" number. - // Overall perf does not seem to be very sensitive to this number, so I picked 128 as a limit. - if (_builder.Capacity < 128) + if (Count != 0) { - if (Count != 0) - { - Clear(); - } - - pool.Free(this, CancellationToken.None); - return; + Clear(); } - else - { - ObjectPool>.ForgetTrackedObject(this); - } - } - } - // 2) Expose the pool or the way to create a pool or the way to get an instance. - // for now we will expose both and figure which way works better - private static readonly ObjectPool> s_poolInstance = CreatePool(); - public static ArrayBuilder GetInstance() - { - ArrayBuilder builder = s_poolInstance.Allocate(); - Debug.Assert(builder.Count == 0); - return builder; + pool.Free(this, CancellationToken.None); + return; + } + else + { + ObjectPool>.ForgetTrackedObject(this); + } } + } - public static ArrayBuilder GetInstance(int capacity) - { - ArrayBuilder builder = GetInstance(); - builder.EnsureCapacity(capacity); - return builder; - } + // 2) Expose the pool or the way to create a pool or the way to get an instance. + // for now we will expose both and figure which way works better + private static readonly ObjectPool> s_poolInstance = CreatePool(); + public static ArrayBuilder GetInstance() + { + ArrayBuilder builder = s_poolInstance.Allocate(); + Debug.Assert(builder.Count == 0); + return builder; + } - public static ArrayBuilder GetInstance(int capacity, T fillWithValue) - { - ArrayBuilder builder = GetInstance(); - builder.EnsureCapacity(capacity); + public static ArrayBuilder GetInstance(int capacity) + { + ArrayBuilder builder = GetInstance(); + builder.EnsureCapacity(capacity); + return builder; + } - for (int i = 0; i < capacity; i++) - { - builder.Add(fillWithValue); - } + public static ArrayBuilder GetInstance(int capacity, T fillWithValue) + { + ArrayBuilder builder = GetInstance(); + builder.EnsureCapacity(capacity); - return builder; + for (int i = 0; i < capacity; i++) + { + builder.Add(fillWithValue); } - internal static ObjectPool> CreatePool() => CreatePool(128); // we rarely need more than 10 + return builder; + } + + internal static ObjectPool> CreatePool() => CreatePool(128); // we rarely need more than 10 - internal static ObjectPool> CreatePool(int size) - { - ObjectPool>? pool = null; - pool = new ObjectPool>(() => new ArrayBuilder(pool), size); - return pool; - } + internal static ObjectPool> CreatePool(int size) + { + ObjectPool>? pool = null; + pool = new ObjectPool>(() => new ArrayBuilder(pool), size); + return pool; + } - #endregion + #endregion - internal Enumerator GetEnumerator() => new(this); + internal Enumerator GetEnumerator() => new(this); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - internal Dictionary> ToDictionary(Func keySelector, IEqualityComparer? comparer = null) - where K : notnull + internal Dictionary> ToDictionary(Func keySelector, IEqualityComparer? comparer = null) + where K : notnull + { + if (Count == 1) { - if (Count == 1) - { - var dictionary1 = new Dictionary>(1, comparer); - T value = this[0]; - dictionary1.Add(keySelector(value), ImmutableArray.Create(value)); - return dictionary1; - } + var dictionary1 = new Dictionary>(1, comparer); + T value = this[0]; + dictionary1.Add(keySelector(value), ImmutableArray.Create(value)); + return dictionary1; + } - if (Count == 0) - { - return new Dictionary>(comparer); - } + if (Count == 0) + { + return new Dictionary>(comparer); + } - // bucketize - // prevent reallocation. it may not have 'count' entries, but it won't have more. - var accumulator = new Dictionary>(Count, comparer); - for (int i = 0; i < Count; i++) + // bucketize + // prevent reallocation. it may not have 'count' entries, but it won't have more. + var accumulator = new Dictionary>(Count, comparer); + for (int i = 0; i < Count; i++) + { + T item = this[i]; + K key = keySelector(item); + if (!accumulator.TryGetValue(key, out ArrayBuilder? bucket)) { - T item = this[i]; - K key = keySelector(item); - if (!accumulator.TryGetValue(key, out ArrayBuilder? bucket)) - { - bucket = ArrayBuilder.GetInstance(); - accumulator.Add(key, bucket); - } - - bucket.Add(item); + bucket = ArrayBuilder.GetInstance(); + accumulator.Add(key, bucket); } - var dictionary = new Dictionary>(accumulator.Count, comparer); + bucket.Add(item); + } - // freeze - foreach (KeyValuePair> pair in accumulator) - { - dictionary.Add(pair.Key, pair.Value.ToImmutableAndFree()); - } + var dictionary = new Dictionary>(accumulator.Count, comparer); - return dictionary; + // freeze + foreach (KeyValuePair> pair in accumulator) + { + dictionary.Add(pair.Key, pair.Value.ToImmutableAndFree()); } - public void AddRange(ArrayBuilder items) => _builder.AddRange(items._builder); + return dictionary; + } - public void AddRange(ArrayBuilder items) where U : T => _builder.AddRange(items._builder); + public void AddRange(ArrayBuilder items) => _builder.AddRange(items._builder); - public void AddRange(ImmutableArray items) => _builder.AddRange(items); + public void AddRange(ArrayBuilder items) where U : T => _builder.AddRange(items._builder); - public void AddRange(ImmutableArray items, int length) => _builder.AddRange(items, length); + public void AddRange(ImmutableArray items) => _builder.AddRange(items); - public void AddRange(ImmutableArray items) where S : class, T => AddRange(ImmutableArray.CastUp(items)); + public void AddRange(ImmutableArray items, int length) => _builder.AddRange(items, length); - public void AddRange(T[] items, int start, int length) + public void AddRange(ImmutableArray items) where S : class, T => AddRange(ImmutableArray.CastUp(items)); + + public void AddRange(T[] items, int start, int length) + { + for (int i = start, end = start + length; i < end; i++) { - for (int i = start, end = start + length; i < end; i++) - { - Add(items[i]); - } + Add(items[i]); } + } - public void AddRange(IEnumerable items) => _builder.AddRange(items); + public void AddRange(IEnumerable items) => _builder.AddRange(items); - public void AddRange(params T[] items) => _builder.AddRange(items); + public void AddRange(params T[] items) => _builder.AddRange(items); - public void AddRange(T[] items, int length) => _builder.AddRange(items, length); + public void AddRange(T[] items, int length) => _builder.AddRange(items, length); - public void Clip(int limit) - { - Debug.Assert(limit <= Count); - _builder.Count = limit; - } + public void Clip(int limit) + { + Debug.Assert(limit <= Count); + _builder.Count = limit; + } + + public void ZeroInit(int count) + { + _builder.Clear(); + _builder.Count = count; + } - public void ZeroInit(int count) + public void AddMany(T item, int count) + { + for (int i = 0; i < count; i++) { - _builder.Clear(); - _builder.Count = count; + Add(item); } + } + + public void RemoveDuplicates() + { + using var set = PooledHashSet.GetInstance(); - public void AddMany(T item, int count) + int j = 0; + for (int i = 0; i < Count; i++) { - for (int i = 0; i < count; i++) + if (set.Add(this[i])) { - Add(item); + this[j] = this[i]; + j++; } } - public void RemoveDuplicates() - { - using var set = PooledHashSet.GetInstance(); - - int j = 0; - for (int i = 0; i < Count; i++) - { - if (set.Add(this[i])) - { - this[j] = this[i]; - j++; - } - } + Clip(j); + } - Clip(j); - } + public ImmutableArray SelectDistinct(Func selector) + { + using var result = ArrayBuilder.GetInstance(Count); + using var set = PooledHashSet.GetInstance(); - public ImmutableArray SelectDistinct(Func selector) + foreach (T? item in _builder) { - using var result = ArrayBuilder.GetInstance(Count); - using var set = PooledHashSet.GetInstance(); - - foreach (T? item in _builder) + S? selected = selector(item); + if (set.Add(selected)) { - S? selected = selector(item); - if (set.Add(selected)) - { - result.Add(selected); - } + result.Add(selected); } - - return result.ToImmutable(); } + + return result.ToImmutable(); } } diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/BoundedCacheWithFactory.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/BoundedCacheWithFactory.cs index 70cd5b5113..d3fbaf64d1 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/BoundedCacheWithFactory.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/BoundedCacheWithFactory.cs @@ -1,77 +1,76 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. -namespace Analyzer.Utilities +namespace Analyzer.Utilities; + +/// +/// Provides bounded cache for analyzers. +/// Acts as a good alternative to +/// when the cached value has a cyclic reference to the key preventing early garbage collection of entries. +/// +internal class BoundedCacheWithFactory + where TKey : class { - /// - /// Provides bounded cache for analyzers. - /// Acts as a good alternative to - /// when the cached value has a cyclic reference to the key preventing early garbage collection of entries. - /// - internal class BoundedCacheWithFactory - where TKey : class - { - // Bounded weak reference cache. - // Size 5 is an arbitrarily chosen bound, which can be tuned in future as required. - private readonly List> _weakReferencedEntries = - [ - new WeakReference(null), - new WeakReference(null), - new WeakReference(null), - new WeakReference(null), - new WeakReference(null) - ]; + // Bounded weak reference cache. + // Size 5 is an arbitrarily chosen bound, which can be tuned in future as required. + private readonly List> _weakReferencedEntries = + [ + new WeakReference(null), + new WeakReference(null), + new WeakReference(null), + new WeakReference(null), + new WeakReference(null) + ]; - public TValue GetOrCreateValue(TKey key, Func valueFactory) + public TValue GetOrCreateValue(TKey key, Func valueFactory) + { + lock (_weakReferencedEntries) { - lock (_weakReferencedEntries) + int indexToSetTarget = -1; + for (int i = 0; i < _weakReferencedEntries.Count; i++) { - int indexToSetTarget = -1; - for (int i = 0; i < _weakReferencedEntries.Count; i++) + WeakReference weakReferencedEntry = _weakReferencedEntries[i]; + if (!weakReferencedEntry.TryGetTarget(out Entry? cachedEntry) || + cachedEntry == null) { - WeakReference weakReferencedEntry = _weakReferencedEntries[i]; - if (!weakReferencedEntry.TryGetTarget(out Entry? cachedEntry) || - cachedEntry == null) + if (indexToSetTarget == -1) { - if (indexToSetTarget == -1) - { - indexToSetTarget = i; - } - - continue; + indexToSetTarget = i; } - if (Equals(cachedEntry.Key, key)) - { - // Move the cache hit item to the end of the list - // so it would be least likely to be evicted on next cache miss. - _weakReferencedEntries.RemoveAt(i); - _weakReferencedEntries.Add(weakReferencedEntry); - return cachedEntry.Value; - } + continue; } - if (indexToSetTarget == -1) + if (Equals(cachedEntry.Key, key)) { - indexToSetTarget = 0; + // Move the cache hit item to the end of the list + // so it would be least likely to be evicted on next cache miss. + _weakReferencedEntries.RemoveAt(i); + _weakReferencedEntries.Add(weakReferencedEntry); + return cachedEntry.Value; } - - var newEntry = new Entry(key, valueFactory(key)); - _weakReferencedEntries[indexToSetTarget].SetTarget(newEntry); - return newEntry.Value; } - } - private sealed class Entry - { - public Entry(TKey key, TValue value) + if (indexToSetTarget == -1) { - Key = key; - Value = value; + indexToSetTarget = 0; } - public TKey Key { get; } + var newEntry = new Entry(key, valueFactory(key)); + _weakReferencedEntries[indexToSetTarget].SetTarget(newEntry); + return newEntry.Value; + } + } - public TValue Value { get; } + private sealed class Entry + { + public Entry(TKey key, TValue value) + { + Key = key; + Value = value; } + + public TKey Key { get; } + + public TValue Value { get; } } } diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/DiagnosticExtensions.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/DiagnosticExtensions.cs index be456a6521..6f36221330 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/DiagnosticExtensions.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/DiagnosticExtensions.cs @@ -6,255 +6,254 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -namespace Analyzer.Utilities.Extensions -{ - internal static class FixtureUtils - { - public static Diagnostic CreateDiagnostic( - this SyntaxNode node, - DiagnosticDescriptor rule, - params object[] args) - => node.CreateDiagnostic(rule, properties: null, args); - - public static Diagnostic CreateDiagnostic( - this SyntaxNode node, - DiagnosticDescriptor rule, - ImmutableDictionary? properties, - params object[] args) - => node.CreateDiagnostic(rule, additionalLocations: ImmutableArray.Empty, properties, args); - - public static Diagnostic CreateDiagnostic( - this SyntaxNode node, - DiagnosticDescriptor rule, - ImmutableArray additionalLocations, - ImmutableDictionary? properties, - params object[] args) - => node - .GetLocation() - .CreateDiagnostic( - rule: rule, - additionalLocations: additionalLocations, - properties: properties, - args: args); - - public static Diagnostic CreateDiagnostic( - this IOperation operation, - DiagnosticDescriptor rule, - params object[] args) - => operation.CreateDiagnostic(rule, properties: null, args); - - public static Diagnostic CreateDiagnostic( - this IOperation operation, - DiagnosticDescriptor rule, - ImmutableDictionary? properties, - params object[] args) => operation.Syntax.CreateDiagnostic(rule, properties, args); - - public static Diagnostic CreateDiagnostic( - this IOperation operation, - DiagnosticDescriptor rule, - ImmutableArray additionalLocations, - ImmutableDictionary? properties, - params object[] args) => operation.Syntax.CreateDiagnostic(rule, additionalLocations, properties, args); - - public static Diagnostic CreateDiagnostic( - this SyntaxToken token, - DiagnosticDescriptor rule, - params object[] args) => token.GetLocation().CreateDiagnostic(rule, args); - - public static Diagnostic CreateDiagnostic( - this ISymbol symbol, - DiagnosticDescriptor rule, - params object[] args) => symbol.Locations.CreateDiagnostic(rule, args); - - public static Diagnostic CreateDiagnostic( - this ISymbol symbol, - DiagnosticDescriptor rule, - ImmutableDictionary? properties, - params object[] args) => symbol.Locations.CreateDiagnostic(rule, properties, args); - - public static Diagnostic CreateDiagnostic( - this Location location, - DiagnosticDescriptor rule, - params object[] args) - => location - .CreateDiagnostic( - rule: rule, - properties: ImmutableDictionary.Empty, - args: args); - - public static Diagnostic CreateDiagnostic( - this Location location, - DiagnosticDescriptor rule, - ImmutableDictionary? properties, - params object[] args) - => location.CreateDiagnostic(rule, ImmutableArray.Empty, properties, args); - - public static Diagnostic CreateDiagnostic( - this Location location, - DiagnosticDescriptor rule, - ImmutableArray additionalLocations, - ImmutableDictionary? properties, - params object[] args) - { - if (!location.IsInSource) - { - location = Location.None; - } +namespace Analyzer.Utilities.Extensions; - return Diagnostic.Create( - descriptor: rule, - location: location, +internal static class FixtureUtils +{ + public static Diagnostic CreateDiagnostic( + this SyntaxNode node, + DiagnosticDescriptor rule, + params object[] args) + => node.CreateDiagnostic(rule, properties: null, args); + + public static Diagnostic CreateDiagnostic( + this SyntaxNode node, + DiagnosticDescriptor rule, + ImmutableDictionary? properties, + params object[] args) + => node.CreateDiagnostic(rule, additionalLocations: ImmutableArray.Empty, properties, args); + + public static Diagnostic CreateDiagnostic( + this SyntaxNode node, + DiagnosticDescriptor rule, + ImmutableArray additionalLocations, + ImmutableDictionary? properties, + params object[] args) + => node + .GetLocation() + .CreateDiagnostic( + rule: rule, additionalLocations: additionalLocations, properties: properties, - messageArgs: args); + args: args); + + public static Diagnostic CreateDiagnostic( + this IOperation operation, + DiagnosticDescriptor rule, + params object[] args) + => operation.CreateDiagnostic(rule, properties: null, args); + + public static Diagnostic CreateDiagnostic( + this IOperation operation, + DiagnosticDescriptor rule, + ImmutableDictionary? properties, + params object[] args) => operation.Syntax.CreateDiagnostic(rule, properties, args); + + public static Diagnostic CreateDiagnostic( + this IOperation operation, + DiagnosticDescriptor rule, + ImmutableArray additionalLocations, + ImmutableDictionary? properties, + params object[] args) => operation.Syntax.CreateDiagnostic(rule, additionalLocations, properties, args); + + public static Diagnostic CreateDiagnostic( + this SyntaxToken token, + DiagnosticDescriptor rule, + params object[] args) => token.GetLocation().CreateDiagnostic(rule, args); + + public static Diagnostic CreateDiagnostic( + this ISymbol symbol, + DiagnosticDescriptor rule, + params object[] args) => symbol.Locations.CreateDiagnostic(rule, args); + + public static Diagnostic CreateDiagnostic( + this ISymbol symbol, + DiagnosticDescriptor rule, + ImmutableDictionary? properties, + params object[] args) => symbol.Locations.CreateDiagnostic(rule, properties, args); + + public static Diagnostic CreateDiagnostic( + this Location location, + DiagnosticDescriptor rule, + params object[] args) + => location + .CreateDiagnostic( + rule: rule, + properties: ImmutableDictionary.Empty, + args: args); + + public static Diagnostic CreateDiagnostic( + this Location location, + DiagnosticDescriptor rule, + ImmutableDictionary? properties, + params object[] args) + => location.CreateDiagnostic(rule, ImmutableArray.Empty, properties, args); + + public static Diagnostic CreateDiagnostic( + this Location location, + DiagnosticDescriptor rule, + ImmutableArray additionalLocations, + ImmutableDictionary? properties, + params object[] args) + { + if (!location.IsInSource) + { + location = Location.None; } - public static Diagnostic CreateDiagnostic( - this IEnumerable locations, - DiagnosticDescriptor rule, - params object[] args) => locations.CreateDiagnostic(rule, null, args); + return Diagnostic.Create( + descriptor: rule, + location: location, + additionalLocations: additionalLocations, + properties: properties, + messageArgs: args); + } - public static Diagnostic CreateDiagnostic( - this IEnumerable locations, - DiagnosticDescriptor rule, - ImmutableDictionary? properties, - params object[] args) - { - IEnumerable inSource = locations.Where(l => l.IsInSource); - if (!inSource.Any()) - { - return Diagnostic.Create(rule, null, args); - } + public static Diagnostic CreateDiagnostic( + this IEnumerable locations, + DiagnosticDescriptor rule, + params object[] args) => locations.CreateDiagnostic(rule, null, args); - return Diagnostic.Create(rule, - location: inSource.First(), - additionalLocations: inSource.Skip(1), - properties: properties, - messageArgs: args); + public static Diagnostic CreateDiagnostic( + this IEnumerable locations, + DiagnosticDescriptor rule, + ImmutableDictionary? properties, + params object[] args) + { + IEnumerable inSource = locations.Where(l => l.IsInSource); + if (!inSource.Any()) + { + return Diagnostic.Create(rule, null, args); } - /// - /// TODO: Revert this reflection based workaround once we move to Microsoft.CodeAnalysis version 3.0 - /// - private static readonly PropertyInfo? s_syntaxTreeDiagnosticOptionsProperty = - typeof(SyntaxTree).GetTypeInfo().GetDeclaredProperty("DiagnosticOptions"); - - private static readonly PropertyInfo? s_compilationOptionsSyntaxTreeOptionsProviderProperty = - typeof(CompilationOptions).GetTypeInfo().GetDeclaredProperty("SyntaxTreeOptionsProvider"); - - public static void ReportNoLocationDiagnostic( - this CompilationAnalysisContext context, - DiagnosticDescriptor rule, - params object[] args) - => context.Compilation.ReportNoLocationDiagnostic(rule, context.ReportDiagnostic, properties: null, args); - - public static void ReportNoLocationDiagnostic( - this SyntaxNodeAnalysisContext context, - DiagnosticDescriptor rule, - params object[] args) - => context.Compilation.ReportNoLocationDiagnostic(rule, context.ReportDiagnostic, properties: null, args); - - public static void ReportNoLocationDiagnostic( - this Compilation compilation, - DiagnosticDescriptor rule, - Action addDiagnostic, - ImmutableDictionary? properties, - params object[] args) + return Diagnostic.Create(rule, + location: inSource.First(), + additionalLocations: inSource.Skip(1), + properties: properties, + messageArgs: args); + } + + /// + /// TODO: Revert this reflection based workaround once we move to Microsoft.CodeAnalysis version 3.0 + /// + private static readonly PropertyInfo? s_syntaxTreeDiagnosticOptionsProperty = + typeof(SyntaxTree).GetTypeInfo().GetDeclaredProperty("DiagnosticOptions"); + + private static readonly PropertyInfo? s_compilationOptionsSyntaxTreeOptionsProviderProperty = + typeof(CompilationOptions).GetTypeInfo().GetDeclaredProperty("SyntaxTreeOptionsProvider"); + + public static void ReportNoLocationDiagnostic( + this CompilationAnalysisContext context, + DiagnosticDescriptor rule, + params object[] args) + => context.Compilation.ReportNoLocationDiagnostic(rule, context.ReportDiagnostic, properties: null, args); + + public static void ReportNoLocationDiagnostic( + this SyntaxNodeAnalysisContext context, + DiagnosticDescriptor rule, + params object[] args) + => context.Compilation.ReportNoLocationDiagnostic(rule, context.ReportDiagnostic, properties: null, args); + + public static void ReportNoLocationDiagnostic( + this Compilation compilation, + DiagnosticDescriptor rule, + Action addDiagnostic, + ImmutableDictionary? properties, + params object[] args) + { + DiagnosticSeverity? effectiveSeverity = GetEffectiveSeverity(); + if (!effectiveSeverity.HasValue) { - DiagnosticSeverity? effectiveSeverity = GetEffectiveSeverity(); - if (!effectiveSeverity.HasValue) - { - // Disabled rule - return; - } + // Disabled rule + return; + } - if (effectiveSeverity.Value != rule.DefaultSeverity) - { + if (effectiveSeverity.Value != rule.DefaultSeverity) + { #pragma warning disable RS0030 // The symbol 'DiagnosticDescriptor.DiagnosticDescriptor.#ctor' is banned in this project: Use 'DiagnosticDescriptorHelper.Create' instead - rule = new DiagnosticDescriptor(rule.Id, rule.Title, rule.MessageFormat, rule.Category, - effectiveSeverity.Value, rule.IsEnabledByDefault, rule.Description, rule.HelpLinkUri, rule.CustomTags.ToArray()); + rule = new DiagnosticDescriptor(rule.Id, rule.Title, rule.MessageFormat, rule.Category, + effectiveSeverity.Value, rule.IsEnabledByDefault, rule.Description, rule.HelpLinkUri, rule.CustomTags.ToArray()); #pragma warning restore RS0030 - } + } - var diagnostic = Diagnostic.Create(rule, Location.None, properties, args); - addDiagnostic(diagnostic); - return; + var diagnostic = Diagnostic.Create(rule, Location.None, properties, args); + addDiagnostic(diagnostic); + return; + + DiagnosticSeverity? GetEffectiveSeverity() + { + // Microsoft.CodeAnalysis version >= 3.7 exposes options through 'CompilationOptions.SyntaxTreeOptionsProvider.TryGetDiagnosticValue' + // Microsoft.CodeAnalysis version 3.3 - 3.7 exposes options through 'SyntaxTree.DiagnosticOptions'. This API is deprecated in 3.7. - DiagnosticSeverity? GetEffectiveSeverity() + object? syntaxTreeOptionsProvider = s_compilationOptionsSyntaxTreeOptionsProviderProperty?.GetValue(compilation.Options); + MethodInfo? syntaxTreeOptionsProviderTryGetDiagnosticValueMethod = syntaxTreeOptionsProvider?.GetType().GetRuntimeMethods().FirstOrDefault(m => m.Name == "TryGetDiagnosticValue"); + if (syntaxTreeOptionsProviderTryGetDiagnosticValueMethod == null && s_syntaxTreeDiagnosticOptionsProperty == null) { - // Microsoft.CodeAnalysis version >= 3.7 exposes options through 'CompilationOptions.SyntaxTreeOptionsProvider.TryGetDiagnosticValue' - // Microsoft.CodeAnalysis version 3.3 - 3.7 exposes options through 'SyntaxTree.DiagnosticOptions'. This API is deprecated in 3.7. + return rule.DefaultSeverity; + } - object? syntaxTreeOptionsProvider = s_compilationOptionsSyntaxTreeOptionsProviderProperty?.GetValue(compilation.Options); - MethodInfo? syntaxTreeOptionsProviderTryGetDiagnosticValueMethod = syntaxTreeOptionsProvider?.GetType().GetRuntimeMethods().FirstOrDefault(m => m.Name == "TryGetDiagnosticValue"); - if (syntaxTreeOptionsProviderTryGetDiagnosticValueMethod == null && s_syntaxTreeDiagnosticOptionsProperty == null) - { - return rule.DefaultSeverity; - } + ReportDiagnostic? overriddenSeverity = null; + foreach (SyntaxTree tree in compilation.SyntaxTrees) + { + ReportDiagnostic? configuredValue = null; - ReportDiagnostic? overriddenSeverity = null; - foreach (SyntaxTree tree in compilation.SyntaxTrees) + // Prefer 'CompilationOptions.SyntaxTreeOptionsProvider', if available. + if (s_compilationOptionsSyntaxTreeOptionsProviderProperty != null) { - ReportDiagnostic? configuredValue = null; - - // Prefer 'CompilationOptions.SyntaxTreeOptionsProvider', if available. - if (s_compilationOptionsSyntaxTreeOptionsProviderProperty != null) + if (syntaxTreeOptionsProviderTryGetDiagnosticValueMethod != null) { - if (syntaxTreeOptionsProviderTryGetDiagnosticValueMethod != null) + // public abstract bool TryGetDiagnosticValue(SyntaxTree tree, string diagnosticId, out ReportDiagnostic severity); + // public abstract bool TryGetDiagnosticValue(SyntaxTree tree, string diagnosticId, CancellationToken cancellationToken, out ReportDiagnostic severity); + object?[] parameters; + if (syntaxTreeOptionsProviderTryGetDiagnosticValueMethod.GetParameters().Length == 3) { - // public abstract bool TryGetDiagnosticValue(SyntaxTree tree, string diagnosticId, out ReportDiagnostic severity); - // public abstract bool TryGetDiagnosticValue(SyntaxTree tree, string diagnosticId, CancellationToken cancellationToken, out ReportDiagnostic severity); - object?[] parameters; - if (syntaxTreeOptionsProviderTryGetDiagnosticValueMethod.GetParameters().Length == 3) - { - parameters = [tree, rule.Id, null]; - } - else - { - parameters = [tree, rule.Id, CancellationToken.None, null]; - } - - if (syntaxTreeOptionsProviderTryGetDiagnosticValueMethod.Invoke(syntaxTreeOptionsProvider, parameters) is true && - parameters.Last() is ReportDiagnostic value) - { - configuredValue = value; - } + parameters = [tree, rule.Id, null]; } - } - else - { - RoslynDebug.Assert(s_syntaxTreeDiagnosticOptionsProperty != null); - var options = (ImmutableDictionary)s_syntaxTreeDiagnosticOptionsProperty.GetValue(tree)!; - if (options.TryGetValue(rule.Id, out ReportDiagnostic value)) + else + { + parameters = [tree, rule.Id, CancellationToken.None, null]; + } + + if (syntaxTreeOptionsProviderTryGetDiagnosticValueMethod.Invoke(syntaxTreeOptionsProvider, parameters) is true && + parameters.Last() is ReportDiagnostic value) { configuredValue = value; } } - - if (configuredValue == null) + } + else + { + RoslynDebug.Assert(s_syntaxTreeDiagnosticOptionsProperty != null); + var options = (ImmutableDictionary)s_syntaxTreeDiagnosticOptionsProperty.GetValue(tree)!; + if (options.TryGetValue(rule.Id, out ReportDiagnostic value)) { - continue; + configuredValue = value; } + } - if (configuredValue == ReportDiagnostic.Suppress) - { - // Any suppression entry always wins. - return null; - } + if (configuredValue == null) + { + continue; + } - if (overriddenSeverity == null) - { - overriddenSeverity = configuredValue; - } - else if (overriddenSeverity.Value.IsLessSevereThan(configuredValue.Value)) - { - // Choose the most severe value for conflicts. - overriddenSeverity = configuredValue; - } + if (configuredValue == ReportDiagnostic.Suppress) + { + // Any suppression entry always wins. + return null; } - return overriddenSeverity.HasValue ? overriddenSeverity.Value.ToDiagnosticSeverity() : rule.DefaultSeverity; + if (overriddenSeverity == null) + { + overriddenSeverity = configuredValue; + } + else if (overriddenSeverity.Value.IsLessSevereThan(configuredValue.Value)) + { + // Choose the most severe value for conflicts. + overriddenSeverity = configuredValue; + } } + + return overriddenSeverity.HasValue ? overriddenSeverity.Value.ToDiagnosticSeverity() : rule.DefaultSeverity; } } } diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/IMethodSymbolExtensions.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/IMethodSymbolExtensions.cs index baac491429..949e91704d 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/IMethodSymbolExtensions.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/IMethodSymbolExtensions.cs @@ -8,83 +8,82 @@ using MSTest.Analyzers.Helpers; -namespace Analyzer.Utilities.Extensions +namespace Analyzer.Utilities.Extensions; + +internal static class IMethodSymbolExtensions { - internal static class IMethodSymbolExtensions + /// + /// Checks if the given method is an implementation of the given interface method + /// Substituted with the given typeargument. + /// + public static bool IsImplementationOfInterfaceMethod(this IMethodSymbol method, ITypeSymbol? typeArgument, [NotNullWhen(returnValue: true)] INamedTypeSymbol? interfaceType, string interfaceMethodName) { - /// - /// Checks if the given method is an implementation of the given interface method - /// Substituted with the given typeargument. - /// - public static bool IsImplementationOfInterfaceMethod(this IMethodSymbol method, ITypeSymbol? typeArgument, [NotNullWhen(returnValue: true)] INamedTypeSymbol? interfaceType, string interfaceMethodName) - { - INamedTypeSymbol? constructedInterface = typeArgument != null ? interfaceType?.Construct(typeArgument) : interfaceType; + INamedTypeSymbol? constructedInterface = typeArgument != null ? interfaceType?.Construct(typeArgument) : interfaceType; - return constructedInterface?.GetMembers(interfaceMethodName).FirstOrDefault() is IMethodSymbol interfaceMethod && - SymbolEqualityComparer.Default.Equals(method, method.ContainingType.FindImplementationForInterfaceMember(interfaceMethod)); - } + return constructedInterface?.GetMembers(interfaceMethodName).FirstOrDefault() is IMethodSymbol interfaceMethod && + SymbolEqualityComparer.Default.Equals(method, method.ContainingType.FindImplementationForInterfaceMember(interfaceMethod)); + } - /// - /// Checks if the given method implements IDisposable.Dispose() - /// - public static bool IsDisposeImplementation(this IMethodSymbol method, Compilation compilation) - { - INamedTypeSymbol? iDisposable = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIDisposable); - return method.IsDisposeImplementation(iDisposable); - } + /// + /// Checks if the given method implements IDisposable.Dispose() + /// + public static bool IsDisposeImplementation(this IMethodSymbol method, Compilation compilation) + { + INamedTypeSymbol? iDisposable = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIDisposable); + return method.IsDisposeImplementation(iDisposable); + } - /// - /// Checks if the given method implements IAsyncDisposable.Dispose() - /// - public static bool IsAsyncDisposeImplementation(this IMethodSymbol method, Compilation compilation) + /// + /// Checks if the given method implements IAsyncDisposable.Dispose() + /// + public static bool IsAsyncDisposeImplementation(this IMethodSymbol method, Compilation compilation) + { + INamedTypeSymbol? iAsyncDisposable = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIAsyncDisposable); + INamedTypeSymbol? valueTaskType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask); + return method.IsAsyncDisposeImplementation(iAsyncDisposable, valueTaskType); + } + + /// + /// Checks if the given method implements or overrides an implementation of . + /// + public static bool IsDisposeImplementation([NotNullWhen(returnValue: true)] this IMethodSymbol? method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? iDisposable) + { + if (method == null) { - INamedTypeSymbol? iAsyncDisposable = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIAsyncDisposable); - INamedTypeSymbol? valueTaskType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask); - return method.IsAsyncDisposeImplementation(iAsyncDisposable, valueTaskType); + return false; } - /// - /// Checks if the given method implements or overrides an implementation of . - /// - public static bool IsDisposeImplementation([NotNullWhen(returnValue: true)] this IMethodSymbol? method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? iDisposable) + if (method.IsOverride) { - if (method == null) - { - return false; - } + return method.OverriddenMethod.IsDisposeImplementation(iDisposable); + } - if (method.IsOverride) - { - return method.OverriddenMethod.IsDisposeImplementation(iDisposable); - } + // Identify the implementor of IDisposable.Dispose in the given method's containing type and check + // if it is the given method. + return method.ReturnsVoid && + method.Parameters.IsEmpty && + method.IsImplementationOfInterfaceMethod(null, iDisposable, "Dispose"); + } - // Identify the implementor of IDisposable.Dispose in the given method's containing type and check - // if it is the given method. - return method.ReturnsVoid && - method.Parameters.IsEmpty && - method.IsImplementationOfInterfaceMethod(null, iDisposable, "Dispose"); + /// + /// Checks if the given method implements "IAsyncDisposable.Dispose" or overrides an implementation of "IAsyncDisposable.Dispose". + /// + public static bool IsAsyncDisposeImplementation([NotNullWhen(returnValue: true)] this IMethodSymbol? method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? iAsyncDisposable, [NotNullWhen(returnValue: true)] INamedTypeSymbol? valueTaskType) + { + if (method == null) + { + return false; } - /// - /// Checks if the given method implements "IAsyncDisposable.Dispose" or overrides an implementation of "IAsyncDisposable.Dispose". - /// - public static bool IsAsyncDisposeImplementation([NotNullWhen(returnValue: true)] this IMethodSymbol? method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? iAsyncDisposable, [NotNullWhen(returnValue: true)] INamedTypeSymbol? valueTaskType) + if (method.IsOverride) { - if (method == null) - { - return false; - } - - if (method.IsOverride) - { - return method.OverriddenMethod.IsAsyncDisposeImplementation(iAsyncDisposable, valueTaskType); - } - - // Identify the implementor of IAsyncDisposable.Dispose in the given method's containing type and check - // if it is the given method. - return SymbolEqualityComparer.Default.Equals(method.ReturnType, valueTaskType) && - method.Parameters.IsEmpty && - method.IsImplementationOfInterfaceMethod(null, iAsyncDisposable, "DisposeAsync"); + return method.OverriddenMethod.IsAsyncDisposeImplementation(iAsyncDisposable, valueTaskType); } + + // Identify the implementor of IAsyncDisposable.Dispose in the given method's containing type and check + // if it is the given method. + return SymbolEqualityComparer.Default.Equals(method.ReturnType, valueTaskType) && + method.Parameters.IsEmpty && + method.IsImplementationOfInterfaceMethod(null, iAsyncDisposable, "DisposeAsync"); } } diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ISymbolExtensions.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ISymbolExtensions.cs index 312037e727..e65e1d13a6 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ISymbolExtensions.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ISymbolExtensions.cs @@ -4,65 +4,64 @@ using Microsoft.CodeAnalysis; -namespace Analyzer.Utilities.Extensions +namespace Analyzer.Utilities.Extensions; + +internal static class ISymbolExtensions { - internal static class ISymbolExtensions + public static SymbolVisibility GetResultantVisibility(this ISymbol symbol) { - public static SymbolVisibility GetResultantVisibility(this ISymbol symbol) - { - // Start by assuming it's visible. - SymbolVisibility visibility = SymbolVisibility.Public; + // Start by assuming it's visible. + SymbolVisibility visibility = SymbolVisibility.Public; - switch (symbol.Kind) - { - case SymbolKind.Alias: - // Aliases are uber private. They're only visible in the same file that they - // were declared in. - return SymbolVisibility.Private; + switch (symbol.Kind) + { + case SymbolKind.Alias: + // Aliases are uber private. They're only visible in the same file that they + // were declared in. + return SymbolVisibility.Private; - case SymbolKind.Parameter: - // Parameters are only as visible as their containing symbol - return GetResultantVisibility(symbol.ContainingSymbol); + case SymbolKind.Parameter: + // Parameters are only as visible as their containing symbol + return GetResultantVisibility(symbol.ContainingSymbol); - case SymbolKind.TypeParameter: - // Type Parameters are private. - return SymbolVisibility.Private; - } + case SymbolKind.TypeParameter: + // Type Parameters are private. + return SymbolVisibility.Private; + } - while (symbol != null && symbol.Kind != SymbolKind.Namespace) + while (symbol != null && symbol.Kind != SymbolKind.Namespace) + { + switch (symbol.DeclaredAccessibility) { - switch (symbol.DeclaredAccessibility) - { - // If we see anything private, then the symbol is private. - case Accessibility.NotApplicable: - case Accessibility.Private: - return SymbolVisibility.Private; - - // If we see anything internal, then knock it down from public to - // internal. - case Accessibility.Internal: - case Accessibility.ProtectedAndInternal: - visibility = SymbolVisibility.Internal; - break; + // If we see anything private, then the symbol is private. + case Accessibility.NotApplicable: + case Accessibility.Private: + return SymbolVisibility.Private; - // For anything else (Public, Protected, ProtectedOrInternal), the - // symbol stays at the level we've gotten so far. - } + // If we see anything internal, then knock it down from public to + // internal. + case Accessibility.Internal: + case Accessibility.ProtectedAndInternal: + visibility = SymbolVisibility.Internal; + break; - symbol = symbol.ContainingSymbol; + // For anything else (Public, Protected, ProtectedOrInternal), the + // symbol stays at the level we've gotten so far. } - return visibility; + symbol = symbol.ContainingSymbol; } - public static ITypeSymbol? GetMemberType(this ISymbol? symbol) - => symbol switch - { - IEventSymbol eventSymbol => eventSymbol.Type, - IFieldSymbol fieldSymbol => fieldSymbol.Type, - IMethodSymbol methodSymbol => methodSymbol.ReturnType, - IPropertySymbol propertySymbol => propertySymbol.Type, - _ => null, - }; + return visibility; } + + public static ITypeSymbol? GetMemberType(this ISymbol? symbol) + => symbol switch + { + IEventSymbol eventSymbol => eventSymbol.Type, + IFieldSymbol fieldSymbol => fieldSymbol.Type, + IMethodSymbol methodSymbol => methodSymbol.ReturnType, + IPropertySymbol propertySymbol => propertySymbol.Type, + _ => null, + }; } diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ITypeSymbolExtensions.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ITypeSymbolExtensions.cs index af2751cbfb..86ee2ec619 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ITypeSymbolExtensions.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ITypeSymbolExtensions.cs @@ -6,86 +6,85 @@ using Microsoft.CodeAnalysis; -namespace Analyzer.Utilities.Extensions +namespace Analyzer.Utilities.Extensions; + +internal static class ITypeSymbolExtensions { - internal static class ITypeSymbolExtensions + public static bool IsAssignableTo( + [NotNullWhen(returnValue: true)] this ITypeSymbol? fromSymbol, + [NotNullWhen(returnValue: true)] ITypeSymbol? toSymbol, + Compilation compilation) + => fromSymbol != null && toSymbol != null && compilation.ClassifyCommonConversion(fromSymbol, toSymbol).IsImplicit; + + public static bool Inherits([NotNullWhen(returnValue: true)] this ITypeSymbol? type, [NotNullWhen(returnValue: true)] ITypeSymbol? possibleBase) { - public static bool IsAssignableTo( - [NotNullWhen(returnValue: true)] this ITypeSymbol? fromSymbol, - [NotNullWhen(returnValue: true)] ITypeSymbol? toSymbol, - Compilation compilation) - => fromSymbol != null && toSymbol != null && compilation.ClassifyCommonConversion(fromSymbol, toSymbol).IsImplicit; + if (type == null || possibleBase == null) + { + return false; + } - public static bool Inherits([NotNullWhen(returnValue: true)] this ITypeSymbol? type, [NotNullWhen(returnValue: true)] ITypeSymbol? possibleBase) + switch (possibleBase.TypeKind) { - if (type == null || possibleBase == null) - { - return false; - } + case TypeKind.Class: + if (type.TypeKind == TypeKind.Interface) + { + return false; + } - switch (possibleBase.TypeKind) - { - case TypeKind.Class: - if (type.TypeKind == TypeKind.Interface) - { - return false; - } + return DerivesFrom(type, possibleBase, baseTypesOnly: true); - return DerivesFrom(type, possibleBase, baseTypesOnly: true); + case TypeKind.Interface: + return DerivesFrom(type, possibleBase); - case TypeKind.Interface: - return DerivesFrom(type, possibleBase); + default: + return false; + } + } - default: - return false; - } + public static bool DerivesFrom([NotNullWhen(returnValue: true)] this ITypeSymbol? symbol, [NotNullWhen(returnValue: true)] ITypeSymbol? candidateBaseType, bool baseTypesOnly = false, bool checkTypeParameterConstraints = true) + { + if (candidateBaseType == null || symbol == null) + { + return false; } - public static bool DerivesFrom([NotNullWhen(returnValue: true)] this ITypeSymbol? symbol, [NotNullWhen(returnValue: true)] ITypeSymbol? candidateBaseType, bool baseTypesOnly = false, bool checkTypeParameterConstraints = true) + if (!baseTypesOnly && candidateBaseType.TypeKind == TypeKind.Interface) { - if (candidateBaseType == null || symbol == null) + IEnumerable allInterfaces = symbol.AllInterfaces.OfType(); + if (SymbolEqualityComparer.Default.Equals(candidateBaseType.OriginalDefinition, candidateBaseType)) { - return false; + // Candidate base type is not a constructed generic type, so use original definition for interfaces. + allInterfaces = allInterfaces.Select(i => i.OriginalDefinition); } - if (!baseTypesOnly && candidateBaseType.TypeKind == TypeKind.Interface) + if (allInterfaces.Contains(candidateBaseType, SymbolEqualityComparer.Default)) { - IEnumerable allInterfaces = symbol.AllInterfaces.OfType(); - if (SymbolEqualityComparer.Default.Equals(candidateBaseType.OriginalDefinition, candidateBaseType)) - { - // Candidate base type is not a constructed generic type, so use original definition for interfaces. - allInterfaces = allInterfaces.Select(i => i.OriginalDefinition); - } - - if (allInterfaces.Contains(candidateBaseType, SymbolEqualityComparer.Default)) - { - return true; - } + return true; } + } - if (checkTypeParameterConstraints && symbol.TypeKind == TypeKind.TypeParameter) + if (checkTypeParameterConstraints && symbol.TypeKind == TypeKind.TypeParameter) + { + var typeParameterSymbol = (ITypeParameterSymbol)symbol; + foreach (ITypeSymbol constraintType in typeParameterSymbol.ConstraintTypes) { - var typeParameterSymbol = (ITypeParameterSymbol)symbol; - foreach (ITypeSymbol constraintType in typeParameterSymbol.ConstraintTypes) + if (constraintType.DerivesFrom(candidateBaseType, baseTypesOnly, checkTypeParameterConstraints)) { - if (constraintType.DerivesFrom(candidateBaseType, baseTypesOnly, checkTypeParameterConstraints)) - { - return true; - } + return true; } } + } - while (symbol != null) + while (symbol != null) + { + if (SymbolEqualityComparer.Default.Equals(symbol, candidateBaseType)) { - if (SymbolEqualityComparer.Default.Equals(symbol, candidateBaseType)) - { - return true; - } - - symbol = symbol.BaseType; + return true; } - return false; + symbol = symbol.BaseType; } + + return false; } } diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ObjectPool.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ObjectPool.cs index 81e9291e92..6a3a7e88aa 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ObjectPool.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ObjectPool.cs @@ -16,269 +16,268 @@ using System.Runtime.CompilerServices; #endif -namespace Analyzer.Utilities.PooledObjects +namespace Analyzer.Utilities.PooledObjects; + +/// +/// Generic implementation of object pooling pattern with predefined pool size limit. The main +/// purpose is that limited number of frequently used objects can be kept in the pool for +/// further recycling. +/// +/// Notes: +/// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there +/// is no space in the pool, extra returned objects will be dropped. +/// +/// 2) it is implied that if object was obtained from a pool, the caller will return it back in +/// a relatively short time. Keeping checked out objects for long durations is ok, but +/// reduces usefulness of pooling. Just new up your own. +/// +/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. +/// Rationale: +/// If there is no intent for reusing the object, do not use pool - just use "new". +/// +internal sealed class ObjectPool where T : class { - /// - /// Generic implementation of object pooling pattern with predefined pool size limit. The main - /// purpose is that limited number of frequently used objects can be kept in the pool for - /// further recycling. - /// - /// Notes: - /// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there - /// is no space in the pool, extra returned objects will be dropped. - /// - /// 2) it is implied that if object was obtained from a pool, the caller will return it back in - /// a relatively short time. Keeping checked out objects for long durations is ok, but - /// reduces usefulness of pooling. Just new up your own. - /// - /// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. - /// Rationale: - /// If there is no intent for reusing the object, do not use pool - just use "new". - /// - internal sealed class ObjectPool where T : class - { - [DebuggerDisplay("{Value,nq}")] + [DebuggerDisplay("{Value,nq}")] #pragma warning disable CA1815 // Override equals and operator equals on value types #pragma warning disable CA1051 // Do not declare visible instance fields - private struct Element - { - internal T? Value; - } + private struct Element + { + internal T? Value; + } #pragma warning restore CA1051 // Do not declare visible instance fields #pragma warning restore CA1815 // Override equals and operator equals on value types - /// - /// Not using System.Func{T} because this file is linked into the (debugger) Formatter, - /// which does not have that type (since it compiles against .NET 2.0). - /// - internal delegate T Factory(); + /// + /// Not using System.Func{T} because this file is linked into the (debugger) Formatter, + /// which does not have that type (since it compiles against .NET 2.0). + /// + internal delegate T Factory(); - // Storage for the pool objects. The first item is stored in a dedicated field because we - // expect to be able to satisfy most requests from it. - private T? _firstItem; - private readonly Element[] _items; + // Storage for the pool objects. The first item is stored in a dedicated field because we + // expect to be able to satisfy most requests from it. + private T? _firstItem; + private readonly Element[] _items; - // factory is stored for the lifetime of the pool. We will call this only when pool needs to - // expand. compared to "new T()", Func gives more flexibility to implementers and faster - // than "new T()". - private readonly Factory _factory; + // factory is stored for the lifetime of the pool. We will call this only when pool needs to + // expand. compared to "new T()", Func gives more flexibility to implementers and faster + // than "new T()". + private readonly Factory _factory; #if DETECT_LEAKS - private static readonly ConditionalWeakTable leakTrackers = new ConditionalWeakTable(); - - private class LeakTracker : IDisposable - { - private volatile bool disposed; - + private static readonly ConditionalWeakTable leakTrackers = new ConditionalWeakTable(); + + private class LeakTracker : IDisposable + { + private volatile bool disposed; + #if TRACE_LEAKS - internal volatile object Trace = null; + internal volatile object Trace = null; #endif - public void Dispose() - { - disposed = true; - GC.SuppressFinalize(this); - } - - private string GetTrace() - { + public void Dispose() + { + disposed = true; + GC.SuppressFinalize(this); + } + + private string GetTrace() + { #if TRACE_LEAKS - return Trace == null ? "" : Trace.ToString(); + return Trace == null ? "" : Trace.ToString(); #else - return "Leak tracing information is disabled. Define TRACE_LEAKS on ObjectPool`1.cs to get more info \n"; + return "Leak tracing information is disabled. Define TRACE_LEAKS on ObjectPool`1.cs to get more info \n"; #endif - } - - ~LeakTracker() + } + + ~LeakTracker() + { + if (!this.disposed && !Environment.HasShutdownStarted) { - if (!this.disposed && !Environment.HasShutdownStarted) - { - var trace = GetTrace(); - - // If you are seeing this message it means that object has been allocated from the pool - // and has not been returned back. This is not critical, but turns pool into rather - // inefficient kind of "new". - Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nPool detected potential leaking of {typeof(T)}. \n Location of the leak: \n {GetTrace()} TRACEOBJECTPOOLLEAKS_END"); - } + var trace = GetTrace(); + + // If you are seeing this message it means that object has been allocated from the pool + // and has not been returned back. This is not critical, but turns pool into rather + // inefficient kind of "new". + Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nPool detected potential leaking of {typeof(T)}. \n Location of the leak: \n {GetTrace()} TRACEOBJECTPOOLLEAKS_END"); } } + } #endif - internal ObjectPool(Factory factory) - : this(factory, Environment.ProcessorCount * 2) - { } + internal ObjectPool(Factory factory) + : this(factory, Environment.ProcessorCount * 2) + { } - internal ObjectPool(Factory factory, int size) - { - Debug.Assert(size >= 1); - _factory = factory; - _items = new Element[size - 1]; - } + internal ObjectPool(Factory factory, int size) + { + Debug.Assert(size >= 1); + _factory = factory; + _items = new Element[size - 1]; + } - private T CreateInstance() + private T CreateInstance() + { + T inst = _factory(); + return inst; + } + + /// + /// Produces an instance. + /// + /// + /// Search strategy is a simple linear probing which is chosen for it cache-friendliness. + /// Note that Free will try to store recycled objects close to the start thus statistically + /// reducing how far we will typically search. + /// + internal T Allocate() + { + // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements. + // Note that the initial read is optimistically not synchronized. That is intentional. + // We will interlock only when we have a candidate. in a worst case we may miss some + // recently returned objects. Not a big deal. + T? inst = _firstItem; + if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst)) { - T inst = _factory(); - return inst; + inst = AllocateSlow(); } - /// - /// Produces an instance. - /// - /// - /// Search strategy is a simple linear probing which is chosen for it cache-friendliness. - /// Note that Free will try to store recycled objects close to the start thus statistically - /// reducing how far we will typically search. - /// - internal T Allocate() +#if DETECT_LEAKS + var tracker = new LeakTracker(); + leakTrackers.Add(inst, tracker); + +#if TRACE_LEAKS + var frame = CaptureStackTrace(); + tracker.Trace = frame; +#endif +#endif + return inst; + } + + private T AllocateSlow() + { + Element[] items = _items; + + for (int i = 0; i < items.Length; i++) { - // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements. // Note that the initial read is optimistically not synchronized. That is intentional. // We will interlock only when we have a candidate. in a worst case we may miss some // recently returned objects. Not a big deal. - T? inst = _firstItem; - if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst)) + T? inst = items[i].Value; + if (inst != null && + inst == Interlocked.CompareExchange(ref items[i].Value, null, inst)) { - inst = AllocateSlow(); + return inst; } - -#if DETECT_LEAKS - var tracker = new LeakTracker(); - leakTrackers.Add(inst, tracker); - -#if TRACE_LEAKS - var frame = CaptureStackTrace(); - tracker.Trace = frame; -#endif -#endif - return inst; } - private T AllocateSlow() + return CreateInstance(); + } + + /// + /// Returns objects to the pool. + /// + /// + /// Search strategy is a simple linear probing which is chosen for it cache-friendliness. + /// Note that Free will try to store recycled objects close to the start thus statistically + /// reducing how far we will typically search in Allocate. + /// + internal void Free(T obj, CancellationToken cancellationToken) + { + // Do not free in presence of cancellation. + // See https://github.com/dotnet/roslyn/issues/46859 for details. + if (cancellationToken.IsCancellationRequested) { - Element[] items = _items; + return; + } - for (int i = 0; i < items.Length; i++) - { - // Note that the initial read is optimistically not synchronized. That is intentional. - // We will interlock only when we have a candidate. in a worst case we may miss some - // recently returned objects. Not a big deal. - T? inst = items[i].Value; - if (inst != null && - inst == Interlocked.CompareExchange(ref items[i].Value, null, inst)) - { - return inst; - } - } + Validate(obj); + ForgetTrackedObject(obj); - return CreateInstance(); + if (_firstItem == null) + { + // Intentionally not using interlocked here. + // In a worst case scenario two objects may be stored into same slot. + // It is very unlikely to happen and will only mean that one of the objects will get collected. + _firstItem = obj; } - - /// - /// Returns objects to the pool. - /// - /// - /// Search strategy is a simple linear probing which is chosen for it cache-friendliness. - /// Note that Free will try to store recycled objects close to the start thus statistically - /// reducing how far we will typically search in Allocate. - /// - internal void Free(T obj, CancellationToken cancellationToken) + else { - // Do not free in presence of cancellation. - // See https://github.com/dotnet/roslyn/issues/46859 for details. - if (cancellationToken.IsCancellationRequested) - { - return; - } - - Validate(obj); - ForgetTrackedObject(obj); + FreeSlow(obj); + } + } - if (_firstItem == null) + private void FreeSlow(T obj) + { + Element[] items = _items; + for (int i = 0; i < items.Length; i++) + { + if (items[i].Value == null) { // Intentionally not using interlocked here. // In a worst case scenario two objects may be stored into same slot. // It is very unlikely to happen and will only mean that one of the objects will get collected. - _firstItem = obj; - } - else - { - FreeSlow(obj); + items[i].Value = obj; + break; } } + } - private void FreeSlow(T obj) + /// + /// Removes an object from leak tracking. + /// + /// This is called when an object is returned to the pool. It may also be explicitly + /// called if an object allocated from the pool is intentionally not being returned + /// to the pool. This can be of use with pooled arrays if the consumer wants to + /// return a larger array to the pool than was originally allocated. + /// + [Conditional("DEBUG")] + internal static void ForgetTrackedObject(T old, T? replacement = null) + { +#if DETECT_LEAKS + LeakTracker tracker; + if (leakTrackers.TryGetValue(old, out tracker)) { - Element[] items = _items; - for (int i = 0; i < items.Length; i++) - { - if (items[i].Value == null) - { - // Intentionally not using interlocked here. - // In a worst case scenario two objects may be stored into same slot. - // It is very unlikely to happen and will only mean that one of the objects will get collected. - items[i].Value = obj; - break; - } - } + tracker.Dispose(); + leakTrackers.Remove(old); } - - /// - /// Removes an object from leak tracking. - /// - /// This is called when an object is returned to the pool. It may also be explicitly - /// called if an object allocated from the pool is intentionally not being returned - /// to the pool. This can be of use with pooled arrays if the consumer wants to - /// return a larger array to the pool than was originally allocated. - /// - [Conditional("DEBUG")] - internal static void ForgetTrackedObject(T old, T? replacement = null) + else { -#if DETECT_LEAKS - LeakTracker tracker; - if (leakTrackers.TryGetValue(old, out tracker)) - { - tracker.Dispose(); - leakTrackers.Remove(old); - } - else - { - var trace = CaptureStackTrace(); - Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nObject of type {typeof(T)} was freed, but was not from pool. \n Callstack: \n {trace} TRACEOBJECTPOOLLEAKS_END"); - } - - if (replacement != null) - { - tracker = new LeakTracker(); - leakTrackers.Add(replacement, tracker); - } -#endif + var trace = CaptureStackTrace(); + Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nObject of type {typeof(T)} was freed, but was not from pool. \n Callstack: \n {trace} TRACEOBJECTPOOLLEAKS_END"); } -#if DETECT_LEAKS - private static Lazy _stackTraceType = new Lazy(() => Type.GetType("System.Diagnostics.StackTrace")); - - private static object CaptureStackTrace() + if (replacement != null) { - return Activator.CreateInstance(_stackTraceType.Value); + tracker = new LeakTracker(); + leakTrackers.Add(replacement, tracker); } #endif + } - [Conditional("DEBUG")] - private void Validate(object obj) - { - Debug.Assert(_firstItem != obj, "freeing twice?"); +#if DETECT_LEAKS + private static Lazy _stackTraceType = new Lazy(() => Type.GetType("System.Diagnostics.StackTrace")); - Element[] items = _items; - for (int i = 0; i < items.Length; i++) - { - T? value = items[i].Value; - if (value == null) - { - return; - } + private static object CaptureStackTrace() + { + return Activator.CreateInstance(_stackTraceType.Value); + } +#endif - Debug.Assert(value != obj, "freeing twice?"); + [Conditional("DEBUG")] + private void Validate(object obj) + { + Debug.Assert(_firstItem != obj, "freeing twice?"); + + Element[] items = _items; + for (int i = 0; i < items.Length; i++) + { + T? value = items[i].Value; + if (value == null) + { + return; } + + Debug.Assert(value != obj, "freeing twice?"); } } } diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/PooledHashSet.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/PooledHashSet.cs index 0581b9e1c5..e35fb5a48f 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/PooledHashSet.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/PooledHashSet.cs @@ -6,85 +6,84 @@ #pragma warning disable CA1000 // Do not declare static members on generic types -namespace Analyzer.Utilities.PooledObjects +namespace Analyzer.Utilities.PooledObjects; + +// HashSet that can be recycled via an object pool +internal sealed class PooledHashSet : HashSet, IDisposable { - // HashSet that can be recycled via an object pool - internal sealed class PooledHashSet : HashSet, IDisposable + private readonly ObjectPool>? _pool; + + private PooledHashSet(ObjectPool>? pool, IEqualityComparer? comparer) + : base(comparer) { - private readonly ObjectPool>? _pool; + _pool = pool; + } - private PooledHashSet(ObjectPool>? pool, IEqualityComparer? comparer) - : base(comparer) + public void Dispose() => Free(CancellationToken.None); + + public void Free(CancellationToken cancellationToken) + { + // Do not free in presence of cancellation. + // See https://github.com/dotnet/roslyn/issues/46859 for details. + if (cancellationToken.IsCancellationRequested) { - _pool = pool; + return; } - public void Dispose() => Free(CancellationToken.None); + Clear(); + _pool?.Free(this, cancellationToken); + } - public void Free(CancellationToken cancellationToken) + public ImmutableHashSet ToImmutableAndFree() + { + ImmutableHashSet result; + if (Count == 0) { - // Do not free in presence of cancellation. - // See https://github.com/dotnet/roslyn/issues/46859 for details. - if (cancellationToken.IsCancellationRequested) - { - return; - } - - Clear(); - _pool?.Free(this, cancellationToken); + result = ImmutableHashSet.Empty; } - - public ImmutableHashSet ToImmutableAndFree() + else { - ImmutableHashSet result; - if (Count == 0) - { - result = ImmutableHashSet.Empty; - } - else - { - result = this.ToImmutableHashSet(Comparer); - Clear(); - } - - _pool?.Free(this, CancellationToken.None); - return result; + result = this.ToImmutableHashSet(Comparer); + Clear(); } - public ImmutableHashSet ToImmutable() - => Count == 0 ? ImmutableHashSet.Empty : this.ToImmutableHashSet(Comparer); + _pool?.Free(this, CancellationToken.None); + return result; + } + + public ImmutableHashSet ToImmutable() + => Count == 0 ? ImmutableHashSet.Empty : this.ToImmutableHashSet(Comparer); - // global pool - private static readonly ObjectPool> s_poolInstance = CreatePool(); - private static readonly ConcurrentDictionary, ObjectPool>> s_poolInstancesByComparer = new(); + // global pool + private static readonly ObjectPool> s_poolInstance = CreatePool(); + private static readonly ConcurrentDictionary, ObjectPool>> s_poolInstancesByComparer = new(); - // if someone needs to create a pool; - public static ObjectPool> CreatePool(IEqualityComparer? comparer = null) - { - ObjectPool>? pool = null; - pool = new ObjectPool>(() => new PooledHashSet(pool, comparer), 128); - return pool; - } + // if someone needs to create a pool; + public static ObjectPool> CreatePool(IEqualityComparer? comparer = null) + { + ObjectPool>? pool = null; + pool = new ObjectPool>(() => new PooledHashSet(pool, comparer), 128); + return pool; + } - public static PooledHashSet GetInstance(IEqualityComparer? comparer = null) - { - ObjectPool> pool = comparer == null ? - s_poolInstance : - s_poolInstancesByComparer.GetOrAdd(comparer, CreatePool); - PooledHashSet instance = pool.Allocate(); - Debug.Assert(instance.Count == 0); - return instance; - } + public static PooledHashSet GetInstance(IEqualityComparer? comparer = null) + { + ObjectPool> pool = comparer == null ? + s_poolInstance : + s_poolInstancesByComparer.GetOrAdd(comparer, CreatePool); + PooledHashSet instance = pool.Allocate(); + Debug.Assert(instance.Count == 0); + return instance; + } - public static PooledHashSet GetInstance(IEnumerable initializer, IEqualityComparer? comparer = null) + public static PooledHashSet GetInstance(IEnumerable initializer, IEqualityComparer? comparer = null) + { + PooledHashSet instance = GetInstance(comparer); + foreach (T? value in initializer) { - PooledHashSet instance = GetInstance(comparer); - foreach (T? value in initializer) - { - instance.Add(value); - } - - return instance; + instance.Add(value); } + + return instance; } } diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ReportDiagnosticExtensions.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ReportDiagnosticExtensions.cs index e418965bdb..d01b7011c7 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ReportDiagnosticExtensions.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ReportDiagnosticExtensions.cs @@ -1,51 +1,50 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. -namespace Microsoft.CodeAnalysis +namespace Microsoft.CodeAnalysis; + +internal static class ReportDiagnosticExtensions { - internal static class ReportDiagnosticExtensions + public static DiagnosticSeverity? ToDiagnosticSeverity(this ReportDiagnostic reportDiagnostic) => reportDiagnostic switch + { + ReportDiagnostic.Error => DiagnosticSeverity.Error, + ReportDiagnostic.Warn => DiagnosticSeverity.Warning, + ReportDiagnostic.Info => DiagnosticSeverity.Info, + ReportDiagnostic.Hidden => DiagnosticSeverity.Hidden, + ReportDiagnostic.Suppress => null, + ReportDiagnostic.Default => null, + _ => throw new NotImplementedException(), + }; + + public static bool IsLessSevereThan(this ReportDiagnostic current, ReportDiagnostic other) => current switch { - public static DiagnosticSeverity? ToDiagnosticSeverity(this ReportDiagnostic reportDiagnostic) => reportDiagnostic switch - { - ReportDiagnostic.Error => DiagnosticSeverity.Error, - ReportDiagnostic.Warn => DiagnosticSeverity.Warning, - ReportDiagnostic.Info => DiagnosticSeverity.Info, - ReportDiagnostic.Hidden => DiagnosticSeverity.Hidden, - ReportDiagnostic.Suppress => null, - ReportDiagnostic.Default => null, - _ => throw new NotImplementedException(), - }; - - public static bool IsLessSevereThan(this ReportDiagnostic current, ReportDiagnostic other) => current switch - { - ReportDiagnostic.Error => false, - - ReportDiagnostic.Warn => - other switch - { - ReportDiagnostic.Error => true, - _ => false - }, - - ReportDiagnostic.Info => - other switch - { - ReportDiagnostic.Error => true, - ReportDiagnostic.Warn => true, - _ => false - }, - - ReportDiagnostic.Hidden => - other switch - { - ReportDiagnostic.Error => true, - ReportDiagnostic.Warn => true, - ReportDiagnostic.Info => true, - _ => false - }, - - ReportDiagnostic.Suppress => true, - - _ => false - }; - } + ReportDiagnostic.Error => false, + + ReportDiagnostic.Warn => + other switch + { + ReportDiagnostic.Error => true, + _ => false + }, + + ReportDiagnostic.Info => + other switch + { + ReportDiagnostic.Error => true, + ReportDiagnostic.Warn => true, + _ => false + }, + + ReportDiagnostic.Hidden => + other switch + { + ReportDiagnostic.Error => true, + ReportDiagnostic.Warn => true, + ReportDiagnostic.Info => true, + _ => false + }, + + ReportDiagnostic.Suppress => true, + + _ => false + }; } diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/RoslynDebug.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/RoslynDebug.cs index 8488921e8a..8a70ae87aa 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/RoslynDebug.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/RoslynDebug.cs @@ -3,17 +3,16 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -namespace Analyzer.Utilities +namespace Analyzer.Utilities; + +internal static class RoslynDebug { - internal static class RoslynDebug - { - /// - [Conditional("DEBUG")] - public static void Assert([DoesNotReturnIf(false)] bool b) => Debug.Assert(b); + /// + [Conditional("DEBUG")] + public static void Assert([DoesNotReturnIf(false)] bool b) => Debug.Assert(b); - /// - [Conditional("DEBUG")] - public static void Assert([DoesNotReturnIf(false)] bool b, string message) - => Debug.Assert(b, message); - } + /// + [Conditional("DEBUG")] + public static void Assert([DoesNotReturnIf(false)] bool b, string message) + => Debug.Assert(b, message); } diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/SymbolVisibility.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/SymbolVisibility.cs index ca562cb046..f52ee30917 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/SymbolVisibility.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/SymbolVisibility.cs @@ -1,14 +1,13 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. -namespace Analyzer.Utilities.Extensions -{ +namespace Analyzer.Utilities.Extensions; + #pragma warning disable CA1027 // Mark enums with FlagsAttribute - internal enum SymbolVisibility +internal enum SymbolVisibility #pragma warning restore CA1027 // Mark enums with FlagsAttribute - { - Public = 0, - Internal = 1, - Private = 2, - Friend = Internal, - } +{ + Public = 0, + Internal = 1, + Private = 2, + Friend = Internal, } diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/WellKnownTypeProvider.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/WellKnownTypeProvider.cs index b994f452f5..088bd257ca 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/WellKnownTypeProvider.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/WellKnownTypeProvider.cs @@ -14,282 +14,281 @@ using Roslyn.Utilities; -namespace Analyzer.Utilities +namespace Analyzer.Utilities; + +/// +/// Provides and caches well known types in a compilation. +/// +public class WellKnownTypeProvider { + private static readonly BoundedCacheWithFactory s_providerCache = new(); + + private WellKnownTypeProvider(Compilation compilation) + { + Compilation = compilation; + _fullNameToTypeMap = new ConcurrentDictionary(); + _referencedAssemblies = new Lazy>( + () => Compilation.Assembly.Modules + .SelectMany(m => m.ReferencedAssemblySymbols) + .Distinct(SymbolEqualityComparer.Default) + .ToImmutableArray(), + LazyThreadSafetyMode.ExecutionAndPublication); + } + + public static WellKnownTypeProvider GetOrCreate(Compilation compilation) + { + return s_providerCache.GetOrCreateValue(compilation, CreateWellKnownTypeProvider); + + // Local functions + static WellKnownTypeProvider CreateWellKnownTypeProvider(Compilation compilation) => new(compilation); + } + + public Compilation Compilation { get; } + /// - /// Provides and caches well known types in a compilation. + /// All the referenced assembly symbols. /// - public class WellKnownTypeProvider - { - private static readonly BoundedCacheWithFactory s_providerCache = new(); + /// + /// Seems to be less memory intensive than: + /// foreach (Compilation.Assembly.Modules) + /// foreach (Module.ReferencedAssemblySymbols) + /// + private readonly Lazy> _referencedAssemblies; - private WellKnownTypeProvider(Compilation compilation) - { - Compilation = compilation; - _fullNameToTypeMap = new ConcurrentDictionary(); - _referencedAssemblies = new Lazy>( - () => Compilation.Assembly.Modules - .SelectMany(m => m.ReferencedAssemblySymbols) - .Distinct(SymbolEqualityComparer.Default) - .ToImmutableArray(), - LazyThreadSafetyMode.ExecutionAndPublication); - } + /// + /// Mapping of full name to . + /// + private readonly ConcurrentDictionary _fullNameToTypeMap; - public static WellKnownTypeProvider GetOrCreate(Compilation compilation) - { - return s_providerCache.GetOrCreateValue(compilation, CreateWellKnownTypeProvider); + /// + /// Static cache of full type names (with namespaces) to namespace name parts, + /// so we can query . + /// + /// + /// + /// Example: "System.Collections.Generic.List`1" => [ "System", "Collections", "Generic" ] + /// + /// https://github.com/dotnet/roslyn/blob/9e786147b8cb884af454db081bb747a5bd36a086/src/Compilers/CSharp/Portable/Symbols/AssemblySymbol.cs#L455 + /// suggests the TypeNames collection can be checked to avoid expensive operations. But realizing TypeNames seems to be + /// as memory intensive as unnecessary calls GetTypeByMetadataName() in some cases. So we'll go with namespace names. + /// + private static readonly ConcurrentDictionary> _fullTypeNameToNamespaceNames = new(); - // Local functions - static WellKnownTypeProvider CreateWellKnownTypeProvider(Compilation compilation) => new(compilation); + /// + /// Attempts to get the type by the full type name. + /// + /// Namespace + type name, e.g. "System.Exception". + /// Named type symbol, if any. + /// True if found in the compilation, false otherwise. + [PerformanceSensitive("https://github.com/dotnet/roslyn-analyzers/issues/4893", AllowCaptures = false)] + public bool TryGetOrCreateTypeByMetadataName( + string fullTypeName, + [NotNullWhen(returnValue: true)] out INamedTypeSymbol? namedTypeSymbol) + { + if (_fullNameToTypeMap.TryGetValue(fullTypeName, out namedTypeSymbol)) + { + return namedTypeSymbol is not null; } - public Compilation Compilation { get; } - - /// - /// All the referenced assembly symbols. - /// - /// - /// Seems to be less memory intensive than: - /// foreach (Compilation.Assembly.Modules) - /// foreach (Module.ReferencedAssemblySymbols) - /// - private readonly Lazy> _referencedAssemblies; - - /// - /// Mapping of full name to . - /// - private readonly ConcurrentDictionary _fullNameToTypeMap; - - /// - /// Static cache of full type names (with namespaces) to namespace name parts, - /// so we can query . - /// - /// - /// - /// Example: "System.Collections.Generic.List`1" => [ "System", "Collections", "Generic" ] - /// - /// https://github.com/dotnet/roslyn/blob/9e786147b8cb884af454db081bb747a5bd36a086/src/Compilers/CSharp/Portable/Symbols/AssemblySymbol.cs#L455 - /// suggests the TypeNames collection can be checked to avoid expensive operations. But realizing TypeNames seems to be - /// as memory intensive as unnecessary calls GetTypeByMetadataName() in some cases. So we'll go with namespace names. - /// - private static readonly ConcurrentDictionary> _fullTypeNameToNamespaceNames = new(); - - /// - /// Attempts to get the type by the full type name. - /// - /// Namespace + type name, e.g. "System.Exception". - /// Named type symbol, if any. - /// True if found in the compilation, false otherwise. - [PerformanceSensitive("https://github.com/dotnet/roslyn-analyzers/issues/4893", AllowCaptures = false)] - public bool TryGetOrCreateTypeByMetadataName( - string fullTypeName, - [NotNullWhen(returnValue: true)] out INamedTypeSymbol? namedTypeSymbol) - { - if (_fullNameToTypeMap.TryGetValue(fullTypeName, out namedTypeSymbol)) + return TryGetOrCreateTypeByMetadataNameSlow(fullTypeName, out namedTypeSymbol); + } + + private bool TryGetOrCreateTypeByMetadataNameSlow( + string fullTypeName, + [NotNullWhen(returnValue: true)] out INamedTypeSymbol? namedTypeSymbol) + { + namedTypeSymbol = _fullNameToTypeMap.GetOrAdd( + fullTypeName, + fullyQualifiedMetadataName => { - return namedTypeSymbol is not null; - } + // Caching null results is intended. - return TryGetOrCreateTypeByMetadataNameSlow(fullTypeName, out namedTypeSymbol); - } + // sharwell says: Suppose you reference assembly A with public API X.Y, and you reference assembly B with + // internal API X.Y. Even though you can use X.Y from assembly A, compilation.GetTypeByMetadataName will + // fail outright because it finds two types with the same name. - private bool TryGetOrCreateTypeByMetadataNameSlow( - string fullTypeName, - [NotNullWhen(returnValue: true)] out INamedTypeSymbol? namedTypeSymbol) - { - namedTypeSymbol = _fullNameToTypeMap.GetOrAdd( - fullTypeName, - fullyQualifiedMetadataName => - { - // Caching null results is intended. + INamedTypeSymbol? type = null; - // sharwell says: Suppose you reference assembly A with public API X.Y, and you reference assembly B with - // internal API X.Y. Even though you can use X.Y from assembly A, compilation.GetTypeByMetadataName will - // fail outright because it finds two types with the same name. + ImmutableArray namespaceNames; + // Assuming we're on .NET Standard 2.0 or later, cache the type names that are probably compile time constants. + if (string.IsInterned(fullTypeName) != null) + { + namespaceNames = _fullTypeNameToNamespaceNames.GetOrAdd( + fullTypeName, + GetNamespaceNamesFromFullTypeName); + } + else + { + namespaceNames = GetNamespaceNamesFromFullTypeName(fullTypeName); + } - INamedTypeSymbol? type = null; + if (IsSubsetOfCollection(namespaceNames, Compilation.Assembly.NamespaceNames)) + { + type = Compilation.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName); + } - ImmutableArray namespaceNames; - // Assuming we're on .NET Standard 2.0 or later, cache the type names that are probably compile time constants. - if (string.IsInterned(fullTypeName) != null) - { - namespaceNames = _fullTypeNameToNamespaceNames.GetOrAdd( - fullTypeName, - GetNamespaceNamesFromFullTypeName); - } - else - { - namespaceNames = GetNamespaceNamesFromFullTypeName(fullTypeName); - } + if (type is null) + { + RoslynDebug.Assert(namespaceNames != null); - if (IsSubsetOfCollection(namespaceNames, Compilation.Assembly.NamespaceNames)) + foreach (IAssemblySymbol? referencedAssembly in _referencedAssemblies.Value) { - type = Compilation.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName); - } + if (!IsSubsetOfCollection(namespaceNames, referencedAssembly.NamespaceNames)) + { + continue; + } - if (type is null) - { - RoslynDebug.Assert(namespaceNames != null); + INamedTypeSymbol? currentType = referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName); + if (currentType is null) + { + continue; + } - foreach (IAssemblySymbol? referencedAssembly in _referencedAssemblies.Value) + switch (currentType.GetResultantVisibility()) { - if (!IsSubsetOfCollection(namespaceNames, referencedAssembly.NamespaceNames)) - { - continue; - } + case SymbolVisibility.Public: + case SymbolVisibility.Internal when referencedAssembly.GivesAccessTo(Compilation.Assembly): + break; - INamedTypeSymbol? currentType = referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName); - if (currentType is null) - { + default: continue; - } + } - switch (currentType.GetResultantVisibility()) - { - case SymbolVisibility.Public: - case SymbolVisibility.Internal when referencedAssembly.GivesAccessTo(Compilation.Assembly): - break; + if (type is object) + { + // Multiple visible types with the same metadata name are present. + return null; + } - default: - continue; - } + type = currentType; + } + } - if (type is object) - { - // Multiple visible types with the same metadata name are present. - return null; - } + return type; + }); - type = currentType; - } - } + return namedTypeSymbol != null; + } - return type; - }); + /// + /// Gets a type by its full type name. + /// + /// Namespace + type name, e.g. "System.Exception". + /// The if found, null otherwise. + public INamedTypeSymbol? GetOrCreateTypeByMetadataName(string fullTypeName) + { + TryGetOrCreateTypeByMetadataName(fullTypeName, out INamedTypeSymbol? namedTypeSymbol); + return namedTypeSymbol; + } - return namedTypeSymbol != null; - } + /// + /// Determines if is a with its type + /// argument satisfying . + /// + /// Type potentially representing a . + /// Predicate to check the 's type argument. + /// True if is a with its + /// type argument satisfying , false otherwise. + internal bool IsTaskOfType([NotNullWhen(returnValue: true)] ITypeSymbol? typeSymbol, Func typeArgumentPredicate) => typeSymbol != null + && typeSymbol.OriginalDefinition != null + && SymbolEqualityComparer.Default.Equals(typeSymbol.OriginalDefinition, + GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask1)) + && typeSymbol is INamedTypeSymbol namedTypeSymbol + && namedTypeSymbol.TypeArguments.Length == 1 + && typeArgumentPredicate(namedTypeSymbol.TypeArguments[0]); + + private static ImmutableArray GetNamespaceNamesFromFullTypeName(string fullTypeName) + { + using var namespaceNamesBuilder = ArrayBuilder.GetInstance(); + RoslynDebug.Assert(namespaceNamesBuilder != null); - /// - /// Gets a type by its full type name. - /// - /// Namespace + type name, e.g. "System.Exception". - /// The if found, null otherwise. - public INamedTypeSymbol? GetOrCreateTypeByMetadataName(string fullTypeName) + int prevStartIndex = 0; + for (int i = 0; i < fullTypeName.Length; i++) { - TryGetOrCreateTypeByMetadataName(fullTypeName, out INamedTypeSymbol? namedTypeSymbol); - return namedTypeSymbol; + if (fullTypeName[i] == '.') + { + namespaceNamesBuilder.Add(fullTypeName[prevStartIndex..i]); + prevStartIndex = i + 1; + } + else if (!IsIdentifierPartCharacter(fullTypeName[i])) + { + break; + } } - /// - /// Determines if is a with its type - /// argument satisfying . - /// - /// Type potentially representing a . - /// Predicate to check the 's type argument. - /// True if is a with its - /// type argument satisfying , false otherwise. - internal bool IsTaskOfType([NotNullWhen(returnValue: true)] ITypeSymbol? typeSymbol, Func typeArgumentPredicate) => typeSymbol != null - && typeSymbol.OriginalDefinition != null - && SymbolEqualityComparer.Default.Equals(typeSymbol.OriginalDefinition, - GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask1)) - && typeSymbol is INamedTypeSymbol namedTypeSymbol - && namedTypeSymbol.TypeArguments.Length == 1 - && typeArgumentPredicate(namedTypeSymbol.TypeArguments[0]); - - private static ImmutableArray GetNamespaceNamesFromFullTypeName(string fullTypeName) - { - using var namespaceNamesBuilder = ArrayBuilder.GetInstance(); - RoslynDebug.Assert(namespaceNamesBuilder != null); + return namespaceNamesBuilder.ToImmutable(); + } - int prevStartIndex = 0; - for (int i = 0; i < fullTypeName.Length; i++) + /// + /// Returns true if the Unicode character can be a part of an identifier. + /// + /// The Unicode character. + private static bool IsIdentifierPartCharacter(char ch) + { + // identifier-part-character: + // letter-character + // decimal-digit-character + // connecting-character + // combining-character + // formatting-character + + if (ch < 'a') // '\u0061' + { + if (ch < 'A') // '\u0041' { - if (fullTypeName[i] == '.') - { - namespaceNamesBuilder.Add(fullTypeName[prevStartIndex..i]); - prevStartIndex = i + 1; - } - else if (!IsIdentifierPartCharacter(fullTypeName[i])) - { - break; - } + return ch is >= '0' // '\u0030' + and <= '9'; // '\u0039' } - return namespaceNamesBuilder.ToImmutable(); + return ch is <= 'Z' // '\u005A' + or '_'; // '\u005F' } - /// - /// Returns true if the Unicode character can be a part of an identifier. - /// - /// The Unicode character. - private static bool IsIdentifierPartCharacter(char ch) + if (ch <= 'z') // '\u007A' { - // identifier-part-character: - // letter-character - // decimal-digit-character - // connecting-character - // combining-character - // formatting-character - - if (ch < 'a') // '\u0061' - { - if (ch < 'A') // '\u0041' - { - return ch is >= '0' // '\u0030' - and <= '9'; // '\u0039' - } - - return ch is <= 'Z' // '\u005A' - or '_'; // '\u005F' - } + return true; + } - if (ch <= 'z') // '\u007A' - { - return true; - } + if (ch <= '\u007F') // max ASCII + { + return false; + } - if (ch <= '\u007F') // max ASCII - { - return false; - } + UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory(ch); - UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory(ch); + return cat switch + { + // Letter + UnicodeCategory.UppercaseLetter + or UnicodeCategory.LowercaseLetter + or UnicodeCategory.TitlecaseLetter + or UnicodeCategory.ModifierLetter + or UnicodeCategory.OtherLetter + or UnicodeCategory.LetterNumber + or UnicodeCategory.DecimalDigitNumber + or UnicodeCategory.ConnectorPunctuation + or UnicodeCategory.NonSpacingMark + or UnicodeCategory.SpacingCombiningMark + or UnicodeCategory.Format => true, + _ => false, + }; + } - return cat switch - { - // Letter - UnicodeCategory.UppercaseLetter - or UnicodeCategory.LowercaseLetter - or UnicodeCategory.TitlecaseLetter - or UnicodeCategory.ModifierLetter - or UnicodeCategory.OtherLetter - or UnicodeCategory.LetterNumber - or UnicodeCategory.DecimalDigitNumber - or UnicodeCategory.ConnectorPunctuation - or UnicodeCategory.NonSpacingMark - or UnicodeCategory.SpacingCombiningMark - or UnicodeCategory.Format => true, - _ => false, - }; + private static bool IsSubsetOfCollection(ImmutableArray set1, ICollection set2) + { + if (set1.Length > set2.Count) + { + return false; } - private static bool IsSubsetOfCollection(ImmutableArray set1, ICollection set2) + for (int i = 0; i < set1.Length; i++) { - if (set1.Length > set2.Count) + if (!set2.Contains(set1[i])) { return false; } - - for (int i = 0; i < set1.Length; i++) - { - if (!set2.Contains(set1[i])) - { - return false; - } - } - - return true; } + + return true; } } diff --git a/src/TestFramework/TestFramework/Assertions/Assert.IsInstanceOfType.cs b/src/TestFramework/TestFramework/Assertions/Assert.IsInstanceOfType.cs index e2d7cf5cef..e069e966b6 100644 --- a/src/TestFramework/TestFramework/Assertions/Assert.IsInstanceOfType.cs +++ b/src/TestFramework/TestFramework/Assertions/Assert.IsInstanceOfType.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Diagnostics.CodeAnalysis; @@ -157,7 +157,9 @@ public static void IsInstanceOfType([NotNull] object? value, [StringSyntax(St /// The expected type of . public static void IsInstanceOfType([NotNull] object? value, out T instance, [StringSyntax(StringSyntaxAttribute.CompositeFormat)] string? message, params object?[]? parameters) { +#pragma warning disable CA2263 // Prefer generic overload when type is known IsInstanceOfType(value, typeof(T), message, parameters); +#pragma warning restore CA2263 // Prefer generic overload when type is known instance = (T)value; } diff --git a/test/Utilities/Microsoft.Testing.TestInfrastructure/CommandLine.cs b/test/Utilities/Microsoft.Testing.TestInfrastructure/CommandLine.cs index 318ee7c15b..5e2cddb931 100644 --- a/test/Utilities/Microsoft.Testing.TestInfrastructure/CommandLine.cs +++ b/test/Utilities/Microsoft.Testing.TestInfrastructure/CommandLine.cs @@ -121,7 +121,7 @@ private static string ClearBOM(string outputLine) { int firstChar = outputLine[0]; int byteOrderMark = 65279; - return firstChar == byteOrderMark ? outputLine.Substring(1) : outputLine; + return firstChar == byteOrderMark ? outputLine[1..] : outputLine; } public void Dispose() => _process?.Kill();