diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ApiCompatServiceProvider.cs b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ApiCompatServiceProvider.cs index 4edb495c0e5d..6d51ecc24a23 100644 --- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ApiCompatServiceProvider.cs +++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ApiCompatServiceProvider.cs @@ -30,21 +30,20 @@ public ApiCompatServiceProvider(Func logFa AccessibilitySymbolFilter accessibilitySymbolFilter = new(respectInternals); SymbolEqualityComparer symbolEqualityComparer = new(); - // The attribute data symbol filter is a composite that contains both the accessibility - // symbol filter and the doc id symbol filter. - CompositeSymbolFilter attributeDataSymbolFilter = new(accessibilitySymbolFilter); - if (excludeAttributesFiles is not null) - { - attributeDataSymbolFilter.Add(new DocIdSymbolFilter(excludeAttributesFiles)); - } + ISymbolFilter attributeDataSymbolFilter = SymbolFilterFactory.GetFilterFromFiles( + apiExclusionFilePaths: excludeAttributesFiles, + accessibilitySymbolFilter: accessibilitySymbolFilter, + respectInternals: respectInternals); + + AttributeDataEqualityComparer attributeDataEqualityComparer = new(symbolEqualityComparer, + new TypedConstantEqualityComparer(symbolEqualityComparer)); ApiComparerSettings apiComparerSettings = new( - accessibilitySymbolFilter, - symbolEqualityComparer, - attributeDataSymbolFilter, - new AttributeDataEqualityComparer(symbolEqualityComparer, - new TypedConstantEqualityComparer(symbolEqualityComparer)), - respectInternals); + symbolFilter: accessibilitySymbolFilter, + symbolEqualityComparer: symbolEqualityComparer, + attributeDataSymbolFilter: attributeDataSymbolFilter, + attributeDataEqualityComparer: attributeDataEqualityComparer, + includeInternalSymbols: respectInternals); return new ApiCompatRunner(SuppressibleLog, SuppressionEngine, diff --git a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/GenAPIApp.cs b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/GenAPIApp.cs index f537d26e823b..0d82dd24e4c4 100644 --- a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/GenAPIApp.cs +++ b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/GenAPIApp.cs @@ -48,22 +48,6 @@ public static void Run(ILog log, includeEffectivelyPrivateSymbols: true, includeExplicitInterfaceImplementationSymbols: true); - // Configure the symbol filter - CompositeSymbolFilter symbolFilter = new(); - if (excludeApiFiles is not null) - { - symbolFilter.Add(new DocIdSymbolFilter(excludeApiFiles)); - } - symbolFilter.Add(new ImplicitSymbolFilter()); - symbolFilter.Add(accessibilitySymbolFilter); - - // Configure the attribute data symbol filter - CompositeSymbolFilter attributeDataSymbolFilter = new(); - if (excludeAttributesFiles is not null) - { - attributeDataSymbolFilter.Add(new DocIdSymbolFilter(excludeAttributesFiles)); - } - attributeDataSymbolFilter.Add(accessibilitySymbolFilter); // Invoke the CSharpFileBuilder for each directly loaded assembly. foreach (IAssemblySymbol? assemblySymbol in assemblySymbols) @@ -75,8 +59,8 @@ public static void Run(ILog log, textWriter.Write(headerFileText); using CSharpFileBuilder fileBuilder = new(log, - symbolFilter, - attributeDataSymbolFilter, + SymbolFilterFactory.GetFilterFromFiles(excludeApiFiles, respectInternals: respectInternals), + SymbolFilterFactory.GetFilterFromFiles(excludeAttributesFiles, respectInternals: respectInternals), textWriter, exceptionMessage, includeAssemblyAttributes, diff --git a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/DocIdSymbolFilter.cs b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/DocIdSymbolFilter.cs index 3bb8ba16fac6..9bd20dd20b6d 100644 --- a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/DocIdSymbolFilter.cs +++ b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/DocIdSymbolFilter.cs @@ -9,9 +9,46 @@ namespace Microsoft.DotNet.ApiSymbolExtensions.Filtering /// Implements the logic of filtering out api. /// Reads the file with the list of attributes, types, members in DocId format. /// - public class DocIdSymbolFilter(string[] docIdsToExcludeFiles) : ISymbolFilter + public class DocIdSymbolFilter : ISymbolFilter { - private readonly HashSet _docIdsToExclude = new(ReadDocIdsAttributes(docIdsToExcludeFiles)); + private readonly HashSet _docIdsToExclude; + + /// + /// Creates a filter to exclude APIs using the DocIDs provided in the specified files. + /// + /// A collection of files each containing multiple DocIDs to exclude. + /// An instance of the symbol filter. + public static DocIdSymbolFilter CreateFromFiles(params string[] filesWithDocIdsToExclude) + { + List docIds = new(); + + foreach (string docIdsToExcludeFile in filesWithDocIdsToExclude) + { + if (string.IsNullOrWhiteSpace(docIdsToExcludeFile)) + { + continue; + } + + foreach (string docId in ReadDocIdsFromList(File.ReadAllLines(docIdsToExcludeFile))) + { + docIds.Add(docId); + } + } + + return new DocIdSymbolFilter(docIds); + } + + /// + /// Creates a filter to exclude APIs using the DocIDs provided in the specified list. + /// + /// A collection of DocIDs to exclude. + /// An instance of the symbol filter. + public static DocIdSymbolFilter CreateFromLists(params string[] docIdsToExclude) + => new DocIdSymbolFilter(ReadDocIdsFromList(docIdsToExclude)); + + // Private constructor to avoid creating an instance with an empty list. + private DocIdSymbolFilter(IEnumerable docIdsToExclude) + => _docIdsToExclude = [.. docIdsToExclude]; /// /// Determines whether the should be included. @@ -29,20 +66,17 @@ public bool Include(ISymbol symbol) return true; } - private static IEnumerable ReadDocIdsAttributes(IEnumerable docIdsToExcludeFiles) + private static IEnumerable ReadDocIdsFromList(params string[] ids) { - foreach (string docIdsToExcludeFile in docIdsToExcludeFiles) + foreach (string id in ids) { - foreach (string id in File.ReadAllLines(docIdsToExcludeFile)) - { #if NET - if (!string.IsNullOrWhiteSpace(id) && !id.StartsWith('#') && !id.StartsWith("//")) + if (!string.IsNullOrWhiteSpace(id) && !id.StartsWith('#') && !id.StartsWith("//")) #else - if (!string.IsNullOrWhiteSpace(id) && !id.StartsWith("#") && !id.StartsWith("//")) + if (!string.IsNullOrWhiteSpace(id) && !id.StartsWith("#") && !id.StartsWith("//")) #endif - { - yield return id.Trim(); - } + { + yield return id.Trim(); } } } diff --git a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/SymbolFilterFactory.cs b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/SymbolFilterFactory.cs new file mode 100644 index 000000000000..18ea253e5368 --- /dev/null +++ b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/SymbolFilterFactory.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.CodeAnalysis; + +namespace Microsoft.DotNet.ApiSymbolExtensions.Filtering; + +/// +/// A factory class to create symbol filters. +/// +public static class SymbolFilterFactory +{ + /// + /// Creates a composite filter to exclude APIs using the DocIDs provided in the specifed file paths. + /// + /// A collection of paths where the exclusion files should be searched. + /// An optional custom accessibility symbol filter to use. + /// Whether to include internal symbols or not. + /// Whether to include effectively private symbols or not. + /// Whether to include explicit interface implementation symbols or not. + /// Whether to include implicit symbols or not. + /// An instance of the symbol filter. + public static ISymbolFilter GetFilterFromFiles(string[]? apiExclusionFilePaths, + AccessibilitySymbolFilter? accessibilitySymbolFilter = null, + bool respectInternals = false, + bool includeEffectivelyPrivateSymbols = true, + bool includeExplicitInterfaceImplementationSymbols = true, + bool includeImplicitSymbolFilter = true) + { + DocIdSymbolFilter? docIdSymbolFilter = + apiExclusionFilePaths?.Length > 0 ? + DocIdSymbolFilter.CreateFromFiles(apiExclusionFilePaths) : null; + + return GetCompositeSymbolFilter(docIdSymbolFilter, accessibilitySymbolFilter, respectInternals, includeEffectivelyPrivateSymbols, includeExplicitInterfaceImplementationSymbols, includeImplicitSymbolFilter); + } + + /// + /// Creates a composite filter to exclude APIs using the DocIDs provided in the specifed list. + /// + /// A collection of exclusion list. + /// An optional custom accessibility symbol filter to use. + /// Whether to include internal symbols or not. + /// Whether to include effectively private symbols or not. + /// Whether to include explicit interface implementation symbols or not. + /// Whether to include implicit symbols or not. + /// An instance of the symbol filter. + public static ISymbolFilter GetFilterFromList(string[]? apiExclusionList, + AccessibilitySymbolFilter? accessibilitySymbolFilter = null, + bool respectInternals = false, + bool includeEffectivelyPrivateSymbols = true, + bool includeExplicitInterfaceImplementationSymbols = true, + bool includeImplicitSymbolFilter = true) + { + DocIdSymbolFilter? docIdSymbolFilter = + apiExclusionList?.Count() > 0 ? + DocIdSymbolFilter.CreateFromLists(apiExclusionList) : null; + + return GetCompositeSymbolFilter(docIdSymbolFilter, accessibilitySymbolFilter, respectInternals, includeEffectivelyPrivateSymbols, includeExplicitInterfaceImplementationSymbols, includeImplicitSymbolFilter); + } + + private static ISymbolFilter GetCompositeSymbolFilter(DocIdSymbolFilter? customFilter, + AccessibilitySymbolFilter? accessibilitySymbolFilter, + bool respectInternals, + bool includeEffectivelyPrivateSymbols, + bool includeExplicitInterfaceImplementationSymbols, + bool includeImplicitSymbolFilter) + { + accessibilitySymbolFilter ??= new( + respectInternals, + includeEffectivelyPrivateSymbols, + includeExplicitInterfaceImplementationSymbols); + + CompositeSymbolFilter filter = new(); + + if (customFilter != null) + { + filter.Add(customFilter); + } + if (includeImplicitSymbolFilter) + { + filter.Add(new ImplicitSymbolFilter()); + } + + filter.Add(accessibilitySymbolFilter); + + return filter; + } +} diff --git a/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/AttributesMustMatchTests.cs b/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/AttributesMustMatchTests.cs index 11d9b98aea4f..993a6b98cabf 100644 --- a/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/AttributesMustMatchTests.cs +++ b/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/AttributesMustMatchTests.cs @@ -20,7 +20,7 @@ public class AttributesMustMatchTests * - ReturnValues * - Constructors * - Generic Parameters - * + * * Grouped into: * - Type * - Member @@ -28,9 +28,6 @@ public class AttributesMustMatchTests private static readonly TestRuleFactory s_ruleFactory = new((settings, context) => new AttributesMustMatch(settings, context)); - private static ISymbolFilter GetAccessibilityAndAttributeSymbolFiltersAsComposite(params string[] excludeAttributeFiles) => - new CompositeSymbolFilter().Add(new AccessibilitySymbolFilter(false)).Add(new DocIdSymbolFilter(excludeAttributeFiles)); - public static TheoryData TypesCases => new() { // No change to type's attributes @@ -39,7 +36,7 @@ private static ISymbolFilter GetAccessibilityAndAttributeSymbolFiltersAsComposit namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -56,7 +53,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -77,7 +74,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -94,7 +91,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -116,7 +113,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -133,7 +130,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -156,7 +153,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -173,7 +170,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -196,7 +193,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -212,7 +209,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -311,7 +308,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -337,7 +334,7 @@ public void F() {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -370,7 +367,7 @@ public void F() {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -396,7 +393,7 @@ public class First { namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -429,7 +426,7 @@ public class First { namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -457,7 +454,7 @@ public class First { namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -492,7 +489,7 @@ public class First { namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -518,7 +515,7 @@ public First() {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -551,7 +548,7 @@ public First() {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -577,7 +574,7 @@ public class First { namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -610,7 +607,7 @@ public class First { namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -634,7 +631,7 @@ public void F([Bar] int v, [Foo(""S"", A = true, B = 0)] string s) {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -666,7 +663,7 @@ public void F([Baz] int v, [Foo(""T"")] string s) {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -687,7 +684,7 @@ public class First<[Bar] T1, [Foo(""S"", A = true, B = 0)] T2> {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -716,7 +713,7 @@ public class First<[Baz] T1, [Foo(""T"")] T2> {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -740,7 +737,7 @@ public class First { namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -792,7 +789,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -815,7 +812,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -832,7 +829,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -1332,7 +1329,7 @@ public void EnsureDiagnosticIsReported(string leftSyntax, string rightSyntax, Co IAssemblySymbol left = SymbolFactory.GetAssemblyFromSyntax(leftSyntax); IAssemblySymbol right = SymbolFactory.GetAssemblyFromSyntax(rightSyntax); ApiComparer differ = new(s_ruleFactory); - differ.Settings.AttributeDataSymbolFilter = GetAccessibilityAndAttributeSymbolFiltersAsComposite(filePath); + differ.Settings.AttributeDataSymbolFilter = SymbolFilterFactory.GetFilterFromFiles([filePath], respectInternals: false); IEnumerable actual = differ.GetDifferences(left, right); @@ -1349,7 +1346,7 @@ public void EnsureStrictModeReported(string leftSyntax, string rightSyntax, Comp IAssemblySymbol left = SymbolFactory.GetAssemblyFromSyntax(leftSyntax); IAssemblySymbol right = SymbolFactory.GetAssemblyFromSyntax(rightSyntax); ApiComparer differ = new(s_ruleFactory, new ApiComparerSettings(strictMode: true)); - differ.Settings.AttributeDataSymbolFilter = GetAccessibilityAndAttributeSymbolFiltersAsComposite(filePath); + differ.Settings.AttributeDataSymbolFilter = SymbolFilterFactory.GetFilterFromFiles([filePath], respectInternals: false); IEnumerable actual = differ.GetDifferences(left, right); @@ -1366,7 +1363,7 @@ public void TestExclusionsFilteredOut() namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -1382,7 +1379,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -1398,7 +1395,7 @@ public class First {} IAssemblySymbol left = SymbolFactory.GetAssemblyFromSyntax(leftSyntax); IAssemblySymbol right = SymbolFactory.GetAssemblyFromSyntax(rightSyntax); ApiComparer differ = new(s_ruleFactory); - differ.Settings.AttributeDataSymbolFilter = GetAccessibilityAndAttributeSymbolFiltersAsComposite(filePath); + differ.Settings.AttributeDataSymbolFilter = SymbolFilterFactory.GetFilterFromFiles([filePath], respectInternals: false); IEnumerable actual = differ.GetDifferences(left, right); @@ -1418,7 +1415,7 @@ public void AttributesExcludedButMembersValidated() namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -1433,7 +1430,7 @@ public class First {} namespace CompatTests { using System; - + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class FooAttribute : Attribute { public FooAttribute(String s) {} @@ -1448,7 +1445,7 @@ public class First {} IAssemblySymbol left = SymbolFactory.GetAssemblyFromSyntax(leftSyntax); IAssemblySymbol right = SymbolFactory.GetAssemblyFromSyntax(rightSyntax); ApiComparer differ = new(ruleFactory, new ApiComparerSettings(strictMode: true)); - differ.Settings.AttributeDataSymbolFilter = GetAccessibilityAndAttributeSymbolFiltersAsComposite(filePath); + differ.Settings.AttributeDataSymbolFilter = SymbolFilterFactory.GetFilterFromFiles([filePath], respectInternals: false); IEnumerable actual = differ.GetDifferences(left, right).ToArray(); diff --git a/test/Microsoft.DotNet.ApiSymbolExtensions.Tests/SymbolFilterFactoryTests.cs b/test/Microsoft.DotNet.ApiSymbolExtensions.Tests/SymbolFilterFactoryTests.cs new file mode 100644 index 000000000000..220d0c2ee436 --- /dev/null +++ b/test/Microsoft.DotNet.ApiSymbolExtensions.Tests/SymbolFilterFactoryTests.cs @@ -0,0 +1,114 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using Xunit; +using Microsoft.DotNet.ApiSymbolExtensions.Filtering; +using Microsoft.CodeAnalysis; + +namespace Microsoft.DotNet.ApiSymbolExtensions.Tests; + +public class SymbolFilterFactoryTests +{ + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Test_FilterFromFiles(bool includeCustomType) + { + Test_FilterFromFiles_Internal(includeCustomType, accessibilitySymbolFilter: null); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Test_FilterFromFiles_CustomAccessibilityFilter(bool includeCustomType) + { + Test_FilterFromFiles_Internal(includeCustomType, new AccessibilitySymbolFilter(includeInternalSymbols: true)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Test_FilterFromList(bool includeCustomType) + { + Test_FilterFromList_Internal(includeCustomType, accessibilitySymbolFilter: null); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Test_FilterFromList_WithCustomAccessibilityFilter(bool includeCustomType) + { + Test_FilterFromList_Internal(includeCustomType, new AccessibilitySymbolFilter(includeInternalSymbols: true)); + } + + private void Test_FilterFromFiles_Internal(bool includeCustomType, AccessibilitySymbolFilter accessibilitySymbolFilter) + { + using TempDirectory root = new(); + string filePath = Path.Combine(root.DirPath, "exclusions.txt"); + using (FileStream fileStream = File.Create(filePath)) + { + using StreamWriter writer = new(fileStream); + writer.WriteLine("T:System.Int32"); + writer.WriteLine("T:System.String"); + if (!includeCustomType) + { + writer.WriteLine("T:MyNamespace.MyClass"); + } + } + + CompositeSymbolFilter filter = SymbolFilterFactory.GetFilterFromFiles( + apiExclusionFilePaths: [filePath], + accessibilitySymbolFilter: accessibilitySymbolFilter, + respectInternals: true, + includeEffectivelyPrivateSymbols: true, + includeExplicitInterfaceImplementationSymbols: true) as CompositeSymbolFilter; + + Test_GetFilter_Internal(filter, includeCustomType); + } + + private void Test_FilterFromList_Internal(bool includeCustomType, AccessibilitySymbolFilter accessibilitySymbolFilter) + { + List exclusions = ["T:System.Int32", "T:System.String"]; + if (!includeCustomType) + { + exclusions.Add("T:MyNamespace.MyClass"); + } + + CompositeSymbolFilter filter = SymbolFilterFactory.GetFilterFromList( + apiExclusionList: exclusions.ToArray(), + accessibilitySymbolFilter: accessibilitySymbolFilter, + respectInternals: true, + includeEffectivelyPrivateSymbols: true, + includeExplicitInterfaceImplementationSymbols: true) as CompositeSymbolFilter; + + Test_GetFilter_Internal(filter, includeCustomType); + } + + private void Test_GetFilter_Internal(CompositeSymbolFilter compositeFilter, bool includeCustomType) + { + Assert.NotNull(compositeFilter); + + Assert.Equal(3, compositeFilter.Filters.Count); + + DocIdSymbolFilter docIdFilter = compositeFilter.Filters[0] as DocIdSymbolFilter; + Assert.NotNull(docIdFilter); + + ImplicitSymbolFilter implicitFilter = compositeFilter.Filters[1] as ImplicitSymbolFilter; + Assert.NotNull(implicitFilter); + + AccessibilitySymbolFilter accessibilityFilter = compositeFilter.Filters[2] as AccessibilitySymbolFilter; + Assert.NotNull(accessibilityFilter); + + IAssemblySymbol assemblySymbol = SymbolFactory.GetAssemblyFromSyntax(@" +namespace MyNamespace +{ + public class MyClass { } +}"); + Assert.NotNull(assemblySymbol); + INamedTypeSymbol myClass = assemblySymbol.GetTypeByMetadataName("MyNamespace.MyClass"); + Assert.NotNull(myClass); + Assert.Equal(includeCustomType, docIdFilter.Include(myClass)); + } +} diff --git a/test/Microsoft.DotNet.GenAPI.Tests/CSharpFileBuilderTests.cs b/test/Microsoft.DotNet.GenAPI.Tests/CSharpFileBuilderTests.cs index 81856d2842ce..30599082426b 100644 --- a/test/Microsoft.DotNet.GenAPI.Tests/CSharpFileBuilderTests.cs +++ b/test/Microsoft.DotNet.GenAPI.Tests/CSharpFileBuilderTests.cs @@ -32,34 +32,26 @@ private void RunTest(string original, bool includeEffectivelyPrivateSymbols = true, bool includeExplicitInterfaceImplementationSymbols = true, bool allowUnsafe = false, - string excludedAttributeFile = null, + string[] excludedAttributeList = null, [CallerMemberName] string assemblyName = "") { StringWriter stringWriter = new(); - // Configure symbol filters - AccessibilitySymbolFilter accessibilitySymbolFilter = new( - includeInternalSymbols, - includeEffectivelyPrivateSymbols, - includeExplicitInterfaceImplementationSymbols); - - CompositeSymbolFilter symbolFilter = new CompositeSymbolFilter() - .Add(new ImplicitSymbolFilter()) - .Add(accessibilitySymbolFilter); - - CompositeSymbolFilter attributeDataSymbolFilter = new(); - if (excludedAttributeFile is not null) - { - attributeDataSymbolFilter.Add(new DocIdSymbolFilter(new string[] { excludedAttributeFile })); - } - attributeDataSymbolFilter.Add(accessibilitySymbolFilter); - Mock log = new(); IAssemblySymbolWriter csharpFileBuilder = new CSharpFileBuilder( log.Object, - symbolFilter, - attributeDataSymbolFilter, + SymbolFilterFactory.GetFilterFromList( + apiExclusionList: [], + accessibilitySymbolFilter: null, + includeInternalSymbols, + includeEffectivelyPrivateSymbols, + includeExplicitInterfaceImplementationSymbols), + SymbolFilterFactory.GetFilterFromList( + apiExclusionList: excludedAttributeList ?? [], accessibilitySymbolFilter: null, + includeInternalSymbols, + includeEffectivelyPrivateSymbols, + includeExplicitInterfaceImplementationSymbols), stringWriter, null, false, @@ -2764,10 +2756,6 @@ public class PublicClass { } [Fact] public void TestAttributesExcludedWithFilter() { - using TempDirectory root = new(); - string filePath = Path.Combine(root.DirPath, "exclusions.txt"); - File.WriteAllText(filePath, "T:A.AnyTestAttribute"); - RunTest(original: """ namespace A { @@ -2803,7 +2791,7 @@ public partial class PublicClass } """, includeInternalSymbols: false, - excludedAttributeFile: filePath); + excludedAttributeList: ["T:A.AnyTestAttribute"]); } [Fact]