From bcc47d3e3a249056adbcf7501a221454ce671225 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 4 Oct 2023 16:19:52 -0500 Subject: [PATCH 1/9] wip --- .../AZC1200Tests.cs | 47 ++++ .../AzureAnalyzerVerifier.cs | 8 +- .../Azure.ClientSdk.Analyzers.sln | 124 +++++----- .../InternalsVisibleToAnalyzer.cs | 219 ++++++++++++++++++ 4 files changed, 337 insertions(+), 61 deletions(-) create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC1200Tests.cs create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC1200Tests.cs b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC1200Tests.cs new file mode 100644 index 00000000000..f412931cf46 --- /dev/null +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC1200Tests.cs @@ -0,0 +1,47 @@ +// 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 AZC1200Tests + { + private List<(string fileName, string source)> _sharedSourceFiles; + + public AZC1200Tests() + { + _sharedSourceFiles = new List<(string fileName, string source)>() { + + ("IFoo.cs", @" + namespace Azure.Foo + { + internal interface IFoo + { + } + } + "), + }; + } + + [Fact] + public async Task AZC0020ProducedForMutableJsonDocumentUsage() + { + string code = @" +using System; +using Azure.Foo; + +namespace LibraryNamespace +{ + public class Model : IFoo + { + } +}"; + await Verifier.VerifyAnalyzerAsync(code, _sharedSourceFiles); + } + + } +} 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..4f0c4513775 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 @@ -18,11 +18,11 @@ 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 diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.sln b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.sln index 425e6242af2..a9abc7ecb7b 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.sln +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.sln @@ -1,57 +1,67 @@ - -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 +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 + 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/InternalsVisibleToAnalyzer.cs b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs new file mode 100644 index 00000000000..3ddd1ec8a5c --- /dev/null +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +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 + { + private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( + "AZC1200", + "TypeExistsInAnotherAssembly 4", + "{0} is defined in assembly {1} and is not publicly visible.", + "Naming", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + 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(AnalyzeNode, SyntaxKind.LocalDeclarationStatement); + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InterfaceDeclaration); + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.Parameter); + } + + 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); + } + //else + //{ + // // your analysis code here + // var diagnostic = Diagnostic.Create(Rule, symbol.Locations[0], $"Sym sym:{context.Symbol.Kind} assm:{context.Compilation.Assembly.Identity.Name} {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}"); + // context.ReportDiagnostic(diagnostic); + //} + //else + //{ + // // your analysis code here + // var diagnostic = Diagnostic.Create(Rule, symbol.Locations[0], $"Sym sym:{context.Symbol.Kind} assm:{context.Compilation.Assembly.Identity.Name} {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}"); + // context.ReportDiagnostic(diagnostic); + //} + + // Check if the symbol exists in another assembly and is not public + //if (symbol.ContainingAssembly != null && + // symbol.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && + // !symbol.IsPublic(context)) + //{ + // var diagnostic = Diagnostic.Create(Rule, symbol.Locations[0], $"If sym:{context.Symbol.Kind} assm:{context.Compilation.Assembly.Identity.Name} {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}"); + // context.ReportDiagnostic(diagnostic); + //} + //else + //{ + // var diagnostic = Diagnostic.Create(Rule, symbol.Locations[0], $"Else sym:{context.Symbol.Kind} assm:{context.Compilation.Assembly.Identity.Name} {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}"); + // context.ReportDiagnostic(diagnostic); + + //} + } + + private static void AnalysisParameter(IParameterSymbol parameterSymbol, SymbolAnalysisContext context) + { + var parentSymbol = context.Symbol; + } + + private static void AnalysisField(IFieldSymbol symbol, SymbolAnalysisContext context) + { + var parentSymbol = context.Symbol; + if (symbol.ContainingAssembly != null && + symbol.Type.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && + !symbol.Type.IsPublic()) + { + var diagnostic = Diagnostic.Create(Rule, 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.ContainingAssembly != null && + symbol.Type.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && + !symbol.Type.IsPublic()) + { + var diagnostic = Diagnostic.Create(Rule, 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.ContainingAssembly != null && + symbol.ReturnType.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && + !symbol.ReturnType.IsPublic()) + { + var diagnostic = Diagnostic.Create(Rule, 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.ContainingAssembly != null && + interfaceSymbol.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && + !interfaceSymbol.IsPublic()) + { + var diagnostic = Diagnostic.Create(Rule, parentSymbol.Locations[0], $"Type {context.Symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} implements interface {interfaceSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} which", interfaceSymbol.ContainingAssembly.Identity.Name); + context.ReportDiagnostic(diagnostic); + } + } + } + if (symbol.BaseType != null) + { + if (symbol.BaseType.ContainingAssembly != null && + symbol.BaseType.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && + !symbol.BaseType.IsPublic()) + { + var diagnostic = Diagnostic.Create(Rule, parentSymbol.Locations[0], $"Type {context.Symbol.Name} derives from base type {symbol.BaseType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} which", symbol.BaseType.ContainingAssembly.Identity.Name); + context.ReportDiagnostic(diagnostic); + } + } + } + + public void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + if (context.Node is ParameterSyntax parameterSyntax) + { + AnalyzeParameter(parameterSyntax, context); + } + } + + private void AnalyzeInterfaceDeclaration(InterfaceDeclarationSyntax interfaceDeclarationSyntax, SyntaxNodeAnalysisContext context) + { + throw new NotImplementedException(); + } + + private void AnalyzeLocalDeclaration(LocalDeclarationStatementSyntax localDeclarationStatementSyntax, SyntaxNodeAnalysisContext context) + { + throw new NotImplementedException(); + } + + private void AnalyzeParameter(ParameterSyntax parameterSyntax, SyntaxNodeAnalysisContext context) + { + var typeInfo = context.SemanticModel.GetTypeInfo(parameterSyntax.Type); + var containingAssembly = typeInfo.Type.ContainingAssembly; + if (containingAssembly != null && + containingAssembly.Identity != context.Compilation.Assembly.Identity && + !typeInfo.Type.IsPublic()) + { + var diagnostic = Diagnostic.Create(Rule, parameterSyntax.GetLocation(), $"Parameter {parameterSyntax.Identifier.ValueText} of type {typeInfo.Type.Name}", typeInfo.Type.ContainingAssembly); + context.ReportDiagnostic(diagnostic); + } + } + } + + // Extension method for checking IsPublic + public static class SymbolExtensions + { + public static bool IsPublic(this ISymbol symbol) + { + return symbol.DeclaredAccessibility == Accessibility.Public || + symbol.DeclaredAccessibility == Accessibility.ProtectedOrInternal || + symbol.DeclaredAccessibility == Accessibility.Protected; + } + + private static bool IsInternalsVisibleTo(this IAssemblySymbol sourceAssembly, IAssemblySymbol targetAssembly) + { + var metadata = sourceAssembly.GetMetadata(); + var peref = metadata.GetReference(); + var path = peref.FilePath; + return false; + //return sourceAssembly.GetAttributes() + // .Where(a => a.AttributeClass.Name == "InternalsVisibleToAttribute") + // .Select(a => MetadataReference.CreateFromAssemblyAttribute(a)) + // .Any(r => SymbolEqualityComparer.Default.Equals(targetAssembly, r.Resolve(targetAssembly, ignoreAssemblyKey: true))); + } + } +} From ca66247eef8fa748337a51094a65c23fbd7231fe Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Thu, 5 Oct 2023 15:06:00 -0500 Subject: [PATCH 2/9] with Friend attribute detection --- .../Azure.ClientSdk.Analyzers/Descriptors.cs | 457 +++++++++--------- .../InternalsVisibleToAnalyzer.cs | 159 ++---- 2 files changed, 276 insertions(+), 340 deletions(-) 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 87380df0a26..23caa2ac2e5 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/Descriptors.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/Descriptors.cs @@ -1,227 +1,234 @@ -// 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, "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.", - "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.", - "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.", - "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.", - "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", - "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", - "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", "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.", "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", "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", "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", - "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.", - "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.", - "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.", - "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 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.", +// 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, "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.", + "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.", + "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.", + "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.", + "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", + "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", + "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", "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.", "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", "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", "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", + "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.", + "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.", + "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.", + "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 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.", - "Usage", - DiagnosticSeverity.Warning, true); - #endregion - - #region General - public static DiagnosticDescriptor AZC0100 = new DiagnosticDescriptor( - nameof(AZC0100), - "ConfigureAwait(false) must be used.", - "ConfigureAwait(false) must be used.", - "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).", - "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.", - "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.", - "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.", - "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.", - "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.", - "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.", - "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}.", - "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.", - "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.", - "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) {...}`).", - "Usage", - DiagnosticSeverity.Warning, true); - #endregion - } -} + 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.", + "Usage", + DiagnosticSeverity.Warning, true); + #endregion + + #region General + public static DiagnosticDescriptor AZC0100 = new DiagnosticDescriptor( + nameof(AZC0100), + "ConfigureAwait(false) must be used.", + "ConfigureAwait(false) must be used.", + "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).", + "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.", + "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.", + "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.", + "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.", + "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.", + "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.", + "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}.", + "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.", + "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.", + "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) {...}`).", + "Usage", + DiagnosticSeverity.Warning, true); + + public static DiagnosticDescriptor AZC0112 = new DiagnosticDescriptor( + "AZC1200", + "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 index 3ddd1ec8a5c..57d603d1acb 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Azure.ClientSdk.Analyzers @@ -13,15 +8,7 @@ namespace Azure.ClientSdk.Analyzers [DiagnosticAnalyzer(LanguageNames.CSharp)] public class InternalsVisibleToAnalyzer : DiagnosticAnalyzer { - private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( - "AZC1200", - "TypeExistsInAnotherAssembly 4", - "{0} is defined in assembly {1} and is not publicly visible.", - "Naming", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Descriptors.AZC0112); public override void Initialize(AnalysisContext context) { @@ -33,9 +20,6 @@ public override void Initialize(AnalysisContext context) context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Property); context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Field); context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Parameter); - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement); - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InterfaceDeclaration); - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.Parameter); } private static void AnalyzeSymbol(SymbolAnalysisContext context) @@ -61,48 +45,34 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) { AnalysisParameter(parameterSymbol, context); } - //else - //{ - // // your analysis code here - // var diagnostic = Diagnostic.Create(Rule, symbol.Locations[0], $"Sym sym:{context.Symbol.Kind} assm:{context.Compilation.Assembly.Identity.Name} {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}"); - // context.ReportDiagnostic(diagnostic); - //} - //else - //{ - // // your analysis code here - // var diagnostic = Diagnostic.Create(Rule, symbol.Locations[0], $"Sym sym:{context.Symbol.Kind} assm:{context.Compilation.Assembly.Identity.Name} {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}"); - // context.ReportDiagnostic(diagnostic); - //} - - // Check if the symbol exists in another assembly and is not public - //if (symbol.ContainingAssembly != null && - // symbol.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && - // !symbol.IsPublic(context)) - //{ - // var diagnostic = Diagnostic.Create(Rule, symbol.Locations[0], $"If sym:{context.Symbol.Kind} assm:{context.Compilation.Assembly.Identity.Name} {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}"); - // context.ReportDiagnostic(diagnostic); - //} - //else - //{ - // var diagnostic = Diagnostic.Create(Rule, symbol.Locations[0], $"Else sym:{context.Symbol.Kind} assm:{context.Compilation.Assembly.Identity.Name} {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}"); - // context.ReportDiagnostic(diagnostic); - - //} } - private static void AnalysisParameter(IParameterSymbol parameterSymbol, SymbolAnalysisContext context) + private static void AnalysisParameter(IParameterSymbol symbol, SymbolAnalysisContext context) { var parentSymbol = context.Symbol; + if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context)) + { + // Check if the symbol is decorated with an attribute named FooAttribute + 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.ContainingAssembly != null && - symbol.Type.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && - !symbol.Type.IsPublic()) + if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context)) { - var diagnostic = Diagnostic.Create(Rule, parentSymbol.Locations[0], $"Field {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} has a type which", symbol.Type.ContainingAssembly.Identity.Name); + 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); } @@ -111,11 +81,13 @@ private static void AnalysisField(IFieldSymbol symbol, SymbolAnalysisContext con private static void AnalysisProperty(IPropertySymbol symbol, SymbolAnalysisContext context) { var parentSymbol = context.Symbol; - if (symbol.ContainingAssembly != null && - symbol.Type.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && - !symbol.Type.IsPublic()) + if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context)) { - var diagnostic = Diagnostic.Create(Rule, parentSymbol.Locations[0], $"Property with type {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} has a type which", symbol.Type.ContainingAssembly.Identity.Name); + 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); } @@ -124,11 +96,13 @@ private static void AnalysisProperty(IPropertySymbol symbol, SymbolAnalysisConte private static void AnalysisMethod(IMethodSymbol symbol, SymbolAnalysisContext context) { var parentSymbol = context.Symbol; - if (symbol.ContainingAssembly != null && - symbol.ReturnType.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && - !symbol.ReturnType.IsPublic()) + if (symbol.ReturnType.IsVisibleInternalWithoutFriendAttribute(context)) { - var diagnostic = Diagnostic.Create(Rule, parentSymbol.Locations[0], $"Method {symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} returns a type which", symbol.ReturnType.ContainingAssembly.Identity.Name); + 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); } @@ -141,79 +115,34 @@ private static void AnalysisNamedType(INamedTypeSymbol symbol, SymbolAnalysisCon { foreach (var interfaceSymbol in symbol.Interfaces) { - if (interfaceSymbol.ContainingAssembly != null && - interfaceSymbol.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && - !interfaceSymbol.IsPublic()) + if (interfaceSymbol.IsVisibleInternalWithoutFriendAttribute(context)) { - var diagnostic = Diagnostic.Create(Rule, parentSymbol.Locations[0], $"Type {context.Symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} implements interface {interfaceSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} which", interfaceSymbol.ContainingAssembly.Identity.Name); + 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.ContainingAssembly != null && - symbol.BaseType.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && - !symbol.BaseType.IsPublic()) + if (symbol.BaseType.IsVisibleInternalWithoutFriendAttribute(context)) { - var diagnostic = Diagnostic.Create(Rule, parentSymbol.Locations[0], $"Type {context.Symbol.Name} derives from base type {symbol.BaseType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} which", symbol.BaseType.ContainingAssembly.Identity.Name); + 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); } } } - - public void AnalyzeNode(SyntaxNodeAnalysisContext context) - { - if (context.Node is ParameterSyntax parameterSyntax) - { - AnalyzeParameter(parameterSyntax, context); - } - } - - private void AnalyzeInterfaceDeclaration(InterfaceDeclarationSyntax interfaceDeclarationSyntax, SyntaxNodeAnalysisContext context) - { - throw new NotImplementedException(); - } - - private void AnalyzeLocalDeclaration(LocalDeclarationStatementSyntax localDeclarationStatementSyntax, SyntaxNodeAnalysisContext context) - { - throw new NotImplementedException(); - } - - private void AnalyzeParameter(ParameterSyntax parameterSyntax, SyntaxNodeAnalysisContext context) - { - var typeInfo = context.SemanticModel.GetTypeInfo(parameterSyntax.Type); - var containingAssembly = typeInfo.Type.ContainingAssembly; - if (containingAssembly != null && - containingAssembly.Identity != context.Compilation.Assembly.Identity && - !typeInfo.Type.IsPublic()) - { - var diagnostic = Diagnostic.Create(Rule, parameterSyntax.GetLocation(), $"Parameter {parameterSyntax.Identifier.ValueText} of type {typeInfo.Type.Name}", typeInfo.Type.ContainingAssembly); - context.ReportDiagnostic(diagnostic); - } - } } - // Extension method for checking IsPublic public static class SymbolExtensions { - public static bool IsPublic(this ISymbol symbol) - { - return symbol.DeclaredAccessibility == Accessibility.Public || - symbol.DeclaredAccessibility == Accessibility.ProtectedOrInternal || - symbol.DeclaredAccessibility == Accessibility.Protected; - } - - private static bool IsInternalsVisibleTo(this IAssemblySymbol sourceAssembly, IAssemblySymbol targetAssembly) - { - var metadata = sourceAssembly.GetMetadata(); - var peref = metadata.GetReference(); - var path = peref.FilePath; - return false; - //return sourceAssembly.GetAttributes() - // .Where(a => a.AttributeClass.Name == "InternalsVisibleToAttribute") - // .Select(a => MetadataReference.CreateFromAssemblyAttribute(a)) - // .Any(r => SymbolEqualityComparer.Default.Equals(targetAssembly, r.Resolve(targetAssembly, ignoreAssemblyKey: true))); - } + public static bool IsVisibleInternalWithoutFriendAttribute(this ISymbol symbol, SymbolAnalysisContext context) => + symbol.DeclaredAccessibility == Accessibility.Internal && + symbol.ContainingAssembly != null && + symbol.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && + !symbol.GetAttributes().Any(ad => ad.AttributeClass.Name == "FriendAttribute"); } } From de57b6f3495ddaee43eedf60a8677412d31525b4 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Fri, 6 Oct 2023 16:11:15 -0500 Subject: [PATCH 3/9] working with basic test --- eng/Directory.Build.targets | 3 ++- .../{AZC1200Tests.cs => AZC0112Tests.cs} | 27 ++++++------------- .../Azure.ClientSdk.Analyzers.Tests.csproj | 1 + .../AzureAnalyzerVerifier.cs | 21 +++++++++++---- .../Azure.ClientSdk.Analyzers.sln | 18 +++++++++++++ .../Azure.ClientSdk.Analyzers/Descriptors.cs | 2 +- .../IInternalInterface.cs | 6 +++++ .../Properties/AssemblyInfo.cs | 6 +++++ .../PublicClass.cs | 14 ++++++++++ ...TestReferenceWithInternalsVisibleTo.csproj | 8 ++++++ 10 files changed, 80 insertions(+), 26 deletions(-) rename src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/{AZC1200Tests.cs => AZC0112Tests.cs} (50%) create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/IInternalInterface.cs create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Properties/AssemblyInfo.cs create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/PublicClass.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..4d85214146e 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/AZC1200Tests.cs b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC0112Tests.cs similarity index 50% rename from src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC1200Tests.cs rename to src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC0112Tests.cs index f412931cf46..0c6b73f94b9 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC1200Tests.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC0112Tests.cs @@ -4,43 +4,32 @@ using System.Collections.Generic; using System.Threading.Tasks; using Xunit; -using Verifier = Azure.ClientSdk.Analyzers.Tests.AzureAnalyzerVerifier; +using Verifier = Azure.ClientSdk.Analyzers.Tests.AzureAnalyzerVerifier; namespace Azure.ClientSdk.Analyzers.Tests { - public class AZC1200Tests + public class AZC0112Tests { private List<(string fileName, string source)> _sharedSourceFiles; - public AZC1200Tests() - { - _sharedSourceFiles = new List<(string fileName, string source)>() { - - ("IFoo.cs", @" - namespace Azure.Foo - { - internal interface IFoo - { - } - } - "), - }; - } + public AZC0112Tests() + { } [Fact] public async Task AZC0020ProducedForMutableJsonDocumentUsage() { string code = @" using System; -using Azure.Foo; +using TestReferenceWithInternalsVisibleTo; +using System.Reflection; namespace LibraryNamespace { - public class Model : IFoo + public class {|AZC0112:Model|} : IInternalInterface { } }"; - await Verifier.VerifyAnalyzerAsync(code, _sharedSourceFiles); + 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 4e207cd1a60..0e52b779110 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 4f0c4513775..89d57660726 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; @@ -24,8 +25,9 @@ namespace Azure.ClientSdk.Analyzers.Tests 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) => @@ -35,11 +37,20 @@ public static CSharpAnalyzerTest CreateAnalyzer(string return solution.WithProjectParseOptions(projectId, parseOptions.WithLanguageVersion(languageVersion)); }}, TestCode = source, - TestBehaviors = TestBehaviors.SkipGeneratedCodeCheck + 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 a9abc7ecb7b..3a17920aa30 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.sln +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.sln @@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution 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 @@ -57,6 +59,22 @@ Global {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 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 23caa2ac2e5..accce3fe8c4 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/Descriptors.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/Descriptors.cs @@ -224,7 +224,7 @@ internal class Descriptors DiagnosticSeverity.Warning, true); public static DiagnosticDescriptor AZC0112 = new DiagnosticDescriptor( - "AZC1200", + "AZC0112", "Misuse of internal type via InternalsVisibleTo.", "{0} is defined in assembly {1} and is marked internal without a [Friend] attribute.", "Naming", diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/IInternalInterface.cs b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/IInternalInterface.cs new file mode 100644 index 00000000000..784977ffb81 --- /dev/null +++ b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/IInternalInterface.cs @@ -0,0 +1,6 @@ +namespace TestReferenceWithInternalsVisibleTo +{ + internal interface IInternalInterface + { + } +} 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/PublicClass.cs b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/PublicClass.cs new file mode 100644 index 00000000000..7a4051aded7 --- /dev/null +++ b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/PublicClass.cs @@ -0,0 +1,14 @@ +namespace TestReferenceWithInternalsVisibleTo +{ + public class PublicClass + { + public int PublicProperty { get; set; } + internal int InternalProperty { get; set; } + + public void PublicMethod() + { } + + internal void InternalMethod() + { } + } +} 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 + + + From 515c1eb079b1a64abbeec71fe35cf29c5a2bcfba Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Mon, 9 Oct 2023 12:14:48 -0500 Subject: [PATCH 4/9] tests complete --- .../AZC0112Tests.cs | 210 +++++++++++++++++- .../InternalsVisibleToAnalyzer.cs | 69 +++++- .../Classes.cs | 30 +++ .../FriendAttribute.cs | 11 + .../IInternalInterface.cs | 6 - .../Interfaces.cs | 15 ++ .../PublicClass.cs | 14 -- 7 files changed, 321 insertions(+), 34 deletions(-) create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Classes.cs create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/FriendAttribute.cs delete mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/IInternalInterface.cs create mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/Interfaces.cs delete mode 100644 src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/PublicClass.cs 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 index 0c6b73f94b9..46aa69e2809 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC0112Tests.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC0112Tests.cs @@ -12,11 +12,184 @@ public class AZC0112Tests { private List<(string fileName, string source)> _sharedSourceFiles; - public 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 AZC0020ProducedForMutableJsonDocumentUsage() + 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; @@ -25,8 +198,35 @@ public async Task AZC0020ProducedForMutableJsonDocumentUsage() namespace LibraryNamespace { - public class {|AZC0112:Model|} : IInternalInterface - { + 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/InternalsVisibleToAnalyzer.cs b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs index 57d603d1acb..bbac9e619df 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs @@ -1,6 +1,8 @@ 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 @@ -20,6 +22,7 @@ public override void Initialize(AnalysisContext context) 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) @@ -50,7 +53,7 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) private static void AnalysisParameter(IParameterSymbol symbol, SymbolAnalysisContext context) { var parentSymbol = context.Symbol; - if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context)) + if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) { // Check if the symbol is decorated with an attribute named FooAttribute var diagnostic = Diagnostic.Create( @@ -66,7 +69,7 @@ private static void AnalysisParameter(IParameterSymbol symbol, SymbolAnalysisCon private static void AnalysisField(IFieldSymbol symbol, SymbolAnalysisContext context) { var parentSymbol = context.Symbol; - if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context)) + if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) { var diagnostic = Diagnostic.Create( Descriptors.AZC0112, @@ -81,7 +84,7 @@ private static void AnalysisField(IFieldSymbol symbol, SymbolAnalysisContext con private static void AnalysisProperty(IPropertySymbol symbol, SymbolAnalysisContext context) { var parentSymbol = context.Symbol; - if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context)) + if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) { var diagnostic = Diagnostic.Create( Descriptors.AZC0112, @@ -96,7 +99,7 @@ private static void AnalysisProperty(IPropertySymbol symbol, SymbolAnalysisConte private static void AnalysisMethod(IMethodSymbol symbol, SymbolAnalysisContext context) { var parentSymbol = context.Symbol; - if (symbol.ReturnType.IsVisibleInternalWithoutFriendAttribute(context)) + if (symbol.ReturnType.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) { var diagnostic = Diagnostic.Create( Descriptors.AZC0112, @@ -115,7 +118,7 @@ private static void AnalysisNamedType(INamedTypeSymbol symbol, SymbolAnalysisCon { foreach (var interfaceSymbol in symbol.Interfaces) { - if (interfaceSymbol.IsVisibleInternalWithoutFriendAttribute(context)) + if (interfaceSymbol.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) { var diagnostic = Diagnostic.Create( Descriptors.AZC0112, @@ -128,9 +131,57 @@ private static void AnalysisNamedType(INamedTypeSymbol symbol, SymbolAnalysisCon } if (symbol.BaseType != null) { - if (symbol.BaseType.IsVisibleInternalWithoutFriendAttribute(context)) + 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); + 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 void AnalyzeInvocation(SyntaxNodeAnalysisContext context) + { + var invocation = (InvocationExpressionSyntax)context.Node; + var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation); + + // Do something with the symbol info... + } + + private void AnalyzeIdentifier(SyntaxNodeAnalysisContext context) + { + var identifier = (IdentifierNameSyntax)context.Node; + var symbolInfo = context.SemanticModel.GetSymbolInfo(identifier); + var t = symbolInfo.GetType(); + // Do something with the symbol info... + } + + 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); } } @@ -139,10 +190,10 @@ private static void AnalysisNamedType(INamedTypeSymbol symbol, SymbolAnalysisCon public static class SymbolExtensions { - public static bool IsVisibleInternalWithoutFriendAttribute(this ISymbol symbol, SymbolAnalysisContext context) => + public static bool IsVisibleInternalWithoutFriendAttribute(this ISymbol symbol, Compilation contextCompilation) => symbol.DeclaredAccessibility == Accessibility.Internal && symbol.ContainingAssembly != null && - symbol.ContainingAssembly.Identity != context.Compilation.Assembly.Identity && + 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..ea52f10bb2f --- /dev/null +++ b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/FriendAttribute.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +internal class FriendAttribute : Attribute +{ + public FriendAttribute(string friendAssembly) + { + } +} diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/IInternalInterface.cs b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/IInternalInterface.cs deleted file mode 100644 index 784977ffb81..00000000000 --- a/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/IInternalInterface.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace TestReferenceWithInternalsVisibleTo -{ - internal interface IInternalInterface - { - } -} 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/PublicClass.cs b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/PublicClass.cs deleted file mode 100644 index 7a4051aded7..00000000000 --- a/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/PublicClass.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace TestReferenceWithInternalsVisibleTo -{ - public class PublicClass - { - public int PublicProperty { get; set; } - internal int InternalProperty { get; set; } - - public void PublicMethod() - { } - - internal void InternalMethod() - { } - } -} From 59924434fc9b470e82bd2c0104a1940e4856bb2f Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Mon, 9 Oct 2023 12:33:59 -0500 Subject: [PATCH 5/9] cleanup --- .../Azure.ClientSdk.Analyzers.Tests/AzureAnalyzerVerifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 89d57660726..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 @@ -37,7 +37,7 @@ public static CSharpAnalyzerTest CreateAnalyzer(string return solution.WithProjectParseOptions(projectId, parseOptions.WithLanguageVersion(languageVersion)); }}, TestCode = source, - TestBehaviors = TestBehaviors.SkipGeneratedCodeCheck, + TestBehaviors = TestBehaviors.SkipGeneratedCodeCheck }; if (additionalReferences != null) { From d9fd4d96849d069a80aba725c831bcf739c5c858 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Mon, 9 Oct 2023 12:43:42 -0500 Subject: [PATCH 6/9] fix --- .../Azure.ClientSdk.Analyzers.Tests/AZC0112Tests.cs | 2 -- 1 file changed, 2 deletions(-) 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 index 46aa69e2809..0aacc9c9ba8 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC0112Tests.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers.Tests/AZC0112Tests.cs @@ -10,8 +10,6 @@ namespace Azure.ClientSdk.Analyzers.Tests { public class AZC0112Tests { - private List<(string fileName, string source)> _sharedSourceFiles; - [Fact] public async Task AZC0020WhenInheritingFromInternalInterface() { From b7566a55ccf86a43aa140997127b00d179bb1615 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Mon, 9 Oct 2023 13:12:27 -0500 Subject: [PATCH 7/9] cleanup --- .../InternalsVisibleToAnalyzer.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs index bbac9e619df..78011ad8d7f 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs @@ -143,22 +143,6 @@ private static void AnalysisNamedType(INamedTypeSymbol symbol, SymbolAnalysisCon } } - private void AnalyzeInvocation(SyntaxNodeAnalysisContext context) - { - var invocation = (InvocationExpressionSyntax)context.Node; - var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation); - - // Do something with the symbol info... - } - - private void AnalyzeIdentifier(SyntaxNodeAnalysisContext context) - { - var identifier = (IdentifierNameSyntax)context.Node; - var symbolInfo = context.SemanticModel.GetSymbolInfo(identifier); - var t = symbolInfo.GetType(); - // Do something with the symbol info... - } - private static void AnalyzeMemberAccess(SyntaxNodeAnalysisContext context) { var memberAccess = (MemberAccessExpressionSyntax)context.Node; From d94017a87f43d7df4209d2d60012da751f9dd4cd Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 23 Jan 2024 09:56:09 -0600 Subject: [PATCH 8/9] fb --- eng/Directory.Build.targets | 2 +- .../Azure.ClientSdk.Analyzers/Descriptors.cs | 2 +- .../TestReferenceWithInternalsVisibleTo/FriendAttribute.cs | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/eng/Directory.Build.targets b/eng/Directory.Build.targets index 4d85214146e..7d02a699502 100644 --- a/eng/Directory.Build.targets +++ b/eng/Directory.Build.targets @@ -12,7 +12,7 @@ false - + true false 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 c9dbc97a202..a3db298b73b 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/Descriptors.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/Descriptors.cs @@ -261,7 +261,7 @@ internal class Descriptors public static DiagnosticDescriptor AZC0112 = new DiagnosticDescriptor( "AZC0112", - "Misuse of internal type via InternalsVisibleTo.", + "Misuse of internal type via [InternalsVisibleTo].", "{0} is defined in assembly {1} and is marked internal without a [Friend] attribute.", "Naming", DiagnosticSeverity.Warning, true); diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/FriendAttribute.cs b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/FriendAttribute.cs index ea52f10bb2f..841711312c3 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/FriendAttribute.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/TestReferenceWithInternalsVisibleTo/FriendAttribute.cs @@ -5,7 +5,10 @@ internal class FriendAttribute : Attribute { + public string FriendAssembly { get; } + public FriendAttribute(string friendAssembly) { + FriendAssembly = friendAssembly; } } From 0ccef099cd2c29729e6591d925402444c78f7cc7 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 23 Jan 2024 09:57:14 -0600 Subject: [PATCH 9/9] fb --- .../Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs index 78011ad8d7f..63a83ac6eaa 100644 --- a/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs +++ b/src/dotnet/Azure.ClientSdk.Analyzers/Azure.ClientSdk.Analyzers/InternalsVisibleToAnalyzer.cs @@ -55,7 +55,6 @@ private static void AnalysisParameter(IParameterSymbol symbol, SymbolAnalysisCon var parentSymbol = context.Symbol; if (symbol.Type.IsVisibleInternalWithoutFriendAttribute(context.Compilation)) { - // Check if the symbol is decorated with an attribute named FooAttribute var diagnostic = Diagnostic.Create( Descriptors.AZC0112, parentSymbol.Locations[0],