New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
KnownReference: Add support for testing for referenced libraries #6726
Conversation
analyzers/src/SonarAnalyzer.Common/Helpers/KnownReference.Predicates.cs
Outdated
Show resolved
Hide resolved
internal static Func<AssemblyIdentity, bool> OptionalPublicKeyTokenIs(string key) => | ||
x => !x.HasPublicKey || PublicKeyEqualHex(x, key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for cases like StackExchange.Redis which comes with a strong name or without (others are e.g. Dapper or NitoAsyncEx).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we'll ever need to be that precise.
internal static Func<AssemblyIdentity, bool> OptionalPublicKeyTokenIs(string key) => | |
x => !x.HasPublicKey || PublicKeyEqualHex(x, key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First round, I didn't look into the UTs yet as things will change a bit. I asked for some simplifications
analyzers/src/SonarAnalyzer.Common/Helpers/KnownReference.Predicates.cs
Outdated
Show resolved
Hide resolved
internal static Func<AssemblyIdentity, bool> StartsWith(string name) => | ||
x => x.Name.StartsWith(name); | ||
|
||
internal static Func<AssemblyIdentity, bool> EndsWith(string name) => | ||
x => x.Name.EndsWith(name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have a clear example in mind? Experience with KnownType shows that we typically exactly know what we're looking for.
internal static Func<AssemblyIdentity, bool> StartsWith(string name) => | |
x => x.Name.StartsWith(name); | |
internal static Func<AssemblyIdentity, bool> EndsWith(string name) => | |
x => x.Name.EndsWith(name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For StartsWith I was thinking of something like Azure.Storage, Azure.ResourceManager, AWSSDK or Microsoft.Extensions and for Contains something like IdentityModel.Tokens or SqlClient or Logger
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will typically need to work with a price KnownType to do this check. And that one comes from a precisely known assembly => we should not need these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are also checks like "IsTestProject" or "Is this targeting .Net Core" or "Is this a Web/WinForms/Console/WPF project" or "Is this a cloud app". These shortcuts can come in handy for such questions, especially for frameworks that have different implementations on different platforms like entity framework, SqlClient, or Asp.Net MVC.
internal static Func<AssemblyIdentity, bool> OptionalPublicKeyTokenIs(string key) => | ||
x => !x.HasPublicKey || PublicKeyEqualHex(x, key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we'll ever need to be that precise.
internal static Func<AssemblyIdentity, bool> OptionalPublicKeyTokenIs(string key) => | |
x => !x.HasPublicKey || PublicKeyEqualHex(x, key); |
internal static Func<AssemblyIdentity, bool> PublicKeyTokenIs(string key) => | ||
x => x.HasPublicKey && PublicKeyEqualHex(x, key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above
internal static Func<AssemblyIdentity, bool> PublicKeyTokenIs(string key) => | |
x => x.HasPublicKey && PublicKeyEqualHex(x, key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is a strong name it is the most reliable source of identity. Also if we don't add the possibility to check for the strong name no one will ever add the functionality even in cases where it would be useful. The functionality is now there and fully covered by tests with our own public key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to be that precise at all. We'll likely use these for an early bailout, as you plan to use them. And there will be no harm if we trigger on something that is a false match.
Let's remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also highly doubt that we would ever track a library that updates it's public key between versions.
analyzers/src/SonarAnalyzer.Common/Helpers/KnownReference.Predicates.cs
Outdated
Show resolved
Hide resolved
analyzers/src/SonarAnalyzer.Common/Helpers/KnownReferenceExtensions.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM to unblock the sprint.
I left further improvement comments.
Please also remove the methods from the previous review. We won't need them any time soon.
@@ -24,4 +24,17 @@ internal static class CompilationExtensions | |||
{ | |||
public static INamedTypeSymbol GetTypeByMetadataName(this Compilation compilation, KnownType knownType) => | |||
compilation.GetTypeByMetadataName(knownType.FullName); | |||
|
|||
public static IMethodSymbol GetTypeMethod(this Compilation compilation, SpecialType type, string methodName) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public static IMethodSymbol GetTypeMethod(this Compilation compilation, SpecialType type, string methodName) => | |
public static IMethodSymbol SpecialTypeMethod(this Compilation compilation, SpecialType type, string methodName) => |
(IMethodSymbol)compilation.GetSpecialType(type) | ||
.GetMembers(methodName) | ||
.SingleOrDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be a single line
public static bool IsNetFrameworkTarget(this Compilation compilation) => | ||
// There's no direct way of checking compilation target framework yet (09/2020). | ||
// See https://github.com/dotnet/roslyn/issues/3798 | ||
compilation.ObjectType.ContainingAssembly.Name == "mscorlib"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is somewhat overlapping with NetFrameworkVersionProvider
class
https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/src/SonarAnalyzer.Common/Helpers/NetFrameworkVersionProvider.cs
While this extension can live here, the implementation should be in that class to have this kind of ugly detection should live on one place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementations can not easily be merged. I created #6751
NameIs("xunit.assert"), | ||
NameIs("xunit").And(VersionLowerThen("2.0"))); | ||
|
||
internal KnownReference(Func<AssemblyIdentity, bool> predicate, params Func<AssemblyIdentity, bool>[] or) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will simplify it quite a lot. As this is supposed to be instantiated only from within this class (and therefore it would be beneficial to have it private
), we don't need to enforce it syntactically. You can throw in case it's empty
internal KnownReference(Func<AssemblyIdentity, bool> predicate, params Func<AssemblyIdentity, bool>[] or) | |
internal KnownReference(params Func<AssemblyIdentity, bool>[] orPredicates) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be a bad API design. We can enforce proper usage at compile time and should do so.
|
||
using static SonarAnalyzer.Helpers.KnownReference.Predicates; | ||
|
||
namespace SonarAnalyzer.Helpers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be file scoped namespace
using static SonarAnalyzer.Helpers.KnownReference; | ||
using static SonarAnalyzer.Helpers.KnownReference.Predicates; | ||
|
||
namespace SonarAnalyzer.UnitTest; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thsi file should live in Helpers
directory, same as the class it tests.
And have .Helpers
namespace
namespace SonarAnalyzer.UnitTest; | |
namespace SonarAnalyzer.UnitTest.Helpers; |
var compilation = new Mock<Compilation>("compilationName", ImmutableArray<MetadataReference>.Empty, new Dictionary<string, string>(), false, null, null); | ||
var identity = new AssemblyIdentity(name); | ||
compilation.SetupGet(x => x.ReferencedAssemblyNames).Returns(new[] { identity }); | ||
sut.IsReferencedBy(compilation.Object).Should().Be(expected); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's extract the repetitive scaffodling into a static method
var compilation = new Mock<Compilation>("compilationName", ImmutableArray<MetadataReference>.Empty, new Dictionary<string, string>(), false, null, null); | |
var identity = new AssemblyIdentity(name); | |
compilation.SetupGet(x => x.ReferencedAssemblyNames).Returns(new[] { identity }); | |
sut.IsReferencedBy(compilation.Object).Should().Be(expected); | |
sut.IsReferencedBy(CreateCompilation(name)).Should().Be(expected); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Version can be an optional parameter
var compilation = SolutionBuilder.Create() | ||
.AddProject(AnalyzerLanguage.CSharp) | ||
.AddReferences(NuGetMetadataReference.XunitFramework("2.4.2")) | ||
.AddSnippet("// Empty file") | ||
.GetCompilation(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a helper for this
var compilation = SolutionBuilder.Create() | |
.AddProject(AnalyzerLanguage.CSharp) | |
.AddReferences(NuGetMetadataReference.XunitFramework("2.4.2")) | |
.AddSnippet("// Empty file") | |
.GetCompilation(); | |
var compilation = TestHelper.CompileCS("// Empty file", NuGetMetadataReference.XunitFramework("2.4.2")).Model.Compilation |
var compilation = SolutionBuilder.Create() | ||
.AddProject(AnalyzerLanguage.CSharp) | ||
.AddReferences(NuGetMetadataReference.XunitFrameworkV1) | ||
.AddSnippet("// Empty file") | ||
.GetCompilation(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
var compilation = SolutionBuilder.Create() | ||
.AddProject(AnalyzerLanguage.CSharp) | ||
.AddSnippet("// Empty file") | ||
.GetCompilation(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also here
Kudos, SonarCloud Quality Gate passed! |
Kudos, SonarCloud Quality Gate passed! |
A precondition for #6662
For the Why see also: https://sonarsource.slack.com/archives/C012KBFFYD6/p1675686569055439