diff --git a/docs/docs/dotnet-api-docs.md b/docs/docs/dotnet-api-docs.md index b12b7d313f0..f216084b375 100644 --- a/docs/docs/dotnet-api-docs.md +++ b/docs/docs/dotnet-api-docs.md @@ -154,45 +154,18 @@ To disable the default filtering rules, set the `disableDefaultFilter` property To show private methods, set the `includePrivateMembers` config to `true`. When enabled, internal only langauge keywords such as `private` or `internal` starts to appear in the declaration of all APIs, to accurately reflect API accessibility. -There are two ways of customizing the API filters: +### The `` documentation comment -### Custom with Code +The `` documentation comment excludes the type or member on a per API basis using C# documentation comment: -To use a custom filtering with code: - -1. Use docfx .NET API generation as a NuGet library: - -```xml - -``` - -2. Configure the filter options: - -```cs -var options = new DotnetApiOptions -{ - // Filter based on types - IncludeApi = symbol => ... - - // Filter based on attributes - IncludeAttribute = symbol => ... -} - -await DotnetApiCatalog.GenerateManagedReferenceYamlFiles("docfx.json", options); +```csharp +/// +public class Foo { } ``` -The filter callbacks takes an [`ISymbol`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.isymbol?view=roslyn-dotnet) interface and produces an [`SymbolIncludeState`](../api/Docfx.Dotnet.SymbolIncludeState.yml) enum to choose between include the API, exclude the API or use the default filtering behavior. - -The callbacks are raised before applying the default rules but after processing type accessibility rules. Private types and members cannot be marked as include unless `includePrivateMembers` is true. - -Hiding the parent symbol also hides all of its child symbols, e.g.: -- If a namespace is hidden, all child namespaces and types underneath it are hidden. -- If a class is hidden, all nested types underneath it are hidden. -- If an interface is hidden, explicit implementations of that interface are also hidden. - -### Custom with Filter Rules +### Custom filter rules -To add additional filter rules, add a custom YAML file and set the `filter` property in `docfx.json` to point to the custom YAML filter: +To bulk filter APIs with custom filter rules, add a custom YAML file and set the `filter` property in `docfx.json` to point to the custom YAML filter: ```json { @@ -265,3 +238,38 @@ apiRules: ``` Where the `ctorArguments` property specifies a list of match conditions based on constructor parameters and the `ctorNamedArguments` property specifies match conditions using named constructor arguments. + + +### Custom code filter + +To use a custom filtering with code: + +1. Use docfx .NET API generation as a NuGet library: + +```xml + +``` + +2. Configure the filter options: + +```cs +var options = new DotnetApiOptions +{ + // Filter based on types + IncludeApi = symbol => ... + + // Filter based on attributes + IncludeAttribute = symbol => ... +} + +await DotnetApiCatalog.GenerateManagedReferenceYamlFiles("docfx.json", options); +``` + +The filter callbacks takes an [`ISymbol`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.isymbol?view=roslyn-dotnet) interface and produces an [`SymbolIncludeState`](../api/Docfx.Dotnet.SymbolIncludeState.yml) enum to choose between include the API, exclude the API or use the default filtering behavior. + +The callbacks are raised before applying the default rules but after processing type accessibility rules. Private types and members cannot be marked as include unless `includePrivateMembers` is true. + +Hiding the parent symbol also hides all of its child symbols, e.g.: +- If a namespace is hidden, all child namespaces and types underneath it are hidden. +- If a class is hidden, all nested types underneath it are hidden. +- If an interface is hidden, explicit implementations of that interface are also hidden. diff --git a/src/Docfx.Dotnet/SymbolFilter.cs b/src/Docfx.Dotnet/SymbolFilter.cs index 2b2b1551a5e..9f492290330 100644 --- a/src/Docfx.Dotnet/SymbolFilter.cs +++ b/src/Docfx.Dotnet/SymbolFilter.cs @@ -26,24 +26,25 @@ public SymbolFilter(ExtractMetadataConfig config, DotnetApiOptions options) public bool IncludeApi(ISymbol symbol) { - return !IsCompilerGeneratedDisplayClass(symbol) && IsSymbolAccessible(symbol) && IncludeApiCore(symbol); - - bool IncludeApiCore(ISymbol symbol) + return _cache.GetOrAdd(symbol, _ => { - return _cache.GetOrAdd(symbol, _ => _options.IncludeApi?.Invoke(_) switch - { - SymbolIncludeState.Include => true, - SymbolIncludeState.Exclude => false, - _ => IncludeApiDefault(symbol), - }); - } + return !IsCompilerGeneratedDisplayClass(symbol) && + IsSymbolAccessible(symbol) && + !HasExcludeDocumentComment(symbol) && + _options.IncludeApi?.Invoke(_) switch + { + SymbolIncludeState.Include => true, + SymbolIncludeState.Exclude => false, + _ => IncludeApiDefault(symbol), + }; + }); bool IncludeApiDefault(ISymbol symbol) { if (_filterRule is not null && !_filterRule.CanVisitApi(RoslynFilterData.GetSymbolFilterData(symbol))) return false; - return symbol.ContainingSymbol is null || IncludeApiCore(symbol.ContainingSymbol); + return symbol.ContainingSymbol is null || IncludeApi(symbol.ContainingSymbol); } static bool IsCompilerGeneratedDisplayClass(ISymbol symbol) @@ -54,24 +55,22 @@ static bool IsCompilerGeneratedDisplayClass(ISymbol symbol) public bool IncludeAttribute(ISymbol symbol) { - return IsSymbolAccessible(symbol) && IncludeAttributeCore(symbol); - - bool IncludeAttributeCore(ISymbol symbol) + return _attributeCache.GetOrAdd(symbol, _ => { - return _attributeCache.GetOrAdd(symbol, _ => _options.IncludeAttribute?.Invoke(_) switch + return IsSymbolAccessible(symbol) && !HasExcludeDocumentComment(symbol) && _options.IncludeAttribute?.Invoke(_) switch { SymbolIncludeState.Include => true, SymbolIncludeState.Exclude => false, _ => IncludeAttributeDefault(symbol), - }); - } + }; + }); bool IncludeAttributeDefault(ISymbol symbol) { if (_filterRule is not null && !_filterRule.CanVisitAttribute(RoslynFilterData.GetSymbolFilterData(symbol))) return false; - return symbol.ContainingSymbol is null || IncludeAttributeCore(symbol.ContainingSymbol); + return symbol.ContainingSymbol is null || IncludeAttribute(symbol.ContainingSymbol); } } @@ -127,4 +126,12 @@ bool IsEiiAndIncludesContainingSymbols(IEnumerable symbols) return symbols.Any() && symbols.All(s => IncludeApi(s.ContainingSymbol)); } } + + private static bool HasExcludeDocumentComment(ISymbol symbol) + { + return symbol.GetDocumentationCommentXml() is { } xml && ( + xml.Contains("") || + xml.Contains("") || + xml.Contains(" + public void F1() {} + } + } + """; + + var output = Verify(code); + var foo = output.Items[0].Items[0]; + Assert.Equal("public class Foo", foo.Syntax.Content[SyntaxLanguage.CSharp]); + Assert.Empty(foo.Items); + } }