// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Analyzer.Testing; using Xunit; using Xunit.Abstractions; namespace Internal.AspNetCore.Analyzers.Tests { public class PubternabilityAnalyzerTests : DiagnosticVerifier { private const string InternalDefinitions = @" namespace A.Internal.Namespace { public class C {} public delegate C CD (); public class CAAttribute: System.Attribute {} public class Program { public static void Main() {} } }"; public PubternabilityAnalyzerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } [Theory] [MemberData(nameof(PublicMemberDefinitions))] [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/22440")] public async Task PublicExposureOfPubternalTypeProducesPUB0001(string member) { var code = GetSourceFromNamespaceDeclaration($@" namespace A {{ public class T {{ {member} }} }}"); var diagnostic = Assert.Single(await GetDiagnostics(code.Source)); Assert.Equal("PUB0001", diagnostic.Id); AnalyzerAssert.DiagnosticLocation(code.DefaultMarkerLocation, diagnostic.Location); } [Theory] [MemberData(nameof(PublicMemberWithAllowedDefinitions))] public async Task PublicExposureOfPubternalMembersSometimesAllowed(string member) { var code = GetSourceFromNamespaceDeclaration($@" namespace A {{ public class T {{ {member} }} }}"); Assert.Empty(await GetDiagnostics(code.Source)); } [Theory] [MemberData(nameof(PublicTypeDefinitions))] public async Task PublicExposureOfPubternalTypeProducesInTypeDefinitionPUB0001(string member) { var code = GetSourceFromNamespaceDeclaration($@" namespace A {{ {member} }}"); var diagnostic = Assert.Single(await GetDiagnostics(code.Source)); Assert.Equal("PUB0001", diagnostic.Id); AnalyzerAssert.DiagnosticLocation(code.DefaultMarkerLocation, diagnostic.Location); } [Theory] [MemberData(nameof(PublicMemberDefinitions))] public async Task PrivateUsageOfPubternalTypeDoesNotProduce(string member) { var code = GetSourceFromNamespaceDeclaration($@" namespace A {{ internal class T {{ {member} }} }}"); var diagnostics = await GetDiagnostics(code.Source); Assert.Empty(diagnostics); } [Theory] [MemberData(nameof(PrivateMemberDefinitions))] public async Task PrivateUsageOfPubternalTypeDoesNotProduceInPublicClasses(string member) { var code = GetSourceFromNamespaceDeclaration($@" namespace A {{ public class T {{ {member} }} }}"); var diagnostics = await GetDiagnostics(code.Source); Assert.Empty(diagnostics); } [Theory] [MemberData(nameof(PublicTypeWithAllowedDefinitions))] public async Task PublicExposureOfPubternalTypeSometimesAllowed(string member) { var code = GetSourceFromNamespaceDeclaration($@" namespace A {{ {member} }}"); var diagnostics = await GetDiagnostics(code.Source); Assert.Empty(diagnostics); } [Theory] [MemberData(nameof(PrivateMemberDefinitions))] [MemberData(nameof(PublicMemberDefinitions))] [QuarantinedTest] public async Task DefinitionOfPubternalCrossAssemblyProducesPUB0002(string member) { var code = TestSource.Read($@" using A.Internal.Namespace; namespace A {{ internal class T {{ {member} }} }}"); var diagnostic = Assert.Single(await GetDiagnosticWithProjectReference(code.Source)); Assert.Equal("PUB0002", diagnostic.Id); AnalyzerAssert.DiagnosticLocation(code.DefaultMarkerLocation, diagnostic.Location); } [Theory] [MemberData(nameof(TypeUsages))] public async Task UsageOfPubternalCrossAssemblyProducesPUB0002(string usage) { var code = TestSource.Read($@" using A.Internal.Namespace; namespace A {{ public class T {{ private void M() {{ {usage} }} }} }}"); var diagnostic = Assert.Single(await GetDiagnosticWithProjectReference(code.Source)); Assert.Equal("PUB0002", diagnostic.Id); AnalyzerAssert.DiagnosticLocation(code.DefaultMarkerLocation, diagnostic.Location); } public static IEnumerable PublicMemberDefinitions => ApplyModifiers(MemberDefinitions, "public", "protected"); public static IEnumerable PublicMemberWithAllowedDefinitions => ApplyModifiers(AllowedMemberDefinitions, "public"); public static IEnumerable PublicTypeDefinitions => ApplyModifiers(TypeDefinitions, "public"); public static IEnumerable PublicTypeWithAllowedDefinitions => ApplyModifiers(AllowedDefinitions, "public"); public static IEnumerable PrivateMemberDefinitions => ApplyModifiers(MemberDefinitions, "private", "internal"); public static IEnumerable TypeUsages => ApplyModifiers(TypeUsageStrings, string.Empty); public static string[] MemberDefinitions => new [] { "/*MM*/C c;", "T(/*MM*/C c) {}", "/*MM*/CD c { get; }", "event /*MM*/CD c;", "delegate /*MM*/C WOW();" }; public static string[] TypeDefinitions => new [] { "delegate /*MM*/C WOW();", "class /*MM*/T: P { } public class P {}", "class /*MM*/T: C {}", "class T { public class /*MM*/T1: C { } }" }; public static string[] AllowedMemberDefinitions => new [] { "T([CA]int c) {}", "[CA] MOD int f;", "[CA] MOD int f { get; set; }", "[CA] MOD class CC { }" }; public static string[] AllowedDefinitions => new [] { "class T: I { } interface I {}" }; public static string[] TypeUsageStrings => new [] { "/*MM*/var c = new C();", "/*MM*/CD d = () => null;", "var t = typeof(/*MM*/CAAttribute);" }; private static IEnumerable ApplyModifiers(string[] code, params string[] mods) { foreach (var mod in mods) { foreach (var s in code) { if (s.Contains("MOD")) { yield return new object[] { s.Replace("MOD", mod) }; } else { yield return new object[] { mod + " " + s }; } } } } private TestSource GetSourceFromNamespaceDeclaration(string namespaceDefinition) { return TestSource.Read("using A.Internal.Namespace;" + InternalDefinitions + namespaceDefinition); } private Task GetDiagnosticWithProjectReference(string code) { var libraray = CreateProject(InternalDefinitions); var mainProject = CreateProject(code).AddProjectReference(new ProjectReference(libraray.Id)); return GetDiagnosticsAsync(mainProject.Documents.ToArray(), new PubternalityAnalyzer(), new [] { "PUB0002" }); } private Task GetDiagnostics(string code) { return GetDiagnosticsAsync(new[] { code }, new PubternalityAnalyzer(), new [] { "PUB0002" }); } } }