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
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
695358b
Add KnowReference and tests
martin-strecker-sonarsource Feb 6, 2023
7547b03
Fix code smell (parameter names)
martin-strecker-sonarsource Feb 6, 2023
6b5d8f4
Add overloads for version passed as string.
martin-strecker-sonarsource Feb 7, 2023
5f39f96
Add support for public key checks
martin-strecker-sonarsource Feb 7, 2023
c739408
Move predicates to own file
martin-strecker-sonarsource Feb 7, 2023
d3842bd
Rename to IsReferencedBy
martin-strecker-sonarsource Feb 7, 2023
92deb06
Clean-up
martin-strecker-sonarsource Feb 7, 2023
8422676
Add argument null checks
martin-strecker-sonarsource Feb 7, 2023
ec96228
Clean-up
martin-strecker-sonarsource Feb 7, 2023
ab95c9b
Remove Or and replace with params
martin-strecker-sonarsource Feb 8, 2023
4caa689
argument naming
martin-strecker-sonarsource Feb 8, 2023
af2cdc8
test Naming
martin-strecker-sonarsource Feb 8, 2023
c4ae3a4
Move extension methods to the right file ine the right folder
martin-strecker-sonarsource Feb 9, 2023
657c32e
Add suing after move
martin-strecker-sonarsource Feb 9, 2023
9bef92a
Use IndexOf for "Contains"
martin-strecker-sonarsource Feb 10, 2023
9450d62
Adopt tests for string comparison
martin-strecker-sonarsource Feb 10, 2023
886b451
Use nested class
martin-strecker-sonarsource Feb 10, 2023
98364c7
Move to extensions folder
martin-strecker-sonarsource Feb 10, 2023
abed89f
Remove "Any" extension method
martin-strecker-sonarsource Feb 10, 2023
d619346
Rename parameter
martin-strecker-sonarsource Feb 10, 2023
6d341b1
Exception message
martin-strecker-sonarsource Feb 10, 2023
078a782
Rename KnownReference to KnownAssembly
martin-strecker-sonarsource Feb 13, 2023
f9bbb8a
Rename SpecialTypeMethod
martin-strecker-sonarsource Feb 13, 2023
0cd2ee6
Formatting
martin-strecker-sonarsource Feb 13, 2023
88e2f90
Move file
martin-strecker-sonarsource Feb 13, 2023
d2794d2
File scoped namespace
martin-strecker-sonarsource Feb 13, 2023
98ddc1b
Extract MockCompilation
martin-strecker-sonarsource Feb 13, 2023
0c21bd5
Simplify project setup for tests
martin-strecker-sonarsource Feb 13, 2023
54ed0dd
Rename method
martin-strecker-sonarsource Feb 13, 2023
5117f76
Remove EditorBrowsable
martin-strecker-sonarsource Feb 13, 2023
cb7633f
Add public key token to XUnit
martin-strecker-sonarsource Feb 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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) =>

(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


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";
Comment on lines +31 to +34
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


public static bool References(this Compilation compilation, KnownReference reference)
=> reference.IsReferencedBy(compilation);
}
Expand Up @@ -18,18 +18,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.Helpers
{
public static class CompilationExtensions
{
public static IMethodSymbol GetTypeMethod(this Compilation compilation, SpecialType type, string methodName) =>
(IMethodSymbol)compilation.GetSpecialType(type)
.GetMembers(methodName)
.SingleOrDefault();
namespace SonarAnalyzer.Extensions;

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";
}
internal static class KnownReferenceExtensions
{
internal static Func<AssemblyIdentity, bool> And(this Func<AssemblyIdentity, bool> @this, Func<AssemblyIdentity, bool> predicate)
=> identity => @this(identity) && predicate(identity);
}
@@ -0,0 +1,77 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.ComponentModel;

namespace SonarAnalyzer.Helpers;

public sealed partial class KnownReference
{
private const StringComparison AssemblyNameComparission = StringComparison.OrdinalIgnoreCase;

[EditorBrowsable(EditorBrowsableState.Never)]
internal static class Predicates
{
internal static Func<AssemblyIdentity, bool> NameIs(string name) =>
x => x.Name.Equals(name, AssemblyNameComparission);

internal static Func<AssemblyIdentity, bool> StartsWith(string name) =>
x => x.Name.StartsWith(name, AssemblyNameComparission);

internal static Func<AssemblyIdentity, bool> EndsWith(string name) =>
x => x.Name.EndsWith(name, AssemblyNameComparission);

internal static Func<AssemblyIdentity, bool> Contains(string name) =>
x => x.Name.IndexOf(name, 0, AssemblyNameComparission) >= 0;

internal static Func<AssemblyIdentity, bool> VersionLowerThen(string version) =>
VersionLowerThen(Version.Parse(version));

internal static Func<AssemblyIdentity, bool> VersionLowerThen(Version version) =>
x => x.Version < version;

internal static Func<AssemblyIdentity, bool> VersionGreaterOrEqual(string version) =>
VersionGreaterOrEqual(Version.Parse(version));

internal static Func<AssemblyIdentity, bool> VersionGreaterOrEqual(Version version) =>
x => x.Version >= version;

internal static Func<AssemblyIdentity, bool> VersionBetween(string from, string to) =>
VersionBetween(Version.Parse(from), Version.Parse(to));

internal static Func<AssemblyIdentity, bool> VersionBetween(Version from, Version to) =>
x => x.Version >= from && x.Version <= to;

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

private static bool PublicKeyEqualHex(AssemblyIdentity identity, string hexString)
{
var normalizedHexString = hexString.Replace("-", string.Empty);
return ArraysEqual(identity.PublicKeyToken.ToArray(), normalizedHexString) || ArraysEqual(identity.PublicKey.ToArray(), normalizedHexString);

static bool ArraysEqual(byte[] key, string hexString) =>
BitConverter.ToString(key).Replace("-", string.Empty).Equals(hexString, StringComparison.OrdinalIgnoreCase);
}
}
}
46 changes: 46 additions & 0 deletions analyzers/src/SonarAnalyzer.Common/Helpers/KnownReference.cs
@@ -0,0 +1,46 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

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

{
public sealed partial class KnownReference
{
private readonly Func<IEnumerable<AssemblyIdentity>, bool> predicate;

public static KnownReference XUnit_Assert { get; } = new(
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.

: this(predicate is null || or.Any(x => x is null)
? throw new ArgumentNullException(nameof(predicate), "All predicates must be non-null.")
: identities => identities.Any(identitiy => predicate(identitiy) || or.Any(orPredicate => orPredicate(identitiy))))
{
}

internal KnownReference(Func<IEnumerable<AssemblyIdentity>, bool> predicate) =>
this.predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
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 two constructors, this class is meant to be created only with the previous one. Assign the predicate there. Those few UTs will survive without it ,they can use the other one anyway

Suggested change
internal KnownReference(Func<IEnumerable<AssemblyIdentity>, bool> predicate) =>
this.predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));

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 ctor is needed for cases where more than one assembly reference needs to be present (e.g. WPF: WindowsBase, PresentationCore, and PresentationFramework).


public bool IsReferencedBy(Compilation compilation) =>
predicate(compilation.ReferencedAssemblyNames);
}
}