From 7787c1b232138013999e3092db8081dfcfdc0155 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 23 Jan 2024 10:27:04 -0600 Subject: [PATCH] Introduce new InternalsVisibleToAnalyzer analyzer (ACZ0112) with `FriendAttribute` concept (#7086) --- eng/Directory.Build.targets | 3 +- .../AZC0112Tests.cs | 234 +++++++++ .../Azure.ClientSdk.Analyzers.Tests.csproj | 1 + .../AzureAnalyzerVerifier.cs | 27 +- .../Azure.ClientSdk.Analyzers.sln | 142 +++--- .../Azure.ClientSdk.Analyzers/Descriptors.cs | 453 +++++++++--------- .../InternalsVisibleToAnalyzer.cs | 182 +++++++ .../Classes.cs | 30 ++ .../FriendAttribute.cs | 14 + .../Interfaces.cs | 15 + .../Properties/AssemblyInfo.cs | 6 + ...TestReferenceWithInternalsVisibleTo.csproj | 8 + 12 files changed, 826 insertions(+), 289 deletions(-) create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC0112Tests.cs create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Classes.cs create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/FriendAttribute.cs create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Interfaces.cs create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Properties/AssemblyInfo.cs create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/TestReferenceWithInternalsVisibleTo.csproj diff --git a/eng/Directory.Build.targets b/eng/Directory.Build.targets index 602a1d23f72..7d02a699502 100644 --- a/eng/Directory.Build.targets +++ b/eng/Directory.Build.targets @@ -12,7 +12,8 @@ false - + + true false $(MSBuildThisFileDirectory)AzureSDKToolsKey.snk diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC0112Tests.cs b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC0112Tests.cs new file mode 100644 index 00000000000..0aacc9c9ba8 --- /dev/null +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC0112Tests.cs @@ -0,0 +1,234 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using Verifier = Azure.ClientSdk.Analyzers.Tests.AzureAnalyzerVerifier; + +namespace Azure.ClientSdk.Analyzers.Tests +{ + public class AZC0112Tests + { + [Fact] + public async Task AZC0020WhenInheritingFromInternalInterface() + { + string code = @" +using System; +using TestReferenceWithInternalsVisibleTo; + +namespace LibraryNamespace +{ + public class {|AZC0112:MyClass|} : IInternalInterface + { + } +}"; + await Verifier.VerifyAnalyzerAsync(code, additionalReferences: new[] { typeof(TestReferenceWithInternalsVisibleTo.PublicClass) }); + } + + [Fact] + public async Task NoAZC0020WhenInheritingFromInternalInterfaceWithFriendAttribute() + { + string code = @" +using System; +using TestReferenceWithInternalsVisibleTo; + +namespace LibraryNamespace +{ + public class MyClass : IInternalInterfaceWithFriendAttribute + { + } +}"; + await Verifier.VerifyAnalyzerAsync(code, additionalReferences: new[] { typeof(TestReferenceWithInternalsVisibleTo.PublicClass) }); + } + + [Fact] + public async Task AZC0020WhenDerivingFromInternalClass() + { + string code = @" +using System; +using TestReferenceWithInternalsVisibleTo; + +namespace LibraryNamespace +{ + internal class {|AZC0112:MyClass|} : InternalClass + { + } +}"; + await Verifier.VerifyAnalyzerAsync(code, additionalReferences: new[] { typeof(TestReferenceWithInternalsVisibleTo.PublicClass) }); + } + + [Fact] + public async Task NoAZC0020WhenInheritingFromInternalClassWithFriendAttribute() + { + string code = @" +using System; +using TestReferenceWithInternalsVisibleTo; + +namespace LibraryNamespace +{ + internal class MyClass : InternalClassWithFriendAttribute + { + } +}"; + await Verifier.VerifyAnalyzerAsync(code, additionalReferences: new[] { typeof(TestReferenceWithInternalsVisibleTo.PublicClass) }); + } + + [Fact] + public async Task AZC0020WhenDeclaringInternalProperty() + { + string code = @" +using System; +using TestReferenceWithInternalsVisibleTo; + +namespace LibraryNamespace +{ + public class MyClass + { + internal InternalClass {|AZC0112:PropReferencesInternalType|} { {|AZC0112:get|}; set;} + } +}"; + await Verifier.VerifyAnalyzerAsync(code, additionalReferences: new[] { typeof(TestReferenceWithInternalsVisibleTo.PublicClass) }); + } + + [Fact] + public async Task NoAZC0020WhenDeclaringInternalPropertyWithFriendAttribute() + { + string code = @" +using System; +using TestReferenceWithInternalsVisibleTo; + +namespace LibraryNamespace +{ + public class MyClass + { + internal InternalClassWithFriendAttribute PropReferencesInternalType { get; set;} + } +}"; + await Verifier.VerifyAnalyzerAsync(code, additionalReferences: new[] { typeof(TestReferenceWithInternalsVisibleTo.PublicClass) }); + } + + [Fact] + public async Task NoAZC0020WhenDeclaringInternalFieldWithFriendAttribute() + { + string code = @" +using System; +using TestReferenceWithInternalsVisibleTo; + +namespace LibraryNamespace +{ + public class MyClass + { + internal InternalClassWithFriendAttribute fieldReferencesInternalType; + } +}"; + await Verifier.VerifyAnalyzerAsync(code, additionalReferences: new[] { typeof(TestReferenceWithInternalsVisibleTo.PublicClass) }); + } + + [Fact] + public async Task AZC0020WhenDeclaringInternalField() + { + string code = @" +using System; +using TestReferenceWithInternalsVisibleTo; + +namespace LibraryNamespace +{ + public class MyClass + { + internal InternalClass {|AZC0112:fieldReferencesInternalType|}; + } +}"; + await Verifier.VerifyAnalyzerAsync(code, additionalReferences: new[] { typeof(TestReferenceWithInternalsVisibleTo.PublicClass) }); + } + + [Fact] + public async Task AZC0020WhenReferencingInternalProperty() + { + string code = @" +using System; +using TestReferenceWithInternalsVisibleTo; +using System.Reflection; + +namespace LibraryNamespace +{ + public class MyClass + { + public void MyMethod() + { + var myClass = new PublicClass(); + var value = {|AZC0112:myClass.InternalProperty|}; + } + } +}"; + await Verifier.VerifyAnalyzerAsync(code, additionalReferences: new[] { typeof(TestReferenceWithInternalsVisibleTo.PublicClass) }); + } + + [Fact] + public async Task NoAZC0020WhenReferencingInternalPropertyWithFriendAttribute() + { + string code = @" +using System; +using TestReferenceWithInternalsVisibleTo; +using System.Reflection; + +namespace LibraryNamespace +{ + public class MyClass + { + public void MyMethod() + { + var myClass = new PublicClass(); + var value = myClass.InternalPropertyWithFriendAttribute; + } + } +}"; + await Verifier.VerifyAnalyzerAsync(code, additionalReferences: new[] { typeof(TestReferenceWithInternalsVisibleTo.PublicClass) }); + } + + [Fact] + public async Task AZC0020WhenReferencingInternalMethod() + { + string code = @" +using System; +using TestReferenceWithInternalsVisibleTo; +using System.Reflection; + +namespace LibraryNamespace +{ + public class MyClass + { + public void MyMethod() + { + var myClass = new PublicClass(); + {|AZC0112:myClass.InternalMethod|}(); + } + } +}"; + await Verifier.VerifyAnalyzerAsync(code, additionalReferences: new[] { typeof(TestReferenceWithInternalsVisibleTo.PublicClass) }); + } + + [Fact] + public async Task NoAZC0020WhenReferencingInternalMethodWithFriendAttribute() + { + string code = @" +using System; +using TestReferenceWithInternalsVisibleTo; +using System.Reflection; + +namespace LibraryNamespace +{ + public class MyClass + { + public void MyMethod() + { + var myClass = new PublicClass(); + myClass.InternalMethodWithFriendAttribute(); + } + } +}"; + await Verifier.VerifyAnalyzerAsync(code, additionalReferences: new[] { typeof(TestReferenceWithInternalsVisibleTo.PublicClass) }); + } + + } +} diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/Azure.ClientSdk.Analyzers.Tests.csproj b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/Azure.ClientSdk.Analyzers.Tests.csproj index aa76426ffc9..ad4302b7f66 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/Azure.ClientSdk.Analyzers.Tests.csproj +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/Azure.ClientSdk.Analyzers.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AzureAnalyzerVerifier.cs b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AzureAnalyzerVerifier.cs index 3992a80d1f0..def85d13b10 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AzureAnalyzerVerifier.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AzureAnalyzerVerifier.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -18,14 +19,15 @@ namespace Azure.ClientSdk.Analyzers.Tests { private static readonly ReferenceAssemblies DefaultReferenceAssemblies = ReferenceAssemblies.Default.AddPackages(ImmutableArray.Create( - new PackageIdentity("Azure.Core", "1.26.0"), - new PackageIdentity("Microsoft.Bcl.AsyncInterfaces", "1.1.0"), + new PackageIdentity("Azure.Core", "1.35.0"), + new PackageIdentity("Microsoft.Bcl.AsyncInterfaces", "1.1.1"), new PackageIdentity("Newtonsoft.Json", "12.0.3"), - new PackageIdentity("System.Text.Json", "4.6.0"), - new PackageIdentity("System.Threading.Tasks.Extensions", "4.5.3"))); + new PackageIdentity("System.Text.Json", "4.7.2"), + new PackageIdentity("System.Threading.Tasks.Extensions", "4.5.4"))); - public static CSharpAnalyzerTest CreateAnalyzer(string source, LanguageVersion languageVersion = LanguageVersion.Latest) - => new CSharpAnalyzerTest + public static CSharpAnalyzerTest CreateAnalyzer(string source, LanguageVersion languageVersion = LanguageVersion.Latest, Type[] additionalReferences = null) + { + var test = new CSharpAnalyzerTest { ReferenceAssemblies = DefaultReferenceAssemblies, SolutionTransforms = {(solution, projectId) => @@ -37,9 +39,18 @@ public static CSharpAnalyzerTest CreateAnalyzer(string TestCode = source, TestBehaviors = TestBehaviors.SkipGeneratedCodeCheck }; + if (additionalReferences != null) + { + foreach (var reference in additionalReferences) + { + test.TestState.AdditionalReferences.Add(reference.Assembly); + } + } + return test; + } - public static Task VerifyAnalyzerAsync(string source, LanguageVersion languageVersion = LanguageVersion.Latest) - => CreateAnalyzer(source, languageVersion).RunAsync(CancellationToken.None); + public static Task VerifyAnalyzerAsync(string source, LanguageVersion languageVersion = LanguageVersion.Latest, Type[] additionalReferences = null) + => CreateAnalyzer(source, languageVersion, additionalReferences).RunAsync(CancellationToken.None); public static Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] diagnostics) { diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.sln b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.sln index 425e6242af2..3a17920aa30 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.sln +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.sln @@ -1,57 +1,85 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.33103.201 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.ClientSdk.Analyzers", "Azure.ClientSdk.Analyzers\Azure.ClientSdk.Analyzers.csproj", "{0C85C003-716E-415A-850F-D0597DACA658}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.ClientSdk.Analyzers.Tests", "Azure.ClientSdk.Analyzers.Tests\Azure.ClientSdk.Analyzers.Tests.csproj", "{53ABAABB-3B1C-493B-88FB-B1E22A95D232}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{65E960CB-422D-47FB-BEFD-A88B3474CD86}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - ci.yml = ci.yml - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0C85C003-716E-415A-850F-D0597DACA658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C85C003-716E-415A-850F-D0597DACA658}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C85C003-716E-415A-850F-D0597DACA658}.Debug|x64.ActiveCfg = Debug|Any CPU - {0C85C003-716E-415A-850F-D0597DACA658}.Debug|x64.Build.0 = Debug|Any CPU - {0C85C003-716E-415A-850F-D0597DACA658}.Debug|x86.ActiveCfg = Debug|Any CPU - {0C85C003-716E-415A-850F-D0597DACA658}.Debug|x86.Build.0 = Debug|Any CPU - {0C85C003-716E-415A-850F-D0597DACA658}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C85C003-716E-415A-850F-D0597DACA658}.Release|Any CPU.Build.0 = Release|Any CPU - {0C85C003-716E-415A-850F-D0597DACA658}.Release|x64.ActiveCfg = Release|Any CPU - {0C85C003-716E-415A-850F-D0597DACA658}.Release|x64.Build.0 = Release|Any CPU - {0C85C003-716E-415A-850F-D0597DACA658}.Release|x86.ActiveCfg = Release|Any CPU - {0C85C003-716E-415A-850F-D0597DACA658}.Release|x86.Build.0 = Release|Any CPU - {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|Any CPU.Build.0 = Debug|Any CPU - {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|x64.ActiveCfg = Debug|Any CPU - {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|x64.Build.0 = Debug|Any CPU - {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|x86.ActiveCfg = Debug|Any CPU - {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|x86.Build.0 = Debug|Any CPU - {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|Any CPU.ActiveCfg = Release|Any CPU - {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|Any CPU.Build.0 = Release|Any CPU - {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|x64.ActiveCfg = Release|Any CPU - {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|x64.Build.0 = Release|Any CPU - {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|x86.ActiveCfg = Release|Any CPU - {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {F28E9386-1FF2-4372-A91A-88B507A36B23} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33103.201 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.ClientSdk.Analyzers", "Azure.ClientSdk.Analyzers\Azure.ClientSdk.Analyzers.csproj", "{0C85C003-716E-415A-850F-D0597DACA658}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.ClientSdk.Analyzers.Tests", "Azure.ClientSdk.Analyzers.Tests\Azure.ClientSdk.Analyzers.Tests.csproj", "{53ABAABB-3B1C-493B-88FB-B1E22A95D232}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{65E960CB-422D-47FB-BEFD-A88B3474CD86}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + ci.yml = ci.yml + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestReferenceWithInternalsVisibleTo", "TestReferenceWithInternalsVisibleTo\TestReferenceWithInternalsVisibleTo.csproj", "{7FC92CF2-C6CB-4BAF-9397-D26713EF728C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|arm64 = Debug|arm64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|arm64 = Release|arm64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0C85C003-716E-415A-850F-D0597DACA658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Debug|arm64.ActiveCfg = Debug|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Debug|arm64.Build.0 = Debug|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Debug|x64.Build.0 = Debug|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Debug|x86.Build.0 = Debug|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Release|Any CPU.Build.0 = Release|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Release|arm64.ActiveCfg = Release|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Release|arm64.Build.0 = Release|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Release|x64.ActiveCfg = Release|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Release|x64.Build.0 = Release|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Release|x86.ActiveCfg = Release|Any CPU + {0C85C003-716E-415A-850F-D0597DACA658}.Release|x86.Build.0 = Release|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|arm64.ActiveCfg = Debug|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|arm64.Build.0 = Debug|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|x64.ActiveCfg = Debug|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|x64.Build.0 = Debug|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|x86.ActiveCfg = Debug|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Debug|x86.Build.0 = Debug|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|Any CPU.Build.0 = Release|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|arm64.ActiveCfg = Release|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|arm64.Build.0 = Release|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|x64.ActiveCfg = Release|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|x64.Build.0 = Release|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|x86.ActiveCfg = Release|Any CPU + {53ABAABB-3B1C-493B-88FB-B1E22A95D232}.Release|x86.Build.0 = Release|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Debug|arm64.ActiveCfg = Debug|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Debug|arm64.Build.0 = Debug|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Debug|x64.ActiveCfg = Debug|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Debug|x64.Build.0 = Debug|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Debug|x86.ActiveCfg = Debug|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Debug|x86.Build.0 = Debug|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Release|Any CPU.Build.0 = Release|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Release|arm64.ActiveCfg = Release|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Release|arm64.Build.0 = Release|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Release|x64.ActiveCfg = Release|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Release|x64.Build.0 = Release|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Release|x86.ActiveCfg = Release|Any CPU + {7FC92CF2-C6CB-4BAF-9397-D26713EF728C}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F28E9386-1FF2-4372-A91A-88B507A36B23} + EndGlobalSection +EndGlobal diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/Descriptors.cs b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/Descriptors.cs index 25016015a76..a3db298b73b 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/Descriptors.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/Descriptors.cs @@ -1,141 +1,141 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.CodeAnalysis; - -namespace Azure.ClientSdk.Analyzers -{ - internal class Descriptors - { - private static readonly string AZC0001Title = "Use one of the following pre-approved namespace groups (https://azure.github.io/azure-sdk/registered_namespaces.html): " + string.Join(", ", ClientAssemblyNamespaceAnalyzer.AllowedNamespacePrefix); - - #region Guidelines - public static DiagnosticDescriptor AZC0001 = new DiagnosticDescriptor( - nameof(AZC0001), AZC0001Title, - "Namespace '{0}' shouldn't contain public types. " + AZC0001Title, DiagnosticCategory.Usage, DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0002 = new DiagnosticDescriptor( - nameof(AZC0002), - "DO ensure all service methods, both asynchronous and synchronous, take an optional CancellationToken parameter called 'cancellationToken' or a RequestContext parameter called 'context'.", - "Client method should have an optional CancellationToken called cancellationToken (both name and it being optional matters) or a RequestContext called context as the last parameter.", - DiagnosticCategory.Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, - "https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-service-methods-cancellation" - ); - - public static DiagnosticDescriptor AZC0003 = new DiagnosticDescriptor( - nameof(AZC0003), - "DO make service methods virtual.", - "DO make service methods virtual.", - DiagnosticCategory.Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, - "https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-service-methods-virtual" - ); - - public static DiagnosticDescriptor AZC0004 = new DiagnosticDescriptor( - nameof(AZC0004), - "DO provide both asynchronous and synchronous variants for all service methods.", - "DO provide both asynchronous and synchronous variants for all service methods.", - DiagnosticCategory.Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, - "https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-service-methods-sync-and-async" - ); - - public static DiagnosticDescriptor AZC0005 = new DiagnosticDescriptor( - nameof(AZC0005), - "DO provide protected parameterless constructor for mocking.", - "DO provide protected parameterless constructor for mocking.", - DiagnosticCategory.Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, - "https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-client-constructor-for-mocking" - ); - - public static DiagnosticDescriptor AZC0006 = new DiagnosticDescriptor( - nameof(AZC0006), - "DO provide constructor overloads that allow specifying additional options.", - "A client type should have a public constructor with equivalent parameters that takes a Azure.Core.ClientOptions-derived type as the last argument", - DiagnosticCategory.Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, - "https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-client-constructor-overloads" - ); - - public static DiagnosticDescriptor AZC0007 = new DiagnosticDescriptor( - nameof(AZC0007), - "DO provide a minimal constructor that takes only the parameters required to connect to the service.", - "A client type should have a public constructor with equivalent parameters that doesn't take a Azure.Core.ClientOptions-derived type as the last argument", - DiagnosticCategory.Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, - "https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-client-constructor-minimal" - ); - - public static DiagnosticDescriptor AZC0008 = new DiagnosticDescriptor( - nameof(AZC0008), "ClientOptions should have a nested enum called ServiceVersion", - "Client type should have a nested enum called ServiceVersion", DiagnosticCategory.Usage, DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0009 = new DiagnosticDescriptor( - nameof(AZC0009), "ClientOptions constructors should take a ServiceVersion as their first parameter", - "ClientOptions constructors should take a ServiceVersion as their first parameter. Default constructor should be overloaded to provide ServiceVersion.", DiagnosticCategory.Usage, DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0010 = new DiagnosticDescriptor( - nameof(AZC0010), "ClientOptions constructors should default ServiceVersion to latest supported service version", - "ClientOptions constructors should default ServiceVersion to latest supported service version", DiagnosticCategory.Usage, DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0011 = new DiagnosticDescriptor( - nameof(AZC0011), "Avoid InternalsVisibleTo to non-test assemblies", - "Internal visible to product libraries effectively become public API and have to be versioned appropriately", DiagnosticCategory.Usage, DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0012 = new DiagnosticDescriptor( - nameof(AZC0012), "Avoid single word type names", - "Single word class names are too generic and have high chance of collision with BCL types or types from other libraries", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0013 = new DiagnosticDescriptor( - nameof(AZC0013), - "Use TaskCreationOptions.RunContinuationsAsynchronously when instantiating TaskCompletionSource", - "All the task’s continuations are executed synchronously unless TaskCreationOptions.RunContinuationsAsynchronously option is specified. This may cause deadlocks and other threading issues if all \"async\" continuations have to run in the thread that sets the result of a task.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0014 = new DiagnosticDescriptor( - nameof(AZC0014), - "Avoid using banned types in public API", - "Types from {0} assemblies should not be exposed as part of public API surface.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0015 = new DiagnosticDescriptor( - nameof(AZC0015), - "Unexpected client method return type.", - "Client methods should return Pageable/AsyncPageable/Operation/Task>/Response/Response/Task/Task> or other client class found {0} instead.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0016 = new DiagnosticDescriptor( - nameof(AZC0016), - "Invalid ServiceVersion member name.", - "All parts of ServiceVersion members' names must begin with a number or uppercase letter and cannot have consecutive underscores.", - "Usage", +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.CodeAnalysis; + +namespace Azure.ClientSdk.Analyzers +{ + internal class Descriptors + { + private static readonly string AZC0001Title = "Use one of the following pre-approved namespace groups (https://azure.github.io/azure-sdk/registered_namespaces.html): " + string.Join(", ", ClientAssemblyNamespaceAnalyzer.AllowedNamespacePrefix); + + #region Guidelines + public static DiagnosticDescriptor AZC0001 = new DiagnosticDescriptor( + nameof(AZC0001), AZC0001Title, + "Namespace '{0}' shouldn't contain public types. " + AZC0001Title, DiagnosticCategory.Usage, DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0002 = new DiagnosticDescriptor( + nameof(AZC0002), + "DO ensure all service methods, both asynchronous and synchronous, take an optional CancellationToken parameter called 'cancellationToken' or a RequestContext parameter called 'context'.", + "Client method should have an optional CancellationToken called cancellationToken (both name and it being optional matters) or a RequestContext called context as the last parameter.", + DiagnosticCategory.Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, + "https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-service-methods-cancellation" + ); + + public static DiagnosticDescriptor AZC0003 = new DiagnosticDescriptor( + nameof(AZC0003), + "DO make service methods virtual.", + "DO make service methods virtual.", + DiagnosticCategory.Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, + "https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-service-methods-virtual" + ); + + public static DiagnosticDescriptor AZC0004 = new DiagnosticDescriptor( + nameof(AZC0004), + "DO provide both asynchronous and synchronous variants for all service methods.", + "DO provide both asynchronous and synchronous variants for all service methods.", + DiagnosticCategory.Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, + "https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-service-methods-sync-and-async" + ); + + public static DiagnosticDescriptor AZC0005 = new DiagnosticDescriptor( + nameof(AZC0005), + "DO provide protected parameterless constructor for mocking.", + "DO provide protected parameterless constructor for mocking.", + DiagnosticCategory.Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, + "https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-client-constructor-for-mocking" + ); + + public static DiagnosticDescriptor AZC0006 = new DiagnosticDescriptor( + nameof(AZC0006), + "DO provide constructor overloads that allow specifying additional options.", + "A client type should have a public constructor with equivalent parameters that takes a Azure.Core.ClientOptions-derived type as the last argument", + DiagnosticCategory.Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, + "https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-client-constructor-overloads" + ); + + public static DiagnosticDescriptor AZC0007 = new DiagnosticDescriptor( + nameof(AZC0007), + "DO provide a minimal constructor that takes only the parameters required to connect to the service.", + "A client type should have a public constructor with equivalent parameters that doesn't take a Azure.Core.ClientOptions-derived type as the last argument", + DiagnosticCategory.Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null, + "https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-client-constructor-minimal" + ); + + public static DiagnosticDescriptor AZC0008 = new DiagnosticDescriptor( + nameof(AZC0008), "ClientOptions should have a nested enum called ServiceVersion", + "Client type should have a nested enum called ServiceVersion", DiagnosticCategory.Usage, DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0009 = new DiagnosticDescriptor( + nameof(AZC0009), "ClientOptions constructors should take a ServiceVersion as their first parameter", + "ClientOptions constructors should take a ServiceVersion as their first parameter. Default constructor should be overloaded to provide ServiceVersion.", DiagnosticCategory.Usage, DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0010 = new DiagnosticDescriptor( + nameof(AZC0010), "ClientOptions constructors should default ServiceVersion to latest supported service version", + "ClientOptions constructors should default ServiceVersion to latest supported service version", DiagnosticCategory.Usage, DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0011 = new DiagnosticDescriptor( + nameof(AZC0011), "Avoid InternalsVisibleTo to non-test assemblies", + "Internal visible to product libraries effectively become public API and have to be versioned appropriately", DiagnosticCategory.Usage, DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0012 = new DiagnosticDescriptor( + nameof(AZC0012), "Avoid single word type names", + "Single word class names are too generic and have high chance of collision with BCL types or types from other libraries", + DiagnosticCategory.Usage, DiagnosticSeverity.Warning, true); - public static DiagnosticDescriptor AZC0017 = new DiagnosticDescriptor( - nameof(AZC0017), - "Invalid convenience method signature.", - "Convenience methods shouldn't have parameters with the RequestContent type.", - "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null); - - public static DiagnosticDescriptor AZC0018 = new DiagnosticDescriptor( - nameof(AZC0018), - "Invalid protocol method signature.", - "Protocol methods should take a RequestContext parameter called `context` and not use a model type in a parameter or return type.", - "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null); - - public static DiagnosticDescriptor AZC0019 = new DiagnosticDescriptor( - nameof(AZC0019), - "Potential ambiguous call exists.", - "There will be an ambiguous call error when the user calls with only the required parameters. All parameters of the protocol method should be required.", + public static DiagnosticDescriptor AZC0013 = new DiagnosticDescriptor( + nameof(AZC0013), + "Use TaskCreationOptions.RunContinuationsAsynchronously when instantiating TaskCompletionSource", + "All the task’s continuations are executed synchronously unless TaskCreationOptions.RunContinuationsAsynchronously option is specified. This may cause deadlocks and other threading issues if all \"async\" continuations have to run in the thread that sets the result of a task.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0014 = new DiagnosticDescriptor( + nameof(AZC0014), + "Avoid using banned types in public API", + "Types from {0} assemblies should not be exposed as part of public API surface.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0015 = new DiagnosticDescriptor( + nameof(AZC0015), + "Unexpected client method return type.", + "Client methods should return Pageable/AsyncPageable/Operation/Task>/Response/Response/Task/Task> or other client class found {0} instead.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0016 = new DiagnosticDescriptor( + nameof(AZC0016), + "Invalid ServiceVersion member name.", + "All parts of ServiceVersion members' names must begin with a number or uppercase letter and cannot have consecutive underscores.", + "Usage", + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0017 = new DiagnosticDescriptor( + nameof(AZC0017), + "Invalid convenience method signature.", + "Convenience methods shouldn't have parameters with the RequestContent type.", + "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null); + + public static DiagnosticDescriptor AZC0018 = new DiagnosticDescriptor( + nameof(AZC0018), + "Invalid protocol method signature.", + "Protocol methods should take a RequestContext parameter called `context` and not use a model type in a parameter or return type.", "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null); - public static DiagnosticDescriptor AZC0020 = new DiagnosticDescriptor( - nameof(AZC0020), - "Avoid using banned types in public APIs", - "The Azure.Core internal shared source types {0} should not be used outside of the Azure.Core library.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); + public static DiagnosticDescriptor AZC0019 = new DiagnosticDescriptor( + nameof(AZC0019), + "Potential ambiguous call exists.", + "There will be an ambiguous call error when the user calls with only the required parameters. All parameters of the protocol method should be required.", + "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: null); + + public static DiagnosticDescriptor AZC0020 = new DiagnosticDescriptor( + nameof(AZC0020), + "Avoid using banned types in public APIs", + "The Azure.Core internal shared source types {0} should not be used outside of the Azure.Core library.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); public static readonly DiagnosticDescriptor AZC0030 = new DiagnosticDescriptor( nameof(AZC0030), @@ -172,92 +172,99 @@ internal class Descriptors DiagnosticSeverity.Warning, true, "Suffix is not recommended. Consider to remove or modify it."); - #endregion - - #region General - public static DiagnosticDescriptor AZC0100 = new DiagnosticDescriptor( - nameof(AZC0100), - "ConfigureAwait(false) must be used.", - "ConfigureAwait(false) must be used.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0101 = new DiagnosticDescriptor( - nameof(AZC0101), - "Use ConfigureAwait(false) instead of ConfigureAwait(true).", - "Use ConfigureAwait(false) instead of ConfigureAwait(true).", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0102 = new DiagnosticDescriptor( - nameof(AZC0102), - "Do not use GetAwaiter().GetResult().", - "Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0103 = new DiagnosticDescriptor( - nameof(AZC0103), - "Do not wait synchronously in asynchronous scope.", - "Do not use {0} in asynchronous scope. Use await keyword instead.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0104 = new DiagnosticDescriptor( - nameof(AZC0104), - "Use EnsureCompleted() directly on asynchronous method return value.", - "Don't use {0}. Call EnsureCompleted() extension method directly on the return value of the asynchronous method that has 'bool async' parameter.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0105 = new DiagnosticDescriptor( - nameof(AZC0105), - "DO NOT add 'async' parameter to public methods.", - "DO provide both asynchronous and synchronous variants for all service methods instead of one variant with 'async' parameter.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0106 = new DiagnosticDescriptor( - nameof(AZC0106), - "Non-public asynchronous method needs 'async' parameter.", - "Non-public asynchronous method that is called in synchronous scope should have a boolean 'async' parameter.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0107 = new DiagnosticDescriptor( - nameof(AZC0107), - "DO NOT call public asynchronous method in synchronous scope.", - "Public asynchronous method shouldn't be called in synchronous scope. Use synchronous version of the method if it is available.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0108 = new DiagnosticDescriptor( - nameof(AZC0108), - "Incorrect 'async' parameter value.", - "In {0} scope 'async' parameter for the '{1}' method call should {2}.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0109 = new DiagnosticDescriptor( - nameof(AZC0109), - "Misuse of 'async' parameter.", - "'async' parameter in asynchronous method can't be changed and can only be used as an exclusive condition in '?:' operator or conditional statement.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0110 = new DiagnosticDescriptor( - nameof(AZC0110), - "DO NOT use await keyword in possibly synchronous scope.", - "Asynchronous method with `async` parameter can be called from both synchronous and asynchronous scopes. 'await' keyword can be safely used either in guaranteed asynchronous scope (i.e. `if (async) {...}`) or if `async` parameter is passed into awaited method. Awaiting on variables, fields, properties, conditional operators or async methods that don't use `async` parameter isn't allowed outside of the guaranteed asynchronous scope.", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - - public static DiagnosticDescriptor AZC0111 = new DiagnosticDescriptor( - nameof(AZC0111), - "DO NOT use EnsureCompleted in possibly asynchronous scope.", - "Asynchronous method with `async` parameter can be called from both synchronous and asynchronous scopes. 'EnsureCompleted' extension method can be safely used on in guaranteed synchronous scope (i.e. `if (!async) {...}`).", - DiagnosticCategory.Usage, - DiagnosticSeverity.Warning, true); - #endregion - } -} + #endregion + + #region General + public static DiagnosticDescriptor AZC0100 = new DiagnosticDescriptor( + nameof(AZC0100), + "ConfigureAwait(false) must be used.", + "ConfigureAwait(false) must be used.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0101 = new DiagnosticDescriptor( + nameof(AZC0101), + "Use ConfigureAwait(false) instead of ConfigureAwait(true).", + "Use ConfigureAwait(false) instead of ConfigureAwait(true).", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0102 = new DiagnosticDescriptor( + nameof(AZC0102), + "Do not use GetAwaiter().GetResult().", + "Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0103 = new DiagnosticDescriptor( + nameof(AZC0103), + "Do not wait synchronously in asynchronous scope.", + "Do not use {0} in asynchronous scope. Use await keyword instead.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0104 = new DiagnosticDescriptor( + nameof(AZC0104), + "Use EnsureCompleted() directly on asynchronous method return value.", + "Don't use {0}. Call EnsureCompleted() extension method directly on the return value of the asynchronous method that has 'bool async' parameter.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0105 = new DiagnosticDescriptor( + nameof(AZC0105), + "DO NOT add 'async' parameter to public methods.", + "DO provide both asynchronous and synchronous variants for all service methods instead of one variant with 'async' parameter.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0106 = new DiagnosticDescriptor( + nameof(AZC0106), + "Non-public asynchronous method needs 'async' parameter.", + "Non-public asynchronous method that is called in synchronous scope should have a boolean 'async' parameter.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0107 = new DiagnosticDescriptor( + nameof(AZC0107), + "DO NOT call public asynchronous method in synchronous scope.", + "Public asynchronous method shouldn't be called in synchronous scope. Use synchronous version of the method if it is available.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0108 = new DiagnosticDescriptor( + nameof(AZC0108), + "Incorrect 'async' parameter value.", + "In {0} scope 'async' parameter for the '{1}' method call should {2}.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0109 = new DiagnosticDescriptor( + nameof(AZC0109), + "Misuse of 'async' parameter.", + "'async' parameter in asynchronous method can't be changed and can only be used as an exclusive condition in '?:' operator or conditional statement.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0110 = new DiagnosticDescriptor( + nameof(AZC0110), + "DO NOT use await keyword in possibly synchronous scope.", + "Asynchronous method with `async` parameter can be called from both synchronous and asynchronous scopes. 'await' keyword can be safely used either in guaranteed asynchronous scope (i.e. `if (async) {...}`) or if `async` parameter is passed into awaited method. Awaiting on variables, fields, properties, conditional operators or async methods that don't use `async` parameter isn't allowed outside of the guaranteed asynchronous scope.", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0111 = new DiagnosticDescriptor( + nameof(AZC0111), + "DO NOT use EnsureCompleted in possibly asynchronous scope.", + "Asynchronous method with `async` parameter can be called from both synchronous and asynchronous scopes. 'EnsureCompleted' extension method can be safely used on in guaranteed synchronous scope (i.e. `if (!async) {...}`).", + DiagnosticCategory.Usage, + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0112 = new DiagnosticDescriptor( + "AZC0112", + "Misuse of internal type via [InternalsVisibleTo].", + "{0} is defined in assembly {1} and is marked internal without a [Friend] attribute.", + "Naming", + DiagnosticSeverity.Warning, true); + #endregion + } +} diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs new file mode 100644 index 00000000000..63a83ac6eaa --- /dev/null +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs @@ -0,0 +1,182 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Azure.ClientSdk.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class InternalsVisibleToAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Descriptors.AZC0112); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + // Register for symbol actions + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method); + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Property); + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Field); + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Parameter); + context.RegisterSyntaxNodeAction(AnalyzeMemberAccess, SyntaxKind.SimpleMemberAccessExpression); + } + + private static void AnalyzeSymbol(SymbolAnalysisContext context) + { + var symbol = context.Symbol; + if (symbol is INamedTypeSymbol namedTypeSymbol) + { + AnalysisNamedType(namedTypeSymbol, context); + } + else if (symbol is IMethodSymbol methodSymbol) + { + AnalysisMethod(methodSymbol, context); + } + else if (symbol is IPropertySymbol propertySymbol) + { + AnalysisProperty(propertySymbol, context); + } + else if (symbol is IFieldSymbol fieldSymbol) + { + AnalysisField(fieldSymbol, context); + } + else if (symbol is IParameterSymbol parameterSymbol) + { + AnalysisParameter(parameterSymbol, context); + } + } + + private static void AnalysisParameter(IParameterSymbol symbol, SymbolAnalysisContext context) + { + var parentSymbol = context.Symbol; + if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) + { + var diagnostic = Diagnostic.Create( + Descriptors.AZC0112, + parentSymbol.Locations[0], + $"Parameter {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} has a type which", + symbol.Type.ContainingAssembly.Identity.Name); + context.ReportDiagnostic(diagnostic); + } + + } + + private static void AnalysisField(IFieldSymbol symbol, SymbolAnalysisContext context) + { + var parentSymbol = context.Symbol; + if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) + { + var diagnostic = Diagnostic.Create( + Descriptors.AZC0112, + parentSymbol.Locations[0], + $"Field {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} has a type which", + symbol.Type.ContainingAssembly.Identity.Name); + context.ReportDiagnostic(diagnostic); + } + + } + + private static void AnalysisProperty(IPropertySymbol symbol, SymbolAnalysisContext context) + { + var parentSymbol = context.Symbol; + if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) + { + var diagnostic = Diagnostic.Create( + Descriptors.AZC0112, + parentSymbol.Locations[0], + $"Property with type {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} has a type which", + symbol.Type.ContainingAssembly.Identity.Name); + context.ReportDiagnostic(diagnostic); + } + + } + + private static void AnalysisMethod(IMethodSymbol symbol, SymbolAnalysisContext context) + { + var parentSymbol = context.Symbol; + if (symbol.ReturnType.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) + { + var diagnostic = Diagnostic.Create( + Descriptors.AZC0112, + parentSymbol.Locations[0], + $"Method {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} returns a type which", + symbol.ReturnType.ContainingAssembly.Identity.Name); + context.ReportDiagnostic(diagnostic); + } + + } + + private static void AnalysisNamedType(INamedTypeSymbol symbol, SymbolAnalysisContext context) + { + var parentSymbol = context.Symbol; + if (symbol.Interfaces.Length > 0) + { + foreach (var interfaceSymbol in symbol.Interfaces) + { + if (interfaceSymbol.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) + { + var diagnostic = Diagnostic.Create( + Descriptors.AZC0112, + parentSymbol.Locations[0], + $"Type {parentSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} implements interface {interfaceSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} which", + interfaceSymbol.ContainingAssembly.Identity.Name); + context.ReportDiagnostic(diagnostic); + } + } + } + if (symbol.BaseType != null) + { + if (symbol.BaseType.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) + { + var diagnostic = Diagnostic.Create( + Descriptors.AZC0112, + parentSymbol.Locations[0], + $"Type {parentSymbol.Name} derives from base type {symbol.BaseType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} which", + symbol.BaseType.ContainingAssembly.Identity.Name); + context.ReportDiagnostic(diagnostic); + } + } + } + + private static void AnalyzeMemberAccess(SyntaxNodeAnalysisContext context) + { + var memberAccess = (MemberAccessExpressionSyntax)context.Node; + + if (memberAccess.Expression is IdentifierNameSyntax identifierName && memberAccess.Name is IdentifierNameSyntax memberName) + { + var symbol = context.SemanticModel.GetSymbolInfo(memberName).Symbol; + if (symbol is IPropertySymbol propertySymbol && propertySymbol.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) + { + var diagnostic = Diagnostic.Create( + Descriptors.AZC0112, + memberAccess.GetLocation(), + $"Accessed property {propertySymbol.Name} has a type which", + propertySymbol.Type.ContainingAssembly.Identity.Name); + context.ReportDiagnostic(diagnostic); + } + else if(symbol is IMethodSymbol methodSymbol && methodSymbol.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) + { + var diagnostic = Diagnostic.Create( + Descriptors.AZC0112, + memberAccess.GetLocation(), + $"Accessed method {methodSymbol.Name} has a return type which", + methodSymbol.ReturnType.ContainingAssembly.Identity.Name); + context.ReportDiagnostic(diagnostic); + } + } + } + } + + public static class SymbolExtensions + { + public static bool IsVisibleInternalWithoutFriendAttribute(this ISymbol symbol, Compilation contextCompilation) => + symbol.DeclaredAccessibility == Accessibility.Internal && + symbol.ContainingAssembly != null && + symbol.ContainingAssembly.Identity != contextCompilation.Assembly.Identity && + !symbol.GetAttributes().Any(ad => ad.AttributeClass.Name == "FriendAttribute"); + } +} diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Classes.cs b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Classes.cs new file mode 100644 index 00000000000..181ce46625c --- /dev/null +++ b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Classes.cs @@ -0,0 +1,30 @@ +namespace TestReferenceWithInternalsVisibleTo +{ + internal class InternalClass + { + } + + [Friend("TestProject")] + internal class InternalClassWithFriendAttribute + { + } + + public class PublicClass + { + public int PublicProperty { get; set; } + internal int InternalProperty { get; set; } + + [Friend("TestProject")] + internal int InternalPropertyWithFriendAttribute { get; set; } + + public void PublicMethod() + { } + + internal void InternalMethod() + { } + + [Friend("TestProject")] + internal void InternalMethodWithFriendAttribute() + { } + } +} diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/FriendAttribute.cs b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/FriendAttribute.cs new file mode 100644 index 00000000000..841711312c3 --- /dev/null +++ b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/FriendAttribute.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +internal class FriendAttribute : Attribute +{ + public string FriendAssembly { get; } + + public FriendAttribute(string friendAssembly) + { + FriendAssembly = friendAssembly; + } +} diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Interfaces.cs b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Interfaces.cs new file mode 100644 index 00000000000..cfa9c0acf18 --- /dev/null +++ b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Interfaces.cs @@ -0,0 +1,15 @@ +namespace TestReferenceWithInternalsVisibleTo +{ + internal interface IInternalInterface + { + } + + [Friend("TestProject")] + internal interface IInternalInterfaceWithFriendAttribute + { + } + + public interface IPublicInterface + { + } +} diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Properties/AssemblyInfo.cs b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..05e8ede1d4f --- /dev/null +++ b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("TestProject")] diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/TestReferenceWithInternalsVisibleTo.csproj b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/TestReferenceWithInternalsVisibleTo.csproj new file mode 100644 index 00000000000..98200f4fdb6 --- /dev/null +++ b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/TestReferenceWithInternalsVisibleTo.csproj @@ -0,0 +1,8 @@ + + + + netstandard2.0 + False + + +