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);
+ }
}