Skip to content

Commit 07580a8

Browse files
mareklinkasfilipi
authored andcommitted
Add analyzer for detecting BestFriend usages on public declarations (#2434)
* Add analyzer for detecting BestFriend usages on public declaration * Rewrite analyzer as Symbol analyzer * Add test to prove IntClass.PubMember reports a warning
1 parent 7dfadca commit 07580a8

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using System.Linq;
9+
using System.Reflection;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.CodeAnalysis.Diagnostics;
12+
using Microsoft.ML.CodeAnalyzer.Tests.Helpers;
13+
using Xunit;
14+
15+
namespace Microsoft.ML.InternalCodeAnalyzer.Tests
16+
{
17+
public sealed class BestFriendOnPublicDeclarationTest : DiagnosticVerifier<BestFriendOnPublicDeclarationsAnalyzer>
18+
{
19+
private readonly Lazy<string> SourceAttribute = TestUtils.LazySource("BestFriendAttribute.cs");
20+
private readonly Lazy<string> SourceDeclaration = TestUtils.LazySource("BestFriendOnPublicDeclaration.cs");
21+
22+
[Fact]
23+
public void BestFriendOnPublicDeclaration()
24+
{
25+
Solution solution = null;
26+
var projA = CreateProject("ProjectA", ref solution, SourceDeclaration.Value, SourceAttribute.Value);
27+
28+
var analyzer = new BestFriendOnPublicDeclarationsAnalyzer();
29+
30+
var refs = new List<MetadataReference> {
31+
RefFromType<object>(), RefFromType<Attribute>(),
32+
MetadataReference.CreateFromFile(Assembly.Load("netstandard, Version=2.0.0.0").Location),
33+
MetadataReference.CreateFromFile(Assembly.Load("System.Runtime, Version=0.0.0.0").Location)
34+
};
35+
36+
var comp = projA.GetCompilationAsync().Result.WithReferences(refs.ToArray());
37+
var compilationWithAnalyzers = comp.WithAnalyzers(ImmutableArray.Create((DiagnosticAnalyzer)analyzer));
38+
var allDiags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result;
39+
40+
var projectTrees = new HashSet<SyntaxTree>(projA.Documents.Select(r => r.GetSyntaxTreeAsync().Result));
41+
var diags = allDiags
42+
.Where(d => d.Location == Location.None || d.Location.IsInMetadata || projectTrees.Contains(d.Location.SourceTree))
43+
.OrderBy(d => d.Location.SourceSpan.Start).ToArray();
44+
45+
var diag = analyzer.SupportedDiagnostics[0];
46+
var expected = new DiagnosticResult[] {
47+
diag.CreateDiagnosticResult(8, 6, "PublicClass"),
48+
diag.CreateDiagnosticResult(11, 10, "PublicField"),
49+
diag.CreateDiagnosticResult(14, 10, "PublicProperty"),
50+
diag.CreateDiagnosticResult(20, 10, "PublicMethod"),
51+
diag.CreateDiagnosticResult(26, 10, "PublicDelegate"),
52+
diag.CreateDiagnosticResult(29, 10, "PublicClass"),
53+
diag.CreateDiagnosticResult(35, 6, "PublicStruct"),
54+
diag.CreateDiagnosticResult(40, 6, "PublicEnum"),
55+
diag.CreateDiagnosticResult(47, 6, "PublicInterface"),
56+
diag.CreateDiagnosticResult(102, 10, "PublicMethod")
57+
};
58+
59+
VerifyDiagnosticResults(diags, analyzer, expected);
60+
}
61+
}
62+
}
63+
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using System;
2+
using Microsoft.ML;
3+
4+
namespace TestNamespace
5+
{
6+
// all of the best friend declaration should fail the diagnostic
7+
8+
[BestFriend]
9+
public class PublicClass
10+
{
11+
[BestFriend]
12+
public int PublicField;
13+
14+
[BestFriend]
15+
public string PublicProperty
16+
{
17+
get { return string.Empty; }
18+
}
19+
20+
[BestFriend]
21+
public bool PublicMethod()
22+
{
23+
return true;
24+
}
25+
26+
[BestFriend]
27+
public delegate string PublicDelegate();
28+
29+
[BestFriend]
30+
public PublicClass()
31+
{
32+
}
33+
}
34+
35+
[BestFriend]
36+
public struct PublicStruct
37+
{
38+
}
39+
40+
[BestFriend]
41+
public enum PublicEnum
42+
{
43+
EnumValue1,
44+
EnumValue2
45+
}
46+
47+
[BestFriend]
48+
public interface PublicInterface
49+
{
50+
}
51+
52+
// these should work
53+
54+
[BestFriend]
55+
internal class InternalClass
56+
{
57+
[BestFriend]
58+
internal int InternalField;
59+
60+
[BestFriend]
61+
internal string InternalProperty
62+
{
63+
get { return string.Empty; }
64+
}
65+
66+
[BestFriend]
67+
internal bool InternalMethod()
68+
{
69+
return true;
70+
}
71+
72+
[BestFriend]
73+
internal delegate string InternalDelegate();
74+
75+
[BestFriend]
76+
internal InternalClass()
77+
{
78+
}
79+
}
80+
81+
[BestFriend]
82+
internal struct InternalStruct
83+
{
84+
}
85+
86+
[BestFriend]
87+
internal enum InternalEnum
88+
{
89+
EnumValue1,
90+
EnumValue2
91+
}
92+
93+
[BestFriend]
94+
internal interface InternalInterface
95+
{
96+
}
97+
98+
// this should fail the diagnostic
99+
// a repro for https://github.com/dotnet/machinelearning/pull/2434#discussion_r254770946
100+
internal class InternalClassWithPublicMember
101+
{
102+
[BestFriend]
103+
public void PublicMethod()
104+
{
105+
}
106+
}
107+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using System.Collections.Immutable;
7+
using System.Linq;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.Diagnostics;
10+
11+
namespace Microsoft.ML.InternalCodeAnalyzer
12+
{
13+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
14+
public sealed class BestFriendOnPublicDeclarationsAnalyzer : DiagnosticAnalyzer
15+
{
16+
private const string Category = "Access";
17+
internal const string DiagnosticId = "MSML_BestFriendOnPublicDeclaration";
18+
19+
private const string Title = "Public declarations should not have " + AttributeName + " attribute.";
20+
private const string Format = "The " + AttributeName + " should not be applied to publicly visible members.";
21+
22+
private const string Description =
23+
"The " + AttributeName + " attribute is not valid on public identifiers.";
24+
25+
private static DiagnosticDescriptor Rule =
26+
new DiagnosticDescriptor(DiagnosticId, Title, Format, Category,
27+
DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
28+
29+
private const string AttributeName = "Microsoft.ML.BestFriendAttribute";
30+
31+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
32+
ImmutableArray.Create(Rule);
33+
34+
public override void Initialize(AnalysisContext context)
35+
{
36+
context.EnableConcurrentExecution();
37+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
38+
39+
context.RegisterCompilationStartAction(CompilationStart);
40+
}
41+
42+
private void CompilationStart(CompilationStartAnalysisContext context)
43+
{
44+
var list = new List<string> { AttributeName, "Microsoft.ML.Internal.CpuMath.Core.BestFriendAttribute" };
45+
46+
foreach (var attributeName in list)
47+
{
48+
var attribute = context.Compilation.GetTypeByMetadataName(attributeName);
49+
50+
if (attribute == null)
51+
continue;
52+
53+
context.RegisterSymbolAction(c => AnalyzeCore(c, attribute), SymbolKind.NamedType, SymbolKind.Method, SymbolKind.Field, SymbolKind.Property);
54+
}
55+
}
56+
57+
private void AnalyzeCore(SymbolAnalysisContext context, INamedTypeSymbol attributeType)
58+
{
59+
if (context.Symbol.DeclaredAccessibility != Accessibility.Public)
60+
return;
61+
62+
var attribute = context.Symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass == attributeType);
63+
if (attribute == null)
64+
return;
65+
66+
var diagnostic = Diagnostic.Create(Rule, attribute.ApplicationSyntaxReference.GetSyntax().GetLocation(), context.Symbol.Name);
67+
context.ReportDiagnostic(diagnostic);
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)