Skip to content
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

Merged
merged 31 commits into from Feb 14, 2023

Conversation

martin-strecker-sonarsource
Copy link
Contributor

@martin-strecker-sonarsource martin-strecker-sonarsource added Type: Improvement Making existing code better. Area: VB.NET VB.NET rules related issues. Area: C# C# rules related issues. labels Feb 6, 2023
@github-actions github-actions bot added this to In progress in Best Kanban Feb 6, 2023
@martin-strecker-sonarsource martin-strecker-sonarsource marked this pull request as ready for review February 7, 2023 15:31
Comment on lines 55 to 56
internal static Func<AssemblyIdentity, bool> OptionalPublicKeyTokenIs(string key) =>
x => !x.HasPublicKey || PublicKeyEqualHex(x, key);
Copy link
Contributor Author

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).

Copy link
Contributor

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.

Suggested change
internal static Func<AssemblyIdentity, bool> OptionalPublicKeyTokenIs(string key) =>
x => !x.HasPublicKey || PublicKeyEqualHex(x, key);

@github-actions github-actions bot moved this from In progress to Review in progress in Best Kanban Feb 8, 2023
Copy link
Contributor

@pavel-mikula-sonarsource pavel-mikula-sonarsource left a 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

Comment on lines 28 to 32
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);
Copy link
Contributor

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.

Suggested change
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);

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Comment on lines 55 to 56
internal static Func<AssemblyIdentity, bool> OptionalPublicKeyTokenIs(string key) =>
x => !x.HasPublicKey || PublicKeyEqualHex(x, key);
Copy link
Contributor

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.

Suggested change
internal static Func<AssemblyIdentity, bool> OptionalPublicKeyTokenIs(string key) =>
x => !x.HasPublicKey || PublicKeyEqualHex(x, key);

Comment on lines 58 to 59
internal static Func<AssemblyIdentity, bool> PublicKeyTokenIs(string key) =>
x => x.HasPublicKey && PublicKeyEqualHex(x, key);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Suggested change
internal static Func<AssemblyIdentity, bool> PublicKeyTokenIs(string key) =>
x => x.HasPublicKey && PublicKeyEqualHex(x, key);

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

@github-actions github-actions bot moved this from Review in progress to In progress in Best Kanban Feb 8, 2023
Copy link
Contributor

@pavel-mikula-sonarsource pavel-mikula-sonarsource left a 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) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public static IMethodSymbol GetTypeMethod(this Compilation compilation, SpecialType type, string methodName) =>
public static IMethodSymbol SpecialTypeMethod(this Compilation compilation, SpecialType type, string methodName) =>

Comment on lines 29 to 31
(IMethodSymbol)compilation.GetSpecialType(type)
.GetMembers(methodName)
.SingleOrDefault();
Copy link
Contributor

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

Comment on lines +33 to +36
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";
Copy link
Contributor

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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

Suggested change
internal KnownReference(Func<AssemblyIdentity, bool> predicate, params Func<AssemblyIdentity, bool>[] or)
internal KnownReference(params Func<AssemblyIdentity, bool>[] orPredicates)

Copy link
Contributor Author

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
Copy link
Contributor

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;
Copy link
Contributor

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

Suggested change
namespace SonarAnalyzer.UnitTest;
namespace SonarAnalyzer.UnitTest.Helpers;

Comment on lines 65 to 68
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);
Copy link
Contributor

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

Suggested change
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);

Copy link
Contributor

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

Comment on lines 267 to 271
var compilation = SolutionBuilder.Create()
.AddProject(AnalyzerLanguage.CSharp)
.AddReferences(NuGetMetadataReference.XunitFramework("2.4.2"))
.AddSnippet("// Empty file")
.GetCompilation();
Copy link
Contributor

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

Suggested change
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

Comment on lines 278 to 282
var compilation = SolutionBuilder.Create()
.AddProject(AnalyzerLanguage.CSharp)
.AddReferences(NuGetMetadataReference.XunitFrameworkV1)
.AddSnippet("// Empty file")
.GetCompilation();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Comment on lines 289 to 292
var compilation = SolutionBuilder.Create()
.AddProject(AnalyzerLanguage.CSharp)
.AddSnippet("// Empty file")
.GetCompilation();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here

Best Kanban automation moved this from Review in progress to Review approved Feb 10, 2023
@sonarcloud
Copy link

sonarcloud bot commented Feb 14, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@sonarcloud
Copy link

sonarcloud bot commented Feb 14, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

100.0% 100.0% Coverage
0.0% 0.0% Duplication

Best Kanban automation moved this from Review approved to Validate Peach Feb 14, 2023
@martin-strecker-sonarsource martin-strecker-sonarsource deleted the Martin/KnownReferences branch February 14, 2023 12:59
@martin-strecker-sonarsource martin-strecker-sonarsource moved this from Validate Peach to Done in Best Kanban Feb 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: C# C# rules related issues. Area: VB.NET VB.NET rules related issues. Type: Improvement Making existing code better.
Projects
Best Kanban
  
Done
Development

Successfully merging this pull request may close these issues.

None yet

2 participants