-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor Moq1300 to use IOperation-based analysis (#125)
Refactor Moq1300 to use `IOperation`-based analysis instead of syntax analysis. This method results in a slightly less specific diagnostic (it uses the location of the entire `.As<T>()` method instead of only the type parameter, however it has several benefits: 1. About double the performance / speed 2. Cuts the memory allocations in half 3. I believe a bit more robust to "interesting" syntax trees Here's a BenchmarkDotNet run comparing the old and new analyzers: ``` | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | |-------------------------- |---------:|--------:|---------:|------:|--------:|----------:|----------:|----------:|----------:|------------:| | OldMoq1300WithDiagnostics | 204.2 ms | 5.01 ms | 14.44 ms | 1.72 | 0.13 | 8000.0000 | 4000.0000 | 1000.0000 | 69.35 MB | 1.36 | | NewMoq1300WithDiagnostics | 162.3 ms | 3.21 ms | 3.15 ms | 1.38 | 0.06 | 6000.0000 | 2000.0000 | - | 62.42 MB | 1.22 | | Moq1300Baseline | 118.0 ms | 2.35 ms | 5.12 ms | 1.00 | 0.00 | 5000.0000 | 2000.0000 | - | 50.97 MB | 1.00 | ```
- Loading branch information
1 parent
be45e11
commit 2dcad69
Showing
9 changed files
with
178 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using Microsoft.CodeAnalysis.Operations; | ||
|
||
namespace Moq.Analyzers; | ||
|
||
internal static class CompilationExtensions | ||
{ | ||
/// <summary> | ||
/// An extension method that performs <see cref="Compilation.GetTypeByMetadataName(string)"/> for multiple metadata names. | ||
/// </summary> | ||
/// <param name="compilation">The <see cref="Compilation"/> to inspect.</param> | ||
/// <param name="metadataNames">A list of type names to query.</param> | ||
/// <returns><see langword="null"/> if the type can't be found or there was an ambiguity during lookup.</returns> | ||
public static ImmutableArray<INamedTypeSymbol> GetTypesByMetadataNames(this Compilation compilation, ReadOnlySpan<string> metadataNames) | ||
{ | ||
ImmutableArray<INamedTypeSymbol>.Builder builder = ImmutableArray.CreateBuilder<INamedTypeSymbol>(metadataNames.Length); | ||
|
||
foreach (string metadataName in metadataNames) | ||
{ | ||
INamedTypeSymbol? type = compilation.GetTypeByMetadataName(metadataName); | ||
if (type is not null) | ||
{ | ||
builder.Add(type); | ||
} | ||
} | ||
|
||
return builder.ToImmutable(); | ||
} | ||
|
||
/// <summary> | ||
/// Get the Moq.Mock and Moq.Mock`1 type symbols (if part of the compilation). | ||
/// </summary> | ||
/// <param name="compilation">The <see cref="Compilation"/> to inspect.</param> | ||
/// <returns> | ||
/// <see cref="INamedTypeSymbol"/>s for the Moq.Mock symbols that are part of the compilation. | ||
/// An empty array if none (never <see langword="null"/>). | ||
/// </returns> | ||
public static ImmutableArray<INamedTypeSymbol> GetMoqMock(this Compilation compilation) | ||
{ | ||
return compilation.GetTypesByMetadataNames([WellKnownTypeNames.MoqMock, WellKnownTypeNames.MoqMock1]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
namespace Moq.Analyzers; | ||
|
||
internal static class EnumerableExtensions | ||
{ | ||
/// <inheritdoc cref="DefaultIfNotSingle{TSource}(IEnumerable{TSource}, Func{TSource, bool})"/> | ||
public static TSource? DefaultIfNotSingle<TSource>(this IEnumerable<TSource> source) | ||
{ | ||
return source.DefaultIfNotSingle(_ => true); | ||
} | ||
|
||
/// <summary> | ||
/// Returns the only element of a sequence that satisfies a specified condition or default if no such element exists or more than one element satisfies the condition. | ||
/// </summary> | ||
/// <typeparam name="TSource">The type of the <paramref name="source"/> collection.</typeparam> | ||
/// <param name="source">The collection to enumerate.</param> | ||
/// <param name="predicate">A function to test each element for a condition.</param> | ||
/// <returns> | ||
/// The single element that satisfies the condition, or default if no such element exists or more than one element satisfies the condition. | ||
/// </returns> | ||
/// <remarks> | ||
/// This should be equivalent to calling <see cref="Enumerable.SingleOrDefault{TSource}(IEnumerable{TSource}, Func{TSource, bool})"/> | ||
/// combined with a catch that returns <see langword="null"/>. | ||
/// </remarks> | ||
public static TSource? DefaultIfNotSingle<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) | ||
{ | ||
bool isFound = false; | ||
TSource? item = default; | ||
|
||
foreach (TSource element in source.Where(predicate)) | ||
{ | ||
if (isFound) | ||
{ | ||
// We already found an element, thus there's multiple matches; return default. | ||
return default; | ||
} | ||
|
||
isFound = true; | ||
item = element; | ||
} | ||
|
||
return item; | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
namespace Moq.Analyzers; | ||
|
||
internal static class SymbolExtensions | ||
{ | ||
/// <summary> | ||
/// Determines whether the symbol is an instance of the specified symbol. | ||
/// </summary> | ||
/// <typeparam name="TSymbol">The type of the <see cref="ISymbol"/> to compare.</typeparam> | ||
/// <param name="symbol">The symbol to compare.</param> | ||
/// <param name="other">The symbol to compare to.</param> | ||
/// <param name="symbolEqualityComparer">The <see cref="SymbolEqualityComparer"/> to use for equality.</param> | ||
/// <returns> | ||
/// <see langword="true"/> if <paramref name="symbol"/> is an instance of <paramref name="other"/>, either as a direct match, | ||
/// or as a specialaization; otherwise, <see langword="false"/>. | ||
/// </returns> | ||
/// <remarks> | ||
/// As an example, <c>Type.Method<int>()</c> is an instance of <c>Type.Method<T>()</c>. | ||
/// </remarks> | ||
public static bool IsInstanceOf<TSymbol>(this ISymbol symbol, TSymbol other, SymbolEqualityComparer? symbolEqualityComparer = null) | ||
where TSymbol : class, ISymbol | ||
{ | ||
symbolEqualityComparer ??= SymbolEqualityComparer.Default; | ||
|
||
return symbol switch | ||
{ | ||
IMethodSymbol methodSymbol => symbolEqualityComparer.Equals(methodSymbol.OriginalDefinition, other), | ||
_ => symbolEqualityComparer.Equals(symbol, other), | ||
}; | ||
} | ||
|
||
/// <inheritdoc cref="IsInstanceOf{TSymbol}(ISymbol, TSymbol, SymbolEqualityComparer?)"/> | ||
/// <param name="symbol">The symbol to compare.</param> | ||
/// <param name="others"> | ||
/// The symbols to compare to. Returns <see langword="true"/> if <paramref name="symbol"/> matches any of others. | ||
/// </param> | ||
/// <param name="symbolEqualityComparer">The <see cref="SymbolEqualityComparer"/> to use for equality.</param> | ||
public static bool IsInstanceOf<TSymbol>(this ISymbol symbol, IEnumerable<TSymbol> others, SymbolEqualityComparer? symbolEqualityComparer = null) | ||
where TSymbol : class, ISymbol | ||
{ | ||
symbolEqualityComparer ??= SymbolEqualityComparer.Default; | ||
|
||
return others.Any(other => symbol.IsInstanceOf(other, symbolEqualityComparer)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace Moq.Analyzers; | ||
|
||
internal static class WellKnownTypeNames | ||
{ | ||
public const string MoqMock = "Moq.Mock"; | ||
public const string MoqMock1 = "Moq.Mock`1"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters